From b317f05b042653b599b83fde10ffbcdc1cae1e0d Mon Sep 17 00:00:00 2001 From: Jorgen Lundman Date: Mon, 23 Oct 2023 08:58:56 +0900 Subject: [PATCH 1/9] 1: all macOS files part1: module/os/macos/* + include/os/macos/* This does then inlcude lib/*macos* as well, using grep so there are outliers, like manpage. Signed-off-by: Jorgen Lundman --- .github/workflows/macos-build.yaml | 54 + cmd/os/macos/InvariantDisks/.gitignore | 3 + cmd/os/macos/InvariantDisks/BSD.LICENSE.md | 27 + .../InvariantDisks.xcodeproj/project.pbxproj | 375 + .../InvariantDisks/IDBaseLinker.cpp | 71 + .../InvariantDisks/IDBaseLinker.hpp | 44 + .../InvariantDisks/InvariantDisks/IDCLI.cpp | 171 + .../InvariantDisks/InvariantDisks/IDCLI.hpp | 44 + .../InvariantDisks/IDDAHandlerIdle.cpp | 49 + .../InvariantDisks/IDDAHandlerIdle.hpp | 44 + .../IDDiskArbitrationDispatcher.cpp | 106 + .../IDDiskArbitrationDispatcher.hpp | 55 + .../IDDiskArbitrationHandler.hpp | 42 + .../InvariantDisks/IDDiskArbitrationUtils.cpp | 272 + .../InvariantDisks/IDDiskArbitrationUtils.hpp | 61 + .../InvariantDisks/IDDiskInfoLogger.cpp | 47 + .../InvariantDisks/IDDiskInfoLogger.hpp | 39 + .../InvariantDisks/IDDispatchUtils.cpp | 44 + .../InvariantDisks/IDDispatchUtils.hpp | 41 + .../InvariantDisks/IDException.cpp | 17 + .../InvariantDisks/IDException.hpp | 84 + .../InvariantDisks/IDFileUtils.hpp | 27 + .../InvariantDisks/IDFileUtils.mm | 92 + .../InvariantDisks/IDImagePathLinker.cpp | 39 + .../InvariantDisks/IDImagePathLinker.hpp | 30 + .../InvariantDisks/IDLogUtils.cpp | 181 + .../InvariantDisks/IDLogUtils.hpp | 78 + .../InvariantDisks/IDMediaPathLinker.cpp | 43 + .../InvariantDisks/IDMediaPathLinker.hpp | 30 + .../InvariantDisks/IDSerialLinker.cpp | 80 + .../InvariantDisks/IDSerialLinker.hpp | 33 + .../InvariantDisks/IDSymlinkHandle.cpp | 75 + .../InvariantDisks/IDSymlinkHandle.hpp | 53 + .../InvariantDisks/IDUUIDLinker.cpp | 46 + .../InvariantDisks/IDUUIDLinker.hpp | 30 + .../InvariantDisks/InvariantDisks/Makefile.am | 43 + .../InvariantDisks/git-version.h | 3 + .../InvariantDisks/InvariantDisks/main.cpp | 40 + cmd/os/macos/InvariantDisks/Makefile.am | 1 + .../InvariantDisks/OPENSOLARIS.LICENSE.txt | 384 + cmd/os/macos/InvariantDisks/README.md | 36 + .../net.the-color-black.InvariantDisks.plist | 16 + cmd/os/macos/Makefile.am | 8 + cmd/os/macos/mount_zfs/.gitignore | 1 + cmd/os/macos/mount_zfs/Makefile.am | 28 + cmd/os/macos/mount_zfs/mount_zfs.c | 325 + cmd/os/macos/zconfigd/.gitignore | 1 + cmd/os/macos/zconfigd/Makefile.am | 8 + cmd/os/macos/zconfigd/zconfigd.c | 224 + cmd/os/macos/zconfigd/zconfigd.h | 43 + cmd/os/macos/zfs_util/.gitignore | 1 + .../zfs_util/English.lproj/InfoPlist.strings | 14 + cmd/os/macos/zfs_util/Info.plist | 131 + cmd/os/macos/zfs_util/Makefile.am | 59 + cmd/os/macos/zfs_util/PkgInfo | 1 + cmd/os/macos/zfs_util/zfs_util.c | 1065 +++ cmd/os/macos/zsysctl/.gitignore | 1 + cmd/os/macos/zsysctl/Makefile.am | 5 + cmd/os/macos/zsysctl/zsysctl.c | 1222 +++ cmd/zpool/os/macos/Info-zfs.plist | 20 + cmd/zpool/os/macos/Info-zpool.plist | 22 + cmd/zpool/os/macos/zpool-entitlements.plist | 14 + cmd/zpool/os/macos/zpool_vdev_os.c | 201 + include/os/macos/Makefile.am | 1 + include/os/macos/spl/IOKit/IOLib.h | 50 + include/os/macos/spl/Makefile.am | 1 + include/os/macos/spl/libkern/libkern.h | 43 + include/os/macos/spl/linux/init.h | 26 + include/os/macos/spl/linux/kernel.h | 25 + include/os/macos/spl/linux/module.h | 28 + include/os/macos/spl/rpc/Makefile.am | 3 + include/os/macos/spl/rpc/types.h | 32 + include/os/macos/spl/rpc/xdr.h | 179 + include/os/macos/spl/stddef.h | 41 + include/os/macos/spl/string.h | 35 + include/os/macos/spl/sys/Makefile.am | 49 + .../os/macos/spl/sys/aarch64/asm_linkage.h | 73 + include/os/macos/spl/sys/acl.h | 127 + include/os/macos/spl/sys/atomic.h | 413 + include/os/macos/spl/sys/byteorder.h | 70 + include/os/macos/spl/sys/callb.h | 66 + include/os/macos/spl/sys/cmn_err.h | 87 + include/os/macos/spl/sys/condvar.h | 105 + include/os/macos/spl/sys/console.h | 41 + include/os/macos/spl/sys/cred.h | 72 + include/os/macos/spl/sys/ctype.h | 27 + include/os/macos/spl/sys/debug.h | 299 + include/os/macos/spl/sys/disp.h | 25 + include/os/macos/spl/sys/dkio.h | 527 ++ include/os/macos/spl/sys/errno.h | 29 + include/os/macos/spl/sys/fcntl.h | 46 + include/os/macos/spl/sys/file.h | 67 + include/os/macos/spl/sys/ia32/asm_linkage.h | 185 + include/os/macos/spl/sys/inttypes.h | 31 + include/os/macos/spl/sys/isa_defs.h | 690 ++ include/os/macos/spl/sys/kmem.h | 157 + include/os/macos/spl/sys/kmem_cache.h | 25 + include/os/macos/spl/sys/kmem_impl.h | 495 ++ include/os/macos/spl/sys/kstat.h | 227 + include/os/macos/spl/sys/list.h | 145 + include/os/macos/spl/sys/misc.h | 25 + include/os/macos/spl/sys/mod_os.h | 273 + include/os/macos/spl/sys/mutex.h | 175 + include/os/macos/spl/sys/param.h | 43 + include/os/macos/spl/sys/policy.h | 91 + include/os/macos/spl/sys/priv.h | 531 ++ include/os/macos/spl/sys/proc.h | 47 + include/os/macos/spl/sys/processor.h | 38 + include/os/macos/spl/sys/procfs_list.h | 64 + include/os/macos/spl/sys/random.h | 63 + include/os/macos/spl/sys/rwlock.h | 86 + include/os/macos/spl/sys/seg_kmem.h | 87 + include/os/macos/spl/sys/sid.h | 106 + include/os/macos/spl/sys/signal.h | 59 + include/os/macos/spl/sys/simd.h | 47 + include/os/macos/spl/sys/simd_aarch64.h | 96 + include/os/macos/spl/sys/simd_x86.h | 745 ++ include/os/macos/spl/sys/string.h | 45 + include/os/macos/spl/sys/strings.h | 27 + include/os/macos/spl/sys/stropts.h | 247 + include/os/macos/spl/sys/sunddi.h | 204 + include/os/macos/spl/sys/sysmacros.h | 274 + include/os/macos/spl/sys/systeminfo.h | 40 + include/os/macos/spl/sys/systm.h | 36 + include/os/macos/spl/sys/taskq.h | 120 + include/os/macos/spl/sys/taskq_impl.h | 181 + include/os/macos/spl/sys/thread.h | 196 + include/os/macos/spl/sys/time.h | 90 + include/os/macos/spl/sys/timer.h | 88 + include/os/macos/spl/sys/trace.h | 26 + include/os/macos/spl/sys/tsd.h | 54 + include/os/macos/spl/sys/types.h | 142 + include/os/macos/spl/sys/types32.h | 30 + include/os/macos/spl/sys/uio.h | 234 + include/os/macos/spl/sys/utsname.h | 48 + include/os/macos/spl/sys/varargs.h | 32 + include/os/macos/spl/sys/vfs.h | 84 + include/os/macos/spl/sys/vmem.h | 186 + include/os/macos/spl/sys/vmem_impl.h | 182 + include/os/macos/spl/sys/vmsystm.h | 35 + include/os/macos/spl/sys/vnode.h | 238 + include/os/macos/spl/sys/wmsum.h | 73 + include/os/macos/spl/sys/zmod.h | 122 + include/os/macos/spl/sys/zone.h | 38 + include/os/macos/zfs/Makefile.am | 1 + include/os/macos/zfs/sys/Makefile.am | 10 + include/os/macos/zfs/sys/ZFSDataset.h | 142 + include/os/macos/zfs/sys/ZFSDatasetProxy.h | 82 + include/os/macos/zfs/sys/ZFSDatasetScheme.h | 126 + include/os/macos/zfs/sys/ZFSPool.h | 127 + include/os/macos/zfs/sys/finderinfo.h | 36 + include/os/macos/zfs/sys/hfs_internal.h | 191 + include/os/macos/zfs/sys/ldi_buf.h | 88 + include/os/macos/zfs/sys/ldi_impl_osx.h | 226 + include/os/macos/zfs/sys/ldi_osx.h | 153 + include/os/macos/zfs/sys/trace_zfs.h | 68 + include/os/macos/zfs/sys/vdev_disk_os.h | 35 + include/os/macos/zfs/sys/zfs_boot.h | 53 + include/os/macos/zfs/sys/zfs_bootenv_os.h | 25 + include/os/macos/zfs/sys/zfs_context_os.h | 211 + include/os/macos/zfs/sys/zfs_ctldir.h | 123 + include/os/macos/zfs/sys/zfs_dir.h | 82 + include/os/macos/zfs/sys/zfs_ioctl_compat.h | 207 + include/os/macos/zfs/sys/zfs_mount.h | 59 + include/os/macos/zfs/sys/zfs_vfsops_os.h | 326 + include/os/macos/zfs/sys/zfs_vnops_os.h | 264 + include/os/macos/zfs/sys/zfs_znode_impl.h | 219 + include/os/macos/zfs/sys/zpl.h | 27 + include/os/macos/zfs/sys/zvolIO.h | 136 + include/os/macos/zfs/sys/zvol_os.h | 81 + lib/libefi/rdwr_efi_macos.c | 1598 ++++ lib/libshare/os/macos/nfs.c | 404 + lib/libshare/os/macos/smb.c | 434 ++ lib/libspl/include/os/macos/Makefile.am | 1 + lib/libspl/include/os/macos/dirent.h | 37 + lib/libspl/include/os/macos/libdiskmgt.h | 84 + lib/libspl/include/os/macos/mach/Makefile.am | 3 + lib/libspl/include/os/macos/mach/boolean.h | 26 + lib/libspl/include/os/macos/mach/task.h | 29 + lib/libspl/include/os/macos/mntent.h | 144 + lib/libspl/include/os/macos/poll.h | 31 + lib/libspl/include/os/macos/pthread.h | 36 + lib/libspl/include/os/macos/rpc/Makefile.am | 3 + lib/libspl/include/os/macos/rpc/xdr.h | 40 + lib/libspl/include/os/macos/stdio.h | 34 + lib/libspl/include/os/macos/stdlib.h | 29 + lib/libspl/include/os/macos/string.h | 40 + lib/libspl/include/os/macos/synch.h | 82 + lib/libspl/include/os/macos/sys/Makefile.am | 17 + .../os/macos/sys/aarch64/asm_linkage.h | 71 + lib/libspl/include/os/macos/sys/byteorder.h | 288 + lib/libspl/include/os/macos/sys/errno.h | 31 + lib/libspl/include/os/macos/sys/fcntl.h | 75 + lib/libspl/include/os/macos/sys/file.h | 46 + .../include/os/macos/sys/ia32/asm_linkage.h | 185 + .../include/os/macos/sys/kernel_types.h | 43 + lib/libspl/include/os/macos/sys/misc.h | 26 + lib/libspl/include/os/macos/sys/mnttab.h | 86 + lib/libspl/include/os/macos/sys/mount.h | 87 + lib/libspl/include/os/macos/sys/param.h | 62 + lib/libspl/include/os/macos/sys/stat.h | 77 + lib/libspl/include/os/macos/sys/sysmacros.h | 105 + lib/libspl/include/os/macos/sys/time.h | 79 + lib/libspl/include/os/macos/sys/uio.h | 177 + lib/libspl/include/os/macos/sys/vfs.h | 26 + lib/libspl/include/os/macos/sys/xattr.h | 35 + .../include/os/macos/sys/zfs_context_os.h | 76 + lib/libspl/include/os/macos/time.h | 83 + lib/libspl/include/os/macos/unistd.h | 94 + lib/libspl/os/macos/getexecname.c | 33 + lib/libspl/os/macos/gethostid.c | 38 + lib/libspl/os/macos/zone.c | 28 + lib/libzfs/os/macos/libzfs_dataset_os.c | 27 + lib/libzfs/os/macos/libzfs_getmntany.c | 476 ++ lib/libzfs/os/macos/libzfs_mount_os.c | 1004 +++ lib/libzfs/os/macos/libzfs_pool_os.c | 343 + lib/libzfs/os/macos/libzfs_util_os.c | 697 ++ lib/libzfs_core/os/macos/libzfs_core_ioctl.c | 95 + lib/libzutil/os/macos/zutil_device_path_os.c | 208 + lib/libzutil/os/macos/zutil_import_os.c | 631 ++ lib/os/macos/libdiskmgt/Makefile.am | 24 + lib/os/macos/libdiskmgt/disks_private.h | 82 + lib/os/macos/libdiskmgt/diskutil.c | 381 + lib/os/macos/libdiskmgt/dm.c | 39 + lib/os/macos/libdiskmgt/entry.c | 336 + lib/os/macos/libdiskmgt/inuse_corestorage.c | 99 + lib/os/macos/libdiskmgt/inuse_fs.c | 77 + lib/os/macos/libdiskmgt/inuse_macswap.c | 82 + lib/os/macos/libdiskmgt/inuse_mnt.c | 54 + lib/os/macos/libdiskmgt/inuse_partition.c | 74 + lib/os/macos/libdiskmgt/inuse_zpool.c | 161 + lib/os/macos/libdiskmgt/libdiskmgt.c | 39 + lib/os/macos/libdiskmgt/slice.c | 104 + man/man7/zfsprops-macos.7 | 95 + module/os/macos/.gitignore | 3 + module/os/macos/Makefile.am | 464 ++ module/os/macos/README.md | 8 + module/os/macos/spl/README.md | 14 + module/os/macos/spl/spl-atomic.c | 50 + module/os/macos/spl/spl-condvar.c | 264 + module/os/macos/spl/spl-cred.c | 153 + module/os/macos/spl/spl-ddi.c | 410 + module/os/macos/spl/spl-debug.c | 10 + module/os/macos/spl/spl-err.c | 83 + module/os/macos/spl/spl-kmem.c | 6873 +++++++++++++++++ module/os/macos/spl/spl-kstat.c | 1294 ++++ module/os/macos/spl/spl-list.c | 197 + module/os/macos/spl/spl-mutex.c | 923 +++ module/os/macos/spl/spl-osx.c | 599 ++ module/os/macos/spl/spl-policy.c | 185 + module/os/macos/spl/spl-proc.c | 30 + module/os/macos/spl/spl-proc_list.c | 157 + module/os/macos/spl/spl-processor.c | 213 + module/os/macos/spl/spl-qsort.c | 178 + module/os/macos/spl/spl-rwlock.c | 447 ++ module/os/macos/spl/spl-seg_kmem.c | 347 + module/os/macos/spl/spl-taskq.c | 2899 +++++++ module/os/macos/spl/spl-thread.c | 468 ++ module/os/macos/spl/spl-time.c | 138 + module/os/macos/spl/spl-tsd.c | 389 + module/os/macos/spl/spl-uio.c | 148 + module/os/macos/spl/spl-vmem.c | 4147 ++++++++++ module/os/macos/spl/spl-vnode.c | 479 ++ module/os/macos/spl/spl-xdr.c | 529 ++ module/os/macos/spl/spl-zlib.c | 199 + module/os/macos/zfs/.gitignore | 2 + module/os/macos/zfs/Info.plist | 113 + module/os/macos/zfs/InfoPlist.strings | 5 + module/os/macos/zfs/ZFSDataset.cpp | 855 ++ module/os/macos/zfs/ZFSDatasetProxy.cpp | 466 ++ module/os/macos/zfs/ZFSDatasetScheme.cpp | 1112 +++ module/os/macos/zfs/ZFSPool.cpp | 867 +++ module/os/macos/zfs/abd_os.c | 788 ++ module/os/macos/zfs/arc_os.c | 794 ++ module/os/macos/zfs/ldi_iokit.cpp | 2058 +++++ module/os/macos/zfs/ldi_osx.c | 2433 ++++++ module/os/macos/zfs/ldi_vnode.c | 1020 +++ module/os/macos/zfs/policy.c | 354 + module/os/macos/zfs/qat.c | 105 + module/os/macos/zfs/qat_compress.c | 569 ++ module/os/macos/zfs/qat_crypt.c | 630 ++ module/os/macos/zfs/spa_misc_os.c | 113 + module/os/macos/zfs/sysctl_os.c | 914 +++ module/os/macos/zfs/trace.c | 50 + module/os/macos/zfs/vdev_disk.c | 995 +++ module/os/macos/zfs/vdev_file.c | 462 ++ module/os/macos/zfs/zfs_acl.c | 2784 +++++++ module/os/macos/zfs/zfs_boot.cpp | 2981 +++++++ module/os/macos/zfs/zfs_ctldir.c | 1726 +++++ module/os/macos/zfs/zfs_debug.c | 263 + module/os/macos/zfs/zfs_dir.c | 1219 +++ module/os/macos/zfs/zfs_file_os.c | 458 ++ module/os/macos/zfs/zfs_fuid_os.c | 52 + module/os/macos/zfs/zfs_ioctl_os.c | 488 ++ module/os/macos/zfs/zfs_osx.cpp | 309 + module/os/macos/zfs/zfs_racct.c | 32 + module/os/macos/zfs/zfs_vfsops.c | 3051 ++++++++ module/os/macos/zfs/zfs_vnops_os.c | 3814 +++++++++ module/os/macos/zfs/zfs_vnops_osx.c | 5250 +++++++++++++ module/os/macos/zfs/zfs_vnops_osx_lib.c | 2151 ++++++ module/os/macos/zfs/zfs_vnops_osx_xattr.c | 801 ++ module/os/macos/zfs/zfs_znode.c | 2369 ++++++ module/os/macos/zfs/zio_crypt.c | 2080 +++++ module/os/macos/zfs/zvolIO.cpp | 1369 ++++ module/os/macos/zfs/zvol_os.c | 1179 +++ scripts/load_macos.sh | 16 + scripts/pkg_macos.sh | 460 ++ 307 files changed, 99638 insertions(+) create mode 100644 .github/workflows/macos-build.yaml create mode 100644 cmd/os/macos/InvariantDisks/.gitignore create mode 100644 cmd/os/macos/InvariantDisks/BSD.LICENSE.md create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks.xcodeproj/project.pbxproj create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDBaseLinker.cpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDBaseLinker.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDCLI.cpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDCLI.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDDAHandlerIdle.cpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDDAHandlerIdle.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationDispatcher.cpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationDispatcher.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationHandler.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationUtils.cpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationUtils.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskInfoLogger.cpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskInfoLogger.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDDispatchUtils.cpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDDispatchUtils.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDException.cpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDException.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDFileUtils.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDFileUtils.mm create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDImagePathLinker.cpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDImagePathLinker.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDLogUtils.cpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDLogUtils.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDMediaPathLinker.cpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDMediaPathLinker.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDSerialLinker.cpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDSerialLinker.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDSymlinkHandle.cpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDSymlinkHandle.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDUUIDLinker.cpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/IDUUIDLinker.hpp create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/Makefile.am create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/git-version.h create mode 100644 cmd/os/macos/InvariantDisks/InvariantDisks/main.cpp create mode 100644 cmd/os/macos/InvariantDisks/Makefile.am create mode 100644 cmd/os/macos/InvariantDisks/OPENSOLARIS.LICENSE.txt create mode 100644 cmd/os/macos/InvariantDisks/README.md create mode 100644 cmd/os/macos/InvariantDisks/launchd/net.the-color-black.InvariantDisks.plist create mode 100644 cmd/os/macos/Makefile.am create mode 100644 cmd/os/macos/mount_zfs/.gitignore create mode 100644 cmd/os/macos/mount_zfs/Makefile.am create mode 100644 cmd/os/macos/mount_zfs/mount_zfs.c create mode 100644 cmd/os/macos/zconfigd/.gitignore create mode 100644 cmd/os/macos/zconfigd/Makefile.am create mode 100644 cmd/os/macos/zconfigd/zconfigd.c create mode 100644 cmd/os/macos/zconfigd/zconfigd.h create mode 100644 cmd/os/macos/zfs_util/.gitignore create mode 100644 cmd/os/macos/zfs_util/English.lproj/InfoPlist.strings create mode 100644 cmd/os/macos/zfs_util/Info.plist create mode 100644 cmd/os/macos/zfs_util/Makefile.am create mode 100644 cmd/os/macos/zfs_util/PkgInfo create mode 100644 cmd/os/macos/zfs_util/zfs_util.c create mode 100644 cmd/os/macos/zsysctl/.gitignore create mode 100644 cmd/os/macos/zsysctl/Makefile.am create mode 100644 cmd/os/macos/zsysctl/zsysctl.c create mode 100644 cmd/zpool/os/macos/Info-zfs.plist create mode 100644 cmd/zpool/os/macos/Info-zpool.plist create mode 100644 cmd/zpool/os/macos/zpool-entitlements.plist create mode 100644 cmd/zpool/os/macos/zpool_vdev_os.c create mode 100644 include/os/macos/Makefile.am create mode 100644 include/os/macos/spl/IOKit/IOLib.h create mode 100644 include/os/macos/spl/Makefile.am create mode 100644 include/os/macos/spl/libkern/libkern.h create mode 100644 include/os/macos/spl/linux/init.h create mode 100644 include/os/macos/spl/linux/kernel.h create mode 100644 include/os/macos/spl/linux/module.h create mode 100644 include/os/macos/spl/rpc/Makefile.am create mode 100644 include/os/macos/spl/rpc/types.h create mode 100644 include/os/macos/spl/rpc/xdr.h create mode 100644 include/os/macos/spl/stddef.h create mode 100644 include/os/macos/spl/string.h create mode 100644 include/os/macos/spl/sys/Makefile.am create mode 100644 include/os/macos/spl/sys/aarch64/asm_linkage.h create mode 100644 include/os/macos/spl/sys/acl.h create mode 100644 include/os/macos/spl/sys/atomic.h create mode 100644 include/os/macos/spl/sys/byteorder.h create mode 100644 include/os/macos/spl/sys/callb.h create mode 100644 include/os/macos/spl/sys/cmn_err.h create mode 100644 include/os/macos/spl/sys/condvar.h create mode 100644 include/os/macos/spl/sys/console.h create mode 100644 include/os/macos/spl/sys/cred.h create mode 100644 include/os/macos/spl/sys/ctype.h create mode 100644 include/os/macos/spl/sys/debug.h create mode 100644 include/os/macos/spl/sys/disp.h create mode 100644 include/os/macos/spl/sys/dkio.h create mode 100644 include/os/macos/spl/sys/errno.h create mode 100644 include/os/macos/spl/sys/fcntl.h create mode 100644 include/os/macos/spl/sys/file.h create mode 100644 include/os/macos/spl/sys/ia32/asm_linkage.h create mode 100644 include/os/macos/spl/sys/inttypes.h create mode 100644 include/os/macos/spl/sys/isa_defs.h create mode 100644 include/os/macos/spl/sys/kmem.h create mode 100644 include/os/macos/spl/sys/kmem_cache.h create mode 100644 include/os/macos/spl/sys/kmem_impl.h create mode 100644 include/os/macos/spl/sys/kstat.h create mode 100644 include/os/macos/spl/sys/list.h create mode 100644 include/os/macos/spl/sys/misc.h create mode 100644 include/os/macos/spl/sys/mod_os.h create mode 100644 include/os/macos/spl/sys/mutex.h create mode 100644 include/os/macos/spl/sys/param.h create mode 100644 include/os/macos/spl/sys/policy.h create mode 100644 include/os/macos/spl/sys/priv.h create mode 100644 include/os/macos/spl/sys/proc.h create mode 100644 include/os/macos/spl/sys/processor.h create mode 100644 include/os/macos/spl/sys/procfs_list.h create mode 100644 include/os/macos/spl/sys/random.h create mode 100644 include/os/macos/spl/sys/rwlock.h create mode 100644 include/os/macos/spl/sys/seg_kmem.h create mode 100644 include/os/macos/spl/sys/sid.h create mode 100644 include/os/macos/spl/sys/signal.h create mode 100644 include/os/macos/spl/sys/simd.h create mode 100644 include/os/macos/spl/sys/simd_aarch64.h create mode 100644 include/os/macos/spl/sys/simd_x86.h create mode 100644 include/os/macos/spl/sys/string.h create mode 100644 include/os/macos/spl/sys/strings.h create mode 100644 include/os/macos/spl/sys/stropts.h create mode 100644 include/os/macos/spl/sys/sunddi.h create mode 100644 include/os/macos/spl/sys/sysmacros.h create mode 100644 include/os/macos/spl/sys/systeminfo.h create mode 100644 include/os/macos/spl/sys/systm.h create mode 100644 include/os/macos/spl/sys/taskq.h create mode 100644 include/os/macos/spl/sys/taskq_impl.h create mode 100644 include/os/macos/spl/sys/thread.h create mode 100644 include/os/macos/spl/sys/time.h create mode 100644 include/os/macos/spl/sys/timer.h create mode 100644 include/os/macos/spl/sys/trace.h create mode 100644 include/os/macos/spl/sys/tsd.h create mode 100644 include/os/macos/spl/sys/types.h create mode 100644 include/os/macos/spl/sys/types32.h create mode 100644 include/os/macos/spl/sys/uio.h create mode 100644 include/os/macos/spl/sys/utsname.h create mode 100644 include/os/macos/spl/sys/varargs.h create mode 100644 include/os/macos/spl/sys/vfs.h create mode 100644 include/os/macos/spl/sys/vmem.h create mode 100644 include/os/macos/spl/sys/vmem_impl.h create mode 100644 include/os/macos/spl/sys/vmsystm.h create mode 100644 include/os/macos/spl/sys/vnode.h create mode 100644 include/os/macos/spl/sys/wmsum.h create mode 100644 include/os/macos/spl/sys/zmod.h create mode 100644 include/os/macos/spl/sys/zone.h create mode 100644 include/os/macos/zfs/Makefile.am create mode 100644 include/os/macos/zfs/sys/Makefile.am create mode 100644 include/os/macos/zfs/sys/ZFSDataset.h create mode 100644 include/os/macos/zfs/sys/ZFSDatasetProxy.h create mode 100644 include/os/macos/zfs/sys/ZFSDatasetScheme.h create mode 100644 include/os/macos/zfs/sys/ZFSPool.h create mode 100644 include/os/macos/zfs/sys/finderinfo.h create mode 100644 include/os/macos/zfs/sys/hfs_internal.h create mode 100644 include/os/macos/zfs/sys/ldi_buf.h create mode 100644 include/os/macos/zfs/sys/ldi_impl_osx.h create mode 100644 include/os/macos/zfs/sys/ldi_osx.h create mode 100644 include/os/macos/zfs/sys/trace_zfs.h create mode 100644 include/os/macos/zfs/sys/vdev_disk_os.h create mode 100644 include/os/macos/zfs/sys/zfs_boot.h create mode 100644 include/os/macos/zfs/sys/zfs_bootenv_os.h create mode 100644 include/os/macos/zfs/sys/zfs_context_os.h create mode 100644 include/os/macos/zfs/sys/zfs_ctldir.h create mode 100644 include/os/macos/zfs/sys/zfs_dir.h create mode 100644 include/os/macos/zfs/sys/zfs_ioctl_compat.h create mode 100644 include/os/macos/zfs/sys/zfs_mount.h create mode 100644 include/os/macos/zfs/sys/zfs_vfsops_os.h create mode 100644 include/os/macos/zfs/sys/zfs_vnops_os.h create mode 100644 include/os/macos/zfs/sys/zfs_znode_impl.h create mode 100644 include/os/macos/zfs/sys/zpl.h create mode 100644 include/os/macos/zfs/sys/zvolIO.h create mode 100644 include/os/macos/zfs/sys/zvol_os.h create mode 100644 lib/libefi/rdwr_efi_macos.c create mode 100644 lib/libshare/os/macos/nfs.c create mode 100644 lib/libshare/os/macos/smb.c create mode 100644 lib/libspl/include/os/macos/Makefile.am create mode 100644 lib/libspl/include/os/macos/dirent.h create mode 100644 lib/libspl/include/os/macos/libdiskmgt.h create mode 100644 lib/libspl/include/os/macos/mach/Makefile.am create mode 100644 lib/libspl/include/os/macos/mach/boolean.h create mode 100644 lib/libspl/include/os/macos/mach/task.h create mode 100644 lib/libspl/include/os/macos/mntent.h create mode 100644 lib/libspl/include/os/macos/poll.h create mode 100644 lib/libspl/include/os/macos/pthread.h create mode 100644 lib/libspl/include/os/macos/rpc/Makefile.am create mode 100644 lib/libspl/include/os/macos/rpc/xdr.h create mode 100644 lib/libspl/include/os/macos/stdio.h create mode 100644 lib/libspl/include/os/macos/stdlib.h create mode 100644 lib/libspl/include/os/macos/string.h create mode 100644 lib/libspl/include/os/macos/synch.h create mode 100644 lib/libspl/include/os/macos/sys/Makefile.am create mode 100644 lib/libspl/include/os/macos/sys/aarch64/asm_linkage.h create mode 100644 lib/libspl/include/os/macos/sys/byteorder.h create mode 100644 lib/libspl/include/os/macos/sys/errno.h create mode 100644 lib/libspl/include/os/macos/sys/fcntl.h create mode 100644 lib/libspl/include/os/macos/sys/file.h create mode 100644 lib/libspl/include/os/macos/sys/ia32/asm_linkage.h create mode 100644 lib/libspl/include/os/macos/sys/kernel_types.h create mode 100644 lib/libspl/include/os/macos/sys/misc.h create mode 100644 lib/libspl/include/os/macos/sys/mnttab.h create mode 100644 lib/libspl/include/os/macos/sys/mount.h create mode 100644 lib/libspl/include/os/macos/sys/param.h create mode 100644 lib/libspl/include/os/macos/sys/stat.h create mode 100644 lib/libspl/include/os/macos/sys/sysmacros.h create mode 100644 lib/libspl/include/os/macos/sys/time.h create mode 100644 lib/libspl/include/os/macos/sys/uio.h create mode 100644 lib/libspl/include/os/macos/sys/vfs.h create mode 100644 lib/libspl/include/os/macos/sys/xattr.h create mode 100644 lib/libspl/include/os/macos/sys/zfs_context_os.h create mode 100644 lib/libspl/include/os/macos/time.h create mode 100644 lib/libspl/include/os/macos/unistd.h create mode 100644 lib/libspl/os/macos/getexecname.c create mode 100644 lib/libspl/os/macos/gethostid.c create mode 100644 lib/libspl/os/macos/zone.c create mode 100644 lib/libzfs/os/macos/libzfs_dataset_os.c create mode 100644 lib/libzfs/os/macos/libzfs_getmntany.c create mode 100644 lib/libzfs/os/macos/libzfs_mount_os.c create mode 100644 lib/libzfs/os/macos/libzfs_pool_os.c create mode 100644 lib/libzfs/os/macos/libzfs_util_os.c create mode 100644 lib/libzfs_core/os/macos/libzfs_core_ioctl.c create mode 100644 lib/libzutil/os/macos/zutil_device_path_os.c create mode 100644 lib/libzutil/os/macos/zutil_import_os.c create mode 100644 lib/os/macos/libdiskmgt/Makefile.am create mode 100644 lib/os/macos/libdiskmgt/disks_private.h create mode 100644 lib/os/macos/libdiskmgt/diskutil.c create mode 100644 lib/os/macos/libdiskmgt/dm.c create mode 100644 lib/os/macos/libdiskmgt/entry.c create mode 100644 lib/os/macos/libdiskmgt/inuse_corestorage.c create mode 100644 lib/os/macos/libdiskmgt/inuse_fs.c create mode 100644 lib/os/macos/libdiskmgt/inuse_macswap.c create mode 100644 lib/os/macos/libdiskmgt/inuse_mnt.c create mode 100644 lib/os/macos/libdiskmgt/inuse_partition.c create mode 100644 lib/os/macos/libdiskmgt/inuse_zpool.c create mode 100644 lib/os/macos/libdiskmgt/libdiskmgt.c create mode 100644 lib/os/macos/libdiskmgt/slice.c create mode 100644 man/man7/zfsprops-macos.7 create mode 100644 module/os/macos/.gitignore create mode 100644 module/os/macos/Makefile.am create mode 100644 module/os/macos/README.md create mode 100644 module/os/macos/spl/README.md create mode 100644 module/os/macos/spl/spl-atomic.c create mode 100644 module/os/macos/spl/spl-condvar.c create mode 100644 module/os/macos/spl/spl-cred.c create mode 100644 module/os/macos/spl/spl-ddi.c create mode 100644 module/os/macos/spl/spl-debug.c create mode 100644 module/os/macos/spl/spl-err.c create mode 100644 module/os/macos/spl/spl-kmem.c create mode 100644 module/os/macos/spl/spl-kstat.c create mode 100644 module/os/macos/spl/spl-list.c create mode 100644 module/os/macos/spl/spl-mutex.c create mode 100644 module/os/macos/spl/spl-osx.c create mode 100644 module/os/macos/spl/spl-policy.c create mode 100644 module/os/macos/spl/spl-proc.c create mode 100644 module/os/macos/spl/spl-proc_list.c create mode 100644 module/os/macos/spl/spl-processor.c create mode 100644 module/os/macos/spl/spl-qsort.c create mode 100644 module/os/macos/spl/spl-rwlock.c create mode 100644 module/os/macos/spl/spl-seg_kmem.c create mode 100644 module/os/macos/spl/spl-taskq.c create mode 100644 module/os/macos/spl/spl-thread.c create mode 100644 module/os/macos/spl/spl-time.c create mode 100644 module/os/macos/spl/spl-tsd.c create mode 100644 module/os/macos/spl/spl-uio.c create mode 100644 module/os/macos/spl/spl-vmem.c create mode 100644 module/os/macos/spl/spl-vnode.c create mode 100644 module/os/macos/spl/spl-xdr.c create mode 100644 module/os/macos/spl/spl-zlib.c create mode 100644 module/os/macos/zfs/.gitignore create mode 100644 module/os/macos/zfs/Info.plist create mode 100644 module/os/macos/zfs/InfoPlist.strings create mode 100644 module/os/macos/zfs/ZFSDataset.cpp create mode 100644 module/os/macos/zfs/ZFSDatasetProxy.cpp create mode 100644 module/os/macos/zfs/ZFSDatasetScheme.cpp create mode 100644 module/os/macos/zfs/ZFSPool.cpp create mode 100644 module/os/macos/zfs/abd_os.c create mode 100644 module/os/macos/zfs/arc_os.c create mode 100644 module/os/macos/zfs/ldi_iokit.cpp create mode 100644 module/os/macos/zfs/ldi_osx.c create mode 100644 module/os/macos/zfs/ldi_vnode.c create mode 100644 module/os/macos/zfs/policy.c create mode 100644 module/os/macos/zfs/qat.c create mode 100644 module/os/macos/zfs/qat_compress.c create mode 100644 module/os/macos/zfs/qat_crypt.c create mode 100644 module/os/macos/zfs/spa_misc_os.c create mode 100644 module/os/macos/zfs/sysctl_os.c create mode 100644 module/os/macos/zfs/trace.c create mode 100644 module/os/macos/zfs/vdev_disk.c create mode 100644 module/os/macos/zfs/vdev_file.c create mode 100644 module/os/macos/zfs/zfs_acl.c create mode 100644 module/os/macos/zfs/zfs_boot.cpp create mode 100644 module/os/macos/zfs/zfs_ctldir.c create mode 100644 module/os/macos/zfs/zfs_debug.c create mode 100644 module/os/macos/zfs/zfs_dir.c create mode 100644 module/os/macos/zfs/zfs_file_os.c create mode 100644 module/os/macos/zfs/zfs_fuid_os.c create mode 100644 module/os/macos/zfs/zfs_ioctl_os.c create mode 100644 module/os/macos/zfs/zfs_osx.cpp create mode 100644 module/os/macos/zfs/zfs_racct.c create mode 100644 module/os/macos/zfs/zfs_vfsops.c create mode 100644 module/os/macos/zfs/zfs_vnops_os.c create mode 100644 module/os/macos/zfs/zfs_vnops_osx.c create mode 100644 module/os/macos/zfs/zfs_vnops_osx_lib.c create mode 100644 module/os/macos/zfs/zfs_vnops_osx_xattr.c create mode 100644 module/os/macos/zfs/zfs_znode.c create mode 100644 module/os/macos/zfs/zio_crypt.c create mode 100644 module/os/macos/zfs/zvolIO.cpp create mode 100644 module/os/macos/zfs/zvol_os.c create mode 100755 scripts/load_macos.sh create mode 100755 scripts/pkg_macos.sh diff --git a/.github/workflows/macos-build.yaml b/.github/workflows/macos-build.yaml new file mode 100644 index 000000000000..de387c9adbfd --- /dev/null +++ b/.github/workflows/macos-build.yaml @@ -0,0 +1,54 @@ +name: Build on macOS +on: push + +jobs: + build: + runs-on: macos-latest + steps: + - name: Check out repository code + uses: actions/checkout@v3 + #- name: csrutil disable + # run: | + # sudo csrutil disable + #- name: csrutil enable + # run: | + # sudo csrutil enable --without kext + #- name: spctl kext-consent disable + # run: | + # sudo spctl kext-consent disable + - name: install deps + run: | + brew install automake libtool gawk coreutils + - name: install deps + run: | + #brew install openssl@3 + - name: autogen + run: | + ./autogen.sh + - name: configure + run: | + #https://stackoverflow.com/a/62591864 + ./configure CPPFLAGS="-I/usr/local/opt/gettext/include -I/usr/local/opt/openssl/include" LDFLAGS="-L/usr/local/opt/gettext/lib/ -L/usr/local/opt/openssl/lib" + - name: build + run: | + make -j 2 + #- name: install + # run: | + # sudo make install DESTDIR=/// + + #- name: load + # run: | + # sudo kextload -v /Library/Extensions/zfs.kext + + #- name: ls + # run: | + # sudo ls -Rla + # ls -Rla + #- name: rsync + # run: | + # rsync -avx --exclude /out/ ${{github.workspace}}/ ${{github.workspace}}/out/ --no-links + #- name: Upload dev build + # uses: actions/upload-artifact@v3.1.0 + # with: + # name: dev_build + # path: ${{github.workspace}}/out/* diff --git a/cmd/os/macos/InvariantDisks/.gitignore b/cmd/os/macos/InvariantDisks/.gitignore new file mode 100644 index 000000000000..da11ea5ebaf7 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/.gitignore @@ -0,0 +1,3 @@ +/DerivedData/ +*.xcworkspace/ +xcuserdata/ diff --git a/cmd/os/macos/InvariantDisks/BSD.LICENSE.md b/cmd/os/macos/InvariantDisks/BSD.LICENSE.md new file mode 100644 index 000000000000..37443bfcc54b --- /dev/null +++ b/cmd/os/macos/InvariantDisks/BSD.LICENSE.md @@ -0,0 +1,27 @@ +Copyright (c) 2014, Gerhard Röthlin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* 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. + +* Neither the name of the Project nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks.xcodeproj/project.pbxproj b/cmd/os/macos/InvariantDisks/InvariantDisks.xcodeproj/project.pbxproj new file mode 100644 index 000000000000..fa0ffd5589ba --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks.xcodeproj/project.pbxproj @@ -0,0 +1,375 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 700AE3C91915139C00929DAE /* IDUUIDLinker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 700AE3C71915139C00929DAE /* IDUUIDLinker.cpp */; }; + 700AE3CC191530A000929DAE /* IDSerialLinker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 700AE3CA191530A000929DAE /* IDSerialLinker.cpp */; }; + 704C39201A3C777F00929DAE /* IDDispatchUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 704C391E1A3C777F00929DAE /* IDDispatchUtils.cpp */; }; + 704C39231A3C7E9600929DAE /* IDDAHandlerIdle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 704C39211A3C7E9600929DAE /* IDDAHandlerIdle.cpp */; }; + 707E3F0E1B6CDC2400929DAE /* IDLogUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 707E3F0C1B6CDC2400929DAE /* IDLogUtils.cpp */; }; + 70C63F28190CF5E800929DAE /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70C63F27190CF5E800929DAE /* main.cpp */; }; + 70C63F32190CF78800929DAE /* IDCLI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70C63F30190CF78800929DAE /* IDCLI.cpp */; }; + 70C63F35190CF87400929DAE /* IDException.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70C63F33190CF87400929DAE /* IDException.cpp */; }; + 70C63F44190CFFA500929DAE /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70C63F43190CFFA500929DAE /* CoreFoundation.framework */; }; + 70C63F46190CFFAC00929DAE /* DiskArbitration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70C63F45190CFFAC00929DAE /* DiskArbitration.framework */; }; + 70C63F4B190D1AEF00929DAE /* IDDiskArbitrationDispatcher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70C63F49190D1AEF00929DAE /* IDDiskArbitrationDispatcher.cpp */; }; + 70C63F4F190D2AA000929DAE /* IDDiskInfoLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70C63F4D190D2AA000929DAE /* IDDiskInfoLogger.cpp */; }; + 70C63F52190D2CC900929DAE /* IDDiskArbitrationUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70C63F50190D2CC900929DAE /* IDDiskArbitrationUtils.cpp */; }; + 70C63F55190D4A4300929DAE /* IDMediaPathLinker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70C63F53190D4A4300929DAE /* IDMediaPathLinker.cpp */; }; + 70C63F58190D574000929DAE /* IDFileUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 70C63F56190D574000929DAE /* IDFileUtils.mm */; }; + 70C63F5A190D713B00929DAE /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70C63F59190D713B00929DAE /* IOKit.framework */; }; + 70C9002E1C6780EF001A40C8 /* IDImagePathLinker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70C9002C1C6780EF001A40C8 /* IDImagePathLinker.cpp */; }; + 70D4742E1BDD0E4200929DAE /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70D4742D1BDD0E4200929DAE /* Foundation.framework */; }; + 70D474311BDD17E800929DAE /* IDSymlinkHandle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70D4742F1BDD17E800929DAE /* IDSymlinkHandle.cpp */; }; + 70D474371BDD21E100929DAE /* IDBaseLinker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70D474351BDD21E100929DAE /* IDBaseLinker.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 70C63F22190CF5E800929DAE /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 700AE3C71915139C00929DAE /* IDUUIDLinker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IDUUIDLinker.cpp; sourceTree = ""; }; + 700AE3C81915139C00929DAE /* IDUUIDLinker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IDUUIDLinker.hpp; sourceTree = ""; }; + 700AE3CA191530A000929DAE /* IDSerialLinker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IDSerialLinker.cpp; sourceTree = ""; }; + 700AE3CB191530A000929DAE /* IDSerialLinker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IDSerialLinker.hpp; sourceTree = ""; }; + 704C391E1A3C777F00929DAE /* IDDispatchUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IDDispatchUtils.cpp; sourceTree = ""; }; + 704C391F1A3C777F00929DAE /* IDDispatchUtils.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IDDispatchUtils.hpp; sourceTree = ""; }; + 704C39211A3C7E9600929DAE /* IDDAHandlerIdle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IDDAHandlerIdle.cpp; sourceTree = ""; }; + 704C39221A3C7E9600929DAE /* IDDAHandlerIdle.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IDDAHandlerIdle.hpp; sourceTree = ""; }; + 707E3F0C1B6CDC2400929DAE /* IDLogUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IDLogUtils.cpp; sourceTree = ""; }; + 707E3F0D1B6CDC2400929DAE /* IDLogUtils.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IDLogUtils.hpp; sourceTree = ""; }; + 70C63F24190CF5E800929DAE /* InvariantDisks */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = InvariantDisks; sourceTree = BUILT_PRODUCTS_DIR; }; + 70C63F27190CF5E800929DAE /* main.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = main.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; + 70C63F30190CF78800929DAE /* IDCLI.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = IDCLI.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; + 70C63F31190CF78800929DAE /* IDCLI.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; lineEnding = 0; path = IDCLI.hpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; + 70C63F33190CF87400929DAE /* IDException.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = IDException.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; + 70C63F34190CF87400929DAE /* IDException.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; lineEnding = 0; path = IDException.hpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; + 70C63F43190CFFA500929DAE /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 70C63F45190CFFAC00929DAE /* DiskArbitration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DiskArbitration.framework; path = System/Library/Frameworks/DiskArbitration.framework; sourceTree = SDKROOT; }; + 70C63F49190D1AEF00929DAE /* IDDiskArbitrationDispatcher.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = IDDiskArbitrationDispatcher.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; + 70C63F4A190D1AEF00929DAE /* IDDiskArbitrationDispatcher.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; lineEnding = 0; path = IDDiskArbitrationDispatcher.hpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 70C63F4C190D1B4200929DAE /* IDDiskArbitrationHandler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; lineEnding = 0; path = IDDiskArbitrationHandler.hpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; + 70C63F4D190D2AA000929DAE /* IDDiskInfoLogger.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = IDDiskInfoLogger.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; + 70C63F4E190D2AA000929DAE /* IDDiskInfoLogger.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; lineEnding = 0; path = IDDiskInfoLogger.hpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 70C63F50190D2CC900929DAE /* IDDiskArbitrationUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = IDDiskArbitrationUtils.cpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; + 70C63F51190D2CC900929DAE /* IDDiskArbitrationUtils.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; lineEnding = 0; path = IDDiskArbitrationUtils.hpp; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 70C63F53190D4A4300929DAE /* IDMediaPathLinker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IDMediaPathLinker.cpp; sourceTree = ""; }; + 70C63F54190D4A4300929DAE /* IDMediaPathLinker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IDMediaPathLinker.hpp; sourceTree = ""; }; + 70C63F56190D574000929DAE /* IDFileUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = IDFileUtils.mm; sourceTree = ""; }; + 70C63F57190D574000929DAE /* IDFileUtils.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IDFileUtils.hpp; sourceTree = ""; }; + 70C63F59190D713B00929DAE /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; + 70C9002C1C6780EF001A40C8 /* IDImagePathLinker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IDImagePathLinker.cpp; sourceTree = ""; }; + 70C9002D1C6780EF001A40C8 /* IDImagePathLinker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IDImagePathLinker.hpp; sourceTree = ""; }; + 70D4742D1BDD0E4200929DAE /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 70D4742F1BDD17E800929DAE /* IDSymlinkHandle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IDSymlinkHandle.cpp; sourceTree = ""; }; + 70D474301BDD17E800929DAE /* IDSymlinkHandle.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IDSymlinkHandle.hpp; sourceTree = ""; }; + 70D474351BDD21E100929DAE /* IDBaseLinker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IDBaseLinker.cpp; sourceTree = ""; }; + 70D474361BDD21E100929DAE /* IDBaseLinker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IDBaseLinker.hpp; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 70C63F21190CF5E800929DAE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 70C63F5A190D713B00929DAE /* IOKit.framework in Frameworks */, + 70C63F46190CFFAC00929DAE /* DiskArbitration.framework in Frameworks */, + 70C63F44190CFFA500929DAE /* CoreFoundation.framework in Frameworks */, + 70D4742E1BDD0E4200929DAE /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 70C63F1B190CF5E800929DAE = { + isa = PBXGroup; + children = ( + 70C63F26190CF5E800929DAE /* InvariantDisks */, + 70C63F47190D15FE00929DAE /* Frameworks */, + 70C63F25190CF5E800929DAE /* Products */, + ); + sourceTree = ""; + }; + 70C63F25190CF5E800929DAE /* Products */ = { + isa = PBXGroup; + children = ( + 70C63F24190CF5E800929DAE /* InvariantDisks */, + ); + name = Products; + sourceTree = ""; + }; + 70C63F26190CF5E800929DAE /* InvariantDisks */ = { + isa = PBXGroup; + children = ( + 70C63F27190CF5E800929DAE /* main.cpp */, + 70C63F30190CF78800929DAE /* IDCLI.cpp */, + 70C63F31190CF78800929DAE /* IDCLI.hpp */, + 70C63F33190CF87400929DAE /* IDException.cpp */, + 70C63F34190CF87400929DAE /* IDException.hpp */, + 70C63F50190D2CC900929DAE /* IDDiskArbitrationUtils.cpp */, + 70C63F51190D2CC900929DAE /* IDDiskArbitrationUtils.hpp */, + 70C63F4C190D1B4200929DAE /* IDDiskArbitrationHandler.hpp */, + 70C63F4D190D2AA000929DAE /* IDDiskInfoLogger.cpp */, + 70C63F4E190D2AA000929DAE /* IDDiskInfoLogger.hpp */, + 704C39211A3C7E9600929DAE /* IDDAHandlerIdle.cpp */, + 704C39221A3C7E9600929DAE /* IDDAHandlerIdle.hpp */, + 70D474351BDD21E100929DAE /* IDBaseLinker.cpp */, + 70D474361BDD21E100929DAE /* IDBaseLinker.hpp */, + 70C63F53190D4A4300929DAE /* IDMediaPathLinker.cpp */, + 70C63F54190D4A4300929DAE /* IDMediaPathLinker.hpp */, + 700AE3C71915139C00929DAE /* IDUUIDLinker.cpp */, + 700AE3C81915139C00929DAE /* IDUUIDLinker.hpp */, + 700AE3CA191530A000929DAE /* IDSerialLinker.cpp */, + 700AE3CB191530A000929DAE /* IDSerialLinker.hpp */, + 70C9002C1C6780EF001A40C8 /* IDImagePathLinker.cpp */, + 70C9002D1C6780EF001A40C8 /* IDImagePathLinker.hpp */, + 70C63F49190D1AEF00929DAE /* IDDiskArbitrationDispatcher.cpp */, + 70C63F4A190D1AEF00929DAE /* IDDiskArbitrationDispatcher.hpp */, + 70C63F56190D574000929DAE /* IDFileUtils.mm */, + 70C63F57190D574000929DAE /* IDFileUtils.hpp */, + 70D4742F1BDD17E800929DAE /* IDSymlinkHandle.cpp */, + 70D474301BDD17E800929DAE /* IDSymlinkHandle.hpp */, + 704C391E1A3C777F00929DAE /* IDDispatchUtils.cpp */, + 704C391F1A3C777F00929DAE /* IDDispatchUtils.hpp */, + 707E3F0C1B6CDC2400929DAE /* IDLogUtils.cpp */, + 707E3F0D1B6CDC2400929DAE /* IDLogUtils.hpp */, + ); + path = InvariantDisks; + sourceTree = ""; + }; + 70C63F47190D15FE00929DAE /* Frameworks */ = { + isa = PBXGroup; + children = ( + 70D4742D1BDD0E4200929DAE /* Foundation.framework */, + 70C63F59190D713B00929DAE /* IOKit.framework */, + 70C63F45190CFFAC00929DAE /* DiskArbitration.framework */, + 70C63F43190CFFA500929DAE /* CoreFoundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 70C63F23190CF5E800929DAE /* InvariantDisks */ = { + isa = PBXNativeTarget; + buildConfigurationList = 70C63F2D190CF5E800929DAE /* Build configuration list for PBXNativeTarget "InvariantDisks" */; + buildPhases = ( + 70C63F42190CFB6800929DAE /* Git Version */, + 70C63F20190CF5E800929DAE /* Sources */, + 70C63F21190CF5E800929DAE /* Frameworks */, + 70C63F22190CF5E800929DAE /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = InvariantDisks; + productName = InvariantDisks; + productReference = 70C63F24190CF5E800929DAE /* InvariantDisks */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 70C63F1C190CF5E800929DAE /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0800; + ORGANIZATIONNAME = "the-color-black.net"; + }; + buildConfigurationList = 70C63F1F190CF5E800929DAE /* Build configuration list for PBXProject "InvariantDisks" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 70C63F1B190CF5E800929DAE; + productRefGroup = 70C63F25190CF5E800929DAE /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 70C63F23190CF5E800929DAE /* InvariantDisks */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 70C63F42190CFB6800929DAE /* Git Version */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/.git", + ); + name = "Git Version"; + outputPaths = ( + "$(DERIVED_SOURCES_DIR)/git-version.h", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "GIT_VERSION=$(git describe --tags --long --abbrev=6 --dirty=*)\necho \"#define GIT_VERSION \\\"${GIT_VERSION}\\\"\" > ${DERIVED_FILE_DIR}/git-version.h\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 70C63F20190CF5E800929DAE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 700AE3C91915139C00929DAE /* IDUUIDLinker.cpp in Sources */, + 700AE3CC191530A000929DAE /* IDSerialLinker.cpp in Sources */, + 70C63F32190CF78800929DAE /* IDCLI.cpp in Sources */, + 70C63F52190D2CC900929DAE /* IDDiskArbitrationUtils.cpp in Sources */, + 70C63F55190D4A4300929DAE /* IDMediaPathLinker.cpp in Sources */, + 704C39201A3C777F00929DAE /* IDDispatchUtils.cpp in Sources */, + 704C39231A3C7E9600929DAE /* IDDAHandlerIdle.cpp in Sources */, + 70C63F35190CF87400929DAE /* IDException.cpp in Sources */, + 70C9002E1C6780EF001A40C8 /* IDImagePathLinker.cpp in Sources */, + 70D474311BDD17E800929DAE /* IDSymlinkHandle.cpp in Sources */, + 707E3F0E1B6CDC2400929DAE /* IDLogUtils.cpp in Sources */, + 70D474371BDD21E100929DAE /* IDBaseLinker.cpp in Sources */, + 70C63F4B190D1AEF00929DAE /* IDDiskArbitrationDispatcher.cpp in Sources */, + 70C63F4F190D2AA000929DAE /* IDDiskInfoLogger.cpp in Sources */, + 70C63F58190D574000929DAE /* IDFileUtils.mm in Sources */, + 70C63F28190CF5E800929DAE /* main.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 70C63F2B190CF5E800929DAE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.12; + ONLY_ACTIVE_ARCH = YES; + }; + name = Debug; + }; + 70C63F2C190CF5E800929DAE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.12; + }; + name = Release; + }; + 70C63F2E190CF5E800929DAE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 70C63F2F190CF5E800929DAE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 70C63F1F190CF5E800929DAE /* Build configuration list for PBXProject "InvariantDisks" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 70C63F2B190CF5E800929DAE /* Debug */, + 70C63F2C190CF5E800929DAE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 70C63F2D190CF5E800929DAE /* Build configuration list for PBXNativeTarget "InvariantDisks" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 70C63F2E190CF5E800929DAE /* Debug */, + 70C63F2F190CF5E800929DAE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 70C63F1C190CF5E800929DAE /* Project object */; +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDBaseLinker.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDBaseLinker.cpp new file mode 100644 index 000000000000..6769f649c15f --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDBaseLinker.cpp @@ -0,0 +1,71 @@ +// +// IDBaseLinker.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.05.03. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDBaseLinker.hpp" + +#include "IDDiskArbitrationUtils.hpp" +#include "IDFileUtils.hpp" + +namespace ID +{ + BaseLinker::BaseLinker(std::string base, LogClient const & logger) : + DiskArbitrationHandler(logger), + m_base(std::move(base)) + { + createCleanPath(m_base); + } + + void BaseLinker::diskDisappeared(DADiskRef disk, DiskInformation const & di) + { + removeLinksForDisk(di); + } + + void BaseLinker::addLinkForDisk(std::string const & link, DiskInformation const & di) + { + try + { + if (link.empty()) + return; + std::string devicePath = "/dev/" + di.mediaBSDName; + logger().logDefault("Creating symlink: ", link, " -> ", devicePath); + m_links.emplace(devicePath, SymlinkHandle(link, devicePath)); + } + catch (std::exception const & e) + { + logger().logError("Could not create symlink: ", e.what()); + } + } + + void BaseLinker::removeLinksForDisk(DiskInformation const & di) + { + try + { + std::string devicePath = "/dev/" + di.mediaBSDName; + auto found = m_links.equal_range(devicePath); + for (auto it = found.first; it != found.second; ++it) + { + logger().logDefault("Removing symlink: ", it->second.link()); + it->second.reset(); + } + m_links.erase(found.first, found.second); + } + catch (std::exception const & e) + { + logger().logError("Could not remove symlink: ", e.what()); + } + } + + std::string const & BaseLinker::base() const + { + return m_base; + } +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDBaseLinker.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDBaseLinker.hpp new file mode 100644 index 000000000000..26fd3c9544c9 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDBaseLinker.hpp @@ -0,0 +1,44 @@ +// +// IDBaseLinker.hpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.05.03. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_BASE_LINKER_HPP +#define ID_BASE_LINKER_HPP + +#include "IDDiskArbitrationHandler.hpp" + +#include "IDSymlinkHandle.hpp" + +#include +#include + +namespace ID +{ + class BaseLinker : public DiskArbitrationHandler + { + public: + explicit BaseLinker(std::string base, LogClient const & logger); + + public: + virtual void diskDisappeared(DADiskRef disk, DiskInformation const & info) override; + + protected: + void addLinkForDisk(std::string const & link, DiskInformation const & di); + void removeLinksForDisk(DiskInformation const & di); + std::string const & base() const; + + private: + std::string m_base; + std::multimap m_links; + }; +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDCLI.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDCLI.cpp new file mode 100644 index 000000000000..fd4ca8205940 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDCLI.cpp @@ -0,0 +1,171 @@ +// +// IDCLI.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDCLI.hpp" + +#include "IDException.hpp" +#include "IDDiskArbitrationDispatcher.hpp" +#include "IDDiskInfoLogger.hpp" +#include "IDDAHandlerIdle.hpp" +#include "IDMediaPathLinker.hpp" +#include "IDUUIDLinker.hpp" +#include "IDSerialLinker.hpp" +#include "IDImagePathLinker.hpp" +#include "IDDispatchUtils.hpp" +#include "IDLogUtils.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "git-version.h" + +namespace ID +{ + struct CLI::Impl + { + std::mutex mutex; + DispatchSource signalSourceINT; + DispatchSource signalSourceTERM; + bool showHelp = false; + bool verbose = false; + std::string basePath = "/var/run/disk"; + std::string logPath; + int64_t idleTimeoutNS = 4000000000; + CFRunLoopRef runloop = nullptr; + LogClient * logger = nullptr; + }; + + CLI::CLI(int & argc, char ** argv, LogClient & logger) : + m_impl(new Impl) + { + m_impl->logger = &logger; + // Setup + dispatch_function_t stopHandler = [](void * ctx){ static_cast(ctx)->stop();}; + m_impl->signalSourceINT = createSourceSignal(SIGINT, this, stopHandler); + m_impl->signalSourceTERM = createSourceSignal(SIGTERM, this, stopHandler); + // UI + showVersion(); + parse(argc, argv); + } + + CLI::~CLI() + { + } + + int CLI::exec() + { + // Print help and terminate + if (m_impl->showHelp) + { + showHelp(); + return 0; + } + // Start runloop + { + std::lock_guard lock(m_impl->mutex); + if (m_impl->runloop) + throw Exception("CLI already running"); + m_impl->runloop = CFRunLoopGetCurrent(); + } + auto & logger = *m_impl->logger; + if (!m_impl->logPath.empty()) + logger.addLogFile(m_impl->logPath.c_str()); + DiskArbitrationDispatcher dispatcher; + dispatcher.addHandler(std::make_shared(m_impl->basePath, m_impl->idleTimeoutNS, logger)); + dispatcher.addHandler(std::make_shared(m_impl->verbose, logger)); + dispatcher.addHandler(std::make_shared(m_impl->basePath + "/by-path", logger)); + dispatcher.addHandler(std::make_shared(m_impl->basePath + "/by-id", logger)); + dispatcher.addHandler(std::make_shared(m_impl->basePath + "/by-serial", logger)); + dispatcher.addHandler(std::make_shared(m_impl->basePath + "/by-image-path", logger)); + dispatcher.start(); + CFRunLoopRun(); + { + std::lock_guard lock(m_impl->mutex); + m_impl->runloop = nullptr; + } + return 0; + } + + void CLI::stop() + { + std::lock_guard lock(m_impl->mutex); + if (m_impl->runloop) + CFRunLoopStop(m_impl->runloop); + } + + struct CLIFlagHandler + { + size_t argCount; + std::function func; + }; + + void CLI::showVersion() const + { + std::cout << "InvariantDisk " << GIT_VERSION << std::endl; + } + + void CLI::showHelp() const + { + std::cout << "Usage: InvariantDisks [-hv] [-p ] [-t ]\n"; + std::cout << "\t-h:\tprint help and exit\n"; + std::cout << "\t-v:\tverbose logging\n"; + std::cout << "\t-p :\tset base path for symlinks (" << m_impl->basePath << ")\n"; + std::cout << "\t-l :\tset optional path for logging (" << m_impl->logPath << ")\n"; + std::cout << "\t-t :\tset idle timeout (" << m_impl->idleTimeoutNS/1000000 << " ms)\n"; + } + + void CLI::parse(int & argc, char ** argv) + { + // Command Line Parsing + std::map cliFlags = + { + {"-h", { 0, [&](char **){ m_impl->showHelp = true; }}}, + {"-v", { 0, [&](char **){ m_impl->verbose = true; }}}, + {"-p", { 1, [&](char ** a){ m_impl->basePath = a[1]; }}}, + {"-l", { 1, [&](char ** a){ m_impl->logPath = a[1]; }}}, + {"-t", { 1, [&](char ** a){ + try + { + int64_t timeoutInNS = std::stol(a[1])*1000000ll; + if (timeoutInNS < 0) + throw std::out_of_range("negative"); + m_impl->idleTimeoutNS = timeoutInNS; + } + catch (std::exception const & e) + { + Throw() << "Idle Timeout " << a[1] << " is not a valid timeout: " << e.what(); + } + }}} + }; + for (int argIdx = 0; argIdx < argc; ++argIdx) + { + auto flagIt = cliFlags.find(argv[argIdx]); + if (flagIt != cliFlags.end()) + { + CLIFlagHandler const & f = flagIt->second; + if (argIdx + f.argCount >= argc) + Throw() << "Flag " << argv[argIdx] << " requires " << f.argCount << " arguments"; + f.func(&argv[argIdx]); + argIdx += f.argCount; + } + } + } +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDCLI.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDCLI.hpp new file mode 100644 index 000000000000..6ca417b96ff1 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDCLI.hpp @@ -0,0 +1,44 @@ +// +// IDCLI.hpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_CLI_HPP +#define ID_CLI_HPP + +#include +#include + +namespace ID +{ + class LogClient; + + class CLI + { + public: + CLI(int & argc, char ** argv, LogClient & logger); + ~CLI(); + + public: + int exec(); + void stop(); + + private: + void showVersion() const; + void showHelp() const; + void parse(int & argc, char ** argv); + + private: + struct Impl; + std::unique_ptr m_impl; + }; +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDDAHandlerIdle.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDAHandlerIdle.cpp new file mode 100644 index 000000000000..3a68986148f2 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDAHandlerIdle.cpp @@ -0,0 +1,49 @@ +// +// IDDAHandlerIdle.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDDAHandlerIdle.hpp" + +#include "IDFileUtils.hpp" + +namespace ID +{ + DAHandlerIdle::DAHandlerIdle(std::string base, int64_t idleTimeoutNS, + LogClient const & logger) : + DiskArbitrationHandler(logger), + m_base(std::move(base)), m_idleTimeout(idleTimeoutNS), + m_idleTimer(createSourceTimer(this, [](void * ctx){ static_cast(ctx)->idle(); })) + { + createPath(m_base); + busy(); + } + + void DAHandlerIdle::diskAppeared(DADiskRef /*disk*/, DiskInformation const & /*info*/) + { + busy(); + } + + void DAHandlerIdle::diskDisappeared(DADiskRef /*disk*/, DiskInformation const & /*info*/) + { + busy(); + } + + void DAHandlerIdle::idle() + { + createFile(m_base + "/invariant.idle"); + } + + void DAHandlerIdle::busy() + { + removeFSObject(m_base + "/invariant.idle"); + scheduleSingleshot(m_idleTimer, m_idleTimeout); + } +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDDAHandlerIdle.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDAHandlerIdle.hpp new file mode 100644 index 000000000000..41087c829dd1 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDAHandlerIdle.hpp @@ -0,0 +1,44 @@ +// +// IDDAHandlerIdle.hpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_DAHANDLERIDLE_HPP +#define ID_DAHANDLERIDLE_HPP + +#include "IDDiskArbitrationHandler.hpp" +#include "IDDispatchUtils.hpp" + +#include + +namespace ID +{ + class DAHandlerIdle : public DiskArbitrationHandler + { + public: + explicit DAHandlerIdle(std::string base, int64_t idleTimeoutNS, + LogClient const & logger); + + public: + virtual void diskAppeared(DADiskRef disk, DiskInformation const & info) override; + virtual void diskDisappeared(DADiskRef disk, DiskInformation const & info) override; + + private: + void idle(); + void busy(); + + private: + std::string m_base; + int64_t m_idleTimeout; + DispatchSource m_idleTimer; + }; +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationDispatcher.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationDispatcher.cpp new file mode 100644 index 000000000000..92022158fc4d --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationDispatcher.cpp @@ -0,0 +1,106 @@ +// +// IDDiskArbitrationDispatcher.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDDiskArbitrationDispatcher.hpp" + +#include "IDDiskArbitrationHandler.hpp" +#include "IDDiskArbitrationUtils.hpp" + +#include + +#include +#include +#include + +namespace ID +{ + struct DiskArbitrationDispatcher::Impl + { + std::mutex mutex; + std::vector handler; + DASessionRef session = nullptr; + bool scheduled = false; + }; + + DiskArbitrationDispatcher::DiskArbitrationDispatcher() : + m_impl(new Impl) + { + m_impl->session = DASessionCreate(kCFAllocatorDefault); + DARegisterDiskAppearedCallback(m_impl->session, nullptr, [](DADiskRef disk, void * ctx) + { static_cast(ctx)->diskAppeared(disk); }, this); + DARegisterDiskDisappearedCallback(m_impl->session, nullptr, [](DADiskRef disk, void * ctx) + { static_cast(ctx)->diskDisappeared(disk); }, this); + } + + DiskArbitrationDispatcher::~DiskArbitrationDispatcher() + { + stop(); + CFRelease(m_impl->session); + } + + void DiskArbitrationDispatcher::addHandler(Handler handler) + { + std::lock_guard lock(m_impl->mutex); + m_impl->handler.push_back(std::move(handler)); + } + + void DiskArbitrationDispatcher::removeHandler(Handler const & handler) + { + std::lock_guard lock(m_impl->mutex); + m_impl->handler.erase(std::find(m_impl->handler.begin(), m_impl->handler.end(), handler), + m_impl->handler.end()); + } + + void DiskArbitrationDispatcher::clearHandler() + { + std::lock_guard lock(m_impl->mutex); + m_impl->handler.clear(); + } + + void DiskArbitrationDispatcher::start() + { + std::lock_guard lock(m_impl->mutex); + if (!m_impl->scheduled) + { + DASessionScheduleWithRunLoop(m_impl->session, + CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + m_impl->scheduled = true; + } + } + + void DiskArbitrationDispatcher::stop() + { + std::lock_guard lock(m_impl->mutex); + if (m_impl->scheduled) + { + DASessionUnscheduleFromRunLoop(m_impl->session, + CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + m_impl->scheduled = false; + } + } + + void DiskArbitrationDispatcher::diskAppeared(DADiskRef disk) const + { + DiskInformation info = getDiskInformation(disk); + std::lock_guard lock(m_impl->mutex); + for (auto const & handler: m_impl->handler) + handler->diskAppeared(disk, info); + } + + void DiskArbitrationDispatcher::diskDisappeared(DADiskRef disk) const + { + DiskInformation info = getDiskInformation(disk); + std::lock_guard lock(m_impl->mutex); + for (auto const & handler: m_impl->handler) + handler->diskDisappeared(disk, info); + } +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationDispatcher.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationDispatcher.hpp new file mode 100644 index 000000000000..9ee646fae932 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationDispatcher.hpp @@ -0,0 +1,55 @@ +// +// IDDiskArbitrationDispatcher.hpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_DISKARBITRATIONDISPATCHER_HPP +#define ID_DISKARBITRATIONDISPATCHER_HPP + +#include + +#include + +namespace ID +{ + class DiskArbitrationHandler; + + /*! + \brief Dispatches DiskArbitration events, wrapper around the DiskArbitration framework + */ + class DiskArbitrationDispatcher + { + public: + typedef std::shared_ptr Handler; + + public: + DiskArbitrationDispatcher(); + ~DiskArbitrationDispatcher(); + + public: + void addHandler(Handler handler); + void removeHandler(Handler const & handler); + void clearHandler(); + + public: + void start(); + void stop(); + + private: + void diskAppeared(DADiskRef disk) const; + void diskDisappeared(DADiskRef disk) const; + + private: + struct Impl; + std::unique_ptr m_impl; + }; +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationHandler.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationHandler.hpp new file mode 100644 index 000000000000..80d891a21631 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationHandler.hpp @@ -0,0 +1,42 @@ +// +// IDDiskArbitrationHandler.hpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_DISKARBITRATIONHANDLER_HPP +#define ID_DISKARBITRATIONHANDLER_HPP + +#include + +#include "IDLogUtils.hpp" + +namespace ID +{ + class DiskInformation; + + class DiskArbitrationHandler + { + public: + explicit DiskArbitrationHandler(LogClient const & logger) : m_logger(logger) {} + virtual ~DiskArbitrationHandler() = default; + + public: + virtual void diskAppeared(DADiskRef disk, DiskInformation const & info) = 0; + virtual void diskDisappeared(DADiskRef disk, DiskInformation const & info) = 0; + + protected: + LogClient const & logger() const { return m_logger; } + + private: + LogClient m_logger; + }; +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationUtils.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationUtils.cpp new file mode 100644 index 000000000000..1180b10b68cb --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationUtils.cpp @@ -0,0 +1,272 @@ +// +// IDDiskArbitrationUtils.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDDiskArbitrationUtils.hpp" + +#include + +#include +#include + +namespace ID +{ + std::ostream & operator<<(std::ostream & os, DADiskRef disk) + { + return os << getDiskInformation(disk); + } + + std::ostream & operator<<(std::ostream & os, DiskInformation const & disk) + { + return os << "Disk: (\n" + << "\tVolumeKind=\"" << disk.volumeKind << "\"\n" + << "\tVolumeUUID=\"" << disk.volumeUUID << "\"\n" + << "\tVolumeName=\"" << disk.volumeName << "\"\n" + << "\tVolumePath=\"" << disk.volumePath << "\"\n" + << "\tMediaKind=\"" << disk.mediaKind << "\"\n" + << "\tMediaType=\"" << disk.mediaType << "\"\n" + << "\tMediaUUID=\"" << disk.mediaUUID << "\"\n" + << "\tMediaBSDName=\"" << disk.mediaBSDName << "\"\n" + << "\tMediaName=\"" << disk.mediaName << "\"\n" + << "\tMediaPath=\"" << disk.mediaPath << "\"\n" + << "\tMediaContent=\"" << disk.mediaContent << "\"\n" + << "\tMedia(Whole,Leaf,Writable)=(" << disk.mediaWhole << ", " + << disk.mediaLeaf << ", " << disk.mediaWritable << ")\n" + << "\tDeviceGUID=\"" << disk.deviceGUID << "\"\n" + << "\tDevicePath=\"" << disk.devicePath << "\"\n" + << "\tDeviceProtocol=\"" << disk.deviceProtocol << "\"\n" + << "\tDeviceModel=\"" << disk.deviceModel << "\"\n" + << "\tBusName=\"" << disk.busName << "\"\n" + << "\tBusPath=\"" << disk.busPath << "\"\n" + << "\tIOSerial=\"" << disk.ioSerial << "\"\n" + << "\tImagePath=\"" << disk.imagePath << "\"\n" + << ")"; + } + + std::string to_string(CFStringRef str) + { + std::string result; + CFRange strRange = CFRangeMake(0, CFStringGetLength(str)); + CFIndex strBytes = 0; + CFStringGetBytes(str, strRange, kCFStringEncodingUTF8, 0, false, nullptr, 0, &strBytes); + if (strBytes > 0) + { + result.resize(static_cast(strBytes), '\0'); + CFStringGetBytes(str, strRange, kCFStringEncodingUTF8, 0, false, + reinterpret_cast(&result[0]), strBytes, nullptr); + } + return result; + } + + std::string to_string(CFURLRef url) + { + CFStringRef str = CFURLCopyPath(url); + std::string result = to_string(str); + CFRelease(str); + return result; + } + + std::string to_string(CFDataRef data) + { + char const * bytesBegin = reinterpret_cast(CFDataGetBytePtr(data)); + char const * bytesEnd = bytesBegin + CFDataGetLength(data); + std::stringstream ss; + ss << std::hex; + for (char const * byteIt = bytesBegin; byteIt != bytesEnd; ++byteIt) + ss << static_cast(*byteIt); + return ss.str(); + } + + std::string to_string(CFUUIDRef uuid) + { + CFStringRef str = CFUUIDCreateString(kCFAllocatorDefault, uuid); + std::string result = to_string(str); + CFRelease(str); + return result; + } + + std::string to_string(CFTypeRef variant) + { + if (!variant) + return std::string(); + CFTypeID typeID = CFGetTypeID(variant); + if (typeID == CFStringGetTypeID()) + return to_string(CFStringRef(variant)); + else if (typeID == CFURLGetTypeID()) + return to_string(CFURLRef(variant)); + else if (typeID == CFDataGetTypeID()) + return to_string(CFDataRef(variant)); + else if (typeID == CFUUIDGetTypeID()) + return to_string(CFUUIDRef(variant)); + return std::string(); + } + + std::string interpret_as_string(CFDataRef data) + { + char const * bytesBegin = reinterpret_cast(CFDataGetBytePtr(data)); + char const * bytesEnd = bytesBegin + CFDataGetLength(data); + return std::string(bytesBegin, bytesEnd); + } + + template + std::string stringFromDictionary(CFDictionaryRef dict, CFStringRef key) + { + if (T value = static_cast(CFDictionaryGetValue(dict, key))) + return to_string(value); + return std::string(); + } + + int64_t numberFromDictionary(CFDictionaryRef dict, CFStringRef key) + { + if (CFNumberRef value = static_cast(CFDictionaryGetValue(dict, key))) + { + int64_t number = 0; + CFNumberGetValue(value, kCFNumberSInt64Type, &number); + return number; + } + return 0; + } + + bool boolFromDictionary(CFDictionaryRef dict, CFStringRef key) + { + if (CFBooleanRef value = static_cast(CFDictionaryGetValue(dict, key))) + { + return CFBooleanGetValue(value); + } + return false; + } + + std::string stringFromIOObjectWithParents(io_object_t ioObject, CFStringRef key) + { + std::string result; + CFTypeRef resultRef = IORegistryEntrySearchCFProperty(ioObject, kIOServicePlane, key, + kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents); + if (resultRef) + { + result = to_string(resultRef); + CFRelease(resultRef); + } + return result; + } + + std::string serialNumberFromIOObject(io_object_t ioObject) + { + static CFStringRef const serialStrings[] = { + CFSTR("Serial Number"), + CFSTR("INQUIRY Unit Serial Number"), + CFSTR("USB Serial Number") + }; + for (CFStringRef serialString: serialStrings) + { + std::string serial = stringFromIOObjectWithParents(ioObject, serialString); + if (!serial.empty()) + return serial; + } + return std::string(); + } + + std::string imagePathFromIOObject(io_object_t ioObject) + { + std::string path; + CFStringRef key = CFSTR("image-path"); + CFTypeRef resultRef = IORegistryEntrySearchCFProperty(ioObject, kIOServicePlane, key, + kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents); + if (resultRef) + { + if (CFGetTypeID(resultRef) == CFDataGetTypeID()) + { + CFDataRef resultDataRef = CFDataRef(resultRef); + path = interpret_as_string(resultDataRef); + } + CFRelease(resultRef); + } + return path; + } + + static std::string coreStorageMark = "/CoreStoragePhysical/"; + + DiskInformation getDiskInformation(DADiskRef disk) + { + DiskInformation info; + // DiskArbitration + CFDictionaryRef descDict = DADiskCopyDescription(disk); + info.volumeKind = stringFromDictionary(descDict, kDADiskDescriptionVolumeKindKey); + info.volumeUUID = stringFromDictionary(descDict, kDADiskDescriptionVolumeUUIDKey); + info.volumeName = stringFromDictionary(descDict, kDADiskDescriptionVolumeNameKey); + info.volumePath = stringFromDictionary(descDict, kDADiskDescriptionVolumePathKey); + info.mediaKind = stringFromDictionary(descDict, kDADiskDescriptionMediaKindKey); + info.mediaType = stringFromDictionary(descDict, kDADiskDescriptionMediaTypeKey); + info.mediaUUID = stringFromDictionary(descDict, kDADiskDescriptionMediaUUIDKey); + info.mediaBSDName = stringFromDictionary(descDict, kDADiskDescriptionMediaBSDNameKey); + info.mediaName = stringFromDictionary(descDict, kDADiskDescriptionMediaNameKey); + info.mediaPath = stringFromDictionary(descDict, kDADiskDescriptionMediaPathKey); + info.mediaContent = stringFromDictionary(descDict, kDADiskDescriptionMediaContentKey); + info.mediaWhole = boolFromDictionary(descDict, kDADiskDescriptionMediaWholeKey); + info.mediaLeaf = boolFromDictionary(descDict, kDADiskDescriptionMediaLeafKey); + info.mediaWritable = boolFromDictionary(descDict, kDADiskDescriptionMediaWritableKey); + info.deviceGUID = stringFromDictionary(descDict, kDADiskDescriptionDeviceGUIDKey); + info.devicePath = stringFromDictionary(descDict, kDADiskDescriptionDevicePathKey); + info.deviceProtocol = stringFromDictionary(descDict, kDADiskDescriptionDeviceProtocolKey); + info.deviceModel = stringFromDictionary(descDict, kDADiskDescriptionDeviceModelKey); + info.busName = stringFromDictionary(descDict, kDADiskDescriptionBusNameKey); + info.busPath = stringFromDictionary(descDict, kDADiskDescriptionBusPathKey); + CFRelease(descDict); + // IOKit + io_service_t io = DADiskCopyIOMedia(disk); + info.ioSerial = serialNumberFromIOObject(io); + info.imagePath = imagePathFromIOObject(io); + CFMutableDictionaryRef ioDict = nullptr; + if (IORegistryEntryCreateCFProperties(io, &ioDict, kCFAllocatorDefault, 0) == kIOReturnSuccess) + { + // TODO: Pick out useful IOKit properties + CFRelease(ioDict); + } + IOObjectRelease(io); + // Guess wether this is an actual device + bool isCoreStorage = info.mediaPath.find(coreStorageMark) != std::string::npos; + bool isVirtual = info.deviceProtocol == kIOPropertyPhysicalInterconnectTypeVirtual; + info.isDevice = !isCoreStorage && !isVirtual; + return info; + } + + bool isDevice(DiskInformation const & di) + { + return di.isDevice; + } + + bool isWhole(DiskInformation const & di) + { + return di.mediaWhole; + } + + bool isRealDevice(DiskInformation const & di) + { + // Check if the MediaPath is in 'IODeviceTree:/', since this seems to + // work to reject APFS container. + static std::regex const devicePattern("^IODeviceTree:/.*$"); + return std::regex_match(di.mediaPath, devicePattern); + } + + std::string partitionSuffix(DiskInformation const & di) + { + if (!isWhole(di)) + { + size_t suffixStart = di.mediaBSDName.find_last_not_of("0123456789"); + if (suffixStart != std::string::npos && + suffixStart+1 < di.mediaBSDName.size() && + di.mediaBSDName[suffixStart] == 's') + { + return ':' + di.mediaBSDName.substr(suffixStart+1); + } + } + return std::string(); + } +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationUtils.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationUtils.hpp new file mode 100644 index 000000000000..3908824ec659 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskArbitrationUtils.hpp @@ -0,0 +1,61 @@ +// +// IDDiskArbitrationUtils.hpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_DISKARBITRATIONUTILS_HPP +#define ID_DISKARBITRATIONUTILS_HPP + +#include + +#include +#include + +namespace ID +{ + struct DiskInformation + { + std::string volumeKind; + std::string volumeUUID; + std::string volumeName; + std::string volumePath; + std::string mediaKind; + std::string mediaType; + std::string mediaUUID; + std::string mediaBSDName; + std::string mediaName; + std::string mediaPath; + std::string mediaContent; + bool isDevice; + bool mediaWhole; + bool mediaLeaf; + bool mediaWritable; + std::string deviceGUID; + std::string devicePath; + std::string deviceProtocol; + std::string deviceModel; + std::string busName; + std::string busPath; + std::string ioSerial; + std::string imagePath; + }; + + DiskInformation getDiskInformation(DADiskRef disk); + + bool isDevice(DiskInformation const & di); + bool isWhole(DiskInformation const & di); + bool isRealDevice(DiskInformation const & di); + std::string partitionSuffix(DiskInformation const & di); + + std::ostream & operator<<(std::ostream & os, DADiskRef disk); + std::ostream & operator<<(std::ostream & os, DiskInformation const & disk); +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskInfoLogger.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskInfoLogger.cpp new file mode 100644 index 000000000000..38aebcbfae4d --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskInfoLogger.cpp @@ -0,0 +1,47 @@ +// +// IDDiskInfoLogger.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDDiskInfoLogger.hpp" + +#include "IDDiskArbitrationUtils.hpp" + +#include + +#include + +namespace ID +{ + DiskInfoLogger::DiskInfoLogger(bool verbose, LogClient const & logger) : + DiskArbitrationHandler(logger), + m_verbose(verbose) + { + } + + void DiskInfoLogger::diskAppeared(DADiskRef /*disk*/, DiskInformation const & info) + { + if (m_verbose) + logger().logInfo("Disk Appeared: ", formatDisk(info)); + } + + void DiskInfoLogger::diskDisappeared(DADiskRef /*disk*/, DiskInformation const & info) + { + if (m_verbose) + logger().logInfo("Disk Disappeared: ", formatDisk(info)); + } + + std::string DiskInfoLogger::formatDisk(DiskInformation const & info) const + { + std::stringstream ss; + ss << info; + return ss.str(); + } +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskInfoLogger.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskInfoLogger.hpp new file mode 100644 index 000000000000..e1fe4655f020 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDiskInfoLogger.hpp @@ -0,0 +1,39 @@ +// +// IDDiskInfoLogger.hpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_DISKINFOLOGGER_HPP +#define ID_DISKINFOLOGGER_HPP + +#include "IDDiskArbitrationHandler.hpp" + +#include + +namespace ID +{ + class DiskInfoLogger : public DiskArbitrationHandler + { + public: + explicit DiskInfoLogger(bool verbose, LogClient const & logger); + + public: + virtual void diskAppeared(DADiskRef disk, DiskInformation const & info) override; + virtual void diskDisappeared(DADiskRef disk, DiskInformation const & info) override; + + private: + std::string formatDisk(DiskInformation const & info) const; + + private: + bool m_verbose; + }; +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDDispatchUtils.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDispatchUtils.cpp new file mode 100644 index 000000000000..f8d7314edbd0 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDispatchUtils.cpp @@ -0,0 +1,44 @@ +// +// IDDispatchUtils.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.12.13. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDDispatchUtils.hpp" + +namespace ID +{ + DispatchSource createSourceSignal(int sig, void * ctx, dispatch_function_t handler) + { + signal(sig, SIG_IGN); + dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, sig, 0, + DISPATCH_TARGET_QUEUE_DEFAULT); + dispatch_set_context(source, ctx); + dispatch_source_set_event_handler_f(source, handler); + dispatch_resume(source); + return DispatchSource(source); + } + + DispatchSource createSourceTimer(void * ctx, dispatch_function_t handler) + { + dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, + DISPATCH_TARGET_QUEUE_DEFAULT); + dispatch_set_context(source, ctx); + dispatch_source_set_event_handler_f(source, handler); + dispatch_resume(source); + return DispatchSource(source); + } + + void scheduleSingleshot(DispatchSource & timerSource, int64_t delayInNS) + { + dispatch_time_t start = dispatch_time(0, delayInNS); + // Schedule a single shot timer with a tolerance of 256ms + dispatch_source_set_timer(timerSource.get(), start, DISPATCH_TIME_FOREVER, 256000000); + } +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDDispatchUtils.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDispatchUtils.hpp new file mode 100644 index 000000000000..94f58bec0e36 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDDispatchUtils.hpp @@ -0,0 +1,41 @@ +// +// IDDispatchUtils.h +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.12.13. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_DISPATCHUTILS_HPP +#define ID_DISPATCHUTILS_HPP + +#include + +#include + +namespace ID +{ + struct DispatchDelete + { + void operator()(dispatch_source_s * source) + { + dispatch_source_set_event_handler_f(source, nullptr); + dispatch_release(source); + } + }; + + typedef std::unique_ptr DispatchSource; + + // Signal Source + DispatchSource createSourceSignal(int sig, void * ctx, dispatch_function_t handler); + + // Timer Source + DispatchSource createSourceTimer(void * ctx, dispatch_function_t handler); + void scheduleSingleshot(DispatchSource & timerSource, int64_t delayInNS); +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDException.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDException.cpp new file mode 100644 index 000000000000..e271282df5ec --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDException.cpp @@ -0,0 +1,17 @@ +// +// IDException.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDException.hpp" + +namespace ID +{ +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDException.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDException.hpp new file mode 100644 index 000000000000..1e0c8f9bf005 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDException.hpp @@ -0,0 +1,84 @@ +// +// IDException.hpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_EXCEPTION_HPP +#define ID_EXCEPTION_HPP + +#include +#include + +#include +#include + +namespace ID +{ + /*! + \brief Base class for all Seal exceptions + */ + class Exception : public std::runtime_error + { + public: + using std::runtime_error::runtime_error; + }; + + /*! + \brief Helper class for scope bounded exception throwing + */ + template + class Throw + { + public: + ~Throw() noexcept(false) + { + throw E(m_ss.str()); + } + + public: + /*! + Forward everything to the embedded stringstream + */ + template + Throw & operator<<(T && t) + { + m_ss << std::forward(t); + return *this; + } + + /*! + For IO Manipulators + */ + Throw & operator<<(std::ostream & (*m)(std::ostream&)) + { + m_ss << m; + return *this; + } + + public: + /*! + Format Core Foundation errors + */ + Throw & operator<<(CFErrorRef error) + { + CFStringRef es = CFErrorCopyDescription(error); + char const * esp = CFStringGetCStringPtr(es, kCFStringEncodingUTF8); + if (esp) + *this << esp; + CFRelease(es); + return *this; + } + + private: + std::stringstream m_ss; + }; +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDFileUtils.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDFileUtils.hpp new file mode 100644 index 000000000000..1670b2f5d64c --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDFileUtils.hpp @@ -0,0 +1,27 @@ +// +// IDFileUtils.hpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_FILEUTILS_HPP +#define ID_FILEUTILS_HPP + +#include + +namespace ID +{ + void createPath(std::string const & path); + void createCleanPath(std::string const & path); + void createFile(std::string const & path); + void createSymlink(std::string const & link, std::string const & target); + void removeFSObject(std::string const & path); +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDFileUtils.mm b/cmd/os/macos/InvariantDisks/InvariantDisks/IDFileUtils.mm new file mode 100644 index 000000000000..410555fed6e3 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDFileUtils.mm @@ -0,0 +1,92 @@ +// +// IDFileUtils.mm +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDFileUtils.hpp" + +#include "IDException.hpp" + +#import +#import +#import + +namespace ID +{ + void createPath(std::string const & path) + { + NSError * error = nullptr; + NSFileManager * manager = [NSFileManager defaultManager]; + BOOL success = [manager createDirectoryAtPath:[NSString stringWithUTF8String:path.c_str()] + withIntermediateDirectories:YES attributes:nullptr error:&error]; + if (!success) + { + Throw e; + e << "Error creating directory " << path << ": " << [[error description] UTF8String]; + } + } + + void createCleanPath(std::string const & path) + { + removeFSObject(path); + createPath(path); + } + + void createFile(std::string const & path) + { + if (path.empty()) + throw Exception("Can not create file with empty path"); + removeFSObject(path); + NSError * error = nullptr; + NSData * empty = [NSData data]; + BOOL success = [empty writeToFile:[NSString stringWithUTF8String:path.c_str()] + options:0 error:&error]; + if (!success) + { + Throw e; + e << "Error creating file " << path << ": " << [[error description] UTF8String]; + } + } + + void createSymlink(std::string const & link, std::string const & target) + { + if (link.empty() || target.empty()) + throw Exception("Can not create symlink with empty path"); + removeFSObject(link); + NSError * error = nullptr; + NSFileManager * manager = [NSFileManager defaultManager]; + BOOL success = [manager createSymbolicLinkAtPath:[NSString stringWithUTF8String:link.c_str()] + withDestinationPath:[NSString stringWithUTF8String:target.c_str()] + error:&error]; + if (!success) + { + Throw e; + e << "Error creating symlink " << link << " pointing to " << target << ": " + << [[error description] UTF8String]; + } + } + + void removeFSObject(std::string const & path) + { + if (path.empty()) + throw Exception("Can not remove file system object with empty path"); + NSError * error = nullptr; + NSFileManager * manager = [NSFileManager defaultManager]; + BOOL success = [manager removeItemAtPath:[NSString stringWithUTF8String:path.c_str()] error:&error]; + if (!success && error.domain != NSCocoaErrorDomain && error.code != 4) + { + NSError * underlying = error.userInfo[NSUnderlyingErrorKey]; + if (underlying && underlying.domain == NSPOSIXErrorDomain && underlying.code == ENOENT) + return; // Ignore non-existing files + Throw e; + e << "Error removing file system object " << path << ": " << [[error description] UTF8String]; + } + } +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDImagePathLinker.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDImagePathLinker.cpp new file mode 100644 index 000000000000..16938e42976c --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDImagePathLinker.cpp @@ -0,0 +1,39 @@ +// +// IDImagePathLinker.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDImagePathLinker.hpp" + +#include "IDDiskArbitrationUtils.hpp" + +namespace ID +{ + ImagePathLinker::ImagePathLinker(std::string const & base, LogClient const & logger) : + BaseLinker(base, logger) + { + } + + static std::string formatImagePath(DiskInformation const & di) + { + std::string filteredPath = di.imagePath + partitionSuffix(di); + std::replace(filteredPath.begin(), filteredPath.end(), '/', '-'); + return filteredPath; + } + + void ImagePathLinker::diskAppeared(DADiskRef disk, DiskInformation const & di) + { + if (!di.imagePath.empty() && !di.mediaBSDName.empty()) + { + std::string imagePath = formatImagePath(di); + addLinkForDisk(base() + "/" + imagePath, di); + } + } +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDImagePathLinker.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDImagePathLinker.hpp new file mode 100644 index 000000000000..ff81b63d79a1 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDImagePathLinker.hpp @@ -0,0 +1,30 @@ +// +// IDImagePathLinker.hpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_IMAGEPATHLINKER_HPP +#define ID_IMAGEPATHLINKER_HPP + +#include "IDBaseLinker.hpp" + +namespace ID +{ + class ImagePathLinker : public BaseLinker + { + public: + explicit ImagePathLinker(std::string const & base, LogClient const & logger); + + public: + virtual void diskAppeared(DADiskRef disk, DiskInformation const & info) override; + }; +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDLogUtils.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDLogUtils.cpp new file mode 100644 index 000000000000..bbdc56af9b05 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDLogUtils.cpp @@ -0,0 +1,181 @@ +// +// IDASLUtils.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2015.08.01. +// Copyright (c) 2015 the-color-black.net. All rights reserved. +// + +#include "IDLogUtils.hpp" + +#include +#include + +#include +#include + +// Auto Detect the use of ASL or OS Log if it is not already enforced by the +// build environment +#if !(defined(ID_USE_ASL) || defined(ID_USE_OS_LOG)) + #include + #include + #if defined (MAC_OS_X_VERSION_10_12) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) + #define ID_USE_OS_LOG + #else + #define ID_USE_ASL + #endif +#endif + +#if defined(ID_USE_ASL) +// Uses ASL: +// https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man3/asl.3.html +#include +#elif defined(ID_USE_OS_LOG) +// Uses the new OS Log facilities: +// https://developer.apple.com/reference/os/logging +#include +#else +#error "Neither ID_USE_ASL nor ID_USE_OS_LOG have been defined" +#endif + +static char const * const logFacility = "net.the-color-black"; +static char const * const logCategory = "InvariantDisks"; + +namespace ID +{ +#if defined(ID_USE_ASL) + + class LogClient::Impl + { + public: + explicit Impl() : + client(asl_open(logCategory, logFacility, ASL_OPT_STDERR)) + { + } + + ~Impl() + { + asl_close(client); + for (auto fd: fds) + close(fd); + } + + public: + Impl(Impl const &) = delete; + Impl & operator=(Impl const &) = delete; + + public: + int addLogFile(char const * logFile) + { + int fd = open(logFile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) + { + asl_log(client, 0, ASL_LEVEL_ERR, "Error opening log file \"%s\" (%m)", logFile); + return -1; + } + int r = asl_add_log_file(client, fd); + if (r != 0) + { + asl_log(client, 0, ASL_LEVEL_ERR, "Error registering file \"%s\" (%d)", logFile, r); + close(fd); + return -1; + } + fds.push_back(fd); + return fd; + } + + void removeLogFile(int fd) + { + auto found = std::find(fds.begin(), fds.end(), fd); + if (found != fds.end()) + { + std::swap(*found, fds.back()); + fds.pop_back(); + asl_remove_log_file(client, fd); + close(fd); + } + } + + public: + aslclient client; + std::vector fds; + }; + + void LogClient::logInfo(std::string const & msg) const + { + asl_log(m_impl->client, 0, ASL_LEVEL_INFO, "%s", msg.c_str()); + } + + void LogClient::logDefault(std::string const & msg) const + { + asl_log(m_impl->client, 0, ASL_LEVEL_NOTICE, "%s", msg.c_str()); + } + + void LogClient::logError(std::string const & msg) const + { + asl_log(m_impl->client, 0, ASL_LEVEL_ERR, "%s", msg.c_str()); + } + +#elif defined(ID_USE_OS_LOG) + + class LogClient::Impl + { + public: + Impl() : + client(os_log_create(logFacility, logCategory)) + { + } + + ~Impl() + { + // Should release client from os_log_create, but no function for + // this is documented or defined. + } + + public: + Impl(Impl const &) = delete; + Impl & operator=(Impl const &) = delete; + + public: + int addLogFile(char const *) + { + os_log_with_type(client, OS_LOG_TYPE_DEFAULT, + "Log Files are no longer supported with os_log, use the logging subsystem instead"); + return 0; + } + + void removeLogFile(int) + { + } + + public: + os_log_t client; + }; + + void LogClient::logInfo(std::string const & msg) const + { + os_log_with_type(m_impl->client, OS_LOG_TYPE_INFO, "%{public}s", msg.c_str()); + } + + void LogClient::logDefault(std::string const & msg) const + { + os_log_with_type(m_impl->client, OS_LOG_TYPE_DEFAULT, "%{public}s", msg.c_str()); + } + + void LogClient::logError(std::string const & msg) const + { + os_log_with_type(m_impl->client, OS_LOG_TYPE_ERROR, "%{public}s", msg.c_str()); + } + +#endif + + LogClient::LogClient() : + m_impl(std::make_shared()) + { + } + + int LogClient::addLogFile(char const * logFile) + { + return m_impl->addLogFile(logFile); + } +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDLogUtils.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDLogUtils.hpp new file mode 100644 index 000000000000..6caa386d59db --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDLogUtils.hpp @@ -0,0 +1,78 @@ +// +// IDASLUtils.h +// InvariantDisks +// +// Created by Gerhard Röthlin on 2015.08.01. +// Copyright (c) 2015 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_LOGUTILS_HPP +#define ID_LOGUTILS_HPP + +#include +#include + +namespace ID +{ + class LogClient + { + public: + explicit LogClient(); + + public: + int addLogFile(char const * logFile); + + public: + template + void logInfo(ARGS const & ... args) const; + + template + void logDefault(ARGS const & ... args) const; + + template + void logError(ARGS const & ... args) const; + + public: + void logInfo(std::string const & msg) const; + void logDefault(std::string const & msg) const; + void logError(std::string const & msg) const; + + private: + class Impl; + std::shared_ptr m_impl; + }; + + // Private Implementation + template + void LogClient::logInfo(ARGS const & ... args) const + { + std::stringstream ss; + int ignored[] = {(ss << args, 0)...}; + (void)ignored; + logInfo(ss.str()); + } + + template + void LogClient::logDefault(ARGS const & ... args) const + { + std::stringstream ss; + int ignored[] = {(ss << args, 0)...}; + (void)ignored; + logDefault(ss.str()); + } + + template + void LogClient::logError(ARGS const & ... args) const + { + std::stringstream ss; + int ignored[] = {(ss << args, 0)...}; + (void)ignored; + logError(ss.str()); + } +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDMediaPathLinker.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDMediaPathLinker.cpp new file mode 100644 index 000000000000..e58dc6b9aea3 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDMediaPathLinker.cpp @@ -0,0 +1,43 @@ +// +// IDMediaPathLinker.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDMediaPathLinker.hpp" + +#include "IDDiskArbitrationUtils.hpp" + +namespace ID +{ + MediaPathLinker::MediaPathLinker(std::string const & base, LogClient const & logger) : + BaseLinker(base, logger) + { + } + + static std::string prefixDevice = "IODeviceTree:/"; + + static std::string filterMediaPath(std::string const & mediaPath) + { + if (mediaPath.compare(0, prefixDevice.size(), prefixDevice) != 0) + return std::string(); + std::string filteredPath = mediaPath.substr(prefixDevice.size()); + std::replace(filteredPath.begin(), filteredPath.end(), '/', '-'); + return filteredPath; + } + + void MediaPathLinker::diskAppeared(DADiskRef disk, DiskInformation const & di) + { + std::string mediaPath = filterMediaPath(di.mediaPath); + if (!mediaPath.empty() && !di.mediaBSDName.empty()) + { + addLinkForDisk(base() + "/" + mediaPath, di); + } + } +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDMediaPathLinker.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDMediaPathLinker.hpp new file mode 100644 index 000000000000..b7bf2f06a716 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDMediaPathLinker.hpp @@ -0,0 +1,30 @@ +// +// IDMediaPathLinker.hpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_MEDIAPATHLINKER_HPP +#define ID_MEDIAPATHLINKER_HPP + +#include "IDBaseLinker.hpp" + +namespace ID +{ + class MediaPathLinker : public BaseLinker + { + public: + explicit MediaPathLinker(std::string const & base, LogClient const & logger); + + public: + virtual void diskAppeared(DADiskRef disk, DiskInformation const & info) override; + }; +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDSerialLinker.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDSerialLinker.cpp new file mode 100644 index 000000000000..ec3a98e67c01 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDSerialLinker.cpp @@ -0,0 +1,80 @@ +// +// IDSerialLinker.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.05.03. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDSerialLinker.hpp" + +#include "IDDiskArbitrationUtils.hpp" + +#include +#include + +namespace ID +{ + SerialLinker::SerialLinker(std::string base, LogClient const & logger) : + BaseLinker(std::move(base), logger) + { + } + + static bool isInvalidSerialChar(char c) + { + if (isalnum(c)) + return false; + if (c == '-' || c == '_') + return false; + return true; + } + + std::string trim(std::string const & s) + { + size_t first = s.find_first_not_of(' '); + size_t last = s.find_last_not_of(' '); + if (first != std::string::npos) + return s.substr(first, last - first + 1); + return s; + } + + std::string formatSerial(DiskInformation const & di) + { + std::string model = trim(di.deviceModel); + std::string serial = trim(di.ioSerial); + std::string formated; + if (!serial.empty()) + { + if (model.empty()) + formated = serial; + else + formated = model + "-" + serial; + } + std::replace(formated.begin(), formated.end(), ' ', '_'); + formated.erase(std::remove_if(formated.begin(), formated.end(), isInvalidSerialChar), formated.end()); + if (!formated.empty()) + formated += partitionSuffix(di); + return formated; + } + + std::string SerialLinker::formatSerialPath(DiskInformation const & di) const + { + std::string serial = formatSerial(di); + if (!serial.empty()) + serial = base() + "/" + serial; + return serial; + } + + void SerialLinker::diskAppeared(DADiskRef disk, DiskInformation const & di) + { + if (isDevice(di) && isRealDevice(di)) + { + addLinkForDisk(formatSerialPath(di), di); + } + } + +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDSerialLinker.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDSerialLinker.hpp new file mode 100644 index 000000000000..f4d1a5f3554a --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDSerialLinker.hpp @@ -0,0 +1,33 @@ +// +// IDSerialLinker.hpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.05.03. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_SERIALLINKER_HPP +#define ID_SERIALLINKER_HPP + +#include "IDBaseLinker.hpp" + +namespace ID +{ + class SerialLinker : public BaseLinker + { + public: + explicit SerialLinker(std::string base, LogClient const & logger); + + public: + virtual void diskAppeared(DADiskRef disk, DiskInformation const & info) override; + + private: + std::string formatSerialPath(DiskInformation const & di) const; + }; +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDSymlinkHandle.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDSymlinkHandle.cpp new file mode 100644 index 000000000000..7e7d04253a05 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDSymlinkHandle.cpp @@ -0,0 +1,75 @@ +// +// IDSymlinkHandle.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2015.10.25. +// Copyright (c) 2015 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDSymlinkHandle.hpp" + +#include "IDFileUtils.hpp" +#include "IDLogUtils.hpp" + +namespace ID +{ + SymlinkHandle::SymlinkHandle() + { + } + + SymlinkHandle::SymlinkHandle(std::string const & link, std::string const & target) : + m_link(link), m_target(target) + { + createSymlink(link, target); + } + + SymlinkHandle::~SymlinkHandle() + { + try + { + reset(); + } + catch (std::exception const & e) + { + // Swallow exceptions during destruction + } + } + + SymlinkHandle::SymlinkHandle(SymlinkHandle && other) noexcept : + m_link(std::move(other.m_link)), m_target(std::move(other.m_target)) + { + other.m_link.clear(); + other.m_target.clear(); + } + + SymlinkHandle & SymlinkHandle::operator=(SymlinkHandle && other) + { + swap(m_link, other.m_link); + other.reset(); + return *this; + } + + void SymlinkHandle::reset() + { + if (!m_link.empty()) + { + removeFSObject(m_link); + m_link.clear(); + m_target.clear(); + } + } + + std::string const & SymlinkHandle::link() const + { + return m_link; + } + + std::string const & SymlinkHandle::target() const + { + return m_target; + } +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDSymlinkHandle.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDSymlinkHandle.hpp new file mode 100644 index 000000000000..9909d700f467 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDSymlinkHandle.hpp @@ -0,0 +1,53 @@ +// +// IDSymlinkHandle.hpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2015.10.25. +// Copyright (c) 2015 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_SYMLINK_HANDLE_HPP +#define ID_SYMLINK_HANDLE_HPP + +#include + +namespace ID +{ + /*! + \brief A class representing a symlink in the file system + + This class represents a symlink in the filesystem that only exists as long as its corresponding + instance exists. + */ + class SymlinkHandle + { + public: + SymlinkHandle(); + explicit SymlinkHandle(std::string const & link, std::string const & target); + ~SymlinkHandle(); + + public: + SymlinkHandle(SymlinkHandle && other) noexcept; + SymlinkHandle & operator=(SymlinkHandle && other); + + public: + /*! + Resets the instance so that it represents no symlink. + */ + void reset(); + + public: + std::string const & link() const; + std::string const & target() const; + + private: + std::string m_link; + std::string m_target; + }; +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDUUIDLinker.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDUUIDLinker.cpp new file mode 100644 index 000000000000..33f37758496f --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDUUIDLinker.cpp @@ -0,0 +1,46 @@ +// +// IDUUIDLinker.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.05.03. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDUUIDLinker.hpp" + +#include "IDDiskArbitrationUtils.hpp" + +#include + +namespace ID +{ + UUIDLinker::UUIDLinker(std::string const & base, LogClient const & logger) : + BaseLinker(base, logger) + { + } + + static std::vector getUUIDs(DiskInformation const & diskInfo) + { + std::vector uuids; + if (!diskInfo.volumeUUID.empty()) + uuids.push_back("volume-" + diskInfo.volumeUUID); + if (!diskInfo.mediaUUID.empty()) + uuids.push_back("media-" + diskInfo.mediaUUID); + if (!diskInfo.deviceGUID.empty()) + uuids.push_back("device-" + diskInfo.deviceGUID); + return uuids; + } + + void UUIDLinker::diskAppeared(DADiskRef disk, DiskInformation const & di) + { + auto mediaUUIDs = getUUIDs(di); + for (auto mediaID: mediaUUIDs) + { + addLinkForDisk(base() + "/" + mediaID, di); + } + } +} diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/IDUUIDLinker.hpp b/cmd/os/macos/InvariantDisks/InvariantDisks/IDUUIDLinker.hpp new file mode 100644 index 000000000000..16b65f02e4ed --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/IDUUIDLinker.hpp @@ -0,0 +1,30 @@ +// +// IDUUIDLinker.hpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.05.03. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#ifndef ID_UUIDLINKER_HPP +#define ID_UUIDLINKER_HPP + +#include "IDBaseLinker.hpp" + +namespace ID +{ + class UUIDLinker : public BaseLinker + { + public: + explicit UUIDLinker(std::string const & base, LogClient const & logger); + + public: + virtual void diskAppeared(DADiskRef disk, DiskInformation const & info) override; + }; +} + +#endif diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/Makefile.am b/cmd/os/macos/InvariantDisks/InvariantDisks/Makefile.am new file mode 100644 index 000000000000..58e3bd9e5e7e --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/Makefile.am @@ -0,0 +1,43 @@ +sbin_PROGRAMS += InvariantDisks +CPPCHECKTARGETS += InvariantDisks + +AM_LIBTOOLFLAGS += --tag=CXX + +InvariantDisks_SOURCES = \ + %D%/IDLogUtils.cpp \ + %D%/IDLogUtils.hpp \ + %D%/IDBaseLinker.cpp \ + %D%/IDBaseLinker.hpp \ + %D%/IDCLI.cpp \ + %D%/IDCLI.hpp \ + %D%/IDDAHandlerIdle.cpp \ + %D%/IDDAHandlerIdle.hpp \ + %D%/IDDiskArbitrationDispatcher.cpp \ + %D%/IDDiskArbitrationDispatcher.hpp \ + %D%/IDDiskArbitrationHandler.hpp \ + %D%/IDDiskArbitrationUtils.cpp \ + %D%/IDDiskArbitrationUtils.hpp \ + %D%/IDDiskInfoLogger.cpp \ + %D%/IDDiskInfoLogger.hpp \ + %D%/IDDispatchUtils.cpp \ + %D%/IDDispatchUtils.hpp \ + %D%/IDException.cpp \ + %D%/IDException.hpp \ + %D%/IDFileUtils.mm \ + %D%/IDFileUtils.hpp \ + %D%/IDImagePathLinker.cpp \ + %D%/IDImagePathLinker.hpp \ + %D%/IDMediaPathLinker.cpp \ + %D%/IDMediaPathLinker.hpp \ + %D%/IDSerialLinker.cpp \ + %D%/IDSerialLinker.hpp \ + %D%/IDSymlinkHandle.cpp \ + %D%/IDSymlinkHandle.hpp \ + %D%/IDUUIDLinker.cpp \ + %D%/IDUUIDLinker.hpp \ + %D%/git-version.h \ + %D%/main.cpp + +InvariantDisks_CXXFLAGS = -std=c++11 -stdlib=libc++ +InvariantDisks_OBJCXXFLAGS = -std=c++11 -stdlib=libc++ +InvariantDisks_LDFLAGS = -static -framework IOKit -framework DiskArbitration -framework CoreFoundation -framework Foundation diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/git-version.h b/cmd/os/macos/InvariantDisks/InvariantDisks/git-version.h new file mode 100644 index 000000000000..f82f7d375d3b --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/git-version.h @@ -0,0 +1,3 @@ + +#include +#define GIT_VERSION ZFS_META_GITREV diff --git a/cmd/os/macos/InvariantDisks/InvariantDisks/main.cpp b/cmd/os/macos/InvariantDisks/InvariantDisks/main.cpp new file mode 100644 index 000000000000..447a9bf3ec8c --- /dev/null +++ b/cmd/os/macos/InvariantDisks/InvariantDisks/main.cpp @@ -0,0 +1,40 @@ +// +// main.cpp +// InvariantDisks +// +// Created by Gerhard Röthlin on 2014.04.27. +// Copyright (c) 2014 the-color-black.net. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. +// Additional licensing options are described in the README file. +// + +#include "IDCLI.hpp" +#include "IDException.hpp" +#include "IDLogUtils.hpp" + +#include + +int main(int argc, char ** argv) +{ + ID::LogClient logger; + try + { + ID::CLI idCommandLine(argc, argv, logger); + return idCommandLine.exec(); + } + catch (ID::Exception const & e) + { + logger.logError(e.what()); + } + catch (std::exception const & e) + { + logger.logError("Terminated by exception: ", e.what()); + } + catch (...) + { + logger.logError("Terminated by unknown exception"); + } + return -1; +} diff --git a/cmd/os/macos/InvariantDisks/Makefile.am b/cmd/os/macos/InvariantDisks/Makefile.am new file mode 100644 index 000000000000..3aaae7a08dd5 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/Makefile.am @@ -0,0 +1 @@ +include $(srcdir)/%D%/InvariantDisks/Makefile.am diff --git a/cmd/os/macos/InvariantDisks/OPENSOLARIS.LICENSE.txt b/cmd/os/macos/InvariantDisks/OPENSOLARIS.LICENSE.txt new file mode 100644 index 000000000000..da23621dc843 --- /dev/null +++ b/cmd/os/macos/InvariantDisks/OPENSOLARIS.LICENSE.txt @@ -0,0 +1,384 @@ +Unless otherwise noted, all files in this distribution are released +under the Common Development and Distribution License (CDDL). +Exceptions are noted within the associated source files. + +-------------------------------------------------------------------- + + +COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0 + +1. Definitions. + + 1.1. "Contributor" means each individual or entity that creates + or contributes to the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Software, prior Modifications used by a Contributor (if any), + and the Modifications made by that particular Contributor. + + 1.3. "Covered Software" means (a) the Original Software, or (b) + Modifications, or (c) the combination of files containing + Original Software with files containing Modifications, in + each case including portions thereof. + + 1.4. "Executable" means the Covered Software in any form other + than Source Code. + + 1.5. "Initial Developer" means the individual or entity that first + makes Original Software available under this License. + + 1.6. "Larger Work" means a work which combines Covered Software or + portions thereof with code not governed by the terms of this + License. + + 1.7. "License" means this document. + + 1.8. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed + herein. + + 1.9. "Modifications" means the Source Code and Executable form of + any of the following: + + A. Any file that results from an addition to, deletion from or + modification of the contents of a file containing Original + Software or previous Modifications; + + B. Any new file that contains any part of the Original + Software or previous Modifications; or + + C. Any new file that is contributed or otherwise made + available under the terms of this License. + + 1.10. "Original Software" means the Source Code and Executable + form of computer software code that is originally released + under this License. + + 1.11. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, + process, and apparatus claims, in any patent Licensable by + grantor. + + 1.12. "Source Code" means (a) the common form of computer software + code in which modifications are made and (b) associated + documentation included in or with such code. + + 1.13. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms + of, this License. For legal entities, "You" includes any + entity which controls, is controlled by, or is under common + control with You. For purposes of this definition, + "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty + percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants. + + 2.1. The Initial Developer Grant. + + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, the Initial + Developer hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer, to use, + reproduce, modify, display, perform, sublicense and + distribute the Original Software (or portions thereof), + with or without Modifications, and/or as part of a Larger + Work; and + + (b) under Patent Claims infringed by the making, using or + selling of Original Software, to make, have made, use, + practice, sell, and offer for sale, and/or otherwise + dispose of the Original Software (or portions thereof). + + (c) The licenses granted in Sections 2.1(a) and (b) are + effective on the date Initial Developer first distributes + or otherwise makes the Original Software available to a + third party under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: (1) for code that You delete from the Original + Software, or (2) for infringements caused by: (i) the + modification of the Original Software, or (ii) the + combination of the Original Software with other software + or devices. + + 2.2. Contributor Grant. + + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, each + Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor to use, reproduce, + modify, display, perform, sublicense and distribute the + Modifications created by such Contributor (or portions + thereof), either on an unmodified basis, with other + Modifications, as Covered Software and/or as part of a + Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either + alone and/or in combination with its Contributor Version + (or portions of such combination), to make, use, sell, + offer for sale, have made, and/or otherwise dispose of: + (1) Modifications made by that Contributor (or portions + thereof); and (2) the combination of Modifications made by + that Contributor with its Contributor Version (or portions + of such combination). + + (c) The licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first distributes or + otherwise makes the Modifications available to a third + party. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: (1) for any code that Contributor has deleted + from the Contributor Version; (2) for infringements caused + by: (i) third party modifications of Contributor Version, + or (ii) the combination of Modifications made by that + Contributor with other software (except as part of the + Contributor Version) or other devices; or (3) under Patent + Claims infringed by Covered Software in the absence of + Modifications made by that Contributor. + +3. Distribution Obligations. + + 3.1. Availability of Source Code. + + Any Covered Software that You distribute or otherwise make + available in Executable form must also be made available in Source + Code form and that Source Code form must be distributed only under + the terms of this License. You must include a copy of this + License with every copy of the Source Code form of the Covered + Software You distribute or otherwise make available. You must + inform recipients of any such Covered Software in Executable form + as to how they can obtain such Covered Software in Source Code + form in a reasonable manner on or through a medium customarily + used for software exchange. + + 3.2. Modifications. + + The Modifications that You create or to which You contribute are + governed by the terms of this License. You represent that You + believe Your Modifications are Your original creation(s) and/or + You have sufficient rights to grant the rights conveyed by this + License. + + 3.3. Required Notices. + + You must include a notice in each of Your Modifications that + identifies You as the Contributor of the Modification. You may + not remove or alter any copyright, patent or trademark notices + contained within the Covered Software, or any notices of licensing + or any descriptive text giving attribution to any Contributor or + the Initial Developer. + + 3.4. Application of Additional Terms. + + You may not offer or impose any terms on any Covered Software in + Source Code form that alters or restricts the applicable version + of this License or the recipients' rights hereunder. You may + choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of + Covered Software. However, you may do so only on Your own behalf, + and not on behalf of the Initial Developer or any Contributor. + You must make it absolutely clear that any such warranty, support, + indemnity or liability obligation is offered by You alone, and You + hereby agree to indemnify the Initial Developer and every + Contributor for any liability incurred by the Initial Developer or + such Contributor as a result of warranty, support, indemnity or + liability terms You offer. + + 3.5. Distribution of Executable Versions. + + You may distribute the Executable form of the Covered Software + under the terms of this License or under the terms of a license of + Your choice, which may contain terms different from this License, + provided that You are in compliance with the terms of this License + and that the license for the Executable form does not attempt to + limit or alter the recipient's rights in the Source Code form from + the rights set forth in this License. If You distribute the + Covered Software in Executable form under a different license, You + must make it absolutely clear that any terms which differ from + this License are offered by You alone, not by the Initial + Developer or Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred + by the Initial Developer or such Contributor as a result of any + such terms You offer. + + 3.6. Larger Works. + + You may create a Larger Work by combining Covered Software with + other code not governed by the terms of this License and + distribute the Larger Work as a single product. In such a case, + You must make sure the requirements of this License are fulfilled + for the Covered Software. + +4. Versions of the License. + + 4.1. New Versions. + + Sun Microsystems, Inc. is the initial license steward and may + publish revised and/or new versions of this License from time to + time. Each version will be given a distinguishing version number. + Except as provided in Section 4.3, no one other than the license + steward has the right to modify this License. + + 4.2. Effect of New Versions. + + You may always continue to use, distribute or otherwise make the + Covered Software available under the terms of the version of the + License under which You originally received the Covered Software. + If the Initial Developer includes a notice in the Original + Software prohibiting it from being distributed or otherwise made + available under any subsequent version of the License, You must + distribute and make the Covered Software available under the terms + of the version of the License under which You originally received + the Covered Software. Otherwise, You may also choose to use, + distribute or otherwise make the Covered Software available under + the terms of any subsequent version of the License published by + the license steward. + + 4.3. Modified Versions. + + When You are an Initial Developer and You want to create a new + license for Your Original Software, You may create and use a + modified version of this License if You: (a) rename the license + and remove any references to the name of the license steward + (except to note that the license differs from this License); and + (b) otherwise make it clear that the license contains terms which + differ from this License. + +5. DISCLAIMER OF WARRANTY. + + COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" + BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, + INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED + SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR + PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND + PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY + COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE + INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY + NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF + WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS + DISCLAIMER. + +6. TERMINATION. + + 6.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to + cure such breach within 30 days of becoming aware of the breach. + Provisions which, by their nature, must remain in effect beyond + the termination of this License shall survive. + + 6.2. If You assert a patent infringement claim (excluding + declaratory judgment actions) against Initial Developer or a + Contributor (the Initial Developer or Contributor against whom You + assert such claim is referred to as "Participant") alleging that + the Participant Software (meaning the Contributor Version where + the Participant is a Contributor or the Original Software where + the Participant is the Initial Developer) directly or indirectly + infringes any patent, then any and all rights granted directly or + indirectly to You by such Participant, the Initial Developer (if + the Initial Developer is not the Participant) and all Contributors + under Sections 2.1 and/or 2.2 of this License shall, upon 60 days + notice from Participant terminate prospectively and automatically + at the expiration of such 60 day notice period, unless if within + such 60 day period You withdraw Your claim with respect to the + Participant Software against such Participant either unilaterally + or pursuant to a written agreement with Participant. + + 6.3. In the event of termination under Sections 6.1 or 6.2 above, + all end user licenses that have been validly granted by You or any + distributor hereunder prior to termination (excluding licenses + granted to You by any distributor) shall survive termination. + +7. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE + INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF + COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE + LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT + LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK + STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL + INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT + APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO + NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR + CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT + APPLY TO YOU. + +8. U.S. GOVERNMENT END USERS. + + The Covered Software is a "commercial item," as that term is + defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial + computer software" (as that term is defined at 48 + C.F.R. 252.227-7014(a)(1)) and "commercial computer software + documentation" as such terms are used in 48 C.F.R. 12.212 + (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 + C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all + U.S. Government End Users acquire Covered Software with only those + rights set forth herein. This U.S. Government Rights clause is in + lieu of, and supersedes, any other FAR, DFAR, or other clause or + provision that addresses Government rights in computer software + under this License. + +9. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed + by the law of the jurisdiction specified in a notice contained + within the Original Software (except to the extent applicable law, + if any, provides otherwise), excluding such jurisdiction's + conflict-of-law provisions. Any litigation relating to this + License shall be subject to the jurisdiction of the courts located + in the jurisdiction and venue specified in a notice contained + within the Original Software, with the losing party responsible + for costs, including, without limitation, court costs and + reasonable attorneys' fees and expenses. The application of the + United Nations Convention on Contracts for the International Sale + of Goods is expressly excluded. Any law or regulation which + provides that the language of a contract shall be construed + against the drafter shall not apply to this License. You agree + that You alone are responsible for compliance with the United + States export administration regulations (and the export control + laws and regulation of any other countries) when You use, + distribute or otherwise make available any Covered Software. + +10. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or + indirectly, out of its utilization of rights under this License + and You agree to work with Initial Developer and Contributors to + distribute such responsibility on an equitable basis. Nothing + herein is intended or shall be deemed to constitute any admission + of liability. + +-------------------------------------------------------------------- + +NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND +DISTRIBUTION LICENSE (CDDL) + +For Covered Software in this distribution, this License shall +be governed by the laws of the State of California (excluding +conflict-of-law provisions). + +Any litigation relating to this License shall be subject to the +jurisdiction of the Federal Courts of the Northern District of +California and the state courts of the State of California, with +venue lying in Santa Clara County, California. diff --git a/cmd/os/macos/InvariantDisks/README.md b/cmd/os/macos/InvariantDisks/README.md new file mode 100644 index 000000000000..7a3c1b59f10f --- /dev/null +++ b/cmd/os/macos/InvariantDisks/README.md @@ -0,0 +1,36 @@ +InvariantDisks +============== + +InvariantDisks is a small program maintaining a mapping from invariant labels to the potentially +varying /dev/diskXsY entries. It is started like: + +`InvariantDisks -p $PREFIX` + +At the moment, three linker modules are available. The media path module creates links in +$prefix/by-path based on the reported media path, based on the physical location of the +device. Example: + + * `./by-path/PCI0@0-SATA@1F,2-PRT0@0-PMP@0-@0:0` + +The UUID module creates links in $prefix/by-id based on the volume and media UUID. Some volumes +have both a volume and a media UUID, others only one of the two. This depends on the partitioning +scheme and the filesystem. Example: + + * `./by-id/volume-AAFA3521-C1B4-3154-B16E-143D133FA21A` + * `./by-id/media-9722675C-36BB-499D-8609-120D6BDF8609` + +The Serial number module creates links to whole drives based on the product type and product +serial number. + + * `./by-serial/WDC_WD30EZRX-00MMMB-WD-WCAWZ12345` + +The Problem and some solutions on Linux are described on +http://zfsonlinux.org/faq.html#WhatDevNamesShouldIUseWhenCreatingMyPool + +License +======= + +This program is copyrighted by me, because I wrote it. +This program is licensed under the "3-clause BSD" License. See the BSD.LICENSE.md file for details. +If desired, it is also licensed under the CDDL OpenSolaris license, see OPENSOLARIS.LICENSE.txt +for details. Other licenses are available on request. diff --git a/cmd/os/macos/InvariantDisks/launchd/net.the-color-black.InvariantDisks.plist b/cmd/os/macos/InvariantDisks/launchd/net.the-color-black.InvariantDisks.plist new file mode 100644 index 000000000000..71ffccc855fd --- /dev/null +++ b/cmd/os/macos/InvariantDisks/launchd/net.the-color-black.InvariantDisks.plist @@ -0,0 +1,16 @@ + + + + + Label + net.the-color-black.InvariantDisks + ProgramArguments + + /usr/local/bin/InvariantDisks + -p + /var/run/disk + + RunAtLoad + + + diff --git a/cmd/os/macos/Makefile.am b/cmd/os/macos/Makefile.am new file mode 100644 index 000000000000..3e950ba296eb --- /dev/null +++ b/cmd/os/macos/Makefile.am @@ -0,0 +1,8 @@ +FS_BUNDLEPREFIX = /Library/Filesystems +FS_BUNDLEDIR = $(DESTDIR)$(FS_BUNDLEPREFIX)/zfs.fs + +include $(srcdir)/%D%/InvariantDisks/Makefile.am +include $(srcdir)/%D%/zconfigd/Makefile.am +include $(srcdir)/%D%/zfs_util/Makefile.am +include $(srcdir)/%D%/zsysctl/Makefile.am +include $(srcdir)/%D%/mount_zfs/Makefile.am diff --git a/cmd/os/macos/mount_zfs/.gitignore b/cmd/os/macos/mount_zfs/.gitignore new file mode 100644 index 000000000000..fc5a368bdff2 --- /dev/null +++ b/cmd/os/macos/mount_zfs/.gitignore @@ -0,0 +1 @@ +mount_zfs diff --git a/cmd/os/macos/mount_zfs/Makefile.am b/cmd/os/macos/mount_zfs/Makefile.am new file mode 100644 index 000000000000..b5a456422f63 --- /dev/null +++ b/cmd/os/macos/mount_zfs/Makefile.am @@ -0,0 +1,28 @@ +# +# Linux steals the "mount_zfs" name first, and since it's one +# giant Makefile you can no longer have two tools with the same name. +# +bin_PROGRAMS += macos_mount_zfs +CPPCHECKTARGETS += macos_mount_zfs + +AM_CPPFLAGS += -I$(top_srcdir)/lib/libzfs + +macos_mount_zfs_SOURCES = \ + %D%/mount_zfs.c + +macos_mount_zfs_LDADD = \ + libzfs.la \ + libzfs_core.la \ + libnvpair.la + +macos_mount_zfs_LDADD += $(LTLIBINTL) + + +mount_zfs: macos_mount_zfs + cp "$^" "$@" + +INSTALL_DATA_HOOKS += mount_zfs-install-exec-hook +mount_zfs-install-exec-hook: + mkdir -p $(FS_BUNDLEDIR)/Contents/Resources + cp -f $(DESTDIR)/$(sbindir)/macos_mount_zfs $(DESTDIR)/$(sbindir)/mount_zfs + cp -f $(DESTDIR)/$(sbindir)/mount_zfs $(FS_BUNDLEDIR)/Contents/Resources/mount_zfs diff --git a/cmd/os/macos/mount_zfs/mount_zfs.c b/cmd/os/macos/mount_zfs/mount_zfs.c new file mode 100644 index 000000000000..b2db0e86ac18 --- /dev/null +++ b/cmd/os/macos/mount_zfs/mount_zfs.c @@ -0,0 +1,325 @@ +/* + * 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 Lawrence Livermore National Security, LLC. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define ZS_COMMENT 0x00000000 /* comment */ +#define ZS_ZFSUTIL 0x00000001 /* caller is zfs(8) */ + +libzfs_handle_t *g_zfs; + +/* + * Opportunistically convert a target string into a pool name. If the + * string does not represent a block device with a valid zfs label + * then it is passed through without modification. + */ +static void +parse_dataset(const char *target, char **dataset) +{ + /* + * Prior to util-linux 2.36.2, if a file or directory in the + * current working directory was named 'dataset' then mount(8) + * would prepend the current working directory to the dataset. + * Check for it and strip the prepended path when it is added. + */ + char cwd[PATH_MAX]; + if (getcwd(cwd, PATH_MAX) == NULL) { + perror("getcwd"); + return; + } + int len = strlen(cwd); + if (strncmp(cwd, target, len) == 0) + target += len; + + /* Assume pool/dataset is more likely */ + strlcpy(*dataset, target, PATH_MAX); + + int fd = open(target, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return; + + nvlist_t *cfg = NULL; + if (zpool_read_label(fd, &cfg, NULL) == 0) { + const char *nm = NULL; + if (!nvlist_lookup_string(cfg, ZPOOL_CONFIG_POOL_NAME, &nm)) + strlcpy(*dataset, nm, PATH_MAX); + nvlist_free(cfg); + } + + if (close(fd)) + perror("close"); +} + +int +main(int argc, char **argv) +{ + zfs_handle_t *zhp; + char prop[ZFS_MAXPROPLEN]; + uint64_t zfs_version = 0; + char mntopts[MNT_LINE_MAX] = { '\0' }; + char badopt[MNT_LINE_MAX] = { '\0' }; + char mtabopt[MNT_LINE_MAX] = { '\0' }; + char mntpoint[PATH_MAX]; + char dataset[PATH_MAX], *pdataset = dataset; + unsigned long mntflags = 0, zfsflags = 0, remount = 0; + int sloppy = 0, fake = 0, verbose = 0, nomtab __maybe_unused = 0; + int zfsutil = 0; + int error, c; + + (void) setlocale(LC_ALL, ""); + (void) setlocale(LC_NUMERIC, "C"); + (void) textdomain(TEXT_DOMAIN); + + opterr = 0; + + /* check options */ + while ((c = getopt_long(argc, argv, "sfnvo:h?", 0, 0)) != -1) { + switch (c) { + case 's': + sloppy = 1; + break; + case 'f': + fake = 1; + break; + case 'n': + nomtab = 1; + break; + case 'v': + verbose++; + break; + case 'o': + (void) strlcpy(mntopts, optarg, sizeof (mntopts)); + break; + case 'h': + case '?': + (void) fprintf(stderr, gettext("Invalid option '%c'\n"), + optopt); + (void) fprintf(stderr, gettext("Usage: mount.zfs " + "[-sfnv] [-o options] \n")); + return (MOUNT_USAGE); + } + } + + argc -= optind; + argv += optind; + + /* check that we only have two arguments */ + if (argc != 2) { + if (argc == 0) + (void) fprintf(stderr, gettext("missing dataset " + "argument\n")); + else if (argc == 1) + (void) fprintf(stderr, + gettext("missing mountpoint argument\n")); + else + (void) fprintf(stderr, gettext("too many arguments\n")); + (void) fprintf(stderr, "usage: mount \n"); + return (MOUNT_USAGE); + } + + parse_dataset(argv[0], &pdataset); + + /* canonicalize the mount point */ + if (realpath(argv[1], mntpoint) == NULL) { + (void) fprintf(stderr, gettext("filesystem '%s' cannot be " + "mounted at '%s' due to canonicalization error: %s\n"), + dataset, argv[1], strerror(errno)); + return (MOUNT_SYSERR); + } + + /* validate mount options and set mntflags */ + error = zfs_parse_mount_options(mntopts, &mntflags, &zfsflags, sloppy, + badopt, mtabopt); + if (error) { + switch (error) { + case ENOMEM: + (void) fprintf(stderr, gettext("filesystem '%s' " + "cannot be mounted due to a memory allocation " + "failure.\n"), dataset); + return (MOUNT_SYSERR); + case ENOENT: + (void) fprintf(stderr, gettext("filesystem '%s' " + "cannot be mounted due to invalid option " + "'%s'.\n"), dataset, badopt); + (void) fprintf(stderr, gettext("Use the '-s' option " + "to ignore the bad mount option.\n")); + return (MOUNT_USAGE); + default: + (void) fprintf(stderr, gettext("filesystem '%s' " + "cannot be mounted due to internal error %d.\n"), + dataset, error); + return (MOUNT_SOFTWARE); + } + } + + if (verbose) + (void) fprintf(stdout, gettext("mount.zfs:\n" + " dataset: \"%s\"\n mountpoint: \"%s\"\n" + " mountflags: 0x%lx\n zfsflags: 0x%lx\n" + " mountopts: \"%s\"\n mtabopts: \"%s\"\n"), + dataset, mntpoint, mntflags, zfsflags, mntopts, mtabopt); + + if (mntflags & MS_REMOUNT) { + nomtab = 1; + remount = 1; + } + + if (zfsflags & ZS_ZFSUTIL) + zfsutil = 1; + + if ((g_zfs = libzfs_init()) == NULL) { + (void) fprintf(stderr, "%s\n", libzfs_error_init(errno)); + return (MOUNT_SYSERR); + } + + /* try to open the dataset to access the mount point */ + if ((zhp = zfs_open(g_zfs, dataset, + ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT)) == NULL) { + (void) fprintf(stderr, gettext("filesystem '%s' cannot be " + "mounted, unable to open the dataset\n"), dataset); + libzfs_fini(g_zfs); + return (MOUNT_USAGE); + } + + zfs_adjust_mount_options(zhp, mntpoint, mntopts, mtabopt); + + /* treat all snapshots as legacy mount points */ + if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT) + (void) strlcpy(prop, ZFS_MOUNTPOINT_LEGACY, ZFS_MAXPROPLEN); + else + (void) zfs_prop_get(zhp, ZFS_PROP_MOUNTPOINT, prop, + sizeof (prop), NULL, NULL, 0, B_FALSE); + + /* + * Fetch the max supported zfs version in case we get ENOTSUP + * back from the mount command, since we need the zfs handle + * to do so. + */ + zfs_version = zfs_prop_get_int(zhp, ZFS_PROP_VERSION); + if (zfs_version == 0) { + fprintf(stderr, gettext("unable to fetch " + "ZFS version for filesystem '%s'\n"), dataset); + return (MOUNT_SYSERR); + } + + /* + * Legacy mount points may only be mounted using 'mount', never using + * 'zfs mount'. However, since 'zfs mount' actually invokes 'mount' + * we differentiate the two cases using the 'zfsutil' mount option. + * This mount option should only be supplied by the 'zfs mount' util. + * + * The only exception to the above rule is '-o remount' which is + * always allowed for non-legacy datasets. This is done because when + * using zfs as your root file system both rc.sysinit/umountroot and + * systemd depend on 'mount -o remount ' to work. + */ + if (zfsutil && (strcmp(prop, ZFS_MOUNTPOINT_LEGACY) == 0)) { + (void) fprintf(stderr, gettext( + "filesystem '%s' cannot be mounted using 'zfs mount'.\n" + "Use 'zfs set mountpoint=%s' or 'mount -t zfs %s %s'.\n" + "See zfs(8) for more information.\n"), + dataset, mntpoint, dataset, mntpoint); + return (MOUNT_USAGE); + } + + if (!zfsutil && !(remount || fake) && + strcmp(prop, ZFS_MOUNTPOINT_LEGACY)) { + (void) fprintf(stderr, gettext( + "filesystem '%s' cannot be mounted using 'mount'.\n" + "Use 'zfs set mountpoint=%s' or 'zfs mount %s'.\n" + "See zfs(8) for more information.\n"), + dataset, "legacy", dataset); + return (MOUNT_USAGE); + } + + if (!fake) { + error = do_mount(zhp, mntpoint, mntopts, mntflags); + // error = mount(dataset, mntpoint, MNTTYPE_ZFS, + // mntflags, mntopts); + } + + zfs_close(zhp); + libzfs_fini(g_zfs); + + if (error) { + switch (errno) { + case ENOENT: + (void) fprintf(stderr, gettext("mount point " + "'%s' does not exist\n"), mntpoint); + return (MOUNT_SYSERR); + case EBUSY: + (void) fprintf(stderr, gettext("filesystem " + "'%s' is already mounted\n"), dataset); + return (MOUNT_BUSY); + case ENOTSUP: + if (zfs_version > ZPL_VERSION) { + (void) fprintf(stderr, + gettext("filesystem '%s' (v%d) is not " + "supported by this implementation of " + "ZFS (max v%d).\n"), dataset, + (int)zfs_version, (int)ZPL_VERSION); + } else { + (void) fprintf(stderr, + gettext("filesystem '%s' mount " + "failed for unknown reason.\n"), dataset); + } + return (MOUNT_SYSERR); +#ifdef MS_MANDLOCK + case EPERM: + if (mntflags & MS_MANDLOCK) { + (void) fprintf(stderr, gettext("filesystem " + "'%s' has the 'nbmand=on' property set, " + "this mount\noption may be disabled in " + "your kernel. Use 'zfs set nbmand=off'\n" + "to disable this option and try to " + "mount the filesystem again.\n"), dataset); + return (MOUNT_SYSERR); + } + /* fallthru */ +#endif + default: + (void) fprintf(stderr, gettext("filesystem " + "'%s' can not be mounted: %s\n"), dataset, + strerror(errno)); + return (MOUNT_USAGE); + } + } + + return (MOUNT_SUCCESS); +} diff --git a/cmd/os/macos/zconfigd/.gitignore b/cmd/os/macos/zconfigd/.gitignore new file mode 100644 index 000000000000..3339f06b4fa7 --- /dev/null +++ b/cmd/os/macos/zconfigd/.gitignore @@ -0,0 +1 @@ +/zconfigd diff --git a/cmd/os/macos/zconfigd/Makefile.am b/cmd/os/macos/zconfigd/Makefile.am new file mode 100644 index 000000000000..0ddef0cbefdb --- /dev/null +++ b/cmd/os/macos/zconfigd/Makefile.am @@ -0,0 +1,8 @@ +sbin_PROGRAMS += zconfigd +CPPCHECKTARGETS += zconfigd + +zconfigd_SOURCES = \ + %D%/zconfigd.c \ + %D%/zconfigd.h + +zconfigd_LDFLAGS = -framework IOKit -framework Foundation diff --git a/cmd/os/macos/zconfigd/zconfigd.c b/cmd/os/macos/zconfigd/zconfigd.c new file mode 100644 index 000000000000..6d6c896bbe08 --- /dev/null +++ b/cmd/os/macos/zconfigd/zconfigd.c @@ -0,0 +1,224 @@ +/* + * Copyright © 2003-2012 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +/* + * © Copyright 2001-2002 Apple Inc. All rights reserved. + * + * IMPORTANT: + * This Apple software is supplied to you by Apple Computer, Inc. (“Apple”) + * in consideration of your agreement to the following terms, and your use, + * installation, modification or redistribution of this Apple software + * constitutes acceptance of these terms. If you do not agree with these + * terms, please do not use, install, modify or redistribute this + * Apple software. + * + * In consideration of your agreement to abide by the following terms, + * and subject to these terms, Apple grants you a personal, non exclusive + * license, under Apple’s copyrights in this original Apple software + * (the “Apple Software”), to use, reproduce, modify and redistribute + * the Apple Software, with or without modifications, in source and/or + * binary forms; provided that if you redistribute the Apple Software + * in its entirety and without modifications, you must retain this notice + * and the following text and disclaimers in all such redistributions + * of the Apple Software. Neither the name, trademarks, service marks + * or logos of Apple Computer, Inc. may be used to endorse or promote + * products derived from the Apple Software without specific prior written + * permission from Apple. Except as expressly stated in this notice, no other + * rights or licenses, express or implied, are granted by Apple herein, + * including but not limited to any patent rights that may be infringed + * by your derivative works or by other works in which the Apple Software + * may be incorporated. + * + * The Apple Software is provided by Apple on an "AS IS" basis. + * APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT + * LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR + * ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + * + * IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, + * REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, + * HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING + * NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Much of this file is a modified version of Apple's USB Notification Example: + * http://www.opensource.apple.com/source/IOUSBFamily/IOUSB + * Family-630.4.5/Examples/USBNotification%20Example/main.c + */ + +/* + * 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 OpenZFS on OS X. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include + +#include "zconfigd.h" + +// globals +static IONotificationPortRef gNotifyPort; +static io_iterator_t gKextLoadedIter; + +static void +SignalHandler(int sigraised) +{ + (void) sigraised; + fprintf(stderr, "\nInterrupted\n"); + + // Clean up here + IONotificationPortDestroy(gNotifyPort); + + if (gKextLoadedIter) { + IOObjectRelease(gKextLoadedIter); + gKextLoadedIter = 0; + } + + fflush(stdout); + fflush(stderr); + + // exit(0) should not be called from a signal handler. Use _exit(0) + // instead. + // + _exit(0); +} + +static void +ZFSKextLoaded(void *refCon, io_iterator_t iterator) +{ + io_service_t myservice; + Boolean doAction = FALSE; + struct stat sbuf; + (void) refCon; + + while ((myservice = IOIteratorNext(iterator))) { + fprintf(stderr, "Found match\n"); + doAction = TRUE; + IOObjectRelease(myservice); + } + if (doAction && stat(ZSYSCTL_CONF_FILE, &sbuf) == 0) { + fprintf(stderr, "Running "ZSYSCTL_CMD_WITH_ARGS"\n"); + system(ZSYSCTL_CMD_WITH_ARGS); + } + + fflush(stdout); + fflush(stderr); +} + +int +main(int argc, const char *argv[]) +{ + mach_port_t masterPort; + CFMutableDictionaryRef matchingDict; + CFRunLoopSourceRef runLoopSource; + kern_return_t kr; + sig_t oldHandler; + + (void) argc; + (void) argv; + + // Set up a signal handler so we can clean up when we're interrupted + // from the command line. Otherwise we stay in our run loop forever. + oldHandler = signal(SIGINT, SignalHandler); + if (oldHandler == SIG_ERR) + fprintf(stderr, "Could not establish new signal handler"); + + // first create a master_port for my task + kr = IOMainPort(MACH_PORT_NULL, &masterPort); + if (kr || !masterPort) { + fprintf(stderr, "ERR: Couldn't create a master IOKit " + "Port(%08x)\n", kr); + return (-1); + } + + fprintf(stderr, "Looking for service matching %s\n", + kNetLundmanZfsZvol); + + // Set up the matching criteria for the service we're interested in + matchingDict = IOServiceNameMatching(kNetLundmanZfsZvol); + if (!matchingDict) { + fprintf(stderr, "Can't create a %s matching dictionary\n", + kNetLundmanZfsZvol); + mach_port_deallocate(mach_task_self(), masterPort); + return (-1); + } + + // Create a notification port and add its run loop event source to our + // run loop. This is how async notifications get set up. + gNotifyPort = IONotificationPortCreate(masterPort); + runLoopSource = IONotificationPortGetRunLoopSource(gNotifyPort); + + CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, + kCFRunLoopDefaultMode); + + // Now set up a notification to be called when zfs.kext loads + kr = IOServiceAddMatchingNotification(gNotifyPort, + kIOFirstMatchNotification, matchingDict, ZFSKextLoaded, NULL, + &gKextLoadedIter); + + // Iterate once to get already-present services and arm the notification + ZFSKextLoaded(NULL, gKextLoadedIter); + + // Now done with the master_port + mach_port_deallocate(mach_task_self(), masterPort); + masterPort = 0; + + fprintf(stderr, "Starting the run loop\n"); + + fflush(stdout); + fflush(stderr); + + // Start the run loop. Now we'll receive notifications. + CFRunLoopRun(); + + // We should never get here + return (0); +} diff --git a/cmd/os/macos/zconfigd/zconfigd.h b/cmd/os/macos/zconfigd/zconfigd.h new file mode 100644 index 000000000000..d382674a247f --- /dev/null +++ b/cmd/os/macos/zconfigd/zconfigd.h @@ -0,0 +1,43 @@ +/* + * 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 OpenZFS on OS X. All rights reserved. + */ + +#ifndef ZCONFIGD_H +#define ZCONFIGD_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZSYSCTL_CMD_PATH SBINDIR "/zsysctl" +#define ZSYSCTL_CONF_FILE SYSCONFDIR "/zfs/zsysctl.conf" +#define ZSYSCTL_CMD_WITH_ARGS ZSYSCTL_CMD_PATH" -f "ZSYSCTL_CONF_FILE + +#define kNetLundmanZfsZvol "osx_openzfsonosx_zfs_zvol" + +#ifdef __cplusplus +} +#endif + +#endif /* ZCONFIGD_H */ diff --git a/cmd/os/macos/zfs_util/.gitignore b/cmd/os/macos/zfs_util/.gitignore new file mode 100644 index 000000000000..e16cf3097713 --- /dev/null +++ b/cmd/os/macos/zfs_util/.gitignore @@ -0,0 +1 @@ +/zfs.util diff --git a/cmd/os/macos/zfs_util/English.lproj/InfoPlist.strings b/cmd/os/macos/zfs_util/English.lproj/InfoPlist.strings new file mode 100644 index 000000000000..5a07a98365e1 --- /dev/null +++ b/cmd/os/macos/zfs_util/English.lproj/InfoPlist.strings @@ -0,0 +1,14 @@ + + + + + FSPersonalities + + ZFS + + FSName + ZFS Dataset + + + + diff --git a/cmd/os/macos/zfs_util/Info.plist b/cmd/os/macos/zfs_util/Info.plist new file mode 100644 index 000000000000..50740b498960 --- /dev/null +++ b/cmd/os/macos/zfs_util/Info.plist @@ -0,0 +1,131 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + org.openzfsonosx.filesystems.zfs + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + zfs + CFBundlePackageType + fs + CFBundleShortVersionString + 1.5.2 + CFBundleSignature + ???? + CFBundleVersion + 1.5.2 + FSMediaTypes + + ZFS_Dataset + + FSMediaProperties + + Content Hint + ZFS_Dataset + Leaf + + + FSProbeArguments + -p + FSProbeExecutable + zfs_util + FSProbeOrder + 1000 + autodiskmount + + + 6A898CC3-1DD2-11B2-99A6-080020736631 + + FSMediaProperties + + Content Hint + 6A898CC3-1DD2-11B2-99A6-080020736631 + Leaf + + + FSProbeArguments + -p + FSProbeExecutable + zfs_util + FSProbeOrder + 850 + autodiskmount + + + Whole + + FSMediaProperties + + Leaf + + Whole + + + FSProbeArguments + -p + FSProbeExecutable + zfs_util + FSProbeOrder + 20000 + autodiskmount + + + + FSPersonalities + + ZFS + + FSName + ZFS + + FSFormatArguments + + FSFormatContentMask + ZFS_Dataset + FSFormatExecutable + zfs_util + FSMountExecutable + zfs_util + FSMountArguments + + FSVerificationExecutable + fsck_zfs + FSVerificationArguments + + FSLiveVerificationArguments + + FSXMLOutputArgument + -x + FSRepairExecutable + fsck_zfs + FSRepairArguments + + FSFormatMinimumSize + 268435456 + FSFormatMaximumSize + 9223372034707292160 + autodiskmount + + + ZFS Pool + + FSName + ZFS Pool + FSSubType + 0 + + ZFS Dataset + + FSName + ZFS Dataset + FSSubType + 1 + + + + diff --git a/cmd/os/macos/zfs_util/Makefile.am b/cmd/os/macos/zfs_util/Makefile.am new file mode 100644 index 000000000000..74b293697439 --- /dev/null +++ b/cmd/os/macos/zfs_util/Makefile.am @@ -0,0 +1,59 @@ +sbin_PROGRAMS += zfs_util +CPPCHECKTARGETS += zfs_util + +INFO_PLIST = %D%/Info.plist +INFOPLIST_STRINGS = %D%/English.lproj/InfoPlist.strings +PKGINFO = %D%/PkgInfo + +zfs_util_SOURCES = \ + %D%/zfs_util.c + +zfs_util_LDADD = \ + libzfs.la + +zfs_util_LDFLAGS = -static -framework IOKit + +# build the /Library/Filesystems/zfs.fs/ bundle + +noinst_zfs_fs_SOURCES = + +bin_PROGRAMS += noinst_zfs.fs + +dist_noinst_DATA += $(INFO_PLIST) $(INFOPLIST_STRINGS) $(PKGINFO) + +noinst_zfs.fs$(EXEEXT): zfs_util + mkdir -p zfs.fs/Contents/Resources/English.lproj + cp -f $(PKGINFO) zfs.fs/Contents/ + cp -f $(INFO_PLIST) zfs.fs/Contents/ + cp -f zfs_util zfs.fs/Contents/Resources/ + cp -f $(INFOPLIST_STRINGS) zfs.fs/Contents/Resources/English.lproj/ + /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $(ZFS_META_VERSION)" zfs.fs/Contents/Info.plist + /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $(ZFS_META_VERSION)" zfs.fs/Contents/Info.plist + plutil -convert binary1 zfs.fs/Contents/Resources/English.lproj/InfoPlist.strings + +INSTALL_DATA_HOOKS += fs-install-exec-local +fs-install-exec-local: noinst_zfs.fs + rm -f $(FS_BUNDLEDIR)/Contents/$(PKGINFO) + rm -f $(FS_BUNDLEDIR)/Contents/$(INFO_PLIST) + rm -f $(FS_BUNDLEDIR)/Contents/Resources/English.lproj/$(INFOPLIST_STRINGS) + mkdir -p $(FS_BUNDLEDIR) + rsync -aH zfs.fs/ $(FS_BUNDLEDIR)/ + ln -fs /usr/bin/true $(FS_BUNDLEDIR)/Contents/Resources/fsck_zfs + ln -fs /usr/bin/true $(FS_BUNDLEDIR)/Contents/Resources/newfs_zfs + chown -R root:wheel $(FS_BUNDLEDIR) || echo "Unable to chown root:wheel in $(FS_BUNDLEDIR)" + + +CLEANFILES += zfs.fs/Contents/Resources/English.lproj/InfoPlist.strings \ + zfs.fs/Contents/Resources/zfs_util \ + zfs.fs/Contents/Info.plist \ + zfs.fs/Contents/PkgInfo \ + zfs.fs/Contents/Resources/English.lproj/InfoPlist.strings \ + zfs.fs/Contents/Resources/fsck_zfs \ + zfs.fs/Contents/Resources/newfs_zfs + +CLEAN_LOCAL += zfsutil_clean_local +zfsutil_clean_local: + @rmdir zfs.fs/Contents/Resources/English.lproj \ + zfs.fs/Contents/Resources \ + zfs.fs/Contents \ + zfs.fs || true diff --git a/cmd/os/macos/zfs_util/PkgInfo b/cmd/os/macos/zfs_util/PkgInfo new file mode 100644 index 000000000000..b2c4b86a164e --- /dev/null +++ b/cmd/os/macos/zfs_util/PkgInfo @@ -0,0 +1 @@ +fs ???? \ No newline at end of file diff --git a/cmd/os/macos/zfs_util/zfs_util.c b/cmd/os/macos/zfs_util/zfs_util.c new file mode 100644 index 000000000000..a5214c41bf4a --- /dev/null +++ b/cmd/os/macos/zfs_util/zfs_util.c @@ -0,0 +1,1065 @@ +/* + * 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) 2007 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * The contents of this file constitute Original Code as defined in and + * are subject to the Apple Public Source License Version 1.2 (the + * "License"). You may not use this file except in compliance with the + * License. Please obtain a copy of the License at + * http://www.apple.com/publicsource and read it before using this file. + * + * This Original Code and all software distributed under the License are + * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the + * License for the specific language governing rights and limitations + * under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef FSUC_GETUUID +#define FSUC_GETUUID 'k' +#endif + +#ifndef FSUC_SETUUID +#define FSUC_SETUUID 's' +#endif + +#define ZPOOL_IMPORT_ALL_COOKIE \ + "/var/run/org.openzfsonosx.zpool-import-all.didRun" +#define INVARIANT_DISKS_IDLE_FILE \ + "/var/run/disk/invariant.idle" +#define IS_INVARIANT_DISKS_LOADED_CMD \ + "/bin/launchctl list -x org.openzfsonosx.InvariantDisks &>/dev/null" +#define INVARIANT_DISKS_TIMEOUT_SECONDS 60 + +#ifdef DEBUG +int zfs_util_debug = 1; +#else +int zfs_util_debug = 0; +#endif + +#define printf zfs_util_log + +// #define ZFS_AUTOIMPORT_ZPOOL_CACHE_ONLY + +const char *progname; +libzfs_handle_t *g_zfs; + +static void +zfs_util_log(const char *format, ...) +{ + va_list args; + char buf[1024]; + + if (zfs_util_debug == 0) + return; + + setlogmask(LOG_UPTO(LOG_NOTICE)); + + va_start(args, format); + (void) vsnprintf(buf, sizeof (buf), format, args); + fputs(buf, stderr); + va_end(args); + + if (*(&buf[strlen(buf) - 1]) == '\n') + *(&buf[strlen(buf) - 1]) = '\0'; + va_start(args, format); + vsyslog(LOG_NOTICE, format, args); + va_end(args); +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s action_arg device_arg [Flags] \n", progname); + fprintf(stderr, "action_arg:\n"); + fprintf(stderr, " -%c (Probe for mounting)\n", FSUC_PROBE); + fprintf(stderr, "device_arg:\n"); + fprintf(stderr, " device we are acting upon (for example, " + "'disk0s1')\n"); + fprintf(stderr, "Flags:\n"); + fprintf(stderr, " required for Probe\n"); + fprintf(stderr, " indicates removable or fixed (for example " + "'fixed')\n"); + fprintf(stderr, " indicates readonly or writable (for example " + "'readonly')\n"); + fprintf(stderr, "Examples:\n"); + fprintf(stderr, " %s -p disk0s1 removable readonly\n", progname); +} + +/* + * Given disk2s1, look up "disk2" is IOKit and attempt to determine if + * it is an optical device. + */ +static int +is_optical_media(const char *bsdname) +{ + CFMutableDictionaryRef matchingDict; + int ret = 0; + io_service_t service, start; + kern_return_t kernResult; + io_iterator_t iter; + + if ((matchingDict = IOBSDNameMatching(kIOMainPortDefault, + 0, bsdname)) == NULL) + return (0); + + start = IOServiceGetMatchingService(kIOMainPortDefault, matchingDict); + if (IO_OBJECT_NULL == start) + return (0); + + service = start; + + /* + * Create an iterator across all parents of the service object + * passed in. since only disk2 would match with ConfirmsTo, + * and not disk2s1, so we search the parents until we find "Whole", + * ie, disk2. + */ + kernResult = IORegistryEntryCreateIterator(service, + kIOServicePlane, + kIORegistryIterateRecursively | kIORegistryIterateParents, + &iter); + + if (KERN_SUCCESS == kernResult) { + Boolean isWholeMedia = false; + IOObjectRetain(service); + do { + // Lookup "Whole" if we can + if (IOObjectConformsTo(service, kIOMediaClass)) { + CFTypeRef wholeMedia; + wholeMedia = + IORegistryEntryCreateCFProperty(service, + CFSTR(kIOMediaWholeKey), + kCFAllocatorDefault, + 0); + if (wholeMedia) { + isWholeMedia = + CFBooleanGetValue(wholeMedia); + CFRelease(wholeMedia); + } + } + + // If we found "Whole", check the service type. + if (isWholeMedia && + ((IOObjectConformsTo(service, kIOCDMediaClass)) || + (IOObjectConformsTo(service, kIODVDMediaClass)))) { + ret = 1; // Is optical, skip + } + + IOObjectRelease(service); + } while ((service = IOIteratorNext(iter)) && !isWholeMedia); + IOObjectRelease(iter); + } + + IOObjectRelease(start); + return (ret); +} + + +#ifdef ZFS_AUTOIMPORT_ZPOOL_CACHE_ONLY + +#define PRIV_SYS_CONFIG 0 +static __inline int +priv_ineffect(int priv) +{ + assert(priv == PRIV_SYS_CONFIG); + return (geteuid() == 0); +} + +/* + * Perform the import for the given configuration. This passes the heavy + * lifting off to zpool_import_props(), and then mounts the datasets contained + * within the pool. + */ +static int +do_import(nvlist_t *config, const char *newname, const char *mntopts, + nvlist_t *props, int flags) +{ + zpool_handle_t *zhp; + char *name; + uint64_t state; + uint64_t version; + + verify(nvlist_lookup_string(config, ZPOOL_CONFIG_POOL_NAME, + &name) == 0); + + verify(nvlist_lookup_uint64(config, + ZPOOL_CONFIG_POOL_STATE, &state) == 0); + verify(nvlist_lookup_uint64(config, + ZPOOL_CONFIG_VERSION, &version) == 0); + if (!SPA_VERSION_IS_SUPPORTED(version)) { + printf("cannot import '%s': pool is formatted using an " + "unsupported ZFS version\n", name); + return (1); + } else if (state != POOL_STATE_EXPORTED && + !(flags & ZFS_IMPORT_ANY_HOST)) { + uint64_t hostid; + + if (nvlist_lookup_uint64(config, ZPOOL_CONFIG_HOSTID, + &hostid) == 0) { + unsigned long system_hostid = gethostid() & 0xffffffff; + + if ((unsigned long)hostid != system_hostid) { + char *hostname; + uint64_t timestamp; + time_t t; + + verify(nvlist_lookup_string(config, + ZPOOL_CONFIG_HOSTNAME, &hostname) == 0); + verify(nvlist_lookup_uint64(config, + ZPOOL_CONFIG_TIMESTAMP, ×tamp) == 0); + t = timestamp; + printf("cannot import " "'%s': pool may be in " + "use from other system, it was last " + "accessed by %s (hostid: 0x%lx) on %s\n", + name, hostname, (unsigned long)hostid, + asctime(localtime(&t))); + printf("use '-f' to import anyway\n"); + return (1); + } + } else { + printf("cannot import '%s': pool may be in use from " + "other system\n", name); + printf("use '-f' to import anyway\n"); + return (1); + } + } + + if (zpool_import_props(g_zfs, config, newname, props, flags) != 0) + return (1); + + if (newname != NULL) + name = (char *)newname; + + if ((zhp = zpool_open_canfail(g_zfs, name)) == NULL) + return (1); + + if (zpool_get_state(zhp) != POOL_STATE_UNAVAIL && + !(flags & ZFS_IMPORT_ONLY) && + zpool_enable_datasets(zhp, mntopts, 0) != 0) { + zpool_close(zhp); + return (1); + } + + zpool_close(zhp); + return (0); +} + +static int +zpool_import_by_guid(uint64_t searchguid) +{ + int err = 0; + nvlist_t *pools = NULL; + nvpair_t *elem; + nvlist_t *config; + nvlist_t *found_config = NULL; + nvlist_t *policy = NULL; + boolean_t first; + int flags = ZFS_IMPORT_NORMAL; + uint32_t rewind_policy = ZPOOL_NO_REWIND; + uint64_t pool_state, txg = -1ULL; + importargs_t idata = { 0 }; +#ifdef ZFS_AUTOIMPORT_ZPOOL_STATUS_OK_ONLY + char *msgid; + zpool_status_t reason; + zpool_errata_t errata; +#endif + + if ((g_zfs = libzfs_init()) == NULL) + return (1); + + /* In the future, we can capture further policy and include it here */ + if (nvlist_alloc(&policy, NV_UNIQUE_NAME, 0) != 0 || + nvlist_add_uint64(policy, ZPOOL_LOAD_REQUEST_TXG, txg) != 0 || + nvlist_add_uint32(policy, ZPOOL_LOAD_REWIND_POLICY, + rewind_policy) != 0) + goto error; + + if (!priv_ineffect(PRIV_SYS_CONFIG)) { + printf("cannot discover pools: permission denied\n"); + nvlist_free(policy); + return (1); + } + + idata.guid = searchguid; + + pools = zpool_search_import(g_zfs, &idata); + + if (pools == NULL && idata.exists) { + printf("cannot import '%llu': a pool with that guid is already " + "created/imported\n", searchguid); + err = 1; + } else if (pools == NULL) { + printf("cannot import '%llu': no such pool available\n", + searchguid); + err = 1; + } + + if (err == 1) { + nvlist_free(policy); + return (1); + } + + /* + * At this point we have a list of import candidate configs. Even though + * we were searching by guid, we still need to post-process the list to + * deal with pool state. + */ + err = 0; + elem = NULL; + first = B_TRUE; + while ((elem = nvlist_next_nvpair(pools, elem)) != NULL) { + + verify(nvpair_value_nvlist(elem, &config) == 0); + + verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_STATE, + &pool_state) == 0); + if (pool_state == POOL_STATE_DESTROYED) + continue; + + verify(nvlist_add_nvlist(config, ZPOOL_LOAD_POLICY, + policy) == 0); + + uint64_t guid; + + /* + * Search for a pool by guid. + */ + verify(nvlist_lookup_uint64(config, ZPOOL_CONFIG_POOL_GUID, + &guid) == 0); + + if (guid == searchguid) + found_config = config; + } + + /* + * If we were searching for a specific pool, verify that we found a + * pool, and then do the import. + */ + if (err == 0) { + if (found_config == NULL) { + printf("zfs.util: FATAL cannot import '%llu': " + "no such pool available\n", searchguid); + err = B_TRUE; + } else { +#ifdef ZFS_AUTOIMPORT_ZPOOL_STATUS_OK_ONLY + reason = zpool_import_status(config, &msgid, &errata); + if (reason == ZPOOL_STATUS_OK) + err |= do_import(found_config, NULL, NULL, NULL, + flags); + else + err = 1; +#else + err |= do_import(found_config, NULL, NULL, NULL, flags); +#endif + } + } + +error: + nvlist_free(pools); + nvlist_free(policy); + libzfs_fini(g_zfs); + + return (err ? 1 : 0); +} +#endif // ZFS_AUTOIMPORT_ZPOOL_CACHE_ONLY + +struct probe_args { + char *pool_name; + int name_len; + uint64_t pool_guid; + uint64_t vdev_guid; +}; +typedef struct probe_args probe_args_t; + +const char *UNKNOWN_STRING = "Unknown"; + +static int +zfs_probe(const char *devpath, probe_args_t *args) +{ + nvlist_t *config = NULL; + int ret = FSUR_UNRECOGNIZED; + int fd; + uint64_t guid; + int i, again = 0; + struct stat sbuf; + + // printf("+zfs_probe : devpath %s\n", devpath); + + if (system(IS_INVARIANT_DISKS_LOADED_CMD) == 0) { + /* InvariantDisks is loaded */ + i = 0; + while (i != INVARIANT_DISKS_TIMEOUT_SECONDS) { + if (stat(INVARIANT_DISKS_IDLE_FILE, &sbuf) == 0) { + // printf("Found %s after %d iterations of " + // "sleeping 1 second\n", + // INVARIANT_DISKS_IDLE_FILE, i); + break; + } + sleep(1); + i++; + } + if (i == INVARIANT_DISKS_TIMEOUT_SECONDS) { + printf("zfs.util: FATAL: File %s not found within " + "%d seconds\n", + INVARIANT_DISKS_IDLE_FILE, + INVARIANT_DISKS_TIMEOUT_SECONDS); + } + } + +retry: + + if ((fd = open(devpath, O_RDONLY)) < 0) { + printf("zfs.util: FATAL: Could not open devpath %s: %d\n", + devpath, errno); + goto out; + } + + if (zpool_read_label(fd, &config, NULL) != 0) { + printf("zfs.util: FATAL: Could not read label devpath %s: %d\n", + devpath, errno); + (void) close(fd); + goto out; + } + + (void) close(fd); + + if (config != NULL) { + const char *name; + ret = FSUR_RECOGNIZED; + args->pool_guid = (nvlist_lookup_uint64(config, + ZPOOL_CONFIG_POOL_GUID, &guid) == 0) ? guid : 0; + args->vdev_guid = (nvlist_lookup_uint64(config, + ZPOOL_CONFIG_GUID, &guid) == 0) ? guid : 0; + if (args->pool_name && + nvlist_lookup_string(config, ZPOOL_CONFIG_POOL_NAME, + &name) == 0) + strlcpy(args->pool_name, name, MAXPATHLEN); + nvlist_free(config); + } else { + if (again++ < 5) { + // printf("zfs.util: read_label config is NULL\n"); + sleep(1); + goto retry; + } + // printf("zfs.util: FATAL: read_label config is NULL\n"); + } +out: + printf("-zfs_probe : ret %s\n", + ret == FSUR_RECOGNIZED ? "FSUR_RECOGNIZED" : "FSUR_UNRECOGNIZED"); + return (ret); +} + +/* Look up "/dev/rdisk5" in ioreg to see if it is a pseudodisk */ +static int +zfs_probe_iokit(const char *devpath, probe_args_t *args) +{ + const char *name; + CFMutableDictionaryRef matchingDict; + io_service_t service = IO_OBJECT_NULL; + CFStringRef cfstr = NULL; + int result = FSUR_UNRECOGNIZED; + + // Make sure it is "disk5s1" not "/dev/" and not "rdisk" + if (strncmp("/dev/disk", devpath, 9) == 0) + name = &devpath[5]; + else if (strncmp("/dev/rdisk", devpath, 10) == 0) + name = &devpath[6]; + else if (strncmp("rdisk", devpath, 5) == 0) + name = &devpath[1]; + else + name = devpath; + + printf("%s: looking for '%s' in ioreg\n", __func__, name); + + matchingDict = IOBSDNameMatching(kIOMainPortDefault, 0, name); + if (NULL == matchingDict) { + printf("%s: IOBSDNameMatching returned NULL dictionary\n", + __func__); + goto fail; + } + + /* + * Fetch the object with the matching BSD node name. + * Note that there should only be one match, so + * IOServiceGetMatchingService is used instead of + * IOServiceGetMatchingServices to simplify the code. + */ + service = IOServiceGetMatchingService(kIOMainPortDefault, + matchingDict); + + if (IO_OBJECT_NULL == service) { + printf("%s: IOServiceGetMatchingService returned NULL.\n", + __func__); + goto fail; + } + + if (IOObjectConformsTo(service, kIOMediaClass)) { + cfstr = IORegistryEntryCreateCFProperty(service, + CFSTR("ZFS Dataset"), kCFAllocatorDefault, 0); + if (cfstr != NULL) { + const char *str; + + str = CFStringGetCStringPtr(cfstr, + kCFStringEncodingUTF8); + + if (str != NULL) + (void) strlcpy(args->pool_name, str, + sizeof (io_name_t)); + + result = FSUR_RECOGNIZED; + } + } + +fail: + if (service != IO_OBJECT_NULL) + IOObjectRelease(service); + if (cfstr != NULL) + CFRelease(cfstr); + + printf("%s: result %s name '%s'\n", __func__, + result == FSUR_RECOGNIZED ? "FSUR_RECOGNIZED" : + result == FSUR_UNRECOGNIZED ? "FSUR_UNRECOGNIZED" : + "UNKNOWN", + result == FSUR_RECOGNIZED ? args->pool_name : ""); + + return (result); +} + +#ifdef ZFS_AUTOIMPORT_ZPOOL_CACHE_ONLY +void +zpool_read_cachefile(void) +{ + int fd; + struct stat stbf; + void *buf = NULL; + nvlist_t *nvlist, *child; + nvpair_t *nvpair; + uint64_t guid; + int importrc = 0; + + // printf("reading cachefile\n"); + + fd = open(ZPOOL_CACHE, O_RDONLY); + if (fd < 0) + return; + + if (fstat(fd, &stbf) || !stbf.st_size) + goto out; + + buf = kmem_alloc(stbf.st_size, 0); + if (!buf) + goto out; + + if (read(fd, buf, stbf.st_size) != stbf.st_size) + goto out; + + if (nvlist_unpack(buf, stbf.st_size, &nvlist, KM_PUSHPAGE) != 0) + goto out; + + nvpair = NULL; + while ((nvpair = nvlist_next_nvpair(nvlist, nvpair)) != NULL) { + if (nvpair_type(nvpair) != DATA_TYPE_NVLIST) + continue; + + VERIFY(nvpair_value_nvlist(nvpair, &child) == 0); + + printf("Cachefile has pool '%s'\n", nvpair_name(nvpair)); + + if (nvlist_lookup_uint64(child, ZPOOL_CONFIG_POOL_GUID, + &guid) == 0) { + printf("Cachefile has pool '%s' guid %llu\n", + nvpair_name(nvpair), guid); + + importrc = zpool_import_by_guid(guid); + printf("zpool import error %d\n", importrc); + } + + } + nvlist_free(nvlist); + +out: + close(fd); + if (buf) + kmem_free(buf, stbf.st_size); + +} +#endif + + +/* + * Each vdev in a pool should each have unique uuid? + */ +static int +zfs_util_uuid_gen(probe_args_t *probe, char *uuid_str) +{ + unsigned char uuid[CC_MD5_DIGEST_LENGTH]; + // MD5_CTX md5c; + CC_MD5_CTX md5c; + /* namespace (generated by uuidgen) */ + /* 50670853-FBD2-4EC3-9802-73D847BF7E62 */ + char namespace[16] = {0x50, 0x67, 0x08, 0x53, /* - */ + 0xfb, 0xd2, /* - */ 0x4e, 0xc3, /* - */ + 0x98, 0x02, /* - */ + 0x73, 0xd8, 0x47, 0xbf, 0x7e, 0x62}; + + /* Validate arguments */ + if (!probe->vdev_guid) { + printf("zfs.util: FATAL: %s missing argument\n", __func__); + return (EINVAL); + } + + /* + * UUID version 3 (MD5) namespace variant: + * hash namespace (uuid) together with name + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + + CC_MD5_Init(&md5c); + CC_MD5_Update(&md5c, &namespace, sizeof (namespace)); + CC_MD5_Update(&md5c, &probe->vdev_guid, sizeof (probe->vdev_guid)); + CC_MD5_Final(uuid, &md5c); + +#pragma GCC diagnostic pop + + /* + * To make UUID version 3, twiddle a few bits: + * xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx + * [uint32]-[uin-t32]-[uin-t32][uint32] + * M should be 0x3 to indicate uuid v3 + * N should be 0x8, 0x9, 0xa, or 0xb + */ + uuid[6] = (uuid[6] & 0x0F) | 0x30; + uuid[8] = (uuid[8] & 0x3F) | 0x80; + + // Convert binary to ascii + uuid_unparse_upper(uuid, uuid_str); + + return (0); +} + +struct attrNameBuf { + uint32_t length; + attrreference_t nameRef; + char name[MAXPATHLEN]; +} __attribute__((aligned(4), packed)); + +int +main(int argc, char **argv) +{ + struct statfs *statfs; + char blockdevice[MAXPATHLEN]; + char rawdevice[MAXPATHLEN]; + char what; + char *cp; + char *devname; + probe_args_t probe_args; + struct stat sb; + int ret = FSUR_INVAL; + int i, num, len, is_mounted = 0; + struct attrlist attr; + struct attrNameBuf nameBuf; + char volname[MAXPATHLEN]; + char *pool_name = NULL; + + /* save & strip off program name */ + progname = argv[0]; + argc--; + argv++; + + if (argc < 2 || argv[0][0] != '-') { + usage(); + goto out; + } + + what = argv[0][1]; + printf("zfs.util called with option %c: pid %d\n", what, getpid()); + + devname = argv[1]; + cp = strrchr(devname, '/'); + if (cp != 0) + devname = cp + 1; + if (*devname == 'r') + devname++; + +/* XXX Only checking ZFS pseudo devices, so this can be skipped */ +/* We have to check all probe devices to get rid of the popup */ + if (is_optical_media(devname)) { + printf("zfs.util: is_optical_media(%s)\n", devname); + goto out; + } + + (void) snprintf(rawdevice, sizeof (rawdevice), "/dev/r%s", devname); + (void) snprintf(blockdevice, sizeof (blockdevice), "/dev/%s", devname); + // printf("blockdevice is %s\n", blockdevice); + + + /* Sometimes this is a bit of a race, so we will retry a few times */ + for (i = 0; i < 5; i++) { + + if (stat(blockdevice, &sb) == 0) break; + + // printf("%s: %d stat %s failed, %s\n", progname, i, + // blockdevice, strerror(errno)); + sleep(1); + } + if (i >= 5) { + printf("%s: FATAL: stat %s failed, %s\n", progname, blockdevice, + strerror(errno)); + goto out; + } + + /* + * XXX Should check vfs_typenum is ZFS, and also must check + * for com.apple.mimic_hfs mounts (somehow) + * Check if the blockdevice refers to a mounted filesystem + */ + do { + num = getmntinfo(&statfs, MNT_NOWAIT); + if (num <= 0) { + printf("%s: FATAL: getmntinfo error %d\n", + __func__, num); + break; + } + + len = strlen(blockdevice); + for (i = 0; i < num; i++) { + if (strlen(statfs[i].f_mntfromname) == len && + strcmp(statfs[i].f_mntfromname, + blockdevice) == 0) { + // printf("matched mountpoint %s\n", + // statfs[i].f_mntonname); + is_mounted = B_TRUE; + break; + } + /* Skip this mountpoint */ + } + + if (!is_mounted) { + printf("%s no match - not mounted\n", __func__); + break; + } + } while (0); + + + memset(&probe_args, 0, sizeof (probe_args_t)); + len = MAXNAMELEN; + pool_name = kmem_alloc(len, KM_SLEEP); + if (!pool_name) { + printf("FATAL: alloc failed\n"); + ret = FSUR_UNRECOGNIZED; + goto out; + } + + probe_args.pool_name = pool_name; + probe_args.name_len = len; + + /* Check the request type */ + switch (what) { + case FSUC_PROBE: + + /* XXX For now only checks mounted fs (root fs) */ + if (!is_mounted) { + printf("FSUR_PROBE : unmounted fs: %s\n", + rawdevice); + + /* rawdevice might be pseudo for devdisk mounts */ + ret = zfs_probe_iokit(rawdevice, &probe_args); + + /* otherwise, read disk */ + if (ret == FSUR_UNRECOGNIZED) + ret = zfs_probe(rawdevice, &probe_args); + + /* + * Validate guid and name, valid vdev + * must have a vdev_guid, but not + * necessarily a pool_guid + */ + if (ret == FSUR_RECOGNIZED && + (probe_args.vdev_guid == 0)) { + ret = FSUR_UNRECOGNIZED; + } + + if (ret == FSUR_RECOGNIZED) { + printf("FSUC_PROBE %s : FSUR_RECOGNIZED :" + " %s : pool guid 0x%016LLx vdev guid " + "0x%016LLx\n", + blockdevice, probe_args.pool_name, + probe_args.pool_guid, + probe_args.vdev_guid); + /* Output pool name for DiskArbitration */ + write(1, probe_args.pool_name, + strlen(probe_args.pool_name)); + } else { + printf("FSUC_PROBE %s : FSUR_UNRECOGNIZED :" + " %d\n", blockdevice, ret); + ret = FSUR_UNRECOGNIZED; + } + + break; + + } else { /* is_mounted == true */ + + memset(&attr, 0, sizeof (attr)); + memset(&nameBuf, 0, sizeof (nameBuf)); + memset(&volname, 0, sizeof (volname)); + attr.bitmapcount = 5; + attr.volattr = ATTR_VOL_INFO | ATTR_VOL_NAME; + + ret = getattrlist(statfs[i].f_mntonname, &attr, + &nameBuf, sizeof (nameBuf), 0); + if (ret != 0) { + printf("%s FATAL: couldn't stat mount [%s]\n", + __func__, statfs[i].f_mntonname); + ret = FSUR_UNRECOGNIZED; + break; + } + if (nameBuf.length < offsetof(struct attrNameBuf, + name)) { + printf("PROBE: FATAL: short attrlist return\n"); + ret = FSUR_UNRECOGNIZED; + break; + } + if (nameBuf.length > sizeof (nameBuf)) { + printf("PROBE: FATAL: overflow attrlist\n"); + ret = FSUR_UNRECOGNIZED; + break; + } + + snprintf(volname, nameBuf.nameRef.attr_length, "%s", + ((char *)&nameBuf.nameRef) + + nameBuf.nameRef.attr_dataoffset); + + printf("volname [%s]\n", volname); + write(1, volname, strlen(volname)); + ret = FSUR_RECOGNIZED; + break; + + } // ismounted + + break; + + /* Done */ + + case FSUC_GETUUID: + { + uint32_t buf[5]; + + /* Try to get a UUID either way */ + /* First, zpool vdev disks */ + if (!is_mounted) { + char uuid[40]; + + memset(&probe_args, 0, sizeof (probe_args_t)); + + ret = zfs_probe(rawdevice, &probe_args); + + /* Validate vdev guid */ + if (ret == FSUR_RECOGNIZED && + probe_args.vdev_guid == 0) { + ret = FSUR_UNRECOGNIZED; + } + + if (ret != FSUR_RECOGNIZED) { + printf("FSUC_GET_UUID %s : " + "FSUR_UNRECOGNIZED %d\n", + blockdevice, ret); + ret = FSUR_IO_FAIL; + break; + } + + /* Generate valid UUID from guids */ + if (zfs_util_uuid_gen(&probe_args, uuid) != 0) { + printf("FSUC_GET_UUID %s : " + "uuid_gen error %d\n", + blockdevice, ret); + ret = FSUR_IO_FAIL; + break; + } + + printf("FSUC_GET_UUID %s : FSUR_RECOGNIZED :" + " pool guid 0x%016llx :" + " vdev guid 0x%016llx : UUID %s\n", + blockdevice, probe_args.pool_guid, + probe_args.vdev_guid, uuid); + + /* Output the vdev guid for DiskArbitration */ + write(1, uuid, sizeof (uuid)); + ret = FSUR_IO_SUCCESS; + + break; + + } else { /* is_mounted == true */ + + /* Otherwise, ZFS filesystem pseudo device */ + + // struct attrlist attr; + memset(&buf, 0, sizeof (buf)); + memset(&attr, 0, sizeof (attr)); + attr.bitmapcount = 5; + attr.volattr = ATTR_VOL_INFO | ATTR_VOL_UUID; + + /* Retrieve UUID from mp */ + ret = getattrlist(statfs[i].f_mntonname, &attr, + &buf, sizeof (buf), 0); + if (ret != 0) { + printf("%s FATAL: couldn't stat " + "mount [%s]\n", + __func__, statfs[i].f_mntonname); + ret = FSUR_IO_FAIL; + break; + } + + /* + * buf[0] is count of uint32_t values returned, + * including itself + */ + if (buf[0] < (5 * sizeof (uint32_t))) { + printf("FATAL: getattrlist result " + "len %d != %d\n", + buf[0], (5 * sizeof (uint32_t))); + ret = FSUR_IO_FAIL; + break; + } + + /* + * getattr results are big-endian uint32_t + * and need to be swapped to host. + * Verified by reading UUID from mounted HFS + * via getattrlist and validating result. + */ + buf[1] = OSSwapBigToHostInt32(buf[1]); + buf[2] = OSSwapBigToHostInt32(buf[2]); + buf[3] = OSSwapBigToHostInt32(buf[3]); + buf[4] = OSSwapBigToHostInt32(buf[4]); + + /* + * Validate UUID version 3 + * (namespace variant w/MD5) + * We need to check a few bits: + * xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx + * [uint32]-[ uint32]-[ uint32][uint32] + * M should be 0x3xxx to indicate version 3 + * N should be 0x8xxx, 0x9xxx, 0xaxxx, or 0xbxxx + */ + if (buf[2] != + ((buf[2] & 0xFFFF0FFF) | 0x00003000)) { + printf("FATAL: missing v3 in UUID\n"); + ret = FSUR_IO_FAIL; + } + if (buf[3] != + ((buf[3] & 0x3FFFFFFF) | 0x80000000)) { + printf("FATAL: missing variant bits\n"); + ret = FSUR_IO_FAIL; + } + if (ret == FSUR_IO_FAIL) + break; + + /* + * As char (reverse) + * result_uuid[6]=(result_uuid[6]&0x0F) | 0x30; + * result_uuid[8]=(result_uuid[8]&0x3F) | 0x80; + */ + printf("uuid: %08X-%04X-%04X-%04X-%04X%08X\n", + buf[1], (buf[2]&0xffff0000)>>16, + buf[2]&0x0000ffff, + (buf[3]&0xffff0000)>>16, buf[3]&0x0000ffff, + buf[4]); + /* Print all caps to please DiskArbitration */ + + /* Print UUID string (no newline) to stdout */ + fprintf(stdout, "%08X-%04X-%04X-%04X-%04X%08X", + buf[1], (buf[2]&0xffff0000)>>16, + buf[2]&0x0000ffff, + (buf[3]&0xffff0000)>>16, buf[3]&0x0000ffff, + buf[4]); + ret = FSUR_IO_SUCCESS; + + break; + } + break; + } + + case FSUC_SETUUID: + /* Set a UUID */ + printf("FSUC_SETUUID\n"); + ret = FSUR_INVAL; + break; + case FSUC_MOUNT: + /* Reject automount */ + printf("FSUC_MOUNT\n"); + ret = FSUR_IO_FAIL; + break; + case FSUC_UNMOUNT: + /* Reject unmount */ + printf("FSUC_UNMOUNT\n"); + ret = FSUR_IO_FAIL; + break; + default: + printf("unrecognized command %c\n", what); + ret = FSUR_INVAL; + usage(); + } +out: + if (pool_name) + kmem_free(pool_name, len); + printf("Clean exit: %d (%d)\n", getpid(), ret); + closelog(); + exit(ret); + + return (ret); /* ...and make main fit the ANSI spec. */ +} diff --git a/cmd/os/macos/zsysctl/.gitignore b/cmd/os/macos/zsysctl/.gitignore new file mode 100644 index 000000000000..a20dff2e7321 --- /dev/null +++ b/cmd/os/macos/zsysctl/.gitignore @@ -0,0 +1 @@ +zsysctl diff --git a/cmd/os/macos/zsysctl/Makefile.am b/cmd/os/macos/zsysctl/Makefile.am new file mode 100644 index 000000000000..d49642efc7da --- /dev/null +++ b/cmd/os/macos/zsysctl/Makefile.am @@ -0,0 +1,5 @@ +sbin_PROGRAMS += zsysctl +CPPCHECKTARGETS += zsysctl + +zsysctl_SOURCES = \ + %D%/zsysctl.c diff --git a/cmd/os/macos/zsysctl/zsysctl.c b/cmd/os/macos/zsysctl/zsysctl.c new file mode 100644 index 000000000000..2b8491c57dc6 --- /dev/null +++ b/cmd/os/macos/zsysctl/zsysctl.c @@ -0,0 +1,1222 @@ + +/* + * Copyright (c) 1999-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +/* + * Copyright (c) 1993 + * The Regents of the University of California. 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. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +#include + +#include +#include +#include +#include +#include +#include +#ifdef __APPLE__ +#include +#include +#include +#else +#include +#endif /* __APPLE__ */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#pragma GCC diagnostic push +#pragma GCC diagnostic \ + ignored "-Wincompatible-pointer-types-discards-qualifiers" + +struct ctlname topname[] = CTL_NAMES; +struct ctlname kernname[] = CTL_KERN_NAMES; +struct ctlname vmname[] = CTL_VM_NAMES; +struct ctlname hwname[] = CTL_HW_NAMES; +struct ctlname username[] = CTL_USER_NAMES; +struct ctlname debugname[CTL_DEBUG_MAXID]; +struct ctlname *vfsname; +#ifdef CTL_MACHDEP_NAMES +struct ctlname machdepname[] = (char *)CTL_MACHDEP_NAMES; +#endif + +#pragma GCC diagnostic pop + + +char names[BUFSIZ]; +int lastused; + +struct list { + struct ctlname *list; + int size; +}; +struct list toplist = { topname, CTL_MAXID }; +struct list secondlevel[] = { + { 0, 0 }, /* CTL_UNSPEC */ + { kernname, KERN_MAXID }, /* CTL_KERN */ + { vmname, VM_MAXID }, /* CTL_VM */ + { 0, 0 }, /* CTL_VFS */ + { 0, 0 }, /* CTL_NET */ + { 0, CTL_DEBUG_MAXID }, /* CTL_DEBUG */ + { hwname, HW_MAXID }, /* CTL_HW */ +#ifdef CTL_MACHDEP_NAMES + { machdepname, CPU_MAXID }, /* CTL_MACHDEP */ +#else + { 0, 0 }, /* CTL_MACHDEP */ +#endif + { username, USER_MAXID }, /* CTL_USER_NAMES */ +}; + +static const char *conffile; + +static int Aflag, aflag, bflag, hflag, nflag, wflag, Xflag; +static int foundSome = 0; +static int invalid_name_used = 0; + +void listall(char *prefix, struct list *lp); +void old_parse(char *string, int flags); +static void debuginit(void); +static void vfsinit(void); +int findname(char *string, char *level, char **bufp, struct list *namelist); +static void usage(void); + +static void parse(char *string, int flags); +static int parsefile(const char *); +static int oidfmt(int *, int, char *, uint_t *); +static int show_var(int *, int, int); +static int sysctl_all(int *oid, int len); +static int name2oid(char *, int *); + +/* + * Variables requiring special processing. + */ +#define CLOCK 0x00000001 +#define BOOTTIME 0x00000002 +#define CONSDEV 0x00000004 + +int +main(int argc, char *argv[]) +{ + // extern char *optarg; // unused + extern int optind; + int ch, lvl1; + + while ((ch = getopt(argc, argv, "Aabf:nwX")) != EOF) { + switch (ch) { + case 'A': Aflag = 1; break; + case 'a': aflag = 1; break; + case 'b': bflag = 1; break; + case 'h': hflag = 1; break; + case 'n': nflag = 1; break; + case 'w': wflag = 1; break; + case 'f': wflag = 1; conffile = optarg; break; + case 'X': Xflag = Aflag = 1; break; + default: usage(); + } + } + argc -= optind; + argv += optind; + + if (argc == 0 && (Aflag || aflag)) { + debuginit(); + vfsinit(); + for (lvl1 = 1; lvl1 < CTL_MAXID; lvl1++) + listall(topname[lvl1].ctl_name, &secondlevel[lvl1]); + exit(sysctl_all(0, 0)); + } + + if (conffile != NULL) { + exit(parsefile(conffile)); + } + + if (argc == 0) + usage(); + + for (; *argv != NULL; ++argv) + parse(*argv, 1); + + + exit(invalid_name_used ? 1 : 0); +} + +/* + * List all variables known to the system. + */ +void +listall(char *prefix, struct list *lp) +{ + int lvl2; + char *cp, name[BUFSIZ]; + + if (lp->list == 0) + return; + strcpy(name, prefix); + cp = &name[strlen(name)]; + *cp++ = '.'; + for (lvl2 = 0; lvl2 < lp->size; lvl2++) { + if (lp->list[lvl2].ctl_name == 0) + continue; + strcpy(cp, lp->list[lvl2].ctl_name); + old_parse(name, Aflag); + } +} + +/* + * Parse a name into a MIB entry. + * Lookup and print out the MIB entry if it exists. + * Set a new value if requested. + */ +void +old_parse(char *string, int flags) +{ + int indx, type, len; + size_t size; + int special = 0; + void *newval = 0; + int intval, newsize = 0; + unsigned int uintval; + int useUnsignedInt = 0; + quad_t quadval; + struct list *lp; + struct vfsconf vfc; + int mib[CTL_MAXNAME]; + char *cp, *bufp, buf[BUFSIZ]; + + bufp = buf; + snprintf(buf, BUFSIZ, "%s", string); + if ((cp = strchr(string, '=')) != NULL) { + if (!wflag) { + fprintf(stderr, "Must specify -w to set variables\n"); + exit(2); + } + *strchr(buf, '=') = '\0'; + *cp++ = '\0'; + while (isspace(*cp)) + cp++; + newval = cp; + newsize = strlen(cp); + } + if ((indx = findname(string, (char *)"top", &bufp, &toplist)) == -1) + return; + mib[0] = indx; + if (indx == CTL_VFS) + vfsinit(); + if (indx == CTL_DEBUG) + debuginit(); + lp = &secondlevel[indx]; + if (lp->list == 0) { + if (!foundSome) + fprintf(stderr, "%s: class is not implemented\n", + topname[indx].ctl_name); + return; + } + if (bufp == NULL) { + listall(topname[indx].ctl_name, lp); + return; + } + if ((indx = findname(string, (char *)"second", &bufp, lp)) == -1) + return; + mib[1] = indx; + type = lp->list[indx].ctl_type; + len = 2; + switch (mib[0]) { + + case CTL_KERN: + switch (mib[1]) { + case KERN_PROF: + return; + case KERN_VNODE: + case KERN_FILE: + if (flags == 0) + return; + fprintf(stderr, + "Use pstat to view %s information\n", + string); + return; + case KERN_PROC: + if (flags == 0) + return; + fprintf(stderr, + "Use ps to view %s information\n", + string); + return; + case KERN_CLOCKRATE: + special |= CLOCK; + break; + case KERN_BOOTTIME: + special |= BOOTTIME; + break; + case KERN_HOSTID: + useUnsignedInt = 1; + break; + } + break; + + case CTL_HW: + useUnsignedInt = 1; + break; + + case CTL_VM: break; + + case CTL_DEBUG: + mib[2] = CTL_DEBUG_VALUE; + len = 3; + break; + + case CTL_MACHDEP: +#ifdef CPU_CONSDEV + if (mib[1] == CPU_CONSDEV) + special |= CONSDEV; +#endif + break; + + case CTL_VFS: + mib[3] = mib[1]; + mib[1] = VFS_GENERIC; + mib[2] = VFS_CONF; + len = 4; + size = sizeof (vfc); + if (sysctl(mib, 4, &vfc, &size, (void *)0, + (size_t)0) < 0) { + perror("vfs print"); + return; + } + if (flags == 0 && vfc.vfc_refcount == 0) + return; + if (!nflag) + fprintf(stdout, + "%s has %d mounted instance%s\n", + string, vfc.vfc_refcount, + vfc.vfc_refcount != 1 ? "s" : ""); + else + fprintf(stdout, "%d\n", vfc.vfc_refcount); + return; + + case CTL_USER: + break; + + default: + fprintf(stderr, "Illegal top level value: %d\n", + mib[0]); + return; + + } + if (bufp) { + fprintf(stderr, "name %s in %s is unknown\n", bufp, string); + return; + } + if (newsize > 0) { + switch (type) { + case CTLTYPE_INT: + if (useUnsignedInt) { + uintval = strtoul(newval, NULL, 0); + if ((uintval == 0) && + (errno == EINVAL)) { + fprintf(stderr, + "invalid argument: %s\n", + (char *)newval); + return; + } + newval = &uintval; + newsize = sizeof (uintval); + } else { + intval = strtol(newval, NULL, 0); + if ((intval == 0) && + (errno == EINVAL)) { + fprintf(stderr, + "invalid argument: %s\n", + (char *)newval); + return; + } + newval = &intval; + newsize = sizeof (intval); + } + break; + + case CTLTYPE_QUAD: + quadval = strtoq(newval, NULL, 0); + if ((quadval == 0) && (errno == EINVAL)) { + fprintf(stderr, + "invalid argument: %s\n", + (char *)newval); + return; + } + newval = &quadval; + newsize = sizeof (quadval); + break; + } + } + size = BUFSIZ; + if (sysctl(mib, len, buf, &size, newsize ? newval : 0, newsize) == -1) { + if (flags == 0) + return; + switch (errno) { + case ENOTSUP: + fprintf(stderr, "%s: value is not available\n", + string); + return; + case ENOTDIR: + fprintf(stderr, + "%s: specification is incomplete\n", + string); + return; + case ENOMEM: + fprintf(stderr, + "%s: type is unknown to this program\n", + string); + return; + case ENOENT: + fprintf(stderr, "%s: no such MIB\n", + string); + return; + default: + perror(string); + return; + } + } + if (special & CLOCK) { + struct clockinfo *clkp = (struct clockinfo *)buf; + + if (!nflag) + fprintf(stdout, "%s: ", string); + fprintf(stdout, + "hz = %d, tick = %d, profhz = %d, stathz = %d\n", + clkp->hz, clkp->tick, clkp->profhz, clkp->stathz); + return; + } + if (special & BOOTTIME) { + struct timeval *btp = (struct timeval *)buf; + + if (!nflag) + fprintf(stdout, "%s = %s\n", string, + ctime((time_t *)&btp->tv_sec)); + else + fprintf(stdout, "%ld\n", btp->tv_sec); + return; + } + if (special & CONSDEV) { + dev_t dev = *(dev_t *)buf; + + if (!nflag) + fprintf(stdout, "%s = %s\n", string, + devname(dev, S_IFCHR)); + else + fprintf(stdout, "0x%x\n", dev); + return; + } + switch (type) { + case CTLTYPE_INT: + if (newsize == 0) { + if (!nflag) + fprintf(stdout, "%s = ", string); + fprintf(stdout, useUnsignedInt ? "%u\n" : + "%d\n", *(int *)buf); + } else { + if (!nflag) + fprintf(stdout, useUnsignedInt ? + "%s: %u -> " : "%s: %d -> ", + string, *(int *)buf); + fprintf(stdout, useUnsignedInt ? "%u\n" : + "%d\n", *(int *)newval); + } + return; + + case CTLTYPE_STRING: + if (newsize == 0) { + if (!nflag) + fprintf(stdout, "%s = ", string); + fprintf(stdout, "%s\n", buf); + } else { + if (!nflag) + fprintf(stdout, "%s: %s -> ", + string, buf); + fprintf(stdout, "%s\n", (char *)newval); + } + return; + + case CTLTYPE_QUAD: + if (newsize == 0) { + if (!nflag) + fprintf(stdout, "%s = ", string); + fprintf(stdout, "%qd\n", *(quad_t *)buf); + } else { + if (!nflag) + fprintf(stdout, "%s: %qd -> ", string, + *(quad_t *)buf); + fprintf(stdout, "%qd\n", *(quad_t *)newval); + } + return; + + case CTLTYPE_NODE: + case CTLTYPE_STRUCT: + return; + + default: + fprintf(stderr, "%s: unknown type returned\n", + string); + return; + } +} + +/* + * Initialize the set of debugging names + */ +static void +debuginit(void) +{ + int mib[3], loc, i; + size_t size; + + if (secondlevel[CTL_DEBUG].list != 0) + return; + secondlevel[CTL_DEBUG].list = debugname; + mib[0] = CTL_DEBUG; + mib[2] = CTL_DEBUG_NAME; + for (loc = lastused, i = 0; i < CTL_DEBUG_MAXID; i++) { + mib[1] = i; + size = BUFSIZ - loc; + if (sysctl(mib, 3, &names[loc], &size, NULL, 0) == -1) + continue; + debugname[i].ctl_name = &names[loc]; + debugname[i].ctl_type = CTLTYPE_INT; + loc += size; + } + lastused = loc; +} + +/* + * Initialize the set of filesystem names + */ +static void +vfsinit(void) +{ + int mib[4], maxtypenum, cnt, loc, size; + struct vfsconf vfc; + size_t buflen; + + if (secondlevel[CTL_VFS].list != 0) + return; + mib[0] = CTL_VFS; + mib[1] = VFS_GENERIC; + mib[2] = VFS_MAXTYPENUM; + buflen = 4; + if (sysctl(mib, 3, &maxtypenum, &buflen, (void *)0, (size_t)0) < 0) + return; + if ((vfsname = malloc(maxtypenum * sizeof (*vfsname))) == 0) + return; + memset(vfsname, 0, maxtypenum * sizeof (*vfsname)); + mib[2] = VFS_CONF; + buflen = sizeof (vfc); + for (loc = lastused, cnt = 0; cnt < maxtypenum; cnt++) { + mib[3] = cnt; + if (sysctl(mib, 4, &vfc, &buflen, (void *)0, (size_t)0) < 0) { + if (errno == ENOTSUP) + continue; + perror("vfsinit"); + free(vfsname); + return; + } + strcat(&names[loc], vfc.vfc_name); + vfsname[cnt].ctl_name = &names[loc]; + vfsname[cnt].ctl_type = CTLTYPE_INT; + size = strlen(vfc.vfc_name) + 1; + loc += size; + } + lastused = loc; + secondlevel[CTL_VFS].list = vfsname; + secondlevel[CTL_VFS].size = maxtypenum; +} + +/* + * Scan a list of names searching for a particular name. + */ +int +findname(char *string, char *level, char **bufp, struct list *namelist) +{ + char *name; + int i; + + /* Make 'sysctl kern.' style behave the same as 'sysctl kern' 3360872 */ + if (bufp[0][strlen(*bufp)-1] == '.') + bufp[0][strlen(*bufp)-1] = '\0'; + if (namelist->list == 0 || (name = strsep(bufp, ".")) == NULL) { + if (!foundSome) { + fprintf(stderr, "%s: incomplete specification\n", + string); + invalid_name_used = 1; + } + return (-1); + } + for (i = 0; i < namelist->size; i++) + if (namelist->list[i].ctl_name != NULL && + strcmp(name, namelist->list[i].ctl_name) == 0) + break; + if (i == namelist->size) { + if (!foundSome) { + fprintf(stderr, "%s level name %s in %s is invalid\n", + level, name, string); + invalid_name_used = 1; + } + return (-1); + } + return (i); +} + +static void +usage(void) +{ + (void) fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n", + "usage: zsysctl [-bn] variable ...", + " zsysctl [-bn] -w variable=value ...", + " zsysctl [-bn] -a", + " zsysctl [-bn] -A", + " zsysctl [-bn] -X", + " zsysctl -f [conffile]"); + exit(1); +} + +/* + * Parse a name into a MIB entry. + * Lookup and print out the MIB entry if it exists. + * Set a new value if requested. + */ +static void +parse(char *string, int flags) +{ + int len, i, j; + void *newval = 0; + int intval, newsize = 0; + unsigned int uintval; + quad_t quadval; + int mib[CTL_MAXNAME]; + char *cp, *bufp, buf[BUFSIZ], fmt[BUFSIZ]; + uint_t kind; + + bufp = buf; + if (snprintf(buf, BUFSIZ, "%s", string) >= BUFSIZ) + errx(1, "MIB too long"); + snprintf(buf, BUFSIZ, "%s", string); + if ((cp = strchr(string, '=')) != NULL) { + if (!wflag) + errx(2, "must specify -w to set variables"); + *strchr(buf, '=') = '\0'; + *cp++ = '\0'; + while (isspace(*cp)) + cp++; + newval = cp; + newsize = strlen(cp); + } else { + if (wflag) + usage(); + } + len = name2oid(bufp, mib); + + if (len < 0) { + if (cp != NULL) { + while (*cp != '\0') cp--; + *cp = '='; + } + old_parse(string, flags); + return; + } + + /* + * An non-zero return here is an OID space containing parameters which + * needs to be ignored in the interests of backward compatibility with + * pre-newsysctl sysctls. + */ + if (oidfmt(mib, len, fmt, &kind)) + return; + + if (!wflag) { + if ((kind & CTLTYPE) == CTLTYPE_NODE) { + sysctl_all(mib, len); + foundSome = 1; + old_parse(string, flags); + } else { + i = show_var(mib, len, 1); + if (!i && !bflag) + putchar('\n'); + } + } else { + if ((kind & CTLTYPE) == CTLTYPE_NODE) + errx(1, "oid '%s' isn't a leaf node", bufp); + + if (!(kind&CTLFLAG_WR)) + errx(1, "oid '%s' is read only", bufp); + + switch (kind & CTLTYPE) { + case CTLTYPE_INT: + if ((*fmt == 'I') && (*(fmt + 1) == 'U')) { + uintval = (unsigned int) strtoul(newval, + NULL, 0); + if ((uintval == 0) && + (errno == EINVAL)) { + errx(1, "invalid argument: %s", + (char *)newval); + return; + } + newval = &uintval; + newsize = sizeof (uintval); + } else { + intval = (int)strtol(newval, NULL, 0); + if ((intval == 0) && + (errno == EINVAL)) { + errx(1, "invalid argument: %s", + (char *)newval); + return; + } + newval = &intval; + newsize = sizeof (intval); + } + break; + case CTLTYPE_STRING: + break; + case CTLTYPE_QUAD: + quadval = strtoq(newval, NULL, 0); + if ((quadval == 0) && (errno == EINVAL)) { + errx(1, "invalid argument %s", + (char *)newval); + return; + } + newval = &quadval; + newsize = sizeof (quadval); + break; + default: + errx(1, "oid '%s' is type %d," + " cannot set that", bufp, + kind & CTLTYPE); + } + + i = show_var(mib, len, 1); + if (sysctl(mib, len, 0, 0, newval, newsize) == -1) { + if (!i && !bflag) + putchar('\n'); + switch (errno) { + case ENOTSUP: + errx(1, "%s: value is not available", + string); + case ENOTDIR: + errx(1, + "%s: specification is incomplete", + string); + case ENOMEM: + errx(1, + "%s: type is unknown to this program", + string); + default: + warn("%s", string); + return; + } + } + if (!bflag) + printf(" -> "); + i = nflag; + nflag = 1; + j = show_var(mib, len, 1); + if (!j && !bflag) + putchar('\n'); + nflag = i; + } +} + +static int +parsefile(const char *filename) +{ + FILE *file; + char line[BUFSIZ], *p, *pq, *pdq; + // int warncount = 0, lineno = 0; + + file = fopen(filename, "r"); + if (file == NULL) + err(1, "%s", filename); + while (fgets(line, sizeof (line), file) != NULL) { + // lineno++; + p = line; + pq = strchr(line, '\''); + pdq = strchr(line, '\"'); + /* Replace the first # with \0. */ + while ((p = strchr(p, '#')) != NULL) { + if (pq != NULL && p > pq) { + if ((p = strchr(pq+1, '\'')) != NULL) + *(++p) = '\0'; + break; + } else if (pdq != NULL && p > pdq) { + if ((p = strchr(pdq+1, '\"')) != NULL) + *(++p) = '\0'; + break; + } else if (p == line || *(p-1) != '\\') { + *p = '\0'; + break; + } + p++; + } + /* Trim spaces */ + p = line + strlen(line) - 1; + while (p >= line && isspace((int)*p)) { + *p = '\0'; + p--; + } + p = line; + while (isspace((int)*p)) + p++; + if (*p == '\0') + continue; + else + /* warncount += */parse(p, 1); // lineno); + } + fclose(file); + + // return (warncount); + return (0); +} + +/* These functions will dump out various interesting structures. */ + +static int +S_clockinfo(int l2, void *p) +{ + struct clockinfo *ci = (struct clockinfo *)p; + + if (l2 != sizeof (*ci)) { + warnx("S_clockinfo %d != %ld", l2, sizeof (*ci)); + return (1); + } + printf(hflag ? "{ hz = %'d, tick = %'d, tickadj = %'d," + " profhz = %'d, stathz = %'d }" : + "{ hz = %d, tick = %d, tickadj = %d, profhz = %d, stathz = %d }", + ci->hz, ci->tick, ci->tickadj, ci->profhz, ci->stathz); + return (0); +} + +static int +S_loadavg(int l2, void *p) +{ + struct loadavg *tv = (struct loadavg *)p; + + if (l2 != sizeof (*tv)) { + warnx("S_loadavg %d != %ld", l2, sizeof (*tv)); + return (1); + } + printf(hflag ? "{ %'.2f %'.2f %'.2f }" : "{ %.2f %.2f %.2f }", + (double)tv->ldavg[0]/(double)tv->fscale, + (double)tv->ldavg[1]/(double)tv->fscale, + (double)tv->ldavg[2]/(double)tv->fscale); + return (0); +} + +static int +S_timeval(int l2, void *p) +{ + struct timeval *tv = (struct timeval *)p; + time_t tv_sec; + char *p1, *p2; + + if (l2 != sizeof (*tv)) { + warnx("S_timeval %d != %ld", l2, sizeof (*tv)); + return (1); + } + printf(hflag ? "{ sec = %'jd, usec = %'ld } " : + "{ sec = %jd, usec = %ld } ", + (intmax_t)tv->tv_sec, (long)tv->tv_usec); + tv_sec = tv->tv_sec; + p1 = strdup(ctime(&tv_sec)); + for (p2 = p1; *p2; p2++) + if (*p2 == '\n') + *p2 = '\0'; + fputs(p1, stdout); + free(p1); + return (0); +} + +static int +S_xswusage(int l2, void *p) +{ + struct xsw_usage *xsu = (struct xsw_usage *)p; + + if (l2 != sizeof (*xsu)) { + warnx("S_xswusage %d != %ld", l2, sizeof (*xsu)); + return (1); + } + fprintf(stdout, + "total = %.2fM used = %.2fM free = %.2fM %s", + ((double)xsu->xsu_total) / (1024.0 * 1024.0), + ((double)xsu->xsu_used) / (1024.0 * 1024.0), + ((double)xsu->xsu_avail) / (1024.0 * 1024.0), + xsu->xsu_encrypted ? "(encrypted)" : ""); + return (0); +} + +static int +T_dev_t(int l2, void *p) +{ + dev_t *d = (dev_t *)p; + + if (l2 != sizeof (*d)) { + warnx("T_dev_T %d != %ld", l2, sizeof (*d)); + return (1); + } + if ((int)(*d) != -1) { + if (minor(*d) > 255 || minor(*d) < 0) + printf("{ major = %d, minor = 0x%x }", + major(*d), minor(*d)); + else + printf("{ major = %d, minor = %d }", + major(*d), minor(*d)); + } + return (0); +} + +/* + * These functions uses a presently undocumented interface to the kernel + * to walk the tree and get the type so it can print the value. + * This interface is under work and consideration, and should probably + * be killed with a big axe by the first person who can find the time. + * (be aware though, that the proper interface isn't as obvious as it + * may seem, there are various conflicting requirements. + */ + +static int +name2oid(char *name, int *oidp) +{ + int oid[2]; + int i; + size_t j; + + oid[0] = 0; + oid[1] = 3; + + j = CTL_MAXNAME * sizeof (int); + i = sysctl(oid, 2, oidp, &j, name, strlen(name)); + if (i < 0) + return (i); + j /= sizeof (int); + return (j); +} + +static int +oidfmt(int *oid, int len, char *fmt, uint_t *kind) +{ + int qoid[CTL_MAXNAME+2]; + uchar_t buf[BUFSIZ]; + int i; + size_t j; + + qoid[0] = 0; + qoid[1] = 4; + memcpy(qoid + 2, oid, len * sizeof (int)); + + j = sizeof (buf); + i = sysctl(qoid, len + 2, buf, &j, 0, 0); + if (i) { + /* + * An ENOENT error return indicates that the OID in question + * is a node OID followed not by additional OID elements, but + * by integer parameters. We really do not want to support + * this type of thing going forward, but we alow it here for + * historical compatibility. Eventually, this will go away. + */ + if (errno == ENOENT) + return (ENOENT); + err(1, "sysctl fmt %d %ld %d", i, j, errno); + } + + if (kind) + *kind = *(uint_t *)buf; + + if (fmt) + strcpy(fmt, (char *)(buf + sizeof (uint_t))); + return (0); +} + +/* + * This formats and outputs the value of one variable + * + * Returns zero if anything was actually output. + * Returns one if didn't know what to do with this. + * Return minus one if we had errors. + */ + +static int +show_var(int *oid, int nlen, int show_masked) +{ + uchar_t buf[BUFSIZ], *val, *mval, *p; + char name[BUFSIZ], /* descr[BUFSIZ], */ *fmt; + int qoid[CTL_MAXNAME+2]; + int i; + int retval; + size_t j, len; + uint_t kind; + int (*func)(int, void *) = 0; + + qoid[0] = 0; + memcpy(qoid + 2, oid, nlen * sizeof (int)); + + qoid[1] = 1; + j = sizeof (name); + i = sysctl(qoid, nlen + 2, name, &j, 0, 0); + if (i || !j) + err(1, "sysctl name %d %ld %d", i, j, errno); + + /* find an estimate of how much we need for this var */ + j = 0; + i = sysctl(oid, nlen, 0, &j, 0, 0); + j += j; /* we want to be sure :-) */ + + val = mval = malloc(j); + len = j; + i = sysctl(oid, nlen, val, &len, 0, 0); + if (i || !len) { + retval = 1; + goto RETURN; + } + + if (bflag) { + fwrite(val, 1, len, stdout); + retval = 0; + goto RETURN; + } + + qoid[1] = 4; + j = sizeof (buf); + i = sysctl(qoid, nlen + 2, buf, &j, 0, 0); + /* + * An ENOENT error return indicates that the OID in question + * is a node OID followed not by additional OID elements, but + * by integer parameters. We really do not want to support + * this type of thing going forward, but we alow it here for + * historical compatibility. Eventially, this will go away. + */ + if (i && errno == ENOENT) { + retval = 1; + goto RETURN; + } + + if (i || !j) + err(1, "sysctl fmt %d %ld %d", i, j, errno); + + kind = *(uint_t *)buf; + if (!show_masked && (kind & CTLFLAG_MASKED)) { + retval = 1; + goto RETURN; + } + + fmt = (char *)(buf + sizeof (uint_t)); + + p = val; + switch (*fmt) { + case '-': + /* deprecated, do not print */ + retval = 0; + goto RETURN; + + case 'A': + if (!nflag) + printf("%s: ", name); + printf("%s", p); + retval = 0; + goto RETURN; + + case 'I': + if (!nflag) + printf("%s: ", name); + fmt++; + val = (unsigned char *)""; + while (len >= sizeof (int)) { + if (*fmt == 'U') + printf("%s%u", val, *(unsigned int *)p); + else + printf("%s%d", val, *(int *)p); + val = (unsigned char *)" "; + len -= sizeof (int); + p += sizeof (int); + } + retval = 0; + goto RETURN; + + case 'L': + if (!nflag) + printf("%s: ", name); + fmt++; + val = (unsigned char *)""; + while (len >= sizeof (long)) { + if (*fmt == 'U') + printf("%s%lu", val, + *(unsigned long *)p); + else + printf("%s%ld", val, *(long *)p); + val = (unsigned char *)" "; + len -= sizeof (long); + p += sizeof (long); + } + retval = 0; + goto RETURN; + + case 'P': + if (!nflag) + printf("%s: ", name); + printf("%p", *(void **)p); + retval = 0; + goto RETURN; + + case 'Q': + if (!nflag) + printf("%s: ", name); + fmt++; + val = (unsigned char *)""; + while (len >= sizeof (long long)) { + if (*fmt == 'U') + printf("%s%llu", val, + *(unsigned long long *)p); + else + printf("%s%lld", val, *(long long *)p); + val = (unsigned char *)" "; + len -= sizeof (long long); + p += sizeof (long long); + } + retval = 0; + goto RETURN; + + case 'T': + case 'S': + i = 0; + if (strcmp(fmt, "S,clockinfo") == 0) + func = S_clockinfo; + else if (strcmp(fmt, "S,timeval") == 0) + func = S_timeval; + else if (strcmp(fmt, "S,loadavg") == 0) + func = S_loadavg; + else if (strcmp(fmt, "S,xsw_usage") == 0) + func = S_xswusage; + else if (strcmp(fmt, "T,dev_t") == 0) + func = T_dev_t; + if (func) { + if (!nflag) + printf("%s: ", name); + retval = (*func)(len, p); + goto RETURN; + } + /* FALL THROUGH */ + zfs_fallthrough; + default: + if (!Aflag) { + retval = 1; + goto RETURN; + } + if (!nflag) + printf("%s: ", name); + printf("Format:%s Length:%ld Dump:0x", fmt, len); + while (len--) { + printf("%02x", *p++); + if (Xflag || p < val+16) + continue; + printf("..."); + break; + } + retval = 0; + goto RETURN; + } + + retval = 1; +RETURN: + free(mval); + return (retval); +} + +static int +sysctl_all(int *oid, int len) +{ + int name1[22], name2[22]; + int i, j; + size_t l1, l2; + + name1[0] = 0; + name1[1] = 2; + l1 = 2; + if (len) { + memcpy(name1+2, oid, len*sizeof (int)); + l1 += len; + } else { + name1[2] = 1; + l1++; + } + while (1) { + l2 = sizeof (name2); + j = sysctl(name1, l1, name2, &l2, 0, 0); + if (j < 0) { + if (errno == ENOENT) + return (0); + else + err(1, "sysctl(getnext) %d %ld", j, l2); + } + + l2 /= sizeof (int); + + if (l2 < len) + return (0); + + for (i = 0; i < len; i++) + if (name2[i] != oid[i]) + return (0); + + i = show_var(name2, l2, 0); + if (!i && !bflag) + putchar('\n'); + + memcpy(name1+2, name2, l2*sizeof (int)); + l1 = 2 + l2; + } +} diff --git a/cmd/zpool/os/macos/Info-zfs.plist b/cmd/zpool/os/macos/Info-zfs.plist new file mode 100644 index 000000000000..097b9735459a --- /dev/null +++ b/cmd/zpool/os/macos/Info-zfs.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + org.openzfsonosx.zfs.zfs + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + zfs + CFBundleVersion + 1.0 + SecTaskAccess + + allowed + + + diff --git a/cmd/zpool/os/macos/Info-zpool.plist b/cmd/zpool/os/macos/Info-zpool.plist new file mode 100644 index 000000000000..2813497d88e9 --- /dev/null +++ b/cmd/zpool/os/macos/Info-zpool.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + org.openzfsonosx.zfs.zpool + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + zpool + CFBundleVersion + 1.0 + SecTaskAccess + + allowed + + NSSystemAdministrationUsageDescription + zpool would like to have FullDiskAccess + + diff --git a/cmd/zpool/os/macos/zpool-entitlements.plist b/cmd/zpool/os/macos/zpool-entitlements.plist new file mode 100644 index 000000000000..ef41d356a4e8 --- /dev/null +++ b/cmd/zpool/os/macos/zpool-entitlements.plist @@ -0,0 +1,14 @@ + + + + + com.apple.security.application-groups + + org.openzfsonosx.zfs.zpool + + com.apple.security.app-sandbox + + com.apple.security.automation.apple-events + + + diff --git a/cmd/zpool/os/macos/zpool_vdev_os.c b/cmd/zpool/os/macos/zpool_vdev_os.c new file mode 100644 index 000000000000..01fc90efda82 --- /dev/null +++ b/cmd/zpool/os/macos/zpool_vdev_os.c @@ -0,0 +1,201 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "zpool_util.h" +#include + +#include +#include +#include +#include +#include + +boolean_t +check_sector_size_database(char *path, int *sector_size) +{ + (void) path; + (void) sector_size; + return (B_FALSE); +} + +static void +check_error(int err) +{ + /* + * ENXIO/ENODEV is a valid error message if the device doesn't live in + * /dev/dsk. Don't bother printing an error message in this case. + */ + if (err == ENXIO || err == ENODEV) + return; + + (void) fprintf(stderr, gettext("warning: device in use checking " + "failed: %s\n"), strerror(err)); +} + +static int +check_slice(const char *path, int force, boolean_t isspare) +{ + char *msg; + int error = 0; + dm_who_type_t who; + + if (force) + who = DM_WHO_ZPOOL_FORCE; + else if (isspare) + who = DM_WHO_ZPOOL_SPARE; + else + who = DM_WHO_ZPOOL; + + if (dm_inuse((char *)path, &msg, who, &error) || error) { + if (error != 0) { + check_error(error); + return (0); + } else { + vdev_error("%s", msg); + free(msg); + return (-1); + } + } + return (0); +} + +/* + * Validate that a disk including all partitions are safe to use. + * + * For EFI labeled disks this can done relatively easily with the libefi + * library. The partition numbers are extracted from the label and used + * to generate the expected /dev/ paths. Each partition can then be + * checked for conflicts. + * + * For non-EFI labeled disks (MBR/EBR/etc) the same process is possible + * but due to the lack of a readily available libraries this scanning is + * not implemented. Instead only the device path as given is checked. + */ +static int +check_disk(const char *path, int force, + boolean_t isspare, boolean_t iswholedisk) +{ + struct dk_gpt *vtoc; + char slice_path[MAXPATHLEN]; + int err = 0; + int fd, i; + int flags = O_RDONLY|O_DIRECT; + + if (!iswholedisk) + return (check_slice(path, force, isspare)); + + /* only spares can be shared, other devices require exclusive access */ + if (!isspare) + flags |= O_EXCL; + + if ((fd = open(path, flags)) < 0) { + return (-1); + } + + /* + * Expected to fail for non-EFI labeled disks. Just check the device + * as given and do not attempt to detect and scan partitions. + */ + err = efi_alloc_and_read(fd, &vtoc); + if (err) { + (void) close(fd); + return (check_slice(path, force, isspare)); + } + + /* + * The primary efi partition label is damaged however the secondary + * label at the end of the device is intact. Rather than use this + * label we should play it safe and treat this as a non efi device. + */ + if (vtoc->efi_flags & EFI_GPT_PRIMARY_CORRUPT) { + efi_free(vtoc); + (void) close(fd); + + if (force) { + /* Partitions will now be created using the backup */ + return (0); + } else { + vdev_error(gettext("%s contains a corrupt primary " + "EFI label.\n"), path); + return (-1); + } + } + + for (i = 0; i < vtoc->efi_nparts; i++) { + if (vtoc->efi_parts[i].p_tag == V_UNASSIGNED || + uuid_is_null((uchar_t *)&vtoc->efi_parts[i].p_guid)) + continue; + (void) snprintf(slice_path, sizeof (slice_path), + "%ss%d", path, i+1); + + err = check_slice(slice_path, force, isspare); + if (err) + break; + } + + efi_free(vtoc); + (void) close(fd); + + return (err); +} + +int +check_device(const char *path, boolean_t force, + boolean_t isspare, boolean_t iswholedisk) +{ + int error; + + error = check_disk(path, force, isspare, iswholedisk); + if (error != 0) + return (error); + + return (check_file(path, force, isspare)); +} + +int +check_file(const char *file, boolean_t force, boolean_t isspare) +{ + if (dm_in_swap_dir(file)) { + vdev_error(gettext( + "%s is located within the swapfile directory.\n"), file); + return (-1); + } + + return (check_file_generic(file, force, isspare)); +} + +void +after_zpool_upgrade(zpool_handle_t *zhp) +{ + (void) zhp; +} diff --git a/include/os/macos/Makefile.am b/include/os/macos/Makefile.am new file mode 100644 index 000000000000..a9564c3e3cba --- /dev/null +++ b/include/os/macos/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = spl zfs \ No newline at end of file diff --git a/include/os/macos/spl/IOKit/IOLib.h b/include/os/macos/spl/IOKit/IOLib.h new file mode 100644 index 000000000000..91af8655e3f3 --- /dev/null +++ b/include/os/macos/spl/IOKit/IOLib.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 (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) 2023 Sean Doran + * + */ + +#ifndef _SPL_IOLIB_H +#define _SPL_IOLIB_H + +#include +#include + +#include_next + +#ifndef IOMallocType +#define IOMallocType(T) (T *)IOMallocAligned(sizeof (T), _Alignof(T)) +/* + * Do a compile-time check that pointer P is of type T *. + * Any kind of optimization eliminates the declaration and + * assignment, leaving only the free itself and setting + * the pointer to NULL to frustrate use-after-free. + */ +#define IOFreeType(P, T) do { IOFreeAligned(P, sizeof (T)); \ + T tmp; \ + P = &tmp; \ + P = NULL; } while (0) +#endif + +#endif diff --git a/include/os/macos/spl/Makefile.am b/include/os/macos/spl/Makefile.am new file mode 100644 index 000000000000..75cad0836e61 --- /dev/null +++ b/include/os/macos/spl/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = sys rpc diff --git a/include/os/macos/spl/libkern/libkern.h b/include/os/macos/spl/libkern/libkern.h new file mode 100644 index 000000000000..b1fd44555ab3 --- /dev/null +++ b/include/os/macos/spl/libkern/libkern.h @@ -0,0 +1,43 @@ +/* + * 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 Jorgen Lundman + * + */ + +#ifndef _SPL_LIBKERN_H +#define _SPL_LIBKERN_H + +/* + * We wrap this header to handle that copyinstr()'s final argument is + * mandatory on OSX. Wrap it to call our ddi_copyinstr to make it optional. + */ + +#include +#include + +#include_next +#undef copyinstr +#define copyinstr(U, K, L, D) ddi_copyinstr((U), (K), (L), (D)) + +#endif diff --git a/include/os/macos/spl/linux/init.h b/include/os/macos/spl/linux/init.h new file mode 100644 index 000000000000..4ab1523c16ba --- /dev/null +++ b/include/os/macos/spl/linux/init.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 _LINUX_INIT_H +#define _LINUX_INIT_H + + +#endif diff --git a/include/os/macos/spl/linux/kernel.h b/include/os/macos/spl/linux/kernel.h new file mode 100644 index 000000000000..73a2b2eaad2e --- /dev/null +++ b/include/os/macos/spl/linux/kernel.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 _LINUX_KERNEL_H +#define _LINUX_KERNEL_H + +#endif diff --git a/include/os/macos/spl/linux/module.h b/include/os/macos/spl/linux/module.h new file mode 100644 index 000000000000..264d6c058dd9 --- /dev/null +++ b/include/os/macos/spl/linux/module.h @@ -0,0 +1,28 @@ +/* + * 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 _LINUX_MODULE_H +#define _LINUX_MODULE_H + +#include +#include + +#endif diff --git a/include/os/macos/spl/rpc/Makefile.am b/include/os/macos/spl/rpc/Makefile.am new file mode 100644 index 000000000000..770d26812ea6 --- /dev/null +++ b/include/os/macos/spl/rpc/Makefile.am @@ -0,0 +1,3 @@ +KERNEL_H = \ + $(top_srcdir)/include/os/macos/spl/rpc/types.h \ + $(top_srcdir)/include/os/macos/spl/rpc/xdr.h diff --git a/include/os/macos/spl/rpc/types.h b/include/os/macos/spl/rpc/types.h new file mode 100644 index 000000000000..e089e0ed8c27 --- /dev/null +++ b/include/os/macos/spl/rpc/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 (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 Jorgen Lundman + * + */ +#ifndef _SPL_RPC_TYPES_H +#define _SPL_RPC_TYPES_H + +typedef int bool_t; + +#endif /* SPL_RPC_TYPES_H */ diff --git a/include/os/macos/spl/rpc/xdr.h b/include/os/macos/spl/rpc/xdr.h new file mode 100644 index 000000000000..fa8472de6e02 --- /dev/null +++ b/include/os/macos/spl/rpc/xdr.h @@ -0,0 +1,179 @@ +/* + * 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) 1989, 2011, Oracle and/or its affiliates. All rights reserved. + */ +/* 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. + */ + +/* + * xdr.h, External Data Representation Serialization Routines. + * + */ + +#ifndef _SPL_RPC_XDR_H +#define _SPL_RPC_XDR_H + + +#include +#include + +/* + * XDR enums and types. + */ +enum xdr_op { + XDR_ENCODE, + XDR_DECODE +}; + +struct xdr_ops; + +typedef struct { + const struct xdr_ops *x_ops; /* Also used to let caller know if */ + /* xdrmem_create() succeeds (sigh..) */ + caddr_t x_addr; /* Current buffer addr */ + caddr_t x_addr_end; /* End of the buffer */ + enum xdr_op x_op; /* Stream direction */ +} XDR; + +#ifdef _KERNEL +typedef bool_t (*xdrproc_t)(XDR *xdrs, void *ptr); +#else +typedef bool_t (*xdrproc_t)(XDR *xdrs, void *ptr, unsigned int u); +#endif + +struct xdr_ops { + bool_t (*xdr_control)(XDR *, int, void *); + + bool_t (*xdr_char)(XDR *, char *); + bool_t (*xdr_u_short)(XDR *, unsigned short *); + bool_t (*xdr_u_int)(XDR *, unsigned *); + bool_t (*xdr_u_longlong_t)(XDR *, u_longlong_t *); + + bool_t (*xdr_opaque)(XDR *, caddr_t, const uint_t); + bool_t (*xdr_string)(XDR *, char **, const uint_t); + bool_t (*xdr_array)(XDR *, caddr_t *, uint_t *, const uint_t, + const uint_t, const xdrproc_t); +}; + +/* + * XDR control operator. + */ +#define XDR_GET_BYTES_AVAIL 1 + +struct xdr_bytesrec { + bool_t xc_is_last_record; + size_t xc_num_avail; +}; + +/* + * XDR functions. + */ +void xdrmem_create(XDR *xdrs, const caddr_t addr, const uint_t size, + const enum xdr_op op); +#define xdr_destroy(xdrs) ((void) 0) + +#define xdr_control(xdrs, req, info) \ + (xdrs)->x_ops->xdr_control((xdrs), (req), (info)) + +/* + * For precaution, the following are defined as static inlines instead of macros + * to get some amount of type safety. + * + * Also, macros wouldn't work in the case where typecasting is done, because it + * must be possible to reference the functions' addresses by these names. + */ +static inline bool_t +xdr_char(XDR *xdrs, char *cp) +{ + return (xdrs->x_ops->xdr_char(xdrs, cp)); +} + +static inline bool_t +xdr_u_short(XDR *xdrs, unsigned short *usp) +{ + return (xdrs->x_ops->xdr_u_short(xdrs, usp)); +} + +static inline bool_t +xdr_short(XDR *xdrs, short *sp) +{ + return (xdrs->x_ops->xdr_u_short(xdrs, (unsigned short *) sp)); +} + +static inline bool_t +xdr_u_int(XDR *xdrs, unsigned *up) +{ + return (xdrs->x_ops->xdr_u_int(xdrs, up)); +} + +static inline bool_t +xdr_int(XDR *xdrs, int *ip) +{ + return (xdrs->x_ops->xdr_u_int(xdrs, (unsigned *)ip)); +} + +static inline bool_t +xdr_u_longlong_t(XDR *xdrs, u_longlong_t *ullp) +{ + return (xdrs->x_ops->xdr_u_longlong_t(xdrs, ullp)); +} + +static inline bool_t +xdr_longlong_t(XDR *xdrs, longlong_t *llp) +{ + return (xdrs->x_ops->xdr_u_longlong_t(xdrs, (u_longlong_t *)llp)); +} + +/* + * Fixed-length opaque data. + */ +static inline bool_t +xdr_opaque(XDR *xdrs, caddr_t cp, const uint_t cnt) +{ + return (xdrs->x_ops->xdr_opaque(xdrs, cp, cnt)); +} + +/* + * Variable-length string. + * The *sp buffer must have (maxsize + 1) bytes. + */ +static inline bool_t +xdr_string(XDR *xdrs, char **sp, const uint_t maxsize) +{ + return (xdrs->x_ops->xdr_string(xdrs, sp, maxsize)); +} + +/* + * Variable-length arrays. + */ +static inline bool_t xdr_array(XDR *xdrs, caddr_t *arrp, uint_t *sizep, + const uint_t maxsize, const uint_t elsize, const xdrproc_t elproc) +{ + return (xdrs->x_ops->xdr_array(xdrs, arrp, sizep, maxsize, elsize, + elproc)); +} + +#endif /* SPL_RPC_XDR_H */ diff --git a/include/os/macos/spl/stddef.h b/include/os/macos/spl/stddef.h new file mode 100644 index 000000000000..2df5b2523ffc --- /dev/null +++ b/include/os/macos/spl/stddef.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 (C) 2021 Jorgen Lundman + * + */ + +#ifndef _SPL_STDDEF_H +#define _SPL_STDDEF_H + +#include +#include +#if defined(MAC_OS_X_VERSION_10_12) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) +#include_next +#endif + +/* Older macOS does not have size_t in stddef.h */ +#include + +#endif diff --git a/include/os/macos/spl/string.h b/include/os/macos/spl/string.h new file mode 100644 index 000000000000..15c1b30a405f --- /dev/null +++ b/include/os/macos/spl/string.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 + */ +#ifndef _SPL_STRING_H +#define _SPL_STRING_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include_next + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/os/macos/spl/sys/Makefile.am b/include/os/macos/spl/sys/Makefile.am new file mode 100644 index 000000000000..570377456952 --- /dev/null +++ b/include/os/macos/spl/sys/Makefile.am @@ -0,0 +1,49 @@ +KERNEL_H = \ + $(top_srcdir)/include/os/macos/spl/sys/atomic.h \ + $(top_srcdir)/include/os/macos/spl/sys/byteorder.h \ + $(top_srcdir)/include/os/macos/spl/sys/callb.h \ + $(top_srcdir)/include/os/macos/spl/sys/cmn_err.h \ + $(top_srcdir)/include/os/macos/spl/sys/condvar.h \ + $(top_srcdir)/include/os/macos/spl/sys/console.h \ + $(top_srcdir)/include/os/macos/spl/sys/cred.h \ + $(top_srcdir)/include/os/macos/spl/sys/debug.h \ + $(top_srcdir)/include/os/macos/spl/sys/errno.h \ + $(top_srcdir)/include/os/macos/spl/sys/fcntl.h \ + $(top_srcdir)/include/os/macos/spl/sys/file.h \ + $(top_srcdir)/include/os/macos/spl/sys/inttypes.h \ + $(top_srcdir)/include/os/macos/spl/sys/isa_defs.h \ + $(top_srcdir)/include/os/macos/spl/sys/kmem.h \ + $(top_srcdir)/include/os/macos/spl/sys/kmem_impl.h \ + $(top_srcdir)/include/os/macos/spl/sys/kstat.h \ + $(top_srcdir)/include/os/macos/spl/sys/list.h \ + $(top_srcdir)/include/os/macos/spl/sys/mod_os.h \ + $(top_srcdir)/include/os/macos/spl/sys/mutex.h \ + $(top_srcdir)/include/os/macos/spl/sys/param.h \ + $(top_srcdir)/include/os/macos/spl/sys/policy.h \ + $(top_srcdir)/include/os/macos/spl/sys/priv.h \ + $(top_srcdir)/include/os/macos/spl/sys/proc.h \ + $(top_srcdir)/include/os/macos/spl/sys/processor.h \ + $(top_srcdir)/include/os/macos/spl/sys/random.h \ + $(top_srcdir)/include/os/macos/spl/sys/rwlock.h \ + $(top_srcdir)/include/os/macos/spl/sys/seg_kmem.h \ + $(top_srcdir)/include/os/macos/spl/sys/signal.h \ + $(top_srcdir)/include/os/macos/spl/sys/stropts.h \ + $(top_srcdir)/include/os/macos/spl/sys/sunddi.h \ + $(top_srcdir)/include/os/macos/spl/sys/sysmacros.h \ + $(top_srcdir)/include/os/macos/spl/sys/systeminfo.h \ + $(top_srcdir)/include/os/macos/spl/sys/systm.h \ + $(top_srcdir)/include/os/macos/spl/sys/taskq.h \ + $(top_srcdir)/include/os/macos/spl/sys/taskq_impl.h \ + $(top_srcdir)/include/os/macos/spl/sys/thread.h \ + $(top_srcdir)/include/os/macos/spl/sys/time.h \ + $(top_srcdir)/include/os/macos/spl/sys/timer.h \ + $(top_srcdir)/include/os/macos/spl/sys/tsd.h \ + $(top_srcdir)/include/os/macos/spl/sys/types.h \ + $(top_srcdir)/include/os/macos/spl/sys/utsname.h \ + $(top_srcdir)/include/os/macos/spl/sys/varargs.h \ + $(top_srcdir)/include/os/macos/spl/sys/vfs.h \ + $(top_srcdir)/include/os/macos/spl/sys/vmem.h \ + $(top_srcdir)/include/os/macos/spl/sys/vmem_impl.h \ + $(top_srcdir)/include/os/macos/spl/sys/vmsystm.h \ + $(top_srcdir)/include/os/macos/spl/sys/vnode.h \ + $(top_srcdir)/include/os/macos/spl/sys/zone.h diff --git a/include/os/macos/spl/sys/aarch64/asm_linkage.h b/include/os/macos/spl/sys/aarch64/asm_linkage.h new file mode 100644 index 000000000000..8885e501045f --- /dev/null +++ b/include/os/macos/spl/sys/aarch64/asm_linkage.h @@ -0,0 +1,73 @@ + +/* + * 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 https://opensource.org/licenses/CDDL-1.0. + * 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 _AARCH64_SYS_ASM_LINKAGE_H +#define _AARCH64_SYS_ASM_LINKAGE_H + +/* You can set to nothing on Unix platforms */ +#undef ASMABI +#define ASMABI __attribute__((sysv_abi)) + +#define SECTION_TEXT .text +#define SECTION_STATIC .const +#define SECTION_STATIC1(...) .const +// #define SECTION_STATIC1(x) .rodata##x + +#define ASM_ENTRY_ALIGN 16 + +#define PAGE @PAGE +#define PAGEOFF @PAGEOFF +// Linux has them empty +// #define PAGE +// #define PAGEOFF + +/* + * semi-colon is comment, so use secret %% + * M1 is 64 bit only + * and needs "_" prepended, but we add one without, in case + * the assembler function needs to call itself + */ +#define ENTRY(x) \ + .text %% \ + .balign ASM_ENTRY_ALIGN %% \ + .globl _##x %% \ +_##x: %% \ +x: \ + bti c // hint #34 + +#define ENTRY_ALIGN(x, a) \ + .text %% \ + .balign a %% \ + .globl _##x %% \ +_##x: %% \ +x: \ + bti c // hint #34 + +#define FUNCTION(x) \ +x: + +#define SET_SIZE(x) + +#define SET_OBJ(x) + + +#endif diff --git a/include/os/macos/spl/sys/acl.h b/include/os/macos/spl/sys/acl.h new file mode 100644 index 000000000000..840ba7f43cb8 --- /dev/null +++ b/include/os/macos/spl/sys/acl.h @@ -0,0 +1,127 @@ +/* + * 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. + */ + +#ifndef _SPL_ACL_H +#define _SPL_ACL_H + +#include + +typedef struct ace { + uid_t a_who; + uint32_t a_access_mask; + uint16_t a_flags; + uint16_t a_type; +} ace_t; + +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; + +#define MAX_ACL_ENTRIES 1024 + +#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) + +#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 + +#define ACE_TYPE_FLAGS (ACE_OWNER|ACE_GROUP|ACE_EVERYONE|ACE_IDENTIFIER_GROUP) + +#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) + +#define VSA_ACE 0x0010 +#define VSA_ACECNT 0x0020 +#define VSA_ACE_ALLTYPES 0x0040 +#define VSA_ACE_ACLFLAGS 0x0080 + +typedef struct trivial_acl { + uint32_t allow0; /* allow mask for bits only in owner */ + uint32_t deny1; /* deny mask for bits not in owner */ + uint32_t deny2; /* deny mask for bits not in group */ + uint32_t owner; /* allow mask matching mode */ + uint32_t group; /* allow mask matching mode */ + uint32_t everyone; /* allow mask matching mode */ +} trivial_acl_t; + +#endif /* _SPL_ACL_H */ diff --git a/include/os/macos/spl/sys/atomic.h b/include/os/macos/spl/sys/atomic.h new file mode 100644 index 000000000000..982fc3cdc837 --- /dev/null +++ b/include/os/macos/spl/sys/atomic.h @@ -0,0 +1,413 @@ +/* + * 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 + */ + +/* + * + * OSX Atomic functions using clang builtins. + * + * Jorgen Lundman + * + */ + +#ifndef _SPL_ATOMIC_H +#define _SPL_ATOMIC_H +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Increment target + */ + +#define ATOMIC_INC(name, type) \ + static inline __attribute__((always_inline)) \ + void atomic_inc_##name(volatile type *target) \ + { \ + (void) __atomic_add_fetch(target, 1, __ATOMIC_SEQ_CST); \ + } + + +ATOMIC_INC(8, uint8_t) +ATOMIC_INC(uchar, uchar_t) +ATOMIC_INC(16, uint16_t) +ATOMIC_INC(32, uint32_t) +ATOMIC_INC(64, uint64_t) + +/* BEGIN CSTYLED */ +ATOMIC_INC(ushort, ushort_t) +ATOMIC_INC(uint, uint_t) +ATOMIC_INC(ulong, ulong_t) +/* END CSTYLED */ + +#define ATOMIC_INC_NV(name, type) \ + static inline __attribute__((always_inline)) \ + type atomic_inc_##name##_nv(volatile type * target) \ + { \ + return (__atomic_add_fetch(target, 1, \ + __ATOMIC_SEQ_CST)); \ + } + + + +ATOMIC_INC_NV(8, uint8_t) +ATOMIC_INC_NV(uchar, uchar_t) +ATOMIC_INC_NV(16, uint16_t) +ATOMIC_INC_NV(32, uint32_t) +ATOMIC_INC_NV(64, uint64_t) + +/* BEGIN CSTYLED */ +ATOMIC_INC_NV(ushort, ushort_t) +ATOMIC_INC_NV(uint, uint_t) +ATOMIC_INC_NV(ulong, ulong_t) +/* END CSTYLED */ + +/* + * Decrement target + */ + + +#define ATOMIC_DEC(name, type) \ + static inline __attribute__((always_inline)) \ + void atomic_dec_##name(volatile type *target) \ + { \ + (void) __atomic_sub_fetch(target, 1, \ + __ATOMIC_SEQ_CST); \ + } + + + +ATOMIC_DEC(8, uint8_t) +ATOMIC_DEC(uchar, uchar_t) +ATOMIC_DEC(16, uint16_t) +ATOMIC_DEC(32, uint32_t) +ATOMIC_DEC(64, uint64_t) + +/* BEGIN CSTYLED */ +ATOMIC_DEC(uint, uint_t) +ATOMIC_DEC(ulong, ulong_t) +ATOMIC_DEC(ushort, ushort_t) +/* END CSTYLED */ + +#define ATOMIC_DEC_NV(name, type) \ + static inline __attribute__((always_inline)) \ + type atomic_dec_##name##_nv(volatile type *target) \ + { \ + return (__atomic_sub_fetch(target, 1, \ + __ATOMIC_SEQ_CST)); \ + } + + +ATOMIC_DEC_NV(16, uint16_t) +ATOMIC_DEC_NV(32, uint32_t) +ATOMIC_DEC_NV(64, uint64_t) +ATOMIC_DEC_NV(8, uint8_t) +ATOMIC_DEC_NV(uchar, uchar_t) + +/* BEGIN CSTYLED */ +ATOMIC_DEC_NV(uint, uint_t) +ATOMIC_DEC_NV(ulong, ulong_t) +ATOMIC_DEC_NV(ushort, ushort_t) +/* END CSTYLED */ + +/* + * Add delta to target + */ + +#define ATOMIC_ADD(name, type1, type2) \ + static inline __attribute__((always_inline)) \ + void atomic_add_##name(volatile type1 *target, type2 bits) \ + { \ + (void) __atomic_add_fetch((volatile type2 *) target, \ + bits, __ATOMIC_SEQ_CST); \ + } + + +ATOMIC_ADD(16, uint16_t, int16_t) +ATOMIC_ADD(32, uint32_t, int32_t) +ATOMIC_ADD(64, uint64_t, int64_t) +ATOMIC_ADD(8, uint8_t, int8_t) +ATOMIC_ADD(char, uchar_t, signed char) + +/* BEGIN CSTYLED */ +ATOMIC_ADD(int, uint_t, int) +ATOMIC_ADD(long, ulong_t, long) +ATOMIC_ADD(short, ushort_t, short) +/* END CSTYLED */ + +#define ATOMIC_ADD_NV(name, type1, type2) \ + static inline __attribute__((always_inline)) \ + type1 atomic_add_##name##_nv(volatile type1 *target, \ + type2 x) \ + { \ + return ((type1)__atomic_add_fetch( \ + (volatile type2 *)target, x, \ + __ATOMIC_SEQ_CST)); \ + } + +ATOMIC_ADD_NV(16, uint16_t, int16_t) +ATOMIC_ADD_NV(32, uint32_t, int32_t) +ATOMIC_ADD_NV(64, uint64_t, int64_t) +ATOMIC_ADD_NV(8, uint8_t, int8_t) +ATOMIC_ADD_NV(char, uchar_t, signed char) + +/* BEGIN CSTYLED */ +ATOMIC_ADD_NV(int, uint_t, int) +ATOMIC_ADD_NV(long, ulong_t, long) +ATOMIC_ADD_NV(short, ushort_t, short) +/* END CSTYLED */ + +/* + * Subtract delta to target + */ + +#define ATOMIC_SUB(name, type1, type2) \ + static inline __attribute__((always_inline)) \ + void atomic_sub_##name(volatile type1 *target, type2 bits) \ + { \ + (void) __atomic_sub_fetch((volatile type2 *)target, \ + bits, __ATOMIC_SEQ_CST); \ + } + + +ATOMIC_SUB(16, uint16_t, int16_t) +ATOMIC_SUB(32, uint32_t, int32_t) +ATOMIC_SUB(64, uint64_t, int64_t) +ATOMIC_SUB(8, uint8_t, int8_t) +ATOMIC_SUB(char, uchar_t, signed char) + +/* BEGIN CSTYLED */ +ATOMIC_SUB(int, uint_t, int) +ATOMIC_SUB(long, ulong_t, long) +ATOMIC_SUB(short, ushort_t, short) +/* END CSTYLED */ + +#define ATOMIC_SUB_NV(name, type1, type2) \ + static inline __attribute__((always_inline)) \ + type1 atomic_sub_##name##_nv(volatile type1 *target, \ + type2 bits) \ + { \ + return ((type1) __atomic_sub_fetch( \ + (volatile type2 *)target, \ + bits, __ATOMIC_SEQ_CST)); \ + } + +ATOMIC_SUB_NV(16, uint16_t, int16_t) +ATOMIC_SUB_NV(32, uint32_t, int32_t) +ATOMIC_SUB_NV(64, uint64_t, int64_t) +ATOMIC_SUB_NV(8, uint8_t, int8_t) +ATOMIC_SUB_NV(char, uchar_t, signed char) + +/* BEGIN CSTYLED */ +ATOMIC_SUB_NV(int, uint_t, int) +ATOMIC_SUB_NV(long, ulong_t, long) +ATOMIC_SUB_NV(short, ushort_t, short) +/* END CSTYLED */ + +/* + * logical OR bits with target + */ + +#define ATOMIC_OR(name, type) \ + static inline __attribute__((always_inline)) \ + void atomic_or_##name(volatile type *target, type bits) \ + { \ + (void) __atomic_or_fetch(target, bits, __ATOMIC_SEQ_CST); \ + } + + +ATOMIC_OR(16, uint16_t) +ATOMIC_OR(32, uint32_t) +ATOMIC_OR(64, uint64_t) +ATOMIC_OR(8, uint8_t) +ATOMIC_OR(uchar, uchar_t) + +/* BEGIN CSTYLED */ +ATOMIC_OR(uint, uint_t) +ATOMIC_OR(ulong, ulong_t) +ATOMIC_OR(ushort, ushort_t) +/* END CSTYLED */ + +#define ATOMIC_OR_NV(name, type) \ + static inline __attribute__((always_inline)) \ + type atomic_or_##name##_nv(volatile type *target, type bits) \ + { \ + return (__atomic_or_fetch(target, bits, __ATOMIC_SEQ_CST)); \ + } + +ATOMIC_OR_NV(16, uint16_t) +ATOMIC_OR_NV(32, uint32_t) +ATOMIC_OR_NV(64, uint64_t) +ATOMIC_OR_NV(8, uint8_t) +ATOMIC_OR_NV(uchar, uchar_t) + +/* BEGIN CSTYLED */ +ATOMIC_OR_NV(uint, uint_t) +ATOMIC_OR_NV(ulong, ulong_t) +ATOMIC_OR_NV(ushort, ushort_t) +/* END CSTYLED */ + +/* + * logical AND bits with target + */ + +#define ATOMIC_AND(name, type) \ + static inline __attribute__((always_inline)) \ + void atomic_and_##name(volatile type *target, type bits) \ + { \ + (void) __atomic_and_fetch(target, bits, __ATOMIC_SEQ_CST); \ + } + +ATOMIC_AND(16, uint16_t) +ATOMIC_AND(32, uint32_t) +ATOMIC_AND(64, uint64_t) +ATOMIC_AND(8, uint8_t) +ATOMIC_AND(uchar, uchar_t) + +/* BEGIN CSTYLED */ +ATOMIC_AND(uint, uint_t) +ATOMIC_AND(ulong, ulong_t) +ATOMIC_AND(ushort, ushort_t) +/* END CSTYLED */ + +/* + * Compare And Set + * if *arg1 == arg2, then set *arg1 = arg3; return old value. + */ + +#define ATOMIC_CAS(name, type) \ + static inline __attribute__((always_inline)) \ + type atomic_cas_##name(volatile type *target, type exp, type des) \ + { \ + __atomic_compare_exchange_n(target, \ + &exp, des, B_FALSE, \ + __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); \ + return (exp); \ + } + +ATOMIC_CAS(16, uint16_t) +ATOMIC_CAS(32, uint32_t) +ATOMIC_CAS(64, uint64_t) +ATOMIC_CAS(8, uint8_t) +ATOMIC_CAS(uchar, uchar_t) + +/* BEGIN CSTYLED */ +ATOMIC_CAS(uint, uint_t) +ATOMIC_CAS(ulong, ulong_t) +ATOMIC_CAS(ushort, ushort_t) +/* END CSTYLED */ + +#define ATOMIC_SWAP(name, type) \ + static inline __attribute__((always_inline)) \ + type atomic_swap_##name(volatile type *target, type bits) \ + { \ + return (__atomic_exchange_n(target, bits, __ATOMIC_SEQ_CST)); \ + } + +ATOMIC_SWAP(16, uint16_t) +ATOMIC_SWAP(32, uint32_t) +ATOMIC_SWAP(64, uint64_t) +ATOMIC_SWAP(8, uint8_t) +ATOMIC_SWAP(uchar, uchar_t) + +/* BEGIN CSTYLED */ +ATOMIC_SWAP(uint, uint_t) +ATOMIC_SWAP(ulong, ulong_t) +ATOMIC_SWAP(ushort, ushort_t) +/* END CSTYLED */ + +extern void * +atomic_cas_ptr(volatile void *target, void *cmp, void *newval); + +static inline uint64_t +atomic_load_64(volatile uint64_t *target) +{ + return (__atomic_load_n(target, __ATOMIC_ACQUIRE)); +} + +static inline void +atomic_store_64(volatile uint64_t *target, uint64_t bits) +{ + return (__atomic_store_n(target, bits, __ATOMIC_RELEASE)); +} + +#if !defined(MAC_OS_X_VERSION_11) || \ + (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_11) +#define os_cast_to_atomic_pointer(p) \ + (__typeof__(*(p)) volatile _Atomic *)(uintptr_t)(p) +#define atomic_store(object, desired) \ + __c11_atomic_store(object, desired, __ATOMIC_SEQ_CST) +#define atomic_store_nonatomic(object, desired) \ + atomic_store(os_cast_to_atomic_pointer(object), desired) +#define atomic_load(object) __c11_atomic_load(object, __ATOMIC_SEQ_CST) +#define atomic_load_nonatomic(object) \ + atomic_load(os_cast_to_atomic_pointer(object)) +#define atomic_fetch_add(object, operand) \ + __c11_atomic_fetch_add(object, operand, __ATOMIC_SEQ_CST) +#define atomic_fetch_sub(object, operand) \ + __c11_atomic_fetch_sub(object, operand, __ATOMIC_SEQ_CST) +#else +#include +#endif + +static inline __attribute__((always_inline)) void +membar_producer(void) +{ + /* + * On arm, Xcode 14 clang produces "dmb ish" here, which + * matches the assembly in xnu's dtrace_membar_prodcer() + */ + __c11_atomic_thread_fence(__ATOMIC_SEQ_CST); +} + +static inline __attribute__((always_inline)) void +membar_consumer(void) +{ + __c11_atomic_thread_fence(__ATOMIC_ACQ_REL); +} + +/* + * Macro versions of full barrier. + * + * This is used to guard against possibly insufficiently-strong acquire + * semantics in the xnu calls to lck_mtx_{lock,unlock} and msleep in + * mutex_{enter,exit} resp. cv_wait\*. It is not expensive on Apple Silicon + * in our code base, but it's unnecessary in x86-64. + * + * xcode clang on Apple Silicon will make this a "dmb ish" and take care about + * its own ordering of operations. This provides an interproccessor total + * happens-before/happens-after ordering that x86-64 (and in the past, Sparc + * with TSO, total store ordering) gives us by default. + */ +#ifdef __arm64__ +#define spl_data_barrier() __atomic_thread_fence(__ATOMIC_SEQ_CST) +#else +#define spl_data_barrier() do {} while (0) +#endif + +#ifdef __cplusplus +} +#endif +#endif /* _SPL_ATOMIC_H */ diff --git a/include/os/macos/spl/sys/byteorder.h b/include/os/macos/spl/sys/byteorder.h new file mode 100644 index 000000000000..498e467f2718 --- /dev/null +++ b/include/os/macos/spl/sys/byteorder.h @@ -0,0 +1,70 @@ +/* + * 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 Jorgen Lundman + * + */ + +#ifndef _SPL_BYTEORDER_H +#define _SPL_BYTEORDER_H + +#include +#include + +#include +#include + +#define LE_16(x) OSSwapHostToLittleInt16(x) +#define LE_32(x) OSSwapHostToLittleInt32(x) +#define LE_64(x) OSSwapHostToLittleInt64(x) +#define BE_16(x) OSSwapHostToBigInt16(x) +#define BE_32(x) OSSwapHostToBigInt32(x) +#define BE_64(x) OSSwapHostToBigInt64(x) + +#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)) + + +/* 10.8 is lacking in htonll */ +#if !defined(htonll) +#define htonll(x) __DARWIN_OSSwapInt64(x) +#endif +#if !defined(ntohll) +#define ntohll(x) __DARWIN_OSSwapInt64(x) +#endif + +#ifdef __LITTLE_ENDIAN__ +#define _ZFS_LITTLE_ENDIAN +#endif + +#ifdef __BIG_ENDIAN__ +#define _ZFS_BIG_ENDIAN +#endif + +#endif /* SPL_BYTEORDER_H */ diff --git a/include/os/macos/spl/sys/callb.h b/include/os/macos/spl/sys/callb.h new file mode 100644 index 000000000000..3d86b9d41c05 --- /dev/null +++ b/include/os/macos/spl/sys/callb.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 (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 _SPL_CALLB_H +#define _SPL_CALLB_H + +#include + +#define CALLB_CPR_ASSERT(cp) ASSERT(MUTEX_HELD((cp)->cc_lockp)); + +typedef struct callb_cpr { + kmutex_t *cc_lockp; +} callb_cpr_t; + +#define CALLB_CPR_INIT(cp, lockp, func, name) { \ + (cp)->cc_lockp = lockp; \ +} + +#define CALLB_CPR_SAFE_BEGIN(cp) { \ + CALLB_CPR_ASSERT(cp); \ +} + +#define CALLB_CPR_SAFE_END(cp, lockp) { \ + CALLB_CPR_ASSERT(cp); \ +} + +#define CALLB_CPR_EXIT(cp) { \ + ASSERT(MUTEX_HELD((cp)->cc_lockp)); \ + mutex_exit((cp)->cc_lockp); \ +} + + +#define CALLOUT_FLAG_ROUNDUP 0x1 +#define CALLOUT_FLAG_ABSOLUTE 0x2 +#define CALLOUT_FLAG_HRESTIME 0x4 +#define CALLOUT_FLAG_32BIT 0x8 + +/* Move me to more correct "sys/callo.h" file when convenient. */ +#define CALLOUT_NORMAL 1 +typedef uint64_t callout_id_t; +callout_id_t timeout_generic(int, void (*)(void *), void *, hrtime_t, + hrtime_t, int); + +#endif /* _SPL_CALLB_H */ diff --git a/include/os/macos/spl/sys/cmn_err.h b/include/os/macos/spl/sys/cmn_err.h new file mode 100644 index 000000000000..e7ab5ce0cf7e --- /dev/null +++ b/include/os/macos/spl/sys/cmn_err.h @@ -0,0 +1,87 @@ +/* + * 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 2012 Nexenta Systems, Inc. All rights reserved. + */ + +#ifndef _SPL_CMN_ERR_H +#define _SPL_CMN_ERR_H + +#include +#include +#include + +#define CE_CONT 0 /* continuation */ +#define CE_NOTE 1 /* notice */ +#define CE_WARN 2 /* warning */ +#define CE_PANIC 3 /* panic */ +#define CE_IGNORE 4 /* print nothing */ + +#ifdef _KERNEL + +extern void vcmn_err(int, const char *, __va_list); +extern void cmn_err(int, const char *, ...); + +#define cmn_err_once(ce, ...) \ +{ \ + static volatile uint32_t printed = 0; \ + if (atomic_cas_32(&printed, 0, 1) == 0) { \ + cmn_err(ce, __VA_ARGS__); \ + } \ +} + +#define vcmn_err_once(ce, fmt, ap) \ +{ \ + static volatile uint32_t printed = 0; \ + if (atomic_cas_32(&printed, 0, 1) == 0) { \ + vcmn_err(ce, fmt, ap); \ + } \ +} + +#define zcmn_err_once(zone, ce, ...) \ +{ \ + static volatile uint32_t printed = 0; \ + if (atomic_cas_32(&printed, 0, 1) == 0) { \ + zcmn_err(zone, ce, __VA_ARGS__); \ + } \ +} + +#define vzcmn_err_once(zone, ce, fmt, ap) \ +{ \ + static volatile uint32_t printed = 0; \ + if (atomic_cas_32(&printed, 0, 1) == 0) { \ + vzcmn_err(zone, ce, fmt, ap); \ + } \ +} + +#endif /* _KERNEL */ + +#define fm_panic panic + +#endif /* SPL_CMN_ERR_H */ diff --git a/include/os/macos/spl/sys/condvar.h b/include/os/macos/spl/sys/condvar.h new file mode 100644 index 000000000000..8bf89b1be748 --- /dev/null +++ b/include/os/macos/spl/sys/condvar.h @@ -0,0 +1,105 @@ +/* + * 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 OSX_CONDVAR_H +#define OSX_CONDVAR_H + +#include +#include + +#define hz 100 /* sysctl kern.clockrate */ + +typedef enum { + CV_DEFAULT, + CV_DRIVER +} kcv_type_t; + + +struct cv { + uint64_t pad; +}; + +typedef struct cv kcondvar_t; + +void spl_cv_init(kcondvar_t *cvp, char *name, kcv_type_t type, void *arg); +void spl_cv_destroy(kcondvar_t *cvp); +void spl_cv_signal(kcondvar_t *cvp); +void spl_cv_broadcast(kcondvar_t *cvp); +int spl_cv_wait(kcondvar_t *cvp, kmutex_t *mp, int flags, const char *msg); +int spl_cv_timedwait(kcondvar_t *, kmutex_t *, clock_t, int, const char *msg); +int cv_timedwait_hires(kcondvar_t *cvp, kmutex_t *mp, + hrtime_t tim, hrtime_t res, int flag); + +/* + * Use these wrapper macros to obtain the CV variable + * name to make ZFS more gdb debugging friendly! + * This name shows up as a thread's wait_event string. + */ +#define cv_wait(cvp, mp) \ + (void) spl_cv_wait((cvp), (mp), PRIBIO, #cvp) + +#define cv_wait_io(cvp, mp) \ + (void) spl_cv_wait((cvp), (mp), PRIBIO, #cvp) + +#define cv_wait_idle(cvp, mp) \ + (void) spl_cv_wait((cvp), (mp), PRIBIO, #cvp) + +#define cv_timedwait(cvp, mp, tim) \ + spl_cv_timedwait((cvp), (mp), (tim), PRIBIO, #cvp) + +#define cv_timedwait_io(cvp, mp, tim) \ + spl_cv_timedwait((cvp), (mp), (tim), PRIBIO, #cvp) + +#define cv_timedwait_idle(cvp, mp, tim) \ + spl_cv_timedwait((cvp), (mp), (tim), PRIBIO, #cvp) + +#define cv_wait_interruptible(cvp, mp) \ + (void) spl_cv_wait((cvp), (mp), PRIBIO|PCATCH, #cvp) + +#define cv_timedwait_interruptible(cvp, mp, tim) \ + spl_cv_timedwait((cvp), (mp), (tim), PRIBIO|PCATCH, #cvp) + +/* cv_wait_sig is the correct name for cv_wait_interruptible */ +#define cv_wait_sig(cvp, mp) \ + spl_cv_wait((cvp), (mp), PRIBIO|PCATCH, #cvp) + +#define cv_wait_io_sig(cvp, mp) \ + spl_cv_wait((cvp), (mp), PRIBIO|PCATCH, #cvp) + +#define cv_timedwait_sig(cvp, mp, tim) \ + spl_cv_timedwait((cvp), (mp), (tim), PRIBIO|PCATCH, #cvp) + +#define TICK_TO_NSEC(tick) ((hrtime_t)(tick) * 1000000000 / hz) +#define cv_reltimedwait(cvp, mp, tim, type) \ + cv_timedwait_hires((cvp), (mp), TICK_TO_NSEC((tim)), 0, 0) + +#define cv_timedwait_sig_hires(cvp, mp, tim, res, flag) \ + cv_timedwait_hires(cvp, mp, tim, res, (flag)|PCATCH) + +#define cv_timedwait_idle_hires(cvp, mp, tim, res, flag) \ + cv_timedwait_hires(cvp, mp, tim, res, (flag)|PCATCH) + +#define cv_init spl_cv_init +#define cv_destroy spl_cv_destroy +#define cv_broadcast spl_cv_broadcast +#define cv_signal spl_cv_signal + +#endif diff --git a/include/os/macos/spl/sys/console.h b/include/os/macos/spl/sys/console.h new file mode 100644 index 000000000000..57c962210512 --- /dev/null +++ b/include/os/macos/spl/sys/console.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 + */ + +#ifndef _SPL_SYS_CONSOLE_H +#define _SPL_SYS_CONSOLE_H + +static inline void +console_vprintf(const char *fmt, va_list args) +{ + vprintf(fmt, args); +} + +static inline void +console_printf(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + console_vprintf(fmt, args); + va_end(args); +} + +#endif /* _SPL_SYS_CONSOLE_H */ diff --git a/include/os/macos/spl/sys/cred.h b/include/os/macos/spl/sys/cred.h new file mode 100644 index 000000000000..4f97eec56fc4 --- /dev/null +++ b/include/os/macos/spl/sys/cred.h @@ -0,0 +1,72 @@ +/* + * 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 Jorgen Lundman + * + */ + +#ifndef _SPL_CRED_H +#define _SPL_CRED_H + +#include +#include +#include + +typedef struct ucred cred_t; + +#define kcred spl_kcred() +#define CRED() (cred_t *)kauth_cred_get() +#define KUID_TO_SUID(x) (x) +#define KGID_TO_SGID(x) (x) + +#include +#include + +// Older OSX API +#if !(MAC_OS_X_VERSION_MIN_REQUIRED >= 1070) +#define kauth_cred_getruid(x) (x)->cr_ruid +#define kauth_cred_getrgid(x) (x)->cr_rgid +#define kauth_cred_getsvuid(x) (x)->cr_svuid +#define kauth_cred_getsvgid(x) (x)->cr_svgid +#endif + + +extern void crhold(cred_t *cr); +extern void crfree(cred_t *cr); +extern uid_t crgetuid(const cred_t *cr); +extern uid_t crgetruid(const cred_t *cr); +extern uid_t crgetsuid(const cred_t *cr); +extern uid_t crgetfsuid(const cred_t *cr); +extern gid_t crgetgid(const cred_t *cr); +extern gid_t crgetrgid(const cred_t *cr); +extern gid_t crgetsgid(const cred_t *cr); +extern gid_t crgetfsgid(const cred_t *cr); +extern int crgetngroups(const cred_t *cr); +extern gid_t *crgetgroups(const cred_t *cr); +extern void crgetgroupsfree(gid_t *gids); +extern int spl_cred_ismember_gid(cred_t *cr, gid_t gid); +extern cred_t *spl_kcred(void); + +#define crgetsid(cred, i) (NULL) + +#endif /* _SPL_CRED_H */ diff --git a/include/os/macos/spl/sys/ctype.h b/include/os/macos/spl/sys/ctype.h new file mode 100644 index 000000000000..74554873305c --- /dev/null +++ b/include/os/macos/spl/sys/ctype.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, 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 _SPL_CTYPE_H +#define _SPL_CTYPE_H + +#define iscntrl(C) (uchar(C) <= 0x1f || uchar(C) == 0x7f) + +#endif diff --git a/include/os/macos/spl/sys/debug.h b/include/os/macos/spl/sys/debug.h new file mode 100644 index 000000000000..7c110503e268 --- /dev/null +++ b/include/os/macos/spl/sys/debug.h @@ -0,0 +1,299 @@ +/* + * 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$ + */ + +/* + * Available Solaris debug functions. All of the ASSERT() macros will be + * compiled out when NDEBUG is defined, this is the default behavior for + * the SPL. To enable assertions use the --enable-debug with configure. + * The VERIFY() functions are never compiled out and cannot be disabled. + * + * PANIC() - Panic the node and print message. + * ASSERT() - Assert X is true, if not panic. + * ASSERT3B() - Assert boolean X OP Y is true, if not panic. + * ASSERT3S() - Assert signed X OP Y is true, if not panic. + * ASSERT3U() - Assert unsigned X OP Y is true, if not panic. + * ASSERT3P() - Assert pointer X OP Y is true, if not panic. + * ASSERT0() - Assert value is zero, if not panic. + * ASSERT0P() - Assert pointer is null, if not panic. + * VERIFY() - Verify X is true, if not panic. + * VERIFY3B() - Verify boolean X OP Y is true, if not panic. + * VERIFY3S() - Verify signed X OP Y is true, if not panic. + * VERIFY3U() - Verify unsigned X OP Y is true, if not panic. + * VERIFY3P() - Verify pointer X OP Y is true, if not panic. + * VERIFY0() - Verify value is zero, if not panic. + * VERIFY0P() - Verify pointer is null, if not panic. + */ + +#ifndef _SPL_DEBUG_H +#define _SPL_DEBUG_H + +#include + +/* SPL has own 'dprintf' as zfs_debug.c version uses mutex */ +#ifdef __cplusplus +extern "C" { +#endif + +extern int zfs_flags; + +/* Simple dprintf for SPL only */ +#ifndef dprintf +#define dprintf(...) \ + if (zfs_flags & 1) \ + printf(__VA_ARGS__) +#endif + +#ifndef __printflike +#define __printflike(a, b) __attribute__((__format__(__printf__, a, b))) +#endif + +/* + * Common DEBUG functionality. + */ +int spl_panic(const char *file, const char *func, int line, + const char *fmt, ...); +void spl_dumpstack(void); + +void spl_backtrace(char *thesignal); +int getpcstack(uintptr_t *pcstack, int pcstack_limit); +void print_symbol(uintptr_t symbol); + +#ifndef expect +#define expect(expr, value) (__builtin_expect((expr), (value))) +#endif +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +#ifndef __maybe_unused +#define __maybe_unused __attribute__((unused)) +#endif + +/* BEGIN CSTYLED */ +#define PANIC(fmt, a...) \ + spl_panic(__FILE__, __FUNCTION__, __LINE__, fmt, ## a) + +#define VERIFY(cond) \ + (void) (unlikely(!(cond)) && \ + spl_panic(__FILE__, __FUNCTION__, __LINE__, \ + "%s", "VERIFY(" #cond ") failed\n")) + +#define VERIFY3B(LEFT, OP, RIGHT) do { \ + boolean_t _verify3_left = (boolean_t)(LEFT); \ + boolean_t _verify3_right = (boolean_t)(RIGHT); \ + if (!(_verify3_left OP _verify3_right)) \ + spl_panic(__FILE__, __FUNCTION__, __LINE__, \ + "VERIFY3(" #LEFT " " #OP " " #RIGHT ") " \ + "failed (%d " #OP " %d)\n", \ + (boolean_t) (_verify3_left), \ + (boolean_t) (_verify3_right)); \ + } while (0) + +#define VERIFY3S(LEFT, OP, RIGHT) do { \ + int64_t _verify3_left = (int64_t)(LEFT); \ + int64_t _verify3_right = (int64_t)(RIGHT); \ + if (!(_verify3_left OP _verify3_right)) \ + spl_panic(__FILE__, __FUNCTION__, __LINE__, \ + "VERIFY3(" #LEFT " " #OP " " #RIGHT ") " \ + "failed (%lld " #OP " %lld)\n", \ + (long long) (_verify3_left), \ + (long long) (_verify3_right)); \ + } while (0) + +#define VERIFY3U(LEFT, OP, RIGHT) do { \ + uint64_t _verify3_left = (uint64_t)(LEFT); \ + uint64_t _verify3_right = (uint64_t)(RIGHT); \ + if (!(_verify3_left OP _verify3_right)) \ + spl_panic(__FILE__, __FUNCTION__, __LINE__, \ + "VERIFY3(" #LEFT " " #OP " " #RIGHT ") " \ + "failed (%llu " #OP " %llu)\n", \ + (unsigned long long) (_verify3_left), \ + (unsigned long long) (_verify3_right)); \ + } while (0) + +#define VERIFY3P(LEFT, OP, RIGHT) do { \ + uintptr_t _verify3_left = (uintptr_t)(LEFT); \ + uintptr_t _verify3_right = (uintptr_t)(RIGHT); \ + if (!(_verify3_left OP _verify3_right)) \ + spl_panic(__FILE__, __FUNCTION__, __LINE__, \ + "VERIFY3(" #LEFT " " #OP " " #RIGHT ") " \ + "failed (%p " #OP " %p)\n", \ + (void *) (_verify3_left), \ + (void *) (_verify3_right)); \ + } while (0) + +#define VERIFY0(RIGHT) do { \ + int64_t _verify3_left = (int64_t)(0); \ + int64_t _verify3_right = (int64_t)(RIGHT); \ + if (!(_verify3_left == _verify3_right)) \ + spl_panic(__FILE__, __FUNCTION__, __LINE__, \ + "VERIFY3(0 == " #RIGHT ") " \ + "failed (0 == %lld)\n", \ + (long long) (_verify3_right)); \ + } while (0) + +#define VERIFY0P(RIGHT) do { \ + const uintptr_t _verify0_right = (uintptr_t)(RIGHT); \ + if (unlikely(!(0 == _verify0_right))) \ + spl_panic(__FILE__, __FUNCTION__, __LINE__, \ + "VERIFY0P(" #RIGHT ") " \ + "failed (NULL == %p)\n", \ + (void *)_verify0_right); \ + } while (0) + +#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] + + + +/* + * Debugging disabled (--disable-debug) + */ +#ifdef NDEBUG + +/* + * To avoid "variable 'x' set but not used" with all ASSERTS + */ + +#define ASSERT(x) ((void) sizeof ((uintptr_t)(x))) +#define ASSERT3B(x, y, z) \ + ((void) sizeof ((uintptr_t)(x)), (void) sizeof ((uintptr_t)(z))) +#define ASSERT3S(x, y, z) \ + ((void) sizeof ((uintptr_t)(x)), (void) sizeof ((uintptr_t)(z))) +#define ASSERT3U(x, y, z) \ + ((void) sizeof ((uintptr_t)(x)), (void) sizeof ((uintptr_t)(z))) +#define ASSERT3P(x, y, z) \ + ((void) sizeof ((uintptr_t)(x)), (void) sizeof ((uintptr_t)(z))) +#define ASSERT0(x) ((void) sizeof ((uintptr_t)(x))) +#define ASSERT0P(x) ((void) sizeof ((uintptr_t)(x))) +#define IMPLY(A, B) \ + ((void) sizeof ((uintptr_t)(A)), (void) sizeof ((uintptr_t)(B))) +#define EQUIV(A, B) \ + ((void) sizeof ((uintptr_t)(A)), (void) sizeof ((uintptr_t)(B))) +#define ASSERTV(X) __maybe_unused X + + +/* + * Debugging enabled (--enable-debug) + */ +#else + +#ifdef MACOS_ASSERT_SHOULD_PANIC +#define ASSERT3B VERIFY3B +#define ASSERT3S VERIFY3S +#define ASSERT3U VERIFY3U +#define ASSERT3P VERIFY3P +#define ASSERT0 VERIFY0 +#define ASSERT0P VERIFY0P +#define ASSERT VERIFY +#define ASSERTV(X) X __maybe_unused +#define IMPLY(A, B) \ + ((void)(((!(A)) || (B)) || \ + spl_panic(__FILE__, __FUNCTION__, __LINE__, \ + "(" #A ") implies (" #B ")"))) +#define EQUIV(A, B) \ + ((void)((!!(A) == !!(B)) || \ + spl_panic(__FILE__, __FUNCTION__, __LINE__, \ + "(" #A ") is equivalent to (" #B ")"))) +/* END CSTYLED */ +#else /* MACOS_ASSERT_SHOULD_PANIC */ + +#define PRINT printf + +__attribute__((noinline)) int assfail(const char *str, const char *file, + unsigned int line) __attribute__((optnone)); + +#define ASSERT(cond) \ + (void) (unlikely(!(cond)) && assfail(#cond, __FILE__, __LINE__) && \ + PRINT("ZFS: %s %s %d : %s\n", __FILE__, __FUNCTION__, __LINE__, \ + "ASSERTION(" #cond ") failed\n")) + +#define ASSERT3_IMPL(LEFT, OP, RIGHT, TYPE, FMT, CAST) \ + do { \ + if (!((TYPE)(LEFT) OP(TYPE)(RIGHT)) && \ + assfail(#LEFT #OP #RIGHT, __FILE__, __LINE__)) \ + PRINT("ZFS: %s %s %d : ASSERT3( %s " #OP " %s) " \ + "failed (" FMT " " #OP " " FMT ")\n", \ + __FILE__, __FUNCTION__, \ + __LINE__, #LEFT, #RIGHT, \ + CAST(LEFT), CAST(RIGHT)); \ + } while (0) + + +#define ASSERTF(cond, fmt, a...) \ + do { \ + if (unlikely(!(cond))) \ + panic("ZFS: ASSERTION(" #cond ") failed: " fmt, ## a); \ + } while (0) + + +#define ASSERT3B(x, y, z) ASSERT3_IMPL(x, y, z, int64_t, "%u", \ + (boolean_t)) +#define ASSERT3S(x, y, z) ASSERT3_IMPL(x, y, z, int64_t, "%lld", \ + (long long)) +#define ASSERT3U(x, y, z) ASSERT3_IMPL(x, y, z, uint64_t, "%llu", \ + (unsigned long long)) + +#define ASSERT3P(x, y, z) ASSERT3_IMPL(x, y, z, uintptr_t, "%p", (void *)) +#define ASSERT0(x) ASSERT3_IMPL(0, ==, x, int64_t, "%lld", (long long)) +#define ASSERT0P(x) ASSERT3_IMPL(0, ==, x, uintptr_t, "%p", (void *)) +#define ASSERTV(x) x + + +/* + * IMPLY and EQUIV are assertions of the form: + * + * if (a) then (b) + * and + * if (a) then (b) *AND* if (b) then (a) + */ +static inline int +spl_implyout(const char *buf, const char *file, const char *func, int line) +{ + spl_panic(file, func, line, "%s", buf); + return (0); +} + +#define IMPLY(A, B) \ + ((void) sizeof ((uintptr_t)(A)), (void) sizeof ((uintptr_t)(B))) +#define EQUIV(A, B) \ + ((void) sizeof ((uintptr_t)(A)), (void) sizeof ((uintptr_t)(B))) + +#endif /* MACOS_ASSERT_SHOULD_PANIC */ +#endif /* NDEBUG */ + +#define zfs_fallthrough __attribute__((__fallthrough__)) + +#ifdef __cplusplus +} +#endif + +#endif /* SPL_DEBUG_H */ diff --git a/include/os/macos/spl/sys/disp.h b/include/os/macos/spl/sys/disp.h new file mode 100644 index 000000000000..3b1bcbb25cc9 --- /dev/null +++ b/include/os/macos/spl/sys/disp.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 _SPL_DISP_H +#define _SPL_DISP_H + +#endif diff --git a/include/os/macos/spl/sys/dkio.h b/include/os/macos/spl/sys/dkio.h new file mode 100644 index 000000000000..d10314b3e427 --- /dev/null +++ b/include/os/macos/spl/sys/dkio.h @@ -0,0 +1,527 @@ +/* + * 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 + * + * $FreeBSD$ + */ +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _OPENSOLARIS_SYS_DKIO_H_ +#define _OPENSOLARIS_SYS_DKIO_H_ + +#include /* Needed for NDKMAP define */ +#include /* Needed for NDKMAP define */ + +#ifdef __cplusplus +extern "C" { +#endif + +#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 + +/* + * 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 */ + uint_t dki_slave; /* slave 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 */ + +/* + * 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 */ +}; + +/* + * 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 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 + + +/* + * 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 */ + +#define DKIOC (0x04 << 8) +#define DKIOCTRIM (DKIOC | 35) + +/* + * ioctl to free space (e.g. SCSI UNMAP) off a disk. + * Pass a dkioc_free_list_t containing a list of extents to be freed. + */ +#define DKIOCFREE (DKIOC|50) + +#define DF_WAIT_SYNC 0x00000001 /* Wait for full write-out of free. */ + +typedef struct dkioc_free_list_ext_s { + uint64_t dfle_start; + uint64_t dfle_length; +} dkioc_free_list_ext_t; + +typedef struct dkioc_free_list_s { + uint64_t dfl_flags; + uint64_t dfl_num_exts; + uint64_t dfl_offset; + dkioc_free_list_ext_t dfl_exts[1]; +} dkioc_free_list_t; +#define DFL_SZ(num_exts) \ + (sizeof (dkioc_free_list_t) + \ + (num_exts - 1) * sizeof (dkioc_free_list_ext_t)) + +/* Frees a variable-length dkioc_free_list_t structure. */ +static inline void +dfl_free(dkioc_free_list_t *dfl) +{ + kmem_free(dfl, DFL_SZ(dfl->dfl_num_exts)); +} + +#ifdef __cplusplus +} +#endif + +#endif /* _OPENSOLARIS_SYS_DKIO_H_ */ diff --git a/include/os/macos/spl/sys/errno.h b/include/os/macos/spl/sys/errno.h new file mode 100644 index 000000000000..67574721cc5c --- /dev/null +++ b/include/os/macos/spl/sys/errno.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 + */ + +#include_next + +#define EBADE EBADMACHO +#define ECKSUM EBADE +#define EFRAGS EIDRM +#define EREMOTEIO ENOLINK +#define ENOTACTIVE ENOPOLICY +#define ECHRNG EMULTIHOP diff --git a/include/os/macos/spl/sys/fcntl.h b/include/os/macos/spl/sys/fcntl.h new file mode 100644 index 000000000000..1a4d8fb5474c --- /dev/null +++ b/include/os/macos/spl/sys/fcntl.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 (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 Jorgen Lundman + * + */ + +#ifndef _SPL_FCNTL_H +#define _SPL_FCNTL_H + +#include +#include +#if !defined(MAC_OS_X_VERSION_10_9) || \ + (MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_9) +#include +#endif + +#include_next + +#define F_FREESP 11 + +#define O_LARGEFILE 0 +#define O_RSYNC 0 +#define O_DIRECT 0 + +#endif /* _SPL_FCNTL_H */ diff --git a/include/os/macos/spl/sys/file.h b/include/os/macos/spl/sys/file.h new file mode 100644 index 000000000000..b648c6690e23 --- /dev/null +++ b/include/os/macos/spl/sys/file.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 (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 Jorgen Lundman + * + */ + +#ifndef _SPL_FILE_H +#define _SPL_FILE_H + +#define FIGNORECASE 0x00080000 +#define FKIOCTL 0x80000000 +#define ED_CASE_CONFLICT 0x10 + +#include + +/* + * XNU has all the proc structs as opaque and with no functions we + * are allowed to call, so we implement file IO from within the kernel + * as vnode operations. + * The second mode is when we are given a "fd" from userland, which we + * map in here, using getf()/releasef(). + * When it comes to IO, if "fd" is set, we use it (fo_rdwr()) as it + * can handle both files, and pipes. + * In kernel space file ops, we use vn_rdwr on the vnode. + */ +struct spl_fileproc { + void *f_vnode; /* underlying vnode */ + list_node_t f_next; /* * next getf() link for releasef() */ + int f_fd; /* * userland file descriptor */ + off_t f_offset; /* offset for stateful IO */ + void *f_proc; /* opaque */ + void *f_fp; /* opaque */ + int f_writes; /* did write? for close sync */ + int f_ioflags; /* IO_APPEND */ + minor_t f_file; /* minor of the file */ + void *f_private; /* zfsdev_state_t */ +}; +/* Members with '*' are not used when 'fd' is not given */ + +void releasefp(struct spl_fileproc *fp); +void *getf(int fd); +void releasef(int fd); + +struct vnode *getf_vnode(void *fp); + +#endif /* SPL_FILE_H */ diff --git a/include/os/macos/spl/sys/ia32/asm_linkage.h b/include/os/macos/spl/sys/ia32/asm_linkage.h new file mode 100644 index 000000000000..e55d4b440104 --- /dev/null +++ b/include/os/macos/spl/sys/ia32/asm_linkage.h @@ -0,0 +1,185 @@ +/* + * 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 https://opensource.org/licenses/CDDL-1.0. + * 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 _IA32_SYS_ASM_LINKAGE_H +#define _IA32_SYS_ASM_LINKAGE_H + +#ifndef ENDBR +#define ENDBR +#endif +#ifndef RET +#define RET ret +#endif + +/* You can set to nothing on Unix platforms */ +#undef ASMABI +#define ASMABI __attribute__((sysv_abi)) + +#define SECTION_TEXT .text +#define SECTION_STATIC .const + +#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 +#define XMM_ALIGN_LOG 16 + +/* + * 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. + */ +#undef ENTRY +#define ENTRY(x) \ + .text; \ + .balign ASM_ENTRY_ALIGN; \ + .globl x; \ +x: + +#define ENTRY_NP(x) \ + .text; \ + .balign ASM_ENTRY_ALIGN; \ + .globl _##x; \ +_##x: + + +#define ENTRY_ALIGN(x, a) \ + .text; \ + .balign a; \ + .globl _##x; \ +_##x: + +#define FUNCTION(x) \ +x: + +/* + * ENTRY2 is identical to ENTRY but provides two labels for the entry point. + */ +#define ENTRY2(x, y) \ + .text; \ + .balign ASM_ENTRY_ALIGN; \ + .globl x, y; \ +x:; \ +y: + +#define ENTRY_NP2(x, y) \ + .text; \ + .balign ASM_ENTRY_ALIGN; \ + .globl x, y; \ +x:; \ +y: + + +/* + * SET_SIZE trails a function and set the size for the ELF symbol table. + */ +#define SET_SIZE(x) + +#define SET_OBJ(x) + +#endif /* _ASM */ + +#ifdef __cplusplus +} +#endif + +#endif /* _IA32_SYS_ASM_LINKAGE_H */ diff --git a/include/os/macos/spl/sys/inttypes.h b/include/os/macos/spl/sys/inttypes.h new file mode 100644 index 000000000000..c9f6a316aa8b --- /dev/null +++ b/include/os/macos/spl/sys/inttypes.h @@ -0,0 +1,31 @@ +/* + * 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 Jorgen Lundman + * + */ + +#ifndef _SPL_INTTYPES_H +#define _SPL_INTTYPES_H + +#endif /* SPL_INTTYPES_H */ diff --git a/include/os/macos/spl/sys/isa_defs.h b/include/os/macos/spl/sys/isa_defs.h new file mode 100644 index 000000000000..f702dc51e10c --- /dev/null +++ b/include/os/macos/spl/sys/isa_defs.h @@ -0,0 +1,690 @@ +/* + * 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_ISA_DEFS_H +#define _SYS_ISA_DEFS_H + +/* + * This header file serves to group a set of well known defines and to + * set these for each instruction set architecture. These defines may + * be divided into two groups; characteristics of the processor and + * implementation choices for Solaris on a processor. + * + * Processor Characteristics: + * + * _LITTLE_ENDIAN / _BIG_ENDIAN: + * The natural byte order of the processor. A pointer to an int points + * to the least/most significant byte of that int. + * + * _STACK_GROWS_UPWARD / _STACK_GROWS_DOWNWARD: + * The processor specific direction of stack growth. A push onto the + * stack increases/decreases the stack pointer, so it stores data at + * successively higher/lower addresses. (Stackless machines ignored + * without regrets). + * + * _LONG_LONG_HTOL / _LONG_LONG_LTOH: + * A pointer to a long long points to the most/least significant long + * within that long long. + * + * _BIT_FIELDS_HTOL / _BIT_FIELDS_LTOH: + * The C compiler assigns bit fields from the high/low to the low/high end + * of an int (most to least significant vs. least to most significant). + * + * _IEEE_754: + * The processor (or supported implementations of the processor) + * supports the ieee-754 floating point standard. No other floating + * point standards are supported (or significant). Any other supported + * floating point formats are expected to be cased on the ISA processor + * symbol. + * + * _CHAR_IS_UNSIGNED / _CHAR_IS_SIGNED: + * The C Compiler implements objects of type `char' as `unsigned' or + * `signed' respectively. This is really an implementation choice of + * the compiler writer, but it is specified in the ABI and tends to + * be uniform across compilers for an instruction set architecture. + * Hence, it has the properties of a processor characteristic. + * + * _CHAR_ALIGNMENT / _SHORT_ALIGNMENT / _INT_ALIGNMENT / _LONG_ALIGNMENT / + * _LONG_LONG_ALIGNMENT / _DOUBLE_ALIGNMENT / _LONG_DOUBLE_ALIGNMENT / + * _POINTER_ALIGNMENT / _FLOAT_ALIGNMENT: + * The ABI defines alignment requirements of each of the primitive + * object types. Some, if not all, may be hardware requirements as + * well. The values are expressed in "byte-alignment" units. + * + * _MAX_ALIGNMENT: + * The most stringent alignment requirement as specified by the ABI. + * Equal to the maximum of all the above _XXX_ALIGNMENT values. + * + * _ALIGNMENT_REQUIRED: + * True or false (1 or 0) whether or not the hardware requires the ABI + * alignment. + * + * _LONG_LONG_ALIGNMENT_32 + * The 32-bit ABI supported by a 64-bit kernel may have different + * alignment requirements for primitive object types. The value of this + * identifier is expressed in "byte-alignment" units. + * + * _HAVE_CPUID_INSN + * This indicates that the architecture supports the 'cpuid' + * instruction as defined by Intel. (Intel allows other vendors + * to extend the instruction for their own purposes.) + * + * + * Implementation Choices: + * + * _ILP32 / _LP64: + * This specifies the compiler data type implementation as specified in + * the relevant ABI. The choice between these is strongly influenced + * by the underlying hardware, but is not absolutely tied to it. + * Currently only two data type models are supported: + * + * _ILP32: + * Int/Long/Pointer are 32 bits. This is the historical UNIX + * and Solaris implementation. Due to its historical standing, + * this is the default case. + * + * _LP64: + * Long/Pointer are 64 bits, Int is 32 bits. This is the chosen + * implementation for 64-bit ABIs such as SPARC V9. + * + * _I32LPx: + * A compilation environment where 'int' is 32-bit, and + * longs and pointers are simply the same size. + * + * In all cases, Char is 8 bits and Short is 16 bits. + * + * _SUNOS_VTOC_8 / _SUNOS_VTOC_16 / _SVR4_VTOC_16: + * This specifies the form of the disk VTOC (or label): + * + * _SUNOS_VTOC_8: + * This is a VTOC form which is upwardly compatible with the + * SunOS 4.x disk label and allows 8 partitions per disk. + * + * _SUNOS_VTOC_16: + * In this format the incore vtoc image matches the ondisk + * version. It allows 16 slices per disk, and is not + * compatible with the SunOS 4.x disk label. + * + * Note that these are not the only two VTOC forms possible and + * additional forms may be added. One possible form would be the + * SVr4 VTOC form. The symbol for that is reserved now, although + * it is not implemented. + * + * _SVR4_VTOC_16: + * This VTOC form is compatible with the System V Release 4 + * VTOC (as implemented on the SVr4 Intel and 3b ports) with + * 16 partitions per disk. + * + * + * _DMA_USES_PHYSADDR / _DMA_USES_VIRTADDR + * This describes the type of addresses used by system DMA: + * + * _DMA_USES_PHYSADDR: + * This type of DMA, used in the x86 implementation, + * requires physical addresses for DMA buffers. The 24-bit + * addresses used by some legacy boards is the source of the + * "low-memory" (<16MB) requirement for some devices using DMA. + * + * _DMA_USES_VIRTADDR: + * This method of DMA allows the use of virtual addresses for + * DMA transfers. + * + * _FIRMWARE_NEEDS_FDISK / _NO_FDISK_PRESENT + * This indicates the presence/absence of an fdisk table. + * + * _FIRMWARE_NEEDS_FDISK + * The fdisk table is required by system firmware. If present, + * it allows a disk to be subdivided into multiple fdisk + * partitions, each of which is equivalent to a separate, + * virtual disk. This enables the co-existence of multiple + * operating systems on a shared hard disk. + * + * _NO_FDISK_PRESENT + * If the fdisk table is absent, it is assumed that the entire + * media is allocated for a single operating system. + * + * _HAVE_TEM_FIRMWARE + * Defined if this architecture has the (fallback) option of + * using prom_* calls for doing I/O if a suitable kernel driver + * is not available to do it. + * + * _DONT_USE_1275_GENERIC_NAMES + * Controls whether or not device tree node names should + * comply with the IEEE 1275 "Generic Names" Recommended + * Practice. With _DONT_USE_GENERIC_NAMES, device-specific + * names identifying the particular device will be used. + * + * __i386_COMPAT + * This indicates whether the i386 ABI is supported as a *non-native* + * mode for the platform. When this symbol is defined: + * - 32-bit xstat-style system calls are enabled + * - 32-bit xmknod-style system calls are enabled + * - 32-bit system calls use i386 sizes -and- alignments + * + * Note that this is NOT defined for the i386 native environment! + * + * __x86 + * This is ONLY a synonym for defined(__i386) || defined(__amd64) + * which is useful only insofar as these two architectures share + * common attributes. Analogous to __sparc. + * + * _PSM_MODULES + * This indicates whether or not the implementation uses PSM + * modules for processor support, reading /etc/mach from inside + * the kernel to extract a list. + * + * _RTC_CONFIG + * This indicates whether or not the implementation uses /etc/rtc_config + * to configure the real-time clock in the kernel. + * + * _UNIX_KRTLD + * This indicates that the implementation uses a dynamically + * linked unix + krtld to form the core kernel image at boot + * time, or (in the absence of this symbol) a prelinked kernel image. + * + * _OBP + * This indicates the firmware interface is OBP. + * + * _SOFT_HOSTID + * This indicates that the implementation obtains the hostid + * from the file /etc/hostid, rather than from hardware. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The following set of definitions characterize Solaris on AMD's + * 64-bit systems. + */ +#if defined(__x86_64) || defined(__amd64) + +#if !defined(__amd64) +#define __amd64 /* preferred guard */ +#endif + +#if !defined(__x86) +#define __x86 +#endif + +/* + * Define the appropriate "processor characteristics" + */ +#define _STACK_GROWS_DOWNWARD +#define _LONG_LONG_LTOH +#define _BIT_FIELDS_LTOH +#define _IEEE_754 +#define _CHAR_IS_SIGNED +#define _BOOL_ALIGNMENT 1 +#define _CHAR_ALIGNMENT 1 +#define _SHORT_ALIGNMENT 2 +#define _INT_ALIGNMENT 4 +#define _FLOAT_ALIGNMENT 4 +#define _FLOAT_COMPLEX_ALIGNMENT 4 +#define _LONG_ALIGNMENT 8 +#define _LONG_LONG_ALIGNMENT 8 +#define _DOUBLE_ALIGNMENT 8 +#define _DOUBLE_COMPLEX_ALIGNMENT 8 +#define _LONG_DOUBLE_ALIGNMENT 16 +#define _LONG_DOUBLE_COMPLEX_ALIGNMENT 16 +#define _POINTER_ALIGNMENT 8 +#define _MAX_ALIGNMENT 16 +#define _ALIGNMENT_REQUIRED 1 + +/* + * Different alignment constraints for the i386 ABI in compatibility mode + */ +#define _LONG_LONG_ALIGNMENT_32 4 + +/* + * Define the appropriate "implementation choices". + */ +#if !defined(_LP64) +#error "_LP64 not defined" +#endif +#if !defined(_I32LPx) +#define _I32LPx +#endif +#define _MULTI_DATAMODEL +#define _SUNOS_VTOC_16 +#define _DMA_USES_PHYSADDR +#define _FIRMWARE_NEEDS_FDISK +#define __i386_COMPAT +#define _PSM_MODULES +#define _RTC_CONFIG +#define _SOFT_HOSTID +#define _DONT_USE_1275_GENERIC_NAMES +#define _HAVE_CPUID_INSN + +/* + * The feature test macro __i386 is generic for all processors implementing + * the Intel 386 instruction set or a superset of it. Specifically, this + * includes all members of the 386, 486, and Pentium family of processors. + */ +#elif defined(__i386) || defined(__i386__) + +#if !defined(__i386) +#define __i386 +#endif + +#if !defined(__x86) +#define __x86 +#endif + +/* + * Define the appropriate "processor characteristics" + */ +#define _STACK_GROWS_DOWNWARD +#define _LONG_LONG_LTOH +#define _BIT_FIELDS_LTOH +#define _IEEE_754 +#define _CHAR_IS_SIGNED +#define _BOOL_ALIGNMENT 1 +#define _CHAR_ALIGNMENT 1 +#define _SHORT_ALIGNMENT 2 +#define _INT_ALIGNMENT 4 +#define _FLOAT_ALIGNMENT 4 +#define _FLOAT_COMPLEX_ALIGNMENT 4 +#define _LONG_ALIGNMENT 4 +#define _LONG_LONG_ALIGNMENT 4 +#define _DOUBLE_ALIGNMENT 4 +#define _DOUBLE_COMPLEX_ALIGNMENT 4 +#define _LONG_DOUBLE_ALIGNMENT 4 +#define _LONG_DOUBLE_COMPLEX_ALIGNMENT 4 +#define _POINTER_ALIGNMENT 4 +#define _MAX_ALIGNMENT 4 +#define _ALIGNMENT_REQUIRED 0 + +#define _LONG_LONG_ALIGNMENT_32 _LONG_LONG_ALIGNMENT + +/* + * Define the appropriate "implementation choices". + */ +#if !defined(_ILP32) +#define _ILP32 +#endif +#if !defined(_I32LPx) +#define _I32LPx +#endif +#define _SUNOS_VTOC_16 +#define _DMA_USES_PHYSADDR +#define _FIRMWARE_NEEDS_FDISK +#define _PSM_MODULES +#define _RTC_CONFIG +#define _SOFT_HOSTID +#define _DONT_USE_1275_GENERIC_NAMES +#define _HAVE_CPUID_INSN + +#elif defined(__aarch64__) + +/* + * Define the appropriate "processor characteristics" + */ +#define _STACK_GROWS_DOWNWARD +#define _LONG_LONG_LTOH +#define _BIT_FIELDS_LTOH +#define _IEEE_754 +#define _CHAR_IS_UNSIGNED +#define _BOOL_ALIGNMENT 1 +#define _CHAR_ALIGNMENT 1 +#define _SHORT_ALIGNMENT 2 +#define _INT_ALIGNMENT 4 +#define _FLOAT_ALIGNMENT 4 +#define _FLOAT_COMPLEX_ALIGNMENT 4 +#define _LONG_ALIGNMENT 8 +#define _LONG_LONG_ALIGNMENT 8 +#define _DOUBLE_ALIGNMENT 8 +#define _DOUBLE_COMPLEX_ALIGNMENT 8 +#define _LONG_DOUBLE_ALIGNMENT 16 +#define _LONG_DOUBLE_COMPLEX_ALIGNMENT 16 +#define _POINTER_ALIGNMENT 8 +#define _MAX_ALIGNMENT 16 +#define _ALIGNMENT_REQUIRED 1 + +#define _LONG_LONG_ALIGNMENT_32 _LONG_LONG_ALIGNMENT + +/* + * Define the appropriate "implementation choices" + */ +#if !defined(_LP64) +#error "_LP64 not defined" +#endif +#define _SUNOS_VTOC_16 +#define _DMA_USES_PHYSADDR +#define _FIRMWARE_NEEDS_FDISK +#define _PSM_MODULES +#define _RTC_CONFIG +#define _DONT_USE_1275_GENERIC_NAMES +#define _HAVE_CPUID_INSN + +#elif defined(__riscv) + +/* + * Define the appropriate "processor characteristics" + */ +#define _STACK_GROWS_DOWNWARD +#define _LONG_LONG_LTOH +#define _BIT_FIELDS_LTOH +#define _IEEE_754 +#define _CHAR_IS_UNSIGNED +#define _BOOL_ALIGNMENT 1 +#define _CHAR_ALIGNMENT 1 +#define _SHORT_ALIGNMENT 2 +#define _INT_ALIGNMENT 4 +#define _FLOAT_ALIGNMENT 4 +#define _FLOAT_COMPLEX_ALIGNMENT 4 +#define _LONG_ALIGNMENT 8 +#define _LONG_LONG_ALIGNMENT 8 +#define _DOUBLE_ALIGNMENT 8 +#define _DOUBLE_COMPLEX_ALIGNMENT 8 +#define _LONG_DOUBLE_ALIGNMENT 16 +#define _LONG_DOUBLE_COMPLEX_ALIGNMENT 16 +#define _POINTER_ALIGNMENT 8 +#define _MAX_ALIGNMENT 16 +#define _ALIGNMENT_REQUIRED 1 + +#define _LONG_LONG_ALIGNMENT_32 _LONG_LONG_ALIGNMENT + +/* + * Define the appropriate "implementation choices" + */ +#if !defined(_LP64) +#define _LP64 +#endif +#define _SUNOS_VTOC_16 +#define _DMA_USES_PHYSADDR +#define _FIRMWARE_NEEDS_FDISK +#define _PSM_MODULES +#define _RTC_CONFIG +#define _DONT_USE_1275_GENERIC_NAMES +#define _HAVE_CPUID_INSN + +#elif defined(__arm__) + +/* + * Define the appropriate "processor characteristics" + */ +#define _STACK_GROWS_DOWNWARD +#define _LONG_LONG_LTOH +#define _BIT_FIELDS_LTOH +#define _IEEE_754 +#define _CHAR_IS_SIGNED +#define _BOOL_ALIGNMENT 1 +#define _CHAR_ALIGNMENT 1 +#define _SHORT_ALIGNMENT 2 +#define _INT_ALIGNMENT 4 +#define _FLOAT_ALIGNMENT 4 +#define _FLOAT_COMPLEX_ALIGNMENT 4 +#define _LONG_ALIGNMENT 4 +#define _LONG_LONG_ALIGNMENT 4 +#define _DOUBLE_ALIGNMENT 4 +#define _DOUBLE_COMPLEX_ALIGNMENT 4 +#define _LONG_DOUBLE_ALIGNMENT 4 +#define _LONG_DOUBLE_COMPLEX_ALIGNMENT 4 +#define _POINTER_ALIGNMENT 4 +#define _MAX_ALIGNMENT 4 +#define _ALIGNMENT_REQUIRED 0 + +#define _LONG_LONG_ALIGNMENT_32 _LONG_LONG_ALIGNMENT + +/* + * Define the appropriate "implementation choices". + */ +#if !defined(_ILP32) +#define _ILP32 +#endif +#if !defined(_I32LPx) +#define _I32LPx +#endif +#define _SUNOS_VTOC_16 +#define _DMA_USES_PHYSADDR +#define _FIRMWARE_NEEDS_FDISK +#define _PSM_MODULES +#define _RTC_CONFIG +#define _DONT_USE_1275_GENERIC_NAMES +#define _HAVE_CPUID_INSN + +#elif defined(__mips__) + +/* + * Define the appropriate "processor characteristics" + */ +#define _STACK_GROWS_DOWNWARD +#define _LONG_LONG_LTOH +#define _BIT_FIELDS_LTOH +#define _IEEE_754 +#define _CHAR_IS_SIGNED +#define _BOOL_ALIGNMENT 1 +#define _CHAR_ALIGNMENT 1 +#define _SHORT_ALIGNMENT 2 +#define _INT_ALIGNMENT 4 +#define _FLOAT_ALIGNMENT 4 +#define _FLOAT_COMPLEX_ALIGNMENT 4 +#if defined(__mips_n64) +#define _LONG_ALIGNMENT 8 +#define _LONG_LONG_ALIGNMENT 8 +#define _DOUBLE_ALIGNMENT 8 +#define _DOUBLE_COMPLEX_ALIGNMENT 8 +#define _LONG_DOUBLE_ALIGNMENT 8 +#define _LONG_DOUBLE_COMPLEX_ALIGNMENT 8 +#define _POINTER_ALIGNMENT 8 +#define _MAX_ALIGNMENT 8 +#define _ALIGNMENT_REQUIRED 0 + +#define _LONG_LONG_ALIGNMENT_32 _INT_ALIGNMENT +/* + * Define the appropriate "implementation choices". + */ +#if !defined(_LP64) +#error "_LP64 not defined" +#endif +#else +#define _LONG_ALIGNMENT 4 +#define _LONG_LONG_ALIGNMENT 4 +#define _DOUBLE_ALIGNMENT 4 +#define _DOUBLE_COMPLEX_ALIGNMENT 4 +#define _LONG_DOUBLE_ALIGNMENT 4 +#define _LONG_DOUBLE_COMPLEX_ALIGNMENT 4 +#define _POINTER_ALIGNMENT 4 +#define _MAX_ALIGNMENT 4 +#define _ALIGNMENT_REQUIRED 0 + +#define _LONG_LONG_ALIGNMENT_32 _LONG_LONG_ALIGNMENT + +/* + * Define the appropriate "implementation choices". + */ +#if !defined(_ILP32) +#define _ILP32 +#endif +#if !defined(_I32LPx) +#define _I32LPx +#endif +#endif +#define _SUNOS_VTOC_16 +#define _DMA_USES_PHYSADDR +#define _FIRMWARE_NEEDS_FDISK +#define _PSM_MODULES +#define _RTC_CONFIG +#define _DONT_USE_1275_GENERIC_NAMES +#define _HAVE_CPUID_INSN + +#elif defined(__powerpc__) + +#if defined(__BIG_ENDIAN__) +#define _BIT_FIELDS_HTOL +#else +#define _BIT_FIELDS_LTOH +#endif + +/* + * The following set of definitions characterize the Solaris on SPARC systems. + * + * The symbol __sparc indicates any of the SPARC family of processor + * architectures. This includes SPARC V7, SPARC V8 and SPARC V9. + * + * The symbol __sparcv8 indicates the 32-bit SPARC V8 architecture as defined + * by Version 8 of the SPARC Architecture Manual. (SPARC V7 is close enough + * to SPARC V8 for the former to be subsumed into the latter definition.) + * + * The symbol __sparcv9 indicates the 64-bit SPARC V9 architecture as defined + * by Version 9 of the SPARC Architecture Manual. + * + * The symbols __sparcv8 and __sparcv9 are mutually exclusive, and are only + * relevant when the symbol __sparc is defined. + */ +/* + * XXX Due to the existence of 5110166, "defined(__sparcv9)" needs to be added + * to support backwards builds. This workaround should be removed in s10_71. + */ +#elif defined(__sparc) || defined(__sparcv9) || defined(__sparc__) +#if !defined(__sparc) +#define __sparc +#endif + +/* + * You can be 32-bit or 64-bit, but not both at the same time. + */ +#if defined(__sparcv8) && defined(__sparcv9) +#error "SPARC Versions 8 and 9 are mutually exclusive choices" +#endif + +/* + * Existing compilers do not set __sparcv8. Years will transpire before + * the compilers can be depended on to set the feature test macro. In + * the interim, we'll set it here on the basis of historical behaviour; + * if you haven't asked for SPARC V9, then you must've meant SPARC V8. + */ +#if !defined(__sparcv9) && !defined(__sparcv8) +#define __sparcv8 +#endif + +/* + * Define the appropriate "processor characteristics" shared between + * all Solaris on SPARC systems. + */ +#define _STACK_GROWS_DOWNWARD +#define _LONG_LONG_HTOL +#define _BIT_FIELDS_HTOL +#define _IEEE_754 +#define _CHAR_IS_SIGNED +#define _BOOL_ALIGNMENT 1 +#define _CHAR_ALIGNMENT 1 +#define _SHORT_ALIGNMENT 2 +#define _INT_ALIGNMENT 4 +#define _FLOAT_ALIGNMENT 4 +#define _FLOAT_COMPLEX_ALIGNMENT 4 +#define _LONG_LONG_ALIGNMENT 8 +#define _DOUBLE_ALIGNMENT 8 +#define _DOUBLE_COMPLEX_ALIGNMENT 8 +#define _ALIGNMENT_REQUIRED 1 + +/* + * Define the appropriate "implementation choices" shared between versions. + */ +#define _SUNOS_VTOC_8 +#define _DMA_USES_VIRTADDR +#define _NO_FDISK_PRESENT +#define _HAVE_TEM_FIRMWARE +#define _OBP + +/* + * The following set of definitions characterize the implementation of + * 32-bit Solaris on SPARC V8 systems. + */ +#if defined(__sparcv8) + +/* + * Define the appropriate "processor characteristics" + */ +#define _LONG_ALIGNMENT 4 +#define _LONG_DOUBLE_ALIGNMENT 8 +#define _LONG_DOUBLE_COMPLEX_ALIGNMENT 8 +#define _POINTER_ALIGNMENT 4 +#define _MAX_ALIGNMENT 8 + +#define _LONG_LONG_ALIGNMENT_32 _LONG_LONG_ALIGNMENT + +/* + * Define the appropriate "implementation choices" + */ +#define _ILP32 +#if !defined(_I32LPx) +#define _I32LPx +#endif + +/* + * The following set of definitions characterize the implementation of + * 64-bit Solaris on SPARC V9 systems. + */ +#elif defined(__sparcv9) + +/* + * Define the appropriate "processor characteristics" + */ +#define _LONG_ALIGNMENT 8 +#define _LONG_DOUBLE_ALIGNMENT 16 +#define _LONG_DOUBLE_COMPLEX_ALIGNMENT 16 +#define _POINTER_ALIGNMENT 8 +#define _MAX_ALIGNMENT 16 + +#define _LONG_LONG_ALIGNMENT_32 _LONG_LONG_ALIGNMENT + +/* + * Define the appropriate "implementation choices" + */ +#if !defined(_LP64) +#error "_LP64 not defined" +#endif +#if !defined(_I32LPx) +#define _I32LPx +#endif +#define _MULTI_DATAMODEL + +#else +#error "unknown SPARC version" +#endif + +/* + * #error is strictly ansi-C, but works as well as anything for K&R systems. + */ +#else +#error "ISA not supported" +#endif + +#if defined(_ILP32) && defined(_LP64) +#error "Both _ILP32 and _LP64 are defined" +#endif + +#define ____cacheline_aligned __attribute__((aligned(64))) + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_ISA_DEFS_H */ diff --git a/include/os/macos/spl/sys/kmem.h b/include/os/macos/spl/sys/kmem.h new file mode 100644 index 000000000000..f80c2d2e98b1 --- /dev/null +++ b/include/os/macos/spl/sys/kmem.h @@ -0,0 +1,157 @@ +/* + * 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) 2008 MacZFS Project + * Copyright (C) 2013 Jorgen Lundman + * Copyright (C) 2017 Sean Doran + * + */ + +#ifndef _SPL_KMEM_H +#define _SPL_KMEM_H + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// XNU total amount of memory +extern uint64_t physmem; + +#define KM_SLEEP 0x0000 /* can block for memory; success guaranteed */ +#define KM_NOSLEEP 0x0001 /* cannot block for memory; may fail */ +#define KM_PANIC 0x0002 /* if memory cannot be allocated, panic */ +#define KM_PUSHPAGE 0x0004 /* can block for memory; may use reserve */ +#define KM_NORMALPRI 0x0008 /* with KM_NOSLEEP, lower priority allocation */ +#define KM_NODEBUG 0x0010 /* NOT IMPLEMENTED ON OSX */ +#define KM_NO_VBA 0x0020 /* OSX: don't descend to the bucket layer */ +#define KM_VMFLAGS 0x00ff /* flags that must match VM_* flags */ + +#define KM_FLAGS 0xffff /* all settable kmem flags */ + +/* + * Kernel memory allocator: DDI interfaces. + * See kmem_alloc(9F) for details. + */ + +// Work around symbol collisions in XNU +#define kmem_alloc(size, kmflags) zfs_kmem_alloc((size), (kmflags)) +#define kmem_zalloc(size, kmflags) zfs_kmem_zalloc((size), (kmflags)) +#define kmem_free(buf, size) zfs_kmem_free((buf), (size)) + +void *zfs_kmem_alloc(size_t size, int kmflags); +void *zfs_kmem_zalloc(size_t size, int kmflags); +void zfs_kmem_free(const void *buf, size_t size); + +void spl_kmem_init(uint64_t); +void spl_kmem_thread_init(void); +void spl_kmem_mp_init(void); +void spl_kmem_thread_fini(void); +void spl_kmem_fini(void); + +size_t kmem_size(void); +size_t kmem_used(void); +int64_t kmem_avail(void); +size_t kmem_num_pages_wanted(void); +int spl_vm_pool_low(void); +int32_t spl_minimal_physmem_p(void); +int64_t spl_adjust_pressure(int64_t); +int64_t spl_free_wrapper(void); +int64_t spl_free_manual_pressure_wrapper(void); +boolean_t spl_free_fast_pressure_wrapper(void); +void spl_free_set_pressure(int64_t); +void spl_free_set_fast_pressure(boolean_t); +uint64_t spl_free_last_pressure_wrapper(void); + +#define KMC_NOTOUCH 0x00010000 +#define KMC_NODEBUG 0x00020000 +#define KMC_NOMAGAZINE 0x00040000 +#define KMC_NOHASH 0x00080000 +#define KMC_QCACHE 0x00100000 +#define KMC_KMEM_ALLOC 0x00200000 /* internal use only */ +#define KMC_IDENTIFIER 0x00400000 /* internal use only */ +#define KMC_PREFILL 0x00800000 +#define KMC_ARENA_SLAB 0x01000000 /* use a bigger kmem cache */ + +struct kmem_cache; + +typedef struct kmem_cache kmem_cache_t; + +/* Client response to kmem move callback */ +typedef enum kmem_cbrc { + KMEM_CBRC_YES, + KMEM_CBRC_NO, + KMEM_CBRC_LATER, + KMEM_CBRC_DONT_NEED, + KMEM_CBRC_DONT_KNOW +} kmem_cbrc_t; + +#define POINTER_IS_VALID(p) (!((uintptr_t)(p) & 0x3)) +#define POINTER_INVALIDATE(pp) (*(pp) = (void *)((uintptr_t)(*(pp)) | 0x1)) + +kmem_cache_t *kmem_cache_create(char *name, size_t bufsize, size_t align, + int (*constructor)(void *, void *, int), + void (*destructor)(void *, void *), + void (*reclaim)(void *), + void *_private, vmem_t *vmp, int cflags); +void kmem_cache_destroy(kmem_cache_t *cache); +void *kmem_cache_alloc(kmem_cache_t *cache, int flags); +void kmem_cache_free(kmem_cache_t *cache, const void *buf); +void kmem_cache_free_to_slab(kmem_cache_t *cache, void *buf); +extern boolean_t kmem_cache_reap_active(void); +void kmem_cache_reap_now(kmem_cache_t *cache); +void kmem_depot_ws_zero(kmem_cache_t *cache); +void kmem_reap(void); +void kmem_reap_idspace(void); +kmem_cache_t *kmem_cache_buf_in_cache(kmem_cache_t *, void *); + +int kmem_debugging(void); +void kmem_cache_set_move(kmem_cache_t *, + kmem_cbrc_t (*)(void *, void *, size_t, void *)); + +char *kmem_asprintf(const char *fmt, ...); +extern char *kmem_strdup(const char *str); +extern void kmem_strfree(char *str); +char *kmem_vasprintf(const char *fmt, va_list ap); +char *kmem_strstr(const char *in, const char *str); +void strident_canon(char *s, size_t n); +extern int kmem_scnprintf(char *str, size_t size, + const char *fmt, ...); + +boolean_t spl_arc_no_grow(size_t, boolean_t, kmem_cache_t **); + +extern uint64_t spl_kmem_cache_inuse(kmem_cache_t *cache); +extern uint64_t spl_kmem_cache_entry_size(kmem_cache_t *cache); + +#ifdef __cplusplus +} +#endif + +#endif /* _SPL_KMEM_H */ diff --git a/include/os/macos/spl/sys/kmem_cache.h b/include/os/macos/spl/sys/kmem_cache.h new file mode 100644 index 000000000000..2dc08b171266 --- /dev/null +++ b/include/os/macos/spl/sys/kmem_cache.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 _SPL_KMEM_CACHE_H +#define _SPL_KMEM_CACHE_H + +#endif diff --git a/include/os/macos/spl/sys/kmem_impl.h b/include/os/macos/spl/sys/kmem_impl.h new file mode 100644 index 000000000000..f53ed4366d53 --- /dev/null +++ b/include/os/macos/spl/sys/kmem_impl.h @@ -0,0 +1,495 @@ +/* + * 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) 1994, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#ifndef _SYS_KMEM_IMPL_H +#define _SYS_KMEM_IMPL_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * kernel memory allocator: implementation-private data structures + * + * Lock order: + * 1. cache_lock + * 2. cc_lock in order by CPU ID + * 3. cache_depot_lock + * + * Do not call kmem_cache_alloc() or taskq_dispatch() while holding any of the + * above locks. + */ + +#define KMF_AUDIT 0x00000001 /* transaction auditing */ +#define KMF_DEADBEEF 0x00000002 /* deadbeef checking */ +#define KMF_REDZONE 0x00000004 /* redzone checking */ +#define KMF_CONTENTS 0x00000008 /* freed-buffer content logging */ +#define KMF_STICKY 0x00000010 /* if set, override /etc/system */ +#define KMF_NOMAGAZINE 0x00000020 /* disable per-cpu magazines */ +#define KMF_FIREWALL 0x00000040 /* put all bufs before unmapped pages */ +#define KMF_LITE 0x00000100 /* lightweight debugging */ + +#define KMF_HASH 0x00000200 /* cache has hash table */ +#define KMF_RANDOMIZE 0x00000400 /* randomize other kmem_flags */ + +#define KMF_DUMPDIVERT 0x00001000 /* use alternate memory at dump time */ +#define KMF_DUMPUNSAFE 0x00002000 /* flag caches used at dump time */ +#define KMF_PREFILL 0x00004000 /* Prefill the slab when created. */ + +#define KMF_BUFTAG (KMF_DEADBEEF | KMF_REDZONE) +#define KMF_TOUCH (KMF_BUFTAG | KMF_LITE | KMF_CONTENTS) +#define KMF_RANDOM (KMF_TOUCH | KMF_AUDIT | KMF_NOMAGAZINE) +#define KMF_DEBUG (KMF_RANDOM | KMF_FIREWALL) + +#define KMEM_STACK_DEPTH 15 + +#define KMEM_FREE_PATTERN 0xdeadbeefdeadbeefULL +#define KMEM_UNINITIALIZED_PATTERN 0xbaddcafebaddcafeULL +#define KMEM_REDZONE_PATTERN 0xfeedfacefeedfaceULL +#define KMEM_REDZONE_BYTE 0xbb + +/* + * Upstream platforms handle size == 0 as valid alloc, we + * can not return NULL, as that invalidates KM_SLEEP. So + * we return a valid hardcoded address, instead of actually taking up + * memory by fudging size to 1 byte. If read/writes are + * attempted, we will get page fault (which is correct, they + * asked for zero bytes after all) + */ +#define KMEM_ZERO_SIZE_PTR ((void *)16) + +/* + * Redzone size encodings for kmem_alloc() / kmem_free(). We encode the + * allocation size, rather than storing it directly, so that kmem_free() + * can distinguish frees of the wrong size from redzone violations. + * + * A size of zero is never valid. + */ +#define KMEM_SIZE_ENCODE(x) (251 * (x) + 1) +#define KMEM_SIZE_DECODE(x) ((x) / 251) +#define KMEM_SIZE_VALID(x) ((x) % 251 == 1 && (x) != 1) + + +#define KMEM_ALIGN 8 /* min guaranteed alignment */ +#define KMEM_ALIGN_SHIFT 3 /* log2(KMEM_ALIGN) */ +#define KMEM_VOID_FRACTION 8 /* never waste more than 1/8 of slab */ + +#define KMEM_SLAB_IS_PARTIAL(sp) \ + ((sp)->slab_refcnt > 0 && (sp)->slab_refcnt < (sp)->slab_chunks) +#define KMEM_SLAB_IS_ALL_USED(sp) \ + ((sp)->slab_refcnt == (sp)->slab_chunks) + +/* + * The bufctl (buffer control) structure keeps some minimal information + * about each buffer: its address, its slab, and its current linkage, + * which is either on the slab's freelist (if the buffer is free), or + * on the cache's buf-to-bufctl hash table (if the buffer is allocated). + * In the case of non-hashed, or "raw", caches (the common case), only + * the freelist linkage is necessary: the buffer address is at a fixed + * offset from the bufctl address, and the slab is at the end of the page. + * + * NOTE: bc_next must be the first field; raw buffers have linkage only. + */ +typedef struct kmem_bufctl { + struct kmem_bufctl *bc_next; /* next bufctl struct */ + void *bc_addr; /* address of buffer */ + struct kmem_slab *bc_slab; /* controlling slab */ +} kmem_bufctl_t; + +/* + * The KMF_AUDIT version of the bufctl structure. The beginning of this + * structure must be identical to the normal bufctl structure so that + * pointers are interchangeable. + */ +typedef struct kmem_bufctl_audit { + struct kmem_bufctl *bc_next; /* next bufctl struct */ + void *bc_addr; /* address of buffer */ + struct kmem_slab *bc_slab; /* controlling slab */ + kmem_cache_t *bc_cache; /* controlling cache */ + hrtime_t bc_timestamp; /* transaction time */ + kthread_t *bc_thread; /* thread doing transaction */ + struct kmem_bufctl *bc_lastlog; /* last log entry */ + void *bc_contents; /* contents at last free */ + int bc_depth; /* stack depth */ + pc_t bc_stack[KMEM_STACK_DEPTH]; /* pc stack */ +} kmem_bufctl_audit_t; + +/* + * A kmem_buftag structure is appended to each buffer whenever any of the + * KMF_BUFTAG flags (KMF_DEADBEEF, KMF_REDZONE, KMF_VERIFY) are set. + */ +typedef struct kmem_buftag { + uint64_t bt_redzone; /* 64-bit redzone pattern */ + kmem_bufctl_t *bt_bufctl; /* bufctl */ + intptr_t bt_bxstat; /* bufctl ^ (alloc/free) */ +} kmem_buftag_t; + +/* + * A variant of the kmem_buftag structure used for KMF_LITE caches. + * Previous callers are stored in reverse chronological order. (i.e. most + * recent first) + */ +typedef struct kmem_buftag_lite { + kmem_buftag_t bt_buftag; /* a normal buftag */ + pc_t bt_history[1]; /* zero or more callers */ +} kmem_buftag_lite_t; + +#define KMEM_BUFTAG_LITE_SIZE(f) \ + (offsetof(kmem_buftag_lite_t, bt_history[f])) + +#define KMEM_BUFTAG(cp, buf) \ + ((kmem_buftag_t *)((char *)(buf) + (cp)->cache_buftag)) + +#define KMEM_BUFCTL(cp, buf) \ + ((kmem_bufctl_t *)((char *)(buf) + (cp)->cache_bufctl)) + +#define KMEM_BUF(cp, bcp) \ + ((void *)((char *)(bcp) - (cp)->cache_bufctl)) + +#define KMEM_SLAB(cp, buf) \ + ((kmem_slab_t *)P2END((uintptr_t)(buf), (cp)->cache_slabsize) - 1) + +/* + * Test for using alternate memory at dump time. + */ +#define KMEM_DUMP(cp) ((cp)->cache_flags & KMF_DUMPDIVERT) +#define KMEM_DUMPCC(ccp) ((ccp)->cc_flags & KMF_DUMPDIVERT) + +/* + * The "CPU" macro loads a cpu_t that refers to the cpu that the current + * thread is running on at the time the macro is executed. A context switch + * may occur immediately after loading this data structure, leaving this + * thread pointing at the cpu_t for the previous cpu. This is not a problem; + * we'd just end up checking the previous cpu's per-cpu cache, and then check + * the other layers of the kmem cache if need be. + * + * It's not even a problem if the old cpu gets DR'ed out during the context + * switch. The cpu-remove DR operation bzero()s the cpu_t, but doesn't free + * it. So the cpu_t's cpu_cache_offset would read as 0, causing us to use + * cpu 0's per-cpu cache. + * + * So, there is no need to disable kernel preemption while using the CPU macro + * below since if we have been context switched, there will not be any + * correctness problem, just a momentary use of a different per-cpu cache. + */ + +#define KMEM_CPU_CACHE(cp) \ + (&cp->cache_cpu[CPU_SEQID]) + +#define KMOM_MAGAZINE_VALID(cp, mp) \ + (((kmem_slab_t *)P2END((uintptr_t)(mp), PAGESIZE) - 1)->slab_cache == \ + (cp)->cache_magtype->mt_cache) + +#define KMEM_MAGAZINE_VALID(cp, mp) \ + (((kmem_slab_t *)P2END((uintptr_t)(mp), PAGESIZE) - 1)->slab_cache == \ + (cp)->cache_magtype->mt_cache) + +#define KMEM_SLAB_OFFSET(sp, buf) \ + ((size_t)((uintptr_t)(buf) - (uintptr_t)((sp)->slab_base))) + +#define KMEM_SLAB_MEMBER(sp, buf) \ + (KMEM_SLAB_OFFSET(sp, buf) < (sp)->slab_cache->cache_slabsize) + +#define KMEM_BUFTAG_ALLOC 0xa110c8edUL +#define KMEM_BUFTAG_FREE 0xf4eef4eeUL + +/* slab_later_count thresholds */ +#define KMEM_DISBELIEF 3 + +/* slab_flags */ +#define KMEM_SLAB_NOMOVE 0x1 +#define KMEM_SLAB_MOVE_PENDING 0x2 + +typedef struct kmem_slab { + struct kmem_cache *slab_cache; /* controlling cache */ + void *slab_base; /* base of allocated memory */ + avl_node_t slab_link; /* slab linkage */ + struct kmem_bufctl *slab_head; /* first free buffer */ + long slab_refcnt; /* outstanding allocations */ + long slab_chunks; /* chunks (bufs) in this slab */ + uint32_t slab_stuck_offset; /* unmoved buffer offset */ + uint16_t slab_later_count; /* cf KMEM_CBRC_LATER */ + uint16_t slab_flags; /* bits to mark the slab */ + hrtime_t slab_create_time; /* when was slab created? */ +} kmem_slab_t; + +#define KMEM_HASH_INITIAL 64 + +#define KMEM_HASH(cp, buf) \ + ((cp)->cache_hash_table + \ + (((uintptr_t)(buf) >> (cp)->cache_hash_shift) & (cp)->cache_hash_mask)) + +#define KMEM_CACHE_NAMELEN 31 + +typedef struct kmem_magazine { + void *mag_next; + void *mag_round[1]; /* one or more rounds */ +} kmem_magazine_t; + +/* + * The magazine types for fast per-cpu allocation + */ +typedef struct kmem_magtype { + short mt_magsize; /* magazine size (number of rounds) */ + int mt_align; /* magazine alignment */ + size_t mt_minbuf; /* all smaller buffers qualify */ + size_t mt_maxbuf; /* no larger buffers qualify */ + kmem_cache_t *mt_cache; /* magazine cache */ +} kmem_magtype_t; + +#define KMEM_CPU_CACHE_SIZE 128 /* must be power of 2 */ +#define KMEM_CPU_PAD (KMEM_CPU_CACHE_SIZE - sizeof (kmutex_t) - \ + 2 * sizeof (uint64_t) - 2 * sizeof (void *) - sizeof (int) - \ + 5 * sizeof (short)) +#define KMEM_CACHE_SIZE(ncpus) \ + __builtin_offsetof(kmem_cache_t, cache_cpu[ncpus]) + + /* Offset from kmem_cache->cache_cpu for per cpu caches */ +#define KMEM_CPU_CACHE_OFFSET(cpuid) \ + __builtin_offsetof(kmem_cache_t, cache_cpu[cpuid]) - \ + __builtin_offsetof(kmem_cache_t, cache_cpu) + +/* + * Per CPU cache data + */ +typedef struct kmem_cpu_cache { + kmutex_t cc_lock; /* protects this cpu's local cache */ + uint64_t cc_alloc; /* allocations from this cpu */ + uint64_t cc_free; /* frees to this cpu */ + kmem_magazine_t *cc_loaded; /* the currently loaded magazine */ + kmem_magazine_t *cc_ploaded; /* the previously loaded magazine */ + int cc_flags; /* CPU-local copy of cache_flags */ + short cc_rounds; /* number of objects in loaded mag */ + short cc_prounds; /* number of objects in previous mag */ + short cc_magsize; /* number of rounds in a full mag */ + short cc_dump_rounds; /* dump time copy of cc_rounds */ + short cc_dump_prounds; /* dump time copy of cc_prounds */ + char cc_pad[KMEM_CPU_PAD]; /* for nice alignment */ +} kmem_cpu_cache_t; + +/* + * The magazine lists used in the depot. + */ +typedef struct kmem_maglist { + kmem_magazine_t *ml_list; /* magazine list */ + long ml_total; /* number of magazines */ + long ml_min; /* min since last update */ + long ml_reaplimit; /* max reapable magazines */ + uint64_t ml_alloc; /* allocations from this list */ +} kmem_maglist_t; + +typedef struct kmem_defrag { + /* + * Statistics + */ + uint64_t kmd_callbacks; /* move callbacks */ + uint64_t kmd_yes; /* KMEM_CBRC_YES responses */ + uint64_t kmd_no; /* NO responses */ + uint64_t kmd_later; /* LATER responses */ + uint64_t kmd_dont_need; /* DONT_NEED responses */ + uint64_t kmd_dont_know; /* DONT_KNOW responses */ + uint64_t kmd_slabs_freed; /* slabs freed by moves */ + uint64_t kmd_defrags; /* kmem_cache_defrag() */ + uint64_t kmd_scans; /* kmem_cache_scan() */ + + /* + * Consolidator fields + */ + avl_tree_t kmd_moves_pending; /* buffer moves pending */ + list_t kmd_deadlist; /* deferred slab frees */ + size_t kmd_deadcount; /* # of slabs in kmd_deadlist */ + uint8_t kmd_reclaim_numer; /* slab usage threshold */ + uint8_t kmd_pad1; /* compiler padding */ + uint16_t kmd_consolidate; /* triggers consolidator */ + uint32_t kmd_pad2; /* compiler padding */ + size_t kmd_slabs_sought; /* reclaimable slabs sought */ + size_t kmd_slabs_found; /* reclaimable slabs found */ + size_t kmd_tries; /* nth scan interval counter */ + /* + * Fields used to ASSERT that the client does not kmem_cache_free() + * objects passed to the move callback. + */ + void *kmd_from_buf; /* object to move */ + void *kmd_to_buf; /* move destination */ + kthread_t *kmd_thread; /* thread calling move */ +} kmem_defrag_t; + +/* + * Cache callback function types + */ +typedef int (*constructor_fn_t)(void*, void*, int); +typedef void (*destructor_fn_t)(void*, void*); +typedef void (*reclaim_fn_t)(void*); + +/* + * Cache + */ +struct kmem_cache { + +/* + * Statistics + */ + uint64_t cache_slab_create; /* slab creates */ + uint64_t cache_slab_destroy; /* slab destroys */ + uint64_t cache_slab_alloc; /* slab layer allocations */ + uint64_t cache_slab_free; /* slab layer frees */ + uint64_t cache_alloc_fail; /* total failed allocations */ + uint64_t cache_buftotal; /* total buffers */ + uint64_t cache_bufmax; /* max buffers ever */ + uint64_t cache_bufslab; /* buffers free in slab layer */ + uint64_t cache_reap; /* cache reaps */ + kmutex_t cache_reap_lock; /* one reap at a time */ + uint64_t cache_rescale; /* hash table rescales */ + uint64_t cache_lookup_depth; /* hash lookup depth */ + uint64_t cache_depot_contention; /* mutex contention count */ + uint64_t cache_depot_contention_prev; /* previous snapshot */ + uint64_t cache_alloc_count; /* Number of allocations in cache */ + /* successful calls with KM_NO_VBA flag set */ + uint64_t no_vba_success; + uint64_t no_vba_fail; + /* number of times we set arc growth suppression time */ + uint64_t arc_no_grow_set; + /* number of times spl_zio_is_suppressed returned true for this cache */ + uint64_t arc_no_grow; + + /* + * Cache properties + */ + char cache_name[KMEM_CACHE_NAMELEN + 1]; + size_t cache_bufsize; /* object size */ + size_t cache_align; /* object alignment */ + int (*cache_constructor)(void *, void *, int); + void (*cache_destructor)(void *, void *); + void (*cache_reclaim)(void *); + kmem_cbrc_t (*cache_move)(void *, void *, size_t, void *); + void *cache_private; /* opaque arg to callbacks */ + vmem_t *cache_arena; /* vmem source for slabs */ + int cache_cflags; /* cache creation flags */ + int cache_flags; /* various cache state info */ + uint32_t cache_mtbf; /* induced alloc failure rate */ + uint32_t cache_pad1; /* compiler padding */ + kstat_t *cache_kstat; /* exported statistics */ + list_node_t cache_link; /* cache linkage */ + + /* + * Slab layer + */ + kmutex_t cache_lock; /* protects slab layer */ + + size_t cache_chunksize; /* buf + alignment [+ debug] */ + size_t cache_slabsize; /* size of a slab */ + size_t cache_maxchunks; /* max buffers per slab */ + size_t cache_bufctl; /* buf-to-bufctl distance */ + size_t cache_buftag; /* buf-to-buftag distance */ + size_t cache_verify; /* bytes to verify */ + size_t cache_contents; /* bytes of saved content */ + size_t cache_color; /* next slab color */ + size_t cache_mincolor; /* maximum slab color */ + size_t cache_maxcolor; /* maximum slab color */ + size_t cache_hash_shift; /* get to interesting bits */ + size_t cache_hash_mask; /* hash table mask */ + list_t cache_complete_slabs; /* completely allocated slabs */ + size_t cache_complete_slab_count; + avl_tree_t cache_partial_slabs; /* partial slab freelist */ + size_t cache_partial_binshift; /* for AVL sort bins */ + kmem_cache_t *cache_bufctl_cache; /* source of bufctls */ + kmem_bufctl_t **cache_hash_table; /* hash table base */ + kmem_defrag_t *cache_defrag; /* slab consolidator fields */ + + /* + * Depot layer + */ + kmutex_t cache_depot_lock; /* protects depot */ + kmem_magtype_t *cache_magtype; /* magazine type */ + kmem_maglist_t cache_full; /* full magazines */ + kmem_maglist_t cache_empty; /* empty magazines */ + void *cache_dumpfreelist; /* heap during crash dump */ + void *cache_dumplog; /* log entry during dump */ + + /* + * Per CPU structures + */ + // XNU adjust to suit __builtin_offsetof + kmem_cpu_cache_t cache_cpu[1]; /* per-cpu data */ + +}; + +typedef struct kmem_cpu_log_header { + kmutex_t clh_lock; + char *clh_current; + size_t clh_avail; + int clh_chunk; + int clh_hits; +#if defined(SPL_DEBUG_MUTEX) + char clh_pad[128 - sizeof (kmutex_t) - sizeof (char *) - + sizeof (size_t) - 2 * sizeof (int)]; +#else + char clh_pad[64 - sizeof (kmutex_t) - sizeof (char *) - + sizeof (size_t) - 2 * sizeof (int)]; +#endif +} kmem_cpu_log_header_t; + +typedef struct kmem_log_header { + kmutex_t lh_lock; + char *lh_base; + int *lh_free; + size_t lh_chunksize; + int lh_nchunks; + int lh_head; + int lh_tail; + int lh_hits; + kmem_cpu_log_header_t lh_cpu[1]; /* ncpus actually allocated */ +} kmem_log_header_t; + +/* kmem_move kmm_flags */ +#define KMM_DESPERATE 0x1 +#define KMM_NOTIFY 0x2 +#define KMM_DEBUG 0x4 + +typedef struct kmem_move { + kmem_slab_t *kmm_from_slab; + void *kmm_from_buf; + void *kmm_to_buf; + avl_node_t kmm_entry; + int kmm_flags; +} kmem_move_t; + +/* + * In order to consolidate partial slabs, it must be possible for the cache to + * have partial slabs. + */ +#define KMEM_IS_MOVABLE(cp) \ + (((cp)->cache_chunksize * 2) <= (cp)->cache_slabsize) + +#endif diff --git a/include/os/macos/spl/sys/kstat.h b/include/os/macos/spl/sys/kstat.h new file mode 100644 index 000000000000..584d73e47274 --- /dev/null +++ b/include/os/macos/spl/sys/kstat.h @@ -0,0 +1,227 @@ +/* + * 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 _SPL_KSTAT_H +#define _SPL_KSTAT_H + +#include +#include +#include +#include +#include + +#define KSTAT_STRLEN 255 +#define KSTAT_RAW_MAX (128*1024) + +/* + * For reference valid classes are: + * disk, tape, net, controller, vm, kvm, hat, streams, kstat, misc + */ + +#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 stats; ks_ndata == 1 */ +#define KSTAT_TYPE_IO 3 /* I/O stats; ks_ndata == 1 */ +#define KSTAT_TYPE_TIMER 4 /* event timer; ks_ndata >= 1 */ +#define KSTAT_TYPE_TXG 5 /* txg sync; ks_ndata >= 1 */ +#define KSTAT_NUM_TYPES 6 + +#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 +#define KSTAT_DATA_LONG 5 +#define KSTAT_DATA_ULONG 6 +#define KSTAT_DATA_STRING 7 +#define KSTAT_NUM_DATAS 8 + +#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 + +#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_UNSUPPORTED (KSTAT_FLAG_VAR_SIZE | KSTAT_FLAG_WRITABLE | \ + KSTAT_FLAG_PERSISTENT | KSTAT_FLAG_DORMANT) +#define KSTAT_FLAG_INVALID 0x20 +#define KSTAT_FLAG_LONGSTRINGS 0x40 +#define KSTAT_FLAG_NO_HEADERS 0x80 + +#define KS_MAGIC 0x9d9d9d9d + +/* Dynamic updates */ +#define KSTAT_READ 0 +#define KSTAT_WRITE 1 + +struct kstat_s; + +typedef int kid_t; /* unique kstat id */ +typedef int kstat_update_t(struct kstat_s *, int); /* dynamic update cb */ + +struct seq_file { + char *sf_buf; + size_t sf_size; +}; + +void seq_printf(struct seq_file *m, const char *fmt, ...); + +typedef struct kstat_raw_ops { + int (*headers)(char *buf, size_t size); + int (*seq_headers)(struct seq_file *); + int (*data)(char *buf, size_t size, void *data); + void *(*addr)(struct kstat_s *ksp, loff_t index); +} kstat_raw_ops_t; + +typedef struct kstat_s { + int ks_magic; /* magic value */ + kid_t ks_kid; /* unique kstat ID */ + hrtime_t ks_crtime; /* creation time */ + hrtime_t ks_snaptime; /* last access time */ + char ks_module[KSTAT_STRLEN+1]; /* provider module name */ + int ks_instance; /* provider module instance */ + char ks_name[KSTAT_STRLEN+1]; /* kstat name */ + char ks_class[KSTAT_STRLEN+1]; /* kstat class */ + uchar_t ks_type; /* kstat data type */ + 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; /* size of kstat data section */ + struct proc_dir_entry *ks_proc; /* proc linkage */ + kstat_update_t *ks_update; /* dynamic updates */ + void *ks_private; /* private data */ + void *ks_private1; /* private data */ + kmutex_t ks_private_lock; /* kstat private data lock */ + kmutex_t *ks_lock; /* kstat data lock */ + kstat_raw_ops_t ks_raw_ops; /* ops table for raw type */ + char *ks_raw_buf; /* buf used for raw ops */ + size_t ks_raw_bufsize; /* size of raw ops buffer */ +} kstat_t; + +typedef struct kstat_named_s { + char name[KSTAT_STRLEN]; /* name of counter */ + uchar_t data_type; /* data type */ + union { + char c[16]; /* 128-bit int */ + int32_t i32; /* 32-bit signed int */ + uint32_t ui32; /* 32-bit unsigned int */ + int64_t i64; /* 64-bit signed int */ + uint64_t ui64; /* 64-bit unsigned int */ + long l; /* native signed long */ + ulong_t ul; /* native unsigned long */ + struct { + union { + char *ptr; /* NULL-term string */ + char __pad[8]; /* 64-bit padding */ + } addr; + uint32_t len; /* # bytes for strlen + '\0' */ + } string; + } value; +} kstat_named_t; + +#define KSTAT_NAMED_STR_PTR(knptr) ((knptr)->value.string.addr.ptr) +#define KSTAT_NAMED_STR_BUFLEN(knptr) ((knptr)->value.string.len) + +typedef struct kstat_intr { + uint_t intrs[KSTAT_NUM_INTRS]; +} kstat_intr_t; + +typedef struct kstat_io { + 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 */ + hrtime_t wtime; /* cumulative wait (pre-service) time */ + hrtime_t wlentime; /* cumulative wait len * 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; + +typedef struct kstat_timer { + char name[KSTAT_STRLEN+1]; /* event name */ + 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; + +void spl_kstat_init(void); +void spl_kstat_fini(void); + +extern 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)); + +extern void __kstat_set_seq_raw_ops(kstat_t *ksp, + int (*headers)(struct seq_file *), + int (*data)(char *buf, size_t size, void *data), + void* (*addr)(kstat_t *ksp, loff_t index)); + +extern kstat_t *__kstat_create(const char *ks_module, int ks_instance, + const char *ks_name, const char *ks_class, + uchar_t ks_type, ulong_t ks_ndata, + uchar_t ks_flags); +extern void __kstat_install(kstat_t *ksp); +extern void __kstat_delete(kstat_t *ksp); + +#define kstat_create(m, i, n, c, t, s, f) \ + __kstat_create(m, i, n, c, t, s, f) +#define kstat_install(k) __kstat_install(k) +#define kstat_delete(k) __kstat_delete(k) + +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_named_init(kstat_named_t *, const char *, uchar_t); + +#define kstat_set_seq_raw_ops(k, h, d, a) __kstat_set_seq_raw_ops(k, h, d, a) +#define kstat_set_raw_ops(k, h, d, a) __kstat_set_raw_ops(k, h, d, a) +void kstat_named_setstr(kstat_named_t *knp, const char *src); + +struct sbuf; +struct sysctl_req; +extern int sbuf_printf(struct sbuf *, const char *, ...) __printflike(2, 3); +extern void sbuf_finish(struct sbuf *); +extern void sbuf_delete(struct sbuf *); +extern struct sbuf *sbuf_new_for_sysctl(struct sbuf *s, char *buf, + int length, struct sysctl_req *req); + +extern struct sysctl_oid_list *spl_kstat_find_oid(char *, char *); + +#endif /* _SPL_KSTAT_H */ diff --git a/include/os/macos/spl/sys/list.h b/include/os/macos/spl/sys/list.h new file mode 100644 index 000000000000..c9a72a53a6ad --- /dev/null +++ b/include/os/macos/spl/sys/list.h @@ -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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + +#ifndef _SPL_LIST_H +#define _SPL_LIST_H + +#include +#include + +/* + * NOTE: I have implemented the Solaris list API in terms of the native + * linux API. This has certain advantages in terms of leveraging the linux + * list debugging infrastructure, but it also means that the internals of a + * list differ slightly than on Solaris. This is not a problem as long as + * all callers stick to the published API. The two major differences are: + * + * 1) A list_node_t is mapped to a linux list_head struct which changes + * the name of the list_next/list_prev pointers to next/prev respectively. + * + * 2) A list_node_t which is not attached to a list on Solaris is denoted + * by having its list_next/list_prev pointers set to NULL. Under linux + * the next/prev pointers are set to LIST_POISON1 and LIST_POISON2 + * respectively. At this moment this only impacts the implementation + * of the list_link_init() and list_link_active() functions. + */ + +typedef struct list_node { + struct list_node *list_next; + struct list_node *list_prev; +} list_node_t; + + + +typedef struct list { + size_t list_size; + size_t list_offset; + list_node_t list_head; +} 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_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_link_active(list_node_t *); +int list_is_empty(list_t *); + +#define LIST_POISON1 NULL +#define LIST_POISON2 NULL + +#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.list_next == &(a)->list_head) + + +static inline void +list_link_init(list_node_t *node) +{ + node->list_next = LIST_POISON1; + node->list_prev = LIST_POISON2; +} + +static inline void +__list_del(list_node_t *prev, list_node_t *next) +{ + next->list_prev = prev; + prev->list_next = next; +} + +static inline void list_del(list_node_t *entry) +{ + __list_del(entry->list_prev, entry->list_next); + entry->list_next = LIST_POISON1; + entry->list_prev = LIST_POISON2; +} + +static inline void * +list_remove_head(list_t *list) +{ + list_node_t *head = list->list_head.list_next; + if (head == &list->list_head) + return (NULL); + + list_del(head); + return (list_object(list, head)); +} + +static inline void * +list_remove_tail(list_t *list) +{ + list_node_t *tail = list->list_head.list_prev; + if (tail == &list->list_head) + return (NULL); + + list_del(tail); + return (list_object(list, tail)); +} + +static inline void +list_link_replace(list_node_t *old_node, list_node_t *new_node) +{ + ASSERT(list_link_active(old_node)); + ASSERT(!list_link_active(new_node)); + + new_node->list_next = old_node->list_next; + new_node->list_prev = old_node->list_prev; + old_node->list_prev->list_next = new_node; + old_node->list_next->list_prev = new_node; + list_link_init(old_node); +} + +#endif /* SPL_LIST_H */ diff --git a/include/os/macos/spl/sys/misc.h b/include/os/macos/spl/sys/misc.h new file mode 100644 index 000000000000..0a88a2b549fc --- /dev/null +++ b/include/os/macos/spl/sys/misc.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 _SPL_SYS_MISC_H +#define _SPL_SYS_MISC_H + +#endif /* SPL_SYS_MISC_H */ diff --git a/include/os/macos/spl/sys/mod_os.h b/include/os/macos/spl/sys/mod_os.h new file mode 100644 index 000000000000..9f25d3a162a3 --- /dev/null +++ b/include/os/macos/spl/sys/mod_os.h @@ -0,0 +1,273 @@ +/* + * 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 _SPL_MOD_H +#define _SPL_MOD_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include + +#define MODULE_INIT(s) +#define MODULE_AUTHOR(s) +#define MODULE_LICENSE(s) +#define MODULE_VERSION(s) +#define ZFS_MODULE_DESCRIPTION(s) +#define ZFS_MODULE_AUTHOR(s) +#define ZFS_MODULE_LICENSE(s) +#define ZFS_MODULE_VERSION(s) + +#define __init __attribute__((unused)) +#define __exit __attribute__((unused)) + +/* + * The init/fini functions need to be called, but they are all static + */ +#define module_init(fn) \ + int wrap_ ## fn(void) \ + { \ + return (fn()); \ + } + +#define module_exit(fn) \ + void wrap_ ## fn(void) \ + { \ + fn(); \ + } + +// XNU defines SYSCTL_HANDLER_ARGS with "()" so it no worky. +// #define ZFS_MODULE_PARAM_ARGS SYSCTL_HANDLER_ARGS +#define ZFS_MODULE_PARAM_ARGS \ + struct sysctl_oid *oidp, void *arg1, int arg2, struct sysctl_req *req + +#define ZMOD_RW CTLFLAG_RW +#define ZMOD_RD CTLFLAG_RD + +/* BEGIN CSTYLED */ + +/* Handle some FreeBSD sysctl differences */ +#define SYSCTL_CONST_STRING(parent, nbr, name, access, ptr, descr) \ + SYSCTL_STRING(parent, nbr, name, access, ptr, sizeof(ptr), descr) +#define SYSCTL_UQUAD(parent, nbr, name, access, ptr, val, descr) \ + SYSCTL_QUAD(parent, nbr, name, access, ptr, descr) + +#define CTLFLAG_RWTUN CTLFLAG_RW +#define CTLFLAG_RDTUN CTLFLAG_RD +#define CTLTYPE_UINT CTLTYPE_INT +#define CTLTYPE_ULONG CTLTYPE_INT +#define CTLTYPE_U64 CTLTYPE_QUAD +#define CTLTYPE_S64 CTLTYPE_QUAD +#define CTLFLAG_MPSAFE 0 + +/* + * Why do all SYSCTL take "val" except for LONG/ULONG ? + * Jump through hoops here to handle that. + */ +#define ZSYSCTL_INT SYSCTL_INT +#define ZSYSCTL_UINT SYSCTL_UINT +#define ZSYSCTL_STRING SYSCTL_STRING +#define ZSYSCTL_LONG(parent, nbr, name, access, ptr, val, descr) \ + SYSCTL_LONG(parent, nbr, name, access, ptr, descr) +#if defined SYSCTL_ULONG +#define ZSYSCTL_ULONG(parent, nbr, name, access, ptr, val, descr) \ + SYSCTL_ULONG(parent, nbr, name, access, ptr, descr) +#else +#define ZSYSCTL_ULONG(parent, nbr, name, access, ptr, val, descr) \ + SYSCTL_LONG(parent, nbr, name, access, ptr, descr) +#endif +/* + * Appears to be no default for 64bit values in Linux, if + * ZOL adds it using STANDARD_PARAM_DEF let us guess + * they will go with LLONG/ULLONG + */ +#define ZSYSCTL_LLONG(parent, nbr, name, access, ptr, val, descr) \ + SYSCTL_QUAD(parent, nbr, name, access, ptr, descr) +#define ZSYSCTL_ULLONG(parent, nbr, name, access, ptr, val, descr) \ + SYSCTL_QUAD(parent, nbr, name, access, ptr, descr) +#define ZSYSCTL_U64(parent, nbr, name, access, ptr, val, descr) \ + SYSCTL_QUAD(parent, nbr, name, access, ptr, descr) +#define ZSYSCTL_S64(parent, nbr, name, access, ptr, val, descr) \ + SYSCTL_QUAD(parent, nbr, name, access, ptr, descr) + +/* See sysctl_os.c for the constructor work */ +#define ZFS_MODULE_PARAM(scope_prefix, name_prefix, name, type, perm, desc) \ + SYSCTL_DECL( _tunable_ ## scope_prefix); \ + ZSYSCTL_##type( _tunable_ ## scope_prefix, OID_AUTO, name, perm, \ + &name_prefix ## name, 0, desc) ; \ + __attribute__((constructor)) void \ + _zcnst_sysctl__tunable_ ## scope_prefix ## _ ## name (void) \ + { \ + sysctl_register_oid(&sysctl__tunable_ ## scope_prefix ## _ ## name ); \ + } \ + __attribute__((destructor)) void \ + _zdest_sysctl__tunable_ ## scope_prefix ## _ ## name (void) \ + { \ + sysctl_unregister_oid(&sysctl__tunable_ ## scope_prefix ## _ ## name ); \ + } + +/* + * Same as above, but direct names; so they can be empty. + * Used internally in macOS. + */ +#define ZFS_MODULE_IMPL(scope, variable, name, type, perm, desc) \ + SYSCTL_DECL( _tunable ## scope); \ + ZSYSCTL_##type( _tunable ## scope, OID_AUTO, name, perm, \ + &variable, 0, desc) ; \ + __attribute__((constructor)) void \ + _zcnst_sysctl__tunable ## scope ## _ ## name (void) \ + { \ + sysctl_register_oid(&sysctl__tunable ## scope ## _ ## name ); \ + } \ + __attribute__((destructor)) void \ + _zdest_sysctl__tunable ## scope ## _ ## name (void) \ + { \ + sysctl_unregister_oid(&sysctl__tunable ## scope ## _ ## name ); \ + } + +/* Function callback sysctls */ +#define ZFS_MODULE_PARAM_CALL_IMPL(parent, name, perm, args, desc) \ + SYSCTL_DECL(parent); \ + SYSCTL_PROC(parent, OID_AUTO, name, perm | args, desc) ; \ + __attribute__((constructor)) void \ + _zcnst_sysctl_ ## parent ## _ ## name (void) \ + { \ + sysctl_register_oid(&sysctl_## parent ## _ ## name ); \ + } \ + __attribute__((destructor)) void \ + _zdest_sysctl_ ## parent ## _ ## name (void) \ + { \ + sysctl_unregister_oid(&sysctl_ ## parent ## _ ## name ); \ + } + +/* + * Too few arguments? You probably added a new MODULE_PARAM_CALL + * but have yet to create a #define for it below, see for example + * blake3_param_set_args - ie, "func" + "_args" + */ +#define ZFS_MODULE_PARAM_CALL(scope_prefix, name_prefix, name, func, _, perm, desc) \ + ZFS_MODULE_PARAM_CALL_IMPL(_tunable_ ## scope_prefix, name, perm, func ## _args(name_prefix ## name), desc) + +#define ZFS_MODULE_VIRTUAL_PARAM_CALL ZFS_MODULE_PARAM_CALL + +/* + * FreeBSD anchor the function name (+ _args) to work out the + * CTLTYPE_* to use (and print "LU" etc). To call a wrapper + * function in sysctl_os.c, which calls the real function. + * We could also map "param_set_charp" to "CTLTYPE_STRING" more + * automatically, however, we still need manual update for the + * wrapping functions so it would not gain anything. + */ + +#define param_set_arc_u64_args(var) \ + CTLTYPE_QUAD, &var, sizeof (var), param_set_arc_u64, "QU" + +#define param_set_arc_min_args(var) \ + CTLTYPE_ULONG, &var, sizeof (var), param_set_arc_min, "LU" + +#define param_set_arc_max_args(var) \ + CTLTYPE_QUAD, &var, sizeof (var), param_set_arc_max, "QU" + +#define param_set_arc_int_args(var) \ + CTLTYPE_INT, &var, sizeof (var), param_set_arc_int, "I" + +#define param_set_deadman_failmode_args(var) \ + CTLTYPE_STRING, NULL, 0, param_set_deadman_failmode, "A" + +#define param_set_active_allocator_args(var) \ + CTLTYPE_STRING, NULL, 0, param_set_active_allocator, "A" + +#define param_set_deadman_synctime_args(var) \ + CTLTYPE_ULONG, NULL, 0, param_set_deadman_synctime, "LU" + +#define param_set_deadman_ziotime_args(var) \ + CTLTYPE_ULONG, NULL, 0, param_set_deadman_ziotime, "LU" + +#define param_set_multihost_interval_args(var) \ + CTLTYPE_ULONG, &var, sizeof (var), param_set_multihost_interval, "LU" + +#define param_set_slop_shift_args(var) \ + CTLTYPE_INT, &var, sizeof (var), param_set_slop_shift, "I" + +#define param_set_min_auto_ashift_args(var) \ + CTLTYPE_U64, &var, sizeof (var), param_set_min_auto_ashift, "QU" + +#define param_set_max_auto_ashift_args(var) \ + CTLTYPE_U64, &var, sizeof (var), param_set_max_auto_ashift, "QU" + +#define fletcher_4_param_set_args(var) \ + CTLTYPE_STRING, NULL, 0, fletcher_4_param, "A" + +#define blake3_param_set_args(var) \ + CTLTYPE_STRING, NULL, 0, blake3_param, "A" + +#define icp_gcm_avx_set_chunk_size_args(var) \ + CTLTYPE_STRING, var, 0, param_icp_gcm_avx_set_chunk_size, "A" + +#define icp_gcm_impl_set_args(var) \ + CTLTYPE_STRING, var, 0, param_icp_gcm_impl_set, "A" + +#define icp_aes_impl_set_args(var) \ + CTLTYPE_STRING, var, 0, param_icp_aes_impl_set, "A" + +#define zfs_vdev_raidz_impl_set_args(var) \ + CTLTYPE_STRING, var, 0, param_zfs_vdev_raidz_impl_set, "A" + +#define sha256_param_set_args(var) \ + CTLTYPE_STRING, NULL, 0, sha256_param, "A" + +#define sha512_param_set_args(var) \ + CTLTYPE_STRING, NULL, 0, sha512_param, "A" + +/* + * Too few arguments? You probably added a new MODULE_PARAM_CALL + * but have yet to create a #define for it above, see for example + * blake3_param_set_args - ie, "func" + "_args". As well as + * possible handler in os/macos/zfs/syscall_os.c + */ +#define module_param_call(name, _set, _get, var, mode) \ + extern int param_ ## func(ZFS_MODULE_PARAM_ARGS); \ + ZFS_MODULE_PARAM_CALL_IMPL(_tunable, name, ZMOD_RW, \ + _set ## _args(var), "xxx") + +#define module_param_named(a, b, c, d) + +#define module_init_early(fn) \ +void \ +wrap_ ## fn(void *dummy __unused) \ +{ \ + fn(); \ +} + +kern_return_t spl_start(kmod_info_t *ki, void *d); +kern_return_t spl_stop(kmod_info_t *ki, void *d); + +struct zfs_kernel_param_s; +typedef struct zfs_kernel_param_s zfs_kernel_param_t; + +extern int param_set_uint(char *v, zfs_kernel_param_t *kp); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SPL_MOD_H */ diff --git a/include/os/macos/spl/sys/mutex.h b/include/os/macos/spl/sys/mutex.h new file mode 100644 index 000000000000..ad280fa54c49 --- /dev/null +++ b/include/os/macos/spl/sys/mutex.h @@ -0,0 +1,175 @@ +/* + * 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 + */ + +/* + * + * OSX mutex functions + * + * Jorgen Lundman + * + */ + +#ifndef OSX_MUTEX_H +#define OSX_MUTEX_H + +#include +#include + +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + MUTEX_ADAPTIVE = 0, /* spin if owner is running, otherwise block */ + MUTEX_SPIN = 1, /* block interrupts and spin */ + MUTEX_DRIVER = 4, /* driver (DDI) mutex */ + MUTEX_DEFAULT = 6 /* kernel default mutex */ +} kmutex_type_t; + +#define MUTEX_NOLOCKDEP 0 + +/* + * Alas lck_mtx_t; is opaque and not available at compile time, and we + * really want to embed them. Luckily, mutex size has not changed in + * many versions of OSX. We should possibly to a startup check of + * the size though. + */ +typedef struct { + uint32_t opaque[4]; +} wrapper_mutex_t; + +/* + * To enable watchdog to keep an eye on mutex being held for too long + * define this debug variable. + */ + +#ifdef DEBUG +#define SPL_DEBUG_MUTEX +#endif + +#ifdef SPL_DEBUG_MUTEX +#define SPL_MUTEX_WATCHDOG_SLEEP 10 /* How long to sleep between checking */ +#define SPL_MUTEX_WATCHDOG_TIMEOUT 60 /* When is a mutex held too long? */ +#endif + +/* + * Solaris kmutex defined. + * + * and is embedded into ZFS structures (see dbuf) so we need to match the + * size carefully. It appears to be 32 bytes. Or rather, it needs to be + * aligned. + */ + +typedef struct kmutex { + void *m_owner; + wrapper_mutex_t m_lock; + + uint64_t m_waiters; + uint64_t m_sleepers; + +#ifdef SPL_DEBUG_MUTEX + void *leak; + uint64_t m_initialised; +#define MUTEX_INIT 0x123456789abcdef0ULL +#define MUTEX_DESTROYED 0xaabbccddaabbccddULL +#endif + +} kmutex_t; + +#include + +#define MUTEX_HELD(x) (mutex_owned(x)) +#define MUTEX_NOT_HELD(x) (!mutex_owned(x)) + +/* + * On OS X, CoreStorage provides these symbols, so we have to redefine them, + * preferably without having to modify SPL users. + */ +#ifdef SPL_DEBUG_MUTEX + +#define mutex_init(A, B, C, D) \ + spl_mutex_init(A, B, C, D, __FILE__, __FUNCTION__, __LINE__) +void spl_mutex_init(kmutex_t *mp, char *name, kmutex_type_t type, + void *ibc, const char *f, const char *fn, const int l); + +#else + +#define mutex_init spl_mutex_init +void spl_mutex_init(kmutex_t *mp, char *name, kmutex_type_t type, void *ibc); + +#endif + +#ifdef SPL_DEBUG_MUTEX +#define mutex_enter(X) spl_mutex_enter((X), __FILE__, __func__, __LINE__) +void spl_mutex_enter(kmutex_t *mp, const char *file, + const char *func, const int line); +#define mutex_enter_interruptible(X) \ + spl_mutex_enter_interruptible((X), __FILE__, __func__, __LINE__) +int spl_mutex_enter_interruptible(kmutex_t *mp, const char *file, + const char *func, const int line); +void spl_dbg_mutex_destroy(kmutex_t *, const char *, + const char *, const int); +#define mutex_destroy(X) spl_dbg_mutex_destroy(X, __FILE__, __func__, __LINE__) +#else +#define mutex_enter spl_mutex_enter +void spl_mutex_enter(kmutex_t *mp); +#define mutex_enter_interruptible spl_mutex_enter_interruptible +int spl_mutex_enter_interruptible(kmutex_t *mp); +#define mutex_destroy spl_mutex_destroy +#endif +#define mutex_enter_nested(A, B) mutex_enter(A) + +#define mutex_exit spl_mutex_exit +#define mutex_owned spl_mutex_owned +#define mutex_owner spl_mutex_owner + +void spl_mutex_destroy(kmutex_t *mp); +void spl_mutex_exit(kmutex_t *mp); +#ifdef SPL_DEBUG_MUTEX +int spl_mutex_tryenter(kmutex_t *mp, + const char *file, const char *func, const int line); +#define mutex_tryenter(M) spl_mutex_tryenter(M, __FILE__, __func__, __LINE__) +#else +int spl_mutex_tryenter(kmutex_t *mp); +#define mutex_tryenter spl_mutex_tryenter +#endif +int spl_mutex_owned(kmutex_t *mp); + +struct thread *spl_mutex_owner(kmutex_t *mp); + +int spl_mutex_subsystem_init(void); +void spl_mutex_subsystem_fini(void); + +extern lck_grp_attr_t *spl_mtx_grp_attr; +extern lck_attr_t *spl_mtx_lck_attr; +extern lck_grp_t *spl_mtx_grp; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/os/macos/spl/sys/param.h b/include/os/macos/spl/sys/param.h new file mode 100644 index 000000000000..1d01fa47b4a9 --- /dev/null +++ b/include/os/macos/spl/sys/param.h @@ -0,0 +1,43 @@ +/* + * 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 Jorgen Lundman + * + */ + +#ifndef _SPL_PARAM_H +#define _SPL_PARAM_H + +#include +#include + +#include_next +#include + +/* Pages to bytes and back */ +#define ptob(pages) (pages << PAGE_SHIFT) +#define btop(bytes) (bytes >> PAGE_SHIFT) + +#define MAXUID UINT32_MAX + +#endif /* SPL_PARAM_H */ diff --git a/include/os/macos/spl/sys/policy.h b/include/os/macos/spl/sys/policy.h new file mode 100644 index 000000000000..2e786ea291e1 --- /dev/null +++ b/include/os/macos/spl/sys/policy.h @@ -0,0 +1,91 @@ +/* + * 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) 2008 MacZFS + * Copyright (C) 2013 Jorgen Lundman + * + */ + +#ifndef _SPL_POLICY_H +#define _SPL_POLICY_H + +#ifdef _KERNEL + +#include +#include + +struct vattr; + +int secpolicy_fs_unmount(cred_t *, struct mount *); +int secpolicy_nfs(const cred_t *); +int secpolicy_sys_config(const cred_t *, boolean_t); +int secpolicy_zfs(const cred_t *); +int secpolicy_zinject(const cred_t *); + +/* + * This function to be called from xxfs_setattr(). + * Must be called with the node's attributes read-write locked. + * + * cred_t * - acting credentials + * struct vnode * - vnode we're operating on + * struct vattr *va - new attributes, va_mask may be + * changed on return from a call + * struct vattr *oldva - old attributes, need include owner + * and mode only + * int flags - setattr flags + * int iaccess(void *node, int mode, cred_t *cr) + * - non-locking internal access function + * mode be checked + * w/ VREAD|VWRITE|VEXEC, not fs + * internal mode encoding. + * + * void *node - internal node (inode, tmpnode) to + * pass as arg to iaccess + */ +int secpolicy_vnode_setattr(cred_t *, struct vnode *, vattr_t *, + const vattr_t *, int, int (void *, int, cred_t *), void *); + +int secpolicy_vnode_stky_modify(const cred_t *); +int secpolicy_setid_setsticky_clear(struct vnode *vp, vattr_t *vap, + const vattr_t *ovap, cred_t *cr); + +int secpolicy_vnode_remove(struct vnode *, const cred_t *); +int secpolicy_vnode_create_gid(const cred_t *); +int secpolicy_vnode_setids_setgids(struct vnode *, const cred_t *, gid_t); +int secpolicy_vnode_setdac(struct vnode *, const cred_t *, uid_t); +int secpolicy_vnode_chown(struct vnode *, const cred_t *, uid_t); +struct znode; +int secpolicy_vnode_setid_retain(struct znode *, const cred_t *, boolean_t); +int secpolicy_xvattr(vattr_t *, uid_t, const cred_t *, mode_t); +int secpolicy_setid_clear(vattr_t *, const cred_t *); +int secpolicy_basic_link(const cred_t *); +int secpolicy_fs_mount_clearopts(const cred_t *, struct mount *); +int secpolicy_fs_mount(const cred_t *, struct vnode *, struct mount *); +int secpolicy_zfs_proc(const cred_t *, proc_t *); +int secpolicy_vnode_any_access(const cred_t *, struct vnode *, uid_t); +int secpolicy_vnode_access2(const cred_t *, struct vnode *, + uid_t, mode_t, mode_t); + +#endif /* _KERNEL */ + +#endif /* SPL_POLICY_H */ diff --git a/include/os/macos/spl/sys/priv.h b/include/os/macos/spl/sys/priv.h new file mode 100644 index 000000000000..a8da8f101ec5 --- /dev/null +++ b/include/os/macos/spl/sys/priv.h @@ -0,0 +1,531 @@ +/* + * Copyright (c) 2006 nCircle Network Security, Inc. + * All rights reserved. + * + * This software was developed by Robert N. M. Watson for the TrustedBSD + * Project under contract to nCircle Network Security, Inc. + * + * 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 AUTHOR 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 AUTHOR, NCIRCLE NETWORK SECURITY, + * INC., 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$ + */ + +/* + * Privilege checking interface for BSD kernel. + */ +#ifndef _SPL_PRIV_H +#define _SPL_PRIV_H + +/* + * Privilege list, sorted loosely by kernel subsystem. + * + * Think carefully before adding or reusing one of these privileges -- are + * there existing instances referring to the same privilege? Third party + * vendors may request the assignment of privileges to be used in loadable + * modules. Particular numeric privilege assignments are part of the + * loadable kernel module ABI, and should not be changed across minor + * releases. + * + * When adding a new privilege, remember to determine if it's appropriate for + * use in jail, and update the privilege switch in kern_jail.c as necessary. + */ + +/* + * Track beginning of privilege list. + */ +#define _PRIV_LOWEST 1 + +/* + * The remaining privileges typically correspond to one or a small + * number of specific privilege checks, and have (relatively) precise + * meanings. They are loosely sorted into a set of base system + * privileges, such as the ability to reboot, and then loosely by + * subsystem, indicated by a subsystem name. + */ +#define _PRIV_ROOT 1 /* Removed. */ +#define PRIV_ACCT 2 /* Manage process accounting. */ +#define PRIV_MAXFILES 3 /* Exceed system open files limit. */ +#define PRIV_MAXPROC 4 /* Exceed system processes limit. */ +#define PRIV_KTRACE 5 /* Set/clear KTRFAC_ROOT on ktrace. */ +#define PRIV_SETDUMPER 6 /* Configure dump device. */ +#define PRIV_REBOOT 8 /* Can reboot system. */ +#define PRIV_SWAPON 9 /* Can swapon(). */ +#define PRIV_SWAPOFF 10 /* Can swapoff(). */ +#define PRIV_MSGBUF 11 /* Can read kernel message buffer. */ +#define PRIV_IO 12 /* Can perform low-level I/O. */ +#define PRIV_KEYBOARD 13 /* Reprogram keyboard. */ +#define PRIV_DRIVER 14 /* Low-level driver privilege. */ +#define PRIV_ADJTIME 15 /* Set time adjustment. */ +#define PRIV_NTP_ADJTIME 16 /* Set NTP time adjustment. */ +#define PRIV_CLOCK_SETTIME 17 /* Can call clock_settime. */ +#define PRIV_SETTIMEOFDAY 18 /* Can call settimeofday. */ +#define _PRIV_SETHOSTID 19 /* Removed. */ +#define _PRIV_SETDOMAINNAME 20 /* Removed. */ + +/* + * Audit subsystem privileges. + */ +#define PRIV_AUDIT_CONTROL 40 /* Can configure audit. */ +#define PRIV_AUDIT_FAILSTOP 41 /* Can run during audit fail stop. */ +#define PRIV_AUDIT_GETAUDIT 42 /* Can get proc audit properties. */ +#define PRIV_AUDIT_SETAUDIT 43 /* Can set proc audit properties. */ +#define PRIV_AUDIT_SUBMIT 44 /* Can submit an audit record. */ + +/* + * Credential management privileges. + */ +#define PRIV_CRED_SETUID 50 /* setuid. */ +#define PRIV_CRED_SETEUID 51 /* seteuid to !ruid and !svuid. */ +#define PRIV_CRED_SETGID 52 /* setgid. */ +#define PRIV_CRED_SETEGID 53 /* setgid to !rgid and !svgid. */ +#define PRIV_CRED_SETGROUPS 54 /* Set process additional groups. */ +#define PRIV_CRED_SETREUID 55 /* setreuid. */ +#define PRIV_CRED_SETREGID 56 /* setregid. */ +#define PRIV_CRED_SETRESUID 57 /* setresuid. */ +#define PRIV_CRED_SETRESGID 58 /* setresgid. */ +#define PRIV_SEEOTHERGIDS 59 /* Exempt bsd.seeothergids. */ +#define PRIV_SEEOTHERUIDS 60 /* Exempt bsd.seeotheruids. */ + +/* + * Debugging privileges. + */ +#define PRIV_DEBUG_DIFFCRED 80 /* Exempt debugging other users. */ +#define PRIV_DEBUG_SUGID 81 /* Exempt debugging setuid proc. */ +#define PRIV_DEBUG_UNPRIV 82 /* Exempt unprivileged debug limit. */ +#define PRIV_DEBUG_DENIED 83 /* Exempt P2_NOTRACE. */ + +/* + * Dtrace privileges. + */ +#define PRIV_DTRACE_KERNEL 90 /* Allow use of DTrace on the kernel. */ +#define PRIV_DTRACE_PROC 91 /* Allow attaching DTrace to process. */ +#define PRIV_DTRACE_USER 92 /* Process may submit DTrace events. */ + +/* + * Firmware privilegs. + */ +#define PRIV_FIRMWARE_LOAD 100 /* Can load firmware. */ + +/* + * Jail privileges. + */ +#define PRIV_JAIL_ATTACH 110 /* Attach to a jail. */ +#define PRIV_JAIL_SET 111 /* Set jail parameters. */ +#define PRIV_JAIL_REMOVE 112 /* Remove a jail. */ + +/* + * Kernel environment priveleges. + */ +#define PRIV_KENV_SET 120 /* Set kernel env. variables. */ +#define PRIV_KENV_UNSET 121 /* Unset kernel env. variables. */ + +/* + * Loadable kernel module privileges. + */ +#define PRIV_KLD_LOAD 130 /* Load a kernel module. */ +#define PRIV_KLD_UNLOAD 131 /* Unload a kernel module. */ + +/* + * Privileges associated with the MAC Framework and specific MAC policy + * modules. + */ +#define PRIV_MAC_PARTITION 140 /* Privilege in mac_partition policy. */ +#define PRIV_MAC_PRIVS 141 /* Privilege in the mac_privs policy. */ + +/* + * Process-related privileges. + */ +#define PRIV_PROC_LIMIT 160 /* Exceed user process limit. */ +#define PRIV_PROC_SETLOGIN 161 /* Can call setlogin. */ +#define PRIV_PROC_SETRLIMIT 162 /* Can raise resources limits. */ +#define PRIV_PROC_SETLOGINCLASS 163 /* Can call setloginclass(2). */ + +/* + * System V IPC privileges. + */ +#define PRIV_IPC_READ 170 /* Can override IPC read perm. */ +#define PRIV_IPC_WRITE 171 /* Can override IPC write perm. */ +#define PRIV_IPC_ADMIN 172 /* Can override IPC owner-only perm. */ +#define PRIV_IPC_MSGSIZE 173 /* Exempt IPC message queue limit. */ + +/* + * POSIX message queue privileges. + */ +#define PRIV_MQ_ADMIN 180 /* Can override msgq owner-only perm. */ + +/* + * Performance monitoring counter privileges. + */ +#define PRIV_PMC_MANAGE 190 /* Can administer PMC. */ +#define PRIV_PMC_SYSTEM 191 /* Can allocate a system-wide PMC. */ + +/* + * Scheduling privileges. + */ +#define PRIV_SCHED_DIFFCRED 200 /* Exempt scheduling other users. */ +#define PRIV_SCHED_SETPRIORITY 201 /* Can set lower nice value for proc. */ +#define PRIV_SCHED_RTPRIO 202 /* Can set real time scheduling. */ +#define PRIV_SCHED_SETPOLICY 203 /* Can set scheduler policy. */ +#define PRIV_SCHED_SET 204 /* Can set thread scheduler. */ +#define PRIV_SCHED_SETPARAM 205 /* Can set thread scheduler params. */ +#define PRIV_SCHED_CPUSET 206 /* Can manipulate cpusets. */ +#define PRIV_SCHED_CPUSET_INTR 207 /* Can adjust IRQ to CPU binding. */ + +/* + * POSIX semaphore privileges. + */ +#define PRIV_SEM_WRITE 220 /* Can override sem write perm. */ + +/* + * Signal privileges. + */ +#define PRIV_SIGNAL_DIFFCRED 230 /* Exempt signalling other users. */ +#define PRIV_SIGNAL_SUGID 231 /* Non-conserv signal setuid proc. */ + +/* + * Sysctl privileges. + */ +#define PRIV_SYSCTL_DEBUG 240 /* Can invoke sysctl.debug. */ +#define PRIV_SYSCTL_WRITE 241 /* Can write sysctls. */ +#define PRIV_SYSCTL_WRITEJAIL 242 /* Can write sysctls, jail permitted. */ + +/* + * TTY privileges. + */ +#define PRIV_TTY_CONSOLE 250 /* Set console to tty. */ +#define PRIV_TTY_DRAINWAIT 251 /* Set tty drain wait time. */ +#define PRIV_TTY_DTRWAIT 252 /* Set DTR wait on tty. */ +#define PRIV_TTY_EXCLUSIVE 253 /* Override tty exclusive flag. */ +#define _PRIV_TTY_PRISON 254 /* Removed. */ +#define PRIV_TTY_STI 255 /* Simulate input on another tty. */ +#define PRIV_TTY_SETA 256 /* Set tty termios structure. */ + +/* + * UFS-specific privileges. + */ +#define PRIV_UFS_EXTATTRCTL 270 /* Can configure EAs on UFS1. */ +#define PRIV_UFS_QUOTAOFF 271 /* quotaoff(). */ +#define PRIV_UFS_QUOTAON 272 /* quotaon(). */ +#define PRIV_UFS_SETUSE 273 /* setuse(). */ + +/* + * ZFS-specific privileges. + */ +#define PRIV_ZFS_POOL_CONFIG 280 /* Can configure ZFS pools. */ + +/* Can inject faults in the ZFS fault injection framework. */ +#define PRIV_ZFS_INJECT 281 + +/* Can attach/detach ZFS file systems to/from jails. */ +#define PRIV_ZFS_JAIL 282 + +/* + * NFS-specific privileges. + */ +#define PRIV_NFS_DAEMON 290 /* Can become the NFS daemon. */ +#define PRIV_NFS_LOCKD 291 /* Can become NFS lock daemon. */ + +/* + * VFS privileges. + */ +#define PRIV_VFS_READ 310 /* Override vnode DAC read perm. */ +#define PRIV_VFS_WRITE 311 /* Override vnode DAC write perm. */ +#define PRIV_VFS_ADMIN 312 /* Override vnode DAC admin perm. */ +#define PRIV_VFS_EXEC 313 /* Override vnode DAC exec perm. */ +#define PRIV_VFS_LOOKUP 314 /* Override vnode DAC lookup perm. */ +#define PRIV_VFS_BLOCKRESERVE 315 /* Can use free block reserve. */ +#define PRIV_VFS_CHFLAGS_DEV 316 /* Can chflags() a device node. */ +#define PRIV_VFS_CHOWN 317 /* Can set user; group to non-member. */ +#define PRIV_VFS_CHROOT 318 /* chroot(). */ +#define PRIV_VFS_RETAINSUGID 319 /* Can retain sugid bits on change. */ +#define PRIV_VFS_EXCEEDQUOTA 320 /* Exempt from quota restrictions. */ +#define PRIV_VFS_EXTATTR_SYSTEM 321 /* Operate on system EA namespace. */ +#define PRIV_VFS_FCHROOT 322 /* fchroot(). */ +#define PRIV_VFS_FHOPEN 323 /* Can fhopen(). */ +#define PRIV_VFS_FHSTAT 324 /* Can fhstat(). */ +#define PRIV_VFS_FHSTATFS 325 /* Can fhstatfs(). */ +#define PRIV_VFS_GENERATION 326 /* stat() returns generation number. */ +#define PRIV_VFS_GETFH 327 /* Can retrieve file handles. */ +#define PRIV_VFS_GETQUOTA 328 /* getquota(). */ +#define PRIV_VFS_LINK 329 /* bsd.hardlink_check_uid */ +#define PRIV_VFS_MKNOD_BAD 330 /* Can mknod() to mark bad inodes. */ +#define PRIV_VFS_MKNOD_DEV 331 /* Can mknod() to create dev nodes. */ +#define PRIV_VFS_MKNOD_WHT 332 /* Can mknod() to create whiteout. */ +#define PRIV_VFS_MOUNT 333 /* Can mount(). */ +#define PRIV_VFS_MOUNT_OWNER 334 /* Can manage other users' fsystems. */ +#define PRIV_VFS_MOUNT_EXPORTED 335 /* Can set MNT_EXPORTED on mount. */ +#define PRIV_VFS_MOUNT_PERM 336 /* Override dev node perms at mount. */ +#define PRIV_VFS_MOUNT_SUIDDIR 337 /* Can set MNT_SUIDDIR on mount. */ +#define PRIV_VFS_MOUNT_NONUSER 338 /* Can perform a non-user mount. */ +#define PRIV_VFS_SETGID 339 /* Can setgid if not in group. */ +#define PRIV_VFS_SETQUOTA 340 /* setquota(). */ +#define PRIV_VFS_STICKYFILE 341 /* Can set sticky bit on file. */ +#define PRIV_VFS_SYSFLAGS 342 /* Can modify system flags. */ +#define PRIV_VFS_UNMOUNT 343 /* Can unmount(). */ +#define PRIV_VFS_STAT 344 /* Override vnode MAC stat perm. */ + +/* + * Virtual memory privileges. + */ +#define PRIV_VM_MADV_PROTECT 360 /* Can set MADV_PROTECT. */ +#define PRIV_VM_MLOCK 361 /* Can mlock(), mlockall(). */ +#define PRIV_VM_MUNLOCK 362 /* Can munlock(), munlockall(). */ +/* Can override the global swap reservation limits. */ +#define PRIV_VM_SWAP_NOQUOTA 363 +/* Can override the per-uid swap reservation limits. */ +#define PRIV_VM_SWAP_NORLIMIT 364 + +/* + * Device file system privileges. + */ +#define PRIV_DEVFS_RULE 370 /* Can manage devfs rules. */ +#define PRIV_DEVFS_SYMLINK 371 /* Can create symlinks in devfs. */ + +/* + * Random number generator privileges. + */ +#define PRIV_RANDOM_RESEED 380 /* Closing /dev/random reseeds. */ + +/* + * Network stack privileges. + */ +#define PRIV_NET_BRIDGE 390 /* Administer bridge. */ +#define PRIV_NET_GRE 391 /* Administer GRE. */ +#define _PRIV_NET_PPP 392 /* Removed. */ +#define _PRIV_NET_SLIP 393 /* Removed. */ +#define PRIV_NET_BPF 394 /* Monitor BPF. */ +#define PRIV_NET_RAW 395 /* Open raw socket. */ +#define PRIV_NET_ROUTE 396 /* Administer routing. */ +#define PRIV_NET_TAP 397 /* Can open tap device. */ +#define PRIV_NET_SETIFMTU 398 /* Set interface MTU. */ +#define PRIV_NET_SETIFFLAGS 399 /* Set interface flags. */ +#define PRIV_NET_SETIFCAP 400 /* Set interface capabilities. */ +#define PRIV_NET_SETIFNAME 401 /* Set interface name. */ +#define PRIV_NET_SETIFMETRIC 402 /* Set interface metrics. */ +#define PRIV_NET_SETIFPHYS 403 /* Set interface physical layer prop. */ +#define PRIV_NET_SETIFMAC 404 /* Set interface MAC label. */ +#define PRIV_NET_ADDMULTI 405 /* Add multicast addr. to ifnet. */ +#define PRIV_NET_DELMULTI 406 /* Delete multicast addr. from ifnet. */ +#define PRIV_NET_HWIOCTL 407 /* Issue hardware ioctl on ifnet. */ +#define PRIV_NET_SETLLADDR 408 /* Set interface link-level address. */ +#define PRIV_NET_ADDIFGROUP 409 /* Add new interface group. */ +#define PRIV_NET_DELIFGROUP 410 /* Delete interface group. */ +#define PRIV_NET_IFCREATE 411 /* Create cloned interface. */ +#define PRIV_NET_IFDESTROY 412 /* Destroy cloned interface. */ +#define PRIV_NET_ADDIFADDR 413 /* Add protocol addr to interface. */ +#define PRIV_NET_DELIFADDR 414 /* Delete protocol addr on interface. */ +#define PRIV_NET_LAGG 415 /* Administer lagg interface. */ +#define PRIV_NET_GIF 416 /* Administer gif interface. */ +#define PRIV_NET_SETIFVNET 417 /* Move interface to vnet. */ +#define PRIV_NET_SETIFDESCR 418 /* Set interface description. */ +#define PRIV_NET_SETIFFIB 419 /* Set interface fib. */ +#define PRIV_NET_VXLAN 420 /* Administer vxlan. */ + +/* + * 802.11-related privileges. + */ +#define PRIV_NET80211_GETKEY 440 /* Query 802.11 keys. */ +#define PRIV_NET80211_MANAGE 441 /* Administer 802.11. */ + +/* + * Placeholder for AppleTalk privileges, not supported anymore. + */ +#define _PRIV_NETATALK_RESERVEDPORT 450 /* Bind low port number. */ + +/* + * ATM privileges. + */ +#define PRIV_NETATM_CFG 460 +#define PRIV_NETATM_ADD 461 +#define PRIV_NETATM_DEL 462 +#define PRIV_NETATM_SET 463 + +/* + * Bluetooth privileges. + */ +#define PRIV_NETBLUETOOTH_RAW 470 /* Open raw bluetooth socket. */ + +/* + * Netgraph and netgraph module privileges. + */ +#define PRIV_NETGRAPH_CONTROL 480 /* Open netgraph control socket. */ +#define PRIV_NETGRAPH_TTY 481 /* Configure tty for netgraph. */ + +/* + * IPv4 and IPv6 privileges. + */ +#define PRIV_NETINET_RESERVEDPORT 490 /* Bind low port number. */ +#define PRIV_NETINET_IPFW 491 /* Administer IPFW firewall. */ +#define PRIV_NETINET_DIVERT 492 /* Open IP divert socket. */ +#define PRIV_NETINET_PF 493 /* Administer pf firewall. */ +#define PRIV_NETINET_DUMMYNET 494 /* Administer DUMMYNET. */ +#define PRIV_NETINET_CARP 495 /* Administer CARP. */ +#define PRIV_NETINET_MROUTE 496 /* Administer multicast routing. */ +#define PRIV_NETINET_RAW 497 /* Open netinet raw socket. */ +#define PRIV_NETINET_GETCRED 498 /* Query netinet pcb credentials. */ +#define PRIV_NETINET_ADDRCTRL6 499 /* Administer IPv6 address scopes. */ +#define PRIV_NETINET_ND6 500 /* Administer IPv6 neighbor disc. */ +#define PRIV_NETINET_SCOPE6 501 /* Administer IPv6 address scopes. */ +#define PRIV_NETINET_ALIFETIME6 502 /* Administer IPv6 address lifetimes. */ +#define PRIV_NETINET_IPSEC 503 /* Administer IPSEC. */ +#define PRIV_NETINET_REUSEPORT 504 /* Allow [rapid] port/address reuse. */ +#define PRIV_NETINET_SETHDROPTS 505 /* Set certain IPv4/6 header options. */ +#define PRIV_NETINET_BINDANY 506 /* Allow bind to any address. */ +#define PRIV_NETINET_HASHKEY 507 /* Get and set hash keys for IPv4/6. */ + +/* + * Placeholders for IPX/SPX privileges, not supported any more. + */ +#define _PRIV_NETIPX_RESERVEDPORT 520 /* Bind low port number. */ +#define _PRIV_NETIPX_RAW 521 /* Open netipx raw socket. */ + +/* + * NCP privileges. + */ +#define PRIV_NETNCP 530 /* Use another user's connection. */ + +/* + * SMB privileges. + */ +#define PRIV_NETSMB 540 /* Use another user's connection. */ + +/* + * VM86 privileges. + */ +#define PRIV_VM86_INTCALL 550 /* Allow invoking vm86 int handlers. */ + +/* + * Set of reserved privilege values, which will be allocated to code as + * needed, in order to avoid renumbering later privileges due to insertion. + */ +#define _PRIV_RESERVED0 560 +#define _PRIV_RESERVED1 561 +#define _PRIV_RESERVED2 562 +#define _PRIV_RESERVED3 563 +#define _PRIV_RESERVED4 564 +#define _PRIV_RESERVED5 565 +#define _PRIV_RESERVED6 566 +#define _PRIV_RESERVED7 567 +#define _PRIV_RESERVED8 568 +#define _PRIV_RESERVED9 569 +#define _PRIV_RESERVED10 570 +#define _PRIV_RESERVED11 571 +#define _PRIV_RESERVED12 572 +#define _PRIV_RESERVED13 573 +#define _PRIV_RESERVED14 574 +#define _PRIV_RESERVED15 575 + +/* + * Define a set of valid privilege numbers that can be used by loadable + * modules that don't yet have privilege reservations. Ideally, these should + * not be used, since their meaning is opaque to any policies that are aware + * of specific privileges, such as jail, and as such may be arbitrarily + * denied. + */ +#define PRIV_MODULE0 600 +#define PRIV_MODULE1 601 +#define PRIV_MODULE2 602 +#define PRIV_MODULE3 603 +#define PRIV_MODULE4 604 +#define PRIV_MODULE5 605 +#define PRIV_MODULE6 606 +#define PRIV_MODULE7 607 +#define PRIV_MODULE8 608 +#define PRIV_MODULE9 609 +#define PRIV_MODULE10 610 +#define PRIV_MODULE11 611 +#define PRIV_MODULE12 612 +#define PRIV_MODULE13 613 +#define PRIV_MODULE14 614 +#define PRIV_MODULE15 615 + +/* + * DDB(4) privileges. + */ +#define PRIV_DDB_CAPTURE 620 /* Allow reading of DDB capture log. */ + +/* + * Arla/nnpfs privileges. + */ +#define PRIV_NNPFS_DEBUG 630 /* Perforn ARLA_VIOC_NNPFSDEBUG. */ + +/* + * cpuctl(4) privileges. + */ +#define PRIV_CPUCTL_WRMSR 640 /* Write model-specific register. */ +#define PRIV_CPUCTL_UPDATE 641 /* Update cpu microcode. */ + +/* + * Capi4BSD privileges. + */ +#define PRIV_C4B_RESET_CTLR 650 /* Load firmware, reset controller. */ +#define PRIV_C4B_TRACE 651 /* Unrestricted CAPI message tracing. */ + +/* + * OpenAFS privileges. + */ +#define PRIV_AFS_ADMIN 660 /* Can change AFS client settings. */ +#define PRIV_AFS_DAEMON 661 /* Can become the AFS daemon. */ + +/* + * Resource Limits privileges. + */ +#define PRIV_RCTL_GET_RACCT 670 +#define PRIV_RCTL_GET_RULES 671 +#define PRIV_RCTL_GET_LIMITS 672 +#define PRIV_RCTL_ADD_RULE 673 +#define PRIV_RCTL_REMOVE_RULE 674 + +/* + * mem(4) privileges. + */ +#define PRIV_KMEM_READ 680 /* Open mem/kmem for reading. */ +#define PRIV_KMEM_WRITE 681 /* Open mem/kmem for writing. */ + +/* + * Track end of privilege list. + */ +#define _PRIV_HIGHEST 682 + +/* + * Validate that a named privilege is known by the privilege system. Invalid + * privileges presented to the privilege system by a priv_check interface + * will result in a panic. This is only approximate due to sparse allocation + * of the privilege space. + */ +#define PRIV_VALID(x) ((x) > _PRIV_LOWEST && (x) < _PRIV_HIGHEST) + +#ifdef _KERNEL +/* + * Privilege check interfaces, modeled after historic suser() interfaces, but + * with the addition of a specific privilege name. No flags are currently + * defined for the API. Historically, flags specified using the real uid + * instead of the effective uid, and whether or not the check should be + * allowed in jail. + */ +struct thread; +struct ucred; +int priv_check(struct thread *td, int priv); +int priv_check_cred(struct ucred *cred, int priv, int flags); +#endif + +#endif /* _SPL_PRIV_H */ diff --git a/include/os/macos/spl/sys/proc.h b/include/os/macos/spl/sys/proc.h new file mode 100644 index 000000000000..132964d9c1c1 --- /dev/null +++ b/include/os/macos/spl/sys/proc.h @@ -0,0 +1,47 @@ +/* + * 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, 2020 Jorgen Lundman + * + */ + +#ifndef _SPL_PROC_H +#define _SPL_PROC_H + +#include +#include +#include_next +#include +#include + +#define proc_t struct proc + +extern proc_t p0; /* process 0 */ + +static inline boolean_t +zfs_proc_is_caller(proc_t *p) +{ + return (p == curproc); +} + +#endif /* SPL_PROC_H */ diff --git a/include/os/macos/spl/sys/processor.h b/include/os/macos/spl/sys/processor.h new file mode 100644 index 000000000000..993939576ddc --- /dev/null +++ b/include/os/macos/spl/sys/processor.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 (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 Jorgen Lundman + * + */ + +#ifndef _SPL_PROCESSOR_H +#define _SPL_PROCESSOR_H + +#include + +extern uint32_t getcpuid(void); +extern uint64_t spl_cpuid_features(void); + +typedef int processorid_t; + +#endif /* _SPL_PROCESSOR_H */ diff --git a/include/os/macos/spl/sys/procfs_list.h b/include/os/macos/spl/sys/procfs_list.h new file mode 100644 index 000000000000..d77fb72c3066 --- /dev/null +++ b/include/os/macos/spl/sys/procfs_list.h @@ -0,0 +1,64 @@ +/* + * 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 _SPL_PROCFS_LIST_H +#define _SPL_PROCFS_LIST_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct procfs_list procfs_list_t; +struct procfs_list { + void *pl_private; + void *pl_next_data; + kmutex_t pl_lock; + list_t pl_list; + uint64_t pl_next_id; + int (*pl_show)(struct seq_file *f, void *p); + int (*pl_show_header)(struct seq_file *f); + int (*pl_clear)(procfs_list_t *procfs_list); + size_t pl_node_offset; +}; + +typedef struct procfs_list_node { + list_node_t pln_link; + uint64_t pln_id; +} procfs_list_node_t; + +void procfs_list_install(const char *module, + const char *submodule, + 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); +void procfs_list_uninstall(procfs_list_t *procfs_list); +void procfs_list_destroy(procfs_list_t *procfs_list); +void procfs_list_add(procfs_list_t *procfs_list, void *p); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/os/macos/spl/sys/random.h b/include/os/macos/spl/sys/random.h new file mode 100644 index 000000000000..3696b13a155e --- /dev/null +++ b/include/os/macos/spl/sys/random.h @@ -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 (C) 2013 Jorgen Lundman + * + */ + +#ifndef _SPL_RANDOM_H +#define _SPL_RANDOM_H + +#include_next + + +static inline int +random_get_bytes(uint8_t *ptr, size_t len) +{ + read_random(ptr, len); + return (0); +} + +static inline int +random_get_pseudo_bytes(uint8_t *ptr, size_t len) +{ + read_random(ptr, len); + return (0); +} + +static inline uint32_t +random_in_range(uint32_t range) +{ + uint32_t r; + + ASSERT(range != 0); + + if (range == 1) + return (0); + + read_random((void *)&r, sizeof (r)); + + return (r % range); +} + +#endif /* _SPL_RANDOM_H */ diff --git a/include/os/macos/spl/sys/rwlock.h b/include/os/macos/spl/sys/rwlock.h new file mode 100644 index 000000000000..e3baa9bd1531 --- /dev/null +++ b/include/os/macos/spl/sys/rwlock.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. + */ + +#ifndef _SPL_RWLOCK_H +#define _SPL_RWLOCK_H + +#ifdef DEBUG +#define SPL_DEBUG_RWLOCK +#endif + +#include +#include + +typedef enum { + RW_DRIVER = 2, + RW_DEFAULT = 4 +} krw_type_t; + +typedef enum { + RW_NONE = 0, + RW_WRITER = 1, + RW_READER = 2 +} krw_t; + +#define RW_NOLOCKDEP 0 + +struct krwlock { + uint32_t rw_lock[4]; /* opaque lck_rw_t data */ + kthread_t *rw_owner; /* writer (exclusive) lock only */ + int rw_readers; /* reader lock only */ + int rw_pad; /* */ +#ifdef SPL_DEBUG_RWLOCK + void *leak; +#endif +}; +typedef struct krwlock krwlock_t; + +#define RW_WRITE_HELD(x) (rw_write_held((x))) +#define RW_LOCK_HELD(x) (rw_lock_held((x))) +#define RW_READ_HELD(x) (rw_read_held((x))) + +#ifdef SPL_DEBUG_RWLOCK +#define rw_init(A, B, C, D) \ + rw_initx(A, B, C, D, __FILE__, __FUNCTION__, __LINE__) +extern void rw_initx(krwlock_t *, char *, krw_type_t, void *, + const char *, const char *, int); +#else +extern void rw_init(krwlock_t *, char *, krw_type_t, void *); +#endif +extern void rw_destroy(krwlock_t *); +extern void rw_enter(krwlock_t *, krw_t); +extern int rw_tryenter(krwlock_t *, krw_t); +extern void rw_exit(krwlock_t *); +extern void rw_downgrade(krwlock_t *); +extern int rw_tryupgrade(krwlock_t *); +extern int rw_write_held(krwlock_t *); +extern int rw_read_held(krwlock_t *); +extern int rw_lock_held(krwlock_t *); +extern int rw_isinit(krwlock_t *); + +int spl_rwlock_init(void); +void spl_rwlock_fini(void); + +#endif /* _SPL_RWLOCK_H */ diff --git a/include/os/macos/spl/sys/seg_kmem.h b/include/os/macos/spl/sys/seg_kmem.h new file mode 100644 index 000000000000..1ec517142858 --- /dev/null +++ b/include/os/macos/spl/sys/seg_kmem.h @@ -0,0 +1,87 @@ +/* + * 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. + */ + +#ifndef _VM_SEG_KMEM_H +#define _VM_SEG_KMEM_H + + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* + * VM - Kernel Segment Driver + */ + +#if defined(_KERNEL) + +extern uint64_t segkmem_total_allocated; + +/* segregated vmem arenas for abd */ +extern vmem_t *abd_arena; +extern vmem_t *abd_subpage_arena; + +/* + * segkmem page vnodes + */ +#define kvp (kvps[KV_KVP]) +#define zvp (kvps[KV_ZVP]) +#if defined(__sparc) +#define mpvp (kvps[KV_MPVP]) +#define promvp (kvps[KV_PROMVP]) +#endif /* __sparc */ + +void *segkmem_alloc(vmem_t *, size_t, int); +extern void segkmem_free(vmem_t *, const void *, size_t); +extern void kernelheap_init(void); +extern void kernelheap_fini(void); + +extern void segkmem_abd_init(void); +extern void segkmem_abd_fini(void); + +/* + * Flags for segkmem_xalloc(). + * + * SEGKMEM_SHARELOCKED requests pages which are locked SE_SHARED to be + * returned rather than unlocked which is now the default. Note that + * memory returned by SEGKMEM_SHARELOCKED cannot be freed by segkmem_free(). + * This is a hack for seg_dev that should be cleaned up in the future. + */ +#define SEGKMEM_SHARELOCKED 0x20000 + +#define SEGKMEM_USE_LARGEPAGES (segkmem_lpsize > PAGESIZE) + +#define IS_KMEM_VA_LARGEPAGE(vaddr) \ + (((vaddr) >= heap_lp_base) && ((vaddr) < heap_lp_end)) + +#endif /* _KERNEL */ + +#ifdef __cplusplus +} +#endif + +#endif /* _VM_SEG_KMEM_H */ diff --git a/include/os/macos/spl/sys/sid.h b/include/os/macos/spl/sys/sid.h new file mode 100644 index 000000000000..1e0ea7f2519c --- /dev/null +++ b/include/os/macos/spl/sys/sid.h @@ -0,0 +1,106 @@ +/* + * 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 _SPL_SID_H +#define _SPL_SID_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define crgetzoneid(x) (GLOBAL_ZONEID) + +typedef struct ksiddomain { + char *kd_name; +} ksiddomain_t; + +typedef enum ksid_index { + KSID_USER, + KSID_GROUP, + KSID_OWNER, + KSID_COUNT +} ksid_index_t; + +typedef int ksid_t; + +/* Should be in kidmap.h */ +typedef int32_t idmap_stat; + +static inline ksiddomain_t * +ksid_lookupdomain(const char *dom) +{ + ksiddomain_t *kd; + int len = strlen(dom); + + kd = (ksiddomain_t *)kmem_zalloc(sizeof (ksiddomain_t), KM_SLEEP); + kd->kd_name = (char *)kmem_zalloc(len + 1, KM_SLEEP); + memcpy(kd->kd_name, dom, len); + + return (kd); +} + +static inline void +ksiddomain_rele(ksiddomain_t *ksid) +{ + kmem_free(ksid->kd_name, strlen(ksid->kd_name) + 1); + kmem_free(ksid, sizeof (ksiddomain_t)); +} + +#define UID_NOBODY 65534 +#define GID_NOBODY 65534 + +static __inline uint_t +ksid_getid(ksid_t *ks) +{ + panic("%s has been unexpectedly called", __func__); + return (0); +} + +static __inline const char * +ksid_getdomain(ksid_t *ks) +{ + panic("%s has been unexpectedly called", __func__); + return (0); +} + +static __inline uint_t +ksid_getrid(ksid_t *ks) +{ + panic("%s has been unexpectedly called", __func__); + return (0); +} + +#define kidmap_getsidbyuid(zone, uid, sid_prefix, rid) (1) +#define kidmap_getsidbygid(zone, gid, sid_prefix, rid) (1) + +#ifdef __cplusplus +} +#endif + +#endif /* _SPL_SID_H */ diff --git a/include/os/macos/spl/sys/signal.h b/include/os/macos/spl/sys/signal.h new file mode 100644 index 000000000000..f4bf1845e92d --- /dev/null +++ b/include/os/macos/spl/sys/signal.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 (C) 2020 Jorgen Lundman + * + */ + +#ifndef _SPL_SYS_SIGNAL_H +#define _SPL_SYS_SIGNAL_H + +#include +#include_next +#include + +#define FORREAL 0 /* Usual side-effects */ +#define JUSTLOOKING 1 /* Don't stop the process */ + +struct proc; + +extern int thread_issignal(struct proc *, thread_t, sigset_t); + +#define THREADMASK (sigmask(SIGILL)|sigmask(SIGTRAP)|\ + sigmask(SIGIOT)|sigmask(SIGEMT)|\ + sigmask(SIGFPE)|sigmask(SIGBUS)|\ + sigmask(SIGSEGV)|sigmask(SIGSYS)|\ + sigmask(SIGPIPE)|sigmask(SIGKILL)|\ + sigmask(SIGTERM)|sigmask(SIGINT)) + +static __inline__ int +issig(int why) +{ + return (thread_issignal(current_proc(), current_thread(), + THREADMASK)); +} + +/* Always called with curthread */ +#define signal_pending(p) issig(0) + +#endif /* SPL_SYS_SIGNAL_H */ diff --git a/include/os/macos/spl/sys/simd.h b/include/os/macos/spl/sys/simd.h new file mode 100644 index 000000000000..cd233ce947a4 --- /dev/null +++ b/include/os/macos/spl/sys/simd.h @@ -0,0 +1,47 @@ +/* + * 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 _MACOS_SIMD_H +#define _MACOS_SIMD_H + +#if defined(__amd64__) || defined(__i386__) +#include + +#elif defined(__aarch64__) +#include + +#else +#define kfpu_allowed() 0 +#define kfpu_initialize(tsk) do {} while (0) +#define kfpu_begin() do {} while (0) +#define kfpu_end() do {} while (0) +#define kfpu_init() (0) +#define kfpu_fini() do {} while (0) +#endif + +#endif diff --git a/include/os/macos/spl/sys/simd_aarch64.h b/include/os/macos/spl/sys/simd_aarch64.h new file mode 100644 index 000000000000..f6af72d313d5 --- /dev/null +++ b/include/os/macos/spl/sys/simd_aarch64.h @@ -0,0 +1,96 @@ +/* + * 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 + */ + +/* + * SIMD support: + * + * Following functions should be called to determine whether CPU feature + * is supported. All functions are usable in kernel and user space. + * If a SIMD algorithm is using more than one instruction set + * all relevant feature test functions should be called. + * + * Supported features: + * zfs_neon_available() + * zfs_sha256_available() + * zfs_sha512_available() + */ + +#ifndef _MACOS_SIMD_AARCH64_H +#define _MACOS_SIMD_AARCH64_H + +#include + +#define kfpu_allowed() 1 +#define kfpu_initialize(tsk) do {} while (0) +#define kfpu_begin() do {} while (0) +#define kfpu_end() do {} while (0) +#define kfpu_init() (0) +#define kfpu_fini() do {} while (0) + + +#define get_ftr(id, __val) { \ + asm("mrs %0, "#id : "=r" (__val)); \ + } + +/* + * Check if NEON is available + */ +static inline boolean_t +zfs_neon_available(void) +{ + /* All armv8 has neon, macOS only runs on armv8 */ + return (B_TRUE); +} + +/* + * Check if SHA256 is available + */ +static inline boolean_t +zfs_sha256_available(void) +{ + uint64_t ftr; + get_ftr(ID_AA64ISAR0_EL1, ftr); + return ((ftr >> 12) & 0x3); +} + +/* + * Check if SHA512 is available + */ +static inline boolean_t +zfs_sha512_available(void) +{ + uint64_t ftr; + get_ftr(ID_AA64ISAR0_EL1, ftr); + return ((ftr >> 12) & 0x3); +} + +/* + * Check if AESV8 is available + */ +static inline boolean_t +zfs_aesv8_available(void) +{ + uint64_t ftr; + get_ftr(ID_AA64ISAR0_EL1, ftr); + return ((ftr >> 4) & 0xf); +} + +#endif /* _MACOS_SIMD_AARCH64_H */ diff --git a/include/os/macos/spl/sys/simd_x86.h b/include/os/macos/spl/sys/simd_x86.h new file mode 100644 index 000000000000..7b670816889e --- /dev/null +++ b/include/os/macos/spl/sys/simd_x86.h @@ -0,0 +1,745 @@ +/* + * 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) 2016 Gvozden Neskovic . + */ + +/* + * USER API: + * + * Kernel fpu methods: + * kfpu_begin() + * kfpu_end() + * + * SIMD support: + * + * Following functions should be called to determine whether CPU feature + * is supported. All functions are usable in kernel and user space. + * If a SIMD algorithm is using more than one instruction set + * all relevant feature test functions should be called. + * + * Supported features: + * zfs_sse_available() + * zfs_sse2_available() + * zfs_sse3_available() + * zfs_ssse3_available() + * zfs_sse4_1_available() + * zfs_sse4_2_available() + * + * zfs_avx_available() + * zfs_avx2_available() + * + * zfs_bmi1_available() + * zfs_bmi2_available() + * + * zfs_avx512f_available() + * zfs_avx512cd_available() + * zfs_avx512er_available() + * zfs_avx512pf_available() + * zfs_avx512bw_available() + * zfs_avx512dq_available() + * zfs_avx512vl_available() + * zfs_avx512ifma_available() + * zfs_avx512vbmi_available() + * + * NOTE(AVX-512VL): If using AVX-512 instructions with 128Bit registers + * also add zfs_avx512vl_available() to feature check. + */ + +#ifndef _SIMD_X86_H +#define _SIMD_X86_H + +#include + +#define kfpu_init() (0) +#define kfpu_fini() do {} while (0) + +#define kfpu_begin() ((void)0) +#define kfpu_end() ((void)0) +#define kfpu_allowed() 1 + + +/* only for __x86 */ +#if defined(__x86) + +#include + +#if defined(_KERNEL) +#include +#include + +#ifdef __APPLE__ +// XNU fpu.h +static inline uint64_t +xgetbv(uint32_t c) +{ + uint32_t mask_hi, mask_lo; + __asm__ __volatile__("xgetbv" : "=a"(mask_lo), "=d"(mask_hi) : "c" (c)); + return (((uint64_t)mask_hi<<32) + (uint64_t)mask_lo); +} + +#endif + +extern uint64_t spl_cpuid_features(void); +extern uint64_t spl_cpuid_leaf7_features(void); + +#endif + +/* + * CPUID feature tests for user-space. Linux kernel provides an interface for + * CPU feature testing. + */ +#if !defined(_KERNEL) + +#include + +/* + * 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 +} 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) + +/* + * Descriptions of supported instruction sets + */ +static const cpuid_feature_desc_t spl_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 }, +}; + +/* + * 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(&spl_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); + +#endif /* !defined(_KERNEL) */ + + +/* + * Detect register set support + */ +static inline boolean_t +__simd_state_enabled(const uint64_t state) +{ + boolean_t has_osxsave; + uint64_t xcr0; + +#if defined(_KERNEL) + has_osxsave = !!(spl_cpuid_features() & CPUID_FEATURE_OSXSAVE); +#elif !defined(_KERNEL) + has_osxsave = __cpuid_has_osxsave(); +#endif + 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) +{ +#if defined(_KERNEL) + return (!!(spl_cpuid_features() & CPUID_FEATURE_SSE)); +#elif !defined(_KERNEL) + return (__cpuid_has_sse()); +#endif +} + +/* + * Check if SSE2 instruction set is available + */ +static inline boolean_t +zfs_sse2_available(void) +{ +#if defined(_KERNEL) + return (!!(spl_cpuid_features() & CPUID_FEATURE_SSE2)); +#elif !defined(_KERNEL) + return (__cpuid_has_sse2()); +#endif +} + +/* + * Check if SSE3 instruction set is available + */ +static inline boolean_t +zfs_sse3_available(void) +{ +#if defined(_KERNEL) + return (!!(spl_cpuid_features() & CPUID_FEATURE_SSE3)); +#elif !defined(_KERNEL) + return (__cpuid_has_sse3()); +#endif +} + +/* + * Check if SSSE3 instruction set is available + */ +static inline boolean_t +zfs_ssse3_available(void) +{ +#if defined(_KERNEL) + return (!!(spl_cpuid_features() & CPUID_FEATURE_SSSE3)); +#elif !defined(_KERNEL) + return (__cpuid_has_ssse3()); +#endif +} + +/* + * Check if SSE4.1 instruction set is available + */ +static inline boolean_t +zfs_sse4_1_available(void) +{ +#if defined(_KERNEL) + return (!!(spl_cpuid_features() & CPUID_FEATURE_SSE4_1)); +#elif !defined(_KERNEL) + return (__cpuid_has_sse4_1()); +#endif +} + +/* + * Check if SSE4.2 instruction set is available + */ +static inline boolean_t +zfs_sse4_2_available(void) +{ +#if defined(_KERNEL) + return (!!(spl_cpuid_features() & CPUID_FEATURE_SSE4_2)); +#elif !defined(_KERNEL) + return (__cpuid_has_sse4_2()); +#endif +} + +/* + * Check if AVX instruction set is available + */ +static inline boolean_t +zfs_avx_available(void) +{ + boolean_t has_avx; + return (FALSE); // Currently broken on macOS +#if defined(_KERNEL) + return (!!(spl_cpuid_features() & CPUID_FEATURE_AVX1_0)); +#elif !defined(_KERNEL) + has_avx = __cpuid_has_avx(); +#endif + + return (has_avx && __ymm_enabled()); +} + +/* + * Check if AVX2 instruction set is available + */ +static inline boolean_t +zfs_avx2_available(void) +{ + boolean_t has_avx2; + return (FALSE); // Currently broken on macOS +#if defined(_KERNEL) +#if defined(HAVE_AVX2) + has_avx2 = (!!(spl_cpuid_leaf7_features() & CPUID_LEAF7_FEATURE_AVX2)); +#else + has_avx2 = B_FALSE; +#endif +#elif !defined(_KERNEL) + has_avx2 = __cpuid_has_avx2(); +#endif + + return (has_avx2 && __ymm_enabled()); +} + +/* + * Check if BMI1 instruction set is available + */ +static inline boolean_t +zfs_bmi1_available(void) +{ +#if defined(_KERNEL) +#if defined(CPUID_LEAF7_FEATURE_BMI1) + return (!!(spl_cpuid_leaf7_features() & CPUID_LEAF7_FEATURE_BMI1)); +#else + return (B_FALSE); +#endif +#elif !defined(_KERNEL) + return (__cpuid_has_bmi1()); +#endif +} + +/* + * Check if BMI2 instruction set is available + */ +static inline boolean_t +zfs_bmi2_available(void) +{ +#if defined(_KERNEL) +#if defined(CPUID_LEAF7_FEATURE_BMI2) + return (!!(spl_cpuid_leaf7_features() & CPUID_LEAF7_FEATURE_BMI2)); +#else + return (B_FALSE); +#endif +#elif !defined(_KERNEL) + return (__cpuid_has_bmi2()); +#endif +} + +/* + * Check if AES instruction set is available + */ +static inline boolean_t +zfs_aes_available(void) +{ +#if defined(_KERNEL) +#if defined(HAVE_AES) + return (!!(spl_cpuid_features() & CPUID_FEATURE_AES)); +#else + return (B_FALSE); +#endif +#elif !defined(_KERNEL) + return (__cpuid_has_aes()); +#endif +} + +/* + * Check if PCLMULQDQ instruction set is available + */ +static inline boolean_t +zfs_pclmulqdq_available(void) +{ +#if defined(_KERNEL) +#if defined(HAVE_PCLMULQDQ) + return (!!(spl_cpuid_features() & CPUID_FEATURE_PCLMULQDQ)); +#else + return (B_FALSE); +#endif +#elif !defined(_KERNEL) + return (__cpuid_has_pclmulqdq()); +#endif +} + +/* + * Check if MOVBE instruction is available + */ +static inline boolean_t +zfs_movbe_available(void) +{ +#if defined(X86_FEATURE_MOVBE) + return (!!(spl_cpuid_features() & CPUID_FEATURE_MOVBE)); +#else + return (B_FALSE); +#endif +} + +/* + * Check if SHA_NI instruction set is available + */ +static inline boolean_t +zfs_shani_available(void) +{ +#if defined(X86_FEATURE_SHA_NI) + return (!!(spl_cpuid_features() & X86_FEATURE_SHA_NI)); +#else + return (B_FALSE); +#endif +} + +/* + * 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) +{ + boolean_t has_avx512 = B_FALSE; + + return (FALSE); // Currently broken on macOS +#if defined(_KERNEL) +#if defined(HAVE_AVX512F) && defined(CPUID_LEAF7_FEATURE_AVX512F) + return (!!(spl_cpuid_leaf7_features() & CPUID_LEAF7_FEATURE_AVX512F)); +#else + has_avx512 = B_FALSE; +#endif +#elif !defined(_KERNEL) + has_avx512 = __cpuid_has_avx512f(); +#endif + + return (has_avx512 && __zmm_enabled()); +} + +/* Check if AVX512CD instruction set is available */ +static inline boolean_t +zfs_avx512cd_available(void) +{ + boolean_t has_avx512 = B_FALSE; + + return (FALSE); // Currently broken on macOS +#if defined(_KERNEL) +#if defined(HAVE_AVX512F) && defined(HAVE_AVX512CD) && \ + defined(CPUID_LEAF7_FEATURE_AVX512F) && \ + defined(CPUID_LEAF7_FEATURE_AVX512CD) + has_avx512 = (spl_cpuid_leaf7_features() & + (CPUID_LEAF7_FEATURE_AVX512F | CPUID_LEAF7_FEATURE_AVX512CD)) == + (CPUID_LEAF7_FEATURE_AVX512F | CPUID_LEAF7_FEATURE_AVX512CD); +#else + has_avx512 = B_FALSE; +#endif +#elif !defined(_KERNEL) + has_avx512 = __cpuid_has_avx512cd(); +#endif + + return (has_avx512 && __zmm_enabled()); +} + +/* Check if AVX512ER instruction set is available */ +static inline boolean_t +zfs_avx512er_available(void) +{ + boolean_t has_avx512 = B_FALSE; + + return (FALSE); // Currently broken on macOS +#if defined(_KERNEL) +#if defined(HAVE_AVX512F) && defined(HAVE_AVX512ER) && \ + defined(CPUID_LEAF7_FEATURE_AVX512ER) + has_avx512 = (spl_cpuid_leaf7_features() & + (CPUID_LEAF7_FEATURE_AVX512F | CPUID_LEAF7_FEATURE_AVX512ER)) == + (CPUID_LEAF7_FEATURE_AVX512F | CPUID_LEAF7_FEATURE_AVX512ER); +#else + has_avx512 = B_FALSE; +#endif +#elif !defined(_KERNEL) + has_avx512 = __cpuid_has_avx512er(); +#endif + + return (has_avx512 && __zmm_enabled()); +} + +/* Check if AVX512PF instruction set is available */ +static inline boolean_t +zfs_avx512pf_available(void) +{ + boolean_t has_avx512 = B_FALSE; + + return (FALSE); // Currently broken on macOS +#if defined(_KERNEL) +#if defined(HAVE_AVX512PF) && defined(HAVE_AVX512F) && \ + defined(CPUID_LEAF7_FEATURE_AVX512PF) + has_avx512 = (spl_cpuid_leaf7_features() & + (CPUID_LEAF7_FEATURE_AVX512F | CPUID_LEAF7_FEATURE_AVX512PF)) == + (CPUID_LEAF7_FEATURE_AVX512F | CPUID_LEAF7_FEATURE_AVX512PF); +#else + has_avx512 = B_FALSE; +#endif +#elif !defined(_KERNEL) + has_avx512 = __cpuid_has_avx512pf(); +#endif + + return (has_avx512 && __zmm_enabled()); +} + +/* Check if AVX512BW instruction set is available */ +static inline boolean_t +zfs_avx512bw_available(void) +{ + boolean_t has_avx512 = B_FALSE; + + return (FALSE); // Currently broken on macOS +#if defined(_KERNEL) +#if defined(HAVE_AVX512BW) && defined(HAVE_AVX512F) && \ + defined(CPUID_LEAF7_FEATURE_AVX512F) && \ + defined(CPUID_LEAF7_FEATURE_AVX512BW) + has_avx512 = (spl_cpuid_leaf7_features() & + (CPUID_LEAF7_FEATURE_AVX512F | CPUID_LEAF7_FEATURE_AVX512BW)) == + (CPUID_LEAF7_FEATURE_AVX512F | CPUID_LEAF7_FEATURE_AVX512BW); +#else + has_avx512 = B_FALSE; +#endif +#elif !defined(_KERNEL) + has_avx512 = __cpuid_has_avx512bw(); +#endif + + return (has_avx512 && __zmm_enabled()); +} + +/* Check if AVX512DQ instruction set is available */ +static inline boolean_t +zfs_avx512dq_available(void) +{ + boolean_t has_avx512 = B_FALSE; + + return (FALSE); // Currently broken on macOS +#if defined(_KERNEL) +#if defined(HAVE_AVX512DQ) && defined(HAVE_AVX512F) && \ + defined(CPUID_LEAF7_FEATURE_AVX512F) && \ + defined(CPUID_LEAF7_FEATURE_AVX512DQ) + has_avx512 = (spl_cpuid_leaf7_features() & + (CPUID_LEAF7_FEATURE_AVX512F|CPUID_LEAF7_FEATURE_AVX512DQ)) == + (CPUID_LEAF7_FEATURE_AVX512F|CPUID_LEAF7_FEATURE_AVX512DQ); +#else + has_avx512 = B_FALSE; +#endif +#elif !defined(_KERNEL) + has_avx512 = __cpuid_has_avx512dq(); +#endif + + return (has_avx512 && __zmm_enabled()); +} + +/* Check if AVX512VL instruction set is available */ +static inline boolean_t +zfs_avx512vl_available(void) +{ + boolean_t has_avx512 = B_FALSE; + + return (FALSE); // Currently broken on macOS +#if defined(_KERNEL) +#if defined(HAVE_AVX512VL) && defined(HAVE_AVX512F) && \ + defined(CPUID_LEAF7_FEATURE_AVX512F) && \ + defined(CPUID_LEAF7_FEATURE_AVX512VL) + has_avx512 = (spl_cpuid_leaf7_features() & + (CPUID_LEAF7_FEATURE_AVX512F|CPUID_LEAF7_FEATURE_AVX512VL)) == + (CPUID_LEAF7_FEATURE_AVX512F|CPUID_LEAF7_FEATURE_AVX512VL); +#else + has_avx512 = B_FALSE; +#endif +#elif !defined(_KERNEL) + has_avx512 = __cpuid_has_avx512vl(); +#endif + + return (has_avx512 && __zmm_enabled()); +} + +/* Check if AVX512IFMA instruction set is available */ +static inline boolean_t +zfs_avx512ifma_available(void) +{ + boolean_t has_avx512 = B_FALSE; + + return (FALSE); // Currently broken on macOS +#if defined(_KERNEL) +#if defined(HAVE_AVX512IFMA) && defined(HAVE_AVX512F) && \ + defined(CPUID_LEAF7_FEATURE_AVX512F) && \ + defined(CPUID_LEAF7_FEATURE_AVX512IFMA) + has_avx512 = (spl_cpuid_leaf7_features() & + (CPUID_LEAF7_FEATURE_AVX512F|CPUID_LEAF7_FEATURE_AVX512IFMA)) == + (CPUID_LEAF7_FEATURE_AVX512F|CPUID_LEAF7_FEATURE_AVX512IFMA); +#else + has_avx512 = B_FALSE; +#endif +#elif !defined(_KERNEL) + has_avx512 = __cpuid_has_avx512ifma(); +#endif + + return (has_avx512 && __zmm_enabled()); +} + +/* Check if AVX512VBMI instruction set is available */ +static inline boolean_t +zfs_avx512vbmi_available(void) +{ + boolean_t has_avx512 = B_FALSE; + + return (FALSE); // Currently broken on macOS +#if defined(_KERNEL) +#if defined(HAVE_AVX512VBMI) && defined(HAVE_AVX512F) && \ + defined(CPUID_LEAF7_FEATURE_AVX512F) && \ + defined(CPUID_LEAF7_FEATURE_AVX512VBMI) + has_avx512 = (spl_cpuid_leaf7_features() & + (CPUID_LEAF7_FEATURE_AVX512F|CPUID_LEAF7_FEATURE_AVX512VBMI)) == + (CPUID_LEAF7_FEATURE_AVX512F|CPUID_LEAF7_FEATURE_AVX512VBMI); +#else + has_avx512 = B_FALSE; +#endif +#elif !defined(_KERNEL) + has_avx512 = __cpuid_has_avx512f() && + __cpuid_has_avx512vbmi(); +#endif + + return (has_avx512 && __zmm_enabled()); +} + +#endif /* defined(__x86) */ + +#endif /* _SIMD_X86_H */ diff --git a/include/os/macos/spl/sys/string.h b/include/os/macos/spl/sys/string.h new file mode 100644 index 000000000000..a2fec04dbb16 --- /dev/null +++ b/include/os/macos/spl/sys/string.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, 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 _SPL_SYS_STRING_H +#define _SPL_SYS_STRING_H + +#include +#include +#include + +static inline int +kstrtoul(const char *cp, unsigned int base, unsigned long *res) +{ + char *end; + + *res = strtoul(cp, &end, base); + + /* skip newline character, if any */ + if (*end == '\n') + end++; + if (*cp == 0 || *end != 0) + return (-EINVAL); + return (0); +} + +#endif diff --git a/include/os/macos/spl/sys/strings.h b/include/os/macos/spl/sys/strings.h new file mode 100644 index 000000000000..ce5260910d27 --- /dev/null +++ b/include/os/macos/spl/sys/strings.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, 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 _SPL_STRINGS_H +#define _SPL_STRINGS_H + +int kstrtoul(const char *s, unsigned int base, unsigned long *res); + +#endif diff --git a/include/os/macos/spl/sys/stropts.h b/include/os/macos/spl/sys/stropts.h new file mode 100644 index 000000000000..fd94725f10c5 --- /dev/null +++ b/include/os/macos/spl/sys/stropts.h @@ -0,0 +1,247 @@ +/* + * 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 Jorgen Lundman + * + */ + + +#ifndef _SPL_STROPTS_H +#define _SPL_STROPTS_H + +#define LOCORE +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define isprint(c) ((c) >= ' ' && (c) <= '~') + +/* + * Find highest one bit set. + * Returns bit number + 1 of highest bit that is set, otherwise returns 0. + * High order bit is 31 (or 63 in _LP64 kernel). + */ +static inline int +highbit64(unsigned long long i) +{ + int h = 1; + if (i == 0) + return (0); + if (i & 0xffffffff00000000ull) { + h += 32; i >>= 32; + } + if (i & 0xffff0000) { + h += 16; i >>= 16; + } + if (i & 0xff00) { + h += 8; i >>= 8; + } + if (i & 0xf0) { + h += 4; i >>= 4; + } + if (i & 0xc) { + h += 2; i >>= 2; + } + if (i & 0x2) { + h += 1; + } + return (h); +} + +static inline int +highbit(unsigned long long i) +{ + int h = 1; + if (i == 0) + return (0); + if (i & 0xffffffff00000000ull) { + h += 32; i >>= 32; + } + if (i & 0xffff0000) { + h += 16; i >>= 16; + } + if (i & 0xff00) { + h += 8; i >>= 8; + } + if (i & 0xf0) { + h += 4; i >>= 4; + } + if (i & 0xc) { + h += 2; i >>= 2; + } + if (i & 0x2) { + h += 1; + } + return (h); +} + +/* + * Find lowest one bit set. + * Returns bit number + 1 of lowest bit that is set, otherwise returns 0. + * Low order bit is 0. + */ +static inline int +lowbit(unsigned long long i) +{ + int h = 1; + + if (i == 0) + return (0); + + if (!(i & 0xffffffff)) { + h += 32; i >>= 32; + } + if (!(i & 0xffff)) { + h += 16; i >>= 16; + } + if (!(i & 0xff)) { + h += 8; i >>= 8; + } + if (!(i & 0xf)) { + h += 4; i >>= 4; + } + if (!(i & 0x3)) { + h += 2; i >>= 2; + } + if (!(i & 0x1)) { + h += 1; + } + return (h); +} + +static inline int +isdigit(char c) +{ + return (c >= ' ' && c <= '9'); +} + + +static inline char * +strpbrk(const char *s, const char *b) +{ + const char *p; + do { + for (p = b; *p != '\0' && *p != *s; ++p) + ; + if (*p != '\0') + return ((char *)s); + } while (*s++); + return (NULL); +} + + +static inline char * +strrchr(const char *p, int ch) +{ + union { + const char *cp; + char *p; + } u; + char *save; + + u.cp = p; + for (save = NULL; /* empty */; ++u.p) { + if (*u.p == ch) + save = u.p; + if (*u.p == '\0') + return (save); + } + /* NOTREACHED */ +} + +static inline int +is_ascii_str(const char *str) +{ + unsigned char ch; + + while ((ch = (unsigned char)*str++) != '\0') { + if (ch >= 0x80) + return (0); + } + return (1); +} + + +static inline void * +kmemchr(const void *s, int c, size_t n) +{ + if (n != 0) { + const unsigned char *p = (const unsigned char *)s; + do { + if (*p++ == (unsigned char)c) + return ((void *)(uintptr_t)(p - 1)); + } while (--n != 0); + } + return (NULL); +} + +#ifndef memchr +#define memchr kmemchr +#endif + +#define IDX(c) ((unsigned char)(c) / LONG_BIT) +#define BIT(c) ((unsigned long)1 << ((unsigned char)(c) % LONG_BIT)) + +static inline size_t +strcspn(const char *__restrict s, const char *__restrict charset) +{ + /* + * NB: idx and bit are temporaries whose use causes gcc 3.4.2 to + * generate better code. Without them, gcc gets a little confused. + */ + const char *s1; + unsigned long bit; + unsigned long tbl[(UCHAR_MAX + 1) / LONG_BIT]; + int idx; + + if (*s == '\0') + return (0); + + tbl[0] = 1; + tbl[3] = tbl[2] = tbl[1] = 0; + + for (; *charset != '\0'; charset++) { + idx = IDX(*charset); + bit = BIT(*charset); + tbl[idx] |= bit; + } + + for (s1 = s; ; s1++) { + idx = IDX(*s1); + bit = BIT(*s1); + if ((tbl[idx] & bit) != 0) + break; + } + return (s1 - s); +} + +#ifdef __cplusplus +} +#endif + +#endif /* SPL_STROPTS_H */ diff --git a/include/os/macos/spl/sys/sunddi.h b/include/os/macos/spl/sys/sunddi.h new file mode 100644 index 000000000000..c134308deae2 --- /dev/null +++ b/include/os/macos/spl/sys/sunddi.h @@ -0,0 +1,204 @@ +/* + * 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) 1990, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright 2012 Garrett D'Amore . All rights reserved. + * Copyright (c) 2012 by Delphix. All rights reserved. + */ + + + +#ifndef _SPL_SUNDDI_H +#define _SPL_SUNDDI_H + +#include +#include +#include +#include +#include +#include +#include + +typedef int ddi_devid_t; + +#define DDI_DEV_T_NONE ((dev_t)-1) +#define DDI_DEV_T_ANY ((dev_t)-2) +#define DI_MAJOR_T_UNKNOWN ((major_t)0) + +#define DDI_PROP_DONTPASS 0x0001 +#define DDI_PROP_CANSLEEP 0x0002 + +#define DDI_SUCCESS 0 +#define DDI_FAILURE -1 + +#define ddi_prop_lookup_string(x1, x2, x3, x4, x5) (*x5 = NULL) +#define ddi_prop_free(x) (void)0 +#define ddi_root_node() (void)0 + +#define isalnum(ch) (isalpha(ch) || isdigit(ch)) +#define isalpha(ch) (isupper(ch) || islower(ch)) +#define isdigit(ch) ((ch) >= '0' && (ch) <= '9') +#define islower(ch) ((ch) >= 'a' && (ch) <= 'z') +#define isspace(ch) (((ch) == ' ') || ((ch) == '\r') || ((ch) == '\n') || \ + ((ch) == '\t') || ((ch) == '\f')) +#define isupper(ch) ((ch) >= 'A' && (ch) <= 'Z') +#define isxdigit(ch) (isdigit(ch) || ((ch) >= 'a' && (ch) <= 'f') || \ + ((ch) >= 'A' && (ch) <= 'F')) +#define tolower(C) (((C) >= 'A' && (C) <= 'Z') ? (C) - 'A' + 'a' : (C)) +#define toupper(C) (((C) >= 'a' && (C) <= 'z') ? (C) - 'a' + 'A': (C)) +#define isgraph(C) ((C) >= 0x21 && (C) <= 0x7E) +#define ispunct(C) (((C) >= 0x21 && (C) <= 0x2F) || \ + ((C) >= 0x3A && (C) <= 0x40) || \ + ((C) >= 0x5B && (C) <= 0x60) || \ + ((C) >= 0x7B && (C) <= 0x7E)) + +// Define proper Solaris API calls, and clean ZFS up to use +int ddi_copyin(const void *from, void *to, size_t len, int flags); +int ddi_copyout(const void *from, void *to, size_t len, int flags); +int ddi_copyinstr(const void *uaddr, void *kaddr, size_t len, size_t *done); + +static inline int +ddi_strtol(const char *str, char **nptr, int base, long *result) +{ + *result = strtol(str, nptr, base); + if (*result == 0) + return (EINVAL); + else if (*result == LONG_MIN || *result == LONG_MAX) + return (ERANGE); + return (0); +} + +static inline int +ddi_strtoul(const char *str, char **nptr, int base, unsigned long *result) +{ + *result = strtoul(str, nptr, base); + if (*result == 0) + return (EINVAL); + else if (*result == ULONG_MAX) + return (ERANGE); + return (0); +} + +static inline int +ddi_strtoull(const char *str, char **nptr, int base, + unsigned long long *result) +{ + *result = (unsigned long long)strtouq(str, nptr, base); + if (*result == 0) + return (EINVAL); + else if (*result == ULLONG_MAX) + return (ERANGE); + return (0); +} + +static inline int +ddi_strtoll(const char *str, char **nptr, int base, long long *result) +{ + *result = (unsigned long long)strtoq(str, nptr, base); + if (*result == 0) + return (EINVAL); + else if (*result == ULLONG_MAX) + return (ERANGE); + return (0); +} + +#ifndef OTYPCNT +#define OTYPCNT 5 +#define OTYP_BLK 0 +#define OTYP_MNT 1 +#define OTYP_CHR 2 +#define OTYP_SWP 3 +#define OTYP_LYR 4 +#endif + +#define P2END(x, align) (-(~(x) & -(align))) + +#define ddi_name_to_major(name) devsw_name2blk(name, NULL, 0) + +struct dev_info { + dev_t dev; // Major / Minor + void *devc; + void *devb; +}; +typedef struct dev_info dev_info_t; + + +int ddi_strtoul(const char *, char **, int, unsigned long *); +int ddi_strtol(const char *, char **, int, long *); +int ddi_soft_state_init(void **, size_t, size_t); +int ddi_soft_state_zalloc(void *, int); +void *ddi_get_soft_state(void *, int); +void ddi_soft_state_free(void *, int); +void ddi_soft_state_fini(void **); +int ddi_create_minor_node(dev_info_t *, char *, int, + minor_t, char *, int); +void ddi_remove_minor_node(dev_info_t *, char *); + +int ddi_driver_major(dev_info_t *); + +typedef void *ldi_ident_t; + +#define DDI_SUCCESS 0 +#define DDI_FAILURE -1 + +#define DDI_PSEUDO "" + +#define ddi_prop_update_int64(a, b, c, d) DDI_SUCCESS +#define ddi_prop_update_string(a, b, c, d) DDI_SUCCESS + +#define bioerror(bp, er) (buf_seterror((bp), (er))) +#define biodone(bp) buf_biodone(bp) + +#define ddi_ffs ffs +static inline long ddi_fls(long mask) { \ + /* Algorithm courtesy of Steve Chessin. */ \ + while (mask) { \ + long nx; \ + if ((nx = (mask & (mask - 1))) == 0) { \ + break; \ + } \ + mask = nx; \ + } \ + return (ffs(mask)); \ +} + +#define getminor(X) minor((X)) + + + +/* + * This data structure is entirely private to the soft state allocator. + */ +struct i_ddi_soft_state { + void **array; /* the array of pointers */ + kmutex_t lock; /* serialize access to this struct */ + size_t size; /* how many bytes per state struct */ + size_t n_items; /* how many structs herein */ + struct i_ddi_soft_state *next; /* 'dirty' elements */ +}; + +#define MIN_N_ITEMS 8 /* 8 void *'s == 32 bytes */ + +extern int strspn(const char *string, char *charset); + + +#endif /* SPL_SUNDDI_H */ diff --git a/include/os/macos/spl/sys/sysmacros.h b/include/os/macos/spl/sys/sysmacros.h new file mode 100644 index 000000000000..9c4a6818b301 --- /dev/null +++ b/include/os/macos/spl/sys/sysmacros.h @@ -0,0 +1,274 @@ +/* + * 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 Jorgen Lundman + * + */ + +#ifndef _SPL_SYSMACROS_H +#define _SPL_SYSMACROS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _KERNEL +#define _KERNEL __KERNEL__ +#endif + +#define FALSE 0 +#define TRUE 1 + +#if 0 +#define INT8_MAX (127) +#define INT8_MIN (-128) +#define UINT8_MAX (255) +#define UINT8_MIN (0) + +#define INT16_MAX (32767) +#define INT16_MIN (-32768) +#define UINT16_MAX (65535) +#define UINT16_MIN (0) + +#define INT32_MAX INT_MAX +#define INT32_MIN INT_MIN +#define UINT32_MAX UINT_MAX +#define UINT32_MIN UINT_MIN + +#define INT64_MAX LLONG_MAX +#define INT64_MIN LLONG_MIN +#define UINT64_MAX ULLONG_MAX +#define UINT64_MIN ULLONG_MIN + +#define NBBY 8 +#define MAXBSIZE 8192 +#endif + +#define MAXMSGLEN 256 +#define MAXNAMELEN 256 +#define MAXPATHLEN PATH_MAX +#define MAXOFFSET_T LLONG_MAX +#define DEV_BSIZE 512 +#define DEV_BSHIFT 9 /* log2(DEV_BSIZE) */ + +#define proc_pageout NULL +#define curproc (struct proc *)current_proc() + +#define CPU_SEQID (getcpuid()) +#define CPU_SEQID_UNSTABLE (getcpuid()) +#define is_system_labeled() 0 + +extern unsigned int max_ncpus; +#define boot_ncpus max_ncpus +extern unsigned int num_ecores; + +#ifndef RLIM64_INFINITY +#define RLIM64_INFINITY (~0ULL) +#endif + +/* + * 0..MAX_PRIO-1: Process priority + * 0..MAX_RT_PRIO-1: RT priority tasks + * MAX_RT_PRIO..MAX_PRIO-1: SCHED_NORMAL tasks + * + * Treat shim tasks as SCHED_NORMAL tasks + */ + +/* + * In OSX, the kernel thread priorities start at 81 and goes to + * 95 MAXPRI_KERNEL. BASEPRI_REALTIME starts from 96. Since + * swap priority is at 92. ZFS priorities should have a base below + * 81 in general. Xnu will dynamically adjust priorities of + * some taskq threads around maxclsyspri. + */ +#define minclsyspri 70 /* well below the render server and other graphics */ +#define defclsyspri 75 /* five below the xnu kernel services */ +#define maxclsyspri 80 /* 1 less than base, 2 less than networking */ +/* + * taskqs for scrubs can be lower-priority, and are better that way for wide + * pools (where the number of vdevs is 50% or more the number of cores). + * + * This value is just below the level of bluetoothd or userland audio + */ +#define DSL_SCAN_ISS_SYSPRI 59 + +/* + * Missing macros + */ +#define PAGESIZE PAGE_SIZE + +/* from Solaris sys/byteorder.h */ +#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)) + + +/* Dtrace probes do not exist in the linux kernel */ +#ifdef DTRACE_PROBE +#undef DTRACE_PROBE +#endif /* DTRACE_PROBE */ +#define DTRACE_PROBE(a) ((void)0) + +#ifdef DTRACE_PROBE1 +#undef DTRACE_PROBE1 +#endif /* DTRACE_PROBE1 */ +#define DTRACE_PROBE1(a, b, c) ((void)0) + +#ifdef DTRACE_PROBE2 +#undef DTRACE_PROBE2 +#endif /* DTRACE_PROBE2 */ +#define DTRACE_PROBE2(a, b, c, d, e) ((void)0) + +#ifdef DTRACE_PROBE3 +#undef DTRACE_PROBE3 +#endif /* DTRACE_PROBE3 */ +#define DTRACE_PROBE3(a, b, c, d, e, f, g) ((void)0) + +#ifdef DTRACE_PROBE4 +#undef DTRACE_PROBE4 +#endif /* DTRACE_PROBE4 */ +#define DTRACE_PROBE4(a, b, c, d, e, f, g, h, i) ((void)0) + +/* Missing globals */ +extern char spl_version[32]; +extern unsigned long spl_hostid; +extern char hw_serial[11]; + +/* Missing misc functions */ +extern uint32_t zone_get_hostid(void *zone); +extern void spl_setup(void); +extern void spl_cleanup(void); + +#define makedevice(maj, min) makedev(maj, min) + +/* 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 DIV_ROUND_UP +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +#endif + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0])) +#endif + +/* + * 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) & -(align))) +#define P2PHASE(x, align) ((x) & ((align) - 1)) +#define P2NPHASE(x, align) (-(x) & ((align) - 1)) +#define ISP2(x) (((x) & ((x) - 1)) == 0) +#define IS_P2ALIGNED(v, a) ((((uintptr_t)(v)) & ((uintptr_t)(a) - 1)) == 0) +#define P2BOUNDARY(off, len, align) \ + (((off) ^ ((off) + (len) - 1)) > (align) - 1) + +/* + * 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) & -(type)(align))) +#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))) + +/* + * P2* Macros from Illumos + */ + +/* + * return x rounded up to the next phase (offset) within align. + * phase should be < align. + * eg, P2PHASEUP(0x1234, 0x100, 0x10) == 0x1310 (0x13*align + phase) + * eg, P2PHASEUP(0x5600, 0x100, 0x10) == 0x5610 (0x56*align + phase) + */ +#define P2PHASEUP(x, align, phase) ((phase) - (((phase) - (x)) & -(align))) + +/* + * Return TRUE if they have the same highest bit set. + * eg, P2SAMEHIGHBIT(0x1234, 0x1001) == TRUE (the high bit is 0x1000) + * eg, P2SAMEHIGHBIT(0x1234, 0x3010) == FALSE (high bit of 0x3010 is 0x2000) + */ +#define P2SAMEHIGHBIT(x, y) (((x) ^ (y)) < ((x) & (y))) + +/* + * End Illumos copy-fest + */ + +/* avoid any possibility of clashing with version */ +#if defined(_KERNEL) && !defined(_KMEMUSER) && !defined(offsetof) +/* + * Use the correct builtin mechanism. The Traditional macro is + * not safe on this platform. + */ +#define offsetof(s, m) __builtin_offsetof(s, m) +#endif + +#define SET_ERROR(err) \ + (__set_error(__FILE__, __func__, __LINE__, err), err) + +#ifdef __cplusplus +} +#endif + +#endif /* _SPL_SYSMACROS_H */ diff --git a/include/os/macos/spl/sys/systeminfo.h b/include/os/macos/spl/sys/systeminfo.h new file mode 100644 index 000000000000..d1c15744ec5d --- /dev/null +++ b/include/os/macos/spl/sys/systeminfo.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 (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 Jorgen Lundman + * + */ + +#ifndef _SPL_SYSTEMINFO_H +#define _SPL_SYSTEMINFO_H + +#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 */ + +const char *spl_panicstr(void); +int spl_system_inshutdown(void); + + +#endif /* SPL_SYSTEMINFO_H */ diff --git a/include/os/macos/spl/sys/systm.h b/include/os/macos/spl/sys/systm.h new file mode 100644 index 000000000000..54b75e29b53e --- /dev/null +++ b/include/os/macos/spl/sys/systm.h @@ -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) 2013 Jorgen Lundman + * + */ + +#ifndef _SPL_SYSTM_H +#define _SPL_SYSTM_H + +#include_next +#include + +typedef uintptr_t pc_t; + +#endif /* SPL_SYSTM_H */ diff --git a/include/os/macos/spl/sys/taskq.h b/include/os/macos/spl/sys/taskq.h new file mode 100644 index 000000000000..a7e691e2f070 --- /dev/null +++ b/include/os/macos/spl/sys/taskq.h @@ -0,0 +1,120 @@ +/* + * 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) 2015 Jorgen Lundman + */ + +#ifndef _SYS_TASKQ_H +#define _SYS_TASKQ_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define TASKQ_NAMELEN 31 + +typedef struct taskq taskq_t; +typedef uintptr_t taskqid_t; +typedef void (task_func_t)(void *); + +struct proc; +struct taskq_ent; + +/* New ZFS expects to find taskq_ent_t as well */ +#include + +/* + * Public flags for taskq_create(): bit range 0-15 + */ +#define TASKQ_PREPOPULATE 0x0001 /* Prepopulate with threads and data */ +#define TASKQ_CPR_SAFE 0x0002 /* Use CPR safe protocol */ +#define TASKQ_DYNAMIC 0x0004 /* Use dynamic thread scheduling */ +#define TASKQ_THREADS_CPU_PCT 0x0008 /* number of threads as % of ncpu */ +#define TASKQ_DC_BATCH 0x0010 /* Taskq uses SDC in batch mode */ + +#ifdef __APPLE__ +#define TASKQ_TIMESHARE 0x0020 /* macOS dynamic thread priority */ +#define TASKQ_REALLY_DYNAMIC 0x0040 /* don't filter out TASKQ_DYNAMIC */ +#endif + +/* + * Flags for taskq_dispatch. TQ_SLEEP/TQ_NOSLEEP should be same as + * KM_SLEEP/KM_NOSLEEP. + */ +#define TQ_SLEEP 0x00 /* Can block for memory */ +#define TQ_NOSLEEP 0x01 /* cannot block for memory; may fail */ +#define TQ_NOQUEUE 0x02 /* Do not enqueue if can't dispatch */ +#define TQ_NOALLOC 0x04 /* cannot allocate memory; may fail */ +#define TQ_FRONT 0x08 /* Put task at the front of the queue */ + +#define TASKQID_INVALID ((taskqid_t)0) + +#ifdef _KERNEL + +extern taskq_t *system_taskq; +/* Global dynamic task queue for long delay */ +extern taskq_t *system_delay_taskq; + +extern int spl_taskq_init(void); +extern void spl_taskq_fini(void); +extern void taskq_mp_init(void); + +extern taskq_t *taskq_create(const char *, int, pri_t, int, int, uint_t); +extern taskq_t *taskq_create_instance(const char *, int, int, pri_t, int, + int, uint_t); +extern taskq_t *taskq_create_proc(const char *, int, pri_t, int, int, + proc_t *, uint_t); +extern taskq_t *taskq_create_sysdc(const char *, int, int, int, + proc_t *, uint_t, uint_t); +extern taskqid_t taskq_dispatch(taskq_t *, task_func_t, void *, uint_t); +extern void nulltask(void *); +extern void taskq_destroy(taskq_t *); +extern void taskq_wait(taskq_t *); +#define HAVE_TASKQ_WAIT_ID +extern void taskq_wait_id(taskq_t *, taskqid_t); +extern void taskq_suspend(taskq_t *); +extern int taskq_suspended(taskq_t *); +extern void taskq_resume(taskq_t *); +extern int taskq_member(taskq_t *, kthread_t *); +extern int taskq_cancel_id(taskq_t *, taskqid_t); +extern taskq_t *taskq_of_curthread(void); +extern int taskq_empty_ent(struct taskq_ent *); + +extern void taskq_wait_outstanding(taskq_t *, taskqid_t); + +extern void system_taskq_init(void); +extern void system_taskq_fini(void); + +#endif /* _KERNEL */ + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_TASKQ_H */ diff --git a/include/os/macos/spl/sys/taskq_impl.h b/include/os/macos/spl/sys/taskq_impl.h new file mode 100644 index 000000000000..60e86b367350 --- /dev/null +++ b/include/os/macos/spl/sys/taskq_impl.h @@ -0,0 +1,181 @@ +/* + * 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 (C) 2015 Jorgen Lundman + */ + + +#ifndef _SYS_TASKQ_IMPL_H +#define _SYS_TASKQ_IMPL_H + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct taskq_bucket taskq_bucket_t; + +typedef struct taskq_ent { + struct taskq_ent *tqent_next; + struct taskq_ent *tqent_prev; + task_func_t *tqent_func; + void *tqent_arg; + union { + taskq_bucket_t *tqent_bucket; + uintptr_t tqent_flags; + } tqent_un; + kthread_t *tqent_thread; + kcondvar_t tqent_cv; +#ifdef __APPLE__ + /* Used to simulate TS_STOPPED */ + kmutex_t tqent_thread_lock; + kcondvar_t tqent_thread_cv; +#endif +} taskq_ent_t; + +#define TQENT_FLAG_PREALLOC 0x1 + +/* + * Taskq Statistics fields are not protected by any locks. + */ +typedef struct tqstat { + uint_t tqs_hits; + uint_t tqs_misses; + uint_t tqs_overflow; /* no threads to allocate */ + uint_t tqs_tcreates; /* threads created */ + uint_t tqs_tdeaths; /* threads died */ + uint_t tqs_maxthreads; /* max # of alive threads */ + uint_t tqs_nomem; /* # of times there were no memory */ + uint_t tqs_disptcreates; +} tqstat_t; + +/* + * Per-CPU hash bucket manages taskq_bent_t structures using freelist. + */ +struct taskq_bucket { + kmutex_t tqbucket_lock; + taskq_t *tqbucket_taskq; /* Enclosing taskq */ + taskq_ent_t tqbucket_freelist; + uint_t tqbucket_nalloc; /* # of allocated entries */ + uint_t tqbucket_nfree; /* # of free entries */ + kcondvar_t tqbucket_cv; + ushort_t tqbucket_flags; + hrtime_t tqbucket_totaltime; + tqstat_t tqbucket_stat; +}; + +/* + * Bucket flags. + */ +#define TQBUCKET_CLOSE 0x01 +#define TQBUCKET_SUSPEND 0x02 + +#define TASKQ_INTERFACE_FLAGS 0x0000ffff /* defined in */ + +/* + * taskq implementation flags: bit range 16-31 + */ +#define TASKQ_CHANGING 0x00010000 /* nthreads != target */ +#define TASKQ_SUSPENDED 0x00020000 /* taskq is suspended */ +#define TASKQ_NOINSTANCE 0x00040000 /* no instance number */ +#define TASKQ_THREAD_CREATED 0x00080000 /* a thread has been created */ +#define TASKQ_DUTY_CYCLE 0x00100000 /* using the SDC class */ + +struct taskq { + char tq_name[TASKQ_NAMELEN + 1]; + kmutex_t tq_lock; + krwlock_t tq_threadlock; + kcondvar_t tq_dispatch_cv; + kcondvar_t tq_wait_cv; + kcondvar_t tq_exit_cv; + pri_t tq_pri; /* Scheduling priority */ + uint_t tq_flags; + int tq_active; + int tq_nthreads; + int tq_nthreads_target; + int tq_nthreads_max; + int tq_threads_ncpus_pct; + int tq_nalloc; + int tq_minalloc; + int tq_maxalloc; + kcondvar_t tq_maxalloc_cv; + int tq_maxalloc_wait; + taskq_ent_t *tq_freelist; + taskq_ent_t tq_task; + int tq_maxsize; + taskq_bucket_t *tq_buckets; /* Per-cpu array of buckets */ + int tq_instance; + uint_t tq_nbuckets; /* # of buckets (2^n) */ + union { + kthread_t *_tq_thread; + kthread_t **_tq_threadlist; + } tq_thr; + + list_node_t tq_cpupct_link; /* linkage for taskq_cpupct_list */ + proc_t *tq_proc; /* process for taskq threads */ + int tq_cpupart; /* cpupart id bound to */ + uint_t tq_DC; /* duty cycle for SDC */ + + /* + * Statistics. + */ + kstat_t *tq_kstat; /* Exported statistics */ + hrtime_t tq_totaltime; /* Time spent processing tasks */ + uint64_t tq_tasks; /* Total # of tasks posted */ + uint64_t tq_executed; /* Total # of tasks executed */ + int tq_maxtasks; /* Max number of tasks in the queue */ + int tq_tcreates; + int tq_tdeaths; +}; + +/* Special form of taskq dispatch that uses preallocated entries. */ +void taskq_dispatch_ent(taskq_t *, task_func_t, void *, uint_t, taskq_ent_t *); + + +#define tq_thread tq_thr._tq_thread +#define tq_threadlist tq_thr._tq_threadlist + +/* The MAX guarantees we have at least one thread */ +#define TASKQ_THREADS_PCT(ncpus, pct) MAX(((ncpus) * (pct)) / 100, 1) + +/* Extra ZOL / Apple */ +extern void taskq_init_ent(taskq_ent_t *t); +extern taskqid_t taskq_dispatch_delay(taskq_t *tq, task_func_t func, void *arg, + uint_t flags, clock_t expire_time); + + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_TASKQ_IMPL_H */ diff --git a/include/os/macos/spl/sys/thread.h b/include/os/macos/spl/sys/thread.h new file mode 100644 index 000000000000..52d9c135b8a6 --- /dev/null +++ b/include/os/macos/spl/sys/thread.h @@ -0,0 +1,196 @@ +/* + * 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) 2008 MacZFS + * Copyright (C) 2013 Jorgen Lundman + * + */ + +#ifndef _SPL_THREAD_H +#define _SPL_THREAD_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * OsX thread type is + * typedef struct thread *thread_t; + * + * Map that to the ZFS thread type: kthread_t + */ +#define kthread thread +#define kthread_t struct kthread + +/* + * Thread interfaces + */ +#define TP_MAGIC 0x53535353 + +#define TS_FREE 0x00 /* Thread at loose ends */ +#define TS_SLEEP 0x01 /* Awaiting an event */ +#define TS_RUN 0x02 /* Runnable, but not yet on a processor */ +#define TS_ONPROC 0x04 /* Thread is being run on a processor */ +#define TS_ZOMB 0x08 /* Thread has died but hasn't been reaped */ +#define TS_STOPPED 0x10 /* Stopped, initial state */ +#define TS_WAIT 0x20 /* Waiting to become runnable */ + + +typedef void (*thread_func_t)(void *); + + +#define curthread ((kthread_t *)current_thread()) /* current thread pointer */ +#define curproj (ttoproj(curthread)) /* current project pointer */ + +#define thread_join(t) VERIFY(0) + +// Drop the p0 argument, not used. + +#ifdef SPL_DEBUG_THREAD + +#define thread_create(A, B, C, D, E, F, G, H) \ + spl_thread_create_named(__FILE__, A, B, C, D, E, G, __FILE__, __LINE__, H) +#define thread_create_named(name, A, B, C, D, E, F, G, H) \ + spl_thread_create_named(name, A, B, C, D, E, G, __FILE__, __LINE__, H) +#define thread_create_named_with_extpol_and_qos(name, \ + S, T, Q, A, B, C, D, E, F, G) \ + spl_thread_create_named_with_extpol_and_qos(S, T, Q, \ + name, A, B, C, D, E, F, __FILE__, __LINE__, G) + +extern kthread_t *spl_thread_create_named(const char *name, + caddr_t stk, size_t stksize, + thread_func_t proc, void *arg, size_t len, /* proc_t *pp, */ int state, + const char *, int, pri_t pri); + +extern kthread_t *spl_thread_create_named_with_extpol_and_qos( + thread_extended_policy_t tmsharepol, + thread_throughput_qos_policy_t thoughpol, + thread_latency_qos_t latpol, + const char *name, + caddr_t stk, size_t stksize, + thread_func_t proc, void *arg, size_t len, /* proc_t *pp, */ int state, + const char *, int, pri_t pri); + +#else + +#define thread_create(A, B, C, D, E, F, G, H) \ + spl_thread_create_named(__FILE__, A, B, C, D, E, G, H) +#define thread_create_named(name, A, B, C, D, E, F, G, H) \ + spl_thread_create_named(name, A, B, C, D, E, G, H) +#define thread_create_named_with_extpol_and_qos(name, \ + S, T, Q, A, B, C, D, E, F, G) \ + spl_thread_create_named_with_extpol_and_qos( \ + S, T, Q, name, A, B, C, D, E, F, G) + +extern kthread_t *spl_thread_create_named(const char *name, + caddr_t stk, size_t stksize, + thread_func_t proc, void *arg, size_t len, + /* proc_t *pp, */ + int state, + pri_t pri); + +extern kthread_t *spl_thread_create_named_with_extpol_and_qos( + thread_extended_policy_t tmsharepol, + thread_throughput_qos_policy_t thoughpol, + thread_latency_qos_policy_t latpol, + const char *name, + caddr_t stk, size_t stksize, + thread_func_t proc, + void *arg, size_t len, /* proc_t *pp, */ int state, + pri_t pri); + +#endif + +#if defined(MAC_OS_X_VERSION_10_9) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_9) +/* Missing in 10.9 - none of this will be run, but handles us compile */ +#define THREAD_LATENCY_QOS_POLICY 7 +#define THREAD_LATENCY_QOS_POLICY_COUNT ((mach_msg_type_number_t) \ + (sizeof (thread_latency_qos_policy_data_t) / sizeof (integer_t))) +#define THREAD_THROUGHPUT_QOS_POLICY 8 +#define THREAD_THROUGHPUT_QOS_POLICY_COUNT ((mach_msg_type_number_t) \ + (sizeof (thread_throughput_qos_policy_data_t) / sizeof (integer_t))) +typedef integer_t thread_latency_qos_t; +typedef integer_t thread_throughput_qos_t; +struct thread_throughput_qos_policy { + thread_throughput_qos_t thread_throughput_qos_tier; +}; +struct thread_latency_qos_policy { + thread_latency_qos_t thread_latency_qos_tier; +}; +typedef struct thread_throughput_qos_policy +thread_throughput_qos_policy_data_t; +typedef struct thread_latency_qos_policy +thread_latency_qos_policy_data_t; +#endif + +#define thread_exit spl_thread_exit +extern void spl_thread_exit(void) __attribute__((noreturn)); + +extern kthread_t *spl_current_thread(void); + +extern void spl_set_thread_importance(thread_t, pri_t, const char *); + +extern void spl_set_thread_timeshare(thread_t, + thread_extended_policy_data_t *, const char *); + +extern void spl_set_thread_throughput(thread_t, + thread_throughput_qos_policy_data_t *, const char *); + +extern void spl_set_thread_latency(thread_t, + thread_latency_qos_policy_data_t *, const char *); + +#define delay osx_delay +extern void osx_delay(int); + +#define KPREEMPT_SYNC 0 +static inline void kpreempt(int flags) +{ + (void) thread_block(THREAD_CONTINUE_NULL); +} + +static inline char * +getcomm(void) +{ + static char name[MAXCOMLEN + 1]; + proc_selfname(name, sizeof (name)); + /* Not thread safe */ + return (name); +} + +#define getpid() proc_selfpid() + +#ifdef __cplusplus +} +#endif + +#endif /* _SPL_THREAD_H */ diff --git a/include/os/macos/spl/sys/time.h b/include/os/macos/spl/sys/time.h new file mode 100644 index 000000000000..d02711280208 --- /dev/null +++ b/include/os/macos/spl/sys/time.h @@ -0,0 +1,90 @@ +/* + * 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) 2008 MacZFS + * Copyright (C) 2013 Jorgen Lundman + * + */ + +#ifndef _SPL_TIME_H +#define _SPL_TIME_H + +#include +#include_next +#include +#include + +#if defined(CONFIG_64BIT) +#define TIME_MAX INT64_MAX +#define TIME_MIN INT64_MIN +#else +#define TIME_MAX INT32_MAX +#define TIME_MIN INT32_MIN +#endif + +#define SEC 1 +#define MILLISEC 1000 +#define MICROSEC 1000000 +#define NANOSEC 1000000000 + +/* Already defined in include/linux/time.h */ +#undef CLOCK_THREAD_CPUTIME_ID +#undef CLOCK_REALTIME +#undef CLOCK_MONOTONIC +#undef CLOCK_PROCESS_CPUTIME_ID + +typedef enum clock_type { + __CLOCK_REALTIME0 = 0, /* obsolete; same as CLOCK_REALTIME */ + CLOCK_VIRTUAL = 1, /* thread's user-level CPU clock */ + CLOCK_THREAD_CPUTIME_ID = 2, /* thread's user+system CPU clock */ + CLOCK_REALTIME = 3, /* wall clock */ + CLOCK_MONOTONIC = 4, /* high resolution monotonic clock */ + CLOCK_PROCESS_CPUTIME_ID = 5, /* process's user+system CPU clock */ + CLOCK_HIGHRES = CLOCK_MONOTONIC, /* alternate name */ + CLOCK_PROF = CLOCK_THREAD_CPUTIME_ID, /* alternate name */ +} clock_type_t; + +#define TIMESPEC_OVERFLOW(ts) \ + ((ts)->tv_sec < TIME_MIN || (ts)->tv_sec > TIME_MAX) + +typedef long long hrtime_t; + +extern hrtime_t gethrtime(void); +extern void gethrestime(struct timespec *); +extern time_t gethrestime_sec(void); +extern void hrt2ts(hrtime_t hrt, struct timespec *tsp); + +#define SEC_TO_TICK(sec) ((sec) * hz) +#define NSEC_TO_TICK(nsec) ((nsec) / (NANOSEC / hz)) + +#define MSEC2NSEC(m) ((hrtime_t)(m) * (NANOSEC / MILLISEC)) +#define NSEC2MSEC(n) ((n) / (NANOSEC / MILLISEC)) + +#define USEC2NSEC(m) ((hrtime_t)(m) * (NANOSEC / MICROSEC)) +#define NSEC2USEC(n) ((n) / (NANOSEC / MICROSEC)) + +#define NSEC2SEC(n) ((n) / (NANOSEC / SEC)) +#define SEC2NSEC(m) ((hrtime_t)(m) * (NANOSEC / SEC)) + + +#endif /* _SPL_TIME_H */ diff --git a/include/os/macos/spl/sys/timer.h b/include/os/macos/spl/sys/timer.h new file mode 100644 index 000000000000..c2d0b416afd5 --- /dev/null +++ b/include/os/macos/spl/sys/timer.h @@ -0,0 +1,88 @@ +/* + * 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) 2008 MacZFS + * Copyright (C) 2013 Jorgen Lundman + * + */ + +#ifndef _SPL_TIMER_H +#define _SPL_TIMER_H + +#include + +/* Open Solaris lbolt is in hz */ +static inline int64_t +zfs_lbolt(void) +{ + struct timeval tv; + int64_t lbolt_hz; + microuptime(&tv); + lbolt_hz = ((int64_t)tv.tv_sec * USEC_PER_SEC + tv.tv_usec) / 10000; + return (lbolt_hz); +} + + +#define lbolt zfs_lbolt() +#define lbolt64 zfs_lbolt() + +#define ddi_get_lbolt() (zfs_lbolt()) +#define ddi_get_lbolt64() (zfs_lbolt()) + +#define typecheck(type, x) \ + ( \ + { type __dummy; \ + typeof(x) __dummy2; \ + (void) (&__dummy == &__dummy2); \ + 1; \ + }) + + + +#define ddi_time_before(a, b) (typecheck(clock_t, a) && \ + typecheck(clock_t, b) && \ + ((a) - (b) < 0)) +#define ddi_time_after(a, b) ddi_time_before(b, a) + +#define ddi_time_before64(a, b) (typecheck(int64_t, a) && \ + typecheck(int64_t, b) && \ + ((a) - (b) < 0)) +#define ddi_time_after64(a, b) ddi_time_before64(b, a) + + + +extern void delay(clock_t ticks); + +#define usleep_range(wakeup, whocares) \ + do { \ + hrtime_t delta = wakeup - gethrtime(); \ + if (delta > 0) { \ + struct timespec ts; \ + ts.tv_sec = delta / NANOSEC; \ + ts.tv_nsec = delta % NANOSEC; \ + (void) msleep(NULL, NULL, PWAIT, "usleep_range", &ts); \ + } \ + } while (0) + + +#endif /* _SPL_TIMER_H */ diff --git a/include/os/macos/spl/sys/trace.h b/include/os/macos/spl/sys/trace.h new file mode 100644 index 000000000000..7b72d3a98d49 --- /dev/null +++ b/include/os/macos/spl/sys/trace.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 _SPL_TRACE_H +#define _SPL_TRACE_H + + +#endif diff --git a/include/os/macos/spl/sys/tsd.h b/include/os/macos/spl/sys/tsd.h new file mode 100644 index 000000000000..cfc48000a5dd --- /dev/null +++ b/include/os/macos/spl/sys/tsd.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 (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 MacZFS + * Copyright (C) 2013, 2020 Jorgen Lundman + * + */ + + +#ifndef _SPL_TSD_H +#define _SPL_TSD_H + +#include + +#define TSD_HASH_TABLE_BITS_DEFAULT 9 +#define TSD_KEYS_MAX 32768 +#define DTOR_PID (PID_MAX_LIMIT+1) +#define PID_KEY (TSD_KEYS_MAX+1) + +typedef void (*dtor_func_t)(void *); + +extern int tsd_set(uint_t, void *); +extern void *tsd_get(uint_t); +extern void *tsd_get_by_thread(uint_t, thread_t); +extern void tsd_create(uint_t *, dtor_func_t); +extern void tsd_destroy(uint_t *); +extern void tsd_exit(void); + +uint64_t spl_tsd_size(void); +void tsd_thread_exit(void); +int spl_tsd_init(void); +void spl_tsd_fini(void); + +#endif /* _SPL_TSD_H */ diff --git a/include/os/macos/spl/sys/types.h b/include/os/macos/spl/sys/types.h new file mode 100644 index 000000000000..2d4ca3459b5d --- /dev/null +++ b/include/os/macos/spl/sys/types.h @@ -0,0 +1,142 @@ +/* + * 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) 2008 MacZFS + * Copyright (C) 2013 Jorgen Lundman + * + */ + +#ifndef _SPL_TYPES_H +#define _SPL_TYPES_H + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +#include +#include +#include_next +#include +#include +#include + +#if !defined(MAC_OS_X_VERSION_10_12) || \ + (MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_12) +#include +#include +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Avoid kcdata.h header error */ +extern unsigned long strnlen(const char *, unsigned long); + +#ifdef __cplusplus +} +#endif + +#include + +#include + +#ifndef ULLONG_MAX +#define ULLONG_MAX (~0ULL) +#endif + +#ifndef LLONG_MAX +#define LLONG_MAX ((long long)(~0ULL>>1)) +#endif + +#ifdef NEED_SOLARIS_BOOLEAN + +#if defined(__XOPEN_OR_POSIX) +typedef enum { _B_FALSE, _B_TRUE } boolean_t; +#else +typedef enum { B_FALSE, B_TRUE } boolean_t; +#endif /* defined(__XOPEN_OR_POSIX) */ +#else + +#define B_FALSE 0 +#define B_TRUE 1 + +#endif /* NEED_SOLARIS_BOOLEAN */ + +typedef short pri_t; +typedef unsigned long ulong_t; +typedef unsigned long long u_longlong_t; +typedef unsigned long long rlim64_t; +typedef unsigned long long loff_t; +typedef long long longlong_t; +typedef unsigned char uchar_t; +typedef unsigned int uint_t; +typedef unsigned short ushort_t; +typedef void *spinlock_t; +typedef long long offset_t; +typedef struct timespec timestruc_t; /* definition per SVr4 */ +typedef struct timespec timespec_t; +typedef ulong_t pgcnt_t; +typedef unsigned int umode_t; +#define NODEV32 (dev32_t)(-1) +typedef uint32_t dev32_t; +typedef uint_t minor_t; +typedef short index_t; + +#include +#define FCREAT O_CREAT +#define FTRUNC O_TRUNC +#define FEXCL O_EXCL +#define FNOCTTY O_NOCTTY +#define FNOFOLLOW O_NOFOLLOW + +#ifdef __APPLE__ +#define FSYNC O_SYNC /* file (data+inode) integrity while writing */ +#define FDSYNC O_DSYNC /* file data only integrity while writing */ +#define FOFFMAX 0x0000 /* not used */ +#define FRSYNC 0x0000 /* not used */ +#else +#define FRSYNC 0x8000 /* sync read operations at same level of */ + /* integrity as specified for writes by */ + /* FSYNC and FDSYNC flags */ +#define FOFFMAX 0x2000 /* large file */ +#endif + +#define EXPORT_SYMBOL(X) +#define module_param(X, Y, Z) +#define MODULE_PARM_DESC(X, Y) + +#ifdef __GNUC__ +#define member_type(type, member) __typeof__(((type *)0)->member) +#else +#define member_type(type, member) void +#endif + +#define container_of(ptr, type, member) ((type *) \ + ((char *)(member_type(type, member) *) \ + { ptr } - offsetof(type, member))) + +typedef struct timespec inode_timespec_t; +typedef void zidmap_t; + +#endif /* _SPL_TYPES_H */ diff --git a/include/os/macos/spl/sys/types32.h b/include/os/macos/spl/sys/types32.h new file mode 100644 index 000000000000..799a956b5e27 --- /dev/null +++ b/include/os/macos/spl/sys/types32.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 (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 _SPL_TYPES32_H +#define _SPL_TYPES32_H + +typedef uint32_t caddr32_t; +typedef int32_t daddr32_t; +typedef int32_t time32_t; +typedef uint32_t size32_t; + +#endif /* SPL_TYPE32_H */ diff --git a/include/os/macos/spl/sys/uio.h b/include/os/macos/spl/sys/uio.h new file mode 100644 index 000000000000..f7f0d8152080 --- /dev/null +++ b/include/os/macos/spl/sys/uio.h @@ -0,0 +1,234 @@ +/* + * 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 (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 _SPL_UIO_H +#define _SPL_UIO_H + +#include_next +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct iovec iovec_t; + +typedef enum uio_seg zfs_uio_seg_t; +typedef enum uio_rw zfs_uio_rw_t; + +/* + * Invent a 3rd kind of uio for iokit. + * Used by zvol_os.c to issue IO to an IOMemoryDescriptor*, + * Where we, in spl-uio.c's uiomove, issue iomem->writeBytes + * (readBytes) instead. Offset is always from 0, counting up. + * and iovbase is the iomem void *. + */ +#define UIO_FUNCSPACE 99 + +typedef size_t (*zfs_uio_func)(char *addr, uint64_t offset, size_t len, + zfs_uio_rw_t rw, const void *privptr); + +/* + * Hybrid uio, use OS uio for IO and communicating with XNU + * and internal uio for ZFS / crypto. The default mode is + * ZFS style, as zio_crypt.c creates uios on the stack, and + * they are uninitialised. However, all XNU entries will use + * ZFS_UIO_INIT_XNU(), so we can set uio_iov = NULL, to signify + * that it is a XNU uio. ZFS uio will always set uio_iov before + * it can use them. + */ +typedef struct zfs_uio { + /* Type A: XNU uio. */ + struct uio *uio_xnu; + /* Type B: Internal uio */ + const struct iovec *uio_iov; + int uio_iovcnt; + off_t uio_loffset; + zfs_uio_seg_t uio_segflg; + boolean_t uio_fault_disable; + uint16_t uio_fmode; + uint16_t uio_extflg; + ssize_t uio_resid; + size_t uio_skip; + zfs_uio_func uio_iofunc; +} zfs_uio_t; + + +/* + * Given a XNU "uio", we wrap it in a ZFS "uio", and set iov to NULL + * to indicate we should call XNU methods. However, sometimes, XNU + * passes a NULL uio (e.g. lookup size in listxattr) so we need to + * make the uio look like a ZFS uio for methods like setoffset() to + * work. + */ +extern struct iovec empty_iov; + +#define ZFS_UIO_INIT_XNU(U, X) \ + zfs_uio_t _U = { 0 }; \ + zfs_uio_t *U = &_U; \ + if ((X) != NULL) { \ + (U)->uio_iov = NULL; \ + (U)->uio_xnu = X; \ + } else { \ + (U)->uio_iov = &empty_iov; \ + } + +static inline zfs_uio_seg_t +zfs_uio_segflg(zfs_uio_t *uio) +{ + if (uio->uio_iov == NULL) + return (uio_isuserspace(uio->uio_xnu) ? + UIO_USERSPACE : UIO_SYSSPACE); + return (uio->uio_segflg); +} + +static inline void +zfs_uio_setrw(zfs_uio_t *uio, zfs_uio_rw_t inout) +{ + if (uio->uio_iov == NULL) + uio_setrw(uio->uio_xnu, inout); +} + +static inline int +zfs_uio_iovcnt(zfs_uio_t *uio) +{ + if (uio->uio_iov == NULL) + return (uio_iovcnt(uio->uio_xnu)); + return (uio->uio_iovcnt); +} + +static inline off_t +zfs_uio_offset(zfs_uio_t *uio) +{ + if (uio->uio_iov == NULL) + return (uio_offset(uio->uio_xnu)); + return (uio->uio_loffset); +} + +static inline size_t +zfs_uio_resid(zfs_uio_t *uio) +{ + if (uio->uio_iov == NULL) + return (uio_resid(uio->uio_xnu)); + return (uio->uio_resid); +} + +static inline void +zfs_uio_setoffset(zfs_uio_t *uio, off_t off) +{ + if (uio->uio_iov == NULL) { + uio_setoffset(uio->uio_xnu, off); + return; + } + uio->uio_loffset = off; +} + +static inline void +zfs_uio_advance(zfs_uio_t *uio, size_t size) +{ + if (uio->uio_iov == NULL) { + uio_update(uio->uio_xnu, size); + } else { + uio->uio_resid -= size; + uio->uio_loffset += size; + } +} + +/* zfs_uio_iovlen(uio, 0) = uio_curriovlen() */ +static inline uint64_t +zfs_uio_iovlen(zfs_uio_t *uio, unsigned int idx) +{ + if (uio->uio_iov == NULL) { + user_size_t iov_len; + if (uio_getiov(uio->uio_xnu, idx, NULL, &iov_len) < 0) + return (0ULL); + return (iov_len); + } + return (uio->uio_iov[idx].iov_len); +} + +static inline void * +zfs_uio_iovbase(zfs_uio_t *uio, unsigned int idx) +{ + if (uio->uio_iov == NULL) { + user_addr_t iov_base; + if (uio_getiov(uio->uio_xnu, idx, &iov_base, NULL) < 0) + return (NULL); + return ((void *)iov_base); + } + return (uio->uio_iov[(idx)].iov_base); +} + +static inline void +zfs_uio_iovec_init(zfs_uio_t *uio, const struct iovec *iov, + unsigned long nr_segs, off_t offset, zfs_uio_seg_t seg, ssize_t resid, + size_t skip) +{ + uio->uio_iov = iov; + uio->uio_iovcnt = nr_segs; + uio->uio_loffset = offset; + uio->uio_segflg = seg; + uio->uio_fmode = 0; + uio->uio_extflg = 0; + uio->uio_resid = resid; + uio->uio_skip = skip; + uio->uio_iofunc = NULL; +} + +static inline void +zfs_uio_iovec_func_init(zfs_uio_t *uio, const struct iovec *iov, + unsigned long nr_segs, off_t offset, zfs_uio_seg_t seg, ssize_t resid, + size_t skip, zfs_uio_func func) +{ + zfs_uio_iovec_init(uio, iov, nr_segs, offset, seg, resid, skip); + uio->uio_iofunc = func; +} + +extern int zfs_uio_prefaultpages(ssize_t, zfs_uio_t *); +#define zfs_uio_fault_disable(uio, set) +#define zfs_uio_fault_move(p, n, rw, u) zfs_uiomove((p), (n), (rw), (u)) + +ssize_t readv(int, const struct iovec *, int); +ssize_t writev(int, const struct iovec *, int); + +#ifdef __cplusplus +} +#endif +#endif /* SPL_UIO_H */ diff --git a/include/os/macos/spl/sys/utsname.h b/include/os/macos/spl/sys/utsname.h new file mode 100644 index 000000000000..b6bcab77bb74 --- /dev/null +++ b/include/os/macos/spl/sys/utsname.h @@ -0,0 +1,48 @@ +/* + * 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) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + + +/* + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + + +#ifndef _SPL_UTSNAME_H +#define _SPL_UTSNAME_H + +#define _SYS_NMLN 257 +struct opensolaris_utsname { + char sysname[_SYS_NMLN]; + char nodename[_SYS_NMLN]; + char release[_SYS_NMLN]; + char version[_SYS_NMLN]; + char machine[_SYS_NMLN]; +}; + +typedef struct opensolaris_utsname utsname_t; + +extern utsname_t *utsname(void); + +#endif /* SPL_UTSNAME_H */ diff --git a/include/os/macos/spl/sys/varargs.h b/include/os/macos/spl/sys/varargs.h new file mode 100644 index 000000000000..b7371a1f2a8a --- /dev/null +++ b/include/os/macos/spl/sys/varargs.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 (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 Jorgen Lundman + * + */ +#ifndef _SPL_VARARGS_H +#define _SPL_VARARGS_H + +#define __va_list va_list + +#endif /* SPL_VARARGS_H */ diff --git a/include/os/macos/spl/sys/vfs.h b/include/os/macos/spl/sys/vfs.h new file mode 100644 index 000000000000..aa78dc434793 --- /dev/null +++ b/include/os/macos/spl/sys/vfs.h @@ -0,0 +1,84 @@ +/* + * 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) 1988, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +/* 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 _SPL_ZFS_H +#define _SPL_ZFS_H + +#include +#include + +#define MAXFIDSZ 64 + +typedef struct mount vfs_t; + +#define vn_vfswlock(vp) (0) +#define vn_vfsunlock(vp) +#define VFS_HOLD(vfsp) +#define VFS_RELE(vfsp) + + + +/* + * File identifier. Should be unique per filesystem on a single + * machine. This is typically called by a stateless file server + * in order to generate "file handles". + * + * Do not change the definition of struct fid ... fid_t without + * letting the CacheFS group know about it! They will have to do at + * least two things, in the same change that changes this structure: + * 1. change CFSVERSION in usr/src/uts/common/sys/fs/cachefs_fs.h + * 2. put the old version # in the canupgrade array + * in cachfs_upgrade() in usr/src/cmd/fs.d/cachefs/fsck/fsck.c + * This is necessary because CacheFS stores FIDs on disk. + * + * Many underlying file systems cast a struct fid into other + * file system dependent structures which may require 4 byte alignment. + * Because a fid starts with a short it may not be 4 byte aligned, the + * fid_pad will force the alignment. + */ +#define MAXFIDSZ 64 +#define OLD_MAXFIDSZ 16 + +typedef struct fid { + union { + long fid_pad; + struct { + ushort_t len; /* length of data in bytes */ + char data[MAXFIDSZ]; /* data (variable len) */ + } _fid; + } un; +} fid_t; + + +extern void (*mountroot_post_hook)(void); + +#endif /* SPL_ZFS_H */ diff --git a/include/os/macos/spl/sys/vmem.h b/include/os/macos/spl/sys/vmem.h new file mode 100644 index 000000000000..bd6f6a34da75 --- /dev/null +++ b/include/os/macos/spl/sys/vmem.h @@ -0,0 +1,186 @@ +/* + * 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) 1999, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012 by Delphix. All rights reserved. + */ + +#ifndef _SYS_VMEM_H +#define _SYS_VMEM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Make sure IOMalloc uses kernel_map, and not kalloc.zones +// This is from XNU kalloc_max and kalloc_kernmap_size +// #define KMEM_QUANTUM (PAGESIZE << 2) * 16 + PAGESIZE; +#define KMEM_QUANTUM PAGESIZE + + /* + * Per-allocation flags + */ +#define VM_SLEEP 0x00000000 /* same as KM_SLEEP */ +#define VM_NOSLEEP 0x00000001 /* same as KM_NOSLEEP */ +#define VM_PANIC 0x00000002 /* same as KM_PANIC */ +#define VM_PUSHPAGE 0x00000004 /* same as KM_PUSHPAGE */ +#define VM_NORMALPRI 0x00000008 /* same as KM_NORMALPRI */ +#define VM_NODEBUG 0x00000010 /* matches KM_NODE~BUG, */ + /* not implemented on OSX */ +#define VM_NO_VBA 0x00000020 /* OSX: do not descend to the bucket layer */ +#define VM_KMFLAGS 0x000000ff /* flags that must match KM_* flags */ + +#define VM_BESTFIT 0x00000100 +#define VM_FIRSTFIT 0x00000200 +#define VM_NEXTFIT 0x00000400 + +/* + * The following flags are restricted for use only within the kernel. + * VM_MEMLOAD is for use by the HAT to avoid infinite recursion. + * VM_NORELOC is used by the kernel when static VA->PA mappings are required. + */ +#define VM_MEMLOAD 0x00000800 +#define VM_NORELOC 0x00001000 + +/* + * VM_ABORT requests that vmem_alloc() *ignore* the VM_SLEEP/VM_NOSLEEP flags + * and forgo reaping if the allocation or attempted import, fails. This + * flag is a segkmem-specific flag, and should not be used by anyone else. + */ +#define VM_ABORT 0x00002000 + +/* + * VM_ENDALLOC requests that large addresses be preferred in allocations. + * Has no effect if VM_NEXTFIT is active. + */ +#define VM_ENDALLOC 0x00004000 + +#define VM_FLAGS 0x0000FFFF + +/* + * Arena creation flags + */ +#define VMC_POPULATOR 0x00010000 +#define VMC_NO_QCACHE 0x00020000 /* cannot use quantum caches */ +#define VMC_IDENTIFIER 0x00040000 /* not backed by memory */ +// VMC_XALLOC 0x00080000 below +// VMC_XALIGN 0x00100000 below +#define VMC_DUMPSAFE 0x00200000 /* can use alternate dump memory */ +// KMC_IDENTIFIER == 0x00400000 +// KMC_PREFILL == 0x00800000 +#define VMC_TIMEFREE 0x01000000 /* keep span creation time, */ + /* newest spans to front */ +#define VMC_OLDFIRST 0x02000000 /* must accompany VMC_TIMEFREE, */ + /* oldest spans to front */ + +/* + * internal use only; the import function uses the vmem_ximport_t interface + * and may increase the request size if it so desires. + * VMC_XALIGN, for use with vmem_xcreate, specifies that + * the address returned by the import function will be + * aligned according to the alignment argument. + */ +#define VMC_XALLOC 0x00080000 +#define VMC_XALIGN 0x00100000 +#define VMC_FLAGS 0xFFFF0000 + +/* + * Public segment types + */ +#define VMEM_ALLOC 0x01 +#define VMEM_FREE 0x02 + +/* + * Implementation-private segment types + */ +#define VMEM_SPAN 0x10 +#define VMEM_ROTOR 0x20 +#define VMEM_WALKER 0x40 + +/* + * VMEM_REENTRANT indicates to vmem_walk() that the callback routine may + * call back into the arena being walked, so vmem_walk() must drop the + * arena lock before each callback. The caveat is that since the arena + * isn't locked, its state can change. Therefore it is up to the callback + * routine to handle cases where the segment isn't of the expected type. + * For example, we use this to walk heap_arena when generating a crash dump; + * see segkmem_dump() for sample usage. + */ +#define VMEM_REENTRANT 0x80000000 + +struct vmem; + +typedef struct vmem vmem_t; +typedef void *(vmem_alloc_t)(vmem_t *, size_t, int); +typedef void (vmem_free_t)(vmem_t *, const void *, size_t); + +/* + * Alternate import style; the requested size is passed in a pointer, + * which can be increased by the import function if desired. + */ +typedef void *(vmem_ximport_t)(vmem_t *, size_t *, size_t, int); + +#ifdef _KERNEL +extern vmem_t *vmem_init(const char *, void *, size_t, size_t, + vmem_alloc_t *, vmem_free_t *); +extern void vmem_fini(vmem_t *); +extern void vmem_update(void *); +extern int vmem_is_populator(void); +extern size_t vmem_seg_size; +#endif + +extern vmem_t *vmem_create(const char *, void *, size_t, size_t, + vmem_alloc_t *, vmem_free_t *, vmem_t *, size_t, int); +extern vmem_t *vmem_xcreate(const char *, void *, size_t, size_t, + vmem_ximport_t *, vmem_free_t *, vmem_t *, size_t, int); +extern void vmem_destroy(vmem_t *); +extern void *vmem_alloc_impl(vmem_t *, size_t, int); +extern void *vmem_xalloc(vmem_t *, size_t, size_t, size_t, size_t, + void *, void *, int); +extern void vmem_free_impl(vmem_t *, const void *, size_t); +extern void vmem_xfree(vmem_t *, const void *, size_t); +extern void *vmem_add(vmem_t *, void *, size_t, int); +extern int vmem_contains(vmem_t *, void *, size_t); +extern void vmem_walk(vmem_t *, int, void (*)(void *, void *, size_t), void *); +extern size_t vmem_size(vmem_t *, int); +extern size_t vmem_size_locked(vmem_t *, int); +extern size_t vmem_size_semi_atomic(vmem_t *, int); +extern void vmem_qcache_reap(vmem_t *vmp); +extern int64_t vmem_buckets_size(int); +extern int64_t abd_arena_empty_space(void); +extern int64_t abd_arena_total_size(void); + +/* + * Since Linux code uses vmem_alloc()/vmem_free and it is not + * the illumos one, wrap kmem_alloc()/kmem_free. + */ +#define vmem_free(A, B) zfs_kmem_free((A), (B)) +#define vmem_alloc(A, B) zfs_kmem_alloc((A), (B)) +#define vmem_zalloc(A, B) zfs_kmem_zalloc((A), (B)) + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_VMEM_H */ diff --git a/include/os/macos/spl/sys/vmem_impl.h b/include/os/macos/spl/sys/vmem_impl.h new file mode 100644 index 000000000000..59d4d08e3ed5 --- /dev/null +++ b/include/os/macos/spl/sys/vmem_impl.h @@ -0,0 +1,182 @@ +/* + * 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 1999-2001, 2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SYS_VMEM_IMPL_H +#define _SYS_VMEM_IMPL_H + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct vmem_seg vmem_seg_t; + +#define VMEM_STACK_DEPTH 20 + +struct vmem_seg { + /* + * The first four fields must match vmem_freelist_t exactly. + */ + uintptr_t vs_start; /* start of segment (inclusive) */ + uintptr_t vs_end; /* end of segment (exclusive) */ + vmem_seg_t *vs_knext; /* next of kin (alloc, free, span) */ + vmem_seg_t *vs_kprev; /* prev of kin */ + + vmem_seg_t *vs_anext; /* next in arena */ + vmem_seg_t *vs_aprev; /* prev in arena */ + uint8_t vs_type; /* alloc, free, span */ + uint8_t vs_import; /* non-zero if segment was imported */ + uint8_t vs_depth; /* stack depth if KMF_AUDIT active */ + /* + * if VM_FREESORT is set on the arena, then + * this field is set at span creation time. + */ + hrtime_t vs_span_createtime; + /* + * The following fields are present only when KMF_AUDIT is set. + */ + kthread_t *vs_thread; + hrtime_t vs_timestamp; + pc_t vs_stack[VMEM_STACK_DEPTH]; +}; + +typedef struct vmem_freelist { + uintptr_t vs_start; /* always zero */ + uintptr_t vs_end; /* segment size */ + vmem_seg_t *vs_knext; /* next of kin */ + vmem_seg_t *vs_kprev; /* prev of kin */ +} vmem_freelist_t; + +#define VS_SIZE(vsp) ((vsp)->vs_end - (vsp)->vs_start) + +/* + * Segment hashing + */ +#define VMEM_HASH_INDEX(a, s, q, m) \ + ((((a) + ((a) >> (s)) + ((a) >> ((s) << 1))) >> (q)) & (m)) + +#define VMEM_HASH(vmp, addr) \ + (&(vmp)->vm_hash_table[VMEM_HASH_INDEX(addr, \ + (vmp)->vm_hash_shift, (vmp)->vm_qshift, (vmp)->vm_hash_mask)]) + +#define VMEM_QCACHE_SLABSIZE(max) \ + MAX(1 << highbit(3 * (max)), 64) + +#define VMEM_NAMELEN 30 +#define VMEM_HASH_INITIAL 16 +#define VMEM_NQCACHE_MAX 16 +#define VMEM_FREELISTS (sizeof (void *) * 8) + +typedef struct vmem_kstat { + kstat_named_t vk_mem_inuse; /* memory in use */ + kstat_named_t vk_mem_import; /* memory imported */ + kstat_named_t vk_mem_total; /* total memory in arena */ + kstat_named_t vk_source_id; /* vmem id of vmem source */ + kstat_named_t vk_alloc; /* number of allocations */ + kstat_named_t vk_free; /* number of frees */ + kstat_named_t vk_wait; /* number of allocations that waited */ + kstat_named_t vk_fail; /* number of allocations that failed */ + kstat_named_t vk_lookup; /* hash lookup count */ + kstat_named_t vk_search; /* freelist search count */ + kstat_named_t vk_populate_fail; /* populates that failed */ + kstat_named_t vk_contains; /* vmem_contains() calls */ + kstat_named_t vk_contains_search; /* vmem_contains() search cnt */ + kstat_named_t vk_parent_alloc; /* called the source allocator */ + kstat_named_t vk_parent_free; /* called the source free function */ + kstat_named_t vk_threads_waiting; /* threads in cv_wait in vmem */ + /* allocator function */ + kstat_named_t vk_excess; /* count of retained excess imports */ + kstat_named_t vk_lowest_stack; /* least remaining stack seen */ + kstat_named_t vk_async_stack_calls; /* times allocated off-thread */ +} vmem_kstat_t; + + +/* forward declaration of opaque xnu struct */ +typedef struct thread_call *thread_call_t; + +/* parameters passed between thread_call threads */ +typedef struct cb_params { + /* has the worker thread been entered? */ + _Atomic boolean_t in_child; + /* sanity check on thread_call_enter1() behaviour */ + _Atomic boolean_t already_pending; + /* how much to allocate, what vmem_alloc_impl() flags to use */ + _Atomic size_t size; + int vmflag; + /* wrapped_vmem_alloc_impl() return value */ + void *r_alloc; + /* has the worker thread finished? */ + _Atomic boolean_t c_done; +} cb_params_t; + +struct vmem { + char vm_name[VMEM_NAMELEN]; /* arena name */ + kcondvar_t vm_cv; /* cv for blocking allocations */ + kmutex_t vm_lock; /* arena lock */ + uint32_t vm_id; /* vmem id */ + hrtime_t vm_createtime; + uint32_t vm_mtbf; /* induced alloc failure rate */ + int vm_cflags; /* arena creation flags */ + int vm_qshift; /* log2(vm_quantum) */ + size_t vm_quantum; /* vmem quantum */ + size_t vm_qcache_max; /* maximum size to front by kmem */ + size_t vm_min_import; /* smallest amount to import */ + void *(*vm_source_alloc)(vmem_t *, size_t, int); + void (*vm_source_free)(vmem_t *, void *, size_t); + vmem_t *vm_source; /* vmem source for imported memory */ + vmem_t *vm_next; /* next in vmem_list */ + kstat_t *vm_ksp; /* kstat */ + ssize_t vm_nsegfree; /* number of free vmem_seg_t's */ + vmem_seg_t *vm_segfree; /* free vmem_seg_t list */ + vmem_seg_t **vm_hash_table; /* allocated-segment hash table */ + size_t vm_hash_mask; /* hash_size - 1 */ + size_t vm_hash_shift; /* log2(vm_hash_mask + 1) */ + ulong_t vm_freemap; /* bitmap of non-empty freelists */ + vmem_seg_t vm_seg0; /* anchor segment */ + vmem_seg_t vm_rotor; /* rotor for VM_NEXTFIT allocations */ + vmem_seg_t *vm_hash0[VMEM_HASH_INITIAL]; /* initial hash table */ + void *vm_qcache[VMEM_NQCACHE_MAX]; /* quantum caches */ + vmem_freelist_t vm_freelist[VMEM_FREELISTS + 1]; /* power-of-2 flists */ + vmem_kstat_t vm_kstat; /* kstat data */ + thread_call_t vm_stack_call_thread; /* worker thread for vmem_alloc */ + kmutex_t vm_stack_lock; /* synchronize with worker thread */ + kcondvar_t vm_stack_cv; + _Atomic bool vm_cb_busy; /* gateway before thread_call_enter1() */ + cb_params_t vm_cb; /* maybe used in vmem_alloc_in_worker_thread */ +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_VMEM_IMPL_H */ diff --git a/include/os/macos/spl/sys/vmsystm.h b/include/os/macos/spl/sys/vmsystm.h new file mode 100644 index 000000000000..b09abee3914a --- /dev/null +++ b/include/os/macos/spl/sys/vmsystm.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 (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 Jorgen Lundman + * + */ + +#ifndef _SPL_VMSYSTM_H +#define _SPL_VMSYSTM_H + +#include + +#define xcopyout(K, U, L) copyout((K), (user_addr_t)(U), (L)) + +#endif /* SPL_VMSYSTM_H */ diff --git a/include/os/macos/spl/sys/vnode.h b/include/os/macos/spl/sys/vnode.h new file mode 100644 index 000000000000..e3a8c13810d2 --- /dev/null +++ b/include/os/macos/spl/sys/vnode.h @@ -0,0 +1,238 @@ +/* + * 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) 2008 MacZFS + * Copyright (C) 2013 Jorgen Lundman + * + */ + +#ifndef _SPL_VNODE_H +#define _SPL_VNODE_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +// Be aware that Apple defines "typedef struct vnode *vnode_t" and +// ZFS uses "typedef struct vnode vnode_t". +// uio and vnode wrappers can be removed now. +// uio_t -> zfs_uio_t +// vnode_t -> struct vnode (as it is only used in os/macos/ +// proc_t is to work around vn_rdwr( ..., proc_t p) +#undef uio_t +#undef vnode_t +#undef proc_t +#define proc_t struct proc * +#include_next +#undef proc_t +#define proc_t struct proc +#define vnode_t struct vnode +#define uio_t struct uio + +#define LOOKUP_XATTR 0x02 /* lookup up extended attr dir */ + +struct caller_context; +typedef struct caller_context caller_context_t; +typedef int vcexcl_t; + +enum vcexcl { NONEXCL, EXCL }; + +#define B_INVAL 0x01 +#define B_TRUNC 0x02 + +#define CREATE_XATTR_DIR 0x04 /* Create extended attr dir */ +#define ATTR_NOACLCHECK 0x20 + +#define IS_DEVVP(vp) \ + (vnode_ischr(vp) || vnode_isblk(vp) || vnode_isfifo(vp)) + +enum rm { RMFILE, RMDIRECTORY }; /* rm or rmdir (remove) */ +enum create { CRCREAT, CRMKNOD, CRMKDIR }; /* reason for create */ + +#define va_mask va_active +#define va_nodeid va_fileid +#define va_nblocks va_filerev + +/* + * vnode attr translations + */ +#define ATTR_TYPE VNODE_ATTR_va_type +#define ATTR_MODE VNODE_ATTR_va_mode +#define ATTR_ACL VNODE_ATTR_va_acl +#define ATTR_UID VNODE_ATTR_va_uid +#define ATTR_GID VNODE_ATTR_va_gid +#define ATTR_ATIME VNODE_ATTR_va_access_time +#define ATTR_MTIME VNODE_ATTR_va_modify_time +#define ATTR_CTIME VNODE_ATTR_va_change_time +#define ATTR_CRTIME VNODE_ATTR_va_create_time +#define ATTR_SIZE VNODE_ATTR_va_data_size +#define ATTR_NOSET 0 + +#define ATTR_XVATTR (1LL<<63) /* See XNU: bsd/sys/vnode.h */ +#define AT_XVATTR ATTR_XVATTR + +#define va_size va_data_size +#define va_atime va_access_time +#define va_mtime va_modify_time +#define va_ctime va_change_time +#define va_crtime va_create_time +#define va_bytes va_data_size + +typedef struct vnode_attr vattr; +typedef struct vnode_attr vattr_t; + +/* vsa_mask values */ +#define VSA_ACL 0x0001 +#define VSA_ACLCNT 0x0002 +#define VSA_DFACL 0x0004 +#define VSA_DFACLCNT 0x0008 +#define VSA_ACE 0x0010 +#define VSA_ACECNT 0x0020 +#define VSA_ACE_ALLTYPES 0x0040 +#define VSA_ACE_ACLFLAGS 0x0080 /* get/set ACE ACL flags */ + + +extern struct vnode *vn_alloc(int flag); + +extern int vn_open(char *pnamep, enum uio_seg seg, int filemode, + int createmode, struct vnode **vpp, enum create crwhy, mode_t umask); +extern int vn_openat(char *pnamep, enum uio_seg seg, int filemode, + int createmode, struct vnode **vpp, enum create crwhy, + mode_t umask, struct vnode *startvp); + +#define vn_renamepath(tdvp, svp, tnm, lentnm) do { } while (0) +#define vn_free(vp) do { } while (0) +#define vn_pages_remove(vp, fl, op) do { } while (0) + +/* XNU is a vn_rdwr, so we work around it to match arguments */ +/* This should be deprecated, if not now, soon. */ +extern int zfs_vn_rdwr(enum uio_rw rw, struct vnode *vp, caddr_t base, + ssize_t len, offset_t offset, enum uio_seg seg, int ioflag, + rlim64_t ulimit, cred_t *cr, ssize_t *residp); + +#define vn_rdwr(rw, vp, b, l, o, s, flg, li, cr, resid) \ + zfs_vn_rdwr((rw), (vp), (b), (l), (o), (s), (flg), (li), (cr), (resid)) + +/* Other vn_rdwr for zfs_file_t ops */ +struct spl_fileproc; +extern int spl_vn_rdwr(enum uio_rw rw, struct spl_fileproc *, caddr_t base, + ssize_t len, offset_t offset, enum uio_seg seg, int ioflag, + rlim64_t ulimit, cred_t *cr, ssize_t *residp); + +extern int vn_remove(char *fnamep, enum uio_seg seg, enum rm dirflag); +extern int vn_rename(char *from, char *to, enum uio_seg seg); + +#define LK_RETRY 0 +#define LK_SHARED 0 +#define VN_UNLOCK(vp) +static inline int +vn_lock(struct vnode *vp, int fl) +{ + return (0); +} + +#define VN_HOLD(vp) vnode_getwithref(vp) +#define VN_RELE(vp) vnode_put(vp) + +void spl_rele_async(void *arg); +void vn_rele_async(struct vnode *vp, void *taskq); + +extern int vnode_iocount(struct vnode *); + +#define F_SEEK_HOLE SEEK_HOLE + +#define VN_RELE_ASYNC(vp, tq) vn_rele_async((vp), (tq)) + +#define vn_exists(vp) +#define vn_is_readonly(vp) vnode_vfsisrdonly(vp) + +#define vnode_pager_setsize(vp, sz) ubc_setsize((vp), (sz)) + +#define VATTR_NULL(v) do { } while (0) + +extern int VOP_CLOSE(struct vnode *vp, int flag, int count, + offset_t off, void *cr, void *); +extern int VOP_FSYNC(struct vnode *vp, int flags, void* unused, void *); +extern int VOP_SPACE(struct vnode *vp, int cmd, struct flock *fl, + int flags, offset_t off, cred_t *cr, void *ctx); + +extern int VOP_GETATTR(struct vnode *vp, vattr_t *vap, int flags, + void *x3, void *x4); + +#define VOP_UNLOCK(vp, fl) do { } while (0) + +void vfs_mountedfrom(struct mount *vfsp, char *osname); + +#define build_path(A, B, C, D, E, F) spl_build_path(A, B, C, D, E, F) +extern int spl_build_path(struct vnode *vp, char *buff, int buflen, + int *outlen, int flags, vfs_context_t ctx); + +extern struct vnode *rootdir; + +static inline int chklock(struct vnode *vp, int iomode, + unsigned long long offset, ssize_t len, int fmode, void *ct) +{ + return (0); +} + +#define vn_ismntpt(vp) (vnode_mountedhere(vp) != NULL) + +extern errno_t VOP_LOOKUP (struct vnode *, struct vnode **, + struct componentname *, vfs_context_t); +extern errno_t VOP_MKDIR (struct vnode *, struct vnode **, + struct componentname *, struct vnode_attr *, + vfs_context_t); +extern errno_t VOP_REMOVE (struct vnode *, struct vnode *, + struct componentname *, int, vfs_context_t); +extern errno_t VOP_SYMLINK (struct vnode *, struct vnode **, + struct componentname *, struct vnode_attr *, + char *, vfs_context_t); + +void spl_vnode_fini(void); +int spl_vnode_init(void); + +extern void spl_cache_purgevfs(struct mount *mp, boolean_t reload); +#define cache_purgevfs spl_cache_purgevfs + +vfs_context_t vfs_context_kernel(void); +vfs_context_t spl_vfs_context_kernel(void); +extern int spl_vnode_notify(struct vnode *vp, uint32_t type, + struct vnode_attr *vap); +extern int spl_vfs_get_notify_attributes(struct vnode_attr *vap); +extern void spl_hijack_mountroot(void *func); +extern void spl_setrootvnode(struct vnode *vp); + +struct vnode *getrootdir(void); +void spl_vfs_start(void); + +#endif /* SPL_VNODE_H */ diff --git a/include/os/macos/spl/sys/wmsum.h b/include/os/macos/spl/sys/wmsum.h new file mode 100644 index 000000000000..a8442554bb58 --- /dev/null +++ b/include/os/macos/spl/sys/wmsum.h @@ -0,0 +1,73 @@ +/* + * 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 + */ + +/* + * wmsum counters are a reduced version of aggsum counters, optimized for + * write-mostly scenarios. They do not provide optimized read functions, + * but instead allow much cheaper add function. The primary usage is + * infrequently read statistic counters, not requiring exact precision. + * + * Find a macOS variant of whatever this is + */ + +#ifndef _SYS_WMSUM_H +#define _SYS_WMSUM_H + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Repeat what's in userland lib/libspl/include/sys/wmsum.h, as macOS does not + * have an API like the FreeBSD counter(9) or Linux's percpu_counter KPI. + */ + +#define wmsum_t aggsum_t + +static inline void +wmsum_init(wmsum_t *ws, uint64_t value) +{ + aggsum_init(ws, value); +} + +static inline void +wmsum_fini(wmsum_t *ws) +{ + aggsum_fini(ws); +} + +static inline uint64_t +wmsum_value(wmsum_t *ws) +{ + return (aggsum_value(ws)); +} + +static inline void +wmsum_add(wmsum_t *ws, int64_t delta) +{ + aggsum_add(ws, delta); +} + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_WMSUM_H */ diff --git a/include/os/macos/spl/sys/zmod.h b/include/os/macos/spl/sys/zmod.h new file mode 100644 index 000000000000..5738d40f9e9c --- /dev/null +++ b/include/os/macos/spl/sys/zmod.h @@ -0,0 +1,122 @@ +/* + * zlib.h -- interface of the 'zlib' general purpose compression library + * version 1.2.5, April 19th, 2010 + * + * Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * Jean-loup Gailly + * Mark Adler + */ + +#ifndef _SPL_ZMOD_H +#define _SPL_ZMOD_H + + +#include +#include +#include + +struct _zmemheader { + uint64_t length; + char data[0]; +}; + +static inline void * +zfs_zalloc(void* opaque, uInt items, uInt size) +{ + struct _zmemheader *hdr; + size_t alloc_size = (items * size) + sizeof (uint64_t); + hdr = kmem_zalloc(alloc_size, KM_SLEEP); + hdr->length = alloc_size; + return (&hdr->data); +} + +static inline void +zfs_zfree(void *opaque, void *addr) +{ + struct _zmemheader *hdr; + hdr = addr; + hdr--; + kmem_free(hdr, hdr->length); +} + +/* + * Uncompress the buffer 'src' into the buffer 'dst'. The caller must store + * the expected decompressed data size externally so it can be passed in. + * The resulting decompressed size is then returned through dstlen. This + * function return Z_OK on success, or another error code on failure. + */ +static inline int + z_uncompress(void *dst, size_t *dstlen, const void *src, size_t srclen) +{ + z_stream zs; + int err; + + memset(&zs, 0, sizeof (zs)); + zs.next_in = (uchar_t *)src; + zs.avail_in = srclen; + zs.next_out = dst; + zs.avail_out = *dstlen; + zs.zalloc = zfs_zalloc; + zs.zfree = zfs_zfree; + if ((err = inflateInit(&zs)) != Z_OK) + return (err); + if ((err = inflate(&zs, Z_FINISH)) != Z_STREAM_END) { + (void) inflateEnd(&zs); + return (err == Z_OK ? Z_BUF_ERROR : err); + } + *dstlen = zs.total_out; + return (inflateEnd(&zs)); +} + +static inline int +z_compress_level(void *dst, size_t *dstlen, const void *src, size_t srclen, + int level) +{ + z_stream zs; + int err; + memset(&zs, 0, sizeof (zs)); + zs.next_in = (uchar_t *)src; + zs.avail_in = srclen; + zs.next_out = dst; + zs.avail_out = *dstlen; + zs.zalloc = zfs_zalloc; + zs.zfree = zfs_zfree; + if ((err = deflateInit(&zs, level)) != Z_OK) + return (err); + if ((err = deflate(&zs, Z_FINISH)) != Z_STREAM_END) { + (void) deflateEnd(&zs); + return (err == Z_OK ? Z_BUF_ERROR : err); + } + *dstlen = zs.total_out; + return (deflateEnd(&zs)); +} + +static inline int +z_compress(void *dst, size_t *dstlen, const void *src, size_t srclen) +{ + return (z_compress_level(dst, dstlen, src, srclen, + Z_DEFAULT_COMPRESSION)); +} + + +int spl_zlib_init(void); +void spl_zlib_fini(void); + +#endif /* SPL_ZMOD_H */ diff --git a/include/os/macos/spl/sys/zone.h b/include/os/macos/spl/sys/zone.h new file mode 100644 index 000000000000..deefad54a1d9 --- /dev/null +++ b/include/os/macos/spl/sys/zone.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 (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 Jorgen Lundman + * + */ + +#ifndef _SPL_ZONE_H +#define _SPL_ZONE_H + +#include + +#define GLOBAL_ZONEID 0 + +#define zone_dataset_visible(x, y) (1) +#define INGLOBALZONE(z) (1) + +#endif /* SPL_ZONE_H */ diff --git a/include/os/macos/zfs/Makefile.am b/include/os/macos/zfs/Makefile.am new file mode 100644 index 000000000000..081839c48c8f --- /dev/null +++ b/include/os/macos/zfs/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = sys diff --git a/include/os/macos/zfs/sys/Makefile.am b/include/os/macos/zfs/sys/Makefile.am new file mode 100644 index 000000000000..9779de08e0b3 --- /dev/null +++ b/include/os/macos/zfs/sys/Makefile.am @@ -0,0 +1,10 @@ +KERNEL_H = \ + $(top_srcdir)/include/os/macos/zfs/sys/kstat_osx.h \ + $(top_srcdir)/include/os/macos/spl/sys/ldi_buf.h \ + $(top_srcdir)/include/os/macos/spl/sys/ldi_impl_osx.h \ + $(top_srcdir)/include/os/macos/spl/sys/ldi_osx.h \ + $(top_srcdir)/include/os/macos/spl/sys/trace_zfs.h \ + $(top_srcdir)/include/os/macos/spl/sys/vdev_disk_os.h \ + $(top_srcdir)/include/os/macos/spl/sys/zfs_ioctl_compat.h \ + $(top_srcdir)/include/os/macos/spl/sys/zfs_vfsops.h \ + $(top_srcdir)/include/os/macos/spl/sys/zfs_znode_impl.h diff --git a/include/os/macos/zfs/sys/ZFSDataset.h b/include/os/macos/zfs/sys/ZFSDataset.h new file mode 100644 index 000000000000..1981de56cb92 --- /dev/null +++ b/include/os/macos/zfs/sys/ZFSDataset.h @@ -0,0 +1,142 @@ +/* + * 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) 2016, Evan Susarret. All rights reserved. + */ + +#ifndef ZFSDATASET_H_INCLUDED +#define ZFSDATASET_H_INCLUDED + +#ifdef __cplusplus + +#include +#include +#include + +#ifdef super +#undef super +#endif +#define super IOMedia + +// #define kZFSContentHint "6A898CC3-1DD2-11B2-99A6-080020736631" +#define kZFSContentHint "ZFS_Dataset" + +#define kZFSIOMediaPrefix "ZFS " +#define kZFSIOMediaSuffix " Media" +#define kZFSDatasetNameKey "ZFS Dataset" +#define kZFSDatasetClassKey "ZFSDataset" + +class ZFSDataset : public IOMedia +{ + OSDeclareDefaultStructors(ZFSDataset) +public: +#if 0 + /* XXX Only for debug tracing */ + virtual bool open(IOService *client, + IOOptionBits options, IOStorageAccess access = 0); + virtual bool isOpen(const IOService *forClient = 0) const; + virtual void close(IOService *client, + IOOptionBits options); + + virtual bool handleOpen(IOService *client, + IOOptionBits options, void *access); + virtual bool handleIsOpen(const IOService *client) const; + virtual void handleClose(IOService *client, + IOOptionBits options); + + virtual bool attach(IOService *provider); + virtual void detach(IOService *provider); + + virtual bool start(IOService *provider); + virtual void stop(IOService *provider); +#endif + + virtual bool init(UInt64 base, UInt64 size, + UInt64 preferredBlockSize, + IOMediaAttributeMask attributes, + bool isWhole, bool isWritable, + const char *contentHint = 0, + OSDictionary *properties = 0) override; + virtual void free() override; + + static ZFSDataset * withDatasetNameAndSize(const char *name, + uint64_t size); + + virtual void read(IOService *client, + UInt64 byteStart, IOMemoryDescriptor *buffer, + IOStorageAttributes *attributes, + IOStorageCompletion *completion) override; + virtual void write(IOService *client, + UInt64 byteStart, IOMemoryDescriptor *buffer, + IOStorageAttributes *attributes, + IOStorageCompletion *completion) override; + +#if defined(MAC_OS_X_VERSION_10_11) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) + virtual IOReturn synchronize(IOService *client, + UInt64 byteStart, UInt64 byteCount, + IOStorageSynchronizeOptions options = 0) override; +#else + virtual IOReturn synchronizeCache(IOService *client) override; +#endif + + virtual IOReturn unmap(IOService *client, + IOStorageExtent *extents, UInt32 extentsCount, +#if defined(MAC_OS_X_VERSION_10_11) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) + IOStorageUnmapOptions options = 0) override; +#else + UInt32 options = 0); +#endif + + virtual bool lockPhysicalExtents(IOService *client) override; + virtual IOStorage *copyPhysicalExtent(IOService *client, + UInt64 *byteStart, UInt64 *byteCount) override; + virtual void unlockPhysicalExtents(IOService *client) override; + +#if defined(MAC_OS_X_VERSION_10_10) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) + virtual IOReturn setPriority(IOService *client, + IOStorageExtent *extents, UInt32 extentsCount, + IOStoragePriority priority) override; +#endif + + virtual UInt64 getPreferredBlockSize() const override; + virtual UInt64 getSize() const override; + virtual UInt64 getBase() const override; + + virtual bool isEjectable() const override; + virtual bool isFormatted() const override; + virtual bool isWhole() const override; + virtual bool isWritable() const override; + + virtual const char *getContent() const override; + virtual const char *getContentHint() const override; + virtual IOMediaAttributeMask getAttributes() const override; + +protected: +private: + bool setDatasetName(const char *); +}; + +#endif /* __cplusplus */ + +#endif /* ZFSDATASET_H_INCLUDED */ diff --git a/include/os/macos/zfs/sys/ZFSDatasetProxy.h b/include/os/macos/zfs/sys/ZFSDatasetProxy.h new file mode 100644 index 000000000000..e51d5dd7035c --- /dev/null +++ b/include/os/macos/zfs/sys/ZFSDatasetProxy.h @@ -0,0 +1,82 @@ +/* + * 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) 2016, Evan Susarret. All rights reserved. + */ + +#ifndef ZFSDATASETPROXY_H_INCLUDED +#define ZFSDATASETPROXY_H_INCLUDED + +#include + +class ZFSDatasetProxy : public IOBlockStorageDevice +{ + OSDeclareDefaultStructors(ZFSDatasetProxy); +public: + + virtual void free(void) override; + virtual bool init(OSDictionary *properties) override; + virtual bool start(IOService *provider) override; + + /* IOBlockStorageDevice */ + virtual IOReturn doSynchronizeCache(void) override; + virtual IOReturn doAsyncReadWrite(IOMemoryDescriptor *, + UInt64, UInt64, IOStorageAttributes *, + IOStorageCompletion *) override; + virtual UInt32 doGetFormatCapacities(UInt64 *, + UInt32) const override; + virtual IOReturn doFormatMedia(UInt64 byteCapacity) override; + virtual IOReturn doEjectMedia() override; + virtual char *getVendorString() override; + virtual char *getProductString() override; + virtual char *getRevisionString() override; + virtual char *getAdditionalDeviceInfoString() override; + virtual IOReturn reportWriteProtection(bool *) override; + virtual IOReturn reportRemovability(bool *) override; + virtual IOReturn reportMediaState(bool *, bool *) override; + virtual IOReturn reportBlockSize(UInt64 *) override; + virtual IOReturn reportEjectability(bool *) override; + virtual IOReturn reportMaxValidBlock(UInt64 *) override; + + virtual IOReturn setWriteCacheState(bool enabled) override; + virtual IOReturn getWriteCacheState(bool *enabled) override; +#if 0 + virtual void read(IOService *client, UInt64 byteStart, + IOMemoryDescriptor *buffer, IOStorageAttributes *attr, + IOStorageCompletion *completion); + virtual void write(IOService *client, UInt64 byteStart, + IOMemoryDescriptor *buffer, IOStorageAttributes *attr, + IOStorageCompletion *completion); +#endif + +protected: +private: + /* These are declared class static to share across instances */ + const char *vendorString; + const char *revisionString; + const char *infoString; + /* These are per-instance */ + const char *productString; + uint64_t _pool_bcount; + bool isReadOnly; +}; + +#endif /* ZFSDATASETPROXY_H_INCLUDED */ diff --git a/include/os/macos/zfs/sys/ZFSDatasetScheme.h b/include/os/macos/zfs/sys/ZFSDatasetScheme.h new file mode 100644 index 000000000000..68983eecfe3a --- /dev/null +++ b/include/os/macos/zfs/sys/ZFSDatasetScheme.h @@ -0,0 +1,126 @@ +/* + * 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) 2016, Evan Susarret. All rights reserved. + */ + +#ifndef ZFSDATASETSCHEME_H_INCLUDED +#define ZFSDATASETSCHEME_H_INCLUDED + +#define kZFSDatasetSchemeClass "ZFSDatasetScheme" + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +int zfs_osx_proxy_get_osname(const char *bsdname, + char *osname, int len); +int zfs_osx_proxy_get_bsdname(const char *osname, + char *bsdname, int len); + + +void zfs_osx_proxy_remove(const char *osname); +int zfs_osx_proxy_create(const char *osname); + +#ifdef __cplusplus +} /* extern "C" */ + +/* Not C external */ +ZFSDataset * zfs_osx_proxy_get(const char *osname); + +class ZFSDatasetScheme : public IOPartitionScheme +{ + OSDeclareDefaultStructors(ZFSDatasetScheme); +public: + + virtual void free(void) override; + virtual bool init(OSDictionary *properties) override; + virtual bool start(IOService *provider) override; + virtual IOService *probe(IOService *provider, SInt32 *score) override; + + bool addDataset(const char *osname); + bool removeDataset(const char *osname, bool force); + + /* Compatibility shims */ + virtual void read(IOService *client, + UInt64 byteStart, + IOMemoryDescriptor *buffer, + IOStorageAttributes *attributes, + IOStorageCompletion *completion) override; + + virtual void write(IOService *client, + UInt64 byteStart, + IOMemoryDescriptor *buffer, + IOStorageAttributes *attributes, + IOStorageCompletion *completion) override; + +#if defined(MAC_OS_X_VERSION_10_11) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) + virtual IOReturn synchronize(IOService *client, + UInt64 byteStart, + UInt64 byteCount, + IOStorageSynchronizeOptions options = 0) override; +#else + virtual IOReturn synchronizeCache(IOService *client) override; +#endif + + virtual IOReturn unmap(IOService *client, + IOStorageExtent *extents, + UInt32 extentsCount, +#if defined(MAC_OS_X_VERSION_10_11) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) + IOStorageUnmapOptions options = 0) override; +#else + UInt32 options = 0) override; +#endif + + virtual bool lockPhysicalExtents(IOService *client) override; + + virtual IOStorage *copyPhysicalExtent(IOService *client, + UInt64 * byteStart, + UInt64 * byteCount) override; + + virtual void unlockPhysicalExtents(IOService *client) override; + +#if defined(MAC_OS_X_VERSION_10_10) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) + virtual IOReturn setPriority(IOService *client, + IOStorageExtent *extents, + UInt32 extentsCount, + IOStoragePriority priority) override; +#endif + +protected: +private: + OSSet *_datasets; + OSOrderedSet *_holes; + uint64_t _max_id; + + uint32_t getNextPartitionID(); + void returnPartitionID(uint32_t part_id); +}; + +#endif /* __cplusplus */ +#endif /* ZFSDATASETSCHEME_H_INCLUDED */ diff --git a/include/os/macos/zfs/sys/ZFSPool.h b/include/os/macos/zfs/sys/ZFSPool.h new file mode 100644 index 000000000000..bcb415aa79ba --- /dev/null +++ b/include/os/macos/zfs/sys/ZFSPool.h @@ -0,0 +1,127 @@ +/* + * 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) 2016, Evan Susarret. All rights reserved. + */ + +#ifndef ZFSPOOL_H_INCLUDED +#define ZFSPOOL_H_INCLUDED + +#ifdef __cplusplus +#include + +#pragma mark - ZFSPool + +#define kZFSPoolNameKey "ZFS Pool Name" +#define kZFSPoolSizeKey "ZFS Pool Size" +#define kZFSPoolGUIDKey "ZFS Pool GUID" +#define kZFSPoolReadOnlyKey "ZFS Pool Read-Only" + +typedef struct spa spa_t; + +class ZFSPool : public IOService { + OSDeclareDefaultStructors(ZFSPool); + +protected: +#if 0 + /* XXX Only for debug tracing */ + virtual bool open(IOService *client, + IOOptionBits options, void *arg = 0); + virtual bool isOpen(const IOService *forClient = 0) const; + virtual void close(IOService *client, + IOOptionBits options); +#endif + + bool setPoolName(const char *name); + + virtual bool handleOpen(IOService *client, + IOOptionBits options, void *arg) override; + virtual bool handleIsOpen(const IOService *client) const override; + virtual void handleClose(IOService *client, + IOOptionBits options) override; + + virtual bool init(OSDictionary *properties, spa_t *spa); + virtual void free() override; + +#if 0 + /* IOBlockStorageDevice */ + virtual IOReturn doSynchronizeCache(void); + virtual IOReturn doAsyncReadWrite(IOMemoryDescriptor *, + UInt64, UInt64, IOStorageAttributes *, + IOStorageCompletion *); + virtual UInt32 doGetFormatCapacities(UInt64 *, + UInt32) const; + virtual IOReturn doFormatMedia(UInt64 byteCapacity); + virtual IOReturn doEjectMedia(); + virtual char *getVendorString(); + virtual char *getProductString(); + virtual char *getRevisionString(); + virtual char *getAdditionalDeviceInfoString(); + virtual IOReturn reportWriteProtection(bool *); + virtual IOReturn reportRemovability(bool *); + virtual IOReturn reportMediaState(bool *, bool *); + virtual IOReturn reportBlockSize(UInt64 *); + virtual IOReturn reportEjectability(bool *); + virtual IOReturn reportMaxValidBlock(UInt64 *); + +public: + virtual void read(IOService *client, UInt64 byteStart, + IOMemoryDescriptor *buffer, IOStorageAttributes *attr, + IOStorageCompletion *completion) override; + virtual void write(IOService *client, UInt64 byteStart, + IOMemoryDescriptor *buffer, IOStorageAttributes *attr, + IOStorageCompletion *completion) override; +#endif +public: + static ZFSPool * withProviderAndPool(IOService *, spa_t *); + +private: + OSSet *_openClients; + spa_t *_spa; + +#if 0 + /* These are declared class static to share across instances */ + static const char *vendorString; + static const char *revisionString; + static const char *infoString; + /* These are per-instance */ + const char *productString; + bool isReadOnly; +#endif +}; + +/* C++ wrapper, C uses opaque pointer reference */ +typedef struct spa_iokit { + ZFSPool *proxy; +} spa_iokit_t; + +extern "C" { +#endif /* __cplusplus */ + +/* C functions */ +void spa_iokit_pool_proxy_destroy(spa_t *spa); +int spa_iokit_pool_proxy_create(spa_t *spa); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ZFSPOOL_H_INCLUDED */ diff --git a/include/os/macos/zfs/sys/finderinfo.h b/include/os/macos/zfs/sys/finderinfo.h new file mode 100644 index 000000000000..ee3b48017bf5 --- /dev/null +++ b/include/os/macos/zfs/sys/finderinfo.h @@ -0,0 +1,36 @@ +#ifndef FINDERINFO_H +#define FINDERINFO_H + + +struct FndrExtendedDirInfo { + u_int32_t document_id; + u_int32_t date_added; + u_int16_t extended_flags; + u_int16_t reserved3; + u_int32_t write_gen_counter; +} __attribute__((aligned(2), packed)); + +struct FndrExtendedFileInfo { + u_int32_t document_id; + u_int32_t date_added; + u_int16_t extended_flags; + u_int16_t reserved2; + u_int32_t write_gen_counter; +} __attribute__((aligned(2), packed)); + +/* Finder information */ +struct FndrFileInfo { + u_int32_t fdType; + u_int32_t fdCreator; + u_int16_t fdFlags; + struct { + int16_t v; + int16_t h; + } fdLocation; + int16_t opaque; +} __attribute__((aligned(2), packed)); +typedef struct FndrFileInfo FndrFileInfo; + + + +#endif diff --git a/include/os/macos/zfs/sys/hfs_internal.h b/include/os/macos/zfs/sys/hfs_internal.h new file mode 100644 index 000000000000..ea2a8ad9bafa --- /dev/null +++ b/include/os/macos/zfs/sys/hfs_internal.h @@ -0,0 +1,191 @@ + +#ifndef HFS_INTERNAL_H +#define HFS_INTERNAL_H + +// BGH - Definitions of HFS vnops that we will need to emulate +// including supporting structures. + +struct hfs_journal_info { + off_t jstart; + off_t jsize; +}; + +struct user32_access_t { + uid_t uid; + short flags; + short num_groups; + int num_files; + user32_addr_t file_ids; + user32_addr_t groups; + user32_addr_t access; +}; + +struct user64_access_t { + uid_t uid; + short flags; + short num_groups; + int num_files; + user64_addr_t file_ids; + user64_addr_t groups; + user64_addr_t access; +}; + +struct user32_ext_access_t { + uint32_t flags; + uint32_t num_files; + uint32_t map_size; + user32_addr_t file_ids; + user32_addr_t bitmap; + user32_addr_t access; + uint32_t num_parents; + user32_addr_t parents; +}; + +struct user64_ext_access_t { + uint32_t flags; + uint32_t num_files; + uint32_t map_size; + user64_addr_t file_ids; + user64_addr_t bitmap; + user64_addr_t access; + uint32_t num_parents; + user64_addr_t parents; +}; + +/* + * HFS specific fcntl()'s + */ +#define HFS_BULKACCESS (FCNTL_FS_SPECIFIC_BASE + 0x00001) +#define HFS_GET_MOUNT_TIME (FCNTL_FS_SPECIFIC_BASE + 0x00002) +#define HFS_GET_LAST_MTIME (FCNTL_FS_SPECIFIC_BASE + 0x00003) +#define HFS_GET_BOOT_INFO (FCNTL_FS_SPECIFIC_BASE + 0x00004) +#define HFS_SET_BOOT_INFO (FCNTL_FS_SPECIFIC_BASE + 0x00005) + +/* HFS FS CONTROL COMMANDS */ + +#define HFSIOC_RESIZE_PROGRESS _IOR('h', 1, u_int32_t) +#define HFS_RESIZE_PROGRESS IOCBASECMD(HFSIOC_RESIZE_PROGRESS) + +#define HFSIOC_RESIZE_VOLUME _IOW('h', 2, u_int64_t) +#define HFS_RESIZE_VOLUME IOCBASECMD(HFSIOC_RESIZE_VOLUME) + +#define HFSIOC_CHANGE_NEXT_ALLOCATION _IOWR('h', 3, u_int32_t) +#define HFS_CHANGE_NEXT_ALLOCATION IOCBASECMD(HFSIOC_CHANGE_NEXT_ALLOCATION) +/* + * Magic value for next allocation to use with fcntl to set next allocation + * to zero and never update it again on new block allocation. + */ +#define HFS_NO_UPDATE_NEXT_ALLOCATION 0xffffFFFF + +#define HFSIOC_GETCREATETIME _IOR('h', 4, time_t) +#define HFS_GETCREATETIME IOCBASECMD(HFSIOC_GETCREATETIME) + +#define HFSIOC_SETBACKINGSTOREINFO _IOW('h', 7, struct hfs_backingstoreinfo) +#define HFS_SETBACKINGSTOREINFO IOCBASECMD(HFSIOC_SETBACKINGSTOREINFO) + +#define HFSIOC_CLRBACKINGSTOREINFO _IO('h', 8) +#define HFS_CLRBACKINGSTOREINFO IOCBASECMD(HFSIOC_CLRBACKINGSTOREINFO) + +#define HFSIOC_BULKACCESS _IOW('h', 9, struct user32_access_t) +#define HFS_BULKACCESS_FSCTL IOCBASECMD(HFSIOC_BULKACCESS) + +#define HFSIOC_SETACLSTATE _IOW('h', 10, int32_t) +#define HFS_SETACLSTATE IOCBASECMD(HFSIOC_SETACLSTATE) + +#define HFSIOC_PREV_LINK _IOWR('h', 11, u_int32_t) +#define HFS_PREV_LINK IOCBASECMD(HFSIOC_PREV_LINK) + +#define HFSIOC_NEXT_LINK _IOWR('h', 12, u_int32_t) +#define HFS_NEXT_LINK IOCBASECMD(HFSIOC_NEXT_LINK) + +#define HFSIOC_GETPATH _IOWR('h', 13, pathname_t) +#define HFS_GETPATH IOCBASECMD(HFSIOC_GETPATH) +#define HFS_GETPATH_VOLUME_RELATIVE 0x1 + +/* This define is deemed secret by Apple */ +#define BUILDPATH_VOLUME_RELATIVE 0x8 + +/* Enable/disable extent-based extended attributes */ +#define HFSIOC_SET_XATTREXTENTS_STATE _IOW('h', 14, u_int32_t) +#define HFS_SET_XATTREXTENTS_STATE IOCBASECMD(HFSIOC_SET_XATTREXTENTS_STATE) + +#define HFSIOC_EXT_BULKACCESS _IOW('h', 15, struct user32_ext_access_t) +#define HFS_EXT_BULKACCESS_FSCTL IOCBASECMD(HFSIOC_EXT_BULKACCESS) + +#define HFSIOC_MARK_BOOT_CORRUPT _IO('h', 16) +#define HFS_MARK_BOOT_CORRUPT IOCBASECMD(HFSIOC_MARK_BOOT_CORRUPT) + +#define HFSIOC_GET_JOURNAL_INFO _IOR('h', 17, struct hfs_journal_info) +#define HFS_FSCTL_GET_JOURNAL_INFO IOCBASECMD(HFSIOC_GET_JOURNAL_INFO) + +#define HFSIOC_SET_VERY_LOW_DISK _IOW('h', 20, u_int32_t) +#define HFS_FSCTL_SET_VERY_LOW_DISK IOCBASECMD(HFSIOC_SET_VERY_LOW_DISK) + +#define HFSIOC_SET_LOW_DISK _IOW('h', 21, u_int32_t) +#define HFS_FSCTL_SET_LOW_DISK IOCBASECMD(HFSIOC_SET_LOW_DISK) + +#define HFSIOC_SET_DESIRED_DISK _IOW('h', 22, u_int32_t) +#define HFS_FSCTL_SET_DESIRED_DISK IOCBASECMD(HFSIOC_SET_DESIRED_DISK) + +#define HFSIOC_SET_ALWAYS_ZEROFILL _IOW('h', 23, int32_t) +#define HFS_SET_ALWAYS_ZEROFILL IOCBASECMD(HFSIOC_SET_ALWAYS_ZEROFILL) + +#define HFSIOC_VOLUME_STATUS _IOR('h', 24, u_int32_t) +#define HFS_VOLUME_STATUS IOCBASECMD(HFSIOC_VOLUME_STATUS) + +/* Disable metadata zone for given volume */ +#define HFSIOC_DISABLE_METAZONE _IO('h', 25) +#define HFS_DISABLE_METAZONE IOCBASECMD(HFSIOC_DISABLE_METAZONE) + +/* Change the next CNID value */ +#define HFSIOC_CHANGE_NEXTCNID _IOWR('h', 26, u_int32_t) +#define HFS_CHANGE_NEXTCNID IOCBASECMD(HFSIOC_CHANGE_NEXTCNID) + +/* Get the low disk space values */ +#define HFSIOC_GET_VERY_LOW_DISK _IOR('h', 27, u_int32_t) +#define HFS_FSCTL_GET_VERY_LOW_DISK IOCBASECMD(HFSIOC_GET_VERY_LOW_DISK) + +#define HFSIOC_GET_LOW_DISK _IOR('h', 28, u_int32_t) +#define HFS_FSCTL_GET_LOW_DISK IOCBASECMD(HFSIOC_GET_LOW_DISK) + +#define HFSIOC_GET_DESIRED_DISK _IOR('h', 29, u_int32_t) +#define HFS_FSCTL_GET_DESIRED_DISK IOCBASECMD(HFSIOC_GET_DESIRED_DISK) + +/* + * revisiond only uses this when something transforms in a way + * the kernel can't track such as "foo.rtf" -> "foo.rtfd" + */ +#define HFSIOC_TRANSFER_DOCUMENT_ID _IOW('h', 32, u_int32_t) +#define HFS_TRANSFER_DOCUMENT_ID IOCBASECMD(HFSIOC_TRANSFER_DOCUMENT_ID) + + +/* fcntl.h */ +#define F_MAKECOMPRESSED 80 + +/* Get file system information for the given volume */ +// #define HFSIOC_GET_FSINFO _IOWR('h', 45, hfs_fsinfo) +// #define HFS_GET_FSINFO IOCBASECMD(HFSIOC_GET_FSINFO) + +/* Re-pin hotfile data; argument controls what state gets repinned */ +#define HFSIOC_REPIN_HOTFILE_STATE _IOWR('h', 46, u_int32_t) +#define HFS_REPIN_HOTFILE_STATE IOCBASECMD(HFSIOC_REPIN_HOTFILE_STATE) + +/* Mark a directory or file as worth caching on any underlying "fast" device */ +#define HFSIOC_SET_HOTFILE_STATE _IOWR('h', 47, u_int32_t) +#define HFS_SET_HOTFILE_STATE IOCBASECMD(HFSIOC_SET_HOTFILE_STATE) + +#define APFSIOC_SET_NEAR_LOW_DISK _IOW('J', 17, u_int32_t) +#define APFSIOC_GET_NEAR_LOW_DISK _IOR('J', 18, u_int32_t) + +#ifndef FSIOC_FIOSEEKHOLE +#define FSIOC_FIOSEEKHOLE _IOWR('A', 16, off_t) +#define FSCTL_FIOSEEKHOLE IOCBASECMD(FSIOC_FIOSEEKHOLE) +#endif +#ifndef FSIOC_FIOSEEKDATA +#define FSIOC_FIOSEEKDATA _IOWR('A', 17, off_t) +#define FSCTL_FIOSEEKDATA IOCBASECMD(FSIOC_FIOSEEKDATA) +#endif + +// END of definitions + +#endif diff --git a/include/os/macos/zfs/sys/ldi_buf.h b/include/os/macos/zfs/sys/ldi_buf.h new file mode 100644 index 000000000000..1e94a71b0947 --- /dev/null +++ b/include/os/macos/zfs/sys/ldi_buf.h @@ -0,0 +1,88 @@ +/* + * 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) 2015, Evan Susarret. All rights reserved. + * + * OS X implementation of ldi_ named functions for ZFS written by + * Evan Susarret in 2015. + */ + +#ifndef _SYS_LDI_BUF_H +#define _SYS_LDI_BUF_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Can not include C++ header in C, so we make space for it here. + * We check it is enough space with CTASSERT in ldi_iokit.cpp. + * If we one day compile everything with C++ we can embed IOStorageCompletion + * directly here. + */ +struct opaque_iocompletion { + void *space[3]; +}; + +/* + * Buffer context for LDI strategy + */ +typedef struct ldi_buf { + /* For client use */ + int (*b_iodone)(struct ldi_buf *); /* Callback */ + union { + void *b_addr; /* Passed buffer address */ + } b_un; /* Union to match illumos */ + uint64_t b_bcount; /* Size of IO */ + uint64_t b_bufsize; /* Size of buffer */ + uint64_t b_lblkno; /* logical block number */ + uint64_t b_resid; /* Remaining IO size */ + int b_flags; /* Read or write, options */ + int b_error; /* IO error code */ + void *b_private; /* caller own ptr */ + struct opaque_iocompletion b_completion; +} ldi_buf_t; /* XXX Currently 64b */ + +ldi_buf_t *ldi_getrbuf(int); +void ldi_freerbuf(ldi_buf_t *); +void ldi_bioinit(ldi_buf_t *); + +/* Define macros to get and release a buffer */ +#define getrbuf(flags) ldi_getrbuf(flags) +#define freerbuf(lbp) ldi_freerbuf(lbp) +#define bioinit(lbp) ldi_bioinit(lbp) +#define geterror(lbp) (lbp->b_error) +#define biowait(lbp) (0) + +#define lbtodb(bytes) \ + (bytes >> DEV_BSHIFT) +#define dbtolb(blkno) \ + (blkno << DEV_BSHIFT) +#define ldbtob(blkno) dbtolb(blkno) + +/* Redefine B_BUSY */ +#define B_BUSY B_PHYS + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* _SYS_LDI_BUF_H */ diff --git a/include/os/macos/zfs/sys/ldi_impl_osx.h b/include/os/macos/zfs/sys/ldi_impl_osx.h new file mode 100644 index 000000000000..68c8d121ab15 --- /dev/null +++ b/include/os/macos/zfs/sys/ldi_impl_osx.h @@ -0,0 +1,226 @@ +/* + * 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) 1994, 2010, Oracle and/or its affiliates. All rights reserved. + */ +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +/* + * Copyright (c) 2013, Joyent, Inc. All rights reserved. + */ +/* + * Copyright (c) 2015, Evan Susarret. All rights reserved. + */ +/* + * Portions of this document are copyright Oracle and Joyent. + * OS X implementation of ldi_ named functions for ZFS written by + * Evan Susarret in 2015. + */ + +#ifndef _SYS_LDI_IMPL_OSX_H +#define _SYS_LDI_IMPL_OSX_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * OS X + */ +#define LDI_TYPE_INVALID 0x0 /* uninitialized */ +#define LDI_TYPE_IOKIT 0x1 /* IOMedia device */ +#define LDI_TYPE_VNODE 0x2 /* vnode (bdev) device */ + +/* + * OS X + */ +#define LDI_STATUS_OFFLINE 0x0 /* device offline (dead-end) */ +#define LDI_STATUS_CLOSED 0x1 /* just initialized or closed */ +#define LDI_STATUS_CLOSING 0x2 /* close in-progress */ +#define LDI_STATUS_OPENING 0x3 /* open in-progress */ +#define LDI_STATUS_ONLINE 0x4 /* device is open and active */ +typedef uint_t ldi_status_t; + +/* + * LDI hash definitions + */ +#define LH_HASH_SZ 32 /* number of hash lists */ + +/* + * Flag for LDI handle's lh_flags field + */ +#define LH_FLAGS_NOTIFY 0x0001 /* invoked in context of a notify */ + + +/* + * LDI handle (OS X) + */ +typedef struct _handle_iokit *handle_iokit_t; +typedef struct _handle_vnode *handle_vnode_t; +typedef struct _handle_notifier *handle_notifier_t; + +struct ldi_handle { + /* protected by ldi_handle_hash_lock */ + list_node_t lh_node; /* list membership */ + uint_t lh_ref; /* active references */ + uint_t lh_flags; /* for notify event */ + + /* protected by handle lh_lock */ + kmutex_t lh_lock; /* internal lock */ + kcondvar_t lh_cv; /* for concurrent open */ + ldi_status_t lh_status; /* Closed, Offline, Online */ + uint_t lh_openref; /* open client count */ + + /* unique/static fields in the handle */ + union ldi_handle_tsd { + handle_iokit_t iokit_tsd; + handle_vnode_t vnode_tsd; + } lh_tsd; /* union */ + handle_notifier_t lh_notifier; /* pointer */ + uint_t lh_type; /* IOKit or vnode */ + uint_t lh_fmode; /* FREAD | FWRITE */ + dev_t lh_dev; /* device number */ + uint_t pad; /* pad to 96 bytes */ +}; /* XXX Currently 96b */ + +/* Shared functions */ +struct ldi_handle *handle_alloc_common(uint_t, dev_t, int); +struct ldi_handle *handle_find(dev_t, int, boolean_t); +struct ldi_handle *handle_add(struct ldi_handle *); +int handle_status_change(struct ldi_handle *, int); +void handle_hold(struct ldi_handle *); +void handle_release(struct ldi_handle *); +ldi_status_t handle_open_start(struct ldi_handle *); +void handle_open_done(struct ldi_handle *, ldi_status_t); + +/* Handle IOKit functions */ +void handle_free_iokit(struct ldi_handle *); +struct ldi_handle *handle_alloc_iokit(dev_t, int); +int handle_register_notifier(struct ldi_handle *); +int handle_close_iokit(struct ldi_handle *); +int handle_free_ioservice(struct ldi_handle *); +int handle_alloc_ioservice(struct ldi_handle *); +int handle_remove_notifier(struct ldi_handle *); +int handle_set_wce_iokit(struct ldi_handle *, int *); +int handle_get_size_iokit(struct ldi_handle *, uint64_t *); +int handle_get_dev_path_iokit(struct ldi_handle *lh, + char *path, int len); +int handle_get_media_info_iokit(struct ldi_handle *, + struct dk_minfo *); +int handle_get_media_info_ext_iokit(struct ldi_handle *, + struct dk_minfo_ext *); +int handle_check_media_iokit(struct ldi_handle *, int *); +int handle_is_solidstate_iokit(struct ldi_handle *, int *); +int handle_sync_iokit(struct ldi_handle *); +int buf_strategy_iokit(ldi_buf_t *, struct ldi_handle *); +int ldi_open_media_by_dev(dev_t, int, ldi_handle_t *); +int ldi_open_media_by_path(char *, int, ldi_handle_t *); +int handle_get_bootinfo_iokit(struct ldi_handle *, + struct io_bootinfo *); +int handle_features_iokit(struct ldi_handle *, + uint32_t *); +int handle_unmap_iokit(struct ldi_handle *, + dkioc_free_list_ext_t *); + +/* Handle vnode functions */ +dev_t dev_from_path(char *); +void handle_free_vnode(struct ldi_handle *); +struct ldi_handle *handle_alloc_vnode(dev_t, int); +int handle_close_vnode(struct ldi_handle *); +int handle_get_size_vnode(struct ldi_handle *, uint64_t *); +int handle_get_dev_path_vnode(struct ldi_handle *lh, + char *path, int len); +int handle_get_media_info_vnode(struct ldi_handle *, + struct dk_minfo *); +int handle_get_media_info_ext_vnode(struct ldi_handle *, + struct dk_minfo_ext *); +int handle_check_media_vnode(struct ldi_handle *, int *); +int handle_is_solidstate_vnode(struct ldi_handle *, int *); +int handle_sync_vnode(struct ldi_handle *); +int buf_strategy_vnode(ldi_buf_t *, struct ldi_handle *); +int ldi_open_vnode_by_path(char *, dev_t, int, ldi_handle_t *); +int handle_get_bootinfo_vnode(struct ldi_handle *, + struct io_bootinfo *); +int handle_features_vnode(struct ldi_handle *, + uint32_t *); +int handle_unmap_vnode(struct ldi_handle *, + dkioc_free_list_ext_t *); + +/* + * LDI event information + */ +typedef struct ldi_ev_callback_impl { + struct ldi_handle *lec_lhp; +#ifdef illumos + dev_info_t *lec_dip; +#endif + dev_t lec_dev; + int lec_spec; + int (*lec_notify)(ldi_handle_t, ldi_ev_cookie_t, void *, void *); + void (*lec_finalize)(ldi_handle_t, ldi_ev_cookie_t, int, + void *, void *); + void *lec_arg; + void *lec_cookie; + void *lec_id; + list_node_t lec_list; +} ldi_ev_callback_impl_t; /* XXX Currently 72b */ + +/* + * Members of "struct ldi_ev_callback_list" are protected by their le_lock + * member. The struct is currently only used once, as a file-level global, + * and the locking protocol is currently implemented in ldi_ev_lock() and + * ldi_ev_unlock(). + * + * When delivering events to subscribers, ldi_invoke_notify() and + * ldi_invoke_finalize() will walk the list of callbacks: le_head. It is + * possible that an invoked callback function will need to unregister an + * arbitrary number of callbacks from this list. + * + * To enable ldi_ev_remove_callbacks() to remove elements from the list + * without breaking the walk-in-progress, we store the next element in the + * walk direction on the struct as le_walker_next and le_walker_prev. + */ +struct ldi_ev_callback_list { + kmutex_t le_lock; + kcondvar_t le_cv; + uint64_t le_busy; + void *le_thread; + list_t le_head; + ldi_ev_callback_impl_t *le_walker_next; + ldi_ev_callback_impl_t *le_walker_prev; +}; /* XXX Currently 96b, but only used once */ + +int ldi_invoke_notify(dev_info_t *, dev_t, int, char *, void *); +void ldi_invoke_finalize(dev_info_t *, dev_t, int, char *, int, void *); +int e_ddi_offline_notify(dev_info_t *); +void e_ddi_offline_finalize(dev_info_t *, int); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* _SYS_LDI_IMPL_OSX_H */ diff --git a/include/os/macos/zfs/sys/ldi_osx.h b/include/os/macos/zfs/sys/ldi_osx.h new file mode 100644 index 000000000000..2d78017c4245 --- /dev/null +++ b/include/os/macos/zfs/sys/ldi_osx.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 (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) 1994, 2010, Oracle and/or its affiliates. All rights reserved. + */ +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +/* + * Copyright (c) 2013, Joyent, Inc. All rights reserved. + */ +/* + * Copyright (c) 2015, Evan Susarret. All rights reserved. + */ +/* + * Portions of this document are copyright Oracle and Joyent. + * OS X implementation of ldi_ named functions for ZFS written by + * Evan Susarret in 2015. + */ + +#ifndef _SYS_LDI_OSX_H +#define _SYS_LDI_OSX_H + +#include + +/* + * OS X - The initialization/destructor functions are available + * for zfs-osx.cpp to call during zfs_init/zfs_fini. + */ +#ifdef __cplusplus +extern "C" { + +int ldi_init(void *); /* passes IOService provider */ +void ldi_fini(); /* teardown */ +#endif /* __cplusplus */ + +/* + * Opaque layered driver data structures. + * vdev_disk and other C callers may use these LDI interfaces + * ldi_ident_t is already defined as typedef void* by spl sunddi.h + */ +typedef struct __ldi_handle *ldi_handle_t; +typedef struct __ldi_callback_id *ldi_callback_id_t; +typedef struct __ldi_ev_cookie *ldi_ev_cookie_t; + +/* + * LDI event interface related + */ +#define LDI_EV_SUCCESS 0 +#define LDI_EV_FAILURE (-1) +#define LDI_EV_NONE (-2) /* no matching callbacks registered */ +#define LDI_EV_OFFLINE "LDI:EVENT:OFFLINE" +#define LDI_EV_DEGRADE "LDI:EVENT:DEGRADE" +#define LDI_EV_DEVICE_REMOVE "LDI:EVENT:DEVICE_REMOVE" + +#define LDI_EV_CB_VERS_1 1 +#define LDI_EV_CB_VERS LDI_EV_CB_VERS_1 + +typedef struct ldi_ev_callback { + uint_t cb_vers; + int (*cb_notify)(ldi_handle_t, ldi_ev_cookie_t, void *, void *); + void (*cb_finalize)(ldi_handle_t, ldi_ev_cookie_t, int, + void *, void *); +} ldi_ev_callback_t; + +/* Structs passed to media_get_info */ +struct dk_minfo { + uint32_t dki_capacity; /* Logical block count */ + uint32_t dki_lbsize; /* Logical block size */ +}; /* (8b) */ + +struct dk_minfo_ext { + uint64_t dki_capacity; /* Logical block count */ + uint32_t dki_lbsize; /* Logical block size */ + uint32_t dki_pbsize; /* Physical block size */ +}; /* (16b) */ + +struct io_bootinfo { + char dev_path[MAXPATHLEN]; /* IODeviceTree path */ + uint64_t dev_size; /* IOMedia device size */ +}; + +/* + * XXX This struct is defined in spl but was unused until now. + * There is a reference in zvol.c zvol_ioctl, commented out. + */ +#if 0 +struct dk_callback { + void (*dkc_callback)(void *dkc_cookie, int error); + void *dkc_cookie; + int dkc_flag; +}; /* XXX Currently 20b */ +#endif + +/* XXX Already defined in spl dkio.h (used elsewhere) */ +#if 0 +#define DKIOCFLUSHWRITECACHE (DKIOC | 34) +#endif + +#define FLUSH_VOLATILE 0x1 +#define DKIOCGMEDIAINFOEXT (DKIOC | 48) + +/* XXX Created this additional ioctl */ +#define DKIOCGETBOOTINFO (DKIOC | 99) + +/* + * LDI Handle manipulation functions + */ +int ldi_open_by_dev(dev_t, int, int, cred_t *, + ldi_handle_t *, __unused ldi_ident_t); +int ldi_open_by_name(char *, int, cred_t *, + ldi_handle_t *, __unused ldi_ident_t); + +int ldi_close(ldi_handle_t, int, cred_t *); + +int ldi_sync(ldi_handle_t); +int ldi_get_size(ldi_handle_t, uint64_t *); +int ldi_ioctl(ldi_handle_t, int, intptr_t, int, cred_t *, int *); +int ldi_strategy(ldi_handle_t, ldi_buf_t *); + +/* + * LDI events related declarations + */ +extern int ldi_ev_get_cookie(ldi_handle_t, char *, ldi_ev_cookie_t *); +extern char *ldi_ev_get_type(ldi_ev_cookie_t); +extern int ldi_ev_register_callbacks(ldi_handle_t, ldi_ev_cookie_t, + ldi_ev_callback_t *, void *, ldi_callback_id_t *); +extern int ldi_ev_remove_callbacks(ldi_callback_id_t); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* _SYS_LDI_OSX_H */ diff --git a/include/os/macos/zfs/sys/trace_zfs.h b/include/os/macos/zfs/sys/trace_zfs.h new file mode 100644 index 000000000000..f32ba529ecd1 --- /dev/null +++ b/include/os/macos/zfs/sys/trace_zfs.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 (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 + */ + +#if defined(_KERNEL) && defined(HAVE_DECLARE_EVENT_CLASS) + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM zfs + +#if !defined(_TRACE_ZFS_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_ZFS_H + +#include +#include + +/* + * The sys/trace_dbgmsg.h header defines tracepoint events for + * dprintf(), dbgmsg(), and SET_ERROR(). + */ +#define _SYS_TRACE_DBGMSG_INDIRECT +#include +#undef _SYS_TRACE_DBGMSG_INDIRECT + +/* + * Redefine the DTRACE_PROBE* functions to use Linux tracepoints + */ +#undef DTRACE_PROBE1 +#define DTRACE_PROBE1(name, t1, arg1) \ + trace_zfs_##name((arg1)) + +#undef DTRACE_PROBE2 +#define DTRACE_PROBE2(name, t1, arg1, t2, arg2) \ + trace_zfs_##name((arg1), (arg2)) + +#undef DTRACE_PROBE3 +#define DTRACE_PROBE3(name, t1, arg1, t2, arg2, t3, arg3) \ + trace_zfs_##name((arg1), (arg2), (arg3)) + +#undef DTRACE_PROBE4 +#define DTRACE_PROBE4(name, t1, arg1, t2, arg2, t3, arg3, t4, arg4) \ + trace_zfs_##name((arg1), (arg2), (arg3), (arg4)) + +#endif /* _TRACE_ZFS_H */ + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH sys +#define TRACE_INCLUDE_FILE trace +#include + +#endif /* _KERNEL && HAVE_DECLARE_EVENT_CLASS */ diff --git a/include/os/macos/zfs/sys/vdev_disk_os.h b/include/os/macos/zfs/sys/vdev_disk_os.h new file mode 100644 index 000000000000..d084b9072298 --- /dev/null +++ b/include/os/macos/zfs/sys/vdev_disk_os.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 + */ +#ifndef _ZFS_VDEV_DISK_OS_H +#define _ZFS_VDEV_DISK_OS_H + +#include + +typedef struct vdev_disk { + ldi_handle_t vd_lh; + list_t vd_ldi_cbs; + boolean_t vd_ldi_offline; +} vdev_disk_t; + +extern int vdev_disk_ldi_physio(ldi_handle_t, caddr_t, size_t, uint64_t, int); + +#endif diff --git a/include/os/macos/zfs/sys/zfs_boot.h b/include/os/macos/zfs/sys/zfs_boot.h new file mode 100644 index 000000000000..cad5c0bdfd90 --- /dev/null +++ b/include/os/macos/zfs/sys/zfs_boot.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 (c) 2016, Evan Susarret. All rights reserved. + */ + +#ifndef ZFS_BOOT_H_INCLUDED +#define ZFS_BOOT_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Link data vdevs to virtual devices */ +int zfs_boot_update_bootinfo(spa_t *spa); + +int zfs_attach_devicedisk(zfsvfs_t *zfsvfs); +int zfs_detach_devicedisk(zfsvfs_t *zfsvfs); +int zfs_devdisk_get_path(void *, char *, int); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + + + +#ifdef __cplusplus +#include +bool zfs_boot_init(IOService *); +void zfs_boot_fini(); +#endif /* __cplusplus */ + + +#endif /* ZFS_BOOT_H_INCLUDED */ diff --git a/include/os/macos/zfs/sys/zfs_bootenv_os.h b/include/os/macos/zfs/sys/zfs_bootenv_os.h new file mode 100644 index 000000000000..77b8b8ecd9ea --- /dev/null +++ b/include/os/macos/zfs/sys/zfs_bootenv_os.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#ifndef _ZFS_BOOTENV_OS_H +#define _ZFS_BOOTENV_OS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define BOOTENV_OS BE_MACOS_VENDOR + +#ifdef __cplusplus +} +#endif + +#endif /* _ZFS_BOOTENV_OS_H */ diff --git a/include/os/macos/zfs/sys/zfs_context_os.h b/include/os/macos/zfs/sys/zfs_context_os.h new file mode 100644 index 000000000000..dc7bac352a7a --- /dev/null +++ b/include/os/macos/zfs/sys/zfs_context_os.h @@ -0,0 +1,211 @@ +/* + * 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 _SPL_ZFS_CONTEXT_OS_H +#define _SPL_ZFS_CONTEXT_OS_H + +#include +#include +#include +#include + +#include + +#define ZIO_OS_FIELDS \ + struct { \ + ldi_buf_t zm_buf; \ + } macos; + + +#define MSEC_TO_TICK(msec) ((msec) / (MILLISEC / hz)) + +#define KMALLOC_MAX_SIZE MAXPHYS + +#ifndef MAX_UPL_TRANSFER +#define MAX_UPL_TRANSFER 256 +#endif + +#define flock64_t struct flock + +/* + * XNU reserves fileID 1-15, so we remap them high. + * 2 is root-of-the-mount. + * If ID is same as root, return 2. Otherwise, if it is 0-15, return + * adjusted, otherwise, return as-is. + * See hfs_format.h: kHFSRootFolderID, kHFSExtentsFileID, ... + */ +#define INO_ROOT 2ULL +#define INO_RESERVED 16ULL /* [0-15] reserved. */ +#define INO_ISRESERVED(ID) ((ID) < (INO_RESERVED)) +/* 0xFFFFFFFFFFFFFFF0 */ +#define INO_MAP ((uint64_t)-INO_RESERVED) /* -16, -15, .., -1 */ + +#define INO_ZFSTOXNU(ID, ROOT) \ + ((ID) == (ROOT)?INO_ROOT:(INO_ISRESERVED(ID)?INO_MAP+(ID):(ID))) + +/* + * This macro relies on *unsigned*. + * If asking for 2, return rootID. If in special range, adjust to + * normal, otherwise, return as-is. + */ +#define INO_XNUTOZFS(ID, ROOT) \ + ((ID) == INO_ROOT)?(ROOT): \ + (INO_ISRESERVED((ID)-INO_MAP))?((ID)-INO_MAP):(ID) + +struct spa_iokit; +typedef struct spa_iokit spa_iokit_t; + +#define noinline __attribute__((noinline)) + +/* really? */ +#define kpreempt_disable() ((void)0) +#define kpreempt_enable() ((void)0) +#define cond_resched() (void)thread_block(THREAD_CONTINUE_NULL); +#define schedule() (void)thread_block(THREAD_CONTINUE_NULL); + +#define current curthread + +extern boolean_t ml_set_interrupts_enabled(boolean_t); + +/* Make sure kmem and vmem are already included */ +#include +#include + +/* + * We could add another field to zfs_cmd_t, but since we should be + * moving to the new-style ioctls, send and recv still hang on to old, + * we will just (ab)use a field not used on macOS. + * We use this field to keep userland's file offset pointer, and kernel + * fp_offset in sync, as we have no means to access "fp_offset" in XNU. + */ +#define zc_fd_offset zc_zoneid + +typedef int fstrans_cookie_t; +#define spl_fstrans_mark() (0) +#define spl_fstrans_unmark(x) (x = 0) + +#ifdef _KERNEL + +struct hlist_node { + struct hlist_node *next, **pprev; +}; + +struct hlist_head { + struct hlist_node *first; +}; + +typedef int atomic_t; + +#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x)) + +#define barrier() __asm__ __volatile__("": : :"memory") +#define smp_rmb() barrier() + +/* BEGIN CSTYLED */ +#define hlist_for_each(p, head) \ + for (p = (head)->first; p; p = (p)->next) + +#define hlist_entry(ptr, type, field) container_of(ptr, type, field) +/* END CSTYLED */ + +#define WRITE_ONCE_PTR(x, n) atomic_store( \ + os_cast_to_atomic_pointer(x), (n)) + +static inline void +hlist_add_head(struct hlist_node *n, struct hlist_head *h) +{ + n->next = h->first; + if (h->first != NULL) + h->first->pprev = &n->next; + WRITE_ONCE_PTR(&h->first, n); + n->pprev = &h->first; +} + +static inline void +hlist_del(struct hlist_node *n) +{ + WRITE_ONCE_PTR(n->pprev, n->next); + if (n->next != NULL) + n->next->pprev = n->pprev; +} + + +#define HLIST_HEAD_INIT { } +#define HLIST_HEAD(name) struct hlist_head name = HLIST_HEAD_INIT +#define INIT_HLIST_HEAD(head) (head)->first = NULL + +/* BEGIN CSTYLED */ +#define INIT_HLIST_NODE(node) \ + do { \ + (node)->next = NULL; \ + (node)->pprev = NULL; \ + } while (0) + +/* END CSTYLED */ + +static inline int +atomic_read(const atomic_t *v) +{ + barrier(); + return (atomic_load(os_cast_to_atomic_pointer(v))); +} + +static inline int +atomic_inc(atomic_t *v) +{ + return (1 + atomic_fetch_add(os_cast_to_atomic_pointer(v), 1)); +} + +static inline int +atomic_dec(atomic_t *v) +{ + return (-1 + atomic_fetch_sub(os_cast_to_atomic_pointer(v), 1)); +} + +extern void spl_qsort(void *array, size_t nm, size_t member_size, + int (*cmpf)(const void *, const void *)); +#define qsort spl_qsort + +#define strstr kmem_strstr + +#define task_io_account_read(n) +#define task_io_account_write(n) + +#ifndef SEEK_HOLE +#define SEEK_HOLE 3 +#endif + +#ifndef SEEK_DATA +#define SEEK_DATA 4 +#endif + +void sysctl_os_init(void); +void sysctl_os_fini(void); + +/* See rant in vdev_file.c */ +#define CLOSE_ON_UNMOUNT + +#ifndef MODULE_PARAM_MAX +#define MODULE_PARAM_MAX 1024 +#endif + +#endif // _KERNEL +#endif diff --git a/include/os/macos/zfs/sys/zfs_ctldir.h b/include/os/macos/zfs/sys/zfs_ctldir.h new file mode 100644 index 000000000000..d31d1cdd6045 --- /dev/null +++ b/include/os/macos/zfs/sys/zfs_ctldir.h @@ -0,0 +1,123 @@ +/* + * 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 Lawrence Livermore National Security, LLC. + * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). + * LLNL-CODE-403049. + * Rewritten for Linux by: + * Rohan Puri + * Brian Behlendorf + */ + +#ifndef _ZFS_CTLDIR_H +#define _ZFS_CTLDIR_H + +#include +#include +#include +#include + +#define ZFS_CTLDIR_NAME ".zfs" +#define ZFS_SNAPDIR_NAME "snapshot" +#define ZFS_SHAREDIR_NAME "shares" + +#define zfs_has_ctldir(zdp) \ + ((zdp)->z_id == ZTOZSB(zdp)->z_root && \ + (ZTOZSB(zdp)->z_ctldir != NULL)) +#define zfs_show_ctldir(zdp) \ + (zfs_has_ctldir(zdp) && \ + (ZTOZSB(zdp)->z_show_ctldir)) + +struct path; + +extern int zfs_expire_snapshot; + +/* zfsctl generic functions */ +extern int zfsctl_create(zfsvfs_t *); +extern void zfsctl_destroy(zfsvfs_t *); +extern struct vnode *zfsctl_root(znode_t *); +extern void zfsctl_init(void); +extern void zfsctl_fini(void); +extern boolean_t zfsctl_is_node(struct vnode *ip); +extern boolean_t zfsctl_is_snapdir(struct vnode *ip); +extern int zfsctl_fid(struct vnode *ip, fid_t *fidp); + +/* zfsctl '.zfs' functions */ +extern int zfsctl_root_lookup(struct vnode *dvp, char *name, + struct vnode **vpp, int flags, int *direntflags, + struct componentname *realpnp); + +/* zfsctl '.zfs/snapshot' functions */ +extern int zfsctl_snapdir_lookup(struct vnode *dip, char *name, + struct vnode **ipp, int flags, cred_t *cr, int *direntflags, + struct componentname *realpnp); +extern int zfsctl_snapdir_rename(struct vnode *sdip, char *sname, + struct vnode *tdip, char *tname, cred_t *cr, int flags); +extern int zfsctl_snapdir_remove(struct vnode *dip, char *name, cred_t *cr, + int flags); +extern int zfsctl_snapdir_mkdir(struct vnode *dip, char *dirname, vattr_t *vap, + struct vnode **ipp, cred_t *cr, int flags); +extern int zfsctl_snapshot_mount(struct vnode *, int flags); +extern int zfsctl_snapshot_unmount(const char *, int flags); +extern int zfsctl_snapshot_unmount_node(struct vnode *, const char *, + int flags); +extern int zfsctl_snapshot_unmount_delay(spa_t *spa, uint64_t objsetid, + int delay); +extern int zfsctl_snapdir_vget(struct mount *sb, uint64_t objsetid, + int gen, struct vnode **ipp); + +/* zfsctl '.zfs/shares' functions */ +extern int zfsctl_shares_lookup(struct vnode *dip, char *name, + struct vnode **ipp, int flags, cred_t *cr, int *direntflags, + struct componentname *realpnp); + +extern int zfsctl_vnop_lookup(struct vnop_lookup_args *); +extern int zfsctl_vnop_getattr(struct vnop_getattr_args *); +extern int zfsctl_vnop_readdir(struct vnop_readdir_args *); +extern int zfsctl_vnop_mkdir(struct vnop_mkdir_args *); +extern int zfsctl_vnop_rmdir(struct vnop_rmdir_args *); +extern int zfsctl_vnop_access(struct vnop_access_args *); +extern int zfsctl_vnop_open(struct vnop_open_args *); +extern int zfsctl_vnop_close(struct vnop_close_args *); +extern int zfsctl_vnop_inactive(struct vnop_inactive_args *); +extern int zfsctl_vnop_reclaim(struct vnop_reclaim_args *); + +extern void zfs_ereport_snapshot_post(const char *subclass, spa_t *spa, + const char *name); + +extern void zfsctl_mount_signal(char *, boolean_t); + + +/* + * These vnodes numbers are reserved for the .zfs control directory. + * It is important that they be no larger that 48-bits because only + * 6 bytes are reserved in the NFS file handle for the object number. + * However, they should be as large as possible to avoid conflicts + * with the objects which are assigned monotonically by the dmu. + */ +#define ZFSCTL_INO_ROOT 0x0000FFFFFFFFFFFFULL +#define ZFSCTL_INO_SHARES 0x0000FFFFFFFFFFFEULL +#define ZFSCTL_INO_SNAPDIR 0x0000FFFFFFFFFFFDULL +#define ZFSCTL_INO_SNAPDIRS 0x0000FFFFFFFFFFFCULL +#define ZFSCTL_EXPIRE_SNAPSHOT 300 + +#endif /* _ZFS_CTLDIR_H */ diff --git a/include/os/macos/zfs/sys/zfs_dir.h b/include/os/macos/zfs/sys/zfs_dir.h new file mode 100644 index 000000000000..cfee82308a7d --- /dev/null +++ b/include/os/macos/zfs/sys/zfs_dir.h @@ -0,0 +1,82 @@ +/* + * 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 _SYS_FS_ZFS_DIR_H +#define _SYS_FS_ZFS_DIR_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* zfs_dirent_lock() flags */ +#define ZNEW 0x0001 /* entry should not exist */ +#define ZEXISTS 0x0002 /* entry should exist */ +#define ZSHARED 0x0004 /* shared access (zfs_dirlook()) */ +#define ZXATTR 0x0008 /* we want the xattr dir */ +#define ZRENAMING 0x0010 /* znode is being renamed */ +#define ZCILOOK 0x0020 /* case-insensitive lookup requested */ +#define ZCIEXACT 0x0040 /* c-i requires c-s match (rename) */ +#define ZHAVELOCK 0x0080 /* z_name_lock is already held */ + +/* mknode flags */ +#define IS_ROOT_NODE 0x01 /* create a root node */ +#define IS_XATTR 0x02 /* create an extended attribute node */ +#define IS_REPLAY 0x04 /* we are replaying intent log */ + +extern int zfs_dirent_lock(zfs_dirlock_t **dlpp, znode_t *dzp, char *name, + znode_t **zpp, int flag, int *direntflags, + struct componentname *realpnp); + +extern void zfs_dirent_unlock(zfs_dirlock_t *); +extern int zfs_link_create(zfs_dirlock_t *, znode_t *, dmu_tx_t *, int); +extern int zfs_link_destroy(zfs_dirlock_t *, znode_t *, dmu_tx_t *, int, + boolean_t *); + +extern int zfs_dirlook(znode_t *, char *name, znode_t **, int, + int *deflg, struct componentname *rpnp); + +extern void zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, + uint_t flag, znode_t **zpp, zfs_acl_ids_t *acl_ids); + +extern void zfs_rmnode(znode_t *); +extern void zfs_dl_name_switch(zfs_dirlock_t *dl, char *new, char **old); +extern boolean_t zfs_dirempty(znode_t *); +extern void zfs_unlinked_add(znode_t *, dmu_tx_t *); +extern void zfs_unlinked_drain(zfsvfs_t *zfsvfs); +extern void zfs_unlinked_drain_stop_wait(zfsvfs_t *zfsvfs); +extern int zfs_sticky_remove_access(znode_t *, znode_t *, cred_t *cr); + +extern int zfs_get_xattrdir(znode_t *, znode_t **, cred_t *, int); +extern int zfs_make_xattrdir(znode_t *, vattr_t *, znode_t **, cred_t *); + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_FS_ZFS_DIR_H */ diff --git a/include/os/macos/zfs/sys/zfs_ioctl_compat.h b/include/os/macos/zfs/sys/zfs_ioctl_compat.h new file mode 100644 index 000000000000..c613925b184b --- /dev/null +++ b/include/os/macos/zfs/sys/zfs_ioctl_compat.h @@ -0,0 +1,207 @@ +/* + * 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 Jorgen Lundan . All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SYS_ZFS_IOCTL_COMPAT_H +#define _SYS_ZFS_IOCTL_COMPAT_H + +#include +#include +#include +#include +#include +#include + +#ifdef _KERNEL +#include +#endif /* _KERNEL */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Backwards ioctl compatibility + */ + +/* ioctl versions for vfs.zfs.version.ioctl */ +#define ZFS_IOCVER_UNDEF -1 +#define ZFS_IOCVER_NONE 0 +#define ZFS_IOCVER_1_9_4 1 +#define ZFS_IOCVER_ZOF 15 + +/* compatibility conversion flag */ +#define ZFS_CMD_COMPAT_NONE 0 +#define ZFS_CMD_COMPAT_V15 1 +#define ZFS_CMD_COMPAT_V28 2 + +#define ZFS_IOC_COMPAT_PASS 254 +#define ZFS_IOC_COMPAT_FAIL 255 + +#define ZFS_IOCREQ(ioreq) ((ioreq) & 0xff) + +typedef struct zfs_iocparm { + uint32_t zfs_ioctl_version; + uint64_t zfs_cmd; + uint64_t zfs_cmd_size; + + /* + * ioctl() return codes can not be used to communicate - + * as XNU will skip copyout() if there is an error, so it + * is passed along in this wrapping structure. + */ + int zfs_ioc_error; /* ioctl error value */ +} zfs_iocparm_t; + +typedef struct zfs_cmd_1_9_4 +{ + char zc_name[MAXPATHLEN]; /* name of pool or dataset */ + uint64_t zc_nvlist_src; /* really (char *) */ + uint64_t zc_nvlist_src_size; + uint64_t zc_nvlist_dst; /* really (char *) */ + uint64_t zc_nvlist_dst_size; + boolean_t zc_nvlist_dst_filled; /* put an nvlist in dst? */ + int zc_pad2; + + /* + * The following members are for legacy ioctls which haven't been + * converted to the new method. + */ + uint64_t zc_history; /* really (char *) */ + char zc_value[MAXPATHLEN * 2]; + char zc_string[MAXNAMELEN]; + uint64_t zc_guid; + uint64_t zc_nvlist_conf; /* really (char *) */ + uint64_t zc_nvlist_conf_size; + uint64_t zc_cookie; + uint64_t zc_objset_type; + uint64_t zc_perm_action; + uint64_t zc_history_len; + uint64_t zc_history_offset; + uint64_t zc_obj; + uint64_t zc_iflags; /* internal to zfs(7fs) */ + zfs_share_t zc_share; + dmu_objset_stats_t zc_objset_stats; + struct drr_begin zc_begin_record; + zinject_record_t zc_inject_record; + uint32_t zc_defer_destroy; + uint32_t zc_flags; + uint64_t zc_action_handle; + int zc_cleanup_fd; + uint8_t zc_simple; + uint8_t zc_pad3[3]; + boolean_t zc_resumable; + uint32_t zc_pad4; + uint64_t zc_sendobj; + uint64_t zc_fromobj; + uint64_t zc_createtxg; + zfs_stat_t zc_stat; + int zc_ioc_error; /* ioctl error value */ + uint64_t zc_dev; /* OSX doesn't have ddi_driver_major */ +} zfs_cmd_1_9_4_t; + +// Figure this out +unsigned static long zfs_ioctl_1_9_4[] = +{ + // ZFS_IOC_POOL_CREATE = _IOWR('Z', 0, struct zfs_cmd), + + 0, /* 0 ZFS_IOC_POOL_CREATE */ + 1, /* 1 ZFS_IOC_POOL_DESTROY */ + 2, /* 2 ZFS_IOC_POOL_IMPORT */ + 3, /* 3 ZFS_IOC_POOL_EXPORT */ + 4, /* 4 ZFS_IOC_POOL_CONFIGS */ + 5, /* 5 ZFS_IOC_POOL_STATS */ + 6, /* 6 ZFS_IOC_POOL_TRYIMPORT */ + 7, /* 7 ZFS_IOC_POOL_SCRUB */ + 8, /* 8 ZFS_IOC_POOL_FREEZE */ + 9, /* 9 ZFS_IOC_POOL_UPGRADE */ + 10, /* 10 ZFS_IOC_POOL_GET_HISTORY */ + 11, /* 11 ZFS_IOC_VDEV_ADD */ + 12, /* 12 ZFS_IOC_VDEV_REMOVE */ + 13, /* 13 ZFS_IOC_VDEV_SET_STATE */ + 14, /* 14 ZFS_IOC_VDEV_ATTACH */ + 15, /* 15 ZFS_IOC_VDEV_DETACH */ + 16, /* 16 ZFS_IOC_VDEV_SETPATH */ + 18, /* 17 ZFS_IOC_OBJSET_STATS */ + 19, /* 18 ZFS_IOC_OBJSET_ZPLPROPS */ + 20, /* 19 ZFS_IOC_DATASET_LIST_NEXT */ + 21, /* 20 ZFS_IOC_SNAPSHOT_LIST_NEXT */ + 22, /* 21 ZFS_IOC_SET_PROP */ + ZFS_IOC_COMPAT_PASS, /* 22 ZFS_IOC_CREATE_MINOR */ + ZFS_IOC_COMPAT_PASS, /* 23 ZFS_IOC_REMOVE_MINOR */ + 23, /* 24 ZFS_IOC_CREATE */ + 24, /* 25 ZFS_IOC_DESTROY */ + 25, /* 26 ZFS_IOC_ROLLBACK */ + 26, /* 27 ZFS_IOC_RENAME */ + 27, /* 28 ZFS_IOC_RECV */ + 28, /* 29 ZFS_IOC_SEND */ + 29, /* 30 ZFS_IOC_INJECT_FAULT */ + 30, /* 31 ZFS_IOC_CLEAR_FAULT */ + 31, /* 32 ZFS_IOC_INJECT_LIST_NEXT */ + 32, /* 33 ZFS_IOC_ERROR_LOG */ + 33, /* 34 ZFS_IOC_CLEAR */ + 34, /* 35 ZFS_IOC_PROMOTE */ + 35, /* 36 ZFS_IOC_DESTROY_SNAPS */ + 36, /* 37 ZFS_IOC_SNAPSHOT */ + 37, /* 38 ZFS_IOC_DSOBJ_TO_DSNAME */ + 38, /* 39 ZFS_IOC_OBJ_TO_PATH */ + 39, /* 40 ZFS_IOC_POOL_SET_PROPS */ + 40, /* 41 ZFS_IOC_POOL_GET_PROPS */ + 41, /* 42 ZFS_IOC_SET_FSACL */ + 42, /* 43 ZFS_IOC_GET_FSACL */ + ZFS_IOC_COMPAT_PASS, /* 44 ZFS_IOC_ISCSI_PERM_CHECK */ + 43, /* 45 ZFS_IOC_SHARE */ + 44, /* 46 ZFS_IOC_IHNERIT_PROP */ + 58, /* 47 ZFS_IOC_JAIL */ + 59, /* 48 ZFS_IOC_UNJAIL */ + 45, /* 49 ZFS_IOC_SMB_ACL */ + 46, /* 50 ZFS_IOC_USERSPACE_ONE */ + 47, /* 51 ZFS_IOC_USERSPACE_MANY */ + 48, /* 52 ZFS_IOC_USERSPACE_UPGRADE */ + 17, /* 53 ZFS_IOC_SETFRU */ +}; + +#ifdef _KERNEL +int zfs_ioctl_compat_pre(zfs_cmd_t *, int *, const int); +void zfs_ioctl_compat_post(zfs_cmd_t *, const int, const int); +nvlist_t *zfs_ioctl_compat_innvl(zfs_cmd_t *, nvlist_t *, const int, + const int); +nvlist_t *zfs_ioctl_compat_outnvl(zfs_cmd_t *, nvlist_t *, const int, + const int); +#endif /* _KERNEL */ +void zfs_cmd_compat_get(zfs_cmd_t *, caddr_t, const int); +void zfs_cmd_compat_put(zfs_cmd_t *, caddr_t, const int, const int); + +int zcommon_init(void); +int icp_init(void); +int zstd_init(void); +void zcommon_fini(void); +void icp_fini(void); +void zstd_fini(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_ZFS_IOCTL_COMPAT_H */ diff --git a/include/os/macos/zfs/sys/zfs_mount.h b/include/os/macos/zfs/sys/zfs_mount.h new file mode 100644 index 000000000000..db72f882a35a --- /dev/null +++ b/include/os/macos/zfs/sys/zfs_mount.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 + */ + +#ifndef _SYS_ZFS_MOUNT_H_ +#define _SYS_ZFS_MOUNT_H_ + +#include + +struct zfs_mount_args { + const char *fspec; + int mflag; + const char *optptr; + int optlen; + int struct_size; +}; + + +/* + * Maximum option string length accepted or returned by mount(2). + */ +#define MAX_MNTOPT_STR 1024 /* max length of mount options string */ + +#ifdef _KERNEL +#define MS_RDONLY MNT_RDONLY +#define MS_NOEXEC MNT_NOEXEC +#define MS_NOSUID MNT_NOSUID +#define MS_NODEV MNT_NODEV +#define MS_BIND 0 +#define MS_REMOUNT MNT_UPDATE +#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 +#define MS_FORCE MNT_FORCE +#define MS_DETACH MNT_DETACH +#define MS_OVERLAY MNT_UNION +#define MS_CRYPT MNT_CPROTECT +#endif + +#endif /* _SYS_ZFS_IOCTL_H */ diff --git a/include/os/macos/zfs/sys/zfs_vfsops_os.h b/include/os/macos/zfs/sys/zfs_vfsops_os.h new file mode 100644 index 000000000000..09c7b95802a0 --- /dev/null +++ b/include/os/macos/zfs/sys/zfs_vfsops_os.h @@ -0,0 +1,326 @@ +/* + * 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. + */ + +#ifndef _SYS_FS_ZFS_VFSOPS_H +#define _SYS_FS_ZFS_VFSOPS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct zfs_sb; +struct znode; + +#ifdef __APPLE__ +#define APPLE_SA_RECOVER +/* #define WITH_SEARCHFS */ +/* #define WITH_READDIRATTR */ +#define HAVE_PAGEOUT_V2 1 +#define HIDE_TRIVIAL_ACL 1 +#ifndef __arm64__ +#define HAVE_NAMED_STREAMS 1 +#endif +#endif + +/* + * Status of the zfs_unlinked_drain thread. + */ +typedef enum drain_state { + ZFS_DRAIN_SHUTDOWN = 0, + ZFS_DRAIN_RUNNING, + ZFS_DRAIN_SHUTDOWN_REQ +} drain_state_t; + + +typedef struct zfsvfs zfsvfs_t; + +struct zfsvfs { + vfs_t *z_vfs; /* generic fs struct */ + zfsvfs_t *z_parent; /* parent fs */ + objset_t *z_os; /* objset reference */ + uint64_t z_root; /* id of root znode */ + uint64_t z_unlinkedobj; /* id of unlinked zapobj */ + uint64_t z_max_blksz; /* maximum block size for files */ + uint64_t z_fuid_obj; /* fuid table object number */ + uint64_t z_fuid_size; /* fuid table size */ + avl_tree_t z_fuid_idx; /* fuid tree keyed by index */ + avl_tree_t z_fuid_domain; /* fuid tree keyed by domain */ + krwlock_t z_fuid_lock; /* fuid lock */ + boolean_t z_fuid_loaded; /* fuid tables are loaded */ + boolean_t z_fuid_dirty; /* need to sync fuid table ? */ + struct zfs_fuid_info *z_fuid_replay; /* fuid info for replay */ + uint64_t z_assign; /* TXG_NOWAIT or set by zil_replay() */ + zilog_t *z_log; /* intent log pointer */ + uint_t z_acl_mode; /* acl chmod/mode behavior */ + uint_t z_acl_inherit; /* acl inheritance behavior */ + zfs_case_t z_case; /* case-sense */ + boolean_t z_utf8; /* utf8-only */ + int z_norm; /* normalization flags */ + boolean_t z_atime; /* enable atimes mount option */ + boolean_t z_unmounted; /* unmounted */ + rrmlock_t z_teardown_lock; + krwlock_t z_teardown_inactive_lock; + list_t z_all_znodes; /* all vnodes in the fs */ + kmutex_t z_znodes_lock; /* lock for z_all_znodes */ + struct vnode *z_ctldir; /* .zfs directory pointer */ + uint64_t z_ctldir_startid; /* Start of snapdir range */ + boolean_t z_show_ctldir; /* expose .zfs in the root dir */ + boolean_t z_issnap; /* true if this is a snapshot */ + boolean_t z_vscan; /* virus scan on/off */ + boolean_t z_use_fuids; /* version allows fuids */ + boolean_t z_replay; /* set during ZIL replay */ + boolean_t z_use_sa; /* version allow system attributes */ + boolean_t z_xattr_sa; /* allow xattrs to be stores as SA */ + uint64_t z_version; + uint64_t z_shares_dir; /* hidden shares dir */ + dataset_kstats_t z_kstat; /* fs kstats */ + kmutex_t z_lock; + + /* for controlling async zfs_unlinked_drain */ + kmutex_t z_drain_lock; + kcondvar_t z_drain_cv; + drain_state_t z_drain_state; + + uint64_t z_userquota_obj; + uint64_t z_groupquota_obj; + uint64_t z_userobjquota_obj; + uint64_t z_groupobjquota_obj; + uint64_t z_projectquota_obj; + uint64_t z_projectobjquota_obj; + +#ifdef __APPLE__ + dev_t z_rdev; /* proxy device for mount */ + boolean_t z_rdonly; /* is mount read-only? */ + time_t z_mount_time; /* mount timestamp (for Spotlight) */ + time_t z_last_unmount_time; /* unmount timestamp (for Spotlight) */ + boolean_t z_xattr; /* enable atimes mount option */ + + avl_tree_t z_hardlinks; /* linkid hash avl tree for vget */ + avl_tree_t z_hardlinks_linkid; /* sorted on linkid */ + krwlock_t z_hardlinks_lock; /* lock to access z_hardlinks */ + + uint64_t z_notification_conditions; /* HFSIOC_VOLUME_STATUS */ + uint64_t z_freespace_notify_warninglimit; + uint64_t z_freespace_notify_dangerlimit; + uint64_t z_freespace_notify_desiredlevel; + + void *z_devdisk; /* Hold fake disk if prop devdisk is on */ + + uint64_t z_findernotify_space; + + zfs_mimic_t z_mimic; +#endif + uint64_t z_replay_eof; /* New end of file - replay only */ + sa_attr_type_t *z_attr_table; /* SA attr mapping->id */ + + uint64_t z_hold_size; /* znode hold array size */ + avl_tree_t *z_hold_trees; /* znode hold trees */ + kmutex_t *z_hold_locks; /* znode hold locks */ + taskqid_t z_drain_task; /* task id for the unlink drain task */ +}; +#define ZFS_OBJ_MTX_SZ 64 + +#ifdef __APPLE__ +struct hardlinks_struct { + avl_node_t hl_node; + avl_node_t hl_node_linkid; + uint64_t hl_parent; // parentid of entry + uint64_t hl_fileid; // the fileid (z_id) for vget + uint32_t hl_linkid; // the linkid, persistent over renames + char hl_name[PATH_MAX]; // cached name for vget +}; +typedef struct hardlinks_struct hardlinks_t; + +int zfs_vfs_uuid_unparse(uuid_t uuid, char *dst); +int zfs_vfs_uuid_gen(const char *osname, uuid_t uuid); +#endif + +#define ZFS_TEARDOWN_INIT(zfsvfs) \ + rrm_init(&(zfsvfs)->z_teardown_lock, B_FALSE) + +#define ZFS_TEARDOWN_DESTROY(zfsvfs) \ + rrm_destroy(&(zfsvfs)->z_teardown_lock) + +#define ZFS_TEARDOWN_TRY_ENTER_READ(zfsvfs) \ + rw_tryenter(&(zfsvfs)->z_teardown_lock, RW_READER) + +#define ZFS_TEARDOWN_ENTER_READ(zfsvfs, tag) \ + rrm_enter_read(&(zfsvfs)->z_teardown_lock, tag); + +#define ZFS_TEARDOWN_EXIT_READ(zfsvfs, tag) \ + rrm_exit(&(zfsvfs)->z_teardown_lock, tag) + +#define ZFS_TEARDOWN_ENTER_WRITE(zfsvfs, tag) \ + rrm_enter(&(zfsvfs)->z_teardown_lock, RW_WRITER, tag) + +#define ZFS_TEARDOWN_EXIT_WRITE(zfsvfs) \ + rrm_exit(&(zfsvfs)->z_teardown_lock, tag) + +#define ZFS_TEARDOWN_EXIT(zfsvfs, tag) \ + rrm_exit(&(zfsvfs)->z_teardown_lock, tag) + +#define ZFS_TEARDOWN_READ_HELD(zfsvfs) \ + RRM_READ_HELD(&(zfsvfs)->z_teardown_lock) + +#define ZFS_TEARDOWN_WRITE_HELD(zfsvfs) \ + RRM_WRITE_HELD(&(zfsvfs)->z_teardown_lock) + +#define ZFS_TEARDOWN_HELD(zfsvfs) \ + RRM_LOCK_HELD(&(zfsvfs)->z_teardown_lock) + +#define ZSB_XATTR 0x0001 /* Enable user xattrs */ + +/* + * Normal filesystems (those not under .zfs/snapshot) have a total + * file ID size limited to 12 bytes (including the length field) due to + * NFSv2 protocol's limitation of 32 bytes for a filehandle. For historical + * reasons, this same limit is being imposed by the Solaris NFSv3 implementation + * (although the NFSv3 protocol actually permits a maximum of 64 bytes). It + * is not possible to expand beyond 12 bytes without abandoning support + * of NFSv2. + * + * For normal filesystems, we partition up the available space as follows: + * 2 bytes fid length (required) + * 6 bytes object number (48 bits) + * 4 bytes generation number (32 bits) + * + * We reserve only 48 bits for the object number, as this is the limit + * currently defined and imposed by the DMU. + */ +typedef struct zfid_short { + uint16_t zf_len; + uint8_t zf_object[6]; /* obj[i] = obj >> (8 * i) */ + uint8_t zf_gen[4]; /* gen[i] = gen >> (8 * i) */ +} zfid_short_t; + +/* + * Filesystems under .zfs/snapshot have a total file ID size of 22 bytes + * (including the length field). This makes files under .zfs/snapshot + * accessible by NFSv3 and NFSv4, but not NFSv2. + * + * For files under .zfs/snapshot, we partition up the available space + * as follows: + * 2 bytes fid length (required) + * 6 bytes object number (48 bits) + * 4 bytes generation number (32 bits) + * 6 bytes objset id (48 bits) + * 4 bytes currently just zero (32 bits) + * + * We reserve only 48 bits for the object number and objset id, as these are + * the limits currently defined and imposed by the DMU. + */ +typedef struct zfid_long { + zfid_short_t z_fid; + uint8_t zf_setid[6]; /* obj[i] = obj >> (8 * i) */ + uint8_t zf_setgen[4]; /* gen[i] = gen >> (8 * i) */ +} zfid_long_t; + +#define SHORT_FID_LEN (sizeof (zfid_short_t) - sizeof (uint16_t)) +#define LONG_FID_LEN (sizeof (zfid_long_t) - sizeof (uint16_t)) + +extern uint_t zfs_fsyncer_key; + +extern int zfs_suspend_fs(zfsvfs_t *zfsvfs); +extern int zfs_resume_fs(zfsvfs_t *zfsvfs, struct dsl_dataset *ds); +extern int zfs_userspace_one(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, + const char *domain, uint64_t rid, uint64_t *valuep); +extern int zfs_userspace_many(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, + uint64_t *cookiep, void *vbuf, uint64_t *bufsizep); +extern int zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, + const char *domain, uint64_t rid, uint64_t quota); +extern boolean_t zfs_owner_overquota(zfsvfs_t *zfsvfs, struct znode *, + boolean_t isgroup); +extern boolean_t zfs_fuid_overquota(zfsvfs_t *zfsvfs, boolean_t isgroup, + uint64_t fuid); +extern int zfs_set_version(zfsvfs_t *zfsvfs, uint64_t newvers); +extern int zfsvfs_create_impl(zfsvfs_t **zfvp, zfsvfs_t *zfsvfs, objset_t *os); + +extern int zfs_get_zplprop(objset_t *os, zfs_prop_t prop, + uint64_t *value); + +extern int zfs_sb_create(const char *name, zfsvfs_t **zfsvfsp); +extern int zfs_sb_setup(zfsvfs_t *zfsvfs, boolean_t mounting); +extern void zfs_sb_free(zfsvfs_t *zfsvfs); +extern int zfs_check_global_label(const char *dsname, const char *hexsl); +extern boolean_t zfs_is_readonly(zfsvfs_t *zfsvfs); + + + + +extern int zfs_vfs_init(struct vfsconf *vfsp); +extern int zfs_vfs_start(struct mount *mp, int flags, vfs_context_t context); +extern int zfs_vfs_mount(struct mount *mp, vnode_t *devvp, + user_addr_t data, vfs_context_t context); +extern int zfs_vfs_unmount(struct mount *mp, int mntflags, + vfs_context_t context); +extern int zfs_vfs_root(struct mount *mp, vnode_t **vpp, + vfs_context_t context); +extern int zfs_vfs_vget(struct mount *mp, ino64_t ino, vnode_t **vpp, + vfs_context_t context); +extern int zfs_vfs_getattr(struct mount *mp, struct vfs_attr *fsap, + vfs_context_t context); +extern int zfs_vfs_setattr(struct mount *mp, struct vfs_attr *fsap, + vfs_context_t context); +extern int zfs_vfs_sync(struct mount *mp, int waitfor, vfs_context_t context); +extern int zfs_vfs_fhtovp(struct mount *mp, int fhlen, unsigned char *fhp, + vnode_t **vpp, vfs_context_t context); +extern int zfs_vfs_vptofh(vnode_t *vp, int *fhlenp, unsigned char *fhp, + vfs_context_t context); +extern int zfs_vfs_sysctl(int *name, uint_t namelen, user_addr_t oldp, + size_t *oldlenp, user_addr_t newp, size_t newlen, vfs_context_t context); +extern int zfs_vfs_quotactl(struct mount *mp, int cmds, uid_t uid, + caddr_t datap, vfs_context_t context); +extern int zfs_vfs_mountroot(struct mount *mp, struct vnode *vp, + vfs_context_t context); + +extern void zfs_init(void); +extern void zfs_fini(void); + +extern int zfs_vnode_lock(vnode_t *vp, int flags); +extern void zfs_freevfs(struct mount *vfsp); + +extern int zfsvfs_create(const char *name, boolean_t rd, zfsvfs_t **zfvp); +extern void zfsvfs_free(zfsvfs_t *zfsvfs); + +extern int zfs_get_temporary_prop(dsl_dataset_t *ds, zfs_prop_t zfs_prop, + uint64_t *val, char *setpoint); + +extern int zfs_end_fs(zfsvfs_t *zfsvfs, struct dsl_dataset *ds); + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_FS_ZFS_VFSOPS_H */ diff --git a/include/os/macos/zfs/sys/zfs_vnops_os.h b/include/os/macos/zfs/sys/zfs_vnops_os.h new file mode 100644 index 000000000000..67d87257a512 --- /dev/null +++ b/include/os/macos/zfs/sys/zfs_vnops_os.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 (c) 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#ifndef _SYS_FS_ZFS_VNOPS_OS_H +#define _SYS_FS_ZFS_VNOPS_OS_H + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Spotlight specific fcntl()'s + */ + +// Older defines +#define SPOTLIGHT_GET_MOUNT_TIME (FCNTL_FS_SPECIFIC_BASE + 0x00002) +#define SPOTLIGHT_GET_UNMOUNT_TIME (FCNTL_FS_SPECIFIC_BASE + 0x00003) + +// Newer defines, will these need a OSX version test to compile on older? +#define SPOTLIGHT_IOC_GET_MOUNT_TIME _IOR('h', 18, u_int32_t) +#define SPOTLIGHT_FSCTL_GET_MOUNT_TIME \ + IOCBASECMD(SPOTLIGHT_IOC_GET_MOUNT_TIME) +#define SPOTLIGHT_IOC_GET_LAST_MTIME _IOR('h', 19, u_int32_t) +#define SPOTLIGHT_FSCTL_GET_LAST_MTIME \ + IOCBASECMD(SPOTLIGHT_IOC_GET_LAST_MTIME) + +/* + * Account for user timespec structure differences + */ +#ifdef ZFS_LEOPARD_ONLY +typedef struct timespec timespec_user32_t; +typedef struct user_timespec timespec_user64_t; +#else +typedef struct user32_timespec timespec_user32_t; +typedef struct user64_timespec timespec_user64_t; +#endif + +#define UNKNOWNUID ((uid_t)99) +#define UNKNOWNGID ((gid_t)99) + +#define DTTOVT(dtype) (iftovt_tab[(dtype)]) +#define kTextEncodingMacUnicode 0x7e +#define ZAP_AVENAMELEN (ZAP_MAXNAMELEN / 4) + +/* Finder information */ +struct finderinfo { + u_int32_t fi_type; /* files only */ + u_int32_t fi_creator; /* files only */ + u_int16_t fi_flags; + struct { + int16_t v; + int16_t h; + } fi_location; + int8_t fi_opaque[18]; +} __attribute__((aligned(2), packed)); +typedef struct finderinfo finderinfo_t; + +enum { + /* Finder Flags */ + kHasBeenInited = 0x0100, + kHasCustomIcon = 0x0400, + kIsStationery = 0x0800, + kNameLocked = 0x1000, + kHasBundle = 0x2000, + kIsInvisible = 0x4000, + kIsAlias = 0x8000 +}; + +/* Attribute packing information */ +typedef struct attrinfo { + struct attrlist *ai_attrlist; + void **ai_attrbufpp; + void **ai_varbufpp; + void *ai_varbufend; + vfs_context_t ai_context; +} attrinfo_t; + +/* + * Attributes that we can get for free from the zap (ie without a znode) + */ +#define ZFS_DIR_ENT_ATTRS ( \ + ATTR_CMN_NAME | ATTR_CMN_DEVID | ATTR_CMN_FSID | \ + ATTR_CMN_OBJTYPE | ATTR_CMN_OBJTAG | ATTR_CMN_OBJID | \ + ATTR_CMN_OBJPERMANENTID | ATTR_CMN_SCRIPT | \ + ATTR_CMN_FILEID) + +/* + * Attributes that we support + */ +#define ZFS_ATTR_BIT_MAP_COUNT 5 + +#define ZFS_ATTR_CMN_VALID ( \ + ATTR_CMN_NAME | ATTR_CMN_DEVID | ATTR_CMN_FSID | \ + ATTR_CMN_OBJTYPE | ATTR_CMN_OBJTAG | ATTR_CMN_OBJID | \ + ATTR_CMN_OBJPERMANENTID | ATTR_CMN_PAROBJID | \ + ATTR_CMN_SCRIPT | ATTR_CMN_CRTIME | ATTR_CMN_MODTIME | \ + ATTR_CMN_CHGTIME | ATTR_CMN_ACCTIME | \ + ATTR_CMN_BKUPTIME | ATTR_CMN_FNDRINFO | \ + ATTR_CMN_OWNERID | ATTR_CMN_GRPID | \ + ATTR_CMN_ACCESSMASK | ATTR_CMN_FLAGS | \ + ATTR_CMN_USERACCESS | ATTR_CMN_FILEID | \ + ATTR_CMN_PARENTID) + +#define ZFS_ATTR_DIR_VALID ( \ + ATTR_DIR_LINKCOUNT | ATTR_DIR_ENTRYCOUNT | \ + ATTR_DIR_MOUNTSTATUS) + +#define ZFS_ATTR_FILE_VALID ( \ + ATTR_FILE_LINKCOUNT |ATTR_FILE_TOTALSIZE | \ + ATTR_FILE_ALLOCSIZE | ATTR_FILE_IOBLOCKSIZE | \ + ATTR_FILE_DEVTYPE | ATTR_FILE_DATALENGTH | \ + ATTR_FILE_DATAALLOCSIZE | ATTR_FILE_RSRCLENGTH | \ + ATTR_FILE_RSRCALLOCSIZE) + +extern int zfs_remove(znode_t *dzp, char *name, cred_t *cr, int flags); +extern int zfs_mkdir(znode_t *dzp, char *dirname, vattr_t *vap, + znode_t **zpp, cred_t *cr, int flags, vsecattr_t *vsecp, + zidmap_t *mnt_ns); +extern int zfs_rmdir(znode_t *dzp, char *name, znode_t *cwd, + cred_t *cr, int flags); +extern int zfs_setattr(znode_t *zp, vattr_t *vap, int flag, cred_t *cr, + zidmap_t *mnt_ns); +extern int zfs_rename(znode_t *sdzp, char *snm, znode_t *tdzp, + char *tnm, cred_t *cr, int flags, uint64_t rflags, vattr_t *wo_vap, + zidmap_t *mnt_ns); +extern int zfs_symlink(znode_t *dzp, char *name, vattr_t *vap, + char *link, znode_t **zpp, cred_t *cr, int flags, zidmap_t *mnt_ns); +extern int zfs_link(znode_t *tdzp, znode_t *sp, + char *name, cred_t *cr, int flags); +extern int zfs_space(znode_t *zp, int cmd, struct flock *bfp, int flag, + offset_t offset, cred_t *cr); +extern int zfs_create(znode_t *dzp, char *name, vattr_t *vap, int excl, + int mode, znode_t **zpp, cred_t *cr, int flag, vsecattr_t *vsecp, + zidmap_t *mnt_ns); +extern int zfs_write_simple(znode_t *zp, const void *data, size_t len, + loff_t pos, size_t *resid); + +extern int zfs_open(struct vnode *ip, int mode, int flag, cred_t *cr); +extern int zfs_close(struct vnode *ip, int flag, cred_t *cr); +extern int zfs_lookup(znode_t *dzp, char *nm, znode_t **zpp, + int flags, cred_t *cr, int *direntflags, struct componentname *realpnp); +extern int zfs_ioctl(vnode_t *vp, ulong_t com, intptr_t data, int flag, + cred_t *cred, int *rvalp, caller_context_t *ct); +extern int zfs_readdir(vnode_t *vp, zfs_uio_t *uio, cred_t *cr, int *eofp, + int flags, int *a_numdirent); +extern int zfs_fsync(znode_t *zp, int syncflag, cred_t *cr); +extern int zfs_getattr(vnode_t *vp, vattr_t *vap, int flags, + cred_t *cr, caller_context_t *ct); +extern int zfs_macos_getacl(znode_t *zp, struct kauth_acl **aclpp, + boolean_t skipaclchk, cred_t *cr); +extern int zfs_readlink(vnode_t *vp, zfs_uio_t *uio, cred_t *cr); + +extern void zfs_inactive(vnode_t *vp); + +/* zfs_vops_osx.c calls */ +extern int zfs_znode_getvnode(znode_t *zp, zfsvfs_t *zfsvfs); + +extern void getnewvnode_reserve(int num); +extern void getnewvnode_drop_reserve(void); +extern int zfs_vfsops_init(void); +extern int zfs_vfsops_fini(void); +extern int zfs_znode_asyncgetvnode(znode_t *zp, zfsvfs_t *zfsvfs); +extern void zfs_znode_asyncput(znode_t *zp); +extern int zfs_znode_asyncwait(zfsvfs_t *, znode_t *zp); + +/* zfs_vnops_osx_lib calls */ +extern int zfs_ioflags(int ap_ioflag); +extern int zfs_getattr_znode_unlocked(struct vnode *vp, vattr_t *vap); +extern int ace_trivial_common(void *acep, int aclcnt, + uint64_t (*walk)(void *, uint64_t, int aclcnt, + uint16_t *, uint16_t *, uint32_t *)); +extern void acl_trivial_access_masks(mode_t mode, boolean_t isdir, + trivial_acl_t *masks); +extern int zpl_obtain_xattr(struct znode *, const char *name, mode_t mode, + cred_t *cr, struct vnode **vpp, int flag); + +extern void commonattrpack(attrinfo_t *aip, zfsvfs_t *zfsvfs, znode_t *zp, + const char *name, ino64_t objnum, enum vtype vtype, + boolean_t user64); +extern void dirattrpack(attrinfo_t *aip, znode_t *zp); +extern void fileattrpack(attrinfo_t *aip, zfsvfs_t *zfsvfs, znode_t *zp); +extern void nameattrpack(attrinfo_t *aip, const char *name, int namelen); +extern int getpackedsize(struct attrlist *alp, boolean_t user64); +extern void getfinderinfo(znode_t *zp, cred_t *cr, finderinfo_t *fip); +extern uint32_t getuseraccess(znode_t *zp, vfs_context_t ctx); +extern void finderinfo_update(uint8_t *finderinfo, znode_t *zp); +extern void zfs_zrele_async(znode_t *zp); + +/* + * OSX ACL Helper funcions + * + * OSX uses 'guids' for the 'who' part of ACLs, and uses a 'well known' + * binary sequence to signify the special rules of "owner", "group" and + * "everybody". We translate between this "well-known" guid and ZFS' + * flags ACE_OWNER, ACE_GROUP and ACE_EVERYBODY. + * + */ +#define KAUTH_WKG_NOT 0 /* not a well-known GUID */ +#define KAUTH_WKG_OWNER 1 +#define KAUTH_WKG_GROUP 2 +#define KAUTH_WKG_NOBODY 3 +#define KAUTH_WKG_EVERYBODY 4 + +extern int kauth_wellknown_guid(guid_t *guid); +extern void aces_from_acl(ace_t *aces, int *nentries, struct kauth_acl *k_acl, + int *seen_type); +extern void nfsacl_set_wellknown(int wkg, guid_t *guid); +extern int zfs_addacl_trivial(znode_t *zp, ace_t *aces, int *nentries, + int seen_type); + +extern struct vnodeopv_desc zfs_dvnodeop_opv_desc; +extern struct vnodeopv_desc zfs_fvnodeop_opv_desc; +extern struct vnodeopv_desc zfs_symvnodeop_opv_desc; +extern struct vnodeopv_desc zfs_xdvnodeop_opv_desc; +extern struct vnodeopv_desc zfs_evnodeop_opv_desc; +extern struct vnodeopv_desc zfs_fifonodeop_opv_desc; +extern struct vnodeopv_desc zfs_ctldir_opv_desc; +extern int (**zfs_ctldirops)(void *); + + +extern int zpl_xattr_list(struct vnode *dvp, zfs_uio_t *uio, + ssize_t *, cred_t *cr); +extern int zpl_xattr_get(struct vnode *ip, const char *name, + zfs_uio_t *uio, ssize_t *, cred_t *cr); +extern int zpl_xattr_set(struct vnode *ip, const char *name, + zfs_uio_t *uio, int flags, cred_t *cr); +extern const char *zpl_xattr_prefixname(const char *name); + +extern void zfs_findernotify_refresh(struct mount *mp); + + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_FS_ZFS_VNOPS_H */ diff --git a/include/os/macos/zfs/sys/zfs_znode_impl.h b/include/os/macos/zfs/sys/zfs_znode_impl.h new file mode 100644 index 000000000000..35db550d68d9 --- /dev/null +++ b/include/os/macos/zfs/sys/zfs_znode_impl.h @@ -0,0 +1,219 @@ +/* + * 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, 2015 by Delphix. All rights reserved. + * Copyright (c) 2014 Integros [integros.com] + * Copyright 2016 Nexenta Systems, Inc. All rights reserved. + */ + +#ifndef _MACOS_ZFS_SYS_ZNODE_IMPL_H +#define _MACOS_ZFS_SYS_ZNODE_IMPL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZFS_UIMMUTABLE 0x0000001000000000ull // OSX +#define ZFS_UAPPENDONLY 0x0000004000000000ull // OSX + +// #define ZFS_IMMUTABLE (ZFS_UIMMUTABLE | ZFS_SIMMUTABLE) +// #define ZFS_APPENDONLY (ZFS_UAPPENDONLY | ZFS_SAPPENDONLY) + +#define ZFS_TRACKED 0x0010000000000000ull +#define ZFS_COMPRESSED 0x0020000000000000ull + +#define ZFS_SIMMUTABLE 0x0040000000000000ull +#define ZFS_SAPPENDONLY 0x0080000000000000ull + +#define SA_ZPL_ADDTIME(z) z->z_attr_table[ZPL_ADDTIME] +#define SA_ZPL_DOCUMENTID(z) z->z_attr_table[ZPL_DOCUMENTID] + +#define ZGET_FLAG_UNLINKED (1<<0) /* Also lookup unlinked */ +#define ZGET_FLAG_ASYNC (1<<3) /* taskq the vnode_create call */ + +extern int zfs_zget_ext(zfsvfs_t *zfsvfs, uint64_t obj_num, + struct znode **zpp, int flags); + + +/* + * Directory entry locks control access to directory entries. + * They are used to protect creates, deletes, and renames. + * Each directory znode has a mutex and a list of locked names. + */ +#define ZNODE_OS_FIELDS \ + struct zfsvfs *z_zfsvfs; \ + struct vnode *z_vnode; \ + uint64_t z_uid; \ + uint64_t z_gid; \ + uint64_t z_gen; \ + uint64_t z_atime[2]; \ + uint64_t z_links; \ + uint32_t z_vid; \ + boolean_t z_is_mapped; \ + uint32_t z_document_id; \ + uint64_t z_finder_parentid; \ + boolean_t z_finder_hardlink; \ + uint64_t z_write_gencount; \ + char z_name_cache[MAXPATHLEN]; \ + boolean_t z_skip_truncate_undo_decmpfs; \ + taskq_ent_t z_attach_taskq; \ + kcondvar_t z_attach_cv; \ + kmutex_t z_attach_lock; \ + hrtime_t z_snap_mount_time; \ + krwlock_t z_map_lock; + +#define ZFS_LINK_MAX UINT64_MAX + +/* + * ZFS minor numbers can refer to either a control device instance or + * a zvol. Depending on the value of zss_type, zss_data points to either + * a zvol_state_t or a zfs_onexit_t. + */ +enum zfs_soft_state_type { + ZSST_ZVOL, + ZSST_CTLDEV +}; + +typedef struct zfs_soft_state { + enum zfs_soft_state_type zss_type; + void *zss_data; +} zfs_soft_state_t; + +/* + * Convert between znode pointers and vnode pointers + */ +#define ZTOV(ZP) ((ZP)->z_vnode) +#define ZTOI(ZP) ((ZP)->z_vnode) +#define VTOZ(VP) ((znode_t *)vnode_fsnode((VP))) +#define ITOZ(VP) ((znode_t *)vnode_fsnode((VP))) + +#define VTOM(VP) ((mount_t *)vnode_mount((VP))) + +/* These are not used so far, VN_HOLD returncode must be checked. */ +#define zhold(zp) VN_HOLD(ZTOV(zp)) +#define zrele(zp) VN_RELE(ZTOV(zp)) + +#define ZTOZSB(zp) ((zp)->z_zfsvfs) +#define ITOZSB(vp) ((zfsvfs_t *)vfs_fsprivate(vnode_mount(vp))) +#define ZTOTYPE(zp) (vnode_vtype(ZTOV(zp))) +#define ZTOGID(zp) ((zp)->z_gid) +#define ZTOUID(zp) ((zp)->z_uid) +#define ZTONLNK(zp) ((zp)->z_links) +#define Z_ISBLK(type) ((type) == VBLK) +#define Z_ISCHR(type) ((type) == VCHR) +#define Z_ISLNK(type) ((type) == VLNK) +#define Z_ISDIR(type) ((type) == VDIR) + +#define zn_has_cached_data(zp, start, end) ((zp)->z_is_mapped) +#define zn_flush_cached_data(zp, sync) \ + (void) ubc_msync(ZTOV(zp), 0, \ + ubc_getsize(ZTOV(zp)), NULL, UBC_PUSHALL | UBC_SYNC); +#define zn_rlimit_fsize(size) (0) +#define zn_rlimit_fsize_uio(zp, uio) (0) + +/* Called on entry to each ZFS vnode and vfs operation */ +static inline int +zfs_enter(zfsvfs_t *zfsvfs, const char *tag) +{ + ZFS_TEARDOWN_ENTER_READ(zfsvfs, tag); + if (unlikely((zfsvfs)->z_unmounted)) { + ZFS_TEARDOWN_EXIT_READ(zfsvfs, tag); + return (SET_ERROR(EIO)); + } + return (0); +} + +/* Must be called before exiting the vop */ +static inline void +zfs_exit(zfsvfs_t *zfsvfs, const char *tag) +{ + ZFS_TEARDOWN_EXIT_READ(zfsvfs, tag); +} + +/* + * Macros for dealing with dmu_buf_hold + */ +#define ZFS_OBJ_MTX_SZ 64 +#define ZFS_OBJ_MTX_MAX (1024 * 1024) +#define ZFS_OBJ_HASH(zfsvfs, obj) ((obj) & ((zfsvfs->z_hold_size) - 1)) + +extern unsigned int zfs_object_mutex_size; + +/* Encode ZFS stored time values from a struct timespec */ +#define ZFS_TIME_ENCODE(tp, stmp) \ + { \ + (stmp)[0] = (uint64_t)(tp)->tv_sec; \ + (stmp)[1] = (uint64_t)(tp)->tv_nsec; \ + } + +/* Decode ZFS stored time values to a struct timespec */ +#define ZFS_TIME_DECODE(tp, stmp) \ + { \ + (tp)->tv_sec = (time_t)(stmp)[0]; \ + (tp)->tv_nsec = (long)(stmp)[1]; \ +} + +#define ZFS_ACCESSTIME_STAMP(zfsvfs, zp) \ + if ((zfsvfs)->z_atime && !vfs_isrdonly(zfsvfs->z_vfs)) \ + zfs_tstamp_update_setup_ext(zp, ACCESSED, NULL, NULL, B_FALSE); + +extern void zfs_tstamp_update_setup_ext(struct znode *, + uint_t, uint64_t [2], uint64_t [2], boolean_t); +extern void zfs_tstamp_update_setup(struct znode *, + uint_t, uint64_t [2], uint64_t [2]); +extern void zfs_znode_free(struct znode *); + +extern zil_get_data_t zfs_get_data; +extern zil_replay_func_t *const zfs_replay_vector[TX_MAX_TYPE]; +extern int zfsfstype; + +extern int zfs_znode_parent_and_name(struct znode *zp, struct znode **dzpp, + char *buf); +extern void zfs_setattr_generate_id(struct znode *, uint64_t, char *name); + +extern int zfs_setattr_set_documentid(struct znode *zp, + boolean_t update_flags); + +/* Legacy macOS uses fnv_32a hash for hostid. */ +#define FNV1_32A_INIT ((uint32_t)0x811c9dc5) +uint32_t fnv_32a_str(const char *str, uint32_t hval); + +int zfs_setbsdflags(struct znode *, uint32_t bsdflags, xvattr_t *); +uint32_t zfs_getbsdflags(struct znode *zp); + +#ifdef __cplusplus +} +#endif + +#endif /* _MACOS_SYS_FS_ZFS_ZNODE_H */ diff --git a/include/os/macos/zfs/sys/zpl.h b/include/os/macos/zfs/sys/zpl.h new file mode 100644 index 000000000000..5d391c6a960c --- /dev/null +++ b/include/os/macos/zfs/sys/zpl.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 + */ +#ifndef _SYS_ZPL_H +#define _SYS_ZPL_H + + + + +#endif // _SYS_ZPL_H diff --git a/include/os/macos/zfs/sys/zvolIO.h b/include/os/macos/zfs/sys/zvolIO.h new file mode 100644 index 000000000000..d85edb55794d --- /dev/null +++ b/include/os/macos/zfs/sys/zvolIO.h @@ -0,0 +1,136 @@ +/* + * 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, 2016 Jorgen Lundman + */ + +#ifndef ZVOLIO_H_INCLUDED +#define ZVOLIO_H_INCLUDED + +/* Linux polutes 'current' */ +#undef current + +#ifdef __cplusplus +#include + +extern "C" { +#endif /* __cplusplus */ + +#include +#include + +extern size_t zvolIO_strategy(char *addr, uint64_t offset, + size_t len, zfs_uio_rw_t rw, const void *privptr); + +#ifdef __cplusplus +} /* extern "C" */ + +class org_openzfsonosx_zfs_zvol : public IOService +{ + OSDeclareDefaultStructors(org_openzfsonosx_zfs_zvol) + +private: + +public: + virtual bool init(OSDictionary* dictionary = NULL) override; + virtual void free(void) override; + virtual IOService* probe(IOService* provider, SInt32* score) override; + virtual bool start(IOService* provider) override; + virtual void stop(IOService* provider) override; + + virtual bool handleOpen(IOService *client, + IOOptionBits options, void *arg) override; + virtual bool handleIsOpen(const IOService *client) const override; + virtual void handleClose(IOService *client, + IOOptionBits options) override; + virtual bool isOpen(const IOService *forClient = 0) const override; + +private: + OSSet *_openClients; +}; + +#include + +class org_openzfsonosx_zfs_zvol_device : public IOBlockStorageDevice +{ + OSDeclareDefaultStructors(org_openzfsonosx_zfs_zvol_device) + +private: + // IOService *m_provider; + zvol_state_t *zv; + +public: + virtual bool init(zvol_state_t *c_zv, + OSDictionary* properties = 0); + + virtual bool attach(IOService* provider) override; + virtual void detach(IOService* provider) override; + virtual IOReturn doEjectMedia(void) override; + virtual IOReturn doFormatMedia(UInt64 byteCapacity) override; + virtual UInt32 doGetFormatCapacities(UInt64 * capacities, + UInt32 capacitiesMaxCount) const override; + + virtual IOReturn doLockUnlockMedia(bool doLock) override; + virtual IOReturn doSynchronizeCache(void) override; + virtual char *getVendorString(void) override; + virtual char *getProductString(void) override; + virtual char *getRevisionString(void) override; + virtual char *getAdditionalDeviceInfoString(void) override; + virtual IOReturn reportBlockSize(UInt64 *blockSize) override; + virtual IOReturn reportEjectability(bool *isEjectable) override; + virtual IOReturn reportLockability(bool *isLockable) override; + virtual IOReturn reportMaxValidBlock(UInt64 *maxBlock) override; + virtual IOReturn reportMediaState(bool *mediaPresent, + bool *changedState) override; + + virtual IOReturn reportPollRequirements(bool *pollRequired, + bool *pollIsExpensive) override; + + virtual IOReturn reportRemovability(bool *isRemovable) override; + virtual IOReturn reportWriteProtection(bool *isWriteProtected) override; + virtual IOReturn getWriteCacheState(bool *enabled) override; + virtual IOReturn setWriteCacheState(bool enabled) override; + virtual IOReturn doAsyncReadWrite(IOMemoryDescriptor *buffer, + UInt64 block, UInt64 nblks, + IOStorageAttributes *attributes, + IOStorageCompletion *completion) override; + + virtual IOReturn doDiscard(UInt64 block, UInt64 nblks) override; + virtual IOReturn doUnmap(IOBlockStorageDeviceExtent *extents, + UInt32 extentsCount, UInt32 options) override; + + virtual bool handleOpen(IOService *client, + IOOptionBits options, void *access) override; + + virtual void handleClose(IOService *client, + IOOptionBits options) override; + + virtual int getBSDName(void); + virtual int renameDevice(void); + virtual int offlineDevice(void); + virtual int onlineDevice(void); + virtual int refreshDevice(void); + + virtual void clearState(void); +}; +#endif /* __cplusplus */ + +#endif /* ZVOLIO_H_INCLUDED */ diff --git a/include/os/macos/zfs/sys/zvol_os.h b/include/os/macos/zfs/sys/zvol_os.h new file mode 100644 index 000000000000..bd84953f0263 --- /dev/null +++ b/include/os/macos/zfs/sys/zvol_os.h @@ -0,0 +1,81 @@ +/* + * 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 _SYS_ZVOL_OS_h +#define _SYS_ZVOL_OS_h + +#ifdef __cplusplus +extern "C" { +#endif + +/* struct wrapper for IOKit class */ +typedef struct zvol_iokit zvol_iokit_t; +typedef struct zvol_state zvol_state_t; + +struct zvol_state_os { + dev_t zvo_dev; /* device id */ + + zvol_iokit_t *zvo_iokitdev; /* IOKit device */ + uint64_t zvo_openflags; /* Remember flags used at open */ + char zvo_bsdname[MAXPATHLEN]; /* /dev/diskX */ +}; + +extern int zvol_os_ioctl(dev_t, unsigned long, caddr_t, + int isblk, cred_t *, int *rvalp); +extern int zvol_os_open_zv(zvol_state_t *, int, int, struct proc *p); +extern int zvol_os_open(dev_t dev, int flag, int otyp, struct proc *p); +extern int zvol_os_close_zv(zvol_state_t *, int, int, struct proc *p); +extern int zvol_os_close(dev_t dev, int flag, int otyp, struct proc *p); +extern int zvol_os_read(dev_t dev, struct uio *uio, int p); +extern int zvol_os_write(dev_t dev, struct uio *uio, int p); + +extern int zvol_os_read_zv(zvol_state_t *zv, zfs_uio_t *uio); +extern int zvol_os_write_zv(zvol_state_t *zv, zfs_uio_t *uio); +extern int zvol_os_unmap(zvol_state_t *zv, uint64_t off, uint64_t bytes); + +extern void zvol_os_strategy(struct buf *bp); +extern int zvol_os_get_volume_blocksize(dev_t dev); + +extern void zvol_os_lock_zv(zvol_state_t *zv); +extern void zvol_os_unlock_zv(zvol_state_t *zv); + +extern void *zvolRemoveDevice(zvol_state_t *); +extern int zvolRemoveDeviceTerminate(void *iokitdev); +extern int zvolCreateNewDevice(zvol_state_t *zv); +extern int zvolRegisterDevice(zvol_state_t *zv); + +extern int zvolRenameDevice(zvol_state_t *zv); +extern int zvolSetVolsize(zvol_state_t *zv); + +extern void zvol_add_symlink(zvol_state_t *zv, const char *bsd_disk, + const char *bsd_rdisk); + +extern void zvol_remove_symlink(zvol_state_t *zv); + +extern void zfs_ereport_zvol_post(const char *subclass, const char *name, + const char *bsd, const char *rbsd); + +extern boolean_t zvol_os_is_zvol_impl(const char *path); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libefi/rdwr_efi_macos.c b/lib/libefi/rdwr_efi_macos.c new file mode 100644 index 000000000000..3df30c0ce1da --- /dev/null +++ b/lib/libefi/rdwr_efi_macos.c @@ -0,0 +1,1598 @@ +/* + * 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. + */ + +/* + * Copyright (c) 2013, 2020 Jorgen Lundman + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 } +}; + +#ifdef DEBUG +int efi_debug = 1; +#else +int efi_debug = 0; +#endif + +static int efi_read(int, struct dk_gpt *); +/* Additional macOS support functions */ + +#include +#include +#include +#include + +static const CFStringRef CoreStorageLogicalVolumeMediaPathSubstring = + CFSTR("/CoreStoragePhysical/"); +static const CFStringRef VirtualInterfaceDeviceProtocolSubstring = + CFSTR(kIOPropertyPhysicalInterconnectTypeVirtual); + +typedef struct { + DASessionRef session; + DADiskRef disk; +} DADiskSession; + +static Boolean +CFDictionaryValueIfPresentMatchesSubstring(CFDictionaryRef dict, + CFStringRef key, CFStringRef substr) +{ + Boolean ret = false; + CFStringRef existing; + if (dict && + CFDictionaryGetValueIfPresent(dict, key, + (const void **)&existing)) { + CFRange range = CFStringFind(existing, substr, + kCFCompareCaseInsensitive); + if (range.location != kCFNotFound) + ret = true; + } + return (ret); +} + +static int +setupDADiskSession(DADiskSession *ds, const char *bsdName) +{ + int err = 0; + + ds->session = DASessionCreate(NULL); + if (ds->session == NULL) { + err = EINVAL; + } + + if (err == 0) { + ds->disk = DADiskCreateFromBSDName(NULL, ds->session, bsdName); + if (ds->disk == NULL) + err = EINVAL; + } + return (err); +} + +static void +teardownDADiskSession(DADiskSession *ds) +{ + if (ds->session != NULL) + CFRelease(ds->session); + if (ds->disk != NULL) + CFRelease(ds->disk); +} + +static int +isDeviceMatchForKeyAndSubstr(char *device, CFStringRef key, CFStringRef substr, + Boolean *isMatch) +{ + int error; + DADiskSession ds = { 0 }; + + if (!isMatch) + return (-1); + + if ((error = setupDADiskSession(&ds, device)) == 0) { + CFDictionaryRef descDict = NULL; + if ((descDict = DADiskCopyDescription(ds.disk)) != NULL) { + *isMatch = + CFDictionaryValueIfPresentMatchesSubstring(descDict, + key, substr); + } else { + error = -1; + (void) fprintf(stderr, + "no DADiskCopyDescription for device %s\n", + device); + *isMatch = false; + } + } + + teardownDADiskSession(&ds); + return (error); +} + +/* + * Caller is responsible for supplying a /dev/disk* block device path + * or the BSD name (disk*). + */ +static int +osx_device_isvirtual(char *device) +{ + Boolean isCoreStorageLV = false; + Boolean isVirtualInterface = false; + + if (efi_debug) + (void) fprintf(stderr, "Checking if '%s' is virtual\n", device); + + isDeviceMatchForKeyAndSubstr(device, + kDADiskDescriptionMediaPathKey, + CoreStorageLogicalVolumeMediaPathSubstring, + &isCoreStorageLV); + + isDeviceMatchForKeyAndSubstr(device, + kDADiskDescriptionDeviceProtocolKey, + VirtualInterfaceDeviceProtocolSubstring, + &isVirtualInterface); + + if (efi_debug) + (void) fprintf(stderr, + "Is CoreStorage LV %d : is virtual interface %d\n", + isCoreStorageLV, + isVirtualInterface); + + return (isCoreStorageLV /* || isVirtualInterface*/); +} + +/* + * 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, DKIOCGETBLOCKSIZE, §or_size) < 0) + return (-1); + + if (ioctl(fd, DKIOCGETBLOCKCOUNT, &capacity_size) < 0) + return (-1); + + *lbsize = (uint_t)sector_size; + *capacity = (diskaddr_t)(capacity_size); + + return (0); +} + +static int +efi_get_info(int fd, struct dk_cinfo *dki_info) +{ + int rval = 0; + + // DKIOCISVIRTUAL 32bit + // DKIOCISSOLIDSTATE 32bit + char pathbuf[PATH_MAX]; + ushort_t poi; + if (fcntl(fd, F_GETPATH, pathbuf) >= 0) { + if ((strncmp(pathbuf, "/dev/disk", 9) == 0)) { + strcpy(dki_info->dki_cname, "disk"); + dki_info->dki_ctype = DKC_DIRECT; + rval = sscanf(pathbuf, "/dev/disk%hus%hu", + &poi, + &dki_info->dki_partition); + + switch (rval) { + case 0: + errno = EINVAL; + goto error; + case 1: + dki_info->dki_partition = 0; + } + strlcpy(dki_info->dki_dname, + &pathbuf[5], + sizeof (dki_info->dki_dname)); + } + + /* + * rdisk in OSX do not have partitions, also it will fail. Use + * disk instead. + */ + if ((strncmp(pathbuf, "/dev/rdisk", 10) == 0)) { + strcpy(dki_info->dki_cname, "disk"); + dki_info->dki_ctype = DKC_DIRECT; + dki_info->dki_partition = 0; + + rval = sscanf(pathbuf, "/dev/r%[a-zA-Z0-9]", + dki_info->dki_dname); + } + + if (efi_debug) + (void) fprintf(stderr, + "rval %d, name '%s' and part %d\n", + rval, + dki_info->dki_dname, + dki_info->dki_partition); + } + + if (osx_device_isvirtual(dki_info->dki_dname)) { + dki_info->dki_ctype = DKC_VBD; + if (efi_debug) + (void) fprintf(stderr, + "'%s' is virtual\n", + pathbuf); + } else { + if (efi_debug) + (void) fprintf(stderr, + "'%s' is not virtual\n", + pathbuf); + } + + 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); + + error = 0; + break; + + default: + if (efi_debug) + (void) fprintf(stderr, "unsupported ioctl()\n"); + + errno = EIO; + return (-1); + } + + return (error); +} + +int +efi_rescan(int fd) +{ + (void) fd; + 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 (memcmp(&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)); + memset(&mb, 0, sizeof (mb)); + mb.signature = LE_16(MBB_MAGIC); + } else { + (void) memcpy(&mb, buf, sizeof (mb)); + if (mb.signature != LE_16(MBB_MAGIC)) { + memset(&mb, 0, sizeof (mb)); + mb.signature = LE_16(MBB_MAGIC); + } + } + + memset(&mb.parts, 0, 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 = (uint64_t)NBLOCKS(vtoc->efi_nparts, + vtoc->efi_lbasize) * + (uint64_t)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); + } + memcpy( + &efi_parts[i].efi_gpe_UniquePartitionGUID, + &vtoc->efi_parts[i].p_uguid, + 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); +} + +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"); + } +} diff --git a/lib/libshare/os/macos/nfs.c b/lib/libshare/os/macos/nfs.c new file mode 100644 index 000000000000..791bba544ca0 --- /dev/null +++ b/lib/libshare/os/macos/nfs.c @@ -0,0 +1,404 @@ +/* + * Copyright (c) 2007 Pawel Jakub Dawidek + * 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 + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "libshare_impl.h" +#include "nfs.h" + +#define OPTSSIZE 1024 +#define MAXLINESIZE (PATH_MAX + OPTSSIZE) +#define ZFS_EXPORTS_FILE "/etc/exports" +#define ZFS_EXPORTS_LOCK ZFS_EXPORTS_FILE".lock" + +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); + } + const char *shareopts = impl_share->sa_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; + const 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) +{ + (void) shareopts; + return (SA_OK); +} + +/* + * Commit the shares by restarting mountd. + */ +static int +nfs_commit_shares(void) +{ + return (SA_OK); +} + +const sa_fstype_t libshare_nfs_type = { + .enable_share = nfs_enable_share, + .disable_share = nfs_disable_share, + .is_shared = nfs_is_shared, + + .validate_shareopts = nfs_validate_shareopts, + .commit_shares = nfs_commit_shares, +}; diff --git a/lib/libshare/os/macos/smb.c b/lib/libshare/os/macos/smb.c new file mode 100644 index 000000000000..7a60e8a0951d --- /dev/null +++ b/lib/libshare/os/macos/smb.c @@ -0,0 +1,434 @@ +/* + * 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) 2016 Jorgen Lundman + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libshare_impl.h" +#include "smb.h" + +#include +#include + +static boolean_t smb_available(void); + +#define SMB_NAME_MAX 255 +#define SHARING_CMD_PATH "/usr/sbin/sharing" + +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); + +/* + * Parse out a "value" part of a "line" of input. By skipping white space. + * If line ends up being empty, read the next line, skipping white spare. + * strdup() value before returning. + */ +static int +get_attribute(const char *attr, char *line, char **value, FILE *file) +{ + char *r = line; + char line2[512]; + + if (strncasecmp(attr, line, strlen(attr))) + return (0); + + r += strlen(attr); + + while (isspace(*r)) r++; // Skip whitespace + + // Nothing left? Read next line + if (!*r) { + if (!fgets(line2, sizeof (line2), file)) + return (0); + // Eat newlines + if ((r = strchr(line2, '\r'))) *r = 0; + if ((r = strchr(line2, '\n'))) *r = 0; + // Parse new input + r = line2; + while (isspace(*r)) r++; // Skip whitespace + } + + // Did we get something? + if (*r) { + *value = strdup(r); + return (1); + } + return (0); +} + +static int +spawn_with_pipe(const char *path, char *argv[], int flags) +{ + int fd[2]; + pid_t pid; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) != 0) + return (-1); + + pid = fork(); + + // Child + if (pid == 0) { + close(fd[0]); + dup2(fd[1], STDIN_FILENO); + dup2(fd[1], STDOUT_FILENO); + if (flags) dup2(fd[1], STDERR_FILENO); + (void) execvp(path, argv); + _exit(-1); + } + // Parent and error + close(fd[1]); + if (pid == -1) { + close(fd[0]); + return (-1); + } + return (fd[0]); +} + +/* + * Retrieve the list of SMB shares. We execute "dscl . -readall /SharePoints" + * which gets us shares in the format: + * dsAttrTypeNative:directory_path: /Volumes/BOOM/zfstest + * dsAttrTypeNative:smb_name: zfstest + * dsAttrTypeNative:smb_shared: 1 + * dsAttrTypeNative:smb_guestaccess: 1 + * + * Note that long lines can be continued on the next line, with a leading space: + * dsAttrTypeNative:smb_name: + * lundman's Public Folder + * + * We don't use "sharing -l" as its output format is "peculiar". + * + * This is a temporary implementation that should be replaced with + * direct DirectoryService API calls. + * + */ +static int +smb_retrieve_shares(void) +{ + char line[512]; + char *path = NULL, *shared = NULL, *name = NULL; + char *guest = NULL, *r; + smb_share_t *shares, *new_shares = NULL; + int fd; + FILE *file = NULL; + const char *argv[8] = { + "/usr/bin/dscl", + ".", + "-readall", + "/SharePoints" + }; + + fd = spawn_with_pipe(argv[0], (char **)argv, 0); + + if (fd < 0) + return (SA_SYSTEM_ERR); + + file = fdopen(fd, "r"); + if (!file) { + close(fd); + return (SA_SYSTEM_ERR); + } + + while (fgets(line, sizeof (line), file)) { + + if ((r = strchr(line, '\r'))) *r = 0; + if ((r = strchr(line, '\n'))) *r = 0; + + if (get_attribute("dsAttrTypeNative:smb_name:", + line, &name, file) || + get_attribute("dsAttrTypeNative:directory_path:", + line, &path, file) || + get_attribute("dsAttrTypeNative:smb_guestaccess:", + line, &guest, file) || + get_attribute("dsAttrTypeNative:smb_shared:", + line, &shared, file)) { + + // If we have all desired attributes, create a new + // share, & it is currently shared (not just listed) + if (name && path && guest && shared && + atoi(shared) != 0) { + + shares = (smb_share_t *) + malloc(sizeof (smb_share_t)); + + if (shares) { + strlcpy(shares->name, name, + sizeof (shares->name)); + strlcpy(shares->path, path, + sizeof (shares->path)); + shares->guest_ok = atoi(guest); + +#ifdef DEBUG + fprintf(stderr, + "ZFS: smbshare '%s' mount '%s'\r\n", + name, path); +#endif + + shares->next = new_shares; + new_shares = shares; + } // shares malloc + + // Make it free all variables + strlcpy(line, "-", sizeof (line)); + + } // if all + + } // if got_attribute + + if (strncmp("-", line, sizeof (line)) == 0) { + if (name) { free(name); name = NULL; } + if (path) { free(path); path = NULL; } + if (guest) { free(guest); guest = NULL; } + if (shared) { free(shared); shared = NULL; } + } // if "-" + } // while fgets + + fclose(file); + close(fd); + + if (name) { free(name); name = NULL; } + if (path) { free(path); path = NULL; } + if (guest) { free(guest); guest = NULL; } + if (shared) { free(shared); shared = NULL; } + + /* + * The ZOL implementation here just leaks the previous list in + * "smb_shares" each time this is called, and it is called a lot. + * We really should iterate through and release nodes. Alternatively + * only update if we have not run before, and have a way to force + * a refresh after enabling/disabling a share. + */ + smb_shares = new_shares; + + return (SA_OK); +} + +/* + * 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) +{ + const char *argv[10]; + int rc; + smb_share_t *shares = smb_shares; + (void) sharename; + + /* Loop through shares and check if our share is also smbshared */ + while (shares != NULL) { + if (strcmp(sharepath, shares->path) == 0) { + break; + } + shares = shares->next; + } + + /* + * CMD: sharing -a /mountpoint -s 001 -g 001 + * Where -s 001 specified sharing smb, not ftp nor afp. + * and -g 001 specifies to enable guest on smb. + * Note that the OS X 10.11 man-page incorrectly claims 010 for smb + */ + argv[0] = (const char *)SHARING_CMD_PATH; + argv[1] = "-a"; + argv[2] = sharepath; + argv[3] = "-s"; + argv[4] = "001"; + argv[5] = "-g"; + argv[6] = "001"; + argv[7] = NULL; + +#ifdef DEBUG + fprintf(stderr, + "ZFS: enabling share '%s' at '%s'\r\n", + sharename, sharepath); +#endif + + rc = libzfs_run_process(argv[0], (char **)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) +{ + const char *shareopts; + + if (!smb_available()) + return (SA_SYSTEM_ERR); + + if (smb_is_share_active(impl_share)) + smb_disable_share(impl_share); + + shareopts = impl_share->sa_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; + const char *argv[8]; + + /* CMD: sharing -r name */ + argv[0] = SHARING_CMD_PATH; + argv[1] = (char *)"-r"; + argv[2] = (char *)sharename; + argv[3] = NULL; + +#ifdef DEBUG + fprintf(stderr, "ZFS: disabling share '%s' \r\n", + sharename); +#endif + + rc = libzfs_run_process(argv[0], (char **)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); +} + +static int +smb_update_shares(void) +{ + /* Not implemented */ + return (0); +} + +const sa_fstype_t libshare_smb_type = { + .enable_share = smb_enable_share, + .disable_share = smb_disable_share, + .is_shared = smb_is_share_active, + + .validate_shareopts = smb_validate_shareopts, + .commit_shares = smb_update_shares, +}; + + +/* + * Provides a convenient wrapper for determining SMB availability + */ +static boolean_t +smb_available(void) +{ + + if (access(SHARING_CMD_PATH, F_OK) != 0) + return (B_FALSE); + + return (B_TRUE); +} diff --git a/lib/libspl/include/os/macos/Makefile.am b/lib/libspl/include/os/macos/Makefile.am new file mode 100644 index 000000000000..1d3c559bed89 --- /dev/null +++ b/lib/libspl/include/os/macos/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = mach rpc sys diff --git a/lib/libspl/include/os/macos/dirent.h b/lib/libspl/include/os/macos/dirent.h new file mode 100644 index 000000000000..b7ffe3d89cf1 --- /dev/null +++ b/lib/libspl/include/os/macos/dirent.h @@ -0,0 +1,37 @@ +/* + * 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_DIRENT_H +#define _LIBSPL_DIRENT_H + +#include_next + + +/* Handle Linux use of 64 names */ + +#define readdir64 readdir +#define dirent64 dirent + +#endif diff --git a/lib/libspl/include/os/macos/libdiskmgt.h b/lib/libspl/include/os/macos/libdiskmgt.h new file mode 100644 index 000000000000..b1a3b54e6e7f --- /dev/null +++ b/lib/libspl/include/os/macos/libdiskmgt.h @@ -0,0 +1,84 @@ +/* + * 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) 2016, Brendon Humphrey (brendon.humphrey@mac.com). + */ + +#ifndef _LIBDISKMGT_H +#define _LIBDISKMGT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* attribute definitions */ + +#define DM_USED_BY "used_by" +#define DM_USED_NAME "used_name" +#define DM_USE_MOUNT "mount" +#define DM_USE_FS "fs" +#define DM_USE_FS_NO_FORCE "fs_nf" +#define DM_USE_EXPORTED_ZPOOL "exported_zpool" +#define DM_USE_ACTIVE_ZPOOL "active_zpool" +#define DM_USE_SPARE_ZPOOL "spare_zpool" +#define DM_USE_L2CACHE_ZPOOL "l2cache_zpool" +#define DM_USE_CORESTORAGE_PV "corestorage_pv" +#define DM_USE_CORESTORAGE_LOCKED_LV "corestorage_locked_lv" +#define DM_USE_CORESTORAGE_CONVERTING_LV "corestorage_converting_lv" +#define DM_USE_CORESTORAGE_OFFLINE_LV "corestorage_offline_lv" +#define DM_USE_OS_PARTITION "reserved_os_partititon" +#define DM_USE_OS_PARTITION_NO_FORCE "reserved_os_partititon_nf" + +#define NOINUSE_SET getenv("NOINUSE_CHECK") != NULL + +typedef enum { + DM_WHO_ZPOOL = 0, + DM_WHO_ZPOOL_FORCE, + DM_WHO_ZPOOL_SPARE +} dm_who_type_t; + +/* slice stat name */ +typedef enum { + DM_SLICE_STAT_USE = 0 +} dm_slice_stat_t; + +/* + * Unlike the Solaris implementation, libdiskmgt must be initialised, + * and torn down when no longer used. + */ +void libdiskmgt_init(void); +void libdiskmgt_fini(void); + +/* + * This is a partial implementation of (or similar to) libdiskmgt, + * adapted for OSX use. + */ +int dm_in_swap_dir(const char *dev_name); +int dm_inuse(char *dev_name, char **msg, dm_who_type_t who, int *errp); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libspl/include/os/macos/mach/Makefile.am b/lib/libspl/include/os/macos/mach/Makefile.am new file mode 100644 index 000000000000..733185033dbd --- /dev/null +++ b/lib/libspl/include/os/macos/mach/Makefile.am @@ -0,0 +1,3 @@ +libspldir = $(includedir)/libspl/mach +libspl_HEADERS = \ + $(top_srcdir)/lib/libspl/include/os/macos/mach/boolean.h diff --git a/lib/libspl/include/os/macos/mach/boolean.h b/lib/libspl/include/os/macos/mach/boolean.h new file mode 100644 index 000000000000..47c93a3151b7 --- /dev/null +++ b/lib/libspl/include/os/macos/mach/boolean.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 (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 + */ + +/* Deal with XNU's own boolean_t version */ + +#define boolean_t xnu_boolean_t +#include_next +#undef boolean_t diff --git a/lib/libspl/include/os/macos/mach/task.h b/lib/libspl/include/os/macos/mach/task.h new file mode 100644 index 000000000000..eb4f9f877f7a --- /dev/null +++ b/lib/libspl/include/os/macos/mach/task.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 + */ + +#ifndef _SPL_MACH_TASK_H +#define _SPL_MACH_TASK_H + +#define thread_create xnu_thread_create +#include_next +#undef thread_create + +#endif diff --git a/lib/libspl/include/os/macos/mntent.h b/lib/libspl/include/os/macos/mntent.h new file mode 100644 index 000000000000..8183dda00b7f --- /dev/null +++ b/lib/libspl/include/os/macos/mntent.h @@ -0,0 +1,144 @@ +/* + * 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) 1984, 1986, 1987, 1988, 1989 AT&T + * All Rights Reserved + */ + +#ifndef _SYS_MNTENT_H +#define _SYS_MNTENT_H + + +#ifdef __cplusplus +extern "C" { +#endif + +#define MNTTAB "/etc/mnttab" +#define VFSTAB "/etc/vfstab" +#define MNTMAXSTR 128 + +#define MNTTYPE_ZFS "zfs" /* ZFS file system */ +#define MNTTYPE_UFS "ufs" /* Unix file system */ +#define MNTTYPE_NFS "nfs" /* NFS file system */ +#define MNTTYPE_NFS3 "nfs3" /* NFS Version 3 file system */ +#define MNTTYPE_NFS4 "nfs4" /* NFS Version 4 file system */ +#define MNTTYPE_CACHEFS "cachefs" /* Cache File System */ +#define MNTTYPE_PCFS "pcfs" /* PC (MSDOS) file system */ +#define MNTTYPE_PC MNTTYPE_PCFS /* Deprecated name; use MNTTYPE_PCFS */ +#define MNTTYPE_LOFS "lofs" /* Loop back file system */ +#define MNTTYPE_LO MNTTYPE_LOFS /* Deprecated name; use MNTTYPE_LOFS */ +#define MNTTYPE_HSFS "hsfs" /* High Sierra (9660) file system */ +#define MNTTYPE_SWAP "swap" /* Swap file system */ +#define MNTTYPE_TMPFS "tmpfs" /* Tmp volatile file system */ +#define MNTTYPE_AUTOFS "autofs" /* Automounter ``file'' system */ +#define MNTTYPE_MNTFS "mntfs" /* In-kernel mnttab */ +#define MNTTYPE_DEV "dev" /* /dev file system */ +#define MNTTYPE_CTFS "ctfs" /* Contract file system */ +#define MNTTYPE_OBJFS "objfs" /* Kernel object file system */ +#define MNTTYPE_SHAREFS "sharefs" /* Kernel sharetab file system */ + + +#define MNTOPT_RO "ro" /* Read only */ +#define MNTOPT_RW "rw" /* Read/write */ +#define MNTOPT_RQ "rq" /* Read/write with quotas */ +#define MNTOPT_QUOTA "quota" /* Check quotas */ +#define MNTOPT_NOQUOTA "noquota" /* Don't check quotas */ +#define MNTOPT_ONERROR "onerror" /* action to taken on error */ +#define MNTOPT_SOFT "soft" /* Soft mount */ +#define MNTOPT_SEMISOFT "semisoft" /* partial soft, uncommited interface */ +#define MNTOPT_HARD "hard" /* Hard mount */ +#define MNTOPT_SUID "suid" /* Both setuid and devices allowed */ +#define MNTOPT_NOSUID "nosuid" /* Neither setuid nor devices allowed */ +#define MNTOPT_DEVICES "devices" /* Device-special allowed */ +#define MNTOPT_NODEVICES "nodevices" /* Device-special disallowed */ +#define MNTOPT_SETUID "setuid" /* Set uid allowed */ +#define MNTOPT_NOSETUID "nosetuid" /* Set uid not allowed */ +#define MNTOPT_GRPID "grpid" /* SysV-compatible gid on create */ +#define MNTOPT_REMOUNT "remount" /* Change mount options */ +#define MNTOPT_NOSUB "nosub" /* Disallow mounts on subdirs */ +#define MNTOPT_MULTI "multi" /* Do multi-component lookup */ +#define MNTOPT_INTR "intr" /* Allow NFS ops to be interrupted */ +#define MNTOPT_NOINTR "nointr" /* Don't allow interrupted ops */ +#define MNTOPT_PORT "port" /* NFS server IP port number */ +#define MNTOPT_SECURE "secure" /* Secure (AUTH_DES) mounting */ +#define MNTOPT_RSIZE "rsize" /* Max NFS read size (bytes) */ +#define MNTOPT_WSIZE "wsize" /* Max NFS write size (bytes) */ +#define MNTOPT_TIMEO "timeo" /* NFS timeout (1/10 sec) */ +#define MNTOPT_RETRANS "retrans" /* Max retransmissions (soft mnts) */ +#define MNTOPT_ACTIMEO "actimeo" /* Attr cache timeout (sec) */ +#define MNTOPT_ACREGMIN "acregmin" /* Min attr cache timeout (files) */ +#define MNTOPT_ACREGMAX "acregmax" /* Max attr cache timeout (files) */ +#define MNTOPT_ACDIRMIN "acdirmin" /* Min attr cache timeout (dirs) */ +#define MNTOPT_ACDIRMAX "acdirmax" /* Max attr cache timeout (dirs) */ +#define MNTOPT_NOAC "noac" /* Don't cache attributes at all */ +#define MNTOPT_NOCTO "nocto" /* No close-to-open consistency */ +#define MNTOPT_BG "bg" /* Do mount retries in background */ +#define MNTOPT_FG "fg" /* Do mount retries in foreground */ +#define MNTOPT_RETRY "retry" /* Number of mount retries */ +#define MNTOPT_DEV "dev" /* Device id of mounted fs */ +#define MNTOPT_POSIX "posix" /* Get static pathconf for mount */ +#define MNTOPT_MAP "map" /* Automount map */ +#define MNTOPT_DIRECT "direct" /* Automount direct map mount */ +#define MNTOPT_INDIRECT "indirect" /* Automount indirect map mount */ +#define MNTOPT_LLOCK "llock" /* Local locking (no lock manager) */ +#define MNTOPT_IGNORE "ignore" /* Ignore this entry */ +#define MNTOPT_VERS "vers" /* protocol version number indicator */ +#define MNTOPT_PROTO "proto" /* protocol network_id indicator */ +#define MNTOPT_SEC "sec" /* Security flavor indicator */ +#define MNTOPT_SYNCDIR "syncdir" /* Synchronous local directory ops */ +#define MNTOPT_NOSETSEC "nosec" /* Do no allow setting sec attrs */ +#define MNTOPT_NOPRINT "noprint" /* Do not print messages */ +#define MNTOPT_LARGEFILES "largefiles" /* allow large files */ +#define MNTOPT_NOLARGEFILES "nolargefiles" /* don't allow large files */ +#define MNTOPT_FORCEDIRECTIO "forcedirectio" /* Force DirectIO on all files */ +#define MNTOPT_NOFORCEDIRECTIO "noforcedirectio" /* No Force DirectIO */ +#define MNTOPT_DISABLEDIRECTIO "disabledirectio" /* Disable DirectIO ioctls */ +#define MNTOPT_PUBLIC "public" /* Use NFS public file handlee */ +#define MNTOPT_LOGGING "logging" /* enable logging */ +#define MNTOPT_NOLOGGING "nologging" /* disable logging */ +#define MNTOPT_ATIME "atime" /* update atime for files */ +#define MNTOPT_NOATIME "noatime" /* do not update atime for files */ +#define MNTOPT_GLOBAL "global" /* Cluster-wide global mount */ +#define MNTOPT_NOGLOBAL "noglobal" /* Mount local to single node */ +#define MNTOPT_DFRATIME "dfratime" /* Deferred access time updates */ +#define MNTOPT_NODFRATIME "nodfratime" /* No Deferred access time updates */ +#define MNTOPT_NBMAND "nbmand" /* allow non-blocking mandatory locks */ +#define MNTOPT_NONBMAND "nonbmand" /* deny non-blocking mandatory locks */ +#define MNTOPT_XATTR "xattr" /* enable extended attributes */ +#define MNTOPT_NOXATTR "noxattr" /* disable extended attributes */ +#define MNTOPT_EXEC "exec" /* enable executables */ +#define MNTOPT_NOEXEC "noexec" /* disable executables */ +#define MNTOPT_RESTRICT "restrict" /* restricted autofs mount */ +#define MNTOPT_BROWSE "browse" /* browsable autofs mount */ +#define MNTOPT_NOBROWSE "nobrowse" /* non-browsable autofs mount */ +/* VFS will not ignore ownership information on filesystem objects */ +#define MNTOPT_OWNERS "owners" +/* VFS will ignore ownership information on filesystem objects */ +#define MNTOPT_NOOWNERS "noowners" + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_MNTENT_H */ diff --git a/lib/libspl/include/os/macos/poll.h b/lib/libspl/include/os/macos/poll.h new file mode 100644 index 000000000000..2bb5203d00c4 --- /dev/null +++ b/lib/libspl/include/os/macos/poll.h @@ -0,0 +1,31 @@ +/* + * 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 _LIBSPL_POLL_H +#define _LIBSPL_POLL_H + +#include_next + +#ifndef O_DIRECT +#define O_DIRECT 0 +#endif + +#endif diff --git a/lib/libspl/include/os/macos/pthread.h b/lib/libspl/include/os/macos/pthread.h new file mode 100644 index 000000000000..b2cd6f51d1e9 --- /dev/null +++ b/lib/libspl/include/os/macos/pthread.h @@ -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 + */ + +#ifndef _LIBSPL_PTHREAD_H +#define _LIBSPL_PTHREAD_H + +#include_next + +/* + * macOS does not have pthread_setname_np(tid, name) but rather their own + * pthread_setname_np(name); which sets the name from inside the thread. + * As we have been unable to find a macOS utility that actually displays + * the thread-names (let us know if you find one) we will skip setting + * names for (userland) threads. + */ +#define pthread_setname_np(tid, name) + +#endif diff --git a/lib/libspl/include/os/macos/rpc/Makefile.am b/lib/libspl/include/os/macos/rpc/Makefile.am new file mode 100644 index 000000000000..645ec772f92e --- /dev/null +++ b/lib/libspl/include/os/macos/rpc/Makefile.am @@ -0,0 +1,3 @@ +libspldir = $(includedir)/libspl/sys +libspl_HEADERS = \ + $(top_srcdir)/lib/libspl/include/os/macos/rpc/xdr.h diff --git a/lib/libspl/include/os/macos/rpc/xdr.h b/lib/libspl/include/os/macos/rpc/xdr.h new file mode 100644 index 000000000000..3dcbf2ab1b8c --- /dev/null +++ b/lib/libspl/include/os/macos/rpc/xdr.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 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_MACOS_RPC_XDR_H +#define LIBSPL_MACOS_RPC_XDR_H + +#include +#include_next + +// -Wincompatible-pointer-types + +#endif /* LIBSPL_MACOS_RPC_XDR_H */ diff --git a/lib/libspl/include/os/macos/stdio.h b/lib/libspl/include/os/macos/stdio.h new file mode 100644 index 000000000000..50294f658ed5 --- /dev/null +++ b/lib/libspl/include/os/macos/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. + */ + +#ifndef _LIBSPL_OSX_STDIO_H +#define _LIBSPL_OSX_STDIO_H + +#define dprintf xnu_dprintf +#include_next +#undef dprintf +#define dprintf printf +#endif diff --git a/lib/libspl/include/os/macos/stdlib.h b/lib/libspl/include/os/macos/stdlib.h new file mode 100644 index 000000000000..e95d4f65deb9 --- /dev/null +++ b/lib/libspl/include/os/macos/stdlib.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 + */ +#ifndef _LIBSPL_OSX_STDLIB_H +#define _LIBSPL_OSX_STDLIB_H + +#include_next +#include +#include + +#endif diff --git a/lib/libspl/include/os/macos/string.h b/lib/libspl/include/os/macos/string.h new file mode 100644 index 000000000000..c094d836f377 --- /dev/null +++ b/lib/libspl/include/os/macos/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 + */ + +#ifndef _LIBSPL_OSX_STRING_H +#define _LIBSPL_OSX_STRING_H + +#include_next + +/* OsX will assert if src == dst */ +static inline size_t +spl_strlcpy(char *__dst, const char *__source, size_t __size) +{ + if (__dst == __source) + return (0); + return (strlcpy(__dst, __source, __size)); +} + +#undef strlcpy +#define strlcpy spl_strlcpy + +#endif /* _LIBSPL_OSX_STRING_H */ diff --git a/lib/libspl/include/os/macos/synch.h b/lib/libspl/include/os/macos/synch.h new file mode 100644 index 000000000000..28bc1820869c --- /dev/null +++ b/lib/libspl/include/os/macos/synch.h @@ -0,0 +1,82 @@ +/* + * 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. + * Copyright 2014 Zettabyte Software, LLC. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _LIBSPL_SYNCH_H +#define _LIBSPL_SYNCH_H + +#ifndef __sun__ + +#include +#include + +/* + * Definitions of synchronization types. + */ +#define USYNC_THREAD 0x00 /* private to a process */ +#define USYNC_PROCESS 0x01 /* shared by processes */ + +typedef pthread_rwlock_t rwlock_t; + +#define DEFAULTRWLOCK PTHREAD_RWLOCK_INITIALIZER + +static inline int +rwlock_init(rwlock_t *rwlp, int type, void *arg) +{ + pthread_rwlockattr_t attr; + int err = 0; + (void) arg; + + VERIFY0(pthread_rwlockattr_init(&attr)); + switch (type) { + case USYNC_THREAD: + VERIFY0(pthread_rwlockattr_setpshared(&attr, + PTHREAD_PROCESS_PRIVATE)); + break; + case USYNC_PROCESS: + VERIFY0(pthread_rwlockattr_setpshared(&attr, + PTHREAD_PROCESS_SHARED)); + break; + default: + VERIFY0(1); + } + + err = pthread_rwlock_init(rwlp, &attr); + VERIFY0(pthread_rwlockattr_destroy(&attr)); + + return (err); +} + +#define rwlock_destroy(x) pthread_rwlock_destroy((x)) +#define rw_rdlock(x) pthread_rwlock_rdlock((x)) +#define rw_wrlock(x) pthread_rwlock_wrlock((x)) +#define rw_unlock(x) pthread_rwlock_unlock((x)) +#define rw_tryrdlock(x) pthread_rwlock_tryrdlock((x)) +#define rw_trywrlock(x) pthread_rwlock_trywrlock((x)) + +#endif /* __sun__ */ + +#endif diff --git a/lib/libspl/include/os/macos/sys/Makefile.am b/lib/libspl/include/os/macos/sys/Makefile.am new file mode 100644 index 000000000000..fb063b875c35 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/Makefile.am @@ -0,0 +1,17 @@ +libspldir = $(includedir)/libspl/sys +libspl_HEADERS = \ + $(top_srcdir)/lib/libspl/include/os/macos/mach/boolean.h \ + $(top_srcdir)/lib/libspl/include/os/macos/rpc/xdr.h \ + $(top_srcdir)/lib/libspl/include/os/macos/sys/byteorder.h \ + $(top_srcdir)/lib/libspl/include/os/macos/sys/errno.h \ + $(top_srcdir)/lib/libspl/include/os/macos/sys/file.h \ + $(top_srcdir)/lib/libspl/include/os/macos/sys/kernel_types.h \ + $(top_srcdir)/lib/libspl/include/os/macos/sys/mnttab.h \ + $(top_srcdir)/lib/libspl/include/os/macos/sys/mount.h \ + $(top_srcdir)/lib/libspl/include/os/macos/sys/param.h \ + $(top_srcdir)/lib/libspl/include/os/macos/sys/stat.h \ + $(top_srcdir)/lib/libspl/include/os/macos/sys/sysmacros.h \ + $(top_srcdir)/lib/libspl/include/os/macos/sys/uio.h \ + $(top_srcdir)/lib/libspl/include/os/macos/sys/vfs.h \ + $(top_srcdir)/lib/libspl/include/os/macos/sys/xattr.h \ + $(top_srcdir)/lib/libspl/include/os/macos/sys/zfs_context_os.h diff --git a/lib/libspl/include/os/macos/sys/aarch64/asm_linkage.h b/lib/libspl/include/os/macos/sys/aarch64/asm_linkage.h new file mode 100644 index 000000000000..56c2364d3817 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/aarch64/asm_linkage.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 (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 https://opensource.org/licenses/CDDL-1.0. + * 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 _AARCH64_SYS_ASM_LINKAGE_H +#define _AARCH64_SYS_ASM_LINKAGE_H + +/* You can set to nothing on Unix platforms */ +#undef ASMABI +#define ASMABI __attribute__((sysv_abi)) + +#define SECTION_TEXT .text +#define SECTION_STATIC .const +#define SECTION_STATIC1(...) .const +// #define SECTION_STATIC1(...) .rodata##__VA_ARGS__ + +#define ASM_ENTRY_ALIGN 16 + +#define PAGE @PAGE +#define PAGEOFF @PAGEOFF +// Linux has them empty +// #define PAGE +// #define PAGEOFF + +/* + * semi-colon is comment, so use secret %% + * M1 is 64 bit only + * and needs "_" prepended, but we add one without, in case + * the assembler function needs to call itself + */ +#define ENTRY(x) \ + .text %% \ + .balign ASM_ENTRY_ALIGN %% \ + .globl _##x %% \ +_##x: %% \ +x: + +#define ENTRY_ALIGN(x, a) \ + .text %% \ + .balign a %% \ + .globl _##x %% \ +_##x: %% \ +x: + +#define FUNCTION(x) \ +x: + +#define SET_SIZE(x) + +#define SET_OBJ(x) + + +#endif diff --git a/lib/libspl/include/os/macos/sys/byteorder.h b/lib/libspl/include/os/macos/sys/byteorder.h new file mode 100644 index 000000000000..f925e9529e61 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/byteorder.h @@ -0,0 +1,288 @@ +/* + * 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 +#include <_types.h> + +/* + * Define the order of 32-bit words in 64-bit words. + */ +#define _QUAD_HIGHWORD 1 +#define _QUAD_LOWWORD 0 + +/* + * Definitions for byte order, according to byte significance from low + * address to high. + */ +#undef _LITTLE_ENDIAN +/* LSB first: i386, vax */ +#define _LITTLE_ENDIAN 1234 +/* LSB first in word, MSW first in long */ +#define _PDP_ENDIAN 3412 + +#define _BYTE_ORDER _LITTLE_ENDIAN + +/* + * Deprecated variants that don't have enough underscores to be useful in more + * strict namespaces. + */ +#if __BSD_VISIBLE +#define LITTLE_ENDIAN _LITTLE_ENDIAN +#define PDP_ENDIAN _PDP_ENDIAN +#define BYTE_ORDER _BYTE_ORDER +#endif + +#define __bswap16_gen(x) (__uint16_t)((x) << 8 | (x) >> 8) +#define __bswap32_gen(x) \ + (((__uint32_t)__bswap16((x) & 0xffff) << 16) | __bswap16((x) >> 16)) +#define __bswap64_gen(x) \ + (((__uint64_t)__bswap32((x) & 0xffffffff) << 32) | __bswap32((x) >> 32)) + +#ifdef __GNUCLIKE_BUILTIN_CONSTANT_P +#define __bswap16(x) \ + ((__uint16_t)(__builtin_constant_p(x) ? \ + __bswap16_gen((__uint16_t)(x)) : __bswap16_var(x))) +#define __bswap32(x) \ + (__builtin_constant_p(x) ? \ + __bswap32_gen((__uint32_t)(x)) : __bswap32_var(x)) +#define __bswap64(x) \ + (__builtin_constant_p(x) ? \ + __bswap64_gen((__uint64_t)(x)) : __bswap64_var(x)) +#else +/* XXX these are broken for use in static initializers. */ +#define __bswap16(x) __bswap16_var(x) +#define __bswap32(x) __bswap32_var(x) +#define __bswap64(x) __bswap64_var(x) +#endif + +/* These are defined as functions to avoid multiple evaluation of x. */ + +static __inline __uint16_t +__bswap16_var(__uint16_t _x) +{ + + return (__bswap16_gen(_x)); +} + +static __inline __uint32_t +__bswap32_var(__uint32_t _x) +{ + +#ifdef __GNUCLIKE_ASM + __asm("bswap %0" : "+r" (_x)); + return (_x); +#else + return (__bswap32_gen(_x)); +#endif +} +#define __htonl(x) __bswap32(x) +#define __htons(x) __bswap16(x) +#define __ntohl(x) __bswap32(x) +#define __ntohs(x) __bswap16(x) + +#include +#include + +#if defined(__GNUC__) && defined(_ASM_INLINES) && \ + (defined(__i386) || defined(__amd64)) +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * macros for conversion between host and (internet) network byte order + */ + +#if defined(_BIG_ENDIAN) && !defined(ntohl) && !defined(__lint) +/* big-endian */ +#if defined(_BIG_ENDIAN) && (defined(__amd64__) || defined(__amd64)) +#error "incompatible ENDIAN / ARCH combination" +#endif +#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 + +/* 10.8 is lacking in htonll */ +#if !defined(htonll) +#define htonll(x) __DARWIN_OSSwapInt64(x) +#endif + +#if !defined(ntohll) +#define ntohll(x) __DARWIN_OSSwapInt64(x) +#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 _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 + +/* + * 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/macos/sys/errno.h b/lib/libspl/include/os/macos/sys/errno.h new file mode 100644 index 000000000000..af4846ebb903 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/errno.h @@ -0,0 +1,31 @@ +/* + * 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_next + +#define EBADE EBADMACHO +#define ECKSUM EBADE +#define EFRAGS EIDRM +#define EREMOTEIO ENOLINK +#define ENOTACTIVE ENOPOLICY +#define ECHRNG EMULTIHOP + +#define ERESTART (-1) /* restart syscall */ diff --git a/lib/libspl/include/os/macos/sys/fcntl.h b/lib/libspl/include/os/macos/sys/fcntl.h new file mode 100644 index 000000000000..3c3cebd39c79 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/fcntl.h @@ -0,0 +1,75 @@ +/* + * 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 _LIBSPL_SYS_FCNTL_H +#define _LIBSPL_SYS_FCNTL_H + +#include_next + +#define O_LARGEFILE 0 +#define O_RSYNC 0 + +#ifndef O_DIRECT +#define O_DIRECT 0 +#endif + +#include +#include + +#if !defined(MAC_OS_X_VERSION_10_10) || \ + (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) + +#define AT_FDCWD -2 +#include +#include +#include +static inline int +openat(int fd, const char *path, int oflag, ...) +{ + va_list arg; + mode_t mode = 0; + char dir[PATH_MAX], fullpath[PATH_MAX]; + if (oflag & O_CREAT) { + va_start(arg, oflag); + mode = va_arg(arg, mode_t); + va_end(arg); + } + if (fd == AT_FDCWD || path[0] == '/') + return (open(path, oflag, mode)); + if (fcntl(fd, F_GETPATH, dir) == -1) + return (-1); + snprintf(fullpath, sizeof (fullpath), "%s/%s", dir, path); + return (open(fullpath, oflag, mode)); +} + +#include +static DIR * +fdopendir(int fd) +{ + char dir[PATH_MAX]; + if (fcntl(fd, F_GETPATH, dir) == -1) + return (NULL); + return (opendir(dir)); +} +#endif + +#endif diff --git a/lib/libspl/include/os/macos/sys/file.h b/lib/libspl/include/os/macos/sys/file.h new file mode 100644 index 000000000000..94a33cbb336e --- /dev/null +++ b/lib/libspl/include/os/macos/sys/file.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 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 + +#define FCREAT O_CREAT +#define FTRUNC O_TRUNC +#define FOFFMAX 0 +#define FSYNC O_SYNC +#define FDSYNC O_DSYNC +#define FRSYNC O_RSYNC +#define FEXCL O_EXCL + +#define IO_DIRECT 0 + +#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/macos/sys/ia32/asm_linkage.h b/lib/libspl/include/os/macos/sys/ia32/asm_linkage.h new file mode 100644 index 000000000000..e55d4b440104 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/ia32/asm_linkage.h @@ -0,0 +1,185 @@ +/* + * 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 https://opensource.org/licenses/CDDL-1.0. + * 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 _IA32_SYS_ASM_LINKAGE_H +#define _IA32_SYS_ASM_LINKAGE_H + +#ifndef ENDBR +#define ENDBR +#endif +#ifndef RET +#define RET ret +#endif + +/* You can set to nothing on Unix platforms */ +#undef ASMABI +#define ASMABI __attribute__((sysv_abi)) + +#define SECTION_TEXT .text +#define SECTION_STATIC .const + +#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 +#define XMM_ALIGN_LOG 16 + +/* + * 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. + */ +#undef ENTRY +#define ENTRY(x) \ + .text; \ + .balign ASM_ENTRY_ALIGN; \ + .globl x; \ +x: + +#define ENTRY_NP(x) \ + .text; \ + .balign ASM_ENTRY_ALIGN; \ + .globl _##x; \ +_##x: + + +#define ENTRY_ALIGN(x, a) \ + .text; \ + .balign a; \ + .globl _##x; \ +_##x: + +#define FUNCTION(x) \ +x: + +/* + * ENTRY2 is identical to ENTRY but provides two labels for the entry point. + */ +#define ENTRY2(x, y) \ + .text; \ + .balign ASM_ENTRY_ALIGN; \ + .globl x, y; \ +x:; \ +y: + +#define ENTRY_NP2(x, y) \ + .text; \ + .balign ASM_ENTRY_ALIGN; \ + .globl x, y; \ +x:; \ +y: + + +/* + * SET_SIZE trails a function and set the size for the ELF symbol table. + */ +#define SET_SIZE(x) + +#define SET_OBJ(x) + +#endif /* _ASM */ + +#ifdef __cplusplus +} +#endif + +#endif /* _IA32_SYS_ASM_LINKAGE_H */ diff --git a/lib/libspl/include/os/macos/sys/kernel_types.h b/lib/libspl/include/os/macos/sys/kernel_types.h new file mode 100644 index 000000000000..5796351a2024 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/kernel_types.h @@ -0,0 +1,43 @@ +/* + * 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 LIBSPL_SYS_KERNEL_TYPES_H +#define LIBSPL_SYS_KERNEL_TYPES_H + +/* + * Unfortunately, XNU defines uio_t, proc_t and vnode_t differently to + * ZFS, so we need to hack around it. + */ + +#undef vnode_t +#undef uio_t +#define proc_t kernel_proc_t +#include_next +#define vnode_t struct vnode +#define uio_t struct uio +#undef proc_t + + +/* Other missing Linux types */ +typedef off_t loff_t; + +#endif diff --git a/lib/libspl/include/os/macos/sys/misc.h b/lib/libspl/include/os/macos/sys/misc.h new file mode 100644 index 000000000000..ac49138f21a0 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/misc.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 _LIBSPL_SYS_MISC_H +#define _LIBSPL_SYS_MISC_H + +#endif diff --git a/lib/libspl/include/os/macos/sys/mnttab.h b/lib/libspl/include/os/macos/sys/mnttab.h new file mode 100644 index 000000000000..4e8a7c6b1302 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/mnttab.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, 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 +#include +#include +#include + +#ifdef MNTTAB +#undef MNTTAB +#endif /* MNTTAB */ + +#include +#include +#define MNTTAB _PATH_DEVNULL +#define MS_NOMNTTAB 0x0 +#define MS_RDONLY 0x1 +#define umount2(p, f) unmount(p, f) +#define MNT_LINE_MAX 4096 + +#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; + uint_t mnt_major; + uint_t mnt_minor; + uint32_t mnt_fssubtype; +}; + +#define extmnttab mnttab + +struct stat64; +struct statfs; + +extern DIR *fdopendir(int fd); +extern int openat64(int, const char *, int, ...); + +extern int getmntany(FILE *fd, struct mnttab *mgetp, struct mnttab *mrefp); +extern int getmntent(FILE *fp, struct mnttab *mp); +extern char *hasmntopt(struct mnttab *mnt, const char *opt); +extern int getextmntent(const char *path, struct extmnttab *entry, + struct stat64 *statbuf); + +extern void statfs2mnttab(struct statfs *sfs, struct mnttab *mp); + +#ifndef AT_SYMLINK_NOFOLLOW +#define AT_SYMLINK_NOFOLLOW 0x100 +#endif + +extern int fstatat64(int, const char *, struct stat *, int); + +#endif diff --git a/lib/libspl/include/os/macos/sys/mount.h b/lib/libspl/include/os/macos/sys/mount.h new file mode 100644 index 000000000000..0153b7f53583 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/mount.h @@ -0,0 +1,87 @@ +/* + * 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 +#include +#include +#include +#include + +/* Unfortunately, XNU has a different meaning for "vnode_t". */ +#undef vnode_t +#include_next +#define vnode_t struct vnode + +/* + * 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 MNT_NODEV +#define S_WRITE 0 +#define MS_BIND 0 +#define MS_REMOUNT MNT_UPDATE +#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 +#define MS_FORCE MNT_FORCE +#define MS_DETACH MNT_DETACH +#define MS_OVERLAY MNT_UNION +#define MS_CRYPT MNT_CPROTECT + +#endif /* _LIBSPL_SYS_MOUNT_H */ diff --git a/lib/libspl/include/os/macos/sys/param.h b/lib/libspl/include/os/macos/sys/param.h new file mode 100644 index 000000000000..ca5bb154e3a0 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/param.h @@ -0,0 +1,62 @@ +/* + * 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 +#include + +/* + * 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 + +#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 */ + +extern size_t spl_pagesize(void); +#define PAGESIZE (spl_pagesize()) + +extern int execvpe(const char *name, char * const argv[], char * const envp[]); + +struct zfs_handle; + +#endif diff --git a/lib/libspl/include/os/macos/sys/stat.h b/lib/libspl/include/os/macos/sys/stat.h new file mode 100644 index 000000000000..1c7858194db6 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/stat.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 (c) 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#ifndef _LIBSPL_SYS_STAT_H +#define _LIBSPL_SYS_STAT_H + +#include_next + +#include +#include /* for BLKGETSIZE64 */ + +#define MAXOFFSET_T OFF_MAX + +#ifndef _KERNEL +#include +#endif + +static inline int +fstat_blk(int fd, struct stat *st) +{ + if (fstat(fd, st) == -1) + return (-1); + + /* In OS X we need to use ioctl to get the size of a block dev */ + if (st->st_mode & (S_IFBLK | S_IFCHR)) { + uint32_t blksize; + uint64_t blkcnt; + + if (ioctl(fd, DKIOCGETBLOCKSIZE, &blksize) < 0) { + return (-1); + } + if (ioctl(fd, DKIOCGETBLOCKCOUNT, &blkcnt) < 0) { + return (-1); + } + + st->st_size = (off_t)((uint64_t)blksize * blkcnt); + } + + return (0); +} + + +/* + * Deal with Linux use of 64 for everything. + * OsX has moved past it, dropped all 32 versions, and + * standard form is 64 bit. + */ + +#define stat64 stat +#define lstat64 lstat +#define fstat64 fstat +#define fstat64_blk fstat_blk +#define statfs64 statfs + +#endif /* _LIBSPL_SYS_STAT_H */ diff --git a/lib/libspl/include/os/macos/sys/sysmacros.h b/lib/libspl/include/os/macos/sys/sysmacros.h new file mode 100644 index 000000000000..7480eb85a559 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/sysmacros.h @@ -0,0 +1,105 @@ +/* + * 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 + +/* 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) & -(align))) +#define P2ROUNDUP_TYPED(x, align, type) \ + (-(-(type)(x) & -(type)(align))) +#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) & -(type)(align))) +#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 version */ +#if defined(_KERNEL) && !defined(_KMEMUSER) && !defined(offsetof) +#define offsetof(s, m) ((size_t)(&(((s *)0)->m))) +#endif + +#ifndef RLIM64_INFINITY +#define RLIM64_INFINITY (~0ULL) +#endif + +#endif /* _LIBSPL_SYS_SYSMACROS_H */ diff --git a/lib/libspl/include/os/macos/sys/time.h b/lib/libspl/include/os/macos/sys/time.h new file mode 100644 index 000000000000..9212f43f4c0d --- /dev/null +++ b/lib/libspl/include/os/macos/sys/time.h @@ -0,0 +1,79 @@ +/* + * 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 _LIBSPL_SYS_OSX_TIME_H +#define _LIBSPL_SYS_OSX_TIME_H + +#include_next +#include +#include + +/* + * clock_gettime() is defined from 10.12 (High Sierra) onwards. + * For older platforms, we define in here. + */ + +#if !defined(MAC_OS_X_VERSION_10_12) || \ + (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12) + +#include +#include +#include +#include +#include +#include + + +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC_RAW 4 +#define CLOCK_MONOTONIC 6 + +static inline int +clock_gettime(clockid_t clk_id, struct timespec *tp) +{ + int retval = 0; + struct timeval now; + clock_serv_t cclock; + mach_timespec_t mts; + + switch (clk_id) { + case CLOCK_MONOTONIC_RAW: + case CLOCK_MONOTONIC: + + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, + &cclock); + retval = clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + + tp->tv_sec = mts.tv_sec; + tp->tv_nsec = mts.tv_nsec; + break; + case CLOCK_REALTIME: + gettimeofday(&now, NULL); + tp->tv_sec = now.tv_sec; + tp->tv_nsec = now.tv_usec * 1000; + break; + } + return (retval); +} +#endif + +#endif diff --git a/lib/libspl/include/os/macos/sys/uio.h b/lib/libspl/include/os/macos/sys/uio.h new file mode 100644 index 000000000000..d08c8569ced6 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/uio.h @@ -0,0 +1,177 @@ +/* + * 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_OSX_SYS_UIO_H +#define _LIBSPL_OSX_SYS_UIO_H + +#include +#include_next + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include + +/* + * I/O parameter information. A uio structure describes the I/O which + * is to be performed by an operation. Typically the data movement will + * be performed by a routine such as uiomove(), which updates the uio + * structure to reflect what was done. + */ + +ssize_t readv(int, const struct iovec *, int); +ssize_t writev(int, const struct iovec *, int); + +/* + * I/O direction. + */ + +/* + * Segment flag values. + */ +typedef enum uio_seg { UIO_USERSPACE, UIO_SYSSPACE, UIO_USERISPACE } uio_seg_t; + +typedef enum uio_seg zfs_uio_seg_t; + +struct uio { + struct iovec *uio_iov; /* pointer to array of iovecs */ + int uio_iovcnt; /* number of iovecs */ + off_t uio_offset; /* file offset */ + uio_seg_t uio_segflg; /* address space (kernel or user) */ + off_t uio_limit; /* u-limit (maximum byte offset) */ + ssize_t uio_resid; /* residual count */ + enum uio_rw uio_rw; + int uio_max_iovs; /* max iovecs this uio_t can hold */ + uint32_t uio_index; /* Current index */ +}; + + +uio_t *uio_create(int iovcount, off_t offset, int spacetype, int iodirection); +void uio_free(uio_t *uio); +int uio_addiov(uio_t *uio, user_addr_t baseaddr, user_size_t length); +int uio_isuserspace(uio_t *uio); +int uio_getiov(uio_t *uio, int index, user_addr_t *baseaddr, + user_size_t *length); +int uio_iovcnt(uio_t *uio); +off_t uio_offset(uio_t *uio); +void uio_update(uio_t *uio, user_size_t count); +uint64_t uio_resid(uio_t *uio); +user_addr_t uio_curriovbase(uio_t *uio); +user_size_t uio_curriovlen(uio_t *uio); +void uio_setoffset(uio_t *uio, off_t a_offset); +uio_t *uio_duplicate(uio_t *uio); +int uio_rw(uio_t *a_uio); +void uio_setrw(uio_t *a_uio, int a_value); + +int uiomove(void *, uint32_t, enum uio_rw, struct uio *); +int spllib_uiomove(const uint8_t *, uint32_t, struct uio *); +void uioskip(struct uio *, uint32_t); +int uiodup(struct uio *, struct uio *, struct iovec *, int); + +// xuio struct is not used in this platform, but we define it +// to allow compilation and easier patching +typedef enum xuio_type { + UIOTYPE_ASYNCIO, + UIOTYPE_ZEROCOPY, +} xuio_type_t; + + +#define UIOA_IOV_MAX 16 + +typedef struct uioa_page_s { + int uioa_pfncnt; + void **uioa_ppp; + caddr_t uioa_base; + size_t uioa_len; +} uioa_page_t; + +typedef struct xuio { + uio_t *xu_uio; + enum xuio_type xu_type; + union { + struct { + uint32_t xu_a_state; + ssize_t xu_a_mbytes; + uioa_page_t *xu_a_lcur; + void **xu_a_lppp; + void *xu_a_hwst[4]; + uioa_page_t xu_a_locked[UIOA_IOV_MAX]; + } xu_aio; + struct { + int xu_zc_rw; + void *xu_zc_priv; + } 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 + +/* + * same as uiomove() but doesn't modify uio structure. + * return in cbytes how many bytes were copied. + */ +static inline int uiocopy(const unsigned char *p, uint32_t n, + enum uio_rw rw, struct uio *uio, uint64_t *cbytes) +{ + int result; + struct uio *nuio = uio_duplicate(uio); + unsigned long long x = uio_resid(uio); + if (!nuio) + return (ENOMEM); + uio_setrw(nuio, rw); + result = spllib_uiomove(p, n, nuio); + *cbytes = (x - uio_resid(nuio)); + uio_free(nuio); + return (result); +} + +// Apple's uiomove puts the uio_rw in uio_create +#define uiomove(A, B, C, D) spllib_uiomove((A), (B), (D)) +#define uioskip(A, B) uio_update((A), (B)) + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_UIO_H */ diff --git a/lib/libspl/include/os/macos/sys/vfs.h b/lib/libspl/include/os/macos/sys/vfs.h new file mode 100644 index 000000000000..a2ffcc08d877 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/vfs.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 ZFS_SYS_VFS_H_ +#define ZFS_SYS_VFS_H_ + +#endif /* !ZFS_SYS_VFS_H_ */ diff --git a/lib/libspl/include/os/macos/sys/xattr.h b/lib/libspl/include/os/macos/sys/xattr.h new file mode 100644 index 000000000000..045f681b1e23 --- /dev/null +++ b/lib/libspl/include/os/macos/sys/xattr.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 (c) 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#ifndef _LIBSPL_SYS_XATTR_H +#define _LIBSPL_SYS_XATTR_H + +#include_next + +/* macOS has one more argument */ +#define setxattr(A, B, C, D, E) setxattr(A, B, C, D, E, 0) +#define getxattr(A, B, C, D, E) getxattr(A, B, C, D, E, 0) + +#endif diff --git a/lib/libspl/include/os/macos/sys/zfs_context_os.h b/lib/libspl/include/os/macos/sys/zfs_context_os.h new file mode 100644 index 000000000000..bd6ea9103a9e --- /dev/null +++ b/lib/libspl/include/os/macos/sys/zfs_context_os.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, 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_ + +#include + +#define ZFS_EXPORTS_PATH "/etc/exports" +#define MNTTYPE_ZFS_SUBTYPE ('Z'<<24|'F'<<16|'S'<<8) + +/* + * XNU reserves fileID 1-15, so we remap them high. + * 2 is root-of-the-mount. + * If ID is same as root, return 2. Otherwise, if it is 0-15, return + * adjusted, otherwise, return as-is. + * See hfs_format.h: kHFSRootFolderID, kHFSExtentsFileID, ... + */ +#define INO_ROOT 2ULL +#define INO_RESERVED 16ULL /* [0-15] reserved. */ +#define INO_ISRESERVED(ID) ((ID) < (INO_RESERVED)) +/* 0xFFFFFFFFFFFFFFF0 */ +#define INO_MAP ((uint64_t)-INO_RESERVED) /* -16, -15, .., -1 */ + +#define INO_ZFSTOXNU(ID, ROOT) \ + ((ID) == (ROOT)?INO_ROOT:(INO_ISRESERVED(ID)?INO_MAP+(ID):(ID))) + +/* + * This macro relies on *unsigned*. + * If asking for 2, return rootID. If in special range, adjust to + * normal, otherwise, return as-is. + */ +#define INO_XNUTOZFS(ID, ROOT) \ + ((ID) == INO_ROOT)?(ROOT): \ + (INO_ISRESERVED((ID)-INO_MAP))?((ID)-INO_MAP):(ID) + +struct spa_iokit; +typedef struct spa_iokit spa_iokit_t; + +#define zc_fd_offset zc_zoneid + +struct zfs_handle; + +extern void zfs_rollback_os(struct zfs_handle *zhp); +extern void libzfs_macos_wrapfd(int *srcfd, boolean_t send); +extern void libzfs_macos_wrapclose(void); +extern int libzfs_macos_pipefd(int *read_fd, int *write_fd); + +#include +#include +#if !defined(MAC_OS_VERSION_12_0) || \ + (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_VERSION_12_0) +#define kIOMainPortDefault kIOMasterPortDefault +#define IOMainPort IOMasterPort +#endif /* MAC_OS */ + +#endif diff --git a/lib/libspl/include/os/macos/time.h b/lib/libspl/include/os/macos/time.h new file mode 100644 index 000000000000..81bbd27fa92d --- /dev/null +++ b/lib/libspl/include/os/macos/time.h @@ -0,0 +1,83 @@ +/* + * 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 _LIBSPL_TIME_H +#define _LIBSPL_TIME_H + +#include_next +#include +#include + +/* Linux also has a timer_create() API we need to emulate. */ + +/* + * OsX version can probably be implemented by using: + * dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + * dispatch_source_set_event_handler(timer1, ^{vector1(timer1);}); + * dispatch_source_set_cancel_handler(timer1 + * dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC); + * dispatch_source_set_timer(timer1, start, NSEC_PER_SEC / 5, 0); + */ + +typedef void *timer_t; + +#if !defined(MAC_OS_X_VERSION_10_12) || \ + (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12) +typedef int clockid_t; +#endif + +struct itimerspec { + struct timespec it_interval; /* timer period */ + struct timespec it_value; /* timer expiration */ +}; + +struct sigevent; + +static inline int +timer_create(clockid_t clockid, + struct sigevent *sevp, + timer_t *timerid) +{ + (void) clockid; + (void) sevp; + (void) timerid; + return (0); +} + +static inline int +timer_settime(timer_t id, int flags, + const struct itimerspec *its, struct itimerspec *remainvalue) +{ + (void) id; + (void) flags; + (void) its; + (void) remainvalue; + return (0); +} + +static inline int +timer_delete(timer_t id) +{ + (void) id; + return (0); +} + +#endif diff --git a/lib/libspl/include/os/macos/unistd.h b/lib/libspl/include/os/macos/unistd.h new file mode 100644 index 000000000000..b9a9e8e16ba2 --- /dev/null +++ b/lib/libspl/include/os/macos/unistd.h @@ -0,0 +1,94 @@ +/* + * 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 _LIBSPL_UNISTD_H +#define _LIBSPL_UNISTD_H + +#include_next +#include +#include + +#include +#include + +/* Handle Linux use of 64 names */ + +#define open64 open +#define pread64 pread +#define pwrite64 pwrite +#define ftruncate64 ftruncate +#define lseek64 lseek + + +static inline int +fdatasync(int fd) +{ + if (fcntl(fd, F_FULLFSYNC) == -1) + return (-1); + return (0); +} + +#ifndef _SC_PHYS_PAGES +#define _SC_PHYS_PAGES 200 +#endif + +static inline int +pipe2(int fildes[2], int flags) +{ + int rv; + int old; + + if ((rv = pipe(fildes)) != 0) + return (rv); + + if (flags & O_NONBLOCK) { + old = fcntl(fildes[0], F_GETFL); + if (old >= 0) + fcntl(fildes[0], F_SETFL, old | O_NONBLOCK); + old = fcntl(fildes[1], F_GETFL); + if (old >= 0) + fcntl(fildes[1], F_SETFL, old | O_NONBLOCK); + } + if (flags & O_CLOEXEC) { + old = fcntl(fildes[0], F_GETFD); + if (old >= 0) + fcntl(fildes[0], F_SETFD, old | FD_CLOEXEC); + old = fcntl(fildes[1], F_GETFD); + if (old >= 0) + fcntl(fildes[1], F_SETFD, old | FD_CLOEXEC); + } + return (0); +} + +#if !defined(MAC_OS_X_VERSION_10_12) || \ + (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12) +#define mkostemp(template, oflag) mkstemp((template)) +#define mkostemps(template, slen, oflag) mkstemps((template), (slen)) +#endif + +static inline +void +setproctitle(const char *fmt, ...) +{ + (void) fmt; +} + +#endif diff --git a/lib/libspl/os/macos/getexecname.c b/lib/libspl/os/macos/getexecname.c new file mode 100644 index 000000000000..4ae774dba8a5 --- /dev/null +++ b/lib/libspl/os/macos/getexecname.c @@ -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 + */ + +#include +#include +#include +#include "../../libspl_impl.h" + +__attribute__((visibility("hidden"))) ssize_t +getexecname_impl(char *execname) +{ + strlcpy(execname, getprogname(), PATH_MAX); + return (strlen(execname)); +} diff --git a/lib/libspl/os/macos/gethostid.c b/lib/libspl/os/macos/gethostid.c new file mode 100644 index 000000000000..434d37004a9c --- /dev/null +++ b/lib/libspl/os/macos/gethostid.c @@ -0,0 +1,38 @@ +/* + * 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, Jorgen Lundman + */ + +#include +#include +#include +#include + +unsigned long +get_system_hostid(void) +{ + size_t len; + uint32_t myhostid = 0; + len = sizeof (myhostid); + sysctlbyname("kern.hostid", &myhostid, &len, NULL, 0); + return (myhostid); +} diff --git a/lib/libspl/os/macos/zone.c b/lib/libspl/os/macos/zone.c new file mode 100644 index 000000000000..96382e6a6ae1 --- /dev/null +++ b/lib/libspl/os/macos/zone.c @@ -0,0 +1,28 @@ +/* + * 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 + +zoneid_t +getzoneid(void) +{ + return (GLOBAL_ZONEID); +} diff --git a/lib/libzfs/os/macos/libzfs_dataset_os.c b/lib/libzfs/os/macos/libzfs_dataset_os.c new file mode 100644 index 000000000000..b71a3f388453 --- /dev/null +++ b/lib/libzfs/os/macos/libzfs_dataset_os.c @@ -0,0 +1,27 @@ +#include +#include "../../libzfs_impl.h" + +int +zfs_destroy_snaps_nvl_os(libzfs_handle_t *hdl, nvlist_t *snaps) +{ + struct mnttab entry; + int ret = 0; + nvpair_t *pair; + + for (pair = nvlist_next_nvpair(snaps, NULL); + pair != NULL; + pair = nvlist_next_nvpair(snaps, pair)) { + zfs_handle_t *zhp = zfs_open(hdl, nvpair_name(pair), + ZFS_TYPE_SNAPSHOT); + if (zhp != NULL) { + if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT && + libzfs_mnttab_find(hdl, zhp->zfs_name, &entry) + == 0) + ret |= zfs_snapshot_unmount(zhp, MS_FORCE); + zfs_close(zhp); + } + } + if (ret != 0) + fprintf(stderr, gettext("could not unmount snapshot(s)\n")); + return (ret); +} diff --git a/lib/libzfs/os/macos/libzfs_getmntany.c b/lib/libzfs/os/macos/libzfs_getmntany.c new file mode 100644 index 000000000000..8dc35dece9c1 --- /dev/null +++ b/lib/libzfs/os/macos/libzfs_getmntany.c @@ -0,0 +1,476 @@ +/* + * 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) 2013, 2021 Jorgen Lundman */ + +#include +#include +#include +#include /* for isspace() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../libzfs_impl.h" +#include + +/* + * Usually getmntany would live in libspl, further down the library + * dependency tree, but in the case of "mimick" of hfs/apfs, we need + * libzfs to test if the mount is *actually* ZFS pretending to be hfs. + */ + +#define DIFF(xx) ((mrefp->xx != NULL) && \ + (mgetp->xx == NULL || strcmp(mrefp->xx, mgetp->xx) != 0)) + +static struct statfs *gsfs = NULL; +static int allfs = 0; +/* + * We will also query the extended filesystem capabilities API, to lookup + * other mount options, for example, XATTR. We can not use the MNTNOUSERXATTR + * option due to VFS rejecting with EACCESS. + */ + +#include +typedef struct attrlist attrlist_t; + +struct attrBufS { + u_int32_t length; + vol_capabilities_set_t caps; +} __attribute__((aligned(4), packed)); + +static int +chdir_block_begin(int newroot_fd) +{ + int cwdfd, error; + + cwdfd = open(".", O_RDONLY | O_DIRECTORY); + if (cwdfd == -1) + return (-1); + + if (fchdir(newroot_fd) == -1) { + error = errno; + (void) close(cwdfd); + errno = error; + return (-1); + } + return (cwdfd); +} + +static void +chdir_block_end(int cwdfd) +{ + int error = errno; + (void) fchdir(cwdfd); + (void) close(cwdfd); + errno = error; +} + +int +openat64(int dirfd, const char *path, int flags, ...) +{ + int cwdfd, filefd; + + if ((cwdfd = chdir_block_begin(dirfd)) == -1) + return (-1); + + if ((flags & O_CREAT) != 0) { + va_list ap; + int mode; + + va_start(ap, flags); + mode = va_arg(ap, int); + va_end(ap); + + filefd = open(path, flags, mode); + } else + filefd = open(path, flags); + + chdir_block_end(cwdfd); + return (filefd); +} + +int +fstatat64(int dirfd, const char *path, struct stat *statbuf, int flag) +{ + int cwdfd, error; + + if ((cwdfd = chdir_block_begin(dirfd)) == -1) + return (-1); + + if (flag == AT_SYMLINK_NOFOLLOW) + error = lstat(path, statbuf); + else + error = stat(path, statbuf); + + chdir_block_end(cwdfd); + return (error); +} + + +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, const char *opt) +{ + char tmpopts[256]; + char *f, *opts = tmpopts; + + if (mnt->mnt_mntopts == NULL) + return (NULL); + (void) strlcpy(opts, mnt->mnt_mntopts, 256); + 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); +} + + +#include +#include +#include +#include +#include + + +static char * +MYCFStringCopyUTF8String(CFStringRef aString) +{ + if (aString == NULL) + return (NULL); + + CFIndex length = CFStringGetLength(aString); + CFIndex maxSize = + CFStringGetMaximumSizeForEncoding(length, + kCFStringEncodingUTF8); + char *buffer = (char *)malloc(maxSize); + if (CFStringGetCString(aString, buffer, maxSize, + kCFStringEncodingUTF8)) { + return (buffer); + } + return (NULL); +} + +/* + * Given "/dev/disk6" connect to IOkit and fetch the dataset + * name "BOOM/lower", and use it instead. + * Return 0 for no match (not ZFS) + * Return 1 for ZFS (path expanded) + */ +static int +expand_disk_to_zfs(char *devname, int len) +{ + char *result = NULL; + CFMutableDictionaryRef matchingDict; + io_service_t service; + CFStringRef cfstr; + char *device; + + if (strncmp(devname, "/dev/disk", 9) != 0) + return (0); + + device = &devname[5]; + + matchingDict = IOBSDNameMatching(kIOMainPortDefault, 0, device); + if (NULL == matchingDict) + return (0); + + /* + * Fetch the object with the matching BSD node name. + * Note that there should only be one match, so + * IOServiceGetMatchingService is used instead of + * IOServiceGetMatchingServices to simplify the code. + */ + service = IOServiceGetMatchingService(kIOMainPortDefault, + matchingDict); + + if (IO_OBJECT_NULL == service) + return (0); + + cfstr = IORegistryEntryCreateCFProperty(service, + CFSTR("ZFS Dataset"), kCFAllocatorDefault, 0); + if (cfstr) { + result = MYCFStringCopyUTF8String(cfstr); + CFRelease(cfstr); + } + + IOObjectRelease(service); + + if (result) { + strlcpy(devname, result, len); + free(result); + return (1); + } + return (0); +} + +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) +#ifdef __FreeBSD__ + OPTADD(MNTOPT_NOSUID); +#elif defined(__APPLE__) + OPTADD(MNTOPT_NOSETUID); +#endif + else + OPTADD(MNTOPT_SETUID); + if (flags & MNT_UPDATE) + OPTADD(MNTOPT_REMOUNT); + if (flags & MNT_NOATIME) + OPTADD(MNTOPT_NOATIME); + else + OPTADD(MNTOPT_ATIME); + { + struct attrBufS attrBuf; + attrlist_t attrList; + + memset(&attrList, 0, sizeof (attrList)); + attrList.bitmapcount = ATTR_BIT_MAP_COUNT; + attrList.volattr = ATTR_VOL_INFO|ATTR_VOL_CAPABILITIES; + + if (getattrlist(sfs->f_mntonname, &attrList, &attrBuf, + sizeof (attrBuf), 0) == 0) { + + if (attrBuf.caps[VOL_CAPABILITIES_INTERFACES] & + VOL_CAP_INT_EXTENDED_ATTR) { + OPTADD(MNTOPT_XATTR); + } else { + OPTADD(MNTOPT_NOXATTR); + } // If EXTENDED + } // if getattrlist + } + if (flags & MNT_NOEXEC) + OPTADD(MNTOPT_NOEXEC); + else + OPTADD(MNTOPT_EXEC); + if (flags & MNT_NODEV) + OPTADD(MNTOPT_NODEVICES); + else + OPTADD(MNTOPT_DEVICES); + if (flags & MNT_DONTBROWSE) + OPTADD(MNTOPT_NOBROWSE); + else + OPTADD(MNTOPT_BROWSE); + if (flags & MNT_IGNORE_OWNERSHIP) + OPTADD(MNTOPT_NOOWNERS); + else + OPTADD(MNTOPT_OWNERS); + +#undef OPTADD + + // If a disk is /dev/diskX, lets see if it has "zfs_dataset_name" + // set, and if so, use it instead, for mount matching - also update + // fstypename, as libzfs_mnttab_find() checks for it. + int is_actually_zfs = 0; + + // This is rather unattractive, is there a better way. + static libzfs_handle_t **internal_zfs = NULL; + + /* If we are linked with zpool/zfs there is a g_zfs handle */ + if (internal_zfs == NULL) + internal_zfs = dlsym(RTLD_DEFAULT, "g_zfs"); + + if (expand_disk_to_zfs(sfs->f_mntfromname, sizeof (sfs->f_mntfromname))) + is_actually_zfs = 1; + // check if it is a valid dataset (fastpast, first char isn't '/' + // in case mimic is enabled + else if (sfs->f_mntfromname[0] != '/' && + internal_zfs != NULL && + zfs_dataset_exists(*internal_zfs, sfs->f_mntfromname, + ZFS_TYPE_DATASET)) + is_actually_zfs = 1; + + if (is_actually_zfs) + mp->mnt_fstype = strdup(MNTTYPE_ZFS); + else + mp->mnt_fstype = strdup(sfs->f_fstypename); + + mp->mnt_special = strdup(sfs->f_mntfromname); + mp->mnt_mountp = strdup(sfs->f_mntonname); + mp->mnt_mntopts = strdup(mntopts); + + // Apple addition + mp->mnt_fssubtype = sfs->f_fssubtype; + +} + +static int +statfs_init(void) +{ + struct statfs *sfs; + int error; + + if (gsfs != NULL) { + free(gsfs); + gsfs = NULL; + } + allfs = getfsstat(NULL, 0, MNT_NOWAIT); + 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_NOWAIT); + 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) +{ + int i, error; + + error = statfs_init(); + if (error != 0) + return (error); + + for (i = 0; i < allfs; i++) { + statfs2mnttab(&gsfs[i], mgetp); + if (mrefp->mnt_special != NULL && mgetp->mnt_special != NULL && + strcmp(mrefp->mnt_special, mgetp->mnt_special) != 0) { + continue; + } + if (mrefp->mnt_mountp != NULL && mgetp->mnt_mountp != NULL && + strcmp(mrefp->mnt_mountp, mgetp->mnt_mountp) != 0) { + continue; + } + if (mrefp->mnt_fstype != NULL && mgetp->mnt_fstype != NULL && + strcmp(mrefp->mnt_fstype, mgetp->mnt_fstype) != 0) { + continue; + } + return (0); + } + return (-1); +} + +int +getmntent(FILE *fp, struct mnttab *mp) +{ + static int index = -1; + int error = 0; + (void) fp; + + if (index < 0) { + error = statfs_init(); + } + + if (error != 0) + return (error); + + index++; + + // If we have finished "reading" the mnttab, reset it to + // start from the beginning, and return EOF. + if (index >= allfs) { + index = -1; + return (-1); + } + + statfs2mnttab(&gsfs[index], mp); + return (0); +} + +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/libzfs/os/macos/libzfs_mount_os.c b/lib/libzfs/os/macos/libzfs_mount_os.c new file mode 100644 index 000000000000..412b75b7cacd --- /dev/null +++ b/lib/libzfs/os/macos/libzfs_mount_os.c @@ -0,0 +1,1004 @@ +/* + * 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, 2019 by Delphix. All rights reserved. + * Copyright 2016 Igor Kozhukhov + * Copyright 2017 RackTop Systems. + * Copyright (c) 2018 Datto Inc. + * Copyright 2018 OmniOS Community Edition (OmniOSce) Association. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../libzfs_impl.h" +#include + +#include +#include +#include +#include + +#include +#include +#include + +/* + * The default OpenZFS icon. Compare against known values to see if it needs + * updating. Allowing users to set own. + * No file: copy icon + * correct size: do nothing + * other size: user custom icon, do nothing + */ + +/* icon name on root of a mount */ +#define MOUNT_POINT_CUSTOM_ICON ".VolumeIcon.icns" + +/* source icon name from inside zfs.kext bundle */ +#define CUSTOM_ICON_PATH \ + KERNEL_MODPREFIX "/zfs.kext/Contents/Resources/VolumeIcon.icns" + +#include + + +/* + * On OSX we can set the icon to an Open ZFS specific one, just to be extra + * shiny + */ +static void +zfs_mount_seticon(const char *mountpoint) +{ + /* For a root file system, add a volume icon. */ + ssize_t attrsize; + uint16_t finderinfo[16]; + struct stat sbuf; + char *path = NULL; + FILE *dstfp = NULL, *srcfp = NULL; + unsigned char buf[1024]; + unsigned int red; + + if (getenv("__ZFS_DISABLE_VOLUME_ICON") != NULL) + return; + + if (asprintf(&path, "%s/%s", mountpoint, MOUNT_POINT_CUSTOM_ICON) == -1) + return; + + /* If we can stat it, and it has a size, leave it be. */ + if ((stat(path, &sbuf) == 0 && sbuf.st_size > 0)) + goto out; + + /* Looks like we should copy the icon over */ + + /* check if we can read in the default ZFS icon */ + srcfp = fopen(CUSTOM_ICON_PATH, "r"); + + /* No source icon */ + if (!srcfp) + goto out; + + /* Open the output icon for writing */ + dstfp = fopen(path, "w"); + if (!dstfp) + goto out; + + /* Copy icon */ + while ((red = fread(buf, 1, sizeof (buf), srcfp)) > 0) + (void) fwrite(buf, 1, red, dstfp); + + /* We have copied it, set icon */ + attrsize = getxattr(mountpoint, XATTR_FINDERINFO_NAME, &finderinfo, + sizeof (finderinfo), 0); + if (attrsize != sizeof (finderinfo)) + (void) memset(&finderinfo, 0, sizeof (finderinfo)); + if ((finderinfo[4] & BE_16(0x0400)) == 0) { + finderinfo[4] |= BE_16(0x0400); + (void) setxattr(mountpoint, XATTR_FINDERINFO_NAME, &finderinfo, + sizeof (finderinfo), 0); + } + + /* Now tell Finder to update */ +#if 0 + int fd = -1; + strlcpy(template, mountpoint, sizeof (template)); + strlcat(template, "/tempXXXXXX", sizeof (template)); + if ((fd = mkstemp(template)) != -1) { + unlink(template); // Just delete it right away + close(fd); + } +#endif + +out: + if (dstfp != NULL) + fclose(dstfp); + if (srcfp != NULL) + fclose(srcfp); + if (path != NULL) + free(path); +} + +static void +check_special(zfs_handle_t *zhp) +{ + zpool_handle_t *zph = zhp->zpool_hdl; + uint64_t feat_refcount; + nvlist_t *features; + + /* check that features can be enabled */ + if (zpool_get_prop_int(zph, ZPOOL_PROP_VERSION, NULL) + < SPA_VERSION_FEATURES) + return; + + /* SPA_FEATURE_PROJECT_QUOTA SPA_FEATURE_USEROBJ_ACCOUNTING */ + features = zpool_get_features(zph); + if (!features) + return; + + if (nvlist_lookup_uint64(features, + spa_feature_table[SPA_FEATURE_PROJECT_QUOTA].fi_guid, + &feat_refcount) != 0 && + nvlist_lookup_uint64(features, + spa_feature_table[SPA_FEATURE_USEROBJ_ACCOUNTING].fi_guid, + &feat_refcount) != 0) + return; + + printf(gettext("If importing from zfs-1.9.4 (or earlier), " + "then possibly enable features: \n" + " project_quota & userobj_accounting\n")); + +} + +/* Not entirely sure what these do, but let's keep it close to upstream */ + +#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_NOSETUID, MS_NOSUID, ZS_COMMENT }, + { MNTOPT_SETUID, 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(const 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); +} + +void +zfs_adjust_mount_options(zfs_handle_t *zhp, const char *mntpoint, + char *mntopts, char *mtabopt) +{ + (void) zhp; + (void) mntopts; + (void) mtabopt; + + /* A hint used to determine an auto-mounted snapshot mount point */ + append_mntopt(MNTOPT_MNTPOINT, mntpoint, mntopts, NULL, B_FALSE); +} + +/* + * if (zmount(zhp, zfs_get_name(zhp), mountpoint, MS_OPTIONSTR | flags, + * MNTTYPE_ZFS, NULL, 0, mntopts, sizeof (mntopts)) != 0) { + */ +int +do_mount(zfs_handle_t *zhp, const char *dir, const char *optptr, int mflag) +{ + int rv; + const char *spec = zfs_get_name(zhp); + const char *fstype = MNTTYPE_ZFS; + struct zfs_mount_args mnt_args; + char *rpath = NULL; + zfs_cmd_t zc = { "\0" }; + int devdisk = ZFS_DEVDISK_POOLONLY; + int ispool = 0; // the pool dataset, that is + int optlen = 0; + const char *value = NULL; + nvlist_t *args = NULL; + + assert(spec != NULL); + assert(dir != NULL); + assert(fstype != NULL); + assert(mflag >= 0); + + if (optptr != NULL) + optlen = strlen(optptr); + + /* + * Figure out if we want this mount as a /dev/diskX mount, if so + * ask kernel to create one for us, then use it to mount. + */ + + // Use dataset name by default + mnt_args.fspec = spec; + + /* + * Lookup the dataset property devdisk, and depending on its + * setting, we need to create a /dev/diskX for the mount + */ + if (zhp) { + + /* If we are in zfs-tests, no devdisks */ + if (getenv("__ZFS_MAIN_MOUNTPOINT_DIR") != NULL) + devdisk = ZFS_DEVDISK_OFF; + else + devdisk = zfs_prop_get_int(zhp, ZFS_PROP_DEVDISK); + + if (zhp && zhp->zpool_hdl && + strcmp(zpool_get_name(zhp->zpool_hdl), + zfs_get_name(zhp)) == 0) + ispool = 1; + + if ((devdisk == ZFS_DEVDISK_ON) || + ((devdisk == ZFS_DEVDISK_POOLONLY) && + ispool)) { + + strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); + + zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0); + + args = fnvlist_alloc(); + fnvlist_add_string(args, ZPOOL_CONFIG_POOL_NAME, + zhp->zfs_name); + zcmd_write_src_nvlist(zhp->zfs_hdl, &zc, args); + + rv = zfs_ioctl(zhp->zfs_hdl, + ZFS_IOC_PROXY_DATASET, &zc); + + /* Free innvl */ + nvlist_free(args); + args = NULL; + + /* args = outnvl */ + + if (rv == 0 && + zcmd_read_dst_nvlist(zhp->zfs_hdl, &zc, &args) == 0) + if (nvlist_exists(args, ZPOOL_CONFIG_PATH)) + value = fnvlist_lookup_string(args, + ZPOOL_CONFIG_PATH); + + zcmd_free_nvlists(&zc); + +#ifdef DEBUG + if (rv) + fprintf(stderr, + "proxy dataset returns %d '%s'\n", + rv, value ? value : ""); +#endif + + // Mount using /dev/diskX, use temporary buffer to + // give it full name + if (rv == 0 && value != NULL) { + snprintf(zc.zc_name, sizeof (zc.zc_name), + "/dev/%s", value); + mnt_args.fspec = zc.zc_name; + } + + /* free outnvl */ + if (args != NULL) + nvlist_free(args); + } + } + + // We don't pass flags to XNU, we use optstr + mflag = 0; + + // Some arguments need to be told to XNU + if (strstr(optptr, "remount") != NULL) + mflag |= MNT_UPDATE; + + mnt_args.mflag = mflag; + mnt_args.optptr = optptr; + mnt_args.optlen = optlen; + mnt_args.struct_size = sizeof (mnt_args); + + /* + * There is a bug in XNU where /var/tmp is resolved as + * "private/var/tmp" without the leading "/", and both mount(2) and + * diskutil mount avoid this by calling realpath() first. So we will + * do the same. + */ + rpath = realpath(dir, NULL); + +#ifdef ZFS_DEBUG + printf("%s calling mount with fstype %s, %s %s, fspec %s, mflag %d," + " optptr %s, optlen %d, devdisk %d, ispool %d\n", + __func__, fstype, (rpath ? "rpath" : "dir"), + (rpath ? rpath : dir), mnt_args.fspec, mflag, optptr, optlen, + devdisk, ispool); +#endif + rv = mount(fstype, rpath ? rpath : dir, mflag, &mnt_args); + + /* Check if we need to create/update icon */ + if (rv == 0) + zfs_mount_seticon(dir); + else + rv = errno; + + /* 1.9.4 did not have projectquotas, check if user should upgrade */ + if (rv == EIO) + check_special(zhp); + + + if (rpath) free(rpath); + + return (rv); +} + +static int +do_unmount_impl(const char *mntpt, int flags) +{ + const char force_opt[] = "force"; + const char *argv[7] = { + "/usr/sbin/diskutil", + "unmount", + NULL, NULL, NULL, NULL }; + int rc, count = 2; + + if (flags & MS_FORCE) { + argv[count] = force_opt; + count++; + } + + argv[count] = (const char *)mntpt; + rc = libzfs_run_process(argv[0], (char **)argv, + STDOUT_VERBOSE|STDERR_VERBOSE); + + /* + * There is a bug, where we can not unmount, with the error + * already unmounted, even though it wasn't. But it is easy + * to work around by calling 'umount'. Until a real fix is done... + * re-test this: 202004/lundman + */ + if (rc != 0) { + const char *argv[7] = { + "/sbin/umount", + NULL, NULL, NULL, NULL }; + int count = 1; + + if (flags & MS_FORCE) { + argv[count] = "-f"; + count++; + } + argv[count] = (const char *)mntpt; + rc = libzfs_run_process(argv[0], (char **)argv, + STDOUT_VERBOSE|STDERR_VERBOSE); + } + + return (rc ? EINVAL : 0); +} + + +void unmount_snapshots(zfs_handle_t *zhp, const char *mntpt, int flags); + +int +do_unmount(zfs_handle_t *zhp, const char *mntpt, int flags) +{ + int rv = 0; + /* + * On OSX, the kernel can not unmount all snapshots for us, as XNU + * rejects the unmount before it reaches ZFS. But we can easily handle + * unmounting snapshots from userland. + */ + unmount_snapshots(zhp, mntpt, flags); + + rv = do_unmount_impl(mntpt, flags); + + /* We might need to remove the proxy as well */ + if (rv == 0 && zhp != NULL) { + zfs_cmd_t zc = { "\0" }; + nvlist_t *args = NULL; + + strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name)); + + zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0); + + args = fnvlist_alloc(); + fnvlist_add_string(args, ZPOOL_CONFIG_POOL_NAME, + zhp->zfs_name); + zcmd_write_src_nvlist(zhp->zfs_hdl, &zc, args); + + rv = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_PROXY_REMOVE, &zc); + + /* Free innvl */ + nvlist_free(args); + args = NULL; + + /* args = outnvl */ + + zcmd_free_nvlists(&zc); + + // We don't care about proxy failing + rv = 0; + } + + return (rv); +} + +/* + * Given "/Volumes/BOOM" look for any lower mounts with ".zfs/snapshot/" + * in them - issue unmount. + */ +void +unmount_snapshots(zfs_handle_t *zhp, const char *mntpt, int flags) +{ + struct mnttab entry; + int mntlen = strlen(mntpt); + + (void) flags; + + if (zhp == NULL) + return; + /* + * The automounting will kick in, and zed mounts it - so + * we temporarily disable it + */ + uint64_t automount = getpid(); + uint64_t saved_automount = 0; + size_t len = sizeof (automount); + size_t slen = sizeof (saved_automount); + + /* Remember what the user has it set to */ + sysctlbyname("kstat.zfs.darwin.tunable.zfs_auto_snapshot", + &saved_automount, &slen, NULL, 0); + + /* Disable automounting */ + sysctlbyname("kstat.zfs.darwin.tunable.zfs_auto_snapshot", + NULL, NULL, &automount, len); + + while (getmntent(NULL, &entry) == 0) { + /* Starts with our mountpoint ? */ + if (strncmp(mntpt, entry.mnt_mountp, mntlen) == 0) { + /* The next part is "/.zfs/snapshot/" ? */ + if (strncmp("/.zfs/snapshot/", + &entry.mnt_mountp[mntlen], 15) == 0) { + /* Unmount it */ + do_unmount_impl(entry.mnt_mountp, MS_FORCE); + } + } + } + + /* Restore automount setting */ + sysctlbyname("kstat.zfs.darwin.tunable.zfs_auto_snapshot", + NULL, NULL, &saved_automount, len); +} + +int +zfs_mount_delegation_check(void) +{ + return ((geteuid() != 0) ? EACCES : 0); +} + +static char * +zfs_snapshot_mountpoint(zfs_handle_t *zhp) +{ + char *dataset_name, *snapshot_mountpoint, *parent_mountpoint; + libzfs_handle_t *hdl = zhp->zfs_hdl; + zfs_handle_t *parent; + char *r; + + dataset_name = zfs_strdup(hdl, zhp->zfs_name); + if (dataset_name == NULL) { + (void) fprintf(stderr, gettext("not enough memory")); + return (NULL); + } + + r = strrchr(dataset_name, '@'); + + if (r == NULL) { + (void) fprintf(stderr, gettext("snapshot '%s' " + "has no '@'\n"), zhp->zfs_name); + free(dataset_name); + return (NULL); + } + + r[0] = 0; + + /* Open the dataset */ + if ((parent = zfs_open(hdl, dataset_name, + ZFS_TYPE_FILESYSTEM)) == NULL) { + (void) fprintf(stderr, + gettext("unable to open parent dataset '%s'\n"), + dataset_name); + free(dataset_name); + return (NULL); + } + + if (!zfs_is_mounted(parent, &parent_mountpoint)) { + (void) fprintf(stderr, + gettext("parent dataset '%s' must be mounted\n"), + dataset_name); + free(dataset_name); + zfs_close(parent); + return (NULL); + } + + zfs_close(parent); + + snapshot_mountpoint = + zfs_asprintf(hdl, "%s/.zfs/snapshot/%s/", + parent_mountpoint, &r[1]); + + free(dataset_name); + free(parent_mountpoint); + + return (snapshot_mountpoint); +} + +/* + * Mount a snapshot; called from "zfs mount dataset@snapshot". + * Given "dataset@snapshot" construct mountpoint path of the + * style "/mountpoint/dataset/.zfs/snapshot/$name/". Ensure + * parent "dataset" is mounted, then issue mount for snapshot. + */ +int +zfs_snapshot_mount(zfs_handle_t *zhp, const char *options, + int flags) +{ + int ret = 0; + char *mountpoint; + + /* + * The automounting will kick in, and zed mounts it - so + * we temporarily disable it + */ + uint64_t automount = getpid(); + uint64_t saved_automount = 0; + size_t len = sizeof (automount); + size_t slen = sizeof (saved_automount); + + /* Remember what the user has it set to */ + sysctlbyname("kstat.zfs.darwin.tunable.zfs_auto_snapshot", + &saved_automount, &slen, NULL, 0); + + /* Disable automounting */ + sysctlbyname("kstat.zfs.darwin.tunable.zfs_auto_snapshot", + NULL, NULL, &automount, len); + + if (zfs_is_mounted(zhp, NULL)) { + ret = EBUSY; + goto out; + } + + mountpoint = zfs_snapshot_mountpoint(zhp); + if (mountpoint == NULL) { + ret = EINVAL; + goto out; + } + + ret = zfs_mount_at(zhp, options, MS_RDONLY | flags, + mountpoint); + + /* If zed is running, it can mount it before us */ + if (ret == -1 && errno == EINVAL) + ret = 0; + + if (ret == 0) { + (void) fprintf(stderr, + gettext("ZFS: snapshot mountpoint '%s'\n"), + mountpoint); + } + + free(mountpoint); + +out: + /* Restore automount setting */ + sysctlbyname("kstat.zfs.darwin.tunable.zfs_auto_snapshot", + NULL, NULL, &saved_automount, len); + + return (ret); +} + +int +zfs_snapshot_unmount(zfs_handle_t *zhp, int flags) +{ + int ret = 0; + char *mountpoint; + + /* + * The automounting will kick in, and zed mounts it - so + * we temporarily disable it + */ + uint64_t automount = getpid(); + uint64_t saved_automount = 0; + size_t len = sizeof (automount); + size_t slen = sizeof (saved_automount); + + /* Remember what the user has it set to */ + sysctlbyname("kstat.zfs.darwin.tunable.zfs_auto_snapshot", + &saved_automount, &slen, NULL, 0); + + /* Disable automounting */ + sysctlbyname("kstat.zfs.darwin.tunable.zfs_auto_snapshot", + NULL, NULL, &automount, len); + + if (!zfs_is_mounted(zhp, NULL)) { + ret = ENOENT; + goto out; + } + + mountpoint = zfs_snapshot_mountpoint(zhp); + if (mountpoint == NULL) { + ret = EINVAL; + goto out; + } + + ret = zfs_unmount(zhp, mountpoint, flags); + + free(mountpoint); + +out: + sysctlbyname("kstat.zfs.darwin.tunable.zfs_auto_snapshot", + NULL, NULL, &saved_automount, len); + + return (ret); +} + +static int +do_unmount_volume(const char *mntpt, int flags) +{ + const char force_opt[] = "force"; + const char *argv[7] = { + "/usr/sbin/diskutil", + NULL, NULL, NULL, NULL }; + int rc, count = 1; + + // Check if ends with "s1" partition + int idx = strlen(mntpt); + while (idx > 0 && + isdigit(mntpt[idx])) + idx--; + if (mntpt[idx] == 's') + argv[count++] = "unmount"; + else + argv[count++] = "unmountDisk"; + + if (flags & MS_FORCE) { + argv[count] = force_opt; + count++; + } + + argv[count] = (const char *)mntpt; + rc = libzfs_run_process(argv[0], (char **)argv, + STDOUT_VERBOSE|STDERR_VERBOSE); + + return (rc ? EINVAL : 0); +} + +void +zpool_disable_volume_os(const char *name) +{ + CFMutableDictionaryRef matching = 0; + char *fullname = NULL; + int result; + io_service_t service = 0; + io_service_t child; + char bsdstr[MAXPATHLEN]; + char bsdstr2[MAXPATHLEN]; + CFStringRef bsdname; + CFStringRef bsdname2; + io_iterator_t iter; + + if (asprintf(&fullname, "ZVOL %s Media", name) < 0) + return; + + matching = IOServiceNameMatching(fullname); + if (matching == 0) + goto out; + service = IOServiceGetMatchingService(kIOMainPortDefault, matching); + if (service == 0) + goto out; + // printf("GetMatching said %p\n", service); + + // Get BSDName? + bsdname = IORegistryEntryCreateCFProperty(service, + CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0); + if (bsdname && + CFStringGetCString(bsdname, bsdstr, sizeof (bsdstr), + kCFStringEncodingUTF8)) { + // printf("BSDName '%s'\n", bsdstr); + + // Now loop through and check if apfs has any synthesized + // garbage attached, as they didnt make "diskutil unmountdisk" + // handle it, we have to do it manually. (minus 1 apple!) + + result = IORegistryEntryCreateIterator(service, + kIOServicePlane, + kIORegistryIterateRecursively, + &iter); + + // printf("iterating ret %d \n", result); + if (result == 0) { + while ((child = IOIteratorNext(iter)) != 0) { + + bsdname2 = IORegistryEntryCreateCFProperty( + child, CFSTR(kIOBSDNameKey), + kCFAllocatorDefault, 0); + + if (bsdname2 && + CFStringGetCString(bsdname2, bsdstr2, + sizeof (bsdstr2), kCFStringEncodingUTF8)) { + CFRelease(bsdname2); + printf( + "... asking apfs to eject '%s'\n", + bsdstr2); + do_unmount_volume(bsdstr2, 0); + + } // Has BSDName? + + IOObjectRelease(child); + } + IOObjectRelease(iter); + } // iterate + + CFRelease(bsdname); + printf("... asking ZVOL to export '%s'\n", bsdstr); + do_unmount_volume(bsdstr, 0); + } + +out: + if (service != 0) + IOObjectRelease(service); + if (fullname != NULL) + free(fullname); + +} + +static int +zpool_disable_volumes(zfs_handle_t *nzhp, void *data) +{ + // Same pool? + if (nzhp && nzhp->zpool_hdl && zpool_get_name(nzhp->zpool_hdl) && + data && + strcmp(zpool_get_name(nzhp->zpool_hdl), (char *)data) == 0) { + if (zfs_get_type(nzhp) == ZFS_TYPE_VOLUME) { + /* + * /var/run/zfs/zvol/dsk/$POOL/$volume + */ + + zpool_disable_volume_os(zfs_get_name(nzhp)); + + } + } + (void) zfs_iter_children_v2(nzhp, 0, zpool_disable_volumes, data); + zfs_close(nzhp); + return (0); +} + +/* + * Since volumes can be mounted, we need to ask diskutil to unmountdisk + * to make sure Spotlight and all that, let go of the mount. + */ +void +zpool_disable_datasets_os(zpool_handle_t *zhp, boolean_t force) +{ + libzfs_handle_t *hdl = zhp->zpool_hdl; + + (void) force; + zfs_iter_root(hdl, zpool_disable_volumes, (void *)zpool_get_name(zhp)); +} diff --git a/lib/libzfs/os/macos/libzfs_pool_os.c b/lib/libzfs/os/macos/libzfs_pool_os.c new file mode 100644 index 000000000000..07fd25d20dde --- /dev/null +++ b/lib/libzfs/os/macos/libzfs_pool_os.c @@ -0,0 +1,343 @@ +/* + * 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 + * Copyright (c) 2018 Datto Inc. + * Copyright (c) 2017 Open-E, Inc. All Rights Reserved. + * Copyright (c) 2017, Intel Corporation. + * Copyright (c) 2018, loli10K + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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) 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) +{ + const 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 -. + */ +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/macos/libzfs_util_os.c b/lib/libzfs/os/macos/libzfs_util_os.c new file mode 100644 index 000000000000..f72f0185a8bf --- /dev/null +++ b/lib/libzfs/os/macos/libzfs_util_os.c @@ -0,0 +1,697 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../../libzfs_impl.h" +#include "zfs_prop.h" +#include +#include +#include + +#define ZDIFF_SHARESDIR "/.zfs/shares/" + + +int +zfs_ioctl(libzfs_handle_t *hdl, int request, zfs_cmd_t *zc) +{ + return (lzc_ioctl_fd(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/kextload zfs.kext' 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/kextload zfs.kext' 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[] = "/dev/"; + 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 to wait for ZFS_DEV + */ +static int +libzfs_load_module_impl(const char *module) +{ + const char *argv[4] = {"/sbin/kextload", (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/kextload", (char **)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) +{ + + // Using this as a libzfs_init_os() - we should probably do it properly + libdiskmgt_init(); + + return (libzfs_load_module_impl(ZFS_DRIVER)); +} + +int +find_shares_object(differ_info_t *di) +{ + (void) di; + 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) + */ +char * +zfs_version_kernel(void) +{ + size_t rlen = 0; + + if (sysctlbyname("zfs.kext_version", + NULL, &rlen, NULL, 0) == -1) + return (NULL); + + char *version = malloc(rlen + 1); + if (version == NULL) + return (NULL); + + if (sysctlbyname("zfs.kext_version", + version, &rlen, NULL, 0) == -1) { + free(version); + return (NULL); + } + + return (version); +} + +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; + } + memcpy(buf, p, lp); + buf[lp] = '/'; + memcpy(buf + lp + 1, name, 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) { + goto done; + } + memp[0] = "sh"; + memp[1] = bp; + memcpy(memp + 2, argv + 1, 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)); +} + +void +zfs_rollback_os(zfs_handle_t *zhp) +{ + (void) zhp; +} + +struct pipe2file { + int from; + int to; +}; +typedef struct pipe2file pipe2file_t; + +// #define VERBOSE_WRAPFD +static int pipe_relay_readfd = -1; +static int pipe_relay_writefd = -1; +static int pipe_relay_send; +static volatile int signal_received = 0; +static int pipe_relay_pid = 0; + +static void pipe_io_relay_intr(int signum) +{ + (void) signum; + signal_received = 1; +} + +static void * +pipe_io_relay(void *arg) +{ + pipe2file_t *p2f = (pipe2file_t *)arg; + int readfd, writefd; + unsigned char *buffer; + unsigned char space[1024]; + int size = 1024 * 1024; + int red, sent; + uint64_t __maybe_unused total = 0; + + readfd = p2f->from; + writefd = p2f->to; + free(p2f); + p2f = NULL; + + buffer = malloc(size); + if (buffer == NULL) { + buffer = space; + size = sizeof (space); + } + +#ifdef VERBOSE_WRAPFD + fprintf(stderr, "%s: thread up: read(%d) write(%d)\r\n", __func__, + readfd, writefd); +#endif + + /* + * If ^C is hit, we must close the fds in the correct order, or + * we deadlock. So we need to install a signal handler, let's be + * nice and check if one is installed, and chain them in. + */ + struct sigaction sa; + sigset_t blocked; + + /* Process: Ignore SIGINT */ + + sigemptyset(&blocked); + sigaddset(&blocked, SIGINT); + sigaddset(&blocked, SIGPIPE); + sigprocmask(SIG_SETMASK, &blocked, NULL); + + sa.sa_handler = pipe_io_relay_intr; + sa.sa_mask = blocked; + sa.sa_flags = 0; + + sigaction(SIGINT, &sa, NULL); + + errno = 0; + + for (;;) { + + red = read(readfd, buffer, size); +#ifdef VERBOSE_WRAPFD + fprintf(stderr, "%s: read(%d): %d (errno %d)\r\n", __func__, + readfd, red, errno); +#endif + if (red == 0) + break; + if (red < 0 && errno != EWOULDBLOCK) + break; + + sent = write(writefd, buffer, red); +#ifdef VERBOSE_WRAPFD + fprintf(stderr, "%s: write(%d): %d (errno %d)\r\n", __func__, + writefd, sent, errno); +#endif + if (sent < 0) + break; + + if (signal_received) { +#ifdef VERBOSE_WRAPFD + fprintf(stderr, "sigint handler - exit\r\n"); +#endif + break; + } + + total += red; + } + + +#ifdef VERBOSE_WRAPFD + fprintf(stderr, "loop exit (closing)\r\n"); +#endif + + close(readfd); + close(writefd); + + if (buffer != space) + free(buffer); + +#ifdef VERBOSE_WRAPFD + fprintf(stderr, "%s: thread done: %llu bytes\r\n", __func__, total); +#endif + + return (NULL); +} + +/* + * XNU only lets us do IO on vnodes, not pipes, so create a Unix + * Domain socket, open it to get a vnode for the kernel, and spawn + * thread to relay IO. As used by sendrecv, we are given a FD it wants + * to send to the kernel, and we'll replace it with the pipe FD instead. + * If pipe/fork already exists, use same descriptors. (multiple send/recv) + * + * In addition to this, upstream will do their "zfs send" by having the kernel + * look in fd->f_offset for the userland file-position, then update it + * again after IO completes, so userland is kept in-sync. + * + * In XNU, we have no access to "f_offset". For "zfs send", it is possible + * to change the "fd" to have O_APPEND, then have kernel use IO_APPEND + * when writing to it. Once back in userland, any write()s will SEEK_END + * due to O_APPEND. This was tested, but it feels "questionable" to + * add O_APPEND to a file descriptor opened by the shell (zfs send > file). + * Even though this would work for "zfs send", we also need "zfs recv" to + * work. + * + * So now when zfs adds the "fd" to either "zc", or the "innvl", to pass it + * to the kernel via ioctl() - annoyingly we still have OLD and NEW ioctl + * for send and recv - we will also pass the file offset, either in + * zc.zoneid (not used in XNU) or innvl "input_fd_offset". + * Since the kernel might do writes, we need to SEEK_END once we return. + */ +void +libzfs_macos_wrapfd(int *srcfd, boolean_t send) +{ + char template[100]; + int error; + struct stat sb; + pipe2file_t *p2f = NULL; + + pipe_relay_send = send; + +#ifdef VERBOSE_WRAPFD + fprintf(stderr, "%s: checking if we need pipe wrap\r\n", __func__); +#endif + + // Check if it is a pipe + error = fstat(*srcfd, &sb); + + if (error != 0) + return; + + if (!S_ISFIFO(sb.st_mode)) + return; + + if (pipe_relay_pid != 0) { +#ifdef VERBOSE_WRAPFD + fprintf(stderr, "%s: pipe relay already started ... \r\n", + __func__); +#endif + if (send) { + *srcfd = pipe_relay_writefd; + } else { + *srcfd = pipe_relay_readfd; + } + return; + } + + p2f = (pipe2file_t *)malloc(sizeof (pipe2file_t)); + if (p2f == NULL) + return; + +#ifdef VERBOSE_WRAPFD + fprintf(stderr, "%s: is pipe: work on fd %d\r\n", __func__, *srcfd); +#endif + + /* + * mktemp() has been deprecated, even though we want to use it + * with mkfifo() (which is safe from MitM). Jump some hoops to + * work around it. + */ + do { + snprintf(template, sizeof (template), "/tmp/.zfs.pipe.%u", + arc4random()); + } while (mkfifo(template, 0600) != 0); + + pipe_relay_readfd = open(template, O_RDONLY | O_NONBLOCK); + +#ifdef VERBOSE_WRAPFD + fprintf(stderr, "%s: pipe_relay_readfd %d (%d)\r\n", __func__, + pipe_relay_readfd, error); +#endif + + pipe_relay_writefd = open(template, O_WRONLY | O_NONBLOCK); + +#ifdef VERBOSE_WRAPFD + fprintf(stderr, "%s: pipe_relay_writefd %d (%d)\r\n", __func__, + pipe_relay_writefd, error); +#endif + + // set it to delete + unlink(template); + + // Check delayed so unlink() is always called. + if (pipe_relay_readfd < 0) + goto out; + if (pipe_relay_writefd < 0) + goto out; + + /* Open needs NONBLOCK, so switch back to BLOCK */ + int flags; + flags = fcntl(pipe_relay_readfd, F_GETFL); + flags &= ~O_NONBLOCK; + fcntl(pipe_relay_readfd, F_SETFL, flags); + flags = fcntl(pipe_relay_writefd, F_GETFL); + flags &= ~O_NONBLOCK; + fcntl(pipe_relay_writefd, F_SETFL, flags); + + // create IO thread + // Send, kernel was to be given *srcfd - to write to. + // Instead we give it pipe_relay_writefd. + // thread then uses read(pipe_relay_readfd) -> write(*srcfd) + if (send) { + p2f->from = pipe_relay_readfd; + p2f->to = *srcfd; + } else { + p2f->from = *srcfd; + p2f->to = pipe_relay_writefd; + } +#ifdef VERBOSE_WRAPFD + fprintf(stderr, "%s: forking\r\n", __func__); +#endif + + error = fork(); + if (error == 0) { + + // Close the fd we don't need + if (send) + close(pipe_relay_writefd); + else + close(pipe_relay_readfd); + + setsid(); + pipe_io_relay(p2f); + _exit(0); + } + + if (error < 0) + goto out; + + pipe_relay_pid = error; + + // Return open(file) fd to kernel only after all error cases + if (send) { + *srcfd = pipe_relay_writefd; + close(pipe_relay_readfd); + } else { + *srcfd = pipe_relay_readfd; + close(pipe_relay_writefd); + } + return; + +out: + if (p2f != NULL) + free(p2f); + + if (pipe_relay_readfd >= 0) + close(pipe_relay_readfd); + + if (pipe_relay_writefd >= 0) + close(pipe_relay_writefd); +} + +/* + * libzfs_diff uses pipe() to make 2 connected FDs, + * one is passed to kernel, and the other end it creates + * a thread to relay IO (to STDOUT). + * We can not do IO on anything by vnode opened FDs, so + * we'll use mkfifo, and open it twice, the WRONLY side + * is passed to kernel (now it is a vnode), and other other + * is used in the "differ" thread. + */ +int +libzfs_macos_pipefd(int *read_fd, int *write_fd) +{ + char template[100]; + + /* + * mktemp() has been deprecated, even though we want to use it + * with mkfifo() (which is safe from MitM). Jump some hoops to + * work around it. + */ + do { + snprintf(template, sizeof (template), "/tmp/.zfs.pipe.%u", + arc4random()); + } while (mkfifo(template, 0600) != 0); + + *read_fd = open(template, O_RDONLY | O_NONBLOCK); + +#ifdef VERBOSE_WRAPFD + fprintf(stderr, "%s: readfd %d\r\n", __func__, + *read_fd); +#endif + if (*read_fd < 0) { + unlink(template); + return (-1); + } + + *write_fd = open(template, O_WRONLY | O_NONBLOCK); + +#ifdef VERBOSE_WRAPFD + fprintf(stderr, "%s: writefd %d\r\n", __func__, + *write_fd); +#endif + + // set it to delete + unlink(template); + + if (*write_fd < 0) { + close(*read_fd); + return (-1); + } + + /* Open needs NONBLOCK, so switch back to BLOCK */ + int flags; + flags = fcntl(*read_fd, F_GETFL); + flags &= ~O_NONBLOCK; + fcntl(*read_fd, F_SETFL, flags); + flags = fcntl(*write_fd, F_GETFL); + flags &= ~O_NONBLOCK; + fcntl(*write_fd, F_SETFL, flags); + + + return (0); + +} + + +void +libzfs_macos_wrapclose(void) +{ +} diff --git a/lib/libzfs_core/os/macos/libzfs_core_ioctl.c b/lib/libzfs_core/os/macos/libzfs_core_ioctl.c new file mode 100644 index 000000000000..facf86c2bc0e --- /dev/null +++ b/lib/libzfs_core/os/macos/libzfs_core_ioctl.c @@ -0,0 +1,95 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +static int +zcmd_ioctl_compat(int fd, int request, zfs_cmd_t *zc, const int cflag) +{ + int ret; + void *zc_c; + 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_ZOF; + zp.zfs_ioc_error = 0; + + ret = ioctl(fd, ncmd, &zp); + + /* + * If ioctl worked, get actual rc from kernel, which goes + * into errno, and return -1 if not-zero. + */ + if (ret == 0) { + errno = zp.zfs_ioc_error; + if (zp.zfs_ioc_error != 0) + ret = -1; + } + return (ret); + + default: + abort(); + return (EINVAL); + } + + /* Pass-through ioctl, rarely used if at all */ + + ret = ioctl(fd, ncmd, zc_c); + ASSERT0(ret); + + zfs_cmd_compat_get(zc, (caddr_t)zc_c, cflag); + free(zc_c); + + return (ret); +} + +/* + * This is the macOS version of ioctl(). Because the XNU kernel + * handles copyin() and copyout(), we must return success from the + * ioctl() handler (or it will not copyout() for userland), + * and instead embed the error return value in the zc structure. + */ +int +lzc_ioctl_fd(int fd, unsigned long request, zfs_cmd_t *zc) +{ + size_t oldsize; + int ret, cflag = ZFS_CMD_COMPAT_NONE; + + 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/macos/zutil_device_path_os.c b/lib/libzutil/os/macos/zutil_device_path_os.c new file mode 100644 index 000000000000..bbcfe0f241e8 --- /dev/null +++ b/lib/libzutil/os/macos/zutil_device_path_os.c @@ -0,0 +1,208 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include + +#include + +/* + * Note: The caller must free the returned string. + */ +char * +zfs_strip_partition(const char *dev) +{ + unsigned int disk, slice; + char *partless; + char whole_disk[MAXPATHLEN]; + + partless = strdup(dev); + + /* Ends with "diskNsP" - where 'N' and 'P' are integers - strip sP */ + if (sscanf(partless, "disk%us%u", &disk, &slice) == 2) { + char *r; + r = strrchr(partless, 's'); + if (r != NULL) + *r = 0; + } else if ((sscanf(partless, "%[^:]:%u", whole_disk, &slice)) == 2) { + char *r; + r = strrchr(partless, ':'); + if (r != NULL) { + if (strchr(partless, '@')) { // by-path + if (slice == 1) + r[1] = '0'; + } else // by-serial + *r = 0; + } + } + + return (partless); +} + +int +zfs_append_partition(char *path, size_t max_len) +{ + int len = strlen(path); + char dpath[max_len]; + if (strncmp(path, "/var/", 5) == 0) { + (void) strlcpy(dpath, "/private", max_len); + (void) strlcat(dpath, path, max_len); + } else + strlcpy(dpath, path, max_len); + + + if (strncmp(dpath, "/private/var/run/disk/by-id", 27) == 0) { + return (len); + } else if (strncmp(dpath, "/private/var/run/disk/by-path", 29) == 0) { + if (path[len - 1] == '0' && + path[len - 2] == ':') + path[len - 1] = '1'; + + } else if (strncmp(dpath, "/private/var/run/disk/by-serial", 31) == 0) { + if (len + 2 >= max_len) + return (-1); + + if (strchr(path, ':') == NULL) { + (void) strcat(path, ":1"); + len += 2; + } + } else { + + if (len + 2 >= max_len) + return (-1); + + if (isdigit(path[len-1])) { + (void) strcat(path, "s1"); + len += 2; + } else { + (void) strcat(path, "1"); + len += 1; + } + } + + return (len); +} + +/* + * Strip the path from a device name. + * On FreeBSD we only want to remove "/dev/" from the beginning of + * paths if present. + */ +const char * +zfs_strip_path(const char *path) +{ + char *r; + r = strrchr(path, '/'); + if (r == NULL) + return (r); + return (&r[1]); +} + +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) +{ + 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); +} + +/* + * 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); +} + +boolean_t +is_mpath_whole_disk(const char *path) +{ + (void) path; + return (B_FALSE); +} + +/* + * 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) +{ + (void) dev_name; + return (B_FALSE); +} diff --git a/lib/libzutil/os/macos/zutil_import_os.c b/lib/libzutil/os/macos/zutil_import_os.c new file mode 100644 index 000000000000..d991f278d72f --- /dev/null +++ b/lib/libzutil/os/macos/zutil_import_os.c @@ -0,0 +1,631 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "zutil_import.h" + +#ifdef HAVE_LIBUDEV +#include +#include +#endif + +/* + * We allow /dev/ to be search in DEBUG build + * DEFAULT_IMPORT_PATH_SIZE is decremented by one to remove /dev! + * See below in zpool_find_import_blkid() to skip. + */ +#define DEFAULT_IMPORT_PATH_SIZE 4 + +#define DEV_BYID_PATH "/private/var/run/disk/by-id/" + +static const char * +zpool_default_import_path[DEFAULT_IMPORT_PATH_SIZE] = { + "/private/var/run/disk/by-id", + "/private/var/run/disk/by-path", + "/private/var/run/disk/by-serial", + "/dev" /* Only with DEBUG build */ +}; + +static boolean_t +is_watchdog_dev(const char *dev) +{ + /* For 'watchdog' dev */ + if (strcmp(dev, "watchdog") == 0) + return (B_TRUE); + + /* For 'watchdog */ + if (strstr(dev, "watchdog") == dev && isdigit(dev[8])) + return (B_TRUE); + + return (B_FALSE); +} + +int +zfs_dev_flush(int fd) +{ + (void) fd; +// return (ioctl(fd, BLKFLSBUF)); + return (0); +} + +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))); +} + +/* + * We have had issues with lio_listio() and AIO on BigSur, where + * we receive waves of EAGAIN, and have to loop, often up to + * 100 times before labels are read. Until this problem can be + * understood better, we use the old serial style here. + */ +int +zpool_read_label(int fd, nvlist_t **config, int *num_labels) +{ + struct stat64 statbuf; + int l, count = 0; + vdev_phys_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; + off_t offset = label_offset(size, l) + VDEV_SKIP_SIZE; + + if (pread64(fd, label, sizeof (vdev_phys_t), + offset) != sizeof (vdev_phys_t)) + continue; + + if (nvlist_unpack(label->vp_nvlist, + sizeof (label->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); +} + +void +zpool_open_func(void *arg) +{ + rdsk_node_t *rn = arg; + libpc_handle_t *hdl = rn->rn_hdl; + struct stat64 statbuf; + nvlist_t *config; + const char *bname; + char *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 = zfs_basename(dupname); + error = ((strcmp(bname, "hpet") == 0) || is_watchdog_dev(bname)); + if ((strncmp(bname, "core", 4) == 0) || + (strncmp(bname, "fd", 2) == 0) || + (strncmp(bname, "fuse", 4) == 0) || + (strncmp(bname, "hpet", 4) == 0) || + (strncmp(bname, "lp", 2) == 0) || + (strncmp(bname, "parport", 7) == 0) || + (strncmp(bname, "ppp", 3) == 0) || + (strncmp(bname, "random", 6) == 0) || + (strncmp(bname, "rtc", 3) == 0) || + (strncmp(bname, "tty", 3) == 0) || + (strncmp(bname, "urandom", 7) == 0) || + (strncmp(bname, "usbmon", 6) == 0) || + (strncmp(bname, "vcs", 3) == 0) || + (strncmp(bname, "pty", 3) == 0) || // lots, skip for speed + (strncmp(bname, "bpf", 3) == 0) || + (strncmp(bname, "audit", 5) == 0) || + (strncmp(bname, "autofs", 6) == 0) || + (strncmp(bname, "console", 7) == 0) || + (strncmp(bname, "zfs", 3) == 0) || + (strncmp(bname, "oslog_stream", 12) == 0) || + (strncmp(bname, "com", 3) == 0)) // /dev/com_digidesign_semiface + error = 1; + + free(dupname); + if (error) + return; + + /* + * Ignore failed stats. We only want regular files and block devices. + */ + if (stat(rn->rn_name, &statbuf) != 0 || + (!S_ISREG(statbuf.st_mode) && + !S_ISBLK(statbuf.st_mode) && + !S_ISCHR(statbuf.st_mode))) + return; + + fd = open(rn->rn_name, O_RDONLY); + 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); +#ifdef DEBUG + printf("%s: zpool_read_label returned error %d " + "(errno: %d name: %s)\n", + __func__, error, errno, rn->rn_name); +#endif + 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) { + const char *path = NULL; + const 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); + } + } + } +} + +const char * const * +zpool_default_search_paths(size_t *count) +{ + *count = DEFAULT_IMPORT_PATH_SIZE; + return ((const char * const *)zpool_default_import_path); +} + +int +zpool_find_import_blkid(libpc_handle_t *hdl, pthread_mutex_t *lock, + avl_tree_t **slice_cache) +{ + int i, dirs; + struct dirent *dp; + char path[MAXPATHLEN]; + char *end; + const char **dir; + size_t __maybe_unused pathleft; + avl_index_t where; + rdsk_node_t *slice; + int error = 0; + + dir = zpool_default_import_path; + dirs = DEFAULT_IMPORT_PATH_SIZE; + + /* + * Go through and read the label configuration information from every + * possible device, organizing the information according to pool GUID + * and toplevel GUID. + */ + *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)); + + for (i = 0; i < dirs; i++) { + char rdsk[MAXPATHLEN]; + int dfd; + DIR *dirp; + +#ifndef DEBUG + /* + * We skip imports in /dev/ in release builds, due to the + * danger of cache/log devices and drive renumbering. + * We have it in zpool_default_import_path to allow + * zfs_resolve_shortname() to still work, ie + * "zpool create disk3" to resolve to /dev/disk3. + */ + if (strncmp("/dev", dir[i], 4) == 0) + continue; +#endif + + /* use realpath to normalize the path */ + if (realpath(dir[i], path) == 0) { + + /* it is safe to skip missing search paths */ + if (errno == ENOENT) + continue; + + return (EPERM); + } + end = &path[strlen(path)]; + *end++ = '/'; + *end = 0; + pathleft = &path[sizeof (path)] - end; + + (void) strlcpy(rdsk, path, sizeof (rdsk)); + + if ((dfd = open(rdsk, O_RDONLY)) < 0 || + (dirp = fdopendir(dfd)) == NULL) { + if (dfd >= 0) + (void) close(dfd); + return (ENOENT); + } + + while ((dp = readdir(dirp)) != NULL) { + const char *name = dp->d_name; + if (name[0] == '.' && + (name[1] == 0 || (name[1] == '.' && name[2] == 0))) + continue; + + slice = zutil_alloc(hdl, sizeof (rdsk_node_t)); + + error = asprintf(&slice->rn_name, "%s%s", + path, name); + if (error == -1) { + free(slice); + return (ENOMEM); + } + + slice->rn_vdev_guid = 0; + slice->rn_lock = lock; + slice->rn_avl = *slice_cache; + slice->rn_hdl = hdl; + slice->rn_labelpaths = B_FALSE; + + // Make rdisk have a lower priority than disk + if (name[0] == 'r') + slice->rn_order = IMPORT_ORDER_DEFAULT + i; + else + slice->rn_order = IMPORT_ORDER_SCAN_OFFSET + i; + + 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); + } + + (void) closedir(dirp); + } + + 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; + +int +zfs_device_get_devid(struct udev_device *dev, char *bufptr, size_t buflen) +{ + (void) dev; + (void) bufptr; + (void) buflen; + return (ENODATA); +} + +int +zfs_device_get_physical(struct udev_device *dev, char *bufptr, size_t buflen) +{ + (void) dev; + (void) bufptr; + (void) buflen; + return (ENODATA); +} + +/* + * 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) +{ + (void) path; + (void) ds; + (void) wholedisk; + return (ENOENT); +} + +/* + * 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; + const char *type, *path; + uint64_t wholedisk = 0; + + /* + * 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); + } + + } 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); + } +} + +void +update_vdevs_config_dev_sysfs_path(nvlist_t *config) +{ + (void) config; +} diff --git a/lib/os/macos/libdiskmgt/Makefile.am b/lib/os/macos/libdiskmgt/Makefile.am new file mode 100644 index 000000000000..773eb5105451 --- /dev/null +++ b/lib/os/macos/libdiskmgt/Makefile.am @@ -0,0 +1,24 @@ + +libdiskmgt_la_CFLAGS = $(AM_CFLAGS) $(LIBRARY_CFLAGS) +libdiskmgt_la_CFLAGS += -fvisibility=default + +libdiskmgt_la_CPPFLAGS = $(AM_CPPFLAGS) +libdiskmgt_la_CPPFLAGS += -I$(srcdir)/%D% + +noinst_LTLIBRARIES += libdiskmgt.la +CPPCHECKTARGETS += libdiskmgt.la + +libdiskmgt_la_SOURCES = \ + %D%/dm.c \ + %D%/libdiskmgt.c \ + %D%/diskutil.c \ + %D%/entry.c \ + %D%/inuse_corestorage.c \ + %D%/inuse_fs.c \ + %D%/inuse_macswap.c \ + %D%/inuse_mnt.c \ + %D%/inuse_partition.c \ + %D%/inuse_zpool.c \ + %D%/slice.c + +libdiskmgt_la_LDFLAGS = -framework DiskArbitration -framework CoreServices diff --git a/lib/os/macos/libdiskmgt/disks_private.h b/lib/os/macos/libdiskmgt/disks_private.h new file mode 100644 index 000000000000..e552fb634b6c --- /dev/null +++ b/lib/os/macos/libdiskmgt/disks_private.h @@ -0,0 +1,82 @@ +/* + * 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) 2016, Brendon Humphrey (brendon.humphrey@mac.com). + */ + +#ifndef DISKS_PRIVATE_H +#define DISKS_PRIVATE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define NVATTRS NV_UNIQUE_NAME | NV_UNIQUE_NAME_TYPE +#define NVATTRS_STAT 0x0 + + typedef void* DU_Info; + + void diskutil_init(void); + void diskutil_fini(void); + + void init_diskutil_info(DU_Info *info); + int diskutil_info_valid(DU_Info info); + void get_diskutil_cs_info(char *slice, DU_Info *info); + void get_diskutil_info(char *slice, DU_Info *info); + int is_cs_disk(DU_Info *info); + int is_cs_converted(DU_Info *info); + int is_cs_locked(DU_Info *info); + int is_cs_logical_volume(DU_Info *info); + int is_cs_physical_volume(DU_Info *info); + int is_cs_online(DU_Info *info); + CFStringRef get_cs_LV_status(DU_Info *info); + + int is_whole_disk(DU_Info info); + int is_efi_partition(DU_Info info); + int is_recovery_partition(DU_Info info); + int is_APFS_partition(DU_Info info); + int is_HFS_partition(DU_Info info); + int is_MSDOS_partition(DU_Info info); + int has_filesystem_type(DU_Info info); + CFStringRef get_filesystem_type(DU_Info info); + + int inuse_corestorage(char *slice, nvlist_t *attrs, int *errp); + int inuse_fs(char *slice, nvlist_t *attrs, int *errp); + int inuse_macswap(const char *dev_name); + int inuse_mnt(char *slice, nvlist_t *attrs, int *errp); + int inuse_partition(char *slice, nvlist_t *attrs, int *errp); + int inuse_active_zpool(char *slice, nvlist_t *attrs, int *errp); + int inuse_exported_zpool(char *slice, nvlist_t *attrs, int *errp); + + void libdiskmgt_add_str(nvlist_t *attrs, const char *name, + const char *val, int *errp); + + nvlist_t *slice_get_stats(char *slice, int stat_type, int *errp); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/os/macos/libdiskmgt/diskutil.c b/lib/os/macos/libdiskmgt/diskutil.c new file mode 100644 index 000000000000..e6e42d13f264 --- /dev/null +++ b/lib/os/macos/libdiskmgt/diskutil.c @@ -0,0 +1,381 @@ +/* + * 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) 2016, Brendon Humphrey (brendon.humphrey@mac.com). + */ + +#include +#include +#include +#include +#include +#include +#include +#include "disks_private.h" +#include + +static int out_pipe[2], err_pipe[2]; +static CFMutableDictionaryRef diskutil_cs_info_cache = NULL; +static CFMutableDictionaryRef diskutil_info_cache = NULL; + +static Boolean +CFDictionaryValueIfPresentMatchesSubstring(CFDictionaryRef dict, + CFStringRef key, CFStringRef substr) +{ + Boolean ret = false; + CFStringRef existing; + if (dict && + CFDictionaryGetValueIfPresent(dict, key, + (const void **)&existing)) { + CFRange range = CFStringFind(existing, substr, + kCFCompareCaseInsensitive); + if (range.location != kCFNotFound) + ret = true; + } + return (ret); +} + +static int +run_command(char *argv[], int *out_length) +{ + pid_t pid; + int status = 0; + struct stat out_stat; + + pipe(out_pipe); // create a pipe + pipe(err_pipe); + pid = fork(); + + if (pid == 0) { + close(out_pipe[0]); + close(err_pipe[0]); + dup2(out_pipe[1], STDOUT_FILENO); + dup2(err_pipe[1], STDERR_FILENO); + + execv(argv[0], argv); + } + + // Parent + close(out_pipe[1]); + close(err_pipe[1]); + waitpid(pid, &status, 0); + + fstat(out_pipe[0], &out_stat); + + *out_length = (int)out_stat.st_size; + + return (status); +} + +static void +read_buffers(char *out_buffer, int out_length) +{ + out_buffer[read(out_pipe[0], out_buffer, out_length)] = 0; +} + +void +diskutil_init(void) +{ + diskutil_info_cache = CFDictionaryCreateMutable(NULL, 0, + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + diskutil_cs_info_cache = CFDictionaryCreateMutable(NULL, 0, + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); +} + +void +diskutil_fini(void) +{ + CFRelease(diskutil_cs_info_cache); + CFRelease(diskutil_info_cache); +} + +void +init_diskutil_info(DU_Info *info) +{ + (void) info; + info = NULL; +} + +int +diskutil_info_valid(DU_Info info) +{ + return (info != NULL); +} + +void +get_diskutil_cs_info(char *slice, DU_Info *info) +{ + int status = 0; + int out_length = 0; + const char *cc[] = { "/usr/sbin/diskutil", "cs", "info", "-plist", + slice, NULL}; + char *output = NULL; + CFPropertyListRef plist = NULL; + CFStringRef slice_str = CFStringCreateWithCString(NULL, + slice, kCFStringEncodingUTF8); + + if (CFDictionaryGetValueIfPresent(diskutil_cs_info_cache, slice_str, + (const void **)&plist)) { + *info = (DU_Info)plist; + } else { + *info = NULL; + status = run_command((char **)cc, &out_length); + + if (WIFEXITED(status) && (WEXITSTATUS(status) == 0)) { + output = (char *)malloc(out_length); + + if (output) { + read_buffers(output, out_length); + + CFErrorRef err; + CFDataRef bytes = CFDataCreate(NULL, + (const unsigned char *)(output), + strlen(output)); + + if (bytes) { + plist = + CFPropertyListCreateWithData(NULL, + bytes, kCFPropertyListImmutable, + NULL, &err); + + if (plist) + CFDictionaryAddValue( + diskutil_cs_info_cache, + slice_str, plist); + + *info = (DU_Info)plist; + } + + free(output); + } + } + } + + CFRelease(slice_str); +} + +void +get_diskutil_info(char *slice, DU_Info *info) +{ + int status = 0; + int out_length = 0; + const char *cc[] = {"/usr/sbin/diskutil", "info", "-plist", + slice, NULL}; + char *output = NULL; + CFPropertyListRef plist = NULL; + CFStringRef slice_str = CFStringCreateWithCString(NULL, slice, + kCFStringEncodingUTF8); + + if (CFDictionaryGetValueIfPresent(diskutil_info_cache, slice_str, + (const void **)&plist)) { + *info = (DU_Info)plist; + } else { + *info = NULL; + status = run_command((char **)cc, &out_length); + + if (WIFEXITED(status) && (WEXITSTATUS(status) == 0)) { + output = (char *)malloc(out_length); + + if (output) { + read_buffers(output, out_length); + + CFErrorRef err; + CFDataRef bytes = CFDataCreate(NULL, + (const unsigned char *)(output), + strlen(output)); + + if (bytes) { + plist = + CFPropertyListCreateWithData(NULL, + bytes, kCFPropertyListImmutable, + NULL, &err); + + if (plist) + CFDictionaryAddValue( + diskutil_info_cache, + slice_str, plist); + + *info = (DU_Info)plist; + } + + free(output); + } + } else { + *info = NULL; + } + + } + + CFRelease(slice_str); +} + +int +is_cs_converted(DU_Info *info) +{ + return CFDictionaryValueIfPresentMatchesSubstring( + (CFDictionaryRef)info, + CFSTR("CoreStorageLogicalVolumeConversionState"), + CFSTR("Complete")); +} + +int +is_cs_locked(DU_Info *info) +{ + return CFDictionaryValueIfPresentMatchesSubstring( + (CFDictionaryRef)info, + CFSTR("CoreStorageLogicalVolumeStatus"), + CFSTR("Locked")); +} + +int +is_cs_online(DU_Info *info) +{ + return CFDictionaryValueIfPresentMatchesSubstring( + (CFDictionaryRef)info, + CFSTR("CoreStorageLogicalVolumeStatus"), + CFSTR("Online")); +} + +CFStringRef +get_cs_LV_status(DU_Info *info) +{ + CFStringRef existing = NULL; + + if (info && + CFDictionaryGetValueIfPresent((CFDictionaryRef)info, + CFSTR("CoreStorageLogicalVolumeStatus"), + (const void **)&existing)) { + return (existing); + } + + return (NULL); +} + +int +is_cs_logical_volume(DU_Info *info) +{ + return CFDictionaryValueIfPresentMatchesSubstring( + (CFDictionaryRef)info, + CFSTR("CoreStorageRole"), + CFSTR("LV")); +} + +int +is_cs_physical_volume(DU_Info *info) +{ + return CFDictionaryValueIfPresentMatchesSubstring( + (CFDictionaryRef)info, + CFSTR("CoreStorageRole"), + CFSTR("PV")); +} + +int +is_cs_disk(DU_Info *info) +{ + return (is_cs_logical_volume(info) || is_cs_physical_volume(info)); +} + +int +is_efi_partition(DU_Info info) +{ + return CFDictionaryValueIfPresentMatchesSubstring( + (CFDictionaryRef)info, + CFSTR("Content"), + CFSTR("EFI")); +} + +int +is_recovery_partition(DU_Info info) +{ + return CFDictionaryValueIfPresentMatchesSubstring( + (CFDictionaryRef)info, + CFSTR("Content"), + CFSTR("Apple_Boot")); +} + +int +is_APFS_partition(DU_Info info) +{ + return CFDictionaryValueIfPresentMatchesSubstring( + (CFDictionaryRef)info, + CFSTR("Content"), + CFSTR("Apple_APFS")); +} + +int +is_HFS_partition(DU_Info info) +{ + return CFDictionaryValueIfPresentMatchesSubstring( + (CFDictionaryRef)info, + CFSTR("Content"), + CFSTR("Apple_HFS")); +} + +int +is_MSDOS_partition(DU_Info info) +{ + return CFDictionaryValueIfPresentMatchesSubstring( + (CFDictionaryRef)info, + CFSTR("Content"), + CFSTR("Microsoft Basic Data")); +} + +int +is_whole_disk(DU_Info info) +{ + int ret = 0; + Boolean is_whole = false; + if (info && + CFDictionaryGetValueIfPresent((CFDictionaryRef)info, + CFSTR("WholeDisk"), + (const void **)&is_whole)) { + ret = is_whole; + } + + return (ret); +} + +int +has_filesystem_type(DU_Info info) +{ + return (info && + CFDictionaryContainsKey((CFDictionaryRef)info, + CFSTR("FilesystemType"))); +} + +CFStringRef +get_filesystem_type(DU_Info info) +{ + CFStringRef existing = NULL; + + if (info && + CFDictionaryGetValueIfPresent((CFDictionaryRef)info, + CFSTR("FilesystemType"), + (const void **)&existing)) { + return (existing); + } + + return (NULL); +} diff --git a/lib/os/macos/libdiskmgt/dm.c b/lib/os/macos/libdiskmgt/dm.c new file mode 100644 index 000000000000..bb2b04e58337 --- /dev/null +++ b/lib/os/macos/libdiskmgt/dm.c @@ -0,0 +1,39 @@ +/* + * 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) 2016, Brendon Humphrey (brendon.humphrey@mac.com). + */ + +#include "disks_private.h" +#include + +void +libdiskmgt_init(void) +{ + diskutil_init(); +} + +void +libdiskmgt_fini(void) +{ + diskutil_fini(); +} diff --git a/lib/os/macos/libdiskmgt/entry.c b/lib/os/macos/libdiskmgt/entry.c new file mode 100644 index 000000000000..77cee062bded --- /dev/null +++ b/lib/os/macos/libdiskmgt/entry.c @@ -0,0 +1,336 @@ +/* + * 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) 2016, Brendon Humphrey (brendon.humphrey@mac.com). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "disks_private.h" + +#define ANY_ZPOOL_USE(who) \ + (((who) == DM_WHO_ZPOOL_FORCE) || \ + ((who) == DM_WHO_ZPOOL) || \ + ((who) == DM_WHO_ZPOOL_SPARE)) + +void dm_get_slice_stats(char *slice, nvlist_t **dev_stats, int *errp); +nvlist_t *dm_get_stats(char *slice, int stat_type, int *errp); +static int build_usage_string(char *dname, const char *by, const char *data, + char **msg, +int *found, int *errp); +void dm_get_usage_string(const char *what, const char *how, + char **usage_string); + + +void +libdiskmgt_add_str(nvlist_t *attrs, const char *name, const char *val, + int *errp) +{ + if (*errp == 0) { + *errp = nvlist_add_string(attrs, name, val); + } +} + +/* + * Returns 'in use' details, if found, about a specific dev_name, + * based on the caller(who). It is important to note that it is possible + * for there to be more than one 'in use' statistic regarding a dev_name. + * The **msg parameter returns a list of 'in use' details. This message + * is formatted via gettext(). + */ +int +dm_inuse(char *dev_name, char **msg, dm_who_type_t who, int *errp) +{ + nvlist_t *dev_stats = NULL; + const char *by, *data; + nvpair_t *nvwhat = NULL; + nvpair_t *nvdesc = NULL; + int found = 0; + + *errp = 0; + *msg = NULL; + + /* + * If the user doesn't want to do in use checking, return. + */ + + if (NOINUSE_SET) + return (0); + + dm_get_slice_stats(dev_name, &dev_stats, errp); + if (dev_stats == NULL) { + /* + * If there is an error, but it isn't a no device found error + * return the error as recorded. Otherwise, with a full + * block name, we might not be able to get the slice + * associated, and will get an ENODEV error. For example, + * an SVM metadevice will return a value from getfullblkname() + * but libdiskmgt won't be able to find this device for + * statistics gathering. This is expected and we should not + * report errnoneous errors. + */ + if (*errp) { + if (*errp == ENODEV) { + *errp = 0; + } + } + // free(dname); + return (found); + } + + for (;;) { + + nvwhat = nvlist_next_nvpair(dev_stats, nvdesc); + nvdesc = nvlist_next_nvpair(dev_stats, nvwhat); + + /* + * End of the list found. + */ + if (nvwhat == NULL || nvdesc == NULL) { + break; + } + /* + * Otherwise, we check to see if this client(who) cares + * about this in use scenario + */ + + ASSERT(strcmp(nvpair_name(nvwhat), DM_USED_BY) == 0); + ASSERT(strcmp(nvpair_name(nvdesc), DM_USED_NAME) == 0); + /* + * If we error getting the string value continue on + * to the next pair(if there is one) + */ + if (nvpair_value_string(nvwhat, &by)) { + continue; + } + if (nvpair_value_string(nvdesc, &data)) { + continue; + } + + switch (who) { + + case DM_WHO_ZPOOL_FORCE: + if (strcmp(by, DM_USE_FS) == 0 || + strcmp(by, DM_USE_EXPORTED_ZPOOL) == 0 || + strcmp(by, DM_USE_OS_PARTITION) == 0) + break; + /* FALLTHROUGH */ + zfs_fallthrough; + case DM_WHO_ZPOOL: + if (build_usage_string(dev_name, + by, data, msg, &found, errp) != 0) { + if (*errp) + goto out; + } + break; + + case DM_WHO_ZPOOL_SPARE: + if (strcmp(by, DM_USE_SPARE_ZPOOL) != 0) { + if (build_usage_string(dev_name, by, + data, msg, &found, errp) != 0) { + if (*errp) + goto out; + } + } + break; + + default: + /* + * nothing found in use for this client + * of libdiskmgt. Default is 'not in use'. + */ + break; + } + } +out: + nvlist_free(dev_stats); + + return (found); +} + +nvlist_t * +dm_get_stats(char *slice, int stat_type, int *errp) +{ + nvlist_t *stats = NULL; + + /* BGH - removed everything except ability to check a slice */ + + if (stat_type == DM_SLICE_STAT_USE) { + /* + * If NOINUSE_CHECK is set, we do not perform + * the in use checking if the user has set stat_type + * DM_SLICE_STAT_USE + */ + if (NOINUSE_SET) { + stats = NULL; + return (stats); + } + } + stats = slice_get_stats(slice, stat_type, errp); + + return (stats); +} + + +/* + * Convenience function to get slice stats. This is where we are going to + * depart from the illumos implementation - libdiskmgt on that + * platform has a lot more tricks that are not applicable + * to O3X. + */ +void +dm_get_slice_stats(char *slice, nvlist_t **dev_stats, int *errp) +{ + *dev_stats = NULL; + *errp = 0; + + if (slice == NULL) { + return; + } + + *dev_stats = dm_get_stats(slice, DM_SLICE_STAT_USE, errp); +} + +void +dm_get_usage_string(const char *what, const char *how, char **usage_string) +{ + if (usage_string == NULL || what == NULL) { + return; + } + *usage_string = NULL; + + if (strcmp(what, DM_USE_MOUNT) == 0) { + if (strcmp(how, "swap") == 0) { + *usage_string = dgettext(TEXT_DOMAIN, + "%s is currently used by swap. Please see swap(1M)." + "\n"); + } else { + *usage_string = dgettext(TEXT_DOMAIN, + "%s is currently mounted on %s." + " Please see umount(1M).\n"); + } + } else if (strcmp(what, DM_USE_FS) == 0 || + strcmp(what, DM_USE_FS_NO_FORCE) == 0) { + *usage_string = dgettext(TEXT_DOMAIN, + "%s contains a %s filesystem.\n"); + } else if (strcmp(what, DM_USE_EXPORTED_ZPOOL) == 0) { + *usage_string = dgettext(TEXT_DOMAIN, + "%s is part of exported or potentially active ZFS pool %s. " + "Please see zpool(1M).\n"); + } else if (strcmp(what, DM_USE_ACTIVE_ZPOOL) == 0) { + *usage_string = dgettext(TEXT_DOMAIN, + "%s is part of active ZFS pool %s. Please see zpool(1M)." + "\n"); + } else if (strcmp(what, DM_USE_SPARE_ZPOOL) == 0) { + *usage_string = dgettext(TEXT_DOMAIN, + "%s is reserved as a hot spare for ZFS pool %s. Please " + "see zpool(1M).\n"); + } else if (strcmp(what, DM_USE_L2CACHE_ZPOOL) == 0) { + *usage_string = dgettext(TEXT_DOMAIN, + "%s is in use as a cache device for ZFS pool %s. " + "Please see zpool(1M).\n"); + } else if (strcmp(what, DM_USE_CORESTORAGE_PV) == 0) { + *usage_string = dgettext(TEXT_DOMAIN, + "%s is in use as a corestorage physical volume. " + "Please see diskutil(8).\n"); + } else if (strcmp(what, DM_USE_CORESTORAGE_LOCKED_LV) == 0) { + *usage_string = dgettext(TEXT_DOMAIN, + "%s is a corestorage logical volume, " + "but cannot be used as it is locked. " + "Please see diskutil(8).\n"); + } else if (strcmp(what, DM_USE_CORESTORAGE_CONVERTING_LV) == 0) { + *usage_string = dgettext(TEXT_DOMAIN, + "%s is a corestorage physical volume, but is still " + "converting (%s).\n" + "Creating a zpool while converting will result in " + "data corruption.\n" + "Please see diskutil(8).\n"); + } else if (strcmp(what, DM_USE_CORESTORAGE_OFFLINE_LV) == 0) { + *usage_string = dgettext(TEXT_DOMAIN, + "%s is a corestorage physical volume, but is not " + "online (%s). Please see diskutil(8).\n"); + } else if (strcmp(what, DM_USE_OS_PARTITION) == 0 || + strcmp(what, DM_USE_OS_PARTITION_NO_FORCE) == 0) { + *usage_string = dgettext(TEXT_DOMAIN, + "%s is a %s partition. " + "Please see diskutil(8).\n"); + } +} + +/* + * Build the usage string for the in use data. Return the build string in + * the msg parameter. This function takes care of reallocing all the memory + * for this usage string. Usage string is returned already formatted for + * localization. + */ +static int +build_usage_string(char *dname, const char *by, const char *data, char **msg, + int *found, int *errp) +{ + int len0; + int len1; + char *use; + char *p; + + *errp = 0; + + dm_get_usage_string(by, data, &use); + if (!use) { + return (-1); + } + + if (*msg) + len0 = strlen(*msg); + else + len0 = 0; + /* LINTED */ + len1 = snprintf(NULL, 0, use, dname, data); + + /* + * If multiple in use details they + * are listed 1 per line for ease of + * reading. dm_find_usage_string + * formats these appropriately. + */ + if ((p = realloc(*msg, len0 + len1 + 1)) == NULL) { + *errp = errno; + free(*msg); + return (-1); + } + *msg = p; + + /* LINTED */ + (void) snprintf(*msg + len0, len1 + 1, use, dname, data); + (*found)++; + return (0); +} diff --git a/lib/os/macos/libdiskmgt/inuse_corestorage.c b/lib/os/macos/libdiskmgt/inuse_corestorage.c new file mode 100644 index 000000000000..be267d99570b --- /dev/null +++ b/lib/os/macos/libdiskmgt/inuse_corestorage.c @@ -0,0 +1,99 @@ +/* + * 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) 2016, Brendon Humphrey (brendon.humphrey@mac.com). + */ + +#include +#include +#include "disks_private.h" + +int +inuse_corestorage(char *slice, nvlist_t *attrs, int *errp) +{ + DU_Info info; + int in_use = 0; + + init_diskutil_info(&info); + + get_diskutil_cs_info(slice, &info); + + if (diskutil_info_valid(info)) { + if (is_cs_physical_volume(info)) { + libdiskmgt_add_str(attrs, DM_USED_BY, + DM_USE_CORESTORAGE_PV, errp); + libdiskmgt_add_str(attrs, DM_USED_NAME, + slice, errp); + in_use = 1; + } else if (is_cs_logical_volume(info)) { + + if (is_cs_locked(info)) { + libdiskmgt_add_str(attrs, DM_USED_BY, + DM_USE_CORESTORAGE_LOCKED_LV, errp); + libdiskmgt_add_str(attrs, DM_USED_NAME, + slice, errp); + in_use = 1; + } else if (!is_cs_converted(info)) { + CFStringRef lv_status = get_cs_LV_status(info); + char lv_status_str[128] = { 0 }; + Boolean success = + CFStringGetCString(lv_status, + lv_status_str, 128, + kCFStringEncodingMacRoman); + + libdiskmgt_add_str(attrs, DM_USED_BY, + DM_USE_CORESTORAGE_CONVERTING_LV, errp); + + if (success) { + libdiskmgt_add_str(attrs, DM_USED_NAME, + lv_status_str, errp); + } else { + libdiskmgt_add_str(attrs, DM_USED_NAME, + "Unknown", errp); + } + in_use = 1; + } else if (!is_cs_online(info)) { + CFStringRef lv_status = get_cs_LV_status(info); + char lv_status_str[128] = { 0 }; + Boolean success = + CFStringGetCString(lv_status, + lv_status_str, 128, + kCFStringEncodingMacRoman); + + libdiskmgt_add_str(attrs, DM_USED_BY, + DM_USE_CORESTORAGE_OFFLINE_LV, errp); + + if (success) { + libdiskmgt_add_str(attrs, DM_USED_NAME, + lv_status_str, errp); + } else { + libdiskmgt_add_str(attrs, + DM_USED_NAME, + "Unknown", errp); + } + in_use = 1; + } + } + } + + return (in_use); +} diff --git a/lib/os/macos/libdiskmgt/inuse_fs.c b/lib/os/macos/libdiskmgt/inuse_fs.c new file mode 100644 index 000000000000..ebaaece7a855 --- /dev/null +++ b/lib/os/macos/libdiskmgt/inuse_fs.c @@ -0,0 +1,77 @@ +/* + * 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) 2016, Brendon Humphrey (brendon.humphrey@mac.com). + */ + +#include +#include +#include +#include "disks_private.h" + + +/* + * Use the heuristics to check for a filesystem on the slice. + */ +int +inuse_fs(char *slice, nvlist_t *attrs, int *errp) +{ + int in_use = 0; + DU_Info info; + + init_diskutil_info(&info); + get_diskutil_info(slice, &info); + + if (diskutil_info_valid(info) && has_filesystem_type(info)) { + CFStringRef filesystem_type = get_filesystem_type(info); + char filesystem_type_str[128] = { 0 }; + Boolean success = + CFStringGetCString(filesystem_type, + filesystem_type_str, 128, + kCFStringEncodingUTF8); + + if (filesystem_type && + (CFStringCompare(filesystem_type, CFSTR("zfs"), + kCFCompareCaseInsensitive) != kCFCompareEqualTo)) { + + if (CFStringCompare(filesystem_type, CFSTR("apfs"), + kCFCompareCaseInsensitive) == kCFCompareEqualTo) { + libdiskmgt_add_str(attrs, DM_USED_BY, + DM_USE_FS_NO_FORCE, errp); + } else { + libdiskmgt_add_str(attrs, DM_USED_BY, + DM_USE_FS, errp); + } + + if (success) { + libdiskmgt_add_str(attrs, DM_USED_NAME, + filesystem_type_str, errp); + } else { + libdiskmgt_add_str(attrs, DM_USED_NAME, + "Unknown", errp); + } + in_use = 1; + } + } + + return (in_use); +} diff --git a/lib/os/macos/libdiskmgt/inuse_macswap.c b/lib/os/macos/libdiskmgt/inuse_macswap.c new file mode 100644 index 000000000000..152dcdad264c --- /dev/null +++ b/lib/os/macos/libdiskmgt/inuse_macswap.c @@ -0,0 +1,82 @@ +/* + * 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) 2016, Brendon Humphrey (brendon.humphrey@mac.com). + */ + +#include +#include +#include +#include +#include +#include +#include "disks_private.h" + +static const char *SWAP_SYSCTL_NAME = "vm.swapfileprefix"; + +/* Return index of last slash or strlen if none */ +static ssize_t +zfs_dirnamelen(const char *path) +{ + const char *end = strrchr(path, '/'); + return (end ? end - path : strlen(path)); +} + +int +inuse_macswap(const char *dev_name) +{ + size_t oldlen = 0; + char *tmp; + char *swap_filename; + char real_swap_path[MAXPATHLEN]; + char real_dev_path[MAXPATHLEN]; + int idx; + + /* Obtain the swap file prefix (path + prototype basename) */ + if (sysctlbyname(SWAP_SYSCTL_NAME, NULL, &oldlen, NULL, 0) != 0) + return (0); + + swap_filename = (char *)malloc(oldlen); + if (sysctlbyname(SWAP_SYSCTL_NAME, swap_filename, &oldlen, NULL, + 0) != 0) + return (0); + + /* + * Get the directory portion of the vm.swapfileprefix sysctl + * once links etc have been resolved. + */ + tmp = realpath(swap_filename, NULL); + idx = zfs_dirnamelen(swap_filename); + + (void) strlcpy(real_swap_path, swap_filename, idx); + free(swap_filename); + free(tmp); + + /* Get the (resolved) directory portion of dev_name */ + tmp = realpath(dev_name, NULL); + idx = zfs_dirnamelen(tmp); + (void) strlcpy(real_dev_path, tmp, idx); + free(tmp); + + /* If the strings are equal, the file is in the swap dir */ + return (strcmp(real_dev_path, real_swap_path) == 0); +} diff --git a/lib/os/macos/libdiskmgt/inuse_mnt.c b/lib/os/macos/libdiskmgt/inuse_mnt.c new file mode 100644 index 000000000000..7bc9e73b224a --- /dev/null +++ b/lib/os/macos/libdiskmgt/inuse_mnt.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) 2016, Brendon Humphrey (brendon.humphrey@mac.com). + */ + +#include +#include +#include +#include +#include +#include "disks_private.h" + +int +inuse_mnt(char *slice, nvlist_t *attrs, int *errp) +{ + struct statfs *mounts; + + /* Read the current set of mounts */ + int num_mounts = getmntinfo(&mounts, MNT_WAIT); + + /* Check whether slice is presently in use */ + for (int i = 0; i < num_mounts; i++) { + int slice_found = (strcmp(mounts[i].f_mntfromname, slice) == 0); + + if (slice_found) { + libdiskmgt_add_str(attrs, DM_USED_BY, DM_USE_MOUNT, + errp); + libdiskmgt_add_str(attrs, DM_USED_NAME, + mounts[i].f_mntonname, errp); + return (1); + } + } + return (0); +} diff --git a/lib/os/macos/libdiskmgt/inuse_partition.c b/lib/os/macos/libdiskmgt/inuse_partition.c new file mode 100644 index 000000000000..f4fb607d882a --- /dev/null +++ b/lib/os/macos/libdiskmgt/inuse_partition.c @@ -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 + */ + +/* + * Copyright (c) 2016, Brendon Humphrey (brendon.humphrey@mac.com). + */ + +#include +#include +#include "disks_private.h" + +int +inuse_partition(char *slice, nvlist_t *attrs, int *errp) +{ + int in_use = 0; + DU_Info info; + + init_diskutil_info(&info); + get_diskutil_info(slice, &info); + + if (diskutil_info_valid(info)) { + if (is_efi_partition(info)) { + libdiskmgt_add_str(attrs, DM_USED_BY, + DM_USE_OS_PARTITION, errp); + libdiskmgt_add_str(attrs, DM_USED_NAME, + "EFI", errp); + in_use = 1; + } else if (is_recovery_partition(info)) { + libdiskmgt_add_str(attrs, DM_USED_BY, + DM_USE_OS_PARTITION_NO_FORCE, errp); + libdiskmgt_add_str(attrs, DM_USED_NAME, + "Recovery", errp); + in_use = 1; + } else if (is_APFS_partition(info)) { + libdiskmgt_add_str(attrs, DM_USED_BY, + DM_USE_OS_PARTITION_NO_FORCE, errp); + libdiskmgt_add_str(attrs, DM_USED_NAME, + "APFS", errp); + in_use = 1; + } else if (is_HFS_partition(info)) { + libdiskmgt_add_str(attrs, DM_USED_BY, + DM_USE_OS_PARTITION, errp); + libdiskmgt_add_str(attrs, DM_USED_NAME, + "HFS", errp); + in_use = 1; + } else if (is_MSDOS_partition(info)) { + libdiskmgt_add_str(attrs, DM_USED_BY, + DM_USE_OS_PARTITION, errp); + libdiskmgt_add_str(attrs, DM_USED_NAME, + "MSDOS", errp); + in_use = 1; + } + } + + return (in_use); +} diff --git a/lib/os/macos/libdiskmgt/inuse_zpool.c b/lib/os/macos/libdiskmgt/inuse_zpool.c new file mode 100644 index 000000000000..7d3042fbeafb --- /dev/null +++ b/lib/os/macos/libdiskmgt/inuse_zpool.c @@ -0,0 +1,161 @@ +/* + * 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. + */ + +/* + * Attempt to dynamically link in the ZFS libzfs.so.1 so that we can + * see if there are any ZFS zpools on any of the slices. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "libdiskmgt.h" +#include "disks_private.h" + +/* + * Pointers to libzfs.so functions that we dynamically resolve. + */ +static int (*zfsdl_zpool_in_use)(libzfs_handle_t *hdl, int fd, +pool_state_t *state, char **name, boolean_t *); +static libzfs_handle_t *(*zfsdl_libzfs_init)(boolean_t); + +static boolean_t initialized = false; +static libzfs_handle_t *zfs_hdl; + +static void *init_zpool(void); + +static int +inuse_zpool_common(char *slice, nvlist_t *attrs, int *errp, const char *type) +{ + int found = 0; + char *name; + int fd; + pool_state_t state; + boolean_t used; + + *errp = 0; + if (slice == NULL) { + return (found); + } + + /* + * Dynamically load libzfs + */ + if (!initialized) { + if (!init_zpool()) { + return (found); + } + initialized = B_TRUE; + } + + if ((fd = open(slice, O_RDONLY)) > 0) { + name = NULL; + if (zfsdl_zpool_in_use(zfs_hdl, fd, &state, + &name, &used) == 0 && used) { + if (strcmp(type, DM_USE_ACTIVE_ZPOOL) == 0) { + if (state == POOL_STATE_ACTIVE) { + found = 1; + } else if (state == POOL_STATE_SPARE) { + found = 1; + type = DM_USE_SPARE_ZPOOL; + } else if (state == POOL_STATE_L2CACHE) { + found = 1; + type = DM_USE_L2CACHE_ZPOOL; + } + } else { + found = 1; + } + + if (found) { + libdiskmgt_add_str(attrs, DM_USED_BY, + type, errp); + libdiskmgt_add_str(attrs, DM_USED_NAME, + name, errp); + } + } + if (name) + free(name); + (void) close(fd); + } + + return (found); +} + +int +inuse_active_zpool(char *slice, nvlist_t *attrs, int *errp) +{ + return (inuse_zpool_common(slice, attrs, errp, DM_USE_ACTIVE_ZPOOL)); +} + +int +inuse_exported_zpool(char *slice, nvlist_t *attrs, int *errp) +{ + return (inuse_zpool_common(slice, attrs, errp, DM_USE_EXPORTED_ZPOOL)); +} + +/* + * Try to dynamically link the zfs functions we need. + */ +static void* +init_zpool(void) +{ + void *lh = NULL; + + if ((lh = dlopen("libzfs.dylib", RTLD_NOW)) == NULL) { + return (lh); + } + + /* + * Instantiate the functions needed to get zpool configuration + * data + */ + if ((zfsdl_libzfs_init = (libzfs_handle_t *(*)(boolean_t)) + dlsym(lh, "libzfs_init")) == NULL || + (zfsdl_zpool_in_use = (int (*)(libzfs_handle_t *, int, + pool_state_t *, char **, boolean_t *)) + dlsym(lh, "zpool_in_use")) == NULL) { + (void) dlclose(lh); + return (NULL); + } + + if ((zfs_hdl = (*zfsdl_libzfs_init)(B_FALSE)) == NULL) { + (void) dlclose(lh); + return (NULL); + } + + return (lh); +} diff --git a/lib/os/macos/libdiskmgt/libdiskmgt.c b/lib/os/macos/libdiskmgt/libdiskmgt.c new file mode 100644 index 000000000000..2439d0bca046 --- /dev/null +++ b/lib/os/macos/libdiskmgt/libdiskmgt.c @@ -0,0 +1,39 @@ +/* + * 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) 2016, Brendon Humphrey (brendon.humphrey@mac.com). + */ + +#include +#include +#include +#include +#include +#include +#include "disks_private.h" +#include "libdiskmgt.h" + +int +dm_in_swap_dir(const char *dev_name) +{ + return (inuse_macswap(dev_name)); +} diff --git a/lib/os/macos/libdiskmgt/slice.c b/lib/os/macos/libdiskmgt/slice.c new file mode 100644 index 000000000000..539720be4f86 --- /dev/null +++ b/lib/os/macos/libdiskmgt/slice.c @@ -0,0 +1,104 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include "libdiskmgt.h" +#include "disks_private.h" +#include + +#ifndef VT_ENOTSUP +#define VT_ENOTSUP (-5) +#endif + +#define FMT_UNKNOWN 0 +#define FMT_VTOC 1 +#define FMT_EFI 2 + +typedef int (*detectorp)(char *, nvlist_t *, int *); + +static detectorp detectors[] = { + inuse_mnt, + inuse_corestorage, + inuse_partition, + inuse_active_zpool, + inuse_exported_zpool, + inuse_fs, /* fs should always be last */ + NULL +}; + +static int add_inuse(char *name, nvlist_t *attrs); + +nvlist_t * +slice_get_stats(char *slice, int stat_type, int *errp) +{ + nvlist_t *stats; + + if (stat_type != DM_SLICE_STAT_USE) { + *errp = EINVAL; + return (NULL); + } + + *errp = 0; + + if (nvlist_alloc(&stats, NVATTRS_STAT, 0) != 0) { + *errp = ENOMEM; + return (NULL); + } + + if ((*errp = add_inuse(slice, stats)) != 0) { + return (NULL); + } + + return (stats); +} + +/* + * Check if/how the slice is used. + */ +static int +add_inuse(char *name, nvlist_t *attrs) +{ + int i = 0; + int error = 0; + + for (i = 0; detectors[i] != NULL; i ++) { + if (detectors[i](name, attrs, &error) || error != 0) { + if (error != 0) { + return (error); + } + break; + } + } + + return (0); +} diff --git a/man/man7/zfsprops-macos.7 b/man/man7/zfsprops-macos.7 new file mode 100644 index 000000000000..639a1a4e21b4 --- /dev/null +++ b/man/man7/zfsprops-macos.7 @@ -0,0 +1,95 @@ +.\" +.\" 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 Sun Microsystems, Inc. All Rights Reserved. +.\" Copyright 2011 Joshua M. Clulow +.\" Copyright (c) 2011, 2019 by Delphix. All rights reserved. +.\" Copyright (c) 2011, Pawel Jakub Dawidek +.\" Copyright (c) 2012, Glen Barber +.\" Copyright (c) 2012, Bryan Drewery +.\" Copyright (c) 2013, Steven Hartland +.\" Copyright (c) 2013 by Saso Kiselkov. All rights reserved. +.\" Copyright (c) 2014, Joyent, Inc. All rights reserved. +.\" Copyright (c) 2014 by Adam Stevko. All rights reserved. +.\" Copyright (c) 2014 Integros [integros.com] +.\" Copyright (c) 2016 Nexenta Systems, Inc. All Rights Reserved. +.\" Copyright (c) 2014, Xin LI +.\" Copyright (c) 2014-2015, The FreeBSD Foundation, All Rights Reserved. +.\" Copyright 2019 Richard Laager. All rights reserved. +.\" Copyright 2018 Nexenta Systems, Inc. +.\" Copyright 2019 Joyent, Inc. +.\" Copyright (c) 2019, Kjeld Schouten-Lebbing +.\" +.Dd April 29, 2021 +.Dt ZFSPROPS-MACOS 7 +.Os +. +.Sh NAME +.Nm zfsprops-macos +.Nd native properties of ZFS datasets specific to macOS +. +.Sh DESCRIPTION +These properties are like the native properties described in +.Xr zfsprops 8 +but they are specific to macOS. +.Pp +The following native properties can be used to change the behavior of a ZFS +dataset. +.Bl -tag -width "" +.It Xo +.Sy com.apple.browse Ns = Ns Sy on Ns | Ns Sy off +.Xc +Equivalent to mount option +.Sy browse/nobrowse . +This option indicates +that the mount point should not be visible via the GUI (i.e., appear +on the Desktop as a separate volume). Setting it to off will result +in Spotlight being unavailable for the specified mount point. +The default value is +.Sy off . +.It Xo +.Sy com.apple.ignoreowner Ns = Ns Sy on Ns | Ns Sy off +.Xc +Equivalent to mount option +.Sy noowners . +Ignore the ownership field for the entire volume. +The default value is +.Sy off . +.It Xo +.Sy com.apple.mimic Ns = Ns Sy hfs Ns | Ns Sy off +.Xc +Some applications check if the filesystem type is "hfs" and refuse to work with +ZFS. +In this situation, this property can be enabled and the filesystem type will be +reported as "hfs". +The default value is +.Sy off . +.It Xo +.Sy com.apple.devdisk Ns = Ns Sy poolonly Ns | Ns Sy on Ns | Ns Sy off +.Xc +Create a /dev/diskX pseudo-disk for the mount, which has better compatibility +with macOS UI. +Due to the cost of creating a /dev/disk entry, the default value is set to +.Sy poolonly +as to only create an entry for the pool's root dataset. +For lower datasets, set to +.Sy on . +.El diff --git a/module/os/macos/.gitignore b/module/os/macos/.gitignore new file mode 100644 index 000000000000..a89dc70106a4 --- /dev/null +++ b/module/os/macos/.gitignore @@ -0,0 +1,3 @@ +*.in +zfs.kext +macos_zfs diff --git a/module/os/macos/Makefile.am b/module/os/macos/Makefile.am new file mode 100644 index 000000000000..1ae3686bfac1 --- /dev/null +++ b/module/os/macos/Makefile.am @@ -0,0 +1,464 @@ +# Makefile used only by macOS. Should define no dependencies for +# other platforms. + +spl_SRCS = \ + %D%/spl/spl-atomic.c \ + %D%/spl/spl-condvar.c \ + %D%/spl/spl-cred.c \ + %D%/spl/spl-debug.c \ + %D%/spl/spl-ddi.c \ + %D%/spl/spl-err.c \ + %D%/spl/spl-kmem.c \ + %D%/spl/spl-kstat.c \ + %D%/spl/spl-list.c \ + %D%/spl/spl-mutex.c \ + %D%/spl/spl-osx.c \ + %D%/spl/spl-policy.c \ + %D%/spl/spl-proc.c \ + %D%/spl/spl-processor.c \ + %D%/spl/spl-proc_list.c \ + %D%/spl/spl-qsort.c \ + %D%/spl/spl-rwlock.c \ + %D%/spl/spl-seg_kmem.c \ + %D%/spl/spl-taskq.c \ + %D%/spl/spl-thread.c \ + %D%/spl/spl-time.c \ + %D%/spl/spl-tsd.c \ + %D%/spl/spl-uio.c \ + %D%/spl/spl-vmem.c \ + %D%/spl/spl-vnode.c \ + %D%/spl/spl-xdr.c + +# libspl_la_LIBS = -lnone + + +KEXTINFO_PLIST = %D%/zfs/Info.plist +PLIST_STRING = %D%/zfs/InfoPlist.strings + +# ZFS_META_VERSION = @ZFS_META_VERSION@ +ZFS_DEBUG_STR = @ZFS_DEBUG_STR@ + +macos_zfs_CPPFLAGS = \ + -Wall \ + -nostdinc \ + -mkernel \ + -fno-builtin-printf \ + -Wframe-larger-than=1000 \ + -D__KERNEL__ \ + -D_KERNEL \ + -DKERNEL \ + -DKERNEL_PRIVATE \ + -DDRIVER_PRIVATE \ + -UHAVE_LARGE_STACKS \ + -DNAMEDSTREAMS=1 \ + -D__DARWIN_64_BIT_INO_T=1 \ + -DAPPLE \ + -DNeXT \ + -include $(top_builddir)/zfs_config.h \ + -I$(top_srcdir)/include/os/macos/spl \ + -I$(top_srcdir)/include/os/macos/zfs \ + -I$(top_srcdir)/module/icp/include \ + -I$(top_srcdir)/include \ + -I@KERNEL_HEADERS@/Headers \ + -I@KERNEL_HEADERS@/PrivateHeaders \ + -I$(top_srcdir)/module/zstd/include + + +macos_zfs_CPPFLAGS += @KERNEL_DEBUG_CPPFLAGS@ + +macos_zfs_CFLAGS = -Wno-sign-conversion -Wno-shorten-64-to-32 \ + -Wno-conditional-uninitialized -Wno-shadow \ + -Wno-implicit-int-conversion $(INLINE_HINT_FUNCTIONS) + +macos_zfs_CXXFLAGS = -Wno-sign-conversion -Wno-shorten-64-to-32 \ + -Wno-conditional-uninitialized -std=gnu++11 $(INLINE_HINT_FUNCTIONS) + +macos_zfs_LDFLAGS = \ + -Xlinker \ + -kext \ + -nostdlib \ + -lkmodc++ \ + -lkmod \ + -lcc_kext + +if TARGET_CPU_AARCH64 +macos_zfs_CPPFLAGS+=-arch arm64e -arch x86_64 +macos_zfs_LDFLAGS+=-arch arm64e -arch x86_64 +zfs_ASM_SOURCES_C = \ + %D%/../../icp/asm-aarch64/aes/aesv8-armx.S \ + %D%/../../icp/asm-aarch64/blake3/b3_aarch64_sse2.S \ + %D%/../../icp/asm-aarch64/blake3/b3_aarch64_sse41.S \ + %D%/../../icp/asm-aarch64/sha2/sha256-armv8.S \ + %D%/../../icp/asm-aarch64/sha2/sha512-armv8.S +endif + +macos_zfs_LIBS = + +# If we don't set this to nothing, it adds "-lz -liconv" +# LIBS = + +bin_PROGRAMS += noinst_zfs.kext +noinst_PROGRAMS += macos_zfs + +noinst_zfs_kext_SOURCES = + + +if TARGET_CPU_X86_64 +zfs_ASM_SOURCES_C = \ + %D%/../../icp/asm-x86_64/aes/aeskey.c \ + %D%/../../icp/algs/modes/gcm_pclmulqdq.c \ + %D%/../../zcommon/zfs_fletcher_intel.c \ + %D%/../../zcommon/zfs_fletcher_sse.c \ + %D%/../../zcommon/zfs_fletcher_avx512.c \ + %D%/../../zfs/vdev_raidz_math_sse2.c \ + %D%/../../zfs/vdev_raidz_math_ssse3.c \ + %D%/../../zfs/vdev_raidz_math_avx2.c \ + %D%/../../zfs/vdev_raidz_math_avx512f.c \ + %D%/../../zfs/vdev_raidz_math_avx512bw.c +zfs_ASM_SOURCES_AS = \ + %D%/../../icp/asm-x86_64/aes/aes_amd64.S \ + %D%/../../icp/asm-x86_64/aes/aes_aesni.S \ + %D%/../../icp/asm-x86_64/modes/aesni-gcm-x86_64.S \ + %D%/../../icp/asm-x86_64/modes/ghash-x86_64.S \ + %D%/../../icp/asm-x86_64/modes/gcm_pclmulqdq.S \ + %D%/../../icp/asm-x86_64/sha2/sha256-x86_64.S \ + %D%/../../icp/asm-x86_64/sha2/sha512-x86_64.S \ + %D%/../../icp/asm-x86_64/blake3/blake3_avx2.S \ + %D%/../../icp/asm-x86_64/blake3/blake3_avx512.S \ + %D%/../../icp/asm-x86_64/blake3/blake3_sse2.S \ + %D%/../../icp/asm-x86_64/blake3/blake3_sse41.S +else +zfs_ASM_SOURCES_C = +zfs_ASM_SOURCES_AS = +endif + +zfs_common_SRCS = \ + %D%/../../zfs/abd.c \ + %D%/../../zfs/aggsum.c \ + %D%/../../zfs/arc.c \ + %D%/../../avl/avl.c \ + %D%/../../zfs/blake3_zfs.c \ + %D%/../../zfs/blkptr.c \ + %D%/../../zfs/bplist.c \ + %D%/../../zfs/bpobj.c \ + %D%/../../zfs/bptree.c \ + %D%/../../zfs/bqueue.c \ + %D%/../../zfs/brt.c \ + %D%/../../zfs/btree.c \ + %D%/../../zfs/zfs_chksum.c \ + %D%/../../zcommon/cityhash.c \ + %D%/../../zfs/dataset_kstats.c \ + %D%/../../zfs/dbuf.c \ + %D%/../../zfs/dbuf_stats.c \ + %D%/../../zfs/ddt.c \ + %D%/../../zfs/ddt_zap.c \ + %D%/../../zfs/dmu.c \ + %D%/../../zfs/dmu_diff.c \ + %D%/../../zfs/dmu_object.c \ + %D%/../../zfs/dmu_objset.c \ + %D%/../../zfs/dmu_recv.c \ + %D%/../../zfs/dmu_redact.c \ + %D%/../../zfs/dmu_send.c \ + %D%/../../zfs/dmu_traverse.c \ + %D%/../../zfs/dmu_tx.c \ + %D%/../../zfs/dmu_zfetch.c \ + %D%/../../zfs/dnode.c \ + %D%/../../zfs/dnode_sync.c \ + %D%/../../zfs/dsl_bookmark.c \ + %D%/../../zfs/dsl_crypt.c \ + %D%/../../zfs/dsl_dataset.c \ + %D%/../../zfs/dsl_deadlist.c \ + %D%/../../zfs/dsl_deleg.c \ + %D%/../../zfs/dsl_destroy.c \ + %D%/../../zfs/dsl_dir.c \ + %D%/../../zfs/dsl_pool.c \ + %D%/../../zfs/dsl_prop.c \ + %D%/../../zfs/dsl_scan.c \ + %D%/../../zfs/dsl_synctask.c \ + %D%/../../zfs/dsl_userhold.c \ + %D%/../../zfs/edonr_zfs.c \ + %D%/../../zfs/fm.c \ + %D%/../../zfs/gzip.c \ + %D%/../../zfs/hkdf.c \ + %D%/../../zfs/lz4.c \ + %D%/../../zfs/lz4_zfs.c \ + %D%/../../zfs/lzjb.c \ + %D%/../../zfs/metaslab.c \ + %D%/../../zfs/mmp.c \ + %D%/../../zfs/multilist.c \ + %D%/../../zfs/objlist.c \ + %D%/../../zfs/pathname.c \ + %D%/../../zfs/range_tree.c \ + %D%/../../zfs/refcount.c \ + %D%/../../zfs/rrwlock.c \ + %D%/../../zfs/sa.c \ + %D%/../../zfs/sha2_zfs.c \ + %D%/../../zfs/skein_zfs.c \ + %D%/../../zfs/spa.c \ + %D%/../../zfs/spa_checkpoint.c \ + %D%/../../zfs/spa_config.c \ + %D%/../../zfs/spa_errlog.c \ + %D%/../../zfs/spa_history.c \ + %D%/../../zfs/spa_log_spacemap.c \ + %D%/../../zfs/spa_misc.c \ + %D%/../../zfs/spa_stats.c \ + %D%/../../zfs/space_map.c \ + %D%/../../zfs/space_reftree.c \ + %D%/../../zfs/txg.c \ + %D%/../../zfs/uberblock.c \ + %D%/../../zfs/unique.c \ + %D%/../../zfs/vdev.c \ + %D%/../../zfs/vdev_draid.c \ + %D%/../../zfs/vdev_draid_rand.c \ + %D%/../../zfs/vdev_indirect.c \ + %D%/../../zfs/vdev_indirect_births.c \ + %D%/../../zfs/vdev_indirect_mapping.c \ + %D%/../../zfs/vdev_initialize.c \ + %D%/../../zfs/vdev_label.c \ + %D%/../../zfs/vdev_mirror.c \ + %D%/../../zfs/vdev_missing.c \ + %D%/../../zfs/vdev_queue.c \ + %D%/../../zfs/vdev_raidz.c \ + %D%/../../zfs/vdev_raidz_math.c \ + %D%/../../zfs/vdev_raidz_math_scalar.c \ + %D%/../../zfs/vdev_rebuild.c \ + %D%/../../zfs/vdev_removal.c \ + %D%/../../zfs/vdev_root.c \ + %D%/../../zfs/vdev_trim.c \ + %D%/../../zfs/zap.c \ + %D%/../../zfs/zap_leaf.c \ + %D%/../../zfs/zap_micro.c \ + %D%/../../zfs/zcp.c \ + %D%/../../zfs/zcp_get.c \ + %D%/../../zfs/zcp_global.c \ + %D%/../../zfs/zcp_iter.c \ + %D%/../../zfs/zcp_set.c \ + %D%/../../zfs/zcp_synctask.c \ + %D%/../../zfs/zfeature.c \ + %D%/../../zcommon/zfeature_common.c \ + %D%/../../zfs/zfs_byteswap.c \ + %D%/../../zfs/zfs_fm.c \ + %D%/../../zfs/zfs_fuid.c \ + %D%/../../zfs/zfs_impl.c \ + %D%/../../zfs/zfs_ioctl.c \ + %D%/../../zfs/zfs_log.c \ + %D%/../../zfs/zfs_onexit.c \ + %D%/../../zfs/zfs_quota.c \ + %D%/../../zfs/zfs_ratelimit.c \ + %D%/../../zfs/zfs_replay.c \ + %D%/../../zfs/zfs_rlock.c \ + %D%/../../zfs/zfs_sa.c \ + %D%/../../zfs/zfs_vnops.c \ + %D%/../../zfs/zil.c \ + %D%/../../zfs/zio.c \ + %D%/../../zfs/zio_checksum.c \ + %D%/../../zfs/zio_compress.c \ + %D%/../../zfs/zio_inject.c \ + %D%/../../zfs/zle.c \ + %D%/../../zfs/zrlock.c \ + %D%/../../zfs/zthr.c \ + %D%/../../zfs/zvol.c + +zfs_os_SRCS = \ + %D%/zfs/abd_os.c \ + %D%/zfs/arc_os.c \ + %D%/zfs/ldi_osx.c \ + %D%/zfs/ldi_vnode.c \ + %D%/zfs/ldi_iokit.cpp \ + %D%/zfs/spa_misc_os.c \ + %D%/zfs/sysctl_os.c \ + %D%/zfs/vdev_disk.c \ + %D%/zfs/vdev_file.c \ + %D%/zfs/zfs_acl.c \ + %D%/zfs/zfs_boot.cpp \ + %D%/zfs/zfs_ctldir.c \ + %D%/zfs/zfs_debug.c \ + %D%/zfs/zfs_dir.c \ + %D%/zfs/zfs_file_os.c \ + %D%/zfs/zfs_fuid_os.c \ + %D%/zfs/zfs_ioctl_os.c \ + %D%/zfs/zfs_osx.cpp \ + %D%/zfs/zfs_racct.c \ + %D%/zfs/zfs_vfsops.c \ + %D%/zfs/zfs_vnops_os.c \ + %D%/zfs/zfs_vnops_osx.c \ + %D%/zfs/zfs_vnops_osx_lib.c \ + %D%/zfs/zfs_vnops_osx_xattr.c \ + %D%/zfs/zfs_znode.c \ + %D%/zfs/zio_crypt.c \ + %D%/zfs/zvol_os.c \ + %D%/zfs/zvolIO.cpp \ + %D%/zfs/ZFSDatasetProxy.cpp \ + %D%/zfs/ZFSDatasetScheme.cpp \ + %D%/zfs/ZFSDataset.cpp \ + %D%/zfs/ZFSPool.cpp + +zcommon_SRCS = \ + %D%/../../nvpair/fnvpair.c \ + %D%/../../nvpair/nvpair.c \ + %D%/../../nvpair/nvpair_alloc_fixed.c \ + %D%/../../nvpair/nvpair_alloc_spl.c \ + %D%/../../unicode/u8_textprep.c \ + %D%/../../unicode/uconv.c \ + %D%/../../zcommon/zfs_comutil.c \ + %D%/../../zcommon/zfs_deleg.c \ + %D%/../../zcommon/zfs_fletcher.c \ + %D%/../../zcommon/zfs_fletcher_superscalar.c \ + %D%/../../zcommon/zfs_fletcher_superscalar4.c \ + %D%/../../zcommon/zfs_namecheck.c \ + %D%/../../zcommon/zfs_prop.c \ + %D%/../../zcommon/zpool_prop.c \ + %D%/../../zcommon/zprop_common.c + +icp_SRCS = \ + %D%/../../icp/api/kcf_cipher.c \ + %D%/../../icp/api/kcf_mac.c \ + %D%/../../icp/api/kcf_ctxops.c \ + %D%/../../icp/core/kcf_callprov.c \ + %D%/../../icp/core/kcf_prov_tabs.c \ + %D%/../../icp/core/kcf_sched.c \ + %D%/../../icp/core/kcf_mech_tabs.c \ + %D%/../../icp/core/kcf_prov_lib.c \ + %D%/../../icp/spi/kcf_spi.c \ + %D%/../../icp/io/aes.c \ + %D%/../../icp/io/sha2_mod.c \ + %D%/../../icp/io/skein_mod.c \ + %D%/../../icp/algs/aes/aes_impl_aesni.c \ + %D%/../../icp/algs/aes/aes_impl_aesv8.c \ + %D%/../../icp/algs/aes/aes_impl_generic.c \ + %D%/../../icp/algs/aes/aes_impl_x86-64.c \ + %D%/../../icp/algs/aes/aes_impl.c \ + %D%/../../icp/algs/aes/aes_modes.c \ + %D%/../../icp/algs/blake3/blake3.c \ + %D%/../../icp/algs/blake3/blake3_generic.c \ + %D%/../../icp/algs/blake3/blake3_impl.c \ + %D%/../../icp/algs/edonr/edonr.c \ + %D%/../../icp/algs/modes/cbc.c \ + %D%/../../icp/algs/modes/ccm.c \ + %D%/../../icp/algs/modes/ctr.c \ + %D%/../../icp/algs/modes/ecb.c \ + %D%/../../icp/algs/modes/gcm_generic.c \ + %D%/../../icp/algs/modes/gcm.c \ + %D%/../../icp/algs/modes/modes.c \ + %D%/../../icp/algs/sha2/sha2_generic.c \ + %D%/../../icp/algs/sha2/sha256_impl.c \ + %D%/../../icp/algs/sha2/sha512_impl.c \ + %D%/../../icp/algs/skein/skein.c \ + %D%/../../icp/algs/skein/skein_block.c \ + %D%/../../icp/algs/skein/skein_iv.c \ + %D%/../../icp/illumos-crypto.c + +lua_SRCS = \ + %D%/../../lua/lapi.c \ + %D%/../../lua/lauxlib.c \ + %D%/../../lua/lbaselib.c \ + %D%/../../lua/lcode.c \ + %D%/../../lua/lcompat.c \ + %D%/../../lua/lcorolib.c \ + %D%/../../lua/lctype.c \ + %D%/../../lua/ldebug.c \ + %D%/../../lua/ldo.c \ + %D%/../../lua/lfunc.c \ + %D%/../../lua/lgc.c \ + %D%/../../lua/llex.c \ + %D%/../../lua/lmem.c \ + %D%/../../lua/lobject.c \ + %D%/../../lua/lopcodes.c \ + %D%/../../lua/lparser.c \ + %D%/../../lua/lstate.c \ + %D%/../../lua/lstring.c \ + %D%/../../lua/lstrlib.c \ + %D%/../../lua/ltable.c \ + %D%/../../lua/ltablib.c \ + %D%/../../lua/ltm.c \ + %D%/../../lua/lvm.c \ + %D%/../../lua/lzio.c \ + %D%/../../lua/setjmp/setjmp.S + +zstd_SRCS = \ + %D%/../../zstd/zfs_zstd.c \ + %D%/../../zstd/lib/common/entropy_common.c \ + %D%/../../zstd/lib/common/error_private.c \ + %D%/../../zstd/lib/common/fse_decompress.c \ + %D%/../../zstd/lib/common/pool.c \ + %D%/../../zstd/lib/common/zstd_common.c \ + %D%/../../zstd/lib/common/xxhash.c \ + %D%/../../zstd/lib/compress/fse_compress.c \ + %D%/../../zstd/lib/compress/hist.c \ + %D%/../../zstd/lib/compress/huf_compress.c \ + %D%/../../zstd/lib/compress/zstd_compress.c \ + %D%/../../zstd/lib/compress/zstd_compress_literals.c \ + %D%/../../zstd/lib/compress/zstd_compress_sequences.c \ + %D%/../../zstd/lib/compress/zstd_compress_superblock.c \ + %D%/../../zstd/lib/compress/zstd_double_fast.c \ + %D%/../../zstd/lib/compress/zstd_fast.c \ + %D%/../../zstd/lib/compress/zstd_lazy.c \ + %D%/../../zstd/lib/compress/zstd_ldm.c \ + %D%/../../zstd/lib/compress/zstd_opt.c \ + %D%/../../zstd/lib/decompress/huf_decompress.c \ + %D%/../../zstd/lib/decompress/zstd_ddict.c \ + %D%/../../zstd/lib/decompress/zstd_decompress.c \ + %D%/../../zstd/lib/decompress/zstd_decompress_block.c + +macos_zfs_SOURCES = \ + $(spl_SRCS) \ + $(zfs_common_SRCS) \ + $(zfs_os_SRCS) \ + $(zcommon_SRCS) \ + $(icp_SRCS) \ + $(lua_SRCS) \ + $(zstd_SRCS) \ + $(zfs_ASM_SOURCES_C) \ + $(zfs_ASM_SOURCES_AS) + +# Ensure these files are always built with -O2 to avoid stack overflow. +%D%/../../zfs/macos_zfs-dsl_scan.$(OBJEXT): CFLAGS := $(CFLAGS:-O0%=-O2) +%D%/../../lua/macos_zfs-lvm.$(OBJEXT): CFLAGS := $(CFLAGS:-O0%=-O2) + +# Zstd uses -O3 by default, so we should follow +%D%/../../zstd/lib/macos_zfs-zstd.$(OBJEXT): CFLAGS := -fno-tree-vectorize -O3 + + +KERNEL_MODDIR= $(DESTDIR)@KERNEL_MODPREFIX@/zfs.kext + +dist_noinst_DATA += $(PLIST_STRING) $(KEXTINFO_PLIST) + +noinst_zfs.kext$(EXEEXT): macos_zfs $(PLIST_STRING) $(KEXTINFO_PLIST) + @echo "" + @mkdir -p %D%/zfs.kext/Contents/Resources/English.lproj %D%/zfs.kext/Contents/MacOS + @cp -f $(KEXTINFO_PLIST) %D%/zfs.kext/Contents/ + /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $(ZFS_META_VERSION)" %D%/zfs.kext/Contents/Info.plist + /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $(ZFS_META_VERSION)" %D%/zfs.kext/Contents/Info.plist + @cp -f $(PLIST_STRING) %D%/zfs.kext/Contents/Resources/English.lproj/ + @cp -f macos_zfs %D%/zfs.kext/Contents/MacOS/zfs + @cp -f $(top_srcdir)/contrib/macOS/VolumeIcon.icns %D%/zfs.kext/Contents/Resources/VolumeIcon.icns +/usr/libexec/PlistBuddy -c "Add :OSBundleRequired string Root" %D%/zfs.kext/Contents/Info.plist + @kextlibs -unsupported -undef-symbols -xml %D%/zfs.kext/ || echo "Ignoring errors..(Most of these are expected)" + +INSTALL_DATA_HOOKS += kext-install-exec-local +kext-install-exec-local: noinst_zfs.kext + rm -rf $(KERNEL_MODDIR) + mkdir -p $(KERNEL_MODDIR) + rsync -r %D%/zfs.kext/ $(KERNEL_MODDIR) + chown -R root:wheel $(KERNEL_MODDIR) || echo "Unable to chown root:wheel $(KERNEL_MODDIR)" + @echo + @echo "To load module: kextload -v $(KERNEL_MODDIR)" + @echo "To uninstall module: rm -rf $(KERNEL_MODDIR)" + @echo + +CLEANFILES += \ + %D%/macos_zfs \ + %D%/zfs.kext/Contents/Resources/English.lproj/InfoPlist.strings \ + %D%/zfs.kext/Contents/Resources/VolumeIcon.icns \ + %D%/zfs.kext/Contents/Info.plist \ + %D%/zfs.kext/Contents/PkgInfo \ + %D%/zfs.kext/Contents/MacOS/zfs + +CLEAN_LOCAL += macos_clean_local +macos_clean_local: + @rmdir \ + %D%/zfs.kext/Contents/Resources/English.lproj \ + %D%/zfs.kext/Contents/Resources \ + %D%/zfs.kext/Contents/MacOS \ + %D%/zfs.kext/Contents \ + %D%/zfs.kext || true diff --git a/module/os/macos/README.md b/module/os/macos/README.md new file mode 100644 index 000000000000..45c21cf4a065 --- /dev/null +++ b/module/os/macos/README.md @@ -0,0 +1,8 @@ + +OpenZFS on OS X, the [macOS](https://openzfsonosx.org) port of [Open ZFS](https://openzfs.org) + +Please use the [OpenZFSOnOsX](https://github.com/openzfsonosx/openzfs) +repository for support, troubleshooting, and using GitHub issues. + +For more compiling information please visit the +[wiki](https://openzfsonosx.org/wiki/Install#Initial_installation_from_source) diff --git a/module/os/macos/spl/README.md b/module/os/macos/spl/README.md new file mode 100644 index 000000000000..cc8dbf288e46 --- /dev/null +++ b/module/os/macos/spl/README.md @@ -0,0 +1,14 @@ +The Solaris Porting Layer, SPL, is a macOS kernel module which provides a +compatibility layer used by the macOS port of Open ZFS. + +# Installation + +The latest version of the SPL is maintained as part of this repository. +Only when building ZFS version 1.9.4 or earlier must an external SPL release +be used. These releases can be found at: + + * Version 1.9.4: https://github.com/openzfsonosx/spl/tree/spl-1.9.4-release + +# Release + +The SPL is released under a CDDL license. diff --git a/module/os/macos/spl/spl-atomic.c b/module/os/macos/spl/spl-atomic.c new file mode 100644 index 000000000000..973f462fdfa2 --- /dev/null +++ b/module/os/macos/spl/spl-atomic.c @@ -0,0 +1,50 @@ +/* + * 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 + * + * Solaris Porting Layer (SPL) Atomic Implementation. + */ + +/* + * + * Copyright (C) 2013 Jorgen Lundman + * + */ + +#include +#include +#include + + +#include +#include +#include +#include + +void * +atomic_cas_ptr(volatile void *target, void *cmp, void *new) +{ +#ifdef __LP64__ + return (void *)__sync_val_compare_and_swap((uint64_t *)target, + (uint64_t)cmp, (uint64_t)new); +#else + return (void *)__sync_val_compare_and_swap((uint32_t *)target, cmp, + new); +#endif +} diff --git a/module/os/macos/spl/spl-condvar.c b/module/os/macos/spl/spl-condvar.c new file mode 100644 index 000000000000..215d04eb4ad1 --- /dev/null +++ b/module/os/macos/spl/spl-condvar.c @@ -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 (C) 2013, 2020 Jorgen Lundman + * + */ + +#include +#include +#include +#include + +extern wait_result_t thread_block(thread_continue_t continuation); + +/* + * cv_timedwait() is similar to cv_wait() except that it additionally expects + * a timeout value specified in ticks. When woken by cv_signal() or + * cv_broadcast() it returns 1, otherwise when the timeout is reached -1 is + * returned. + * + * cv_timedwait_sig() behaves the same as cv_timedwait() but blocks + * interruptibly and can be woken by a signal (EINTR, ERESTART). When + * this occurs 0 is returned. + * + * cv_timedwait_io() and cv_timedwait_sig_io() are variants of cv_timedwait() + * and cv_timedwait_sig() which should be used when waiting for outstanding + * IO to complete. They are responsible for updating the iowait accounting + * when this is supported by the platform. + * + * cv_timedwait_hires() and cv_timedwait_sig_hires() are high resolution + * versions of cv_timedwait() and cv_timedwait_sig(). They expect the timeout + * to be specified as a hrtime_t allowing for timeouts of less than a tick. + * + * N.B. The return values differ slightly from the illumos implementation + * which returns the time remaining, instead of 1, when woken. They both + * return -1 on timeout. Consumers which need to know the time remaining + * are responsible for tracking it themselves. + */ + +#ifdef SPL_DEBUG_MUTEX +void spl_wdlist_settime(void *mpleak, uint64_t value); +#endif + +void +spl_cv_init(kcondvar_t *cvp, char *name, kcv_type_t type, void *arg) +{ +} + +void +spl_cv_destroy(kcondvar_t *cvp) +{ +} + +void +spl_cv_signal(kcondvar_t *cvp) +{ + membar_consumer(); + wakeup_one((caddr_t)cvp); +} + +void +spl_cv_broadcast(kcondvar_t *cvp) +{ + membar_consumer(); + wakeup((caddr_t)cvp); +} + + +/* + * Block on the indicated condition variable and + * release the associated mutex while blocked. + */ +int +spl_cv_wait(kcondvar_t *cvp, kmutex_t *mp, int flags, const char *msg) +{ + int result; + + if (msg != NULL && msg[0] == '&') + ++msg; /* skip over '&' prefixes */ + +#ifdef SPL_DEBUG_MUTEX + spl_wdlist_settime(mp->leak, 0); +#endif + mp->m_owner = NULL; + atomic_inc_64(&mp->m_sleepers); + spl_data_barrier(); + result = msleep(cvp, (lck_mtx_t *)&mp->m_lock, flags, msg, 0); + spl_data_barrier(); + atomic_dec_64(&mp->m_sleepers); + mp->m_owner = current_thread(); +#ifdef SPL_DEBUG_MUTEX + spl_wdlist_settime(mp->leak, gethrestime_sec()); +#endif + + /* + * If already signalled, XNU never releases mutex, so + * do so manually if we know there are threads waiting. + * Avoids a starvation in bqueue_dequeue(). + * Does timedwait() versions need the same? + */ + if (result == EINTR && + (mp->m_waiters > 0 || mp->m_sleepers > 0)) { + mutex_exit(mp); + spl_data_barrier(); + (void) thread_block(THREAD_CONTINUE_NULL); + spl_data_barrier(); + mutex_enter(mp); + } + + /* + * 1 - condvar got cv_signal()/cv_broadcast() + * 0 - received signal (kill -signal) + */ + return (result == EINTR ? 0 : 1); +} + +/* + * Same as cv_wait except the thread will unblock at 'tim' + * (an absolute time) if it hasn't already unblocked. + * + * Returns the amount of time left from the original 'tim' value + * when it was unblocked. + */ +int +spl_cv_timedwait(kcondvar_t *cvp, kmutex_t *mp, clock_t tim, int flags, + const char *msg) +{ + struct timespec ts; + int msleep_result; + + if (msg != NULL && msg[0] == '&') + ++msg; /* skip over '&' prefixes */ + + clock_t timenow = zfs_lbolt(); + + /* Already expired? */ + if (timenow >= tim) + return (-1); + + tim -= timenow; + + ts.tv_sec = (tim / hz); + ts.tv_nsec = (tim % hz) * NSEC_PER_SEC / hz; + + /* Both sec and nsec zero is a blocking call in XNU. (Not poll) */ + if (ts.tv_sec == 0 && ts.tv_nsec == 0) + ts.tv_nsec = 1000; + +#ifdef SPL_DEBUG_MUTEX + spl_wdlist_settime(mp->leak, 0); +#endif + + mp->m_owner = NULL; + atomic_inc_64(&mp->m_sleepers); + spl_data_barrier(); + msleep_result = msleep(cvp, (lck_mtx_t *)&mp->m_lock, flags, msg, &ts); + spl_data_barrier(); + atomic_dec_64(&mp->m_sleepers); + mp->m_owner = current_thread(); + +#ifdef SPL_DEBUG_MUTEX + spl_wdlist_settime(mp->leak, gethrestime_sec()); +#endif + + switch (msleep_result) { + + case EINTR: /* Signal */ + case ERESTART: + return (0); + + case EWOULDBLOCK: /* Timeout: EAGAIN */ + return (-1); + } + + ASSERT0(msleep_result); + return (1); +} + + +/* + * Compatibility wrapper for the cv_timedwait_hires() Illumos interface. + */ +int +cv_timedwait_hires(kcondvar_t *cvp, kmutex_t *mp, hrtime_t tim, + hrtime_t res, int flag) +{ + struct timespec ts; + int msleep_result; + + if (res > 1) { + /* + * Align expiration to the specified resolution. + */ + if (flag & CALLOUT_FLAG_ROUNDUP) + tim += res - 1; + tim = (tim / res) * res; + } + + if ((flag & CALLOUT_FLAG_ABSOLUTE)) { + hrtime_t timenow = gethrtime(); + + /* Already expired? */ + if (timenow >= tim) + return (-1); + + tim -= timenow; + } + + ts.tv_sec = NSEC2SEC(tim); + ts.tv_nsec = tim - SEC2NSEC(ts.tv_sec); + + /* Both sec and nsec set to zero is a blocking call in XNU. */ + if (ts.tv_sec == 0 && ts.tv_nsec == 0) + ts.tv_nsec = 1000; + +#ifdef SPL_DEBUG_MUTEX + spl_wdlist_settime(mp->leak, 0); +#endif + + mp->m_owner = NULL; + atomic_inc_64(&mp->m_sleepers); + spl_data_barrier(); + msleep_result = msleep(cvp, (lck_mtx_t *)&mp->m_lock, + flag, "cv_timedwait_hires", &ts); + spl_data_barrier(); + atomic_dec_64(&mp->m_sleepers); + mp->m_owner = current_thread(); +#ifdef SPL_DEBUG_MUTEX + spl_wdlist_settime(mp->leak, gethrestime_sec()); +#endif + + switch (msleep_result) { + + case EINTR: /* Signal */ + case ERESTART: + return (0); + + case EWOULDBLOCK: /* Timeout */ + return (-1); + } + + ASSERT0(msleep_result); + return (1); +} diff --git a/module/os/macos/spl/spl-cred.c b/module/os/macos/spl/spl-cred.c new file mode 100644 index 000000000000..282588564078 --- /dev/null +++ b/module/os/macos/spl/spl-cred.c @@ -0,0 +1,153 @@ +/* + * 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) 2008 MacZFS + * Copyright (C) 2013 Jorgen Lundman + * + */ + +#include +#include +#include + +/* Return the effective user id */ +uid_t +crgetuid(const cred_t *cr) +{ + if (!cr) + return (0); + return (kauth_cred_getuid((kauth_cred_t)cr)); +} + +/* Return the real user id */ +uid_t +crgetruid(const cred_t *cr) +{ + if (!cr) + return (0); + return (kauth_cred_getruid((kauth_cred_t)cr)); +} + +/* Return the saved user id */ +uid_t +crgetsuid(const cred_t *cr) +{ + if (!cr) + return (0); + return (kauth_cred_getsvuid((kauth_cred_t)cr)); +} + +/* Return the filesystem user id */ +uid_t +crgetfsuid(const cred_t *cr) +{ + if (!cr) + return (0); + return (-1); +} + +/* Return the effective group id */ +gid_t +crgetgid(const cred_t *cr) +{ + if (!cr) + return (0); + return (kauth_cred_getgid((kauth_cred_t)cr)); +} + +/* Return the real group id */ +gid_t +crgetrgid(const cred_t *cr) +{ + if (!cr) + return (0); + return (kauth_cred_getrgid((kauth_cred_t)cr)); +} + +/* Return the saved group id */ +gid_t +crgetsgid(const cred_t *cr) +{ + if (!cr) + return (0); + return (kauth_cred_getsvgid((kauth_cred_t)cr)); +} + +/* Return the filesystem group id */ +gid_t +crgetfsgid(const cred_t *cr) +{ + return (-1); +} + + +extern int kauth_cred_getgroups(kauth_cred_t _cred, gid_t *_groups, + int *_groupcount); +/* + * Unfortunately, to get the count of groups, we have to call XNU which + * memcpy's them over. No real clean way to get around that, but at least + * these calls are done sparingly. + * dsl_deleg.c: dsl_check_user_access() loops the gid the user is in + * to call dsl_check_access(gid) to see if "zfs allow" matches. + * If we can iterate the gids saved in mos, and test with + * kauth_cred_ismember_gid() the equivalent can be achieved. + * However, "zfs allow" does not yet work of macOS. + */ +int +crgetngroups(const cred_t *cr) +{ + return (0); +} + + +/* + * We always allocate NGROUPs here, since we don't know how many there will + * be until after the call. Unlike IllumOS, the ptr returned is allocated + * and must be returned by a call to crgetgroupsfree(). + */ +gid_t * +crgetgroups(const cred_t *cr) +{ + return (NULL); +} + +void +crgetgroupsfree(gid_t *gids) +{ + if (!gids) + return; + kmem_free(gids, sizeof (gid_t) * NGROUPS); +} + +/* + * Return true if "cr" belongs in group "gid". + */ +int +spl_cred_ismember_gid(cred_t *cr, gid_t gid) +{ + int ret = 0; // Is not member. + kauth_cred_ismember_gid((kauth_cred_t)cr, gid, &ret); + if (ret == 1) + return (TRUE); + return (FALSE); +} diff --git a/module/os/macos/spl/spl-ddi.c b/module/os/macos/spl/spl-ddi.c new file mode 100644 index 000000000000..25f4d1205df7 --- /dev/null +++ b/module/os/macos/spl/spl-ddi.c @@ -0,0 +1,410 @@ +/* + * 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 +#include +#include +#include +#include +#include + + +/* + * Allocate a set of pointers to 'n_items' objects of size 'size' + * bytes. Each pointer is initialized to nil. + * + * The 'size' and 'n_items' values are stashed in the opaque + * handle returned to the caller. + * + * This implementation interprets 'set of pointers' to mean 'array + * of pointers' but note that nothing in the interface definition + * precludes an implementation that uses, for example, a linked list. + * However there should be a small efficiency gain from using an array + * at lookup time. + * + * NOTE As an optimization, we make our growable array allocations in + * powers of two (bytes), since that's how much kmem_alloc (currently) + * gives us anyway. It should save us some free/realloc's .. + * + * As a further optimization, we make the growable array start out + * with MIN_N_ITEMS in it. + */ + + +int +ddi_soft_state_init(void **state_p, size_t size, size_t n_items) +{ + struct i_ddi_soft_state *ss; + + if (state_p == NULL || *state_p != NULL || size == 0) + return (EINVAL); + + ss = kmem_zalloc(sizeof (*ss), KM_SLEEP); + mutex_init(&ss->lock, NULL, MUTEX_DRIVER, NULL); + ss->size = size; + + if (n_items < MIN_N_ITEMS) + ss->n_items = MIN_N_ITEMS; + else { + int bitlog; + + if ((bitlog = ddi_fls(n_items)) == ddi_ffs(n_items)) + bitlog--; + ss->n_items = 1 << bitlog; + } + + ASSERT(ss->n_items >= n_items); + + ss->array = kmem_zalloc(ss->n_items * sizeof (void *), KM_SLEEP); + + *state_p = ss; + + return (0); +} + + +/* + * Allocate a state structure of size 'size' to be associated + * with item 'item'. + * + * In this implementation, the array is extended to + * allow the requested offset, if needed. + */ +int +ddi_soft_state_zalloc(void *state, int item) +{ + struct i_ddi_soft_state *ss; + void **array; + void *new_element; + + if ((ss = state) == NULL || item < 0) + return (DDI_FAILURE); + + mutex_enter(&ss->lock); + if (ss->size == 0) { + mutex_exit(&ss->lock); + cmn_err(CE_WARN, "ddi_soft_state_zalloc: bad handle"); + return (DDI_FAILURE); + } + + array = ss->array; /* NULL if ss->n_items == 0 */ + ASSERT(ss->n_items != 0 && array != NULL); + + /* + * refuse to tread on an existing element + */ + if (item < ss->n_items && array[item] != NULL) { + mutex_exit(&ss->lock); + return (DDI_FAILURE); + } + + /* + * Allocate a new element to plug in + */ + new_element = kmem_zalloc(ss->size, KM_SLEEP); + + /* + * Check if the array is big enough, if not, grow it. + */ + if (item >= ss->n_items) { + void **new_array; + size_t new_n_items; + struct i_ddi_soft_state *dirty; + + /* + * Allocate a new array of the right length, copy + * all the old pointers to the new array, then + * if it exists at all, put the old array on the + * dirty list. + * + * Note that we can't kmem_free() the old array. + * + * Why -- well the 'get' operation is 'mutex-free', so we + * can't easily catch a suspended thread that is just about + * to dereference the array we just grew out of. So we + * cons up a header and put it on a list of 'dirty' + * pointer arrays. (Dirty in the sense that there may + * be suspended threads somewhere that are in the middle + * of referencing them). Fortunately, we -can- garbage + * collect it all at ddi_soft_state_fini time. + */ + new_n_items = ss->n_items; + while (new_n_items < (1 + item)) + new_n_items <<= 1; /* double array size .. */ + + ASSERT(new_n_items >= (1 + item)); /* sanity check! */ + + new_array = kmem_zalloc(new_n_items * sizeof (void *), + KM_SLEEP); + /* + * Copy the pointers into the new array + */ + memcpy(new_array, array, ss->n_items * sizeof (void *)); + + /* + * Save the old array on the dirty list + */ + dirty = kmem_zalloc(sizeof (*dirty), KM_SLEEP); + dirty->array = ss->array; + dirty->n_items = ss->n_items; + dirty->next = ss->next; + ss->next = dirty; + + ss->array = (array = new_array); + ss->n_items = new_n_items; + } + + ASSERT(array != NULL && item < ss->n_items && array[item] == NULL); + + array[item] = new_element; + + mutex_exit(&ss->lock); + return (DDI_SUCCESS); +} + + +/* + * Fetch a pointer to the allocated soft state structure. + * + * This is designed to be cheap. + * + * There's an argument that there should be more checking for + * nil pointers and out of bounds on the array.. but we do a lot + * of that in the alloc/free routines. + * + * An array has the convenience that we don't need to lock read-access + * to it c.f. a linked list. However our "expanding array" strategy + * means that we should hold a readers lock on the i_ddi_soft_state + * structure. + * + * However, from a performance viewpoint, we need to do it without + * any locks at all -- this also makes it a leaf routine. The algorithm + * is 'lock-free' because we only discard the pointer arrays at + * ddi_soft_state_fini() time. + */ +void * +ddi_get_soft_state(void *state, int item) +{ + struct i_ddi_soft_state *ss = state; + + ASSERT(ss != NULL && item >= 0); + + if (item < ss->n_items && ss->array != NULL) + return (ss->array[item]); + return (NULL); +} + +/* + * Free the state structure corresponding to 'item.' Freeing an + * element that has either gone or was never allocated is not + * considered an error. Note that we free the state structure, but + * we don't shrink our pointer array, or discard 'dirty' arrays, + * since even a few pointers don't really waste too much memory. + * + * Passing an item number that is out of bounds, or a null pointer will + * provoke an error message. + */ +void +ddi_soft_state_free(void *state, int item) +{ + struct i_ddi_soft_state *ss; + void **array; + void *element; + static char msg[] = "ddi_soft_state_free:"; + + if ((ss = state) == NULL) { + cmn_err(CE_WARN, "%s null handle", + msg); + return; + } + + element = NULL; + + mutex_enter(&ss->lock); + + if ((array = ss->array) == NULL || ss->size == 0) { + cmn_err(CE_WARN, "%s bad handle", + msg); + } else if (item < 0 || item >= ss->n_items) { + cmn_err(CE_WARN, "%s item %d not in range [0..%lu]", + msg, item, ss->n_items - 1); + } else if (array[item] != NULL) { + element = array[item]; + array[item] = NULL; + } + + mutex_exit(&ss->lock); + + if (element) + kmem_free(element, ss->size); +} + + +/* + * Free the entire set of pointers, and any + * soft state structures contained therein. + * + * Note that we don't grab the ss->lock mutex, even though + * we're inspecting the various fields of the data structure. + * + * There is an implicit assumption that this routine will + * never run concurrently with any of the above on this + * particular state structure i.e. by the time the driver + * calls this routine, there should be no other threads + * running in the driver. + */ +void +ddi_soft_state_fini(void **state_p) +{ + struct i_ddi_soft_state *ss, *dirty; + int item; + static char msg[] = "ddi_soft_state_fini:"; + + if (state_p == NULL || (ss = *state_p) == NULL) + return; + + if (ss->size == 0) { + cmn_err(CE_WARN, "%s bad handle", + msg); + return; + } + + if (ss->n_items > 0) { + for (item = 0; item < ss->n_items; item++) + ddi_soft_state_free(ss, item); + kmem_free(ss->array, ss->n_items * sizeof (void *)); + } + + /* + * Now delete any dirty arrays from previous 'grow' operations + */ + for (dirty = ss->next; dirty; dirty = ss->next) { + ss->next = dirty->next; + kmem_free(dirty->array, dirty->n_items * sizeof (void *)); + kmem_free(dirty, sizeof (*dirty)); + } + + mutex_destroy(&ss->lock); + kmem_free(ss, sizeof (*ss)); + + *state_p = NULL; +} + +int +ddi_create_minor_node(dev_info_t *dip, char *name, int spec_type, + minor_t minor_num, char *node_type, int flag) +{ + dev_t dev; + int error = 0; + char *r, *dup; + + dev = makedev(flag, minor_num); + dip->dev = dev; + + /* + * http://lists.apple.com/archives/darwin-kernel/2007/Nov/msg00038.html + * + * devfs_make_name() has an off-by-one error when using directories + * and it appears Apple does not want to fix it. + * + * We then change "/" to "_" and create more Apple-like /dev names + * + */ + int len = strlen(name); + dup = IOMallocAligned(len + 1, _Alignof(char)); + if (dup == NULL) + return (ENOMEM); + memcpy(dup, name, len); + dup[len] = '\0'; + + for (r = dup; + (r = strchr(r, '/')); + *r = '_') + /* empty */; + + dip->devc = NULL; + dip->devb = NULL; + + if (spec_type == S_IFCHR) + dip->devc = devfs_make_node(dev, DEVFS_CHAR, + UID_ROOT, GID_OPERATOR, + 0600, "rdisk_%s", dup); + else + dip->devb = devfs_make_node(dev, DEVFS_BLOCK, + UID_ROOT, GID_OPERATOR, + 0600, "disk_%s", dup); + IOFreeAligned(dup, len + 1); + + return (error); +} + +void +ddi_remove_minor_node(dev_info_t *dip, char *name) +{ + if (dip->devc) { + devfs_remove(dip->devc); + dip->devc = NULL; + } + if (dip->devb) { + devfs_remove(dip->devb); + dip->devb = NULL; + } +} + +int +strspn(const char *string, + char *charset) +{ + const char *p, *q; + + for (q = string; *q != '\0'; ++q) { + for (p = charset; *p != '\0' && *p != *q; ++p) + ; + if (*p == '\0') + break; + } + return (q-string); +} + +#undef strcmp +int +spl_strcmp(const char *s1, const char *s2) +{ + char c1, c2; + + while (1) { + c1 = *s1++; + c2 = *s2++; + + if ((c1 == 0) && + (c2 == 0)) + break; + + if ((c1 == 0) || + (c1 < c2)) + return (-1); + if ((c2 == 0) || + (c1 > c2)) + return (1); + } // while + + return (0); +} diff --git a/module/os/macos/spl/spl-debug.c b/module/os/macos/spl/spl-debug.c new file mode 100644 index 000000000000..28ec1612d432 --- /dev/null +++ b/module/os/macos/spl/spl-debug.c @@ -0,0 +1,10 @@ +#include + + + +/* Debug log support enabled */ +__attribute__((noinline)) int assfail(const char *str, const char *file, + unsigned int line) __attribute__((optnone)) +{ + return (1); /* Must return true for ASSERT macro */ +} diff --git a/module/os/macos/spl/spl-err.c b/module/os/macos/spl/spl-err.c new file mode 100644 index 000000000000..455bf2c8b9d6 --- /dev/null +++ b/module/os/macos/spl/spl-err.c @@ -0,0 +1,83 @@ +/* + * 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, 2020 Jorgen Lundman + * + */ + +#include +#include +#include + +void +vcmn_err(int ce, const char *fmt, va_list ap) +{ + char msg[MAXMSGLEN]; + + vsnprintf(msg, MAXMSGLEN - 1, fmt, ap); + + switch (ce) { + case CE_IGNORE: + break; + case CE_CONT: + printf("%s", msg); + break; + case CE_NOTE: + printf("SPL: Notice: %s\n", msg); + break; + case CE_WARN: + printf("SPL: Warning: %s\n", msg); + break; + case CE_PANIC: + PANIC("%s", msg); + break; + } +} /* vcmn_err() */ + +void +cmn_err(int ce, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vcmn_err(ce, fmt, ap); + va_end(ap); +} /* cmn_err() */ + + +int +spl_panic(const char *file, const char *func, int line, const char *fmt, ...) +{ + char msg[MAXMSGLEN]; + va_list ap; + + va_start(ap, fmt); + (void) vsnprintf(msg, sizeof (msg), fmt, ap); + va_end(ap); + + printf("%s", msg); + panic("%s", msg); + + /* Unreachable */ + return (1); +} diff --git a/module/os/macos/spl/spl-kmem.c b/module/os/macos/spl/spl-kmem.c new file mode 100644 index 000000000000..c0bf68e4edf8 --- /dev/null +++ b/module/os/macos/spl/spl-kmem.c @@ -0,0 +1,6873 @@ +/* + * 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) 2008 MacZFS + * Copyright (C) 2013, 2020 Jorgen Lundman + * Copyright (C) 2014 Brendon Humphrey + * Copyright (C) 2017, 2021, 2023 Sean Doran + * Copyright 2015 Nexenta Systems, Inc. All rights reserved. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// =============================================================== +// Options +// =============================================================== +// #define PRINT_CACHE_STATS 1 + +// =============================================================== +// OS Interface +// =============================================================== + +// 3500 kern.spl_vm_page_free_min, rarely changes +const unsigned int spl_vm_page_free_min = 3500; + +#define SMALL_PRESSURE_INCURSION_PAGES (spl_vm_page_free_min >> 5) + +static kcondvar_t spl_free_thread_cv; +static kmutex_t spl_free_thread_lock; +static boolean_t spl_free_thread_exit; +static volatile _Atomic int64_t spl_free; + +static volatile _Atomic int64_t spl_free_manual_pressure = 0; +static volatile _Atomic boolean_t spl_free_fast_pressure = FALSE; +static _Atomic bool spl_free_maybe_reap_flag = false; +static _Atomic uint64_t spl_free_last_pressure = 0; + +uint64_t spl_enforce_memory_caps = 1; +_Atomic uint64_t spl_dynamic_memory_cap = 0; +hrtime_t spl_dynamic_memory_cap_last_downward_adjust = 0; +uint64_t spl_dynamic_memory_cap_skipped = 0; +kmutex_t spl_dynamic_memory_cap_lock; +uint64_t spl_dynamic_memory_cap_reductions = 0; +uint64_t spl_dynamic_memory_cap_hit_floor = 0; +static uint64_t spl_manual_memory_cap = 0; +static uint64_t spl_memory_cap_enforcements = 0; + +extern void spl_set_arc_no_grow(int); + +/* + * variables informed by "pure" mach_vm_pressure interface + * + * osfmk/vm/vm_pageout.c: "We don't need fully + * accurate monitoring anyway..." + * + * but in macOS_pure we do want modifications of these + * variables to be seen by all the other threads + * consistently, and asap (there may be hundreds + * of simultaneous readers, even if few writers!) + */ +_Atomic uint32_t spl_vm_pages_reclaimed = 0; +_Atomic uint32_t spl_vm_pages_wanted = 0; +_Atomic uint32_t spl_vm_pressure_level = 0; + +/* From osfmk/vm/vm_pageout.h */ +extern kern_return_t mach_vm_pressure_level_monitor( + boolean_t wait_for_pressure, unsigned int *pressure_level); +extern kern_return_t mach_vm_pressure_monitor( + boolean_t wait_for_pressure, + unsigned int nsecs_monitored, + unsigned int *pages_reclaimed_p, + unsigned int *pages_wanted_p); + +/* + * the spl_pressure_level enum only goes to four, + * but we want to watch kstat for whether + * mach's pressure is unavailable + */ +#define MAGIC_PRESSURE_UNAVAILABLE 1001001 + +// Start and end address of kernel memory +extern vm_offset_t virtual_space_start; +extern vm_offset_t virtual_space_end; + +// Can be polled to determine if the VM is experiecing +// a shortage of free pages. +extern int vm_pool_low(void); + +// Which CPU are we executing on? +extern int cpu_number(void); + +// Invoke the kernel debugger +extern void Debugger(const char *message); + +// Read from /dev/random +void read_random(void *buffer, uint_t numbytes); + +// =============================================================== +// Non Illumos Variables +// =============================================================== + +// Flag to cause tasks and threads to terminate as +// the kmem module is preparing to unload. +static int shutting_down = 0; + +// Amount of RAM PAGES in machine +uint64_t physmem = 0; + +// Size in bytes of the memory allocated in seg_kmem +extern uint64_t segkmem_total_mem_allocated; + +// Number of active threads +extern uint64_t zfs_threads; +extern uint64_t zfs_active_mutex; +extern uint64_t zfs_active_rwlock; + +// Half and full amount of RAM bytes in machine +extern uint64_t total_memory; +extern uint64_t real_total_memory; + +#define MULT 1 + +static const char *KMEM_VA_PREFIX = "kmem_va"; +static const char *KMEM_MAGAZINE_PREFIX = "kmem_magazine_"; + +// =============================================================== +// Illumos Variables +// =============================================================== + +struct kmem_cache_kstat { + kstat_named_t kmc_buf_size; + kstat_named_t kmc_align; + kstat_named_t kmc_chunk_size; + kstat_named_t kmc_slab_size; + kstat_named_t kmc_alloc; + kstat_named_t kmc_alloc_fail; + kstat_named_t kmc_free; + kstat_named_t kmc_depot_alloc; + kstat_named_t kmc_depot_free; + kstat_named_t kmc_depot_contention; + kstat_named_t kmc_slab_alloc; + kstat_named_t kmc_slab_free; + kstat_named_t kmc_buf_constructed; + kstat_named_t kmc_buf_avail; + kstat_named_t kmc_buf_inuse; + kstat_named_t kmc_buf_total; + kstat_named_t kmc_buf_max; + kstat_named_t kmc_slab_create; + kstat_named_t kmc_slab_destroy; + kstat_named_t kmc_vmem_source; + kstat_named_t kmc_hash_size; + kstat_named_t kmc_hash_lookup_depth; + kstat_named_t kmc_hash_rescale; + kstat_named_t kmc_full_magazines; + kstat_named_t kmc_empty_magazines; + kstat_named_t kmc_magazine_size; + kstat_named_t kmc_reap; /* number of kmem_cache_reap() calls */ + kstat_named_t kmc_defrag; /* attempts to defrag all partial slabs */ + kstat_named_t kmc_scan; /* attempts to defrag one partial slab */ + kstat_named_t kmc_move_callbacks; /* sum of yes, no, later, dn, dk */ + kstat_named_t kmc_move_yes; + kstat_named_t kmc_move_no; + kstat_named_t kmc_move_later; + kstat_named_t kmc_move_dont_need; + kstat_named_t kmc_move_dont_know; /* obj unrecognized by client ... */ + kstat_named_t kmc_move_hunt_found; /* ... but found in mag layer */ + kstat_named_t kmc_move_slabs_freed; /* slabs freed by consolidator */ + kstat_named_t kmc_move_reclaimable; /* buffers, if consolidator ran */ + kstat_named_t kmc_no_vba_success; + kstat_named_t kmc_no_vba_fail; + kstat_named_t kmc_arc_no_grow_set; + kstat_named_t kmc_arc_no_grow; +} kmem_cache_kstat = { + { "buf_size", KSTAT_DATA_UINT64 }, + { "align", KSTAT_DATA_UINT64 }, + { "chunk_size", KSTAT_DATA_UINT64 }, + { "slab_size", KSTAT_DATA_UINT64 }, + { "alloc", KSTAT_DATA_UINT64 }, + { "alloc_fail", KSTAT_DATA_UINT64 }, + { "free", KSTAT_DATA_UINT64 }, + { "depot_alloc", KSTAT_DATA_UINT64 }, + { "depot_free", KSTAT_DATA_UINT64 }, + { "depot_contention", KSTAT_DATA_UINT64 }, + { "slab_alloc", KSTAT_DATA_UINT64 }, + { "slab_free", KSTAT_DATA_UINT64 }, + { "buf_constructed", KSTAT_DATA_UINT64 }, + { "buf_avail", KSTAT_DATA_UINT64 }, + { "buf_inuse", KSTAT_DATA_UINT64 }, + { "buf_total", KSTAT_DATA_UINT64 }, + { "buf_max", KSTAT_DATA_UINT64 }, + { "slab_create", KSTAT_DATA_UINT64 }, + { "slab_destroy", KSTAT_DATA_UINT64 }, + { "vmem_source", KSTAT_DATA_UINT64 }, + { "hash_size", KSTAT_DATA_UINT64 }, + { "hash_lookup_depth", KSTAT_DATA_UINT64 }, + { "hash_rescale", KSTAT_DATA_UINT64 }, + { "full_magazines", KSTAT_DATA_UINT64 }, + { "empty_magazines", KSTAT_DATA_UINT64 }, + { "magazine_size", KSTAT_DATA_UINT64 }, + { "reap", KSTAT_DATA_UINT64 }, + { "defrag", KSTAT_DATA_UINT64 }, + { "scan", KSTAT_DATA_UINT64 }, + { "move_callbacks", KSTAT_DATA_UINT64 }, + { "move_yes", KSTAT_DATA_UINT64 }, + { "move_no", KSTAT_DATA_UINT64 }, + { "move_later", KSTAT_DATA_UINT64 }, + { "move_dont_need", KSTAT_DATA_UINT64 }, + { "move_dont_know", KSTAT_DATA_UINT64 }, + { "move_hunt_found", KSTAT_DATA_UINT64 }, + { "move_slabs_freed", KSTAT_DATA_UINT64 }, + { "move_reclaimable", KSTAT_DATA_UINT64 }, + { "no_vba_success", KSTAT_DATA_UINT64 }, + { "no_vba_fail", KSTAT_DATA_UINT64 }, + { "arc_no_grow_set", KSTAT_DATA_UINT64 }, + { "arc_no_grow", KSTAT_DATA_UINT64 }, +}; + +static kmutex_t kmem_cache_kstat_lock; + +/* + * The default set of caches to back kmem_alloc(). + * These sizes should be reevaluated periodically. + * + * We want allocations that are multiples of the coherency granularity + * (64 bytes) to be satisfied from a cache which is a multiple of 64 + * bytes, so that it will be 64-byte aligned. For all multiples of 64, + * the next 1 greater than or equal to it must be a + * multiple of 64. + * + * We split the table into two sections: size <= 4k and size > 4k. This + * saves a lot of space and cache footprint in our cache tables. + */ +static const int kmem_alloc_sizes[] = { + 1 * 8, + 2 * 8, + 3 * 8, + 4 * 8, 5 * 8, 6 * 8, 7 * 8, + 4 * 16, 5 * 16, 6 * 16, 7 * 16, + 4 * 32, 5 * 32, 6 * 32, 7 * 32, + 4 * 64, 5 * 64, 6 * 64, 7 * 64, + 4 * 128, 9*64, 5 * 128, 6 * 128, 13*64, 7 * 128, + P2ALIGN(8192 / 8, 64), + P2ALIGN(8192 / 7, 64), + P2ALIGN(8192 / 6, 64), + P2ALIGN(8192 / 5, 64), + P2ALIGN(8192 / 4, 64), + P2ALIGN(8192 / 3, 64), + P2ALIGN(8192 / 2, 64), +}; + +static const int kmem_big_alloc_sizes[] = { + 2 * 4096, 3 * 4096, + 2 * 8192, 3 * 8192, + 4 * 8192, 5 * 8192, 6 * 8192, 7 * 8192, + 8 * 8192, 9 * 8192, 10 * 8192, 11 * 8192, + 12 * 8192, 13 * 8192, 14 * 8192, 15 * 8192, + 16 * 8192 +}; + +#define KMEM_MAXBUF 4096 +#define KMEM_BIG_MAXBUF_32BIT 32768 +#define KMEM_BIG_MAXBUF 131072 + +#define KMEM_BIG_MULTIPLE 4096 /* big_alloc_sizes must be a multiple */ +#define KMEM_BIG_SHIFT 12 /* lg(KMEM_BIG_MULTIPLE) */ + +static kmem_cache_t *kmem_alloc_table[KMEM_MAXBUF >> KMEM_ALIGN_SHIFT]; +static kmem_cache_t *kmem_big_alloc_table[KMEM_BIG_MAXBUF >> KMEM_BIG_SHIFT]; + +#define KMEM_ALLOC_TABLE_MAX (KMEM_MAXBUF >> KMEM_ALIGN_SHIFT) +static size_t kmem_big_alloc_table_max = 0; /* # of filled elements */ + +static kmem_magtype_t kmem_magtype[] = { + { 1, 8, 3200, 65536 }, + { 3, 16, 256, 32768 }, + { 7, 32, 64, 16384 }, + { 15, 64, 0, 8192 }, + { 31, 64, 0, 4096 }, + { 47, 64, 0, 2048 }, + { 63, 64, 0, 1024 }, + { 95, 64, 0, 512 }, + { 143, 64, 0, 0 }, +}; + +static uint32_t kmem_reaping; +static uint32_t kmem_reaping_idspace; + +/* + * kmem tunables + */ +static struct timespec kmem_reap_interval = {15, 0}; +int kmem_depot_contention = 3; /* max failed tryenters per real interval */ +pgcnt_t kmem_reapahead = 0; /* start reaping N pages before pageout */ +int kmem_panic = 1; /* whether to panic on error */ +int kmem_logging = 0; /* kmem_log_enter() override */ +uint32_t kmem_mtbf = 0; /* mean time between failures [default: off] */ +size_t kmem_transaction_log_size; /* transaction log size [2% of memory] */ +size_t kmem_content_log_size; /* content log size [2% of memory] */ +size_t kmem_failure_log_size; /* failure log [4 pages per CPU] */ +size_t kmem_slab_log_size; /* slab create log [4 pages per CPU] */ +size_t kmem_content_maxsave = 256; /* KMF_CONTENTS max bytes to log */ +size_t kmem_lite_minsize = 0; /* minimum buffer size for KMF_LITE */ +size_t kmem_lite_maxalign = 8192; /* maximum buffer alignment for KMF_LITE */ +int kmem_lite_pcs = 4; /* number of PCs to store in KMF_LITE mode */ +size_t kmem_maxverify; /* maximum bytes to inspect in debug routines */ +size_t kmem_minfirewall; /* hardware-enforced redzone threshold */ + +size_t kmem_max_cached = KMEM_BIG_MAXBUF; /* maximum kmem_alloc cache */ + +/* + * Be aware that KMF_AUDIT does not release memory, and you will eventually + * grind to a halt. But it is useful to enable if you can trigger a memory + * fault, and wish to see the calling stack. + */ +#ifdef DEBUG +// can be 0 or KMF_LITE +// or KMF_DEADBEEF | KMF_REDZONE | KMF_CONTENTS +// with or without KMF_AUDIT +// int kmem_flags = KMF_DEADBEEF | KMF_REDZONE | KMF_CONTENTS | KMF_AUDIT; +int kmem_flags = KMF_LITE; +#else +int kmem_flags = 0; +#endif +int kmem_ready; + +static kmem_cache_t *kmem_slab_cache; +static kmem_cache_t *kmem_bufctl_cache; +static kmem_cache_t *kmem_bufctl_audit_cache; + +static kmutex_t kmem_cache_lock; /* inter-cache linkage only */ +static list_t kmem_caches; +extern vmem_t *heap_arena; +static taskq_t *kmem_taskq; +static kmutex_t kmem_flags_lock; +static vmem_t *kmem_metadata_arena; +static vmem_t *kmem_msb_arena; /* arena for metadata caches */ +static vmem_t *kmem_cache_arena; +static vmem_t *kmem_hash_arena; +static vmem_t *kmem_log_arena; +static vmem_t *kmem_oversize_arena; +static vmem_t *kmem_va_arena; +static vmem_t *kmem_default_arena; +static vmem_t *kmem_firewall_arena; + +/* + * kmem slab consolidator thresholds (tunables) + */ +size_t kmem_frag_minslabs = 101; /* minimum total slabs */ +size_t kmem_frag_numer = 1; /* free buffers (numerator) */ +size_t kmem_frag_denom = KMEM_VOID_FRACTION; /* buffers (denominator) */ +/* + * Maximum number of slabs from which to move buffers during a single + * maintenance interval while the system is not low on memory. + */ +size_t kmem_reclaim_max_slabs = 4; // smd 1 +/* + * Number of slabs to scan backwards from the end of the partial slab list + * when searching for buffers to relocate. + */ +size_t kmem_reclaim_scan_range = 48; // smd 12 + +/* consolidator knobs */ +static boolean_t kmem_move_noreap; +static boolean_t kmem_move_blocked; +static boolean_t kmem_move_fulltilt; +static boolean_t kmem_move_any_partial; + +#ifdef DEBUG +/* + * kmem consolidator debug tunables: + * Ensure code coverage by occasionally running the consolidator even when the + * caches are not fragmented (they may never be). These intervals are mean time + * in cache maintenance intervals (kmem_cache_update). + */ +uint32_t kmem_mtb_move = 20; /* defrag 1 slab (~5min) */ +uint32_t kmem_mtb_reap = 240; /* defrag all slabs (~1hrs) */ +uint32_t kmem_mtb_reap_count = 0; +#endif /* DEBUG */ + +static kmem_cache_t *kmem_defrag_cache; +static kmem_cache_t *kmem_move_cache; +static taskq_t *kmem_move_taskq; + +static void kmem_cache_scan(kmem_cache_t *); +static void kmem_cache_defrag(kmem_cache_t *); +static void kmem_slab_prefill(kmem_cache_t *, kmem_slab_t *); + + +kmem_log_header_t *kmem_transaction_log; +kmem_log_header_t *kmem_content_log; +kmem_log_header_t *kmem_failure_log; +kmem_log_header_t *kmem_slab_log; + +static int kmem_lite_count; /* # of PCs in kmem_buftag_lite_t */ + +#define KMEM_BUFTAG_LITE_ENTER(bt, count, caller) \ +if ((count) > 0) { \ +pc_t *_s = ((kmem_buftag_lite_t *)(bt))->bt_history; \ +pc_t *_e; \ +/* memmove() the old entries down one notch */ \ +for (_e = &_s[(count) - 1]; _e > _s; _e--) \ +*_e = *(_e - 1); \ +*_s = (uintptr_t)(caller); \ +} + +#define KMERR_MODIFIED 0 /* buffer modified while on freelist */ +#define KMERR_REDZONE 1 /* redzone violation (write past end of buf) */ +#define KMERR_DUPFREE 2 /* freed a buffer twice */ +#define KMERR_BADADDR 3 /* freed a bad (unallocated) address */ +#define KMERR_BADBUFTAG 4 /* buftag corrupted */ +#define KMERR_BADBUFCTL 5 /* bufctl corrupted */ +#define KMERR_BADCACHE 6 /* freed a buffer to the wrong cache */ +#define KMERR_BADSIZE 7 /* alloc size != free size */ +#define KMERR_BADBASE 8 /* buffer base address wrong */ + +struct { + hrtime_t kmp_timestamp; /* timestamp of panic */ + int kmp_error; /* type of kmem error */ + const void *kmp_buffer; /* buffer that induced panic */ + const void *kmp_realbuf; /* real start address for buffer */ + kmem_cache_t *kmp_cache; /* buffer's cache according to client */ + kmem_cache_t *kmp_realcache; /* actual cache containing buffer */ + kmem_slab_t *kmp_slab; /* slab accoring to kmem_findslab() */ + kmem_bufctl_t *kmp_bufctl; /* bufctl */ +} kmem_panic_info; + +extern uint64_t stat_osif_malloc_success; +extern uint64_t stat_osif_malloc_fail; +extern uint64_t stat_osif_malloc_bytes; +extern uint64_t stat_osif_free; +extern uint64_t stat_osif_free_bytes; +extern uint64_t stat_osif_malloc_sub128k; +extern uint64_t stat_osif_malloc_sub64k; +extern uint64_t stat_osif_malloc_sub32k; +extern uint64_t stat_osif_malloc_page; +extern uint64_t stat_osif_malloc_subpage; + +extern uint64_t spl_bucket_non_pow2_allocs; + +// stats for spl_root_allocator(); +extern uint64_t spl_root_allocator_calls; +extern uint64_t spl_root_allocator_large_bytes_asked; +extern uint64_t spl_root_allocator_small_bytes_asked; +extern uint64_t spl_root_allocator_minalloc_bytes_asked; +extern uint64_t spl_root_allocator_extra_pass; +extern uint64_t spl_root_allocator_recovered; +extern uint64_t spl_root_allocator_recovered_bytes; + +extern uint64_t spl_vmem_unconditional_allocs; +extern uint64_t spl_vmem_unconditional_alloc_bytes; +extern uint64_t spl_vmem_conditional_allocs; +extern uint64_t spl_vmem_conditional_alloc_bytes; +extern uint64_t spl_vmem_conditional_alloc_deny; +extern uint64_t spl_vmem_conditional_alloc_deny_bytes; + +extern uint64_t spl_xat_pressured; +extern uint64_t spl_xat_lastalloc; +extern uint64_t spl_xat_lastfree; +extern uint64_t spl_xat_sleep; + +extern uint64_t spl_vba_fastpath; +extern uint64_t spl_vba_fastexit; +extern uint64_t spl_vba_slowpath; +extern uint64_t spl_vba_parent_memory_appeared; +extern uint64_t spl_vba_parent_memory_blocked; +extern uint64_t spl_vba_hiprio_blocked; +extern uint64_t spl_vba_cv_timeout; +extern uint64_t spl_vba_loop_timeout; +extern uint64_t spl_vba_cv_timeout_blocked; +extern uint64_t spl_vba_loop_timeout_blocked; +extern uint64_t spl_vba_sleep; +extern uint64_t spl_vba_loop_entries; + +extern uint64_t spl_bucket_tunable_large_span; +extern uint64_t spl_bucket_tunable_small_span; +extern void spl_set_bucket_tunable_large_span(uint64_t); +extern void spl_set_bucket_tunable_small_span(uint64_t); + +extern _Atomic uint64_t spl_arc_no_grow_bits; +extern uint64_t spl_arc_no_grow_count; + +extern uint64_t spl_frag_max_walk; +extern uint64_t spl_frag_walked_out; +extern uint64_t spl_frag_walk_cnt; + +uint64_t spl_buckets_mem_free = 0; +uint64_t spl_arc_reclaim_avoided = 0; + +uint64_t kmem_free_to_slab_when_fragmented = 0; + +/* stack splitting via thread_call (vmem_alloc) or taskq (vdev_disk) */ +extern _Atomic unsigned int spl_lowest_vdev_disk_stack_remaining; +extern _Atomic unsigned int spl_lowest_zvol_stack_remaining; +extern _Atomic unsigned int spl_lowest_alloc_stack_remaining; +extern unsigned int spl_split_stack_below; + +typedef struct spl_stats { + kstat_named_t spl_os_alloc; + kstat_named_t spl_active_threads; + kstat_named_t spl_active_mutex; + kstat_named_t spl_active_rwlock; + kstat_named_t spl_active_tsd; + kstat_named_t spl_free_wake_count; + kstat_named_t spl_spl_free; + kstat_named_t spl_spl_free_manual_pressure; + kstat_named_t spl_spl_free_fast_pressure; + kstat_named_t spl_spl_free_negative_count; + kstat_named_t spl_osif_malloc_success; + kstat_named_t spl_osif_malloc_fail; + kstat_named_t spl_osif_malloc_bytes; + kstat_named_t spl_osif_free; + kstat_named_t spl_osif_free_bytes; + + kstat_named_t spl_enforce_memory_caps; + kstat_named_t spl_dynamic_memory_cap; + kstat_named_t spl_dynamic_memory_cap_skipped; + kstat_named_t spl_dynamic_memory_cap_reductions; + kstat_named_t spl_dynamic_memory_cap_hit_floor; + kstat_named_t spl_manual_memory_cap; + kstat_named_t spl_memory_cap_enforcements; + + kstat_named_t spl_osif_malloc_sub128k; + kstat_named_t spl_osif_malloc_sub64k; + kstat_named_t spl_osif_malloc_sub32k; + kstat_named_t spl_osif_malloc_page; + kstat_named_t spl_osif_malloc_subpage; + + kstat_named_t spl_bucket_non_pow2_allocs; + + kstat_named_t spl_vmem_unconditional_allocs; + kstat_named_t spl_vmem_unconditional_alloc_bytes; + kstat_named_t spl_vmem_conditional_allocs; + kstat_named_t spl_vmem_conditional_alloc_bytes; + kstat_named_t spl_vmem_conditional_alloc_deny; + kstat_named_t spl_vmem_conditional_alloc_deny_bytes; + + kstat_named_t spl_xat_pressured; + kstat_named_t spl_xat_bailed; + kstat_named_t spl_xat_lastalloc; + kstat_named_t spl_xat_lastfree; + kstat_named_t spl_xat_sleep; + + kstat_named_t spl_vba_fastpath; + kstat_named_t spl_vba_fastexit; + kstat_named_t spl_vba_slowpath; + kstat_named_t spl_vba_parent_memory_appeared; + kstat_named_t spl_vba_parent_memory_blocked; + kstat_named_t spl_vba_hiprio_blocked; + kstat_named_t spl_vba_cv_timeout; + kstat_named_t spl_vba_loop_timeout; + kstat_named_t spl_vba_cv_timeout_blocked; + kstat_named_t spl_vba_loop_timeout_blocked; + kstat_named_t spl_vba_sleep; + kstat_named_t spl_vba_loop_entries; + + kstat_named_t spl_bucket_tunable_large_span; + kstat_named_t spl_bucket_tunable_small_span; + + kstat_named_t spl_buckets_mem_free; + kstat_named_t spl_arc_no_grow_bits; + kstat_named_t spl_arc_no_grow_count; + kstat_named_t spl_frag_max_walk; + kstat_named_t spl_frag_walked_out; + kstat_named_t spl_frag_walk_cnt; + kstat_named_t spl_arc_reclaim_avoided; + + kstat_named_t kmem_free_to_slab_when_fragmented; + + kstat_named_t spl_vm_pages_reclaimed; + kstat_named_t spl_vm_pages_wanted; + kstat_named_t spl_vm_pressure_level; + kstat_named_t spl_lowest_alloc_stack_remaining; + kstat_named_t spl_lowest_vdev_disk_stack_remaining; + kstat_named_t spl_lowest_zvol_stack_remaining; + kstat_named_t spl_split_stack_below; +} spl_stats_t; + +static spl_stats_t spl_stats = { + {"os_mem_alloc", KSTAT_DATA_UINT64}, + {"active_threads", KSTAT_DATA_UINT64}, + {"active_mutex", KSTAT_DATA_UINT64}, + {"active_rwlock", KSTAT_DATA_UINT64}, + {"active_tsd", KSTAT_DATA_UINT64}, + {"spl_free_wake_count", KSTAT_DATA_UINT64}, + {"spl_spl_free", KSTAT_DATA_INT64}, + {"spl_spl_free_manual_pressure", KSTAT_DATA_UINT64}, + {"spl_spl_free_fast_pressure", KSTAT_DATA_UINT64}, + {"spl_spl_free_negative_count", KSTAT_DATA_UINT64}, + {"spl_osif_malloc_success", KSTAT_DATA_UINT64}, + {"spl_osif_malloc_fail", KSTAT_DATA_UINT64}, + {"spl_osif_malloc_bytes", KSTAT_DATA_UINT64}, + {"spl_osif_free", KSTAT_DATA_UINT64}, + {"spl_osif_free_bytes", KSTAT_DATA_UINT64}, + + {"spl_osif_enforce_memory_caps", KSTAT_DATA_UINT64}, + {"spl_osif_dynamic_memory_cap", KSTAT_DATA_UINT64}, + {"spl_osif_dynamic_memory_cap_skipped", KSTAT_DATA_UINT64}, + {"spl_osif_dynamic_memory_cap_reductions", KSTAT_DATA_UINT64}, + {"spl_osif_dynamic_memory_cap_hit_floor", KSTAT_DATA_UINT64}, + {"spl_osif_manual_memory_cap", KSTAT_DATA_UINT64}, + {"spl_osif_memory_cap_enforcements", KSTAT_DATA_UINT64}, + + {"spl_osif_malloc_sub128k", KSTAT_DATA_UINT64}, + {"spl_osif_malloc_sub64k", KSTAT_DATA_UINT64}, + {"spl_osif_malloc_sub32k", KSTAT_DATA_UINT64}, + {"spl_osif_malloc_page", KSTAT_DATA_UINT64}, + {"spl_osif_malloc_subpage", KSTAT_DATA_UINT64}, + + {"spl_bucket_non_pow2_allocs", KSTAT_DATA_UINT64}, + + {"vmem_unconditional_allocs", KSTAT_DATA_UINT64}, + {"vmem_unconditional_alloc_bytes", KSTAT_DATA_UINT64}, + {"vmem_conditional_allocs", KSTAT_DATA_UINT64}, + {"vmem_conditional_alloc_bytes", KSTAT_DATA_UINT64}, + {"vmem_conditional_alloc_deny", KSTAT_DATA_UINT64}, + {"vmem_conditional_alloc_deny_bytes", KSTAT_DATA_UINT64}, + + {"spl_xat_pressured", KSTAT_DATA_UINT64}, + {"spl_xat_lastalloc", KSTAT_DATA_UINT64}, + {"spl_xat_lastfree", KSTAT_DATA_UINT64}, + {"spl_xat_sleep", KSTAT_DATA_UINT64}, + + {"spl_vba_fastpath", KSTAT_DATA_UINT64}, + {"spl_vba_fastexit", KSTAT_DATA_UINT64}, + {"spl_vba_slowpath", KSTAT_DATA_UINT64}, + {"spl_vba_parent_memory_appeared", KSTAT_DATA_UINT64}, + {"spl_vba_parent_memory_blocked", KSTAT_DATA_UINT64}, + {"spl_vba_hiprio_blocked", KSTAT_DATA_UINT64}, + {"spl_vba_cv_timeout", KSTAT_DATA_UINT64}, + {"spl_vba_loop_timeout", KSTAT_DATA_UINT64}, + {"spl_vba_cv_timeout_blocked", KSTAT_DATA_UINT64}, + {"spl_vba_loop_timeout_blocked", KSTAT_DATA_UINT64}, + {"spl_vba_sleep", KSTAT_DATA_UINT64}, + {"spl_vba_loop_entries", KSTAT_DATA_UINT64}, + + {"spl_tunable_large_span", KSTAT_DATA_UINT64}, + {"spl_tunable_small_span", KSTAT_DATA_UINT64}, + + {"spl_buckets_mem_free", KSTAT_DATA_UINT64}, + {"spl_arc_no_grow_bits", KSTAT_DATA_UINT64}, + {"spl_arc_no_grow_count", KSTAT_DATA_UINT64}, + + {"spl_vmem_frag_max_walk", KSTAT_DATA_UINT64}, + {"spl_vmem_frag_walked_out", KSTAT_DATA_UINT64}, + {"spl_vmem_frag_walk_cnt", KSTAT_DATA_UINT64}, + {"spl_arc_reclaim_avoided", KSTAT_DATA_UINT64}, + + {"kmem_free_to_slab_when_fragmented", KSTAT_DATA_UINT64}, + {"spl_vm_pages_reclaimed", KSTAT_DATA_UINT64}, + {"spl_vm_pages_wanted", KSTAT_DATA_UINT64}, + {"spl_vm_pressure_level", KSTAT_DATA_UINT64}, + {"lowest_alloc_stack_remaining", KSTAT_DATA_UINT64}, + {"lowest_vdev_disk_stack_remaining", KSTAT_DATA_UINT64}, + {"lowest_zvol_stack_remaining", KSTAT_DATA_UINT64}, + {"split_stack_below", KSTAT_DATA_UINT64}, +}; + +static kstat_t *spl_ksp = 0; + +// Stub out caller() +caddr_t +caller() +{ + return ((caddr_t)(0)); +} + +void * +calloc(size_t n, size_t s) +{ + return (zfs_kmem_zalloc(n * s, KM_NOSLEEP)); +} + +#define IS_DIGIT(c) ((c) >= '0' && (c) <= '9') + +#define IS_ALPHA(c) \ +(((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) + +/* + * Get bytes from the /dev/random generator. Returns 0 + * on success. Returns EAGAIN if there is insufficient entropy. + */ +int +random_get_bytes(uint8_t *ptr, size_t len) +{ + read_random(ptr, len); + return (0); +} + +/* + * BGH - Missing from OSX? + * + * Convert a string into a valid C identifier by replacing invalid + * characters with '_'. Also makes sure the string is nul-terminated + * and takes up at most n bytes. + */ +void +strident_canon(char *s, size_t n) +{ + char c; + char *end = s + n - 1; + + if ((c = *s) == 0) + return; + + if (!IS_ALPHA(c) && c != '_') + *s = '_'; + + while (s < end && ((c = *(++s)) != 0)) { + if (!IS_ALPHA(c) && !IS_DIGIT(c) && c != '_') + *s = '_'; + } + *s = 0; +} + +int +strident_valid(const char *id) +{ + int c = *id++; + + if (!IS_ALPHA(c) && c != '_') + return (0); + while ((c = *id++) != 0) { + if (!IS_ALPHA(c) && !IS_DIGIT(c) && c != '_') + return (0); + } + return (1); +} + +static void +copy_pattern(uint64_t pattern, void *buf_arg, size_t size) +{ + uint64_t *bufend = (uint64_t *)((char *)buf_arg + size); + uint64_t *buf = buf_arg; + + while (buf < bufend) + *buf++ = pattern; +} + +static const void * +verify_pattern(uint64_t pattern, const void *buf_arg, size_t size) +{ + const uint64_t *bufend = (const uint64_t *)((char *)buf_arg + size); + const uint64_t *buf; + + for (buf = buf_arg; buf < bufend; buf++) + if (*buf != pattern) + return (buf); + return (NULL); +} + +static void * +verify_and_copy_pattern(uint64_t old, uint64_t new, void *buf_arg, size_t size) +{ + uint64_t *bufend = (uint64_t *)((char *)buf_arg + size); + uint64_t *buf; + + for (buf = buf_arg; buf < bufend; buf++) { + if (*buf != old) { + copy_pattern(old, buf_arg, + (char *)buf - (char *)buf_arg); + return (buf); + } + *buf = new; + } + + return (NULL); +} + +static void +kmem_cache_applyall(void (*func)(kmem_cache_t *), taskq_t *tq, int tqflag) +{ + kmem_cache_t *cp; + + mutex_enter(&kmem_cache_lock); + for (cp = list_head(&kmem_caches); cp != NULL; + cp = list_next(&kmem_caches, cp)) + if (tq != NULL) + (void) taskq_dispatch(tq, (task_func_t *)func, cp, + tqflag); + else + func(cp); + mutex_exit(&kmem_cache_lock); +} + +static void +kmem_cache_applyall_id(void (*func)(kmem_cache_t *), taskq_t *tq, int tqflag) +{ + kmem_cache_t *cp; + + mutex_enter(&kmem_cache_lock); + for (cp = list_head(&kmem_caches); cp != NULL; + cp = list_next(&kmem_caches, cp)) { + if (!(cp->cache_cflags & KMC_IDENTIFIER)) + continue; + if (tq != NULL) + (void) taskq_dispatch(tq, (task_func_t *)func, cp, + tqflag); + else + func(cp); + } + mutex_exit(&kmem_cache_lock); +} + +/* + * Debugging support. Given a buffer address, find its slab. + */ +static kmem_slab_t * +kmem_findslab(kmem_cache_t *cp, const void *buf) +{ + kmem_slab_t *sp; + + mutex_enter(&cp->cache_lock); + for (sp = list_head(&cp->cache_complete_slabs); sp != NULL; + sp = list_next(&cp->cache_complete_slabs, sp)) { + if (KMEM_SLAB_MEMBER(sp, buf)) { + mutex_exit(&cp->cache_lock); + return (sp); + } + } + for (sp = avl_first(&cp->cache_partial_slabs); sp != NULL; + sp = AVL_NEXT(&cp->cache_partial_slabs, sp)) { + if (KMEM_SLAB_MEMBER(sp, buf)) { + mutex_exit(&cp->cache_lock); + return (sp); + } + } + mutex_exit(&cp->cache_lock); + + return (NULL); +} + +static void +kmem_error(int error, kmem_cache_t *cparg, const void *bufarg) +{ + kmem_buftag_t *btp = NULL; + kmem_bufctl_t *bcp = NULL; + kmem_cache_t *cp = cparg; + kmem_slab_t *sp; + const uint64_t *off; + const void *buf = bufarg; + + kmem_logging = 0; /* stop logging when a bad thing happens */ + + kmem_panic_info.kmp_timestamp = gethrtime(); + + sp = kmem_findslab(cp, buf); + if (sp == NULL) { + for (cp = list_tail(&kmem_caches); cp != NULL; + cp = list_prev(&kmem_caches, cp)) { + if ((sp = kmem_findslab(cp, buf)) != NULL) + break; + } + } + + if (sp == NULL) { + cp = NULL; + error = KMERR_BADADDR; + } else { + if (cp != cparg) + error = KMERR_BADCACHE; + else + buf = (char *)bufarg - + ((uintptr_t)bufarg - + (uintptr_t)sp->slab_base) % cp->cache_chunksize; + if (buf != bufarg) + error = KMERR_BADBASE; + if (cp->cache_flags & KMF_BUFTAG) + btp = KMEM_BUFTAG(cp, buf); + if (cp->cache_flags & KMF_HASH) { + mutex_enter(&cp->cache_lock); + for (bcp = *KMEM_HASH(cp, buf); bcp; bcp = bcp->bc_next) + if (bcp->bc_addr == buf) + break; + mutex_exit(&cp->cache_lock); + if (bcp == NULL && btp != NULL) + bcp = btp->bt_bufctl; + if (kmem_findslab(cp->cache_bufctl_cache, bcp) == + NULL || P2PHASE((uintptr_t)bcp, KMEM_ALIGN) || + bcp->bc_addr != buf) { + error = KMERR_BADBUFCTL; + bcp = NULL; + } + } + } + + kmem_panic_info.kmp_error = error; + kmem_panic_info.kmp_buffer = bufarg; + kmem_panic_info.kmp_realbuf = buf; + kmem_panic_info.kmp_cache = cparg; + kmem_panic_info.kmp_realcache = cp; + kmem_panic_info.kmp_slab = sp; + kmem_panic_info.kmp_bufctl = bcp; + + printf("SPL: kernel memory allocator: "); + + switch (error) { + + case KMERR_MODIFIED: + printf("buffer modified after being freed\n"); + off = verify_pattern(KMEM_FREE_PATTERN, buf, + cp->cache_verify); + if (off == NULL) /* shouldn't happen */ + off = buf; + printf("SPL: modification occurred at offset 0x%lx " + "(0x%llx replaced by 0x%llx)\n", + (uintptr_t)off - (uintptr_t)buf, + (longlong_t)KMEM_FREE_PATTERN, (longlong_t)*off); + break; + + case KMERR_REDZONE: + printf("redzone violation: write past end of buffer\n"); + break; + + case KMERR_BADADDR: + printf("invalid free: buffer not in cache\n"); + break; + + case KMERR_DUPFREE: + printf("duplicate free: buffer freed twice\n"); + break; + + case KMERR_BADBUFTAG: + printf("boundary tag corrupted\n"); + printf("SPL: bcp ^ bxstat = %lx, should be %lx\n", + (intptr_t)btp->bt_bufctl ^ btp->bt_bxstat, + KMEM_BUFTAG_FREE); + break; + + case KMERR_BADBUFCTL: + printf("bufctl corrupted\n"); + break; + + case KMERR_BADCACHE: + printf("buffer freed to wrong cache\n"); + printf("SPL: buffer was allocated from %s,\n", + cp->cache_name); + printf("SPL: caller attempting free to %s.\n", + cparg->cache_name); + break; + + case KMERR_BADSIZE: + printf("bad free: free size (%u) != alloc size (%u)\n", + KMEM_SIZE_DECODE(((uint32_t *)btp)[0]), + KMEM_SIZE_DECODE(((uint32_t *)btp)[1])); + break; + + case KMERR_BADBASE: + printf("bad free: free address (%p) != alloc address" + " (%p)\n", bufarg, buf); + break; + } + + printf("SPL: buffer=%p bufctl=%p cache: %s\n", + bufarg, (void *)bcp, cparg->cache_name); + + if (bcp != NULL && (cp->cache_flags & KMF_AUDIT) && + error != KMERR_BADBUFCTL) { + int d; + timestruc_t ts = {0, 0}; + kmem_bufctl_audit_t *bcap = (kmem_bufctl_audit_t *)bcp; + + hrt2ts(kmem_panic_info.kmp_timestamp - bcap->bc_timestamp, &ts); + printf("SPL: previous transaction on buffer %p:\n", buf); + printf("SPL: thread=%p time=T-%ld.%09ld slab=%p cache: %s\n", + (void *)bcap->bc_thread, ts.tv_sec, ts.tv_nsec, + (void *)sp, cp->cache_name); + for (d = 0; d < MIN(bcap->bc_depth, KMEM_STACK_DEPTH); d++) { + print_symbol(bcap->bc_stack[d]); + } + } + + if (kmem_panic > 0) { + /* give time for the logging to register */ + IODelay(1 * 1000 * 1000); + panic("kernel heap corruption detected"); + } + + kmem_logging = 1; /* resume logging */ +} + +static kmem_log_header_t * +kmem_log_init(size_t logsize) +{ + kmem_log_header_t *lhp; + int nchunks = 4 * max_ncpus; + size_t lhsize = (size_t)&((kmem_log_header_t *)0)->lh_cpu[max_ncpus]; + int i; + + /* + * Make sure that lhp->lh_cpu[] is nicely aligned + * to prevent false sharing of cache lines. + */ + lhsize = P2ROUNDUP(lhsize, KMEM_ALIGN); + lhp = vmem_xalloc(kmem_log_arena, lhsize, 64, P2NPHASE(lhsize, 64), 0, + NULL, NULL, VM_SLEEP); + memset(lhp, 0, lhsize); + + mutex_init(&lhp->lh_lock, NULL, MUTEX_DEFAULT, NULL); + lhp->lh_nchunks = nchunks; + lhp->lh_chunksize = P2ROUNDUP(logsize / nchunks + 1, PAGESIZE); + lhp->lh_base = vmem_alloc_impl(kmem_log_arena, + lhp->lh_chunksize * nchunks, VM_SLEEP); + lhp->lh_free = vmem_alloc_impl(kmem_log_arena, + nchunks * sizeof (int), VM_SLEEP); + memset(lhp->lh_base, 0, lhp->lh_chunksize * nchunks); + + for (i = 0; i < max_ncpus; i++) { + kmem_cpu_log_header_t *clhp = &lhp->lh_cpu[i]; + mutex_init(&clhp->clh_lock, NULL, MUTEX_DEFAULT, NULL); + clhp->clh_chunk = i; + } + + for (i = max_ncpus; i < nchunks; i++) + lhp->lh_free[i] = i; + + lhp->lh_head = max_ncpus; + lhp->lh_tail = 0; + + return (lhp); +} + + +static void +kmem_log_fini(kmem_log_header_t *lhp) +{ + int nchunks = 4 * max_ncpus; + size_t lhsize = (size_t)&((kmem_log_header_t *)0)->lh_cpu[max_ncpus]; + int i; + + + + for (i = 0; i < max_ncpus; i++) { + kmem_cpu_log_header_t *clhp = &lhp->lh_cpu[i]; + mutex_destroy(&clhp->clh_lock); + } + + vmem_free_impl(kmem_log_arena, lhp->lh_free, nchunks * sizeof (int)); + + vmem_free_impl(kmem_log_arena, lhp->lh_base, + lhp->lh_chunksize * nchunks); + + mutex_destroy(&lhp->lh_lock); + + lhsize = P2ROUNDUP(lhsize, KMEM_ALIGN); + vmem_xfree(kmem_log_arena, lhp, lhsize); +} + + +static void * +kmem_log_enter(kmem_log_header_t *lhp, void *data, size_t size) +{ + void *logspace; + + kmem_cpu_log_header_t *clhp = &lhp->lh_cpu[CPU_SEQID]; + + // if (lhp == NULL || kmem_logging == 0 || panicstr) + if (lhp == NULL || kmem_logging == 0) + return (NULL); + + mutex_enter(&clhp->clh_lock); + clhp->clh_hits++; + if (size > clhp->clh_avail) { + mutex_enter(&lhp->lh_lock); + lhp->lh_hits++; + lhp->lh_free[lhp->lh_tail] = clhp->clh_chunk; + lhp->lh_tail = (lhp->lh_tail + 1) % lhp->lh_nchunks; + clhp->clh_chunk = lhp->lh_free[lhp->lh_head]; + lhp->lh_head = (lhp->lh_head + 1) % lhp->lh_nchunks; + clhp->clh_current = lhp->lh_base + + clhp->clh_chunk * lhp->lh_chunksize; + clhp->clh_avail = lhp->lh_chunksize; + if (size > lhp->lh_chunksize) + size = lhp->lh_chunksize; + mutex_exit(&lhp->lh_lock); + } + logspace = clhp->clh_current; + clhp->clh_current += size; + clhp->clh_avail -= size; + memcpy(logspace, data, size); + mutex_exit(&clhp->clh_lock); + return (logspace); +} + +#define KMEM_AUDIT(lp, cp, bcp) \ +{ \ +kmem_bufctl_audit_t *_bcp = (kmem_bufctl_audit_t *)(bcp); \ +_bcp->bc_timestamp = gethrtime(); \ +_bcp->bc_thread = spl_current_thread(); \ +_bcp->bc_depth = getpcstack(_bcp->bc_stack, KMEM_STACK_DEPTH); \ +_bcp->bc_lastlog = kmem_log_enter((lp), _bcp, sizeof (*_bcp)); \ +} + +static void +kmem_log_event(kmem_log_header_t *lp, kmem_cache_t *cp, + kmem_slab_t *sp, void *addr) +{ + kmem_bufctl_audit_t bca; + + memset(&bca, 0, sizeof (kmem_bufctl_audit_t)); + bca.bc_addr = addr; + bca.bc_slab = sp; + KMEM_AUDIT(lp, cp, &bca); +} + +/* + * Create a new slab for cache cp. + */ +static kmem_slab_t * +kmem_slab_create(kmem_cache_t *cp, int kmflag) +{ + size_t slabsize = cp->cache_slabsize; + size_t chunksize = cp->cache_chunksize; + int cache_flags = cp->cache_flags; + size_t color, chunks; + char *buf, *slab; + kmem_slab_t *sp; + kmem_bufctl_t *bcp; + vmem_t *vmp = cp->cache_arena; + + ASSERT(MUTEX_NOT_HELD(&cp->cache_lock)); + + color = cp->cache_color + cp->cache_align; + if (color > cp->cache_maxcolor) + color = cp->cache_mincolor; + cp->cache_color = color; + + slab = vmem_alloc_impl(vmp, slabsize, kmflag & KM_VMFLAGS); + + if (slab == NULL) + goto vmem_alloc_failure; + + ASSERT(P2PHASE((uintptr_t)slab, vmp->vm_quantum) == 0); + + /* + * Reverify what was already checked in kmem_cache_set_move(), since the + * consolidator depends (for correctness) on slabs being initialized + * with the 0xbaddcafe memory pattern (setting a low order bit usable by + * clients to distinguish uninitialized memory from known objects). + */ + ASSERT((cp->cache_move == NULL) || !(cp->cache_cflags & KMC_NOTOUCH)); + if (!(cp->cache_cflags & KMC_NOTOUCH)) + copy_pattern(KMEM_UNINITIALIZED_PATTERN, slab, slabsize); + + if (cache_flags & KMF_HASH) { + if ((sp = kmem_cache_alloc(kmem_slab_cache, kmflag)) == NULL) + goto slab_alloc_failure; + chunks = (slabsize - color) / chunksize; + } else { + sp = KMEM_SLAB(cp, slab); + chunks = (slabsize - sizeof (kmem_slab_t) - color) / chunksize; + } + + sp->slab_cache = cp; + sp->slab_head = NULL; + sp->slab_refcnt = 0; + sp->slab_base = buf = slab + color; + sp->slab_chunks = chunks; + sp->slab_stuck_offset = (uint32_t)-1; + sp->slab_later_count = 0; + sp->slab_flags = 0; + sp->slab_create_time = gethrtime(); + + ASSERT(chunks > 0); + while (chunks-- != 0) { + if (cache_flags & KMF_HASH) { + bcp = kmem_cache_alloc(cp->cache_bufctl_cache, kmflag); + if (bcp == NULL) + goto bufctl_alloc_failure; + if (cache_flags & KMF_AUDIT) { + kmem_bufctl_audit_t *bcap = + (kmem_bufctl_audit_t *)bcp; + memset(bcap, 0, sizeof (kmem_bufctl_audit_t)); + bcap->bc_cache = cp; + } + bcp->bc_addr = buf; + bcp->bc_slab = sp; + } else { + bcp = KMEM_BUFCTL(cp, buf); + } + if (cache_flags & KMF_BUFTAG) { + kmem_buftag_t *btp = KMEM_BUFTAG(cp, buf); + btp->bt_redzone = KMEM_REDZONE_PATTERN; + btp->bt_bufctl = bcp; + btp->bt_bxstat = (intptr_t)bcp ^ KMEM_BUFTAG_FREE; + if (cache_flags & KMF_DEADBEEF) { + copy_pattern(KMEM_FREE_PATTERN, buf, + cp->cache_verify); + } + } + bcp->bc_next = sp->slab_head; + sp->slab_head = bcp; + buf += chunksize; + } + + kmem_log_event(kmem_slab_log, cp, sp, slab); + + return (sp); + +bufctl_alloc_failure: + + while ((bcp = sp->slab_head) != NULL) { + sp->slab_head = bcp->bc_next; + kmem_cache_free(cp->cache_bufctl_cache, bcp); + } + kmem_cache_free(kmem_slab_cache, sp); + +slab_alloc_failure: + + vmem_free_impl(vmp, slab, slabsize); + +vmem_alloc_failure: + + if (0 == (kmflag & KM_NO_VBA)) { + kmem_log_event(kmem_failure_log, cp, NULL, NULL); + atomic_inc_64(&cp->cache_alloc_fail); + } + + return (NULL); +} + +/* + * Destroy a slab. + */ +static void +kmem_slab_destroy(kmem_cache_t *cp, kmem_slab_t *sp) +{ + vmem_t *vmp = cp->cache_arena; + void *slab = (void *)P2ALIGN((uintptr_t)sp->slab_base, vmp->vm_quantum); + + ASSERT(MUTEX_NOT_HELD(&cp->cache_lock)); + ASSERT(sp->slab_refcnt == 0); + + if (cp->cache_flags & KMF_HASH) { + kmem_bufctl_t *bcp; + while ((bcp = sp->slab_head) != NULL) { + sp->slab_head = bcp->bc_next; + kmem_cache_free(cp->cache_bufctl_cache, bcp); + } + kmem_cache_free(kmem_slab_cache, sp); + } + vmem_free_impl(vmp, slab, cp->cache_slabsize); +} + +static void * +kmem_slab_alloc_impl(kmem_cache_t *cp, kmem_slab_t *sp, boolean_t prefill) +{ + kmem_bufctl_t *bcp, **hash_bucket; + void *buf; + boolean_t new_slab = (sp->slab_refcnt == 0); + + ASSERT(MUTEX_HELD(&cp->cache_lock)); + /* + * kmem_slab_alloc() drops cache_lock when it creates a new slab, so we + * can't ASSERT(avl_is_empty(&cp->cache_partial_slabs)) here when the + * slab is newly created. + */ + ASSERT(new_slab || (KMEM_SLAB_IS_PARTIAL(sp) && + (sp == avl_first(&cp->cache_partial_slabs)))); + ASSERT(sp->slab_cache == cp); + + cp->cache_slab_alloc++; + cp->cache_bufslab--; + sp->slab_refcnt++; + + bcp = sp->slab_head; + sp->slab_head = bcp->bc_next; + + if (cp->cache_flags & KMF_HASH) { + /* + * Add buffer to allocated-address hash table. + */ + buf = bcp->bc_addr; + hash_bucket = KMEM_HASH(cp, buf); + bcp->bc_next = *hash_bucket; + *hash_bucket = bcp; + if ((cp->cache_flags & (KMF_AUDIT | KMF_BUFTAG)) == KMF_AUDIT) { + KMEM_AUDIT(kmem_transaction_log, cp, bcp); + } + } else { + buf = KMEM_BUF(cp, bcp); + } + + ASSERT(KMEM_SLAB_MEMBER(sp, buf)); + + if (sp->slab_head == NULL) { + ASSERT(KMEM_SLAB_IS_ALL_USED(sp)); + if (new_slab) { + ASSERT(sp->slab_chunks == 1); + } else { + ASSERT(sp->slab_chunks > 1); /* the slab was partial */ + avl_remove(&cp->cache_partial_slabs, sp); + sp->slab_later_count = 0; /* clear history */ + sp->slab_flags &= ~KMEM_SLAB_NOMOVE; + sp->slab_stuck_offset = (uint32_t)-1; + } + list_insert_head(&cp->cache_complete_slabs, sp); + cp->cache_complete_slab_count++; + return (buf); + } + + ASSERT(KMEM_SLAB_IS_PARTIAL(sp)); + /* + * Peek to see if the magazine layer is enabled before + * we prefill. We're not holding the cpu cache lock, + * so the peek could be wrong, but there's no harm in it. + */ + if (new_slab && prefill && (cp->cache_flags & KMF_PREFILL) && + (KMEM_CPU_CACHE(cp)->cc_magsize != 0)) { + kmem_slab_prefill(cp, sp); + return (buf); + } + + if (new_slab) { + avl_add(&cp->cache_partial_slabs, sp); + return (buf); + } + + /* + * The slab is now more allocated than it was, so the + * order remains unchanged. + */ + ASSERT(!avl_update(&cp->cache_partial_slabs, sp)); + return (buf); +} + +/* + * Allocate a raw (unconstructed) buffer from cp's slab layer. + */ +static void * +kmem_slab_alloc(kmem_cache_t *cp, int kmflag) +{ + kmem_slab_t *sp; + void *buf; + boolean_t test_destructor; + + mutex_enter(&cp->cache_lock); + test_destructor = (cp->cache_slab_alloc == 0); + sp = avl_first(&cp->cache_partial_slabs); + if (sp == NULL) { + ASSERT(cp->cache_bufslab == 0); + + /* + * The freelist is empty. Create a new slab. + */ + mutex_exit(&cp->cache_lock); + if ((sp = kmem_slab_create(cp, kmflag)) == NULL) { + return (NULL); + } + mutex_enter(&cp->cache_lock); + cp->cache_slab_create++; + if ((cp->cache_buftotal += sp->slab_chunks) > cp->cache_bufmax) + cp->cache_bufmax = cp->cache_buftotal; + cp->cache_bufslab += sp->slab_chunks; + } + + buf = kmem_slab_alloc_impl(cp, sp, B_TRUE); + ASSERT((cp->cache_slab_create - cp->cache_slab_destroy) == + (cp->cache_complete_slab_count + + avl_numnodes(&cp->cache_partial_slabs) + + (cp->cache_defrag == NULL ? 0 : cp->cache_defrag->kmd_deadcount))); + mutex_exit(&cp->cache_lock); + + if (test_destructor && cp->cache_destructor != NULL) { + copy_pattern(KMEM_UNINITIALIZED_PATTERN, buf, + cp->cache_bufsize); + if (cp->cache_flags & KMF_DEADBEEF) { + copy_pattern(KMEM_FREE_PATTERN, buf, cp->cache_verify); + } + } + + return (buf); +} + +static void kmem_slab_move_yes(kmem_cache_t *, kmem_slab_t *, void *); + +/* + * Free a raw (unconstructed) buffer to cp's slab layer. + */ +static void +kmem_slab_free(kmem_cache_t *cp, void *buf) +{ + kmem_slab_t *sp = NULL; + kmem_bufctl_t *bcp, **prev_bcpp; + + ASSERT(buf != NULL); + + mutex_enter(&cp->cache_lock); + cp->cache_slab_free++; + + if (cp->cache_flags & KMF_HASH) { + /* + * Look up buffer in allocated-address hash table. + */ + prev_bcpp = KMEM_HASH(cp, buf); + while ((bcp = *prev_bcpp) != NULL) { + if (bcp->bc_addr == buf) { + *prev_bcpp = bcp->bc_next; + sp = bcp->bc_slab; + break; + } + cp->cache_lookup_depth++; + prev_bcpp = &bcp->bc_next; + } + } else { + bcp = KMEM_BUFCTL(cp, buf); + sp = KMEM_SLAB(cp, buf); + } + + if (bcp == NULL || sp->slab_cache != cp || !KMEM_SLAB_MEMBER(sp, buf)) { + mutex_exit(&cp->cache_lock); + kmem_error(KMERR_BADADDR, cp, buf); + return; + } + + if (KMEM_SLAB_OFFSET(sp, buf) == sp->slab_stuck_offset) { + /* + * If this is the buffer that prevented the consolidator from + * clearing the slab, we can reset the slab flags now that the + * buffer is freed. (It makes sense to do this in + * kmem_cache_free(), where the client gives up ownership of the + * buffer, but on the hot path the test is too expensive.) + */ + kmem_slab_move_yes(cp, sp, buf); + } + + if ((cp->cache_flags & (KMF_AUDIT | KMF_BUFTAG)) == KMF_AUDIT) { + if (cp->cache_flags & KMF_CONTENTS) + ((kmem_bufctl_audit_t *)bcp)->bc_contents = + kmem_log_enter(kmem_content_log, buf, + cp->cache_contents); + KMEM_AUDIT(kmem_transaction_log, cp, bcp); + } + + bcp->bc_next = sp->slab_head; + sp->slab_head = bcp; + + cp->cache_bufslab++; + ASSERT(sp->slab_refcnt >= 1); + + if (--sp->slab_refcnt == 0) { + /* + * There are no outstanding allocations from this slab, + * so we can reclaim the memory. + */ + if (sp->slab_chunks == 1) { + list_remove(&cp->cache_complete_slabs, sp); + cp->cache_complete_slab_count--; + } else { + avl_remove(&cp->cache_partial_slabs, sp); + } + + cp->cache_buftotal -= sp->slab_chunks; + cp->cache_bufslab -= sp->slab_chunks; + /* + * Defer releasing the slab to the virtual memory subsystem + * while there is a pending move callback, since we guarantee + * that buffers passed to the move callback have only been + * touched by kmem or by the client itself. Since the memory + * patterns baddcafe (uninitialized) and deadbeef (freed) both + * set at least one of the two lowest order bits, the client can + * test those bits in the move callback to determine whether or + * not it knows about the buffer (assuming that the client also + * sets one of those low order bits whenever it frees a buffer). + */ + if (cp->cache_defrag == NULL || + (avl_is_empty(&cp->cache_defrag->kmd_moves_pending) && + !(sp->slab_flags & KMEM_SLAB_MOVE_PENDING))) { + cp->cache_slab_destroy++; + mutex_exit(&cp->cache_lock); + kmem_slab_destroy(cp, sp); + } else { + list_t *deadlist = + &cp->cache_defrag->kmd_deadlist; + /* + * Slabs are inserted at both ends of the + * deadlist to distinguish between slabs + * freed while move callbacks are pending + * (list head) and a slab freed while the + * lock is dropped in kmem_move_buffers() + * (list tail) so that in both cases + * slab_destroy() is called from the + * right context. + */ + if (sp->slab_flags & KMEM_SLAB_MOVE_PENDING) { + list_insert_tail(deadlist, sp); + } else { + list_insert_head(deadlist, sp); + } + cp->cache_defrag->kmd_deadcount++; + mutex_exit(&cp->cache_lock); + } + return; + } + + if (bcp->bc_next == NULL) { + /* Transition the slab from completely allocated to partial. */ + ASSERT(sp->slab_refcnt == (sp->slab_chunks - 1)); + ASSERT(sp->slab_chunks > 1); + list_remove(&cp->cache_complete_slabs, sp); + cp->cache_complete_slab_count--; + avl_add(&cp->cache_partial_slabs, sp); + } else { + (void) avl_update_gt(&cp->cache_partial_slabs, sp); + } + + ASSERT((cp->cache_slab_create - cp->cache_slab_destroy) == + (cp->cache_complete_slab_count + + avl_numnodes(&cp->cache_partial_slabs) + + (cp->cache_defrag == NULL ? 0 : cp->cache_defrag->kmd_deadcount))); + mutex_exit(&cp->cache_lock); +} + +/* + * Return -1 if kmem_error, 1 if constructor fails, 0 if successful. + */ +static int +kmem_cache_alloc_debug(kmem_cache_t *cp, void *buf, int kmflag, int construct, + caddr_t caller) +{ + kmem_buftag_t *btp = KMEM_BUFTAG(cp, buf); + kmem_bufctl_audit_t *bcp = (kmem_bufctl_audit_t *)btp->bt_bufctl; + uint32_t mtbf; + + if (btp->bt_bxstat != ((intptr_t)bcp ^ KMEM_BUFTAG_FREE)) { + kmem_error(KMERR_BADBUFTAG, cp, buf); + return (-1); + } + + btp->bt_bxstat = (intptr_t)bcp ^ KMEM_BUFTAG_ALLOC; + + if ((cp->cache_flags & KMF_HASH) && bcp->bc_addr != buf) { + kmem_error(KMERR_BADBUFCTL, cp, buf); + return (-1); + } + + if (cp->cache_flags & KMF_DEADBEEF) { + if (!construct && (cp->cache_flags & KMF_LITE)) { + if (*(uint64_t *)buf != KMEM_FREE_PATTERN) { + kmem_error(KMERR_MODIFIED, cp, buf); + return (-1); + } + if (cp->cache_constructor != NULL) + *(uint64_t *)buf = btp->bt_redzone; + else + *(uint64_t *)buf = KMEM_UNINITIALIZED_PATTERN; + } else { + construct = 1; + if (verify_and_copy_pattern(KMEM_FREE_PATTERN, + KMEM_UNINITIALIZED_PATTERN, buf, + cp->cache_verify)) { + kmem_error(KMERR_MODIFIED, cp, buf); + return (-1); + } + } + } + btp->bt_redzone = KMEM_REDZONE_PATTERN; + + if ((mtbf = kmem_mtbf | cp->cache_mtbf) != 0 && + gethrtime() % mtbf == 0 && + (kmflag & (KM_NOSLEEP | KM_PANIC)) == KM_NOSLEEP) { + kmem_log_event(kmem_failure_log, cp, NULL, NULL); + if (!construct && cp->cache_destructor != NULL) + cp->cache_destructor(buf, cp->cache_private); + } else { + mtbf = 0; + } + + if (mtbf || (construct && cp->cache_constructor != NULL && + cp->cache_constructor(buf, cp->cache_private, kmflag) != 0)) { + atomic_inc_64(&cp->cache_alloc_fail); + btp->bt_bxstat = (intptr_t)bcp ^ KMEM_BUFTAG_FREE; + if (cp->cache_flags & KMF_DEADBEEF) + copy_pattern(KMEM_FREE_PATTERN, buf, cp->cache_verify); + kmem_slab_free(cp, buf); + return (1); + } + + if (cp->cache_flags & KMF_AUDIT) { + KMEM_AUDIT(kmem_transaction_log, cp, bcp); + } + + if ((cp->cache_flags & KMF_LITE) && + !(cp->cache_cflags & KMC_KMEM_ALLOC)) { + KMEM_BUFTAG_LITE_ENTER(btp, kmem_lite_count, caller); + } + + return (0); +} + +static int +kmem_cache_free_debug(kmem_cache_t *cp, void *buf, caddr_t caller) +{ + kmem_buftag_t *btp = KMEM_BUFTAG(cp, buf); + kmem_bufctl_audit_t *bcp = (kmem_bufctl_audit_t *)btp->bt_bufctl; + kmem_slab_t *sp; + + if (btp->bt_bxstat != ((intptr_t)bcp ^ KMEM_BUFTAG_ALLOC)) { + if (btp->bt_bxstat == ((intptr_t)bcp ^ KMEM_BUFTAG_FREE)) { + kmem_error(KMERR_DUPFREE, cp, buf); + return (-1); + } + sp = kmem_findslab(cp, buf); + if (sp == NULL || sp->slab_cache != cp) + kmem_error(KMERR_BADADDR, cp, buf); + else + kmem_error(KMERR_REDZONE, cp, buf); + return (-1); + } + + btp->bt_bxstat = (intptr_t)bcp ^ KMEM_BUFTAG_FREE; + + if ((cp->cache_flags & KMF_HASH) && bcp->bc_addr != buf) { + kmem_error(KMERR_BADBUFCTL, cp, buf); + return (-1); + } + + if (btp->bt_redzone != KMEM_REDZONE_PATTERN) { + kmem_error(KMERR_REDZONE, cp, buf); + return (-1); + } + + if (cp->cache_flags & KMF_AUDIT) { + if (cp->cache_flags & KMF_CONTENTS) + bcp->bc_contents = kmem_log_enter(kmem_content_log, + buf, cp->cache_contents); + KMEM_AUDIT(kmem_transaction_log, cp, bcp); + } + + if ((cp->cache_flags & KMF_LITE) && + !(cp->cache_cflags & KMC_KMEM_ALLOC)) { + KMEM_BUFTAG_LITE_ENTER(btp, kmem_lite_count, caller); + } + + if (cp->cache_flags & KMF_DEADBEEF) { + if (cp->cache_flags & KMF_LITE) + btp->bt_redzone = *(uint64_t *)buf; + else if (cp->cache_destructor != NULL) + cp->cache_destructor(buf, cp->cache_private); + + copy_pattern(KMEM_FREE_PATTERN, buf, cp->cache_verify); + } + + return (0); +} + +/* + * Free each object in magazine mp to cp's slab layer, and free mp itself. + */ +static void +kmem_magazine_destroy(kmem_cache_t *cp, kmem_magazine_t *mp, int nrounds) +{ + int round; + + ASSERT(!list_link_active(&cp->cache_link) || + taskq_member(kmem_taskq, curthread)); + + for (round = 0; round < nrounds; round++) { + void *buf = mp->mag_round[round]; + + if (cp->cache_flags & KMF_DEADBEEF) { + if (verify_pattern(KMEM_FREE_PATTERN, buf, + cp->cache_verify) != NULL) { + kmem_error(KMERR_MODIFIED, cp, buf); + continue; + } + if ((cp->cache_flags & KMF_LITE) && + cp->cache_destructor != NULL) { + kmem_buftag_t *btp = KMEM_BUFTAG(cp, buf); + *(uint64_t *)buf = btp->bt_redzone; + cp->cache_destructor(buf, cp->cache_private); + *(uint64_t *)buf = KMEM_FREE_PATTERN; + } + } else if (cp->cache_destructor != NULL) { + cp->cache_destructor(buf, cp->cache_private); + } + + kmem_slab_free(cp, buf); + } + ASSERT(KMEM_MAGAZINE_VALID(cp, mp)); + kmem_cache_free(cp->cache_magtype->mt_cache, mp); +} + +/* + * Allocate a magazine from the depot. + */ +static kmem_magazine_t * +kmem_depot_alloc(kmem_cache_t *cp, kmem_maglist_t *mlp) +{ + kmem_magazine_t *mp; + + /* + * If we can't get the depot lock without contention, + * update our contention count. We use the depot + * contention rate to determine whether we need to + * increase the magazine size for better scalability. + */ + if (!mutex_tryenter(&cp->cache_depot_lock)) { + mutex_enter(&cp->cache_depot_lock); + cp->cache_depot_contention++; + } + + if ((mp = mlp->ml_list) != NULL) { + ASSERT(KMEM_MAGAZINE_VALID(cp, mp)); + mlp->ml_list = mp->mag_next; + if (--mlp->ml_total < mlp->ml_min) + mlp->ml_min = mlp->ml_total; + mlp->ml_alloc++; + } + + mutex_exit(&cp->cache_depot_lock); + + return (mp); +} + +/* + * Free a magazine to the depot. + */ +static void +kmem_depot_free(kmem_cache_t *cp, kmem_maglist_t *mlp, kmem_magazine_t *mp) +{ + mutex_enter(&cp->cache_depot_lock); + ASSERT(KMEM_MAGAZINE_VALID(cp, mp)); + mp->mag_next = mlp->ml_list; + mlp->ml_list = mp; + mlp->ml_total++; + mutex_exit(&cp->cache_depot_lock); +} + +/* + * Update the working set statistics for cp's depot. + */ +static void +kmem_depot_ws_update(kmem_cache_t *cp) +{ + mutex_enter(&cp->cache_depot_lock); + cp->cache_full.ml_reaplimit = cp->cache_full.ml_min; + cp->cache_full.ml_min = cp->cache_full.ml_total; + cp->cache_empty.ml_reaplimit = cp->cache_empty.ml_min; + cp->cache_empty.ml_min = cp->cache_empty.ml_total; + mutex_exit(&cp->cache_depot_lock); +} + +/* + * Set the working set statistics for cp's depot to zero. (Everything is + * eligible for reaping.) + */ +void +kmem_depot_ws_zero(kmem_cache_t *cp) +{ + mutex_enter(&cp->cache_depot_lock); + cp->cache_full.ml_reaplimit = cp->cache_full.ml_total; + cp->cache_full.ml_min = cp->cache_full.ml_total; + cp->cache_empty.ml_reaplimit = cp->cache_empty.ml_total; + cp->cache_empty.ml_min = cp->cache_empty.ml_total; + mutex_exit(&cp->cache_depot_lock); +} + +/* + * The number of bytes to reap before we call kpreempt(). + * + * There is a tradeoff between potentially many many preempts when giving + * freeing a large amount of ARC scatter ABDs (the preempts slightly slow down + * the return of memory to parent arenas during a larger reap, which in turn + * slightly delays the return of memory to the operating system) versus + * letting other threads on low-core-count machines make forward progress + * (which was upstream's goal when reap preemption was first introduced) or + * (in more modern times) gaining efficiencies in busy high-core-count + * machines that can have many threads allocating while an inevitably + * long-lived reap is in progress, narrowing the possibility of destroying + * kmem structures that might have to be rebuilt during the next preemption. + * + * Historically 1M was the value from upstream, which was increased for o3x + * for performance reasons. The reap mechanisms have evolved such that 1M + * is once again the better default. + */ +size_t kmem_reap_preempt_bytes = 1024 * 1024; + +/* + * Reap all magazines that have fallen out of the depot's working set. + */ +static void +kmem_depot_ws_reap(kmem_cache_t *cp) +{ + size_t bytes = 0; + long reap; + kmem_magazine_t *mp; + + ASSERT(!list_link_active(&cp->cache_link) || + taskq_member(kmem_taskq, curthread)); + + bool mtx_contended = false; + + if (!mutex_tryenter(&cp->cache_reap_lock)) { + mtx_contended = true; + printf("ZFS: SPL: %s:%s:%d: could not get lock\n", + __FILE__, __func__, __LINE__); + IOSleepWithLeeway(1, 1); + mutex_enter(&cp->cache_reap_lock); + } + + if (mtx_contended) + printf("ZFS: SPL: %s:%s:%d: reap mutex for %s " + "was contended\n", + __FILE__, __func__, __LINE__, + cp->cache_name); + + reap = MIN(cp->cache_full.ml_reaplimit, cp->cache_full.ml_min); + while (reap-- && + (mp = kmem_depot_alloc(cp, &cp->cache_full)) != NULL) { + kmem_magazine_destroy(cp, mp, cp->cache_magtype->mt_magsize); + bytes += cp->cache_magtype->mt_magsize * cp->cache_bufsize; + if (bytes > kmem_reap_preempt_bytes) { + kpreempt(KPREEMPT_SYNC); + bytes = 0; + } + } + + reap = MIN(cp->cache_empty.ml_reaplimit, cp->cache_empty.ml_min); + while (reap-- && + (mp = kmem_depot_alloc(cp, &cp->cache_empty)) != NULL) { + kmem_magazine_destroy(cp, mp, 0); + bytes += cp->cache_magtype->mt_magsize * cp->cache_bufsize; + if (bytes > kmem_reap_preempt_bytes) { + kpreempt(KPREEMPT_SYNC); + bytes = 0; + } + } + + mutex_exit(&cp->cache_reap_lock); +} + +static void +kmem_cpu_reload(kmem_cpu_cache_t *ccp, kmem_magazine_t *mp, int rounds) +{ + ASSERT((ccp->cc_loaded == NULL && ccp->cc_rounds == -1) || + (ccp->cc_loaded && ccp->cc_rounds + rounds == ccp->cc_magsize)); + ASSERT(ccp->cc_magsize > 0); + + ccp->cc_ploaded = ccp->cc_loaded; + ccp->cc_prounds = ccp->cc_rounds; + ccp->cc_loaded = mp; + ccp->cc_rounds = (short)rounds; +} + +/* + * Intercept kmem alloc/free calls during crash dump in order to avoid + * changing kmem state while memory is being saved to the dump device. + * Otherwise, ::kmem_verify will report "corrupt buffers". Note that + * there are no locks because only one CPU calls kmem during a crash + * dump. To enable this feature, first create the associated vmem + * arena with VMC_DUMPSAFE. + */ +static void *kmem_dump_start; /* start of pre-reserved heap */ +static void *kmem_dump_end; /* end of heap area */ +static void *kmem_dump_curr; /* current free heap pointer */ +static size_t kmem_dump_size; /* size of heap area */ + +/* append to each buf created in the pre-reserved heap */ +typedef struct kmem_dumpctl { + void *kdc_next; /* cache dump free list linkage */ +} kmem_dumpctl_t; + +#define KMEM_DUMPCTL(cp, buf) \ +((kmem_dumpctl_t *)P2ROUNDUP((uintptr_t)(buf) + (cp)->cache_bufsize, \ +sizeof (void *))) + +/* Keep some simple stats. */ +#define KMEM_DUMP_LOGS (100) + +typedef struct kmem_dump_log { + kmem_cache_t *kdl_cache; + uint_t kdl_allocs; /* # of dump allocations */ + uint_t kdl_frees; /* # of dump frees */ + uint_t kdl_alloc_fails; /* # of allocation failures */ + uint_t kdl_free_nondump; /* # of non-dump frees */ + uint_t kdl_unsafe; /* cache was used, but unsafe */ +} kmem_dump_log_t; + +static kmem_dump_log_t *kmem_dump_log; +static int kmem_dump_log_idx; + +#define KDI_LOG(cp, stat) { \ +kmem_dump_log_t *kdl; \ +if ((kdl = (kmem_dump_log_t *)((cp)->cache_dumplog)) != NULL) { \ +kdl->stat++; \ +} else if (kmem_dump_log_idx < KMEM_DUMP_LOGS) { \ +kdl = &kmem_dump_log[kmem_dump_log_idx++]; \ +kdl->stat++; \ +kdl->kdl_cache = (cp); \ +(cp)->cache_dumplog = kdl; \ +} \ +} + +/* set non zero for full report */ +uint_t kmem_dump_verbose = 0; + +/* stats for overize heap */ +uint_t kmem_dump_oversize_allocs = 0; +uint_t kmem_dump_oversize_max = 0; + +static void +kmem_dumppr(char **pp, char *e, const char *format, ...) +{ + char *p = *pp; + + if (p < e) { + int n; + va_list ap; + + va_start(ap, format); + n = vsnprintf(p, e - p, format, ap); + va_end(ap); + *pp = p + n; + } +} + +/* + * Called when dumpadm(1M) configures dump parameters. + */ +void +kmem_dump_init(size_t size) +{ + if (kmem_dump_start != NULL) + zfs_kmem_free(kmem_dump_start, kmem_dump_size); + + if (kmem_dump_log == NULL) + kmem_dump_log = + (kmem_dump_log_t *)zfs_kmem_zalloc( + KMEM_DUMP_LOGS * sizeof (kmem_dump_log_t), KM_SLEEP); + + kmem_dump_start = zfs_kmem_alloc(size, KM_SLEEP); + + if (kmem_dump_start != NULL) { + kmem_dump_size = size; + kmem_dump_curr = kmem_dump_start; + kmem_dump_end = (void *)((char *)kmem_dump_start + size); + copy_pattern(KMEM_UNINITIALIZED_PATTERN, kmem_dump_start, size); + } else { + kmem_dump_size = 0; + kmem_dump_curr = NULL; + kmem_dump_end = NULL; + } +} + +/* + * Set flag for each kmem_cache_t if is safe to use alternate dump + * memory. Called just before panic crash dump starts. Set the flag + * for the calling CPU. + */ +void +kmem_dump_begin(void) +{ + if (kmem_dump_start != NULL) { + kmem_cache_t *cp; + + for (cp = list_head(&kmem_caches); cp != NULL; + cp = list_next(&kmem_caches, cp)) { + kmem_cpu_cache_t *ccp = KMEM_CPU_CACHE(cp); + + if (cp->cache_arena->vm_cflags & VMC_DUMPSAFE) { + cp->cache_flags |= KMF_DUMPDIVERT; + ccp->cc_flags |= KMF_DUMPDIVERT; + ccp->cc_dump_rounds = ccp->cc_rounds; + ccp->cc_dump_prounds = ccp->cc_prounds; + ccp->cc_rounds = ccp->cc_prounds = -1; + } else { + cp->cache_flags |= KMF_DUMPUNSAFE; + ccp->cc_flags |= KMF_DUMPUNSAFE; + } + } + } +} + +/* + * finished dump intercept + * print any warnings on the console + * return verbose information to dumpsys() in the given buffer + */ +size_t +kmem_dump_finish(char *buf, size_t size) +{ + int kdi_idx; + int kdi_end = kmem_dump_log_idx; + int percent = 0; + int header = 0; + size_t used; + kmem_cache_t *cp; + kmem_dump_log_t *kdl; + char *e = buf + size; + char *p = buf; + + if (kmem_dump_size == 0 || kmem_dump_verbose == 0) + return (0); + + used = (char *)kmem_dump_curr - (char *)kmem_dump_start; + percent = (used * 100) / kmem_dump_size; + + kmem_dumppr(&p, e, "%% heap used,%d\n", percent); + kmem_dumppr(&p, e, "used bytes,%ld\n", used); + kmem_dumppr(&p, e, "heap size,%ld\n", kmem_dump_size); + kmem_dumppr(&p, e, "Oversize allocs,%d\n", + kmem_dump_oversize_allocs); + kmem_dumppr(&p, e, "Oversize max size,%u\n", + kmem_dump_oversize_max); + + for (kdi_idx = 0; kdi_idx < kdi_end; kdi_idx++) { + kdl = &kmem_dump_log[kdi_idx]; + cp = kdl->kdl_cache; + if (cp == NULL) + break; + if (header == 0) { + kmem_dumppr(&p, e, + "Cache Name,Allocs,Frees,Alloc Fails," + "Nondump Frees,Unsafe Allocs/Frees\n"); + header = 1; + } + kmem_dumppr(&p, e, "%s,%d,%d,%d,%d,%d\n", + cp->cache_name, kdl->kdl_allocs, kdl->kdl_frees, + kdl->kdl_alloc_fails, kdl->kdl_free_nondump, + kdl->kdl_unsafe); + } + + /* return buffer size used */ + if (p < e) + memset(p, 0, e - p); + return (p - buf); +} + +/* + * Allocate a constructed object from alternate dump memory. + */ +void * +kmem_cache_alloc_dump(kmem_cache_t *cp, int kmflag) +{ + void *buf; + void *curr; + char *bufend; + + /* return a constructed object */ + if ((buf = cp->cache_dumpfreelist) != NULL) { + cp->cache_dumpfreelist = KMEM_DUMPCTL(cp, buf)->kdc_next; + KDI_LOG(cp, kdl_allocs); + return (buf); + } + + /* create a new constructed object */ + curr = kmem_dump_curr; + buf = (void *)P2ROUNDUP((uintptr_t)curr, cp->cache_align); + bufend = (char *)KMEM_DUMPCTL(cp, buf) + sizeof (kmem_dumpctl_t); + + /* hat layer objects cannot cross a page boundary */ + if (cp->cache_align < PAGESIZE) { + char *page = (char *)P2ROUNDUP((uintptr_t)buf, PAGESIZE); + if (bufend > page) { + bufend += page - (char *)buf; + buf = (void *)page; + } + } + + /* fall back to normal alloc if reserved area is used up */ + if (bufend > (char *)kmem_dump_end) { + kmem_dump_curr = kmem_dump_end; + KDI_LOG(cp, kdl_alloc_fails); + return (NULL); + } + + /* + * Must advance curr pointer before calling a constructor that + * may also allocate memory. + */ + kmem_dump_curr = bufend; + + /* run constructor */ + if (cp->cache_constructor != NULL && + cp->cache_constructor(buf, cp->cache_private, kmflag) + != 0) { +#ifdef DEBUG + printf("name='%s' cache=0x%p: kmem cache constructor failed\n", + cp->cache_name, (void *)cp); +#endif + /* reset curr pointer iff no allocs were done */ + if (kmem_dump_curr == bufend) + kmem_dump_curr = curr; + + /* fall back to normal alloc if the constructor fails */ + KDI_LOG(cp, kdl_alloc_fails); + return (NULL); + } + + KDI_LOG(cp, kdl_allocs); + return (buf); +} + +/* + * Free a constructed object in alternate dump memory. + */ +int +kmem_cache_free_dump(kmem_cache_t *cp, void *buf) +{ + /* save constructed buffers for next time */ + if ((char *)buf >= (char *)kmem_dump_start && + (char *)buf < (char *)kmem_dump_end) { + KMEM_DUMPCTL(cp, buf)->kdc_next = cp->cache_dumpfreelist; + cp->cache_dumpfreelist = buf; + KDI_LOG(cp, kdl_frees); + return (0); + } + + /* count all non-dump buf frees */ + KDI_LOG(cp, kdl_free_nondump); + + /* just drop buffers that were allocated before dump started */ + if (kmem_dump_curr < kmem_dump_end) + return (0); + + /* fall back to normal free if reserved area is used up */ + return (1); +} + +/* + * Allocate a constructed object from cache cp. + */ +void * +kmem_cache_alloc(kmem_cache_t *cp, int kmflag) +{ + kmem_cpu_cache_t *ccp = KMEM_CPU_CACHE(cp); + kmem_magazine_t *fmp; + void *buf; + mutex_enter(&ccp->cc_lock); + for (;;) { + /* + * If there's an object available in the current CPU's + * loaded magazine, just take it and return. + */ + if (ccp->cc_rounds > 0) { + buf = ccp->cc_loaded->mag_round[--ccp->cc_rounds]; + ccp->cc_alloc++; + mutex_exit(&ccp->cc_lock); + if (ccp->cc_flags & (KMF_BUFTAG | KMF_DUMPUNSAFE)) { + if (ccp->cc_flags & KMF_DUMPUNSAFE) { + ASSERT(!(ccp->cc_flags & + KMF_DUMPDIVERT)); + KDI_LOG(cp, kdl_unsafe); + } + if ((ccp->cc_flags & KMF_BUFTAG) && + kmem_cache_alloc_debug(cp, buf, kmflag, 0, + caller()) != 0) { + if (kmflag & KM_NOSLEEP) + return (NULL); + mutex_enter(&ccp->cc_lock); + continue; + } + } + return (buf); + } + + /* + * The loaded magazine is empty. If the previously loaded + * magazine was full, exchange them and try again. + */ + if (ccp->cc_prounds > 0) { + kmem_cpu_reload(ccp, ccp->cc_ploaded, ccp->cc_prounds); + continue; + } + + /* + * Return an alternate buffer at dump time to preserve + * the heap. + */ + if (ccp->cc_flags & (KMF_DUMPDIVERT | KMF_DUMPUNSAFE)) { + if (ccp->cc_flags & KMF_DUMPUNSAFE) { + ASSERT(!(ccp->cc_flags & KMF_DUMPDIVERT)); + /* log it so that we can warn about it */ + KDI_LOG(cp, kdl_unsafe); + } else { + if ((buf = kmem_cache_alloc_dump(cp, kmflag)) != + NULL) { + mutex_exit(&ccp->cc_lock); + return (buf); + } + break; /* fall back to slab layer */ + } + } + + /* + * If the magazine layer is disabled, break out now. + */ + if (ccp->cc_magsize == 0) + break; + + /* + * Try to get a full magazine from the depot. + */ + fmp = kmem_depot_alloc(cp, &cp->cache_full); + if (fmp != NULL) { + if (ccp->cc_ploaded != NULL) + kmem_depot_free(cp, &cp->cache_empty, + ccp->cc_ploaded); + kmem_cpu_reload(ccp, fmp, ccp->cc_magsize); + continue; + } + + /* + * There are no full magazines in the depot, + * so fall through to the slab layer. + */ + break; + } + mutex_exit(&ccp->cc_lock); + + /* + * We couldn't allocate a constructed object from the magazine layer, + * so get a raw buffer from the slab layer and apply its constructor. + */ + buf = kmem_slab_alloc(cp, kmflag); + + if (buf == NULL) + return (NULL); + + if (cp->cache_flags & KMF_BUFTAG) { + /* + * Make kmem_cache_alloc_debug() apply the constructor for us. + */ + int rc = kmem_cache_alloc_debug(cp, buf, kmflag, 1, caller()); + if (rc != 0) { + if (kmflag & KM_NOSLEEP) + return (NULL); + /* + * kmem_cache_alloc_debug() detected corruption + * but didn't panic (kmem_panic <= 0). We should not be + * here because the constructor failed (indicated by a + * return code of 1). Try again. + */ + ASSERT(rc == -1); + return (kmem_cache_alloc(cp, kmflag)); + } + return (buf); + } + + if (cp->cache_constructor != NULL && + cp->cache_constructor(buf, cp->cache_private, kmflag) != 0) { + atomic_inc_64(&cp->cache_alloc_fail); + kmem_slab_free(cp, buf); + return (NULL); + } + + return (buf); +} + +/* + * The freed argument tells whether or not kmem_cache_free_debug() has already + * been called so that we can avoid the duplicate free error. For example, a + * buffer on a magazine has already been freed by the client but is still + * constructed. + */ +static void +kmem_slab_free_constructed(kmem_cache_t *cp, void *buf, boolean_t freed) +{ + if (!freed && (cp->cache_flags & KMF_BUFTAG)) + if (kmem_cache_free_debug(cp, buf, caller()) == -1) + return; + + /* + * Note that if KMF_DEADBEEF is in effect and KMF_LITE is not, + * kmem_cache_free_debug() will have already applied the destructor. + */ + if ((cp->cache_flags & (KMF_DEADBEEF | KMF_LITE)) != KMF_DEADBEEF && + cp->cache_destructor != NULL) { + if (cp->cache_flags & KMF_DEADBEEF) { /* KMF_LITE implied */ + kmem_buftag_t *btp = KMEM_BUFTAG(cp, buf); + *(uint64_t *)buf = btp->bt_redzone; + cp->cache_destructor(buf, cp->cache_private); + *(uint64_t *)buf = KMEM_FREE_PATTERN; + } else { + cp->cache_destructor(buf, cp->cache_private); + } + } + + kmem_slab_free(cp, buf); +} + +/* + * Used when there's no room to free a buffer to the per-CPU cache. + * Drops and re-acquires &ccp->cc_lock, and returns non-zero if the + * caller should try freeing to the per-CPU cache again. + * Note that we don't directly install the magazine in the cpu cache, + * since its state may have changed wildly while the lock was dropped. + */ +static int +kmem_cpucache_magazine_alloc(kmem_cpu_cache_t *ccp, kmem_cache_t *cp) +{ + kmem_magazine_t *emp; + kmem_magtype_t *mtp; + + ASSERT(MUTEX_HELD(&ccp->cc_lock)); + ASSERT(((uint_t)ccp->cc_rounds == ccp->cc_magsize || + ((uint_t)ccp->cc_rounds == -1)) && + ((uint_t)ccp->cc_prounds == ccp->cc_magsize || + ((uint_t)ccp->cc_prounds == -1))); + + emp = kmem_depot_alloc(cp, &cp->cache_empty); + if (emp != NULL) { + if (ccp->cc_ploaded != NULL) + kmem_depot_free(cp, &cp->cache_full, + ccp->cc_ploaded); + kmem_cpu_reload(ccp, emp, 0); + return (1); + } + /* + * There are no empty magazines in the depot, + * so try to allocate a new one. We must drop all locks + * across kmem_cache_alloc() because lower layers may + * attempt to allocate from this cache. + */ + mtp = cp->cache_magtype; + mutex_exit(&ccp->cc_lock); + emp = kmem_cache_alloc(mtp->mt_cache, KM_NOSLEEP); + mutex_enter(&ccp->cc_lock); + + if (emp != NULL) { + /* + * We successfully allocated an empty magazine. + * However, we had to drop ccp->cc_lock to do it, + * so the cache's magazine size may have changed. + * If so, free the magazine and try again. + */ + if (ccp->cc_magsize != mtp->mt_magsize) { + mutex_exit(&ccp->cc_lock); + kmem_cache_free(mtp->mt_cache, emp); + mutex_enter(&ccp->cc_lock); + return (1); + } + + /* + * We got a magazine of the right size. Add it to + * the depot and try the whole dance again. + */ + kmem_depot_free(cp, &cp->cache_empty, emp); + return (1); + } + + /* + * We couldn't allocate an empty magazine, + * so fall through to the slab layer. + */ + return (0); +} + +/* + * If the cache's parent arena is a leaf arena (i.e., it imports all its memory) + * then we can consider it fragmented if either there is 1 GiB free in the arena + * or one eighth of the arena is free. + * + * This is useful in kmem_cache_free{_debug} to determine whether to free to the + * slab layer if the loaded magazine is full. + */ +static inline boolean_t +kmem_cache_parent_arena_fragmented(kmem_cache_t *cp) +{ + const vmem_kstat_t *kp = &cp->cache_arena->vm_kstat; + const int64_t vk_import = kp->vk_mem_import.value.ui64; + const int64_t vk_inuse = kp->vk_mem_inuse.value.ui64; + const int64_t vk_total = kp->vk_mem_total.value.ui64; + + if (vk_import == vk_total && vk_inuse < vk_total) { + const int64_t vk_free = vk_total - vk_inuse; + const int64_t highthresh = 1024LL*1024LL*1024LL; + // we are fragmented if we have 1GiB free + if (vk_free >= highthresh) + return (B_TRUE); + // we are fragmented if at least 1/8 of the + // total arena space is free + if (vk_free > 0 && vk_total > 0) { + const int64_t eighth_total = vk_total / 8; + if (vk_free >= eighth_total) + return (B_TRUE); + } + } + return (B_FALSE); +} + +/* + * Free a constructed object to cache cp. + */ +void +kmem_cache_free(kmem_cache_t *cp, const void *buf) +{ + kmem_cpu_cache_t *ccp = KMEM_CPU_CACHE(cp); + + /* + * The client must not free either of the buffers passed to the move + * callback function. + */ + ASSERT(cp->cache_defrag == NULL || + cp->cache_defrag->kmd_thread != spl_current_thread() || + (buf != cp->cache_defrag->kmd_from_buf && + buf != cp->cache_defrag->kmd_to_buf)); + + if (ccp->cc_flags & (KMF_BUFTAG | KMF_DUMPDIVERT | KMF_DUMPUNSAFE)) { + if (ccp->cc_flags & KMF_DUMPUNSAFE) { + ASSERT(!(ccp->cc_flags & KMF_DUMPDIVERT)); + /* log it so that we can warn about it */ + KDI_LOG(cp, kdl_unsafe); + } else if (KMEM_DUMPCC(ccp) && !kmem_cache_free_dump(cp, + __DECONST(void *, buf))) { + return; + } + if (ccp->cc_flags & KMF_BUFTAG) { + if (kmem_cache_free_debug(cp, __DECONST(void *, buf), + caller()) == -1) + return; + } + } + + mutex_enter(&ccp->cc_lock); + /* + * Any changes to this logic should be reflected in kmem_slab_prefill() + */ + for (;;) { + /* + * If there's a slot available in the current CPU's + * loaded magazine, just put the object there and return. + */ + if ((uint_t)ccp->cc_rounds < ccp->cc_magsize) { + ccp->cc_loaded->mag_round[ccp->cc_rounds++] = + __DECONST(void *, buf); + ccp->cc_free++; + mutex_exit(&ccp->cc_lock); + return; + } + + /* + * If the magazine layer is disabled, break out now. + */ + if (ccp->cc_magsize == 0) { + break; + } + + /* + * The magazine layer is on, but the loaded magazine is now + * full (of allocatable constructed elements). + * + * If the cache's arena is badly fragmented, break out now; + * this frees to the slab layer. + * + * Note: this is not reflected in kmem_slab_prefill() which + * deals with a freshly allocated slab. + */ + + if (kmem_free_to_slab_when_fragmented == 1 && + kmem_cache_parent_arena_fragmented(cp)) + break; + + /* + * The loaded magazine is full. If the previously loaded + * magazine was empty, exchange them and try again. + */ + if (ccp->cc_prounds == 0) { + kmem_cpu_reload(ccp, ccp->cc_ploaded, ccp->cc_prounds); + continue; + } + + if (!kmem_cpucache_magazine_alloc(ccp, cp)) { + /* + * We couldn't free our constructed object to the + * magazine layer, so apply its destructor and free it + * to the slab layer. + */ + break; + } + } + mutex_exit(&ccp->cc_lock); + kmem_slab_free_constructed(cp, __DECONST(void *, buf), B_TRUE); +} + +/* + * Free a constructed object to cache cp. + * Do not free to the magazine layer. + * This is essentially just kmem_cache_free() without + * the for(;;) loop or the ccp critical section. + */ +void +kmem_cache_free_to_slab(kmem_cache_t *cp, void *buf) +{ + kmem_cpu_cache_t *ccp = KMEM_CPU_CACHE(cp); + + /* + * The client must not free either of the buffers passed to the move + * callback function. + */ + ASSERT(cp->cache_defrag == NULL || + cp->cache_defrag->kmd_thread != spl_current_thread() || + (buf != cp->cache_defrag->kmd_from_buf && + buf != cp->cache_defrag->kmd_to_buf)); + + if (ccp->cc_flags & (KMF_BUFTAG | KMF_DUMPDIVERT | KMF_DUMPUNSAFE)) { + if (ccp->cc_flags & KMF_DUMPUNSAFE) { + ASSERT(!(ccp->cc_flags & KMF_DUMPDIVERT)); + /* log it so that we can warn about it */ + KDI_LOG(cp, kdl_unsafe); + } else if (KMEM_DUMPCC(ccp) && !kmem_cache_free_dump(cp, buf)) { + return; + } + if (ccp->cc_flags & KMF_BUFTAG) { + if (kmem_cache_free_debug(cp, buf, caller()) == -1) + return; + } + } + + /* omitted the for(;;) loop from kmem_cache_free */ + /* also do not take ccp mutex */ + + kmem_slab_free_constructed(cp, buf, B_TRUE); +} + +static void +kmem_slab_prefill(kmem_cache_t *cp, kmem_slab_t *sp) +{ + kmem_cpu_cache_t *ccp = KMEM_CPU_CACHE(cp); + + kmem_bufctl_t *next, *head; + size_t nbufs; + + /* + * Completely allocate the newly created slab and put the pre-allocated + * buffers in magazines. Any of the buffers that cannot be put in + * magazines must be returned to the slab. + */ + ASSERT(MUTEX_HELD(&cp->cache_lock)); + ASSERT(cp->cache_constructor == NULL); + ASSERT(sp->slab_cache == cp); + ASSERT(sp->slab_refcnt == 1); + ASSERT(sp->slab_head != NULL && sp->slab_chunks > sp->slab_refcnt); + ASSERT(avl_find(&cp->cache_partial_slabs, sp, NULL) == NULL); + + head = sp->slab_head; + nbufs = (sp->slab_chunks - sp->slab_refcnt); + sp->slab_head = NULL; + sp->slab_refcnt += nbufs; + cp->cache_bufslab -= nbufs; + cp->cache_slab_alloc += nbufs; + list_insert_head(&cp->cache_complete_slabs, sp); + cp->cache_complete_slab_count++; + mutex_exit(&cp->cache_lock); + mutex_enter(&ccp->cc_lock); + + while (head != NULL) { + void *buf = KMEM_BUF(cp, head); + /* + * If there's a slot available in the current CPU's + * loaded magazine, just put the object there and + * continue. + */ + if ((uint_t)ccp->cc_rounds < ccp->cc_magsize) { + ccp->cc_loaded->mag_round[ccp->cc_rounds++] = + buf; + ccp->cc_free++; + nbufs--; + head = head->bc_next; + continue; + } + + /* + * The loaded magazine is full. If the previously + * loaded magazine was empty, exchange them and try + * again. + */ + if (ccp->cc_prounds == 0) { + kmem_cpu_reload(ccp, ccp->cc_ploaded, + ccp->cc_prounds); + continue; + } + + /* + * If the magazine layer is disabled, break out now. + */ + + if (ccp->cc_magsize == 0) { + break; + } + + if (!kmem_cpucache_magazine_alloc(ccp, cp)) + break; + } + mutex_exit(&ccp->cc_lock); + if (nbufs != 0) { + ASSERT(head != NULL); + + /* + * If there was a failure, return remaining objects to + * the slab + */ + while (head != NULL) { + ASSERT(nbufs != 0); + next = head->bc_next; + head->bc_next = NULL; + kmem_slab_free(cp, KMEM_BUF(cp, head)); + head = next; + nbufs--; + } + } + ASSERT(head == NULL); + ASSERT(nbufs == 0); + mutex_enter(&cp->cache_lock); +} + +void * +zfs_kmem_zalloc(size_t size, int kmflag) +{ + size_t index; + void *buf; + + if ((index = ((size - 1) >> KMEM_ALIGN_SHIFT)) < KMEM_ALLOC_TABLE_MAX) { + kmem_cache_t *cp = kmem_alloc_table[index]; + buf = kmem_cache_alloc(cp, kmflag); + if (buf != NULL) { + if ((cp->cache_flags & KMF_BUFTAG) && !KMEM_DUMP(cp)) { + kmem_buftag_t *btp = KMEM_BUFTAG(cp, buf); + ((uint8_t *)buf)[size] = KMEM_REDZONE_BYTE; + ((uint32_t *)btp)[1] = KMEM_SIZE_ENCODE(size); + + if (cp->cache_flags & KMF_LITE) { + KMEM_BUFTAG_LITE_ENTER(btp, + kmem_lite_count, caller()); + } + } + memset(buf, 0, size); + } + } else { + buf = zfs_kmem_alloc(size, kmflag); + if (buf != NULL) + memset(buf, 0, size); + } + return (buf); +} + +void * +zfs_kmem_alloc(size_t size, int kmflag) +{ + size_t index; + kmem_cache_t *cp; + void *buf; + + if (size == 0) + return (KMEM_ZERO_SIZE_PTR); + + if ((index = ((size - 1) >> KMEM_ALIGN_SHIFT)) < KMEM_ALLOC_TABLE_MAX) { + cp = kmem_alloc_table[index]; + /* fall through to kmem_cache_alloc() */ + + } else if ((index = ((size - 1) >> KMEM_BIG_SHIFT)) < + kmem_big_alloc_table_max) { + cp = kmem_big_alloc_table[index]; + /* fall through to kmem_cache_alloc() */ + + } else { + + buf = vmem_alloc_impl(kmem_oversize_arena, size, + kmflag & KM_VMFLAGS); + if (buf == NULL) + kmem_log_event(kmem_failure_log, NULL, NULL, + (void *)size); + else if (KMEM_DUMP(kmem_slab_cache)) { + /* stats for dump intercept */ + kmem_dump_oversize_allocs++; + if (size > kmem_dump_oversize_max) + kmem_dump_oversize_max = size; + } + return (buf); + } + + buf = kmem_cache_alloc(cp, kmflag); + if ((cp->cache_flags & KMF_BUFTAG) && !KMEM_DUMP(cp) && buf != NULL) { + kmem_buftag_t *btp = KMEM_BUFTAG(cp, buf); + ((uint8_t *)buf)[size] = KMEM_REDZONE_BYTE; + ((uint32_t *)btp)[1] = KMEM_SIZE_ENCODE(size); + + if (cp->cache_flags & KMF_LITE) { + KMEM_BUFTAG_LITE_ENTER(btp, kmem_lite_count, caller()); + } + } + return (buf); +} + +void +zfs_kmem_free(const void *buf, size_t size) +{ + size_t index; + kmem_cache_t *cp; + + if (size == 0 || buf == KMEM_ZERO_SIZE_PTR || buf == NULL) + return; + + if ((index = (size - 1) >> KMEM_ALIGN_SHIFT) < KMEM_ALLOC_TABLE_MAX) { + cp = kmem_alloc_table[index]; + /* fall through to kmem_cache_free() */ + + } else if ((index = ((size - 1) >> KMEM_BIG_SHIFT)) < + kmem_big_alloc_table_max) { + cp = kmem_big_alloc_table[index]; + /* fall through to kmem_cache_free() */ + + } else { + vmem_free_impl(kmem_oversize_arena, buf, size); + return; + } + + if ((cp->cache_flags & KMF_BUFTAG) && !KMEM_DUMP(cp)) { + kmem_buftag_t *btp = KMEM_BUFTAG(cp, buf); + uint32_t *ip = (uint32_t *)btp; + if (ip[1] != KMEM_SIZE_ENCODE(size)) { + if (*(uint64_t *)buf == KMEM_FREE_PATTERN) { + kmem_error(KMERR_DUPFREE, cp, buf); + return; + } + if (KMEM_SIZE_VALID(ip[1])) { + ip[0] = KMEM_SIZE_ENCODE(size); + kmem_error(KMERR_BADSIZE, cp, buf); + } else { + kmem_error(KMERR_REDZONE, cp, buf); + } + return; + } + if (((uint8_t *)buf)[size] != KMEM_REDZONE_BYTE) { + kmem_error(KMERR_REDZONE, cp, buf); + return; + } + btp->bt_redzone = KMEM_REDZONE_PATTERN; + if (cp->cache_flags & KMF_LITE) { + KMEM_BUFTAG_LITE_ENTER(btp, kmem_lite_count, + caller()); + } + } + kmem_cache_free(cp, buf); +} + +/* + * Try to allocate at least `size' bytes of memory without sleeping or + * panicking. Return actual allocated size in `asize'. If allocation failed, + * try final allocation with sleep or panic allowed. + */ +void * +kmem_alloc_tryhard(size_t size, size_t *asize, int kmflag) +{ + void *p; + + *asize = P2ROUNDUP(size, KMEM_ALIGN); + do { + p = kmem_alloc(*asize, (kmflag | KM_NOSLEEP) & ~KM_PANIC); + if (p != NULL) + return (p); + *asize += KMEM_ALIGN; + } while (*asize <= PAGESIZE); + + *asize = P2ROUNDUP(size, KMEM_ALIGN); + return (zfs_kmem_alloc(*asize, kmflag)); +} + +/* + * Reclaim all unused memory from a cache. + */ +static void +kmem_cache_reap(kmem_cache_t *cp) +{ + ASSERT(taskq_member(kmem_taskq, curthread)); + + cp->cache_reap++; + + /* + * Ask the cache's owner to free some memory if possible. + * The idea is to handle things like the inode cache, which + * typically sits on a bunch of memory that it doesn't truly + * *need*. Reclaim policy is entirely up to the owner; this + * callback is just an advisory plea for help. + */ + if (cp->cache_reclaim != NULL) { + long delta; + + /* + * Reclaimed memory should be reapable (not included in the + * depot's working set). + */ + delta = cp->cache_full.ml_total; + cp->cache_reclaim(cp->cache_private); + delta = cp->cache_full.ml_total - delta; + if (delta > 0) { + mutex_enter(&cp->cache_depot_lock); + cp->cache_full.ml_reaplimit += delta; + cp->cache_full.ml_min += delta; + mutex_exit(&cp->cache_depot_lock); + } + } + + kmem_depot_ws_reap(cp); + + if (cp->cache_defrag != NULL && !kmem_move_noreap) { + kmem_cache_defrag(cp); + } +} + +static void +kmem_reap_timeout(void *flag_arg) +{ + uint32_t *flag = (uint32_t *)flag_arg; + + ASSERT(flag == &kmem_reaping || flag == &kmem_reaping_idspace); + __atomic_store_n(flag, 0, __ATOMIC_RELEASE); + ASSERT3U(*flag, ==, 0); +} + +static void +kmem_reap_done(void *flag) +{ + (void) bsd_timeout(kmem_reap_timeout, flag, &kmem_reap_interval); +} + +static void +kmem_reap_start(void *flag) +{ + ASSERT(flag == &kmem_reaping || flag == &kmem_reaping_idspace); + + if (flag == &kmem_reaping) { + kmem_cache_applyall(kmem_cache_reap, kmem_taskq, TQ_NOSLEEP); + /* + * if we have segkp under heap, reap segkp cache. + */ + } + else + kmem_cache_applyall_id(kmem_cache_reap, kmem_taskq, TQ_NOSLEEP); + + /* + * We use taskq_dispatch() to schedule a timeout to clear + * the flag so that kmem_reap() becomes self-throttling: + * we won't reap again until the current reap completes *and* + * at least kmem_reap_interval ticks have elapsed. + */ + if (!taskq_dispatch(kmem_taskq, kmem_reap_done, flag, TQ_NOSLEEP)) + kmem_reap_done(flag); +} + +static void +kmem_reap_common(void *flag_arg) +{ + uint32_t *flag = (uint32_t *)flag_arg; + + ASSERT(flag == &kmem_reaping || flag == &kmem_reaping_idspace); + + /* If conditions are met, try to set flag to 1 */ + if (MUTEX_HELD(&kmem_cache_lock) || kmem_taskq == NULL || + atomic_cas_32(flag, 0, 1) != 0) + return; + /* + * If we are here, the appropriate flag is 1. It will be atomically + * zeroed after the reaping has finished and the timeout has expired. + */ + + /* + * It may not be safe to do memory allocation when a reap + * is called (for example, if vmem_populate() is in the call chain). + * So we start the reap going with a TQ_NOALLOC dispatch. If the + * dispatch fails, we reset the flag, and the next reap will try again. + */ + if (!taskq_dispatch(kmem_taskq, kmem_reap_start, flag, TQ_NOALLOC)) { + __atomic_store_n(flag, 0, __ATOMIC_RELEASE); + ASSERT3U(*flag, ==, 0); + } +} + +/* + * Reclaim all unused memory from all caches. Called from the VM system + * when memory gets tight. + */ +void +kmem_reap(void) +{ + kmem_reap_common(&kmem_reaping); +} + +/* + * Reclaim all unused memory from identifier arenas, called when a vmem + * arena not back by memory is exhausted. Since reaping memory-backed caches + * cannot help with identifier exhaustion, we avoid both a large amount of + * work and unwanted side-effects from reclaim callbacks. + */ +void +kmem_reap_idspace(void) +{ + kmem_reap_common(&kmem_reaping_idspace); +} + +/* + * Purge all magazines from a cache and set its magazine limit to zero. + * All calls are serialized by the kmem_taskq lock, except for the final + * call from kmem_cache_destroy(). + */ +static void +kmem_cache_magazine_purge(kmem_cache_t *cp) +{ + kmem_cpu_cache_t *ccp; + kmem_magazine_t *mp, *pmp; + int rounds, prounds, cpu_seqid; + + ASSERT(!list_link_active(&cp->cache_link) || + taskq_member(kmem_taskq, curthread)); + ASSERT(MUTEX_NOT_HELD(&cp->cache_lock)); + + for (cpu_seqid = 0; cpu_seqid < max_ncpus; cpu_seqid++) { + ccp = &cp->cache_cpu[cpu_seqid]; + + mutex_enter(&ccp->cc_lock); + mp = ccp->cc_loaded; + pmp = ccp->cc_ploaded; + rounds = ccp->cc_rounds; + prounds = ccp->cc_prounds; + ccp->cc_loaded = NULL; + ccp->cc_ploaded = NULL; + ccp->cc_rounds = -1; + ccp->cc_prounds = -1; + ccp->cc_magsize = 0; + mutex_exit(&ccp->cc_lock); + + if (mp) + kmem_magazine_destroy(cp, mp, rounds); + + if (pmp) + kmem_magazine_destroy(cp, pmp, prounds); + } + + kmem_depot_ws_zero(cp); + kmem_depot_ws_reap(cp); +} + +/* + * Enable per-cpu magazines on a cache. + */ +static void +kmem_cache_magazine_enable(kmem_cache_t *cp) +{ + int cpu_seqid; + + if (cp->cache_flags & KMF_NOMAGAZINE) + return; + + for (cpu_seqid = 0; cpu_seqid < max_ncpus; cpu_seqid++) { + kmem_cpu_cache_t *ccp = &cp->cache_cpu[cpu_seqid]; + mutex_enter(&ccp->cc_lock); + ccp->cc_magsize = cp->cache_magtype->mt_magsize; + mutex_exit(&ccp->cc_lock); + } + +} + +static void +kmem_cache_magazine_disable(kmem_cache_t *cp) +{ + int cpu_seqid; + + if (cp->cache_flags & KMF_NOMAGAZINE) + return; + + for (cpu_seqid = 0; cpu_seqid < max_ncpus; cpu_seqid++) { + kmem_cpu_cache_t *ccp = &cp->cache_cpu[cpu_seqid]; + mutex_enter(&ccp->cc_lock); + ccp->cc_magsize = 0; + mutex_exit(&ccp->cc_lock); + } + +} + +/* + * Allow our caller to determine if there are running reaps. + * + * This call is very conservative and may return B_TRUE even when + * reaping activity isn't active. If it returns B_FALSE, then reaping + * activity is definitely inactive. + */ +boolean_t +kmem_cache_reap_active(void) +{ + return (kmem_reaping); +} + +/* + * Fire off a kmem_reap(); that will put a kmem_reap_start() into the taskq if + * conditions are favourable. + * + * This function can be frequently called by common code. Arguably it is + * over-called. + * + * Previously, a kmem_depot_ws_zero(cp) would erase the working set + * information of the kmem cache; it is probably better to let other events + * evolve the magazine working set. + * + * Also previously, a kmem_depot_ws_reap(cp) was dispatched on the kmem taskq. + * This appears to have some unsafeness with respect to concurrency, and this + * unconditional start-a-reap-right-now approach was abandoned by the other + * openzfs ports. On macOS there does not seem to be an advantage in stepping + * around the kmem_reap{,common,start,timeout}() concurrency-controlling + * mechanism (atomic compare-and-swap on kmem_reaping, with an atomic set to + * zero after a delay once the reaping task is done). Moreover, skipping the + * kmem_reaping flag check may have led to double-frees of destroyed depots to + * qcache-equipped vmem arenas. + */ +void +kmem_cache_reap_now(kmem_cache_t *cp __maybe_unused) +{ + ASSERT(list_link_active(&cp->cache_link)); + + kmem_reap(); +} + +/* + * Recompute a cache's magazine size. The trade-off is that larger magazines + * provide a higher transfer rate with the depot, while smaller magazines + * reduce memory consumption. Magazine resizing is an expensive operation; + * it should not be done frequently. + * + * Changes to the magazine size are serialized by the kmem_taskq lock. + * + * Note: at present this only grows the magazine size. It might be useful + * to allow shrinkage too. + */ +static void +kmem_cache_magazine_resize(kmem_cache_t *cp) +{ + kmem_magtype_t *mtp = cp->cache_magtype; + + ASSERT(taskq_member(kmem_taskq, curthread)); + + if (cp->cache_chunksize < mtp->mt_maxbuf) { + kmem_cache_magazine_purge(cp); + mutex_enter(&cp->cache_depot_lock); + cp->cache_magtype = ++mtp; + cp->cache_depot_contention_prev = + cp->cache_depot_contention + INT_MAX; + mutex_exit(&cp->cache_depot_lock); + kmem_cache_magazine_enable(cp); + } +} + +/* + * Rescale a cache's hash table, so that the table size is roughly the + * cache size. We want the average lookup time to be extremely small. + */ +static void +kmem_hash_rescale(kmem_cache_t *cp) +{ + kmem_bufctl_t **old_table, **new_table, *bcp; + size_t old_size, new_size, h; + + ASSERT(taskq_member(kmem_taskq, curthread)); + + new_size = MAX(KMEM_HASH_INITIAL, + 1 << (highbit(3 * cp->cache_buftotal + 4) - 2)); + old_size = cp->cache_hash_mask + 1; + + if ((old_size >> 1) <= new_size && new_size <= (old_size << 1)) + return; + + new_table = vmem_alloc_impl(kmem_hash_arena, new_size * sizeof (void *), + VM_NOSLEEP); + if (new_table == NULL) + return; + memset(new_table, 0, new_size * sizeof (void *)); + + mutex_enter(&cp->cache_lock); + + old_size = cp->cache_hash_mask + 1; + old_table = cp->cache_hash_table; + + cp->cache_hash_mask = new_size - 1; + cp->cache_hash_table = new_table; + cp->cache_rescale++; + + for (h = 0; h < old_size; h++) { + bcp = old_table[h]; + while (bcp != NULL) { + void *addr = bcp->bc_addr; + kmem_bufctl_t *next_bcp = bcp->bc_next; + kmem_bufctl_t **hash_bucket = KMEM_HASH(cp, addr); + bcp->bc_next = *hash_bucket; + *hash_bucket = bcp; + bcp = next_bcp; + } + } + + mutex_exit(&cp->cache_lock); + + vmem_free_impl(kmem_hash_arena, old_table, old_size * sizeof (void *)); +} + +/* + * Perform periodic maintenance on a cache: hash rescaling, depot working-set + * update, magazine resizing, and slab consolidation. + */ +static void +kmem_cache_update(kmem_cache_t *cp) +{ + int need_hash_rescale = 0; + int need_magazine_resize = 0; + + /* + * If the cache has become much larger or smaller than its hash table, + * fire off a request to rescale the hash table. + */ + mutex_enter(&cp->cache_lock); + + if ((cp->cache_flags & KMF_HASH) && + (cp->cache_buftotal > (cp->cache_hash_mask << 1) || + (cp->cache_buftotal < (cp->cache_hash_mask >> 1) && + cp->cache_hash_mask > KMEM_HASH_INITIAL))) + need_hash_rescale = 1; + + mutex_exit(&cp->cache_lock); + + /* + * Update the depot working set statistics. + */ + kmem_depot_ws_update(cp); + + /* + * If there's a lot of contention in the depot, + * increase the magazine size. + */ + mutex_enter(&cp->cache_depot_lock); + + if (cp->cache_chunksize < cp->cache_magtype->mt_maxbuf && + (int)(cp->cache_depot_contention - + cp->cache_depot_contention_prev) > kmem_depot_contention) + need_magazine_resize = 1; + + cp->cache_depot_contention_prev = cp->cache_depot_contention; + + mutex_exit(&cp->cache_depot_lock); + + if (need_hash_rescale) + (void) taskq_dispatch(kmem_taskq, + (task_func_t *)kmem_hash_rescale, cp, TQ_NOSLEEP); + + if (need_magazine_resize) + (void) taskq_dispatch(kmem_taskq, + (task_func_t *)kmem_cache_magazine_resize, + cp, TQ_NOSLEEP); + + // smd : the following if is only true for the dnode cache + if (cp->cache_defrag != NULL) + (void) taskq_dispatch(kmem_taskq, + (task_func_t *)kmem_cache_scan, cp, TQ_NOSLEEP); + +#ifdef DEBUG + else { + // for every other cache, duplicate some of the logic from + // kmem_cache_scan() below + // run reap occasionally even if there is plenty of memory + uint16_t debug_rand; + + (void) random_get_bytes((uint8_t *)&debug_rand, 2); + if (!kmem_move_noreap && + ((debug_rand % kmem_mtb_reap) == 0)) { + /* + * no mutex above, so no need to give it up as + * in kmem_cache_scan() + */ + } + } +#endif + +} + +static void kmem_update(void *); + +static void +kmem_update_timeout(void *dummy) +{ + (void) bsd_timeout(kmem_update, dummy, &kmem_reap_interval); +} + +static void +kmem_update(void *dummy) +{ + kmem_cache_applyall(kmem_cache_update, NULL, TQ_NOSLEEP); + + /* + * We use taskq_dispatch() to reschedule the timeout so that + * kmem_update() becomes self-throttling: it won't schedule + * new tasks until all previous tasks have completed. + */ + if (!taskq_dispatch(kmem_taskq, kmem_update_timeout, dummy, TQ_NOSLEEP)) + kmem_update_timeout(NULL); + +} + +static int +kmem_cache_kstat_update(kstat_t *ksp, int rw) +{ + struct kmem_cache_kstat *kmcp = &kmem_cache_kstat; + kmem_cache_t *cp = ksp->ks_private; + uint64_t cpu_buf_avail; + uint64_t buf_avail = 0; + int cpu_seqid; + long reap; + + if (rw == KSTAT_WRITE) + return (EACCES); + + mutex_enter(&cp->cache_lock); + + kmcp->kmc_alloc_fail.value.ui64 = cp->cache_alloc_fail; + kmcp->kmc_alloc.value.ui64 = cp->cache_slab_alloc; + kmcp->kmc_free.value.ui64 = cp->cache_slab_free; + kmcp->kmc_slab_alloc.value.ui64 = cp->cache_slab_alloc; + kmcp->kmc_slab_free.value.ui64 = cp->cache_slab_free; + kmcp->kmc_no_vba_success.value.ui64 = cp->no_vba_success; + kmcp->kmc_no_vba_fail.value.ui64 = cp->no_vba_fail; + kmcp->kmc_arc_no_grow_set.value.ui64 = cp->arc_no_grow_set; + kmcp->kmc_arc_no_grow.value.ui64 = cp->arc_no_grow; + + for (cpu_seqid = 0; cpu_seqid < max_ncpus; cpu_seqid++) { + kmem_cpu_cache_t *ccp = &cp->cache_cpu[cpu_seqid]; + + mutex_enter(&ccp->cc_lock); + + cpu_buf_avail = 0; + if (ccp->cc_rounds > 0) + cpu_buf_avail += ccp->cc_rounds; + if (ccp->cc_prounds > 0) + cpu_buf_avail += ccp->cc_prounds; + + kmcp->kmc_alloc.value.ui64 += ccp->cc_alloc; + kmcp->kmc_free.value.ui64 += ccp->cc_free; + buf_avail += cpu_buf_avail; + + mutex_exit(&ccp->cc_lock); + } + + mutex_enter(&cp->cache_depot_lock); + + kmcp->kmc_depot_alloc.value.ui64 = cp->cache_full.ml_alloc; + kmcp->kmc_depot_free.value.ui64 = cp->cache_empty.ml_alloc; + kmcp->kmc_depot_contention.value.ui64 = cp->cache_depot_contention; + kmcp->kmc_full_magazines.value.ui64 = cp->cache_full.ml_total; + kmcp->kmc_empty_magazines.value.ui64 = cp->cache_empty.ml_total; + kmcp->kmc_magazine_size.value.ui64 = + (cp->cache_flags & KMF_NOMAGAZINE) ? + 0 : cp->cache_magtype->mt_magsize; + + kmcp->kmc_alloc.value.ui64 += cp->cache_full.ml_alloc; + kmcp->kmc_free.value.ui64 += cp->cache_empty.ml_alloc; + buf_avail += cp->cache_full.ml_total * cp->cache_magtype->mt_magsize; + + reap = MIN(cp->cache_full.ml_reaplimit, cp->cache_full.ml_min); + reap = MIN(reap, cp->cache_full.ml_total); + + mutex_exit(&cp->cache_depot_lock); + + kmcp->kmc_buf_size.value.ui64 = cp->cache_bufsize; + kmcp->kmc_align.value.ui64 = cp->cache_align; + kmcp->kmc_chunk_size.value.ui64 = cp->cache_chunksize; + kmcp->kmc_slab_size.value.ui64 = cp->cache_slabsize; + kmcp->kmc_buf_constructed.value.ui64 = buf_avail; + buf_avail += cp->cache_bufslab; + kmcp->kmc_buf_avail.value.ui64 = buf_avail; + kmcp->kmc_buf_inuse.value.ui64 = cp->cache_buftotal - buf_avail; + kmcp->kmc_buf_total.value.ui64 = cp->cache_buftotal; + kmcp->kmc_buf_max.value.ui64 = cp->cache_bufmax; + kmcp->kmc_slab_create.value.ui64 = cp->cache_slab_create; + kmcp->kmc_slab_destroy.value.ui64 = cp->cache_slab_destroy; + kmcp->kmc_hash_size.value.ui64 = (cp->cache_flags & KMF_HASH) ? + cp->cache_hash_mask + 1 : 0; + kmcp->kmc_hash_lookup_depth.value.ui64 = cp->cache_lookup_depth; + kmcp->kmc_hash_rescale.value.ui64 = cp->cache_rescale; + kmcp->kmc_vmem_source.value.ui64 = cp->cache_arena->vm_id; + kmcp->kmc_reap.value.ui64 = cp->cache_reap; + + if (cp->cache_defrag == NULL) { + kmcp->kmc_move_callbacks.value.ui64 = 0; + kmcp->kmc_move_yes.value.ui64 = 0; + kmcp->kmc_move_no.value.ui64 = 0; + kmcp->kmc_move_later.value.ui64 = 0; + kmcp->kmc_move_dont_need.value.ui64 = 0; + kmcp->kmc_move_dont_know.value.ui64 = 0; + kmcp->kmc_move_hunt_found.value.ui64 = 0; + kmcp->kmc_move_slabs_freed.value.ui64 = 0; + kmcp->kmc_defrag.value.ui64 = 0; + kmcp->kmc_scan.value.ui64 = 0; + kmcp->kmc_move_reclaimable.value.ui64 = 0; + } else { + int64_t reclaimable; + + kmem_defrag_t *kd = cp->cache_defrag; + kmcp->kmc_move_callbacks.value.ui64 = kd->kmd_callbacks; + kmcp->kmc_move_yes.value.ui64 = kd->kmd_yes; + kmcp->kmc_move_no.value.ui64 = kd->kmd_no; + kmcp->kmc_move_later.value.ui64 = kd->kmd_later; + kmcp->kmc_move_dont_need.value.ui64 = kd->kmd_dont_need; + kmcp->kmc_move_dont_know.value.ui64 = kd->kmd_dont_know; + kmcp->kmc_move_hunt_found.value.ui64 = 0; + kmcp->kmc_move_slabs_freed.value.ui64 = kd->kmd_slabs_freed; + kmcp->kmc_defrag.value.ui64 = kd->kmd_defrags; + kmcp->kmc_scan.value.ui64 = kd->kmd_scans; + + reclaimable = cp->cache_bufslab - (cp->cache_maxchunks - 1); + reclaimable = MAX(reclaimable, 0); + reclaimable += ((uint64_t)reap * cp->cache_magtype->mt_magsize); + kmcp->kmc_move_reclaimable.value.ui64 = reclaimable; + } + + mutex_exit(&cp->cache_lock); + return (0); +} + +/* + * Return a named statistic about a particular cache. + * This shouldn't be called very often, so it's currently designed for + * simplicity (leverages existing kstat support) rather than efficiency. + */ +uint64_t +kmem_cache_stat(kmem_cache_t *cp, char *name) +{ + int i; + kstat_t *ksp = cp->cache_kstat; + kstat_named_t *knp = (kstat_named_t *)&kmem_cache_kstat; + uint64_t value = 0; + + if (ksp != NULL) { + mutex_enter(&kmem_cache_kstat_lock); + (void) kmem_cache_kstat_update(ksp, KSTAT_READ); + for (i = 0; i < ksp->ks_ndata; i++) { + if (strcmp(knp[i].name, name) == 0) { + value = knp[i].value.ui64; + break; + } + } + mutex_exit(&kmem_cache_kstat_lock); + } + return (value); +} + +// TRUE if we have more than a critical minimum of memory +// used in arc_memory_throttle; if FALSE, we throttle +static inline bool +spl_minimal_physmem_p_logic() +{ + // do we have enough memory to avoid throttling? + if (spl_vm_pages_wanted > 0 || + (spl_vm_pressure_level > 0 && + spl_vm_pressure_level != MAGIC_PRESSURE_UNAVAILABLE)) + return (false); + // XXX : check reclaiming load? + return (true); +} + +int32_t +spl_minimal_physmem_p(void) +{ + + // arc will throttle throttle if we are paging, otherwise + // we want a small bit of pressure here so that we can compete + // a little with the xnu buffer cache + + return (spl_minimal_physmem_p_logic() && spl_free > -4096LL); +} + +/* + * Return the maximum amount of memory that is (in theory) allocatable + * from the heap. This may be used as an estimate only since there + * is no guarentee this space will still be available when an allocation + * request is made, nor that the space may be allocated in one big request + * due to kernel heap fragmentation. + */ +size_t +kmem_maxavail(void) +{ + return (total_memory); +} + +/* + * Indicate whether memory-intensive kmem debugging is enabled. + */ +int +kmem_debugging(void) +{ + return (kmem_flags & (KMF_AUDIT | KMF_REDZONE)); +} + +/* binning function, sorts finely at the two extremes */ +#define KMEM_PARTIAL_SLAB_WEIGHT(sp, binshift) \ +((((sp)->slab_refcnt <= (binshift)) || \ +(((sp)->slab_chunks - (sp)->slab_refcnt) <= (binshift))) \ +? -(sp)->slab_refcnt \ +: -((binshift) + ((sp)->slab_refcnt >> (binshift)))) + +/* + * Minimizing the number of partial slabs on the freelist minimizes + * fragmentation (the ratio of unused buffers held by the slab layer). There are + * two ways to get a slab off of the freelist: 1) free all the buffers on the + * slab, and 2) allocate all the buffers on the slab. It follows that we want + * the most-used slabs at the front of the list where they have the best chance + * of being completely allocated, and the least-used slabs at a safe distance + * from the front to improve the odds that the few remaining buffers will all be + * freed before another allocation can tie up the slab. For that reason a slab + * with a higher slab_refcnt sorts less than than a slab with a lower + * slab_refcnt. + * + * However, if a slab has at least one buffer that is deemed unfreeable, we + * would rather have that slab at the front of the list regardless of + * slab_refcnt, since even one unfreeable buffer makes the entire slab + * unfreeable. If the client returns KMEM_CBRC_NO in response to a cache_move() + * callback, the slab is marked unfreeable for as long as it remains on the + * freelist. + */ +static int +kmem_partial_slab_cmp(const void *pp0, const void *pp1) +{ + const kmem_cache_t *cp; + const kmem_slab_t *s0 = pp0; + const kmem_slab_t *s1 = pp1; + int w0, w1; + size_t binshift; + + ASSERT(KMEM_SLAB_IS_PARTIAL(s0)); + ASSERT(KMEM_SLAB_IS_PARTIAL(s1)); + ASSERT(s0->slab_cache == s1->slab_cache); + cp = s1->slab_cache; + ASSERT(MUTEX_HELD((struct kmutex *)&cp->cache_lock)); + binshift = cp->cache_partial_binshift; + + /* weight of first slab */ + w0 = KMEM_PARTIAL_SLAB_WEIGHT(s0, binshift); + if (s0->slab_flags & KMEM_SLAB_NOMOVE) { + w0 -= cp->cache_maxchunks; + } + + /* weight of second slab */ + w1 = KMEM_PARTIAL_SLAB_WEIGHT(s1, binshift); + if (s1->slab_flags & KMEM_SLAB_NOMOVE) { + w1 -= cp->cache_maxchunks; + } + + if (w0 < w1) + return (-1); + if (w0 > w1) + return (1); + + // compare slab age if available + hrtime_t c0 = s0->slab_create_time, c1 = s1->slab_create_time; + if (c0 != 0 && c1 != 0 && c0 != c1) { + // higher time is newer; newer sorts before older + if (c0 < c1) // c0 is older than c1 + return (1); // so c0 sorts after c1 + if (c0 > c1) + return (-1); + } + + /* compare pointer values */ + if ((uintptr_t)s0 < (uintptr_t)s1) + return (-1); + if ((uintptr_t)s0 > (uintptr_t)s1) + return (1); + + return (0); +} + +/* + * It must be valid to call the destructor (if any) on a newly created object. + * That is, the constructor (if any) must leave the object in a valid state for + * the destructor. + */ +kmem_cache_t * +kmem_cache_create( + char *name, /* descriptive name for this cache */ + size_t bufsize, /* size of the objects it manages */ + size_t align, /* required object alignment */ + int (*constructor)(void *, void *, int), /* object constructor */ + void (*destructor)(void *, void *), /* object destructor */ + void (*reclaim)(void *), /* memory reclaim callback */ + void *private, /* pass-thru arg for constr/destr/reclaim */ + vmem_t *vmp, /* vmem source for slab allocation */ + int cflags) /* cache creation flags */ +{ + int cpu_seqid; + size_t chunksize; + kmem_cache_t *cp; + kmem_magtype_t *mtp; + size_t csize = KMEM_CACHE_SIZE(max_ncpus); + +#ifdef DEBUG + /* + * Cache names should conform to the rules for valid C identifiers + */ + if (!strident_valid(name)) { + cmn_err(CE_CONT, + "kmem_cache_create: '%s' is an invalid cache name\n" + "cache names must conform to the rules for " + "C identifiers\n", name); + } +#endif /* DEBUG */ + + if (vmp == NULL) + vmp = kmem_default_arena; + + /* + * If this kmem cache has an identifier vmem arena as its source, mark + * it such to allow kmem_reap_idspace(). + */ + ASSERT(!(cflags & KMC_IDENTIFIER)); /* consumer should not set this */ + if (vmp->vm_cflags & VMC_IDENTIFIER) + cflags |= KMC_IDENTIFIER; + + /* + * Get a kmem_cache structure. We arrange that cp->cache_cpu[] + * is aligned on a KMEM_CPU_CACHE_SIZE boundary to prevent + * false sharing of per-CPU data. + */ + cp = vmem_xalloc(kmem_cache_arena, csize, + KMEM_CPU_CACHE_SIZE, + P2NPHASE(csize, KMEM_CPU_CACHE_SIZE), + 0, NULL, NULL, VM_SLEEP); + memset(cp, 0, csize); + list_link_init(&cp->cache_link); + + if (align == 0) + align = KMEM_ALIGN; + + /* + * If we're not at least KMEM_ALIGN aligned, we can't use free + * memory to hold bufctl information (because we can't safely + * perform word loads and stores on it). + */ + if (align < KMEM_ALIGN) + cflags |= KMC_NOTOUCH; + + if ((align & (align - 1)) != 0 || align > vmp->vm_quantum) + panic("kmem_cache_create: bad alignment %lu", align); + + mutex_enter(&kmem_flags_lock); + if (kmem_flags & KMF_RANDOMIZE) + kmem_flags = (((kmem_flags | ~KMF_RANDOM) + 1) & KMF_RANDOM) | + KMF_RANDOMIZE; + cp->cache_flags = (kmem_flags | cflags) & KMF_DEBUG; + mutex_exit(&kmem_flags_lock); + + /* + * Make sure all the various flags are reasonable. + */ + ASSERT(!(cflags & KMC_NOHASH) || !(cflags & KMC_NOTOUCH)); + + if (cp->cache_flags & KMF_LITE) { + if (bufsize >= kmem_lite_minsize && + align <= kmem_lite_maxalign && + P2PHASE(bufsize, kmem_lite_maxalign) != 0) { + cp->cache_flags |= KMF_BUFTAG; + cp->cache_flags &= ~(KMF_AUDIT | KMF_FIREWALL); + } else { + cp->cache_flags &= ~KMF_DEBUG; + } + } + + if (cp->cache_flags & KMF_DEADBEEF) + cp->cache_flags |= KMF_REDZONE; + + if ((cflags & KMC_QCACHE) && (cp->cache_flags & KMF_AUDIT)) + cp->cache_flags |= KMF_NOMAGAZINE; + + if (cflags & KMC_NODEBUG) + cp->cache_flags &= ~KMF_DEBUG; + + if (cflags & KMC_NOTOUCH) + cp->cache_flags &= ~KMF_TOUCH; + + if (cflags & KMC_PREFILL) + cp->cache_flags |= KMF_PREFILL; + + if (cflags & KMC_NOHASH) + cp->cache_flags &= ~(KMF_AUDIT | KMF_FIREWALL); + + if (cflags & KMC_NOMAGAZINE) + cp->cache_flags |= KMF_NOMAGAZINE; + + if ((cp->cache_flags & KMF_AUDIT) && !(cflags & KMC_NOTOUCH)) + cp->cache_flags |= KMF_REDZONE; + + if (!(cp->cache_flags & KMF_AUDIT)) + cp->cache_flags &= ~KMF_CONTENTS; + + if ((cp->cache_flags & KMF_BUFTAG) && bufsize >= kmem_minfirewall && + !(cp->cache_flags & KMF_LITE) && !(cflags & KMC_NOHASH)) + cp->cache_flags |= KMF_FIREWALL; + + if (vmp != kmem_default_arena || kmem_firewall_arena == NULL) + cp->cache_flags &= ~KMF_FIREWALL; + + if (cp->cache_flags & KMF_FIREWALL) { + cp->cache_flags &= ~KMF_BUFTAG; + cp->cache_flags |= KMF_NOMAGAZINE; + ASSERT(vmp == kmem_default_arena); + vmp = kmem_firewall_arena; + } + + /* + * Set cache properties. + */ + (void) strncpy(cp->cache_name, name, KMEM_CACHE_NAMELEN); + strident_canon(cp->cache_name, KMEM_CACHE_NAMELEN + 1); + cp->cache_bufsize = bufsize; + cp->cache_align = align; + cp->cache_constructor = constructor; + cp->cache_destructor = destructor; + cp->cache_reclaim = reclaim; + cp->cache_private = private; + cp->cache_arena = vmp; + cp->cache_cflags = cflags; + + /* + * Determine the chunk size. + */ + chunksize = bufsize; + + if (align >= KMEM_ALIGN) { + chunksize = P2ROUNDUP(chunksize, KMEM_ALIGN); + cp->cache_bufctl = chunksize - KMEM_ALIGN; + } + + if (cp->cache_flags & KMF_BUFTAG) { + cp->cache_bufctl = chunksize; + cp->cache_buftag = chunksize; + if (cp->cache_flags & KMF_LITE) + chunksize += KMEM_BUFTAG_LITE_SIZE(kmem_lite_count); + else + chunksize += sizeof (kmem_buftag_t); + } + + if (cp->cache_flags & KMF_DEADBEEF) { + cp->cache_verify = MIN(cp->cache_buftag, kmem_maxverify); + if (cp->cache_flags & KMF_LITE) + cp->cache_verify = sizeof (uint64_t); + } + + cp->cache_contents = MIN(cp->cache_bufctl, kmem_content_maxsave); + + cp->cache_chunksize = chunksize = P2ROUNDUP(chunksize, align); + + /* + * Now that we know the chunk size, determine the optimal slab size. + */ + + size_t vquantum = vmp->vm_quantum; + + if ((cflags & KMC_ARENA_SLAB) == KMC_ARENA_SLAB) { + VERIFY3U((vmp->vm_cflags & VMC_NO_QCACHE), ==, VMC_NO_QCACHE); + VERIFY3U(vmp->vm_min_import, >, 0); + VERIFY3U(vmp->vm_min_import, >=, (2 * vmp->vm_quantum)); + VERIFY(ISP2(vmp->vm_min_import)); + vquantum = vmp->vm_min_import >> 1; + } + + if (vmp == kmem_firewall_arena) { + cp->cache_slabsize = P2ROUNDUP(chunksize, vquantum); + cp->cache_mincolor = cp->cache_slabsize - chunksize; + cp->cache_maxcolor = cp->cache_mincolor; + cp->cache_flags |= KMF_HASH; + ASSERT(!(cp->cache_flags & KMF_BUFTAG)); + } else if ((cflags & KMC_NOHASH) || (!(cflags & KMC_NOTOUCH) && + !(cp->cache_flags & KMF_AUDIT) && + chunksize < vquantum / + KMEM_VOID_FRACTION)) { + cp->cache_slabsize = vquantum; + cp->cache_mincolor = 0; + cp->cache_maxcolor = + (cp->cache_slabsize - sizeof (kmem_slab_t)) % chunksize; + ASSERT(chunksize + sizeof (kmem_slab_t) <= cp->cache_slabsize); + ASSERT(!(cp->cache_flags & KMF_AUDIT)); + } else { + size_t chunks, bestfit = 0, waste, slabsize; + size_t minwaste = LONG_MAX; + + for (chunks = 1; chunks <= KMEM_VOID_FRACTION; chunks++) { + slabsize = P2ROUNDUP(chunksize * chunks, + vquantum); + chunks = slabsize / chunksize; + waste = (slabsize % chunksize) / chunks; + if (waste < minwaste) { + minwaste = waste; + bestfit = slabsize; + } + } + if (cflags & KMC_QCACHE) + bestfit = VMEM_QCACHE_SLABSIZE(vmp->vm_qcache_max); + cp->cache_slabsize = bestfit; + cp->cache_mincolor = 0; + cp->cache_maxcolor = bestfit % chunksize; + cp->cache_flags |= KMF_HASH; + } + + cp->cache_maxchunks = (cp->cache_slabsize / cp->cache_chunksize); + cp->cache_partial_binshift = highbit(cp->cache_maxchunks / 16) + 1; + + /* + * Disallowing prefill when either the DEBUG or HASH flag is set or when + * there is a constructor avoids some tricky issues with debug setup + * that may be revisited later. We cannot allow prefill in a + * metadata cache because of potential recursion. + */ + if (vmp == kmem_msb_arena || + cp->cache_flags & (KMF_HASH | KMF_BUFTAG) || + cp->cache_constructor != NULL) + cp->cache_flags &= ~KMF_PREFILL; + + if (cp->cache_flags & KMF_HASH) { + ASSERT(!(cflags & KMC_NOHASH)); + cp->cache_bufctl_cache = (cp->cache_flags & KMF_AUDIT) ? + kmem_bufctl_audit_cache : kmem_bufctl_cache; + } + + if (cp->cache_maxcolor >= vquantum) + cp->cache_maxcolor = vquantum - 1; + + cp->cache_color = cp->cache_mincolor; + + mutex_init(&cp->cache_reap_lock, NULL, MUTEX_DEFAULT, NULL); + + /* + * Initialize the rest of the slab layer. + */ + mutex_init(&cp->cache_lock, NULL, MUTEX_DEFAULT, NULL); + + avl_create(&cp->cache_partial_slabs, kmem_partial_slab_cmp, + sizeof (kmem_slab_t), offsetof(kmem_slab_t, slab_link)); + /* LINTED: E_TRUE_LOGICAL_EXPR */ + ASSERT(sizeof (list_node_t) <= sizeof (avl_node_t)); + /* reuse partial slab AVL linkage for complete slab list linkage */ + list_create(&cp->cache_complete_slabs, + sizeof (kmem_slab_t), offsetof(kmem_slab_t, slab_link)); + + if (cp->cache_flags & KMF_HASH) { + cp->cache_hash_table = vmem_alloc_impl(kmem_hash_arena, + KMEM_HASH_INITIAL * sizeof (void *), + VM_SLEEP); + memset(cp->cache_hash_table, 0, + KMEM_HASH_INITIAL * sizeof (void *)); + cp->cache_hash_mask = KMEM_HASH_INITIAL - 1; + cp->cache_hash_shift = highbit((ulong_t)chunksize) - 1; + } + + /* + * Initialize the depot. + */ + mutex_init(&cp->cache_depot_lock, NULL, MUTEX_DEFAULT, NULL); + + for (mtp = kmem_magtype; chunksize <= mtp->mt_minbuf; mtp++) + continue; + + cp->cache_magtype = mtp; + + /* + * Initialize the CPU layer. + */ + for (cpu_seqid = 0; cpu_seqid < max_ncpus; cpu_seqid++) { + kmem_cpu_cache_t *ccp = &cp->cache_cpu[cpu_seqid]; + mutex_init(&ccp->cc_lock, NULL, MUTEX_DEFAULT, NULL); // XNU + ccp->cc_flags = cp->cache_flags; + ccp->cc_rounds = -1; + ccp->cc_prounds = -1; + } + + /* + * Create the cache's kstats. + */ + if ((cp->cache_kstat = kstat_create("unix", 0, cp->cache_name, + "kmem_cache", KSTAT_TYPE_NAMED, + sizeof (kmem_cache_kstat) / sizeof (kstat_named_t), + KSTAT_FLAG_VIRTUAL)) != NULL) { + cp->cache_kstat->ks_data = &kmem_cache_kstat; + cp->cache_kstat->ks_update = kmem_cache_kstat_update; + cp->cache_kstat->ks_private = cp; + cp->cache_kstat->ks_lock = &kmem_cache_kstat_lock; + kstat_install(cp->cache_kstat); + } + + /* + * Add the cache to the global list. This makes it visible + * to kmem_update(), so the cache must be ready for business. + */ + mutex_enter(&kmem_cache_lock); + list_insert_tail(&kmem_caches, cp); + mutex_exit(&kmem_cache_lock); + + if (kmem_ready) + kmem_cache_magazine_enable(cp); + + return (cp); +} + +static int +kmem_move_cmp(const void *buf, const void *p) +{ + const kmem_move_t *kmm = p; + uintptr_t v1 = (uintptr_t)buf; + uintptr_t v2 = (uintptr_t)kmm->kmm_from_buf; + return (v1 < v2 ? -1 : (v1 > v2 ? 1 : 0)); +} + +static void +kmem_reset_reclaim_threshold(kmem_defrag_t *kmd) +{ + kmd->kmd_reclaim_numer = 1; +} + +/* + * Initially, when choosing candidate slabs for buffers to move, we want to be + * very selective and take only slabs that are less than + * (1 / KMEM_VOID_FRACTION) allocated. If we have difficulty finding candidate + * slabs, then we raise the allocation ceiling incrementally. The reclaim + * threshold is reset to (1 / KMEM_VOID_FRACTION) as soon as the cache is no + * longer fragmented. + */ +static void +kmem_adjust_reclaim_threshold(kmem_defrag_t *kmd, int direction) +{ + if (direction > 0) { + /* make it easier to find a candidate slab */ + if (kmd->kmd_reclaim_numer < (KMEM_VOID_FRACTION - 1)) { + kmd->kmd_reclaim_numer++; + } + } else { + /* be more selective */ + if (kmd->kmd_reclaim_numer > 1) { + kmd->kmd_reclaim_numer--; + } + } +} + +uint64_t +spl_kmem_cache_inuse(kmem_cache_t *cache) +{ + return (cache->cache_buftotal); +} + +uint64_t +spl_kmem_cache_entry_size(kmem_cache_t *cache) +{ + return (cache->cache_bufsize); +} + +void +kmem_cache_set_move(kmem_cache_t *cp, + kmem_cbrc_t (*move)(void *, void *, size_t, void *)) +{ + kmem_defrag_t *defrag; + + ASSERT(move != NULL); + /* + * The consolidator does not support NOTOUCH caches because kmem cannot + * initialize their slabs with the 0xbaddcafe memory pattern, which sets + * a low order bit usable by clients to distinguish uninitialized memory + * from known objects (see kmem_slab_create). + */ + ASSERT(!(cp->cache_cflags & KMC_NOTOUCH)); + ASSERT(!(cp->cache_cflags & KMC_IDENTIFIER)); + + /* + * We should not be holding anyone's cache lock when calling + * kmem_cache_alloc(), so allocate in all cases before acquiring the + * lock. + */ + defrag = kmem_cache_alloc(kmem_defrag_cache, KM_SLEEP); + + mutex_enter(&cp->cache_lock); + + if (KMEM_IS_MOVABLE(cp)) { + if (cp->cache_move == NULL) { + ASSERT(cp->cache_slab_alloc == 0); + + cp->cache_defrag = defrag; + defrag = NULL; /* nothing to free */ + memset(cp->cache_defrag, 0, sizeof (kmem_defrag_t)); + avl_create(&cp->cache_defrag->kmd_moves_pending, + kmem_move_cmp, sizeof (kmem_move_t), + offsetof(kmem_move_t, kmm_entry)); + /* LINTED: E_TRUE_LOGICAL_EXPR */ + ASSERT(sizeof (list_node_t) <= sizeof (avl_node_t)); + /* reuse the slab's AVL linkage for deadlist linkage */ + list_create(&cp->cache_defrag->kmd_deadlist, + sizeof (kmem_slab_t), + offsetof(kmem_slab_t, slab_link)); + kmem_reset_reclaim_threshold(cp->cache_defrag); + } + cp->cache_move = move; + } + + mutex_exit(&cp->cache_lock); + + if (defrag != NULL) { + kmem_cache_free(kmem_defrag_cache, defrag); /* unused */ + } +} + +void +kmem_qcache_destroy() +{ + kmem_cache_t *cp; + kmem_cache_t *cache_to_destroy = NULL; + + do { + cache_to_destroy = NULL; + mutex_enter(&kmem_cache_lock); + for (cp = list_head(&kmem_caches); cp != NULL; + cp = list_next(&kmem_caches, cp)) { + if (cp->cache_cflags & KMC_QCACHE) { + cache_to_destroy = cp; + break; + } + } + mutex_exit(&kmem_cache_lock); + + if (cache_to_destroy) { + kmem_cache_destroy(cache_to_destroy); + } + } while (cache_to_destroy); +} + +void +kmem_cache_destroy(kmem_cache_t *cp) +{ + int cpu_seqid; + + /* + * Remove the cache from the global cache list so that no one else + * can schedule tasks on its behalf, wait for any pending tasks to + * complete, purge the cache, and then destroy it. + */ + mutex_enter(&kmem_cache_lock); + list_remove(&kmem_caches, cp); + mutex_exit(&kmem_cache_lock); + + if (kmem_taskq != NULL) + taskq_wait(kmem_taskq); + + if (kmem_move_taskq != NULL && cp->cache_defrag != NULL) + taskq_wait(kmem_move_taskq); + + kmem_cache_magazine_purge(cp); + + /* + * make sure there isn't a reaper + * since it would dereference cp + */ + mutex_enter(&cp->cache_reap_lock); + mutex_exit(&cp->cache_reap_lock); + + mutex_enter(&cp->cache_lock); + + if (cp->cache_buftotal != 0) + cmn_err(CE_WARN, "kmem_cache_destroy: '%s' (%p) not empty", + cp->cache_name, (void *)cp); + if (cp->cache_defrag != NULL) { + avl_destroy(&cp->cache_defrag->kmd_moves_pending); + list_destroy(&cp->cache_defrag->kmd_deadlist); + kmem_cache_free(kmem_defrag_cache, cp->cache_defrag); + cp->cache_defrag = NULL; + } + /* + * The cache is now dead. There should be no further activity. We + * enforce this by setting land mines in the constructor, destructor, + * reclaim, and move routines that induce a kernel text fault if + * invoked. + */ +#if defined(__aarch64__) + /* Setting landmines causes Break 0xC470: Ptrauth failure */ + cp->cache_constructor = (int (*)(void *, void *, int))NULL; + cp->cache_destructor = (void (*)(void *, void *))NULL; + cp->cache_reclaim = (void (*)(void *))NULL; + cp->cache_move = (kmem_cbrc_t (*)(void *, void *, size_t, void *))NULL; +#else + cp->cache_constructor = (int (*)(void *, void *, int))1; + cp->cache_destructor = (void (*)(void *, void *))2; + cp->cache_reclaim = (void (*)(void *))3; + cp->cache_move = (kmem_cbrc_t (*)(void *, void *, size_t, void *))4; +#endif + mutex_exit(&cp->cache_lock); + + kstat_delete(cp->cache_kstat); + + if (cp->cache_hash_table != NULL) + vmem_free_impl(kmem_hash_arena, cp->cache_hash_table, + (cp->cache_hash_mask + 1) * sizeof (void *)); + + for (cpu_seqid = 0; cpu_seqid < max_ncpus; cpu_seqid++) + mutex_destroy(&cp->cache_cpu[cpu_seqid].cc_lock); + + mutex_destroy(&cp->cache_depot_lock); + mutex_destroy(&cp->cache_lock); + mutex_destroy(&cp->cache_reap_lock); + + vmem_free_impl(kmem_cache_arena, cp, KMEM_CACHE_SIZE(max_ncpus)); +} + +static void +kmem_alloc_caches_create(const int *array, size_t count, + kmem_cache_t **alloc_table, size_t maxbuf, + uint_t shift) +{ + char name[KMEM_CACHE_NAMELEN + 1]; + size_t table_unit = (1 << shift); /* range of one alloc_table entry */ + size_t size = table_unit; + int i; + + for (i = 0; i < count; i++) { + size_t cache_size = array[i]; + size_t align = KMEM_ALIGN; + kmem_cache_t *cp; + + /* if the table has an entry for maxbuf, we're done */ + if (size > maxbuf) + break; + + /* cache size must be a multiple of the table unit */ + ASSERT(P2PHASE(cache_size, table_unit) == 0); + + /* + * If they allocate a multiple of the coherency granularity, + * they get a coherency-granularity-aligned address. + */ + if (IS_P2ALIGNED(cache_size, 64)) + align = 64; + if (IS_P2ALIGNED(cache_size, PAGESIZE)) + align = PAGESIZE; + (void) snprintf(name, sizeof (name), + "kmem_alloc_%lu", cache_size); + cp = kmem_cache_create(name, cache_size, align, + NULL, NULL, NULL, NULL, NULL, KMC_KMEM_ALLOC | KMF_HASH); + + while (size <= cache_size) { + alloc_table[(size - 1) >> shift] = cp; + size += table_unit; + } + } + + ASSERT(size > maxbuf); /* i.e. maxbuf <= max(cache_size) */ +} + +static void +kmem_alloc_caches_destroy() +{ + kmem_cache_t *cache_to_destroy = NULL; + kmem_cache_t *cp = NULL; + + do { + cache_to_destroy = NULL; + + // Locate the first cache that has the KMC_KMEM_ALLOC flag. + mutex_enter(&kmem_cache_lock); + + for (cp = list_head(&kmem_caches); cp != NULL; + cp = list_next(&kmem_caches, cp)) { + if (cp->cache_cflags & KMC_KMEM_ALLOC) { + cache_to_destroy = cp; + break; + } + } + + mutex_exit(&kmem_cache_lock); + + // Destroy the cache + if (cache_to_destroy) { + kmem_cache_destroy(cache_to_destroy); + } + + } while (cache_to_destroy); +} + +static void +kmem_destroy_cache_by_name(const char *substr) +{ + kmem_cache_t *cache_to_destroy = NULL; + kmem_cache_t *cp = NULL; + + do { + cache_to_destroy = NULL; + + // Locate the first cache that has the KMC_KMEM_ALLOC flag. + mutex_enter(&kmem_cache_lock); + + for (cp = list_head(&kmem_caches); cp != NULL; + cp = list_next(&kmem_caches, cp)) { + if (kmem_strstr(cp->cache_name, substr)) { + cache_to_destroy = cp; + break; + } + } + + mutex_exit(&kmem_cache_lock); + + // Destroy the cache + if (cache_to_destroy) { + kmem_cache_destroy(cache_to_destroy); + } + + } while (cache_to_destroy); +} + +static void +kmem_cache_init(int pass, int use_large_pages) +{ + int i; + size_t maxbuf; + kmem_magtype_t *mtp; + + for (i = 0; i < sizeof (kmem_magtype) / sizeof (*mtp); i++) { + char name[KMEM_CACHE_NAMELEN + 1]; + + mtp = &kmem_magtype[i]; + (void) snprintf(name, KMEM_CACHE_NAMELEN, "%s%d", + KMEM_MAGAZINE_PREFIX, + mtp->mt_magsize); + mtp->mt_cache = kmem_cache_create( + name, + (mtp->mt_magsize + 1) * sizeof (void *), + mtp->mt_align, NULL, NULL, NULL, NULL, + kmem_msb_arena, KMC_NOHASH); + } + + kmem_slab_cache = kmem_cache_create("kmem_slab_cache", + sizeof (kmem_slab_t), 0, NULL, NULL, + NULL, NULL, + kmem_msb_arena, KMC_NOHASH); + + kmem_bufctl_cache = kmem_cache_create("kmem_bufctl_cache", + sizeof (kmem_bufctl_t), 0, + NULL, NULL, NULL, NULL, + kmem_msb_arena, KMC_NOHASH); + + kmem_bufctl_audit_cache = kmem_cache_create("kmem_bufctl_audit_cache", + sizeof (kmem_bufctl_audit_t), + 0, NULL, NULL, NULL, NULL, + kmem_msb_arena, KMC_NOHASH); + + if (pass == 2) { + kmem_va_arena = vmem_create(KMEM_VA_PREFIX, + NULL, 0, PAGESIZE, + vmem_alloc_impl, vmem_free_impl, heap_arena, + 2 * PAGESIZE, VM_SLEEP); + + kmem_default_arena = vmem_create("kmem_default", + NULL, 0, PAGESIZE, + vmem_alloc_impl, vmem_free_impl, kmem_va_arena, + 0, VMC_DUMPSAFE | VM_SLEEP); + + /* Figure out what our maximum cache size is */ + maxbuf = kmem_max_cached; + if (maxbuf <= KMEM_MAXBUF) { + maxbuf = 0; + kmem_max_cached = KMEM_MAXBUF; + } else { + size_t size = 0; + size_t max = + sizeof (kmem_big_alloc_sizes) / sizeof (int); + /* + * Round maxbuf up to an existing cache size. If maxbuf + * is larger than the largest cache, we truncate it to + * the largest cache's size. + */ + for (i = 0; i < max; i++) { + size = kmem_big_alloc_sizes[i]; + if (maxbuf <= size) + break; + } + kmem_max_cached = maxbuf = size; + } + + /* + * The big alloc table may not be completely overwritten, so + * we clear out any stale cache pointers from the first pass. + */ + memset(kmem_big_alloc_table, 0, sizeof (kmem_big_alloc_table)); + } else { + /* + * During the first pass, the kmem_alloc_* caches + * are treated as metadata. + */ + kmem_default_arena = kmem_msb_arena; + maxbuf = KMEM_BIG_MAXBUF_32BIT; + } + + /* + * Set up the default caches to back kmem_alloc() + */ + kmem_alloc_caches_create( + kmem_alloc_sizes, sizeof (kmem_alloc_sizes) / sizeof (int), + kmem_alloc_table, KMEM_MAXBUF, KMEM_ALIGN_SHIFT); + + kmem_alloc_caches_create( + kmem_big_alloc_sizes, sizeof (kmem_big_alloc_sizes) / sizeof (int), + kmem_big_alloc_table, maxbuf, KMEM_BIG_SHIFT); + + kmem_big_alloc_table_max = maxbuf >> KMEM_BIG_SHIFT; +} + +/* + * At kext unload, kmem_cache_build_slablist() builds a list of free slabs + * from all kmem caches, so kmem_cache_fini() can report the leaks and the + * total number of leaks. + */ + +struct free_slab { + char vm_name[VMEM_NAMELEN]; + char cache_name[KMEM_CACHE_NAMELEN + 1]; + vmem_t *vmp; + size_t slabsize; + void *slab; + list_node_t next; +}; + +static list_t freelist; + +void +kmem_cache_build_slablist(kmem_cache_t *cp) +{ + int cpu_seqid; + + vmem_t *vmp = cp->cache_arena; + kmem_slab_t *sp; + struct free_slab *fs; + + for (sp = list_head(&cp->cache_complete_slabs); sp != NULL; + sp = list_next(&cp->cache_complete_slabs, sp)) { + fs = IOMallocType(struct free_slab); + memset(fs, '\0', sizeof (struct free_slab)); + strlcpy(fs->vm_name, vmp->vm_name, VMEM_NAMELEN); + strlcpy(fs->cache_name, cp->cache_name, + KMEM_CACHE_NAMELEN); + fs->vmp = vmp; + fs->slabsize = cp->cache_slabsize; + fs->slab = (void *)P2ALIGN((uintptr_t)sp->slab_base, + vmp->vm_quantum); + list_link_init(&fs->next); + list_insert_tail(&freelist, fs); + } + + for (sp = avl_first(&cp->cache_partial_slabs); sp != NULL; + sp = AVL_NEXT(&cp->cache_partial_slabs, sp)) { + + fs = IOMallocType(struct free_slab); + memset(fs, '\0', sizeof (struct free_slab)); + strlcpy(fs->vm_name, vmp->vm_name, VMEM_NAMELEN); + strlcpy(fs->cache_name, cp->cache_name, + KMEM_CACHE_NAMELEN); + fs->vmp = vmp; + fs->slabsize = cp->cache_slabsize; + fs->slab = (void *)P2ALIGN((uintptr_t)sp->slab_base, + vmp->vm_quantum); + list_link_init(&fs->next); + list_insert_tail(&freelist, fs); + } + + kstat_delete(cp->cache_kstat); + + if (cp->cache_hash_table != NULL) + vmem_free_impl(kmem_hash_arena, cp->cache_hash_table, + (cp->cache_hash_mask + 1) * sizeof (void *)); + + for (cpu_seqid = 0; cpu_seqid < max_ncpus; cpu_seqid++) + mutex_destroy(&cp->cache_cpu[cpu_seqid].cc_lock); + + mutex_destroy(&cp->cache_depot_lock); + mutex_destroy(&cp->cache_lock); + + vmem_free_impl(kmem_cache_arena, cp, KMEM_CACHE_SIZE(max_ncpus)); +} + + +static void +kmem_cache_fini() +{ + kmem_cache_t *cp; + int i; + struct free_slab *fs; + + list_create(&freelist, sizeof (struct free_slab), + offsetof(struct free_slab, next)); + + mutex_enter(&kmem_cache_lock); + + while ((cp = list_head(&kmem_caches))) { + list_remove(&kmem_caches, cp); + mutex_exit(&kmem_cache_lock); + kmem_cache_build_slablist(cp); + mutex_enter(&kmem_cache_lock); + } + + mutex_exit(&kmem_cache_lock); + + i = 0; + while ((fs = list_head(&freelist))) { + i++; + printf("SPL: %s:%d: released %lu from '%s' to '%s'\n", + __func__, __LINE__, + fs->slabsize, + fs->cache_name, + fs->vm_name); + list_remove(&freelist, fs); + vmem_free_impl(fs->vmp, fs->slab, fs->slabsize); + IOFreeType(fs, struct free_slab); + + } + + printf("SPL: %s:%d: Released %u slabs TOTAL\n", + __func__, __LINE__, i); + + list_destroy(&freelist); +} + +/* + * Reduce dynamic memory cap by a set amount ("reduction"), unless the cap is + * already 1/8 of total_memory or lower. unlike the logic in + * spl-vmem.c:xnu_alloc_throttled(), we likely have not observed xnu being + * ready to deny us memory, so we drop half the cap half as much. + * + * Inter-thread synchronization of spl_dynamic_memory_cap and spl_free here in + * the next two functions is important as there _will_ be multi-core bursts + * of spl_free_wrapper() calls. + */ +int64_t +spl_reduce_dynamic_cap(void) +{ + /* + * take a snapshot of spl_dynamic_memory_cap, which + * may drop while we are in this function + */ + const uint64_t cap_in = spl_dynamic_memory_cap; + + const uint64_t reduce_amount = total_memory >> 8; + + const int64_t thresh = total_memory >> 3; + + const int64_t reduction = (int64_t)(cap_in - reduce_amount); + + const int64_t reduced = MAX(reduction, thresh); + + /* + * Adjust cap downwards if enough time has elapsed + * for previous adjustments to shrink memory use. + * + * We will still tell ARC to shrink by thresh. + */ + mutex_enter(&spl_dynamic_memory_cap_lock); + + const hrtime_t now = gethrtime(); + if (now > spl_dynamic_memory_cap_last_downward_adjust + + SEC2NSEC(60)) { + + if (spl_dynamic_memory_cap == 0 || + spl_dynamic_memory_cap > total_memory) { + spl_dynamic_memory_cap_last_downward_adjust = now; + spl_dynamic_memory_cap = total_memory - reduce_amount; + atomic_inc_64(&spl_dynamic_memory_cap_reductions); + } else if (spl_dynamic_memory_cap > reduced) { + spl_dynamic_memory_cap_last_downward_adjust = now; + spl_dynamic_memory_cap = reduced; + atomic_inc_64(&spl_dynamic_memory_cap_reductions); + } else if (spl_dynamic_memory_cap <= thresh) { + spl_dynamic_memory_cap_last_downward_adjust = now; + spl_dynamic_memory_cap = thresh; + atomic_inc_64(&spl_dynamic_memory_cap_hit_floor); + } else { + atomic_inc_64(&spl_dynamic_memory_cap_skipped); + } + } else { + atomic_inc_64(&spl_dynamic_memory_cap_skipped); + } + + mutex_exit(&spl_dynamic_memory_cap_lock); + + const uint64_t cap_out = spl_dynamic_memory_cap; + const int64_t cap_diff = cap_out - cap_in; + const int64_t minusthresh = -(int64_t)thresh; + + if (cap_diff > minusthresh) { + spl_free = minusthresh; + return (minusthresh); + } else { + spl_free = cap_diff; + return (cap_diff); + } +} + +/* + * This substitutes for kmem_avail() in arc_os.c + * + * If we believe there is free memory but memory caps are active, enforce on + * them, decrementing the dynamic cap if necessary, returning a non-positive + * free memory to ARC if we have reached either enforced cap. + */ +int64_t +spl_free_wrapper(void) +{ + if (spl_enforce_memory_caps != 0 && spl_free > 0) { + if (segkmem_total_mem_allocated >= + spl_dynamic_memory_cap) { + atomic_inc_64(&spl_memory_cap_enforcements); + spl_set_arc_no_grow(B_TRUE); + return (spl_reduce_dynamic_cap()); + } else if (spl_manual_memory_cap > 0 && + segkmem_total_mem_allocated >= spl_manual_memory_cap) { + spl_set_arc_no_grow(B_TRUE); + atomic_inc_64(&spl_memory_cap_enforcements); + const int64_t dec = spl_manual_memory_cap - + segkmem_total_mem_allocated; + const int64_t giveback = -(total_memory >> 10); + if (dec > giveback) { + spl_free = giveback; + return (giveback); + } else { + spl_free = dec; + return (dec); + } + } + } + + return (spl_free); +} + +// this is intended to substitute for kmem_avail() in arc.c +// when arc_reclaim_thread() calls spl_free_set_pressure(0); +int64_t +spl_free_manual_pressure_wrapper(void) +{ + return (spl_free_manual_pressure); +} + +uint64_t +spl_free_last_pressure_wrapper(void) +{ + return (spl_free_last_pressure); +} + +int64_t +spl_free_set_and_wait_pressure(int64_t new_p, boolean_t fast, + clock_t check_interval) +{ + + int64_t snapshot_pressure = 0; + + if (new_p <= 0) + return (0); + + spl_free_fast_pressure = fast; + + if (spl_free_manual_pressure >= 0) + spl_free_manual_pressure += new_p; + else + spl_free_manual_pressure = new_p; + + // wait for another thread to reset pressure + const uint64_t start = zfs_lbolt(); + const uint64_t end_by = start + (hz*60); + const uint64_t double_at = start + (hz/2); + const uint64_t double_again_at = start + hz; + bool doubled = false, doubled_again = false; + uint64_t now; + + spl_free_last_pressure = start; + + for (; spl_free_manual_pressure != 0; ) { + // has another thread set spl_free_manual_pressure? + if (spl_free_manual_pressure < new_p) + spl_free_manual_pressure = new_p; + snapshot_pressure = spl_free_manual_pressure; + mutex_enter(&spl_free_thread_lock); + cv_timedwait_hires(&spl_free_thread_cv, + &spl_free_thread_lock, check_interval, 0, 0); + mutex_exit(&spl_free_thread_lock); + now = zfs_lbolt(); + if (now > end_by) { + printf("%s: ERROR: timed out after one minute!\n", + __func__); + break; + } else if (doubled && now > double_again_at && !doubled_again) { + doubled_again = true; + new_p *= 2; + } else if (now > double_at) { + doubled = true; + new_p *= 2; + } + } + return (snapshot_pressure); +} + +// routinely called by arc_reclaim_thread() with new_p == 0 +void +spl_free_set_pressure(int64_t new_p) +{ + if (new_p > spl_free_manual_pressure || new_p <= 0) + spl_free_manual_pressure = new_p; + if (new_p == 0) { + spl_free_fast_pressure = FALSE; + // wake up both spl_free_thread() to recalculate spl_free + // and any spl_free_set_and_wait_pressure() threads + mutex_enter(&spl_free_thread_lock); + cv_signal(&spl_free_thread_cv); + mutex_exit(&spl_free_thread_lock); + } + spl_free_last_pressure = zfs_lbolt(); +} + +void +spl_free_set_pressure_both(int64_t new_p, boolean_t fast) +{ + spl_free_fast_pressure = fast; + if (new_p > spl_free_manual_pressure || new_p <= 0) + spl_free_manual_pressure = new_p; + spl_free_last_pressure = zfs_lbolt(); +} + +void spl_free_maybe_reap(void); + +void +spl_free_set_emergency_pressure(int64_t new_p) +{ + spl_free_fast_pressure = TRUE; + if (new_p > spl_free_manual_pressure || new_p <= 0) + spl_free_manual_pressure = new_p; + spl_free_maybe_reap(); + spl_free_last_pressure = zfs_lbolt(); +} + +void +spl_free_set_emergency_pressure_additive(int64_t new_p) +{ + spl_free_fast_pressure = TRUE; + spl_free_manual_pressure += new_p; + spl_free_last_pressure = zfs_lbolt(); +} + +void +spl_free_set_pressure_additive(int64_t new_p) +{ + spl_free_manual_pressure += new_p; + spl_free_last_pressure = zfs_lbolt(); +} + +boolean_t +spl_free_fast_pressure_wrapper() +{ + return (spl_free_fast_pressure); +} + +void +spl_free_set_fast_pressure(boolean_t state) +{ + spl_free_fast_pressure = state; + spl_free_last_pressure = zfs_lbolt(); +} + +void +spl_free_reap_caches(void) +{ + // note: this may take some time + static hrtime_t last_reap = 0; + const hrtime_t reap_after = SEC2NSEC(60); + const hrtime_t curtime = gethrtime(); + + if (curtime - last_reap < reap_after) + return; + + kmem_reap(); + vmem_qcache_reap(kmem_va_arena); +} + +void +spl_free_maybe_reap(void) +{ + static _Atomic uint64_t last_reap = 0; + const uint64_t lockout_time = 60 * hz; + + uint64_t now = zfs_lbolt(); + if (now > last_reap + lockout_time) { + last_reap = now; + spl_free_maybe_reap_flag = true; + } +} + +boolean_t +spl_maybe_send_large_pressure(uint64_t now, uint64_t minutes, boolean_t full) +{ + static volatile _Atomic uint64_t spl_last_large_pressure = 0; + const uint64_t interval_ticks = minutes * 60ULL * (uint64_t)hz; + + if (spl_last_large_pressure + interval_ticks > now) + return (false); + + spl_last_large_pressure = now; + + const int64_t sixteenth_total_memory = + (int64_t)real_total_memory / 16LL; + const int64_t sixtyfourth_total_memory = + sixteenth_total_memory / 4LL; + int64_t howmuch = sixteenth_total_memory; + + if (full == false) + howmuch = sixtyfourth_total_memory; + + + dprintf("SPL: %s: %lld bytes at time %llu\n", + __func__, howmuch, now); + + spl_free_set_emergency_pressure(howmuch); + + return (true); +} + +static void +spl_free_thread() +{ + callb_cpr_t cpr; + + CALLB_CPR_INIT(&cpr, &spl_free_thread_lock, callb_generic_cpr, FTAG); + + /* initialize with a reasonably large amount of memory */ + spl_free = MAX(4*1024*1024*1024, + total_memory * 75ULL / 100ULL); + + if (spl_dynamic_memory_cap == 0) + spl_dynamic_memory_cap = total_memory; + + mutex_enter(&spl_free_thread_lock); + + dprintf("SPL: beginning spl_free_thread() loop, spl_free == %lld\n", + spl_free); + + uint64_t recent_lowmem = 0; + uint64_t last_disequilibrium = 0; + + while (!spl_free_thread_exit) { + mutex_exit(&spl_free_thread_lock); + boolean_t lowmem = false; + boolean_t emergency_lowmem = false; + int64_t new_spl_free = 0LL; + + spl_stats.spl_free_wake_count.value.ui64++; + + if (spl_free_maybe_reap_flag == true) { + spl_free_maybe_reap_flag = false; + spl_free_reap_caches(); + } + + uint64_t time_now = zfs_lbolt(); + uint64_t time_now_seconds = 0; + if (time_now > hz) + time_now_seconds = time_now / hz; + + new_spl_free = total_memory - + segkmem_total_mem_allocated; + + /* Ask Mach about pressure */ + + /* + * Don't wait for pressure, just report back + * how much has changed while we were asleep + * (cf. the duration of cv_timedwait_hires + * at justwait: below -- 10 msec is an eternity + * on most hardware, and the osfmk/vm/vm_pageout.c + * code is linear in the nsecs_wanted parameter + * + */ + + uint32_t pages_reclaimed = 0; + uint32_t pages_wanted = 0; + kern_return_t kr_mon = + mach_vm_pressure_monitor(false, + MSEC2NSEC(10), + &pages_reclaimed, + &pages_wanted); + + if (kr_mon == KERN_SUCCESS) { + spl_vm_pages_reclaimed = + pages_reclaimed; + spl_vm_pages_wanted = + pages_wanted; + } else { + printf("%s:%d : mach_vm_pressure_monitor" + " returned error %d, keeping old" + " values reclaimed %u wanted %u\n", + __FILE__, __LINE__, + kr_mon, + spl_vm_pages_reclaimed, + spl_vm_pages_wanted); + } + + /* + * Don't wait for pressure, just report + * back the pressure level + */ + + uint32_t pressure_level = 0; + kr_mon = mach_vm_pressure_level_monitor(false, + &pressure_level); + + if (kr_mon == KERN_SUCCESS) { + spl_vm_pressure_level = + pressure_level; + } else if (kr_mon == KERN_FAILURE) { + /* optioned out of xnu, use SOS value */ + spl_vm_pressure_level = MAGIC_PRESSURE_UNAVAILABLE; + } else { + printf("%s:%d : mach_vm_pressure_level_monitor" + " returned unexpected error %d," + " keeping old level %d\n", + __FILE__, __LINE__, + kr_mon, spl_vm_pressure_level); + } + + if (spl_vm_pressure_level > 0 && + spl_vm_pressure_level != MAGIC_PRESSURE_UNAVAILABLE) { + /* there is pressure */ + lowmem = true; + new_spl_free = -(2LL * PAGE_SIZE * spl_vm_pages_wanted); + if (spl_vm_pressure_level > 1) { + emergency_lowmem = true; + if (new_spl_free > 0) + new_spl_free = -(4LL * + PAGE_SIZE * + spl_vm_pages_wanted); + spl_free_fast_pressure = TRUE; + } + spl_free_manual_pressure += PAGE_SIZE * + spl_vm_pages_wanted; + } else if (spl_vm_pages_wanted > 0) { + /* + * kVMPressureNormal but pages wanted : react more + * strongly if there was some transient pressure that + * was weakly absorbed by the virtual memory system + */ + /* XXX : additional hysteresis maintained below */ + int64_t m = 2LL; + if (spl_vm_pages_wanted * 8 > + spl_vm_pages_reclaimed) + m = 8LL; + + new_spl_free -= m * + PAGE_SIZE * spl_vm_pages_wanted; + } else { + /* + * No pressure. Xnu has freed up some memory + * which we can use. + */ + if (spl_vm_pages_reclaimed > 0) + new_spl_free += (PAGE_SIZE * + spl_vm_pages_reclaimed) >> 1LL; + else { + /* grow a little every pressure-free pass */ + new_spl_free += 1024LL*1024LL; + } + /* + * Cap, bearing in mind that we deflate + * total_memory by 50% at initialization + */ + if (new_spl_free > total_memory) + new_spl_free = total_memory; + } + + /* + * if there is pressure that has not yet reached + * arc_reclaim_thread() then start with a negative + * new_spl_free + */ + if (spl_free_manual_pressure > 0) { + int64_t old_pressure = spl_free_manual_pressure; + new_spl_free -= old_pressure * 2LL; + lowmem = true; + if (spl_free_fast_pressure) { + emergency_lowmem = true; + new_spl_free -= old_pressure * 4LL; + } + } + + /* + * Pressure and declare zero free memory if we are above + * memory caps. This is not the hardest enforcement + * mechanism, so see also enforcement in spl_free_wrapper() + */ + if (spl_enforce_memory_caps) { + if (segkmem_total_mem_allocated >= + spl_dynamic_memory_cap) { + lowmem = true; + emergency_lowmem = true; + if (new_spl_free >= 0) + new_spl_free = + spl_dynamic_memory_cap - + segkmem_total_mem_allocated; + atomic_inc_64(&spl_memory_cap_enforcements); + } else if (spl_manual_memory_cap > 0 && + segkmem_total_mem_allocated >= + spl_manual_memory_cap) { + lowmem = true; + emergency_lowmem = true; + if (new_spl_free >= 0) + new_spl_free = + spl_manual_memory_cap - + segkmem_total_mem_allocated; + atomic_inc_64(&spl_memory_cap_enforcements); + } + } + + /* + * can we allocate at least a 64 MiB segment + * from spl_heap_arena? this probes the reserve + * and also the largest imported spans, which + * vmem_alloc can fragment if needed. + */ + boolean_t reserve_low = false; + extern vmem_t *spl_heap_arena; + const uint64_t sixtyfour = 64ULL*1024ULL*1024ULL; + const uint64_t rvallones = (sixtyfour << 1ULL) - 1ULL; + const uint64_t rvmask = ~rvallones; + uint64_t rvfreebits = spl_heap_arena->vm_freemap; + + if ((rvfreebits & rvmask) == 0) { + reserve_low = true; + } else { + new_spl_free += (int64_t)sixtyfour; + } + + // do we have lots of memory in the spl_heap_arena ? + + boolean_t early_lots_free = false; + const uint64_t onetwentyeight = 128ULL*1024ULL*1024ULL; + const uint64_t sixteen = 16ULL*1024ULL*1024ULL; + if (!reserve_low) { + early_lots_free = true; + } else if (vmem_size_semi_atomic(spl_heap_arena, + VMEM_FREE) > onetwentyeight) { + early_lots_free = true; + new_spl_free += (int64_t)sixteen; + } + + // do we have lots of memory in the bucket_arenas ? + + extern int64_t vmem_buckets_size(int); // non-locking + int64_t buckets_free = vmem_buckets_size(VMEM_FREE); + if ((uint64_t)buckets_free != spl_buckets_mem_free) + spl_buckets_mem_free = (uint64_t)buckets_free; + + if (buckets_free >= 512LL*1024LL*1024LL) { + early_lots_free = true; + new_spl_free += (int64_t)sixteen; + } + if (buckets_free >= 1024LL*1024LL*1024LL) { + reserve_low = false; + new_spl_free += (int64_t)sixteen; + } + + /* + * if we have neither alloced or freed in + * several minutes, then we do not need to + * shrink back if there is a momentary transient + * memory spike (i.e., one that lasts less than a second) + */ + boolean_t memory_equilibrium = false; + const uint64_t five_minutes = 300ULL; + const uint64_t one_minute = 60ULL; + uint64_t last_xat_alloc_seconds = spl_xat_lastalloc; + uint64_t last_xat_free_seconds = spl_xat_lastfree; + + if (last_xat_alloc_seconds + five_minutes > time_now_seconds && + last_xat_free_seconds + five_minutes > time_now_seconds) { + if (last_disequilibrium + one_minute > + time_now_seconds) { + memory_equilibrium = true; + last_disequilibrium = 0; + } + } else { + last_disequilibrium = time_now_seconds; + } + + boolean_t just_alloced = false; + if (last_xat_alloc_seconds + 1 > time_now_seconds) + just_alloced = true; + + /* + * this is a sign of a period of time of low system + * memory, however XNU's generation of this variable + * is not very predictable, but generally it should be + * taken seriously when it's positive (it is often falsely 0) + */ + if ((spl_vm_pages_wanted > 0 && reserve_low && + !early_lots_free && !memory_equilibrium && + !just_alloced) || spl_vm_pages_wanted >= 1024) { + int64_t bminus = (int64_t)spl_vm_pages_wanted * + (int64_t)PAGESIZE * -16LL; + if (bminus > -16LL*1024LL*1024LL) + bminus = -16LL*1024LL*1024LL; + new_spl_free += bminus; + lowmem = true; + emergency_lowmem = true; + // atomic swaps to set these variables used in arc.c + int64_t previous_highest_pressure = 0; + int64_t new_p = -bminus; + previous_highest_pressure = spl_free_manual_pressure; + if (new_p > previous_highest_pressure || new_p <= 0) { + boolean_t fast = FALSE; + if (spl_vm_pages_wanted > + spl_vm_page_free_min / 8) + fast = TRUE; + spl_free_set_pressure_both(-16LL * new_spl_free, + fast); + } + last_disequilibrium = time_now_seconds; + } else if (spl_vm_pages_wanted > 0) { + int64_t bytes_wanted = + (int64_t)spl_vm_pages_wanted * + (int64_t)PAGESIZE; + new_spl_free -= bytes_wanted; + if (reserve_low && !early_lots_free) { + lowmem = true; + if (recent_lowmem == 0) { + recent_lowmem = time_now; + } + if (!memory_equilibrium) { + last_disequilibrium = time_now_seconds; + } + } + } + + /* + * If we have already detected a memory shortage + * and we have not reaped in a while (a short while + * for emergency_lowmem), then do a kmem_reap() now. + * See http://comments.gmane.org/gmane.os.illumos.devel/22552 + * (notably Richard Elling's "A kernel module can call + * kmem_reap() whenever it wishes and some modules, + * like zfs, do so." If we reap, stop processing spl_free + * on this pass, to let the reaps (and arc, if pressure + * has been set above) do their job for a few milliseconds. + */ + if (emergency_lowmem || lowmem) { + static uint64_t last_reap = 0; + uint64_t now = time_now; + uint64_t elapsed = 60*hz; + if (emergency_lowmem) + elapsed = 15*hz; // min.freq. kmem_reap_interval + if (now - last_reap > elapsed) { + last_reap = now; + /* + * spl_free_reap_caches() calls functions + * that will acquire locks and can take a while + * so set spl_free to a small positive value + * to stop arc shrinking too much during this + * period when we expect to be freeing up + * arc-usable memory, but low enough that + * arc_no_grow likely will be set. + */ + const int64_t two_spamax = 32LL * 1024LL * + 1024LL; + if (spl_free < two_spamax) + spl_free = two_spamax; // atomic! + spl_free_reap_caches(); + // we do not have any lock now, so we can jump + // to just before the thread-suspending code + goto justwait; + } + } + + /* + * a number or exceptions to reverse the lowmem + * / emergency_lowmem states if we have recently reaped. + * we also take the strong reaction sting out of + * the set pressure by turning off spl_free_fast_pressure, + * since that automatically provokes an arc shrink + * and arc reap. + */ + + if (!reserve_low || early_lots_free || memory_equilibrium || + just_alloced) { + lowmem = false; + emergency_lowmem = false; + spl_free_fast_pressure = FALSE; + } + + /* + * Stay in a low memory condition for several seconds + * after we first detect that we are in it, giving the + * system (arc, xnu and userland) time to adapt + */ + if (!lowmem && recent_lowmem > 0) { + if (recent_lowmem + 4*hz < time_now) + lowmem = true; + else + recent_lowmem = 0; + } + + // adjust for available memory in spl_heap_arena + // cf arc_available_memory() + if (!emergency_lowmem) { + int64_t heap_free = (int64_t)vmem_size_semi_atomic( + spl_heap_arena, VMEM_FREE); + // grabbed buckets_free up above; we are OK with + // change to it in the meanwhile, + // it'll get an update on the next run. + int64_t combined_free = heap_free + buckets_free; + + if (combined_free != 0) { + const int64_t mb = 1024*1024; + if (!lowmem) { + new_spl_free += combined_free / + 4; + } else { + new_spl_free -= 16LL * mb; + } + } + + // memory footprint has gotten really big, + // decrease spl_free substantially + int64_t total_mem_used = (int64_t) + segkmem_total_mem_allocated; + if ((segkmem_total_mem_allocated * 100LL / + real_total_memory) > 70) { + new_spl_free -= total_mem_used / 64; + } else if ((segkmem_total_mem_allocated * 100LL / + real_total_memory) > 75) { + new_spl_free -= total_mem_used / 32; + lowmem = true; + } + } + + // try to get 1/64 of spl_heap_arena freed up + if (emergency_lowmem && new_spl_free >= 0LL) { + extern vmem_t *spl_root_arena; + uint64_t root_size = vmem_size_semi_atomic( + spl_heap_arena, VMEM_ALLOC | VMEM_FREE); + uint64_t root_free = vmem_size_semi_atomic( + spl_heap_arena, VMEM_FREE); + int64_t difference = root_size - root_free; + int64_t target = root_size / 64; + if (difference < target) { + new_spl_free -= target; + } + // and we should definitely not be returning + // positive now + if (new_spl_free >= 0LL) + new_spl_free = -1024LL; + } + + boolean_t spl_free_is_negative = false; + + if (new_spl_free < 0LL) { + spl_stats.spl_spl_free_negative_count.value.ui64++; + spl_free_is_negative = true; + } + + /* + * leave a little headroom if we have hit our + * allocation maximum + */ + const int64_t spamaxblksz = 16LL * 1024LL; + if ((4LL * spamaxblksz) > + (total_memory - segkmem_total_mem_allocated)) { + if (new_spl_free > 2LL * spamaxblksz) + new_spl_free = 2LL * spamaxblksz; + } + + if (spl_enforce_memory_caps != 0) { + if (spl_dynamic_memory_cap != 0) { + const int64_t m = spl_dynamic_memory_cap - + segkmem_total_mem_allocated; + if (new_spl_free > m) + new_spl_free = m; + } else if (spl_manual_memory_cap != 0) { + const int64_t m = spl_manual_memory_cap - + segkmem_total_mem_allocated; + if (new_spl_free > m) + new_spl_free = m; + } + } + + // NOW set spl_free from calculated new_spl_free + spl_free = new_spl_free; + // the direct equivalent of : + // __c11_atomic_store(&spl_free, new_spl_free, + // __ATOMIC_SEQ_CST); + + /* + * Because we're already negative, arc is likely to have + * been signalled already. We can rely on the _maybe_ in + * spl-vmem.c:xnu_alloc_throttled() [XAT] to try to give + * arc a kick with greater probability. However, if we've + * gone negative several times, and have not tried a full + * kick in a long time, do so now; if the full kick is + * refused because there has been a kick too few minutes + * ago, try a gentler kick. We do this outside the lock, + * as spl_maybe_send_large_pressure may need to take a + * mutex, and we forbid further mutex entry when + * spl_free_lock is held. + */ + + if (spl_free_is_negative) { + static volatile _Atomic uint32_t + negatives_since_last_kick = 0; + + if (negatives_since_last_kick++ > 8) { + if (spl_maybe_send_large_pressure(time_now, 360, + true) || + spl_maybe_send_large_pressure(time_now, 60, + false)) { + negatives_since_last_kick = 0; + } + } + } + + if (lowmem) + recent_lowmem = time_now; + + justwait: + mutex_enter(&spl_free_thread_lock); + CALLB_CPR_SAFE_BEGIN(&cpr); + (void) cv_timedwait_hires(&spl_free_thread_cv, + &spl_free_thread_lock, MSEC2NSEC(10), 0, 0); + CALLB_CPR_SAFE_END(&cpr, &spl_free_thread_lock); + } + spl_free_thread_exit = FALSE; + dprintf("SPL: spl_free_thread_exit set to FALSE " \ + "and exiting: cv_broadcasting\n"); + spl_free_manual_pressure = 0; + cv_broadcast(&spl_free_thread_cv); + CALLB_CPR_EXIT(&cpr); + dprintf("SPL: %s thread_exit\n", __func__); + thread_exit(); +} + + +static int +spl_kstat_update(kstat_t *ksp, int rw) +{ + spl_stats_t *ks = ksp->ks_data; + + if (rw == KSTAT_WRITE) { + + if (ks->spl_spl_free_manual_pressure.value.i64 != + spl_free_manual_pressure) { + spl_free_set_pressure( + ks->spl_spl_free_manual_pressure.value.i64 * 1024 * + 1024); + if (ks->spl_spl_free_manual_pressure.value.i64 > 0) { + spl_free_reap_caches(); + } + } + + if (ks->spl_spl_free_fast_pressure.value.i64 != + spl_free_fast_pressure) { + if (spl_free_wrapper() != 0) { + spl_free_set_fast_pressure(TRUE); + } + } + + if (ks->spl_bucket_tunable_large_span.value.ui64 != + spl_bucket_tunable_large_span) { + spl_set_bucket_tunable_large_span( + ks->spl_bucket_tunable_large_span.value.ui64); + } + + if (ks->spl_bucket_tunable_small_span.value.ui64 != + spl_bucket_tunable_small_span) { + spl_set_bucket_tunable_small_span( + ks->spl_bucket_tunable_small_span.value.ui64); + } + + if (ks->spl_frag_max_walk.value.ui64 != spl_frag_max_walk) { + spl_frag_max_walk = ks->spl_frag_max_walk.value.ui64; + } + + if (ks->kmem_free_to_slab_when_fragmented.value.ui64 != + kmem_free_to_slab_when_fragmented) { + kmem_free_to_slab_when_fragmented = + ks->kmem_free_to_slab_when_fragmented.value.ui64; + } + + if ((unsigned int) ks->spl_split_stack_below.value.ui64 != + spl_split_stack_below) { + spl_split_stack_below = + (unsigned int) + ks->spl_split_stack_below.value.ui64; + } + + if (ks->spl_enforce_memory_caps.value.ui64 != + spl_enforce_memory_caps) { + spl_enforce_memory_caps = + ks->spl_enforce_memory_caps.value.ui64; + } + + if (ks->spl_manual_memory_cap.value.ui64 != + spl_manual_memory_cap) { + uint64_t v = + ks->spl_manual_memory_cap.value.ui64; + if (v < total_memory >> 3) + v = total_memory >> 3; + else if (v > total_memory) + v = 0; + spl_manual_memory_cap = v; + } + + if (ks->spl_dynamic_memory_cap.value.ui64 != + spl_dynamic_memory_cap) { + uint64_t v = + ks->spl_dynamic_memory_cap.value.ui64; + if (v == 0) + v = total_memory; + else if (v < total_memory >> 3) + v = total_memory >> 3; + else if (v > total_memory) + v = total_memory; + spl_dynamic_memory_cap = v; + } + + } else { + ks->spl_os_alloc.value.ui64 = segkmem_total_mem_allocated; + ks->spl_active_threads.value.ui64 = zfs_threads; + ks->spl_active_mutex.value.ui64 = zfs_active_mutex; + ks->spl_active_rwlock.value.ui64 = zfs_active_rwlock; + ks->spl_active_tsd.value.ui64 = spl_tsd_size(); + ks->spl_spl_free.value.i64 = spl_free; + ks->spl_spl_free_manual_pressure.value.i64 = + spl_free_manual_pressure; + ks->spl_spl_free_fast_pressure.value.i64 = + spl_free_fast_pressure; + ks->spl_osif_malloc_success.value.ui64 = + stat_osif_malloc_success; + ks->spl_osif_malloc_fail.value.ui64 = + stat_osif_malloc_fail; + ks->spl_osif_malloc_bytes.value.ui64 = stat_osif_malloc_bytes; + ks->spl_osif_free.value.ui64 = stat_osif_free; + ks->spl_osif_free_bytes.value.ui64 = stat_osif_free_bytes; + + ks->spl_enforce_memory_caps.value.ui64 = + spl_enforce_memory_caps; + ks->spl_dynamic_memory_cap.value.ui64 = + spl_dynamic_memory_cap; + ks->spl_dynamic_memory_cap_skipped.value.ui64 = + spl_dynamic_memory_cap_skipped; + ks->spl_dynamic_memory_cap_reductions.value.ui64 = + spl_dynamic_memory_cap_reductions; + ks->spl_dynamic_memory_cap_hit_floor.value.ui64 = + spl_dynamic_memory_cap_hit_floor; + ks->spl_manual_memory_cap.value.ui64 = + spl_manual_memory_cap; + ks->spl_memory_cap_enforcements.value.ui64 = + spl_memory_cap_enforcements; + + ks->spl_osif_malloc_sub128k.value.ui64 = + stat_osif_malloc_sub128k; + ks->spl_osif_malloc_sub64k.value.ui64 = + stat_osif_malloc_sub64k; + ks->spl_osif_malloc_sub32k.value.ui64 = + stat_osif_malloc_sub32k; + ks->spl_osif_malloc_page.value.ui64 = + stat_osif_malloc_page; + ks->spl_osif_malloc_subpage.value.ui64 = + stat_osif_malloc_subpage; + + ks->spl_bucket_non_pow2_allocs.value.ui64 = + spl_bucket_non_pow2_allocs; + + ks->spl_vmem_unconditional_allocs.value.ui64 = + spl_vmem_unconditional_allocs; + ks->spl_vmem_unconditional_alloc_bytes.value.ui64 = + spl_vmem_unconditional_alloc_bytes; + ks->spl_vmem_conditional_allocs.value.ui64 = + spl_vmem_conditional_allocs; + ks->spl_vmem_conditional_alloc_bytes.value.ui64 = + spl_vmem_conditional_alloc_bytes; + ks->spl_vmem_conditional_alloc_deny.value.ui64 = + spl_vmem_conditional_alloc_deny; + ks->spl_vmem_conditional_alloc_deny_bytes.value.ui64 = + spl_vmem_conditional_alloc_deny_bytes; + + ks->spl_xat_pressured.value.ui64 = spl_xat_pressured; + ks->spl_xat_lastalloc.value.ui64 = spl_xat_lastalloc; + ks->spl_xat_lastfree.value.ui64 = spl_xat_lastfree; + ks->spl_xat_sleep.value.ui64 = spl_xat_sleep; + + ks->spl_vba_fastpath.value.ui64 = + spl_vba_fastpath; + ks->spl_vba_fastexit.value.ui64 = + spl_vba_fastexit; + ks->spl_vba_slowpath.value.ui64 = + spl_vba_slowpath; + ks->spl_vba_parent_memory_appeared.value.ui64 = + spl_vba_parent_memory_appeared; + ks->spl_vba_parent_memory_blocked.value.ui64 = + spl_vba_parent_memory_blocked; + ks->spl_vba_hiprio_blocked.value.ui64 = spl_vba_hiprio_blocked; + ks->spl_vba_cv_timeout.value.ui64 = spl_vba_cv_timeout; + ks->spl_vba_loop_timeout.value.ui64 = spl_vba_loop_timeout; + ks->spl_vba_cv_timeout_blocked.value.ui64 = + spl_vba_cv_timeout_blocked; + ks->spl_vba_loop_timeout_blocked.value.ui64 = + spl_vba_loop_timeout_blocked; + ks->spl_vba_sleep.value.ui64 = spl_vba_sleep; + ks->spl_vba_loop_entries.value.ui64 = spl_vba_loop_entries; + + ks->spl_bucket_tunable_large_span.value.ui64 = + spl_bucket_tunable_large_span; + ks->spl_bucket_tunable_small_span.value.ui64 = + spl_bucket_tunable_small_span; + + ks->spl_buckets_mem_free.value.ui64 = spl_buckets_mem_free; + ks->spl_arc_no_grow_bits.value.ui64 = spl_arc_no_grow_bits; + ks->spl_arc_no_grow_count.value.ui64 = spl_arc_no_grow_count; + + ks->spl_frag_max_walk.value.ui64 = spl_frag_max_walk; + ks->spl_frag_walked_out.value.ui64 = spl_frag_walked_out; + ks->spl_frag_walk_cnt.value.ui64 = spl_frag_walk_cnt; + + ks->spl_arc_reclaim_avoided.value.ui64 = + spl_arc_reclaim_avoided; + + ks->kmem_free_to_slab_when_fragmented.value.ui64 = + kmem_free_to_slab_when_fragmented; + + ks->spl_vm_pages_reclaimed.value.ui64 = + spl_vm_pages_reclaimed; + ks->spl_vm_pages_wanted.value.ui64 = + spl_vm_pages_wanted; + ks->spl_vm_pressure_level.value.ui64 = + spl_vm_pressure_level; + + ks->spl_lowest_alloc_stack_remaining.value.ui64 = + spl_lowest_alloc_stack_remaining; + ks->spl_lowest_vdev_disk_stack_remaining.value.ui64 = + spl_lowest_vdev_disk_stack_remaining; + ks->spl_lowest_zvol_stack_remaining.value.ui64 = + spl_lowest_zvol_stack_remaining; + ks->spl_split_stack_below.value.ui64 = + spl_split_stack_below; + } + + return (0); +} + +void +spl_kmem_init(uint64_t xtotal_memory) +{ + int old_kmem_flags = kmem_flags; + int use_large_pages = 0; + size_t maxverify, minfirewall; + + dprintf("SPL: KMEM starting. Total memory %llu\n", xtotal_memory); + + // Initialise the kstat lock + mutex_init(&kmem_cache_lock, "kmem_cache_lock", MUTEX_DEFAULT, NULL); + mutex_init(&kmem_flags_lock, "kmem_flags_lock", MUTEX_DEFAULT, NULL); + mutex_init(&kmem_cache_kstat_lock, "kmem_kstat_lock", MUTEX_DEFAULT, + NULL); + + spl_kstat_init(); + + + /* + * Small-memory systems (< 24 MB) can't handle kmem_flags overhead. + */ + if (physmem < btop(24 << 20) && !(old_kmem_flags & KMF_STICKY)) + kmem_flags = 0; + + /* + * Don't do firewalled allocations if the heap is less than 1TB + * (i.e. on a 32-bit kernel) + * The resulting VM_NEXTFIT allocations would create too much + * fragmentation in a small heap. + */ + maxverify = minfirewall = PAGESIZE / 2; + + + /* LINTED */ + ASSERT(sizeof (kmem_cpu_cache_t) == KMEM_CPU_CACHE_SIZE); + + list_create(&kmem_caches, sizeof (kmem_cache_t), + offsetof(kmem_cache_t, cache_link)); + + kernelheap_init(); + + kmem_metadata_arena = vmem_create("kmem_metadata", NULL, 0, PAGESIZE, + vmem_alloc_impl, vmem_free_impl, heap_arena, 8 * PAGESIZE, + VM_SLEEP | VMC_NO_QCACHE); + + kmem_msb_arena = vmem_create("kmem_msb", NULL, 0, + PAGESIZE, vmem_alloc_impl, vmem_free_impl, kmem_metadata_arena, 0, + VMC_DUMPSAFE | VM_SLEEP); + + kmem_cache_arena = vmem_create("kmem_cache", NULL, 0, KMEM_ALIGN, + vmem_alloc_impl, vmem_free_impl, kmem_metadata_arena, 0, VM_SLEEP); + + kmem_hash_arena = vmem_create("kmem_hash", NULL, 0, KMEM_ALIGN, + vmem_alloc_impl, vmem_free_impl, kmem_metadata_arena, 0, VM_SLEEP); + + kmem_log_arena = vmem_create("kmem_log", NULL, 0, KMEM_ALIGN, + vmem_alloc_impl, vmem_free_impl, kmem_metadata_arena, 0, VM_SLEEP); + + /* temporary oversize arena for mod_read_system_file */ + kmem_oversize_arena = vmem_create("kmem_oversize", NULL, 0, PAGESIZE, + vmem_alloc_impl, vmem_free_impl, heap_arena, 0, VM_SLEEP); + + // statically declared above kmem_reap_interval = 15 * hz; + + /* + * Read /etc/system. This is a chicken-and-egg problem because + * kmem_flags may be set in /etc/system, but mod_read_system_file() + * needs to use the allocator. The simplest solution is to create + * all the standard kmem caches, read /etc/system, destroy all the + * caches we just created, and then create them all again in light + * of the (possibly) new kmem_flags and other kmem tunables. + */ + + if (old_kmem_flags & KMF_STICKY) + kmem_flags = old_kmem_flags; + + if (!(kmem_flags & KMF_AUDIT)) + vmem_seg_size = offsetof(vmem_seg_t, vs_thread); + + if (kmem_maxverify == 0) + kmem_maxverify = maxverify; + + if (kmem_minfirewall == 0) + kmem_minfirewall = minfirewall; + + /* + * give segkmem a chance to figure out if we are using large pages + * for the kernel heap + */ + // use_large_pages = segkmem_lpsetup(); + use_large_pages = 0; + + /* + * To protect against corruption, we keep the actual number of callers + * KMF_LITE records seperate from the tunable. We arbitrarily clamp + * to 16, since the overhead for small buffers quickly gets out of + * hand. + * + * The real limit would depend on the needs of the largest KMC_NOHASH + * cache. + */ + kmem_lite_count = MIN(MAX(0, kmem_lite_pcs), 16); + kmem_lite_pcs = kmem_lite_count; + + kmem_cache_init(2, use_large_pages); + + if (kmem_flags & (KMF_AUDIT | KMF_RANDOMIZE)) { + if (kmem_transaction_log_size == 0) + kmem_transaction_log_size = MIN(kmem_maxavail() / 50ULL, + PAGESIZE<<4); + kmem_transaction_log = kmem_log_init(kmem_transaction_log_size); + } + + if (kmem_flags & (KMF_CONTENTS | KMF_RANDOMIZE)) { + if (kmem_content_log_size == 0) + kmem_content_log_size = MIN(kmem_maxavail() / 50ULL, + PAGESIZE<<4); + kmem_content_log = kmem_log_init(kmem_content_log_size); + } + + kmem_failure_log = kmem_log_init(kmem_failure_log_size); + + kmem_slab_log = kmem_log_init(kmem_slab_log_size); + + spl_tsd_init(); + spl_rwlock_init(); + spl_taskq_init(); + + /* + * Warn about invalid or dangerous values of kmem_flags. + * Always warn about unsupported values. + */ + if (((kmem_flags & ~(KMF_AUDIT | KMF_DEADBEEF | KMF_REDZONE | + KMF_CONTENTS | KMF_LITE)) != 0) || + ((kmem_flags & KMF_LITE) && kmem_flags != KMF_LITE)) + cmn_err(CE_WARN, "kmem_flags set to unsupported value 0x%x. " + "See the Solaris Tunable Parameters Reference Manual.", + kmem_flags); + +#ifdef DEBUG + if ((kmem_flags & KMF_DEBUG) == 0) + cmn_err(CE_NOTE, "kmem debugging disabled."); +#else + /* + * For non-debug kernels, the only "normal" flags are 0, KMF_LITE, + * KMF_REDZONE, and KMF_CONTENTS (the last because it is only enabled + * if KMF_AUDIT is set). We should warn the user about the performance + * penalty of KMF_AUDIT or KMF_DEADBEEF if they are set and KMF_LITE + * isn't set (since that disables AUDIT). + */ + if (!(kmem_flags & KMF_LITE) && + (kmem_flags & (KMF_AUDIT | KMF_DEADBEEF)) != 0) + cmn_err(CE_WARN, "High-overhead kmem debugging features " + "enabled (kmem_flags = 0x%x). Performance degradation " + "and large memory overhead possible. See the Solaris " + "Tunable Parameters Reference Manual.", kmem_flags); +#endif /* not DEBUG */ + + segkmem_abd_init(); + + kmem_cache_applyall(kmem_cache_magazine_enable, NULL, TQ_SLEEP); + + kmem_ready = 1; + + // Install spl kstats + spl_ksp = kstat_create("spl", 0, "spl_misc", "misc", KSTAT_TYPE_NAMED, + sizeof (spl_stats) / sizeof (kstat_named_t), + KSTAT_FLAG_VIRTUAL|KSTAT_FLAG_WRITABLE); + + if (spl_ksp != NULL) { + spl_ksp->ks_data = &spl_stats; + spl_ksp->ks_update = spl_kstat_update; + kstat_install(spl_ksp); + } +} + +void +spl_kmem_fini(void) +{ + + kmem_cache_applyall(kmem_cache_magazine_disable, NULL, TQ_SLEEP); + + kstat_delete(spl_ksp); + + kmem_log_fini(kmem_slab_log); + kmem_log_fini(kmem_failure_log); + + if (kmem_flags & (KMF_CONTENTS | KMF_RANDOMIZE)) { + if (kmem_content_log_size == 0) + kmem_content_log_size = kmem_maxavail() / 50; + kmem_log_fini(kmem_content_log); + } + + if (kmem_flags & (KMF_AUDIT | KMF_RANDOMIZE)) { + if (kmem_transaction_log_size == 0) + kmem_transaction_log_size = kmem_maxavail() / 50; + kmem_log_fini(kmem_transaction_log); + } + + // Destroy all the "general allocation" caches + kmem_alloc_caches_destroy(); + + // Destroy the VA associated caches + kmem_destroy_cache_by_name(KMEM_VA_PREFIX); + + kmem_qcache_destroy(); + // Destroy metadata caches + kmem_cache_destroy(kmem_bufctl_cache); + kmem_cache_destroy(kmem_bufctl_audit_cache); + kmem_cache_destroy(kmem_slab_cache); // Dont think this one + + // Some caches cannot be destroyed as + // they mutually reference each other. + // So we explicitly pull them apart piece-by-piece. + kmem_cache_fini(); + + segkmem_abd_fini(); + + // Now destroy the vmem arenas used by kmem. + vmem_destroy(kmem_default_arena); + vmem_destroy(kmem_va_arena); + vmem_destroy(kmem_oversize_arena); + vmem_destroy(kmem_log_arena); + vmem_destroy(kmem_hash_arena); + vmem_destroy(kmem_cache_arena); + vmem_destroy(kmem_msb_arena); + vmem_destroy(kmem_metadata_arena); + + kernelheap_fini(); + + list_destroy(&kmem_caches); + + mutex_destroy(&kmem_cache_kstat_lock); + mutex_destroy(&kmem_flags_lock); + mutex_destroy(&kmem_cache_lock); +} + +static void +kmem_move_init(void) +{ + kmem_defrag_cache = kmem_cache_create("kmem_defrag_cache", + sizeof (kmem_defrag_t), 0, NULL, NULL, NULL, NULL, + kmem_msb_arena, KMC_NOHASH); + kmem_move_cache = kmem_cache_create("kmem_move_cache", + sizeof (kmem_move_t), 0, NULL, NULL, NULL, NULL, + kmem_msb_arena, KMC_NOHASH); + + /* + * kmem guarantees that move callbacks are sequential and that even + * across multiple caches no two moves ever execute simultaneously. + * Move callbacks are processed on a separate taskq so that client code + * does not interfere with internal maintenance tasks. + */ + kmem_move_taskq = taskq_create("kmem_move_taskq", 1, + minclsyspri, 100, INT_MAX, TASKQ_PREPOPULATE); +} + +void +kmem_move_fini(void) +{ + + taskq_wait(kmem_move_taskq); + taskq_destroy(kmem_move_taskq); + kmem_move_taskq = 0; + + kmem_cache_destroy(kmem_move_cache); + kmem_cache_destroy(kmem_defrag_cache); + +} + +void +spl_kmem_thread_init(void) +{ + kmem_move_init(); + + // Initialize the spl_free locks + mutex_init(&spl_free_thread_lock, "spl_free_thead_lock", MUTEX_DEFAULT, + NULL); + mutex_init(&spl_dynamic_memory_cap_lock, "spl_dynamic_memory_cap_lock", + MUTEX_DEFAULT, NULL); + + kmem_taskq = taskq_create("kmem_taskq", 1, minclsyspri, + 600, INT_MAX, TASKQ_PREPOPULATE); + + spl_free_thread_exit = FALSE; + (void) cv_init(&spl_free_thread_cv, NULL, CV_DEFAULT, NULL); + (void) thread_create(NULL, 0, spl_free_thread, 0, 0, 0, 0, 92); +} + +void +spl_kmem_thread_fini(void) +{ + shutting_down = 1; + + mutex_enter(&spl_free_thread_lock); + spl_free_thread_exit = TRUE; + while (spl_free_thread_exit) { + cv_signal(&spl_free_thread_cv); + cv_wait(&spl_free_thread_cv, &spl_free_thread_lock); + } + mutex_exit(&spl_free_thread_lock); + cv_destroy(&spl_free_thread_cv); + mutex_destroy(&spl_free_thread_lock); + + mutex_destroy(&spl_dynamic_memory_cap_lock); + + bsd_untimeout(kmem_update, 0); + bsd_untimeout(kmem_reap_timeout, &kmem_reaping); + bsd_untimeout(kmem_reap_timeout, &kmem_reaping_idspace); + + taskq_wait(kmem_taskq); + + taskq_destroy(kmem_taskq); + kmem_taskq = 0; + + kmem_move_fini(); + +} + +void +spl_kmem_mp_init(void) +{ + kmem_update_timeout(NULL); +} + +/* + * Return the slab of the allocated buffer, or NULL if the buffer is not + * allocated. This function may be called with a known slab address to determine + * whether or not the buffer is allocated, or with a NULL slab address to obtain + * an allocated buffer's slab. + */ +static kmem_slab_t * +kmem_slab_allocated(kmem_cache_t *cp, kmem_slab_t *sp, void *buf) +{ + kmem_bufctl_t *bcp, *bufbcp; + + ASSERT(MUTEX_HELD(&cp->cache_lock)); + ASSERT(sp == NULL || KMEM_SLAB_MEMBER(sp, buf)); + + if (cp->cache_flags & KMF_HASH) { + for (bcp = *KMEM_HASH(cp, buf); + (bcp != NULL) && (bcp->bc_addr != buf); + bcp = bcp->bc_next) { + continue; + } + ASSERT(sp != NULL && bcp != NULL ? sp == bcp->bc_slab : 1); + return (bcp == NULL ? NULL : bcp->bc_slab); + } + + if (sp == NULL) { + sp = KMEM_SLAB(cp, buf); + } + bufbcp = KMEM_BUFCTL(cp, buf); + for (bcp = sp->slab_head; + (bcp != NULL) && (bcp != bufbcp); + bcp = bcp->bc_next) { + continue; + } + return (bcp == NULL ? sp : NULL); +} + +static boolean_t +kmem_slab_is_reclaimable(kmem_cache_t *cp, kmem_slab_t *sp, int flags) +{ + long refcnt = sp->slab_refcnt; + + ASSERT(cp->cache_defrag != NULL); + + /* + * For code coverage we want to be able to move an object within the + * same slab (the only partial slab) even if allocating the destination + * buffer resulted in a completely allocated slab. + */ + if (flags & KMM_DEBUG) { + return ((flags & KMM_DESPERATE) || + ((sp->slab_flags & KMEM_SLAB_NOMOVE) == 0)); + } + + /* If we're desperate, we don't care if the client said NO. */ + if (flags & KMM_DESPERATE) { + return (refcnt < sp->slab_chunks); /* any partial */ + } + + if (sp->slab_flags & KMEM_SLAB_NOMOVE) { + return (B_FALSE); + } + + if ((refcnt == 1) || kmem_move_any_partial) { + return (refcnt < sp->slab_chunks); + } + + /* + * The reclaim threshold is adjusted at each kmem_cache_scan() so that + * slabs with a progressively higher percentage of used buffers can be + * reclaimed until the cache as a whole is no longer fragmented. + * + * sp->slab_refcnt kmd_reclaim_numer + * --------------- < ------------------ + * sp->slab_chunks KMEM_VOID_FRACTION + */ + return ((refcnt * KMEM_VOID_FRACTION) < + (sp->slab_chunks * cp->cache_defrag->kmd_reclaim_numer)); +} + +/* + * May be called from the kmem_move_taskq, from kmem_cache_move_notify_task(), + * or when the buffer is freed. + */ +static void +kmem_slab_move_yes(kmem_cache_t *cp, kmem_slab_t *sp, void *from_buf) +{ + ASSERT(MUTEX_HELD(&cp->cache_lock)); + ASSERT(KMEM_SLAB_MEMBER(sp, from_buf)); + + if (!KMEM_SLAB_IS_PARTIAL(sp)) { + return; + } + + if (sp->slab_flags & KMEM_SLAB_NOMOVE) { + if (KMEM_SLAB_OFFSET(sp, from_buf) == sp->slab_stuck_offset) { + avl_remove(&cp->cache_partial_slabs, sp); + sp->slab_flags &= ~KMEM_SLAB_NOMOVE; + sp->slab_stuck_offset = (uint32_t)-1; + avl_add(&cp->cache_partial_slabs, sp); + } + } else { + sp->slab_later_count = 0; + sp->slab_stuck_offset = (uint32_t)-1; + } +} + +static void +kmem_slab_move_no(kmem_cache_t *cp, kmem_slab_t *sp, void *from_buf) +{ + ASSERT(taskq_member(kmem_move_taskq, curthread)); + ASSERT(MUTEX_HELD(&cp->cache_lock)); + ASSERT(KMEM_SLAB_MEMBER(sp, from_buf)); + + if (!KMEM_SLAB_IS_PARTIAL(sp)) { + return; + } + + avl_remove(&cp->cache_partial_slabs, sp); + sp->slab_later_count = 0; + sp->slab_flags |= KMEM_SLAB_NOMOVE; + sp->slab_stuck_offset = KMEM_SLAB_OFFSET(sp, from_buf); + avl_add(&cp->cache_partial_slabs, sp); +} + +static void kmem_move_end(kmem_cache_t *, kmem_move_t *); + +/* + * The move callback takes two buffer addresses, the buffer to be moved, and a + * newly allocated and constructed buffer selected by kmem as the destination. + * It also takes the size of the buffer and an optional user argument specified + * at cache creation time. kmem guarantees that the buffer to be moved has not + * been unmapped by the virtual memory subsystem. Beyond that, it cannot + * guarantee the present whereabouts of the buffer to be moved, so it is up to + * the client to safely determine whether or not it is still using the buffer. + * The client must not free either of the buffers passed to the move callback, + * since kmem wants to free them directly to the slab layer. The client response + * tells kmem which of the two buffers to free: + * + * YES kmem frees the old buffer (the move was successful) + * NO kmem frees the new buffer, marks the slab of the old buffer + * non-reclaimable to avoid bothering the client again + * LATER kmem frees the new buffer, increments slab_later_count + * DONT_KNOW kmem frees the new buffer + * DONT_NEED kmem frees both the old buffer and the new buffer + * + * The pending callback argument now being processed contains both of the + * buffers (old and new) passed to the move callback function, the slab of the + * old buffer, and flags related to the move request, such as whether or not the + * system was desperate for memory. + * + * Slabs are not freed while there is a pending callback, but instead are kept + * on a deadlist, which is drained after the last callback completes. This means + * that slabs are safe to access until kmem_move_end(), no matter how many of + * their buffers have been freed. Once slab_refcnt reaches zero, it stays at + * zero for as long as the slab remains on the deadlist and until the slab is + * freed. + */ +static void +kmem_move_buffer(kmem_move_t *callback) +{ + kmem_cbrc_t response; + kmem_slab_t *sp = callback->kmm_from_slab; + kmem_cache_t *cp = sp->slab_cache; + boolean_t free_on_slab; + + ASSERT(taskq_member(kmem_move_taskq, curthread)); + ASSERT(MUTEX_NOT_HELD(&cp->cache_lock)); + ASSERT(KMEM_SLAB_MEMBER(sp, callback->kmm_from_buf)); + + /* + * The number of allocated buffers on the slab may have changed since we + * last checked the slab's reclaimability (when the pending move was + * enqueued), or the client may have responded NO when asked to move + * another buffer on the same slab. + */ + if (!kmem_slab_is_reclaimable(cp, sp, callback->kmm_flags)) { + kmem_slab_free(cp, callback->kmm_to_buf); + kmem_move_end(cp, callback); + return; + } + + /* + * Checking the slab layer is easy, so we might as well do that here + * in case we can avoid bothering the client. + */ + mutex_enter(&cp->cache_lock); + free_on_slab = (kmem_slab_allocated(cp, sp, + callback->kmm_from_buf) == NULL); + mutex_exit(&cp->cache_lock); + + if (free_on_slab) { + kmem_slab_free(cp, callback->kmm_to_buf); + kmem_move_end(cp, callback); + return; + } + + if (cp->cache_flags & KMF_BUFTAG) { + /* + * Make kmem_cache_alloc_debug() apply the constructor for us. + */ + if (kmem_cache_alloc_debug(cp, callback->kmm_to_buf, + KM_NOSLEEP, 1, caller()) != 0) { + kmem_move_end(cp, callback); + return; + } + } else if (cp->cache_constructor != NULL && + cp->cache_constructor(callback->kmm_to_buf, cp->cache_private, + KM_NOSLEEP) != 0) { + atomic_inc_64(&cp->cache_alloc_fail); + kmem_slab_free(cp, callback->kmm_to_buf); + kmem_move_end(cp, callback); + return; + } + + cp->cache_defrag->kmd_callbacks++; + cp->cache_defrag->kmd_thread = spl_current_thread(); + cp->cache_defrag->kmd_from_buf = callback->kmm_from_buf; + cp->cache_defrag->kmd_to_buf = callback->kmm_to_buf; + DTRACE_PROBE2(kmem__move__start, kmem_cache_t *, cp, kmem_move_t *, + callback); + + response = cp->cache_move(callback->kmm_from_buf, + callback->kmm_to_buf, cp->cache_bufsize, cp->cache_private); + + DTRACE_PROBE3(kmem__move__end, kmem_cache_t *, cp, kmem_move_t *, + callback, kmem_cbrc_t, response); + cp->cache_defrag->kmd_thread = NULL; + cp->cache_defrag->kmd_from_buf = NULL; + cp->cache_defrag->kmd_to_buf = NULL; + + if (response == KMEM_CBRC_YES) { + cp->cache_defrag->kmd_yes++; + kmem_slab_free_constructed(cp, callback->kmm_from_buf, B_FALSE); + /* slab safe to access until kmem_move_end() */ + if (sp->slab_refcnt == 0) + cp->cache_defrag->kmd_slabs_freed++; + mutex_enter(&cp->cache_lock); + kmem_slab_move_yes(cp, sp, callback->kmm_from_buf); + mutex_exit(&cp->cache_lock); + kmem_move_end(cp, callback); + return; + } + + switch (response) { + case KMEM_CBRC_NO: + cp->cache_defrag->kmd_no++; + mutex_enter(&cp->cache_lock); + kmem_slab_move_no(cp, sp, callback->kmm_from_buf); + mutex_exit(&cp->cache_lock); + break; + case KMEM_CBRC_LATER: + cp->cache_defrag->kmd_later++; + mutex_enter(&cp->cache_lock); + if (!KMEM_SLAB_IS_PARTIAL(sp)) { + mutex_exit(&cp->cache_lock); + break; + } + + if (++sp->slab_later_count >= KMEM_DISBELIEF) { + kmem_slab_move_no(cp, sp, + callback->kmm_from_buf); + } else if (!(sp->slab_flags & KMEM_SLAB_NOMOVE)) { + sp->slab_stuck_offset = KMEM_SLAB_OFFSET(sp, + callback->kmm_from_buf); + } + mutex_exit(&cp->cache_lock); + break; + case KMEM_CBRC_DONT_NEED: + cp->cache_defrag->kmd_dont_need++; + kmem_slab_free_constructed(cp, callback->kmm_from_buf, + B_FALSE); + if (sp->slab_refcnt == 0) + cp->cache_defrag->kmd_slabs_freed++; + mutex_enter(&cp->cache_lock); + kmem_slab_move_yes(cp, sp, callback->kmm_from_buf); + mutex_exit(&cp->cache_lock); + break; + case KMEM_CBRC_DONT_KNOW: + /* + * If we don't know if we can move this buffer or not, + * we'll just assume that we can't: if the buffer is + * in fact free, then it is sitting in one of the + * per-CPU magazines or in a full magazine in the depot + * layer. Either way, because defrag is induced in the + * same logic that reaps a cache, it's likely that full + * magazines will be returned to the system soon + * (thereby accomplishing what we're trying to + * accomplish here: return those magazines to their + * slabs). Given this, any work that we might do now to + * locate a buffer in a magazine is wasted (and + * expensive!) work; we bump a counter in this case and + * otherwise assume that we can't move it. + */ + cp->cache_defrag->kmd_dont_know++; + break; + default: + panic("'%s' (%p) unexpected move callback " + "response %d\n", cp->cache_name, (void *)cp, + response); + } + + kmem_slab_free_constructed(cp, callback->kmm_to_buf, B_FALSE); + kmem_move_end(cp, callback); +} + +/* Return B_FALSE if there is insufficient memory for the move request. */ +static boolean_t +kmem_move_begin(kmem_cache_t *cp, kmem_slab_t *sp, void *buf, int flags) +{ + void *to_buf; + avl_index_t index; + kmem_move_t *callback, *pending; + ulong_t n; + + ASSERT(taskq_member(kmem_taskq, curthread)); + ASSERT(MUTEX_NOT_HELD(&cp->cache_lock)); + ASSERT(sp->slab_flags & KMEM_SLAB_MOVE_PENDING); + + callback = kmem_cache_alloc(kmem_move_cache, KM_NOSLEEP); + + if (callback == NULL) + return (B_FALSE); + + callback->kmm_from_slab = sp; + callback->kmm_from_buf = buf; + callback->kmm_flags = flags; + + mutex_enter(&cp->cache_lock); + + n = avl_numnodes(&cp->cache_partial_slabs); + if ((n == 0) || ((n == 1) && !(flags & KMM_DEBUG))) { + mutex_exit(&cp->cache_lock); + kmem_cache_free(kmem_move_cache, callback); + return (B_TRUE); /* there is no need for the move request */ + } + + pending = avl_find(&cp->cache_defrag->kmd_moves_pending, buf, &index); + if (pending != NULL) { + /* + * If the move is already pending and we're desperate now, + * update the move flags. + */ + if (flags & KMM_DESPERATE) { + pending->kmm_flags |= KMM_DESPERATE; + } + mutex_exit(&cp->cache_lock); + kmem_cache_free(kmem_move_cache, callback); + return (B_TRUE); + } + + to_buf = kmem_slab_alloc_impl(cp, avl_first(&cp->cache_partial_slabs), + B_FALSE); + callback->kmm_to_buf = to_buf; + avl_insert(&cp->cache_defrag->kmd_moves_pending, callback, index); + + mutex_exit(&cp->cache_lock); + + if (!taskq_dispatch(kmem_move_taskq, (task_func_t *)kmem_move_buffer, + callback, TQ_NOSLEEP)) { + mutex_enter(&cp->cache_lock); + avl_remove(&cp->cache_defrag->kmd_moves_pending, callback); + mutex_exit(&cp->cache_lock); + kmem_slab_free(cp, to_buf); + kmem_cache_free(kmem_move_cache, callback); + return (B_FALSE); + } + + return (B_TRUE); +} + +static void +kmem_move_end(kmem_cache_t *cp, kmem_move_t *callback) +{ + avl_index_t index; + + ASSERT(cp->cache_defrag != NULL); + ASSERT(taskq_member(kmem_move_taskq, curthread)); + ASSERT(MUTEX_NOT_HELD(&cp->cache_lock)); + + mutex_enter(&cp->cache_lock); + VERIFY(avl_find(&cp->cache_defrag->kmd_moves_pending, + callback->kmm_from_buf, &index) != NULL); + avl_remove(&cp->cache_defrag->kmd_moves_pending, callback); + if (avl_is_empty(&cp->cache_defrag->kmd_moves_pending)) { + list_t *deadlist = &cp->cache_defrag->kmd_deadlist; + kmem_slab_t *sp; + + /* + * The last pending move completed. Release all slabs + * from the front of the dead list except for any slab + * at the tail that needs to be released from the context + * of kmem_move_buffers(). kmem deferred unmapping the + * buffers on these slabs in order to guarantee that + * buffers passed to the move callback have been touched + * only by kmem or by the client itself. + */ + while ((sp = list_remove_head(deadlist)) != NULL) { + if (sp->slab_flags & KMEM_SLAB_MOVE_PENDING) { + list_insert_tail(deadlist, sp); + break; + } + cp->cache_defrag->kmd_deadcount--; + cp->cache_slab_destroy++; + mutex_exit(&cp->cache_lock); + kmem_slab_destroy(cp, sp); + mutex_enter(&cp->cache_lock); + } + } + mutex_exit(&cp->cache_lock); + kmem_cache_free(kmem_move_cache, callback); +} + +/* + * Move buffers from least used slabs first by scanning backwards from the end + * of the partial slab list. Scan at most max_scan candidate slabs and move + * buffers from at most max_slabs slabs (0 for all partial slabs in both cases). + * If desperate to reclaim memory, move buffers from any partial slab, otherwise + * skip slabs with a ratio of allocated buffers at or above the current + * threshold. Return the number of unskipped slabs (at most max_slabs, -1 if the + * scan is aborted) so that the caller can adjust the reclaimability threshold + * depending on how many reclaimable slabs it finds. + * + * kmem_move_buffers() drops and reacquires cache_lock every time it issues a + * move request, since it is not valid for kmem_move_begin() to call + * kmem_cache_alloc() or taskq_dispatch() with cache_lock held. + */ +static int +kmem_move_buffers(kmem_cache_t *cp, size_t max_scan, size_t max_slabs, + int flags) +{ + kmem_slab_t *sp; + void *buf; + int i, j; /* slab index, buffer index */ + int s; /* reclaimable slabs */ + int b; /* allocated (movable) buffers on reclaimable slab */ + boolean_t success; + int refcnt; + int nomove; + + ASSERT(taskq_member(kmem_taskq, curthread)); + ASSERT(MUTEX_HELD(&cp->cache_lock)); + ASSERT(kmem_move_cache != NULL); + ASSERT(cp->cache_move != NULL && cp->cache_defrag != NULL); + ASSERT((flags & KMM_DEBUG) ? !avl_is_empty(&cp->cache_partial_slabs) : + avl_numnodes(&cp->cache_partial_slabs) > 1); + + if (kmem_move_blocked) { + return (0); + } + + if (kmem_move_fulltilt) { + flags |= KMM_DESPERATE; + } + + if (max_scan == 0 || (flags & KMM_DESPERATE)) { + /* + * Scan as many slabs as needed to find the desired number of + * candidate slabs. + */ + max_scan = (size_t)-1; + } + + if (max_slabs == 0 || (flags & KMM_DESPERATE)) { + /* Find as many candidate slabs as possible. */ + max_slabs = (size_t)-1; + } + + sp = avl_last(&cp->cache_partial_slabs); + ASSERT(KMEM_SLAB_IS_PARTIAL(sp)); + for (i = 0, s = 0; (i < max_scan) && (s < max_slabs) && (sp != NULL) && + ((sp != avl_first(&cp->cache_partial_slabs)) || + (flags & KMM_DEBUG)); + sp = AVL_PREV(&cp->cache_partial_slabs, sp), i++) { + + if (!kmem_slab_is_reclaimable(cp, sp, flags)) { + continue; + } + s++; + + /* Look for allocated buffers to move. */ + for (j = 0, b = 0, buf = sp->slab_base; + (j < sp->slab_chunks) && (b < sp->slab_refcnt); + buf = (((char *)buf) + cp->cache_chunksize), j++) { + + if (kmem_slab_allocated(cp, sp, buf) == NULL) { + continue; + } + + b++; + + /* + * Prevent the slab from being destroyed while we drop + * cache_lock and while the pending move is not yet + * registered. Flag the pending move while + * kmd_moves_pending may still be empty, since we can't + * yet rely on a non-zero pending move count to prevent + * the slab from being destroyed. + */ + ASSERT(!(sp->slab_flags & KMEM_SLAB_MOVE_PENDING)); + sp->slab_flags |= KMEM_SLAB_MOVE_PENDING; + /* + * Recheck refcnt and nomove after reacquiring the lock, + * since these control the order of partial slabs, and + * we want to know if we can pick up the scan where we + * left off. + */ + refcnt = sp->slab_refcnt; + nomove = (sp->slab_flags & KMEM_SLAB_NOMOVE); + mutex_exit(&cp->cache_lock); + + success = kmem_move_begin(cp, sp, buf, flags); + + /* + * Now, before the lock is reacquired, kmem could + * process all pending move requests and purge the + * deadlist, so that upon reacquiring the lock, sp has + * been remapped. Or, the client may free all the + * objects on the slab while the pending moves are still + * on the taskq. Therefore, the KMEM_SLAB_MOVE_PENDING + * flag causes the slab to be put at the end of the + * deadlist and prevents it from being destroyed, since + * we plan to destroy it here after reacquiring the + * lock. + */ + mutex_enter(&cp->cache_lock); + ASSERT(sp->slab_flags & KMEM_SLAB_MOVE_PENDING); + sp->slab_flags &= ~KMEM_SLAB_MOVE_PENDING; + + if (sp->slab_refcnt == 0) { + list_t *deadlist = + &cp->cache_defrag->kmd_deadlist; + list_remove(deadlist, sp); + + if (!avl_is_empty( + &cp->cache_defrag->kmd_moves_pending)) { + /* + * A pending move makes it unsafe to + * destroy the slab, because even though + * the move is no longer needed, the + * context where that is determined + * requires the slab to exist. + * Fortunately, a pending move also + * means we don't need to destroy the + * slab here, since it will get + * destroyed along with any other slabs + * on the deadlist after the last + * pending move completes. + */ + list_insert_head(deadlist, sp); + return (-1); + } + + /* + * Destroy the slab now if it was completely + * freed while we dropped cache_lock and there + * are no pending moves. Since slab_refcnt + * cannot change once it reaches zero, no new + * pending moves from that slab are possible. + */ + cp->cache_defrag->kmd_deadcount--; + cp->cache_slab_destroy++; + mutex_exit(&cp->cache_lock); + kmem_slab_destroy(cp, sp); + mutex_enter(&cp->cache_lock); + /* + * Since we can't pick up the scan where we left + * off, abort the scan and say nothing about the + * number of reclaimable slabs. + */ + return (-1); + } + + if (!success) { + /* + * Abort the scan if there is not enough memory + * for the request and say nothing about the + * number of reclaimable slabs. + */ + return (-1); + } + + /* + * The slab's position changed while the lock was + * dropped, so we don't know where we are in the + * sequence any more. + */ + if (sp->slab_refcnt != refcnt) { + /* + * If this is a KMM_DEBUG move, the slab_refcnt + * may have changed because we allocated a + * destination buffer on the same slab. In that + * case, we're not interested in counting it. + */ + return (-1); + } + if ((sp->slab_flags & KMEM_SLAB_NOMOVE) != nomove) + return (-1); + + /* + * Generating a move request allocates a destination + * buffer from the slab layer, bumping the first partial + * slab if it is completely allocated. If the current + * slab becomes the first partial slab as a result, we + * can't continue to scan backwards. + * + * If this is a KMM_DEBUG move and we allocated the + * destination buffer from the last partial slab, then + * the buffer we're moving is on the same slab and our + * slab_refcnt has changed, causing us to return before + * reaching here if there are no partial slabs left. + */ + ASSERT(!avl_is_empty(&cp->cache_partial_slabs)); + if (sp == avl_first(&cp->cache_partial_slabs)) { + /* + * We're not interested in a second KMM_DEBUG + * move. + */ + goto end_scan; + } + } + } +end_scan: + + return (s); +} + +typedef struct kmem_move_notify_args { + kmem_cache_t *kmna_cache; + void *kmna_buf; +} kmem_move_notify_args_t; + +static void +kmem_cache_move_notify_task(void *arg) +{ + kmem_move_notify_args_t *args = arg; + kmem_cache_t *cp = args->kmna_cache; + void *buf = args->kmna_buf; + kmem_slab_t *sp; + + ASSERT(taskq_member(kmem_taskq, curthread)); + ASSERT(list_link_active(&cp->cache_link)); + + zfs_kmem_free(args, sizeof (kmem_move_notify_args_t)); + mutex_enter(&cp->cache_lock); + sp = kmem_slab_allocated(cp, NULL, buf); + + /* Ignore the notification if the buffer is no longer allocated. */ + if (sp == NULL) { + mutex_exit(&cp->cache_lock); + return; + } + + /* Ignore the notification if there's no reason to move the buffer. */ + if (avl_numnodes(&cp->cache_partial_slabs) > 1) { + /* + * So far the notification is not ignored. Ignore the + * notification if the slab is not marked by an earlier refusal + * to move a buffer. + */ + if (!(sp->slab_flags & KMEM_SLAB_NOMOVE) && + (sp->slab_later_count == 0)) { + mutex_exit(&cp->cache_lock); + return; + } + + kmem_slab_move_yes(cp, sp, buf); + ASSERT(!(sp->slab_flags & KMEM_SLAB_MOVE_PENDING)); + sp->slab_flags |= KMEM_SLAB_MOVE_PENDING; + mutex_exit(&cp->cache_lock); + /* see kmem_move_buffers() about dropping the lock */ + (void) kmem_move_begin(cp, sp, buf, KMM_NOTIFY); + mutex_enter(&cp->cache_lock); + ASSERT(sp->slab_flags & KMEM_SLAB_MOVE_PENDING); + sp->slab_flags &= ~KMEM_SLAB_MOVE_PENDING; + if (sp->slab_refcnt == 0) { + list_t *deadlist = &cp->cache_defrag->kmd_deadlist; + list_remove(deadlist, sp); + + if (!avl_is_empty( + &cp->cache_defrag->kmd_moves_pending)) { + list_insert_head(deadlist, sp); + mutex_exit(&cp->cache_lock); + return; + } + + cp->cache_defrag->kmd_deadcount--; + cp->cache_slab_destroy++; + mutex_exit(&cp->cache_lock); + kmem_slab_destroy(cp, sp); + return; + } + } else { + kmem_slab_move_yes(cp, sp, buf); + } + mutex_exit(&cp->cache_lock); +} + +void +kmem_cache_move_notify(kmem_cache_t *cp, void *buf) +{ + kmem_move_notify_args_t *args; + + args = zfs_kmem_alloc(sizeof (kmem_move_notify_args_t), KM_NOSLEEP); + if (args != NULL) { + args->kmna_cache = cp; + args->kmna_buf = buf; + if (!taskq_dispatch(kmem_taskq, + (task_func_t *)kmem_cache_move_notify_task, args, + TQ_NOSLEEP)) + zfs_kmem_free(args, sizeof (kmem_move_notify_args_t)); + } +} + +static void +kmem_cache_defrag(kmem_cache_t *cp) +{ + size_t n; + + ASSERT(cp->cache_defrag != NULL); + + mutex_enter(&cp->cache_lock); + n = avl_numnodes(&cp->cache_partial_slabs); + if (n > 1) { + /* kmem_move_buffers() drops and reacquires cache_lock */ + cp->cache_defrag->kmd_defrags++; + (void) kmem_move_buffers(cp, n, 0, KMM_DESPERATE); + } + mutex_exit(&cp->cache_lock); +} + +/* Is this cache above the fragmentation threshold? */ +static boolean_t +kmem_cache_frag_threshold(kmem_cache_t *cp, uint64_t nfree) +{ + /* + * nfree kmem_frag_numer + * ------------------ > --------------- + * cp->cache_buftotal kmem_frag_denom + */ + return ((nfree * kmem_frag_denom) > + (cp->cache_buftotal * kmem_frag_numer)); +} + +static boolean_t +kmem_cache_is_fragmented(kmem_cache_t *cp, boolean_t *doreap) +{ + boolean_t fragmented; + uint64_t nfree; + + ASSERT(MUTEX_HELD(&cp->cache_lock)); + *doreap = B_FALSE; + + if (kmem_move_fulltilt) { + if (avl_numnodes(&cp->cache_partial_slabs) > 1) { + return (B_TRUE); + } + } else { + if ((cp->cache_complete_slab_count + avl_numnodes( + &cp->cache_partial_slabs)) < kmem_frag_minslabs) { + return (B_FALSE); + } + } + + nfree = cp->cache_bufslab; + fragmented = ((avl_numnodes(&cp->cache_partial_slabs) > 1) && + kmem_cache_frag_threshold(cp, nfree)); + + /* + * Free buffers in the magazine layer appear allocated from the point of + * view of the slab layer. We want to know if the slab layer would + * appear fragmented if we included free buffers from magazines that + * have fallen out of the working set. + */ + if (!fragmented) { + long reap; + + mutex_enter(&cp->cache_depot_lock); + reap = MIN(cp->cache_full.ml_reaplimit, cp->cache_full.ml_min); + reap = MIN(reap, cp->cache_full.ml_total); + mutex_exit(&cp->cache_depot_lock); + + nfree += ((uint64_t)reap * cp->cache_magtype->mt_magsize); + if (kmem_cache_frag_threshold(cp, nfree)) { + *doreap = B_TRUE; + } + } + + return (fragmented); +} + +/* Called periodically from kmem_taskq */ +static void +kmem_cache_scan(kmem_cache_t *cp) +{ + boolean_t reap = B_FALSE; + kmem_defrag_t *kmd; + + ASSERT(taskq_member(kmem_taskq, curthread)); + + mutex_enter(&cp->cache_lock); + + kmd = cp->cache_defrag; + if (kmd->kmd_consolidate > 0) { + kmd->kmd_consolidate--; + mutex_exit(&cp->cache_lock); + kmem_cache_reap(cp); + return; + } + + if (kmem_cache_is_fragmented(cp, &reap)) { + size_t slabs_found; + + /* + * Consolidate reclaimable slabs from the end of the partial + * slab list (scan at most kmem_reclaim_scan_range slabs to find + * reclaimable slabs). Keep track of how many candidate slabs we + * looked for and how many we actually found so we can adjust + * the definition of a candidate slab if we're having trouble + * finding them. + * + * kmem_move_buffers() drops and reacquires cache_lock. + */ + kmd->kmd_scans++; + slabs_found = kmem_move_buffers(cp, kmem_reclaim_scan_range, + kmem_reclaim_max_slabs, 0); + kmd->kmd_slabs_sought += kmem_reclaim_max_slabs; + kmd->kmd_slabs_found += slabs_found; + + if (++kmd->kmd_tries >= kmem_reclaim_scan_range) { + kmd->kmd_tries = 0; + + /* + * If we had difficulty finding candidate slabs in + * previous scans, adjust the threshold so that + * candidates are easier to find. + */ + if (kmd->kmd_slabs_found == kmd->kmd_slabs_sought) { + kmem_adjust_reclaim_threshold(kmd, -1); + } else if ((kmd->kmd_slabs_found * 2) < + kmd->kmd_slabs_sought) { + kmem_adjust_reclaim_threshold(kmd, 1); + } + kmd->kmd_slabs_sought = 0; + kmd->kmd_slabs_found = 0; + } + } else { + kmem_reset_reclaim_threshold(cp->cache_defrag); +#ifdef DEBUG + if (!avl_is_empty(&cp->cache_partial_slabs)) { + /* + * In a debug kernel we want the consolidator to + * run occasionally even when there is plenty of + * memory. + */ + uint16_t debug_rand; + + /* + * smd: note that this only gets called for the + * dnode cache because only the dnode cache has + * kmem_cache_set_move() applied to it + * brendon says move is voluntary and "tricky" + * the reason this is not called is because the source + * is kmem_cache_update(), that only calls this + * function (kmem_cache_scan()) + * if there is a move/defrag (same thing) associated + * with it so hoist some of this code up to to + * kmem_cache_update + */ + + (void) random_get_bytes((uint8_t *)&debug_rand, 2); + if (!kmem_move_noreap && + ((debug_rand % kmem_mtb_reap) == 0)) { + mutex_exit(&cp->cache_lock); + kmem_mtb_reap_count++; + return; + } else if ((debug_rand % kmem_mtb_move) == 0) { + kmd->kmd_scans++; + (void) kmem_move_buffers(cp, + kmem_reclaim_scan_range, 1, KMM_DEBUG); + } + } +#endif /* DEBUG */ + } + + mutex_exit(&cp->cache_lock); + +} + +// =============================================================== +// Status +// =============================================================== + + +size_t +kmem_size(void) +{ + return (total_memory); // smd +} + +// this is used in arc_reclaim_needed. if 1, reclaim is needed. +// returning 1 has the effect of throttling ARC, so be careful. +int +spl_vm_pool_low(void) +{ + bool m = spl_minimal_physmem_p_logic(); + + if (m) + return (0); + else + return (1); +} + +// =============================================================== +// String handling +// =============================================================== + +char * +kmem_strdup(const char *str) +{ + char *buf; + int len; + len = strlen(str) + 1; + buf = kmem_alloc(len, KM_SLEEP); + strlcpy(buf, str, len); + return (buf); +} + +void +kmem_strfree(char *str) +{ + zfs_kmem_free(str, strlen(str) + 1); +} + +char * +kvasprintf(const char *fmt, va_list ap) +{ + unsigned int len; + char *p; + va_list aq; + + va_copy(aq, ap); + len = vsnprintf(NULL, 0, fmt, aq); + va_end(aq); + p = zfs_kmem_alloc(len+1, KM_SLEEP); + if (!p) + return (NULL); + + vsnprintf(p, len+1, fmt, ap); + + return (p); +} + +char * +kmem_vasprintf(const char *fmt, va_list ap) +{ + va_list aq; + char *ptr; + + do { + va_copy(aq, ap); + ptr = kvasprintf(fmt, aq); + va_end(aq); + } while (ptr == NULL); + + return (ptr); +} + +char * +kmem_asprintf(const char *fmt, ...) +{ + va_list ap; + char *ptr; + + do { + va_start(ap, fmt); + ptr = kvasprintf(fmt, ap); + va_end(ap); + } while (ptr == NULL); + + return (ptr); +} + +char * +kmem_strstr(const char *in, const char *str) +{ + char c; + size_t len; + + c = *str++; + if (!c) + return ((char *)in); // Trivial empty string case + + len = strlen(str); + do { + char sc; + + do { + sc = *in++; + if (!sc) + return ((char *)0); + } while (sc != c); + } while (strncmp(in, str, len) != 0); + + return ((char *)(in - 1)); +} + +/* + * kmem_scnprintf() will return the number of characters that it would have + * printed whenever it is limited by value of the size variable, rather than + * the number of characters that it did print. This can cause misbehavior on + * subsequent uses of the return value, so we define a safe version that will + * return the number of characters actually printed, minus the NULL format + * character. Subsequent use of this by the safe string functions is safe + * whether it is snprintf(), strlcat() or strlcpy(). + */ +int +kmem_scnprintf(char *restrict str, size_t size, const char *restrict fmt, ...) +{ + int n; + va_list ap; + + /* Make the 0 case a no-op so that we do not return -1 */ + if (size == 0) + return (0); + + va_start(ap, fmt); + n = vsnprintf(str, size, fmt, ap); + va_end(ap); + + if (n >= size) + n = size - 1; + + return (n); +} + +// suppress timer and related logic for this kmem cache can live here +// three new per-kmem-cache stats: counters: non-vba-success non-vba-fail; +// flag: arc_no_grow +// from zfs/include/sys/spa.h + +#define SPA_MINBLOCKSHIFT 9 +#define SPA_MAXBLOCKSHIFT 24 +#define SPA_MINBLOCKSIZE (1ULL << SPA_MINBLOCKSHIFT) +#define SPA_MAXBLOCKSIZE (1ULL << SPA_MAXBLOCKSHIFT) + +typedef struct { + _Atomic(kmem_cache_t *)cp_metadata; + _Atomic(kmem_cache_t *)cp_filedata; + uint16_t pointed_to; + _Atomic int64_t suppress_count; + _Atomic uint64_t last_bumped; +} ksupp_t; + +typedef struct { + ksupp_t *ks_entry; +} iksupp_t; + +ksupp_t ksvec[SPA_MAXBLOCKSIZE >> SPA_MINBLOCKSHIFT] = + { { NULL, NULL, false, 0, 0 } }; +iksupp_t iksvec[SPA_MAXBLOCKSIZE >> SPA_MINBLOCKSHIFT] = + { { NULL } }; + +/* + * return true if the reclaim thread should be awakened + * because we do not have enough memory on hand + */ +boolean_t +spl_arc_reclaim_needed(const size_t bytes, kmem_cache_t **zp) +{ + + /* + * fast path: + * if our argument is 0, then do the equivalent of + * if (arc_available_memory() < 0) return (B_TRUE); + * which is traditional arc.c appraoch + * so we can arc_reclaim_needed() -> spl_arc_reclaim_needed(0) + * if we desire. + */ + if (bytes == 0 && spl_free < 0) { + return (B_TRUE); + } + + // copy some code from zio_buf_alloc() + size_t c = (bytes - 1) >> SPA_MINBLOCKSHIFT; + VERIFY3U(c, <, SPA_MAXBLOCKSIZE >> SPA_MINBLOCKSHIFT); + + // if there is free memory in the kmem cache slab layer + // then we do not have to reclaim + + if (zp[c]->cache_bufslab > 1) { + if (spl_free < 0) + atomic_inc_64(&spl_arc_reclaim_avoided); + return (B_FALSE); + } + + extern uint64_t vmem_xnu_useful_bytes_free(void); + const uint64_t min_threshold = 64ULL*1024ULL*1024ULL; + const uint64_t pm_pct = real_total_memory >> 8; + const uint64_t high_threshold = MAX(min_threshold, (uint64_t)pm_pct); + const uint64_t low_threshold = bytes; + + const uint64_t f = vmem_xnu_useful_bytes_free(); + + if (f <= low_threshold) { + return (B_TRUE); + } else if (f > high_threshold) { + if (spl_free < 0) + atomic_inc_64(&spl_arc_reclaim_avoided); + return (B_FALSE); + } + + if (spl_free < 0) { + return (B_TRUE); + } else { + return (B_FALSE); + } +} + +/* small auxiliary function since we do not export struct kmem_cache to zfs */ +size_t +kmem_cache_bufsize(kmem_cache_t *cp) +{ + return (cp->cache_bufsize); +} + +/* + * check that we would not have KMERR_BADCACHE error in the event + * we did kmem_cache_free(cp, buf) in a DEBUG setting + * + * returns: NULL if the buf is not found in any cache + * cparg if the buf is found in cparg + * a pointer to the cache the buf is found in, if not cparg + */ + +kmem_cache_t * +kmem_cache_buf_in_cache(kmem_cache_t *cparg, void *bufarg) +{ + kmem_cache_t *cp = cparg; + kmem_slab_t *sp; + void *buf = bufarg; + + sp = kmem_findslab(cp, buf); + if (sp == NULL) { + for (cp = list_tail(&kmem_caches); cp != NULL; + cp = list_prev(&kmem_caches, cp)) { + if ((sp = kmem_findslab(cp, buf)) != NULL) + break; + } + } + + if (sp == NULL) { + printf("SPL: %s: KMERR_BADADDR orig cache = %s\n", + __func__, cparg->cache_name); + return (NULL); + } + + if (cp == NULL) { + printf("SPL: %s: ERROR cp == NULL; cparg == %s", + __func__, cparg->cache_name); + return (NULL); + } + + if (cp != cparg) { + printf("SPL: %s: KMERR_BADCACHE arg cache = %s but found " + "in %s instead\n", + __func__, cparg->cache_name, cp->cache_name); + return (cp); + } + + ASSERT(cp == cparg); + + return (cp); +} diff --git a/module/os/macos/spl/spl-kstat.c b/module/os/macos/spl/spl-kstat.c new file mode 100644 index 000000000000..29aea2d06be3 --- /dev/null +++ b/module/os/macos/spl/spl-kstat.c @@ -0,0 +1,1294 @@ +/* + * 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 Jorgen Lundman + * Copyright (C) 2014 Brendon Humphrey + * Copyright (C) 2023 Sean Doran + * + */ + +/* + * Provides an implementation of kstat that is backed by OSX sysctls. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Statically declared toplevel OID that all kstats + * will hang off. + */ +struct sysctl_oid_list sysctl__kstat_children; +SYSCTL_DECL(_kstat); +SYSCTL_NODE(, OID_AUTO, kstat, CTLFLAG_RW, 0, "kstat tree"); + +/* + * Sysctl node tree structure. + * + * These are wired into the OSX sysctl structure + * and also stored a list/tree/whatever for easy + * location and destruction at shutdown time. + */ +typedef struct sysctl_tree_node { + char tn_kstat_name[KSTAT_STRLEN + 1]; + struct sysctl_oid_list tn_children; + struct sysctl_oid tn_oid; + struct sysctl_tree_node *tn_next; +} sysctl_tree_node_t; + +/* + * Each named kstats consists of one or more named + * fields which are implemented as OIDs parented + * off the kstat OID. + * + * To implement the kstat interface, we need to be able + * to call the update() function on the kstat to + * allow the owner to populate the kstat values from + * internal data. + * + * To do this we need the address of the kstat_named_t + * which contains the data value, and the owning kstat_t. + * + * OIDs allow a single void* user argument, so we will + * use a structure that contains both values and + * point to that. + */ +typedef struct sysctl_leaf { + kstat_t *l_ksp; + kstat_named_t *l_named; + struct sysctl_oid l_oid; /* kstats are backed w/sysctl */ + char l_name[KSTAT_STRLEN + 1]; /* Name of the related sysctl */ + int l_oid_registered; /* !0 = registered */ +} sysctl_leaf_t; + +/* + * Extended kstat structure -- for internal use only. + */ +typedef struct ekstat { + kstat_t e_ks; /* the kstat itself */ + size_t e_size; /* total allocation size */ + kthread_t *e_owner; /* thread holding this kstat */ + kcondvar_t e_cv; /* wait for owner == NULL */ + /* contains the named values from the kstat */ + struct sysctl_oid_list e_children; + struct sysctl_oid e_oid; /* the kstat is itself an OID */ + /* array of OIDs that implement the children */ + sysctl_leaf_t *e_vals; + uint64_t e_num_vals; /* size of e_vals array */ +} ekstat_t; + +struct sysctl_tree_node *tree_nodes = 0; +struct sysctl_oid *e_sysctl = 0; + +#define SBUF_SETFLAG(s, f) do { (s)->s_flags |= (f); } while (0) +#define SBUF_CLEARFLAG(s, f) do { (s)->s_flags &= ~(f); } while (0) +#define SBUF_ISDYNAMIC(s) ((s)->s_flags & SBUF_DYNAMIC) +#define SBUF_ISDYNSTRUCT(s) ((s)->s_flags & SBUF_DYNSTRUCT) +#define SBUF_HASOVERFLOWED(s) ((s)->s_flags & SBUF_OVERFLOWED) +#define SBUF_HASROOM(s) ((s)->s_len < (s)->s_size - 1) +#define SBUF_FREESPACE(s) ((s)->s_size - (s)->s_len - 1) +#define SBUF_CANEXTEND(s) ((s)->s_flags & SBUF_AUTOEXTEND) +#define SBUF_ISFINISHED(s) ((s)->s_flags & SBUF_FINISHED) + +#define SBUF_MINEXTENDSIZE 16 /* Should be power of 2. */ +#define SBUF_MAXEXTENDSIZE PAGE_SIZE +#define SBUF_MAXEXTENDINCR PAGE_SIZE + +#define SBUF_INCLUDENUL 0x00000002 /* FBSD: nulterm byte is counted in len */ +#define SBUF_NULINCLUDED(s) ((s)->s_flags & SBUF_INCLUDENUL) + +void +sbuf_finish(struct sbuf *s) +{ + s->s_buf[s->s_len] = '\0'; + if (SBUF_NULINCLUDED(s)) + s->s_len++; + + SBUF_CLEARFLAG(s, SBUF_OVERFLOWED); + SBUF_SETFLAG(s, SBUF_FINISHED); +} + +char * +sbuf_data(struct sbuf *s) +{ + return (s->s_buf); +} + +int +sbuf_len(struct sbuf *s) +{ + if (SBUF_HASOVERFLOWED(s)) { + return (-1); + } + /* If finished, nulterm is already in len, else add one. */ + if (SBUF_NULINCLUDED(s) && !SBUF_ISFINISHED(s)) + return (s->s_len + 1); + return (s->s_len); +} + +void +sbuf_delete(struct sbuf *s) +{ + int isdyn; + if (SBUF_ISDYNAMIC(s)) { + IOFreeAligned(s->s_buf, s->s_size); + } + isdyn = SBUF_ISDYNSTRUCT(s); + memset(s, 0, sizeof (*s)); + if (isdyn) { + IOFreeType(s, struct sbuf); + } +} + +static int +sbuf_extendsize(int size) +{ + int newsize; + + newsize = SBUF_MINEXTENDSIZE; + while (newsize < size) { + if (newsize < (int)SBUF_MAXEXTENDSIZE) { + newsize *= 2; + } else { + newsize += SBUF_MAXEXTENDINCR; + } + } + + return (newsize); +} + +static int +sbuf_extend(struct sbuf *s, int addlen) +{ + char *newbuf; + int newsize; + + if (!SBUF_CANEXTEND(s)) { + return (-1); + } + + newsize = sbuf_extendsize(s->s_size + addlen); + newbuf = (char *)IOMallocAligned(newsize, _Alignof(char)); + if (newbuf == NULL) { + return (-1); + } + memcpy(newbuf, s->s_buf, s->s_size); + if (SBUF_ISDYNAMIC(s)) { + IOFreeAligned(s->s_buf, s->s_size); + } else { + SBUF_SETFLAG(s, SBUF_DYNAMIC); + } + s->s_buf = newbuf; + s->s_size = newsize; + return (0); +} + +struct sbuf * +sbuf_new(struct sbuf *s, char *buf, int length, int flags) +{ + flags &= SBUF_USRFLAGMSK; + if (s == NULL) { + s = IOMallocType(struct sbuf); + if (s == NULL) { + return (NULL); + } + memset(s, 0, sizeof (*s)); + s->s_flags = flags; + SBUF_SETFLAG(s, SBUF_DYNSTRUCT); + } else { + memset(s, 0, sizeof (*s)); + s->s_flags = flags; + } + s->s_size = length; + if (buf) { + s->s_buf = buf; + return (s); + } + if (flags & SBUF_AUTOEXTEND) { + s->s_size = sbuf_extendsize(s->s_size); + } + s->s_buf = (char *)IOMallocAligned(s->s_size, _Alignof(char)); + if (s->s_buf == NULL) { + if (SBUF_ISDYNSTRUCT(s)) { + IOFreeType(s, struct sbuf); + } + return (NULL); + } + SBUF_SETFLAG(s, SBUF_DYNAMIC); + return (s); +} + +int +sbuf_vprintf(struct sbuf *s, const char *fmt, va_list ap) +{ + __builtin_va_list ap_copy; /* XXX tduffy - blame on him */ + int len; + + if (SBUF_HASOVERFLOWED(s)) { + return (-1); + } + + do { + va_copy(ap_copy, ap); + len = vsnprintf(&s->s_buf[s->s_len], SBUF_FREESPACE(s) + 1, + fmt, ap_copy); + va_end(ap_copy); + } while (len > SBUF_FREESPACE(s) && + sbuf_extend(s, len - SBUF_FREESPACE(s)) == 0); + s->s_len += min(len, SBUF_FREESPACE(s)); + if (!SBUF_HASROOM(s) && !SBUF_CANEXTEND(s)) { + SBUF_SETFLAG(s, SBUF_OVERFLOWED); + } + + if (SBUF_HASOVERFLOWED(s)) { + return (-1); + } + return (0); +} + +int +sbuf_printf(struct sbuf *s, const char *fmt, ...) +{ + va_list ap; + int result; + + va_start(ap, fmt); + result = sbuf_vprintf(s, fmt, ap); + va_end(ap); + return (result); +} + +static void +kstat_set_string(char *dst, const char *src) +{ + memset(dst, 0, KSTAT_STRLEN); + (void) strlcpy(dst, src, KSTAT_STRLEN); +} + +static struct sysctl_oid * +get_oid_with_name(struct sysctl_oid_list *list, char *name) +{ + struct sysctl_oid *oidp; + + SLIST_FOREACH(oidp, list, oid_link) { + if (strcmp(name, oidp->oid_name) == 0) { + return (oidp); + } + } + + return (0); +} + +static void +init_oid_tree_node(struct sysctl_oid_list *parent, char *name, + sysctl_tree_node_t *node) +{ + strlcpy(node->tn_kstat_name, name, KSTAT_STRLEN); + + node->tn_oid.oid_parent = parent; + node->tn_oid.oid_link.sle_next = 0; + node->tn_oid.oid_number = OID_AUTO; + node->tn_oid.oid_arg2 = 0; + node->tn_oid.oid_name = &node->tn_kstat_name[0]; + node->tn_oid.oid_descr = ""; + node->tn_oid.oid_version = SYSCTL_OID_VERSION; + node->tn_oid.oid_refcnt = 0; + node->tn_oid.oid_handler = 0; + node->tn_oid.oid_kind = CTLTYPE_NODE|CTLFLAG_RW|CTLFLAG_OID2; + node->tn_oid.oid_fmt = "N"; + node->tn_oid.oid_arg1 = (void*)(&node->tn_children); + + sysctl_register_oid(&node->tn_oid); + + node->tn_next = tree_nodes; + tree_nodes = node; +} + +static struct sysctl_oid_list * +get_kstat_parent(struct sysctl_oid_list *root, char *module_name, + char *class_name) +{ + struct sysctl_oid *the_module = 0; + struct sysctl_oid *the_class = 0; + sysctl_tree_node_t *new_node = 0; + struct sysctl_oid_list *container = root; + + /* + * Locate/create the module + */ + the_module = get_oid_with_name(root, module_name); + + if (!the_module) { + new_node = IOMalloc(sizeof (sysctl_tree_node_t)); + memset(new_node, 0, sizeof (sysctl_tree_node_t)); + init_oid_tree_node(root, module_name, new_node); + the_module = &new_node->tn_oid; + } + + /* + * Locate/create the class + */ + container = the_module->oid_arg1; + the_class = get_oid_with_name(container, class_name); + + if (!the_class) { + new_node = IOMalloc(sizeof (sysctl_tree_node_t)); + memset(new_node, 0, sizeof (sysctl_tree_node_t)); + init_oid_tree_node(container, class_name, new_node); + the_class = &new_node->tn_oid; + } + + container = the_class->oid_arg1; + return (container); +} + +struct sbuf * +sbuf_new_for_sysctl(struct sbuf *s, char *buf, int length, + struct sysctl_req *req) +{ + /* Supply a default buffer size if none given. */ + if (buf == NULL && length == 0) + length = 64; + s = sbuf_new(s, buf, length, SBUF_FIXEDLEN | SBUF_INCLUDENUL); + /* sbuf_set_drain(s, sbuf_sysctl_drain, req); */ + return (s); +} + +static int +kstat_default_update(kstat_t *ksp, int rw) +{ + ASSERT(ksp != NULL); + + if (rw == KSTAT_WRITE) + return (EACCES); + + return (0); +} + +static int +kstat_resize_raw(kstat_t *ksp) +{ + if (ksp->ks_raw_bufsize == KSTAT_RAW_MAX) + return (ENOMEM); + + IOFree(ksp->ks_raw_buf, ksp->ks_raw_bufsize); + ksp->ks_raw_bufsize = MIN(ksp->ks_raw_bufsize * 2, KSTAT_RAW_MAX); + ksp->ks_raw_buf = IOMalloc(ksp->ks_raw_bufsize); + + return (0); +} + +static void * +kstat_raw_default_addr(kstat_t *ksp, loff_t n) +{ + if (n == 0) + return (ksp->ks_data); + return (NULL); +} + +#define HD_COLUMN_MASK 0xff +#define HD_DELIM_MASK 0xff00 +#define HD_OMIT_COUNT (1 << 16) +#define HD_OMIT_HEX (1 << 17) +#define HD_OMIT_CHARS (1 << 18) + +void +sbuf_hexdump(struct sbuf *sb, const void *ptr, int length, const char *hdr, + int flags) +{ + int i, j, k; + int cols; + const unsigned char *cp; + char delim; + + if ((flags & HD_DELIM_MASK) != 0) + delim = (flags & HD_DELIM_MASK) >> 8; + else + delim = ' '; + + if ((flags & HD_COLUMN_MASK) != 0) + cols = flags & HD_COLUMN_MASK; + else + cols = 16; + + cp = ptr; + for (i = 0; i < length; i += cols) { + if (hdr != NULL) + sbuf_printf(sb, "%s", hdr); + + if ((flags & HD_OMIT_COUNT) == 0) + sbuf_printf(sb, "%04x ", i); + + if ((flags & HD_OMIT_HEX) == 0) { + for (j = 0; j < cols; j++) { + k = i + j; + if (k < length) + sbuf_printf(sb, "%c%02x", delim, cp[k]); + else + sbuf_printf(sb, " "); + } + } + + if ((flags & HD_OMIT_CHARS) == 0) { + sbuf_printf(sb, " |"); + for (j = 0; j < cols; j++) { + k = i + j; + if (k >= length) + sbuf_printf(sb, " "); + else if (cp[k] >= ' ' && cp[k] <= '~') + sbuf_printf(sb, "%c", cp[k]); + else + sbuf_printf(sb, "."); + } + sbuf_printf(sb, "|"); + } + sbuf_printf(sb, "\n"); + } +} + +static int +kstat_handle_raw SYSCTL_HANDLER_ARGS +{ + struct sbuf *sb; + void *data; + kstat_t *ksp = arg1; + void *(*addr_op)(kstat_t *ksp, loff_t index); + int n, has_header, rc = 0; + + /* Check if this RAW has 2 entries, the second for verbose */ + ekstat_t *e = (ekstat_t *)ksp; + if (e->e_num_vals == 2) { + sysctl_leaf_t *val = &e->e_vals[1]; + if (strncmp("verbose", val->l_name, KSTAT_STRLEN) == 0) { + int verbose = 0; + if (val->l_oid.oid_arg1 != NULL) + verbose = *((int *)val->l_oid.oid_arg1); + if (verbose == 0) + return (0); + } + } + + + sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND); + if (sb == NULL) + return (ENOMEM); + + if (ksp->ks_raw_ops.addr) + addr_op = ksp->ks_raw_ops.addr; + else + addr_op = kstat_raw_default_addr; + + VERIFY3P(ksp->ks_lock, !=, NULL); + mutex_enter(ksp->ks_lock); + + /* Update the aggsums before reading */ + (void) ksp->ks_update(ksp, KSTAT_READ); + + ksp->ks_raw_bufsize = PAGE_SIZE; + ksp->ks_raw_buf = IOMallocAligned(PAGE_SIZE, PAGE_SIZE); + + n = 0; + has_header = (ksp->ks_raw_ops.headers || + ksp->ks_raw_ops.seq_headers); + +restart_headers: + if (ksp->ks_raw_ops.headers) { + rc = ksp->ks_raw_ops.headers( + ksp->ks_raw_buf, ksp->ks_raw_bufsize); + } else if (ksp->ks_raw_ops.seq_headers) { + struct seq_file f; + + f.sf_buf = ksp->ks_raw_buf; + f.sf_size = ksp->ks_raw_bufsize; + rc = ksp->ks_raw_ops.seq_headers(&f); + } + if (has_header) { + if (rc == ENOMEM && !kstat_resize_raw(ksp)) + goto restart_headers; + if (rc == 0) + sbuf_printf(sb, "\n%s", ksp->ks_raw_buf); + } + + while ((data = addr_op(ksp, n)) != NULL) { +restart: + if (ksp->ks_raw_ops.data) { + rc = ksp->ks_raw_ops.data(ksp->ks_raw_buf, + ksp->ks_raw_bufsize, data); + if (rc == ENOMEM && !kstat_resize_raw(ksp)) + goto restart; + if (rc == 0) + sbuf_printf(sb, "%s", ksp->ks_raw_buf); + + } else { + ASSERT(ksp->ks_ndata == 1); + sbuf_hexdump(sb, ksp->ks_data, + ksp->ks_data_size, NULL, 0); + } + n++; + } + IOFreeAligned(ksp->ks_raw_buf, PAGE_SIZE); + mutex_exit(ksp->ks_lock); + rc = SYSCTL_OUT(req, sbuf_data(sb), sbuf_len(sb)); + sbuf_delete(sb); + return (rc); +} + +static int +kstat_handle_io SYSCTL_HANDLER_ARGS +{ + struct sbuf *sb; + kstat_t *ksp = arg1; + kstat_io_t *kip = ksp->ks_data; + int rc; + + sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND); + if (sb == NULL) + return (ENOMEM); + /* Update the aggsums before reading */ + (void) ksp->ks_update(ksp, KSTAT_READ); + + /* though wlentime & friends are signed, they will never be negative */ + sbuf_printf(sb, + "%-8llu %-8llu %-8u %-8u %-8llu %-8llu " + "%-8llu %-8llu %-8llu %-8llu %-8u %-8u\n", + kip->nread, kip->nwritten, + kip->reads, kip->writes, + kip->wtime, kip->wlentime, kip->wlastupdate, + kip->rtime, kip->rlentime, kip->rlastupdate, + kip->wcnt, kip->rcnt); + sbuf_finish(sb); + rc = SYSCTL_OUT(req, sbuf_data(sb), sbuf_len(sb)); + sbuf_delete(sb); + return (rc); +} + +static int +kstat_handle_i64 SYSCTL_HANDLER_ARGS +{ + int error = 0; + sysctl_leaf_t *params = (sysctl_leaf_t *)(arg1); + kstat_named_t *named = params->l_named; + kstat_t *ksp = params->l_ksp; + kmutex_t *lock = ksp->ks_lock; + int lock_needs_release = 0; + + if (lock && !MUTEX_NOT_HELD(lock)) { + mutex_enter(lock); + lock_needs_release = 1; + } + + if (ksp->ks_update) { + ksp->ks_update(ksp, KSTAT_READ); + } + + if (!error && req->newptr) { + /* + * Write request - first read add current values for the kstat + * (remember that is sysctl is likely only one of many + * values that make up the kstat). + */ + + /* Copy the new value from user space */ + (void) copyin(req->newptr, &named->value.i64, + sizeof (named->value.i64)); + + /* and invoke the update operation */ + if (ksp->ks_update) { + error = ksp->ks_update(ksp, KSTAT_WRITE); + } + } else { + /* + * Read request + */ + error = SYSCTL_OUT(req, &named->value.i64, sizeof (int64_t)); + } + + if (lock_needs_release) { + mutex_exit(lock); + } + + return (error); +} + +static int +kstat_handle_ui64 SYSCTL_HANDLER_ARGS +{ + int error = 0; + sysctl_leaf_t *params = (sysctl_leaf_t *)(arg1); + kstat_named_t *named = params->l_named; + kstat_t *ksp = params->l_ksp; + kmutex_t *lock = ksp->ks_lock; + int lock_needs_release = 0; + + if (lock && !MUTEX_NOT_HELD(lock)) { + mutex_enter(lock); + lock_needs_release = 1; + } + + if (ksp->ks_update) { + ksp->ks_update(ksp, KSTAT_READ); + } + + if (!error && req->newptr) { + /* + * Write request - first read add current values for the kstat + * (remember that is sysctl is likely only one of many + * values that make up the kstat). + */ + + /* Copy the new value from user space */ + (void) copyin(req->newptr, &named->value.ui64, + sizeof (named->value.ui64)); + + /* and invoke the update operation */ + if (ksp->ks_update) { + error = ksp->ks_update(ksp, KSTAT_WRITE); + } + } else { + /* + * Read request + */ + error = SYSCTL_OUT(req, &named->value.ui64, sizeof (uint64_t)); + } + + if (lock_needs_release) { + mutex_exit(lock); + } + + return (error); +} + +static int +kstat_handle_string SYSCTL_HANDLER_ARGS +{ + int error = 0; + sysctl_leaf_t *params = (sysctl_leaf_t *)(arg1); + kstat_named_t *named = params->l_named; + kstat_t *ksp = params->l_ksp; + kmutex_t *lock = ksp->ks_lock; + int lock_needs_release = 0; + + if (lock && !MUTEX_NOT_HELD(lock)) { + mutex_enter(lock); + lock_needs_release = 1; + } + + if (ksp->ks_update) { + ksp->ks_update(ksp, KSTAT_READ); + } + + if (!error && req->newptr) { + char *inbuf = IOMalloc(256); + + error = SYSCTL_IN(req, inbuf, req->newlen); + + if (error == 0) { + + inbuf[req->newlen] = 0; + + /* + * Copy the new value from user space + * (copyin done by XNU) + */ + kstat_named_setstr(named, (const char *)inbuf); + + /* and invoke the update operation: last call out */ + if (ksp->ks_update) + error = ksp->ks_update(ksp, KSTAT_WRITE); + } + + IOFree(inbuf, 256); + + } else { + + error = SYSCTL_OUT(req, named->value.string.addr.ptr, + named->value.string.len); + } + + if (lock_needs_release) { + mutex_exit(lock); + } + + return (error); +} + +kstat_t * +kstat_create(const char *ks_module, int ks_instance, const char *ks_name, + const char *ks_class, uchar_t ks_type, ulong_t ks_ndata, uchar_t ks_flags) +{ + kstat_t *ksp = 0; + ekstat_t *e = 0; + size_t size = 0; + + if (ks_class == NULL) + ks_class = "misc"; + + /* + * Allocate memory for the new kstat header. + */ + size = sizeof (ekstat_t); + e = (ekstat_t *)IOMalloc(size); + memset(e, 0, size); + if (e == NULL) { + cmn_err(CE_NOTE, "kstat_create('%s', %d, '%s'): " + "insufficient kernel memory", + ks_module, ks_instance, ks_name); + return (NULL); + } + e->e_size = size; + + cv_init(&e->e_cv, NULL, CV_DEFAULT, NULL); + + /* + * Initialize as many fields as we can. The caller may reset + * ks_lock, ks_update, ks_private, and ks_snapshot as necessary. + * Creators of virtual kstats may also reset ks_data. It is + * also up to the caller to initialize the kstat data section, + * if necessary. All initialization must be complete before + * calling kstat_install(). + */ + ksp = &e->e_ks; + + ksp->ks_crtime = gethrtime(); + kstat_set_string(ksp->ks_module, ks_module); + ksp->ks_instance = ks_instance; + kstat_set_string(ksp->ks_name, ks_name); + ksp->ks_type = ks_type; + kstat_set_string(ksp->ks_class, ks_class); + ksp->ks_flags = ks_flags | KSTAT_FLAG_INVALID; + ksp->ks_snaptime = ksp->ks_crtime; + ksp->ks_update = kstat_default_update; + + mutex_init(&ksp->ks_private_lock, NULL, MUTEX_DEFAULT, NULL); + ksp->ks_lock = &ksp->ks_private_lock; + + switch (ksp->ks_type) { + case KSTAT_TYPE_RAW: + ksp->ks_ndata = 1; + ksp->ks_data_size = ks_ndata; + break; + case KSTAT_TYPE_NAMED: + ksp->ks_ndata = ks_ndata; + ksp->ks_data_size = ks_ndata * sizeof (kstat_named_t); + break; + case KSTAT_TYPE_INTR: + ksp->ks_ndata = ks_ndata; + ksp->ks_data_size = ks_ndata * sizeof (kstat_intr_t); + break; + case KSTAT_TYPE_IO: + ASSERT(ks_ndata == 1); + ksp->ks_ndata = ks_ndata; + ksp->ks_data_size = ks_ndata * sizeof (kstat_io_t); + break; + case KSTAT_TYPE_TIMER: + ksp->ks_ndata = ks_ndata; + ksp->ks_data_size = ks_ndata * sizeof (kstat_timer_t); + break; + default: + panic("Undefined kstat type %d\n", ksp->ks_type); + } + + + + /* + * Initialise the sysctl that represents this kstat + */ + e->e_children.slh_first = 0; + + e->e_oid.oid_parent = get_kstat_parent(&sysctl__kstat_children, + ksp->ks_module, ksp->ks_class); + e->e_oid.oid_link.sle_next = 0; + e->e_oid.oid_number = OID_AUTO; + e->e_oid.oid_arg2 = 0; + e->e_oid.oid_name = ksp->ks_name; + e->e_oid.oid_descr = ""; + e->e_oid.oid_version = SYSCTL_OID_VERSION; + e->e_oid.oid_refcnt = 0; + e->e_oid.oid_handler = 0; + e->e_oid.oid_kind = CTLTYPE_NODE|CTLFLAG_RW|CTLFLAG_OID2; + e->e_oid.oid_fmt = "N"; + e->e_oid.oid_arg1 = (void*)(&e->e_children); + + /* If VIRTUAL we allocate memory to store data */ + if (ks_flags & KSTAT_FLAG_VIRTUAL) + ksp->ks_data = NULL; + else + ksp->ks_data = (void *)kmem_zalloc( + ksp->ks_data_size, KM_SLEEP); + + sysctl_register_oid(&e->e_oid); + + return (ksp); +} + +void +kstat_install(kstat_t *ksp) +{ + ekstat_t *e = (ekstat_t *)ksp; + kstat_named_t *named_base = 0; + sysctl_leaf_t *vals_base = 0; + sysctl_leaf_t *params = 0; + int oid_permissions = CTLFLAG_RD; + + if (ksp->ks_type == KSTAT_TYPE_NAMED) { + + if (ksp->ks_flags & KSTAT_FLAG_WRITABLE) { + oid_permissions |= CTLFLAG_RW; + } + + // Create the leaf node OID objects + e->e_vals = (sysctl_leaf_t *)IOMalloc(ksp->ks_ndata * + sizeof (sysctl_leaf_t)); + memset(e->e_vals, 0, ksp->ks_ndata * sizeof (sysctl_leaf_t)); + e->e_num_vals = ksp->ks_ndata; + + named_base = (kstat_named_t *)(ksp->ks_data); + vals_base = e->e_vals; + + for (int i = 0; i < ksp->ks_ndata; i++) { + int oid_valid = 1; + + kstat_named_t *named = &named_base[i]; + sysctl_leaf_t *val = &vals_base[i]; + + // Perform basic initialisation of the sysctl. + // + // The sysctl: kstat.... + snprintf(val->l_name, KSTAT_STRLEN, "%s", named->name); + + val->l_oid.oid_parent = &e->e_children; + val->l_oid.oid_link.sle_next = 0; + val->l_oid.oid_number = OID_AUTO; + val->l_oid.oid_arg2 = 0; + val->l_oid.oid_name = val->l_name; + val->l_oid.oid_descr = ""; + val->l_oid.oid_version = SYSCTL_OID_VERSION; + val->l_oid.oid_refcnt = 0; + + // Based on the kstat type flags, provide location + // of data item and associated type and handler + // flags to the sysctl. + switch (named->data_type) { + case KSTAT_DATA_INT64: + params = (sysctl_leaf_t *)IOMalloc( + sizeof (sysctl_leaf_t)); + params->l_named = named; + params->l_ksp = ksp; + + val->l_oid.oid_handler = + kstat_handle_i64; + val->l_oid.oid_kind = CTLTYPE_QUAD | + oid_permissions | CTLFLAG_OID2; + val->l_oid.oid_fmt = "Q"; + val->l_oid.oid_arg1 = (void*)params; + params = 0; + break; + case KSTAT_DATA_UINT64: + params = (sysctl_leaf_t *)IOMalloc( + sizeof (sysctl_leaf_t)); + params->l_named = named; + params->l_ksp = ksp; + + val->l_oid.oid_handler = + kstat_handle_ui64; + val->l_oid.oid_kind = CTLTYPE_QUAD | + oid_permissions | CTLFLAG_OID2; + val->l_oid.oid_fmt = "Q"; + val->l_oid.oid_arg1 = (void*)params; + break; + case KSTAT_DATA_INT32: + val->l_oid.oid_handler = + sysctl_handle_int; + val->l_oid.oid_kind = CTLTYPE_INT | + oid_permissions | CTLFLAG_OID2; + val->l_oid.oid_fmt = "I"; + val->l_oid.oid_arg1 = &named->value.i32; + break; + case KSTAT_DATA_UINT32: + val->l_oid.oid_handler = + sysctl_handle_int; + val->l_oid.oid_kind = CTLTYPE_INT | + oid_permissions | CTLFLAG_OID2; + val->l_oid.oid_fmt = "IU"; + val->l_oid.oid_arg1 = + &named->value.ui32; + break; + case KSTAT_DATA_LONG: + val->l_oid.oid_handler = + sysctl_handle_long; + val->l_oid.oid_kind = CTLTYPE_INT | + oid_permissions | CTLFLAG_OID2; + val->l_oid.oid_fmt = "L"; + val->l_oid.oid_arg1 = &named->value.l; + break; + case KSTAT_DATA_ULONG: + val->l_oid.oid_handler = + sysctl_handle_long; + val->l_oid.oid_kind = CTLTYPE_INT | + oid_permissions | CTLFLAG_OID2; + val->l_oid.oid_fmt = "L"; + val->l_oid.oid_arg1 = &named->value.ul; + break; + case KSTAT_DATA_STRING: + params = (sysctl_leaf_t *)IOMalloc( + sizeof (sysctl_leaf_t)); + params->l_named = named; + params->l_ksp = ksp; + val->l_oid.oid_handler = + kstat_handle_string; + val->l_oid.oid_kind = CTLTYPE_STRING | + oid_permissions | CTLFLAG_OID2; + val->l_oid.oid_fmt = "S"; + val->l_oid.oid_arg1 = (void*)params; + break; + + case KSTAT_DATA_CHAR: + default: + oid_valid = 0; + break; + } + + /* + * Finally publish the OID, provided that there were + * no issues initialising it. + */ + if (oid_valid) { + sysctl_register_oid(&val->l_oid); + val->l_oid_registered = 1; + } else { + val->l_oid_registered = 0; + } + } + + } else if (ksp->ks_type == KSTAT_TYPE_RAW) { + + e->e_vals = (sysctl_leaf_t *) + IOMalloc(sizeof (sysctl_leaf_t) * 2); + memset(e->e_vals, 0, sizeof (sysctl_leaf_t)); + e->e_num_vals = 2; + sysctl_leaf_t *val = e->e_vals; + + snprintf(val->l_name, KSTAT_STRLEN, "%s", ksp->ks_name); + + val->l_oid.oid_parent = &e->e_children; + val->l_oid.oid_link.sle_next = 0; + val->l_oid.oid_number = OID_AUTO; + val->l_oid.oid_arg2 = 0; + val->l_oid.oid_name = val->l_name; + val->l_oid.oid_descr = ""; + val->l_oid.oid_version = SYSCTL_OID_VERSION; + val->l_oid.oid_refcnt = 0; + + if (ksp->ks_raw_ops.data) { + val->l_oid.oid_handler = + kstat_handle_raw; + val->l_oid.oid_kind = CTLTYPE_STRING | + CTLFLAG_RD | CTLFLAG_OID2; + val->l_oid.oid_fmt = "A"; + val->l_oid.oid_arg1 = (void *) ksp; + sysctl_register_oid(&val->l_oid); + } else { + val->l_oid.oid_handler = + kstat_handle_raw; + val->l_oid.oid_kind = CTLTYPE_OPAQUE | + CTLFLAG_RD | CTLFLAG_OID2; + val->l_oid.oid_fmt = ""; + val->l_oid.oid_arg1 = (void *) ksp; + sysctl_register_oid(&val->l_oid); + } + val->l_oid_registered = 1; + + // Add "verbose" leaf to 2nd node + val++; + + snprintf(val->l_name, KSTAT_STRLEN, "verbose"); + + val->l_oid.oid_parent = &e->e_children; + val->l_oid.oid_link.sle_next = 0; + val->l_oid.oid_number = OID_AUTO; + val->l_oid.oid_arg2 = 0; + val->l_oid.oid_name = val->l_name; + val->l_oid.oid_descr = ""; + val->l_oid.oid_version = SYSCTL_OID_VERSION; + val->l_oid.oid_refcnt = 0; + + val->l_oid.oid_handler = + sysctl_handle_int; + val->l_oid.oid_kind = CTLTYPE_INT | + CTLFLAG_RW | CTLFLAG_OID2; + val->l_oid.oid_fmt = "Q"; + /* Somewhat gross, using arg2 as the variable */ + val->l_oid.oid_arg1 = &val->l_oid.oid_arg2; + sysctl_register_oid(&val->l_oid); + val->l_oid_registered = 1; + + } else if (ksp->ks_type == KSTAT_TYPE_IO) { + + e->e_vals = (sysctl_leaf_t *)IOMalloc(sizeof (sysctl_leaf_t)); + memset(e->e_vals, 0, sizeof (sysctl_leaf_t)); + e->e_num_vals = 1; + sysctl_leaf_t *val = e->e_vals; + + snprintf(val->l_name, KSTAT_STRLEN, "%s", ksp->ks_name); + + val->l_oid.oid_parent = &e->e_children; + val->l_oid.oid_link.sle_next = 0; + val->l_oid.oid_number = OID_AUTO; + val->l_oid.oid_arg2 = 0; + val->l_oid.oid_name = val->l_name; + val->l_oid.oid_descr = ""; + val->l_oid.oid_version = SYSCTL_OID_VERSION; + val->l_oid.oid_refcnt = 0; + + val->l_oid.oid_handler = + kstat_handle_io; + val->l_oid.oid_kind = CTLTYPE_STRING | + CTLFLAG_RD | CTLFLAG_OID2; + val->l_oid.oid_fmt = "A"; + val->l_oid.oid_arg1 = (void *) ksp; + sysctl_register_oid(&val->l_oid); + val->l_oid_registered = 1; + } + + ksp->ks_flags &= ~KSTAT_FLAG_INVALID; +} + +static void +remove_child_sysctls(ekstat_t *e) +{ + kstat_t *ksp = &e->e_ks; + kstat_named_t *named_base = (kstat_named_t *)(ksp->ks_data); + sysctl_leaf_t *vals_base = e->e_vals; + + for (int i = 0; i < ksp->ks_ndata; i++) { + if (vals_base[i].l_oid_registered) { + sysctl_unregister_oid(&vals_base[i].l_oid); + vals_base[i].l_oid_registered = 0; + } + + if (named_base[i].data_type == KSTAT_DATA_INT64 || + named_base[i].data_type == KSTAT_DATA_UINT64 || + named_base[i].data_type == KSTAT_DATA_STRING) { + + sysctl_leaf_t *leaf = (sysctl_leaf_t *) + vals_base[i].l_oid.oid_arg1; /* params */ + IOFree(leaf, sizeof (sysctl_leaf_t)); + + if (named_base[i].data_type == KSTAT_DATA_STRING) { + void *data; + int len; + data = KSTAT_NAMED_STR_PTR(&named_base[i]); + len = KSTAT_NAMED_STR_BUFLEN(&named_base[i]); + // kstat_named_setstr(&named_base[i], NULL); + if (data != NULL) + dprintf( + "%s: unknown if %p:%d was freed.\n", + __func__, data, len); + } + } + } +} + +void +kstat_delete(kstat_t *ksp) +{ + ekstat_t *e = (ekstat_t *)ksp; + kmutex_t *lock = ksp->ks_lock; + int lock_needs_release = 0; + + // destroy the sysctl + if (ksp->ks_type == KSTAT_TYPE_NAMED) { + + if (lock && MUTEX_NOT_HELD(lock)) { + mutex_enter(lock); + lock_needs_release = 1; + } + + remove_child_sysctls(e); + + if (lock_needs_release) { + mutex_exit(lock); + } + } + + sysctl_unregister_oid(&e->e_oid); + + if (e->e_vals) { + IOFree(e->e_vals, sizeof (sysctl_leaf_t) * e->e_num_vals); + } + + if (!(ksp->ks_flags & KSTAT_FLAG_VIRTUAL)) + kmem_free(ksp->ks_data, ksp->ks_data_size); + + ksp->ks_lock = NULL; + mutex_destroy(&ksp->ks_private_lock); + + cv_destroy(&e->e_cv); + IOFree(e, e->e_size); +} + +void +kstat_named_setstr(kstat_named_t *knp, const char *src) +{ + void *data; + int len; + + if (knp->data_type != KSTAT_DATA_STRING) + panic("kstat_named_setstr('%p', '%p'): " + "named kstat is not of type KSTAT_DATA_STRING", + (void *)knp, (void *)src); + + data = KSTAT_NAMED_STR_PTR(knp); + len = KSTAT_NAMED_STR_BUFLEN(knp); + + if (data != NULL && len > 0) { + + // If strings are the same, don't bother swapping them + if (src != NULL && + strcmp(src, data) == 0) + return; + + IOFree(data, len); + KSTAT_NAMED_STR_PTR(knp) = NULL; + KSTAT_NAMED_STR_BUFLEN(knp) = 0; + } + + if (src == NULL) + return; + + len = strlen(src) + 1; + + data = IOMalloc(len); + strlcpy(data, src, len); + KSTAT_NAMED_STR_PTR(knp) = data; + KSTAT_NAMED_STR_BUFLEN(knp) = len; +} + +void +kstat_named_init(kstat_named_t *knp, const char *name, uchar_t data_type) +{ + kstat_set_string(knp->name, name); + knp->data_type = data_type; + + if (data_type == KSTAT_DATA_STRING) + kstat_named_setstr(knp, NULL); +} + + +void +kstat_waitq_enter(kstat_io_t *kiop) +{ +} + +void +kstat_waitq_exit(kstat_io_t *kiop) +{ +} + +void +kstat_runq_enter(kstat_io_t *kiop) +{ +} + +void +kstat_runq_exit(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)) +{ + ksp->ks_raw_ops.headers = headers; + ksp->ks_raw_ops.data = data; + ksp->ks_raw_ops.addr = addr; +} + +void +__kstat_set_seq_raw_ops(kstat_t *ksp, + int (*headers)(struct seq_file *f), + int (*data)(char *buf, size_t size, void *data), + void *(*addr)(kstat_t *ksp, loff_t index)) +{ + ksp->ks_raw_ops.seq_headers = headers; + ksp->ks_raw_ops.data = data; + ksp->ks_raw_ops.addr = addr; +} + +void +spl_kstat_init() +{ + /* + * Create the kstat root OID + */ + sysctl_register_oid(&sysctl__kstat); +} + +void +spl_kstat_fini() +{ + /* + * Destroy the kstat module/class/name tree + * + * Done in two passes, first unregisters all + * of the oids, second releases all the memory. + */ + + sysctl_tree_node_t *iter = tree_nodes; + while (iter) { + sysctl_tree_node_t *tn = iter; + iter = tn->tn_next; + sysctl_unregister_oid(&tn->tn_oid); + } + + while (tree_nodes) { + sysctl_tree_node_t *tn = tree_nodes; + tree_nodes = tn->tn_next; + IOFree(tn, sizeof (sysctl_tree_node_t)); + } + + /* + * Destroy the root oid + */ + sysctl_unregister_oid(&sysctl__kstat); +} + +// piece of shit +struct sysctl_oid_list * +spl_kstat_find_oid(char *module, char *class) +{ + struct sysctl_oid_list *container; + + container = get_kstat_parent(&sysctl__kstat_children, + module, + class); + return (container); +} diff --git a/module/os/macos/spl/spl-list.c b/module/os/macos/spl/spl-list.c new file mode 100644 index 000000000000..ede7a29c4285 --- /dev/null +++ b/module/os/macos/spl/spl-list.c @@ -0,0 +1,197 @@ +/* + * 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. + */ + +/* + * Generic doubly-linked list implementation + */ + +#include +#include +#include +#include + + +#define list_insert_after_node(list, node, object) { \ + list_node_t *lnew = list_d2l(list, object); \ + lnew->list_prev = node; \ + lnew->list_next = node->list_next; \ + node->list_next->list_prev = lnew; \ + node->list_next = lnew; \ +} + +#define list_insert_before_node(list, node, object) { \ + list_node_t *lnew = list_d2l(list, object); \ + lnew->list_next = node; \ + lnew->list_prev = node->list_prev; \ + node->list_prev->list_next = lnew; \ + node->list_prev = lnew; \ +} + +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.list_next = list->list_head.list_prev = + &list->list_head; +} + +void +list_destroy(list_t *list) +{ + list_node_t *node = &list->list_head; + + ASSERT(list); + ASSERT(list->list_head.list_next == node); + ASSERT(list->list_head.list_prev == node); + + node->list_next = node->list_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->list_next != NULL); + lold->list_prev->list_next = lold->list_next; + lold->list_next->list_prev = lold->list_prev; + lold->list_next = lold->list_prev = NULL; +} + + +void * +list_head(list_t *list) +{ + if (list_empty(list)) + return (NULL); + return (list_object(list, list->list_head.list_next)); +} + +void * +list_tail(list_t *list) +{ + if (list_empty(list)) + return (NULL); + return (list_object(list, list->list_head.list_prev)); +} + +void * +list_next(list_t *list, void *object) +{ + list_node_t *node = list_d2l(list, object); + + if (node->list_next != &list->list_head) + return (list_object(list, node->list_next)); + + return (NULL); +} + +void * +list_prev(list_t *list, void *object) +{ + list_node_t *node = list_d2l(list, object); + + if (node->list_prev != &list->list_head) + return (list_object(list, node->list_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->list_prev->list_next = srcnode->list_next; + srcnode->list_next->list_prev = dstnode->list_prev; + dstnode->list_prev = srcnode->list_prev; + srcnode->list_prev->list_next = dstnode; + + /* empty src list */ + srcnode->list_next = srcnode->list_prev = srcnode; +} + +int +list_link_active(list_node_t *link) +{ + return (link->list_next != NULL); +} + +int +list_is_empty(list_t *list) +{ + return (list_empty(list)); +} diff --git a/module/os/macos/spl/spl-mutex.c b/module/os/macos/spl/spl-mutex.c new file mode 100644 index 000000000000..b6d991e85ca8 --- /dev/null +++ b/module/os/macos/spl/spl-mutex.c @@ -0,0 +1,923 @@ +/* + * 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) 2008 MacZFS + * Copyright (C) 2013,2020 Jorgen Lundman + * Copyright (C) 2023 Sean Doran + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Not defined in headers +extern boolean_t lck_mtx_try_lock(lck_mtx_t *lck); + +/* + * SPL mutexes: use the XNU interface, rather than the ones below, + * initialized in spl-osx.c and used in spl-thread.c + */ +lck_grp_attr_t *spl_mtx_grp_attr; +lck_attr_t *spl_mtx_lck_attr; +lck_grp_t *spl_mtx_grp; + +static lck_attr_t *zfs_lock_attr = NULL; +static lck_grp_attr_t *zfs_group_attr = NULL; + +static lck_grp_t *zfs_mutex_group = NULL; + +uint64_t zfs_active_mutex = 0; + +#ifdef SPL_DEBUG_MUTEX +#include +static list_t mutex_list; +static wrapper_mutex_t mutex_list_mtx; +static uint64_t mutex_list_wait_loc; + + +struct leak { + list_node_t mutex_leak_node; + +#define SPL_DEBUG_MUTEX_MAXCHAR_FUNC 24 +#define SPL_DEBUG_MUTEX_MAXCHAR_FILE 40 /* __FILE__ may have ../../... */ + + char last_locked_file[SPL_DEBUG_MUTEX_MAXCHAR_FILE]; + char last_locked_function[SPL_DEBUG_MUTEX_MAXCHAR_FUNC]; + int last_locked_line; + void *mp; + + uint64_t locktime; // time lock was taken + hrtime_t mutex_created_time; + char creation_file[SPL_DEBUG_MUTEX_MAXCHAR_FILE]; + char creation_function[SPL_DEBUG_MUTEX_MAXCHAR_FUNC]; + int creation_line; + uint64_t total_lock_count; + uint64_t total_trylock_success; + uint64_t total_trylock_miss; + uint32_t period_lock_count; + uint32_t period_trylock_miss; +}; + +static int wdlist_exit = 0; + +void +spl_wdlist_settime(void *mpleak, uint64_t value) +{ + struct leak *leak = (struct leak *)mpleak; + VERIFY3P(leak, !=, NULL); + atomic_store_nonatomic(&leak->locktime, value); +} + +inline static void +spl_wdlist_check(void *ignored) +{ + struct leak *mp; + uint64_t prev_noe = gethrestime_sec(); /* we time in seconds */ + + printf("SPL: Mutex watchdog is alive\n"); + + lck_mtx_lock((lck_mtx_t *)&mutex_list_mtx); + + while (!wdlist_exit) { + + struct timespec ts = { .tv_sec = SPL_MUTEX_WATCHDOG_SLEEP }; + int msleep_result; + + /* only this thread can modify these */ + static uint32_t period_lock_record_holder = 0; + static uint32_t period_miss_record_holder = 0; + + msleep_result = msleep((caddr_t)&mutex_list_wait_loc, + (lck_mtx_t *)&mutex_list_mtx, PRIBIO, + "mutex watchdog napping", + &ts); + + /* + * this assertion will fail deliberately (0 == 35) + * when this thread is woken up by + * spl_mutex_subsystem_fini(), but it's also good to + * know if anything other than EAGAIN is seen before + * then, for now. (this *is* SPL_DEBUG_MUTEX after all :-) ) + */ + ASSERT3S(msleep_result, ==, EAGAIN); + + spl_data_barrier(); + + uint64_t noe = gethrestime_sec(); + for (mp = list_head(&mutex_list); + mp; + mp = list_next(&mutex_list, mp)) { + uint64_t locktime = mp->locktime; + if ((locktime > 0) && (noe > locktime) && + noe - locktime >= SPL_MUTEX_WATCHDOG_TIMEOUT) { + printf("SPL: mutex (%p) held for %llus by " + "'%s':%s:%d\n", mp, + noe - mp->locktime, + mp->last_locked_file, + mp->last_locked_function, + mp->last_locked_line); + } // if old + +#define HIGH_LOCKS_PER_RUN 10000 +#define HIGH_TRYLOCK_MISS_PER_RUN 100 + + const uint32_t period_locks = atomic_swap_32( + &mp->period_lock_count, 0); + const uint32_t period_trymiss = atomic_swap_32( + &mp->period_trylock_miss, 0); + + if (period_locks > HIGH_LOCKS_PER_RUN && + period_locks > + (period_lock_record_holder * 100) / 90) { + printf("SPL: hot lock mutex (%p)" + " [created %s:%s:%d]" + " locked %u times in %llu seconds," + " hottest was %u" + " [last locked by %s:%s:%d]\n", + mp, + mp->creation_file, mp->creation_function, + mp->creation_line, period_locks, + noe - prev_noe, + period_lock_record_holder, + mp->last_locked_file, + mp->last_locked_function, + mp->last_locked_line); + if (period_locks > period_lock_record_holder) + period_lock_record_holder = + period_locks; + } + + if (period_trymiss > HIGH_TRYLOCK_MISS_PER_RUN && + period_trymiss > (period_miss_record_holder * + 90) / 100) { + printf("SPL: hot miss mutex (%p)" + " [created %s:%s:%d]" + " had %u mutex_trylock misses in" + " %llu seconds, hottest was %u" + " [last locked by %s:%s:%d]\n", + mp, + mp->creation_file, mp->creation_function, + mp->creation_line, period_trymiss, + noe - prev_noe, + period_miss_record_holder, + mp->last_locked_file, + mp->last_locked_function, + mp->last_locked_line); + if (period_trymiss > period_miss_record_holder) + period_miss_record_holder = + period_trymiss; + } + + } // for all + + /* decay "high score" record by 1% every pass */ + period_lock_record_holder = + (period_lock_record_holder * 100) / 99; + period_miss_record_holder = + (period_miss_record_holder * 100) / 99; + prev_noe = noe; + + } // while not exit + + wdlist_exit = 0; + spl_data_barrier(); + wakeup_one((caddr_t)&mutex_list_wait_loc); + lck_mtx_unlock((lck_mtx_t *)&mutex_list_mtx); + + printf("SPL: watchdog thread exit\n"); + thread_exit(); +} +#endif + + +int +spl_mutex_subsystem_init(void) +{ + zfs_lock_attr = lck_attr_alloc_init(); + zfs_group_attr = lck_grp_attr_alloc_init(); + zfs_mutex_group = lck_grp_alloc_init("zfs-mutex", zfs_group_attr); + +#ifdef SPL_DEBUG_MUTEX + { + unsigned char mutex[128]; + int i; + + memset(mutex, 0xAF, sizeof (mutex)); + lck_mtx_init((lck_mtx_t *)&mutex[0], zfs_mutex_group, + zfs_lock_attr); + for (i = sizeof (mutex) -1; i >= 0; i--) + if (mutex[i] != 0xAF) + break; + + printf("SPL: %s:%d: mutex size is %u\n", + __func__, __LINE__, i+1); + + } + + list_create(&mutex_list, sizeof (struct leak), + offsetof(struct leak, mutex_leak_node)); + /* We can not call mutex_init() as it would use "leak" */ + lck_mtx_init((lck_mtx_t *)&mutex_list_mtx, zfs_mutex_group, + zfs_lock_attr); + + /* create without timesharing or qos */ + (void) thread_create_named_with_extpol_and_qos( + "spl_wdlist_check (mutex)", + NULL, NULL, NULL, + NULL, 0, spl_wdlist_check, NULL, 0, 0, maxclsyspri); +#endif /* SPL_DEBUG_MUTEX */ + return (0); +} + +void +spl_mutex_subsystem_fini(void) +{ +#ifdef SPL_DEBUG_MUTEX + uint64_t total = 0; + printf("SPL: %s:%d: Dumping leaked mutex allocations..." + " zfs_active_mutex == %llu\n", + __func__, __LINE__, atomic_load_64(&zfs_active_mutex)); + + /* Ask the thread to quit */ + lck_mtx_lock((lck_mtx_t *)&mutex_list_mtx); + wdlist_exit = 1; + spl_data_barrier(); + while (wdlist_exit) { + wakeup_one((caddr_t)&mutex_list_wait_loc); + msleep((caddr_t)&mutex_list_wait_loc, + (lck_mtx_t *)&mutex_list_mtx, + PRIBIO, "waiting for mutex watchdog thread to end", 0); + spl_data_barrier(); + } + + /* mutex watchdog thread has quit, we hold the mutex */ + + /* walk the leak list */ + + while (1) { + struct leak *leak, *runner; + uint32_t found; + + leak = list_head(&mutex_list); + + if (leak) { + list_remove(&mutex_list, leak); + } + if (!leak) + break; + + // Run through list and count up how many times this leak is + // found, removing entries as we go. + for (found = 1, runner = list_head(&mutex_list); + runner; + runner = runner ? list_next(&mutex_list, runner) : + list_head(&mutex_list)) { + + if (strcmp(leak->last_locked_file, + runner->last_locked_file) == 0 && + strcmp(leak->last_locked_function, + runner->last_locked_function) == 0 && + leak->last_locked_line == + runner->last_locked_line) { + // Same place + found++; + list_remove(&mutex_list, runner); + IOFreeType(runner, struct leak); + runner = NULL; + } // if same + + } // for all nodes + + printf("SPL: %s:%d mutex %p : last lock %s %s %d :" + " # leaks: %u" + " created %llu seconds ago at %s:%s:%d" + " locked %llu," + "try_s %llu try_w %llu\n", + __func__, __LINE__, + leak->mp, + leak->last_locked_file, + leak->last_locked_function, + leak->last_locked_line, + found, + NSEC2SEC(gethrtime() - leak->mutex_created_time), + leak->creation_file, + leak->creation_function, + leak->creation_line, + leak->total_lock_count, + leak->total_trylock_success, + leak->total_trylock_miss); + + IOFreeType(leak, struct leak); + total += found; + + } + lck_mtx_unlock((lck_mtx_t *)&mutex_list_mtx); + + printf("SPL: %s:%d Dumped %llu leaked allocations.\n", + __func__, __LINE__, total); + + /* We can not call mutex_destroy() as it uses leak */ + lck_mtx_destroy((lck_mtx_t *)&mutex_list_mtx, zfs_mutex_group); + list_destroy(&mutex_list); +#endif + + if (atomic_load_64(&zfs_active_mutex) != 0) { + printf("SPL: %s:%d: zfs_active_mutex is %llu\n", + __func__, __LINE__, atomic_load_64(&zfs_active_mutex)); + } else { + printf("SPL: %s: good, zero zfs_active_mutex\n", + __func__); + } + + lck_attr_free(zfs_lock_attr); + zfs_lock_attr = NULL; + + lck_grp_attr_free(zfs_group_attr); + zfs_group_attr = NULL; + + lck_grp_free(zfs_mutex_group); + zfs_mutex_group = NULL; +} + + + +#ifdef SPL_DEBUG_MUTEX +void +spl_mutex_init(kmutex_t *mp, char *name, kmutex_type_t type, void *ibc, + const char *file, const char *fn, const int line) +#else +void +spl_mutex_init(kmutex_t *mp, char *name, kmutex_type_t type, void *ibc) +#endif +{ + ASSERT(type != MUTEX_SPIN); + ASSERT3P(ibc, ==, NULL); + +#ifdef SPL_DEBUG_MUTEX + VERIFY3U(atomic_load_nonatomic(&mp->m_initialised), !=, MUTEX_INIT); +#endif + + lck_mtx_init((lck_mtx_t *)&mp->m_lock, zfs_mutex_group, zfs_lock_attr); + mp->m_owner = NULL; + mp->m_waiters = 0; + mp->m_sleepers = 0; + + atomic_inc_64(&zfs_active_mutex); + +#ifdef SPL_DEBUG_MUTEX + mp->m_initialised = MUTEX_INIT; + + struct leak *leak; + + leak = IOMallocType(struct leak); + + VERIFY3P(leak, !=, NULL); + + memset(leak, 0, sizeof (struct leak)); + + leak->mutex_created_time = gethrtime(); + strlcpy(leak->last_locked_file, file, SPL_DEBUG_MUTEX_MAXCHAR_FILE); + strlcpy(leak->last_locked_function, fn, SPL_DEBUG_MUTEX_MAXCHAR_FUNC); + leak->last_locked_line = line; + strlcpy(leak->creation_file, file, SPL_DEBUG_MUTEX_MAXCHAR_FILE); + strlcpy(leak->creation_function, fn, SPL_DEBUG_MUTEX_MAXCHAR_FUNC); + leak->creation_line = line; + leak->mp = mp; + + spl_data_barrier(); + + lck_mtx_lock((lck_mtx_t *)&mutex_list_mtx); + list_link_init(&leak->mutex_leak_node); + list_insert_tail(&mutex_list, leak); + mp->leak = leak; + lck_mtx_unlock((lck_mtx_t *)&mutex_list_mtx); +#endif + spl_data_barrier(); +} + +void +spl_mutex_destroy(kmutex_t *mp) +{ + + VERIFY3P(mp, !=, NULL); + +#ifdef SPL_DEBUG_MUTEX + VERIFY3U(atomic_load_nonatomic(&mp->m_initialised), ==, MUTEX_INIT); +#endif + + if (atomic_load_nonatomic(&mp->m_owner) != 0) + panic("SPL: releasing held mutex"); + + lck_mtx_destroy((lck_mtx_t *)&mp->m_lock, zfs_mutex_group); + + atomic_dec_64(&zfs_active_mutex); + +#ifdef SPL_DEBUG_MUTEX + atomic_store_nonatomic(&mp->m_initialised, MUTEX_DESTROYED); + + struct leak *leak = (struct leak *)mp->leak; + + VERIFY3P(leak, !=, NULL); + + /* WAGs, but they rise dynamically on very fast&busy systems */ + +#define BUSY_LOCK_THRESHOLD 1000 * 1000 +#define BUSY_LOCK_PER_SECOND_THRESHOLD 1000 + + /* + * Multiple mutex_destroy() can be in flight from different threads, + * so these have to be protected. We can do that _Atomic declaration + * and the short CAS loop below, since we're just doing + * straightforward compares and new-value stores. + */ + static _Atomic uint64_t busy_lock_per_second_record_holder = 0; + + if (leak->total_lock_count > BUSY_LOCK_THRESHOLD) { + const hrtime_t nsage = + gethrtime() - leak->mutex_created_time; + const uint64_t secage = NSEC2SEC(nsage) + 1; + const uint64_t meanlps = leak->total_lock_count / secage; + const uint64_t hot_thresh = + (busy_lock_per_second_record_holder * 100) / 90; + + if (meanlps > BUSY_LOCK_PER_SECOND_THRESHOLD && + meanlps > hot_thresh) { + printf("SPL: %s:%d: destroyed hot lock (mean lps %llu)" + " %llu mutex_enters since creation at %s:%s:%d" + " %llu seconds ago (hot was %llu lps)" + " [most recent lock %s:%s:%d]\n", + __func__, __LINE__, + meanlps, + leak->total_lock_count, + leak->creation_file, leak->creation_function, + leak->creation_line, + secage, + busy_lock_per_second_record_holder, + leak->last_locked_file, + leak->last_locked_function, leak->last_locked_line); + + /* update the global record holder */ + uint8_t b_lck = false; + while (meanlps > busy_lock_per_second_record_holder) { + if (!atomic_cas_8(&b_lck, false, true)) { + busy_lock_per_second_record_holder = + meanlps; + } + } + } + } + +#define TRYLOCK_CALL_THRESHOLD 1000 * 1000 +#define TRYLOCK_WAIT_MIN_PCT 2 // mutex_trylock misses as % + + static _Atomic uint64_t miss_per_second_record_holder = 0; + + const uint64_t try_calls = + leak->total_trylock_success + + leak->total_trylock_miss; + const uint64_t try_misses = + leak->total_trylock_miss; + + if (try_misses > 0 && try_calls > TRYLOCK_CALL_THRESHOLD) { + const uint64_t notheldpct = + (try_misses * 100) / try_calls; + const uint64_t miss_thresh = + (miss_per_second_record_holder * 100) / 90; + + if (notheldpct > TRYLOCK_WAIT_MIN_PCT && + notheldpct > miss_thresh) { + printf("SPL: %s:%d: destroyed lock which" + " waited often in mutex_trylock:" + " %llu all locks," + " %llu trysuccess, %llu miss, notheldpct %llu," + " created %llu seconds ago at %s:%s:%d" + " (thresh was %llu miss/s)" + " [most recent lock location %s:%s:%d]\n", + __func__, __LINE__, + leak->total_lock_count, + leak->total_trylock_success, + leak->total_trylock_miss, + notheldpct, + NSEC2SEC(gethrtime() - + leak->mutex_created_time), + leak->creation_file, leak->creation_function, + leak->creation_line, + miss_per_second_record_holder, + leak->last_locked_file, + leak->last_locked_function, leak->last_locked_line); + + /* update the global record holder */ + uint8_t b_lck = false; + while (notheldpct > miss_per_second_record_holder) { + if (!atomic_cas_8(&b_lck, false, true)) { + miss_per_second_record_holder = + notheldpct; + } + } + } + } + + lck_mtx_lock((lck_mtx_t *)&mutex_list_mtx); + list_remove(&mutex_list, leak); + mp->leak = NULL; + lck_mtx_unlock((lck_mtx_t *)&mutex_list_mtx); + IOFreeType(leak, struct leak); +#endif +} + +#ifdef SPL_DEBUG_MUTEX +void +spl_mutex_enter(kmutex_t *mp, const char *file, const char *func, + const int line) +#else +void +spl_mutex_enter(kmutex_t *mp) +#endif +{ +#ifdef SPL_DEBUG_MUTEX + VERIFY3U(atomic_load_nonatomic(&mp->m_initialised), ==, MUTEX_INIT); +#endif + +#ifdef DEBUG + if (unlikely(*((uint64_t *)mp) == 0xdeadbeefdeadbeef)) { + panic("SPL: mutex_enter deadbeef"); + __builtin_unreachable(); + } +#endif + + if (atomic_load_nonatomic(&mp->m_owner) == current_thread()) { + panic("mutex_enter: locking against myself!"); + __builtin_unreachable(); + } + + atomic_inc_64(&mp->m_waiters); + spl_data_barrier(); + lck_mtx_lock((lck_mtx_t *)&mp->m_lock); + spl_data_barrier(); + atomic_dec_64(&mp->m_waiters); + atomic_store_nonatomic(&mp->m_owner, current_thread()); + +#ifdef SPL_DEBUG_MUTEX + if (likely(mp->leak)) { + /* + * We have the lock here, so our leak structure will not be + * interfered with by other mutex_* functions operating on + * this lock, except for the periodic spl_wdlist_check() + * thread (see below) or a mutex_tryenter() (which will fail) + */ + struct leak *leak = (struct leak *)mp->leak; + leak->locktime = gethrestime_sec(); + strlcpy(leak->last_locked_file, + file, sizeof (leak->last_locked_file)); + strlcpy(leak->last_locked_function, + func, sizeof (leak->last_locked_function)); + leak->last_locked_line = line; + leak->total_lock_count++; + /* + * We allow a possible inaccuracy here by not + * doing an atomic_inc_32() for the period lock. + * The race can only be between this current thread + * right here, and the spl_wdlist_check() periodic + * read-modify-write. + * + * That RMW is done by an atomic_swap_32() + * which uses SEQ_CST on Mac platforms, + * which should order that read&zero against this + * increment. In particular, the increment here shouldn't + * be here_read_large_old_value_from_memory__to_register, + * here_increment_register, + * periodic_thread_sets_old_value_to_zero, + * here_write_large_value_from_register_to_memory, + * but it is technically possible (the race window is + * very narrow!). + * + * The result would only be a (potential!) spurious printf + * about a hot lock from the periodic thread at its next run, + * and so the cost of a SEQ_CST atomic increment here is + * not justified. + */ + leak->period_lock_count++; + } else { + panic("SPL: %s:%d: where is my leak data?" + " possible compilation mismatch", __func__, __LINE__); + __builtin_unreachable(); + } +#endif + +} + + +/* + * So far, the interruptible part does not work, this just + * calls regular mutex_enter. + */ +#ifdef SPL_DEBUG_MUTEX +int +spl_mutex_enter_interruptible(kmutex_t *mp, const char *file, + const char *func, const int line) +#else +int +spl_mutex_enter_interruptible(kmutex_t *mp) +#endif +{ + int error = 0; +#ifdef SPL_DEBUG_MUTEX + VERIFY3U(atomic_load_nonatomic(&mp->m_initialised), ==, MUTEX_INIT); +#endif + +#ifdef DEBUG + if (unlikely(*((uint64_t *)mp) == 0xdeadbeefdeadbeef)) { + panic("SPL: mutex_enter deadbeef"); + __builtin_unreachable(); + } +#endif + + if (atomic_load_nonatomic(&mp->m_owner) == current_thread()) { + panic("mutex_enter: locking against myself!"); + __builtin_unreachable(); + } + + atomic_inc_64(&mp->m_waiters); + spl_data_barrier(); + lck_mtx_lock((lck_mtx_t *)&mp->m_lock); + spl_data_barrier(); + + if (error != 0) + goto interrupted; + + atomic_dec_64(&mp->m_waiters); + atomic_store_nonatomic(&mp->m_owner, current_thread()); + +#ifdef SPL_DEBUG_MUTEX + if (likely(mp->leak)) { + /* + * We have the lock here, so our leak structure will not be + * interfered with by other mutex_* functions operating on + * this lock, except for the periodic spl_wdlist_check() + * thread (see below) or a mutex_tryenter() (which will fail) + */ + struct leak *leak = (struct leak *)mp->leak; + leak->locktime = gethrestime_sec(); + strlcpy(leak->last_locked_file, + file, sizeof (leak->last_locked_file)); + strlcpy(leak->last_locked_function, + func, sizeof (leak->last_locked_function)); + leak->last_locked_line = line; + leak->total_lock_count++; + /* + * We allow a possible inaccuracy here by not + * doing an atomic_inc_32() for the period lock. + * The race can only be between this current thread + * right here, and the spl_wdlist_check() periodic + * read-modify-write. + * + * That RMW is done by an atomic_swap_32() + * which uses SEQ_CST on Mac platforms, + * which should order that read&zero against this + * increment. In particular, the increment here shouldn't + * be here_read_large_old_value_from_memory__to_register, + * here_increment_register, + * periodic_thread_sets_old_value_to_zero, + * here_write_large_value_from_register_to_memory, + * but it is technically possible (the race window is + * very narrow!). + * + * The result would only be a (potential!) spurious printf + * about a hot lock from the periodic thread at its next run, + * and so the cost of a SEQ_CST atomic increment here is + * not justified. + */ + leak->period_lock_count++; + } else { + panic("SPL: %s:%d: where is my leak data?" + " possible compilation mismatch", __func__, __LINE__); + __builtin_unreachable(); + } +#endif + +interrupted: + + return (error); +} + +void +spl_mutex_exit(kmutex_t *mp) +{ +#ifdef DEBUG + if (unlikely(*((uint64_t *)mp) == 0xdeadbeefdeadbeef)) { + panic("SPL: mutex_exit deadbeef"); + __builtin_unreachable(); + } +#endif + +#ifdef SPL_DEBUG_MUTEX + VERIFY3U(atomic_load_nonatomic(&mp->m_initialised), ==, MUTEX_INIT); +#endif + +#ifdef SPL_DEBUG_MUTEX + if (likely(mp->leak)) { + struct leak *leak = (struct leak *)mp->leak; + uint64_t locktime = leak->locktime; + uint64_t noe = gethrestime_sec(); + if ((locktime > 0) && (noe > locktime) && + noe - locktime >= SPL_MUTEX_WATCHDOG_TIMEOUT) { + printf("SPL: mutex (%p) finally released after %llus " + "was held by %s:'%s':%d\n", + leak, noe - leak->locktime, + leak->last_locked_file, leak->last_locked_function, + leak->last_locked_line); + } + leak->locktime = 0; + } else { + panic("SPL: %s:%d: where is my leak data?", + __func__, __LINE__); + __builtin_unreachable(); + } +#endif + mp->m_owner = NULL; + spl_data_barrier(); + lck_mtx_unlock((lck_mtx_t *)&mp->m_lock); +} + +int +#ifdef SPL_DEBUG_MUTEX +spl_mutex_tryenter(kmutex_t *mp, const char *file, const char *func, + const int line) +#else +spl_mutex_tryenter(kmutex_t *mp) +#endif +{ + int held; + +#ifdef SPL_DEBUG_MUTEX + VERIFY3U(atomic_load_nonatomic(&mp->m_initialised), ==, MUTEX_INIT); +#endif + + atomic_inc_64(&mp->m_waiters); + spl_data_barrier(); + held = lck_mtx_try_lock((lck_mtx_t *)&mp->m_lock); + /* + * Now do a full barrier, because that's the right thing to do after + * we get a lock from lck_mtx...(), which on Apple Silicon uses softer + * acquire semantics than the multithread store ordering we'd like + * in our emulation of heritage Solaris code. + * + * Apple Silicon relevant only. spl_data_barrier() is a noop on + * strong memory model machines like Intel. + * + * Initially this was an unconditional spl_data_barrier(), but the + * point of the barrier is to let other threads know we have the lock + * in happens-before sense (i.e., that the lock is held before the + * other threads issue reads/writes on the affected cache lines, and + * every thread enjoys happens-after on any reads/writes of those + * cache lines after the barrier is issued). The "dmb ish" is cheap + * but not free, and there could be a mutex_tryenter() in a fairly + * tight loop. So we skip it if we don't obtain the lock. We've also + * recently done a full barrier so that we know that a previous lock + * holder's mutex_exit() is in a happened-before state when we do + * lck_mtx_try_lock(). + * + * The atomic_dec_64() will use acquire/release semantics and who + * knows how they slide around relative to the full barrier (it also + * is not necessarily a super-fast instruction), so we don't want to + * slide the barrier into a single if (held) after the atomic decrement. + * + * The atomic decrement also needs to happen before DEBUGging code, so + * it should stay close to the lck_mtx...(). + */ + if (held) + spl_data_barrier(); + atomic_dec_64(&mp->m_waiters); + if (held) { + atomic_store_nonatomic(&mp->m_owner, current_thread()); +#ifdef SPL_DEBUG_MUTEX + if (likely(mp->leak)) { + /* + * see block comment in mutex_enter()'s + * SPL_DEBUG_MUTEX section, and below. + */ + struct leak *leak = (struct leak *)mp->leak; + leak->locktime = gethrestime_sec(); + leak->total_trylock_success++; + leak->total_lock_count++; + leak->period_lock_count++; + strlcpy(leak->last_locked_file, file, + SPL_DEBUG_MUTEX_MAXCHAR_FILE); + strlcpy(leak->last_locked_function, func, + SPL_DEBUG_MUTEX_MAXCHAR_FUNC); + leak->last_locked_line = line; + } else { + panic("SPL: %s:%d: where is my leak data?", + __func__, __LINE__); + __builtin_unreachable(); + } + + } else { + /* + * We are not protected by the lock here, so our + * read-modify-writes must be done atomically, since in the + * periodic spl_wdlist_check() thread these memory locations + * may also have a racing ("simultaneous") RMW. Here we + * avoid the periodic thread potentially not seeing the + * trylock miss that would just go over the threshold for + * a diagnostic printf. + * + * The xnu code below lck_mtx_try_lock() for a miss is + * substantially more expensive than the cost of these atomic + * increments, so we shouldn't be doing mutex_trylock() in + * a tight loop anyway. + */ + VERIFY3P(mp->leak, !=, NULL); + struct leak *leak = (struct leak *)mp->leak; + atomic_inc_64(&leak->total_trylock_miss); + atomic_inc_32(&leak->period_trylock_miss); +#endif + } + return (held); +} + +int +spl_mutex_owned(kmutex_t *mp) +{ + return (atomic_load_nonatomic(&mp->m_owner) == current_thread()); +} + +struct kthread * +spl_mutex_owner(kmutex_t *mp) +{ + return (atomic_load_nonatomic(&mp->m_owner)); +} + +#ifdef SPL_DEBUG_MUTEX +void +spl_dbg_mutex_destroy(kmutex_t *mp, const char *file, + const char *func, const int line) +{ + + extern struct thread *spl_mutex_owner(kmutex_t *); + extern int spl_mutex_owned(kmutex_t *); + extern void spl_mutex_destroy(kmutex_t *); + + membar_consumer(); + VERIFY3P(mp, !=, NULL); + struct thread *o = spl_mutex_owner(mp); + if (o != NULL) { + VERIFY3P(mp->leak, !=, NULL); + struct leak *leak = (struct leak *)mp->leak; + uint64_t noe = gethrestime_sec(); + if (!spl_mutex_owned(mp)) { + panic("%s: mutex has other owner %p" + " destroy call at %s() in %s line %d," + " last mutex_enter in %s:%s:%d" + " %llus ago" + "\n", + __func__, o, func, file, line, + leak->last_locked_file, + leak->last_locked_function, + leak->last_locked_line, + noe - leak->locktime); + } else { + panic("%s: mutex %p is owned by" + " current thread" + " from %s() in %s line %d" + " last mutex_enter in %s:%s:%d" + " %llus ago" + "\n", + __func__, o, func, file, line, + leak->last_locked_file, + leak->last_locked_function, + leak->last_locked_line, + noe - leak->locktime); + } + } + spl_mutex_destroy(mp); +} +#endif diff --git a/module/os/macos/spl/spl-osx.c b/module/os/macos/spl/spl-osx.c new file mode 100644 index 000000000000..b2bada924835 --- /dev/null +++ b/module/os/macos/spl/spl-osx.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 (C) 2013, 2020 Jorgen Lundman + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define _task_user_ +#include + +#include + +static utsname_t utsname_static = { { 0 } }; + +unsigned int max_ncpus = 0; +unsigned int num_ecores = 0; +uint64_t total_memory = 0; +uint64_t real_total_memory = 0; + +// Size in bytes of the memory allocated in seg_kmem +extern uint64_t segkmem_total_mem_allocated; + +extern int bsd_hostname(char *, size_t, size_t *); +static char spl_hostname[MAXHOSTNAMELEN]; + +utsname_t * +utsname(void) +{ + return (&utsname_static); +} + +/* + * Solaris delay is in ticks (hz) and Darwin uses microsecs + * 1 HZ is 10 milliseconds + */ +void +osx_delay(int ticks) +{ + ASSERT3S(ticks, >, 0); + + // ticks are 10 msec units + int64_t ticks_to_go = (int64_t)ticks; + // zfs_lbolt() is in 10 mec units + int64_t start_tick = (int64_t)zfs_lbolt(); + int64_t end_tick = start_tick + (int64_t)ticks_to_go; + + do { + /* + * IOSleepWithLeeway takes() (milliseconds, lw_ms) where the + * leeway in milliseconds lw_ms ultimately results in an + * interval and deadline being given to + * _clock_delay_until_deadline_with_leeway() to schedule a lax + * (TIMEOUT_URGENCY_LEEWAY) wakeup and then does a + * thread_block(). + * + * Without the leeway, a strict deadline is scheduled before + * the thread_block(). + * + * Both of these options are gentle compared to a spin wait, + * which will happen if the interval given to + * _clock..._leeway(int, dead, lee) is less than the threshold + * from ml_delay_should_spin. + * + * The threshold is short on ARM and is related to CPU idle + * latency. On i386 it appears to be 10 microseconds by + * default, so also short compared to one tick, which is at + * least ten milliseconds. + * + * We will always IOSleepWithLeeway a minimum amount, even if + * we are invoked with delay(zero-or-negative-value); + */ + + bool forced_sleep = false; + + ASSERT3S(ticks_to_go, >, 0); + unsigned milliseconds_remaining = ticks_to_go * 10; + + if (milliseconds_remaining < 2) { + milliseconds_remaining = 2; + forced_sleep = true; + } + + ASSERT3U(milliseconds_remaining, <=, 1000 * 60); + + IOSleepWithLeeway(milliseconds_remaining, 1); + + if (forced_sleep) + break; + + int64_t cur_tick = (int64_t)zfs_lbolt(); + ticks_to_go = (end_tick - cur_tick); + + } while (ticks_to_go > 0); +} + + +uint32_t +zone_get_hostid(void *zone) +{ + size_t len; + uint32_t myhostid = 0; + + len = sizeof (myhostid); + sysctlbyname("kern.hostid", &myhostid, &len, NULL, 0); + return (myhostid); +} + +extern void *(*__ihook_malloc)(size_t size); +extern void (*__ihook_free)(void *); + +const char * +spl_panicstr(void) +{ + return (NULL); +} + +extern int get_system_inshutdown(void); + +int +spl_system_inshutdown(void) +{ + // return (get_system_inshutdown()); + return (1); +} + +#include +typedef struct mach_header_64 kernel_mach_header_t; +#include +typedef struct nlist_64 kernel_nlist_t; + +typedef struct segment_command_64 kernel_segment_command_t; + +typedef struct _loaded_kext_summary { + char name[KMOD_MAX_NAME]; + uuid_t uuid; + uint64_t address; + uint64_t size; + uint64_t version; + uint32_t loadTag; + uint32_t flags; + uint64_t reference_list; +} OSKextLoadedKextSummary; + +typedef struct _loaded_kext_summary_header { + uint32_t version; + uint32_t entry_size; + uint32_t numSummaries; + uint32_t reserved; /* explicit alignment for gdb */ + OSKextLoadedKextSummary summaries[0]; +} OSKextLoadedKextSummaryHeader; + +extern OSKextLoadedKextSummaryHeader *gLoadedKextSummaries; + +typedef struct _cframe_t { + struct _cframe_t *prev; + uintptr_t caller; +#if PRINT_ARGS_FROM_STACK_FRAME + unsigned args[0]; +#endif +} cframe_t; + +extern kernel_mach_header_t _mh_execute_header; + +extern kmod_info_t *kmod; /* the list of modules */ + +extern addr64_t kvtophys(vm_offset_t va); + +static int __maybe_unused +panic_print_macho_symbol_name(kernel_mach_header_t *mh, vm_address_t search, + const char *module_name) +{ + kernel_nlist_t *sym = NULL; + struct load_command *cmd; + kernel_segment_command_t *orig_ts = NULL, *orig_le = NULL; + struct symtab_command *orig_st = NULL; + unsigned int i; + char *strings, *bestsym = NULL; + vm_address_t bestaddr __maybe_unused = 0; + vm_address_t diff, curdiff; + + /* + * Assume that if it's loaded and linked into the kernel, + * it's a valid Mach-O + */ + cmd = (struct load_command *)&mh[1]; + for (i = 0; i < mh->ncmds; i++) { + if (cmd->cmd == LC_SEGMENT_64) { + kernel_segment_command_t *orig_sg = + (kernel_segment_command_t *)cmd; + + if (strncmp(SEG_TEXT, orig_sg->segname, + sizeof (orig_sg->segname)) == 0) + orig_ts = orig_sg; + else if (strncmp(SEG_LINKEDIT, orig_sg->segname, + sizeof (orig_sg->segname)) == 0) + orig_le = orig_sg; + /* pre-Lion i386 kexts have a single unnamed segment */ + else if (strncmp("", orig_sg->segname, + sizeof (orig_sg->segname)) == 0) + orig_ts = orig_sg; + } else if (cmd->cmd == LC_SYMTAB) + orig_st = (struct symtab_command *)cmd; + + cmd = (struct load_command *)((uintptr_t)cmd + cmd->cmdsize); + } + + if ((orig_ts == NULL) || (orig_st == NULL) || (orig_le == NULL)) + return (0); + + if ((search < orig_ts->vmaddr) || + (search >= orig_ts->vmaddr + orig_ts->vmsize)) { + /* search out of range for this mach header */ + return (0); + } + + sym = (kernel_nlist_t *)(uintptr_t)(orig_le->vmaddr + + orig_st->symoff - orig_le->fileoff); + strings = (char *)(uintptr_t)(orig_le->vmaddr + + orig_st->stroff - orig_le->fileoff); + diff = search; + + for (i = 0; i < orig_st->nsyms; i++) { + if (sym[i].n_type & N_STAB) continue; + + if (sym[i].n_value <= search) { + curdiff = search - (vm_address_t)sym[i].n_value; + if (curdiff < diff) { + diff = curdiff; + bestaddr = sym[i].n_value; + bestsym = strings + sym[i].n_un.n_strx; + } + } + } + + if (bestsym != NULL) { + if (diff != 0) { + printf("%s : %s + 0x%lx", module_name, bestsym, + (unsigned long)diff); + } else { + printf("%s : %s", module_name, bestsym); + } + return (1); + } + return (0); +} + + +static void __maybe_unused +panic_print_kmod_symbol_name(vm_address_t search) +{ +#if 0 // gLoadedKextSummaries is no longer available + uint_t i; + if (gLoadedKextSummaries == NULL) + return; + for (i = 0; i < gLoadedKextSummaries->numSummaries; ++i) { + OSKextLoadedKextSummary *summary = + gLoadedKextSummaries->summaries + i; + + if ((search >= summary->address) && + (search < (summary->address + summary->size))) { + kernel_mach_header_t *header = + (kernel_mach_header_t *)(uintptr_t)summary->address; + if (panic_print_macho_symbol_name(header, search, + summary->name) == 0) { + printf("%s + %llu", summary->name, + (unsigned long)search - summary->address); + } + break; + } + } +#endif +} + + +static void +panic_print_symbol_name(vm_address_t search) +{ + /* try searching in the kernel */ +#if 0 + if (panic_print_macho_symbol_name(&_mh_execute_header, + search, "mach_kernel") == 0) { + /* that failed, now try to search for the right kext */ + panic_print_kmod_symbol_name(search); + } +#endif +} + + +void +spl_backtrace(char *thesignal) +{ + void *stackptr = NULL; + + printf("SPL: backtrace \"%s\"\n", thesignal); + +#if defined(__i386__) + __asm__ volatile("movl %%ebp, %0" : "=m" (stackptr)); +#elif defined(__x86_64__) + __asm__ volatile("movq %%rbp, %0" : "=m" (stackptr)); +#endif + + int frame_index; + int nframes = 16; + cframe_t *frame = (cframe_t *)stackptr; + + for (frame_index = 0; frame_index < nframes; frame_index++) { + vm_offset_t curframep = (vm_offset_t)frame; + if (!curframep) + break; + if (curframep & 0x3) { + printf("SPL: Unaligned frame\n"); + break; + } +#if 0 + // no kvtophys() available now. Used to verify only? + // pmap_find_phys(kernel_pmap, curframep) ? + if (!kvtophys(curframep) || + !kvtophys(curframep + sizeof (cframe_t) - 1)) { + printf("SPL: No mapping exists for frame pointer\n"); + break; + } +#endif + printf("SPL: %p : 0x%lx ", frame, frame->caller); + panic_print_symbol_name((vm_address_t)frame->caller); + printf("\n"); + frame = frame->prev; + } +} + +int +getpcstack(uintptr_t *pcstack, int pcstack_limit) +{ + int depth = 0; + void *stackptr = NULL; + +#if defined(__i386__) + __asm__ volatile("movl %%ebp, %0" : "=m" (stackptr)); +#elif defined(__x86_64__) + __asm__ volatile("movq %%rbp, %0" : "=m" (stackptr)); +#endif + + int frame_index; + int nframes = pcstack_limit; + cframe_t *frame = (cframe_t *)stackptr; + + for (frame_index = 0; frame_index < nframes; frame_index++) { + vm_offset_t curframep = (vm_offset_t)frame; + if (!curframep) + break; + if (curframep & 0x3) { + break; + } +#if 0 + if (!kvtophys(curframep) || + !kvtophys(curframep + sizeof (cframe_t) - 1)) { + break; + } +#endif + pcstack[depth++] = frame->caller; + frame = frame->prev; + } + + return (depth); +} + +void +print_symbol(uintptr_t symbol) +{ + printf("SPL: "); + panic_print_symbol_name((vm_address_t)(symbol)); + printf("\n"); +} + +int +ddi_copyin(const void *from, void *to, size_t len, int flags) +{ + int ret = 0; + + /* Fake ioctl() issued by kernel, 'from' is a kernel address */ + if (flags & FKIOCTL) + memcpy(to, from, len); + else + ret = copyin((user_addr_t)from, (void *)to, len); + + return (ret); +} + +int +ddi_copyout(const void *from, void *to, size_t len, int flags) +{ + int ret = 0; + + /* Fake ioctl() issued by kernel, 'from' is a kernel address */ + if (flags & FKIOCTL) { + memcpy(to, from, len); + } else { + ret = copyout(from, (user_addr_t)to, len); + } + + return (ret); +} + +/* + * Technically, this call does not exist in illumos, but we use it for + * consistency. + */ +int +ddi_copyinstr(const void *uaddr, void *kaddr, size_t len, size_t *done) +{ + int ret; + size_t local_done; + +#undef copyinstr + ret = copyinstr((user_addr_t)uaddr, kaddr, len, &local_done); + if (done != NULL) + *done = local_done; + return (ret); +} + +kern_return_t +spl_start(kmod_info_t *ki, void *d) +{ + printf("SPL: loading\n"); + + int ncpus; + size_t len = sizeof (ncpus); + + /* + * Boot load time is excessively early, so we have to wait + * until certain subsystems are available. Surely there is + * a more elegant way to do this wait? + */ + + while (current_proc() == NULL) { + printf("SPL: waiting for kernel init...\n"); + delay(hz>>1); + } + + /* + * special purpose xnu-style locks, separate from the spl-mutex.c + * system, where those are available either late or may require + * memory or thread allocation + */ + spl_mtx_lck_attr = lck_attr_alloc_init(); + spl_mtx_grp_attr = lck_grp_attr_alloc_init(); + spl_mtx_grp = lck_grp_alloc_init("spl-mutex", spl_mtx_grp_attr); + + while (1) { + len = sizeof (total_memory); + sysctlbyname("hw.memsize", &total_memory, &len, NULL, 0); + if (total_memory != 0) break; + + printf("SPL: waiting for sysctl...\n"); + delay(hz>>1); + } + + sysctlbyname("hw.logicalcpu_max", &max_ncpus, &len, NULL, 0); + if (!max_ncpus) max_ncpus = 1; + +#if defined(__arm64__) + num_ecores = (max_ncpus > 4) ? 4 : 0; +#endif + + /* + * Setting the total memory to physmem * 50% here, since kmem is + * not in charge of all memory and we need to leave some room for + * the OS X allocator. We internally add pressure if we step over it + */ + real_total_memory = total_memory; + total_memory = total_memory * 50ULL / 100ULL; + physmem = total_memory / PAGE_SIZE; + +#if defined(__arm64__) + /* + * 128GiB Studio Ultras with 12.6.1 and earlier will panic, usually in + * another kernel subsystem (hfs, hardware video encoding/decoding), + * after we allocate more than around 30GiB of memory through + * IOMallocAligned(). + * + * So far this has not been observed on other hw platforms and has not + * been tested in 13.x (Ventura) on these systems. However, few other + * macOS hw platforms have more than 64 GiB of RAM. 96 GiB of RAM is + * likely to be the lower limit for running into this problem, since + * smaller systems either [a] will not have total_memory >= 32 GiB, + * [b] will have kernel pressure signals driven by memory use in + * userland and the HFS/APFS buffer cache, or [c] both [a]and[b]. + * + * For safety, on ARM we default to having a dynamic memory cap of 26 + * GiB any ARM with more than 64GiB of RAM. This will prevent ARC + * growth from climbing much above 20 GiB, consequently limiting the + * various other ZFS caches and overheads. Total consumption will not + * reach the panic-inviting levels around/above 32 GiB, since the ARC + * will be shrunk when approaching the dynamic memory cap. + * + * This has proven safe enough, and can be overridden dynamically by a + * sysctl or zsysctl.conf by setting + * kstat.spl.misc.spl_misc.spl_osif_dynamic_memory_cap to some other + * byte count, including 0 (which will allow growth until + * IOMallocAligned() returns a NULL). + */ + + extern _Atomic uint64_t spl_dynamic_memory_cap; + + if (real_total_memory >= 64LL*1024LL*1024LL*1024LL) + spl_dynamic_memory_cap = 26LL*1024LL*1024LL*1024LL; +#endif + + len = sizeof (utsname_static.sysname); + sysctlbyname("kern.ostype", &utsname_static.sysname, &len, NULL, 0); + + /* + * For some reason, (CTLFLAG_KERN is not set) looking up hostname + * returns 1. So we set it to uuid just to give it *something*. + * As it happens, ZFS sets the nodename on init. + */ + len = sizeof (utsname_static.nodename); + sysctlbyname("kern.uuid", &utsname_static.nodename, &len, NULL, 0); + + len = sizeof (utsname_static.release); + sysctlbyname("kern.osrelease", &utsname_static.release, &len, NULL, 0); + + len = sizeof (utsname_static.version); + sysctlbyname("kern.version", &utsname_static.version, &len, NULL, 0); + + strlcpy(spl_hostname, "noname", sizeof (spl_hostname)); + // Private.exports + // bsd_hostname(spl_hostname, sizeof (spl_hostname), &len); + + strlcpy(utsname_static.nodename, spl_hostname, + sizeof (utsname_static.nodename)); + + spl_mutex_subsystem_init(); + spl_kmem_init(total_memory); + spl_vnode_init(); + spl_kmem_thread_init(); + spl_kmem_mp_init(); + + spl_cpuid_features(); + + return (KERN_SUCCESS); +} + +kern_return_t +spl_stop(kmod_info_t *ki, void *d) +{ + spl_kmem_thread_fini(); + spl_vnode_fini(); + spl_taskq_fini(); + spl_rwlock_fini(); + spl_tsd_fini(); + spl_kmem_fini(); + spl_kstat_fini(); + spl_mutex_subsystem_fini(); + + lck_attr_free(spl_mtx_lck_attr); + spl_mtx_lck_attr = NULL; + lck_grp_attr_free(spl_mtx_grp_attr); + spl_mtx_grp_attr = NULL; + lck_grp_free(spl_mtx_grp); + spl_mtx_grp = NULL; + + return (KERN_SUCCESS); +} diff --git a/module/os/macos/spl/spl-policy.c b/module/os/macos/spl/spl-policy.c new file mode 100644 index 000000000000..6a743ac794f8 --- /dev/null +++ b/module/os/macos/spl/spl-policy.c @@ -0,0 +1,185 @@ +/* + * 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 +#include +#include + +int +spl_priv_check_cred(kauth_cred_t cred, int priv, __unused int flags) +{ + int error; + + if (kauth_cred_getuid(cred) == 0) { + error = 0; + goto out; + } + + /* + * The default is deny, so if no policies have granted it, reject + * with a privilege error here. + */ + error = EPERM; +out: + return (error); +} + +int +secpolicy_fs_unmount(cred_t *cr, struct mount *vfsp) +{ + return (spl_priv_check_cred((kauth_cred_t)cr, PRIV_VFS_UNMOUNT, 0)); +} + +int +secpolicy_nfs(const cred_t *cr) +{ + return (spl_priv_check_cred((kauth_cred_t)cr, PRIV_NFS_DAEMON, 0)); +} + +int +secpolicy_sys_config(const cred_t *cr, boolean_t checkonly) +{ + return (spl_priv_check_cred((kauth_cred_t)cr, PRIV_ZFS_POOL_CONFIG, 0)); +} + +int +secpolicy_zfs(const cred_t *cr) +{ + return (spl_priv_check_cred((kauth_cred_t)cr, PRIV_VFS_MOUNT, 0)); +} + +int +secpolicy_zinject(const cred_t *cr) +{ + return (spl_priv_check_cred((kauth_cred_t)cr, PRIV_ZFS_INJECT, 0)); +} + +int +secpolicy_vnode_any_access(const cred_t *cr, struct vnode *vp, uid_t owner) +{ + // FIXME + return (0); +} + +int +secpolicy_vnode_access2(const cred_t *cr, struct vnode *vp, uid_t owner, + mode_t curmode, mode_t wantmode) +{ + // FIXME + return (0); +} + +int +secpolicy_vnode_setattr(cred_t *cr, struct vnode *vp, vattr_t *vap, + const vattr_t *ovap, int flags, + int unlocked_access(void *, int, cred_t *), + void *node) +{ + // FIXME + return (0); +} + +int +secpolicy_vnode_stky_modify(const cred_t *cred) +{ + return (EPERM); +} + +int +secpolicy_setid_setsticky_clear(vnode_t *vp, vattr_t *vap, const vattr_t *ovap, + cred_t *cr) +{ + // FIXME + return (0); +} + +int +secpolicy_vnode_remove(struct vnode *vp, const cred_t *cr) +{ + return (0); +} + +int +secpolicy_vnode_create_gid(const cred_t *cred) +{ + return (0); +} + +int +secpolicy_vnode_setids_setgids(struct vnode *vp, const cred_t *cr, + gid_t gid) +{ + return (0); +} + +int +secpolicy_vnode_setdac(struct vnode *vp, const cred_t *cr, uid_t u) +{ + return (0); +} + +int +secpolicy_vnode_chown(struct vnode *vp, const cred_t *cr, uid_t u) +{ + return (0); +} + +int +secpolicy_vnode_setid_retain(struct znode *zp, const cred_t *cr, + boolean_t issuidroot) +{ + return (0); +} + +int +secpolicy_xvattr(vattr_t *vap, uid_t uid, const cred_t *cr, mode_t mod) +{ + return (0); +} + +int +secpolicy_setid_clear(vattr_t *vap, const cred_t *cr) +{ + return (0); +} + +int +secpolicy_basic_link(const cred_t *cr) +{ + return (0); +} + +int +secpolicy_fs_mount_clearopts(const cred_t *cr, struct mount *mp) +{ + return (0); +} + +int +secpolicy_fs_mount(const cred_t *cr, struct vnode *vp, struct mount *mp) +{ + return (spl_priv_check_cred((kauth_cred_t)cr, PRIV_VFS_MOUNT, 0)); +} + +int +secpolicy_zfs_proc(const cred_t *cr, proc_t *proc) +{ + return (spl_priv_check_cred((kauth_cred_t)cr, PRIV_VFS_MOUNT, 0)); +} diff --git a/module/os/macos/spl/spl-proc.c b/module/os/macos/spl/spl-proc.c new file mode 100644 index 000000000000..9c90559f0fba --- /dev/null +++ b/module/os/macos/spl/spl-proc.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 +#include +#include + +struct proc { + void *nothing; +}; + +struct proc p0 = {0}; diff --git a/module/os/macos/spl/spl-proc_list.c b/module/os/macos/spl/spl-proc_list.c new file mode 100644 index 000000000000..9ec54e21c87a --- /dev/null +++ b/module/os/macos/spl/spl-proc_list.c @@ -0,0 +1,157 @@ +/* + * 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 +#include +#include + +void *IOMalloc(vm_size_t size); +void IOFree(void *address, vm_size_t size); + +typedef struct procfs_list_iter { + procfs_list_t *pli_pl; + void *pli_elt; +} pli_t; + +void +seq_printf(struct seq_file *f, const char *fmt, ...) +{ + va_list adx; + + va_start(adx, fmt); + (void) vsnprintf(f->sf_buf, f->sf_size, fmt, adx); + va_end(adx); +} + +static int +procfs_list_update(kstat_t *ksp, int rw) +{ + procfs_list_t *pl = ksp->ks_private; + + if (rw == KSTAT_WRITE) + pl->pl_clear(pl); + + return (0); +} + +static int +procfs_list_data(char *buf, size_t size, void *data) +{ + pli_t *p; + void *elt; + procfs_list_t *pl; + struct seq_file f; + + p = data; + pl = p->pli_pl; + elt = p->pli_elt; + IOFree(p, sizeof (*p)); + f.sf_buf = buf; + f.sf_size = size; + return (pl->pl_show(&f, elt)); +} + +static void * +procfs_list_addr(kstat_t *ksp, loff_t n) +{ + procfs_list_t *pl = ksp->ks_private; + void *elt = ksp->ks_private1; + pli_t *p = NULL; + + + if (n == 0) + ksp->ks_private1 = list_head(&pl->pl_list); + else if (elt) + ksp->ks_private1 = list_next(&pl->pl_list, elt); + + if (ksp->ks_private1) { + p = IOMalloc(sizeof (*p)); + p->pli_pl = pl; + p->pli_elt = ksp->ks_private1; + } + + return (p); +} + + +void +procfs_list_install(const char *module, + const char *submodule, + 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) +{ + kstat_t *procfs_kstat; + + 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_show = show; + procfs_list->pl_show_header = show_header; + procfs_list->pl_clear = clear; + procfs_list->pl_next_id = 1; + procfs_list->pl_node_offset = procfs_list_node_off; + + procfs_kstat = kstat_create(module, 0, name, submodule, + KSTAT_TYPE_RAW, 0, KSTAT_FLAG_VIRTUAL); + + if (procfs_kstat) { + procfs_kstat->ks_lock = &procfs_list->pl_lock; + procfs_kstat->ks_ndata = UINT32_MAX; + procfs_kstat->ks_private = procfs_list; + procfs_kstat->ks_update = procfs_list_update; + kstat_set_seq_raw_ops(procfs_kstat, show_header, + procfs_list_data, procfs_list_addr); + kstat_install(procfs_kstat); + procfs_list->pl_private = procfs_kstat; + } +} + +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)); + kstat_delete(procfs_list->pl_private); + 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); +} diff --git a/module/os/macos/spl/spl-processor.c b/module/os/macos/spl/spl-processor.c new file mode 100644 index 000000000000..9b19448f47d9 --- /dev/null +++ b/module/os/macos/spl/spl-processor.c @@ -0,0 +1,213 @@ +/* + * 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 Jorgen Lundman + * + */ + +#include + +extern int cpu_number(void); + +#if defined(__x86_64__) + +#include + +#define _spl_cpuid(func, a, b, c, d) \ + __asm__ __volatile__( \ + " pushq %%rbx \n" \ + " xorq %%rcx,%%rcx \n" \ + " cpuid \n" \ + " movq %%rbx, %%rsi \n" \ + " popq %%rbx \n" : \ + "=a" (a), "=S" (b), "=c" (c), "=d" (d) : "a" (func)) + +static uint64_t _spl_cpuid_features = 0ULL; +static uint64_t _spl_cpuid_features_leaf7 = 0ULL; +static boolean_t _spl_cpuid_has_xgetbv = B_FALSE; + +#elif defined(__aarch64__) + +#include + +static uint64_t _spl_cpuid_id_aa64isar0_el1 = 0ULL; +static uint64_t _spl_cpuid_id_aa64isar1_el1 = 0ULL; + +#else + +#define _spl_cpuid(func, a, b, c, d) \ + a = b = c = d = 0 + +#endif + +uint32_t +getcpuid(void) +{ +#if defined(__aarch64__) + uint64_t mpidr_el1; + + asm volatile("mrs %0, mpidr_el1" : "=r" (mpidr_el1)); + /* + * To save us looking up number of eCores and pCores, we + * just wrap eCores backwards from max_ncpu. + * 0: [P0 P1 P2 ... Px Ex .. E2 E1 E0] : max_ncpu + * + * XNU: Aff2: "1" - PCORE, "0" - ECORE + */ +#define PCORE_BIT (1ULL << 16) + if (mpidr_el1 & PCORE_BIT) + return ((uint32_t)mpidr_el1 & 0xff); + else + return ((max_ncpus -1) - (uint32_t)(mpidr_el1 & 0xff)); + +#else + return ((uint32_t)cpu_number()); +#endif +} + + +#if defined(__x86_64__) +uint64_t +spl_cpuid_features(void) +{ + static int first_time = 1; + uint64_t a, b, c, d; + + if (first_time == 1) { + first_time = 0; + // Wikipedia: stored in EAX, EBX, EDX, ECX (in that order). + _spl_cpuid(0, a, b, d, c); + if (a >= 1) { + _spl_cpuid(1, a, b, d, c); + _spl_cpuid_features = d | (c << 32); + + // GETBV is bit 26 in ECX. Apple defines it as: + // CPUID_FEATURE_XSAVE _HBit(26) + // ( ECX & (1 << 26) + // or, (feature & 400000000000000) + _spl_cpuid_has_xgetbv = + _spl_cpuid_features & CPUID_FEATURE_XSAVE; + } + if (a >= 7) { + c = 0; + _spl_cpuid(7, a, b, d, c); + _spl_cpuid_features_leaf7 = b | (c << 32); + } + + + printf("SPL: CPUID 0x%08llx and leaf7 0x%08llx\n", + _spl_cpuid_features, _spl_cpuid_features_leaf7); + + } + + return (_spl_cpuid_features); +} + +uint64_t +spl_cpuid_leaf7_features(void) +{ + return (_spl_cpuid_features_leaf7); +} + +#endif /* x86_64 */ + + +#if defined(__aarch64__) + +/* 4,5,6,7 -> GET_BITS(4, 8) */ +#define GET_BITS(S, F, T) \ + ((S) >> (F)) & ((1 << ((T) - (F))) - 1) + +uint64_t +spl_cpuid_id_aa64isar0_el1(void) +{ + static int first_time = 1; + + if (first_time == 1) { + first_time = 0; + uint64_t value; + uint32_t aes, sha1, sha2, sha3; + + asm volatile("mrs %0, ID_AA64ISAR0_EL1" : + "=r"(value) ::); + + _spl_cpuid_id_aa64isar0_el1 = value; + + printf("cpu_features0: 0x%016llx \n", + _spl_cpuid_id_aa64isar0_el1); + + aes = GET_BITS(_spl_cpuid_id_aa64isar0_el1, 4, 8); + sha1 = GET_BITS(_spl_cpuid_id_aa64isar0_el1, 8, 12); + sha2 = GET_BITS(_spl_cpuid_id_aa64isar0_el1, 12, 16); + sha3 = GET_BITS(_spl_cpuid_id_aa64isar0_el1, 32, 36); + + printf("cpu_features0: %s%s%s%s%s%s\n", + aes & 3 ? "AES " : "", + aes & 2 ? "PMULL " : "", + sha1 ? "SHA1 " : "", + sha2 ? "SHA256 " : "", + sha2 & 2 ? "SHA512 " : "", + sha3 ? "SHA3 " : ""); + } + + return (_spl_cpuid_id_aa64isar0_el1); +} + +uint64_t +spl_cpuid_id_aa64isar1_el1(void) +{ + static int first_time = 1; + + if (first_time == 1) { + first_time = 0; + uint64_t value; + uint32_t bf16, i8mm; + + asm volatile("mrs %0, ID_AA64ISAR1_EL1" : + "=r"(value) ::); + + _spl_cpuid_id_aa64isar1_el1 = value; + + printf("cpu_features: 0x%016llx \n", + _spl_cpuid_id_aa64isar1_el1); + + bf16 = GET_BITS(_spl_cpuid_id_aa64isar1_el1, 44, 48); + i8mm = GET_BITS(_spl_cpuid_id_aa64isar1_el1, 52, 56); + + printf("cpu_features1: %s%s\n", + bf16 ? "BF16 " : "", + i8mm ? "I8MM " : ""); + } + + return (_spl_cpuid_id_aa64isar1_el1); +} + +uint64_t +spl_cpuid_features(void) +{ + spl_cpuid_id_aa64isar0_el1(); + spl_cpuid_id_aa64isar1_el1(); + return (0ULL); +} + +#endif /* aarch64 */ diff --git a/module/os/macos/spl/spl-qsort.c b/module/os/macos/spl/spl-qsort.c new file mode 100644 index 000000000000..d647bc87de32 --- /dev/null +++ b/module/os/macos/spl/spl-qsort.c @@ -0,0 +1,178 @@ +/* + * Copyright (c) 1995 NeXT Computer, Inc. All Rights Reserved + * + * Copyright (c) 1992, 1993 + * The Regents of the University of California. 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 acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * @(#)qsort.c 8.1 (Berkeley) 6/4/93 + */ + + +#include + +static inline +char *med3(char *, char *, char *, int (*)(const void *, const void *)); +static inline +void swapfunc(char *, char *, int, int); + +#define min(a, b) (a) < (b) ? a : b + +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ +#define swapcode(TYPE, parmi, parmj, n) { \ + long i = (n) / sizeof (TYPE); \ + TYPE *pi = (TYPE *) (parmi); \ + TYPE *pj = (TYPE *) (parmj); \ + do { \ + TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ +} + +#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof (long) || \ + es % sizeof (long) ? 2 : es == sizeof (long)? 0 : 1; + +static inline void +swapfunc(char *a, char *b, int n, int swaptype) +{ + if (swaptype <= 1) + swapcode(long, a, b, n) + else + swapcode(char, a, b, n) +} + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long *)(a); \ + *(long *)(a) = *(long *)(b); \ + *(long *)(b) = t; \ + } else \ + swapfunc(a, b, es, swaptype) + +#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) + +static inline char * +med3(char *a, char *b, char *c, int (*cmp)(const void *, const void *)) +{ + return cmp(a, b) < 0 ? + (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a)) + :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c)); +} + +static void +qsort(void *a, size_t n, size_t es, int (*cmp)(const void *, const void *)) +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int d, swaptype, swap_cnt; + int r; + +loop: + SWAPINIT(a, es); + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp); + pm = med3(pm - d, pm, pm + d, cmp); + pn = med3(pn - 2 * d, pn - d, pn, cmp); + } + pm = med3(pl, pm, pn, cmp); + } + swap(a, pm); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (r = cmp(pb, a)) <= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (r = cmp(pc, a)) >= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swap(pb, pc); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + + pn = (char *)a + n * es; + r = min(pa - (char *)a, pb - pa); + vecswap(a, pb - r, r); + r = min((size_t)(pd - pc), pn - pd - es); + vecswap(pb, pn - r, r); + if ((size_t)(r = pb - pa) > es) + qsort(a, r / es, es, cmp); + if ((size_t)(r = pd - pc) > es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } +/* qsort(pn - r, r / es, es, cmp); */ +} + +void +spl_qsort(void *array, size_t nm, size_t member_size, + int (*cmpf)(const void *, const void *)) +{ + qsort(array, nm, member_size, cmpf); +} diff --git a/module/os/macos/spl/spl-rwlock.c b/module/os/macos/spl/spl-rwlock.c new file mode 100644 index 000000000000..1c4755c6bce2 --- /dev/null +++ b/module/os/macos/spl/spl-rwlock.c @@ -0,0 +1,447 @@ +/* + * 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) 2008 MacZFS + * Copyright (C) 2013, 2020 Jorgen Lundman + * Copyright (C) 2023 Sean Doran + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static lck_attr_t *zfs_rwlock_attr = NULL; +static lck_grp_attr_t *zfs_rwlock_group_attr = NULL; +static lck_grp_t *zfs_rwlock_group = NULL; + +uint64_t zfs_active_rwlock = 0; + +#ifdef SPL_DEBUG_RWLOCK +#include +static list_t rwlock_list; +static wrapper_mutex_t rwlock_list_mutex; +struct leak { + list_node_t rwlock_leak_node; + +#define SPL_DEBUG_RWLOCK_MAXCHAR 32 + char location_file[SPL_DEBUG_RWLOCK_MAXCHAR]; + char location_function[SPL_DEBUG_RWLOCK_MAXCHAR]; + uint64_t location_line; + void *mp; + + uint64_t wdlist_locktime; // time lock was taken + char wdlist_file[32]; // storing holder + uint64_t wdlist_line; +}; + +#endif + +/* + * We run rwlock with DEBUG on for now, as it protects against + * uninitialised access etc, and almost no cost. + */ +#ifndef DEBUG +#define DEBUG +#endif + +#ifdef DEBUG +int +rw_isinit(krwlock_t *rwlp) +{ + if (rwlp->rw_pad != 0x012345678) + return (0); + return (1); +} +#endif + + +#ifdef SPL_DEBUG_RWLOCK + +static void +rwlist_mutex_enter(wrapper_mutex_t *mtxp) +{ + spl_data_barrier(); + lck_mtx_lock((lck_mtx_t *)mtxp); + spl_data_barrier(); +} + +static void +rwlist_mutex_exit(wrapper_mutex_t *mtxp) +{ + spl_data_barrier(); + lck_mtx_unlock((lck_mtx_t *)mtxp); +} + +void +rw_initx(krwlock_t *rwlp, char *name, krw_type_t type, __unused void *arg, + const char *file, const char *fn, int line) +#else +void +rw_init(krwlock_t *rwlp, char *name, krw_type_t type, __unused void *arg) +#endif +{ + ASSERT(type != RW_DRIVER); + +#ifdef DEBUG + VERIFY3U(rwlp->rw_pad, !=, 0x012345678); +#endif + + lck_rw_init((lck_rw_t *)&rwlp->rw_lock[0], + zfs_rwlock_group, zfs_rwlock_attr); + rwlp->rw_owner = NULL; + rwlp->rw_readers = 0; +#ifdef DEBUG + rwlp->rw_pad = 0x012345678; +#endif + atomic_inc_64(&zfs_active_rwlock); + +#ifdef SPL_DEBUG_RWLOCK + struct leak *leak; + + leak = IOMallocType(struct leak); + + if (leak) { + memset(leak, 0, sizeof (struct leak)); + strlcpy(leak->location_file, file, SPL_DEBUG_RWLOCK_MAXCHAR); + strlcpy(leak->location_function, fn, SPL_DEBUG_RWLOCK_MAXCHAR); + leak->location_line = line; + leak->mp = rwlp; + + rwlist_mutex_enter(&rwlock_list_mutex); + list_link_init(&leak->rwlock_leak_node); + list_insert_tail(&rwlock_list, leak); + rwlp->leak = leak; + rwlist_mutex_exit(&rwlock_list_mutex); + } + leak->wdlist_locktime = 0; + leak->wdlist_file[0] = 0; + leak->wdlist_line = 0; +#endif +} + +void +rw_destroy(krwlock_t *rwlp) +{ +#ifdef DEBUG + VERIFY3U(rwlp->rw_pad, ==, 0x012345678); +#endif + + lck_rw_destroy((lck_rw_t *)&rwlp->rw_lock[0], zfs_rwlock_group); +#ifdef DEBUG + rwlp->rw_pad = 0x99; +#endif + atomic_dec_64(&zfs_active_rwlock); + ASSERT3P(atomic_load_nonatomic(&rwlp->rw_owner), ==, NULL); + ASSERT3U(atomic_load_nonatomic(&rwlp->rw_readers), ==, 0); + +#ifdef SPL_DEBUG_RWLOCK + if (rwlp->leak) { + struct leak *leak = (struct leak *)rwlp->leak; + rwlist_mutex_enter(&rwlock_list_mutex); + list_remove(&rwlock_list, leak); + rwlp->leak = NULL; + rwlist_mutex_exit(&rwlock_list_mutex); + IOFreeType(leak, struct leak); + } +#endif +} + +void +rw_enter(krwlock_t *rwlp, krw_t rw) +{ +#ifdef DEBUG + if (rwlp->rw_pad != 0x012345678) + panic("rwlock %p not initialised\n", rwlp); +#endif + + if (rw == RW_READER) { + spl_data_barrier(); + lck_rw_lock_shared((lck_rw_t *)&rwlp->rw_lock[0]); + spl_data_barrier(); + atomic_inc_32((volatile uint32_t *)&rwlp->rw_readers); + ASSERT3P(atomic_load_nonatomic(&rwlp->rw_owner), ==, NULL); + } else { + if (atomic_load_nonatomic(&rwlp->rw_owner) == current_thread()) + panic("rw_enter: locking against myself!"); + spl_data_barrier(); + lck_rw_lock_exclusive((lck_rw_t *)&rwlp->rw_lock[0]); + spl_data_barrier(); + ASSERT3P(rwlp->rw_owner, ==, NULL); + ASSERT3U(rwlp->rw_readers, ==, 0); + atomic_store_nonatomic(&rwlp->rw_owner, current_thread()); + } +} + +/* + * kernel private from osfmk/kern/locks.h + */ +extern boolean_t lck_rw_try_lock(lck_rw_t *lck, lck_rw_type_t lck_rw_type); + +int +rw_tryenter(krwlock_t *rwlp, krw_t rw) +{ + int held = 0; + +#ifdef DEBUG + if (rwlp->rw_pad != 0x012345678) + panic("rwlock %p not initialised\n", rwlp); +#endif + + if (rw == RW_READER) { + spl_data_barrier(); + held = lck_rw_try_lock((lck_rw_t *)&rwlp->rw_lock[0], + LCK_RW_TYPE_SHARED); + if (held) { + spl_data_barrier(); + atomic_inc_32((volatile uint32_t *)&rwlp->rw_readers); + } + } else { + if (atomic_load_nonatomic(&rwlp->rw_owner) == current_thread()) + panic("rw_tryenter: locking against myself!"); + spl_data_barrier(); + held = lck_rw_try_lock((lck_rw_t *)&rwlp->rw_lock[0], + LCK_RW_TYPE_EXCLUSIVE); + if (held) { + spl_data_barrier(); + atomic_store_nonatomic(&rwlp->rw_owner, + current_thread()); + } + } + + return (held); +} + +/* + * It appears a difference between Darwin's + * lck_rw_lock_shared_to_exclusive() and Solaris's rw_tryupgrade() and + * FreeBSD's sx_try_upgrade() is that on failure to upgrade, the prior + * held shared/reader lock is lost on Darwin, but retained on + * Solaris/FreeBSD. We could re-acquire the lock in this situation, + * but it enters a possibility of blocking, when tryupgrade is meant + * to be non-blocking. + * Also note that XNU's lck_rw_lock_shared_to_exclusive() is always + * blocking (when waiting on readers), which means we can not use it. + */ +int +rw_tryupgrade(krwlock_t *rwlp) +{ + int held = 0; + + if (atomic_load_nonatomic(&rwlp->rw_owner) == current_thread()) + panic("rw_enter: locking against myself!"); + + /* More readers than us? give up */ + if (atomic_load_nonatomic(&rwlp->rw_readers) != 1) + return (0); + + /* + * It is ON. We need to drop our READER lock, and try to + * grab the WRITER as quickly as possible. + */ + atomic_dec_32((volatile uint32_t *)&rwlp->rw_readers); + lck_rw_unlock_shared((lck_rw_t *)&rwlp->rw_lock[0]); + + /* Grab the WRITER lock */ + held = lck_rw_try_lock((lck_rw_t *)&rwlp->rw_lock[0], + LCK_RW_TYPE_EXCLUSIVE); + + if (held) { + /* + * Looks like we won the exclusive lock. + * If we are on a relaxed memory ordering system, + * we need a barrier here anyway, which will publish + * the rw_owner write + */ + rwlp->rw_owner = current_thread(); + spl_data_barrier(); + ASSERT3U(rwlp->rw_readers, ==, 0); + return (1); + } + + /* + * The worst has happened, we failed to grab WRITE lock, either + * due to another WRITER lock, or, some READER came along. + * IllumOS implementation returns with the READER lock again + * so we need to grab it. + */ + rw_enter(rwlp, RW_READER); + return (0); + +} + +void +rw_exit(krwlock_t *rwlp) +{ + if (rwlp->rw_owner == current_thread()) { + ASSERT3U(atomic_load_nonatomic(&rwlp->rw_readers), ==, 0); + atomic_store_nonatomic(&rwlp->rw_owner, NULL); + lck_rw_unlock_exclusive((lck_rw_t *)&rwlp->rw_lock[0]); + } else { + ASSERT3P(atomic_load_nonatomic(&rwlp->rw_owner), ==, NULL); + atomic_dec_32((volatile uint32_t *)&rwlp->rw_readers); + lck_rw_unlock_shared((lck_rw_t *)&rwlp->rw_lock[0]); + } +} + +int +rw_lock_held(krwlock_t *rwlp) +{ + /* + * ### not sure about this one ### + */ + return (atomic_load_nonatomic(&rwlp->rw_owner) == current_thread() || + atomic_load_nonatomic(&rwlp->rw_readers) > 0); +} + +int +rw_read_held(krwlock_t *rwlp) +{ + return (rw_lock_held(rwlp) && + atomic_load_nonatomic(&rwlp->rw_owner) == NULL); +} + +int +rw_write_held(krwlock_t *rwlp) +{ + return (atomic_load_nonatomic(&rwlp->rw_owner) == current_thread()); +} + +void +rw_downgrade(krwlock_t *rwlp) +{ + if (atomic_load_nonatomic(&rwlp->rw_owner) != current_thread()) + panic("SPL: rw_downgrade not WRITE lock held\n"); + atomic_store_nonatomic(&rwlp->rw_owner, NULL); + lck_rw_lock_exclusive_to_shared((lck_rw_t *)&rwlp->rw_lock[0]); + spl_data_barrier(); + atomic_inc_32((volatile uint32_t *)&rwlp->rw_readers); +} + +int +spl_rwlock_init(void) +{ + zfs_rwlock_attr = lck_attr_alloc_init(); + zfs_rwlock_group_attr = lck_grp_attr_alloc_init(); + zfs_rwlock_group = lck_grp_alloc_init("zfs-rwlock", + zfs_rwlock_group_attr); + +#ifdef SPL_DEBUG_RWLOCK + list_create(&rwlock_list, sizeof (struct leak), + offsetof(struct leak, rwlock_leak_node)); + lck_mtx_init((lck_mtx_t *)&rwlock_list_mutex, + zfs_rwlock_group, zfs_rwlock_attr); +#endif + + return (0); +} + +void +spl_rwlock_fini(void) +{ + +#ifdef SPL_DEBUG_RWLOCK + uint64_t total = 0; + printf("SPL: %s:%d: Dumping leaked rwlock allocations..." + " zfs_active_rwlock == %llu\n", + __func__, __LINE__, atomic_load_64(&zfs_active_rwlock)); + + rwlist_mutex_enter(&rwlock_list_mutex); + while (1) { + struct leak *leak, *runner; + uint32_t found; + + leak = list_head(&rwlock_list); + + if (leak) { + list_remove(&rwlock_list, leak); + } + if (!leak) break; + + // Run through list and count up how many times this leak is + // found, removing entries as we go. + for (found = 1, runner = list_head(&rwlock_list); + runner; + runner = runner ? list_next(&rwlock_list, runner) : + list_head(&rwlock_list)) { + + if (strcmp(leak->location_file, runner->location_file) + == 0 && + strcmp(leak->location_function, + runner->location_function) == 0 && + leak->location_line == runner->location_line) { + // Same place + found++; + list_remove(&rwlock_list, runner); + IOFreeType(runner, struct leak); + runner = NULL; + } // if same + + } // for all nodes + + printf("SPL: %s:%d rwlock %p : %s %s %llu : # leaks: %u\n", + __func__, __LINE__, + leak->mp, + leak->location_file, + leak->location_function, + leak->location_line, + found); + + IOFreeType(leak, struct leak); + total += found; + + } + rwlist_mutex_exit(&rwlock_list_mutex); + printf("SPL: %s:%d: Dumped %llu leaked allocations.\n", + __func__, __LINE__, total); + + lck_mtx_destroy((lck_mtx_t *)&rwlock_list_mutex, + zfs_rwlock_group); + list_destroy(&rwlock_list); +#endif + + lck_grp_free(zfs_rwlock_group); + zfs_rwlock_group = NULL; + + lck_grp_attr_free(zfs_rwlock_group_attr); + zfs_rwlock_group_attr = NULL; + + lck_attr_free(zfs_rwlock_attr); + zfs_rwlock_attr = NULL; + + if (atomic_load_64(&zfs_active_rwlock) != 0) + printf("SPL: %s:%d, zfs_active_wrlock is %llu\n", + __func__, __LINE__, atomic_load_64(&zfs_active_rwlock)); + else + printf("SPL: %s: good, zero zfs_active_wrlock\n", __func__); + +} diff --git a/module/os/macos/spl/spl-seg_kmem.c b/module/os/macos/spl/spl-seg_kmem.c new file mode 100644 index 000000000000..69aaa6c9e37d --- /dev/null +++ b/module/os/macos/spl/spl-seg_kmem.c @@ -0,0 +1,347 @@ +/* + * 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) 1998, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#include + +#include +#include + +#include +#include +#include + +#include + +/* + * seg_kmem is the primary kernel memory segment driver. It + * maps the kernel heap [kernelheap, ekernelheap), module text, + * and all memory which was allocated before the VM was initialized + * into kas. + * + * Pages which belong to seg_kmem are hashed into &kvp vnode at + * an offset equal to (u_offset_t)virt_addr, and have p_lckcnt >= 1. + * They must never be paged out since segkmem_fault() is a no-op to + * prevent recursive faults. + * + * Currently, seg_kmem pages are sharelocked (p_sharelock == 1) on + * __x86 and are unlocked (p_sharelock == 0) on __sparc. Once __x86 + * supports relocation the #ifdef kludges can be removed. + * + * seg_kmem pages may be subject to relocation by page_relocate(), + * provided that the HAT supports it; if this is so, segkmem_reloc + * will be set to a nonzero value. All boot time allocated memory as + * well as static memory is considered off limits to relocation. + * Pages are "relocatable" if p_state does not have P_NORELOC set, so + * we request P_NORELOC pages for memory that isn't safe to relocate. + * + * The kernel heap is logically divided up into four pieces: + * + * heap32_arena is for allocations that require 32-bit absolute + * virtual addresses (e.g. code that uses 32-bit pointers/offsets). + * + * heap_core is for allocations that require 2GB *relative* + * offsets; in other words all memory from heap_core is within + * 2GB of all other memory from the same arena. This is a requirement + * of the addressing modes of some processors in supervisor code. + * + * heap_arena is the general heap arena. + * + * static_arena is the static memory arena. Allocations from it + * are not subject to relocation so it is safe to use the memory + * physical address as well as the virtual address (e.g. the VA to + * PA translations are static). Caches may import from static_arena; + * all other static memory allocations should use static_alloc_arena. + * + * On some platforms which have limited virtual address space, seg_kmem + * may share [kernelheap, ekernelheap) with seg_kp; if this is so, + * segkp_bitmap is non-NULL, and each bit represents a page of virtual + * address space which is actually seg_kp mapped. + */ + +/* + * Rough stubbed Port for XNU. + * + * Copyright (c) 2014 Brendon Humphrey (brendon.humphrey@mac.com) + */ + + +#ifdef _KERNEL +#define XNU_KERNEL_PRIVATE +#include +extern vm_map_t kernel_map; + +/* + * These extern prototypes has to be carefully checked against XNU source + * in case Apple changes them. They are not defined in the "allowed" parts + * of the kernel.framework + */ +typedef uint8_t vm_tag_t; + +/* + * Tag we use to identify memory we have allocated + * + * (VM_KERN_MEMORY_KEXT - mach_vm_statistics.h) + */ +#define SPL_TAG 6 + + + + +/* + * In kernel lowlevel form of malloc. + */ +void *IOMalloc(vm_size_t size); +void *IOMallocAligned(vm_size_t size, vm_offset_t alignment); + +/* + * Free memory + */ +void IOFree(const void *address, vm_size_t size); +void IOFreeAligned(const void *address, vm_size_t size); + +#endif /* _KERNEL */ + +typedef int page_t; + +void *segkmem_alloc(vmem_t *vmp, size_t size, int vmflag); +void segkmem_free(vmem_t *vmp, const void *inaddr, size_t size); + +/* Total memory held allocated */ +uint64_t segkmem_total_mem_allocated = 0; + +/* primary kernel heap arena */ +vmem_t *heap_arena; + +/* qcaches abd */ +vmem_t *abd_arena; +vmem_t *abd_subpage_arena; + +#ifdef _KERNEL +extern uint64_t total_memory; +uint64_t stat_osif_malloc_success = 0; +uint64_t stat_osif_malloc_fail = 0; +uint64_t stat_osif_free = 0; +uint64_t stat_osif_malloc_bytes = 0; +uint64_t stat_osif_free_bytes = 0; +uint64_t stat_osif_malloc_sub128k = 0; +uint64_t stat_osif_malloc_sub64k = 0; +uint64_t stat_osif_malloc_sub32k = 0; +uint64_t stat_osif_malloc_page = 0; +uint64_t stat_osif_malloc_subpage = 0; +#endif + +void * +osif_malloc(uint64_t size) +{ +#ifdef _KERNEL + // vm_offset_t tr = NULL; + void *tr = NULL; + kern_return_t kr = -1; + + // kern_return_t kr = kmem_alloc(kernel_map, &tr, size); + // tr = IOMalloc(size); + + if (size < PAGESIZE) + atomic_inc_64(&stat_osif_malloc_subpage); + else if (size == PAGESIZE) + atomic_inc_64(&stat_osif_malloc_page); + else if (size < 32768) + atomic_inc_64(&stat_osif_malloc_sub32k); + else if (size < 65536) + atomic_inc_64(&stat_osif_malloc_sub64k); + else if (size < 131072) + atomic_inc_64(&stat_osif_malloc_sub128k); + + /* + * On Intel and ARM we can deal with eight-byte-aligned pointers from + * IOMallocAligned(). Larger alignment may be faster, but may also + * cause problems when we have a system with very large RAM that we + * want to use for ARC and other zfs purposes. + */ + const uint64_t align = 8; + + tr = IOMallocAligned(size, align); + if (tr != NULL) + kr = KERN_SUCCESS; + + if (kr == KERN_SUCCESS) { + atomic_inc_64(&stat_osif_malloc_success); + atomic_add_64(&segkmem_total_mem_allocated, size); + atomic_add_64(&stat_osif_malloc_bytes, size); + return ((void *)tr); + } else { + /* + * Apple documentation says IOMallocAligned() + * may return NULL. Make a note of these and + * bubble the result upwards to deal with, + * which may result in a kmem allocator returning + * NULL, or potentially a panic if VM_PANIC is set. + * + * The only places VM_PANIC is set are in vmem_init() and if + * in the call to vmem_populate is called because the + * VMC_POPULATOR flag is given vmem_create(), so only very + * early in vmem initialization. + */ + atomic_inc_64(&stat_osif_malloc_fail); + return (NULL); + } +#else + return (malloc(size)); +#endif +} + +void +osif_free(const void *buf, uint64_t size) +{ +#ifdef _KERNEL + IOFreeAligned(buf, size); + atomic_inc_64(&stat_osif_free); + atomic_sub_64(&segkmem_total_mem_allocated, size); + atomic_add_64(&stat_osif_free_bytes, size); +#else + free(buf); +#endif /* _KERNEL */ +} + +/* + * Configure vmem, such that the heap arena is fed, + * and drains to the kernel low level allocator. + */ +void +kernelheap_init() +{ + heap_arena = vmem_init("heap", NULL, 0, +#if defined(__arm64__) + 4096, +#else + PAGESIZE, +#endif + segkmem_alloc, + segkmem_free); +} + + +void +kernelheap_fini(void) +{ + vmem_fini(heap_arena); +} + +void * +segkmem_alloc(vmem_t *vmp, size_t size, int maybe_unmasked_vmflag) +{ + return (osif_malloc(size)); +} + +void +segkmem_free(vmem_t *vmp, const void *inaddr, size_t size) +{ + osif_free(inaddr, size); + // since this is mainly called by spl_root_arena and free_arena, + // do we really want to wake up a waiter, just because we have + // transferred from one to the other? + // we already have vmem_add_a_gibibyte waking up waiters + // so specializing here seems wasteful + // (originally included in vmem_experiments) + // cv_signal(&vmp->vm_cv); +} + +/* + * OSX does not use separate heaps for the ZIO buffers, + * the ZFS code is structured such that the zio caches will + * fallback to using the kmem_default arena same + * as all the other caches. + */ +// smd: we nevertheless plumb in an arena with heap as parent, so that +// we can track stats and maintain the VM_ / qc settings differently +void +segkmem_abd_init() +{ + /* + * OpenZFS does not segregate the abd kmem cache out of the general + * heap, leading to large numbers of short-lived slabs exchanged + * between the kmem cache and it's parent. XNU absorbs this with a + * qcache, following its history of absorbing the pre-ABD zio file and + * metadata caches being qcached (which raises the exchanges with the + * general heap from PAGESIZE to 256k). + */ + + extern vmem_t *spl_heap_arena; + +#define BIG_SLAB 131072 +#ifdef __arm64__ +#define BIG_BIG_SLAB (BIG_SLAB * 2) +#else +#define BIG_BIG_SLAB BIG_SLAB +#endif + +#define SMALL_RAM_MACHINE (4ULL * 1024ULL * 1024ULL * 1024ULL) + + if (total_memory >= SMALL_RAM_MACHINE) { + abd_arena = vmem_create("abd_cache", NULL, 0, + PAGESIZE, vmem_alloc_impl, vmem_free_impl, spl_heap_arena, + BIG_BIG_SLAB, VM_SLEEP | VMC_NO_QCACHE); + } else { + abd_arena = vmem_create("abd_cache", NULL, 0, + PAGESIZE, vmem_alloc_impl, vmem_free_impl, spl_heap_arena, + 131072, VM_SLEEP | VMC_NO_QCACHE); + } + + VERIFY3P(abd_arena, !=, NULL); + + /* + * We also have a sub-arena for sub-page allocations, so as to avoid + * memory waste, while segregating ABDs for visibility and + * fragmentation control. + * + * This approach presently assumes SPA_MINBLOCKSIZE is 512 and that + * PAGESIZE is an even multiple of at least several SPA_MINBLOCKSIZE. + * This will be _Static_assert-ed in abd_os.c. + */ + + if (total_memory >= SMALL_RAM_MACHINE) { + abd_subpage_arena = vmem_create("abd_subpage_cache", NULL, 0, + sizeof (void *), vmem_alloc_impl, vmem_free_impl, + spl_heap_arena, + BIG_SLAB, VM_SLEEP | VMC_NO_QCACHE); + } else { + abd_subpage_arena = vmem_create("abd_subpage_cache", NULL, 0, + 512, vmem_alloc_impl, vmem_free_impl, abd_arena, + 131072, VM_SLEEP | VMC_NO_QCACHE); + } + + VERIFY3P(abd_subpage_arena, !=, NULL); +} + +void +segkmem_abd_fini(void) +{ + if (abd_subpage_arena) { + vmem_destroy(abd_subpage_arena); + } + + if (abd_arena) { + vmem_destroy(abd_arena); + } +} diff --git a/module/os/macos/spl/spl-taskq.c b/module/os/macos/spl/spl-taskq.c new file mode 100644 index 000000000000..3b58d3335c97 --- /dev/null +++ b/module/os/macos/spl/spl-taskq.c @@ -0,0 +1,2899 @@ +/* + * 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 2015 Nexenta Systems, Inc. All rights reserved. + */ + +/* + * Copyright (C) 2015, 2020 Jorgen Lundman + * Copyright (C) 2023 Sean Doran + */ + +/* + * Kernel task queues: general-purpose asynchronous task scheduling. + * + * A common problem in kernel programming is the need to schedule tasks + * to be performed later, by another thread. There are several reasons + * you may want or need to do this: + * + * (1) The task isn't time-critical, but your current code path is. + * + * (2) The task may require grabbing locks that you already hold. + * + * (3) The task may need to block (e.g. to wait for memory), but you + * cannot block in your current context. + * + * (4) Your code path can't complete because of some condition, but you can't + * sleep or fail, so you queue the task for later execution when condition + * disappears. + * + * (5) You just want a simple way to launch multiple tasks in parallel. + * + * Task queues provide such a facility. In its simplest form (used when + * performance is not a critical consideration) a task queue consists of a + * single list of tasks, together with one or more threads to service the + * list. There are some cases when this simple queue is not sufficient: + * + * (1) The task queues are very hot and there is a need to avoid data and lock + * contention over global resources. + * + * (2) Some tasks may depend on other tasks to complete, so they can't be put in + * the same list managed by the same thread. + * + * (3) Some tasks may block for a long time, and this should not block other + * tasks in the queue. + * + * To provide useful service in such cases we define a "dynamic task queue" + * which has an individual thread for each of the tasks. These threads are + * dynamically created as they are needed and destroyed when they are not in + * use. The API for managing task pools is the same as for managing task queues + * with the exception of a taskq creation flag TASKQ_DYNAMIC which tells that + * dynamic task pool behavior is desired. + * + * Dynamic task queues may also place tasks in the normal queue (called "backing + * queue") when task pool runs out of resources. Users of task queues may + * disallow such queued scheduling by specifying TQ_NOQUEUE in the dispatch + * flags. + * + * The backing task queue is also used for scheduling internal tasks needed for + * dynamic task queue maintenance. + * + * INTERFACES ================================================================== + * + * taskq_t *taskq_create(name, nthreads, pri, minalloc, maxall, flags); + * + * Create a taskq with specified properties. + * Possible 'flags': + * + * TASKQ_DYNAMIC: Create task pool for task management. If this flag is + * specified, 'nthreads' specifies the maximum number of threads in + * the task queue. Task execution order for dynamic task queues is + * not predictable. + * + * If this flag is not specified (default case) a + * single-list task queue is created with 'nthreads' threads + * servicing it. Entries in this queue are managed by + * taskq_ent_alloc() and taskq_ent_free() which try to keep the + * task population between 'minalloc' and 'maxalloc', but the + * latter limit is only advisory for TQ_SLEEP dispatches and the + * former limit is only advisory for TQ_NOALLOC dispatches. If + * TASKQ_PREPOPULATE is set in 'flags', the taskq will be + * prepopulated with 'minalloc' task structures. + * + * Since non-DYNAMIC taskqs are queues, tasks are guaranteed to be + * executed in the order they are scheduled if nthreads == 1. + * If nthreads > 1, task execution order is not predictable. + * + * TASKQ_PREPOPULATE: Prepopulate task queue with threads. + * Also prepopulate the task queue with 'minalloc' task structures. + * + * TASKQ_THREADS_CPU_PCT: This flag specifies that 'nthreads' should be + * interpreted as a percentage of the # of online CPUs on the + * system. The taskq subsystem will automatically adjust the + * number of threads in the taskq in response to CPU online + * and offline events, to keep the ratio. nthreads must be in + * the range [0,100]. + * + * The calculation used is: + * + * MAX((ncpus_online * percentage)/100, 1) + * + * This flag is not supported for DYNAMIC task queues. + * This flag is not compatible with TASKQ_CPR_SAFE. + * + * TASKQ_CPR_SAFE: This flag specifies that users of the task queue will + * use their own protocol for handling CPR issues. This flag is not + * supported for DYNAMIC task queues. This flag is not compatible + * with TASKQ_THREADS_CPU_PCT. + * + * The 'pri' field specifies the default priority for the threads that + * service all scheduled tasks. + * + * taskq_t *taskq_create_instance(name, instance, nthreads, pri, minalloc, + * maxall, flags); + * + * Like taskq_create(), but takes an instance number (or -1 to indicate + * no instance). + * + * taskq_t *taskq_create_proc(name, nthreads, pri, minalloc, maxall, proc, + * flags); + * + * Like taskq_create(), but creates the taskq threads in the specified + * system process. If proc != &p0, this must be called from a thread + * in that process. + * + * taskq_t *taskq_create_sysdc(name, nthreads, minalloc, maxall, proc, + * dc, flags); + * + * Like taskq_create_proc(), but the taskq threads will use the + * System Duty Cycle (SDC) scheduling class with a duty cycle of dc. + * + * void taskq_destroy(tap): + * + * Waits for any scheduled tasks to complete, then destroys the taskq. + * Caller should guarantee that no new tasks are scheduled in the closing + * taskq. + * + * taskqid_t taskq_dispatch(tq, func, arg, flags): + * + * Dispatches the task "func(arg)" to taskq. The 'flags' indicates whether + * the caller is willing to block for memory. The function returns an + * opaque value which is zero iff dispatch fails. If flags is TQ_NOSLEEP + * or TQ_NOALLOC and the task can't be dispatched, taskq_dispatch() fails + * and returns (taskqid_t)0. + * + * ASSUMES: func != NULL. + * + * Possible flags: + * TQ_NOSLEEP: Do not wait for resources; may fail. + * + * TQ_NOALLOC: Do not allocate memory; may fail. May only be used with + * non-dynamic task queues. + * + * TQ_NOQUEUE: Do not enqueue a task if it can't dispatch it due to + * lack of available resources and fail. If this flag is not + * set, and the task pool is exhausted, the task may be scheduled + * in the backing queue. This flag may ONLY be used with dynamic + * task queues. + * + * NOTE: This flag should always be used when a task queue is used + * for tasks that may depend on each other for completion. + * Enqueueing dependent tasks may create deadlocks. + * + * TQ_SLEEP: May block waiting for resources. May still fail for + * dynamic task queues if TQ_NOQUEUE is also specified, otherwise + * always succeed. + * + * TQ_FRONT: Puts the new task at the front of the queue. Be careful. + * + * NOTE: Dynamic task queues are much more likely to fail in + * taskq_dispatch() (especially if TQ_NOQUEUE was specified), so it + * is important to have backup strategies handling such failures. + * + * void taskq_dispatch_ent(tq, func, arg, flags, tqent) + * + * This is a light-weight form of taskq_dispatch(), that uses a + * preallocated taskq_ent_t structure for scheduling. As a + * result, it does not perform allocations and cannot ever fail. + * Note especially that it cannot be used with TASKQ_DYNAMIC + * taskqs. The memory for the tqent must not be modified or used + * until the function (func) is called. (However, func itself + * may safely modify or free this memory, once it is called.) + * Note that the taskq framework will NOT free this memory. + * + * void taskq_wait(tq): + * + * Waits for all previously scheduled tasks to complete. + * + * NOTE: It does not stop any new task dispatches. + * Do NOT call taskq_wait() from a task: it will cause deadlock. + * + * void taskq_suspend(tq) + * + * Suspend all task execution. Tasks already scheduled for a dynamic task + * queue will still be executed, but all new scheduled tasks will be + * suspended until taskq_resume() is called. + * + * int taskq_suspended(tq) + * + * Returns 1 if taskq is suspended and 0 otherwise. It is intended to + * ASSERT that the task queue is suspended. + * + * void taskq_resume(tq) + * + * Resume task queue execution. + * + * int taskq_member(tq, thread) + * + * Returns 1 if 'thread' belongs to taskq 'tq' and 0 otherwise. The + * intended use is to ASSERT that a given function is called in taskq + * context only. + * + * system_taskq + * + * Global system-wide dynamic task queue for common uses. It may be used by + * any subsystem that needs to schedule tasks and does not need to manage + * its own task queues. It is initialized quite early during system boot. + * + * IMPLEMENTATION ============================================================== + * + * This is schematic representation of the task queue structures. + * + * taskq: + * +-------------+ + * | tq_lock | +---< taskq_ent_free() + * +-------------+ | + * |... | | tqent: tqent: + * +-------------+ | +------------+ +------------+ + * | tq_freelist |-->| tqent_next |--> ... ->| tqent_next | + * +-------------+ +------------+ +------------+ + * |... | | ... | | ... | + * +-------------+ +------------+ +------------+ + * | tq_task | | + * | | +-------------->taskq_ent_alloc() + * +--------------------------------------------------------------------------+ + * | | | tqent tqent | + * | +---------------------+ +--> +------------+ +--> +------------+ | + * | | ... | | | func, arg | | | func, arg | | + * +>+---------------------+ <---|-+ +------------+ <---|-+ +------------+ | + * | tq_taskq.tqent_next | ----+ | | tqent_next | --->+ | | tqent_next |--+ + * +---------------------+ | +------------+ ^ | +------------+ + * +-| tq_task.tqent_prev | +--| tqent_prev | | +--| tqent_prev | ^ + * | +---------------------+ +------------+ | +------------+ | + * | |... | | ... | | | ... | | + * | +---------------------+ +------------+ | +------------+ | + * | ^ | | + * | | | | + * +--------------------------------------+--------------+ TQ_APPEND() -+ + * | | | + * |... | taskq_thread()-----+ + * +-------------+ + * | tq_buckets |--+-------> [ NULL ] (for regular task queues) + * +-------------+ | + * | DYNAMIC TASK QUEUES: + * | + * +-> taskq_bucket[nCPU] taskq_bucket_dispatch() + * +-------------------+ ^ + * +--->| tqbucket_lock | | + * | +-------------------+ +--------+ +--------+ + * | | tqbucket_freelist |-->| tqent |-->...| tqent | ^ + * | +-------------------+<--+--------+<--...+--------+ | + * | | ... | | thread | | thread | | + * | +-------------------+ +--------+ +--------+ | + * | +-------------------+ | + * taskq_dispatch()--+--->| tqbucket_lock | TQ_APPEND()------+ + * TQ_HASH() | +-------------------+ +--------+ +--------+ + * | | tqbucket_freelist |-->| tqent |-->...| tqent | + * | +-------------------+<--+--------+<--...+--------+ + * | | ... | | thread | | thread | + * | +-------------------+ +--------+ +--------+ + * +---> ... + * + * + * Task queues use tq_task field to link new entry in the queue. The queue is a + * circular doubly-linked list. Entries are put in the end of the list with + * TQ_APPEND() and processed from the front of the list by taskq_thread() in + * FIFO order. Task queue entries are cached in the free list managed by + * taskq_ent_alloc() and taskq_ent_free() functions. + * + * All threads used by task queues mark t_taskq field of the thread to + * point to the task queue. + * + * Taskq Thread Management ----------------------------------------------------- + * + * Taskq's non-dynamic threads are managed with several variables and flags: + * + * * tq_nthreads - The number of threads in taskq_thread() for the + * taskq. + * + * * tq_active - The number of threads not waiting on a CV in + * taskq_thread(); includes newly created threads + * not yet counted in tq_nthreads. + * + * * tq_nthreads_target + * - The number of threads desired for the taskq. + * + * * tq_flags & TASKQ_CHANGING + * - Indicates that tq_nthreads != tq_nthreads_target. + * + * * tq_flags & TASKQ_THREAD_CREATED + * - Indicates that a thread is being created in the taskq. + * + * During creation, tq_nthreads and tq_active are set to 0, and + * tq_nthreads_target is set to the number of threads desired. The + * TASKQ_CHANGING flag is set, and taskq_thread_create() is called to + * create the first thread. taskq_thread_create() increments tq_active, + * sets TASKQ_THREAD_CREATED, and creates the new thread. + * + * Each thread starts in taskq_thread(), clears the TASKQ_THREAD_CREATED + * flag, and increments tq_nthreads. It stores the new value of + * tq_nthreads as its "thread_id", and stores its thread pointer in the + * tq_threadlist at the (thread_id - 1). We keep the thread_id space + * densely packed by requiring that only the largest thread_id can exit during + * normal adjustment. The exception is during the destruction of the + * taskq; once tq_nthreads_target is set to zero, no new threads will be created + * for the taskq queue, so every thread can exit without any ordering being + * necessary. + * + * Threads will only process work if their thread id is <= tq_nthreads_target. + * + * When TASKQ_CHANGING is set, threads will check the current thread target + * whenever they wake up, and do whatever they can to apply its effects. + * + * TASKQ_THREAD_CPU_PCT -------------------------------------------------------- + * + * When a taskq is created with TASKQ_THREAD_CPU_PCT, we store their requested + * percentage in tq_threads_ncpus_pct, start them off with the correct thread + * target, and add them to the taskq_cpupct_list for later adjustment. + * + * We register taskq_cpu_setup() to be called whenever a CPU changes state. It + * walks the list of TASKQ_THREAD_CPU_PCT taskqs, adjusts their nthread_target + * if need be, and wakes up all of the threads to process the change. + * + * Dynamic Task Queues Implementation ------------------------------------------ + * + * For a dynamic task queues there is a 1-to-1 mapping between a thread and + * taskq_ent_structure. Each entry is serviced by its own thread and each thread + * is controlled by a single entry. + * + * Entries are distributed over a set of buckets. To avoid using modulo + * arithmetics the number of buckets is 2^n and is determined as the nearest + * power of two roundown of the number of CPUs in the system. Tunable + * variable 'taskq_maxbuckets' limits the maximum number of buckets. Each entry + * is attached to a bucket for its lifetime and can't migrate to other buckets. + * + * Entries that have scheduled tasks are not placed in any list. The dispatch + * function sets their "func" and "arg" fields and signals the corresponding + * thread to execute the task. Once the thread executes the task it clears the + * "func" field and places an entry on the bucket cache of free entries pointed + * by "tqbucket_freelist" field. ALL entries on the free list should have "func" + * field equal to NULL. The free list is a circular doubly-linked list identical + * in structure to the tq_task list above, but entries are taken from it in LIFO + * order - the last freed entry is the first to be allocated. The + * taskq_bucket_dispatch() function gets the most recently used entry from the + * free list, sets its "func" and "arg" fields and signals a worker thread. + * + * After executing each task a per-entry thread taskq_d_thread() places its + * entry on the bucket free list and goes to a timed sleep. If it wakes up + * without getting new task it removes the entry from the free list and destroys + * itself. The thread sleep time is controlled by a tunable variable + * `taskq_thread_timeout'. + * + * There are various statistics kept in the bucket which allows for later + * analysis of taskq usage patterns. Also, a global copy of taskq creation and + * death statistics is kept in the global taskq data structure. Since thread + * creation and death happen rarely, updating such global data does not present + * a performance problem. + * + * NOTE: Threads are not bound to any CPU and there is absolutely no association + * between the bucket and actual thread CPU, so buckets are used only to + * split resources and reduce resource contention. Having threads attached + * to the CPU denoted by a bucket may reduce number of times the job + * switches between CPUs. + * + * Current algorithm creates a thread whenever a bucket has no free + * entries. It would be nice to know how many threads are in the running + * state and don't create threads if all CPUs are busy with existing + * tasks, but it is unclear how such strategy can be implemented. + * + * Currently buckets are created statically as an array attached to task + * queue. On some system with nCPUs < max_ncpus it may waste system + * memory. One solution may be allocation of buckets when they are first + * touched, but it is not clear how useful it is. + * + * SUSPEND/RESUME implementation ----------------------------------------------- + * + * Before executing a task taskq_thread() (executing non-dynamic task + * queues) obtains taskq's thread lock as a reader. The taskq_suspend() + * function gets the same lock as a writer blocking all non-dynamic task + * execution. The taskq_resume() function releases the lock allowing + * taskq_thread to continue execution. + * + * For dynamic task queues, each bucket is marked as TQBUCKET_SUSPEND by + * taskq_suspend() function. After that taskq_bucket_dispatch() always + * fails, so that taskq_dispatch() will either enqueue tasks for a + * suspended backing queue or fail if TQ_NOQUEUE is specified in dispatch + * flags. + * + * NOTE: taskq_suspend() does not immediately block any tasks already + * scheduled for dynamic task queues. It only suspends new tasks + * scheduled after taskq_suspend() was called. + * + * taskq_member() function works by comparing a thread t_taskq pointer with + * the passed thread pointer. + * + * LOCKS and LOCK Hierarchy ---------------------------------------------------- + * + * There are three locks used in task queues: + * + * 1) The taskq_t's tq_lock, protecting global task queue state. + * + * 2) Each per-CPU bucket has a lock for bucket management. + * + * 3) The global taskq_cpupct_lock, which protects the list of + * TASKQ_THREADS_CPU_PCT taskqs. + * + * If both (1) and (2) are needed, tq_lock should be taken *after* the bucket + * lock. + * + * If both (1) and (3) are needed, tq_lock should be taken *after* + * taskq_cpupct_lock. + * + * DEBUG FACILITIES ------------------------------------------------------------ + * + * For DEBUG kernels it is possible to induce random failures to + * taskq_dispatch() function when it is given TQ_NOSLEEP argument. The value of + * taskq_dmtbf and taskq_smtbf tunables control the mean time between induced + * failures for dynamic and static task queues respectively. + * + * Setting TASKQ_STATISTIC to 0 will disable per-bucket statistics. + * + * TUNABLES -------------------------------------------------------------------- + * + * system_taskq_size - Size of the global system_taskq. + * This value is multiplied by nCPUs to determine + * actual size. + * Default value: 64 + * + * taskq_minimum_nthreads_max + * - Minimum size of the thread list for a taskq. + * Useful for testing different thread pool + * sizes by overwriting tq_nthreads_target. + * + * taskq_thread_timeout - Maximum idle time for taskq_d_thread() + * Default value: 5 minutes + * + * taskq_maxbuckets - Maximum number of buckets in any task queue + * Default value: 128 + * + * taskq_search_depth - Maximum # of buckets searched for a free entry + * Default value: 4 + * + * taskq_dmtbf - Mean time between induced dispatch failures + * for dynamic task queues. + * Default value: UINT_MAX (no induced failures) + * + * taskq_smtbf - Mean time between induced dispatch failures + * for static task queues. + * Default value: UINT_MAX (no induced failures) + * + * CONDITIONAL compilation ----------------------------------------------------- + * + * TASKQ_STATISTIC - If set will enable bucket statistic (default). + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* For throttlefree */ +#include +#include +#ifdef __APPLE__ +#include +#include +#endif +#include + +static kmem_cache_t *taskq_ent_cache, *taskq_cache; + +static uint_t taskq_tsd; + +/* + * Pseudo instance numbers for taskqs without explicitly provided instance. + */ +static vmem_t *taskq_id_arena; + +/* Global system task queue for common use */ +taskq_t *system_taskq = NULL; +taskq_t *system_delay_taskq = NULL; + +/* + * Maximum number of entries in global system taskq is + * system_taskq_size * max_ncpus + */ +#ifdef __APPLE__ +#define SYSTEM_TASKQ_SIZE 128 +#else +#define SYSTEM_TASKQ_SIZE 64 +#endif +int system_taskq_size = SYSTEM_TASKQ_SIZE; + +/* + * Minimum size for tq_nthreads_max; useful for those who want to play around + * with increasing a taskq's tq_nthreads_target. + */ +int taskq_minimum_nthreads_max = 1; + +/* + * We want to ensure that when taskq_create() returns, there is at least + * one thread ready to handle requests. To guarantee this, we have to wait + * for the second thread, since the first one cannot process requests until + * the second thread has been created. + */ +#define TASKQ_CREATE_ACTIVE_THREADS 2 + +/* Maximum percentage allowed for TASKQ_THREADS_CPU_PCT */ +#define TASKQ_CPUPCT_MAX_PERCENT 1000 +int taskq_cpupct_max_percent = TASKQ_CPUPCT_MAX_PERCENT; + +/* + * Dynamic task queue threads that don't get any work within + * taskq_thread_timeout destroy themselves + */ +#define TASKQ_THREAD_TIMEOUT (60 * 5) +int taskq_thread_timeout = TASKQ_THREAD_TIMEOUT; + +#define TASKQ_MAXBUCKETS 128 +int taskq_maxbuckets = TASKQ_MAXBUCKETS; + +/* + * When a bucket has no available entries another buckets are tried. + * taskq_search_depth parameter limits the amount of buckets that we search + * before failing. This is mostly useful in systems with many CPUs where we may + * spend too much time scanning busy buckets. + */ +#define TASKQ_SEARCH_DEPTH 4 +int taskq_search_depth = TASKQ_SEARCH_DEPTH; + +/* + * Hashing function: mix various bits of x. May be pretty much anything. + */ +#define TQ_HASH(x) ((x) ^ ((x) >> 11) ^ ((x) >> 17) ^ ((x) ^ 27)) + +/* + * We do not create any new threads when the system is low on memory and start + * throttling memory allocations. The following macro tries to estimate such + * condition. + */ +#ifdef __APPLE__ +#define ENOUGH_MEMORY() (!spl_vm_pool_low()) +#else +#define ENOUGH_MEMORY() (freemem > throttlefree) +#endif + +/* + * Static functions. + */ +static taskq_t *taskq_create_common(const char *, int, int, pri_t, int, + int, proc_t *, uint_t, uint_t); +static void taskq_thread(void *); +static void taskq_d_thread(taskq_ent_t *); +static void taskq_bucket_extend(void *); +static int taskq_constructor(void *, void *, int); +static void taskq_destructor(void *, void *); +static int taskq_ent_constructor(void *, void *, int); +static void taskq_ent_destructor(void *, void *); +static taskq_ent_t *taskq_ent_alloc(taskq_t *, int); +static void taskq_ent_free(taskq_t *, taskq_ent_t *); +static int taskq_ent_exists(taskq_t *, task_func_t, void *); +static taskq_ent_t *taskq_bucket_dispatch(taskq_bucket_t *, task_func_t, + void *); + +/* + * Task queues kstats. + */ +struct taskq_kstat { + kstat_named_t tq_pid; + kstat_named_t tq_tasks; + kstat_named_t tq_executed; + kstat_named_t tq_maxtasks; + kstat_named_t tq_totaltime; + kstat_named_t tq_nalloc; + kstat_named_t tq_nactive; + kstat_named_t tq_pri; + kstat_named_t tq_nthreads; +} taskq_kstat = { + { "pid", KSTAT_DATA_UINT64 }, + { "tasks", KSTAT_DATA_UINT64 }, + { "executed", KSTAT_DATA_UINT64 }, + { "maxtasks", KSTAT_DATA_UINT64 }, + { "totaltime", KSTAT_DATA_UINT64 }, + { "nactive", KSTAT_DATA_UINT64 }, + { "nalloc", KSTAT_DATA_UINT64 }, + { "priority", KSTAT_DATA_UINT64 }, + { "threads", KSTAT_DATA_UINT64 }, +}; + +struct taskq_d_kstat { + kstat_named_t tqd_pri; + kstat_named_t tqd_btasks; + kstat_named_t tqd_bexecuted; + kstat_named_t tqd_bmaxtasks; + kstat_named_t tqd_bnalloc; + kstat_named_t tqd_bnactive; + kstat_named_t tqd_btotaltime; + kstat_named_t tqd_hits; + kstat_named_t tqd_misses; + kstat_named_t tqd_overflows; + kstat_named_t tqd_tcreates; + kstat_named_t tqd_tdeaths; + kstat_named_t tqd_maxthreads; + kstat_named_t tqd_nomem; + kstat_named_t tqd_disptcreates; + kstat_named_t tqd_totaltime; + kstat_named_t tqd_nalloc; + kstat_named_t tqd_nfree; +} taskq_d_kstat = { + { "priority", KSTAT_DATA_UINT64 }, + { "btasks", KSTAT_DATA_UINT64 }, + { "bexecuted", KSTAT_DATA_UINT64 }, + { "bmaxtasks", KSTAT_DATA_UINT64 }, + { "bnalloc", KSTAT_DATA_UINT64 }, + { "bnactive", KSTAT_DATA_UINT64 }, + { "btotaltime", KSTAT_DATA_UINT64 }, + { "hits", KSTAT_DATA_UINT64 }, + { "misses", KSTAT_DATA_UINT64 }, + { "overflows", KSTAT_DATA_UINT64 }, + { "tcreates", KSTAT_DATA_UINT64 }, + { "tdeaths", KSTAT_DATA_UINT64 }, + { "maxthreads", KSTAT_DATA_UINT64 }, + { "nomem", KSTAT_DATA_UINT64 }, + { "disptcreates", KSTAT_DATA_UINT64 }, + { "totaltime", KSTAT_DATA_UINT64 }, + { "nalloc", KSTAT_DATA_UINT64 }, + { "nfree", KSTAT_DATA_UINT64 }, +}; + +static kmutex_t taskq_kstat_lock; +static kmutex_t taskq_d_kstat_lock; +static int taskq_kstat_update(kstat_t *, int); +static int taskq_d_kstat_update(kstat_t *, int); + +/* + * List of all TASKQ_THREADS_CPU_PCT taskqs. + */ +static list_t taskq_cpupct_list; /* protected by cpu_lock */ + +/* + * Collect per-bucket statistic when TASKQ_STATISTIC is defined. + */ +#define TASKQ_STATISTIC 1 + +#if TASKQ_STATISTIC +#define TQ_STAT(b, x) b->tqbucket_stat.x++ +#else +#define TQ_STAT(b, x) +#endif + +/* + * Random fault injection. + */ +uint_t taskq_random; +uint_t taskq_dmtbf = UINT_MAX; /* mean time between injected failures */ +uint_t taskq_smtbf = UINT_MAX; /* mean time between injected failures */ + +/* + * TQ_NOSLEEP dispatches on dynamic task queues are always allowed to fail. + * + * TQ_NOSLEEP dispatches on static task queues can't arbitrarily fail because + * they could prepopulate the cache and make sure that they do not use more + * then minalloc entries. So, fault injection in this case insures that + * either TASKQ_PREPOPULATE is not set or there are more entries allocated + * than is specified by minalloc. TQ_NOALLOC dispatches are always allowed + * to fail, but for simplicity we treat them identically to TQ_NOSLEEP + * dispatches. + */ +#ifdef DEBUG +#define TASKQ_D_RANDOM_DISPATCH_FAILURE(tq, flag) \ + taskq_random = (taskq_random * 2416 + 374441) % 1771875;\ + if ((flag & TQ_NOSLEEP) && \ + taskq_random < 1771875 / taskq_dmtbf) { \ + return (0); \ + } + +#define TASKQ_S_RANDOM_DISPATCH_FAILURE(tq, flag) \ + taskq_random = (taskq_random * 2416 + 374441) % 1771875;\ + if ((flag & (TQ_NOSLEEP | TQ_NOALLOC)) && \ + (!(tq->tq_flags & TASKQ_PREPOPULATE) || \ + (tq->tq_nalloc > tq->tq_minalloc)) && \ + (taskq_random < (1771875 / taskq_smtbf))) { \ + mutex_exit(&tq->tq_lock); \ + return (0); \ + } +#else +#define TASKQ_S_RANDOM_DISPATCH_FAILURE(tq, flag) +#define TASKQ_D_RANDOM_DISPATCH_FAILURE(tq, flag) +#endif + +#define IS_EMPTY(l) (((l).tqent_prev == (l).tqent_next) && \ + ((l).tqent_prev == &(l))) + +/* + * Append `tqe' in the end of the doubly-linked list denoted by l. + */ +#define TQ_APPEND(l, tqe) { \ + tqe->tqent_next = &l; \ + tqe->tqent_prev = l.tqent_prev; \ + tqe->tqent_next->tqent_prev = tqe; \ + tqe->tqent_prev->tqent_next = tqe; \ +} +/* + * Prepend 'tqe' to the beginning of l + */ +#define TQ_PREPEND(l, tqe) { \ + tqe->tqent_next = l.tqent_next; \ + tqe->tqent_prev = &l; \ + tqe->tqent_next->tqent_prev = tqe; \ + tqe->tqent_prev->tqent_next = tqe; \ +} + +/* + * Schedule a task specified by func and arg into the task queue entry tqe. + */ +#define TQ_DO_ENQUEUE(tq, tqe, func, arg, front) { \ + ASSERT(MUTEX_HELD(&tq->tq_lock)); \ + if (front) { \ + TQ_PREPEND(tq->tq_task, tqe); \ + } else { \ + TQ_APPEND(tq->tq_task, tqe); \ + } \ + tqe->tqent_func = (func); \ + tqe->tqent_arg = (arg); \ + tq->tq_tasks++; \ + if (tq->tq_tasks - tq->tq_executed > tq->tq_maxtasks) \ + tq->tq_maxtasks = tq->tq_tasks - tq->tq_executed; \ + cv_signal(&tq->tq_dispatch_cv); \ + DTRACE_PROBE2(taskq__enqueue, taskq_t *, tq, taskq_ent_t *, tqe); \ +} + +#define TQ_ENQUEUE(tq, tqe, func, arg) \ + TQ_DO_ENQUEUE(tq, tqe, func, arg, 0) + +#define TQ_ENQUEUE_FRONT(tq, tqe, func, arg) \ + TQ_DO_ENQUEUE(tq, tqe, func, arg, 1) + +/* + * Do-nothing task which may be used to prepopulate thread caches. + */ +void +nulltask(void *unused) +{ +} + +static int +taskq_constructor(void *buf, void *cdrarg, int kmflags) +{ + taskq_t *tq = buf; + + memset(tq, 0, sizeof (taskq_t)); + + mutex_init(&tq->tq_lock, NULL, MUTEX_DEFAULT, NULL); + rw_init(&tq->tq_threadlock, NULL, RW_DEFAULT, NULL); + cv_init(&tq->tq_dispatch_cv, NULL, CV_DEFAULT, NULL); + cv_init(&tq->tq_exit_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); + + tq->tq_task.tqent_next = &tq->tq_task; + tq->tq_task.tqent_prev = &tq->tq_task; + + return (0); +} + +static void +taskq_destructor(void *buf, void *cdrarg) +{ + taskq_t *tq = buf; + + ASSERT(tq->tq_nthreads == 0); + ASSERT(tq->tq_buckets == NULL); + ASSERT(tq->tq_tcreates == 0); + ASSERT(tq->tq_tdeaths == 0); + + mutex_destroy(&tq->tq_lock); + rw_destroy(&tq->tq_threadlock); + cv_destroy(&tq->tq_dispatch_cv); + cv_destroy(&tq->tq_exit_cv); + cv_destroy(&tq->tq_wait_cv); + cv_destroy(&tq->tq_maxalloc_cv); +} + +static int +taskq_ent_constructor(void *buf, void *cdrarg, int kmflags) +{ + taskq_ent_t *tqe = buf; + + memset(tqe, 0, sizeof (taskq_ent_t)); + cv_init(&tqe->tqent_cv, NULL, CV_DEFAULT, NULL); +#ifdef __APPLE__ + /* Simulate TS_STOPPED */ + mutex_init(&tqe->tqent_thread_lock, NULL, MUTEX_DEFAULT, NULL); + cv_init(&tqe->tqent_thread_cv, NULL, CV_DEFAULT, NULL); +#endif /* __APPLE__ */ + return (0); +} + +static void +taskq_ent_destructor(void *buf, void *cdrarg) +{ + taskq_ent_t *tqe = buf; + + ASSERT(tqe->tqent_thread == NULL); + cv_destroy(&tqe->tqent_cv); +#ifdef __APPLE__ + /* See comment in taskq_d_thread(). */ + mutex_destroy(&tqe->tqent_thread_lock); + cv_destroy(&tqe->tqent_thread_cv); +#endif /* __APPLE__ */ +} + + +struct tqdelay { + // list + list_node_t tqd_listnode; + // time (list sorted on this) + clock_t tqd_time; + // func + taskq_t *tqd_taskq; + task_func_t *tqd_func; + void *tqd_arg; + uint_t tqd_tqflags; +}; + +typedef struct tqdelay tqdelay_t; + +static list_t tqd_list; +static kmutex_t tqd_delay_lock; +static kcondvar_t tqd_delay_cv; +static int tqd_do_exit = 0; + +static void +taskq_delay_dispatcher_thread(void *notused) +{ + callb_cpr_t cpr; + + dprintf("%s: starting\n", __func__); + CALLB_CPR_INIT(&cpr, &tqd_delay_lock, callb_generic_cpr, FTAG); + + mutex_enter(&tqd_delay_lock); + while (tqd_do_exit == 0) { + tqdelay_t *tqdnode; + CALLB_CPR_SAFE_BEGIN(&cpr); + + /* + * If list is empty, just sleep until signal, + * otherwise, sleep on list_head (lowest in the list) + */ + tqdnode = list_head(&tqd_list); + + if (tqdnode == NULL) + (void) cv_wait(&tqd_delay_cv, &tqd_delay_lock); + else + cv_timedwait(&tqd_delay_cv, + &tqd_delay_lock, tqdnode->tqd_time); + CALLB_CPR_SAFE_END(&cpr, &tqd_delay_lock); + + if (tqd_do_exit != 0) + break; + + /* If we got a node, and we slept until expired, run it. */ + tqdnode = list_head(&tqd_list); + if (tqdnode != NULL) { + clock_t now = ddi_get_lbolt(); + /* Time has arrived */ + if (tqdnode->tqd_time <= now) { + list_remove(&tqd_list, tqdnode); + taskq_dispatch(tqdnode->tqd_taskq, + tqdnode->tqd_func, tqdnode->tqd_arg, + tqdnode->tqd_tqflags); + kmem_free(tqdnode, sizeof (tqdelay_t)); + } + } + } + + tqd_do_exit = 0; + cv_broadcast(&tqd_delay_cv); + CALLB_CPR_EXIT(&cpr); /* drops lock */ + dprintf("%s: exit\n", __func__); + thread_exit(); +} + +taskqid_t +taskq_dispatch_delay(taskq_t *tq, task_func_t func, void *arg, uint_t tqflags, + clock_t expire_time) +{ + tqdelay_t *tqdnode; + + tqdnode = kmem_alloc(sizeof (tqdelay_t), KM_SLEEP); + + /* If it has already expired, just dispatch */ + if (expire_time <= ddi_get_lbolt()) { + (void) taskq_dispatch(tq, func, arg, tqflags); + /* + * We free the node here, and still return the pointer. + * If they call taskq_cancel_id() the pointer wil not be + * in the list, so nothing happens. + * We could make this use something like KMEM_ZERO_SIZE_PTR + * but perhaps the callers expect unique ids? + */ + kmem_free(tqdnode, sizeof (tqdelay_t)); + return ((taskqid_t)tqdnode); + } + + tqdnode->tqd_time = expire_time; + tqdnode->tqd_taskq = tq; + tqdnode->tqd_func = func; + tqdnode->tqd_arg = arg; + tqdnode->tqd_tqflags = tqflags; + + mutex_enter(&tqd_delay_lock); + + /* Insert sorted on time */ + tqdelay_t *runner; + for (runner = list_head(&tqd_list); + runner != NULL; + runner = list_next(&tqd_list, runner)) + if (tqdnode->tqd_time < runner->tqd_time) { + list_insert_before(&tqd_list, runner, tqdnode); + break; + } + if (runner == NULL) { + list_insert_tail(&tqd_list, tqdnode); + } + + /* We have added to the list, wake the thread up */ + cv_broadcast(&tqd_delay_cv); + mutex_exit(&tqd_delay_lock); + + return ((taskqid_t)tqdnode); +} + +/* + * Cancel an already dispatched task given the task id. Still pending tasks + * will be immediately canceled, and if the task is active the function will + * block until it completes. Preallocated tasks which are canceled must be + * freed by the caller. + * + * This function returns "0" if it definitely managed to prevent a taskq + * from running (so caller can then clean up instead), and EBUSY etc + * if it had to wait for it to finish. (Or ENOENT for unknown id) + */ +int +taskq_cancel_id(taskq_t *tq, taskqid_t id) +{ + tqdelay_t *task = (tqdelay_t *)id; + tqdelay_t *tqdnode; + + /* delay_taskq active? Linux will call with id==NULL */ + if (task != NULL) { + + /* Don't trust 'task' until it is found in the list */ + mutex_enter(&tqd_delay_lock); + + for (tqdnode = list_head(&tqd_list); + tqdnode != NULL; + tqdnode = list_next(&tqd_list, tqdnode)) { + + if (tqdnode == task) { + /* + * task exists and needs to be cancelled. + * remove it from list, and wake the thread up + * as it might be sleeping on this node. We can + * free the memory as "time" is passed in as a + * variable. + */ + list_remove(&tqd_list, tqdnode); + cv_signal(&tqd_delay_cv); + mutex_exit(&tqd_delay_lock); + + kmem_free(tqdnode, sizeof (tqdelay_t)); + + /* Stopped it from running */ + return (0); + + } // task == tqdnode + } // for + mutex_exit(&tqd_delay_lock); + } // task != NULL + + /* + * Might have just started it - + * but we don't know for *sure*, but presumably + * it has an "id" because it called dispatch_delay(). + */ + if ((void *)id != NULL) { + taskq_wait_id(tq, id); + return (EBUSY); /* EBUSY ? how can we *know* it ran? */ + } + + return (ENOENT); +} + +void +taskq_start_delay_thread(void) +{ + list_create(&tqd_list, sizeof (tqdelay_t), + offsetof(tqdelay_t, tqd_listnode)); + mutex_init(&tqd_delay_lock, NULL, MUTEX_DEFAULT, NULL); + cv_init(&tqd_delay_cv, NULL, CV_DEFAULT, NULL); + tqd_do_exit = 0; + (void) thread_create(NULL, 0, taskq_delay_dispatcher_thread, + NULL, 0, &p0, TS_RUN, minclsyspri); +} + +void +taskq_stop_delay_thread(void) +{ + tqdelay_t *tqdnode; + + mutex_enter(&tqd_delay_lock); + tqd_do_exit = 1; + /* + * The reclaim thread will set arc_reclaim_thread_exit back to + * FALSE when it is finished exiting; we're waiting for that. + */ + while (tqd_do_exit) { + cv_signal(&tqd_delay_cv); + cv_wait(&tqd_delay_cv, &tqd_delay_lock); + } + mutex_exit(&tqd_delay_lock); + mutex_destroy(&tqd_delay_lock); + cv_destroy(&tqd_delay_cv); + + while ((tqdnode = list_head(&tqd_list)) != NULL) { + list_remove(&tqd_list, tqdnode); + kmem_free(tqdnode, sizeof (tqdelay_t)); + } + + list_destroy(&tqd_list); +} + +int +spl_taskq_init(void) +{ + tsd_create(&taskq_tsd, NULL); + + taskq_ent_cache = kmem_cache_create("taskq_ent_cache", + sizeof (taskq_ent_t), 0, taskq_ent_constructor, + taskq_ent_destructor, NULL, NULL, NULL, 0); + taskq_cache = kmem_cache_create("taskq_cache", sizeof (taskq_t), + 0, taskq_constructor, taskq_destructor, NULL, NULL, NULL, 0); + taskq_id_arena = vmem_create("taskq_id_arena", + (void *)1, INT32_MAX, 1, NULL, NULL, NULL, 0, + VM_SLEEP | VMC_IDENTIFIER); + + list_create(&taskq_cpupct_list, sizeof (taskq_t), + offsetof(taskq_t, tq_cpupct_link)); + + mutex_init(&taskq_kstat_lock, NULL, MUTEX_DEFAULT, NULL); + mutex_init(&taskq_d_kstat_lock, NULL, MUTEX_DEFAULT, NULL); + + return (0); +} + +void +spl_taskq_fini(void) +{ + mutex_destroy(&taskq_d_kstat_lock); + mutex_destroy(&taskq_kstat_lock); + + if (taskq_cache) { + kmem_cache_destroy(taskq_cache); + taskq_cache = NULL; + } + if (taskq_ent_cache) { + kmem_cache_destroy(taskq_ent_cache); + taskq_ent_cache = NULL; + } + + list_destroy(&taskq_cpupct_list); + + vmem_destroy(taskq_id_arena); + + tsd_destroy(&taskq_tsd); +} + + + + +static void +taskq_update_nthreads(taskq_t *tq, uint_t ncpus) +{ + uint_t newtarget = TASKQ_THREADS_PCT(ncpus, tq->tq_threads_ncpus_pct); + +#ifndef __APPLE__ + ASSERT(MUTEX_HELD(&cpu_lock)); +#endif + ASSERT(MUTEX_HELD(&tq->tq_lock)); + + /* We must be going from non-zero to non-zero; no exiting. */ + ASSERT3U(tq->tq_nthreads_target, !=, 0); + ASSERT3U(newtarget, !=, 0); + + ASSERT3U(newtarget, <=, tq->tq_nthreads_max); + if (newtarget != tq->tq_nthreads_target) { + tq->tq_flags |= TASKQ_CHANGING; + tq->tq_nthreads_target = newtarget; + cv_broadcast(&tq->tq_dispatch_cv); + cv_broadcast(&tq->tq_exit_cv); + } +} + +#ifndef __APPLE__ +/* No dynamic CPU add/remove in XNU, so we can just use static ncpu math */ + +/* called during task queue creation */ +static void +taskq_cpupct_install(taskq_t *tq, cpupart_t *cpup) +{ + ASSERT(tq->tq_flags & TASKQ_THREADS_CPU_PCT); + + mutex_enter(&cpu_lock); + mutex_enter(&tq->tq_lock); + tq->tq_cpupart = cpup->cp_id; + taskq_update_nthreads(tq, cpup->cp_ncpus); + mutex_exit(&tq->tq_lock); + + list_insert_tail(&taskq_cpupct_list, tq); + mutex_exit(&cpu_lock); +} + +static void +taskq_cpupct_remove(taskq_t *tq) +{ + ASSERT(tq->tq_flags & TASKQ_THREADS_CPU_PCT); + + mutex_enter(&cpu_lock); + list_remove(&taskq_cpupct_list, tq); + mutex_exit(&cpu_lock); +} + +static int +taskq_cpu_setup(cpu_setup_t what, int id, void *arg) +{ + taskq_t *tq; + cpupart_t *cp = cpu[id]->cpu_part; + uint_t ncpus = cp->cp_ncpus; + + ASSERT(MUTEX_HELD(&cpu_lock)); + ASSERT(ncpus > 0); + + switch (what) { + case CPU_OFF: + case CPU_CPUPART_OUT: + /* offlines are called *before* the cpu is offlined. */ + if (ncpus > 1) + ncpus--; + break; + + case CPU_ON: + case CPU_CPUPART_IN: + break; + + default: + return (0); /* doesn't affect cpu count */ + } + + for (tq = list_head(&taskq_cpupct_list); tq != NULL; + tq = list_next(&taskq_cpupct_list, tq)) { + + mutex_enter(&tq->tq_lock); + /* + * If the taskq is part of the cpuset which is changing, + * update its nthreads_target. + */ + if (tq->tq_cpupart == cp->cp_id) { + taskq_update_nthreads(tq, ncpus); + } + mutex_exit(&tq->tq_lock); + } + return (0); +} + +void +taskq_mp_init(void) +{ + mutex_enter(&cpu_lock); + register_cpu_setup_func(taskq_cpu_setup, NULL); + /* + * Make sure we're up to date. At this point in boot, there is only + * one processor set, so we only have to update the current CPU. + */ + (void) taskq_cpu_setup(CPU_ON, CPU->cpu_id, NULL); + mutex_exit(&cpu_lock); +} +#endif /* __APPLE__ */ + + +/* + * Create global system dynamic task queue. + */ +void +system_taskq_init(void) +{ + + /* + * We depart here from opensolaris: + * + * TASKQ_REALLY_DYNAMIC is an o3xism, since not everything can be + * TASKQ_DYNAMIC and thus we eat that flag, + * and we have differfent thread count parameters. + * + * old old spl used system_taskq_size * logical_ncpus + */ + + system_taskq = taskq_create_common("system_taskq", 0, + system_taskq_size * (max_ncpus - num_ecores), + minclsyspri, 4, 512, &p0, 0, + TASKQ_DYNAMIC | TASKQ_PREPOPULATE | TASKQ_REALLY_DYNAMIC); + + system_delay_taskq = taskq_create("system_delay_taskq", + max_ncpus - num_ecores, minclsyspri, + max_ncpus - num_ecores, INT_MAX, TASKQ_PREPOPULATE); + + taskq_start_delay_thread(); + +} + + +void +system_taskq_fini(void) +{ + taskq_stop_delay_thread(); + + if (system_delay_taskq) + taskq_destroy(system_delay_taskq); + if (system_taskq) + taskq_destroy(system_taskq); + system_taskq = NULL; +} + +/* + * taskq_ent_alloc() + * + * Allocates a new taskq_ent_t structure either from the free list or from the + * cache. Returns NULL if it can't be allocated. + * + * Assumes: tq->tq_lock is held. + */ +static taskq_ent_t * +taskq_ent_alloc(taskq_t *tq, int flags) +{ + int kmflags = (flags & TQ_NOSLEEP) ? KM_NOSLEEP : KM_SLEEP; + taskq_ent_t *tqe; + clock_t wait_time; + clock_t wait_rv; + + ASSERT(MUTEX_HELD(&tq->tq_lock)); + + /* + * TQ_NOALLOC allocations are allowed to use the freelist, even if + * we are below tq_minalloc. + */ +again: if ((tqe = tq->tq_freelist) != NULL && + ((flags & TQ_NOALLOC) || tq->tq_nalloc >= tq->tq_minalloc)) { + tq->tq_freelist = tqe->tqent_next; + } else { + if (flags & TQ_NOALLOC) + return (NULL); + + if (tq->tq_nalloc >= tq->tq_maxalloc) { + if (kmflags & KM_NOSLEEP) + 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 (reap free). + */ + wait_time = ddi_get_lbolt() + hz; + while (tq->tq_freelist == NULL) { + tq->tq_maxalloc_wait++; + wait_rv = cv_timedwait(&tq->tq_maxalloc_cv, + &tq->tq_lock, wait_time); + tq->tq_maxalloc_wait--; + if (wait_rv == -1) + break; + } + if (tq->tq_freelist) + goto again; /* reap freelist */ + + } + mutex_exit(&tq->tq_lock); + + tqe = kmem_cache_alloc(taskq_ent_cache, kmflags); + + mutex_enter(&tq->tq_lock); + if (tqe != NULL) + tq->tq_nalloc++; + } + return (tqe); +} + +/* + * taskq_ent_free() + * + * Free taskq_ent_t structure by either putting it on the free list or freeing + * it to the cache. + * + * Assumes: tq->tq_lock is held. + */ +static void +taskq_ent_free(taskq_t *tq, taskq_ent_t *tqe) +{ + ASSERT(MUTEX_HELD(&tq->tq_lock)); + + if (tq->tq_nalloc <= tq->tq_minalloc) { + tqe->tqent_next = tq->tq_freelist; + tq->tq_freelist = tqe; + } else { + tq->tq_nalloc--; + mutex_exit(&tq->tq_lock); + kmem_cache_free(taskq_ent_cache, tqe); + mutex_enter(&tq->tq_lock); + } + + if (tq->tq_maxalloc_wait) + cv_signal(&tq->tq_maxalloc_cv); +} + +/* + * taskq_ent_exists() + * + * Return 1 if taskq already has entry for calling 'func(arg)'. + * + * Assumes: tq->tq_lock is held. + */ +static int +taskq_ent_exists(taskq_t *tq, task_func_t func, void *arg) +{ + taskq_ent_t *tqe; + + ASSERT(MUTEX_HELD(&tq->tq_lock)); + + for (tqe = tq->tq_task.tqent_next; tqe != &tq->tq_task; + tqe = tqe->tqent_next) + if ((tqe->tqent_func == func) && (tqe->tqent_arg == arg)) + return (1); + return (0); +} + +/* + * Dispatch a task "func(arg)" to a free entry of bucket b. + * + * Assumes: no bucket locks is held. + * + * Returns: a pointer to an entry if dispatch was successful. + * NULL if there are no free entries or if the bucket is suspended. + */ +static taskq_ent_t * +taskq_bucket_dispatch(taskq_bucket_t *b, task_func_t func, void *arg) +{ + taskq_ent_t *tqe; + + ASSERT(MUTEX_NOT_HELD(&b->tqbucket_lock)); + ASSERT(func != NULL); + + mutex_enter(&b->tqbucket_lock); + + ASSERT(b->tqbucket_nfree != 0 || IS_EMPTY(b->tqbucket_freelist)); + ASSERT(b->tqbucket_nfree == 0 || !IS_EMPTY(b->tqbucket_freelist)); + + /* + * Get en entry from the freelist if there is one. + * Schedule task into the entry. + */ + if ((b->tqbucket_nfree != 0) && + !(b->tqbucket_flags & TQBUCKET_SUSPEND)) { + tqe = b->tqbucket_freelist.tqent_prev; + + ASSERT(tqe != &b->tqbucket_freelist); + ASSERT(tqe->tqent_thread != NULL); + + tqe->tqent_prev->tqent_next = tqe->tqent_next; + tqe->tqent_next->tqent_prev = tqe->tqent_prev; + b->tqbucket_nalloc++; + b->tqbucket_nfree--; + tqe->tqent_func = func; + tqe->tqent_arg = arg; + TQ_STAT(b, tqs_hits); + cv_signal(&tqe->tqent_cv); + DTRACE_PROBE2(taskq__d__enqueue, taskq_bucket_t *, b, + taskq_ent_t *, tqe); + } else { + tqe = NULL; + TQ_STAT(b, tqs_misses); + } + mutex_exit(&b->tqbucket_lock); + return (tqe); +} + +/* + * Dispatch a task. + * + * Assumes: func != NULL + * + * Returns: NULL if dispatch failed. + * non-NULL if task dispatched successfully. + * Actual return value is the pointer to taskq entry that was used to + * dispatch a task. This is useful for debugging. + */ +taskqid_t +taskq_dispatch(taskq_t *tq, task_func_t func, void *arg, uint_t flags) +{ + taskq_bucket_t *bucket = NULL; /* Which bucket needs extension */ + taskq_ent_t *tqe = NULL; + taskq_ent_t *tqe1; + uint_t bsize; + + ASSERT(tq != NULL); + ASSERT(func != NULL); + + if (!(tq->tq_flags & TASKQ_DYNAMIC)) { + /* + * TQ_NOQUEUE flag can't be used with non-dynamic task queues. + */ + ASSERT(!(flags & TQ_NOQUEUE)); + /* + * Enqueue the task to the underlying queue. + */ + mutex_enter(&tq->tq_lock); + + TASKQ_S_RANDOM_DISPATCH_FAILURE(tq, flags); + + if ((tqe = taskq_ent_alloc(tq, flags)) == NULL) { + mutex_exit(&tq->tq_lock); + return (0); + } + /* Make sure we start without any flags */ + tqe->tqent_un.tqent_flags = 0; + + if (flags & TQ_FRONT) { + TQ_ENQUEUE_FRONT(tq, tqe, func, arg); + } else { + TQ_ENQUEUE(tq, tqe, func, arg); + } + mutex_exit(&tq->tq_lock); + return ((taskqid_t)tqe); + } + + /* + * Dynamic taskq dispatching. + */ + ASSERT(!(flags & (TQ_NOALLOC | TQ_FRONT))); + TASKQ_D_RANDOM_DISPATCH_FAILURE(tq, flags); + + bsize = tq->tq_nbuckets; + + if (bsize == 1) { + /* + * In a single-CPU case there is only one bucket, so get + * entry directly from there. + */ + if ((tqe = taskq_bucket_dispatch(tq->tq_buckets, func, arg)) + != NULL) + return ((taskqid_t)tqe); /* Fastpath */ + bucket = tq->tq_buckets; + } else { + int loopcount; + taskq_bucket_t *b; + // uintptr_t h = ((uintptr_t)CPU + (uintptr_t)arg) >> 3; + uintptr_t h = ((uintptr_t)(CPU_SEQID<<3) + + (uintptr_t)arg) >> 3; + + h = TQ_HASH(h); + + /* + * The 'bucket' points to the original bucket that we hit. If we + * can't allocate from it, we search other buckets, but only + * extend this one. + */ + b = &tq->tq_buckets[h & (bsize - 1)]; + ASSERT(b->tqbucket_taskq == tq); /* Sanity check */ + + /* + * Do a quick check before grabbing the lock. If the bucket does + * not have free entries now, chances are very small that it + * will after we take the lock, so we just skip it. + */ + if (b->tqbucket_nfree != 0) { + if ((tqe = taskq_bucket_dispatch(b, func, arg)) != NULL) + return ((taskqid_t)tqe); /* Fastpath */ + } else { + TQ_STAT(b, tqs_misses); + } + + bucket = b; + loopcount = MIN(taskq_search_depth, bsize); + /* + * If bucket dispatch failed, search loopcount number of buckets + * before we give up and fail. + */ + do { + b = &tq->tq_buckets[++h & (bsize - 1)]; + ASSERT(b->tqbucket_taskq == tq); /* Sanity check */ + loopcount--; + + if (b->tqbucket_nfree != 0) { + tqe = taskq_bucket_dispatch(b, func, arg); + } else { + TQ_STAT(b, tqs_misses); + } + } while ((tqe == NULL) && (loopcount > 0)); + } + + /* + * At this point we either scheduled a task and (tqe != NULL) or failed + * (tqe == NULL). Try to recover from fails. + */ + + /* + * For KM_SLEEP dispatches, try to extend the bucket and retry dispatch. + */ + if ((tqe == NULL) && !(flags & TQ_NOSLEEP)) { + /* + * taskq_bucket_extend() may fail to do anything, but this is + * fine - we deal with it later. If the bucket was successfully + * extended, there is a good chance that taskq_bucket_dispatch() + * will get this new entry, unless someone is racing with us and + * stealing the new entry from under our nose. + * taskq_bucket_extend() may sleep. + */ + taskq_bucket_extend(bucket); + TQ_STAT(bucket, tqs_disptcreates); + if ((tqe = taskq_bucket_dispatch(bucket, func, arg)) != NULL) + return ((taskqid_t)tqe); + } + + ASSERT(bucket != NULL); + + /* + * Since there are not enough free entries in the bucket, add a + * taskq entry to extend it in the background using backing queue + * (unless we already have a taskq entry to perform that extension). + */ + mutex_enter(&tq->tq_lock); + if (!taskq_ent_exists(tq, taskq_bucket_extend, bucket)) { + if ((tqe1 = taskq_ent_alloc(tq, TQ_NOSLEEP)) != NULL) { + TQ_ENQUEUE_FRONT(tq, tqe1, taskq_bucket_extend, bucket); + } else { + TQ_STAT(bucket, tqs_nomem); + } + } + + /* + * Dispatch failed and we can't find an entry to schedule a task. + * Revert to the backing queue unless TQ_NOQUEUE was asked. + */ + if ((tqe == NULL) && !(flags & TQ_NOQUEUE)) { + if ((tqe = taskq_ent_alloc(tq, flags)) != NULL) { + TQ_ENQUEUE(tq, tqe, func, arg); + } else { + TQ_STAT(bucket, tqs_nomem); + } + } + mutex_exit(&tq->tq_lock); + + return ((taskqid_t)tqe); +} + +void +taskq_init_ent(taskq_ent_t *t) +{ + memset(t, 0, sizeof (*t)); +} + +void +taskq_dispatch_ent(taskq_t *tq, task_func_t func, void *arg, uint_t flags, + taskq_ent_t *tqe) +{ + ASSERT(func != NULL); + ASSERT(!(tq->tq_flags & TASKQ_DYNAMIC)); + + /* + * Mark it as a prealloc'd task. This is important + * to ensure that we don't free it later. + */ + tqe->tqent_un.tqent_flags |= TQENT_FLAG_PREALLOC; + /* + * Enqueue the task to the underlying queue. + */ + mutex_enter(&tq->tq_lock); + + if (flags & TQ_FRONT) { + TQ_ENQUEUE_FRONT(tq, tqe, func, arg); + } else { + TQ_ENQUEUE(tq, tqe, func, arg); + } + mutex_exit(&tq->tq_lock); +} + +/* + * Allow our caller to ask if there are tasks pending on the queue. + */ +int +taskq_empty_ent(taskq_ent_t *t) +{ + if (t->tqent_prev == NULL && t->tqent_next == NULL) + return (TRUE); + else + return (IS_EMPTY(*t)); +} + +/* + * Wait for all pending tasks to complete. + * Calling taskq_wait from a task will cause deadlock. + */ +void +taskq_wait(taskq_t *tq) +{ +#ifndef __APPLE__ + ASSERT(tq != curthread->t_taskq); +#endif + + if (tq == NULL) + return; + + 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); + + if (tq->tq_flags & TASKQ_DYNAMIC) { + taskq_bucket_t *b = tq->tq_buckets; + int bid = 0; + for (; (b != NULL) && (bid < tq->tq_nbuckets); b++, bid++) { + mutex_enter(&b->tqbucket_lock); + while (b->tqbucket_nalloc > 0) + cv_wait(&b->tqbucket_cv, &b->tqbucket_lock); + mutex_exit(&b->tqbucket_lock); + } + } +} + +/* + * ZOL implements taskq_wait_id() that can wait for a specific + * taskq to finish, rather than all active taskqs. Until it is + * implemented, we wait for all to complete. + */ +void +taskq_wait_id(taskq_t *tq, taskqid_t id) +{ + return (taskq_wait(tq)); +} + +void +taskq_wait_outstanding(taskq_t *tq, taskqid_t id) +{ + return (taskq_wait(tq)); +} + +/* + * Suspend execution of tasks. + * + * Tasks in the queue part will be suspended immediately upon return from this + * function. Pending tasks in the dynamic part will continue to execute, but all + * new tasks will be suspended. + */ +void +taskq_suspend(taskq_t *tq) +{ + rw_enter(&tq->tq_threadlock, RW_WRITER); + + if (tq->tq_flags & TASKQ_DYNAMIC) { + taskq_bucket_t *b = tq->tq_buckets; + int bid = 0; + for (; (b != NULL) && (bid < tq->tq_nbuckets); b++, bid++) { + mutex_enter(&b->tqbucket_lock); + b->tqbucket_flags |= TQBUCKET_SUSPEND; + mutex_exit(&b->tqbucket_lock); + } + } + /* + * Mark task queue as being suspended. Needed for taskq_suspended(). + */ + mutex_enter(&tq->tq_lock); + ASSERT(!(tq->tq_flags & TASKQ_SUSPENDED)); + tq->tq_flags |= TASKQ_SUSPENDED; + mutex_exit(&tq->tq_lock); +} + +/* + * returns: 1 if tq is suspended, 0 otherwise. + */ +int +taskq_suspended(taskq_t *tq) +{ + return ((tq->tq_flags & TASKQ_SUSPENDED) != 0); +} + +/* + * Resume taskq execution. + */ +void +taskq_resume(taskq_t *tq) +{ + ASSERT(RW_WRITE_HELD(&tq->tq_threadlock)); + + if (tq->tq_flags & TASKQ_DYNAMIC) { + taskq_bucket_t *b = tq->tq_buckets; + int bid = 0; + for (; (b != NULL) && (bid < tq->tq_nbuckets); b++, bid++) { + mutex_enter(&b->tqbucket_lock); + b->tqbucket_flags &= ~TQBUCKET_SUSPEND; + mutex_exit(&b->tqbucket_lock); + } + } + mutex_enter(&tq->tq_lock); + ASSERT(tq->tq_flags & TASKQ_SUSPENDED); + tq->tq_flags &= ~TASKQ_SUSPENDED; + mutex_exit(&tq->tq_lock); + + rw_exit(&tq->tq_threadlock); +} + +int +taskq_member(taskq_t *tq, kthread_t *thread) +{ + return (tq == (taskq_t *)tsd_get_by_thread(taskq_tsd, thread)); +} + +taskq_t * +taskq_of_curthread(void) +{ + return (tsd_get(taskq_tsd)); +} + +/* + * Creates a thread in the taskq. We only allow one outstanding create at + * a time. We drop and reacquire the tq_lock in order to avoid blocking other + * taskq activity while thread_create() or lwp_kernel_create() run. + * + * The first time we're called, we do some additional setup, and do not + * return until there are enough threads to start servicing requests. + */ +static void +taskq_thread_create(taskq_t *tq) +{ + kthread_t *t __maybe_unused; + const boolean_t first = (tq->tq_nthreads == 0); + + ASSERT(MUTEX_HELD(&tq->tq_lock)); + ASSERT(tq->tq_flags & TASKQ_CHANGING); + ASSERT(tq->tq_nthreads < tq->tq_nthreads_target); + ASSERT(!(tq->tq_flags & TASKQ_THREAD_CREATED)); + + tq->tq_flags |= TASKQ_THREAD_CREATED; + tq->tq_active++; + mutex_exit(&tq->tq_lock); + + /* + * With TASKQ_DUTY_CYCLE the new thread must have an LWP + * as explained in ../disp/sysdc.c (for the msacct data). + * Otherwise simple kthreads are preferred. + */ + if ((tq->tq_flags & TASKQ_DUTY_CYCLE) != 0) { + /* Enforced in taskq_create_common */ + printf("SPL: taskq_thread_create(TASKQ_DUTY_CYCLE) seen\n"); +#ifndef __APPLE__ + ASSERT3P(tq->tq_proc, !=, &p0); + t = lwp_kernel_create(tq->tq_proc, taskq_thread, tq, TS_RUN, + tq->tq_pri); +#else + t = thread_create_named(tq->tq_name, + NULL, 0, taskq_thread, tq, 0, tq->tq_proc, + TS_RUN, tq->tq_pri); +#endif + } else { + t = thread_create_named(tq->tq_name, + NULL, 0, taskq_thread, tq, 0, tq->tq_proc, + TS_RUN, tq->tq_pri); + } + + if (!first) { + mutex_enter(&tq->tq_lock); + return; + } + + /* + * We know the thread cannot go away, since tq cannot be + * destroyed until creation has completed. We can therefore + * safely dereference t. + */ + if (tq->tq_flags & TASKQ_THREADS_CPU_PCT) { +#ifdef __APPLE__ + mutex_enter(&tq->tq_lock); + taskq_update_nthreads(tq, max_ncpus - num_ecores); + mutex_exit(&tq->tq_lock); +#else + taskq_cpupct_install(tq, t->t_cpupart); +#endif + } + mutex_enter(&tq->tq_lock); + + /* Wait until we can service requests. */ + while (tq->tq_nthreads != tq->tq_nthreads_target && + tq->tq_nthreads < TASKQ_CREATE_ACTIVE_THREADS) { + cv_wait(&tq->tq_wait_cv, &tq->tq_lock); + } +} + +/* + * Common "sleep taskq thread" function, which handles CPR stuff, as well + * as giving a nice common point for debuggers to find inactive threads. + */ +static clock_t +taskq_thread_wait(taskq_t *tq, kmutex_t *mx, kcondvar_t *cv, + callb_cpr_t *cprinfo, clock_t timeout) +{ + clock_t ret = 0; + + if (!(tq->tq_flags & TASKQ_CPR_SAFE)) { + CALLB_CPR_SAFE_BEGIN(cprinfo); + } + if ((signed long)timeout < 0) + cv_wait(cv, mx); + else + ret = cv_reltimedwait(cv, mx, timeout, TR_CLOCK_TICK); + + if (!(tq->tq_flags & TASKQ_CPR_SAFE)) { + CALLB_CPR_SAFE_END(cprinfo, mx); + } + + return (ret); +} + +#ifdef __APPLE__ + +/* + * Create a thread with appropriate importance and QOS + */ + +static kthread_t * +spl_taskq_thread_create_named(const char *name, + taskq_t *tq, + caddr_t stk, + size_t stksize, + thread_func_t proc, + void *arg, + size_t len, + int state, + pri_t pri) +{ + + kthread_t *new_thread = NULL; + /* + * We pass pri along, but use it to determine + * what QOS to pass along as well + */ + + if (pri < minclsyspri) { + /* + * these are dsl_scan asynchronous reads, + * and need neither QOS nor timesharing + */ + thread_extended_policy_data_t timeshare = { + .timeshare = 0, + }; + new_thread = + spl_thread_create_named_with_extpol_and_qos( + ×hare, NULL, NULL, + name, stk, stksize, proc, arg, len, state, pri); + } else if (tq->tq_maxsize == 1 && + (tq->tq_flags & (TASKQ_DYNAMIC + | TASKQ_THREADS_CPU_PCT + | TASKQ_DUTY_CYCLE + | TASKQ_DC_BATCH)) == 0) { + /* + * This is a strict-FIFO taskq, which should be held at a + * stable priority so that the immediately previous job on + * this sole worker thread has not left the scheduler with the + * wrong opinion of the job that is to come. TIMESHARE could + * let a well-behaved previous job might lead to an + * over-prioritization of the subsequent job. + */ + thread_extended_policy_data_t timeshare = { + .timeshare = 0, + }; + new_thread = + spl_thread_create_named_with_extpol_and_qos( + ×hare, NULL, NULL, + name, stk, stksize, proc, arg, len, state, pri); + } else if (tq->tq_flags & TASKQ_DC_BATCH) { + /* + * Batch SDC scheduling class, + * CPU intensive but not latency sensitive + */ + ASSERT3U(tq->tq_DC, <=, 100); + ASSERT3U(tq->tq_DC, >, 0); + + int pri_pct = (maxclsyspri * tq->tq_DC) / 100 - 1; + if (pri_pct < minclsyspri) + pri_pct = minclsyspri; + if (pri_pct >= maxclsyspri) + pri_pct = maxclsyspri - 1; + + thread_throughput_qos_policy_data_t throughpol = { + .thread_throughput_qos_tier = + THROUGHPUT_QOS_TIER_2, + }; + + thread_latency_qos_policy_data_t latpol = { + .thread_latency_qos_tier = + LATENCY_QOS_TIER_3, + }; + + thread_extended_policy_data_t timeshare = { + .timeshare = 0, + }; + + new_thread = + spl_thread_create_named_with_extpol_and_qos( + ×hare, &throughpol, &latpol, + name, stk, stksize, proc, arg, len, state, pri_pct); + } else if (tq->tq_flags & TASKQ_DUTY_CYCLE) { + /* + * SDC scheduling class, the sysdc threads. + * This is cpu-intensive workload. + * We need the "duty cycle" (DC) + * from tq->tq_DC, which is a percentage + * of CPU + */ + ASSERT3U(tq->tq_DC, <=, 100); + ASSERT3U(tq->tq_DC, >, 0); + + thread_throughput_qos_t tqos; + + int pri_pct = (maxclsyspri * tq->tq_DC) / 100 - 1; + if (pri_pct < minclsyspri) + pri_pct = minclsyspri; + if (pri_pct >= maxclsyspri) + pri_pct = maxclsyspri - 1; + + if (pri < defclsyspri) { + tqos = THROUGHPUT_QOS_TIER_4; + } else if (pri < maxclsyspri) { + tqos = THROUGHPUT_QOS_TIER_3; + } else { + tqos = THROUGHPUT_QOS_TIER_2; + } + + thread_throughput_qos_policy_data_t throughpol = { + .thread_throughput_qos_tier = tqos, + }; + + /* no latency, default timeshare */ + new_thread = + spl_thread_create_named_with_extpol_and_qos( + NULL, &throughpol, NULL, + name, stk, stksize, proc, arg, len, state, pri_pct); + } else if (pri < maxclsyspri) { + /* just a default thread */ + new_thread = + spl_thread_create_named_with_extpol_and_qos( + NULL, NULL, NULL, + name, stk, stksize, proc, arg, len, state, pri); + } else { + /* + * maxclsyspri, low latency ("LEGACY" aka "USER_INITIATED") + */ + + thread_latency_qos_policy_data_t latpol = { + .thread_latency_qos_tier = + LATENCY_QOS_TIER_1, + }; + + new_thread = + spl_thread_create_named_with_extpol_and_qos( + NULL, NULL, &latpol, + name, stk, stksize, proc, arg, len, state, pri); + } + if (!new_thread) { + printf("SPL: %s:%s:%d: unable to create thread" + " '%s', pri %d, DC %d, flags %x\n", + __FILE__, __func__, __LINE__, + (name == NULL) ? "unammed tq thread" : name, + pri, tq->tq_DC, tq->tq_flags); + } + + return (new_thread); +} + +#endif // __APPLE__ + +/* + * Worker thread for processing task queue. + */ +static void +taskq_thread(void *arg) +{ + int thread_id; + + taskq_t *tq = arg; + taskq_ent_t *tqe; + callb_cpr_t cprinfo; + hrtime_t start, end; + boolean_t freeit; + + CALLB_CPR_INIT(&cprinfo, &tq->tq_lock, callb_generic_cpr, + tq->tq_name); + + tsd_set(taskq_tsd, tq); + mutex_enter(&tq->tq_lock); + thread_id = ++tq->tq_nthreads; + ASSERT(tq->tq_flags & TASKQ_THREAD_CREATED); + ASSERT(tq->tq_flags & TASKQ_CHANGING); + tq->tq_flags &= ~TASKQ_THREAD_CREATED; + + VERIFY3S(thread_id, <=, tq->tq_nthreads_max); + + if (tq->tq_nthreads_max == 1) + tq->tq_thread = (kthread_t *)curthread; + else + tq->tq_threadlist[thread_id - 1] = (kthread_t *)curthread; + + /* Allow taskq_create_common()'s taskq_thread_create() to return. */ + if (tq->tq_nthreads == TASKQ_CREATE_ACTIVE_THREADS) + cv_broadcast(&tq->tq_wait_cv); + + for (;;) { + if (tq->tq_flags & TASKQ_CHANGING) { + /* See if we're no longer needed */ + if (thread_id > tq->tq_nthreads_target) { + /* + * To preserve the one-to-one mapping between + * thread_id and thread, we must exit from + * highest thread ID to least. + * + * However, if everyone is exiting, the order + * doesn't matter, so just exit immediately. + * (this is safe, since you must wait for + * nthreads to reach 0 after setting + * tq_nthreads_target to 0) + */ + if (thread_id == tq->tq_nthreads || + tq->tq_nthreads_target == 0) { + break; + } + + /* Wait for higher thread_ids to exit */ + (void) taskq_thread_wait(tq, &tq->tq_lock, + &tq->tq_exit_cv, &cprinfo, -1); + continue; + } + + /* + * If no thread is starting taskq_thread(), we can + * do some bookkeeping. + */ + if (!(tq->tq_flags & TASKQ_THREAD_CREATED)) { + /* Check if we've reached our target */ + if (tq->tq_nthreads == tq->tq_nthreads_target) { + tq->tq_flags &= ~TASKQ_CHANGING; + cv_broadcast(&tq->tq_wait_cv); + } + /* Check if we need to create a thread */ + if (tq->tq_nthreads < tq->tq_nthreads_target) { + taskq_thread_create(tq); + continue; /* tq_lock was dropped */ + } + } + } + if ((tqe = tq->tq_task.tqent_next) == &tq->tq_task) { + if (--tq->tq_active == 0) + cv_broadcast(&tq->tq_wait_cv); + (void) taskq_thread_wait(tq, &tq->tq_lock, + &tq->tq_dispatch_cv, &cprinfo, -1); + tq->tq_active++; + continue; + } + + tqe->tqent_prev->tqent_next = tqe->tqent_next; + tqe->tqent_next->tqent_prev = tqe->tqent_prev; + mutex_exit(&tq->tq_lock); + + /* + * For prealloc'd tasks, we don't free anything. We + * have to check this now, because once we call the + * function for a prealloc'd taskq, we can't touch the + * tqent any longer (calling the function returns the + * ownershp of the tqent back to caller of + * taskq_dispatch.) + */ + if ((!(tq->tq_flags & TASKQ_DYNAMIC)) && + (tqe->tqent_un.tqent_flags & TQENT_FLAG_PREALLOC)) { + /* clear pointers to assist assertion checks */ + tqe->tqent_next = tqe->tqent_prev = NULL; + freeit = B_FALSE; + } else { + freeit = B_TRUE; + } + + rw_enter(&tq->tq_threadlock, RW_READER); + start = gethrtime(); + DTRACE_PROBE2(taskq__exec__start, taskq_t *, tq, + taskq_ent_t *, tqe); + tqe->tqent_func(tqe->tqent_arg); + DTRACE_PROBE2(taskq__exec__end, taskq_t *, tq, + taskq_ent_t *, tqe); + end = gethrtime(); + rw_exit(&tq->tq_threadlock); + + mutex_enter(&tq->tq_lock); + tq->tq_totaltime += end - start; + tq->tq_executed++; + + if (freeit) + taskq_ent_free(tq, tqe); + } + + if (tq->tq_nthreads_max == 1) + tq->tq_thread = NULL; + else + tq->tq_threadlist[thread_id - 1] = NULL; + + /* We're exiting, and therefore no longer active */ + ASSERT(tq->tq_active > 0); + tq->tq_active--; + + ASSERT(tq->tq_nthreads > 0); + tq->tq_nthreads--; + + /* Wake up anyone waiting for us to exit */ + cv_broadcast(&tq->tq_exit_cv); + if (tq->tq_nthreads == tq->tq_nthreads_target) { + if (!(tq->tq_flags & TASKQ_THREAD_CREATED)) + tq->tq_flags &= ~TASKQ_CHANGING; + + cv_broadcast(&tq->tq_wait_cv); + } + + tsd_set(taskq_tsd, NULL); + + CALLB_CPR_EXIT(&cprinfo); + thread_exit(); + +} + +/* + * Worker per-entry thread for dynamic dispatches. + */ +static void +taskq_d_thread(taskq_ent_t *tqe) +{ + taskq_bucket_t *bucket = tqe->tqent_un.tqent_bucket; + taskq_t *tq = bucket->tqbucket_taskq; + kmutex_t *lock = &bucket->tqbucket_lock; + kcondvar_t *cv = &tqe->tqent_cv; + callb_cpr_t cprinfo; + clock_t w = 0; + + CALLB_CPR_INIT(&cprinfo, lock, callb_generic_cpr, tq->tq_name); + +#ifdef __APPLE__ + /* + * There's no way in Mac OS X KPI to create a thread + * in a suspended state (TS_STOPPED). So instead we + * use tqent_thread as a flag and wait for it to get + * initialized. + */ + mutex_enter(&tqe->tqent_thread_lock); + while (tqe->tqent_thread == (kthread_t *)0xCEDEC0DE) + cv_wait(&tqe->tqent_thread_cv, &tqe->tqent_thread_lock); + mutex_exit(&tqe->tqent_thread_lock); +#endif + + mutex_enter(lock); + + for (;;) { + /* + * If a task is scheduled (func != NULL), execute it, otherwise + * sleep, waiting for a job. + */ + if (tqe->tqent_func != NULL) { + hrtime_t start; + hrtime_t end; + + ASSERT(bucket->tqbucket_nalloc > 0); + + /* + * It is possible to free the entry right away before + * actually executing the task so that subsequent + * dispatches may immediately reuse it. But this, + * effectively, creates a two-length queue in the entry + * and may lead to a deadlock if the execution of the + * current task depends on the execution of the next + * scheduled task. So, we keep the entry busy until the + * task is processed. + */ + + mutex_exit(lock); + start = gethrtime(); + DTRACE_PROBE3(taskq__d__exec__start, taskq_t *, tq, + taskq_bucket_t *, bucket, taskq_ent_t *, tqe); + tqe->tqent_func(tqe->tqent_arg); + DTRACE_PROBE3(taskq__d__exec__end, taskq_t *, tq, + taskq_bucket_t *, bucket, taskq_ent_t *, tqe); + end = gethrtime(); + mutex_enter(lock); + bucket->tqbucket_totaltime += end - start; + + /* + * Return the entry to the bucket free list. + */ + tqe->tqent_func = NULL; + TQ_APPEND(bucket->tqbucket_freelist, tqe); + bucket->tqbucket_nalloc--; + bucket->tqbucket_nfree++; + ASSERT(!IS_EMPTY(bucket->tqbucket_freelist)); + /* + * taskq_wait() waits for nalloc to drop to zero on + * tqbucket_cv. + */ + cv_signal(&bucket->tqbucket_cv); + } + + /* + * At this point the entry must be in the bucket free list - + * either because it was there initially or because it just + * finished executing a task and put itself on the free list. + */ + ASSERT(bucket->tqbucket_nfree > 0); + /* + * Go to sleep unless we are closing. + * If a thread is sleeping too long, it dies. + */ + if (! (bucket->tqbucket_flags & TQBUCKET_CLOSE)) { + w = taskq_thread_wait(tq, lock, cv, + &cprinfo, taskq_thread_timeout * hz); + } + + /* + * At this point we may be in two different states: + * + * (1) tqent_func is set which means that a new task is + * dispatched and we need to execute it. + * + * (2) Thread is sleeping for too long or we are closing. In + * both cases destroy the thread and the entry. + */ + + /* If func is NULL we should be on the freelist. */ + ASSERT((tqe->tqent_func != NULL) || + (bucket->tqbucket_nfree > 0)); + /* If func is non-NULL we should be allocated */ + ASSERT((tqe->tqent_func == NULL) || + (bucket->tqbucket_nalloc > 0)); + + /* Check freelist consistency */ + ASSERT((bucket->tqbucket_nfree > 0) || + IS_EMPTY(bucket->tqbucket_freelist)); + ASSERT((bucket->tqbucket_nfree == 0) || + !IS_EMPTY(bucket->tqbucket_freelist)); + + if ((tqe->tqent_func == NULL) && + ((w == -1) || (bucket->tqbucket_flags & TQBUCKET_CLOSE))) { + /* + * This thread is sleeping for too long or we are + * closing - time to die. + * Thread creation/destruction happens rarely, + * so grabbing the lock is not a big performance issue. + * The bucket lock is dropped by CALLB_CPR_EXIT(). + */ + + /* Remove the entry from the free list. */ + tqe->tqent_prev->tqent_next = tqe->tqent_next; + tqe->tqent_next->tqent_prev = tqe->tqent_prev; + ASSERT(bucket->tqbucket_nfree > 0); + bucket->tqbucket_nfree--; + + TQ_STAT(bucket, tqs_tdeaths); + cv_signal(&bucket->tqbucket_cv); + tqe->tqent_thread = NULL; + mutex_enter(&tq->tq_lock); + tq->tq_tdeaths++; + mutex_exit(&tq->tq_lock); + CALLB_CPR_EXIT(&cprinfo); + kmem_cache_free(taskq_ent_cache, tqe); + thread_exit(); + } + } +} + + +/* + * Taskq creation. May sleep for memory. + * Always use automatically generated instances to avoid kstat name space + * collisions. + */ + +taskq_t * +taskq_create(const char *name, int nthreads, pri_t pri, int minalloc, + int maxalloc, uint_t flags) +{ + ASSERT((flags & ~TASKQ_INTERFACE_FLAGS) == 0); + + return (taskq_create_common(name, 0, nthreads, pri, minalloc, + maxalloc, &p0, 0, flags | TASKQ_NOINSTANCE)); +} + +/* + * Create an instance of task queue. It is legal to create task queues with the + * same name and different instances. + * + * taskq_create_instance is used by ddi_taskq_create() where it gets the + * instance from ddi_get_instance(). In some cases the instance is not + * initialized and is set to -1. This case is handled as if no instance was + * passed at all. + */ +taskq_t * +taskq_create_instance(const char *name, int instance, int nthreads, pri_t pri, + int minalloc, int maxalloc, uint_t flags) +{ + ASSERT((flags & ~TASKQ_INTERFACE_FLAGS) == 0); + ASSERT((instance >= 0) || (instance == -1)); + + if (instance < 0) { + flags |= TASKQ_NOINSTANCE; + } + + return (taskq_create_common(name, instance, nthreads, + pri, minalloc, maxalloc, &p0, 0, flags)); +} + +taskq_t * +taskq_create_proc(const char *name, int nthreads, pri_t pri, int minalloc, + int maxalloc, proc_t *proc, uint_t flags) +{ + ASSERT((flags & ~TASKQ_INTERFACE_FLAGS) == 0); +#ifndef __APPLE__ + ASSERT(proc->p_flag & SSYS); +#endif + return (taskq_create_common(name, 0, nthreads, pri, minalloc, + maxalloc, proc, 0, flags | TASKQ_NOINSTANCE)); +} + +taskq_t * +taskq_create_sysdc(const char *name, int nthreads, int minalloc, + int maxalloc, proc_t *proc, uint_t dc, uint_t flags) +{ + ASSERT((flags & ~TASKQ_INTERFACE_FLAGS) == 0); +#ifndef __APPLE__ + ASSERT(proc->p_flag & SSYS); +#else + dprintf("SPL: %s:%d: taskq_create_sysdc(%s, nthreads: %d," + " minalloc: %d, maxalloc: %d, proc, dc: %u, flags: %x)\n", + __func__, __LINE__, name, nthreads, + minalloc, maxalloc, dc, flags); +#endif + return (taskq_create_common(name, 0, nthreads, minclsyspri, minalloc, + maxalloc, proc, dc, flags | TASKQ_NOINSTANCE | TASKQ_DUTY_CYCLE)); +} + +static taskq_t * +taskq_create_common(const char *name, int instance, int nthreads, pri_t pri, + int minalloc, int maxalloc, proc_t *proc, uint_t dc, uint_t flags) +{ + taskq_t *tq = kmem_cache_alloc(taskq_cache, KM_SLEEP); +#ifdef __APPLE__ + uint_t ncpus = max_ncpus - num_ecores; +#else + uint_t ncpus = ((boot_max_ncpus == -1) ? max_ncpus : boot_max_ncpus); +#endif + uint_t bsize; /* # of buckets - always power of 2 */ + int max_nthreads; + + /* + * We are not allowed to use TASKQ_DYNAMIC with taskq_dispatch_ent() + * but that is done by spa.c - so we will simply mask DYNAMIC out. + */ + if (!(flags & TASKQ_REALLY_DYNAMIC)) + flags &= ~TASKQ_DYNAMIC; + + /* + * TASKQ_DYNAMIC, TASKQ_CPR_SAFE and TASKQ_THREADS_CPU_PCT are all + * mutually incompatible. + */ + IMPLY((flags & TASKQ_DYNAMIC), !(flags & TASKQ_CPR_SAFE)); + IMPLY((flags & TASKQ_DYNAMIC), !(flags & TASKQ_THREADS_CPU_PCT)); + IMPLY((flags & TASKQ_CPR_SAFE), !(flags & TASKQ_THREADS_CPU_PCT)); + + /* Cannot have DYNAMIC with DUTY_CYCLE */ + IMPLY((flags & TASKQ_DYNAMIC), !(flags & TASKQ_DUTY_CYCLE)); + + /* Cannot have DUTY_CYCLE with a p0 kernel process */ + IMPLY((flags & TASKQ_DUTY_CYCLE), proc != &p0); + + /* Cannot have DC_BATCH without DUTY_CYCLE */ + ASSERT((flags & (TASKQ_DUTY_CYCLE|TASKQ_DC_BATCH)) + != TASKQ_DC_BATCH); + +#ifdef __APPLE__ + /* Cannot have DC_BATCH or DUTY_CYCLE with TIMESHARE */ + IMPLY((flags & (TASKQ_DUTY_CYCLE|TASKQ_DC_BATCH)), + !(flags & TASKQ_TIMESHARE)); +#endif + + ASSERT(proc != NULL); + + bsize = 1 << (highbit(ncpus) - 1); + ASSERT(bsize >= 1); + bsize = MIN(bsize, taskq_maxbuckets); + + if (flags & TASKQ_DYNAMIC) { + ASSERT3S(nthreads, >=, 1); + tq->tq_maxsize = nthreads; + + /* For dynamic task queues use just one backup thread */ + nthreads = max_nthreads = 1; + + } else if (flags & TASKQ_THREADS_CPU_PCT) { + uint_t pct; + ASSERT3S(nthreads, >=, 0); + pct = nthreads; + + if (pct > taskq_cpupct_max_percent) + pct = taskq_cpupct_max_percent; + + /* + * If you're using THREADS_CPU_PCT, the process for the + * taskq threads must be curproc. This allows any pset + * binding to be inherited correctly. If proc is &p0, + * we won't be creating LWPs, so new threads will be assigned + * to the default processor set. + */ + /* ASSERT(curproc == proc || proc == &p0); */ + tq->tq_threads_ncpus_pct = pct; + nthreads = 1; /* corrected in taskq_thread_create() */ + max_nthreads = TASKQ_THREADS_PCT((max_ncpus - num_ecores), pct); + } else { + ASSERT3S(nthreads, >=, 1); + max_nthreads = nthreads; + } + + if (max_nthreads < taskq_minimum_nthreads_max) + max_nthreads = taskq_minimum_nthreads_max; + + /* + * Make sure the name is 0-terminated, and conforms to the rules for + * C indentifiers + */ + (void) strncpy(tq->tq_name, name, TASKQ_NAMELEN + 1); + strident_canon(tq->tq_name, TASKQ_NAMELEN + 1); + + tq->tq_flags = flags | TASKQ_CHANGING; + tq->tq_active = 0; + tq->tq_instance = instance; + tq->tq_nthreads_target = nthreads; + tq->tq_nthreads_max = max_nthreads; + tq->tq_minalloc = minalloc; + tq->tq_maxalloc = maxalloc; + tq->tq_nbuckets = bsize; + tq->tq_proc = proc; + tq->tq_pri = pri; + tq->tq_DC = dc; + list_link_init(&tq->tq_cpupct_link); + + if (max_nthreads > 1) + tq->tq_threadlist = kmem_alloc( + sizeof (kthread_t *) * max_nthreads, KM_SLEEP); + + mutex_enter(&tq->tq_lock); + if (flags & TASKQ_PREPOPULATE) { + while (minalloc-- > 0) + taskq_ent_free(tq, taskq_ent_alloc(tq, TQ_SLEEP)); + } + + /* + * Before we start creating threads for this taskq, take a + * zone hold so the zone can't go away before taskq_destroy + * makes sure all the taskq threads are gone. This hold is + * similar in purpose to those taken by zthread_create(). + */ +#ifndef __APPLE__ + zone_hold(tq->tq_proc->p_zone); +#endif + /* + * Create the first thread, which will create any other threads + * necessary. taskq_thread_create will not return until we have + * enough threads to be able to process requests. + */ + taskq_thread_create(tq); + mutex_exit(&tq->tq_lock); + + if (flags & TASKQ_DYNAMIC) { + taskq_bucket_t *bucket = kmem_zalloc(sizeof (taskq_bucket_t) * + bsize, KM_SLEEP); + int b_id; + + tq->tq_buckets = bucket; + + /* Initialize each bucket */ + for (b_id = 0; b_id < bsize; b_id++, bucket++) { + mutex_init(&bucket->tqbucket_lock, NULL, MUTEX_DEFAULT, + NULL); + cv_init(&bucket->tqbucket_cv, NULL, CV_DEFAULT, NULL); + bucket->tqbucket_taskq = tq; + bucket->tqbucket_freelist.tqent_next = + bucket->tqbucket_freelist.tqent_prev = + &bucket->tqbucket_freelist; + if (flags & TASKQ_PREPOPULATE) + taskq_bucket_extend(bucket); + } + } + + /* + * Install kstats. + * We have two cases: + * 1) Instance is provided to taskq_create_instance(). In this case it + * should be >= 0 and we use it. + * + * 2) Instance is not provided and is automatically generated + */ + if (flags & TASKQ_NOINSTANCE) { + instance = tq->tq_instance = + (int)(uintptr_t)vmem_alloc_impl(taskq_id_arena, 1, + VM_SLEEP); + } + + if (flags & TASKQ_DYNAMIC) { + if ((tq->tq_kstat = kstat_create("unix", instance, + tq->tq_name, "taskq_d", KSTAT_TYPE_NAMED, + sizeof (taskq_d_kstat) / sizeof (kstat_named_t), + KSTAT_FLAG_VIRTUAL)) != NULL) { + tq->tq_kstat->ks_lock = &taskq_d_kstat_lock; + tq->tq_kstat->ks_data = &taskq_d_kstat; + tq->tq_kstat->ks_update = taskq_d_kstat_update; + tq->tq_kstat->ks_private = tq; + kstat_install(tq->tq_kstat); + } + } else { + if ((tq->tq_kstat = kstat_create("unix", instance, tq->tq_name, + "taskq", KSTAT_TYPE_NAMED, + sizeof (taskq_kstat) / sizeof (kstat_named_t), + KSTAT_FLAG_VIRTUAL)) != NULL) { + tq->tq_kstat->ks_lock = &taskq_kstat_lock; + tq->tq_kstat->ks_data = &taskq_kstat; + tq->tq_kstat->ks_update = taskq_kstat_update; + tq->tq_kstat->ks_private = tq; + kstat_install(tq->tq_kstat); + } + } + + return (tq); +} + +/* + * taskq_destroy(). + * + * Assumes: by the time taskq_destroy is called no one will use this task queue + * in any way and no one will try to dispatch entries in it. + */ +void +taskq_destroy(taskq_t *tq) +{ + taskq_bucket_t *b = tq->tq_buckets; + int bid = 0; + + ASSERT(! (tq->tq_flags & TASKQ_CPR_SAFE)); + + /* + * Destroy kstats. + */ + if (tq->tq_kstat != NULL) { + kstat_delete(tq->tq_kstat); + tq->tq_kstat = NULL; + } + + /* + * Destroy instance if needed. + */ + if (tq->tq_flags & TASKQ_NOINSTANCE) { + vmem_free_impl(taskq_id_arena, + (void *)(uintptr_t)(tq->tq_instance), 1); + tq->tq_instance = 0; + } + + /* + * Unregister from the cpupct list. + */ +#ifndef __APPLE__ + if (tq->tq_flags & TASKQ_THREADS_CPU_PCT) { + taskq_cpupct_remove(tq); + } +#endif + + /* + * Wait for any pending entries to complete. + */ + taskq_wait(tq); + + mutex_enter(&tq->tq_lock); + ASSERT((tq->tq_task.tqent_next == &tq->tq_task) && + (tq->tq_active == 0)); + + /* notify all the threads that they need to exit */ + tq->tq_nthreads_target = 0; + + tq->tq_flags |= TASKQ_CHANGING; + cv_broadcast(&tq->tq_dispatch_cv); + cv_broadcast(&tq->tq_exit_cv); + + while (tq->tq_nthreads != 0) + cv_wait(&tq->tq_wait_cv, &tq->tq_lock); + + if (tq->tq_nthreads_max != 1) + kmem_free(tq->tq_threadlist, sizeof (kthread_t *) * + tq->tq_nthreads_max); + + tq->tq_minalloc = 0; + while (tq->tq_nalloc != 0) + taskq_ent_free(tq, taskq_ent_alloc(tq, TQ_SLEEP)); + + mutex_exit(&tq->tq_lock); + + /* + * Mark each bucket as closing and wakeup all sleeping threads. + */ + for (; (b != NULL) && (bid < tq->tq_nbuckets); b++, bid++) { + taskq_ent_t *tqe; + + mutex_enter(&b->tqbucket_lock); + + b->tqbucket_flags |= TQBUCKET_CLOSE; + /* Wakeup all sleeping threads */ + + for (tqe = b->tqbucket_freelist.tqent_next; + tqe != &b->tqbucket_freelist; tqe = tqe->tqent_next) + cv_signal(&tqe->tqent_cv); + + ASSERT(b->tqbucket_nalloc == 0); + + /* + * At this point we waited for all pending jobs to complete (in + * both the task queue and the bucket and no new jobs should + * arrive. Wait for all threads to die. + */ + while (b->tqbucket_nfree > 0) + cv_wait(&b->tqbucket_cv, &b->tqbucket_lock); + mutex_exit(&b->tqbucket_lock); + mutex_destroy(&b->tqbucket_lock); + cv_destroy(&b->tqbucket_cv); + } + + if (tq->tq_buckets != NULL) { + ASSERT(tq->tq_flags & TASKQ_DYNAMIC); + kmem_free(tq->tq_buckets, + sizeof (taskq_bucket_t) * tq->tq_nbuckets); + + /* Cleanup fields before returning tq to the cache */ + tq->tq_buckets = NULL; + tq->tq_tcreates = 0; + tq->tq_tdeaths = 0; + } else { + ASSERT(!(tq->tq_flags & TASKQ_DYNAMIC)); + } + + /* + * Now that all the taskq threads are gone, we can + * drop the zone hold taken in taskq_create_common + */ +#ifndef __APPLE__ + zone_rele(tq->tq_proc->p_zone); +#endif + + tq->tq_threads_ncpus_pct = 0; + tq->tq_totaltime = 0; + tq->tq_tasks = 0; + tq->tq_maxtasks = 0; + tq->tq_executed = 0; + kmem_cache_free(taskq_cache, tq); +} + +/* + * Extend a bucket with a new entry on the free list and attach a worker thread + * to it. + * + * Argument: pointer to the bucket. + * + * This function may quietly fail. It is only used by taskq_dispatch() which + * handles such failures properly. + */ +static void +taskq_bucket_extend(void *arg) +{ + taskq_ent_t *tqe; + taskq_bucket_t *b = (taskq_bucket_t *)arg; + taskq_t *tq = b->tqbucket_taskq; + int nthreads; +#ifdef __APPLE__ + kthread_t *thread; +#endif + + if (! ENOUGH_MEMORY()) { + TQ_STAT(b, tqs_nomem); + return; + } + + mutex_enter(&tq->tq_lock); + + /* + * Observe global taskq limits on the number of threads. + */ + if (tq->tq_tcreates++ - tq->tq_tdeaths > tq->tq_maxsize) { + tq->tq_tcreates--; + mutex_exit(&tq->tq_lock); + return; + } + mutex_exit(&tq->tq_lock); + + tqe = kmem_cache_alloc(taskq_ent_cache, KM_NOSLEEP); + + if (tqe == NULL) { + mutex_enter(&tq->tq_lock); + TQ_STAT(b, tqs_nomem); + tq->tq_tcreates--; + mutex_exit(&tq->tq_lock); + return; + } + + ASSERT(tqe->tqent_thread == NULL); + + tqe->tqent_un.tqent_bucket = b; + +#ifdef __APPLE__ + /* + * There's no way in Mac OS X KPI to create a thread + * in a suspended state (TS_STOPPED). So instead we + * use tqent_thread as a flag and the thread must wait + * for it to be initialized (below). + */ + tqe->tqent_thread = (kthread_t *)0xCEDEC0DE; + + thread = spl_taskq_thread_create_named(tq->tq_name, + tq, NULL, 0, (thread_func_t)taskq_d_thread, tqe, + 0, TS_RUN, tq->tq_pri); +#else + + /* + * Create a thread in a TS_STOPPED state first. If it is successfully + * created, place the entry on the free list and start the thread. + */ + tqe->tqent_thread = thread_create(NULL, 0, taskq_d_thread, tqe, + 0, tq->tq_proc, TS_STOPPED, tq->tq_pri); +#endif /* __APPLE__ */ + + /* + * Once the entry is ready, link it to the the bucket free list. + */ + mutex_enter(&b->tqbucket_lock); + tqe->tqent_func = NULL; + TQ_APPEND(b->tqbucket_freelist, tqe); + b->tqbucket_nfree++; + TQ_STAT(b, tqs_tcreates); + +#if TASKQ_STATISTIC + nthreads = b->tqbucket_stat.tqs_tcreates - + b->tqbucket_stat.tqs_tdeaths; + b->tqbucket_stat.tqs_maxthreads = MAX(nthreads, + b->tqbucket_stat.tqs_maxthreads); +#endif + + mutex_exit(&b->tqbucket_lock); + /* + * Start the stopped thread. + */ +#ifdef __APPLE__ + mutex_enter(&tqe->tqent_thread_lock); + tqe->tqent_thread = thread; + cv_signal(&tqe->tqent_thread_cv); + mutex_exit(&tqe->tqent_thread_lock); +#else + thread_lock(tqe->tqent_thread); + tqe->tqent_thread->t_taskq = tq; + tqe->tqent_thread->t_schedflag |= TS_ALLSTART; + setrun_locked(tqe->tqent_thread); + thread_unlock(tqe->tqent_thread); +#endif /* __APPLE__ */ +} + +static int +taskq_kstat_update(kstat_t *ksp, int rw) +{ + struct taskq_kstat *tqsp = &taskq_kstat; + taskq_t *tq = ksp->ks_private; + + if (rw == KSTAT_WRITE) + return (EACCES); + +#ifdef __APPLE__ + tqsp->tq_pid.value.ui64 = 0; /* kernel_task'd pid is 0 */ +#else + tqsp->tq_pid.value.ui64 = proc_pid(tq->tq_proc->p_pid); +#endif + tqsp->tq_tasks.value.ui64 = tq->tq_tasks; + tqsp->tq_executed.value.ui64 = tq->tq_executed; + tqsp->tq_maxtasks.value.ui64 = tq->tq_maxtasks; + tqsp->tq_totaltime.value.ui64 = tq->tq_totaltime; + tqsp->tq_nactive.value.ui64 = tq->tq_active; + tqsp->tq_nalloc.value.ui64 = tq->tq_nalloc; + tqsp->tq_pri.value.ui64 = tq->tq_pri; + tqsp->tq_nthreads.value.ui64 = tq->tq_nthreads; + return (0); +} + +static int +taskq_d_kstat_update(kstat_t *ksp, int rw) +{ + struct taskq_d_kstat *tqsp = &taskq_d_kstat; + taskq_t *tq = ksp->ks_private; + taskq_bucket_t *b = tq->tq_buckets; + int bid = 0; + + if (rw == KSTAT_WRITE) + return (EACCES); + + ASSERT(tq->tq_flags & TASKQ_DYNAMIC); + + tqsp->tqd_btasks.value.ui64 = tq->tq_tasks; + tqsp->tqd_bexecuted.value.ui64 = tq->tq_executed; + tqsp->tqd_bmaxtasks.value.ui64 = tq->tq_maxtasks; + tqsp->tqd_bnalloc.value.ui64 = tq->tq_nalloc; + tqsp->tqd_bnactive.value.ui64 = tq->tq_active; + tqsp->tqd_btotaltime.value.ui64 = tq->tq_totaltime; + tqsp->tqd_pri.value.ui64 = tq->tq_pri; + + tqsp->tqd_hits.value.ui64 = 0; + tqsp->tqd_misses.value.ui64 = 0; + tqsp->tqd_overflows.value.ui64 = 0; + tqsp->tqd_tcreates.value.ui64 = 0; + tqsp->tqd_tdeaths.value.ui64 = 0; + tqsp->tqd_maxthreads.value.ui64 = 0; + tqsp->tqd_nomem.value.ui64 = 0; + tqsp->tqd_disptcreates.value.ui64 = 0; + tqsp->tqd_totaltime.value.ui64 = 0; + tqsp->tqd_nalloc.value.ui64 = 0; + tqsp->tqd_nfree.value.ui64 = 0; + + for (; (b != NULL) && (bid < tq->tq_nbuckets); b++, bid++) { + tqsp->tqd_hits.value.ui64 += b->tqbucket_stat.tqs_hits; + tqsp->tqd_misses.value.ui64 += b->tqbucket_stat.tqs_misses; + tqsp->tqd_overflows.value.ui64 += b->tqbucket_stat.tqs_overflow; + tqsp->tqd_tcreates.value.ui64 += b->tqbucket_stat.tqs_tcreates; + tqsp->tqd_tdeaths.value.ui64 += b->tqbucket_stat.tqs_tdeaths; + tqsp->tqd_maxthreads.value.ui64 += + b->tqbucket_stat.tqs_maxthreads; + tqsp->tqd_nomem.value.ui64 += b->tqbucket_stat.tqs_nomem; + tqsp->tqd_disptcreates.value.ui64 += + b->tqbucket_stat.tqs_disptcreates; + tqsp->tqd_totaltime.value.ui64 += b->tqbucket_totaltime; + tqsp->tqd_nalloc.value.ui64 += b->tqbucket_nalloc; + tqsp->tqd_nfree.value.ui64 += b->tqbucket_nfree; + } + return (0); +} diff --git a/module/os/macos/spl/spl-thread.c b/module/os/macos/spl/spl-thread.c new file mode 100644 index 000000000000..ca3f425acd00 --- /dev/null +++ b/module/os/macos/spl/spl-thread.c @@ -0,0 +1,468 @@ +/* + * 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) 2008 MacZFS + * Copyright (C) 2013, 2020 Jorgen Lundman + * Copyright (C) 2023 Sean Doran + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +uint64_t zfs_threads = 0; + +typedef struct initialize_thread_args { + lck_mtx_t *lck; + const char *child_name; + thread_func_t proc; + void *arg; + pri_t pri; + int state; + thread_extended_policy_t tmsharepol; + thread_throughput_qos_policy_t throughpol; + thread_latency_qos_policy_t latpol; + int child_done; + void *wait_channel; +#ifdef SPL_DEBUG_THREAD + const char *caller_filename; + int caller_line; +#endif +} initialize_thread_args_t; + +/* + * do setup work inside the child thread, then launch + * the work, proc(arg) + */ +void +spl_thread_setup(void *v, wait_result_t wr) +{ + + /* we have been created! sanity check and take lock */ + spl_data_barrier(); + VERIFY3P(v, !=, NULL); + + initialize_thread_args_t *a = v; + + lck_mtx_lock(a->lck); + spl_data_barrier(); + + /* set things up */ + + if (a->child_name == NULL) + a->child_name = "anonymous zfs thread"; + +#if defined(MAC_OS_X_VERSION_10_15) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15) + thread_set_thread_name(current_thread(), a->child_name); +#endif + + spl_set_thread_importance(current_thread(), a->pri, a->child_name); + + if (a->tmsharepol) { + spl_set_thread_timeshare(current_thread(), + a->tmsharepol, a->child_name); + } + + if (a->throughpol) { + if (a->tmsharepol) { + ASSERT(a->tmsharepol->timeshare); + } + spl_set_thread_throughput(current_thread(), + a->throughpol, a->child_name); + } + + if (a->latpol) { + if (a->tmsharepol) { + ASSERT(a->tmsharepol->timeshare); + } + spl_set_thread_latency(current_thread(), + a->latpol, a->child_name); + } + + /* save proc and args */ + + thread_func_t proc = a->proc; + void *arg = a->arg; + + /* set done with setup flag, wake parent, release lck */ + + a->child_done = 1; + spl_data_barrier(); + wakeup_one(a->wait_channel); + spl_data_barrier(); + lck_mtx_unlock(a->lck); + + /* jump to proc, which doesn't come back here */ + + proc(arg); + __builtin_unreachable(); + panic("SPL: proc called from spl_thread_setup() returned"); +} + +kthread_t * +spl_thread_create_named( + const char *name, + caddr_t stk, + size_t stksize, + thread_func_t proc, + void *arg, + size_t len, + int state, +#ifdef SPL_DEBUG_THREAD + const char *filename, + int line, +#endif + pri_t pri) +{ + thread_extended_policy_data_t tmsharepol = { + .timeshare = TRUE + }; + + return (spl_thread_create_named_with_extpol_and_qos( + &tmsharepol, NULL, NULL, + name, stk, stksize, proc, arg, + len, state, +#ifdef SPL_DEBUG_THREAD + filename, line, +#endif + pri)); +} + +/* + * For each of the first three args, if NULL then kernel default + * + * no timesharing, no throughput qos, no latency qos + */ +kthread_t * +spl_thread_create_named_with_extpol_and_qos( + thread_extended_policy_t tmsharepol, + thread_throughput_qos_policy_t throughpol, + thread_latency_qos_policy_t latpol, + const char *name, + caddr_t stk, + size_t stksize, + thread_func_t proc, + void *arg, + size_t len, + int state, +#ifdef SPL_DEBUG_THREAD + const char *filename, + int line, +#endif + pri_t pri) +{ + kern_return_t result; + thread_t thread; + +#ifdef SPL_DEBUG_THREAD + printf("Start thread pri %d by '%s':%d\n", pri, + filename, line); +#endif + + uint64_t wait_location; + + wrapper_mutex_t lck; + + lck_mtx_init((lck_mtx_t *)&lck, spl_mtx_grp, spl_mtx_lck_attr); + + initialize_thread_args_t childargs = { + .lck = (lck_mtx_t *)&lck, + .child_name = name, + .proc = proc, + .arg = arg, + .pri = pri, + .state = state, + .tmsharepol = tmsharepol, + .throughpol = throughpol, + .latpol = latpol, + .child_done = 0, + .wait_channel = &wait_location, +#ifdef SPL_DEBUG_THREAD + .caller_filename = filename, + .caller_line = line, +#endif + }; + + spl_data_barrier(); + lck_mtx_lock((lck_mtx_t *)&lck); + spl_data_barrier(); + + result = kernel_thread_start(spl_thread_setup, + (void *)&childargs, &thread); + + if (result != KERN_SUCCESS) { + lck_mtx_unlock((lck_mtx_t *)&lck); + lck_mtx_destroy((lck_mtx_t *)&lck, spl_mtx_grp); + printf("SPL: %s:%d kernel_thread_start error return %d\n", + __func__, __LINE__, result); + return (NULL); + } + + for (; ; ) { + spl_data_barrier(); + (void) msleep(&wait_location, (lck_mtx_t *)&lck, PRIBIO, + "spl thread initialization", 0); + spl_data_barrier(); + if (childargs.child_done != 0) + break; + } + + thread_deallocate(thread); + + atomic_inc_64(&zfs_threads); + + lck_mtx_unlock((lck_mtx_t *)&lck); + lck_mtx_destroy((lck_mtx_t *)&lck, spl_mtx_grp); + + return ((kthread_t *)thread); +} + +kthread_t * +spl_current_thread(void) +{ + thread_t cur_thread = current_thread(); + return ((kthread_t *)cur_thread); +} + +__attribute__((noreturn)) void +spl_thread_exit(void) +{ + atomic_dec_64(&zfs_threads); + + tsd_thread_exit(); + (void) thread_terminate(current_thread()); + __builtin_unreachable(); +} + + +/* + * IllumOS has callout.c - place it here until we find a better place + */ +callout_id_t +timeout_generic(int type, void (*func)(void *), void *arg, + hrtime_t expiration, hrtime_t resolution, int flags) +{ + struct timespec ts; + hrt2ts(expiration, &ts); + bsd_timeout(func, arg, &ts); + /* + * bsd_untimeout() requires func and arg to cancel the timeout, so + * pass it back as the callout_id. If we one day were to implement + * untimeout_generic() they would pass it back to us + */ + return ((callout_id_t)arg); +} + +/* + * Set xnu kernel thread importance based on openzfs pri_t. + * + * Thread importance adjusts upwards and downwards from BASEPRI_KERNEL (defined + * as 81). Higher value is higher priority (e.g. BASEPRI_REALTIME is 96), + * BASEPRI_GRAPHICS is 76, and MAXPRI_USER is 63. + * + * (See osfmk/kern/sched.h) + * + * Many important kernel tasks run at BASEPRI_KERNEL, + * with networking and kernel graphics (Metal etc) running + * at BASEPRI_KERNEL + 1. + * + * We want maxclsyspri threads to have less xnu priority + * BASEPRI_KERNEL, so as to avoid UI stuttering, network + * disconnection and other side-effects of high zfs load with + * high thread priority. + * + * In we define maxclsyspri to 80 with + * defclsyspri and minclsyspri set below that. + */ + +void +spl_set_thread_importance(thread_t thread, pri_t pri, const char *name) +{ + thread_precedence_policy_data_t policy = { 0 }; + + /* + * start by finding an offset from BASEPRI_KERNEL, + * which is found in osfmk/kern/sched.h + * + * (it's 81, importance is a signed-offset from that) + */ + + const pri_t basepri = 81; + const pri_t importance = pri - basepri; + const pri_t importance_floor = DSL_SCAN_ISS_SYSPRI - basepri; + + policy.importance = importance; + + /* + * dont let ANY of our threads run as high as networking & GPU + * + * hard cap on our maximum priority at 81 (BASEPRI_KERNEL), + * which is then our maxclsyspri. + */ + if (policy.importance > 0) + policy.importance = 0; + /* + * set a floor on importance at priority 59, which is just below + * bluetoothd and userland audio, which are of relatively high + * userland importance. + */ + else if (policy.importance < importance_floor) + policy.importance = importance_floor; + + int i = policy.importance; + kern_return_t pol_prec_kret = thread_policy_set(thread, + THREAD_PRECEDENCE_POLICY, + (thread_policy_t)&policy, + THREAD_PRECEDENCE_POLICY_COUNT); + if (pol_prec_kret != KERN_SUCCESS) { + printf("SPL: %s:%d: ERROR failed to set" + " thread precedence to %d ret %d name %s\n", + __func__, __LINE__, i, pol_prec_kret, name); + } +} + +/* + * Set a kernel throughput qos for this thread, + */ + +void +spl_set_thread_throughput(thread_t thread, + thread_throughput_qos_policy_t throughput, const char *name) +{ + + + ASSERT(throughput); + + if (!throughput) + return; + + if (!name) + name = "anonymous zfs thread (throughput)"; + + /* + * TIERs: + * + * 0 is USER_INTERACTIVE, 1 is USER_INITIATED, 1 is LEGACY, + * 2 is UTILITY, 5 is BACKGROUND, 5 is MAINTENANCE + * + * (from xnu/osfmk/kern/thread_policy.c) + */ + + kern_return_t qoskret = thread_policy_set(thread, + THREAD_THROUGHPUT_QOS_POLICY, + (thread_policy_t)throughput, + THREAD_THROUGHPUT_QOS_POLICY_COUNT); + if (qoskret != KERN_SUCCESS) { + printf("SPL: %s:%d: WARNING failed to set" + " thread throughput policy retval: %d " + " (THREAD_THROUGHPUT_QOS_POLICY %x), %s\n", + __func__, __LINE__, qoskret, + throughput->thread_throughput_qos_tier, name); + } +} + +void +spl_set_thread_latency(thread_t thread, + thread_latency_qos_policy_t latency, const char *name) +{ + + ASSERT(latency); + + if (!latency) + return; + + if (!name) + name = "anonymous zfs thread (latency)"; + + /* + * TIERs: + * 0 is USER_INTERACTIVE, 1 is USER_INITIATED, 1 is LEGACY, + * 3 is UTILITY, 3 is BACKGROUND, 5 is MAINTENANCE + * + * (from xnu/osfmk/kern/thread_policy.c) + * + * NB: these differ from throughput tier mapping + */ + + kern_return_t qoskret = thread_policy_set(thread, + THREAD_LATENCY_QOS_POLICY, + (thread_policy_t)latency, + THREAD_LATENCY_QOS_POLICY_COUNT); + if (qoskret != KERN_SUCCESS) { + printf("SPL: %s:%d: WARNING failed to set" + " thread latency policy to %x, retval: %d, '%s'\n", + __func__, __LINE__, + latency->thread_latency_qos_tier, + qoskret, + name); + } +} + +/* + * XNU will dynamically adjust TIMESHARE + * threads around the chosen thread priority. + * The lower the importance (signed value), + * the more XNU will adjust a thread. + * Threads may be adjusted *upwards* from their + * base priority by XNU as well. + */ + +void +spl_set_thread_timeshare(thread_t thread, + thread_extended_policy_t policy, + const char *name) +{ + + ASSERT(policy); + + if (!policy) + return; + + if (!name) { + if (policy->timeshare) + name = "anonymous zfs thread (timeshare->off)"; + else + name = "anonymous zfs thread (timeshare->on)"; + } + + kern_return_t kret = thread_policy_set(thread, + THREAD_EXTENDED_POLICY, + (thread_policy_t)policy, + THREAD_EXTENDED_POLICY_COUNT); + if (kret != KERN_SUCCESS) { + printf("SPL: %s:%d: WARNING failed to set" + " timeshare policy to %d, retval: %d, %s\n", + __func__, __LINE__, kret, + policy->timeshare, name); + } +} diff --git a/module/os/macos/spl/spl-time.c b/module/os/macos/spl/spl-time.c new file mode 100644 index 000000000000..151691d60b6c --- /dev/null +++ b/module/os/macos/spl/spl-time.c @@ -0,0 +1,138 @@ +/* + * 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) 2008 MacZFS + * Copyright (C) 2013 Jorgen Lundman + * + */ + +#include +#include +#include + +/* + * gethrtime() provides high-resolution timestamps with + * machine-dependent origin Hence its primary use is to specify + * intervals. + */ + +static hrtime_t +zfs_abs_to_nano(uint64_t elapsed) +{ + static mach_timebase_info_data_t sTimebaseInfo = { 0, 0 }; + + /* + * If this is the first time we've run, get the timebase. + * We can use denom == 0 to indicate that sTimebaseInfo is + * uninitialised because it makes no sense to have a zero + * denominator in a fraction. + */ + + if (sTimebaseInfo.denom == 0) { + (void) clock_timebase_info(&sTimebaseInfo); + } + + /* + * Convert to nanoseconds. + * return (elapsed * (uint64_t)sTimebaseInfo.numer) / + * (uint64_t)sTimebaseInfo.denom; + * + * Provided the final result is representable in 64 bits the + * following maneuver will deliver that result without intermediate + * overflow. + */ + if (sTimebaseInfo.denom == sTimebaseInfo.numer) + return (elapsed); + else if (sTimebaseInfo.denom == 1) + return (elapsed * (uint64_t)sTimebaseInfo.numer); + else { + /* Decompose elapsed = eta32 * 2^32 + eps32: */ + uint64_t eta32 = elapsed >> 32; + uint64_t eps32 = elapsed & 0x00000000ffffffffLL; + + uint32_t numer = sTimebaseInfo.numer; + uint32_t denom = sTimebaseInfo.denom; + + /* Form product of elapsed64 (decomposed) and numer: */ + uint64_t mu64 = numer * eta32; + uint64_t lambda64 = numer * eps32; + + /* Divide the constituents by denom: */ + uint64_t q32 = mu64/denom; + uint64_t r32 = mu64 - (q32 * denom); /* mu64 % denom */ + + return ((q32 << 32) + ((r32 << 32) + lambda64) / denom); + } +} + + +hrtime_t +gethrtime(void) +{ + static uint64_t start = 0; + if (start == 0) + start = mach_absolute_time(); + return (zfs_abs_to_nano(mach_absolute_time() - start)); +} + + +void +gethrestime(struct timespec *ts) +{ + nanotime(ts); +} + +time_t +gethrestime_sec(void) +{ + struct timeval tv; + + microtime(&tv); + return (tv.tv_sec); +} + +void +hrt2ts(hrtime_t hrt, struct timespec *tsp) +{ + uint32_t sec, nsec, tmp; + + tmp = (uint32_t)(hrt >> 30); + sec = tmp - (tmp >> 2); + sec = tmp - (sec >> 5); + sec = tmp + (sec >> 1); + sec = tmp - (sec >> 6) + 7; + sec = tmp - (sec >> 3); + sec = tmp + (sec >> 1); + sec = tmp + (sec >> 3); + sec = tmp + (sec >> 4); + tmp = (sec << 7) - sec - sec - sec; + tmp = (tmp << 7) - tmp - tmp - tmp; + tmp = (tmp << 7) - tmp - tmp - tmp; + nsec = (uint32_t)hrt - (tmp << 9); + while (nsec >= NANOSEC) { + nsec -= NANOSEC; + sec++; + } + tsp->tv_sec = (time_t)sec; + tsp->tv_nsec = nsec; +} diff --git a/module/os/macos/spl/spl-tsd.c b/module/os/macos/spl/spl-tsd.c new file mode 100644 index 000000000000..6ca970a9f96e --- /dev/null +++ b/module/os/macos/spl/spl-tsd.c @@ -0,0 +1,389 @@ +/* + * 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) 2014 Jorgen Lundman + * + * A thread will call tsd_create(&key, dtor) to allocate a new + * "variable" placement, called a "key". In illumos, this is the index + * into an array of dtors. (If dtor is passed as NULL, TSD internally + * set it to an empty function). So if the dtor array[i] is NULL, it + * is "free" and can be allocated. (returned as *key = i). + * illumos will grow this dtor array with realloc when required. + * Then Any Thread can set a value on this "key index", and this value + * is specific to each thread by calling tsd_set(key, value). + * And can be retrieved with tsd_get(key). + * When tsd_destroy(key) is called, we need to loop through all + * threads different "values", and call the dtor on each one. + * Likewise, we need to know when a thread exists, so we can clean up + * the values (by calling dtor for each one) so we patch into the + * thread_exit() call, to also call tsd_thread_exit(). + * + * In OsX, we build an array of the dtors, and return the key index, + * this is to store the dtor, and know which "key" values are valid. + * Then we build an AVL tree, indexed by , to store + * each thread's value. This allows us to do key access quick. + * On thread_exit, we iterate the dtor array, and for each key + * remove . + * On tsd_destroy(key), we use AVL find nearest with , then + * avl_next as long as key remains the same, to remove each thread value. + * + * Note a key of "0" is considered "invalid" in IllumOS, so we return + * a "1" based index, even though internally it is 0 based. + * + */ + +#include +#include +#include +#include +#include + +/* Initial size of array, and realloc growth size */ +#define TSD_ALLOC_SIZE 10 + +/* array of dtors, allocated in init */ +static dtor_func_t *tsd_dtor_array = NULL; +static uint32_t tsd_dtor_size = 0; +static avl_tree_t tsd_tree; + +struct spl_tsd_node_s +{ + /* The index/key */ + uint_t tsd_key; + thread_t tsd_thread; + + /* The payload */ + void *tsd_value; + + /* Internal mumbo */ + avl_node_t tsd_link_node; +}; +typedef struct spl_tsd_node_s spl_tsd_node_t; + +static kmutex_t spl_tsd_mutex; + +/* + * tsd_set - set thread specific data + * @key: lookup key + * @value: value to set + * + * Caller must prevent racing tsd_create() or tsd_destroy(), protected + * from racing tsd_get() or tsd_set() because it is thread specific. + * This function has been optimized to be fast for the update case. + * When setting the tsd initially it will be slower due to additional + * required locking and potential memory allocations. + * If the value is set to NULL, we also release it. + */ +int +tsd_set(uint_t key, void *value) +{ + spl_tsd_node_t *entry = NULL; + spl_tsd_node_t search; + avl_index_t loc; + uint_t i; + + /* Invalid key values? */ + if ((key < 1) || + (key >= tsd_dtor_size)) { + return (EINVAL); + } + + i = key - 1; + + /* + * First handle the easy case, already has a node/value + * so we just need to find it, update it. + */ + + search.tsd_key = i; + search.tsd_thread = current_thread(); + + mutex_enter(&spl_tsd_mutex); + entry = avl_find(&tsd_tree, &search, &loc); + mutex_exit(&spl_tsd_mutex); + + if (entry) { + + /* If value is set to NULL, release it as well */ + if (value == NULL) { + mutex_enter(&spl_tsd_mutex); + avl_remove(&tsd_tree, entry); + mutex_exit(&spl_tsd_mutex); + kmem_free(entry, sizeof (*entry)); + return (0); + } + entry->tsd_value = value; + return (0); + } + + /* No node, we need to create a new one and insert it. */ + /* But if the value is NULL, then why create one eh? */ + if (value == NULL) + return (0); + + entry = kmem_alloc(sizeof (spl_tsd_node_t), KM_SLEEP); + + entry->tsd_key = i; + entry->tsd_thread = current_thread(); + entry->tsd_value = value; + + mutex_enter(&spl_tsd_mutex); + avl_add(&tsd_tree, entry); + mutex_exit(&spl_tsd_mutex); + + return (0); +} + +/* + * tsd_get - get thread specific data for specified thread + * @key: lookup key + * + * Caller must prevent racing tsd_create() or tsd_destroy(). This + * implementation is designed to be fast and scalable, it does not + * lock the entire table only a single hash bin. + */ +void * +tsd_get_by_thread(uint_t key, thread_t thread) +{ + spl_tsd_node_t *entry = NULL; + spl_tsd_node_t search; + avl_index_t loc; + uint_t i; + + /* Invalid key values? */ + if ((key < 1) || + (key >= tsd_dtor_size)) { + return (NULL); + } + + i = key - 1; + + search.tsd_key = i; + search.tsd_thread = thread; + + mutex_enter(&spl_tsd_mutex); + entry = avl_find(&tsd_tree, &search, &loc); + mutex_exit(&spl_tsd_mutex); + + return (entry ? entry->tsd_value : NULL); +} + +void * +tsd_get(uint_t key) +{ + return (tsd_get_by_thread(key, current_thread())); +} + +static void +tsd_internal_dtor(void *value) +{ +} + +/* + * Create TSD for a pid and fill in key with unique value, remember the dtor + * + * We cheat and create an entry with pid=0, to keep the dtor. + */ +void +tsd_create(uint_t *keyp, dtor_func_t dtor) +{ + uint_t i; + + if (*keyp) + return; + + // Iterate the dtor_array, looking for first NULL + for (i = 0; i < TSD_ALLOC_SIZE; i++) { + if (tsd_dtor_array[i] == NULL) break; + } + + /* Do we need to grow the list? */ + if (i >= tsd_dtor_size) { + printf("SPL: tsd list growing not implemented\n"); + return; + } + + if (dtor == NULL) + dtor = tsd_internal_dtor; + + tsd_dtor_array[i] = dtor; + + *keyp = i + 1; +} + +void +tsd_destroy(uint_t *keyp) +{ + spl_tsd_node_t *entry = NULL, *next = NULL; + spl_tsd_node_t search; + avl_index_t loc; + dtor_func_t dtor = NULL; + uint_t i; + + /* Invalid key values? */ + if ((*keyp < 1) || + (*keyp >= tsd_dtor_size)) { + return; + } + + i = *keyp - 1; + *keyp = 0; + + ASSERT(tsd_dtor_array[i] != NULL); + + dtor = tsd_dtor_array[i]; + tsd_dtor_array[i] = NULL; + + /* + * For each thread; + * if it has a value + * call the dtor + */ + search.tsd_key = i; + search.tsd_thread = NULL; + + mutex_enter(&spl_tsd_mutex); + entry = avl_find(&tsd_tree, &search, &loc); + + /* + * "entry" should really be NULL here, as we searched for the + * NULL thread + */ + if (entry == NULL) + entry = avl_nearest(&tsd_tree, loc, AVL_AFTER); + + /* Now, free node, and go to next, as long as the key matches */ + while (entry && (entry->tsd_key == i)) { + next = AVL_NEXT(&tsd_tree, entry); + + /* If we have a value, call the dtor for this thread */ + if (entry->tsd_value) + dtor(entry->tsd_value); + + avl_remove(&tsd_tree, entry); + + kmem_free(entry, sizeof (*entry)); + + entry = next; + } + + mutex_exit(&spl_tsd_mutex); +} + + + +/* + * A thread is exiting, clear out any tsd values it might have. + */ +void +tsd_thread_exit(void) +{ + spl_tsd_node_t *entry = NULL; + spl_tsd_node_t search; + avl_index_t loc; + int i; + + search.tsd_thread = current_thread(); + + /* For all defined dtor/values */ + for (i = 0; i < tsd_dtor_size; i++) { + + /* If not allocated, skip */ + if (tsd_dtor_array[i] == NULL) continue; + + /* Find out of this thread has a value */ + search.tsd_key = i; + + mutex_enter(&spl_tsd_mutex); + entry = avl_find(&tsd_tree, &search, &loc); + if (entry) avl_remove(&tsd_tree, entry); + mutex_exit(&spl_tsd_mutex); + + if (entry == NULL) continue; + + /* If we have a value, call dtor */ + if (entry->tsd_value) + tsd_dtor_array[i](entry->tsd_value); + + kmem_free(entry, sizeof (*entry)); + } // for all i +} + +static int +tsd_tree_cmp(const void *arg1, const void *arg2) +{ + const spl_tsd_node_t *node1 = arg1; + const spl_tsd_node_t *node2 = arg2; + if (node1->tsd_key > node2->tsd_key) + return (1); + if (node1->tsd_key < node2->tsd_key) + return (-1); + if (node1->tsd_thread > node2->tsd_thread) + return (1); + if (node1->tsd_thread < node2->tsd_thread) + return (-1); + return (0); +} + +int +spl_tsd_init(void) +{ + tsd_dtor_array = kmem_zalloc(sizeof (dtor_func_t) * TSD_ALLOC_SIZE, + KM_SLEEP); + tsd_dtor_size = TSD_ALLOC_SIZE; + + mutex_init(&spl_tsd_mutex, NULL, MUTEX_DEFAULT, NULL); + avl_create(&tsd_tree, tsd_tree_cmp, + sizeof (spl_tsd_node_t), + offsetof(spl_tsd_node_t, tsd_link_node)); + return (0); +} + + +uint64_t +spl_tsd_size(void) +{ + return (avl_numnodes(&tsd_tree)); +} + +void +spl_tsd_fini(void) +{ + spl_tsd_node_t *entry = NULL; + void *cookie = NULL; + + printf("SPL: tsd unloading %llu\n", spl_tsd_size()); + + mutex_enter(&spl_tsd_mutex); + cookie = NULL; + while ((entry = avl_destroy_nodes(&tsd_tree, &cookie))) { + kmem_free(entry, sizeof (*entry)); + } + mutex_exit(&spl_tsd_mutex); + + avl_destroy(&tsd_tree); + mutex_destroy(&spl_tsd_mutex); + + kmem_free(tsd_dtor_array, sizeof (dtor_func_t) * tsd_dtor_size); + tsd_dtor_size = 0; +} diff --git a/module/os/macos/spl/spl-uio.c b/module/os/macos/spl/spl-uio.c new file mode 100644 index 000000000000..b19c949f9ebd --- /dev/null +++ b/module/os/macos/spl/spl-uio.c @@ -0,0 +1,148 @@ +/* + * 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) 2015 by Chunwei Chen. All rights reserved. + * Copyright (C) 2021 Jorgen Lundman + * + */ + +#include +#include +#include + +struct iovec empty_iov = { 0 }; + +static int +zfs_uiomove_iov(void *p, size_t n, zfs_uio_rw_t rw, zfs_uio_t *uio) +{ + const struct iovec *iov = uio->uio_iov; + size_t skip = uio->uio_skip; + int cnt; + + while (n && uio->uio_resid) { + cnt = MIN(iov->iov_len - skip, n); + switch ((int)uio->uio_segflg) { + case UIO_SYSSPACE: + if (rw == UIO_READ) + memcpy(iov->iov_base + skip, p, cnt); + else + memcpy((void *)p, iov->iov_base + skip, + cnt); + break; + + case UIO_FUNCSPACE: + VERIFY3P(uio->uio_iofunc, !=, NULL); + cnt = uio->uio_iofunc(p, skip, cnt, rw, + iov->iov_base); + break; + + default: + VERIFY(0); + return (-1); + } + skip += cnt; + if (skip == iov->iov_len) { + skip = 0; + uio->uio_iov = (++iov); + uio->uio_iovcnt--; + } + uio->uio_skip = skip; + uio->uio_resid -= cnt; + uio->uio_loffset += cnt; + p = (caddr_t)p + cnt; + n -= cnt; + } + return (0); +} + +int +zfs_uiomove(const char *p, size_t n, enum uio_rw rw, zfs_uio_t *uio) +{ + int result; + + if (uio->uio_iov == NULL) { + uio_setrw(uio->uio_xnu, rw); + result = uiomove(p, n, uio->uio_xnu); + return (result); + } + + result = zfs_uiomove_iov((void *)p, n, rw, uio); + return (result); +} + +/* + * same as uiomove() but doesn't modify uio structure. + * return in cbytes how many bytes were copied. + */ +int +zfs_uiocopy(const char *p, size_t n, enum uio_rw rw, zfs_uio_t *uio, + size_t *cbytes) +{ + int result; + if (uio->uio_iov == NULL) { + struct uio *nuio = uio_duplicate(uio->uio_xnu); + unsigned long long x = uio_resid(uio->uio_xnu); + if (!nuio) + return (ENOMEM); + uio_setrw(nuio, rw); + result = uiomove(p, n, nuio); + *cbytes = x-uio_resid(nuio); + uio_free(nuio); + return (result); + } + + zfs_uio_t uio_copy; + + memcpy(&uio_copy, uio, sizeof (zfs_uio_t)); + result = zfs_uiomove_iov((void *)p, n, rw, &uio_copy); + + *cbytes = uio->uio_resid - uio_copy.uio_resid; + + return (result); +} + +void +zfs_uioskip(zfs_uio_t *uio, size_t n) +{ + if (uio->uio_iov == NULL) { + uio_update(uio->uio_xnu, n); + } else { + if (n > uio->uio_resid) + return; + uio->uio_skip += n; + while (uio->uio_iovcnt && + uio->uio_skip >= uio->uio_iov->iov_len) { + uio->uio_skip -= uio->uio_iov->iov_len; + uio->uio_iov++; + uio->uio_iovcnt--; + } + uio->uio_loffset += n; + uio->uio_resid -= n; + } +} + +int +zfs_uio_prefaultpages(ssize_t n, zfs_uio_t *uio) +{ + return (0); +} diff --git a/module/os/macos/spl/spl-vmem.c b/module/os/macos/spl/spl-vmem.c new file mode 100644 index 000000000000..3dc2f77d1458 --- /dev/null +++ b/module/os/macos/spl/spl-vmem.c @@ -0,0 +1,4147 @@ +/* + * 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 (c) 2012 by Delphix. All rights reserved. + * Copyright (c) 2012, Joyent, Inc. All rights reserved. + * Copyright (c) 2017, 2021, 2023 by Sean Doran + */ + +/* + * Big Theory Statement for the virtual memory allocator. + * + * For a more complete description of the main ideas, see: + * + * Jeff Bonwick and Jonathan Adams, + * + * Magazines and vmem: Extending the Slab Allocator to Many CPUs and + * Arbitrary Resources. + * + * Proceedings of the 2001 Usenix Conference. + * Available as http://www.usenix.org/event/usenix01/bonwick.html + * + * + * 1. General Concepts + * ------------------- + * + * 1.1 Overview + * ------------ + * We divide the kernel address space into a number of logically distinct + * pieces, or *arenas*: text, data, heap, stack, and so on. Within these + * arenas we often subdivide further; for example, we use heap addresses + * not only for the kernel heap (kmem_alloc() space), but also for DVMA, + * bp_mapin(), /dev/kmem, and even some device mappings like the TOD chip. + * The kernel address space, therefore, is most accurately described as + * a tree of arenas in which each node of the tree *imports* some subset + * of its parent. The virtual memory allocator manages these arenas and + * supports their natural hierarchical structure. + * + * 1.2 Arenas + * ---------- + * An arena is nothing more than a set of integers. These integers most + * commonly represent virtual addresses, but in fact they can represent + * anything at all. For example, we could use an arena containing the + * integers minpid through maxpid to allocate process IDs. vmem_create() + * and vmem_destroy() create and destroy vmem arenas. In order to + * differentiate between arenas used for adresses and arenas used for + * identifiers, the VMC_IDENTIFIER flag is passed to vmem_create(). This + * prevents identifier exhaustion from being diagnosed as general memory + * failure. + * + * 1.3 Spans + * --------- + * We represent the integers in an arena as a collection of *spans*, or + * contiguous ranges of integers. For example, the kernel heap consists + * of just one span: [kernelheap, ekernelheap). Spans can be added to an + * arena in two ways: explicitly, by vmem_add(), or implicitly, by + * importing, as described in Section 1.5 below. + * + * 1.4 Segments + * ------------ + * Spans are subdivided into *segments*, each of which is either allocated + * or free. A segment, like a span, is a contiguous range of integers. + * Each allocated segment [addr, addr + size) represents exactly one + * vmem_alloc_impl(size) that returned addr. Free segments represent the space + * between allocated segments. If two free segments are adjacent, we + * coalesce them into one larger segment; that is, if segments [a, b) and + * [b, c) are both free, we merge them into a single segment [a, c). + * The segments within a span are linked together in increasing-address order + * so we can easily determine whether coalescing is possible. + * + * Segments never cross span boundaries. When all segments within + * an imported span become free, we return the span to its source. + * + * 1.5 Imported Memory + * ------------------- + * As mentioned in the overview, some arenas are logical subsets of + * other arenas. For example, kmem_va_arena (a virtual address cache + * that satisfies most kmem_slab_create() requests) is just a subset + * of heap_arena (the kernel heap) that provides caching for the most + * common slab sizes. When kmem_va_arena runs out of virtual memory, + * it *imports* more from the heap; we say that heap_arena is the + * *vmem source* for kmem_va_arena. vmem_create() allows you to + * specify any existing vmem arena as the source for your new arena. + * Topologically, since every arena is a child of at most one source, + * the set of all arenas forms a collection of trees. + * + * 1.6 Constrained Allocations + * --------------------------- + * Some vmem clients are quite picky about the kind of address they want. + * For example, the DVMA code may need an address that is at a particular + * phase with respect to some alignment (to get good cache coloring), or + * that lies within certain limits (the addressable range of a device), + * or that doesn't cross some boundary (a DMA counter restriction) -- + * or all of the above. vmem_xalloc() allows the client to specify any + * or all of these constraints. + * + * 1.7 The Vmem Quantum + * -------------------- + * Every arena has a notion of 'quantum', specified at vmem_create() time, + * that defines the arena's minimum unit of currency. Most commonly the + * quantum is either 1 or PAGESIZE, but any power of 2 is legal. + * All vmem allocations are guaranteed to be quantum-aligned. + * + * 1.8 Quantum Caching + * ------------------- + * A vmem arena may be so hot (frequently used) that the scalability of vmem + * allocation is a significant concern. We address this by allowing the most + * common allocation sizes to be serviced by the kernel memory allocator, + * which provides low-latency per-cpu caching. The qcache_max argument to + * vmem_create() specifies the largest allocation size to cache. + * + * 1.9 Relationship to Kernel Memory Allocator + * ------------------------------------------- + * Every kmem cache has a vmem arena as its slab supplier. The kernel memory + * allocator uses vmem_alloc_impl() and vmem_free_impl() to create and + * destroy slabs. + * + * + * 2. Implementation + * ----------------- + * + * 2.1 Segment lists and markers + * ----------------------------- + * The segment structure (vmem_seg_t) contains two doubly-linked lists. + * + * The arena list (vs_anext/vs_aprev) links all segments in the arena. + * In addition to the allocated and free segments, the arena contains + * special marker segments at span boundaries. Span markers simplify + * coalescing and importing logic by making it easy to tell both when + * we're at a span boundary (so we don't coalesce across it), and when + * a span is completely free (its neighbors will both be span markers). + * + * Imported spans will have vs_import set. + * + * The next-of-kin list (vs_knext/vs_kprev) links segments of the same type: + * (1) for allocated segments, vs_knext is the hash chain linkage; + * (2) for free segments, vs_knext is the freelist linkage; + * (3) for span marker segments, vs_knext is the next span marker. + * + * 2.2 Allocation hashing + * ---------------------- + * We maintain a hash table of all allocated segments, hashed by address. + * This allows vmem_free_impl() to discover the target segment in constant + * time. + * vmem_update() periodically resizes hash tables to keep hash chains short. + * + * 2.3 Freelist management + * ----------------------- + * We maintain power-of-2 freelists for free segments, i.e. free segments + * of size >= 2^n reside in vmp->vm_freelist[n]. To ensure constant-time + * allocation, vmem_xalloc() looks not in the first freelist that *might* + * satisfy the allocation, but in the first freelist that *definitely* + * satisfies the allocation (unless VM_BESTFIT is specified, or all larger + * freelists are empty). For example, a 1000-byte allocation will be + * satisfied not from the 512..1023-byte freelist, whose members *might* + * contains a 1000-byte segment, but from a 1024-byte or larger freelist, + * the first member of which will *definitely* satisfy the allocation. + * This ensures that vmem_xalloc() works in constant time. + * + * We maintain a bit map to determine quickly which freelists are non-empty. + * vmp->vm_freemap & (1 << n) is non-zero iff vmp->vm_freelist[n] is non-empty. + * + * The different freelists are linked together into one large freelist, + * with the freelist heads serving as markers. Freelist markers simplify + * the maintenance of vm_freemap by making it easy to tell when we're taking + * the last member of a freelist (both of its neighbors will be markers). + * + * 2.4 Vmem Locking + * ---------------- + * For simplicity, all arena state is protected by a per-arena lock. + * For very hot arenas, use quantum caching for scalability. + * + * 2.5 Vmem Population + * ------------------- + * Any internal vmem routine that might need to allocate new segment + * structures must prepare in advance by calling vmem_populate(), which + * will preallocate enough vmem_seg_t's to get is through the entire + * operation without dropping the arena lock. + * + * 2.6 Auditing + * ------------ + * If KMF_AUDIT is set in kmem_flags, we audit vmem allocations as well. + * Since virtual addresses cannot be scribbled on, there is no equivalent + * in vmem to redzone checking, deadbeef, or other kmem debugging features. + * Moreover, we do not audit frees because segment coalescing destroys the + * association between an address and its segment structure. Auditing is + * thus intended primarily to keep track of who's consuming the arena. + * Debugging support could certainly be extended in the future if it proves + * necessary, but we do so much live checking via the allocation hash table + * that even non-DEBUG systems get quite a bit of sanity checking already. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VMEM_INITIAL 21 /* early vmem arenas */ +#define VMEM_SEG_INITIAL 800 + +/* + * Adding a new span to an arena requires two segment structures: one to + * represent the span, and one to represent the free segment it contains. + */ +#define VMEM_SEGS_PER_SPAN_CREATE 2 + +/* + * Allocating a piece of an existing segment requires 0-2 segment structures + * depending on how much of the segment we're allocating. + * + * To allocate the entire segment, no new segment structures are needed; we + * simply move the existing segment structure from the freelist to the + * allocation hash table. + * + * To allocate a piece from the left or right end of the segment, we must + * split the segment into two pieces (allocated part and remainder), so we + * need one new segment structure to represent the remainder. + * + * To allocate from the middle of a segment, we need two new segment strucures + * to represent the remainders on either side of the allocated part. + */ +#define VMEM_SEGS_PER_EXACT_ALLOC 0 +#define VMEM_SEGS_PER_LEFT_ALLOC 1 +#define VMEM_SEGS_PER_RIGHT_ALLOC 1 +#define VMEM_SEGS_PER_MIDDLE_ALLOC 2 + +/* + * vmem_populate() preallocates segment structures for vmem to do its work. + * It must preallocate enough for the worst case, which is when we must import + * a new span and then allocate from the middle of it. + */ +#define VMEM_SEGS_PER_ALLOC_MAX \ +(VMEM_SEGS_PER_SPAN_CREATE + VMEM_SEGS_PER_MIDDLE_ALLOC) + +/* + * The segment structures themselves are allocated from vmem_seg_arena, so + * we have a recursion problem when vmem_seg_arena needs to populate itself. + * We address this by working out the maximum number of segment structures + * this act will require, and multiplying by the maximum number of threads + * that we'll allow to do it simultaneously. + * + * The worst-case segment consumption to populate vmem_seg_arena is as + * follows (depicted as a stack trace to indicate why events are occurring): + * + * (In order to lower the fragmentation in the heap_arena, we specify a + * minimum import size for the vmem_metadata_arena which is the same size + * as the kmem_va quantum cache allocations. This causes the worst-case + * allocation from the vmem_metadata_arena to be 3 segments.) + * + * vmem_alloc_impl(vmem_seg_arena) -> 2 segs (span create + exact alloc) + * segkmem_alloc(vmem_metadata_arena) + * vmem_alloc_impl(vmem_metadata_arena) -> 3 segs (span create + left alloc) + * vmem_alloc_impl(heap_arena) -> 1 seg (left alloc) + * page_create() + * hat_memload() + * kmem_cache_alloc() + * kmem_slab_create() + * vmem_alloc_impl(hat_memload_arena) -> 2 segs (span create + exact alloc) + * segkmem_alloc(heap_arena) + * vmem_alloc_impl(heap_arena) -> 1 seg (left alloc) + * page_create() + * hat_memload() -> (hat layer won't recurse further) + * + * The worst-case consumption for each arena is 3 segment structures. + * Of course, a 3-seg reserve could easily be blown by multiple threads. + * Therefore, we serialize all allocations from vmem_seg_arena (which is OK + * because they're rare). We cannot allow a non-blocking allocation to get + * tied up behind a blocking allocation, however, so we use separate locks + * for VM_SLEEP and VM_NOSLEEP allocations. Similarly, VM_PUSHPAGE allocations + * must not block behind ordinary VM_SLEEPs. In addition, if the system is + * panicking then we must keep enough resources for panic_thread to do its + * work. Thus we have at most four threads trying to allocate from + * vmem_seg_arena, and each thread consumes at most three segment structures, + * so we must maintain a 12-seg reserve. + */ +#define VMEM_POPULATE_RESERVE 12 + +/* + * vmem_populate() ensures that each arena has VMEM_MINFREE seg structures + * so that it can satisfy the worst-case allocation *and* participate in + * worst-case allocation from vmem_seg_arena. + */ +#define VMEM_MINFREE (VMEM_POPULATE_RESERVE + VMEM_SEGS_PER_ALLOC_MAX) + +static vmem_t vmem0[VMEM_INITIAL]; +static vmem_t *vmem_populator[VMEM_INITIAL]; +static uint32_t vmem_id; +static uint32_t vmem_populators; +static vmem_seg_t vmem_seg0[VMEM_SEG_INITIAL]; +static vmem_seg_t *vmem_segfree; +static kmutex_t vmem_list_lock; +static kmutex_t vmem_segfree_lock; +static kmutex_t vmem_sleep_lock; +static kmutex_t vmem_nosleep_lock; +static kmutex_t vmem_pushpage_lock; +static kmutex_t vmem_panic_lock; +static kmutex_t vmem_xnu_alloc_lock; +static vmem_t *vmem_list; +static vmem_t *vmem_metadata_arena; +static vmem_t *vmem_seg_arena; +static vmem_t *vmem_hash_arena; +static vmem_t *vmem_vmem_arena; +vmem_t *spl_default_arena; // The bottom-most arena for SPL +static vmem_t *spl_default_arena_parent; // dummy arena as a placeholder +#define VMEM_BUCKETS 13 +#define VMEM_BUCKET_LOWBIT 12 +#define VMEM_BUCKET_HIBIT 24 +static vmem_t *vmem_bucket_arena[VMEM_BUCKETS]; +vmem_t *spl_heap_arena; +static void *spl_heap_arena_initial_alloc; +static size_t spl_heap_arena_initial_alloc_size = 0; +#define NUMBER_OF_ARENAS_IN_VMEM_INIT 21 +/* vmem_update() every 15 seconds */ +static struct timespec vmem_update_interval = {15, 0}; +uint32_t vmem_mtbf; /* mean time between failures [default: off] */ +size_t vmem_seg_size = sizeof (vmem_seg_t); + +// must match with include/sys/vmem_impl.h +static vmem_kstat_t vmem_kstat_template = { + { "mem_inuse", KSTAT_DATA_UINT64 }, + { "mem_import", KSTAT_DATA_UINT64 }, + { "mem_total", KSTAT_DATA_UINT64 }, + { "vmem_source", KSTAT_DATA_UINT32 }, + { "alloc", KSTAT_DATA_UINT64 }, + { "free", KSTAT_DATA_UINT64 }, + { "wait", KSTAT_DATA_UINT64 }, + { "fail", KSTAT_DATA_UINT64 }, + { "lookup", KSTAT_DATA_UINT64 }, + { "search", KSTAT_DATA_UINT64 }, + { "populate_fail", KSTAT_DATA_UINT64 }, + { "contains", KSTAT_DATA_UINT64 }, + { "contains_search", KSTAT_DATA_UINT64 }, + { "parent_alloc", KSTAT_DATA_UINT64 }, + { "parent_free", KSTAT_DATA_UINT64 }, + { "threads_waiting", KSTAT_DATA_UINT64 }, + { "excess", KSTAT_DATA_UINT64 }, + { "lowest_stack", KSTAT_DATA_UINT64 }, + { "async_stack_calls", KSTAT_DATA_UINT64 }, +}; + + +/* + * Insert/delete from arena list (type 'a') or next-of-kin list (type 'k'). + */ +#define VMEM_INSERT(vprev, vsp, type) \ +{ \ +vmem_seg_t *_vnext = (vprev)->vs_##type##next; \ +(vsp)->vs_##type##next = (_vnext); \ +(vsp)->vs_##type##prev = (vprev); \ +(vprev)->vs_##type##next = (vsp); \ +(_vnext)->vs_##type##prev = (vsp); \ +} + +#define VMEM_DELETE(vsp, type) \ +{ \ +vmem_seg_t *_vprev = (vsp)->vs_##type##prev; \ +vmem_seg_t *_vnext = (vsp)->vs_##type##next; \ +(_vprev)->vs_##type##next = (_vnext); \ +(_vnext)->vs_##type##prev = (_vprev); \ +} + +// vmem thread block count +uint64_t spl_vmem_threads_waiting = 0; + +// number of allocations > minalloc +uint64_t spl_bucket_non_pow2_allocs = 0; + +// allocator kstats +uint64_t spl_vmem_unconditional_allocs = 0; +uint64_t spl_vmem_unconditional_alloc_bytes = 0; +uint64_t spl_vmem_conditional_allocs = 0; +uint64_t spl_vmem_conditional_alloc_bytes = 0; +uint64_t spl_vmem_conditional_alloc_deny = 0; +uint64_t spl_vmem_conditional_alloc_deny_bytes = 0; + +// bucket allocator kstat +uint64_t spl_xat_pressured = 0; +uint64_t spl_xat_lastalloc = 0; +uint64_t spl_xat_lastfree = 0; +uint64_t spl_xat_sleep = 0; + +uint64_t spl_vba_fastpath = 0; +uint64_t spl_vba_fastexit = 0; +uint64_t spl_vba_slowpath = 0; +uint64_t spl_vba_parent_memory_appeared = 0; +uint64_t spl_vba_parent_memory_blocked = 0; +uint64_t spl_vba_hiprio_blocked = 0; +uint64_t spl_vba_cv_timeout = 0; +uint64_t spl_vba_loop_timeout = 0; +uint64_t spl_vba_cv_timeout_blocked = 0; +uint64_t spl_vba_loop_timeout_blocked = 0; +uint64_t spl_vba_sleep = 0; +uint64_t spl_vba_loop_entries = 0; + +extern uint64_t stat_osif_malloc_fail; + +// bucket minimum span size tunables +uint64_t spl_bucket_tunable_large_span = 0; +uint64_t spl_bucket_tunable_small_span = 0; + +// for XAT & XATB visibility into VBA queue +static _Atomic uint32_t spl_vba_threads[VMEM_BUCKETS] = { 0 }; +static uint32_t + vmem_bucket_id_to_bucket_number[NUMBER_OF_ARENAS_IN_VMEM_INIT] = { 0 }; +boolean_t spl_arc_no_grow(size_t, boolean_t, kmem_cache_t **); +_Atomic uint64_t spl_arc_no_grow_bits = 0; +uint64_t spl_arc_no_grow_count = 0; + +// compare span ages this many steps from the head of the freelist +uint64_t spl_frag_max_walk = 1000; +uint64_t spl_frag_walked_out = 0; +uint64_t spl_frag_walk_cnt = 0; + +extern void spl_free_set_emergency_pressure(int64_t p); +extern uint64_t segkmem_total_mem_allocated; +extern uint64_t total_memory; + +extern uint64_t spl_enforce_memory_caps; +extern _Atomic uint64_t spl_dynamic_memory_cap; +extern hrtime_t spl_dynamic_memory_cap_last_downward_adjust; +extern kmutex_t spl_dynamic_memory_cap_lock; +extern uint64_t spl_dynamic_memory_cap_reductions; +extern uint64_t spl_dynamic_memory_cap_hit_floor; + +#define INITIAL_BLOCK_SIZE 16ULL*1024ULL*1024ULL +static char *initial_default_block = NULL; + +/* + * Get a vmem_seg_t from the global segfree list. + */ +static inline vmem_seg_t * +vmem_getseg_global(void) +{ + vmem_seg_t *vsp; + + mutex_enter(&vmem_segfree_lock); + if ((vsp = vmem_segfree) != NULL) + vmem_segfree = vsp->vs_knext; + mutex_exit(&vmem_segfree_lock); + + if (vsp != NULL) + vsp->vs_span_createtime = 0; + + return (vsp); +} + +/* + * Put a vmem_seg_t on the global segfree list. + */ +static inline void +vmem_putseg_global(vmem_seg_t *vsp) +{ + mutex_enter(&vmem_segfree_lock); + vsp->vs_knext = vmem_segfree; + vmem_segfree = vsp; + mutex_exit(&vmem_segfree_lock); +} + +/* + * Get a vmem_seg_t from vmp's segfree list. + */ +static inline vmem_seg_t * +vmem_getseg(vmem_t *vmp) +{ + vmem_seg_t *vsp; + + ASSERT(vmp->vm_nsegfree > 0); + + vsp = vmp->vm_segfree; + vmp->vm_segfree = vsp->vs_knext; + vmp->vm_nsegfree--; + + return (vsp); +} + +/* + * Put a vmem_seg_t on vmp's segfree list. + */ +static inline void +vmem_putseg(vmem_t *vmp, vmem_seg_t *vsp) +{ + vsp->vs_knext = vmp->vm_segfree; + vmp->vm_segfree = vsp; + vmp->vm_nsegfree++; +} + + +/* + * Add vsp to the appropriate freelist, at the appropriate location, + * keeping the freelist sorted by age. + */ + +/* + * return true when we continue the for loop in + * vmem_freelist_insert_sort_by_time + */ +static inline bool +flist_sort_compare(bool newfirst, + const vmem_seg_t *vhead, + const vmem_seg_t *nextlist, + vmem_seg_t *p, vmem_seg_t *to_insert) +{ + /* + * vsp is the segment we are inserting into the freelist + * p is a freelist poniter or an element inside a non-empty freelist + * if we return false, then vsp is inserted immedaitely after p, + */ + + // always enter the for loop if we're at the front of a flist + if (p == vhead) + return (true); + + const vmem_seg_t *n = p->vs_knext; + + if (n == nextlist || n == NULL) { + // if we are at the tail of the flist, then + // insert vsp between p and n + return (false); + } + + if (n->vs_import == true && to_insert->vs_import == false) { + /* + * put non-imported segments before imported segments + * no matter what their respective create times are, + * thereby making imported segments more likely "age out" + */ + return (false); // inserts to_insert between p and n + } + + if (newfirst == true) { + if (n->vs_span_createtime < to_insert->vs_span_createtime) { + // n is older than me, so insert me between p and n + return (false); + } + } else { + if (n->vs_span_createtime > to_insert->vs_span_createtime) { + // n is newer than me, so insert me between p and n + return (false); + } + } + // continue iterating + return (true); +} + +static void +vmem_freelist_insert_sort_by_time(vmem_t *vmp, vmem_seg_t *vsp) +{ + ASSERT(vmp->vm_cflags & VMC_TIMEFREE); + ASSERT(vsp->vs_span_createtime > 0); + + const bool newfirst = 0 == (vmp->vm_cflags & VMC_OLDFIRST); + + const uint64_t abs_max_walk_steps = 1ULL << 30ULL; + uint32_t max_walk_steps = (uint32_t)MIN(spl_frag_max_walk, + abs_max_walk_steps); + + vmem_seg_t *vprev; + + ASSERT(*VMEM_HASH(vmp, vsp->vs_start) != vsp); + + /* + * in vmem_create_common() the freelists are arranged: + * freelist[0].vs_kprev = NULL, freelist[VMEM_FREELISTS].vs_knext = NULL + * freelist[1].vs_kprev = freelist[0], freelist[1].vs_knext = + * freelist[2] ... + * from vmem_freelist_insert(): + * VS_SIZE is the segment size (->vs_end - ->vs_start), so say 8k-512 + * highbit is the higest bit set PLUS 1, so in this case would be the + * 16k list. so below, vprev is therefore pointing to the 8k list + * in vmem_alloc_impl, the unconstrained allocation takes, for a 8k-512 + * block: vsp = flist[8k].vs_knext + * and calls vmem_seg_create() which sends any leftovers from vsp + * to vmem_freelist_insert + * + * vmem_freelist_insert would take the seg (as above, 8k-512 size), + * vprev points to the 16k list, and VMEM_INSERT(vprev, vsp, k) + * inserts the segment immediately after + * + * so vmem_seg_create(...8k-512...) pushes to the head of the 8k list, + * and vmem_alloc_impl(...8-512k...) will pull from the head of + * the 8k list + * + * below we may want to push to the TAIL of the 8k list, which is + * just before flist[16k]. + */ + + vprev = (vmem_seg_t *)&vmp->vm_freelist[highbit(VS_SIZE(vsp)) - 1]; + + int my_listnum = highbit(VS_SIZE(vsp)) - 1; + + ASSERT(my_listnum >= 1); + ASSERT(my_listnum < VMEM_FREELISTS); + + int next_listnum = my_listnum + 1; + + const vmem_seg_t *nextlist = + (vmem_seg_t *)&vmp->vm_freelist[next_listnum]; + + ASSERT(vsp->vs_span_createtime != 0); + if (vsp->vs_span_createtime == 0) { + printf("SPL: %s: WARNING: vsp->vs_span_createtime == 0 (%s)!\n", + __func__, vmp->vm_name); + } + + // continuing our example, starts with p at flist[8k] + // and n at the following freelist entry + + const vmem_seg_t *vhead = vprev; + vmem_seg_t *p = vprev; + vmem_seg_t *n = p->vs_knext; + + // walk from the freelist head looking for + // a segment whose creation time is earlier than + // the segment to be inserted's creation time, + // then insert before that segment. + + for (uint32_t step = 0; + flist_sort_compare(newfirst, vhead, nextlist, p, vsp) == true; + step++) { + // iterating while predecessor pointer p was created + // at a later tick than funcarg vsp. + // + // below we set p to n and update n. + ASSERT(n != NULL); + if (n == nextlist) { + dprintf("SPL: %s: at marker (%s)(steps: %u) " + "p->vs_start, end == %lu, %lu\n", + __func__, vmp->vm_name, step, + (uintptr_t)p->vs_start, (uintptr_t)p->vs_end); + // IOSleep(1); + // the next entry is the next marker (e.g. 16k marker) + break; + } + if (n->vs_start == 0) { + // from vmem_freelist_delete, this is a head + dprintf("SPL: %s: n->vs_start == 0 (%s)(steps: %u) " + "p->vs_start, end == %lu, %lu\n", + __func__, vmp->vm_name, step, + (uintptr_t)p->vs_start, (uintptr_t)p->vs_end); + // IOSleep(1); + break; + } + if (step >= max_walk_steps) { + ASSERT(nextlist->vs_kprev != NULL); + // we have walked far enough. + // put this segment at the tail of the freelist. + if (nextlist->vs_kprev != NULL) { + n = (vmem_seg_t *)nextlist; + p = nextlist->vs_kprev; + } + dprintf("SPL: %s: walked out (%s)\n", __func__, + vmp->vm_name); + // IOSleep(1); + atomic_inc_64(&spl_frag_walked_out); + break; + } + if (n->vs_knext == NULL) { + dprintf("SPL: %s: n->vs_knext == NULL (my_listnum " + "== %d)\n", __func__, my_listnum); + // IOSleep(1); + break; + } + p = n; + n = n->vs_knext; + atomic_inc_64(&spl_frag_walk_cnt); + } + + ASSERT(p != NULL); + + // insert segment between p and n + + vsp->vs_type = VMEM_FREE; + vmp->vm_freemap |= VS_SIZE(vprev); + VMEM_INSERT(p, vsp, k); + + cv_broadcast(&vmp->vm_cv); +} + +/* + * Add vsp to the appropriate freelist. + */ +static void +vmem_freelist_insert(vmem_t *vmp, vmem_seg_t *vsp) +{ + + if (vmp->vm_cflags & VMC_TIMEFREE) { + vmem_freelist_insert_sort_by_time(vmp, vsp); + return; + } + + vmem_seg_t *vprev; + + ASSERT(*VMEM_HASH(vmp, vsp->vs_start) != vsp); + + vprev = (vmem_seg_t *)&vmp->vm_freelist[highbit(VS_SIZE(vsp)) - 1]; + vsp->vs_type = VMEM_FREE; + vmp->vm_freemap |= VS_SIZE(vprev); + VMEM_INSERT(vprev, vsp, k); + + cv_broadcast(&vmp->vm_cv); +} + +/* + * Take vsp from the freelist. + */ +static void +vmem_freelist_delete(vmem_t *vmp, vmem_seg_t *vsp) +{ + ASSERT(*VMEM_HASH(vmp, vsp->vs_start) != vsp); + ASSERT(vsp->vs_type == VMEM_FREE); + + if (vsp->vs_knext->vs_start == 0 && vsp->vs_kprev->vs_start == 0) { + /* + * The segments on both sides of 'vsp' are freelist heads, + * so taking vsp leaves the freelist at vsp->vs_kprev empty. + */ + ASSERT(vmp->vm_freemap & VS_SIZE(vsp->vs_kprev)); + vmp->vm_freemap ^= VS_SIZE(vsp->vs_kprev); + } + VMEM_DELETE(vsp, k); +} + +/* + * Add vsp to the allocated-segment hash table and update kstats. + */ +static void +vmem_hash_insert(vmem_t *vmp, vmem_seg_t *vsp) +{ + vmem_seg_t **bucket; + + vsp->vs_type = VMEM_ALLOC; + bucket = VMEM_HASH(vmp, vsp->vs_start); + vsp->vs_knext = *bucket; + *bucket = vsp; + + if (vmem_seg_size == sizeof (vmem_seg_t)) { + // vsp->vs_depth = (uint8_t)getpcstack(vsp->vs_stack, + // VMEM_STACK_DEPTH); + // vsp->vs_thread = curthread; + vsp->vs_depth = 0; + vsp->vs_thread = 0; + vsp->vs_timestamp = gethrtime(); + } else { + vsp->vs_depth = 0; + } + + vmp->vm_kstat.vk_alloc.value.ui64++; + vmp->vm_kstat.vk_mem_inuse.value.ui64 += VS_SIZE(vsp); +} + +/* + * Remove vsp from the allocated-segment hash table and update kstats. + */ +static vmem_seg_t * +vmem_hash_delete(vmem_t *vmp, uintptr_t addr, size_t size) +{ + vmem_seg_t *vsp, **prev_vspp; + + prev_vspp = VMEM_HASH(vmp, addr); + while ((vsp = *prev_vspp) != NULL) { + if (vsp->vs_start == addr) { + *prev_vspp = vsp->vs_knext; + break; + } + vmp->vm_kstat.vk_lookup.value.ui64++; + prev_vspp = &vsp->vs_knext; + } + + if (vsp == NULL) + panic("vmem_hash_delete(%p, %lx, %lu): bad free " + "(name: %s, addr, size)", + (void *)vmp, addr, size, vmp->vm_name); + if (VS_SIZE(vsp) != size) + panic("vmem_hash_delete(%p, %lx, %lu): (%s) wrong size" + "(expect %lu)", + (void *)vmp, addr, size, vmp->vm_name, VS_SIZE(vsp)); + + vmp->vm_kstat.vk_free.value.ui64++; + vmp->vm_kstat.vk_mem_inuse.value.ui64 -= size; + + return (vsp); +} + +/* + * Create a segment spanning the range [start, end) and add it to the arena. + */ +static vmem_seg_t * +vmem_seg_create(vmem_t *vmp, vmem_seg_t *vprev, uintptr_t start, uintptr_t end) +{ + vmem_seg_t *newseg = vmem_getseg(vmp); + + newseg->vs_start = start; + newseg->vs_end = end; + newseg->vs_type = 0; + newseg->vs_import = 0; + newseg->vs_span_createtime = 0; + + VMEM_INSERT(vprev, newseg, a); + + return (newseg); +} + +/* + * Remove segment vsp from the arena. + */ +static inline void +vmem_seg_destroy(vmem_t *vmp, vmem_seg_t *vsp) +{ + ASSERT(vsp->vs_type != VMEM_ROTOR); + VMEM_DELETE(vsp, a); + + vmem_putseg(vmp, vsp); +} + +/* + * Add the span [vaddr, vaddr + size) to vmp and update kstats. + */ +static vmem_seg_t * +vmem_span_create(vmem_t *vmp, void *vaddr, size_t size, uint8_t import) +{ + vmem_seg_t *newseg, *span; + uintptr_t start = (uintptr_t)vaddr; + uintptr_t end = start + size; + + ASSERT(MUTEX_HELD(&vmp->vm_lock)); + if ((start | end) & (vmp->vm_quantum - 1)) + panic("vmem_span_create(%p, %p, %lu): misaligned (%s)", + (void *)vmp, vaddr, size, vmp->vm_name); + + span = vmem_seg_create(vmp, vmp->vm_seg0.vs_aprev, start, end); + span->vs_type = VMEM_SPAN; + span->vs_import = import; + + hrtime_t t = 0; + if (vmp->vm_cflags & VMC_TIMEFREE) { + t = gethrtime(); + } + span->vs_span_createtime = t; + + VMEM_INSERT(vmp->vm_seg0.vs_kprev, span, k); + + newseg = vmem_seg_create(vmp, span, start, end); + newseg->vs_span_createtime = t; + + vmem_freelist_insert(vmp, newseg); + + if (import) + vmp->vm_kstat.vk_mem_import.value.ui64 += size; + vmp->vm_kstat.vk_mem_total.value.ui64 += size; + + return (newseg); +} + +/* + * Remove span vsp from vmp and update kstats. + */ +static void +vmem_span_destroy(vmem_t *vmp, vmem_seg_t *vsp) +{ + vmem_seg_t *span = vsp->vs_aprev; + size_t size = VS_SIZE(vsp); + + ASSERT(MUTEX_HELD(&vmp->vm_lock)); + ASSERT(span->vs_type == VMEM_SPAN); + + if (span->vs_import) + vmp->vm_kstat.vk_mem_import.value.ui64 -= size; + vmp->vm_kstat.vk_mem_total.value.ui64 -= size; + + VMEM_DELETE(span, k); + + vmem_seg_destroy(vmp, vsp); + vmem_seg_destroy(vmp, span); +} + +/* + * Allocate the subrange [addr, addr + size) from segment vsp. + * If there are leftovers on either side, place them on the freelist. + * Returns a pointer to the segment representing [addr, addr + size). + */ +static vmem_seg_t * +vmem_seg_alloc(vmem_t *vmp, vmem_seg_t *vsp, uintptr_t addr, size_t size) +{ + uintptr_t vs_start = vsp->vs_start; + uintptr_t vs_end = vsp->vs_end; + size_t vs_size = vs_end - vs_start; + size_t realsize = P2ROUNDUP(size, vmp->vm_quantum); + uintptr_t addr_end = addr + realsize; + + ASSERT(P2PHASE(vs_start, vmp->vm_quantum) == 0); + ASSERT(P2PHASE(addr, vmp->vm_quantum) == 0); + ASSERT(vsp->vs_type == VMEM_FREE); + ASSERT(addr >= vs_start && addr_end - 1 <= vs_end - 1); + ASSERT(addr - 1 <= addr_end - 1); + + hrtime_t parent_seg_span_createtime = vsp->vs_span_createtime; + + /* + * If we're allocating from the start of the segment, and the + * remainder will be on the same freelist, we can save quite + * a bit of work. + */ + if (P2SAMEHIGHBIT(vs_size, vs_size - realsize) && addr == vs_start) { + ASSERT(highbit(vs_size) == highbit(vs_size - realsize)); + vsp->vs_start = addr_end; + vsp = vmem_seg_create(vmp, vsp->vs_aprev, addr, addr + size); + vsp->vs_span_createtime = parent_seg_span_createtime; + vmem_hash_insert(vmp, vsp); + return (vsp); + } + + vmem_freelist_delete(vmp, vsp); + + if (vs_end != addr_end) { + vmem_seg_t *v = vmem_seg_create(vmp, vsp, addr_end, vs_end); + v->vs_span_createtime = parent_seg_span_createtime; + vmem_freelist_insert(vmp, v); + } + + if (vs_start != addr) { + vmem_seg_t *v = + vmem_seg_create(vmp, vsp->vs_aprev, vs_start, addr); + v->vs_span_createtime = parent_seg_span_createtime; + vmem_freelist_insert(vmp, v); + } + + vsp->vs_start = addr; + vsp->vs_end = addr + size; + + vsp->vs_span_createtime = parent_seg_span_createtime; + + vmem_hash_insert(vmp, vsp); + return (vsp); +} + +/* + * Returns 1 if we are populating, 0 otherwise. + * Call it if we want to prevent recursion from HAT. + */ +inline int +vmem_is_populator() +{ + return (mutex_owner(&vmem_sleep_lock) == curthread || + mutex_owner(&vmem_nosleep_lock) == curthread || + mutex_owner(&vmem_pushpage_lock) == curthread || + mutex_owner(&vmem_panic_lock) == curthread); +} + +/* + * Populate vmp's segfree list with VMEM_MINFREE vmem_seg_t structures. + */ +static int +vmem_populate(vmem_t *vmp, int vmflag) +{ + char *p; + vmem_seg_t *vsp; + ssize_t nseg; + size_t size; + kmutex_t *lp; + int i; + + while (vmp->vm_nsegfree < VMEM_MINFREE && + (vsp = vmem_getseg_global()) != NULL) + vmem_putseg(vmp, vsp); + + if (vmp->vm_nsegfree >= VMEM_MINFREE) + return (1); + + /* + * If we're already populating, tap the reserve. + */ + if (vmem_is_populator()) { + ASSERT(vmp->vm_cflags & VMC_POPULATOR); + return (1); + } + + mutex_exit(&vmp->vm_lock); + + // if (panic_thread == curthread) + // lp = &vmem_panic_lock; + // else + + if (vmflag & VM_NOSLEEP) + lp = &vmem_nosleep_lock; + else if (vmflag & VM_PUSHPAGE) + lp = &vmem_pushpage_lock; + else + lp = &vmem_sleep_lock; + + mutex_enter(lp); + + nseg = VMEM_MINFREE + vmem_populators * VMEM_POPULATE_RESERVE; + size = P2ROUNDUP(nseg * vmem_seg_size, vmem_seg_arena->vm_quantum); + nseg = size / vmem_seg_size; + + /* + * The following vmem_alloc_impl() may need to populate vmem_seg_arena + * and all the things it imports from. When doing so, it will tap + * each arena's reserve to prevent recursion (see the block comment + * above the definition of VMEM_POPULATE_RESERVE). + */ + p = vmem_alloc_impl(vmem_seg_arena, size, vmflag & VM_KMFLAGS); + if (p == NULL) { + mutex_exit(lp); + mutex_enter(&vmp->vm_lock); + vmp->vm_kstat.vk_populate_fail.value.ui64++; + return (0); + } + + /* + * Restock the arenas that may have been depleted during population. + */ + for (i = 0; i < vmem_populators; i++) { + mutex_enter(&vmem_populator[i]->vm_lock); + while (vmem_populator[i]->vm_nsegfree < VMEM_POPULATE_RESERVE) + vmem_putseg(vmem_populator[i], + (vmem_seg_t *)(p + --nseg * vmem_seg_size)); + mutex_exit(&vmem_populator[i]->vm_lock); + } + + mutex_exit(lp); + mutex_enter(&vmp->vm_lock); + + /* + * Now take our own segments. + */ + ASSERT(nseg >= VMEM_MINFREE); + while (vmp->vm_nsegfree < VMEM_MINFREE) + vmem_putseg(vmp, (vmem_seg_t *)(p + --nseg * vmem_seg_size)); + + /* + * Give the remainder to charity. + */ + while (nseg > 0) + vmem_putseg_global((vmem_seg_t *)(p + --nseg * vmem_seg_size)); + + return (1); +} + +/* + * Advance a walker from its previous position to 'afterme'. + * Note: may drop and reacquire vmp->vm_lock. + */ +static void +vmem_advance(vmem_t *vmp, vmem_seg_t *walker, vmem_seg_t *afterme) +{ + vmem_seg_t *vprev = walker->vs_aprev; + vmem_seg_t *vnext = walker->vs_anext; + vmem_seg_t *vsp = NULL; + + VMEM_DELETE(walker, a); + + if (afterme != NULL) + VMEM_INSERT(afterme, walker, a); + + /* + * The walker segment's presence may have prevented its neighbors + * from coalescing. If so, coalesce them now. + */ + if (vprev->vs_type == VMEM_FREE) { + if (vnext->vs_type == VMEM_FREE) { + ASSERT(vprev->vs_end == vnext->vs_start); + ASSERT(vprev->vs_span_createtime == + vnext->vs_span_createtime); + vmem_freelist_delete(vmp, vnext); + vmem_freelist_delete(vmp, vprev); + vprev->vs_end = vnext->vs_end; + vmem_freelist_insert(vmp, vprev); + vmem_seg_destroy(vmp, vnext); + } + vsp = vprev; + } else if (vnext->vs_type == VMEM_FREE) { + vsp = vnext; + } + + /* + * vsp could represent a complete imported span, + * in which case we must return it to the source. + */ + if (vsp != NULL && vsp->vs_aprev->vs_import && + vmp->vm_source_free != NULL && + vsp->vs_aprev->vs_type == VMEM_SPAN && + vsp->vs_anext->vs_type == VMEM_SPAN) { + void *vaddr = (void *)vsp->vs_start; + size_t size = VS_SIZE(vsp); + ASSERT(size == VS_SIZE(vsp->vs_aprev)); + vmem_freelist_delete(vmp, vsp); + vmem_span_destroy(vmp, vsp); + vmp->vm_kstat.vk_parent_free.value.ui64++; + mutex_exit(&vmp->vm_lock); + vmp->vm_source_free(vmp->vm_source, vaddr, size); + mutex_enter(&vmp->vm_lock); + } +} + +/* + * VM_NEXTFIT allocations deliberately cycle through all virtual addresses + * in an arena, so that we avoid reusing addresses for as long as possible. + * This helps to catch used-after-freed bugs. It's also the perfect policy + * for allocating things like process IDs, where we want to cycle through + * all values in order. + */ +static void * +vmem_nextfit_alloc(vmem_t *vmp, size_t size, int vmflag) +{ + vmem_seg_t *vsp, *rotor; + uintptr_t addr; + size_t realsize = P2ROUNDUP(size, vmp->vm_quantum); + size_t vs_size; + + mutex_enter(&vmp->vm_lock); + + if (vmp->vm_nsegfree < VMEM_MINFREE && !vmem_populate(vmp, vmflag)) { + mutex_exit(&vmp->vm_lock); + return (NULL); + } + + /* + * The common case is that the segment right after the rotor is free, + * and large enough that extracting 'size' bytes won't change which + * freelist it's on. In this case we can avoid a *lot* of work. + * Instead of the normal vmem_seg_alloc(), we just advance the start + * address of the victim segment. Instead of moving the rotor, we + * create the new segment structure *behind the rotor*, which has + * the same effect. And finally, we know we don't have to coalesce + * the rotor's neighbors because the new segment lies between them. + */ + rotor = &vmp->vm_rotor; + vsp = rotor->vs_anext; + if (vsp->vs_type == VMEM_FREE && (vs_size = VS_SIZE(vsp)) > realsize && + P2SAMEHIGHBIT(vs_size, vs_size - realsize)) { + ASSERT(highbit(vs_size) == highbit(vs_size - realsize)); + addr = vsp->vs_start; + vsp->vs_start = addr + realsize; + hrtime_t t = vsp->vs_span_createtime; + vmem_hash_insert(vmp, + vmem_seg_create(vmp, rotor->vs_aprev, addr, addr + size)); + vsp->vs_span_createtime = t; + mutex_exit(&vmp->vm_lock); + return ((void *)addr); + } + + /* + * Starting at the rotor, look for a segment large enough to + * satisfy the allocation. + */ + for (;;) { + atomic_inc_64(&vmp->vm_kstat.vk_search.value.ui64); + if (vsp->vs_type == VMEM_FREE && VS_SIZE(vsp) >= size) + break; + vsp = vsp->vs_anext; + if (vsp == rotor) { + /* + * We've come full circle. One possibility is that the + * there's actually enough space, but the rotor itself + * is preventing the allocation from succeeding because + * it's sitting between two free segments. Therefore, + * we advance the rotor and see if that liberates a + * suitable segment. + */ + vmem_advance(vmp, rotor, rotor->vs_anext); + vsp = rotor->vs_aprev; + if (vsp->vs_type == VMEM_FREE && VS_SIZE(vsp) >= size) + break; + /* + * If there's a lower arena we can import from, or it's + * a VM_NOSLEEP allocation, let vmem_xalloc() handle it. + * Otherwise, wait until another thread frees something. + */ + if (vmp->vm_source_alloc != NULL || + (vmflag & VM_NOSLEEP)) { + mutex_exit(&vmp->vm_lock); + return (vmem_xalloc(vmp, size, vmp->vm_quantum, + 0, 0, NULL, NULL, + vmflag & (VM_KMFLAGS | VM_NEXTFIT))); + } + atomic_inc_64(&vmp->vm_kstat.vk_wait.value.ui64); + atomic_inc_64( + &vmp->vm_kstat.vk_threads_waiting.value.ui64); + atomic_inc_64(&spl_vmem_threads_waiting); + if (spl_vmem_threads_waiting > 1) + dprintf("SPL: %s: waiting for %lu sized alloc " + "after full circle of %s, waiting " + "threads %llu, total threads waiting " + "= %llu.\n", + __func__, size, vmp->vm_name, + vmp->vm_kstat.vk_threads_waiting.value.ui64, + spl_vmem_threads_waiting); + cv_wait(&vmp->vm_cv, &vmp->vm_lock); + atomic_dec_64(&spl_vmem_threads_waiting); + atomic_dec_64( + &vmp->vm_kstat.vk_threads_waiting.value.ui64); + vsp = rotor->vs_anext; + } + } + + /* + * We found a segment. Extract enough space to satisfy the allocation. + */ + addr = vsp->vs_start; + vsp = vmem_seg_alloc(vmp, vsp, addr, size); + ASSERT(vsp->vs_type == VMEM_ALLOC && + vsp->vs_start == addr && vsp->vs_end == addr + size); + + /* + * Advance the rotor to right after the newly-allocated segment. + * That's where the next VM_NEXTFIT allocation will begin searching. + */ + vmem_advance(vmp, rotor, vsp); + mutex_exit(&vmp->vm_lock); + return ((void *)addr); +} + +/* + * Checks if vmp is guaranteed to have a size-byte buffer somewhere on its + * freelist. If size is not a power-of-2, it can return a false-negative. + * + * Used to decide if a newly imported span is superfluous after re-acquiring + * the arena lock. + */ +static inline int +vmem_canalloc(vmem_t *vmp, size_t size) +{ + int hb; + int flist = 0; + ASSERT(MUTEX_HELD(&vmp->vm_lock)); + + if ((size & (size - 1)) == 0) + flist = lowbit(P2ALIGN(vmp->vm_freemap, size)); + else if ((hb = highbit(size)) < VMEM_FREELISTS) + flist = lowbit(P2ALIGN(vmp->vm_freemap, 1ULL << hb)); + + return (flist); +} + +// Convenience functions for use when gauging +// allocation ability when not holding the lock. +// These are unreliable because vmp->vm_freemap is +// liable to change immediately after being examined. +inline int +vmem_canalloc_lock(vmem_t *vmp, size_t size) +{ + mutex_enter(&vmp->vm_lock); + int i = vmem_canalloc(vmp, size); + mutex_exit(&vmp->vm_lock); + return (i); +} + +int +vmem_canalloc_atomic(vmem_t *vmp, size_t size) +{ + int hb; + int flist = 0; + + ulong_t freemap = + __c11_atomic_load((_Atomic ulong_t *)&vmp->vm_freemap, + __ATOMIC_SEQ_CST); + + if (ISP2(size)) + flist = lowbit(P2ALIGN(freemap, size)); + else if ((hb = highbit(size)) < VMEM_FREELISTS) + flist = lowbit(P2ALIGN(freemap, 1ULL << hb)); + + return (flist); +} + +uint64_t +spl_vmem_xnu_useful_bytes_free(void) +{ + extern _Atomic uint32_t spl_vm_pages_reclaimed; + extern _Atomic uint32_t spl_vm_pages_wanted; + extern _Atomic uint32_t spl_vm_pressure_level; + + /* carve out a small reserve for unconditional allocs */ + const uint64_t reserve = total_memory >> 9ULL; + const uint64_t total_minus_reserve = total_memory - reserve; + + /* + * pages are wanted *and* we are in our reserve area, + * so we report only one page of "usable" memory. + * + * if we are below the reserve, return the amount left + */ + + if (spl_vm_pages_wanted > 0) { + if (segkmem_total_mem_allocated >= total_minus_reserve) + return (PAGE_SIZE * MAX(spl_vm_pages_reclaimed, 1)); + else + return (total_minus_reserve - + (segkmem_total_mem_allocated + + PAGE_SIZE * spl_vm_pages_reclaimed)); + } + + /* + * If there is pressure, and we are in the reserve area, + * then there is no "usable" memory, unless we have reclaimed + * some pages. + * + * beware of large magic guard values, + * the pressure enum only goes to 4. + */ + + if (spl_vm_pressure_level > 0 && + spl_vm_pressure_level < 100) { + if (spl_vm_pages_reclaimed > 0) + return (PAGE_SIZE * spl_vm_pages_reclaimed); + else if (segkmem_total_mem_allocated < total_minus_reserve) + return (PAGE_SIZE); + else + return (0); + } + + /* + * No pressure: return non-reserved bytes not allocated. + * The reserve may be needed for VM_NOWAIT and VM_PANIC flags. + */ + + return (total_minus_reserve - segkmem_total_mem_allocated); +} + +uint64_t +vmem_xnu_useful_bytes_free(void) +{ + return (spl_vmem_xnu_useful_bytes_free()); +} + + +static inline void * +spl_vmem_malloc_unconditionally_unlocked(size_t size) +{ + extern void *osif_malloc(uint64_t); + atomic_inc_64(&spl_vmem_unconditional_allocs); + atomic_add_64(&spl_vmem_unconditional_alloc_bytes, size); + return (osif_malloc(size)); +} + +/* + * Allocate size bytes at offset phase from an align boundary such that the + * resulting segment [addr, addr + size) is a subset of [minaddr, maxaddr) + * that does not straddle a nocross-aligned boundary. + */ +inline void * +vmem_xalloc(vmem_t *vmp, size_t size, size_t align_arg, size_t phase, + size_t nocross, void *minaddr, void *maxaddr, int vmflag) +{ + vmem_seg_t *vsp; + vmem_seg_t *vbest = NULL; + uintptr_t addr = 0, taddr, start, end; + uintptr_t align = (align_arg != 0) ? align_arg : vmp->vm_quantum; + void *vaddr, *xvaddr = NULL; + size_t xsize = 0; + int hb, flist, resv; + uint32_t mtbf; + + if ((align | phase | nocross) & (vmp->vm_quantum - 1)) + panic("vmem_xalloc(%p, %lu, %lu, %lu, %lu, %p, %p, %x): " + "parameters not vm_quantum aligned", + (void *)vmp, size, align_arg, phase, nocross, + minaddr, maxaddr, vmflag); + + if (nocross != 0 && + (align > nocross || P2ROUNDUP(phase + size, align) > nocross)) + panic("vmem_xalloc(%p, %lu, %lu, %lu, %lu, %p, %p, %x): " + "overconstrained allocation", + (void *)vmp, size, align_arg, phase, nocross, + minaddr, maxaddr, vmflag); + + if (phase >= align || (align & (align - 1)) != 0 || + (nocross & (nocross - 1)) != 0) + panic("vmem_xalloc(%p, %lu, %lu, %lu, %lu, %p, %p, %x): " + "parameters inconsistent or invalid", + (void *)vmp, size, align_arg, phase, nocross, + minaddr, maxaddr, vmflag); + + if ((mtbf = vmem_mtbf | vmp->vm_mtbf) != 0 && gethrtime() % mtbf == 0 && + (vmflag & (VM_NOSLEEP | VM_PANIC)) == VM_NOSLEEP) + return (NULL); + + mutex_enter(&vmp->vm_lock); + for (;;) { + if (vmp->vm_nsegfree < VMEM_MINFREE && + !vmem_populate(vmp, vmflag)) + break; +do_alloc: + /* + * highbit() returns the highest bit + 1, which is exactly + * what we want: we want to search the first freelist whose + * members are *definitely* large enough to satisfy our + * allocation. However, there are certain cases in which we + * want to look at the next-smallest freelist (which *might* + * be able to satisfy the allocation): + * + * (1) The size is exactly a power of 2, in which case + * the smaller freelist is always big enough; + * + * (2) All other freelists are empty; + * + * (3) We're in the highest possible freelist, which is + * always empty (e.g. the 4GB freelist on 32-bit systems); + * + * (4) We're doing a best-fit or first-fit allocation. + */ + if ((size & (size - 1)) == 0) { + flist = lowbit(P2ALIGN(vmp->vm_freemap, size)); + } else { + hb = highbit(size); + if ((vmp->vm_freemap >> hb) == 0 || + hb == VMEM_FREELISTS || + (vmflag & (VM_BESTFIT | VM_FIRSTFIT))) + hb--; + flist = lowbit(P2ALIGN(vmp->vm_freemap, 1UL << hb)); + } + + for (vbest = NULL, vsp = (flist == 0) ? NULL : + vmp->vm_freelist[flist - 1].vs_knext; + vsp != NULL; vsp = vsp->vs_knext) { + atomic_inc_64(&vmp->vm_kstat.vk_search.value.ui64); + if (vsp->vs_start == 0) { + /* + * We're moving up to a larger freelist, + * so if we've already found a candidate, + * the fit can't possibly get any better. + */ + if (vbest != NULL) + break; + /* + * Find the next non-empty freelist. + */ + flist = lowbit(P2ALIGN(vmp->vm_freemap, + VS_SIZE(vsp))); + if (flist-- == 0) + break; + vsp = (vmem_seg_t *)&vmp->vm_freelist[flist]; + ASSERT(vsp->vs_knext->vs_type == VMEM_FREE); + continue; + } + if (vsp->vs_end - 1 < (uintptr_t)minaddr) + continue; + if (vsp->vs_start > (uintptr_t)maxaddr - 1) + continue; + start = MAX(vsp->vs_start, (uintptr_t)minaddr); + end = MIN(vsp->vs_end - 1, (uintptr_t)maxaddr - 1) + 1; + taddr = P2PHASEUP(start, align, phase); + if (P2BOUNDARY(taddr, size, nocross)) + taddr += + P2ROUNDUP(P2NPHASE(taddr, nocross), align); + if ((taddr - start) + size > end - start || + (vbest != NULL && VS_SIZE(vsp) >= VS_SIZE(vbest))) + continue; + vbest = vsp; + addr = taddr; + if (!(vmflag & VM_BESTFIT) || VS_SIZE(vbest) == size) + break; + } + if (vbest != NULL) + break; + ASSERT(xvaddr == NULL); + if (size == 0) + panic("vmem_xalloc(): size == 0"); + if (vmp->vm_source_alloc != NULL && nocross == 0 && + minaddr == NULL && maxaddr == NULL) { + size_t aneeded, asize; + size_t aquantum = MAX(vmp->vm_quantum, + vmp->vm_source->vm_quantum); + size_t aphase = phase; + if ((align > aquantum) && + !(vmp->vm_cflags & VMC_XALIGN)) { + aphase = (P2PHASE(phase, aquantum) != 0) ? + align - vmp->vm_quantum : align - aquantum; + ASSERT(aphase >= phase); + } + aneeded = MAX(size + aphase, vmp->vm_min_import); + asize = P2ROUNDUP(aneeded, aquantum); + + if (asize < size) { + /* + * The rounding induced overflow; return NULL + * if we are permitted to fail the allocation + * (and explicitly panic if we aren't). + */ + if ((vmflag & VM_NOSLEEP) && + !(vmflag & VM_PANIC)) { + mutex_exit(&vmp->vm_lock); + return (NULL); + } + + panic("vmem_xalloc(): size overflow"); + } + + /* + * Determine how many segment structures we'll consume. + * The calculation must be precise because if we're + * here on behalf of vmem_populate(), we are taking + * segments from a very limited reserve. + */ + if (size == asize && !(vmp->vm_cflags & VMC_XALLOC)) + resv = VMEM_SEGS_PER_SPAN_CREATE + + VMEM_SEGS_PER_EXACT_ALLOC; + else if (phase == 0 && + align <= vmp->vm_source->vm_quantum) + resv = VMEM_SEGS_PER_SPAN_CREATE + + VMEM_SEGS_PER_LEFT_ALLOC; + else + resv = VMEM_SEGS_PER_ALLOC_MAX; + + ASSERT(vmp->vm_nsegfree >= resv); + vmp->vm_nsegfree -= resv; /* reserve our segs */ + mutex_exit(&vmp->vm_lock); + if (vmp->vm_cflags & VMC_XALLOC) { + ASSERTV(size_t oasize = asize); + vaddr = ((vmem_ximport_t *) + vmp->vm_source_alloc)(vmp->vm_source, + &asize, align, vmflag & VM_KMFLAGS); + ASSERT(asize >= oasize); + ASSERT(P2PHASE(asize, + vmp->vm_source->vm_quantum) == 0); + ASSERT(!(vmp->vm_cflags & VMC_XALIGN) || + IS_P2ALIGNED(vaddr, align)); + } else { + atomic_inc_64( + &vmp->vm_kstat.vk_parent_alloc.value.ui64); + vaddr = vmp->vm_source_alloc(vmp->vm_source, + asize, vmflag & (VM_KMFLAGS | VM_NEXTFIT)); + } + mutex_enter(&vmp->vm_lock); + vmp->vm_nsegfree += resv; /* claim reservation */ + aneeded = size + align - vmp->vm_quantum; + aneeded = P2ROUNDUP(aneeded, vmp->vm_quantum); + if (vaddr != NULL) { + /* + * Since we dropped the vmem lock while + * calling the import function, other + * threads could have imported space + * and made our import unnecessary. In + * order to save space, we return + * excess imports immediately. + */ + // but if there are threads waiting below, + // do not return the excess import, rather + // wake those threads up so they can use it. + if (asize > aneeded && + vmp->vm_source_free != NULL && + vmp->vm_kstat.vk_threads_waiting.value.ui64 + == 0 && vmem_canalloc(vmp, aneeded)) { + ASSERT(resv >= + VMEM_SEGS_PER_MIDDLE_ALLOC); + xvaddr = vaddr; + xsize = asize; + goto do_alloc; + } else if ( + vmp->vm_kstat.vk_threads_waiting.value.ui64 + > 0) { + vmp->vm_kstat.vk_excess.value.ui64++; + cv_broadcast(&vmp->vm_cv); + } + vbest = vmem_span_create(vmp, vaddr, asize, 1); + addr = P2PHASEUP(vbest->vs_start, align, phase); + break; + } else if (vmem_canalloc(vmp, aneeded)) { + /* + * Our import failed, but another thread + * added sufficient free memory to the arena + * to satisfy our request. Go back and + * grab it. + */ + ASSERT(resv >= VMEM_SEGS_PER_MIDDLE_ALLOC); + goto do_alloc; + } + } + + /* + * If the requestor chooses to fail the allocation attempt + * rather than reap wait and retry - get out of the loop. + */ + if (vmflag & VM_ABORT) + break; + mutex_exit(&vmp->vm_lock); + +#if 0 + if (vmp->vm_cflags & VMC_IDENTIFIER) + kmem_reap_idspace(); + else + kmem_reap(); +#endif + + mutex_enter(&vmp->vm_lock); + if (vmflag & VM_NOSLEEP) + break; + atomic_inc_64(&vmp->vm_kstat.vk_wait.value.ui64); + atomic_inc_64(&vmp->vm_kstat.vk_threads_waiting.value.ui64); + atomic_inc_64(&spl_vmem_threads_waiting); + if (spl_vmem_threads_waiting > 0) { + dprintf("SPL: %s: vmem waiting for %lu sized alloc " + "for %s, waiting threads %llu, total threads " + "waiting = %llu\n", + __func__, size, vmp->vm_name, + vmp->vm_kstat.vk_threads_waiting.value.ui64, + spl_vmem_threads_waiting); + extern int64_t spl_free_set_and_wait_pressure(int64_t, + boolean_t, clock_t); + extern int64_t spl_free_manual_pressure_wrapper(void); + mutex_exit(&vmp->vm_lock); + // release other waiting threads + spl_free_set_pressure(0); + int64_t target_pressure = size * + spl_vmem_threads_waiting; + int64_t delivered_pressure = + spl_free_set_and_wait_pressure(target_pressure, + TRUE, USEC2NSEC(500)); + dprintf("SPL: %s: pressure %lld targeted, %lld " + "delivered\n", __func__, target_pressure, + delivered_pressure); + mutex_enter(&vmp->vm_lock); + } + cv_wait(&vmp->vm_cv, &vmp->vm_lock); + atomic_dec_64(&spl_vmem_threads_waiting); + atomic_dec_64(&vmp->vm_kstat.vk_threads_waiting.value.ui64); + } + if (vbest != NULL) { + ASSERT(vbest->vs_type == VMEM_FREE); + ASSERT(vbest->vs_knext != vbest); + /* re-position to end of buffer */ + if (vmflag & VM_ENDALLOC) { + addr += ((vbest->vs_end - (addr + size)) / align) * + align; + } + (void) vmem_seg_alloc(vmp, vbest, addr, size); + mutex_exit(&vmp->vm_lock); + if (xvaddr) { + atomic_inc_64(&vmp->vm_kstat.vk_parent_free.value.ui64); + vmp->vm_source_free(vmp->vm_source, xvaddr, xsize); + } + ASSERT(P2PHASE(addr, align) == phase); + ASSERT(!P2BOUNDARY(addr, size, nocross)); + ASSERT(addr >= (uintptr_t)minaddr); + ASSERT(addr + size - 1 <= (uintptr_t)maxaddr - 1); + return ((void *)addr); + } + if (0 == (vmflag & VM_NO_VBA)) { + vmp->vm_kstat.vk_fail.value.ui64++; + } + mutex_exit(&vmp->vm_lock); + if (vmflag & VM_PANIC) + panic("vmem_xalloc(%p, %lu, %lu, %lu, %lu, %p, %p, %x): " + "cannot satisfy mandatory allocation", + (void *)vmp, size, align_arg, phase, nocross, + minaddr, maxaddr, vmflag); + ASSERT(xvaddr == NULL); + return (NULL); +} + +/* + * Free the segment [vaddr, vaddr + size), where vaddr was a constrained + * allocation. vmem_xalloc() and vmem_xfree() must always be paired because + * both routines bypass the quantum caches. + */ +void +vmem_xfree(vmem_t *vmp, const void *vaddr, size_t size) +{ + vmem_seg_t *vsp, *vnext, *vprev; + + mutex_enter(&vmp->vm_lock); + + vsp = vmem_hash_delete(vmp, (uintptr_t)vaddr, size); + vsp->vs_end = P2ROUNDUP(vsp->vs_end, vmp->vm_quantum); + + /* + * Attempt to coalesce with the next segment. + */ + vnext = vsp->vs_anext; + if (vnext->vs_type == VMEM_FREE) { + ASSERT(vsp->vs_end == vnext->vs_start); + vmem_freelist_delete(vmp, vnext); + vsp->vs_end = vnext->vs_end; + vmem_seg_destroy(vmp, vnext); + } + + /* + * Attempt to coalesce with the previous segment. + */ + vprev = vsp->vs_aprev; + if (vprev->vs_type == VMEM_FREE) { + ASSERT(vprev->vs_end == vsp->vs_start); + vmem_freelist_delete(vmp, vprev); + vprev->vs_end = vsp->vs_end; + vmem_seg_destroy(vmp, vsp); + vsp = vprev; + } + + /* + * If the entire span is free, return it to the source. + */ + if (vsp->vs_aprev->vs_import && vmp->vm_source_free != NULL && + vsp->vs_aprev->vs_type == VMEM_SPAN && + vsp->vs_anext->vs_type == VMEM_SPAN) { + vaddr = (void *)vsp->vs_start; + size = VS_SIZE(vsp); + ASSERT(size == VS_SIZE(vsp->vs_aprev)); + vmem_span_destroy(vmp, vsp); + vmp->vm_kstat.vk_parent_free.value.ui64++; + mutex_exit(&vmp->vm_lock); + vmp->vm_source_free(vmp->vm_source, + __DECONST(void *, vaddr), size); + } else { + vmem_freelist_insert(vmp, vsp); + mutex_exit(&vmp->vm_lock); + } +} + +/* + * vmem_alloc_impl() and auxiliary functions : + * + * Allocate size bytes from arena vmp. Returns the allocated address + * on success, NULL on failure. vmflag specifies VM_SLEEP or VM_NOSLEEP, + * and may also specify best-fit, first-fit, or next-fit allocation policy + * instead of the default instant-fit policy. VM_SLEEP allocations are + * guaranteed to succeed. + */ + +/* + * If there is less space on the kernel stack than + * (dynamically tunable) spl_split_stack_below + * then perform the vmem_alloc in the thread_call + * function. Don't set it to 16384, because then it + * continuously triggers, and we hang. + */ +unsigned long spl_split_stack_below = 8192; + +/* kstat tracking the global minimum free stack space */ +_Atomic unsigned int spl_lowest_alloc_stack_remaining = UINT_MAX; + +/* forward decls */ +static inline void *wrapped_vmem_alloc_impl(vmem_t *, size_t, int); +static void *vmem_alloc_in_worker_thread(vmem_t *, size_t, int); + +/* + * unwrapped vmem_alloc_impl() : + * Examine stack remaining; if it is less than our split stack below + * threshold, or (for code coverage early near kext load time) is less than + * the lowest we have seen call out to a worker thread that will + * perform the wrapped_vmem_alloc_impl() and update stat counters. + */ +void * +vmem_alloc_impl(vmem_t *vmp, size_t size, int vmflag) +{ + const vm_offset_t r = OSKernelStackRemaining(); + + if (vmp->vm_kstat.vk_lowest_stack.value.ui64 == 0) { + vmp->vm_kstat.vk_lowest_stack.value.ui64 = r; + } else if (vmp->vm_kstat.vk_lowest_stack.value.ui64 > r) { + vmp->vm_kstat.vk_lowest_stack.value.ui64 = r; + } + + if (vmem_is_populator()) { + /* + * Current thread holds one of the vmem locks and the worker + * thread invoked in vmem_alloc_in_worker_thread() would + * therefore deadlock. vmem_populate on a vmem cache is an + * early (and rare) operation and typically does descend below + * the vmem source. + */ + return (wrapped_vmem_alloc_impl(vmp, size, vmflag)); + } + + if (r < spl_split_stack_below) { + return (vmem_alloc_in_worker_thread(vmp, size, vmflag)); + } + + return (wrapped_vmem_alloc_impl(vmp, size, vmflag)); +} + +/* + * Executes a wrapped_vmem_alloc_impl() in a kernel worker thread, which + * will start with an essentially empty stack. The stack above the + * immediate client of the vmem_alloc_impl() that + * has thread_enter1()-ed this function is already over a depth threshold. + */ +void +vmem_alloc_update_lowest_cb(thread_call_param_t param0, + thread_call_param_t param1) +{ + /* we enter here with our caller holding vm_stack_lock */ + + spl_data_barrier(); + + /* param 0 is a vmp, set in vmem_create() */ + + vmem_t *vmp = (vmem_t *)param0; + + /* + * several cb_params_t members are _Atomic, + * so the compiler will generate the relevant + * synchronization instructions + */ + cb_params_t *cbp = &vmp->vm_cb; + + VERIFY3U(vmp->vm_cb_busy, ==, B_TRUE); + + VERIFY3U(cbp->in_child, ==, B_FALSE); + + /* tell the caller we are live */ + cbp->in_child = B_TRUE; + + /* are we ever here after pending? */ + ASSERT0(cbp->already_pending); + + atomic_inc_64(&vmp->vm_kstat.vk_async_stack_calls.value.ui64); + + cbp->r_alloc = wrapped_vmem_alloc_impl(vmp, + cbp->size, cbp->vmflag); + + ASSERT3P(cbp->r_alloc, !=, NULL); + + /* indicate that we are done and wait for our caller */ + cbp->c_done = B_TRUE; + + /* + * from this point we cannot use param1, vmp, or cbp + * except to signal that we are done + */ + + mutex_enter(&vmp->vm_stack_lock); + cv_signal(&vmp->vm_stack_cv); + mutex_exit(&vmp->vm_stack_lock); +} + +/* + * Set up parameters and thread_enter1() to send them to a worker thread + * executing vmem_alloc_update_lowest_cb(). Wait for the worker thread + * to set c_done to nonzero. + */ +void * +vmem_alloc_in_worker_thread(vmem_t *vmp, size_t size, int vmflag) +{ + const vm_offset_t sr = OSKernelStackRemaining(); + + if (sr < spl_lowest_alloc_stack_remaining) + spl_lowest_alloc_stack_remaining = sr; + + /* + * Because we have to give up the mutex below, + * gate other threads with an atomic compare-exchange. + * + * We open the gate at the end of this function. + * + * Here and in the worker function we mutate cb_params, which is part + * of the vmem_t structure, so we must let threads for this particular + * arena proceed one at a time. + * + * The worker thread for this arena similarly mutates cb_params. + * + * Finally, invoking the worker thread for this arena multiple times + * in a row can cancel invocations which have not yet started. + */ + + for (unsigned int i = 1; ; i++) { + /* + * if busy == f then busy = true and + * return result is true; otherwise result is + * false and f = true + */ + bool f = false; + if (!__c11_atomic_compare_exchange_strong( + &vmp->vm_cb_busy, &f, true, + __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) { + /* delay and loop */ + if ((i % 5) == 0) + IOSleepWithLeeway(1, 1); + else + IODelay(1); // us + continue; + } else { + VERIFY0(!vmp->vm_cb_busy); + break; + } + } + + mutex_enter(&vmp->vm_stack_lock); + vmp->vm_cb.size = size; + vmp->vm_cb.vmflag = vmflag; + + vmp->vm_cb.c_done = B_FALSE; + vmp->vm_cb.r_alloc = NULL; + vmp->vm_cb.in_child = B_FALSE; + vmp->vm_cb.already_pending = B_FALSE; + + /* + * send a pointer to our parameter struct to the worker thread's + * vmem_alloc_update_lowest_cb()'s param1. + * + * it begins while we hold vmp->vm_stack_lock and + * continues until the end of the function where it + * does a mutex_enter(&vmp->vm_stack_lock) and then + * signals vmp->vm_stack_cv. + */ + spl_data_barrier(); + boolean_t tc_already_pending = + thread_call_enter1(vmp->vm_stack_call_thread, NULL); + + /* in DEBUG, bleat if worker thread was already working */ + ASSERT0(tc_already_pending); + + vmp->vm_cb.already_pending = tc_already_pending; + + /* + * Wait for a cv_signal from our worker thread if it + * has not already completed. (If it has, we can just + * skip the loop and mutex_exit()). + * + * "Impossible" things, left over from before the + * cb_busy flag, which limits concurrency: + * If the worker has died we will time out and panic. + * If we get a spurious signal, it may have been + * for someone else. + * Less impossibly: if we lost the cv_signal from + * the worker, log that and carry on. + * + * N.B.: If there is a preexisting cv_signal() on the condvar, + * cv_timedwait() will return basically immediately; threads will not + * be put to sleep unless there is [a] no other waiter and [b] no + * pre-posted "wakeup_one()" signal. + */ + + for (unsigned int i = 0; ; i++) { + /* + * cv_timedwait: give up vmp->stack_lock, which at the bottom + * of vmem_alloc_update_lowest_cb() our worker thread will + * mutex_enter() and then cv_signal() vmp->stack_cv. + * + * The timeout of one second is an eternity, but will be + * noticed and won't over-spam the logs. If the worker thread + * has not cv_signal()ed us by the deadline, something has + * gone pretty wrong. Check to see if c_done flag is set by + * the worker; if that's set but the expected cv_signal() has + * been lost, that requires investigation. + * + * @e still hold the lock after a cv_timedwait timeout. + * + */ + const clock_t start_time = ddi_get_lbolt(); + const clock_t deadline = start_time + SEC_TO_TICK(1); + int retval = cv_timedwait(&vmp->vm_stack_cv, + &vmp->vm_stack_lock, deadline); + if (retval == -1) { + if (vmp->vm_cb.c_done != B_TRUE) { + // never seen this + printf("SPL: %s:%d (iter %d)" + " timed out (start: %lu now: %llu)" + " waiting for" + " child callback, in_child: %d, arena '%s'", + __func__, __LINE__, i, + start_time, ddi_get_lbolt(), + vmp->vm_cb.in_child, vmp->vm_name); + } else { + // never seen this + printf("SPL: %s:%d (iter %d) timedout," + " (start: %lu now: %llu), " + " lost cv_signal! (arena %s)\n", + __func__, __LINE__, i, + start_time, ddi_get_lbolt(), + vmp->vm_name); + } + } else if (retval == 1 && vmp->vm_cb.c_done != B_TRUE) { + ASSERT0(!vmp->vm_cb.in_child); + /* this happens occasionally */ + const clock_t now = ddi_get_lbolt(); + const clock_t elapsed_time = now - start_time; + clock_t abs_delta; + /* -time until deadline, +time after deadline */ + bool neg_sign; + if (now >= deadline) { + neg_sign = false; + abs_delta = now - deadline; + } else { + neg_sign = true; + abs_delta = deadline - now; + } + printf("SPL: %s:%d (iter %d)" + " in_child %d c_done %d" + " size %lu flags %x," + " mtx_waiters %llu, mtx_sleepers %llu," + " start_time %lu, elapsed %lu," + " deadline %lu, now %lu," + " delta: %s%lu," + " cv_timedwait returned unexpectedly, arena '%s'\n", + __func__, __LINE__, i, + vmp->vm_cb.in_child, vmp->vm_cb.c_done, + vmp->vm_cb.size, vmp->vm_cb.vmflag, + vmp->vm_stack_lock.m_waiters, + vmp->vm_stack_lock.m_sleepers, + start_time, elapsed_time, deadline, now, + (neg_sign) ? "-" : "", abs_delta, + vmp->vm_name); + } else if (retval == 1) { + /* c_done is B_TRUE */ + break; + } else { + printf("SPL: %s:%d (iter %d):" + " EINTR or ERESTART in spl_cv_timedwait()" + " (start: %lu, now: %llu)" + " arena: %s, in_child %d, c_done %d." + " Continuing.\n", + __func__, __LINE__, i, + start_time, zfs_lbolt(), + vmp->vm_name, + vmp->vm_cb.in_child, + vmp->vm_cb.c_done); + } + VERIFY(mutex_owned(&vmp->vm_stack_lock)); + } + + ASSERT3P(vmp->vm_cb.r_alloc, !=, NULL); + + /* + * save this where it can't be stomped by + * a thread waiting for cb_busy to go zero + */ + void *allocation = vmp->vm_cb.r_alloc; + + /* give up busy flag */ + VERIFY0(!vmp->vm_cb_busy); + vmp->vm_cb_busy = false; + + mutex_exit(&vmp->vm_stack_lock); + + return (allocation); +} + +/* + * The guts of vmem_alloc_impl() + */ +static inline void * +wrapped_vmem_alloc_impl(vmem_t *vmp, size_t size, int vmflag) +{ + vmem_seg_t *vsp; + uintptr_t addr; + int hb; + int flist = 0; + uint32_t mtbf; + + if (size - 1 < vmp->vm_qcache_max) + return (kmem_cache_alloc(vmp->vm_qcache[(size - 1) >> + vmp->vm_qshift], vmflag & VM_KMFLAGS)); + + if ((mtbf = vmem_mtbf | vmp->vm_mtbf) != 0 && gethrtime() % mtbf == 0 && + (vmflag & (VM_NOSLEEP | VM_PANIC)) == VM_NOSLEEP) + return (NULL); + + if (vmflag & VM_NEXTFIT) + return (vmem_nextfit_alloc(vmp, size, vmflag)); + + if (vmflag & (VM_BESTFIT | VM_FIRSTFIT)) + return (vmem_xalloc(vmp, size, vmp->vm_quantum, 0, 0, + NULL, NULL, vmflag)); + if (vmp->vm_cflags & VM_NEXTFIT) + return (vmem_nextfit_alloc(vmp, size, vmflag)); + + /* + * Unconstrained instant-fit allocation from the segment list. + */ + mutex_enter(&vmp->vm_lock); + + if (vmp->vm_nsegfree >= VMEM_MINFREE || vmem_populate(vmp, vmflag)) { + if ((size & (size - 1)) == 0) + flist = lowbit(P2ALIGN(vmp->vm_freemap, size)); + else if ((hb = highbit(size)) < VMEM_FREELISTS) + flist = lowbit(P2ALIGN(vmp->vm_freemap, 1UL << hb)); + } + + if (flist-- == 0) { + mutex_exit(&vmp->vm_lock); + return (vmem_xalloc(vmp, size, vmp->vm_quantum, + 0, 0, NULL, NULL, vmflag)); + } + + ASSERT(size <= (1UL << flist)); + vsp = vmp->vm_freelist[flist].vs_knext; + addr = vsp->vs_start; + if (vmflag & VM_ENDALLOC) { + addr += vsp->vs_end - (addr + size); + } + (void) vmem_seg_alloc(vmp, vsp, addr, size); + mutex_exit(&vmp->vm_lock); + return ((void *)addr); +} + +/* + * Free the segment [vaddr, vaddr + size). + */ +void +vmem_free_impl(vmem_t *vmp, const void *vaddr, size_t size) +{ + if (size - 1 < vmp->vm_qcache_max) + kmem_cache_free(vmp->vm_qcache[(size - 1) >> vmp->vm_qshift], + vaddr); + else + vmem_xfree(vmp, vaddr, size); +} + +/* + * Determine whether arena vmp contains the segment [vaddr, vaddr + size). + */ +int +vmem_contains(vmem_t *vmp, void *vaddr, size_t size) +{ + uintptr_t start = (uintptr_t)vaddr; + uintptr_t end = start + size; + vmem_seg_t *vsp; + vmem_seg_t *seg0 = &vmp->vm_seg0; + + mutex_enter(&vmp->vm_lock); + vmp->vm_kstat.vk_contains.value.ui64++; + for (vsp = seg0->vs_knext; vsp != seg0; vsp = vsp->vs_knext) { + vmp->vm_kstat.vk_contains_search.value.ui64++; + ASSERT(vsp->vs_type == VMEM_SPAN); + if (start >= vsp->vs_start && end - 1 <= vsp->vs_end - 1) + break; + } + mutex_exit(&vmp->vm_lock); + return (vsp != seg0); +} + +/* + * Add the span [vaddr, vaddr + size) to arena vmp. + */ +void * +vmem_add(vmem_t *vmp, void *vaddr, size_t size, int vmflag) +{ + if (vaddr == NULL || size == 0) + panic("vmem_add(%p, %p, %lu): bad arguments", + (void *)vmp, vaddr, size); + + ASSERT(!vmem_contains(vmp, vaddr, size)); + + mutex_enter(&vmp->vm_lock); + if (vmem_populate(vmp, vmflag)) + (void) vmem_span_create(vmp, vaddr, size, 0); + else + vaddr = NULL; + mutex_exit(&vmp->vm_lock); + return (vaddr); +} + +/* + * Walk the vmp arena, applying func to each segment matching typemask. + * If VMEM_REENTRANT is specified, the arena lock is dropped across each + * call to func(); otherwise, it is held for the duration of vmem_walk() + * to ensure a consistent snapshot. Note that VMEM_REENTRANT callbacks + * are *not* necessarily consistent, so they may only be used when a hint + * is adequate. + */ +void +vmem_walk(vmem_t *vmp, int typemask, + void (*func)(void *, void *, size_t), void *arg) +{ + vmem_seg_t *vsp; + vmem_seg_t *seg0 = &vmp->vm_seg0; + vmem_seg_t walker; + + if (typemask & VMEM_WALKER) + return; + + memset(&walker, 0, sizeof (walker)); + walker.vs_type = VMEM_WALKER; + + mutex_enter(&vmp->vm_lock); + VMEM_INSERT(seg0, &walker, a); + for (vsp = seg0->vs_anext; vsp != seg0; vsp = vsp->vs_anext) { + if (vsp->vs_type & typemask) { + void *start = (void *)vsp->vs_start; + size_t size = VS_SIZE(vsp); + if (typemask & VMEM_REENTRANT) { + vmem_advance(vmp, &walker, vsp); + mutex_exit(&vmp->vm_lock); + func(arg, start, size); + mutex_enter(&vmp->vm_lock); + vsp = &walker; + } else { + func(arg, start, size); + } + } + } + vmem_advance(vmp, &walker, NULL); + mutex_exit(&vmp->vm_lock); +} + +/* + * Return the total amount of memory whose type matches typemask. Thus: + * + * typemask VMEM_ALLOC yields total memory allocated (in use). + * typemask VMEM_FREE yields total memory free (available). + * typemask (VMEM_ALLOC | VMEM_FREE) yields total arena size. + */ +size_t +vmem_size(vmem_t *vmp, int typemask) +{ + int64_t size = 0; + + if (typemask & VMEM_ALLOC) + size += (int64_t)vmp->vm_kstat.vk_mem_inuse.value.ui64; + if (typemask & VMEM_FREE) + size += (int64_t)vmp->vm_kstat.vk_mem_total.value.ui64 - + (int64_t)vmp->vm_kstat.vk_mem_inuse.value.ui64; + if (size < 0) + size = 0; + + return ((size_t)size); +} + +size_t +vmem_size_locked(vmem_t *vmp, int typemask) +{ + boolean_t m = (mutex_owner(&vmp->vm_lock) == curthread); + + if (!m) + mutex_enter(&vmp->vm_lock); + size_t s = vmem_size(vmp, typemask); + if (!m) + mutex_exit(&vmp->vm_lock); + return (s); +} + +size_t +vmem_size_semi_atomic(vmem_t *vmp, int typemask) +{ + int64_t size = 0; + uint64_t inuse = 0; + uint64_t total = 0; + + __sync_swap(&total, vmp->vm_kstat.vk_mem_total.value.ui64); + __sync_swap(&inuse, vmp->vm_kstat.vk_mem_inuse.value.ui64); + + int64_t inuse_signed = (int64_t)inuse; + int64_t total_signed = (int64_t)total; + + if (typemask & VMEM_ALLOC) + size += inuse_signed; + if (typemask & VMEM_FREE) + size += total_signed - inuse_signed; + + if (size < 0) + size = 0; + + return ((size_t)size); +} + +size_t +spl_vmem_size(vmem_t *vmp, int typemask) +{ + return (vmem_size_locked(vmp, typemask)); +} + +/* + * Create an arena called name whose initial span is [base, base + size). + * The arena's natural unit of currency is quantum, so vmem_alloc_impl() + * guarantees quantum-aligned results. The arena may import new spans + * by invoking afunc() on source, and may return those spans by invoking + * ffunc() on source. To make small allocations fast and scalable, + * the arena offers high-performance caching for each integer multiple + * of quantum up to qcache_max. + */ +static vmem_t * +vmem_create_common(const char *name, void *base, size_t size, size_t quantum, + void *(*afunc)(vmem_t *, size_t, int), + void (*ffunc)(vmem_t *, const void *, size_t), + vmem_t *source, size_t qcache_max, int vmflag) +{ + int i; + size_t nqcache; + vmem_t *vmp, *cur, **vmpp; + vmem_seg_t *vsp; + vmem_freelist_t *vfp; + uint32_t id = atomic_inc_32_nv(&vmem_id); + + if (vmem_vmem_arena != NULL) { + vmp = vmem_alloc_impl(vmem_vmem_arena, sizeof (vmem_t), + vmflag & VM_KMFLAGS); + } else { + ASSERT(id <= VMEM_INITIAL); + vmp = &vmem0[id - 1]; + } + + /* An identifier arena must inherit from another identifier arena */ + ASSERT(source == NULL || ((source->vm_cflags & VMC_IDENTIFIER) == + (vmflag & VMC_IDENTIFIER))); + + if (vmp == NULL) + return (NULL); + memset(vmp, 0, sizeof (vmem_t)); + + (void) snprintf(vmp->vm_name, VMEM_NAMELEN, "%s", name); + mutex_init(&vmp->vm_lock, NULL, MUTEX_DEFAULT, NULL); + cv_init(&vmp->vm_cv, NULL, CV_DEFAULT, NULL); + vmp->vm_cflags = vmflag; + vmflag &= VM_KMFLAGS; + + hrtime_t hrnow = gethrtime(); + + vmp->vm_createtime = hrnow; + + vmp->vm_quantum = quantum; + vmp->vm_qshift = highbit(quantum) - 1; + nqcache = MIN(qcache_max >> vmp->vm_qshift, VMEM_NQCACHE_MAX); + + for (i = 0; i <= VMEM_FREELISTS; i++) { + vfp = &vmp->vm_freelist[i]; + vfp->vs_end = 1UL << i; + vfp->vs_knext = (vmem_seg_t *)(vfp + 1); + vfp->vs_kprev = (vmem_seg_t *)(vfp - 1); + } + + vmp->vm_freelist[0].vs_kprev = NULL; + vmp->vm_freelist[VMEM_FREELISTS].vs_knext = NULL; + vmp->vm_freelist[VMEM_FREELISTS].vs_end = 0; + vmp->vm_hash_table = vmp->vm_hash0; + vmp->vm_hash_mask = VMEM_HASH_INITIAL - 1; + vmp->vm_hash_shift = highbit(vmp->vm_hash_mask); + + vsp = &vmp->vm_seg0; + vsp->vs_anext = vsp; + vsp->vs_aprev = vsp; + vsp->vs_knext = vsp; + vsp->vs_kprev = vsp; + vsp->vs_type = VMEM_SPAN; + vsp->vs_span_createtime = hrnow; + + vsp = &vmp->vm_rotor; + vsp->vs_type = VMEM_ROTOR; + VMEM_INSERT(&vmp->vm_seg0, vsp, a); + + memcpy(&vmp->vm_kstat, &vmem_kstat_template, sizeof (vmem_kstat_t)); + + vmp->vm_id = id; + if (source != NULL) + vmp->vm_kstat.vk_source_id.value.ui32 = source->vm_id; + vmp->vm_source = source; + vmp->vm_source_alloc = afunc; + vmp->vm_source_free = __DECONST(void *, ffunc); + + /* + * Some arenas (like vmem_metadata and kmem_metadata) cannot + * use quantum caching to lower fragmentation. Instead, we + * increase their imports, giving a similar effect. + */ + if (vmp->vm_cflags & VMC_NO_QCACHE) { + if (qcache_max > VMEM_NQCACHE_MAX && ISP2(qcache_max)) { + vmp->vm_min_import = qcache_max; + } else { + vmp->vm_min_import = + VMEM_QCACHE_SLABSIZE(nqcache << vmp->vm_qshift); + } + nqcache = 0; + } + + if (nqcache != 0) { + ASSERT(!(vmflag & VM_NOSLEEP)); + vmp->vm_qcache_max = nqcache << vmp->vm_qshift; + for (i = 0; i < nqcache; i++) { + char buf[VMEM_NAMELEN + 21]; + (void) snprintf(buf, VMEM_NAMELEN + 20, "%s_%lu", + vmp->vm_name, (i + 1) * quantum); + vmp->vm_qcache[i] = kmem_cache_create(buf, + (i + 1) * quantum, quantum, NULL, NULL, NULL, + NULL, vmp, KMC_QCACHE | KMC_NOTOUCH); + } + } + + if ((vmp->vm_ksp = kstat_create("vmem", vmp->vm_id, vmp->vm_name, + "vmem", KSTAT_TYPE_NAMED, sizeof (vmem_kstat_t) / + sizeof (kstat_named_t), KSTAT_FLAG_VIRTUAL)) != NULL) { + vmp->vm_ksp->ks_data = &vmp->vm_kstat; + kstat_install(vmp->vm_ksp); + } + + mutex_enter(&vmem_list_lock); + vmpp = &vmem_list; + while ((cur = *vmpp) != NULL) + vmpp = &cur->vm_next; + *vmpp = vmp; + mutex_exit(&vmem_list_lock); + + if (vmp->vm_cflags & VMC_POPULATOR) { + ASSERT(vmem_populators < VMEM_INITIAL); + vmem_populator[atomic_inc_32_nv(&vmem_populators) - 1] = vmp; + mutex_enter(&vmp->vm_lock); + (void) vmem_populate(vmp, vmflag | VM_PANIC); + mutex_exit(&vmp->vm_lock); + } + + if ((base || size) && vmem_add(vmp, base, size, vmflag) == NULL) { + vmem_destroy(vmp); + return (NULL); + } + + /* set up thread call */ + vmp->vm_cb_busy = false; + mutex_init(&vmp->vm_stack_lock, "lock for thread call", + MUTEX_DEFAULT, NULL); + cv_init(&vmp->vm_stack_cv, NULL, CV_DEFAULT, NULL); + +#if defined(MAC_OS_X_VERSION_10_13) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_13) + vmp->vm_stack_call_thread = thread_call_allocate_with_options( + (thread_call_func_t)vmem_alloc_update_lowest_cb, + (thread_call_param_t)vmp, + THREAD_CALL_PRIORITY_KERNEL, + 0); +#else + vmp->vm_stack_call_thread = thread_call_allocate( + vmem_alloc_update_lowest_cb, vmp); +#endif + + dprintf("SPL: %s:%d: setup of %s done\n", + __func__, __LINE__, vmp->vm_name); + + return (vmp); +} + +vmem_t * +vmem_xcreate(const char *name, void *base, size_t size, size_t quantum, + vmem_ximport_t *afunc, vmem_free_t *ffunc, vmem_t *source, + size_t qcache_max, int vmflag) +{ + ASSERT(!(vmflag & (VMC_POPULATOR | VMC_XALLOC))); + vmflag &= ~(VMC_POPULATOR | VMC_XALLOC); + + return (vmem_create_common(name, base, size, quantum, + (vmem_alloc_t *)afunc, ffunc, source, qcache_max, + vmflag | VMC_XALLOC)); +} + +vmem_t * +vmem_create(const char *name, void *base, size_t size, size_t quantum, + vmem_alloc_t *afunc, vmem_free_t *ffunc, vmem_t *source, + size_t qcache_max, int vmflag) +{ + ASSERT(!(vmflag & (VMC_XALLOC | VMC_XALIGN))); + vmflag &= ~(VMC_XALLOC | VMC_XALIGN); + + return (vmem_create_common(name, base, size, quantum, + afunc, ffunc, source, qcache_max, vmflag)); +} + +/* + * Destroy arena vmp. + */ +void +vmem_destroy(vmem_t *vmp) +{ + vmem_t *cur, **vmpp; + vmem_seg_t *seg0 = &vmp->vm_seg0; + vmem_seg_t *vsp, *anext; + size_t leaked; + + /* check for possible async stack calls */ + + const boolean_t ret_thread_call_cancel __maybe_unused = + thread_call_cancel(vmp->vm_stack_call_thread); + ASSERT0(ret_thread_call_cancel); + + /* tear down async stack call mechanisms */ + + const boolean_t ret_thread_call_free __maybe_unused = + thread_call_free(vmp->vm_stack_call_thread); + ASSERT0(!ret_thread_call_free); + + mutex_destroy(&vmp->vm_stack_lock); + cv_destroy(&vmp->vm_stack_cv); + + /* + * set vm_nsegfree to zero because vmem_free_span_list + * would have already freed vm_segfree. + */ + vmp->vm_nsegfree = 0; + mutex_enter(&vmem_list_lock); + vmpp = &vmem_list; + while ((cur = *vmpp) != vmp) + vmpp = &cur->vm_next; + *vmpp = vmp->vm_next; + mutex_exit(&vmem_list_lock); + + leaked = vmem_size(vmp, VMEM_ALLOC); + if (leaked != 0) + printf("SPL: vmem_destroy('%s'): leaked %lu %s\n", + vmp->vm_name, leaked, (vmp->vm_cflags & VMC_IDENTIFIER) ? + "identifiers" : "bytes"); + + if (vmp->vm_hash_table != vmp->vm_hash0) + if (vmem_hash_arena != NULL) + vmem_free_impl(vmem_hash_arena, vmp->vm_hash_table, + (vmp->vm_hash_mask + 1) * sizeof (void *)); + + /* + * Give back the segment structures for anything that's left in the + * arena, e.g. the primary spans and their free segments. + */ + VMEM_DELETE(&vmp->vm_rotor, a); + for (vsp = seg0->vs_anext; vsp != seg0; vsp = anext) { + anext = vsp->vs_anext; + vmem_putseg_global(vsp); + } + + while (vmp->vm_nsegfree > 0) + vmem_putseg_global(vmem_getseg(vmp)); + + kstat_delete(vmp->vm_ksp); + + mutex_destroy(&vmp->vm_lock); + cv_destroy(&vmp->vm_cv); + vmem_free_impl(vmem_vmem_arena, vmp, sizeof (vmem_t)); +} + + +/* + * Destroy arena vmp. + */ +void +vmem_destroy_internal(vmem_t *vmp) +{ + vmem_t *cur, **vmpp; + vmem_seg_t *seg0 = &vmp->vm_seg0; + vmem_seg_t *vsp, *anext; + size_t leaked; + + mutex_enter(&vmem_list_lock); + vmpp = &vmem_list; + while ((cur = *vmpp) != vmp) + vmpp = &cur->vm_next; + *vmpp = vmp->vm_next; + mutex_exit(&vmem_list_lock); + + leaked = vmem_size(vmp, VMEM_ALLOC); + if (leaked != 0) + printf("SPL: vmem_destroy('%s'): leaked %lu %s\n", + vmp->vm_name, leaked, (vmp->vm_cflags & VMC_IDENTIFIER) ? + "identifiers" : "bytes"); + + if (vmp->vm_hash_table != vmp->vm_hash0) + if (vmem_hash_arena != NULL) + vmem_free_impl(vmem_hash_arena, vmp->vm_hash_table, + (vmp->vm_hash_mask + 1) * sizeof (void *)); + + /* + * Give back the segment structures for anything that's left in the + * arena, e.g. the primary spans and their free segments. + */ + VMEM_DELETE(&vmp->vm_rotor, a); + for (vsp = seg0->vs_anext; vsp != seg0; vsp = anext) { + anext = vsp->vs_anext; + vmem_putseg_global(vsp); + } + + while (vmp->vm_nsegfree > 0) + vmem_putseg_global(vmem_getseg(vmp)); + + if (!(vmp->vm_cflags & VMC_IDENTIFIER) && + vmem_size(vmp, VMEM_ALLOC) != 0) + printf("SPL: vmem_destroy('%s'): STILL %lu bytes at " + "kstat_delete() time\n", + vmp->vm_name, vmem_size(vmp, VMEM_ALLOC)); + + kstat_delete(vmp->vm_ksp); + + mutex_destroy(&vmp->vm_lock); + cv_destroy(&vmp->vm_cv); + + // Alas, to free, requires access to "vmem_vmem_arena" the very thing + // we release first. + // vmem_free_impl(vmem_vmem_arena, vmp, sizeof (vmem_t)); +} + +/* + * Only shrink vmem hashtable if it is 1<vm_lock. In turn, this leads + * to a panic when doing a vmem_free_impl() on an improperly-recorded segment. + * + * Consequently once we hold vmp->vm_lock we must recalculate new_size and + * compare that with the previously-calculated nolock_new_size. If they do + * not match we must clean up and return rather than attempt to use new_table. + */ +static void +vmem_hash_rescale(vmem_t *vmp) +{ + vmem_seg_t **new_table, *vsp; + + const size_t nolock_nseg = + (size_t)(vmp->vm_kstat.vk_alloc.value.ui64 - + vmp->vm_kstat.vk_free.value.ui64); + + const size_t nolock_new_size = MAX(VMEM_HASH_INITIAL, + 1 << (highbit(3 * nolock_nseg + 4) - 2)); + const size_t nolock_old_size = vmp->vm_hash_mask + 1; + + if ((nolock_old_size >> vmem_rescale_minshift) <= nolock_new_size && + nolock_new_size <= (nolock_old_size << 1)) + return; + + new_table = vmem_alloc_impl(vmem_hash_arena, + nolock_new_size * sizeof (void *), + VM_NOSLEEP); + if (new_table == NULL) + return; + memset(new_table, 0, nolock_new_size * sizeof (void *)); + + mutex_enter(&vmp->vm_lock); + + const size_t nseg = (size_t)(vmp->vm_kstat.vk_alloc.value.ui64 - + vmp->vm_kstat.vk_free.value.ui64); + + const size_t new_size = MAX(VMEM_HASH_INITIAL, + 1 << (highbit(3 * nseg + 4) - 2)); + + if (new_size != nolock_new_size) { + printf("ZFS: SPL: %s:%d:%s:" + " race condition found: %s, %ld, %ld\n", + __FILE__, __LINE__, __func__, + vmp->vm_name, + nolock_new_size, new_size); + mutex_exit(&vmp->vm_lock); + vmem_free_impl(vmem_hash_arena, new_table, + nolock_new_size * sizeof (void *)); + return; + } + + const size_t old_size = vmp->vm_hash_mask + 1; + vmem_seg_t **old_table = vmp->vm_hash_table; + + vmp->vm_hash_mask = new_size - 1; + vmp->vm_hash_table = new_table; + vmp->vm_hash_shift = highbit(vmp->vm_hash_mask); + + for (size_t h = 0; h < old_size; h++) { + vsp = old_table[h]; + while (vsp != NULL) { + uintptr_t addr = vsp->vs_start; + vmem_seg_t *next_vsp = vsp->vs_knext; + vmem_seg_t **hash_bucket = VMEM_HASH(vmp, addr); + vsp->vs_knext = *hash_bucket; + *hash_bucket = vsp; + vsp = next_vsp; + } + } + + mutex_exit(&vmp->vm_lock); + + if (old_table != vmp->vm_hash0) + vmem_free_impl(vmem_hash_arena, old_table, + old_size * sizeof (void *)); +} + +/* + * Perform periodic maintenance on all vmem arenas. + */ + +void +vmem_update(void *dummy) +{ + vmem_t *vmp; + + mutex_enter(&vmem_list_lock); + for (vmp = vmem_list; vmp != NULL; vmp = vmp->vm_next) { + /* + * If threads are waiting for resources, wake them up + * periodically so they can issue another kmem_reap() + * to reclaim resources cached by the slab allocator. + * + * In general it is good practice to take the associated + * lock before calling cv_broadcast(). Here it gives any + * waiters a good shot at the lock that may be (re-)taken + * by this thread in vmem_hash_rescale() function. + */ + mutex_enter(&vmp->vm_lock); + cv_broadcast(&vmp->vm_cv); + mutex_exit(&vmp->vm_lock); + + /* + * Rescale the hash table to keep the hash chains short. + */ + vmem_hash_rescale(vmp); + } + mutex_exit(&vmem_list_lock); + + (void) bsd_timeout(vmem_update, dummy, &vmem_update_interval); +} + +void +vmem_qcache_reap(vmem_t *vmp) +{ + int i; + + /* + * Reap any quantum caches that may be part of this vmem. + */ + for (i = 0; i < VMEM_NQCACHE_MAX; i++) + if (vmp->vm_qcache[i]) + kmem_cache_reap_now(vmp->vm_qcache[i]); +} + +/* given a size, return the appropriate vmem_bucket_arena[] entry */ + +static inline uint16_t +vmem_bucket_number(size_t size) +{ + // For VMEM_BUCKET_HIBIT == 12, + // vmem_bucket_arena[n] holds allocations from 2^[n+11]+1 to 2^[n+12], + // so for [n] = 0, 2049-4096, for [n]=5 65537-131072, + // for [n]=7 (256k+1)-512k + // set hb: 512k == 19, 256k+1 == 19, 256k == 18, ... + const int hb = highbit(size-1); + + int bucket = hb - VMEM_BUCKET_LOWBIT; + + // very large allocations go into the 16 MiB bucket + if (hb > VMEM_BUCKET_HIBIT) + bucket = VMEM_BUCKET_HIBIT - VMEM_BUCKET_LOWBIT; + + // very small allocations go into the 4 kiB bucket + if (bucket < 0) + bucket = 0; + + return ((int16_t)bucket); +} + +static inline vmem_t * +vmem_bucket_arena_by_size(size_t size) +{ + uint16_t bucket = vmem_bucket_number(size); + + return (vmem_bucket_arena[bucket]); +} + +inline vmem_t * +spl_vmem_bucket_arena_by_size(size_t size) +{ + return (vmem_bucket_arena_by_size(size)); +} + +/* + * We have just freed memory back to macOS so we let any waiters on the + * lowest-level bucket arenas know they have a chance to make progress in + * their hunt for memory from the operating system. We then tell the heap that + * there may be memory freshly imported into the buckets. + * + * This function broadcasts to waiters on the smallest-span buckets first, and + * because of mutex-ordering this biases towards small-allocation kmem caches. + */ +static inline void +vmem_bucket_wake_all_waiters(void) +{ + for (int i = VMEM_BUCKET_LOWBIT; i < VMEM_BUCKET_HIBIT; i++) { + const int bucket = i - VMEM_BUCKET_LOWBIT; + vmem_t *bvmp = vmem_bucket_arena[bucket]; + mutex_enter(&bvmp->vm_lock); + cv_broadcast(&bvmp->vm_cv); + mutex_exit(&bvmp->vm_lock); + } + mutex_enter(&spl_heap_arena->vm_lock); + cv_broadcast(&spl_heap_arena->vm_cv); + mutex_exit(&spl_heap_arena->vm_lock); +} + +static void * +xnu_alloc_throttled(vmem_t *bvmp, size_t size, int vmflag) +{ + static volatile _Atomic uint64_t fail_at = 0; + static volatile _Atomic int16_t success_ct = 0; + + void *p = spl_vmem_malloc_unconditionally_unlocked(size); + + if (p != NULL) { + /* grow fail_at periodically */ + if (success_ct++ >= 128) { + fail_at += size; + success_ct = 0; + } + spl_xat_lastalloc = gethrtime(); + cv_broadcast(&bvmp->vm_cv); + return (p); + } + + success_ct = 0; + fail_at = segkmem_total_mem_allocated - size; + + /* + * adjust dynamic memory cap downwards by 1/32 (~ 3%) of total_memory + * but do not drop below 1/8 of total_memory.. + * + * see also spl-kmem.c:spl_reduce_dynamic_cap(), which is + * triggered by ARC or other clients inquiring about spl_free() + */ + if (spl_enforce_memory_caps != 0 && + (fail_at < spl_dynamic_memory_cap || + spl_dynamic_memory_cap == 0)) { + mutex_enter(&spl_dynamic_memory_cap_lock); + + spl_dynamic_memory_cap_last_downward_adjust = gethrtime(); + const int64_t thresh = total_memory >> 3; + const int64_t below_fail_at = fail_at - (total_memory >> 5); + const int64_t reduced = MAX(below_fail_at, thresh); + + if (spl_dynamic_memory_cap == 0 || + spl_dynamic_memory_cap >= total_memory) { + spl_dynamic_memory_cap = reduced; + atomic_inc_64(&spl_dynamic_memory_cap_reductions); + } else if (thresh > spl_dynamic_memory_cap) { + spl_dynamic_memory_cap = thresh; + atomic_inc_64(&spl_dynamic_memory_cap_hit_floor); + } else { + spl_dynamic_memory_cap = reduced; + atomic_inc_64(&spl_dynamic_memory_cap_reductions); + } + + mutex_exit(&spl_dynamic_memory_cap_lock); + } + + /* wait until used memory falls below failure_at */ + + extern void spl_set_arc_no_grow(int); + spl_set_arc_no_grow(B_TRUE); + spl_free_set_emergency_pressure(total_memory >> 7LL); + atomic_inc_64(&spl_xat_pressured); + if ((vmflag & (VM_NOSLEEP | VM_PANIC | VM_ABORT)) > 0) + return (NULL); + + for (uint64_t loop_for_mem = 1; ; loop_for_mem++) { + // ASSERT3U((loop_for_mem % 10), ==, 0); // 1 second bleat beat + IOSleepWithLeeway(100, 1); /* hope someone frees memory */ + /* only try to allocate if there is memory */ + if (fail_at > segkmem_total_mem_allocated) { + p = spl_vmem_malloc_unconditionally_unlocked(size); + if (p != NULL) + return (p); + } else { + /* abuse existing kstat */ + atomic_inc_64(&spl_xat_sleep); + } + success_ct = 0; + const uint64_t x = segkmem_total_mem_allocated - size; + if (fail_at > x) + fail_at = x; + spl_set_arc_no_grow(B_TRUE); + spl_free_set_emergency_pressure(total_memory >> 7LL); + atomic_inc_64(&spl_xat_pressured); + /* after ten seconds, just return NULL */ + if (loop_for_mem > 100) + return (NULL); + } +} + +static void +xnu_free_throttled(vmem_t *vmp, const void *vaddr, size_t size) +{ + extern void osif_free(const void *, uint64_t); + + osif_free(vaddr, size); + spl_xat_lastfree = gethrtime(); + vmem_bucket_wake_all_waiters(); +} + +// return 0 if the bit was unset before the atomic OR. +static inline bool +vba_atomic_lock_bucket(volatile _Atomic uint16_t *bbap, uint16_t bucket_bit) +{ + + // We use a test-and-set of the appropriate bit + // in buckets_busy_allocating; if it was not set, + // then break out of the loop. + // + // This compiles into an orl, cmpxchgw instruction pair. + // the return from __c11_atomic_fetch_or() is the + // previous value of buckets_busy_allocating. + + uint16_t prev = + __c11_atomic_fetch_or(bbap, bucket_bit, __ATOMIC_SEQ_CST); + if (prev & bucket_bit) + return (false); // we did not acquire the bit lock here + else + return (true); // we turned the bit from 0 to 1 +} + +static void * +vmem_bucket_alloc(vmem_t *null_vmp, size_t size, const int vmflags) +{ + + if (vmflags & VM_NO_VBA) + return (NULL); + + // caller is spl_heap_arena looking for memory. + // null_vmp will be spl_default_arena_parent, and so + // is just a placeholder. + + vmem_t *calling_arena = spl_heap_arena; + + static volatile _Atomic uint32_t hipriority_allocators = 0; + boolean_t local_hipriority_allocator = false; + + if (0 != (vmflags & (VM_PUSHPAGE | VM_NOSLEEP | VM_PANIC | VM_ABORT))) { + local_hipriority_allocator = true; + hipriority_allocators++; + } + + if (!ISP2(size)) + atomic_inc_64(&spl_bucket_non_pow2_allocs); + + vmem_t *bvmp = vmem_bucket_arena_by_size(size); + + void *fastm = vmem_alloc_impl(bvmp, size, + local_hipriority_allocator ? vmflags : vmflags | VM_BESTFIT); + + if (fastm != NULL) { + atomic_inc_64(&spl_vba_fastpath); + cv_broadcast(&calling_arena->vm_cv); + return (fastm); + } else if ((vmflags & (VM_NOSLEEP | VM_PANIC | VM_ABORT)) > 0) { + atomic_inc_64(&spl_vba_fastexit); + return (NULL); + } + + atomic_inc_64(&spl_vba_slowpath); + + /* work harder to avoid an allocation */ + const int slow_vmflags = vmflags | VM_BESTFIT; + + // there are 13 buckets, so use a 16-bit scalar to hold + // a set of bits, where each bit corresponds to an in-progress + // vmem_alloc_impl(bucket, ...) below. + + static volatile _Atomic uint16_t buckets_busy_allocating = 0; + const uint16_t bucket_number = vmem_bucket_number(size); + const uint16_t bucket_bit = (uint16_t)(1 << bucket_number); + + spl_vba_threads[bucket_number]++; + + static volatile _Atomic uint32_t waiters = 0; + + // First, if we are VM_SLEEP, check for memory, try some pressure, + // and if that doesn't work, force entry into the loop below. + + bool loop_once = false; + + if ((slow_vmflags & (VM_NOSLEEP | VM_PANIC | VM_ABORT)) == 0 && + ! vmem_canalloc_atomic(bvmp, size)) { + if (spl_vmem_xnu_useful_bytes_free() < (MAX(size, + 16ULL*1024ULL*1024ULL))) { + spl_free_set_emergency_pressure( + total_memory >> 7LL); + IOSleepWithLeeway(2, 1); + if (!vmem_canalloc_atomic(bvmp, size) && + (spl_vmem_xnu_useful_bytes_free() < (MAX(size, + 16ULL*1024ULL*1024ULL)))) { + loop_once = true; + } + } + } + + // spin-sleep: if we would need to go to the xnu allocator. + // + // We want to avoid a burst of allocs from bucket_heap's children + // successively hitting a low-memory condition, or alternatively + // each successfully importing memory from xnu when they can share + // a single import. + // + // We also want to take advantage of any memory that becomes available + // in bucket_heap. + // + // If there is more than one thread in this function (~ few percent) + // then the subsequent threads are put into the loop below. They + // can escape the loop if they are [1]non-waiting allocations, or + // [2]if they become the only waiting thread, or + // [3]if the cv_timedwait_hires returns -1 (which represents EWOULDBLOCK + // from msleep() which gets it from _sleep()'s THREAD_TIMED_OUT) + // allocating in the bucket, or [4]if this thread has (rare condition) + // spent a quarter of a second in the loop. + + if (waiters++ > 1 || loop_once) { + atomic_inc_64(&spl_vba_loop_entries); + } + + static _Atomic uint32_t max_waiters_seen = 0; + + if (waiters > max_waiters_seen) { + max_waiters_seen = waiters; + dprintf("SPL: %s: max_waiters_seen increased to %u\n", __func__, + max_waiters_seen); + } + + // local counters, to be added atomically to global kstat variables + uint64_t local_memory_blocked = 0, local_cv_timeout = 0; + uint64_t local_loop_timeout = 0; + uint64_t local_cv_timeout_blocked = 0, local_loop_timeout_blocked = 0; + uint64_t local_sleep = 0, local_hipriority_blocked = 0; + + const uint64_t loop_ticks = 25; // a tick is 10 msec, so 250 msec + const uint64_t hiprio_loop_ticks = 4; // 40 msec + + for (uint64_t entry_time = zfs_lbolt(), + loop_timeout = entry_time + loop_ticks, + hiprio_timeout = entry_time + hiprio_loop_ticks, timedout = 0; + waiters > 1UL || loop_once; /* empty */) { + loop_once = false; + // non-waiting allocations should proceeed to vmem_alloc_impl() + // immediately + if (slow_vmflags & (VM_NOSLEEP | VM_PANIC | VM_ABORT)) { + break; + } + if (vmem_canalloc_atomic(bvmp, size)) { + // We can probably + // vmem_alloc_impl(bvmp, size, slow_vmflags). + // At worst case it will give us a NULL and we will + // end up on the vmp's cv_wait. + // + // We can have threads with different bvmp + // taking this exit, and will proceed concurrently. + // + // However, we should protect against a burst of + // callers hitting the same bvmp before the allocation + // results are reflected in + // vmem_canalloc_atomic(bvmp, ...) + if (local_hipriority_allocator == false && + hipriority_allocators > 0) { + // more high priority allocations are wanted, + // so this thread stays here + local_hipriority_blocked++; + } else if (vba_atomic_lock_bucket( + &buckets_busy_allocating, bucket_bit)) { + // we are not being blocked by another allocator + // to the same bucket, or any higher priority + // allocator + atomic_inc_64(&spl_vba_parent_memory_appeared); + break; + // The vmem_alloc_impl() should return extremely + // quickly from an INSTANTFIT allocation that + // canalloc predicts will succeed. + } else { + // another thread is trying to use the free + // memory in the bucket_## arena; there might + // still be free memory there after its + // allocation is completed, and there might be + // excess in the bucket_heap arena, so stick + // around in this loop. + local_memory_blocked++; + cv_broadcast(&bvmp->vm_cv); + } + } + if (timedout > 0) { + if (local_hipriority_allocator == false && + hipriority_allocators > 0) { + local_hipriority_blocked++; + } else if (vba_atomic_lock_bucket( + &buckets_busy_allocating, bucket_bit)) { + if (timedout & 1) + local_cv_timeout++; + if (timedout & 6 || zfs_lbolt() >= loop_timeout) + local_loop_timeout++; + break; + } else { + if (timedout & 1) { + local_cv_timeout_blocked++; + } + if (timedout & 6) { + local_loop_timeout_blocked++; + } else if (zfs_lbolt() > loop_timeout) { + timedout |= 2; + } + // flush the current thread in xat() out of + // xat()'s for() loop and into xat_bail() + cv_broadcast(&bvmp->vm_cv); + } + } + // The bucket is already allocating, or the bucket needs + // more memory to satisfy vmem_allocat(bvmp, size, VM_NOSLEEP), + // or we want to give the bucket some time to acquire more + // memory. + // substitute for the vmp arena's cv_wait in vmem_xalloc() + // (vmp is the bucket_heap AKA spl_heap_arena) + mutex_enter(&calling_arena->vm_lock); + local_sleep++; + if (local_sleep >= 1000ULL) { + atomic_add_64(&spl_vba_sleep, local_sleep - 1ULL); + local_sleep = 1ULL; + atomic_add_64(&spl_vba_cv_timeout_blocked, + local_cv_timeout_blocked); + local_cv_timeout_blocked = 0; + atomic_add_64(&spl_vba_loop_timeout_blocked, + local_loop_timeout_blocked); + local_loop_timeout_blocked = 0; + atomic_add_64(&spl_vba_hiprio_blocked, + local_hipriority_blocked); + local_hipriority_blocked = 0; + if (local_memory_blocked > 1ULL) { + atomic_add_64(&spl_vba_parent_memory_blocked, + local_memory_blocked - 1ULL); + local_memory_blocked = 1ULL; + } + } + clock_t wait_time = MSEC2NSEC(30); + if (timedout > 0 || local_memory_blocked > 0) { + wait_time = MSEC2NSEC(1); + } + int ret = cv_timedwait_hires(&calling_arena->vm_cv, + &calling_arena->vm_lock, + wait_time, 0, 0); + // We almost certainly have exited because of a + // signal/broadcast, but maybe just timed out. + // Either way, recheck memory. + mutex_exit(&calling_arena->vm_lock); + if (ret == -1) { + // cv_timedwait_hires timer expired + timedout |= 1; + cv_broadcast(&bvmp->vm_cv); + } else if ((timedout & 2) == 0) { + // we were awakened; check to see if we have been + // in the for loop for a long time + uint64_t n = zfs_lbolt(); + if (n > loop_timeout) { + timedout |= 2; + extern uint64_t real_total_memory; + spl_free_set_emergency_pressure( + total_memory >> 7LL); + // flush the current thread in xat() out of + // xat()'s for() loop and into xat_bail() + cv_broadcast(&bvmp->vm_cv); + } else if (local_hipriority_allocator && + n > hiprio_timeout && waiters > 1UL) { + timedout |= 4; + } + } + } + + /* + * Turn on the exclusion bit in buckets_busy_allocating, to + * prevent multiple threads from calling vmem_alloc_impl() on the + * same bucket arena concurrently rather than serially. + * + * This principally reduces the liklihood of asking xnu for + * more memory when other memory is or becomes available. + * + * This exclusion only applies to VM_SLEEP allocations; + * others (VM_PANIC, VM_NOSLEEP, VM_ABORT) will go to + * vmem_alloc_impl() concurrently with any other threads. + * + * Since we aren't doing a test-and-set operation like above, + * we can just use |= and &= below and get correct atomic + * results, instead of using: + * + * __c11_atomic_fetch_or(&buckets_busy_allocating, + * bucket_bit, __ATOMIC_SEQ_CST); + * with the &= down below being written as + * __c11_atomic_fetch_and(&buckets_busy_allocating, + * ~bucket_bit, __ATOMIC_SEQ_CST); + * + * and this makes a difference with no optimization either + * compiling the whole file or with __attribute((optnone)) + * in front of the function decl. In particular, the non- + * optimized version that uses the builtin __c11_atomic_fetch_{and,or} + * preserves the C program order in the machine language output, + * inersting cmpxchgws, while all optimized versions, and the + * non-optimized version using the plainly-written version, reorder + * the "orw regr, memory" and "andw register, memory" (these are atomic + * RMW operations in x86-64 when the memory is naturally aligned) so + * that the strong memory model x86-64 promise that later loads see the + * results of earlier stores. + * + * clang+llvm simply are good at optimizing _Atomics and + * the optimized code differs only in line numbers and + * among all three approaches (as plainly written, using + * the __c11_atomic_fetch_{or,and} with sequential consistency, + * or when compiling with at least -O optimization so an + * atomic_or_16(&buckets_busy_allocating) built with GCC intrinsics + * is actually inlined rather than a function call). + * + */ + + // in case we left the loop by being the only waiter, stop the + // next thread arriving from leaving the for loop because + // vmem_canalloc(bvmp, that_thread's_size) is true. + + buckets_busy_allocating |= bucket_bit; + + // update counters + if (local_sleep > 0) + atomic_add_64(&spl_vba_sleep, local_sleep); + if (local_memory_blocked > 0) + atomic_add_64(&spl_vba_parent_memory_blocked, + local_memory_blocked); + if (local_cv_timeout > 0) + atomic_add_64(&spl_vba_cv_timeout, local_cv_timeout); + if (local_cv_timeout_blocked > 0) + atomic_add_64(&spl_vba_cv_timeout_blocked, + local_cv_timeout_blocked); + if (local_loop_timeout > 0) + atomic_add_64(&spl_vba_loop_timeout, local_loop_timeout); + if (local_loop_timeout_blocked > 0) + atomic_add_64(&spl_vba_loop_timeout_blocked, + local_loop_timeout_blocked); + if (local_hipriority_blocked > 0) + atomic_add_64(&spl_vba_hiprio_blocked, + local_hipriority_blocked); + + // There is memory in this bucket, or there are no other waiters, + // or we aren't a VM_SLEEP allocation, or we iterated out of the + // for loop. + // vmem_alloc_impl() and vmem_xalloc() do their own mutex serializing + // on bvmp->vm_lock, so we don't have to here. + // + // vmem_alloc may take some time to return (especially for VM_SLEEP + // allocations where we did not take the vm_canalloc(bvmp...) break out + // of the for loop). Therefore, if we didn't enter the for loop at all + // because waiters was 0 when we entered this function, + // subsequent callers will enter the for loop. + + void *m = vmem_alloc_impl(bvmp, size, slow_vmflags); + + // allow another vmem_canalloc() through for this bucket + // by atomically turning off the appropriate bit + + /* + * Except clang+llvm DTRT because of _Atomic, could be written as: + * __c11_atomic_fetch_and(&buckets_busy_allocating, + * ~bucket_bit, __ATOMIC_SEQ_CST); + * + * On processors with more relaxed memory models, it might be + * more efficient to do so with release semantics here, and + * in the atomic |= above, with acquire semantics in the bit tests, + * but on the other hand it may be hard to do better than clang+llvm. + */ + + buckets_busy_allocating &= ~bucket_bit; + + if (local_hipriority_allocator) + hipriority_allocators--; + + // if we got an allocation, wake up the arena cv waiters + // to let them try to exit the for(;;) loop above and + // exit the cv_wait() in vmem_xalloc(vmp, ...) + + if (m != NULL) { + cv_broadcast(&calling_arena->vm_cv); + } + + waiters--; + spl_vba_threads[bucket_number]--; + return (m); +} + +static void +vmem_bucket_free(vmem_t *null_vmp, const void *vaddr, size_t size) +{ + vmem_t *calling_arena = spl_heap_arena; + + vmem_free_impl(vmem_bucket_arena_by_size(size), vaddr, size); + + // wake up arena waiters to let them try an alloc + cv_broadcast(&calling_arena->vm_cv); +} + +static inline int64_t +vmem_bucket_arena_free(uint16_t bucket) +{ + VERIFY(bucket < VMEM_BUCKETS); + return ((int64_t)vmem_size_semi_atomic(vmem_bucket_arena[bucket], + VMEM_FREE)); +} + +static inline int64_t +vmem_bucket_arena_used(int bucket) +{ + VERIFY(bucket < VMEM_BUCKETS); + return ((int64_t)vmem_size_semi_atomic(vmem_bucket_arena[bucket], + VMEM_ALLOC)); +} + + +inline int64_t +vmem_buckets_size(int typemask) +{ + int64_t total_size = 0; + + for (uint16_t i = 0; i < VMEM_BUCKETS; i++) { + int64_t u = vmem_bucket_arena_used(i); + int64_t f = vmem_bucket_arena_free(i); + if (typemask & VMEM_ALLOC) + total_size += u; + if (typemask & VMEM_FREE) + total_size += f; + } + if (total_size < 0) + total_size = 0; + + return ((size_t)total_size); +} + +static inline uint64_t +spl_validate_bucket_span_size(uint64_t val) +{ + if (!ISP2(val)) { + printf("SPL: %s: WARNING %llu is not a power of two, " + "not changing.\n", __func__, val); + return (0); + } + if (val < 128ULL*1024ULL || val > 16ULL*1024ULL*1024ULL) { + printf("SPL: %s: WARNING %llu is out of range [128k - 16M], " + "not changing.\n", __func__, val); + return (0); + } + return (val); +} + +static inline void +spl_modify_bucket_span_size(int bucket, uint64_t size) +{ + vmem_t *bvmp = vmem_bucket_arena[bucket]; + + mutex_enter(&bvmp->vm_lock); + bvmp->vm_min_import = size; + mutex_exit(&bvmp->vm_lock); +} + +static inline void +spl_modify_bucket_array() +{ + for (int i = VMEM_BUCKET_LOWBIT; i < VMEM_BUCKET_HIBIT; i++) { + // i = 12, bucket = 0, contains allocs from 8192 to 16383 bytes, + // and should never ask xnu for < 16384 bytes, so as to avoid + // asking xnu for a non-power-of-two size. + const int bucket = i - VMEM_BUCKET_LOWBIT; + const uint32_t bucket_alloc_minimum_size = 1UL << (uint32_t)i; + const uint32_t bucket_parent_alloc_minimum_size = + bucket_alloc_minimum_size * 2UL; + + switch (i) { + // see vmem_init() below for details + case 16: + case 17: + spl_modify_bucket_span_size(bucket, + MAX(spl_bucket_tunable_small_span, + bucket_parent_alloc_minimum_size)); + break; + default: + spl_modify_bucket_span_size(bucket, + MAX(spl_bucket_tunable_large_span, + bucket_parent_alloc_minimum_size)); + break; + } + } +} + +static inline void +spl_printf_bucket_span_sizes(void) +{ + // this doesn't have to be super-exact + dprintf("SPL: %s: ", __func__); + for (int i = VMEM_BUCKET_LOWBIT; i < VMEM_BUCKET_HIBIT; i++) { + int bnum = i - VMEM_BUCKET_LOWBIT; + vmem_t *bvmp = vmem_bucket_arena[bnum]; + dprintf("%llu ", (uint64_t)bvmp->vm_min_import); + } + dprintf("\n"); +} + +static inline void +spl_set_bucket_spans(uint64_t l, uint64_t s) +{ + if (spl_validate_bucket_span_size(l) && + spl_validate_bucket_span_size(s)) { + atomic_swap_64(&spl_bucket_tunable_large_span, l); + atomic_swap_64(&spl_bucket_tunable_small_span, s); + spl_modify_bucket_array(); + } +} + +void +spl_set_bucket_tunable_large_span(uint64_t size) +{ + uint64_t s = 0; + + mutex_enter(&vmem_xnu_alloc_lock); + atomic_swap_64(&s, spl_bucket_tunable_small_span); + spl_set_bucket_spans(size, s); + mutex_exit(&vmem_xnu_alloc_lock); + + spl_printf_bucket_span_sizes(); +} + +void +spl_set_bucket_tunable_small_span(uint64_t size) +{ + uint64_t l = 0; + + mutex_enter(&vmem_xnu_alloc_lock); + atomic_swap_64(&l, spl_bucket_tunable_large_span); + spl_set_bucket_spans(l, size); + mutex_exit(&vmem_xnu_alloc_lock); + + spl_printf_bucket_span_sizes(); +} + +static inline void * +spl_vmem_default_alloc(vmem_t *vmp, size_t size, int vmflags) +{ + extern void *osif_malloc(uint64_t); + return (osif_malloc(size)); +} + +static inline void +spl_vmem_default_free(vmem_t *vmp, const void *vaddr, size_t size) +{ + extern void osif_free(const void *, uint64_t); + osif_free(vaddr, size); +} + +vmem_t * +vmem_init(const char *heap_name, + void *heap_start, size_t heap_size, size_t heap_quantum, + void *(*heap_alloc)(vmem_t *, size_t, int), + void (*heap_free)(vmem_t *, const void *, size_t)) +{ + uint32_t id; + int nseg = VMEM_SEG_INITIAL; + vmem_t *heap; + + // XNU mutexes need initialisation + mutex_init(&vmem_list_lock, "vmem_list_lock", MUTEX_DEFAULT, + NULL); + mutex_init(&vmem_segfree_lock, "vmem_segfree_lock", MUTEX_DEFAULT, + NULL); + mutex_init(&vmem_sleep_lock, "vmem_sleep_lock", MUTEX_DEFAULT, + NULL); + mutex_init(&vmem_nosleep_lock, "vmem_nosleep_lock", MUTEX_DEFAULT, + NULL); + mutex_init(&vmem_pushpage_lock, "vmem_pushpage_lock", MUTEX_DEFAULT, + NULL); + mutex_init(&vmem_panic_lock, "vmem_panic_lock", MUTEX_DEFAULT, + NULL); + mutex_init(&vmem_xnu_alloc_lock, "vmem_xnu_alloc_lock", MUTEX_DEFAULT, + NULL); + + while (--nseg >= 0) + vmem_putseg_global(&vmem_seg0[nseg]); + + /* + * On OSX we ultimately have to use the OS allocator + * as the ource and sink of memory as it is allocated + * and freed. + * + * The spl_root_arena_parent is needed in order to provide a + * base arena with an always-NULL afunc and ffunc in order to + * end the searches done by vmem_[x]alloc and vm_xfree; it + * serves no other purpose; its stats will always be zero. + * + */ + + // id 0 + spl_default_arena_parent = vmem_create("spl_default_arena_parent", + NULL, 0, heap_quantum, NULL, NULL, NULL, 0, VM_SLEEP); + + // illumos/openzfs has a gigantic pile of memory that it can use + // for its first arena; + // o3x is not so lucky, so we start with this + // Intel can go with 4096 alignment, but arm64 needs 16384. So + // we just use the larger. + initial_default_block = + IOMallocAligned(INITIAL_BLOCK_SIZE, 16384); + + VERIFY3P(initial_default_block, !=, NULL); + + memset(initial_default_block, 0, INITIAL_BLOCK_SIZE); + + // The default arena is very low-bandwidth; it supplies the initial + // large allocation for the heap arena below, and it serves as the + // parent of the vmem_metadata arena. It will typically do only 2 + // or 3 parent_alloc calls (to spl_vmem_default_alloc) in total. + + spl_default_arena = vmem_create("spl_default_arena", // id 1 + initial_default_block, INITIAL_BLOCK_SIZE, + heap_quantum, spl_vmem_default_alloc, spl_vmem_default_free, + spl_default_arena_parent, + 32, /* minimum import */ + VM_SLEEP | VMC_POPULATOR | VMC_NO_QCACHE); + + VERIFY(spl_default_arena != NULL); + + // The bucket arenas satisfy allocations & frees from the bucket heap + // that are dispatched to the bucket whose power-of-two label is the + // smallest allocation that vmem_bucket_allocate will ask for. + // + // The bucket arenas in turn exchange memory with XNU's allocator/freer + // in large spans (~ 1 MiB is stable on all systems but creates bucket + // fragmentation) + // + // Segregating by size constrains internal fragmentation within the + // bucket and provides kstat.vmem visiblity and span-size policy to + // be applied to particular buckets (notably the sources of most + // allocations, see the comments below) + // + // For VMEM_BUCKET_HIBIT == 12, + // vmem_bucket_arena[n] holds allocations from 2^[n+11]+1 to 2^[n+12], + // so for [n] = 0, 2049-4096, for [n]=5 65537-131072, + // for [n]=7 (256k+1)-512k + // + // so "kstat.vmvm.vmem.bucket_1048576" should be read as the bucket + // arena containing allocations 1 MiB and smaller, but larger + // than 512 kiB. + + // create arenas for the VMEM_BUCKETS, id 2 - id 14 + + extern uint64_t real_total_memory; + VERIFY3U(real_total_memory, >=, 1024ULL*1024ULL*1024ULL); + + /* + * Minimum bucket span size, which is what we ask IOMallocAligned for. + * See comments in the switch statement below. + * + * By default ask the kernel for at least 128kiB allocations. + */ + spl_bucket_tunable_large_span = spl_bucket_tunable_small_span = + 128ULL * 1024UL; + + dprintf("SPL: %s: real_total_memory %llu, large spans %llu, small " + "spans %llu\n", __func__, real_total_memory, + spl_bucket_tunable_large_span, spl_bucket_tunable_small_span); + + char *buf; + buf = vmem_alloc_impl(spl_default_arena, VMEM_NAMELEN + 21, VM_SLEEP); + + for (int32_t i = VMEM_BUCKET_LOWBIT; i <= VMEM_BUCKET_HIBIT; i++) { + const uint64_t bucket_largest_size = (1ULL << (uint64_t)i); + + (void) snprintf(buf, VMEM_NAMELEN + 20, "%s_%llu", + "bucket", bucket_largest_size); + + dprintf("SPL: %s creating arena %s (i == %d)\n", __func__, buf, + i); + + const int bucket_number = i - VMEM_BUCKET_LOWBIT; + /* + * To reduce the number of IOMalloc/IOFree transactions with + * the kernel, we create vmem bucket arenas with a PAGESIZE or + * bigger quantum, and a minimum import that is several pages + * for small bucket sizes, and twice the bucket size. + * These will serve power-of-two sized blocks to the + * bucket_heap arena. + */ + vmem_t *b = vmem_create(buf, NULL, 0, + heap_quantum, /* minimum export */ + xnu_alloc_throttled, xnu_free_throttled, + spl_default_arena_parent, + 32, /* minimum import */ + VM_SLEEP | VMC_POPULATOR | VMC_NO_QCACHE | VMC_TIMEFREE); + + VERIFY(b != NULL); + + b->vm_source = b; + vmem_bucket_arena[bucket_number] = b; + vmem_bucket_id_to_bucket_number[b->vm_id] = bucket_number; + } + + vmem_free_impl(spl_default_arena, buf, VMEM_NAMELEN + 21); + // spl_heap_arena, the bucket heap, is the primary interface + // to the vmem system + + // all arenas not rooted to vmem_metadata will be rooted to + // spl_heap arena. + + spl_heap_arena = vmem_create("bucket_heap", // id 15 + NULL, 0, heap_quantum, + vmem_bucket_alloc, vmem_bucket_free, spl_default_arena_parent, 0, + VM_SLEEP | VMC_TIMEFREE | VMC_OLDFIRST); + + VERIFY(spl_heap_arena != NULL); + + // add a fixed-sized allocation to spl_heap_arena; this reduces the + // need to talk to the bucket arenas by a substantial margin + // (kstat.vmem.vmem.bucket_heap.{alloc+free} is much greater than + // kstat.vmem.vmem.bucket_heap.parent_{alloc+free}, and improves with + // increasing initial fixed allocation size. + + /* + * Add an initial segment to spl_heap_arena for convenience. + */ + + const size_t mib = 1024ULL * 1024ULL; + const size_t resv_size = 128ULL * mib; + + dprintf("SPL: %s adding fixed allocation of %llu to the bucket_heap\n", + __func__, (uint64_t)resv_size); + + spl_heap_arena_initial_alloc = vmem_add(spl_heap_arena, + vmem_xalloc(spl_default_arena, resv_size, resv_size, + 0, 0, NULL, NULL, VM_SLEEP), + resv_size, VM_SLEEP); + + VERIFY(spl_heap_arena_initial_alloc != NULL); + + /* remember size we allocated */ + spl_heap_arena_initial_alloc_size = resv_size; + + // kstat.vmem.vmem.heap : kmem_cache_alloc() and similar calls + // to handle in-memory datastructures other than abd + + heap = vmem_create(heap_name, // id 16 + NULL, 0, heap_quantum, + vmem_alloc_impl, vmem_free_impl, spl_heap_arena, 0, + VM_SLEEP); + + VERIFY(heap != NULL); + + // Root all the low bandwidth metadata arenas to the default arena. + // The vmem_metadata allocations will all be 32 kiB or larger, + // and the total allocation will generally cap off around 24 MiB. + + vmem_metadata_arena = vmem_create("vmem_metadata", // id 17 + NULL, 0, heap_quantum, vmem_alloc_impl, vmem_free_impl, + spl_default_arena, +#ifdef __arm64__ + 2 * PAGESIZE, +#else + 8 * PAGESIZE, +#endif + VM_SLEEP | VMC_POPULATOR | VMC_NO_QCACHE); + + VERIFY(vmem_metadata_arena != NULL); + + vmem_seg_arena = vmem_create("vmem_seg", // id 18 + NULL, 0, heap_quantum, + vmem_alloc_impl, vmem_free_impl, vmem_metadata_arena, 0, + VM_SLEEP | VMC_POPULATOR); + + VERIFY(vmem_seg_arena != NULL); + + vmem_hash_arena = vmem_create("vmem_hash", // id 19 + NULL, 0, 8, + vmem_alloc_impl, vmem_free_impl, vmem_metadata_arena, 0, + VM_SLEEP); + + VERIFY(vmem_hash_arena != NULL); + + vmem_vmem_arena = vmem_create("vmem_vmem", // id 20 + vmem0, sizeof (vmem0), 1, + vmem_alloc_impl, vmem_free_impl, vmem_metadata_arena, 0, + VM_SLEEP); + + VERIFY(vmem_vmem_arena != NULL); + + // 21 (0-based) vmem_create before this line. - macroized + // NUMBER_OF_ARENAS_IN_VMEM_INIT + for (id = 0; id < vmem_id; id++) { + (void) vmem_xalloc(vmem_vmem_arena, sizeof (vmem_t), + 1, 0, 0, &vmem0[id], &vmem0[id + 1], + VM_NOSLEEP | VM_BESTFIT | VM_PANIC); + } + + dprintf("SPL: starting vmem_update() thread\n"); + vmem_update(NULL); + + return (heap); +} + +struct free_slab { + vmem_t *vmp; + size_t slabsize; + void *slab; + list_node_t next; +}; +static list_t freelist; + +static void vmem_fini_freelist(void *vmp, void *start, size_t size) +{ + struct free_slab *fs; + + fs = IOMallocType(struct free_slab); + fs->vmp = vmp; + fs->slabsize = size; + fs->slab = start; + list_link_init(&fs->next); + list_insert_tail(&freelist, fs); +} + +void +vmem_free_span_list(void) +{ + int total __maybe_unused = 0; + struct free_slab *fs; +// int release = 1; + + while ((fs = list_head(&freelist))) { + total += fs->slabsize; + list_remove(&freelist, fs); + /* + * Commenting out due to BSOD during uninstallation, + * will revisit later. + * + * for (int id = 0; id < VMEM_INITIAL; id++) { + * if (&vmem0[id] == fs->slab) { + * release = 0; + * break; + * } + * } + * + * if (release) + * fs->vmp->vm_source_free(fs->vmp, fs->slab, + * fs->slabsize); + * release = 1; + * + */ + IOFreeType(fs, struct free_slab); + } +} + +static void +vmem_fini_void(void *vmp, void *start, size_t size) +{ +} + +void +vmem_fini(vmem_t *heap) +{ + struct free_slab *fs; + uint64_t total; + + bsd_untimeout(vmem_update, NULL); + + dprintf("SPL: %s: stopped vmem_update. Creating list and walking " + "arenas.\n", __func__); + + /* Create a list of slabs to free by walking the list of allocs */ + list_create(&freelist, sizeof (struct free_slab), + offsetof(struct free_slab, next)); + + /* Walk to list of allocations */ + + /* + * walking with VMEM_REENTRANT causes segment consolidation and + * freeing of spans the freelist contains a list of segments that + * are still allocated at the time of the walk; unfortunately the + * lists cannot be exact without complex multiple passes, locking, + * and a more complex vmem_fini_freelist(). + * + * Walking without VMEM_REENTRANT can produce a nearly-exact list + * of unfreed spans, which Illumos would then free directly after + * the list is complete. + * + * Unfortunately in O3X, that lack of exactness can lead to a panic + * caused by attempting to free to xnu memory that we already freed + * to xnu. Fortunately, we can get a sense of what would have been + * destroyed after the (non-reentrant) walking, and we printf that + * at the end of this function. + */ + + // Walk all still-alive arenas from leaves to the root + + vmem_walk(heap, VMEM_ALLOC | VMEM_REENTRANT, vmem_fini_void, heap); + + vmem_walk(heap, VMEM_ALLOC, vmem_fini_freelist, heap); + + vmem_free_span_list(); + dprintf("\nSPL: %s destroying heap\n", __func__); + vmem_destroy(heap); // PARENT: spl_heap_arena + + dprintf("SPL: %s: walking spl_heap_arena, aka bucket_heap (pass 1)\n", + __func__); + + vmem_walk(spl_heap_arena, VMEM_ALLOC | VMEM_REENTRANT, vmem_fini_void, + spl_heap_arena); + + dprintf("SPL: %s: calling vmem_xfree(spl_default_arena, ptr, %llu);\n", + __func__, (uint64_t)spl_heap_arena_initial_alloc_size); + + // forcibly remove the initial alloc from spl_heap_arena arena, whether + // or not it is empty. below this point, any activity on + // spl_default_arena other than a non-reentrant(!) walk and a destroy + // is unsafe (UAF or MAF). + // However, all the children of spl_heap_arena should now be destroyed. + + vmem_xfree(spl_default_arena, spl_heap_arena_initial_alloc, + spl_heap_arena_initial_alloc_size); + + printf("SPL: %s: walking spl_heap_arena, aka bucket_heap (pass 2)\n", + __func__); + + vmem_walk(spl_heap_arena, VMEM_ALLOC, vmem_fini_freelist, + spl_heap_arena); + vmem_free_span_list(); + + printf("SPL: %s: walking bucket arenas...\n", __func__); + + for (int i = VMEM_BUCKET_LOWBIT; i <= VMEM_BUCKET_HIBIT; i++) { + const int bucket = i - VMEM_BUCKET_LOWBIT; + vmem_walk(vmem_bucket_arena[bucket], + VMEM_ALLOC | VMEM_REENTRANT, vmem_fini_void, + vmem_bucket_arena[bucket]); + + vmem_walk(vmem_bucket_arena[bucket], VMEM_ALLOC, + vmem_fini_freelist, vmem_bucket_arena[bucket]); + } + vmem_free_span_list(); + + dprintf("SPL: %s destroying spl_bucket_arenas...", __func__); + for (int32_t i = VMEM_BUCKET_LOWBIT; i <= VMEM_BUCKET_HIBIT; i++) { + vmem_t *vmpt = vmem_bucket_arena[i - VMEM_BUCKET_LOWBIT]; + dprintf(" %llu", (1ULL << i)); + vmem_destroy(vmpt); // parent: spl_default_arena_parent + } + dprintf("\n"); + + printf("SPL: %s: walking vmem metadata-related arenas...\n", __func__); + + vmem_walk(vmem_vmem_arena, VMEM_ALLOC | VMEM_REENTRANT, + vmem_fini_void, vmem_vmem_arena); + + vmem_walk(vmem_vmem_arena, VMEM_ALLOC, + vmem_fini_freelist, vmem_vmem_arena); + + vmem_free_span_list(); + + // We should not do VMEM_REENTRANT on vmem_seg_arena or + // vmem_hash_arena or below to avoid causing work in + // vmem_seg_arena and vmem_hash_arena. + + vmem_walk(vmem_seg_arena, VMEM_ALLOC, + vmem_fini_freelist, vmem_seg_arena); + + vmem_free_span_list(); + + vmem_walk(vmem_hash_arena, VMEM_ALLOC, + vmem_fini_freelist, vmem_hash_arena); + vmem_free_span_list(); + + vmem_walk(vmem_metadata_arena, VMEM_ALLOC, + vmem_fini_freelist, vmem_metadata_arena); + + vmem_free_span_list(); + dprintf("SPL: %s walking the root arena (spl_default_arena)...\n", + __func__); + + vmem_walk(spl_default_arena, VMEM_ALLOC, + vmem_fini_freelist, spl_default_arena); + + vmem_free_span_list(); + + dprintf("SPL: %s destroying bucket heap\n", __func__); + // PARENT: spl_default_arena_parent (but depends on buckets) + vmem_destroy(spl_heap_arena); + + // destroying the vmem_vmem arena and any arena afterwards + // requires the use of vmem_destroy_internal(), which does + // not talk to vmem_vmem_arena like vmem_destroy() does. + // dprintf("SPL: %s destroying vmem_vmem_arena\n", __func__); + // vmem_destroy_internal(vmem_vmem_arena); + // parent: vmem_metadata_arena + + // destroying the seg arena means we must no longer + // talk to vmem_populate() + dprintf("SPL: %s destroying vmem_seg_arena\n", __func__); + vmem_destroy(vmem_seg_arena); + + // vmem_hash_arena may be freed-to in vmem_destroy_internal() + // so it should be just before the vmem_metadata_arena. + dprintf("SPL: %s destroying vmem_hash_arena\n", __func__); + vmem_destroy(vmem_hash_arena); // parent: vmem_metadata_arena + vmem_hash_arena = NULL; + + // XXX: if we panic on unload below here due to destroyed mutex, + // vmem_init() will need some reworking (e.g. have + // vmem_metadata_arena talk directly to xnu), or alternatively a + // vmem_destroy_internal_internal() function that does not touch + // vmem_hash_arena will need writing. + + dprintf("SPL: %s destroying vmem_metadata_arena\n", __func__); + vmem_destroy(vmem_metadata_arena); // parent: spl_default_arena + + dprintf("\nSPL: %s destroying spl_default_arena\n", __func__); + vmem_destroy(spl_default_arena); // parent: spl_default_arena_parent + dprintf("\nSPL: %s destroying spl_default_arena_parent\n", __func__); + vmem_destroy(spl_default_arena_parent); + + dprintf("SPL: %s destroying vmem_vmem_arena\n", __func__); + vmem_destroy_internal(vmem_vmem_arena); + + printf("SPL: %s: freeing initial_default_block\n", __func__); + IOFreeAligned(initial_default_block, INITIAL_BLOCK_SIZE); + + printf("SPL: arenas removed, now try destroying mutexes... "); + + printf("vmem_xnu_alloc_lock "); + mutex_destroy(&vmem_xnu_alloc_lock); + printf("vmem_panic_lock "); + mutex_destroy(&vmem_panic_lock); + printf("vmem_pushpage_lock "); + mutex_destroy(&vmem_pushpage_lock); + printf("vmem_nosleep_lock "); + mutex_destroy(&vmem_nosleep_lock); + printf("vmem_sleep_lock "); + mutex_destroy(&vmem_sleep_lock); + printf("vmem_segfree_lock "); + mutex_destroy(&vmem_segfree_lock); + printf("vmem_list_lock "); + mutex_destroy(&vmem_list_lock); + + printf("\nSPL: %s: walking list of live slabs at time of call to %s\n", + __func__, __func__); + + // annoyingly, some of these should be returned to xnu, but + // we have no idea which have already been freed to xnu, and + // freeing a second time results in a panic. + + /* Now release the list of allocs to built above */ + total = 0; + uint64_t total_count = 0; + while ((fs = list_head(&freelist))) { + total_count++; + total += fs->slabsize; + list_remove(&freelist, fs); + // extern void segkmem_free(vmem_t *, void *, size_t); + // segkmem_free(fs->vmp, fs->slab, fs->slabsize); + IOFreeType(fs, struct free_slab); + } + if (total != 0 && total_count != 0) { + printf("SPL: %s:%d: WOULD HAVE released %llu bytes" + " (%llu spans) from arenas\n", + __func__, __LINE__, total, total_count); + } else { + printf("SPL: %s:%d good," + " did not have to force release any vmem spans", + __func__, __LINE__); + } + list_destroy(&freelist); + printf("SPL: %s: Brief delay for readability...\n", __func__); + delay(hz); + printf("SPL: %s: done!\n", __func__); +} + +/* + * return true if inuse is much smaller than imported + */ +static inline bool +bucket_fragmented(const uint16_t bn, const uint64_t now) +{ + + // early during uptime, just let buckets grow. + + if (now < 600 * hz) + return (false); + + // if there has been no pressure in the past five minutes, + // then we will just let the bucket grow. + + const uint64_t timeout = 5ULL * 60ULL * hz; + + if (spl_free_last_pressure_wrapper() + timeout < now) + return (false); + + const vmem_t *vmp = vmem_bucket_arena[bn]; + + const int64_t imported = + (int64_t)vmp->vm_kstat.vk_mem_import.value.ui64; + const int64_t inuse = + (int64_t)vmp->vm_kstat.vk_mem_inuse.value.ui64; + const int64_t tiny = 64LL*1024LL*1024LL; + const int64_t small = tiny * 2LL; // 128 M + const int64_t medium = small * 2LL; // 256 + const int64_t large = medium * 2LL; // 512 + const int64_t huge = large * 2LL; // 1 G + const int64_t super_huge = huge * 2LL; // 2 + + const int64_t amount_free = imported - inuse; + + if (amount_free <= tiny || imported <= small) + return (false); + + const int64_t percent_free = (amount_free * 100LL) / imported; + + if (percent_free > 75LL) { + return (true); + } else if (imported <= medium) { + return (percent_free >= 50); + } else if (imported <= large) { + return (percent_free >= 33); + } else if (imported <= huge) { + return (percent_free >= 25); + } else if (imported <= super_huge) { + return (percent_free >= 15); + } else { + return (percent_free >= 10); + } +} + +/* + * Return an adjusted number of bytes free in the + * abd_cache_arena (if it exists), for arc_no_grow + * policy: if there's lots of space, don't allow + * arc growth for a while to see if the gap + * between imported and inuse drops. + */ +int64_t +abd_arena_empty_space(void) +{ + extern vmem_t *abd_arena; + + if (abd_arena == NULL) + return (0); + + const int64_t imported = + (int64_t)abd_arena->vm_kstat.vk_mem_import.value.ui64; + const int64_t inuse = + (int64_t)abd_arena->vm_kstat.vk_mem_inuse.value.ui64; + + /* Hide 10% or 1GiB fragmentation from arc_no_grow */ + int64_t headroom = + (imported * 90LL / 100LL) - inuse; + + if (headroom < 1024LL*1024LL*1024LL) + headroom = 0; + + return (headroom); +} + +int64_t +abd_arena_total_size(void) +{ + extern vmem_t *abd_arena; + + if (abd_arena != NULL) + return (abd_arena->vm_kstat.vk_mem_total.value.ui64); + return (0LL); +} + + +/* + * return true if the bucket for size is fragmented + */ +static inline bool +spl_arc_no_grow_impl(const uint16_t b, const size_t size, + const boolean_t buf_is_metadata, kmem_cache_t **kc) +{ + static _Atomic uint8_t frag_suppression_counter[VMEM_BUCKETS] = { 0 }; + + const uint64_t now = zfs_lbolt(); + + const bool fragmented = bucket_fragmented(b, now); + + if (fragmented) { + if (size < 32768) { + // Don't suppress small qcached blocks when the + // qcache size (bucket_262144) is fragmented, + // since they will push everything else towards + // the tails of ARC lists without eating up a large + // amount of space themselves. + return (false); + } + const uint32_t b_bit = (uint32_t)1 << (uint32_t)b; + spl_arc_no_grow_bits |= b_bit; + const uint32_t sup_at_least_every = MIN(b_bit, 255); + const uint32_t sup_at_most_every = MAX(b_bit, 16); + const uint32_t sup_every = MIN(sup_at_least_every, + sup_at_most_every); + if (frag_suppression_counter[b] >= sup_every) { + frag_suppression_counter[b] = 0; + return (true); + } else { + frag_suppression_counter[b]++; + return (false); + } + } else { + const uint32_t b_bit = (uint32_t)1 << (uint32_t)b; + spl_arc_no_grow_bits &= ~b_bit; + } + + return (false); +} + +static inline uint16_t +vmem_bucket_number_arc_no_grow(const size_t size) +{ + // qcaching on arc + if (size < 128*1024) + return (vmem_bucket_number(262144)); + else + return (vmem_bucket_number(size)); +} + +boolean_t +spl_arc_no_grow(size_t size, boolean_t buf_is_metadata, kmem_cache_t **zp) +{ + const uint16_t b = vmem_bucket_number_arc_no_grow(size); + + const bool rv = spl_arc_no_grow_impl(b, size, buf_is_metadata, zp); + + if (rv) { + atomic_inc_64(&spl_arc_no_grow_count); + } + + return ((boolean_t)rv); +} diff --git a/module/os/macos/spl/spl-vnode.c b/module/os/macos/spl/spl-vnode.c new file mode 100644 index 000000000000..bd5a8e133c21 --- /dev/null +++ b/module/os/macos/spl/spl-vnode.c @@ -0,0 +1,479 @@ +/* + * 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) 2008 MacZFS + * Copyright (C) 2013 Jorgen Lundman + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +int +VOP_SPACE(struct vnode *vp, int cmd, struct flock *fl, int flags, offset_t off, + cred_t *cr, void *ctx) +{ + int error = 0; +#ifdef F_PUNCHHOLE + if (cmd == F_FREESP) { + fpunchhole_t fpht; + fpht.fp_flags = 0; + fpht.fp_offset = fl->l_start; + fpht.fp_length = fl->l_len; + if (vnode_getwithref(vp) == 0) { + error = VNOP_IOCTL(vp, F_PUNCHHOLE, (caddr_t)&fpht, 0, + ctx); + (void) vnode_put(vp); + } + } +#endif + return (error); +} + +int +VOP_FSYNC(struct vnode *vp, int flags, void* unused, void *uused2) +{ + vfs_context_t vctx; + int error; + + vctx = vfs_context_create((vfs_context_t)0); + error = VNOP_FSYNC(vp, (flags == FSYNC), vctx); + (void) vfs_context_rele(vctx); + return (error); +} + +int +VOP_GETATTR(struct vnode *vp, vattr_t *vap, int flags, void *x3, void *x4) +{ + vfs_context_t vctx; + int error; + + vctx = vfs_context_create((vfs_context_t)0); + error = vnode_getattr(vp, vap, vctx); + (void) vfs_context_rele(vctx); + return (error); +} + +extern errno_t vnode_lookup(const char *path, int flags, struct vnode **vpp, + vfs_context_t ctx); + +extern errno_t vnode_lookupat(const char *path, int flags, struct vnode **vpp, + vfs_context_t ctx, struct vnode *start_dvp); + +errno_t +VOP_LOOKUP(struct vnode *dvp, struct vnode **vpp, + struct componentname *cn, vfs_context_t ct) +{ + char *path = IOMalloc(MAXPATHLEN); + char *lookup_name = cn->cn_nameptr; + int result = 0; + + /* + * Lookup a name, to get vnode. + * If dvp is NULL, and it uses full path, just call vnode_lookup(). + * If dvp is supplied, we need to build path (vnode_lookupat() is + * private.exports) + * However, VOP_LOOKUP() is only used by OSX calls, finder and rename. + * We could re-write that code to use /absolute/path. + */ + if (dvp != NULL) { + int len = MAXPATHLEN; + + result = vn_getpath(dvp, path, &len); + if (result != 0) { + IOFree(path, MAXPATHLEN); + return (result); + } + + strlcat(path, "/", MAXPATHLEN); + strlcat(path, cn->cn_nameptr, MAXPATHLEN); + + lookup_name = path; + } + + result = vnode_lookup(lookup_name, 0, vpp, ct); + + IOFree(path, MAXPATHLEN); + return (result); +} + +void +vfs_mountedfrom(struct mount *vfsp, char *osname) +{ + (void) copystr(osname, vfs_statfs(vfsp)->f_mntfromname, MNAMELEN - 1, + 0); +} + +static kmutex_t spl_getf_lock; +static list_t spl_getf_list; + +int +spl_vnode_init(void) +{ + mutex_init(&spl_getf_lock, NULL, MUTEX_DEFAULT, NULL); + list_create(&spl_getf_list, sizeof (struct spl_fileproc), + offsetof(struct spl_fileproc, f_next)); + return (0); +} + +void +spl_vnode_fini(void) +{ + mutex_destroy(&spl_getf_lock); + list_destroy(&spl_getf_list); +} + +#include +struct fileproc; + +extern int fp_drop(struct proc *p, int fd, struct fileproc *fp, int locked); +extern int fp_drop_written(struct proc *p, int fd, struct fileproc *fp, + int locked); +extern int fp_lookup(struct proc *p, int fd, struct fileproc **resultfp, + int locked); +extern int fo_read(struct fileproc *fp, struct uio *uio, int flags, + vfs_context_t ctx); +extern int fo_write(struct fileproc *fp, struct uio *uio, int flags, + vfs_context_t ctx); +extern int file_vnode_withvid(int, struct vnode **, uint32_t *); +extern int file_drop(int); + +/* + * getf(int fd) - hold a lock on a file descriptor, to be released by calling + * releasef(). On OSX we will also look up the vnode of the fd for calls + * to spl_vn_rdwr(). + */ +void * +getf(int fd) +{ + struct fileproc *fp = NULL; + struct spl_fileproc *sfp = NULL; + struct vnode *vp = NULL; + uint32_t vid; + + /* + * We keep the "fp" pointer as well, both for unlocking in releasef() + * and used in vn_rdwr(). + */ + + sfp = kmem_alloc(sizeof (*sfp), KM_SLEEP); + if (!sfp) + return (NULL); + + /* We no longer use fp */ + + dprintf("current_proc %p: fd %d fp %p vp %p\n", current_proc(), + fd, fp, vp); + + sfp->f_vnode = vp; + sfp->f_fd = fd; + sfp->f_offset = 0; + sfp->f_proc = current_proc(); + sfp->f_fp = fp; + + /* Also grab vnode, so we can fish out the minor, for onexit */ + if (!file_vnode_withvid(fd, &vp, &vid)) { + sfp->f_vnode = vp; + + if (vnode_getwithref(vp) != 0) { + file_drop(fd); + return (NULL); + } + + enum vtype type; + type = vnode_vtype(vp); + if (type == VCHR || type == VBLK) { + sfp->f_file = minor(vnode_specrdev(vp)); + } + file_drop(fd); + } + + mutex_enter(&spl_getf_lock); + list_insert_tail(&spl_getf_list, sfp); + mutex_exit(&spl_getf_lock); + + return (sfp); +} + +struct vnode * +getf_vnode(void *fp) +{ + struct spl_fileproc *sfp = (struct spl_fileproc *)fp; + struct vnode *vp = NULL; + uint32_t vid; + + if (!file_vnode_withvid(sfp->f_fd, &vp, &vid)) { + file_drop(sfp->f_fd); + } + + return (vp); +} + +void +releasefp(struct spl_fileproc *fp) +{ + if (fp->f_vnode != NULL) + vnode_put(fp->f_vnode); + + /* Remove node from the list */ + mutex_enter(&spl_getf_lock); + list_remove(&spl_getf_list, fp); + mutex_exit(&spl_getf_lock); + + /* Free the node */ + kmem_free(fp, sizeof (*fp)); +} + +void +releasef(int fd) +{ + struct spl_fileproc *fp = NULL; + struct proc *p; + + p = current_proc(); + mutex_enter(&spl_getf_lock); + for (fp = list_head(&spl_getf_list); fp != NULL; + fp = list_next(&spl_getf_list, fp)) { + if ((fp->f_proc == p) && fp->f_fd == fd) break; + } + mutex_exit(&spl_getf_lock); + if (!fp) + return; // Not found + + releasefp(fp); +} + +/* + * getf()/releasef() IO handler. + */ +#undef vn_rdwr +extern int vn_rdwr(enum uio_rw rw, struct vnode *vp, caddr_t base, int len, + off_t offset, enum uio_seg segflg, int ioflg, kauth_cred_t cred, + int *aresid, struct proc *p); + +int spl_vn_rdwr(enum uio_rw rw, struct spl_fileproc *sfp, + caddr_t base, ssize_t len, offset_t offset, enum uio_seg seg, + int ioflag, rlim64_t ulimit, cred_t *cr, ssize_t *residp) +{ + int error = 0; + int aresid; + + VERIFY3P(sfp->f_vnode, !=, NULL); + + error = vn_rdwr(rw, sfp->f_vnode, base, len, offset, seg, ioflag, + cr, &aresid, sfp->f_proc); + + if (residp) { + *residp = aresid; + } + + return (error); +} + +/* Regular vnode vn_rdwr */ +int zfs_vn_rdwr(enum uio_rw rw, struct vnode *vp, caddr_t base, ssize_t len, + offset_t offset, enum uio_seg seg, int ioflag, rlim64_t ulimit, + cred_t *cr, ssize_t *residp) +{ + uio_t *auio; + int spacetype; + int error = 0; + vfs_context_t vctx; + + spacetype = UIO_SEG_IS_USER_SPACE(seg) ? UIO_USERSPACE32 : UIO_SYSSPACE; + + vctx = vfs_context_create((vfs_context_t)0); + auio = uio_create(1, 0, spacetype, rw); + uio_reset(auio, offset, spacetype, rw); + uio_addiov(auio, (uint64_t)(uintptr_t)base, len); + + if (rw == UIO_READ) { + error = VNOP_READ(vp, auio, ioflag, vctx); + } else { + error = VNOP_WRITE(vp, auio, ioflag, vctx); + } + + if (residp) { + *residp = uio_resid(auio); + } else { + if (uio_resid(auio) && error == 0) + error = EIO; + } + + uio_free(auio); + vfs_context_rele(vctx); + + return (error); +} + +void +spl_rele_async(void *arg) +{ + struct vnode *vp = (struct vnode *)arg; + if (vp) vnode_put(vp); +} + +void +vn_rele_async(struct vnode *vp, void *taskq) +{ + VERIFY(taskq_dispatch((taskq_t *)taskq, + (task_func_t *)spl_rele_async, vp, TQ_SLEEP) != 0); +} + +vfs_context_t +spl_vfs_context_kernel(void) +{ + return (NULL); +} + +#undef build_path +extern int build_path(struct vnode *vp, char *buff, int buflen, int *outlen, + int flags, vfs_context_t ctx); + +int spl_build_path(struct vnode *vp, char *buff, int buflen, int *outlen, + int flags, vfs_context_t ctx) +{ + // Private.exports + // return (build_path(vp, buff, buflen, outlen, flags, ctx)); + printf("%s: missing implementation. All will fail.\n", __func__); + + buff[0] = 0; + *outlen = 0; + return (0); +} + +/* + * vnode_notify was moved from KERNEL_PRIVATE to KERNEL in 10.11, but to be + * backward compatible, we keep the wrapper for now. + */ +extern int vnode_notify(struct vnode *, uint32_t, struct vnode_attr *); +int +spl_vnode_notify(struct vnode *vp, uint32_t type, struct vnode_attr *vap) +{ +#if defined(MAC_OS_X_VERSION_10_11) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) + return (vnode_notify(vp, type, vap)); +#else + return (0); +#endif +} + +extern int vfs_get_notify_attributes(struct vnode_attr *vap); +int +spl_vfs_get_notify_attributes(struct vnode_attr *vap) +{ +#if defined(MAC_OS_X_VERSION_10_11) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) + return (vfs_get_notify_attributes(vap)); +#else + return (0); +#endif +} + +/* Root directory vnode for the system a.k.a. '/' */ +/* + * Must use vfs_rootvnode() to acquire a reference, and + * vnode_put() to release it + */ + +struct vnode * +getrootdir(void) +{ + struct vnode *rvnode; + + rvnode = vfs_rootvnode(); + if (rvnode) + vnode_put(rvnode); + return (rvnode); +} + + +static inline int +spl_cache_purgevfs_impl(struct vnode *vp, void *arg) +{ + cache_purge(vp); + cache_purge_negatives(vp); + + return (VNODE_RETURNED); +#if 0 + if (system_taskq == NULL) + return (VNODE_RETURNED); + vn_rele_async(vp, system_taskq); + return (VNODE_CLAIMED); +#endif +} + +/* + * Apple won't let us call cache_purgevfs() so let's try to get + * as close as possible + */ +void +spl_cache_purgevfs(mount_t mp, boolean_t reload) +{ + (void) vnode_iterate(mp, reload ? VNODE_RELOAD : 0, + spl_cache_purgevfs_impl, NULL); +} + +/* Gross hacks - find solutions */ + +/* + * Sorry, but this is gross. But unable to find a way around it yet.. + * Maybe one day Apple will allow it. + */ +int +vnode_iocount(struct vnode *vp) +{ + int32_t *binvp; + + binvp = (int32_t *)vp; + + return (binvp[25]); +} + +cred_t * +spl_kcred(void) +{ + cred_t *ret; + + /* + * How bad is it to return a released reference? + * We have no way to return it when we are done with it. But + * it is the kernel, so it should not go away. + */ + cred_t *cr = kauth_cred_proc_ref(kernproc); + ret = cr; + kauth_cred_unref(&cr); + + return (ret); +} diff --git a/module/os/macos/spl/spl-xdr.c b/module/os/macos/spl/spl-xdr.c new file mode 100644 index 000000000000..b418f8950e98 --- /dev/null +++ b/module/os/macos/spl/spl-xdr.c @@ -0,0 +1,529 @@ +/* + * 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) 2008 MacZFS + * Copyright (C) 2013 Jorgen Lundman + * + */ + +#include +#include +#include +#include +#include +#include + + +/* + * SPL's XDR mem implementation. + * + * This is used by libnvpair to serialize/deserialize the name-value pair data + * structures into byte arrays in a well-defined and portable manner. + * + * These data structures are used by the DMU/ZFS to flexibly manipulate various + * information in memory and later serialize it/deserialize it to disk. + * Examples of usages include the pool configuration, lists of pool and dataset + * properties, etc. + * + * Reference documentation for the XDR representation and XDR operations can be + * found in RFC 1832 and xdr(3), respectively. + * + * === Implementation shortcomings === + * + * It is assumed that the following C types have the following sizes: + * + * char/unsigned char: 1 byte + * short/unsigned short: 2 bytes + * int/unsigned int: 4 bytes + * longlong_t/u_longlong_t: 8 bytes + * + * The C standard allows these types to be larger (and in the case of ints, + * shorter), so if that is the case on some compiler/architecture, the build + * will fail (on purpose). + * + * If someone wants to fix the code to work properly on such environments, then: + * + * 1) Preconditions should be added to xdrmem_enc functions to make sure the + * caller doesn't pass arguments which exceed the expected range. + * 2) Functions which take signed integers should be changed to properly do + * sign extension. + * 3) For ints with less than 32 bits, well.. I suspect you'll have bigger + * problems than this implementation. + * + * It is also assumed that: + * + * 1) Chars have 8 bits. + * 2) We can always do 32-bit-aligned int memory accesses and byte-aligned + * memcpy, memset and memcmp. + * 3) Arrays passed to xdr_array() are packed and the compiler/architecture + * supports element-sized-aligned memory accesses. + * 4) Negative integers are natively stored in two's complement binary + * representation. + * + * No checks are done for the 4 assumptions above, though. + * + * === Caller expectations === + * + * Existing documentation does not describe the semantics of XDR operations very + * well. Therefore, some assumptions about failure semantics will be made and + * will be described below: + * + * 1) If any encoding operation fails (e.g., due to lack of buffer space), the + * the stream should be considered valid only up to the encoding operation + * previous to the one that first failed. However, the stream size as returned + * by xdr_control() cannot be considered to be strictly correct (it may be + * bigger). + * + * Putting it another way, if there is an encoding failure it's undefined + * whether anything is added to the stream in that operation and therefore + * neither xdr_control() nor future encoding operations on the same stream can + * be relied upon to produce correct results. + * + * 2) If a decoding operation fails, it's undefined whether anything will be + * decoded into passed buffers/pointers during that operation, or what the + * values on those buffers will look like. + * + * Future decoding operations on the same stream will also have similar + * undefined behavior. + * + * 3) When the first decoding operation fails it is OK to trust the results of + * previous decoding operations on the same stream, as long as the caller + * expects a failure to be possible (e.g. due to end-of-stream). + * + * However, this is highly discouraged because the caller should know the + * stream size and should be coded to expect any decoding failure to be data + * corruption due to hardware, accidental or even malicious causes, which should + * be handled gracefully in all cases. + * + * In very rare situations where there are strong reasons to believe the data + * can be trusted to be valid and non-tampered with, then the caller may assume + * a decoding failure to be a bug (e.g. due to mismatched data types) and may + * fail non-gracefully. + * + * 4) Non-zero padding bytes will cause the decoding operation to fail. + * + * 5) Zero bytes on string types will also cause the decoding operation to fail. + * + * 6) It is assumed that either the pointer to the stream buffer given by the + * caller is 32-bit aligned or the architecture supports non-32-bit-aligned int + * memory accesses. + * + * 7) The stream buffer and encoding/decoding buffers/ptrs should not overlap. + * + * 8) If a caller passes pointers to non-kernel memory (e.g., pointers to user + * space or MMIO space), the computer may explode. + */ + +static struct xdr_ops xdrmem_encode_ops; +static struct xdr_ops xdrmem_decode_ops; + +void +xdrmem_create(XDR *xdrs, const caddr_t addr, const uint_t size, + const enum xdr_op op) +{ + switch (op) { + case XDR_ENCODE: + xdrs->x_ops = &xdrmem_encode_ops; + break; + case XDR_DECODE: + xdrs->x_ops = &xdrmem_decode_ops; + break; + default: + printf("SPL: Invalid op value: %d\n", op); + xdrs->x_ops = NULL; /* Let the caller know we failed */ + return; + } + + xdrs->x_op = op; + xdrs->x_addr = addr; + xdrs->x_addr_end = addr + size; + + if (xdrs->x_addr_end < xdrs->x_addr) { + printf("SPL: Overflow while creating xdrmem: %p, %u\n", addr, + size); + xdrs->x_ops = NULL; + } +} +EXPORT_SYMBOL(xdrmem_create); + +static bool_t +xdrmem_control(XDR *xdrs, int req, void *info) +{ + struct xdr_bytesrec *rec = (struct xdr_bytesrec *)info; + + if (req != XDR_GET_BYTES_AVAIL) { + printf("SPL: Called with unknown request: %d\n", req); + return (FALSE); + } + + rec->xc_is_last_record = TRUE; /* always TRUE in xdrmem streams */ + rec->xc_num_avail = xdrs->x_addr_end - xdrs->x_addr; + + return (TRUE); +} + +static bool_t +xdrmem_enc_bytes(XDR *xdrs, caddr_t cp, const uint_t cnt) +{ + uint_t size = roundup(cnt, 4); + uint_t pad; + + if (size < cnt) + return (FALSE); /* Integer overflow */ + + if (xdrs->x_addr > xdrs->x_addr_end) + return (FALSE); + + if (xdrs->x_addr_end - xdrs->x_addr < size) + return (FALSE); + + memcpy(xdrs->x_addr, cp, cnt); + + xdrs->x_addr += cnt; + + pad = size - cnt; + if (pad > 0) { + memset(xdrs->x_addr, 0, pad); + xdrs->x_addr += pad; + } + + return (TRUE); +} + +static bool_t +xdrmem_dec_bytes(XDR *xdrs, caddr_t cp, const uint_t cnt) +{ + static uint32_t zero = 0; + uint_t size = roundup(cnt, 4); + uint_t pad; + + if (size < cnt) + return (FALSE); /* Integer overflow */ + + if (xdrs->x_addr > xdrs->x_addr_end) + return (FALSE); + + if (xdrs->x_addr_end - xdrs->x_addr < size) + return (FALSE); + + memcpy(cp, xdrs->x_addr, cnt); + xdrs->x_addr += cnt; + + pad = size - cnt; + if (pad > 0) { + /* An inverted memchr() would be useful here... */ + if (memcmp(&zero, xdrs->x_addr, pad) != 0) + return (FALSE); + + xdrs->x_addr += pad; + } + + return (TRUE); +} + +static bool_t +xdrmem_enc_uint32(XDR *xdrs, uint32_t val) +{ + if (xdrs->x_addr + sizeof (uint32_t) > xdrs->x_addr_end) + return (FALSE); + + *((uint32_t *)xdrs->x_addr) = BE_32(val); + + xdrs->x_addr += sizeof (uint32_t); + + return (TRUE); +} + +static bool_t +xdrmem_dec_uint32(XDR *xdrs, uint32_t *val) +{ + if (xdrs->x_addr + sizeof (uint32_t) > xdrs->x_addr_end) + return (FALSE); + + *val = BE_32(*((uint32_t *)xdrs->x_addr)); + + xdrs->x_addr += sizeof (uint32_t); + + return (TRUE); +} + +static bool_t +xdrmem_enc_char(XDR *xdrs, char *cp) +{ + uint32_t val; + + // BUILD_BUG_ON(sizeof(char) != 1); + val = *((unsigned char *) cp); + + return (xdrmem_enc_uint32(xdrs, val)); +} + +static bool_t +xdrmem_dec_char(XDR *xdrs, char *cp) +{ + uint32_t val; + + // BUILD_BUG_ON(sizeof(char) != 1); + + if (!xdrmem_dec_uint32(xdrs, &val)) + return (FALSE); + + /* + * If any of the 3 other bytes are non-zero then val will be greater + * than 0xff and we fail because according to the RFC, this block does + * not have a char encoded in it. + */ + if (val > 0xff) + return (FALSE); + + /* & 0xff for -Wimplicit-int-coversion */ + *((unsigned char *) cp) = val & 0xff; + + return (TRUE); +} + +static bool_t +xdrmem_enc_ushort(XDR *xdrs, unsigned short *usp) +{ + // BUILD_BUG_ON(sizeof(unsigned short) != 2); + + return (xdrmem_enc_uint32(xdrs, *usp)); +} + +static bool_t +xdrmem_dec_ushort(XDR *xdrs, unsigned short *usp) +{ + uint32_t val; + + // BUILD_BUG_ON(sizeof(unsigned short) != 2); + + if (!xdrmem_dec_uint32(xdrs, &val)) + return (FALSE); + + /* + * Short ints are not in the RFC, but we assume similar logic as in + * xdrmem_dec_char(). + */ + if (val > 0xffff) + return (FALSE); + + *usp = val & 0xffff; /* the & for -Wimplicit-int-coversion */ + + return (TRUE); +} + +static bool_t +xdrmem_enc_uint(XDR *xdrs, unsigned *up) +{ + // BUILD_BUG_ON(sizeof(unsigned) != 4); + + return (xdrmem_enc_uint32(xdrs, *up)); +} + +static bool_t +xdrmem_dec_uint(XDR *xdrs, unsigned *up) +{ + // BUILD_BUG_ON(sizeof(unsigned) != 4); + + return (xdrmem_dec_uint32(xdrs, (uint32_t *)up)); +} + +static bool_t +xdrmem_enc_ulonglong(XDR *xdrs, u_longlong_t *ullp) +{ + // BUILD_BUG_ON(sizeof(u_longlong_t) != 8); + + if (!xdrmem_enc_uint32(xdrs, *ullp >> 32)) + return (FALSE); + + return (xdrmem_enc_uint32(xdrs, *ullp & 0xffffffff)); +} + +static bool_t +xdrmem_dec_ulonglong(XDR *xdrs, u_longlong_t *ullp) +{ + uint32_t low, high; + + // BUILD_BUG_ON(sizeof(u_longlong_t) != 8); + + if (!xdrmem_dec_uint32(xdrs, &high)) + return (FALSE); + if (!xdrmem_dec_uint32(xdrs, &low)) + return (FALSE); + + *ullp = ((u_longlong_t)high << 32) | low; + + return (TRUE); +} + +static bool_t +xdr_enc_array(XDR *xdrs, caddr_t *arrp, uint_t *sizep, const uint_t maxsize, + const uint_t elsize, const xdrproc_t elproc) +{ + uint_t i; + caddr_t addr = *arrp; + + if (*sizep > maxsize || *sizep > UINT_MAX / elsize) + return (FALSE); + + if (!xdrmem_enc_uint(xdrs, sizep)) + return (FALSE); + + for (i = 0; i < *sizep; i++) { +#ifdef _KERNEL + if (!elproc(xdrs, addr)) +#else + if (!elproc(xdrs, addr, 0)) +#endif + return (FALSE); + addr += elsize; + } + + return (TRUE); +} + +static bool_t +xdr_dec_array(XDR *xdrs, caddr_t *arrp, uint_t *sizep, const uint_t maxsize, + const uint_t elsize, const xdrproc_t elproc) +{ + uint_t i, size; + bool_t alloc = FALSE; + caddr_t addr; + + if (!xdrmem_dec_uint(xdrs, sizep)) + return (FALSE); + + size = *sizep; + + if (size > maxsize || size > UINT_MAX / elsize) + return (FALSE); + + /* + * The Solaris man page says: "If *arrp is NULL when decoding, + * xdr_array() allocates memory and *arrp points to it". + */ + if (*arrp == NULL) { + // BUILD_BUG_ON(sizeof(uint_t) > sizeof(size_t)); + + *arrp = kmem_alloc(size * elsize, KM_NOSLEEP); + if (*arrp == NULL) + return (FALSE); + + alloc = TRUE; + } + + addr = *arrp; + + for (i = 0; i < size; i++) { + if (!elproc(xdrs, addr)) { + if (alloc) + kmem_free(*arrp, size * elsize); + return (FALSE); + } + addr += elsize; + } + + return (TRUE); +} + +static bool_t +xdr_enc_string(XDR *xdrs, char **sp, const uint_t maxsize) +{ + size_t slen = strlen(*sp); + uint_t len; + + if (slen > maxsize) + return (FALSE); + + len = slen; + + if (!xdrmem_enc_uint(xdrs, &len)) + return (FALSE); + + return (xdrmem_enc_bytes(xdrs, *sp, len)); +} + +static bool_t +xdr_dec_string(XDR *xdrs, char **sp, const uint_t maxsize) +{ + uint_t size; + bool_t alloc = FALSE; + + if (!xdrmem_dec_uint(xdrs, &size)) + return (FALSE); + + if (size > maxsize || size > UINT_MAX - 1) + return (FALSE); + + /* + * Solaris man page: "If *sp is NULL when decoding, xdr_string() + * allocates memory and *sp points to it". + */ + if (*sp == NULL) { + // BUILD_BUG_ON(sizeof(uint_t) > sizeof(size_t)); + + *sp = kmem_alloc(size + 1, KM_NOSLEEP); + if (*sp == NULL) + return (FALSE); + + alloc = TRUE; + } + + if (!xdrmem_dec_bytes(xdrs, *sp, size)) + goto fail; + + if (kmemchr(*sp, 0, size) != NULL) + goto fail; + + (*sp)[size] = '\0'; + + return (TRUE); + +fail: + if (alloc) + kmem_free(*sp, size + 1); + + return (FALSE); +} + +static struct xdr_ops xdrmem_encode_ops = { + .xdr_control = xdrmem_control, + .xdr_char = xdrmem_enc_char, + .xdr_u_short = xdrmem_enc_ushort, + .xdr_u_int = xdrmem_enc_uint, + .xdr_u_longlong_t = xdrmem_enc_ulonglong, + .xdr_opaque = xdrmem_enc_bytes, + .xdr_string = xdr_enc_string, + .xdr_array = xdr_enc_array +}; + +static struct xdr_ops xdrmem_decode_ops = { + .xdr_control = xdrmem_control, + .xdr_char = xdrmem_dec_char, + .xdr_u_short = xdrmem_dec_ushort, + .xdr_u_int = xdrmem_dec_uint, + .xdr_u_longlong_t = xdrmem_dec_ulonglong, + .xdr_opaque = xdrmem_dec_bytes, + .xdr_string = xdr_dec_string, + .xdr_array = xdr_dec_array +}; diff --git a/module/os/macos/spl/spl-zlib.c b/module/os/macos/spl/spl-zlib.c new file mode 100644 index 000000000000..5aa92c324a47 --- /dev/null +++ b/module/os/macos/spl/spl-zlib.c @@ -0,0 +1,199 @@ +/* + * + * zlib.h -- interface of the 'zlib' general purpose compression library + * version 1.2.5, April 19th, 2010 + * + * Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * Jean-loup Gailly + * Mark Adler + */ + +#include +#include +#include +#include + +#ifdef DEBUG_SUBSYSTEM +#undef DEBUG_SUBSYSTEM +#endif + +#define DEBUG_SUBSYSTEM SS_ZLIB + +static spl_kmem_cache_t *zlib_workspace_cache; + +/* + * A kmem_cache is used for the zlib workspaces to avoid having to vmalloc + * and vfree for every call. Using a kmem_cache also has the advantage + * that improves the odds that the memory used will be local to this cpu. + * To further improve things it might be wise to create a dedicated per-cpu + * workspace for use. This would take some additional care because we then + * must disable preemption around the critical section, and verify that + * zlib_deflate* and zlib_inflate* never internally call schedule(). + */ +static void * +zlib_workspace_alloc(int flags) +{ + return (kmem_cache_alloc(zlib_workspace_cache, flags & ~(__GFP_FS))); +} + +static void +zlib_workspace_free(void *workspace) +{ + kmem_cache_free(zlib_workspace_cache, workspace); +} + +/* + * Compresses the source buffer into the destination buffer. The level + * parameter has the same meaning as in deflateInit. sourceLen is the byte + * length of the source buffer. Upon entry, destLen is the total size of the + * destination buffer, which must be at least 0.1% larger than sourceLen plus + * 12 bytes. Upon exit, destLen is the actual size of the compressed buffer. + * + * compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + * memory, Z_BUF_ERROR if there was not enough room in the output buffer, + * Z_STREAM_ERROR if the level parameter is invalid. + */ +int +z_compress_level(void *dest, size_t *destLen, const void *source, + size_t sourceLen, int level) +{ + z_stream stream; + int err; + + stream.next_in = (Byte *)source; + stream.avail_in = (uInt)sourceLen; + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + + if ((size_t)stream.avail_out != *destLen) + return (Z_BUF_ERROR); + + stream.workspace = zlib_workspace_alloc(KM_SLEEP); + if (!stream.workspace) + return (Z_MEM_ERROR); + + err = zlib_deflateInit(&stream, level); + if (err != Z_OK) { + zlib_workspace_free(stream.workspace); + return (err); + } + + err = zlib_deflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + zlib_deflateEnd(&stream); + zlib_workspace_free(stream.workspace); + return (err == Z_OK ? Z_BUF_ERROR : err); + } + *destLen = stream.total_out; + + err = zlib_deflateEnd(&stream); + zlib_workspace_free(stream.workspace); + + return (err); +} +EXPORT_SYMBOL(z_compress_level); + +/* + * Decompresses the source buffer into the destination buffer. sourceLen is + * the byte length of the source buffer. Upon entry, destLen is the total + * size of the destination buffer, which must be large enough to hold the + * entire uncompressed data. (The size of the uncompressed data must have + * been saved previously by the compressor and transmitted to the decompressor + * by some mechanism outside the scope of this compression library.) + * Upon exit, destLen is the actual size of the compressed buffer. + * This function can be used to decompress a whole file at once if the + * input file is mmap'ed. + * + * uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + * enough memory, Z_BUF_ERROR if there was not enough room in the output + * buffer, or Z_DATA_ERROR if the input data was corrupted. + */ +int +z_uncompress(void *dest, size_t *destLen, const void *source, + size_t sourceLen) +{ + z_stream stream; + int err; + + stream.next_in = (Byte *)source; + stream.avail_in = (uInt)sourceLen; + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + + if ((size_t)stream.avail_out != *destLen) + return (Z_BUF_ERROR); + + stream.workspace = zlib_workspace_alloc(KM_SLEEP); + if (!stream.workspace) + return (Z_MEM_ERROR); + + err = zlib_inflateInit(&stream); + if (err != Z_OK) { + zlib_workspace_free(stream.workspace); + return (err); + } + + err = zlib_inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + zlib_inflateEnd(&stream); + zlib_workspace_free(stream.workspace); + + if (err == Z_NEED_DICT || + (err == Z_BUF_ERROR && stream.avail_in == 0)) + return (Z_DATA_ERROR); + + return (err); + } + *destLen = stream.total_out; + + err = zlib_inflateEnd(&stream); + zlib_workspace_free(stream.workspace); + + return (err); +} +EXPORT_SYMBOL(z_uncompress); + +int +spl_zlib_init(void) +{ + int size; + SENTRY; + + size = MAX(spl_zlib_deflate_workspacesize(MAX_WBITS, MAX_MEM_LEVEL), + zlib_inflate_workspacesize()); + + zlib_workspace_cache = kmem_cache_create( + "spl_zlib_workspace_cache", + size, 0, NULL, NULL, NULL, NULL, NULL, + KMC_VMEM | KMC_NOEMERGENCY); + if (!zlib_workspace_cache) + SRETURN(1); + + SRETURN(0); +} + +void +spl_zlib_fini(void) +{ + SENTRY; + kmem_cache_destroy(zlib_workspace_cache); + zlib_workspace_cache = NULL; + SEXIT; +} diff --git a/module/os/macos/zfs/.gitignore b/module/os/macos/zfs/.gitignore new file mode 100644 index 000000000000..aaec2f8ea214 --- /dev/null +++ b/module/os/macos/zfs/.gitignore @@ -0,0 +1,2 @@ +zfs +zfs.kext diff --git a/module/os/macos/zfs/Info.plist b/module/os/macos/zfs/Info.plist new file mode 100644 index 000000000000..2483aaf3b939 --- /dev/null +++ b/module/os/macos/zfs/Info.plist @@ -0,0 +1,113 @@ + + + + + BuildMachineOSBuild + 14C1514 + CFBundleDevelopmentRegion + English + CFBundleExecutable + zfs + CFBundleIdentifier + org.openzfsonosx.zfs + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + zfs + CFBundlePackageType + KEXT + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0.0 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 6C131e + DTPlatformVersion + GM + DTSDKBuild + 12F37 + DTSDKName + macosx10.8 + DTXcode + 0620 + DTXcodeBuild + 6C131e + IOKitPersonalities + + org.openzfsonosx.zfs + + CFBundleIdentifier + org.openzfsonosx.zfs + IOClass + org_openzfsonosx_zfs_zvol + IOMatchCategory + org_openzfsonosx_zfs_zvol + IOMediaIcon + + CFBundleIdentifier + org.openzfsonosx.zfs + IOBundleResourceFile + VolumeIcon.icns + + IOProviderClass + IOResources + IOResourceMatch + IOBSD + + org.openzfsonosx.zfs.ZFSDatasetProxy + + CFBundleIdentifier + org.openzfsonosx.zfs + IOClass + ZFSDatasetProxy + IOProbeScore + 1000 + IOMatchCategory + ZFSPool + IOProviderClass + ZFSPool + + org.openzfsonosx.zfs.ZFSDatasetScheme + + CFBundleIdentifier + org.openzfsonosx.zfs + IOClass + ZFSDatasetScheme + IOProbeScore + 5000 + IOMatchCategory + IOStorage + IOPropertyMatch + + Whole + + + IOProviderClass + IOMedia + + + NSHumanReadableCopyright + CDDL (ZFS), BSD (FreeBSD), Copyright © 2012-2021 OpenZFS on OS X. All rights reserved. + OSBundleCompatibleVersion + 1.0.0 + OSBundleLibraries + + com.apple.iokit.IOStorageFamily + 1.6 + com.apple.kpi.bsd + 8.0.0 + com.apple.kpi.iokit + 8.0.0 + com.apple.kpi.libkern + 10.0 + com.apple.kpi.mach + 8.0.0 + com.apple.kpi.unsupported + 8.0.0 + + + diff --git a/module/os/macos/zfs/InfoPlist.strings b/module/os/macos/zfs/InfoPlist.strings new file mode 100644 index 000000000000..0c67376ebacb --- /dev/null +++ b/module/os/macos/zfs/InfoPlist.strings @@ -0,0 +1,5 @@ + + + + + diff --git a/module/os/macos/zfs/ZFSDataset.cpp b/module/os/macos/zfs/ZFSDataset.cpp new file mode 100644 index 000000000000..8a2a0b52b9d6 --- /dev/null +++ b/module/os/macos/zfs/ZFSDataset.cpp @@ -0,0 +1,855 @@ +/* + * 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) 2015, Evan Susarret. All rights reserved. + */ +/* + * ZFSDataset - proxy disk for legacy and com.apple.devicenode mounts. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DPRINTF_FUNC() do { dprintf(""); } while (0); + +OSDefineMetaClassAndStructors(ZFSDataset, IOMedia); + +#if 0 +/* XXX Only for debug tracing */ +bool +ZFSDataset::open(IOService *client, + IOOptionBits options, IOStorageAccess access) +{ + bool ret; + DPRINTF_FUNC(); + + ret = IOMedia::open(client, options, access); + + dprintf("ZFSDataset %s ret %d", ret); + return (ret); +} + +bool +ZFSDataset::isOpen(const IOService *forClient) const +{ + DPRINTF_FUNC(); + return (false); +} + +void +ZFSDataset::close(IOService *client, + IOOptionBits options) +{ + DPRINTF_FUNC(); + IOMedia::close(client, options); +} + +bool +ZFSDataset::handleOpen(IOService *client, + IOOptionBits options, void *access) +{ + bool ret; + DPRINTF_FUNC(); + + ret = IOMedia::handleOpen(client, options, access); + + dprintf("ZFSDataset %s ret %d", ret); + return (ret); +} + +bool +ZFSDataset::handleIsOpen(const IOService *client) const +{ + bool ret; + DPRINTF_FUNC(); + + ret = IOMedia::handleIsOpen(client); + + dprintf("ZFSDataset %s ret %d", ret); + return (ret); +} + +void +ZFSDataset::handleClose(IOService *client, + IOOptionBits options) +{ + DPRINTF_FUNC(); + IOMedia::handleClose(client, options); +} + +bool +ZFSDataset::attach(IOService *provider) +{ + DPRINTF_FUNC(); + return (IOMedia::attach(provider)); +} + +void +ZFSDataset::detach(IOService *provider) +{ + DPRINTF_FUNC(); + IOMedia::detach(provider); +} + +bool +ZFSDataset::start(IOService *provider) +{ + DPRINTF_FUNC(); + return (IOMedia::start(provider)); +} + +void +ZFSDataset::stop(IOService *provider) +{ + DPRINTF_FUNC(); + IOMedia::stop(provider); +} +#endif + +/* XXX Only for debug tracing */ +void +ZFSDataset::free() +{ + DPRINTF_FUNC(); + IOMedia::free(); +} + +/* + * Override init to call IOMedia init then setup properties. + */ +bool +ZFSDataset::init(UInt64 base, UInt64 size, + UInt64 preferredBlockSize, + IOMediaAttributeMask attributes, + bool isWhole, bool isWritable, + const char *contentHint, + OSDictionary *properties) +{ + OSDictionary *newProps = NULL, *deviceDict; + OSNumber *physSize, *logSize; +#if 0 + OSDictionary *protocolDict; + const OSSymbol *virtualSymbol, *internalSymbol; +#endif + bool ret; + + DPRINTF_FUNC(); + + /* Clone or create new properties dictionary */ + if (properties) newProps = OSDictionary::withDictionary(properties); + if (!newProps) newProps = OSDictionary::withCapacity(2); + + /* Allocate dictionaries, numbers, and string symbols */ + deviceDict = OSDictionary::withCapacity(2); +#if 0 + protocolDict = OSDictionary::withCapacity(2); +#endif + + physSize = OSNumber::withNumber(4096, 32); + logSize = OSNumber::withNumber(512, 32); + +#if 0 + kIOPropertyPhysicalInterconnectTypeVirtual + kIOPropertyPhysicalInterconnectTypeKey + kIOPropertyInterconnectFileKey + kIOPropertyInternalKey + kIOPropertyPhysicalInterconnectLocationKey + + kIOPropertyProtocolCharacteristicsKey + kIOPropertyMediumTypeKey + kIOPropertyLogicalBlockSizeKey + kIOPropertyPhysicalBlockSizeKey + kIOPropertyBytesPerPhysicalSectorKey + kIOPropertyDeviceCharacteristicsKey + kIOBlockStorageDeviceTypeKey + kIOBlockStorageDeviceTypeGeneric +#endif + +#if 0 + virtualSymbol = OSSymbol::withCString( + kIOPropertyPhysicalInterconnectTypeVirtual); + internalSymbol = OSSymbol::withCString( + kIOPropertyInternalKey); +#endif + + /* Validate allocations */ + if (!newProps || !deviceDict || !physSize || !logSize +#if 0 + // || !protocolDict || !virtualSymbol || !internalSymbol +#endif + ) { + dprintf("symbol allocation failed"); + OSSafeReleaseNULL(newProps); + OSSafeReleaseNULL(deviceDict); +#if 0 + OSSafeReleaseNULL(protocolDict); +#endif + OSSafeReleaseNULL(physSize); + OSSafeReleaseNULL(logSize); +#if 0 + OSSafeReleaseNULL(virtualSymbol); + OSSafeReleaseNULL(internalSymbol); +#endif + return (false); + } + + /* Setup device characteristics */ + deviceDict->setObject(kIOPropertyPhysicalBlockSizeKey, physSize); + deviceDict->setObject(kIOPropertyLogicalBlockSizeKey, logSize); + OSSafeReleaseNULL(physSize); + OSSafeReleaseNULL(logSize); + +#if 0 + /* Setup protocol characteristics */ + protocolDict->setObject(kIOPropertyPhysicalInterconnectTypeKey, + virtualSymbol); + protocolDict->setObject(kIOPropertyPhysicalInterconnectLocationKey, + internalSymbol); + OSSafeReleaseNULL(virtualSymbol); + OSSafeReleaseNULL(internalSymbol); +#endif + + /* XXX Setup required IOMedia props */ + + /* Set new device and protocol dictionaries */ + if (newProps->setObject(kIOPropertyDeviceCharacteristicsKey, + deviceDict) == false +#if 0 + // || + // newProps->setObject(kIOPropertyProtocolCharacteristicsKey, + // protocolDict) == false +#endif + ) { + dprintf("setup properties failed"); + OSSafeReleaseNULL(newProps); + OSSafeReleaseNULL(deviceDict); +#if 0 + OSSafeReleaseNULL(protocolDict); +#endif + return (false); + } + OSSafeReleaseNULL(deviceDict); +#if 0 + OSSafeReleaseNULL(protocolDict); +#endif + + /* Call IOMedia init with size and newProps */ + ret = IOMedia::init(base, size, preferredBlockSize, + attributes, isWhole, isWritable, contentHint, + newProps); + OSSafeReleaseNULL(newProps); + + if (!ret) dprintf("IOMedia init failed"); + + return (ret); + +#if 0 + /* Get current device and protocol dictionaries */ + lockForArbitration(); + oldDeviceDict = OSDynamicCast(OSDictionary, + getProperty(kIOStorageDeviceCharacteristicsKey)); + oldProtocolDict = OSDynamicCast(OSDictionary, + getProperty(kIOStorageProtocolCharacteristicsKey)); + if (oldDeviceDict) oldDeviceDict->retain(); + if (oldProtocolDict) oldProtocolDict->retain(); + unlockForArbitration(); + + /* Clone existing dictionaries */ + if (oldDeviceDict) { + newDeviceDict = OSDictionary::withDict(oldDeviceDict); + OSSafeReleaseNULL(oldDeviceDict); + } + if (oldProtocolDict) { + newProtocolDict = OSDictionary::withDict(oldProtocolDict); + OSSafeReleaseNULL(oldDeviceDict); + } + + /* Make new if missing */ + if (!newDeviceDict) + newDeviceDict = OSDictionary::withCapacity(2); + if (!newProtocolDict) + newProtocolDict = OSDictionary::withCapacity(2); + + /* Setup device characteristics */ + newDeviceDict->setObject(kIOStoragePhysicalBlocksizeKey, physSize); + newDeviceDict->setObject(kIOStorageLogicalBlocksizeKey, logSize); + OSSafeReleaseNULL(physSize); + OSSafeReleaseNULL(logSize); + + /* Setup protocol characteristics */ + newProtocolDict->setObject(kIOStorageProtocolInterconnectTypeKey, + virtualSymbol); + newProtocolDict->setObject(kIOStorageProtocolInterconnectNameKey, + internalSymbol); + OSSafeReleaseNULL(virtualSymbol); + OSSafeReleaseNULL(internalSymbol); + + /* XXX Setup required IOMedia props */ + + /* Set new device and protocol dictionaries */ + lockForArbitration(); + setProperty(kIOStorageDeviceCharacteristicsKey, newDeviceDict); + setProperty(kIOStorageProtocolCharacteristicsKey, newProtocolDict); + unlockForArbitration(); + + /* Cleanup and return success */ + OSSafeReleaseNULL(newDeviceDict); + OSSafeReleaseNULL(newProtocolDict); + return (true); +#endif +} + +/* + * Set both the IOService name and the ZFS Dataset property. + */ +bool +ZFSDataset::setDatasetName(const char *name) +{ + OSDictionary *prevDict, *newDict = NULL; + OSString *datasetString; + const char *newname; + + if (!name || name[0] == '\0') { + dprintf("missing name"); + return (false); + } + + if ((newname = strrchr(name, '/')) == NULL) { + newname = name; + } else { + /* Advance beyond slash */ + newname++; + } + +#if 0 + size_t len; + /* Length of IOMedia name plus null terminator */ + len = (strlen(kZFSIOMediaPrefix) + strlen(name) + + strlen(kZFSIOMediaSuffix) + 1); + // len = strlen("ZFS ") + strlen(name) + strlen(" Media") + 1; + + newname = (char *)kmem_alloc(len, KM_SLEEP); +#endif + datasetString = OSString::withCString(name); + +#if 0 + nameString = OSString::withCString(newname); + if (newname == NULL || nameString == NULL) { + dprintf("couldn't make name strings"); + OSSafeReleaseNULL(nameString); + if (newname) kmem_free(newname, len); + return (false); + } +#else + if (datasetString == NULL) { + dprintf("couldn't make name strings"); + return (false); + } +#endif + +#if 0 + memset(newname, 0, len); + snprintf(newname, len, "%s%s%s", kZFSIOMediaPrefix, + name, kZFSIOMediaSuffix); + + ASSERT3U(strlen(newname), ==, len-1); +#endif + + /* Lock IORegistryEntry and get current prop dict */ + lockForArbitration(); + if ((prevDict = OSDynamicCast(OSDictionary, + getProperty(kIOPropertyDeviceCharacteristicsKey))) == NULL) { + /* Unlock IORegistryEntry */ + unlockForArbitration(); + dprintf("couldn't get prop dict"); + } + prevDict->retain(); + unlockForArbitration(); + + /* Clone existing dictionary */ + if (prevDict) { + if ((newDict = OSDictionary::withDictionary(prevDict)) == + NULL) { + dprintf("couldn't clone prop dict"); + } + OSSafeReleaseNULL(prevDict); + /* Non-fatal at the moment */ + } + + /* If prevDict did not exist or couldn't be copied, make new */ + if (!newDict && (newDict = OSDictionary::withCapacity(1)) == NULL) { + dprintf("couldn't make new prop dict"); + } + + /* If we have a new or copied dict at this point */ + if (newDict) { + /* Add or replace dictionary Product Name string */ + if (newDict->setObject(kIOPropertyProductNameKey, + datasetString) == false) { + dprintf("couldn't set name"); + OSSafeReleaseNULL(datasetString); + // OSSafeReleaseNULL(nameString); + // kmem_free(newname, len); + OSSafeReleaseNULL(newDict); + return (false); + } + + /* Lock IORegistryEntry and replace prop dict */ + lockForArbitration(); + if (setProperty(kIOPropertyDeviceCharacteristicsKey, + newDict) == false) { + unlockForArbitration(); + dprintf("couldn't set name"); + OSSafeReleaseNULL(datasetString); + // OSSafeReleaseNULL(nameString); + // kmem_free(newname, len); + OSSafeReleaseNULL(newDict); + return (false); + } + unlockForArbitration(); + OSSafeReleaseNULL(newDict); + } + + /* Lock IORegistryEntry to replace property and set name */ + lockForArbitration(); + /* Assign plain ZFS Dataset name */ + setProperty(kZFSDatasetNameKey, datasetString); + /* Assign IOMedia name */ + // setName(name); + setName(newname); + + /* Unlock IORegistryEntry and cleanup allocations */ + unlockForArbitration(); + OSSafeReleaseNULL(datasetString); + // kmem_free(newname, len); + // OSSafeReleaseNULL(nameString); + return (true); +} + +#if 0 +static inline uint64_t +get_objnum(const char *name) +{ + objset_t *os = NULL; + uint64_t objnum; + int error; + + if (!name) + return (0); + + error = dmu_objset_own(name, DMU_OST_ZFS, B_TRUE, FTAG, &os); + if (error != 0) { + dprintf("couldn't open dataset %d", error); + return (0); + } + + objnum = dmu_objset_id(os); + + dmu_objset_disown(os, FTAG); + + return (objnum); +} +#endif + +/* + * Create a proxy device, name it appropriately, and return it. + */ +ZFSDataset * +ZFSDataset::withDatasetNameAndSize(const char *name, uint64_t size) +{ + ZFSDataset *dataset = NULL; + objset_t *os = NULL; + OSString *uuidStr = NULL; + OSObject *property = NULL; + char uuid_cstr[37]; + uint64_t __maybe_unused objnum, readonly, __maybe_unused guid; +#if 0 + // uint64_t ref_size, avail_size, obj_count, obj_free; +#endif + uuid_t uuid; + int error; + bool isWritable; + + DPRINTF_FUNC(); + + if (!name || name[0] == '\0') { + dprintf("missing name"); + /* Nothing allocated or retained yet */ + return (NULL); + } + memset(uuid_cstr, 0, sizeof (uuid_cstr)); + +#if 0 + OSNumber *sizeNum = NULL; + property = copyProperty(kZFSPoolSizeKey, gIOServicePlane, + kIORegistryIterateRecursively|kIORegistryIterateParents); + if (!property) { + dprintf("couldn't get pool size"); + /* Nothing allocated or retained yet */ + return (NULL); + } + if ((sizeNum = OSDynamicCast(OSNumber, property)) == NULL) { + dprintf("couldn't cast pool size"); + goto error; + } + size = sizeNum->unsigned64BitValue(); + sizeNum = NULL; + OSSafeReleaseNULL(property); +#endif + + if (zfs_vfs_uuid_gen(name, uuid) != 0) { + dprintf("UUID gen failed"); + goto error; + } + // uuid_unparse(uuid, uuid_cstr); + zfs_vfs_uuid_unparse(uuid, uuid_cstr); + // snprintf(uuid_cstr, sizeof (uuid_cstr), ""); + + uuidStr = OSString::withCString(uuid_cstr); + if (!uuidStr) { + dprintf("uuidStr alloc failed"); + goto error; + } + + dataset = new ZFSDataset; + if (!dataset) { + dprintf("allocation failed"); + goto error; + } + + /* Own the dmu objset to get properties */ + error = dmu_objset_own(name, DMU_OST_ZFS, B_TRUE, B_FALSE, FTAG, &os); + if (error != 0) { + dprintf("couldn't open dataset %d", error); + goto error; + } + + /* Get the dsl_dir to lookup object number */ + objnum = dmu_objset_id(os); + +#if 0 + dmu_objset_space(os, &ref_size, &avail_size, &obj_count, &obj_free); +#endif + + // if (os->os_dsl_dataset) + // guid = dsl_dataset_phys(os->os_dsl_dataset)->ds_guid; + guid = dmu_objset_fsid_guid(os); + // dsl_prop_get_integer(name, "guid", &guid, NULL) != 0) { + + if (dsl_prop_get_integer(name, "readonly", &readonly, NULL) != 0) { + dmu_objset_disown(os, B_FALSE, FTAG); + dprintf("get readonly property failed"); + goto error; + } + // size = (1<<30); + // isWritable = true; + dmu_objset_disown(os, B_FALSE, FTAG); + +#if 0 + size = ref_size + avail_size; +#endif + + isWritable = (readonly == 0ULL); + + if (dataset->init(/* base */ 0, size, DEV_BSIZE, + /* attributes */ 0, /* isWhole */ false, isWritable, + kZFSContentHint, /* properties */ NULL) == false) { + dprintf("init failed"); + goto error; + } + + if (dataset->setDatasetName(name) == false) { + dprintf("invalid name"); + goto error; + } + + /* Set media UUID */ + dataset->setProperty(kIOMediaUUIDKey, uuidStr); + OSSafeReleaseNULL(uuidStr); + + return (dataset); + +error: + OSSafeReleaseNULL(property); + OSSafeReleaseNULL(uuidStr); + OSSafeReleaseNULL(dataset); + return (NULL); +} + +/* + * Compatibility method simulates a read but returns all zeros. + */ +void +ZFSDataset::read(IOService *client, + UInt64 byteStart, IOMemoryDescriptor *buffer, + IOStorageAttributes *attributes, + IOStorageCompletion *completion) +{ + IOByteCount total, cur_len, done = 0; + addr64_t cur; + + DPRINTF_FUNC(); + if (!buffer) { + if (completion) complete(completion, kIOReturnInvalid, 0); + return; + } + + total = buffer->getLength(); + + /* XXX Get each physical segment of the buffer and zero it */ + while (done < total) { + cur_len = 0; + cur = buffer->getPhysicalSegment(done, &cur_len); + if (cur == 0) break; + if (cur_len != 0) bzero_phys(cur, cur_len); + done += cur_len; + ASSERT3U(done, <=, total); + } + ASSERT3U(done, ==, total); + + // if (!completion || !completion->action) { + if (!completion) { + dprintf("invalid completion"); + return; + } + +// (completion->action)(completion->target, completion->parameter, +// kIOReturnSuccess, total); + complete(completion, kIOReturnSuccess, total); +} + +/* + * Compatibility method simulates a write as a no-op. + */ +void +ZFSDataset::write(IOService *client, + UInt64 byteStart, IOMemoryDescriptor *buffer, + IOStorageAttributes *attributes, + IOStorageCompletion *completion) +{ + IOByteCount total; + DPRINTF_FUNC(); + + if (!buffer) { + if (completion) complete(completion, kIOReturnInvalid); + return; + } + + total = buffer->getLength(); + + // if (!completion || !completion->action) { + if (!completion) { + dprintf("invalid completion"); + return; + } + + /* XXX No-op, just return success */ +// (completion->action)(completion->target, completion->parameter, +// kIOReturnSuccess, total); + complete(completion, kIOReturnSuccess, total); +} + +#ifdef DEBUG +volatile SInt64 num_sync = 0; +#endif + +/* + * Compatibility method simulates a barrier sync as a no-op. + */ +#if defined(MAC_OS_X_VERSION_10_11) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) +IOReturn +ZFSDataset::synchronize(IOService *client, + UInt64 byteStart, UInt64 byteCount, + IOStorageSynchronizeOptions options) +#else +IOReturn +ZFSDataset::synchronizeCache(IOService *client) +#endif +{ +#ifdef DEBUG + SInt64 cur_sync = 0; + DPRINTF_FUNC(); + cur_sync = OSIncrementAtomic64(&num_sync); + dprintf("sync called %lld times", cur_sync); +#endif + + /* XXX Needs to report success for mount_common() */ + return (kIOReturnSuccess); +} + +/* + * Compatibility method returns failure (unsupported). + */ +IOReturn +ZFSDataset::unmap(IOService *client, + IOStorageExtent *extents, UInt32 extentsCount, +#if defined(MAC_OS_X_VERSION_10_11) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) + IOStorageUnmapOptions options) +#else + UInt32 options) +#endif +{ + DPRINTF_FUNC(); + return (kIOReturnUnsupported); +} + +/* + * Compatibility method returns failure (no result). + */ +IOStorage * +ZFSDataset::copyPhysicalExtent(IOService *client, + UInt64 *byteStart, UInt64 *byteCount) +{ + DPRINTF_FUNC(); + return (0); + // return (IOMedia::copyPhysicalExtent(client, byteStart, byteCount)); +} + +/* + * Compatibility method simulates lock as a no-op. + */ +bool +ZFSDataset::lockPhysicalExtents(IOService *client) +{ + DPRINTF_FUNC(); + // return (IOMedia::unlockPhysicalExtents(client)); + return (true); +} + +/* + * Compatibility method simulates unlock as a no-op. + */ +void +ZFSDataset::unlockPhysicalExtents(IOService *client) +{ + DPRINTF_FUNC(); + // IOMedia::unlockPhysicalExtents(client); +} + +/* + * Compatibility method returns failure (unsupported). + */ +#if defined(MAC_OS_X_VERSION_10_10) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) +IOReturn +ZFSDataset::setPriority(IOService *client, + IOStorageExtent *extents, UInt32 extentsCount, + IOStoragePriority priority) +{ + DPRINTF_FUNC(); + return (kIOReturnUnsupported); + // return (IOMedia::setPriority(client, extents, + // extentsCount, priority)); +} +#endif + +/* + * Compatibility method returns default system blocksize. + */ +UInt64 +ZFSDataset::getPreferredBlockSize() const +{ + DPRINTF_FUNC(); + return (DEV_BSIZE); + // return (IOMedia::getPreferredBlockSize()); +} + +/* XXX Only for debug tracing */ +UInt64 +ZFSDataset::getSize() const +{ + DPRINTF_FUNC(); + return (IOMedia::getSize()); +} + +/* XXX Only for debug tracing */ +UInt64 +ZFSDataset::getBase() const +{ + DPRINTF_FUNC(); + return (IOMedia::getBase()); +} + +/* XXX Only for debug tracing */ +bool +ZFSDataset::isEjectable() const +{ + DPRINTF_FUNC(); + return (IOMedia::isEjectable()); +} + +/* XXX Only for debug tracing */ +bool +ZFSDataset::isFormatted() const +{ + DPRINTF_FUNC(); + return (IOMedia::isFormatted()); +} + +/* XXX Only for debug tracing */ +bool +ZFSDataset::isWhole() const +{ + DPRINTF_FUNC(); + return (IOMedia::isWhole()); +} + +/* XXX Only for debug tracing */ +bool +ZFSDataset::isWritable() const +{ + DPRINTF_FUNC(); + return (IOMedia::isWritable()); +} + +/* XXX Only for debug tracing */ +const char * +ZFSDataset::getContent() const +{ + DPRINTF_FUNC(); + return (IOMedia::getContent()); +} + +/* XXX Only for debug tracing */ +const char * +ZFSDataset::getContentHint() const +{ + DPRINTF_FUNC(); + return (IOMedia::getContentHint()); +} + +/* XXX Only for debug tracing */ +IOMediaAttributeMask +ZFSDataset::getAttributes() const +{ + DPRINTF_FUNC(); + return (IOMedia::getAttributes()); +} diff --git a/module/os/macos/zfs/ZFSDatasetProxy.cpp b/module/os/macos/zfs/ZFSDatasetProxy.cpp new file mode 100644 index 000000000000..e1b19fa431d8 --- /dev/null +++ b/module/os/macos/zfs/ZFSDatasetProxy.cpp @@ -0,0 +1,466 @@ +/* + * 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) 2015, Evan Susarret. All rights reserved. + */ + +#include +#include +#include +#include +#include + +#define DPRINTF_FUNC() do { dprintf(""); } while (0); + +/* block size is 512 B, count is 512 M blocks */ +#define ZFS_PROXY_DEV_BSIZE (UInt64)(1<<9) +#define ZFS_PROXY_DEV_BCOUNT (UInt64)(2<<29) +#define kZFSProxyGUIDKey "ZFS Pool GUID" +#define kZFSProxyReadOnlyKey "ZFS Pool Read-Only" + +OSDefineMetaClassAndStructors(ZFSDatasetProxy, IOBlockStorageDevice); + +void +ZFSDatasetProxy::free() +{ + char *str; + + /* vendor, revision, and info share a null char */ + if (vendorString) { + str = (char *)vendorString; + vendorString = 0; + if (revisionString == str) revisionString = 0; + if (infoString == str) infoString = 0; + IOFree(str, strlen(str)+1); + } + + /* Product string contains pool name */ + if (productString) { + str = (char *)productString; + productString = 0; + IOFree(str, strlen(str)+1); + } + + IOBlockStorageDevice::free(); +} + +bool +ZFSDatasetProxy::init(OSDictionary *properties) +{ + char *str = (char *)IOMalloc(1); + + if (!str) { + dprintf("string allocation failed\n"); + return (false); + } + str[0] = '\0'; + vendorString = str; + revisionString = str; + infoString = str; + + if (IOBlockStorageDevice::init(properties) == false) { + dprintf("BlockStorageDevice start failed"); + goto error; + } + + return (true); + +error: + if (str) { + vendorString = 0; + revisionString = 0; + infoString = 0; + IOFree(str, 1); + } + return (false); +} + +bool +ZFSDatasetProxy::start(IOService *provider) +{ + OSObject *property = NULL, *size = NULL; + OSString *nameString = NULL; + OSNumber *sizeNum = NULL; + OSDictionary *deviceDict = NULL, *protocolDict = NULL; + const OSSymbol *virtualSymbol = NULL, *internalSymbol = NULL; + const char *cstr = NULL; + char *pstring = NULL; + int plen = 0; + bool started = false; + + size = copyProperty(kZFSPoolSizeKey, gIOServicePlane, + (kIORegistryIterateRecursively|kIORegistryIterateParents)); + property = copyProperty(kZFSPoolNameKey, gIOServicePlane, + (kIORegistryIterateRecursively|kIORegistryIterateParents)); + + if (!size || !property) { + dprintf("couldn't get pool name or size"); + goto error; + } + + nameString = OSDynamicCast(OSString, property); + if (!nameString) { + dprintf("missing pool name"); + goto error; + } +#if 0 + /* Try hard to get the name string */ + do { + nameString = OSDynamicCast(OSString, property); + + if (nameString) nameString->retain(); + + if (!nameString) { + OSSymbol *nameSymbol; + nameSymbol = OSDynamicCast(OSSymbol, property); + if (!nameSymbol) { + dprintf("couldn't get name"); + goto error; + } + nameString = OSString::withCString( + nameSymbol->getCStringNoCopy()); + } + } while (0); +#endif + + sizeNum = OSDynamicCast(OSNumber, size); + if (!sizeNum) { + dprintf("invalid size"); + goto error; + } + _pool_bcount = sizeNum->unsigned64BitValue() / DEV_BSIZE; + sizeNum = 0; + size->release(); + size = 0; + + cstr = nameString->getCStringNoCopy(); + if (!cstr || (plen = strlen(cstr) + 1) == 1) { + goto error; + } + pstring = (char *)IOMalloc(plen); + if (!pstring) { + goto error; + } + snprintf(pstring, plen, "%s", cstr); + productString = pstring; + pstring = 0; + + if (IOBlockStorageDevice::start(provider) == false) { + dprintf("BlockStorageDevice start failed"); + goto error; + } + started = true; + + deviceDict = OSDynamicCast(OSDictionary, + getProperty(kIOPropertyDeviceCharacteristicsKey)); + if (deviceDict) { + /* Clone a new dictionary */ + deviceDict = OSDictionary::withDictionary(deviceDict); + if (!deviceDict) { + dprintf("dict clone failed"); + goto error; + } + } + + if (!deviceDict) { + dprintf("creating new device dict"); + deviceDict = OSDictionary::withCapacity(1); + } + + if (!deviceDict) { + dprintf("missing device dict"); + goto error; + } + + deviceDict->setObject(kIOPropertyProductNameKey, nameString); + OSSafeReleaseNULL(nameString); + + if (setProperty(kIOPropertyDeviceCharacteristicsKey, + deviceDict) == false) { + dprintf("device dict setProperty failed"); + goto error; + } + OSSafeReleaseNULL(deviceDict); + + protocolDict = OSDynamicCast(OSDictionary, + getProperty(kIOPropertyProtocolCharacteristicsKey)); + if (protocolDict) { + /* Clone a new dictionary */ + protocolDict = OSDictionary::withDictionary(protocolDict); + if (!protocolDict) { + dprintf("dict clone failed"); + goto error; + } + } + + if (!protocolDict) { + dprintf("creating new protocol dict"); + protocolDict = OSDictionary::withCapacity(1); + } + + if (!protocolDict) { + dprintf("missing protocol dict"); + goto error; + } + + virtualSymbol = OSSymbol::withCString( + kIOPropertyPhysicalInterconnectTypeVirtual); + internalSymbol = OSSymbol::withCString( + kIOPropertyInternalKey); + if (!virtualSymbol || !internalSymbol) { + dprintf("symbol alloc failed"); + goto error; + } + + protocolDict->setObject(kIOPropertyPhysicalInterconnectTypeKey, + virtualSymbol); + protocolDict->setObject(kIOPropertyPhysicalInterconnectLocationKey, + internalSymbol); + + OSSafeReleaseNULL(virtualSymbol); + OSSafeReleaseNULL(internalSymbol); + + if (setProperty(kIOPropertyProtocolCharacteristicsKey, + protocolDict) == false) { + dprintf("protocol dict setProperty failed"); + goto error; + } + OSSafeReleaseNULL(protocolDict); + registerService(kIOServiceAsynchronous); + + return (true); + +error: + OSSafeReleaseNULL(size); + OSSafeReleaseNULL(property); + OSSafeReleaseNULL(deviceDict); + OSSafeReleaseNULL(protocolDict); + OSSafeReleaseNULL(nameString); + OSSafeReleaseNULL(virtualSymbol); + OSSafeReleaseNULL(internalSymbol); + if (pstring) IOFree(pstring, plen); + if (started) IOBlockStorageDevice::stop(provider); + return (false); +} + +/* XXX IOBlockStorageDevice */ +IOReturn +ZFSDatasetProxy::doSynchronizeCache(void) +{ + DPRINTF_FUNC(); + return (kIOReturnSuccess); +} + +IOReturn +ZFSDatasetProxy::doAsyncReadWrite(IOMemoryDescriptor *buffer, + UInt64 block, UInt64 nblks, + IOStorageAttributes *attributes, + IOStorageCompletion *completion) +{ + char zero[ZFS_PROXY_DEV_BSIZE]; + size_t len, cur, off = 0; + + DPRINTF_FUNC(); + + if (!buffer) { + IOStorage::complete(completion, kIOReturnError, 0); + return (kIOReturnSuccess); + } + + /* Read vs. write */ + if (buffer->getDirection() == kIODirectionIn) { + /* Zero the read buffer */ + memset(zero, 0, ZFS_PROXY_DEV_BSIZE); + len = buffer->getLength(); + while (len > 0) { + cur = (len > ZFS_PROXY_DEV_BSIZE ? + ZFS_PROXY_DEV_BSIZE : len); + buffer->writeBytes(/* offset */ off, + /* buf */ zero, /* length */ cur); + off += cur; + len -= cur; + } + // dprintf("%s: read: %llu %llu", + // __func__, block, nblks); + IOStorage::complete(completion, kIOReturnSuccess, + buffer->getLength()); + return (kIOReturnSuccess); + } + + if (buffer->getDirection() != kIODirectionOut) { + dprintf("invalid direction %d", buffer->getDirection()); + IOStorage::complete(completion, kIOReturnError, 0); + return (kIOReturnSuccess); + } + + /* + * XXX For now this just returns error for all writes. + * If it turns out that mountroot/bdevvp try to + * verify writable status by reading a block and writing + * it back to disk, lie and say it succeeded. + */ + dprintf("write: %llu %llu", block, nblks); + IOStorage::complete(completion, kIOReturnError, 0); + return (kIOReturnSuccess); +} + +IOReturn +ZFSDatasetProxy::doEjectMedia() +{ + DPRINTF_FUNC(); + /* XXX Called at shutdown, maybe return success? */ + return (kIOReturnError); +} + +IOReturn +ZFSDatasetProxy::doFormatMedia(UInt64 byteCapacity) +{ + DPRINTF_FUNC(); + /* XXX shouldn't need it */ + return (kIOReturnError); + // return (kIOReturnSuccess); +} + +UInt32 +ZFSDatasetProxy::doGetFormatCapacities(UInt64 *capacities, + UInt32 capacitiesMaxCount) const +{ + DPRINTF_FUNC(); + if (capacities && capacitiesMaxCount > 0) { + capacities[0] = (ZFS_PROXY_DEV_BSIZE * ZFS_PROXY_DEV_BCOUNT); + dprintf("capacity %llu", capacities[0]); + } + + /* Always inform caller of capacity count */ + return (1); +} + +/* Returns full pool name from instance private var */ +char * +ZFSDatasetProxy::getProductString() +{ + if (productString) dprintf("[%s]", productString); + /* Return class private string */ + return ((char *)productString); +} + +/* Returns readonly status from instance private var */ +IOReturn +ZFSDatasetProxy::reportWriteProtection(bool *isWriteProtected) +{ + DPRINTF_FUNC(); + if (isWriteProtected) *isWriteProtected = isReadOnly; + return (kIOReturnSuccess); +} + +/* These return class static string for all instances */ +char * +ZFSDatasetProxy::getVendorString() +{ + dprintf("[%s]", vendorString); + /* Return class static string */ + return ((char *)vendorString); +} +char * +ZFSDatasetProxy::getRevisionString() +{ + dprintf("[%s]", revisionString); + /* Return class static string */ + return ((char *)revisionString); +} +char * +ZFSDatasetProxy::getAdditionalDeviceInfoString() +{ + dprintf("[%s]", infoString); + /* Return class static string */ + return ((char *)infoString); +} + +/* Always return media present and unchanged */ +IOReturn +ZFSDatasetProxy::reportMediaState(bool *mediaPresent, + bool *changedState) +{ + DPRINTF_FUNC(); + if (mediaPresent) *mediaPresent = true; + if (changedState) *changedState = false; + return (kIOReturnSuccess); +} + +/* Always report nonremovable and nonejectable */ +IOReturn +ZFSDatasetProxy::reportRemovability(bool *isRemoveable) +{ + DPRINTF_FUNC(); + if (isRemoveable) *isRemoveable = false; + return (kIOReturnSuccess); +} +IOReturn +ZFSDatasetProxy::reportEjectability(bool *isEjectable) +{ + DPRINTF_FUNC(); + if (isEjectable) *isEjectable = false; + return (kIOReturnSuccess); +} + +/* Always report 512b blocksize */ +IOReturn +ZFSDatasetProxy::reportBlockSize(UInt64 *blockSize) +{ + DPRINTF_FUNC(); + if (!blockSize) + return (kIOReturnError); + + *blockSize = ZFS_PROXY_DEV_BSIZE; + return (kIOReturnSuccess); +} + +/* XXX Calculate from dev_bcount, should get size from objset */ +/* XXX Can issue message kIOMessageMediaParametersHaveChanged to update */ +IOReturn +ZFSDatasetProxy::reportMaxValidBlock(UInt64 *maxBlock) +{ + DPRINTF_FUNC(); + if (!maxBlock) + return (kIOReturnError); + + // *maxBlock = 0; + // *maxBlock = ZFS_PROXY_DEV_BCOUNT - 1; + *maxBlock = _pool_bcount - 1; + dprintf("maxBlock %llu", *maxBlock); + + return (kIOReturnSuccess); +} + +IOReturn +ZFSDatasetProxy::getWriteCacheState(bool *enabled) +{ + dprintf("getCacheState\n"); + if (enabled) *enabled = true; + return (kIOReturnSuccess); +} + +IOReturn +ZFSDatasetProxy::setWriteCacheState(bool enabled) +{ + dprintf("setWriteCache\n"); + return (kIOReturnSuccess); +} diff --git a/module/os/macos/zfs/ZFSDatasetScheme.cpp b/module/os/macos/zfs/ZFSDatasetScheme.cpp new file mode 100644 index 000000000000..14f145a89148 --- /dev/null +++ b/module/os/macos/zfs/ZFSDatasetScheme.cpp @@ -0,0 +1,1112 @@ +/* + * 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) 2015, Evan Susarret. All rights reserved. + * Copyright (c) 2017, Jorgen Lundman. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static ZFSDatasetScheme * +zfs_osx_proxy_scheme_by_osname(const char *osname) +{ + ZFSDatasetScheme *scheme = NULL; + OSDictionary *matching; + OSObject *object; + OSString *str; + OSIterator *iter; + char *pool_name, *slash; + size_t len; + + slash = strchr(osname, '/'); + if (slash) { + len = (slash - osname) + 1; + } else { + len = strlen(osname) + 1; + } + + pool_name = (char *)kmem_alloc(len, KM_SLEEP); + if (!pool_name) { + dprintf("string alloc failed"); + return (NULL); + } + snprintf(pool_name, len, "%s", osname); + dprintf("pool_name [%s] from %s", pool_name, osname); + + matching = IOService::serviceMatching(kZFSDatasetSchemeClass); + if (!matching) { + dprintf("couldn't get match dict"); + kmem_free(pool_name, len); + return (NULL); + } + + /* Add the pool name for exact match */ + str = OSString::withCString(pool_name); + matching->setObject(kZFSPoolNameKey, str); + OSSafeReleaseNULL(str); + + object = IOService::copyMatchingService(matching); + + if (object && (scheme = OSDynamicCast(ZFSDatasetScheme, + object)) == NULL) { + object->release(); + } + object = NULL; + + if (scheme && ((str = OSDynamicCast(OSString, + scheme->getProperty(kZFSPoolNameKey))) == NULL || + str->isEqualTo(pool_name) == false)) { + scheme->release(); + scheme = NULL; + } + + if (!scheme) { + int i; + for (i = 0; i < 12; i++) { // up to 6s + iter = IOService::getMatchingServices(matching); + if (iter) break; + IOSleep(500); + } + + if (i) dprintf("%s: tried %d times\n", __func__, i); + + if (!iter) { + dprintf("couldn't get iterator"); + kmem_free(pool_name, len); + OSSafeReleaseNULL(matching); + return (NULL); + } + + while ((object = iter->getNextObject())) { + if (iter->isValid() == false) { + iter->reset(); + continue; + } + scheme = OSDynamicCast(ZFSDatasetScheme, object); + if (!scheme) continue; + + object = scheme->getProperty(kZFSPoolNameKey, + gIOServicePlane, kIORegistryIterateParents | + kIORegistryIterateRecursively); + if (!object) continue; + + str = OSDynamicCast(OSString, object); + if (!str) continue; + + if (str->isEqualTo(pool_name)) break; + + str = NULL; + object = NULL; + scheme = NULL; + } + + if (scheme) scheme->retain(); + OSSafeReleaseNULL(iter); + } + OSSafeReleaseNULL(matching); + kmem_free(pool_name, len); + pool_name = 0; + + if (scheme == NULL) { + dprintf("no matching pool proxy"); + } + return (scheme); + +#if 0 + spa_t *spa; + ZFSPool *pool = 0; + + if (!osname || osname[0] == '\0') { + dprintf("missing dataset argument"); + return (EINVAL); + } + + /* Lookup the pool spa */ + mutex_enter(&spa_namespace_lock); + spa = spa_lookup(osname); + if (spa && spa->spa_iokit_proxy) { + pool = spa->spa_iokit_proxy->proxy; + if (pool) pool->retain(); + } + mutex_exit(&spa_namespace_lock); + + /* Need a pool proxy to attach to */ + if (!pool) { + dprintf("couldn't get pool proxy"); + return (EINVAL); + } + return (0); +#endif +} + +/* + * Get the proxy device by matching a property name and value. + * + * Inputs: + * property: const char string. + * value: const char string. + * + * Return: + * Pointer to proxy on success, NULL on error or missing. + */ +static ZFSDataset * +zfs_osx_proxy_lookup(const char *property, OSObject *value) +{ + OSIterator *iter = NULL; + OSDictionary *matching = NULL; + OSObject *next = NULL, *prop = NULL; + ZFSDataset *dataset = NULL; + + /* Validate arguments */ + if (!property || !value || property[0] == '\0') { + dprintf("invalid argument"); + return (NULL); + } + + /* + * Create the matching dictionary for class. + * Add property and value to match dict. + */ + matching = IOService::serviceMatching(kZFSDatasetClassKey); + if ((matching) == NULL || + (matching->setObject(property, value) == false)) { + dprintf("match dictionary create failed"); + OSSafeReleaseNULL(matching); + return (NULL); + } + + /* Try to copy if there is only one match */ + next = IOService::copyMatchingService(matching); + if (next != NULL && ((dataset = OSDynamicCast(ZFSDataset, + next)) != NULL) && + (prop = dataset->getProperty(property)) != NULL && + (prop->isEqualTo(value))) { + dprintf("quick matched dataset"); + OSSafeReleaseNULL(matching); + /* Leave retain taken by copyMatching */ + return (dataset); + } + /* Unretained references */ + prop = NULL; + dataset = NULL; + /* If set, it was retained by copyMatchingService */ + OSSafeReleaseNULL(next); + + iter = IOService::getMatchingServices(matching); + OSSafeReleaseNULL(matching); + if (iter == NULL) { + dprintf("iterator failed"); + return (NULL); + } + + while ((next = iter->getNextObject())) { + dataset = OSDynamicCast(ZFSDataset, next); + if (!dataset) continue; + + if ((prop = dataset->getProperty(property)) == NULL) { + dataset = NULL; + continue; + } + + if (prop->isEqualTo(value)) { + /* Take a reference on the match */ + dprintf("found match"); + dataset->retain(); + prop = NULL; + break; + } + + prop = NULL; + dataset = NULL; + } + /* Release iterator */ + OSSafeReleaseNULL(iter); + + /* Leave retain */ + return (dataset); +#if 0 + /* + * Copy (first) matching service. + * Cast service to proxy class. + */ + if ((service = IOService::copyMatchingService(matching)) == NULL || + (dataset = OSDynamicCast(ZFSDataset, service)) == NULL) { + dprintf("matching failed"); + OSSafeReleaseNULL(service); + return (NULL); + } + + /* Leave retain from copyMatchingService */ + return (dataset); +#endif +} + +/* + * Get the proxy device for a given dataset name. + * + * Input: + * osname: dataset name e.g. pool/dataset + * + * Return: + * Valid ZFSDataset service, or NULL on error or missing. + */ +ZFSDataset * +zfs_osx_proxy_get(const char *osname) +{ + ZFSDataset *dataset; + OSString *osstr; + + /* Validate arguments, osname is limited to MAXNAMELEN */ + if (!osname || osname[0] == '\0' || osname[0] == '/' || + strnlen(osname, MAXNAMELEN+1) == (MAXNAMELEN+1)) { + dprintf("invalid argument"); + return (NULL); + } + + osstr = OSString::withCString(osname); + if (!osstr) { + dprintf("string alloc failed"); + return (NULL); + } + + dataset = zfs_osx_proxy_lookup(kZFSDatasetNameKey, osstr); + OSSafeReleaseNULL(osstr); + + if (!dataset) { + dprintf("lookup failed"); + return (NULL); + } + + return (dataset); +} + +/* + * Get the proxy device for a given a device name or path. + * + * Input: + * devpath: BSD name as const char* string, e.g. "/dev/diskN" or "diskN" + * must be null-terminated + * + * Return: + * Valid ZFSDataset service, or NULL on error or missing. + */ +static ZFSDataset * +zfs_osx_proxy_from_devpath(const char *devpath) +{ + /* XXX No need to init, will be assigned */ + ZFSDataset *dataset; + OSString *bsdstr; + const char *bsdname; + + /* Validate arguments, devpath is limited to MAXPATHLEN */ + if (!devpath || devpath[0] == '\0' || + strnlen(devpath, MAXPATHLEN+1) == (MAXPATHLEN+1)) { + dprintf("invalid argument"); + return (NULL); + } + + /* If we have a path, remove prefix */ + if (strncmp(devpath, "/dev/", 5) == 0) { + bsdname = devpath + 5; + } else { + bsdname = devpath; + } + + /* Make sure we have (at least) "diskN" at this point */ + if (strncmp(bsdname, "disk", 4) != 0 || bsdname[4] == '\0') { + dprintf("invalid bsdname %s from %s", bsdname, devpath); + return (NULL); + } + + bsdstr = OSString::withCString(bsdname); + if (!bsdstr) { + dprintf("string alloc failed"); + return (NULL); + } + + dataset = zfs_osx_proxy_lookup(kIOBSDNameKey, bsdstr); + OSSafeReleaseNULL(bsdstr); + + if (!dataset) { + dprintf("lookup with %s failed", bsdname); + return (NULL); + } + + return (dataset); +} + +/* + * Given a dataset, get the desired property and write its + * value to the caller-supplied buffer. + * + * Inputs: + * dataset: valid ZFSDataset object, should be retained by + * caller. + * property: const char* of the desired property name key. + * value: char* buffer which should be at least 'len' bytes. + * len: length of value buffer. + * + * Return: + * 0 on success, positive int on error. + */ +static int +zfs_osx_proxy_get_prop_string(ZFSDataset *dataset, + const char *property, char *value, int len) +{ + OSObject *obj; + OSString *valueString; + + /* Validate arguments */ + if (!dataset || !property || !value || len == 0) { + dprintf("invalid argument"); + return (EINVAL); + } + + /* Lock proxy while getting property */ + dataset->lockForArbitration(); + obj = dataset->copyProperty(property); + dataset->unlockForArbitration(); + + if (!obj) { + dprintf("no property %s", property); + return (ENXIO); + } + + valueString = OSDynamicCast(OSString, obj); + /* Validate property value */ + if (!valueString) { + dprintf("couldn't cast value for %s", property); + OSSafeReleaseNULL(obj); + return (ENXIO); + } + + /* Write up to len bytes */ + snprintf(value, len, "%s", valueString->getCStringNoCopy()); + + /* Release string and proxy */ + valueString = 0; + OSSafeReleaseNULL(obj); + + return (0); +} + +extern "C" { + +/* + * Given a ZFS dataset name, get the proxy device and write the + * BSD Name to the caller-supplied buffer. + * + * Inputs: + * osname: dataset name as char* string, e.g. "pool/dataset" + * must be null-terminated + * bsdname: char* string buffer where bsdname will be written + * len: length of bsdname buffer + * + * Return: + * 0 on success, positive int errno on failure. + */ +int +zfs_osx_proxy_get_bsdname(const char *osname, + char *bsdname, int len) +{ + /* XXX No need to init, will be assigned */ + ZFSDataset *dataset; + int ret; + + /* Validate arguments */ + if (!osname || !bsdname || len == 0) { + dprintf("invalid argument"); + return (EINVAL); + } + + /* Get dataset proxy (takes a retain) */ + dataset = zfs_osx_proxy_get(osname); + if (!dataset) { + dprintf("no proxy matching %s", osname); + return (ENOENT); + } + + /* Get BSD name property and write to bsdname buffer */ + ret = zfs_osx_proxy_get_prop_string(dataset, + kIOBSDNameKey, bsdname, len); + OSSafeReleaseNULL(dataset); + + if (ret != 0) { + dprintf("ret %d", ret); + } + + return (ret); +} + +/* + * Given a device name or path, get the proxy device and write the + * ZFS Dataset name to the caller-supplied buffer. + * + * Inputs: + * devpath: BSD name as const char* string, e.g. "/dev/diskN" or "diskN" + * must be null-terminated + * osname: char* string buffer where osname will be written + * len: length of osname buffer + * + * Return: + * 0 on success, positive int errno on failure. + */ +int +zfs_osx_proxy_get_osname(const char *devpath, char *osname, int len) +{ + /* XXX No need to init, will be assigned */ + ZFSDataset *dataset; + int ret; + + /* Validate arguments */ + if (!devpath || !osname || len == 0) { + dprintf("invalid argument"); + return (EINVAL); + } + + /* Get dataset proxy (takes a retain) */ + dataset = zfs_osx_proxy_from_devpath(devpath); + if (!dataset) { + dprintf("no proxy matching %s", devpath); + return (ENOENT); + } + + /* Get dataset name property and write to osname buffer */ + ret = zfs_osx_proxy_get_prop_string(dataset, + kZFSDatasetNameKey, osname, len); + OSSafeReleaseNULL(dataset); + + if (ret != 0) { + dprintf("ret %d", ret); + } + + return (ret); +} + +/* + * Check if a dataset has a proxy device. + * + * Input: + * osname: dataset name e.g. pool/dataset + * + * Return: + * 1 if exists, 0 on error or missing. + */ +int +zfs_osx_proxy_exists(const char *osname) +{ + ZFSDataset *dataset; + + /* Get dataset proxy (takes a retain) */ + if ((dataset = zfs_osx_proxy_get(osname)) != NULL) { + OSSafeReleaseNULL(dataset); + return (1); + } + + return (0); +} + +/* + * Remove the proxy device for a given dataset name. + * + * Input: + * osname: dataset name e.g. pool/dataset + */ +void +zfs_osx_proxy_remove(const char *osname) +{ + ZFSDataset *dataset; + ZFSDatasetScheme *provider; + + /* Get dataset proxy (takes a retain) */ + dataset = zfs_osx_proxy_get(osname); + if (dataset == NULL) { + dprintf("couldn't get dataset"); + return; + } +#if 0 + /* Terminate and release retain */ + dataset->terminate(kIOServiceSynchronous | kIOServiceRequired); + OSSafeReleaseNULL(dataset); +#endif + provider = OSDynamicCast(ZFSDatasetScheme, + dataset->getProvider()); + + OSSafeReleaseNULL(dataset); + + if (!provider) { + dprintf("invalid provider"); + return; + } + + dprintf("removing %s", osname); + provider->removeDataset(osname, /* force */ true); +} + +/* + * Create a proxy device for a given dataset name, unless one exists. + * + * Input: + * osname: dataset name e.g. pool/dataset + * + * Return: + * 0 on success, or positive int on error. + */ +int +zfs_osx_proxy_create(const char *osname) +{ + ZFSDatasetScheme *provider = NULL; + + if (!osname || osname[0] == '\0') { + dprintf("missing dataset argument"); + return (EINVAL); + } + + provider = zfs_osx_proxy_scheme_by_osname(osname); + if (provider == NULL) { + dprintf("can't get pool proxy"); + return (ENOENT); + } + + if (provider->addDataset(osname) == false) { + dprintf("couldn't add dataset"); + provider->release(); + return (ENXIO); + } + + provider->release(); + return (0); +} + +} /* extern "C" */ + +static SInt32 +orderHoles(const OSMetaClassBase *obj1, const OSMetaClassBase *obj2, + __unused void *context) +{ + OSNumber *num1, *num2; + + if (obj1 == NULL || + (num1 = OSDynamicCast(OSNumber, obj1)) == NULL) { + /* Push invalid OSNumbers to end of list */ + return (-1); + } + if (obj2 == NULL || + (num2 = OSDynamicCast(OSNumber, obj2)) == NULL) { + /* If both are non-OSNumber, same ordering */ + if (num1 == NULL) + return (0); + /* If num1 is a valid OSNumber, push num2 to end */ + return (1); + } + + /* + * A comparison result of the object: + *
    + *
  • a negative value if obj2 should precede obj1,
  • + *
  • a positive value if obj1 should precede obj2,
  • + *
  • and 0 if obj1 and obj2 have an equivalent ordering.
  • + *
+ */ + if (num1->isEqualTo(num2)) + return (0); + + if (num1->unsigned32BitValue() < num2->unsigned32BitValue()) { + return (1); + } else { + return (-1); + } +} + +OSDefineMetaClassAndStructors(ZFSDatasetScheme, IOPartitionScheme); + +void +ZFSDatasetScheme::free() +{ + OSSafeReleaseNULL(_datasets); + OSSafeReleaseNULL(_holes); + _max_id = 0; + + IOPartitionScheme::free(); +} + +bool +ZFSDatasetScheme::init(OSDictionary *properties) +{ + _datasets = OSSet::withCapacity(1); + _holes = OSOrderedSet::withCapacity(1, orderHoles); + _max_id = 0; + + if (!_datasets || !_holes) { + dprintf("OSSet allocation failed"); + OSSafeReleaseNULL(_datasets); + OSSafeReleaseNULL(_holes); + return (false); + } + + OSDictionary *newProps = NULL; + if (properties) newProps = OSDictionary::withDictionary(properties); + if (!newProps) newProps = OSDictionary::withCapacity(2); + OSString *str; + str = OSString::withCString("IOGUIDPartitionScheme"); + newProps->setObject("IOClass", str); + OSSafeReleaseNULL(str); + str = OSString::withCString("GUID_partition_scheme"); + newProps->setObject("Content Mask", str); + OSSafeReleaseNULL(str); + + if (IOPartitionScheme::init(newProps) == false) { + dprintf("IOPartitionScheme init failed"); + OSSafeReleaseNULL(newProps); + OSSafeReleaseNULL(_datasets); + OSSafeReleaseNULL(_holes); + return (false); + } + OSSafeReleaseNULL(newProps); + + return (true); +} + +bool +ZFSDatasetScheme::start(IOService *provider) +{ + OSObject *pool_name; + + if (IOPartitionScheme::start(provider) == false) { + dprintf("IOPartitionScheme start failed"); + return (false); + } + + pool_name = getProperty(kZFSPoolNameKey, + gIOServicePlane, kIORegistryIterateRecursively| + kIORegistryIterateParents); + if (pool_name) { + setProperty(kZFSPoolNameKey, pool_name); + } + + // registerService(kIOServiceAsynchronous); + registerService(kIOServiceSynchronous); + + return (true); +} + +IOService * +ZFSDatasetScheme::probe(IOService *provider, SInt32 *score) +{ + OSObject *property; + IOService *parent; + + /* First ask IOPartitionScheme to probe */ + if (IOPartitionScheme::probe(provider, score) == 0) { + dprintf("IOPartitionScheme probe failed"); + return (0); + } + + /* Check for ZFS Pool Name first */ + property = getProperty(kZFSPoolNameKey, gIOServicePlane, + kIORegistryIterateRecursively|kIORegistryIterateParents); + if (!property) { + dprintf("no pool name"); + return (0); + } + + /* Make sure we have a target, and valid provider below */ + if (provider == NULL || + OSDynamicCast(IOMedia, provider) == NULL || + (parent = provider->getProvider()) == NULL) { + dprintf("invalid provider"); + return (0); + } + + /* Make sure provider is driver, and has valid provider below */ + if (OSDynamicCast(IOBlockStorageDriver, parent) == NULL || + (parent = parent->getProvider()) == NULL) { + dprintf("invalid parent"); + return (0); + } + + /* Make sure the parent provider is a proxy */ + if (OSDynamicCast(ZFSDatasetProxy, parent) == NULL) { + dprintf("invalid grandparent"); + return (0); + } + + /* Successful match */ + dprintf("Match"); + // *score = 5000; + return (this); +} + +uint32_t +ZFSDatasetScheme::getNextPartitionID() +{ + uint32_t ret_id = 0ULL; + + /* Try to lock, unless service is terminated */ + if (lockForArbitration(false) == false) { + dprintf("service is terminated"); + return (0ULL); + } + + /* If the partiton list is sparse (has holes) */ + if (_holes->getCount() != 0) { + OSNumber *id_num = OSDynamicCast(OSNumber, + _holes->getFirstObject()); + + /* Just in case the list is invalid */ +#ifdef DEBUG + if (!id_num) panic("invalid hole list"); +#endif + + if (id_num) { + id_num->retain(); + _holes->removeObject(id_num); + ret_id = id_num->unsigned32BitValue(); + OSSafeReleaseNULL(id_num); + goto out; + } + } + + /* If no holes were found, just get next id */ + ret_id = (_max_id += 1); + +out: + unlockForArbitration(); + return (ret_id); +} + +void ZFSDatasetScheme::returnPartitionID(uint32_t part_id) +{ + OSNumber *id_num = OSNumber::withNumber(part_id, 32); + + if (!id_num) dprintf("alloc failed"); + /* XXX Continue and try to decrement max_id if possible */ + + if (lockForArbitration(false) == false) { + dprintf("service is terminated"); + OSSafeReleaseNULL(id_num); + return; + } + + /* Decrementing highest part id */ + if (part_id == _max_id) { + /* First, decrement max */ + _max_id--; + /* no longer needed */ + OSSafeReleaseNULL(id_num); + + /* Now iterate down the hole list */ + while ((id_num = OSDynamicCast(OSNumber, + _holes->getLastObject()))) { + /* Only need to remove consecutive matches */ + if (id_num->unsigned32BitValue() != (_max_id)) { + break; + } + + /* Remove this num from hole list */ + id_num->retain(); + _holes->removeObject(id_num); + OSSafeReleaseNULL(id_num); + /* Decrement max */ + _max_id--; + } + /* Creating a new 'hole' in the ID namespace */ + } else { + /* Better have been able to allocate OSNum */ + if (!id_num) { + unlockForArbitration(); +#ifdef DEBUG + panic("ZFSDatasetScheme %s failed to return partID", + __func__); +#endif + return; + } + + /* + * OSOrderedSet only enforces ordering when + * using setObject(anObject) interface. + * Therefore _holes must not use setFirstObject, + * setLastObject, setObject(index, anObject) + */ + + /* Add a new OSNum to hole list */ + _holes->setObject(id_num); + OSSafeReleaseNULL(id_num); + } + + unlockForArbitration(); +} + +bool +ZFSDatasetScheme::addDataset(const char *osname) +{ + ZFSDataset *dataset; + OSObject *obj; + OSNumber *sizeNum; + char location[24]; + uint64_t size; + uint32_t part_id; + + obj = copyProperty(kZFSPoolSizeKey, gIOServicePlane, + kIORegistryIterateRecursively|kIORegistryIterateParents); + if (!obj) { + dprintf("missing pool size"); + return (false); + } + sizeNum = OSDynamicCast(OSNumber, obj); + if (!sizeNum) { + dprintf("invalid pool size"); + return (false); + } + size = sizeNum->unsigned64BitValue(); + sizeNum = 0; + OSSafeReleaseNULL(obj); + + part_id = getNextPartitionID(); + /* Only using non-zero partition ids */ + if (part_id == 0) { + dprintf("invalid partition ID"); + return (false); + } + snprintf(location, sizeof (location), "%u", part_id); + +#if 0 + OSString *locationStr; + locationStr = OSString::withCString(location); + if (!locationStr) { + dprintf("location string alloc failed"); + return (false); + } + OSSafeReleaseNULL(locationStr); +#endif + + dataset = ZFSDataset::withDatasetNameAndSize(osname, size); + if (!dataset) { + dprintf("couldn't add %s", osname); + return (false); + } + + /* Set location in plane and partiton ID property */ + dataset->setLocation(location); +#ifdef kIOMediaBaseKey + dataset->setProperty(kIOMediaBaseKey, 0ULL, 64); +#endif + dataset->setProperty(kIOMediaPartitionIDKey, part_id, 32); + + // This sets the "diskutil list -> TYPE" field + dataset->setProperty("Content", "ZFS Dataset"); + // This matches with Info.plist, so it calls zfs.util for NAME + dataset->setProperty("Content Hint", + "6A898CC3-1DD2-11B2-99A6-080020736631"); + + if (dataset->attach(this) == false) { + dprintf("attach failed"); + OSSafeReleaseNULL(dataset); + return (false); + } + + if (dataset->start(this) == false) { + dprintf("start failed"); + dataset->detach(this); + OSSafeReleaseNULL(dataset); + return (false); + } + + /* Protect the OSSet by taking IOService lock */ + lockForArbitration(); + _datasets->setObject(dataset); + unlockForArbitration(); + + // dataset->registerService(kIOServiceAsynchronous); + dataset->registerService(kIOServiceSynchronous); + + /* Adding to OSSet takes a retain */ + OSSafeReleaseNULL(dataset); + + return (true); +} + +bool +ZFSDatasetScheme::removeDataset(const char *osname, bool force) +{ + OSCollectionIterator *iter; + ZFSDataset *dataset = NULL; + OSNumber *partNum; + uint32_t part_id = 0; + bool locked; + + if ((locked = lockForArbitration(false)) == false) { + dprintf("couldn't lock terminated service"); + } + + iter = OSCollectionIterator::withCollection(_datasets); + if (!iter) { + dprintf("couldn't get dataset iterator"); + if (locked) unlockForArbitration(); + return (false); + } + + while ((dataset = OSDynamicCast(ZFSDataset, + iter->getNextObject())) != NULL) { + OSObject *property; + OSString *str; + + property = dataset->getProperty(kZFSDatasetNameKey); + if (!property) continue; + + str = OSDynamicCast(OSString, property); + if (!str) continue; + + if (str->isEqualTo(osname)) { + _datasets->removeObject(dataset); + break; + } + } + + if (!dataset) { + dprintf("couldn't get dataset"); + iter->release(); + if (locked) unlockForArbitration(); + return (false); + } + + dataset->retain(); + iter->release(); + iter = 0; + + if (locked) unlockForArbitration(); + + partNum = OSDynamicCast(OSNumber, + dataset->getProperty(kIOMediaPartitionIDKey)); + if (!partNum) { + dprintf("couldn't get partition number"); + } else { + part_id = partNum->unsigned32BitValue(); + } + + if (force) { + dataset->terminate(kIOServiceSynchronous| + kIOServiceRequired); + } else { + dataset->terminate(kIOServiceSynchronous); + } + + dataset->release(); + dataset = 0; + + /* Only return non-zero partition ids */ + if (part_id != 0) { + dprintf("terminated partition %u", part_id); + returnPartitionID(part_id); + } + + return (true); +} + +/* Compatibility shims */ +void +ZFSDatasetScheme::read(IOService *client, + UInt64 byteStart, + IOMemoryDescriptor *buffer, + IOStorageAttributes *attributes, + IOStorageCompletion *completion) +{ + IOStorage::complete(completion, kIOReturnError, 0); +} + +void +ZFSDatasetScheme::write(IOService *client, + UInt64 byteStart, + IOMemoryDescriptor *buffer, + IOStorageAttributes *attributes, + IOStorageCompletion *completion) +{ + IOStorage::complete(completion, kIOReturnError, 0); +} + +#if defined(MAC_OS_X_VERSION_10_11) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) +IOReturn +ZFSDatasetScheme::synchronize(IOService *client, + UInt64 byteStart, + UInt64 byteCount, + IOStorageSynchronizeOptions options) +#else +IOReturn +ZFSDatasetScheme::synchronizeCache(IOService *client) +#endif +{ + return (kIOReturnUnsupported); +} + +IOReturn +ZFSDatasetScheme::unmap(IOService *client, + IOStorageExtent *extents, + UInt32 extentsCount, +#if defined(MAC_OS_X_VERSION_10_11) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) + IOStorageUnmapOptions options) +#else + UInt32 options) +#endif +{ + return (kIOReturnUnsupported); +} + +bool +ZFSDatasetScheme::lockPhysicalExtents(IOService *client) +{ + return (false); +} + +IOStorage * +ZFSDatasetScheme::copyPhysicalExtent(IOService *client, + UInt64 * byteStart, + UInt64 * byteCount) +{ + return (NULL); +} + +void +ZFSDatasetScheme::unlockPhysicalExtents(IOService *client) +{ +} + +#if defined(MAC_OS_X_VERSION_10_10) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) +IOReturn +ZFSDatasetScheme::setPriority(IOService *client, + IOStorageExtent *extents, + UInt32 extentsCount, + IOStoragePriority priority) +{ + return (kIOReturnUnsupported); +} +#endif diff --git a/module/os/macos/zfs/ZFSPool.cpp b/module/os/macos/zfs/ZFSPool.cpp new file mode 100644 index 000000000000..bbdbabe7af46 --- /dev/null +++ b/module/os/macos/zfs/ZFSPool.cpp @@ -0,0 +1,867 @@ +/* + * 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) 2016, Evan Susarret. All rights reserved. + */ + +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +} /* extern "C" */ + +#include + +#define DPRINTF_FUNC() do { dprintf("%s\n", __func__); } while (0); + +#if 0 +/* block size is 512 B, count is 512 M blocks */ +#define ZFS_POOL_DEV_BSIZE (UInt64)(1<<9) +#define ZFS_POOL_DEV_BCOUNT (UInt64)(2<<29) +#endif + +/* + * Returns handle to ZFS IOService, with a retain count. + */ +static IOService * +copy_zfs_handle() +{ + /* Get the ZFS service handle the 'hard way' */ + OSDictionary *matching; + IOService *service = 0; + + matching = IOService::serviceMatching("org_openzfsonosx_zfs_zvol"); + if (matching) { + service = IOService::copyMatchingService(matching); + OSSafeReleaseNULL(matching); + } + + if (!service) { + dprintf("couldn't get zfs IOService"); + return (NULL); + } + + return (service); +#if 0 + /* Got service, make sure it casts */ + zfs_hl = OSDynamicCast(org_openzfsonosx_zfs_zvol, service); + if (zfs_hl == NULL) { + dprintf("couldn't get zfs_hl"); + /* Drop retain from copyMatchingService */ + OSSafeReleaseNULL(service); + return (NULL); + } + + return (zfs_hl); +#endif +} + +OSDefineMetaClassAndStructors(ZFSPool, IOService); + +#if 0 +bool +ZFSPool::open(IOService *client, IOOptionBits options, void *arg) +{ + bool ret; + + IOLog("ZFSPool %s\n", __func__); + + ret = IOService::open(client, options, arg); + + IOLog("ZFSPool %s ret %d\n", __func__, ret); + + return (ret); +} + +bool +ZFSPool::isOpen(const IOService *forClient) const +{ + IOLog("ZFSPool %s\n", __func__); + return (false); +} + +void +ZFSPool::close(IOService *client, IOOptionBits options) +{ + IOLog("ZFSPool %s\n", __func__); + IOService::close(client, options); +} +#endif + +bool +ZFSPool::handleOpen(IOService *client, + IOOptionBits options, void *arg) +{ + bool ret = true; + + dprintf(""); + // IOLog("ZFSPool %s\n", __func__); + + /* XXX IOService open() locks for arbitration around handleOpen */ + // lockForArbitration(); + _openClients->setObject(client); + ret = _openClients->containsObject(client); + // unlockForArbitration(); + + return (ret); +// return (IOService::handleOpen(client, options, NULL)); +} + +bool +ZFSPool::handleIsOpen(const IOService *client) const +{ + bool ret; + + dprintf(""); + // IOLog("ZFSPool %s\n", __func__); + + /* XXX IOService isOpen() locks for arbitration around handleIsOpen */ + // lockForArbitration(); + ret = _openClients->containsObject(client); + // unlockForArbitration(); + + return (ret); +// return (IOService::handleIsOpen(client)); +} + +void +ZFSPool::handleClose(IOService *client, + IOOptionBits options) +{ + dprintf(""); + // IOLog("ZFSPool %s\n", __func__); + + /* XXX IOService close() locks for arbitration around handleClose */ + // lockForArbitration(); + if (_openClients->containsObject(client) == false) { + dprintf("not open"); + } + /* Remove client from set */ + _openClients->removeObject(client); + // unlockForArbitration(); + +// IOService::handleClose(client, options); +} + +#if 0 +/* XXX IOBlockStorageDevice */ +void +ZFSPool::read(IOService *client, UInt64 byteStart, + IOMemoryDescriptor *buffer, IOStorageAttributes *attr, + IOStorageCompletion *completion) +{ + IOLog("ZFSPool %s\n", __func__); + IOStorage::complete(completion, kIOReturnError, 0); +} + +void +ZFSPool::write(IOService *client, UInt64 byteStart, + IOMemoryDescriptor *buffer, IOStorageAttributes *attr, + IOStorageCompletion *completion) +{ + IOLog("ZFSPool %s\n", __func__); + IOStorage::complete(completion, kIOReturnError, 0); +} +#endif + +bool +ZFSPool::setPoolName(const char *name) +{ +/* Assign dataset name from null-terminated string */ + OSString *dsstr; + // const OSSymbol *dsstr; +#if 0 + OSDictionary *dict; + char *newname, *oldname; +#else + char *newname; +#endif + size_t len; + + DPRINTF_FUNC(); + + /* Validate arguments */ + if (!name || (len = strnlen(name, + ZFS_MAX_DATASET_NAME_LEN)) == 0) { + dprintf("missing argument"); + return (false); + } + + /* Truncate too-long names (shouldn't happen) */ + if (len == ZFS_MAX_DATASET_NAME_LEN && + name[ZFS_MAX_DATASET_NAME_LEN] != '\0') { + dprintf("name too long [%s]", name); + /* XXX Just truncate the name */ + len--; + } + + /* Allocate room for name plus null char */ + newname = (char *)kmem_alloc(len+1, KM_SLEEP); + if (!newname) { + dprintf("string alloc failed"); + return (false); + } + snprintf(newname, len+1, "%s", name); + newname[len] = '\0'; /* just in case */ + + /* Save an OSString copy for IORegistry */ + dsstr = OSString::withCString(newname); + // dsstr = OSSymbol::withCString(newname); + + kmem_free(newname, len+1); + + if (!dsstr) { + dprintf("OSString failed"); + return (false); + } + +#if 0 + /* Swap into class private var */ + oldname = (char *)productString; + productString = newname; + newname = 0; + if (oldname) { + kmem_free(oldname, strlen(oldname)+1); + oldname = 0; + } + + /* Get and clone device characteristics prop dict */ + if ((dict = OSDynamicCast(OSDictionary, + getProperty(kIOPropertyDeviceCharacteristicsKey))) == NULL || + (dict = OSDictionary::withDictionary(dict)) == NULL) { + dprintf("couldn't clone prop dict"); + /* Should only happen during initialization */ + } + + if (dict) { + /* Copy string, add to dictionary, and replace prop dict */ + if (dict->setObject(kIOPropertyProductNameKey, + dsstr) == false || + setProperty(kIOPropertyDeviceCharacteristicsKey, + dict) == false) { + dprintf("couldn't set name"); + OSSafeReleaseNULL(dsstr); + OSSafeReleaseNULL(dict); + return (false); + } + OSSafeReleaseNULL(dict); + } +#endif + + /* Set Pool name IOregistry property */ + setProperty(kZFSPoolNameKey, dsstr); + + /* Finally, set the IORegistryEntry/IOService name */ + setName(dsstr->getCStringNoCopy()); + OSSafeReleaseNULL(dsstr); + + return (true); +} + +bool +ZFSPool::init(OSDictionary *properties, spa_t *spa) +{ +#if 0 + /* Allocate dictionaries and symbols */ + OSDictionary *pdict = OSDictionary::withCapacity(2); + OSDictionary *ddict = OSDictionary::withCapacity(4); + const OSSymbol *virtualSymbol = OSSymbol::withCString( + kIOPropertyPhysicalInterconnectTypeVirtual); + const OSSymbol *locationSymbol = OSSymbol::withCString( + kIOPropertyInternalExternalKey); + const OSSymbol *ssdSymbol = OSSymbol::withCString( + kIOPropertyMediumTypeSolidStateKey); + OSNumber *physSize = NULL, *logSize = NULL; + const OSSymbol *vendorSymbol = 0; + const OSSymbol *revisionSymbol = 0; + const OSSymbol *blankSymbol = 0; + OSBoolean *rdonly = 0; + UInt64 phys_bsize, log_bsize; + OSString *str = 0; + const char *cstr = 0; +#endif + uint64_t space; + bool ret = false; + + DPRINTF_FUNC(); + +#if 0 + physSize = OSNumber::withNumber((uint32_t)ZFS_POOL_DEV_BSIZE, 32); + logSize = OSNumber::withNumber((uint32_t)ZFS_POOL_DEV_BSIZE, 32); +#endif + if (!spa) { + dprintf("missing spa"); + goto error; + } + +#if 0 + /* Get physical and logical size from spa */ + phys_bsize = (1ULL<spa_max_ashift); + log_bsize = (1ULL<spa_min_ashift); +#endif + +#if 0 + /* Workaround glitchy behavior with large bsize in xnu */ + if (log_bsize > 8192) log_bsize = 8192; +#endif + +#if 0 + /* XXX Shouldn't be possible */ + if (log_bsize == 0) log_bsize = DEV_BSIZE; + + physSize = OSNumber::withNumber((uint32_t)phys_bsize, 32); + logSize = OSNumber::withNumber((uint32_t)log_bsize, 32); + + /* Validate allocations */ + if (!pdict || !ddict || !virtualSymbol || !locationSymbol || + !ssdSymbol || !physSize || !logSize) { + dprintf("allocation failed"); + goto error; + } +#endif + + /* Need an OSSet for open clients */ + _openClients = OSSet::withCapacity(1); + if (_openClients == NULL) { + dprintf("client OSSet failed"); + goto error; + } + + /* Set spa pointer and this Pool object's name to match */ + if (!spa) { + dprintf("missing spa"); + goto error; + } + _spa = spa; + // setName(spa_name(spa)); + +#if 0 + /* Init class statics every time an instance inits */ + /* Shared across instances, but doesn't hurt to reprint */ + if (vendorString == NULL) { + char *string; + int len = strlen("zpool")+1; + string = (char *)kmem_alloc(len, KM_SLEEP); + if (!string) goto error; + snprintf(string, len, "zpool"); + vendorString = string; + } + + if (revisionString == NULL) { + char *string; + int len = strlen("0.1")+1; + string = (char *)kmem_alloc(len, KM_SLEEP); + if (!string) goto error; + snprintf(string, len, "0.1"); + revisionString = string; + } + + if (revisionString == NULL) { + char *string; + int len = strlen("ZFS Pool")+1; + string = (char *)kmem_alloc(len, KM_SLEEP); + if (!string) goto error; + snprintf(string, len, "ZFS pool"); + infoString = string; + } + + /* For IORegistry keys, cache OSSymbols for class statics */ + /* Leverages OSSymbol cahce pool to reuse across instances */ + vendorSymbol = OSSymbol::withCString(vendorString); + revisionSymbol = OSSymbol::withCString(revisionString); + blankSymbol = OSSymbol::withCString(""); + if (!vendorSymbol || !revisionSymbol || !blankSymbol) { + dprintf("class symbols failed"); + goto error; + } +#endif + + /* Call super init */ + if (IOService::init(properties) == false) { + dprintf("device init failed"); + goto error; + } + +#if 0 + /* Set class private vars */ + productString = NULL; + isReadOnly = false; // XXX should really be true initially + + /* Set Protocol Characteristics */ + if (pdict->setObject(kIOPropertyPhysicalInterconnectLocationKey, + locationSymbol) == false || + pdict->setObject(kIOPropertyPhysicalInterconnectTypeKey, + virtualSymbol) == false) { + dprintf("pdict set properties failed"); + goto error; + } + setProperty(kIOPropertyProtocolCharacteristicsKey, pdict); + + /* Set Device Characteristics */ + if (ddict->setObject(kIOPropertyVendorNameKey, + vendorSymbol) == false || + ddict->setObject(kIOPropertyProductRevisionLevelKey, + revisionSymbol) == false || + ddict->setObject(kIOPropertyProductSerialNumberKey, + blankSymbol) == false || + ddict->setObject(kIOPropertyPhysicalBlockSizeKey, + physSize) == false || + ddict->setObject(kIOPropertyLogicalBlockSizeKey, + logSize) == false || + ddict->setObject(kIOPropertyMediumTypeKey, + ssdSymbol) == false) { + dprintf("ddict set properties failed"); + goto error; + } + setProperty(kIOPropertyDeviceCharacteristicsKey, ddict); + + /* Check for passed in readonly status */ + if (properties && (rdonly = OSDynamicCast(OSBoolean, + properties->getObject(kZFSPoolReadOnlyKey))) != NULL) { + /* Got the boolean */ + isReadOnly = rdonly->getValue(); + dprintf("set %s", (isReadOnly ? "readonly" : "readwrite")); + } + + /* Check for passed in pool GUID */ + if (properties && (str = OSDynamicCast(OSString, + properties->getObject(kZFSPoolGUIDKey))) != NULL) { + /* Got the string, try to set GUID */ + str->retain(); + if (ddict->setObject(kZFSPoolGUIDKey, str) == false) { + dprintf("couldn't set GUID"); + OSSafeReleaseNULL(str); + goto error; + } +#ifdef DEBUG + cstr = str->getCStringNoCopy(); + dprintf("set GUID"); + cstr = 0; +#endif + OSSafeReleaseNULL(str); + } +#endif + + if (setPoolName(spa_name(spa)) == false) { + dprintf("setPoolName failed"); + goto error; + } + + space = spa_get_dspace(spa); +dprintf("space %llu", space); + setProperty(kZFSPoolSizeKey, space, 64); + +#if 0 + /* Check for passed in pool name */ + if (properties && (str = OSDynamicCast(OSString, + properties->getObject(kZFSPoolNameKey))) != NULL && + (cstr = str->getCStringNoCopy()) != NULL) { + /* Got the string, try to set name */ + str->retain(); + if (setPoolName(cstr) == false) { + /* Unlikely */ + dprintf("couldn't setup pool" + " name property [%s]", cstr); + OSSafeReleaseNULL(str); + goto error; + } + + dprintf("set pool name [%s]", cstr); + OSSafeReleaseNULL(str); + } else { + if (setPoolName("invalid") == false) { + dprintf("setPoolName failed"); + goto error; + } + dprintf("set name [invalid]"); + } +#endif + + /* Success */ + ret = true; + +error: +#if 0 + /* All of these will be released on error */ + OSSafeReleaseNULL(pdict); + OSSafeReleaseNULL(ddict); + OSSafeReleaseNULL(virtualSymbol); + OSSafeReleaseNULL(locationSymbol); + OSSafeReleaseNULL(ssdSymbol); + OSSafeReleaseNULL(physSize); + OSSafeReleaseNULL(logSize); + OSSafeReleaseNULL(vendorSymbol); + OSSafeReleaseNULL(revisionSymbol); + OSSafeReleaseNULL(blankSymbol); + OSSafeReleaseNULL(str); +#endif + return (ret); +} + +void +ZFSPool::free() +{ + OSSet *oldSet; +#if 0 + char *pstring; +#endif + + if (_openClients) { + oldSet = _openClients; + _openClients = 0; + OSSafeReleaseNULL(oldSet); + } + _spa = 0; + +#if 0 + pstring = (char *)productString; + productString = 0; + if (pstring) kmem_free(pstring, strlen(pstring) + 1); +#endif + + IOService::free(); +} + +extern "C" { + +void +spa_iokit_pool_proxy_destroy(spa_t *spa) +{ + ZFSPool *proxy; + spa_iokit_t *wrapper; + + if (!spa) { + printf("missing spa"); + return; + } + + /* Get pool proxy */ + wrapper = spa->spa_iokit_proxy; + spa->spa_iokit_proxy = NULL; + + if (wrapper == NULL) { + printf("missing spa_iokit_proxy"); + return; + } + + proxy = wrapper->proxy; + + /* Free the struct */ + kmem_free(wrapper, sizeof (spa_iokit_t)); + if (!proxy) { + printf("missing proxy"); + return; + } + + if (proxy->terminate(kIOServiceSynchronous| + kIOServiceRequired) == false) { + dprintf("terminate failed"); + } + proxy->release(); + + /* + * IOService *provider; + * provider = proxy->getProvider(); + * + * proxy->detach(provider); + * proxy->stop(provider); + * + * proxy->release(); + */ +} + +int +spa_iokit_pool_proxy_create(spa_t *spa) +{ + IOService *zfs_hl; + ZFSPool *proxy; + spa_iokit_t *wrapper; + + if (!spa) { + dprintf("missing spa"); + return (EINVAL); + } + + /* Allocate C struct */ + if ((wrapper = (spa_iokit_t *)kmem_alloc(sizeof (spa_iokit_t), + KM_SLEEP)) == NULL) { + dprintf("couldn't allocate wrapper"); + return (ENOMEM); + } + + /* Get ZFS IOService */ + if ((zfs_hl = copy_zfs_handle()) == NULL) { + dprintf("couldn't get ZFS handle"); + kmem_free(wrapper, sizeof (spa_iokit_t)); + return (ENODEV); + } + + /* Allocate and init ZFS pool proxy */ + proxy = ZFSPool::withProviderAndPool(zfs_hl, spa); + if (!proxy) { + dprintf("Pool proxy creation failed"); + kmem_free(wrapper, sizeof (spa_iokit_t)); + OSSafeReleaseNULL(zfs_hl); + return (ENOMEM); + } + /* Drop retain from copy_zfs_handle */ + OSSafeReleaseNULL(zfs_hl); + + /* Set pool proxy */ + wrapper->proxy = proxy; + spa->spa_iokit_proxy = wrapper; + + return (0); +} + +} /* extern "C" */ + +ZFSPool * +ZFSPool::withProviderAndPool(IOService *zfs_hl, spa_t *spa) +{ + ZFSPool *proxy = new ZFSPool; + + if (!proxy) { + printf("allocation failed"); + return (0); + } + + if (proxy->init(0, spa) == false || + proxy->attach(zfs_hl) == false) { + printf("init/attach failed"); + OSSafeReleaseNULL(proxy); + return (0); + } + + if (proxy->start(zfs_hl) == false) { + printf("start failed"); + proxy->detach(zfs_hl); + OSSafeReleaseNULL(proxy); + return (0); + } + + /* Open zfs_hl, adding proxy to its open clients */ + // if (proxy->open(zfs_hl) == false) { + if (zfs_hl->open(proxy) == false) { + printf("open failed"); + proxy->stop(zfs_hl); + proxy->detach(zfs_hl); + OSSafeReleaseNULL(proxy); + return (0); + } + proxy->registerService(kIOServiceAsynchronous); + + return (proxy); +} + +#if 0 +/* XXX IOBlockStorageDevice */ +IOReturn +ZFSPool::doSynchronizeCache(void) +{ + dprintf(""); + return (kIOReturnSuccess); +} + +IOReturn +ZFSPool::doAsyncReadWrite(IOMemoryDescriptor *buffer, + UInt64 block, UInt64 nblks, + IOStorageAttributes *attributes, + IOStorageCompletion *completion) +{ + char zero[ZFS_POOL_DEV_BSIZE]; + size_t len, cur, off = 0; + + DPRINTF_FUNC(); + + if (!buffer) { + IOStorage::complete(completion, kIOReturnError, 0); + return (kIOReturnSuccess); + } + + /* Read vs. write */ + if (buffer->getDirection() == kIODirectionIn) { + /* Zero the read buffer */ + memset(zero, 0, ZFS_POOL_DEV_BSIZE); + len = buffer->getLength(); + while (len > 0) { + cur = (len > ZFS_POOL_DEV_BSIZE ? + ZFS_POOL_DEV_BSIZE : len); + buffer->writeBytes(/* offset */ off, + /* buf */ zero, /* length */ cur); + off += cur; + len -= cur; + } + // dprintf("read: %llu %llu", block, nblks); + IOStorage::complete(completion, kIOReturnSuccess, + buffer->getLength()); + return (kIOReturnSuccess); + } + + if (buffer->getDirection() != kIODirectionOut) { + dprintf("invalid direction %d", buffer->getDirection()); + IOStorage::complete(completion, kIOReturnError, 0); + return (kIOReturnSuccess); + } + + /* + * XXX For now this just returns error for all writes. + * If it turns out that mountroot/bdevvp try to + * verify writable status by reading a block and writing + * it back to disk, lie and say it succeeded. + */ + dprintf("write: %llu %llu", block, nblks); + IOStorage::complete(completion, kIOReturnError, 0); + return (kIOReturnSuccess); +} + +IOReturn +ZFSPool::doEjectMedia() +{ + DPRINTF_FUNC(); + /* XXX Called at shutdown, maybe return success? */ + return (kIOReturnError); +} + +IOReturn +ZFSPool::doFormatMedia(UInt64 byteCapacity) +{ + DPRINTF_FUNC(); + /* XXX shouldn't need it */ + return (kIOReturnError); + // return (kIOReturnSuccess); +} + +UInt32 +ZFSPool::doGetFormatCapacities(UInt64 *capacities, + UInt32 capacitiesMaxCount) const +{ + DPRINTF_FUNC(); + if (capacities && capacitiesMaxCount > 0) { + capacities[0] = (ZFS_POOL_DEV_BSIZE * ZFS_POOL_DEV_BCOUNT); + dprintf("capacity %llu", capacities[0]); + } + + /* Always inform caller of capacity count */ + return (1); +} + +/* Returns full pool name from instance private var */ +char * +ZFSPool::getProductString() +{ + if (productString) dprintf("[%s]", productString); + /* Return class private string */ + return ((char *)productString); +} + +/* Returns readonly status from instance private var */ +IOReturn +ZFSPool::reportWriteProtection(bool *isWriteProtected) +{ + DPRINTF_FUNC(); + if (isWriteProtected) *isWriteProtected = isReadOnly; + return (kIOReturnSuccess); +} + +/* These return class static string for all instances */ +char * +ZFSPool::getVendorString() +{ + dprintf("[%s]", vendorString); + /* Return class static string */ + return ((char *)vendorString); +} +char * +ZFSPool::getRevisionString() +{ + dprintf("[%s]", revisionString); + /* Return class static string */ + return ((char *)revisionString); +} +char * +ZFSPool::getAdditionalDeviceInfoString() +{ + dprintf("[%s]", infoString); + /* Return class static string */ + return ((char *)infoString); +} + +/* Always return media present and unchanged */ +IOReturn +ZFSPool::reportMediaState(bool *mediaPresent, + bool *changedState) +{ + DPRINTF_FUNC(); + if (mediaPresent) *mediaPresent = true; + if (changedState) *changedState = false; + return (kIOReturnSuccess); +} + +/* Always report nonremovable and nonejectable */ +IOReturn +ZFSPool::reportRemovability(bool *isRemoveable) +{ + DPRINTF_FUNC(); + if (isRemoveable) *isRemoveable = false; + return (kIOReturnSuccess); +} +IOReturn +ZFSPool::reportEjectability(bool *isEjectable) +{ + DPRINTF_FUNC(); + if (isEjectable) *isEjectable = false; + return (kIOReturnSuccess); +} + +/* Always report 512b blocksize */ +IOReturn +ZFSPool::reportBlockSize(UInt64 *blockSize) +{ + DPRINTF_FUNC(); + if (!blockSize) + return (kIOReturnError); + + *blockSize = ZFS_POOL_DEV_BSIZE; + return (kIOReturnSuccess); +} + +/* XXX Calculate from dev_bcount, should get size from objset */ +/* XXX Can issue message kIOMessageMediaParametersHaveChanged to update */ +IOReturn +ZFSPool::reportMaxValidBlock(UInt64 *maxBlock) +{ + DPRINTF_FUNC(); + if (!maxBlock) + return (kIOReturnError); + + // *maxBlock = 0; + *maxBlock = ZFS_POOL_DEV_BCOUNT - 1; + dprintf("maxBlock %llu", *maxBlock); + + return (kIOReturnSuccess); +} +#endif diff --git a/module/os/macos/zfs/abd_os.c b/module/os/macos/zfs/abd_os.c new file mode 100644 index 000000000000..952e8573666d --- /dev/null +++ b/module/os/macos/zfs/abd_os.c @@ -0,0 +1,788 @@ +/* + * 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 by Chunwei Chen. All rights reserved. + * Copyright (c) 2016 by Delphix. All rights reserved. + * Copyright (c) 2020 by Jorgen Lundman. All rights reserved. + * Copyright (c) 2021 by Sean Doran. All rights reserved. + */ + +/* + * See abd.c for a general overview of the arc buffered data (ABD). + * + * Using a large proportion of scattered ABDs decreases ARC fragmentation since + * when we are at the limit of allocatable space, using equal-size chunks will + * allow us to quickly reclaim enough space for a new large allocation (assuming + * it is also scattered). + * + * ABDs are allocated scattered by default unless the caller uses + * abd_alloc_linear() or zfs_abd_scatter_enabled is disabled. + */ + +#include +#include +#include +#include +#include +#ifdef DEBUG +#include +#endif + +typedef struct abd_stats { + kstat_named_t abdstat_struct_size; + kstat_named_t abdstat_scatter_cnt; + kstat_named_t abdstat_scatter_data_size; + kstat_named_t abdstat_scatter_chunk_waste; + kstat_named_t abdstat_linear_cnt; + kstat_named_t abdstat_linear_data_size; +} abd_stats_t; + +static abd_stats_t abd_stats = { + /* Amount of memory occupied by all of the abd_t struct allocations */ + { "struct_size", KSTAT_DATA_UINT64 }, + /* + * The number of scatter ABDs which are currently allocated, excluding + * ABDs which don't own their data (for instance the ones which were + * allocated through abd_get_offset()). + */ + { "scatter_cnt", KSTAT_DATA_UINT64 }, + /* Amount of data stored in all scatter ABDs tracked by scatter_cnt */ + { "scatter_data_size", KSTAT_DATA_UINT64 }, + /* + * The amount of space wasted at the end of the last chunk across all + * scatter ABDs tracked by scatter_cnt. + */ + { "scatter_chunk_waste", KSTAT_DATA_UINT64 }, + /* + * The number of linear ABDs which are currently allocated, excluding + * ABDs which don't own their data (for instance the ones which were + * allocated through abd_get_offset() and abd_get_from_buf()). If an + * ABD takes ownership of its buf then it will become tracked. + */ + { "linear_cnt", KSTAT_DATA_UINT64 }, + /* Amount of data stored in all linear ABDs tracked by linear_cnt */ + { "linear_data_size", KSTAT_DATA_UINT64 }, +}; + +struct { + wmsum_t abdstat_struct_size; + wmsum_t abdstat_scatter_cnt; + wmsum_t abdstat_scatter_data_size; + wmsum_t abdstat_scatter_chunk_waste; + wmsum_t abdstat_linear_cnt; + wmsum_t abdstat_linear_data_size; +} abd_sums; + +/* + * The size of the chunks ABD allocates. Because the sizes allocated from the + * kmem_cache can't change, this tunable can only be modified at boot. Changing + * it at runtime would cause ABD iteration to work incorrectly for ABDs which + * were allocated with the old size, so a safeguard has been put in place which + * will cause the machine to panic if you change it and try to access the data + * within a scattered ABD. + */ + +#if defined(__arm64__) +/* + * On ARM macOS, PAGE_SIZE is not a runtime constant! So here we have to + * guess at compile time. There a balance between fewer kmem_caches, more + * memory use by "tails" of medium-sized ABDs, and more memory use by + * accounting structures if we use 4k versus 16k. + * + * Since the original *subpage* design expected PAGE_SIZE to be constant and + * the pre-subpage ABDs used PAGE_SIZE without requiring it to be a + * compile-time constant, let's use 16k initially and adjust downwards based + * on feedback. + */ +#define ABD_PGSIZE 16384 +#else +#define ABD_PGSIZE PAGE_SIZE +#endif + +const static size_t zfs_abd_chunk_size = ABD_PGSIZE; + +kmem_cache_t *abd_chunk_cache; +static kstat_t *abd_ksp; + +/* + * Sub-ABD_PGSIZE allocations are segregated into kmem caches. This may be + * inefficient or counterproductive if in future the following conditions are + * not met. + */ +_Static_assert(SPA_MINBLOCKSHIFT == 9, "unexpected SPA_MINSBLOCKSHIFT != 9"); +_Static_assert(ISP2(ABD_PGSIZE), "ABD_PGSIZE unexpectedly non power of 2"); +_Static_assert(ABD_PGSIZE >= 4096, "ABD_PGSIZE unexpectedly smaller than 4096"); +_Static_assert(ABD_PGSIZE <= 16384, + "ABD_PGSIZE unexpectedly larger than 16384"); + +#define SUBPAGE_CACHE_INDICES (ABD_PGSIZE >> SPA_MINBLOCKSHIFT) +kmem_cache_t *abd_subpage_cache[SUBPAGE_CACHE_INDICES] = { NULL }; + +/* + * We use a scattered SPA_MAXBLOCKSIZE sized ABD whose chunks are + * just a single zero'd sized zfs_abd_chunk_size buffer. This + * allows us to conserve memory by only using a single zero buffer + * for the scatter chunks. + */ +abd_t *abd_zero_scatter = NULL; +static char *abd_zero_buf = NULL; + +static void +abd_free_chunk(void *c) +{ + kmem_cache_free(abd_chunk_cache, c); +} + +static inline size_t +abd_chunkcnt_for_bytes(size_t size) +{ + return (P2ROUNDUP(size, zfs_abd_chunk_size) / zfs_abd_chunk_size); +} + +static size_t +abd_scatter_chunkcnt(abd_t *abd) +{ + VERIFY(!abd_is_linear(abd)); + return (abd_chunkcnt_for_bytes( + ABD_SCATTER(abd).abd_offset + abd->abd_size)); +} + +boolean_t +abd_size_alloc_linear(size_t size) +{ + return (B_FALSE); +} + +void +abd_update_scatter_stats(abd_t *abd, abd_stats_op_t op) +{ + size_t n = abd_scatter_chunkcnt(abd); + ASSERT(op == ABDSTAT_INCR || op == ABDSTAT_DECR); + if (op == ABDSTAT_INCR) { + ABDSTAT_BUMP(abdstat_scatter_cnt); + ABDSTAT_INCR(abdstat_scatter_data_size, abd->abd_size); + ABDSTAT_INCR(abdstat_scatter_chunk_waste, + n * ABD_SCATTER(abd).abd_chunk_size - abd->abd_size); + } else { + ABDSTAT_BUMPDOWN(abdstat_scatter_cnt); + ABDSTAT_INCR(abdstat_scatter_data_size, -(int)abd->abd_size); + ABDSTAT_INCR(abdstat_scatter_chunk_waste, + abd->abd_size - n * ABD_SCATTER(abd).abd_chunk_size); + } +} + +void +abd_update_linear_stats(abd_t *abd, abd_stats_op_t op) +{ + ASSERT(op == ABDSTAT_INCR || op == ABDSTAT_DECR); + if (op == ABDSTAT_INCR) { + ABDSTAT_BUMP(abdstat_linear_cnt); + ABDSTAT_INCR(abdstat_linear_data_size, abd->abd_size); + } else { + ABDSTAT_BUMPDOWN(abdstat_linear_cnt); + ABDSTAT_INCR(abdstat_linear_data_size, -(int)abd->abd_size); + } +} + +void +abd_verify_scatter(abd_t *abd) +{ + /* + * There is no scatter linear pages in FreeBSD so there is an + * if an error if the ABD has been marked as a linear page. + */ + VERIFY(!abd_is_linear_page(abd)); + VERIFY3U(ABD_SCATTER(abd).abd_offset, <, + zfs_abd_chunk_size); + VERIFY3U(ABD_SCATTER(abd).abd_offset, <, + ABD_SCATTER(abd).abd_chunk_size); + VERIFY3U(ABD_SCATTER(abd).abd_chunk_size, >=, + SPA_MINBLOCKSIZE); + + size_t n = abd_scatter_chunkcnt(abd); + + if (ABD_SCATTER(abd).abd_chunk_size != ABD_PGSIZE) { + VERIFY3U(n, ==, 1); + VERIFY3U(ABD_SCATTER(abd).abd_chunk_size, <, ABD_PGSIZE); + VERIFY3U(abd->abd_size, <=, ABD_SCATTER(abd).abd_chunk_size); + } + + for (int i = 0; i < n; i++) { + VERIFY3P( + ABD_SCATTER(abd).abd_chunks[i], !=, NULL); + } +} + +static inline int +abd_subpage_cache_index(const size_t size) +{ + const int idx = size >> SPA_MINBLOCKSHIFT; + + if ((size % SPA_MINBLOCKSIZE) == 0) + return (idx - 1); + else + return (idx); +} + +static inline uint_t +abd_subpage_enclosing_size(const int i) +{ + return (SPA_MINBLOCKSIZE * (i + 1)); +} + +void +abd_alloc_chunks(abd_t *abd, size_t size) +{ + VERIFY3U(size, >, 0); + if (size <= (zfs_abd_chunk_size - SPA_MINBLOCKSIZE)) { + const int i = abd_subpage_cache_index(size); + VERIFY3S(i, >=, 0); + VERIFY3S(i, <, SUBPAGE_CACHE_INDICES); + const uint_t s = abd_subpage_enclosing_size(i); + VERIFY3U(s, >=, size); + VERIFY3U(s, <, zfs_abd_chunk_size); + void *c = kmem_cache_alloc(abd_subpage_cache[i], KM_SLEEP); + ABD_SCATTER(abd).abd_chunks[0] = c; + ABD_SCATTER(abd).abd_chunk_size = s; + } else { + const size_t n = abd_chunkcnt_for_bytes(size); + + for (int i = 0; i < n; i++) { + void *c = kmem_cache_alloc(abd_chunk_cache, KM_SLEEP); + ABD_SCATTER(abd).abd_chunks[i] = c; + } + ABD_SCATTER(abd).abd_chunk_size = zfs_abd_chunk_size; + } +} + +void +abd_free_chunks(abd_t *abd) +{ + const uint_t abd_cs = ABD_SCATTER(abd).abd_chunk_size; + + if (abd_cs <= (zfs_abd_chunk_size - SPA_MINBLOCKSIZE)) { + VERIFY3U(abd->abd_size, <, zfs_abd_chunk_size); + VERIFY0(P2PHASE(abd_cs, SPA_MINBLOCKSIZE)); + + const int idx = abd_subpage_cache_index(abd_cs); + VERIFY3S(idx, >=, 0); + VERIFY3S(idx, <, SUBPAGE_CACHE_INDICES); + + kmem_cache_free(abd_subpage_cache[idx], + ABD_SCATTER(abd).abd_chunks[0]); + } else { + const size_t n = abd_scatter_chunkcnt(abd); + for (int i = 0; i < n; i++) { + abd_free_chunk(ABD_SCATTER(abd).abd_chunks[i]); + } + } +} + +abd_t * +abd_alloc_struct_impl(size_t size) +{ + size_t chunkcnt = abd_chunkcnt_for_bytes(size); + /* + * In the event we are allocating a gang ABD, the size passed in + * will be 0. We must make sure to set abd_size to the size of an + * ABD struct as opposed to an ABD scatter with 0 chunks. The gang + * ABD struct allocation accounts for an additional 24 bytes over + * a scatter ABD with 0 chunks. + */ + size_t abd_size = MAX(sizeof (abd_t), + offsetof(abd_t, abd_u.abd_scatter.abd_chunks[chunkcnt])); + abd_t *abd = kmem_zalloc(abd_size, KM_PUSHPAGE); + ABDSTAT_INCR(abdstat_struct_size, abd_size); + + return (abd); +} + +void +abd_free_struct_impl(abd_t *abd) +{ + uint_t chunkcnt = abd_is_linear(abd) || abd_is_gang(abd) ? 0 : + abd_scatter_chunkcnt(abd); + ssize_t size = MAX(sizeof (abd_t), + offsetof(abd_t, abd_u.abd_scatter.abd_chunks[chunkcnt])); + + kmem_free(abd, size); + ABDSTAT_INCR(abdstat_struct_size, -size); +} + +/* + * Allocate scatter ABD of size SPA_MAXBLOCKSIZE, where + * each chunk in the scatterlist will be set to abd_zero_buf. + */ +static void +abd_alloc_zero_scatter(void) +{ + size_t n = abd_chunkcnt_for_bytes(SPA_MAXBLOCKSIZE); + abd_zero_buf = kmem_cache_alloc(abd_chunk_cache, KM_SLEEP); + memset(abd_zero_buf, 0, zfs_abd_chunk_size); + abd_zero_scatter = abd_alloc_struct(SPA_MAXBLOCKSIZE); + + abd_zero_scatter->abd_flags |= ABD_FLAG_OWNER | ABD_FLAG_ZEROS; + abd_zero_scatter->abd_size = SPA_MAXBLOCKSIZE; + + ABD_SCATTER(abd_zero_scatter).abd_offset = 0; + ABD_SCATTER(abd_zero_scatter).abd_chunk_size = + zfs_abd_chunk_size; + + for (int i = 0; i < n; i++) { + ABD_SCATTER(abd_zero_scatter).abd_chunks[i] = + abd_zero_buf; + } + + ABDSTAT_BUMP(abdstat_scatter_cnt); + ABDSTAT_INCR(abdstat_scatter_data_size, zfs_abd_chunk_size); +} + +static void +abd_free_zero_scatter(void) +{ + ABDSTAT_BUMPDOWN(abdstat_scatter_cnt); + ABDSTAT_INCR(abdstat_scatter_data_size, -(int)zfs_abd_chunk_size); + + abd_free_struct(abd_zero_scatter); + abd_zero_scatter = NULL; + kmem_cache_free(abd_chunk_cache, abd_zero_buf); +} + +static int +abd_kstats_update(kstat_t *ksp, int rw) +{ + abd_stats_t *as = ksp->ks_data; + + if (rw == KSTAT_WRITE) + return (EACCES); + as->abdstat_struct_size.value.ui64 = + wmsum_value(&abd_sums.abdstat_struct_size); + as->abdstat_scatter_cnt.value.ui64 = + wmsum_value(&abd_sums.abdstat_scatter_cnt); + as->abdstat_scatter_data_size.value.ui64 = + wmsum_value(&abd_sums.abdstat_scatter_data_size); + as->abdstat_scatter_chunk_waste.value.ui64 = + wmsum_value(&abd_sums.abdstat_scatter_chunk_waste); + as->abdstat_linear_cnt.value.ui64 = + wmsum_value(&abd_sums.abdstat_linear_cnt); + as->abdstat_linear_data_size.value.ui64 = + wmsum_value(&abd_sums.abdstat_linear_data_size); + return (0); +} + +void +abd_init(void) +{ + /* check if we guessed ABD_PGSIZE correctly */ + ASSERT3U(ABD_PGSIZE, ==, PAGE_SIZE); + +#ifdef DEBUG + /* + * KMF_BUFTAG | KMF_LITE on the abd kmem_caches causes them to waste + * up to 50% of their memory for redzone. Even in DEBUG builds this + * therefore should be KMC_NOTOUCH unless there are concerns about + * overruns, UAFs, etc involving abd chunks or subpage chunks. + * + * Additionally these KMF_ + * flags require the definitions from + */ + + /* + * DEBUGGING: do this + * const int cflags = KMF_BUFTAG | KMF_LITE; + * or + * const int cflags = KMC_ARENA_SLAB; + */ + + int cflags = KMC_ARENA_SLAB; +#else + int cflags = KMC_ARENA_SLAB; +#endif + +#ifdef _KERNEL +/* This must all match spl-seg_kmem.c : segkmem_abd_init() */ +#define SMALL_RAM_MACHINE (4ULL * 1024ULL * 1024ULL * 1024ULL) + + extern uint64_t total_memory; + + if (total_memory < SMALL_RAM_MACHINE) { + cflags = KMC_NOTOUCH; + } +#endif + + abd_chunk_cache = kmem_cache_create("abd_chunk", zfs_abd_chunk_size, + ABD_PGSIZE, + NULL, NULL, NULL, NULL, abd_arena, cflags); + + wmsum_init(&abd_sums.abdstat_struct_size, 0); + wmsum_init(&abd_sums.abdstat_scatter_cnt, 0); + wmsum_init(&abd_sums.abdstat_scatter_data_size, 0); + wmsum_init(&abd_sums.abdstat_scatter_chunk_waste, 0); + wmsum_init(&abd_sums.abdstat_linear_cnt, 0); + wmsum_init(&abd_sums.abdstat_linear_data_size, 0); + + abd_ksp = kstat_create("zfs", 0, "abdstats", "misc", KSTAT_TYPE_NAMED, + sizeof (abd_stats) / sizeof (kstat_named_t), KSTAT_FLAG_VIRTUAL); + if (abd_ksp != NULL) { + abd_ksp->ks_data = &abd_stats; + abd_ksp->ks_update = abd_kstats_update; + kstat_install(abd_ksp); + } + + abd_alloc_zero_scatter(); + + /* + * Check at compile time that SPA_MINBLOCKSIZE is 512, because we want + * to build sub-page-size linear ABD kmem caches at multiples of + * SPA_MINBLOCKSIZE. If SPA_MINBLOCKSIZE ever changes, a different + * layout should be calculated at runtime. + * + * See also the assertions above the definition of abd_subpbage_cache. + */ + + _Static_assert(SPA_MINBLOCKSIZE == 512, + "unexpected SPA_MINBLOCKSIZE != 512"); + + const int step_size = SPA_MINBLOCKSIZE; + for (int bytes = step_size; bytes < ABD_PGSIZE; bytes += step_size) { + char name[36]; + + (void) snprintf(name, sizeof (name), + "abd_subpage_%lu", (ulong_t)bytes); + + const int index = (bytes >> SPA_MINBLOCKSHIFT) - 1; + VERIFY3S(index, >=, 0); + VERIFY3S(index, <, SUBPAGE_CACHE_INDICES); + +#ifdef DEBUG + int csubflags = KMF_LITE; +#else + int csubflags = 0; +#endif +#ifdef _KERNEL + if (total_memory < SMALL_RAM_MACHINE) + csubflags = cflags; +#endif + abd_subpage_cache[index] = + kmem_cache_create(name, bytes, sizeof (void *), + NULL, NULL, NULL, NULL, abd_subpage_arena, csubflags); + + VERIFY3P(abd_subpage_cache[index], !=, NULL); + } +} + +void +abd_fini(void) +{ + const int step_size = SPA_MINBLOCKSIZE; + for (int bytes = step_size; bytes < ABD_PGSIZE; bytes += step_size) { + const int index = (bytes >> SPA_MINBLOCKSHIFT) - 1; + kmem_cache_destroy(abd_subpage_cache[index]); + abd_subpage_cache[index] = NULL; + } + + abd_free_zero_scatter(); + + if (abd_ksp != NULL) { + kstat_delete(abd_ksp); + abd_ksp = NULL; + } + + wmsum_fini(&abd_sums.abdstat_struct_size); + wmsum_fini(&abd_sums.abdstat_scatter_cnt); + wmsum_fini(&abd_sums.abdstat_scatter_data_size); + wmsum_fini(&abd_sums.abdstat_scatter_chunk_waste); + wmsum_fini(&abd_sums.abdstat_linear_cnt); + wmsum_fini(&abd_sums.abdstat_linear_data_size); + + kmem_cache_destroy(abd_chunk_cache); + abd_chunk_cache = NULL; +} + +void +abd_free_linear_page(abd_t *abd) +{ + /* + * FreeBSD does not have have scatter linear pages + * so there is an error. + */ + VERIFY(0); +} + +/* + * If we're going to use this ABD for doing I/O using the block layer, the + * consumer of the ABD data doesn't care if it's scattered or not, and we don't + * plan to store this ABD in memory for a long period of time, we should + * allocate the ABD type that requires the least data copying to do the I/O. + * + * Currently this is linear ABDs, however if ldi_strategy() can ever issue I/Os + * using a scatter/gather list we should switch to that and replace this call + * with vanilla abd_alloc(). + */ +abd_t * +abd_alloc_for_io(size_t size, boolean_t is_metadata) +{ + return (abd_alloc_linear(size, is_metadata)); +} + + +/* + * return an ABD structure that peers into source ABD sabd. The returned ABD + * may be new, or the one supplied as abd. abd and sabd must point to one or + * more zfs_abd_chunk_size (ABD_PGSIZE) chunks, or point to one and exactly one + * smaller chunk. + * + * The [off, off+size] range must be found within (and thus + * fit within) the source ABD. + */ + +abd_t * +abd_get_offset_scatter(abd_t *abd, abd_t *sabd, size_t off, size_t size) +{ + abd_verify(sabd); + VERIFY3U(off, <=, sabd->abd_size); + + const uint_t sabd_chunksz = ABD_SCATTER(sabd).abd_chunk_size; + + const size_t new_offset = ABD_SCATTER(sabd).abd_offset + off; + + /* subpage ABD range checking */ + if (sabd_chunksz != zfs_abd_chunk_size) { + /* off+size must fit in 1 chunk */ + VERIFY3U(off + size, <=, sabd_chunksz); + /* new_offset must be in bounds of 1 chunk */ + VERIFY3U(new_offset, <=, sabd_chunksz); + /* new_offset + size must be in bounds of 1 chunk */ + VERIFY3U(new_offset + size, <=, sabd_chunksz); + } + + /* + * chunkcnt is abd_chunkcnt_for_bytes(size), which rounds + * up to the nearest chunk, but we also must take care + * of the offset *in the leading chunk* + */ + const size_t chunkcnt = (sabd_chunksz != zfs_abd_chunk_size) + ? 1 + : abd_chunkcnt_for_bytes((new_offset % sabd_chunksz) + size); + + /* sanity checks on chunkcnt */ + VERIFY3U(chunkcnt, <=, abd_scatter_chunkcnt(sabd)); + VERIFY3U(chunkcnt, >, 0); + + /* non-subpage sanity checking */ + if (chunkcnt > 1) { + /* compare with legacy calculation of chunkcnt */ + VERIFY3U(chunkcnt, ==, abd_chunkcnt_for_bytes( + P2PHASE(new_offset, zfs_abd_chunk_size) + size)); + /* EITHER subpage chunk (singular) or std chunks */ + VERIFY3U(sabd_chunksz, ==, zfs_abd_chunk_size); + } + + /* + * If an abd struct is provided, it is only the minimum size (and + * almost certainly provided as an abd_t embedded in a larger + * structure). If we need additional chunks, we need to allocate a + * new struct. + */ + if (abd != NULL && + offsetof(abd_t, abd_u.abd_scatter.abd_chunks[chunkcnt]) > + sizeof (abd_t)) { + abd = NULL; + } + + if (abd == NULL) + abd = abd_alloc_struct(chunkcnt * sabd_chunksz); + + /* + * Even if this buf is filesystem metadata, we only track that + * if we own the underlying data buffer, which is not true in + * this case. Therefore, we don't ever use ABD_FLAG_META here. + */ + + /* update offset, and sanity check it */ + ABD_SCATTER(abd).abd_offset = new_offset % sabd_chunksz; + + VERIFY3U(ABD_SCATTER(abd).abd_offset, <, sabd_chunksz); + VERIFY3U(ABD_SCATTER(abd).abd_offset + size, <=, + chunkcnt * sabd_chunksz); + + ABD_SCATTER(abd).abd_chunk_size = sabd_chunksz; + + if (chunkcnt > 1) { + VERIFY3U(ABD_SCATTER(sabd).abd_chunk_size, ==, + zfs_abd_chunk_size); + } + + /* Copy the scatterlist starting at the correct offset */ + (void) memcpy(&ABD_SCATTER(abd).abd_chunks, + &ABD_SCATTER(sabd).abd_chunks[new_offset / + sabd_chunksz], + chunkcnt * sizeof (void *)); + + return (abd); +} + +static inline size_t +abd_iter_scatter_chunk_offset(struct abd_iter *aiter) +{ + ASSERT(!abd_is_linear(aiter->iter_abd)); + return ((ABD_SCATTER(aiter->iter_abd).abd_offset + + aiter->iter_pos) % + ABD_SCATTER(aiter->iter_abd).abd_chunk_size); +} + +static inline size_t +abd_iter_scatter_chunk_index(struct abd_iter *aiter) +{ + ASSERT(!abd_is_linear(aiter->iter_abd)); + return ((ABD_SCATTER(aiter->iter_abd).abd_offset + aiter->iter_pos) + / ABD_SCATTER(aiter->iter_abd).abd_chunk_size); +} + +/* + * Initialize the abd_iter. + */ +void +abd_iter_init(struct abd_iter *aiter, abd_t *abd) +{ + ASSERT(!abd_is_gang(abd)); + abd_verify(abd); + aiter->iter_abd = abd; + aiter->iter_pos = 0; + aiter->iter_mapaddr = NULL; + aiter->iter_mapsize = 0; +} + +/* + * This is just a helper function to see if we have exhausted the + * abd_iter and reached the end. + */ +boolean_t +abd_iter_at_end(struct abd_iter *aiter) +{ + return (aiter->iter_pos == aiter->iter_abd->abd_size); +} + +/* + * Advance the iterator by a certain amount. Cannot be called when a chunk is + * in use. This can be safely called when the aiter has already exhausted, in + * which case this does nothing. + */ +void +abd_iter_advance(struct abd_iter *aiter, size_t amount) +{ + ASSERT3P(aiter->iter_mapaddr, ==, NULL); + ASSERT0(aiter->iter_mapsize); + + /* There's nothing left to advance to, so do nothing */ + if (abd_iter_at_end(aiter)) + return; + + aiter->iter_pos += amount; +} + +/* + * Map the current chunk into aiter. This can be safely called when the aiter + * has already exhausted, in which case this does nothing. + */ +void +abd_iter_map(struct abd_iter *aiter) +{ + void *paddr; + size_t offset = 0; + + ASSERT3P(aiter->iter_mapaddr, ==, NULL); + ASSERT0(aiter->iter_mapsize); + +#if 0 + /* Panic if someone has changed zfs_abd_chunk_size */ + + IMPLY(!abd_is_linear(aiter->iter_abd), zfs_abd_chunk_size == + ABD_SCATTER(aiter->iter_abd).abd_chunk_size); +#else + /* + * If scattered, VERIFY that we are using ABD_PGSIZE chunks, or we have + * one and only one chunk of less than ABD_PGSIZE. + */ + + if (!abd_is_linear(aiter->iter_abd)) { + if (ABD_SCATTER(aiter->iter_abd).abd_chunk_size != + zfs_abd_chunk_size) { + VERIFY3U( + ABD_SCATTER(aiter->iter_abd).abd_chunk_size, + <, zfs_abd_chunk_size); + VERIFY3U(aiter->iter_abd->abd_size, + <, zfs_abd_chunk_size); + VERIFY3U(aiter->iter_abd->abd_size, + <=, ABD_SCATTER(aiter->iter_abd).abd_chunk_size); + } + } +#endif + + /* There's nothing left to iterate over, so do nothing */ + if (abd_iter_at_end(aiter)) + return; + + if (abd_is_linear(aiter->iter_abd)) { + offset = aiter->iter_pos; + aiter->iter_mapsize = aiter->iter_abd->abd_size - offset; + paddr = ABD_LINEAR_BUF(aiter->iter_abd); + } else { + size_t index = abd_iter_scatter_chunk_index(aiter); + IMPLY(ABD_SCATTER(aiter->iter_abd).abd_chunk_size != ABD_PGSIZE, + index == 0); + offset = abd_iter_scatter_chunk_offset(aiter); + aiter->iter_mapsize = MIN( + ABD_SCATTER(aiter->iter_abd).abd_chunk_size + - offset, + aiter->iter_abd->abd_size - aiter->iter_pos); + paddr = ABD_SCATTER(aiter->iter_abd).abd_chunks[index]; + } + aiter->iter_mapaddr = (char *)paddr + offset; +} + +/* + * Unmap the current chunk from aiter. This can be safely called when the aiter + * has already exhausted, in which case this does nothing. + */ +void +abd_iter_unmap(struct abd_iter *aiter) +{ + if (!abd_iter_at_end(aiter)) { + ASSERT3P(aiter->iter_mapaddr, !=, NULL); + ASSERT3U(aiter->iter_mapsize, >, 0); + } + + aiter->iter_mapaddr = NULL; + aiter->iter_mapsize = 0; +} + +void +abd_cache_reap_now(void) +{ + /* + * This function is called by arc_kmem_reap_soon(), which also invokes + * kmem_cache_reap_now() on several other kmem caches. + * + * kmem_cache_reap_now() now operates on all kmem caches at each + * invocation (ignoring its kmem_cache_t argument except for an ASSERT + * in DEBUG builds) by invoking kmem_reap(). Previously + * kmem_cache_reap_now() would clearing the caches magazine working + * set and starting a reap immediately and without regard to the + * kmem_reaping compare-and-swap flag. + * + * Previously in this function we would call kmem_cache_reap_now() for + * each of the abd_chunk and subpage kmem caches. Now, since this + * function is called after several kmem_cache_reap_now(), it + * can be a noop. + */ +} diff --git a/module/os/macos/zfs/arc_os.c b/module/os/macos/zfs/arc_os.c new file mode 100644 index 000000000000..2af34fa79141 --- /dev/null +++ b/module/os/macos/zfs/arc_os.c @@ -0,0 +1,794 @@ +/* + * 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) 2018, Joyent, Inc. + * Copyright (c) 2011, 2019 by Delphix. All rights reserved. + * Copyright (c) 2014 by Saso Kiselkov. All rights reserved. + * Copyright 2017 Nexenta Systems, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _KERNEL +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +extern arc_stats_t arc_stats; + +static kmutex_t arc_reclaim_lock; +static kcondvar_t arc_reclaim_thread_cv; +static boolean_t arc_reclaim_thread_exit; +static kcondvar_t arc_reclaim_waiters_cv; + +/* + * log2(fraction of ARC which must be free to allow growing). + * I.e. If there is less than arc_c >> arc_no_grow_shift free memory, + * when reading a new block into the ARC, we will evict an equal-sized block + * from the ARC. + * + * This must be less than arc_shrink_shift, so that when we shrink the ARC, + * we will still not allow it to grow. + */ +extern uint_t arc_no_grow_shift; + + +/* + * Return a default max arc size based on the amount of physical memory. + */ +uint64_t +arc_default_max(uint64_t min, uint64_t allmem) +{ + /* Default to 1/3 of all memory. */ + return (MAX(allmem, min)); +} + +#ifdef _KERNEL + +static _Atomic boolean_t arc_reclaim_in_loop = B_FALSE; + +/* + * Return maximum amount of memory that ARC may use. + * + * kmem_size() returns half of the system memory. + * Keep 2^(-4) of that half away from ARC for various overheads, + * and other kmem cache users. + * On a 8 GiB Mac that means 256 MiB, arc_max max under 4 GiB + * On a 128 GiB Mac that means 4 GiB, arc_max max 60 GiB + * + * Greater memory typically implies more threads and more potential I/O + * throughput, so a large reduction is prudent on a large-memory machine. + * + * Since ARC is the primary driver of memory allocation activity, this reduces + * the chances of waiting in the lowest memory allocation layers. + * + */ +uint64_t +arc_all_memory(void) +{ + const uint64_t ks = kmem_size(); + const uint64_t overhead_safety_shift = 4; + const uint64_t leave_this_much_free = ks >> overhead_safety_shift; + const uint64_t ks_minus_overhead = ks - leave_this_much_free; + + return (ks_minus_overhead); +} + +/* + * Return the amount of memory that is considered free. In user space + * which is primarily used for testing we pretend that free memory ranges + * from 0-20% of all memory. + */ +uint64_t +arc_free_memory(void) +{ + int64_t avail; + + avail = spl_free_wrapper(); + return (avail >= 0LL ? avail : 0LL); +} + +/* + * Return the amount of memory that can be consumed before reclaim will be + * needed. Positive if there is sufficient free memory, negative indicates + * the amount of memory that needs to be freed up. + */ +int64_t +arc_available_memory(void) +{ + return (arc_free_memory() - arc_sys_free); +} + +int +arc_memory_throttle(spa_t *spa, uint64_t reserve, uint64_t txg) +{ + + /* possibly wake up arc reclaim thread */ + + if (arc_reclaim_in_loop == B_FALSE) { + if (spl_free_manual_pressure_wrapper() != 0 || + !spl_minimal_physmem_p() || + arc_reclaim_needed()) { + cv_signal(&arc_reclaim_thread_cv); + kpreempt(KPREEMPT_SYNC); + ARCSTAT_INCR(arcstat_memory_throttle_count, 1); + } + } + + return (0); +} + +/* + * arc.c has a arc_reap_zthr we should probably use, instead of + * having our own legacy arc_reclaim_thread(). + */ +static void arc_kmem_reap_now(void) +{ + arc_wait_for_eviction(0, B_FALSE); + + /* arc.c will do the heavy lifting */ + arc_kmem_reap_soon(); +} + +/* + * Threads can block in arc_get_data_impl() waiting for this thread to evict + * enough data and signal them to proceed. When this happens, the threads in + * arc_get_data_impl() are sleeping while holding the hash lock for their + * particular arc header. Thus, we must be careful to never sleep on a + * hash lock in this thread. This is to prevent the following deadlock: + * + * - Thread A sleeps on CV in arc_get_data_impl() holding hash lock "L", + * waiting for the reclaim thread to signal it. + * + * - arc_reclaim_thread() tries to acquire hash lock "L" using mutex_enter, + * fails, and goes to sleep forever. + * + * This possible deadlock is avoided by always acquiring a hash lock + * using mutex_tryenter() from arc_reclaim_thread(). + */ +static void +arc_reclaim_thread(void *unused) +{ + hrtime_t growtime = 0; + + callb_cpr_t cpr; + + CALLB_CPR_INIT(&cpr, &arc_reclaim_lock, callb_generic_cpr, FTAG); + + mutex_enter(&arc_reclaim_lock); + while (!arc_reclaim_thread_exit) { + arc_reclaim_in_loop = B_TRUE; + + mutex_exit(&arc_reclaim_lock); + + const int64_t pre_adjust_free_memory = MIN(spl_free_wrapper(), + arc_available_memory()); + + int64_t manual_pressure = spl_free_manual_pressure_wrapper(); + spl_free_set_pressure(0); // clears both spl pressure variables + + /* + * We call arc_adjust() before (possibly) calling + * arc_kmem_reap_now(), so that we can wake up + * arc_get_data_impl() sooner. + */ + + if (manual_pressure > 0) { + arc_reduce_target_size(MIN(manual_pressure, + (arc_c >> arc_shrink_shift))); + } + + arc_wait_for_eviction(0, B_FALSE); + + int64_t free_memory = arc_available_memory(); + + const int64_t post_adjust_manual_pressure = + spl_free_manual_pressure_wrapper(); + + /* maybe we are getting lots of pressure from spl */ + manual_pressure = MAX(manual_pressure, + post_adjust_manual_pressure); + + spl_free_set_pressure(0); + + const int64_t post_adjust_free_memory = + MIN(spl_free_wrapper(), arc_available_memory()); + + // if arc_adjust() evicted, we expect post_adjust_free_memory + // to be larger than pre_adjust_free_memory (as there should + // be more free memory). + + /* + * d_adj tracks the change of memory across the call + * to arc_wait_for_eviction(), and will count the number + * of bytes the spl_free_thread calculates has been + * made free (signed) + */ + + const int64_t d_adj = post_adjust_free_memory - + pre_adjust_free_memory; + + if (manual_pressure > 0 && post_adjust_manual_pressure == 0) { + // pressure did not get re-signalled during arc_adjust() + if (d_adj > 0) + manual_pressure -= d_adj; + } else if (manual_pressure > 0 && + post_adjust_manual_pressure > 0) { + // otherwise use the most recent pressure value + manual_pressure = post_adjust_manual_pressure; + } + + free_memory = post_adjust_free_memory; + + const hrtime_t curtime = gethrtime(); + + if (free_memory < 0 || manual_pressure > 0) { + + if (manual_pressure > 0 || free_memory <= + (arc_c >> arc_no_grow_shift) + SPA_MAXBLOCKSIZE) { + + arc_no_grow = B_TRUE; + + /* + * Absorb occasional low memory conditions, as + * they may be caused by a single sequentially + * writing thread pushing a lot of dirty data + * into the ARC. + * + * In particular, we want to quickly begin + * re-growing the ARC if we are not in chronic + * high pressure. However, if we're in + * chronic high pressure, we want to reduce + * reclaim thread work by keeping arc_no_grow + * set. + * + * If growtime is in the past, then set it to + * last half a second (which is the length of + * the cv_timedwait_hires() call below). + * + * If growtime is in the future, then make + * sure that it is no further than 60 seconds + * into the future. + * + * If growtime is less than 60 seconds in the + * future, then grow growtime by an + * exponentially increasing value starting + * with 500msec. + * + */ + const hrtime_t agr = SEC2NSEC(arc_grow_retry); + static int grow_pass = 0; + + if (growtime == 0) { + growtime = curtime + MSEC2NSEC(500); + grow_pass = 0; + } else { + // check for 500ms not being enough + ASSERT3U(growtime, >, curtime); + if (growtime <= curtime) + growtime = curtime + + MSEC2NSEC(500); + + // growtime is in the future! + const hrtime_t difference = + growtime - curtime; + + if (difference >= agr) { + // cap arc_grow_retry secs now + growtime = curtime + agr - 1LL; + grow_pass = 0; + } else { + /* + * with each pass, push + * turning off arc_no_grow + * by longer + */ + hrtime_t grow_by = + MSEC2NSEC(500) * + (1LL << grow_pass); + + if (grow_by > (agr >> 1)) + grow_by = agr >> 1; + + growtime += grow_by; + + // add 512 seconds maximum + if (grow_pass < 10) + grow_pass++; + } + } + } + + arc_warm = B_TRUE; + + arc_kmem_reap_now(); + + /* + * If we are still low on memory, shrink the ARC + * so that we have arc_shrink_min free space. + */ + free_memory = arc_available_memory(); + + int64_t to_free = + (arc_c >> arc_shrink_shift) - free_memory; + + if (to_free > 0 || manual_pressure != 0) { + + to_free = MAX(to_free, manual_pressure); + + arc_reduce_target_size(to_free); + + goto lock_and_sleep; + } + } else if (free_memory < (arc_c >> arc_no_grow_shift) && + aggsum_value(&arc_sums.arcstat_size) > + arc_c_min + SPA_MAXBLOCKSIZE) { + // relatively low memory and arc is above arc_c_min + arc_no_grow = B_TRUE; + growtime = curtime + SEC2NSEC(1); + goto lock_and_sleep; + } + + /* + * The abd vmem layer can see a large number of + * frees from the abd kmem cache layer, and unfortunately + * the abd vmem layer might end up fragmented as a result. + * + * Watch for this fragmentation and if it arises + * suppress ARC growth for ten minutes in hopes that + * abd activity driven by ARC replacement or further ARC + * shrinking lets the abd vmem layer defragment. + */ + + if (arc_no_grow != B_TRUE) { + /* + * The gap is between imported and inuse + * in the abd vmem layer + */ + + static hrtime_t when_gap_grew = 0; + static int64_t previous_gap = 0; + static int64_t previous_abd_size = 0; + + int64_t gap = abd_arena_empty_space(); + int64_t abd_size = abd_arena_total_size(); + + if (gap == 0) { + /* + * no abd vmem layer fragmentation + * so don't adjust arc_no_grow + */ + previous_gap = 0; + previous_abd_size = abd_size; + } else if (gap > 0 && gap == previous_gap && + abd_size == previous_abd_size) { + if (curtime < when_gap_grew + SEC2NSEC(600)) { + /* + * our abd arena is unchanged + * try up to ten minutes for kmem layer + * to free slabs to abd vmem layer + */ + arc_no_grow = B_TRUE; + growtime = curtime + + SEC2NSEC(arc_grow_retry); + previous_abd_size = abd_size; + } else { + /* + * ten minutes have expired with no + * good result, shrink the arc a little, + * no more than once every + * arc_grow_retry (5) seconds + */ + arc_no_grow = B_TRUE; + growtime = curtime + + SEC2NSEC(arc_grow_retry); + previous_abd_size = abd_size; + + const int64_t sb = + arc_c >> arc_shrink_shift; + if (arc_c_min + sb > arc_c) { + arc_reduce_target_size(sb); + goto lock_and_sleep; + } + } + } else if (gap > 0 && gap > previous_gap) { + /* + * kmem layer must have freed slabs + * but vmem layer is holding on because + * of fragmentation. Don't grow ARC + * for a minute. + */ + arc_no_grow = B_TRUE; + growtime = curtime + SEC2NSEC(arc_grow_retry); + previous_gap = gap; + when_gap_grew = curtime; + /* + * but if we're growing the abd + * as well as its gap, shrink + */ + if (abd_size > previous_abd_size) { + const int64_t sb = + arc_c >> arc_shrink_shift; + if (arc_c_min + sb > arc_c) + arc_reduce_target_size(sb); + } + previous_abd_size = abd_size; + } else if (gap > 0 && gap < previous_gap) { + /* + * vmem layer successfully freeing. + */ + if (curtime < when_gap_grew + SEC2NSEC(600)) { + arc_no_grow = B_TRUE; + growtime = curtime + + SEC2NSEC(arc_grow_retry); + } + previous_gap = gap; + previous_abd_size = abd_size; + } else { + previous_abd_size = abd_size; + } + } + + if (growtime > 0 && curtime >= growtime) { + if (arc_no_grow == B_TRUE) + dprintf("ZFS: arc growtime expired\n"); + growtime = 0; + arc_no_grow = B_FALSE; + } + +lock_and_sleep: + + arc_reclaim_in_loop = B_FALSE; + + mutex_enter(&arc_reclaim_lock); + + /* + * If d_adj is non-positive, we didn't evict anything, + * perhaps because nothing was evictable. Immediately + * running another pass is unlikely to be helpful. + */ + + if (aggsum_compare(&arc_sums.arcstat_size, arc_c) <= 0 || + d_adj <= 0) { + /* + * We're either no longer overflowing, or we + * can't evict anything more, so we should wake + * up any threads before we go to sleep. + */ + cv_broadcast(&arc_reclaim_waiters_cv); + + /* + * Block until signaled, or after one second (we + * might need to perform arc_kmem_reap_now() + * even if we aren't being signalled) + */ + CALLB_CPR_SAFE_BEGIN(&cpr); + (void) cv_timedwait_hires(&arc_reclaim_thread_cv, + &arc_reclaim_lock, MSEC2NSEC(500), MSEC2NSEC(1), 0); + CALLB_CPR_SAFE_END(&cpr, &arc_reclaim_lock); + + } else if (d_adj >= SPA_MAXBLOCKSIZE * 3) { + // we evicted plenty of buffers, so let's wake up + // all the waiters rather than having them stall + cv_broadcast(&arc_reclaim_waiters_cv); + } else { + // we evicted some buffers but are still overflowing, + // so wake up only one waiter + cv_signal(&arc_reclaim_waiters_cv); + } + } + + arc_reclaim_thread_exit = B_FALSE; + cv_broadcast(&arc_reclaim_thread_cv); + CALLB_CPR_EXIT(&cpr); /* drops arc_reclaim_lock */ + thread_exit(); +} + +/* This is called before arc is initialized, and threads are not running */ +void +arc_lowmem_init(void) +{ + /* + * The ARC tries to keep at least this much memory available for the + * system. This gives the ARC time to shrink in response to memory + * pressure, before running completely out of memory and invoking the + * direct-reclaim ARC shrinker. + * + * arc_wait_for_eviction() waits for half of arc_sys_free. Bump this up + * to 3x to ensure we're above it. + */ + VERIFY3U(arc_all_memory(), >, 0); + arc_sys_free = arc_all_memory() / 128LL; + +} + +/* This is called after arc is initialized, and thread are running */ +void +arc_os_init(void) +{ + mutex_init(&arc_reclaim_lock, NULL, MUTEX_DEFAULT, NULL); + cv_init(&arc_reclaim_thread_cv, NULL, CV_DEFAULT, NULL); + cv_init(&arc_reclaim_waiters_cv, NULL, CV_DEFAULT, NULL); + + arc_reclaim_thread_exit = B_FALSE; + + (void) thread_create(NULL, 0, arc_reclaim_thread, NULL, 0, &p0, + TS_RUN, minclsyspri + 1); + + arc_warm = B_FALSE; + +} + +void +arc_lowmem_fini(void) +{ +} + +void +arc_os_fini(void) +{ + mutex_enter(&arc_reclaim_lock); + arc_reclaim_thread_exit = B_TRUE; + /* + * The reclaim thread will set arc_reclaim_thread_exit back to + * B_FALSE when it is finished exiting; we're waiting for that. + */ + while (arc_reclaim_thread_exit) { + cv_signal(&arc_reclaim_thread_cv); + cv_wait(&arc_reclaim_thread_cv, &arc_reclaim_lock); + } + mutex_exit(&arc_reclaim_lock); + + mutex_destroy(&arc_reclaim_lock); + cv_destroy(&arc_reclaim_thread_cv); + cv_destroy(&arc_reclaim_waiters_cv); +} + +/* + * Uses ARC static variables in logic. + */ +#define arc_meta_limit ARCSTAT(arcstat_meta_limit) /* max size for metadata */ +/* max size for dnodes */ +#define arc_dnode_size_limit ARCSTAT(arcstat_dnode_limit) +#define arc_meta_min ARCSTAT(arcstat_meta_min) /* min size for metadata */ +#define arc_meta_max ARCSTAT(arcstat_meta_max) /* max size of metadata */ + +/* So close, they made arc_min_prefetch_ms be static, but no others */ +#if 0 +int +arc_kstat_update_osx(kstat_t *ksp, int rw) +{ + osx_kstat_t *ks = ksp->ks_data; + boolean_t do_update = B_FALSE; + + if (rw == KSTAT_WRITE) { + + /* Did we change the value ? */ + if (ks->arc_zfs_arc_max.value.ui64 != zfs_arc_max) { + /* Assign new value */ + zfs_arc_max = ks->arc_zfs_arc_max.value.ui64; + do_update = B_TRUE; + } + + if (ks->arc_zfs_arc_min.value.ui64 != zfs_arc_min) { + zfs_arc_min = ks->arc_zfs_arc_min.value.ui64; + do_update = B_TRUE; + } + + if (ks->arc_zfs_arc_meta_limit.value.ui64 != + zfs_arc_meta_limit) { + zfs_arc_meta_limit = + ks->arc_zfs_arc_meta_limit.value.ui64; + do_update = B_TRUE; + } + + if (ks->arc_zfs_arc_meta_min.value.ui64 != zfs_arc_meta_min) { + zfs_arc_meta_min = ks->arc_zfs_arc_meta_min.value.ui64; + do_update = B_TRUE; + } + + if (zfs_arc_grow_retry != + ks->arc_zfs_arc_grow_retry.value.ui64) { + zfs_arc_grow_retry = + ks->arc_zfs_arc_grow_retry.value.ui64; + do_update = B_TRUE; + } + if (zfs_arc_shrink_shift != + ks->arc_zfs_arc_shrink_shift.value.ui64) { + zfs_arc_shrink_shift = + ks->arc_zfs_arc_shrink_shift.value.ui64; + do_update = B_TRUE; + } + if (zfs_arc_p_min_shift != + ks->arc_zfs_arc_p_min_shift.value.ui64) { + zfs_arc_p_min_shift = + ks->arc_zfs_arc_p_min_shift.value.ui64; + do_update = B_TRUE; + } + if (zfs_arc_average_blocksize != + ks->arc_zfs_arc_average_blocksize.value.ui64) { + zfs_arc_average_blocksize = + ks->arc_zfs_arc_average_blocksize.value.ui64; + do_update = B_TRUE; + } + if (zfs_arc_lotsfree_percent != + ks->zfs_arc_lotsfree_percent.value.i64) { + zfs_arc_lotsfree_percent = + ks->zfs_arc_lotsfree_percent.value.i64; + do_update = B_TRUE; + } + if (zfs_arc_sys_free != + ks->zfs_arc_sys_free.value.i64) { + zfs_arc_sys_free = + ks->zfs_arc_sys_free.value.i64; + do_update = B_TRUE; + } + + if (do_update) + arc_tuning_update(B_TRUE); + } else { + + ks->arc_zfs_arc_max.value.ui64 = zfs_arc_max; + ks->arc_zfs_arc_min.value.ui64 = zfs_arc_min; + + ks->arc_zfs_arc_meta_limit.value.ui64 = zfs_arc_meta_limit; + ks->arc_zfs_arc_meta_min.value.ui64 = zfs_arc_meta_min; + + ks->arc_zfs_arc_grow_retry.value.ui64 = + zfs_arc_grow_retry ? zfs_arc_grow_retry : arc_grow_retry; + ks->arc_zfs_arc_shrink_shift.value.ui64 = zfs_arc_shrink_shift; + ks->arc_zfs_arc_p_min_shift.value.ui64 = zfs_arc_p_min_shift; + ks->arc_zfs_arc_average_blocksize.value.ui64 = + zfs_arc_average_blocksize; + ks->zfs_arc_lotsfree_percent.value.i64 = + zfs_arc_lotsfree_percent; + ks->zfs_arc_sys_free.value.i64 = + zfs_arc_sys_free; + } + return (0); +} +#endif + +/* + * Helper function for arc_prune_async() it is responsible for safely + * handling the execution of a registered arc_prune_func_t. + */ +static void +arc_prune_task(void *ptr) +{ + arc_prune_t *ap = (arc_prune_t *)ptr; + arc_prune_func_t *func = ap->p_pfunc; + + if (func != NULL) + func(ap->p_adjust, ap->p_private); + + zfs_refcount_remove(&ap->p_refcnt, func); +} + +/* + * Notify registered consumers they must drop holds on a portion of the ARC + * buffered they reference. This provides a mechanism to ensure the ARC can + * honor the arc_meta_limit and reclaim otherwise pinned ARC buffers. This + * is analogous to dnlc_reduce_cache() but more generic. + * + * This operation is performed asynchronously so it may be safely called + * in the context of the arc_reclaim_thread(). A reference is taken here + * for each registered arc_prune_t and the arc_prune_task() is responsible + * for releasing it once the registered arc_prune_func_t has completed. + */ +void +arc_prune_async(uint64_t adjust) +{ + arc_prune_t *ap; + + mutex_enter(&arc_prune_mtx); + for (ap = list_head(&arc_prune_list); ap != NULL; + ap = list_next(&arc_prune_list, ap)) { + + if (zfs_refcount_count(&ap->p_refcnt) >= 2) + continue; + + zfs_refcount_add(&ap->p_refcnt, ap->p_pfunc); + ap->p_adjust = adjust; + if (taskq_dispatch(arc_prune_taskq, arc_prune_task, + ap, TQ_SLEEP) == TASKQID_INVALID) { + zfs_refcount_remove(&ap->p_refcnt, ap->p_pfunc); + continue; + } + ARCSTAT_BUMP(arcstat_prune); + } + mutex_exit(&arc_prune_mtx); +} + +#else /* from above ifdef _KERNEL */ + +int64_t +arc_available_memory(void) +{ + return (arc_free_memory() - arc_sys_free); +} + +int +arc_memory_throttle(spa_t *spa, uint64_t reserve, uint64_t txg) +{ + return (0); +} + +uint64_t +arc_all_memory(void) +{ + return (ptob(physmem) / 2); +} + +uint64_t +arc_free_memory(void) +{ + int64_t avail; + + avail = spl_free_wrapper(); + return (avail >= 0LL ? avail : 0LL); +} +#endif /* KERNEL */ + +void +arc_register_hotplug(void) +{ +} + +void +arc_unregister_hotplug(void) +{ +} + +void +spl_set_arc_no_grow(int i) +{ + arc_no_grow = i; + if (i == B_TRUE) + membar_producer(); /* make it visible to other threads */ +} diff --git a/module/os/macos/zfs/ldi_iokit.cpp b/module/os/macos/zfs/ldi_iokit.cpp new file mode 100644 index 000000000000..9f23a32426dd --- /dev/null +++ b/module/os/macos/zfs/ldi_iokit.cpp @@ -0,0 +1,2058 @@ +/* + * 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) 1994, 2010, Oracle and/or its affiliates. All rights reserved. + */ +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +/* + * Copyright (c) 2013, Joyent, Inc. All rights reserved. + */ +/* + * Copyright (c) 2015, Evan Susarret. All rights reserved. + */ +/* + * Portions of this document are copyright Oracle and Joyent. + * OS X implementation of ldi_ named functions for ZFS written by + * Evan Susarret in 2015. + */ + +/* Quiet some noisy build warnings */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winconsistent-missing-override" +#pragma GCC diagnostic ignored "-Wdeprecated-register" + +/* + * Apple IOKit (c++) + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * ZFS internal + */ +#include + +/* + * LDI Includes + */ +#include + + +/* Debug prints */ + +/* Attach created IOService objects to the IORegistry under ZFS. */ +// #define LDI_IOREGISTRY_ATTACH + +/* + * Globals + */ +static IOService *ldi_zfs_handle; + +/* Exposed to c callers */ +extern "C" { + +struct _handle_iokit { + IOMedia *media; + IOService *client; +}; /* 16b */ + +struct _handle_notifier { + IONotifier *obj; +}; /* 8b */ + +#define LH_MEDIA(lhp) lhp->lh_tsd.iokit_tsd->media +#define LH_CLIENT(lhp) lhp->lh_tsd.iokit_tsd->client +#define LH_NOTIFIER(lhp) lhp->lh_notifier->obj + +void +handle_free_iokit(struct ldi_handle *lhp) { + if (!lhp) { + dprintf("%s missing lhp\n", __func__); + return; + } + + if (!lhp->lh_tsd.iokit_tsd) { + dprintf("%s missing iokit_tsd\n", __func__); + return; + } + + /* Free IOService client */ + if (handle_free_ioservice(lhp) != 0) { + dprintf("%s lhp %p client %s\n", + __func__, lhp, "couldn't be removed"); + } + + kmem_free(lhp->lh_tsd.iokit_tsd, sizeof (struct _handle_iokit)); + lhp->lh_tsd.iokit_tsd = 0; +} + +/* Returns handle with lock still held */ +struct ldi_handle * +handle_alloc_iokit(dev_t device, int fmode) +{ + struct ldi_handle *lhp, *retlhp; + + /* Search for existing handle */ + if ((retlhp = handle_find(device, fmode, B_TRUE)) != NULL) { + dprintf("%s found handle before alloc\n", __func__); + return (retlhp); + } + + /* Allocate an LDI IOKit handle */ + if ((lhp = handle_alloc_common(LDI_TYPE_IOKIT, device, + fmode)) == NULL) { + dprintf("%s couldn't allocate handle\n", __func__); + return (NULL); + } + + /* Allocate and clear type-specific device data */ + lhp->lh_tsd.iokit_tsd = (struct _handle_iokit *)kmem_alloc( + sizeof (struct _handle_iokit), KM_SLEEP); + LH_MEDIA(lhp) = 0; + LH_CLIENT(lhp) = 0; + + /* Allocate an IOService client for open/close */ + if (handle_alloc_ioservice(lhp) != 0) { + dprintf("%s couldn't allocate IOService client\n", __func__); + handle_release(lhp); + return (NULL); + } + + /* Add the handle to the list, or return match */ + if ((retlhp = handle_add(lhp)) == NULL) { + dprintf("%s handle_add failed\n", __func__); + handle_release(lhp); + return (NULL); + } + + /* Check if new or found handle was returned */ + if (retlhp != lhp) { + dprintf("%s found handle after alloc\n", __func__); + handle_release(lhp); + lhp = 0; + } + + return (retlhp); +} + +int +handle_free_ioservice(struct ldi_handle *lhp) +{ + /* Validate handle pointer */ + ASSERT3U(lhp, !=, NULL); +#ifdef DEBUG + if (!lhp) { + dprintf("%s missing handle\n", __func__); + return (EINVAL); + } + if (!LH_CLIENT(lhp)) { + dprintf("%s missing client\n", __func__); + return (ENODEV); + } +#endif + +#ifdef LDI_IOREGISTRY_ATTACH + /* Detach client from ZFS in IORegistry */ + LH_CLIENT(lhp)->detach(ldi_zfs_handle); +#endif + + LH_CLIENT(lhp)->stop(ldi_zfs_handle); + LH_CLIENT(lhp)->release(); + LH_CLIENT(lhp) = 0; + + return (0); +} + +int +handle_alloc_ioservice(struct ldi_handle *lhp) +{ + IOService *client; + + /* Validate handle pointer */ + ASSERT3U(lhp, !=, NULL); + if (lhp == NULL) { + dprintf("%s missing handle\n", __func__); + return (EINVAL); + } + + /* Allocate and init an IOService client for open/close */ + if ((client = new IOService) == NULL) { + dprintf("%s couldn't allocate new IOService\n", __func__); + return (ENOMEM); + } + if (client->init(0) != true) { + dprintf("%s IOService init failed\n", __func__); + client->release(); + return (ENOMEM); + } + +#ifdef LDI_IOREGISTRY_ATTACH + /* Attach client to ZFS in IORegistry */ + if (client->attach(ldi_zfs_handle) != true) { + dprintf("%s IOService attach failed\n", __func__); + client->release(); + return (ENOMEM); + } +#endif + + /* Start service */ + if (client->start(ldi_zfs_handle) != true) { + dprintf("%s IOService attach failed\n", __func__); + /* Detach client from ZFS in IORegistry */ +#ifdef LDI_IOREGISTRY_ATTACH + client->detach(ldi_zfs_handle); +#endif + client->release(); + return (ENOMEM); + } + + LH_CLIENT(lhp) = client; + return (0); +} + +/* Set status to Offline and post event */ +static bool +handle_media_terminate_cb(void* target, void* refCon, + IOService* newService, IONotifier* notifier) +{ + struct ldi_handle *lhp = (struct ldi_handle *)refCon; + +#ifdef DEBUG + if (!lhp) { + dprintf("%s missing refCon ldi_handle\n", __func__); + return (false); + } +#endif + + /* Take hold on handle to prevent removal */ + handle_hold(lhp); + + dprintf("%s setting lhp %p to Offline status\n", __func__, lhp); + if (handle_status_change(lhp, LDI_STATUS_OFFLINE) != 0) { + dprintf("%s handle_status_change failed\n", __func__); + handle_release(lhp); + return (false); + } + + handle_release(lhp); + return (true); +} + +int +handle_close_iokit(struct ldi_handle *lhp) +{ +#ifdef DEBUG + ASSERT3U(lhp, !=, NULL); + ASSERT3U(lhp->lh_type, ==, LDI_TYPE_IOKIT); + ASSERT3U(lhp->lh_status, ==, LDI_STATUS_CLOSING); + + /* Validate IOMedia and IOService client */ + if (!OSDynamicCast(IOMedia, LH_MEDIA(lhp)) || + !OSDynamicCast(IOService, LH_CLIENT(lhp))) { + dprintf("%s invalid IOMedia or client\n", __func__); + return (ENODEV); + } +#endif /* DEBUG */ + + LH_MEDIA(lhp)->close(LH_CLIENT(lhp)); + LH_MEDIA(lhp) = 0; + return (0); +} + +static int +handle_open_iokit(struct ldi_handle *lhp, IOMedia *media) +{ +#ifdef DEBUG + ASSERT3U(lhp, !=, NULL); + ASSERT3U(media, !=, NULL); + ASSERT3U(lhp->lh_type, ==, LDI_TYPE_IOKIT); + ASSERT3U(lhp->lh_status, ==, LDI_STATUS_OPENING); + + /* Validate IOMedia and IOService client */ + if (!OSDynamicCast(IOMedia, media) || + !OSDynamicCast(IOService, LH_CLIENT(lhp))) { + dprintf("%s invalid IOMedia or client\n", __func__); + return (ENODEV); + } +#endif /* DEBUG */ + /* Retain until open or error */ + media->retain(); + + /* + * If read/write mode is requested, check that the + * device is actually writeable. + */ + if (lhp->lh_fmode & FWRITE && media->isWritable() == false) { + dprintf("%s read-write requested on %s\n", + __func__, "read-only IOMedia"); + media->release(); + return (EPERM); + } + + /* Call open with the IOService client handle */ + if (media->IOMedia::open(LH_CLIENT(lhp), 0, + (lhp->lh_fmode & FWRITE ? kIOStorageAccessReaderWriter : + kIOStorageAccessReader)) == false) { + dprintf("%s IOMedia->open failed\n", __func__); + media->release(); + return (EIO); + } + media->release(); + + /* Assign IOMedia device */ + LH_MEDIA(lhp) = media; + return (0); +} + +int +handle_get_size_iokit(struct ldi_handle *lhp, uint64_t *dev_size) +{ + if (!lhp || !dev_size) { + dprintf("%s missing lhp or dev_size\n", __func__); + return (EINVAL); + } + +#ifdef DEBUG + /* Validate IOMedia */ + if (!OSDynamicCast(IOMedia, LH_MEDIA(lhp))) { + dprintf("%s no IOMedia\n", __func__); + return (ENODEV); + } +#endif + + *dev_size = LH_MEDIA(lhp)->getSize(); + if (*dev_size == 0) { + dprintf("%s %s\n", __func__, + "IOMedia getSize returned 0"); + return (EINVAL); + } + + return (0); +} + +int +handle_get_dev_path_iokit(struct ldi_handle *lhp, + char *path, int len) +{ + int retlen = len; + + if (!lhp || !path || len == 0) { + dprintf("%s missing argument\n", __func__); + return (EINVAL); + } + +#ifdef DEBUG + /* Validate IOMedia */ + if (!OSDynamicCast(IOMedia, LH_MEDIA(lhp))) { + dprintf("%s no IOMedia\n", __func__); + return (ENODEV); + } +#endif + + if (LH_MEDIA(lhp)->getPath(path, &retlen, gIODTPlane) == false) { + dprintf("%s getPath failed\n", __func__); + return (EIO); + } + +dprintf("%s got path [%s]\n", __func__, path); + return (0); +} + +int handle_get_bootinfo_iokit(struct ldi_handle *lhp, + struct io_bootinfo *bootinfo) +{ + int error = 0; + + if (!lhp || !bootinfo) { + dprintf("%s missing argument\n", __func__); +printf("%s missing argument\n", __func__); + return (EINVAL); + } + + if ((error = handle_get_size_iokit(lhp, + &bootinfo->dev_size)) != 0 || + (error = handle_get_dev_path_iokit(lhp, bootinfo->dev_path, + sizeof (bootinfo->dev_path))) != 0) { + dprintf("%s get size or dev_path error %d\n", + __func__, error); + } + + return (error); +} + +int +handle_sync_iokit(struct ldi_handle *lhp) +{ +#ifdef DEBUG + /* Validate IOMedia and client */ + if (!OSDynamicCast(IOMedia, LH_MEDIA(lhp)) || + !OSDynamicCast(IOService, LH_CLIENT(lhp))) { + dprintf("%s invalid IOMedia or client\n", __func__); + return (ENODEV); + } +#endif + +#if defined(MAC_OS_X_VERSION_10_11) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) + /* from module/os/macos/zfs/zfs_vfsops.c */ + extern uint64_t zfs_iokit_sync_paranoia; + /* + * Issue device sync + * + * We can try to issue a Barrier synch here, which is likely to be + * faster, but also is not supported by all devices. + * + */ + IOStorageSynchronizeOptions synctype = (zfs_iokit_sync_paranoia != 0) + ? kIOStorageSynchronizeOptionNone + : kIOStorageSynchronizeOptionBarrier; + IOReturn ret = LH_MEDIA(lhp)->synchronize(LH_CLIENT(lhp), + 0, 0, synctype); + if (ret != kIOReturnSuccess) { + printf("%s %s %d %s\n", __func__, + "IOMedia synchronizeCache (with write barrier) failed", + ret, "(see IOReturn.h)\n"); + return (ENOTSUP); + } +#else + /* Issue device sync */ + if (LH_MEDIA(lhp)->synchronizeCache(LH_CLIENT(lhp)) != + kIOReturnSuccess) { + printf("%s %s\n", __func__, + "IOMedia synchronizeCache failed"); + return (ENOTSUP); + } +#endif + + /* Success */ + return (0); +} + +static dev_t +dev_from_media(IOMedia *media) +{ + OSObject *property; + OSNumber *number; + uint32_t major, minor; + dev_t device = 0; + + /* Validate media */ + if (!media || !OSDynamicCast(IOMedia, media)) { + dprintf("%s no device\n", __func__); + return (0); + } + media->retain(); + + /* Get device major */ + if (NULL == (property = media->getProperty(kIOBSDMajorKey, + gIOServicePlane, kIORegistryIterateRecursively)) || + NULL == (number = OSDynamicCast(OSNumber, property))) { + dprintf("%s couldn't get BSD major\n", __func__); + media->release(); + return (0); + } + major = number->unsigned32BitValue(); + number = NULL; + property = NULL; + + /* Get device minor */ + if (NULL == (property = media->getProperty(kIOBSDMinorKey, + gIOServicePlane, kIORegistryIterateRecursively)) || + NULL == (number = OSDynamicCast(OSNumber, property))) { + dprintf("%s couldn't get BSD major\n", __func__); + media->release(); + return (0); + } + minor = number->unsigned32BitValue(); + number = NULL; + property = NULL; + + /* Cleanup */ + media->release(); + media = NULL; + + device = makedev(major, minor); + + /* Return 0 or valid dev_t */ + return (device); +} + +/* Returns NULL or dictionary with a retain count */ +static OSDictionary * +media_matchdict_from_dev(dev_t device) +{ + OSDictionary *matchDict; + OSNumber *majorNum, *minorNum; + + /* Validate dev_t */ + if (device == 0) { + dprintf("%s no dev_t provided\n", __func__); + return (NULL); + } + + /* Allocate OSNumbers for BSD major and minor (32-bit) */ + if (NULL == (majorNum = OSNumber::withNumber(major(device), 32)) || + NULL == (minorNum = OSNumber::withNumber(minor(device), 32))) { + dprintf("%s couldn't alloc major/minor as OSNumber\n", + __func__); + if (majorNum) { + majorNum->release(); + } + return (NULL); + } + + /* Match on IOMedia */ + if (NULL == (matchDict = IOService::serviceMatching("IOMedia")) || + !(matchDict->setObject(kIOBSDMajorKey, majorNum)) || + !(matchDict->setObject(kIOBSDMinorKey, minorNum))) { + dprintf("%s couldn't get matching dictionary\n", __func__); + if (matchDict) { + matchDict->release(); + } + majorNum->release(); + minorNum->release(); + return (NULL); + } + majorNum->release(); + minorNum->release(); + + /* Return NULL or valid OSDictionary with retain count */ + return (matchDict); +} + +/* Returns NULL or dictionary with a retain count */ +/* + * media_matchdict_from_path + * translate from paths of the form /dev/diskNsN + * or /private/var/run/disk/by-id/media- to a matching + * dictionary. + */ +static OSDictionary * +media_matchdict_from_path(const char *path) +{ + OSDictionary *matchDict = 0; + OSString *bsdName = NULL; + OSString *uuid = NULL; + const char *substr = 0; + bool ret; + + /* Validate path */ + if (path == 0 || strlen(path) <= 1) { + dprintf("%s no path provided\n", __func__); + return (NULL); + } + /* Translate /dev/diskN and InvariantDisks paths */ + if (strncmp(path, "/dev/", 5) != 0 && + strncmp(path, "/var/run/disk/by-id/", 20) != 0 && + strncmp(path, "/private/var/run/disk/by-id/", 28) != 0) { + dprintf("%s Unrecognized path %s\n", __func__, path); + return (NULL); + } + + /* Validate path and alloc bsdName */ + if (strncmp(path, "/dev/", 5) == 0) { + + /* substr starts after '/dev/' */ + substr = path + 5; + /* Get diskN from /dev/diskN or /dev/rdiskN */ + if (strncmp(substr, "disk", 4) == 0) { + bsdName = OSString::withCString(substr); + } else if (strncmp(substr, "rdisk", 5) == 0) { + bsdName = OSString::withCString(substr + 1); + } + } else if (strncmp(path, "/var/run/disk/by-id/", 20) == 0 || + strncmp(path, "/private/var/run/disk/by-id/", 28) == 0) { + /* InvariantDisks paths */ + + /* substr starts after '/by-id/' */ + substr = path + 20; + if (strncmp(path, "/private", 8) == 0) substr += 8; + + /* Handle media UUID, skip volume UUID or device GUID */ + if (strncmp(substr, "media-", 6) == 0) { + /* Lookup IOMedia with UUID */ + uuid = OSString::withCString(substr+strlen("media-")); + } else if (strncmp(substr, "volume-", 7) == 0) { + /* + * volume-UUID is specified by DiskArbitration + * when a Filesystem bundle is able to probe + * the media and retrieve/generate a UUID for + * it's contents. + * So while we could use this and have zfs.util + * probe for vdev GUID (and pool GUID) and + * generate a UUID, we would need to do the same + * here to find the disk, possibly probing + * devices to get the vdev GUID in the process. + */ + dprintf("%s Unsupported volume-UUID path %s\n", + __func__, path); + } else if (strncmp(substr, "device-", 7) == 0) { + /* Lookup IOMedia with device GUID */ + /* + * XXX Not sure when this is used, no devices + * seem to be presented this way. + */ + dprintf("%s Unsupported device-GUID path %s\n", + __func__, path); + } else { + dprintf("%s unrecognized path %s\n", __func__, path); + } + /* by-path and by-serial are handled separately */ + } + + if (!bsdName && !uuid) { + dprintf("%s Invalid path %s\n", __func__, path); + return (NULL); + } + + /* Match on IOMedia by BSD disk name */ + matchDict = IOService::serviceMatching("IOMedia"); + if (!matchDict) { + dprintf("%s couldn't get matching dictionary\n", __func__); + if (bsdName) bsdName->release(); + if (uuid) uuid->release(); + return (NULL); + } + if (bsdName) { + ret = matchDict->setObject(kIOBSDNameKey, bsdName); + + if (!ret) { + dprintf("%s couldn't setup bsd name matching" + " dictionary\n", __func__); + matchDict->release(); + matchDict = 0; + } + if (uuid) uuid->release(); + } else if (uuid) { + if (matchDict->setObject(kIOMediaUUIDKey, uuid) == false) { + dprintf("%s couldn't setup UUID matching" + " dictionary\n", __func__); + matchDict->release(); + matchDict = 0; + } + } else { + dprintf("%s missing matching property\n", __func__); + matchDict->release(); + matchDict = 0; + } + + if (bsdName) bsdName->release(); + if (uuid) uuid->release(); + + /* Return NULL or valid OSDictionary with retain count */ + return (matchDict); +} + +/* Returns NULL or matched IOMedia with a retain count */ +static IOMedia * +media_from_matchdict(OSDictionary *matchDict) +{ + OSIterator *iter = 0; + OSObject *obj = 0; + IOMedia *media = 0; + + if (!matchDict) { + dprintf("%s missing matching dictionary\n", __func__); + return (NULL); + } + + /* + * We could instead use copyMatchingService, since + * there should only be one match. + */ + iter = IOService::getMatchingServices(matchDict); + if (!iter) { + dprintf("%s No iterator from getMatchingServices\n", + __func__); + return (NULL); + } + + /* Get first object from iterator */ + while ((obj = iter->getNextObject()) != NULL) { + if ((media = OSDynamicCast(IOMedia, obj)) == NULL) { + obj = 0; + continue; + } + if (media->isFormatted() == false) { + obj = 0; + media = 0; + continue; + } + + media->retain(); + break; + } + + if (!media) { + dprintf("%s no match found\n", __func__); + iter->release(); + return (NULL); + } + +#ifdef DEBUG + /* Report if there were additional matches */ + if (iter->getNextObject() != NULL) { + dprintf("%s Had more potential matches\n", __func__); + } +#endif + iter->release(); + iter = 0; + + /* Return valid IOMedia with retain count */ + return (media); +} + +/* + * media_from_dev is intended to be called by ldi_open_by_name + * and ldi_open_by_dev with a dev_t, and returns NULL or an IOMedia + * device with a retain count that should be released on open. + */ +static IOMedia * +media_from_dev(dev_t device = 0) +{ + IOMedia *media; + OSDictionary *matchDict; + + /* Get matchDict, will need to be released */ + matchDict = media_matchdict_from_dev(device); + if (!matchDict) { + dprintf("%s couldn't get matching dictionary\n", __func__); + return (NULL); + } + + /* Get first matching IOMedia */ + media = media_from_matchdict(matchDict); + matchDict->release(); + matchDict = 0; + + if (!media) { + dprintf("%s no IOMedia found for dev_t %d\n", __func__, + device); + } + + /* Return NULL or valid media with retain count */ + return (media); +} + +/* + * media_from_device_path + * + * translate /private/var/run/disk/by-path/ to an IOMedia + * handle. The remainder of the path should be a valid + * path in the IORegistry IODTPlane device tree. + */ +static IOMedia * +media_from_device_path(const char *path = 0) +{ + IORegistryEntry *entry = 0; + IOMedia *media = 0; + OSString *osstr; + const char *string, *dash; + + /* Must be /var/run/disk/by-path/, but may have /private prefix */ + if (!path || path[0] == 0 || + (strncmp(path, "/var/run/disk/by-path/", 22) != 0 && + strncmp(path, "/private/var/run/disk/by-path/", 30) != 0)) { + dprintf("%s invalid path [%s]\n", __func__, + (path && path[0] != '\0' ? path : "")); + return (NULL); + } + + /* We need the leading slash in the string, so trim 21 or 29 */ + if (strncmp(path, "/private", 8) == 0) { + osstr = OSString::withCString(path+29); + } else { + osstr = OSString::withCString(path+21); + } + if (!osstr) { + dprintf("%s couldn't get string from path\n", __func__); + return (NULL); + } + + string = osstr->getCStringNoCopy(); + ASSERT(string); + + /* Convert dashes to slashes */ + while ((dash = strchr(string, '-')) != NULL) { + osstr->setChar('/', dash - string); + } + dprintf("%s string [%s]\n", __func__, string); + + entry = IORegistryEntry::fromPath(string, gIODTPlane); + string = 0; + osstr->release(); + osstr = 0; + + if (!entry) { + dprintf("%s IORegistryEntry::fromPath failed\n", __func__); + return (NULL); + } + + if ((media = OSDynamicCast(IOMedia, entry)) == NULL) { + entry->release(); + return (0); + } + + /* Leave a retain count on the media */ + return (media); +} + +/* + * media_from_serial + * + * translate /private/var/run/disk/by-serial/model-serial[:location] + * to an IOMedia handle. The path format is determined by + * InvariantDisks logic in IDSerialLinker.cpp. + */ +static IOMedia * +media_from_serial(const char *path = 0) +{ + IORegistryEntry *entry = 0; + IOMedia *media = 0; + OSDictionary *matching = 0; + OSDictionary *deviceCharacteristics = 0; + OSIterator *iter = 0; + OSString *osstr = 0; + OSString *model = 0; + OSString *serial = 0; + OSNumber *bsdUnit = 0; + OSObject *property = 0; + OSObject *propDict = 0; + OSObject *obj = 0; + const char *substr = 0; + const char *sep1 = 0, *sep2 = 0; + const char *string = 0, *space = 0; + const char *location = 0, *entryLocation = 0; + int newlen = 0, soff = 0; + bool matched = false; + + /* Must be /var/run/disk/by-serial/, but may have /private prefix */ + if (!path || path[0] == 0 || + (strncmp(path, "/var/run/disk/by-serial/", 24) != 0 && + strncmp(path, "/private/var/run/disk/by-serial/", 32) != 0)) { + dprintf("%s invalid path [%s]\n", __func__, + (path && path[0] != '\0' ? path : "")); + return (NULL); + } + + /* substr starts after '/by-serial/' */ + substr = path + 24; + if (strncmp(path, "/private", 8) == 0) substr += 8; + + /* + * For each whole-disk IOMedia: + * Search parents for deviceCharacteristics, or skip. + * Check for Model and Serial Number properties, or skip. + * Trim trailing space and swap underscores within string. + * If "model-serial" matches path so far: + * Match whole-disk IOMedia if no slice specified. + * Or get child IOMedia with matching Location property. + */ + + sep1 = strchr(substr, '-'); + sep2 = strrchr(substr, ':'); + if (sep1 == 0) { + dprintf("%s invalid by-serial path [%s]\n", __func__, substr); + return (NULL); + } + if (sep2 == 0) { + dprintf("%s no slice, whole disk [%s]\n", __func__, substr); + sep2 = substr + (strlen(substr)); + } + + if ((matching = IOService::serviceMatching("IOMedia")) == NULL) { + dprintf("%s couldn't get matching dictionary\n", __func__); + return (NULL); + } + + if ((matching->setObject(kIOMediaWholeKey, kOSBooleanTrue) == false) || + (iter = IOService::getMatchingServices(matching)) == NULL) { + dprintf("%s couldn't get IOMedia iterator\n", __func__); + matching->release(); + return (NULL); + } + matching->release(); + matching = 0; + + while ((obj = iter->getNextObject()) != NULL) { + if ((entry = OSDynamicCast(IORegistryEntry, obj)) == NULL || + (media = OSDynamicCast(IOMedia, entry)) == NULL || + media->isFormatted() == false) { + // media->isWhole() == false) { + continue; + } + + propDict = media->getProperty( + kIOPropertyDeviceCharacteristicsKey, gIOServicePlane, + (kIORegistryIterateRecursively | + kIORegistryIterateParents)); + if ((deviceCharacteristics = OSDynamicCast(OSDictionary, + propDict)) == NULL) { + dprintf("%s no device characteristics, skipping\n", + __func__); + continue; + } + + /* + * Get each property, cast as OSString, then copy + * to a new OSString. + */ + if ((property = deviceCharacteristics->getObject( + kIOPropertyProductNameKey)) == NULL || + (osstr = OSDynamicCast(OSString, property)) == NULL || + (model = OSString::withString(osstr)) == NULL) { + dprintf("%s no product name, skipping\n", __func__); + continue; + } + if ((property = deviceCharacteristics->getObject( + kIOPropertyProductSerialNumberKey)) == NULL || + (osstr = OSDynamicCast(OSString, property)) == NULL || + (serial = OSString::withString(osstr)) == NULL) { + dprintf("%s no serial number, skipping\n", __func__); + model->release(); + model = 0; + continue; + } + + string = model->getCStringNoCopy(); + if (!string) { + model->release(); + model = 0; + serial->release(); + serial = 0; + continue; + } + /* Trim trailing whitespace */ + for (newlen = strlen(string); newlen > 0; newlen--) { + if (string[newlen-1] != ' ') { + model->setChar('\0', newlen); + break; + } + } + + /* + * sep1 is the location of the first '-' in the path. + * even if there is a '-' in the model name, we can skip + * media with model names shorter than that. + */ + if (newlen == 0 || + (newlen < (sep1 - substr)) || + (substr[newlen] != '-')) { + model->release(); + model = 0; + serial->release(); + serial = 0; + continue; + } + + /* Convert spaces to underscores */ + while ((space = strchr(string, ' ')) != NULL) { + model->setChar('_', space - string); + } + + /* Compare the model string with the path */ + if (strncmp(substr, string, newlen) != 0) { + model->release(); + model = 0; + serial->release(); + serial = 0; + continue; + } + dprintf("%s model string matched [%s]\n", + __func__, model->getCStringNoCopy()); + model->release(); + model = 0; + + soff = newlen + 1; + + string = serial->getCStringNoCopy(); + if (!string) { + serial->release(); + serial = 0; + continue; + } + /* Trim trailing whitespace */ + for (newlen = strlen(string); newlen > 0; newlen--) { + if (string[newlen-1] != ' ') { + serial->setChar('\0', newlen); + break; + } + } + /* + * sep2 is the location of the last ':' in the path, or + * the end of the string if there is none. + * even if there is a ':' in the serial number, we can skip + * media with serial number strings shorter than that. + */ + if (newlen == 0 || + (newlen < (sep2 - sep1 - 1)) || + (substr[soff+newlen] != '\0' && + substr[soff+newlen] != ':')) { + serial->release(); + serial = 0; + continue; + } + + /* Convert spaces to underscores */ + while ((space = strchr(string, ' ')) != NULL) { + serial->setChar('_', space - string); + } + + /* Compare the serial string with the path */ + if (strncmp(substr+soff, string, newlen) != 0) { + serial->release(); + serial = 0; + continue; + } + dprintf("%s serial string matched [%s]\n", + __func__, serial->getCStringNoCopy()); + serial->release(); + serial = 0; + + /* + * Still need to get the slice - the component + * after an optional ':' at the end of the + * string, by searching for IOMedia with that + * location string below the whole-disk IOMedia. + */ + /* Set new location of ':' */ + sep2 = substr + (soff + newlen); + /* Found match */ + matched = true; + media->retain(); + break; + } + iter->release(); + iter = 0; + + if (!matched || !media) { + dprintf("%s no matching devices found\n", __func__); + return (NULL); + } + + /* Whole disk path will not end with ':' */ + if (sep2[0] != ':') { + dprintf("%s Found whole disk [%s]\n", __func__, path); + /* Leave a retain count on the media */ + return (media); + } + + /* Remainder of string is location */ + location = sep2 + 1; + dprintf("%s location string [%s]\n", __func__, location); + + if ((bsdUnit = OSDynamicCast(OSNumber, + media->getProperty(kIOBSDUnitKey))) == NULL) { + dprintf("%s couldn't get BSD unit number\n", __func__); + media->release(); + return (NULL); + } + if ((matching = IOService::serviceMatching("IOMedia")) == NULL || + (matching->setObject(kIOMediaWholeKey, kOSBooleanFalse)) == false || + (matching->setObject(kIOBSDUnitKey, bsdUnit)) == false || + (iter = IOService::getMatchingServices(matching)) == NULL) { + dprintf("%s iterator for location failed\n", + __func__); + + if (matching) matching->release(); + /* We had a candidate, but couldn't get the location */ + media->release(); + return (NULL); + } + matching->release(); + matching = 0; + + /* Iterate over children checking for matching location */ + matched = false; + entry = 0; + while ((obj = iter->getNextObject()) != NULL) { + if ((entry = OSDynamicCast(IORegistryEntry, obj)) == NULL || + (OSDynamicCast(IOMedia, entry)) == NULL) { + entry = 0; + continue; + } + + if ((entryLocation = entry->getLocation()) == NULL || + (strlen(entryLocation) != strlen(location)) || + strcmp(entryLocation, location) != 0) { + entry = 0; + continue; + } + + dprintf("%s found match\n", __func__); + matched = true; + entry->retain(); + break; + } + iter->release(); + iter = 0; + + /* Drop the whole-disk media */ + media->release(); + media = 0; + + /* Cast the new entry, if there is one */ + if (!entry || (media = OSDynamicCast(IOMedia, entry)) == NULL) { +if (entry) dprintf("%s had entry but couldn't cast\n", __func__); + dprintf("%s no media found for path %s\n", + __func__, path); + if (entry) entry->release(); + return (NULL); + } + + dprintf("%s media from serial number succeeded\n", __func__); + + /* Leave a retain count on the media */ + return (matched ? media : NULL); +} + +/* + * media_from_path is intended to be called by ldi_open_by_name + * with a char* path, and returns NULL or an IOMedia device with a + * retain count that should be released on open. + */ +static IOMedia * +media_from_path(const char *path = 0) +{ + IOMedia *media; + OSDictionary *matchDict; + + /* Validate path */ + if (path == 0 || strlen(path) <= 1) { + dprintf("%s no path provided\n", __func__); + return (NULL); + } + + if (strncmp(path, "/var/run/disk/by-path/", 22) == 0 || + strncmp(path, "/private/var/run/disk/by-path/", 30) == 0) { + media = media_from_device_path(path); + dprintf("%s media_from_device_path %s\n", __func__, + (media ? "succeeded" : "failed")); + return (media); + } + + if (strncmp(path, "/var/run/disk/by-serial/", 24) == 0 || + strncmp(path, "/private/var/run/disk/by-serial/", 32) == 0) { + media = media_from_serial(path); + dprintf("%s media_from_serial %s\n", __func__, + (media ? "succeeded" : "failed")); + return (media); + } + + /* Try to get /dev/disk or /private/var/run/disk/by-id path */ + matchDict = media_matchdict_from_path(path); + if (!matchDict) { + dprintf("%s couldn't get matching dictionary\n", __func__); + return (NULL); + } + + media = media_from_matchdict(matchDict); + matchDict->release(); + matchDict = 0; + + if (!media) { + dprintf("%s no IOMedia found for path %s\n", __func__, path); + } + + /* Return NULL or valid media with retain count */ + return (media); +} + +/* Completion handler for IOKit strategy */ +static void +ldi_iokit_io_intr(void *target, void *parameter, + IOReturn status, UInt64 actualByteCount) +{ + IOMemoryDescriptor *iomem = (IOMemoryDescriptor *)target; + + ldi_buf_t *lbp = (ldi_buf_t *)parameter; + +#ifdef DEBUG + /* In debug builds, verify buffer pointers */ + ASSERT3U(lbp, !=, 0); + + if (!lbp) { + printf("%s missing a buffer\n", __func__); + return; + } + + if (!iomem) { + printf("%s missing iomem\n", __func__); + return; + } + + // this is very very very noisy in --enable-boot + // ASSERT3U(ldi_zfs_handle, !=, 0); + + if (actualByteCount == 0 || + actualByteCount != lbp->b_bcount || + status != kIOReturnSuccess) { + printf("%s actualByteCOunt != lbp->b_count " + "%llx / %llx, " + "bufsize %llx lbkno %llx resid %llx " + "flags %x err %x\n", + __func__, + actualByteCount, lbp->b_bcount, + lbp->b_bufsize, lbp->b_lblkno, lbp->b_resid, + lbp->b_flags, lbp->b_error); + if (ldi_zfs_handle) { + const char *s = ldi_zfs_handle->stringFromReturn(status); + printf("%s have handle: status %x, status %d, %s, " + "err %x, bufsize %llx lblkno %llx resid %llx " + "flags %x\n", + __func__, status, + ldi_zfs_handle->errnoFromReturn(status), + (s != NULL) ? s : "no stringFromReturn", + lbp->b_error, lbp->b_bufsize, lbp->b_lblkno, + lbp->b_resid, lbp->b_flags); + } else { + printf("%s status %x, ldi_zfs_handle is NULL, " + "err %x, bufsize %llx lblkno %llx resid %llx " + "flags %x\n", + __func__, status, + lbp->b_error, + lbp->b_bufsize, lbp->b_lblkno, lbp->b_resid, + lbp->b_flags); + } + } +#endif + + /* Complete and release IOMemoryDescriptor */ + iomem->complete(); + iomem->release(); + iomem = 0; + + /* Compute resid */ + ASSERT3U(lbp->b_bcount, >=, actualByteCount); + lbp->b_resid = (lbp->b_bcount - actualByteCount); + + /* Set error status */ + if (status == kIOReturnSuccess && + actualByteCount != 0 && lbp->b_resid == 0) { + lbp->b_error = 0; + } else { + lbp->b_error = EIO; + } + + /* Call original completion function */ + if (lbp->b_iodone) { + (void) lbp->b_iodone(lbp); + } +} + +/* + * Uses IOMedia::read asynchronously or IOStorage::read synchronously. + * virtual void read(IOService * client, + * UInt64 byteStart, + * IOMemoryDescriptor * buffer, + * IOStorageAttributes * attributes, + * IOStorageCompletion * completion); + * virtual IOReturn read(IOService * client, + * UInt64 byteStart, + * IOMemoryDescriptor * buffer, + * IOStorageAttributes * attributes = 0, + * UInt64 * actualByteCount = 0); + */ +int +buf_strategy_iokit(ldi_buf_t *lbp, struct ldi_handle *lhp) +{ + + ASSERT3U(lbp, !=, NULL); + ASSERT3U(lhp, !=, NULL); + +#ifdef DEBUG + /* Validate IOMedia */ + if (!OSDynamicCast(IOMedia, LH_MEDIA(lhp)) || + !OSDynamicCast(IOService, LH_CLIENT(lhp))) { + dprintf("%s invalid IOMedia or client\n", __func__); + return (ENODEV); + } +#endif /* DEBUG */ + + /* Allocate a memory descriptor pointing to the data address */ + IOMemoryDescriptor *iomem; + iomem = IOMemoryDescriptor::withAddress( + lbp->b_un.b_addr, lbp->b_bcount, + (lbp->b_flags & B_READ ? kIODirectionIn : kIODirectionOut)); + + /* Verify the buffer */ + if (!iomem || iomem->getLength() != lbp->b_bcount || + iomem->prepare() != kIOReturnSuccess) { + dprintf("%s couldn't allocate IO buffer\n", + __func__); + if (iomem) { + iomem->release(); + } + return (ENOMEM); + } + + /* Recheck instantaneous value of handle status */ + if (lhp->lh_status != LDI_STATUS_ONLINE) { + dprintf("%s device not online\n", __func__); + iomem->complete(); + iomem->release(); + return (ENODEV); + } + + IOStorageAttributes ioattr = { 0 }; + + /* Synchronous or async */ + if (lbp->b_iodone == NULL) { + UInt64 actualByteCount = 0; + IOReturn result; + + /* Read or write */ + if (lbp->b_flags & B_READ) { + result = LH_MEDIA(lhp)->IOStorage::read(LH_CLIENT(lhp), + dbtolb(lbp->b_lblkno), iomem, + &ioattr, &actualByteCount); + } else { + result = LH_MEDIA(lhp)->IOStorage::write(LH_CLIENT(lhp), + dbtolb(lbp->b_lblkno), iomem, + &ioattr, &actualByteCount); + } + + /* Call completion */ + ldi_iokit_io_intr((void *)iomem, (void *)lbp, + result, actualByteCount); + + /* Return success based on result */ + return (result == kIOReturnSuccess ? 0 : EIO); + } + +#if !defined(MAC_OS_X_VERSION_10_9) || \ + (MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_9) + /* Priority of I/O */ + ioattr.priority = kIOStoragePriorityDefault; + + /* higher priority is lower numerical value */ + + if (lbp->b_flags & B_ASYNC) { + ioattr.priority += 16; + } + + if (lbp->b_flags & B_WRITE) { + ioattr.priority += 16; + } + + if (lbp->b_flags & B_THROTTLED_IO) { + ioattr.priority = kIOStoragePriorityBackground; + } + + if (lbp->b_flags & B_FUA) { + /* + * We possibly want to ask IOKit to do a force unit access or + * equivalent here, but this requires further plumbing and + * likely state about whether a device will error on FUA; for + * now just prioritize the IO. Anyway, ZIO & ZIL use + * DKIOCFLUSHWRITECACHE. + */ + ioattr.priority = kIOStoragePriorityHigh; + } + +#endif + + /* + * Make sure there is enough space to hold IOCompletion. + * If this trips, increase the space in ldi_buf.h's + * struct opaque_iocompletion. + */ + CTASSERT(sizeof (struct opaque_iocompletion) >= + sizeof (struct IOStorageCompletion)); + + IOStorageCompletion *iocompletion; + iocompletion = (IOStorageCompletion *)&lbp->b_completion; + iocompletion->target = (void *)iomem; + iocompletion->parameter = lbp; + iocompletion->action = &ldi_iokit_io_intr; + + /* Read or write */ + if (lbp->b_flags & B_READ) { + LH_MEDIA(lhp)->IOMedia::read(LH_CLIENT(lhp), + dbtolb(lbp->b_lblkno), iomem, + &ioattr, iocompletion); + } else { + LH_MEDIA(lhp)->IOMedia::write(LH_CLIENT(lhp), + dbtolb(lbp->b_lblkno), iomem, + &ioattr, iocompletion); + } + + /* Return success, will call io_intr when done */ + return (0); +} + +/* Client interface, alloc and open IOKit handle */ +int +ldi_open_by_media(IOMedia *media = 0, dev_t device = 0, + int fmode = 0, ldi_handle_t *lhp = 0) +{ + struct ldi_handle *retlhp; + ldi_status_t status; + int error; + + /* Validate IOMedia */ + if (!media || !lhp) { + dprintf("%s invalid argument %p or %p\n", + __func__, media, lhp); + return (EINVAL); + } + + /* Retain for duration of open */ + media->retain(); + + /* Get dev_t if not supplied */ + if (device == 0 && (device = dev_from_media(media)) == 0) { + dprintf("%s dev_from_media failed: %p %d\n", __func__, + media, device); + media->release(); + return (ENODEV); + } + + /* In debug build, be loud if we potentially leak a handle */ + ASSERT3U(*(struct ldi_handle **)lhp, ==, NULL); + + /* Allocate IOKit handle */ + retlhp = handle_alloc_iokit(device, fmode); + if (retlhp == NULL) { + dprintf("%s couldn't allocate IOKit handle\n", __func__); + media->release(); + return (ENOMEM); + } + + /* Try to open device with IOMedia */ + status = handle_open_start(retlhp); + if (status == LDI_STATUS_ONLINE) { + dprintf("%s already online, refs %d, openrefs %d\n", __func__, + retlhp->lh_ref, retlhp->lh_openref); + /* Cast retlhp and assign to lhp (may be 0) */ + *lhp = (ldi_handle_t)retlhp; + media->release(); + /* Successfully incremented open ref */ + return (0); + } + if (status != LDI_STATUS_OPENING) { + dprintf("%s invalid status %d\n", __func__, status); + handle_release(retlhp); + retlhp = 0; + media->release(); + return (ENODEV); + } + + error = handle_open_iokit(retlhp, media); + media->release(); + + if (error) { + dprintf("%s Couldn't open handle\n", __func__); + handle_open_done(retlhp, LDI_STATUS_CLOSED); + handle_release(retlhp); + retlhp = 0; + return (EIO); + } + handle_open_done(retlhp, LDI_STATUS_ONLINE); + + /* Register for disk notifications */ + handle_register_notifier(retlhp); + + /* Cast retlhp and assign to lhp (may be 0) */ + *lhp = (ldi_handle_t)retlhp; + /* Pass error from open */ + return (error); +} + +/* Client interface, find IOMedia from dev_t, alloc and open handle */ +int +ldi_open_media_by_dev(dev_t device = 0, int fmode = 0, + ldi_handle_t *lhp = 0) +{ + IOMedia *media = 0; + int error = EINVAL; + + /* Validate arguments */ + if (!lhp || device == 0) { + dprintf("%s missing argument %p %d\n", + __func__, lhp, device); + return (EINVAL); + } + /* In debug build, be loud if we potentially leak a handle */ + ASSERT3U(*((struct ldi_handle **)lhp), ==, NULL); + + /* Get IOMedia from major/minor */ + if ((media = media_from_dev(device)) == NULL) { + dprintf("%s media_from_dev error %d\n", + __func__, error); + return (ENODEV); + } + + /* Try to open by media */ + error = ldi_open_by_media(media, device, fmode, lhp); + + /* Release IOMedia and clear */ + media->release(); + media = 0; + + /* Pass error from open */ + return (error); +} + +/* Client interface, find dev_t and IOMedia/vnode, alloc and open handle */ +int +ldi_open_media_by_path(char *path = 0, int fmode = 0, + ldi_handle_t *lhp = 0) +{ + IOMedia *media = 0; + dev_t device = 0; + int error = EINVAL; + + /* Validate arguments */ + if (!lhp || !path) { + dprintf("%s %s %p %s %d\n", __func__, + "missing lhp or path", lhp, path, fmode); + return (EINVAL); + } + /* In debug build, be loud if we potentially leak a handle */ + ASSERT3U(*((struct ldi_handle **)lhp), ==, NULL); + + /* For /dev/disk*, and InvariantDisk paths */ + if ((media = media_from_path(path)) == NULL) { + dprintf("%s media_from_path failed\n", __func__); + return (ENODEV); + } + + error = ldi_open_by_media(media, device, fmode, lhp); + + /* Release IOMedia and clear */ + media->release(); + media = 0; + + /* Error check open */ + if (error) { + dprintf("%s ldi_open_by_media failed %d\n", + __func__, error); + } + + return (error); +} + +int +handle_remove_notifier(struct ldi_handle *lhp) +{ + handle_notifier_t notifier; + +#ifdef DEBUG + if (!lhp) { + dprintf("%s missing handle\n", __func__); + return (EINVAL); + } +#endif + + if (lhp->lh_notifier == 0) { + dprintf("%s no notifier installed\n", __func__); + return (0); + } + + /* First clear notifier pointer */ + notifier = lhp->lh_notifier; + lhp->lh_notifier = 0; + +#ifdef DEBUG + /* Validate IONotifier object */ + if (!OSDynamicCast(IONotifier, notifier->obj)) { + dprintf("%s %p is not an IONotifier\n", __func__, + notifier->obj); + return (EINVAL); + } +#endif + + notifier->obj->remove(); + kmem_free(notifier, sizeof (handle_notifier_t)); + return (0); +} + +int +handle_register_notifier(struct ldi_handle *lhp) +{ + OSDictionary *matchDict; + handle_notifier_t notifier; + + /* Make sure we have a handle and dev_t */ + if (!lhp || lhp->lh_dev == 0) { + dprintf("%s no handle or missing dev_t\n", __func__); + return (EINVAL); + } + + notifier = (handle_notifier_t)kmem_alloc( + sizeof (struct _handle_notifier), KM_SLEEP); + if (!notifier) { + dprintf("%s couldn't alloc notifier struct\n", __func__); + return (ENOMEM); + } + + /* Get matchDict, will need to be released */ + matchDict = media_matchdict_from_dev(lhp->lh_dev); + if (!matchDict) { + dprintf("%s couldn't get matching dictionary\n", __func__); + kmem_free(notifier, sizeof (handle_notifier_t)); + return (EINVAL); + } + + /* Register IOMedia termination notification */ + notifier->obj = IOService::addMatchingNotification( + gIOTerminatedNotification, matchDict, + handle_media_terminate_cb, /* target */ 0, + /* refCon */ (void *)lhp, /* priority */ 0); + matchDict->release(); + + /* Error check notifier */ + if (!notifier->obj) { + dprintf("%s addMatchingNotification failed\n", + __func__); + kmem_free(notifier, sizeof (handle_notifier_t)); + return (ENOMEM); + } + + /* Assign notifier to handle */ + lhp->lh_notifier = notifier; + return (0); +} + +/* Supports both IOKit and vnode handles by finding IOMedia from dev_t */ +int +handle_set_wce_iokit(struct ldi_handle *lhp, int *wce) +{ + IOMedia *media; + IORegistryEntry *parent; + IOBlockStorageDevice *device; + IOReturn result; + bool value; + + if (!lhp || !wce) { + return (EINVAL); + } + + switch (lhp->lh_type) { + case LDI_TYPE_IOKIT: + if ((media = LH_MEDIA(lhp)) == NULL) { + dprintf("%s couldn't get IOMedia\n", __func__); + return (ENODEV); + } + /* Add a retain count */ + media->retain(); + break; + case LDI_TYPE_VNODE: + if (lhp->lh_dev == 0 || + (media = media_from_dev(lhp->lh_dev)) == 0) { + dprintf("%s couldn't find IOMedia for dev_t %d\n", + __func__, lhp->lh_dev); + return (ENODEV); + } + /* Returned media has a retain count */ + break; + default: + dprintf("%s invalid handle\n", __func__); + return (EINVAL); + } + + /* Walk the parents of this media */ + for (parent = media->getParentEntry(gIOServicePlane); + parent != NULL; + parent = parent->getParentEntry(gIOServicePlane)) { + /* Until a valid device is found */ + device = OSDynamicCast(IOBlockStorageDevice, parent); + if (device != NULL) { + device->retain(); + break; + } + /* Next parent */ + } + media->release(); + media = 0; + + /* If no matching device was found */ + if (!device) { + dprintf("%s no IOBlockStorageDevice found\n", __func__); + return (ENODEV); + } + + result = device->getWriteCacheState(&value); + if (result != kIOReturnSuccess) { + // dprintf("%s couldn't get current write cache state %d\n", + // __func__, ldi_zfs_handle->errnoFromReturn(result)); + device->release(); + return (ENXIO); + } + + /* If requested value does not match current */ + if (value != *wce) { + value = (*wce == 1); + /* Attempt to change the value */ + result = device->setWriteCacheState(value); + } + + /* Set error and wce to return */ + if (result != kIOReturnSuccess) { + // dprintf("%s couldn't set write cache %d\n", + // __func__, ldi_zfs_handle->errnoFromReturn(result)); + /* Flip wce to indicate current status */ + *wce = !(*wce); + device->release(); + return (ENXIO); + } + + device->release(); + return (0); +} + +int +handle_get_media_info_iokit(struct ldi_handle *lhp, + struct dk_minfo *dkm) +{ + uint32_t blksize; + uint64_t blkcount; + + if (!lhp || !dkm) { + return (EINVAL); + } + + /* Validate IOMedia */ + if (!OSDynamicCast(IOMedia, LH_MEDIA(lhp))) { + dprintf("%s invalid IOKit handle\n", __func__); + return (ENODEV); + } + + LH_MEDIA(lhp)->retain(); + + if ((blksize = LH_MEDIA(lhp)->getPreferredBlockSize()) == 0) { + dprintf("%s invalid blocksize\n", __func__); + LH_MEDIA(lhp)->release(); + return (ENXIO); + } + + if ((blkcount = LH_MEDIA(lhp)->getSize() / blksize) == 0) { + dprintf("%s invalid block count\n", __func__); + LH_MEDIA(lhp)->release(); + return (ENXIO); + } + + LH_MEDIA(lhp)->release(); + + /* Set the return values */ + dkm->dki_capacity = blkcount; + dkm->dki_lbsize = blksize; + + return (0); +} + +int +handle_get_media_info_ext_iokit(struct ldi_handle *lhp, + struct dk_minfo_ext *dkmext) +{ + OSObject *prop; + OSNumber *number; + uint32_t blksize, pblksize; + uint64_t blkcount; + + if (!lhp || !dkmext) { + printf("zfs: %s missing lhp or dkmext\n", __func__); + return (EINVAL); + } + + /* Validate IOMedia */ + if (!OSDynamicCast(IOMedia, LH_MEDIA(lhp))) { + printf("zfs: %s invalid IOKit handle\n", __func__); + return (ENODEV); + } + + LH_MEDIA(lhp)->retain(); + + if ((blksize = LH_MEDIA(lhp)->getPreferredBlockSize()) == 0) { + printf("zfs: %s invalid blocksize\n", __func__); + LH_MEDIA(lhp)->release(); + return (ENXIO); + } + + prop = LH_MEDIA(lhp)->getProperty(kIOPropertyPhysicalBlockSizeKey, + gIOServicePlane, kIORegistryIterateRecursively | + kIORegistryIterateParents); + + number = OSDynamicCast(OSNumber, prop); + + if (!prop || !number) { + printf("zfs: %s couldn't get physical blocksize - using preferred\n", __func__); + pblksize = blksize; + } else { + pblksize = number->unsigned32BitValue(); + } + + number = 0; + prop = 0; + + if ((blkcount = LH_MEDIA(lhp)->getSize() / blksize) == 0) { + printf("ZFS: %s invalid block count\n", __func__); + LH_MEDIA(lhp)->release(); + return (ENXIO); + } + + LH_MEDIA(lhp)->release(); + +#ifdef DEBUG + printf("ZFS: %s phys blksize %u, logical blksize %u, blockcount %llu\n", + __func__, pblksize, blksize, blkcount); +#endif + + /* + * The Preferred Block Size may be smaler than the Physical Block + * Size. The latter is what is bubbled up to "diskutil info -plist"'s + * DeviceBlockSize. + * + * In theory this should only lower-limit the ashift when adding a + * vdev. It also is what "zpool get ashift pool vdev" returns. + * + * In practice, different external enclosures can return different + * physical block sizes for the same physical storage device, which + * results in zpool status -vx reporting mismatches, and problems with + * scrubs triggering vdev.bad_ashift and ejecting the physical device + * if it is moved from a working enclousre to a different enclosure. + * + * Therefore return the smaller of kIOPropertyPhysicalBlockSizeKey + * and getPreferredBlockSize in dki_pbsize. + */ + + /* Set the return values */ + + if (pblksize > blksize) { + printf("ZFS: %s set dki_pbsize to %u instead of %u\n", + __func__, blksize, pblksize); + dkmext->dki_pbsize = blksize; + } else { + dkmext->dki_pbsize = pblksize; + } + + dkmext->dki_capacity = blkcount; + dkmext->dki_lbsize = blksize; + + return (0); +} + +int +handle_check_media_iokit(struct ldi_handle *lhp, int *status) +{ + /* Validate arguments */ + if (!lhp || !status) { + return (EINVAL); + } + + /* Validate IOMedia */ + if (!OSDynamicCast(IOMedia, LH_MEDIA(lhp))) { + dprintf("%s invalid IOKit handle\n", __func__); + return (ENODEV); + } + + LH_MEDIA(lhp)->retain(); + + /* Validate device size */ + if (LH_MEDIA(lhp)->getSize() == 0) { + dprintf("%s media reported 0 size\n", __func__); + LH_MEDIA(lhp)->release(); + return (ENXIO); + } + + /* Validate write status if handle fmode is read-write */ + if ((lhp->lh_fmode & FWRITE) && + LH_MEDIA(lhp)->isWritable() == false) { + dprintf("%s media is not writeable\n", __func__); + LH_MEDIA(lhp)->release(); + return (EPERM); + } + + LH_MEDIA(lhp)->release(); + + /* Success */ + *status = 0; + return (0); +} + +int +handle_is_solidstate_iokit(struct ldi_handle *lhp, int *isssd) +{ + OSDictionary *propDict = 0; + OSString *property = 0; + + /* Validate arguments */ + if (!lhp || !isssd) { + return (EINVAL); + } + + /* Validate IOMedia */ + if (!OSDynamicCast(IOMedia, LH_MEDIA(lhp))) { + dprintf("%s invalid IOKit handle\n", __func__); + return (ENODEV); + } + + LH_MEDIA(lhp)->retain(); + + propDict = OSDynamicCast(OSDictionary, LH_MEDIA(lhp)->getProperty( + kIOPropertyDeviceCharacteristicsKey, gIOServicePlane)); + + if (propDict != 0) { + property = OSDynamicCast(OSString, + propDict->getObject(kIOPropertyMediumTypeKey)); + propDict = 0; + } + + if (property != 0 && + property->isEqualTo(kIOPropertyMediumTypeSolidStateKey)) { + *isssd = 1; + } + property = 0; + + LH_MEDIA(lhp)->release(); + + return (0); +} + +int +handle_features_iokit(struct ldi_handle *lhp, + uint32_t *data) +{ + if (!lhp || !data) { + return (EINVAL); + } + + /* Validate IOMedia */ + if (!OSDynamicCast(IOMedia, LH_MEDIA(lhp))) { + dprintf("%s invalid IOKit handle\n", __func__); + return (ENODEV); + } + + LH_MEDIA(lhp)->retain(); + + OSDictionary *dictionary = OSDynamicCast( + /* class */ OSDictionary, + /* object */ LH_MEDIA(lhp)->getProperty( + /* key */ kIOStorageFeaturesKey, + /* plane */ gIOServicePlane)); + + *data = 0; + + if (dictionary) { + OSBoolean *boolean; + +#ifdef DK_FEATURE_BARRIER + boolean = OSDynamicCast( + /* class */ OSBoolean, + /* object */ dictionary->getObject( + /* key */ kIOStorageFeatureBarrier)); + + if (boolean == kOSBooleanTrue) + *(uint32_t *)data |= DK_FEATURE_BARRIER; +#endif + + boolean = OSDynamicCast( + /* class */ OSBoolean, + /* object */ dictionary->getObject( + /* key */ kIOStorageFeatureForceUnitAccess)); + + if (boolean == kOSBooleanTrue) + *(uint32_t *)data |= DK_FEATURE_FORCE_UNIT_ACCESS; + +#ifdef DK_FEATURE_PRIORITY + boolean = OSDynamicCast( + /* class */ OSBoolean, + /* object */ dictionary->getObject( + /* key */ kIOStorageFeaturePriority)); + + if (boolean == kOSBooleanTrue) + *(uint32_t *)data |= DK_FEATURE_PRIORITY; +#endif + + boolean = OSDynamicCast( + /* class */ OSBoolean, + /* object */ dictionary->getObject( + /* key */ kIOStorageFeatureUnmap)); + + if (boolean == kOSBooleanTrue) + *(uint32_t *)data |= DK_FEATURE_UNMAP; + } + + LH_MEDIA(lhp)->release(); + return (0); +} + +int +handle_unmap_iokit(struct ldi_handle *lhp, + dkioc_free_list_ext_t *dkm) +{ + int error = 0; + + if (!lhp || !dkm) { + return (EINVAL); + } + + /* Validate IOMedia */ + if (!OSDynamicCast(IOMedia, LH_MEDIA(lhp))) { + dprintf("%s invalid IOKit handle\n", __func__); + return (ENODEV); + } + + LH_MEDIA(lhp)->retain(); + + /* We need to convert illumos' dkioc_free_list_t to dk_unmap_t */ + IOStorageExtent *extents; + extents = IONew(IOStorageExtent, 1); + extents[0].byteStart = dkm->dfle_start; + extents[0].byteCount = dkm->dfle_length; + + /* + * dkm->dfl_flags vs IOStorageUnmapOptions + * #define DF_WAIT_SYNC 0x00000001 + * Wait for full write-out of free. + * IOStorageUnmapOptions is only 0 + */ + + /* issue unmap */ + error = LH_MEDIA(lhp)->unmap(LH_CLIENT(lhp), + extents, 1, 0); + + if (error != 0) { + dprintf("%s unmap: 0x%x\n", __func__, error); + // Convert IOReturn to errno + error = LH_MEDIA(lhp)->errnoFromReturn(error); + } + + IODelete(extents, IOStorageExtent, 1); + LH_MEDIA(lhp)->release(); + + return (error); +} + + +} /* extern "C" */ diff --git a/module/os/macos/zfs/ldi_osx.c b/module/os/macos/zfs/ldi_osx.c new file mode 100644 index 000000000000..fb2aa0e8c9f0 --- /dev/null +++ b/module/os/macos/zfs/ldi_osx.c @@ -0,0 +1,2433 @@ +/* + * 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) 1994, 2010, Oracle and/or its affiliates. All rights reserved. + */ +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +/* + * Copyright (c) 2013, Joyent, Inc. All rights reserved. + */ +/* + * Copyright (c) 2015, Evan Susarret. All rights reserved. + */ +/* + * Portions of this document are copyright Oracle and Joyent. + * OS X implementation of ldi_ named functions for ZFS written by + * Evan Susarret in 2015. + */ + +/* + * LDI Subsystem on OS X: + * + * Designed as a drop-in replacement for sunldi.h and driver_lyr.c, + * LDI abstracts away platform-specific device handling. This allows + * vdev_disk.c to more closely match 'upstream' illumos/OpenZFS. + * + * LDI handles may use IOKit or vnode ops to locate and use devices. + * - This reduces the call stack and work needed for almost all IO. + * - Allows for vdev discovery and use during early boot, before the + * root device is mounted. + * - Having both types allows use of non-standard kexts which publish + * bdevsw block devices (without IOMedia). + * + * XXX Review correct call stack using dtrace, annotate stack size. + * Previously, vnode_open and VNOP_STRATEGY were used, which required + * allocating buf_t for IO. This meant translating byte offsets to + * block numbers for every IO. Once issued, dtrace showed that a very + * large stack was required: + * VNOP_STRATEGY macro performs work then calls + * spec_strategy (vop->vop_strategy) which performs work then calls + * dkiostrategy (syscall) which passes the IO to an IOMediaBSDClient + * IOMediaBSDClient performed work and passes to its IOMedia provider + * + * Beyond that is a common path shared by vnode and IOMedia: + * IOMedia performs work, then does prepareRequest, breakUpRequest, + * deBlockRequest, and executeRequest. + * Potentially passed down the provider stack through IOPartitionMap + * then to the whole-disk IOMedia, with more work + * Passed down through IOBlockStorageDriver, with more work + * Passed down through IOBlockStorageDevice, with more work + * Finally passed to Family-specific driver (AHCI, diskimage, etc.) + * + * By directly accessing IOMedia, the stack is reduced, and byte + * offsets are passed to read()/write() via ldi_strategy. + * We still need to allocate an IOMemoryDescriptor for the data buf, + * however only an IOMemoryDescriptor::withAddress() reference is + * required, similar to buf_setdataptr. + */ + +/* + * LDI Handle hash lists: + * + * During ldi_init, LH_HASH_SZ lists and locks are allocated. New handles + * will be added to the list indexed by the hash of the dev_t number. + * + * The hash function simply performs a modulus on the dev_t number based on + * the LH_HASH_SZ, as opposed to illumos which hashes based on the vnode + * pointer. + * This has been tested by hashing disk0, disk0s1, disk0s2, disk1, disk1s1, + * etc. to verify results were distributed across hash range. + * + * OS X dev_t numbers should be unique unless a new device claims the same + * dev_t as a removed/failed device. This would only be a collision if we + * still have a handle for the failed device (notification/event handlers + * should remove these before that occurs). + * Since Offline status is a dead-end and the handle cannot be dereferenced + * or freed while iterating the hash list, it is safe to check the status + * and skip a handle if the status is Offline (without taking handle lock). + * + * XXX On illumos the hash function uses the vnode's pointer address as the + * unique key. Since vnode addresses are aligned to the size of the vnode + * struct, the hash function shifts the pointer address to the right in order + * to hash the unique bits of the address. OS X dev_t use all the bits of + * an unsigned 32-bit int. + */ + +/* + * LDI Handle locks: + * + * Handle references and list membership are protected by the hash list + * locks. + * Handle status and other fields are protected by a per-handle mutex. + * + * To prevent deadlocks and artificial delays, the hash list locks should + * be held only for handle hold/release and handle_add/remove (list + * iterate/insert/remove). Those functions avoid blocking. + * Use the handle mutex to change state, and avoid blocking there, too. + * + * XXX Right now handle_status_change does allocate for taskq_dispatch + * with the handle lock held, but uses TQ_NOSLEEP and verifies result. + * + * Non-locking ops such as ldi_strategy, ldi_get_size, and ldi_sync will + * check the instantaneous status/refs before attempting to proceed, and + * can only perform IO while the device is Online. + */ + +/* + * LDI Handle allocation: + * + * ldi_open_by_name and ldi_open_by_dev locate the device and call + * ldi_open_media_by_path, ldi_open_media_by_dev, or ldi_open_vnode_by_path. + * + * From ldi_open_by_media and _by_vnode, we call handle_alloc_{type}. Both + * call handle_alloc_common to allocate and configure the handle. + * + * A handle is allocated in the Closed state with 1 reference. The handle + * is added to the hash list on allocation, unless a duplicate handle exists + * (same dev_t as well as fmode, not in Offline status). If an existing + * handle is found, the newly allocated handle is freed. + * + * handle_open_start is called, which takes the handle lock to check current + * status. Each of these states is possible: + * Offline: device has disappeared between allocation and now (unlikely). + * Closed: new or recently closed handle, changes status to Opening. + * Closing: already in progress. Sleeps on lock and rechecks the status. + * Opening: already in progress. Sleeps on lock and rechecks the status. + * Online: no need to open device, just increment openref count. + * + * If handle_open_start changes the status to Opening, the device is opened + * by calling handle_open_iokit or handle_open_vnode. + * + * This differs from illumos driver_lyr.c where handle_alloc first opens a + * vnode for the device, allocates a handle by vnode, and finally checks for + * a duplicate handle in the list (open, alloc, find vs. alloc, open, find). + * To do so, illumos has a VOP_OPEN that is aware of layered-driver opens. + */ + +/* + * LDI Handle list membership: + * + * Allocate with one reference, to be used or released by the caller. + * Call handle_hold if additional references are needed. + * + * Call handle_release to drop reference. On last release, this calls + * handle_free (but does not remove the handle from the list, see below). + * + * Call handle_add to determine if this handle is a duplicate, inserting + * handle into list or returning an existing handle with a hold. + * Check the result and call handle_release on the new handle if another + * handle was returned (new handle is not added to list). + * + * Each call to handle_find will take optionally take a hold, which should + * be released when no longer needed (used by handle_add). + * + * Calling handle_open increments lh_openref but does not change lh_ref. + * Caller should already have called handle_hold to get a reference. + * + * If lh_ref is 1, call handle_remove_locked (with list lock) to remove the + * handle from the list, then call handle_release_locked to remove last ref + * and free. + * A handle shouldn't remain in the list in Closed status with no refs. + * + * Calling handle_close with the last openref will automatically take list + * lock, call handle_remove_locked, and then handle_release_locked. + */ + +/* + * LDI Handle device objects: + * + * Multiple read-only opens share one read-only handle. + * Multiple read-write opens share one read-write handle. + * + * IOKit handles are allocated with the dev_t number and fmode. + * handle_open_iokit is passed an IOMedia object (which should have a + * retain held). + * Once handle_open returns, the IOMedia can be released by the caller. + * + * Vnode handles are allocated with the dev_t number and fmode. + * handle_open_vnode is passed a path (null-terminated C string). + * vnode_open increments both iocount and refcount, vnode_ref increments + * usecount, vnode_put drops iocount between ops. + * vnode_getwithref takes an iocount, and vnode_rele drops usecount + * before vnode_close decrements iocount and refcount. + */ + +/* + * LDI Handle status: + * + * #define LDI_STATUS_OFFLINE 0x0 + * #define LDI_STATUS_CLOSED 0x1 + * #define LDI_STATUS_CLOSING 0x2 + * #define LDI_STATUS_OPENING 0x3 + * #define LDI_STATUS_ONLINE 0x4 + * + * The handle lock will be taken to change status. + * + * Handle state can only progress from Closed to Opening status, and must + * have a reference held to do so. The lock is dropped for open and close + * ops while the handle is in Opening or Closing status. + * + * If the open is successful, the state is set to Online (with handle lock + * held). This state is required for IO operations to be started. The state + * may have changed by the time an IO completes. + * + * For IOKit devices, and vnode devices that have an IOMedia, a callback is + * registered for IOMedia termination which changes the state to Offline and + * posts event callbacks. + * + * Closing a handle, by the user or as a result of an event, sets the state + * to Closing. Once device close is issued, the state changes from Closing + * to Closed (even if close returned failure). + * + * A handle that still has refs and openrefs will remain in the Online + * state, dropping refs and openrefs each time ldi_close is called. + * + * If there are refs but no openrefs, it remains in the Closed state, and + * drops refs each time handle_release is called. + * This allows clients to call ldi_open_by_* to reopen the handle, in the + * case where one client is opening the handle at the same time another is + * closing it. + * + * If the device has gone missing (IOMedia terminated), the handle will + * change to Offline status. This is a dead-end which issues Offline Notify + * and Finalize events, then cleans up the handle once all clients have + * called ldi_close. + * + * Once all references have been dropped, the handle is removed from the + * hash list with the hash list lock held, then freed. + */ + +/* + * LDI Events: + * + * XXX Degrade event is not implemented, doubt it will be useful. Intended + * to be set when a vdev that is backed by RAID becomes degraded. This is + * not a recommended use case for ZFS, and on OS X we only have AppleRAID + * or custom hardware or software RAID. Also per the comments, the vdev + * would be marked Degraded only to inform the user via zpool status. + * + * XXX Tested in VirtualBox by hotplugging a SATA device, have yet to + * test with USB removal, etc. + * + * ldi_register_ev_callback can be used to add a struct to the event + * callback list containing the handle pointer, a notify callback, and + * a finalize callback. + * + * Supported events are Offline Notify/Finalize, which will be + * posted when the device enters the Offline state (IOMedia terminated). + * + * The event callback functions should be non-blocking. It is recommended + * to update a flag that can be checked prior to calling ldi_strategy. + */ + +/* + * LDI client interfaces: + * + * ldi_open_by_name + * ldi_open_by_dev + * ldi_close + * + * ldi_register_ev_callback + * ldi_unregister_ev_callback + * + * ldi_get_size + * ldi_sync + * ldi_ioctl + * ldi_strategy + * + * ldi_bioinit + * ldi_biofini + */ + +/* + * LDI Buffers: + * + * ldi_strategy uses an abstract buffer for IO, so clients do not need to + * be concerned with type-specific buf_t and IOMemoryDescriptor handling. + * + * Allocate and free ldi_buf_t manually, calling ldi_bioinit after alloc + * and ldi_biofini prior to free. + * + * Synchronous IO can be performed by setting b_iodone to NULL. + * + * Allocate and use a buffer like this: + * + * ldi_buf_t *bp = (ldi_buf_t *)kmem_alloc(sizeof (ldi_buf_t), KM_SLEEP); + * // Verify allocation before proceeding + * error = ldi_bioinit(bp); + * bp->b_bcount = size; + * bp->b_bufsize = size; + * bp->b_offset = offset; + * bp->b_data = data_ptr; + * bp->b_flags = B_BUSY | B_NOCACHE | B_READ; // For example + * bp->b_iodone = &io_intr_func; // For async IO, omit for sync IO + * ldi_strategy(handle, bp); // Issue IO + * + * With an async callback function such as: + * void io_intr_func(ldi_buf_t bp, void *param) + * { + * // Check/copyout bp->b_error and bp->b_resid + * ldi_biofini(bp); + * kmem_free(bp, sizeof (ldi_buf_t)); + * } + */ + +/* + * XXX LDI TO DO + * + * LDI handle stats. In debug builds, we have IO counters - number of IOs, + * number of bytes in/out. + * kstats for handle counts and sysctls for vnode/IOKit modes also implemented. + * + * To implement events, both vnode and IOKit handles register for matching + * notifications from the IOMedia object (if found). + * Using subclassed IOService can also receive IOMessage events, which + * would be issued earlier. + * + * Vnode handles with no IOMedia could post events on (multiple) IO failures. + */ + +/* + * ZFS internal + */ +#include +#include +#include +#include + +/* + * LDI Includes + */ +#include + +/* Debug prints */ +#ifdef DEBUG +#define LDI_EVDBG(args) cmn_err args +#define LDI_EVTRC(args) cmn_err args +#else +#define LDI_EVDBG(args) do {} while (0) +#define LDI_EVTRC(args) do {} while (0) +#endif + +#define ldi_log(fmt, ...) do { \ + dprintf(fmt, __VA_ARGS__); \ + /* delay(hz>>1); */ \ +_NOTE(CONSTCOND) } while (0) + +/* + * Defines + * comment out defines to alter behavior. + */ +// #define LDI_ZERO /* For debugging, zero allocations */ + +/* Find IOMedia by matching on the BSD disk name. */ +static boolean_t ldi_use_iokit_from_path = 1; + +/* Find IOMedia by matching on the BSD major/minor (dev_t) number. */ +static boolean_t ldi_use_iokit_from_dev = 1; + +/* + * Find dev_t by vnode_lookup. + * Resolves symlinks to block devices, symlinks, InvariantDisk links. + */ +static boolean_t ldi_use_dev_from_path = 1; + +/* + * Open device by vnode if all else fails. + * Not intented to be a fallback for unsuccessful IOMedia open, but rather + * for bdev devices that do not have an IOMedia (published by other KEXTs). + */ +static boolean_t ldi_use_vnode_from_path = 1; + +/* + * Sysctls + */ +#include +SYSCTL_DECL(_ldi); +SYSCTL_NODE(, OID_AUTO, ldi, CTLFLAG_RD | CTLFLAG_LOCKED, 0, ""); +SYSCTL_NODE(_ldi, OID_AUTO, debug, CTLFLAG_RD | CTLFLAG_LOCKED, 0, ""); +SYSCTL_UINT(_ldi_debug, OID_AUTO, use_iokit_from_dev, + CTLFLAG_RW | CTLFLAG_LOCKED, &ldi_use_iokit_from_dev, 0, + "ZFS LDI use iokit_from_path"); +SYSCTL_UINT(_ldi_debug, OID_AUTO, use_iokit_from_path, + CTLFLAG_RW | CTLFLAG_LOCKED, &ldi_use_iokit_from_path, 0, + "ZFS LDI use iokit_from_dev"); +SYSCTL_UINT(_ldi_debug, OID_AUTO, use_dev_from_path, + CTLFLAG_RW | CTLFLAG_LOCKED, &ldi_use_dev_from_path, 0, + "ZFS LDI use dev_from_path"); +SYSCTL_UINT(_ldi_debug, OID_AUTO, use_vnode_from_path, + CTLFLAG_RW | CTLFLAG_LOCKED, &ldi_use_vnode_from_path, 0, + "ZFS LDI use vnode_from_path"); + +/* + * Globals + */ +static volatile int64_t ldi_handle_hash_count; + +static list_t ldi_handle_hash_list[LH_HASH_SZ]; +static kmutex_t ldi_handle_hash_lock[LH_HASH_SZ]; + +/* + * Use of "ldi_ev_callback_list" must be protected by ldi_ev_lock() + * and ldi_ev_unlock(). + */ +static struct ldi_ev_callback_list ldi_ev_callback_list; + +static uint32_t ldi_ev_id_pool = 0; + +struct ldi_ev_cookie { + char *ck_evname; + uint_t ck_sync; + uint_t ck_ctype; +}; + +#define CT_DEV_EV_OFFLINE 0x1 +#define CT_DEV_EV_DEGRADED 0x2 +static struct ldi_ev_cookie ldi_ev_cookies[] = { + {LDI_EV_OFFLINE, 1, CT_DEV_EV_OFFLINE}, + {LDI_EV_DEGRADE, 0, CT_DEV_EV_DEGRADED}, + {LDI_EV_DEVICE_REMOVE, 0, 0}, + {NULL} /* must terminate list */ +}; + +/* + * kstats + */ +static kstat_t *ldi_ksp; + +typedef struct ldi_stats { + kstat_named_t handle_count; + kstat_named_t handle_count_iokit; + kstat_named_t handle_count_vnode; + kstat_named_t handle_refs; + kstat_named_t handle_open_rw; + kstat_named_t handle_open_ro; +} ldi_stats_t; + +static ldi_stats_t ldi_stats = { + { "handle_count", KSTAT_DATA_UINT64 }, + { "handle_count_iokit", KSTAT_DATA_UINT64 }, + { "handle_count_vnode", KSTAT_DATA_UINT64 }, + { "handle_refs", KSTAT_DATA_UINT64 }, + { "handle_open_rw", KSTAT_DATA_UINT64 }, + { "handle_open_ro", KSTAT_DATA_UINT64 } +}; + +#define LDISTAT(stat) (ldi_stats.stat.value.ui64) +#define LDISTAT_INCR(stat, val) \ +atomic_add_64(&ldi_stats.stat.value.ui64, (val)) +#define LDISTAT_BUMP(stat) LDISTAT_INCR(stat, 1) +#define LDISTAT_BUMPDOWN(stat) LDISTAT_INCR(stat, -1) + +/* + * Define macros for accessing layered driver hash structures + */ +#define LH_HASH(dev) handle_hash_func(dev) + +static inline uint_t +handle_hash_func(dev_t device) +{ + /* Just cast, macro does modulus to hash value */ + return ((uint_t)device % LH_HASH_SZ); +} + +typedef struct status_change_args { + struct ldi_handle *lhp; + int new_status; +} status_change_args_t; + +static void +handle_status_change_callback(void *arg) +{ + status_change_args_t *sc = (status_change_args_t *)arg; + + /* Validate arg struct */ + if (!sc || !sc->lhp) { + dprintf("%s missing callback struct %p or lh\n", + __func__, sc); + return; + } + if (sc->new_status > LDI_STATUS_ONLINE) { + dprintf("%s invalid status %d\n", + __func__, sc->new_status); + return; + } + + dprintf("%s Invoking notify for handle %p status %d\n", + __func__, sc->lhp, sc->new_status); + ldi_invoke_notify(0 /* dip */, sc->lhp->lh_dev, S_IFBLK, + LDI_EV_OFFLINE, sc->lhp); + + dprintf("%s Invoking finalize for handle %p status %d\n", + __func__, sc->lhp, sc->new_status); + ldi_invoke_finalize(0 /* dip */, sc->lhp->lh_dev, S_IFBLK, + LDI_EV_OFFLINE, LDI_EV_SUCCESS, sc->lhp); + + /* Free callback struct */ + kmem_free(sc, sizeof (status_change_args_t)); +} + +/* Protected by handle lock */ +static int +handle_status_change_locked(struct ldi_handle *lhp, int new_status) +{ + status_change_args_t *sc = 0; + + /* Validate lhp */ + if (!lhp) { + dprintf("%s missing handle\n", __func__); + return (EINVAL); + } + if (new_status > LDI_STATUS_ONLINE) { + dprintf("%s invalid status %d\n", __func__, new_status); + return (EINVAL); + } + + ASSERT3U(lhp, !=, NULL); + ASSERT3U(lhp->lh_dev, !=, 0); + ASSERT(MUTEX_HELD(&lhp->lh_lock)); + + /* Set the status first */ + lhp->lh_status = new_status; + + /* Only Offline needs an event */ + if (new_status != LDI_STATUS_OFFLINE) { + dprintf("%s skipping status %d\n", __func__, new_status); + return (0); + } + + dprintf("%s new_status is Offline %d\n", __func__, new_status); + + /* Allocate struct to pass to event callback */ + /* Allocating with lock held, use KM_NOSLEEP */ + sc = (status_change_args_t *)kmem_alloc(sizeof (status_change_args_t), + KM_NOSLEEP); + if (!sc) { + dprintf("%s couldn't allocate callback struct\n", + __func__); + return (ENOMEM); + } + sc->lhp = lhp; + sc->new_status = new_status; + + mutex_exit(&lhp->lh_lock); /* Currently needs to drop lock */ + handle_status_change_callback((void *)sc); + mutex_enter(&lhp->lh_lock); /* Retake before return */ + + return (0); +} + +/* Protected by handle lock */ +int +handle_status_change(struct ldi_handle *lhp, int new_status) +{ + int error; + + /* Validate lh and new_status */ + if (!lhp) { + dprintf("%s missing handle\n", __func__); + return (EINVAL); + } + if (new_status > LDI_STATUS_ONLINE) { + dprintf("%s invalid state %d\n", __func__, new_status); + return (EINVAL); + } + + mutex_enter(&lhp->lh_lock); + error = handle_status_change_locked(lhp, new_status); + mutex_exit(&lhp->lh_lock); + + return (error); +} + +/* Protected by hash list lock */ +void +handle_hold_locked(struct ldi_handle *lhp) +{ +#ifdef DEBUG + int index; + + ASSERT3U(lhp, !=, NULL); + index = LH_HASH(lhp->lh_dev); + ASSERT(MUTEX_HELD(&ldi_handle_hash_lock[index])); +#endif + + /* Increment ref count and kstat */ + lhp->lh_ref++; + LDISTAT_BUMP(handle_refs); +} + +/* Protected by hash list lock */ +void +handle_hold(struct ldi_handle *lhp) +{ + int index; + + ASSERT3U(lhp, !=, NULL); + ASSERT3U(lhp->lh_dev, !=, 0); + + index = LH_HASH(lhp->lh_dev); + mutex_enter(&ldi_handle_hash_lock[index]); + handle_hold_locked(lhp); + mutex_exit(&ldi_handle_hash_lock[index]); +} + +/* + * Locate existing handle in linked list, may return NULL. Optionally places a + * hold on found handle. + */ +static struct ldi_handle * +handle_find_locked(dev_t device, int fmode, boolean_t hold) +{ + struct ldi_handle *retlhp = NULL, *lhp; + int index = LH_HASH(device); + + /* Validate device */ + if (device == 0) { + dprintf("%s invalid device\n", __func__); + return (NULL); + } + /* If fmode is 0, find any handle with matching dev_t */ + + ASSERT(MUTEX_HELD(&ldi_handle_hash_lock[index])); + + /* Iterate over handle hash list */ + for (lhp = list_head(&ldi_handle_hash_list[index]); + lhp != NULL; + lhp = list_next(&ldi_handle_hash_list[index], lhp)) { + /* Check for matching dev_t and fmode (if set) */ + if (lhp->lh_dev != device) { + continue; + } + + /* Special case for find any */ + if (fmode == 0) { + /* Found a match */ + retlhp = lhp; + break; + } + + /* fmode must match write level */ + if (((lhp->lh_fmode & FWRITE) && !(fmode & FWRITE)) || + (!(lhp->lh_fmode & FWRITE) && (fmode & FWRITE))) { + continue; + } + + /* Found a match */ + retlhp = lhp; + break; + } + + /* Take hold, if requested */ + if (hold && retlhp) { + /* Caller asked for hold on found handle */ + handle_hold_locked(retlhp); + } + + return (retlhp); +} + +/* + * Call without lock held to find a handle by dev_t, + * optionally placing a hold on the found handle. + */ +struct ldi_handle * +handle_find(dev_t device, int fmode, boolean_t hold) +{ + struct ldi_handle *lhp; + int index = LH_HASH(device); + + if (device == 0) { + dprintf("%s invalid device\n", __func__); + return (NULL); + } + + /* Lock for duration of find */ + mutex_enter(&ldi_handle_hash_lock[index]); + + /* Find handle by dev_t (with hold) */ + lhp = handle_find_locked(device, fmode, hold); + + /* Unlock and return handle (could be NULL) */ + mutex_exit(&ldi_handle_hash_lock[index]); + return (lhp); +} + +static void +handle_free(struct ldi_handle *lhp) +{ + ASSERT3U(lhp, !=, NULL); + + /* Validate lhp, references, and status */ + if (lhp->lh_ref != 0 || + lhp->lh_status != LDI_STATUS_CLOSED) { + dprintf("%s ref %d status %d\n", __func__, lhp->lh_ref, + lhp->lh_status); + } + + /* Remove notification handler */ + if (handle_remove_notifier(lhp) != 0) { + dprintf("%s lhp %p notifier %s\n", + __func__, lhp, "couldn't be removed"); + } + + /* Destroy condvar and mutex */ + cv_destroy(&lhp->lh_cv); + mutex_destroy(&lhp->lh_lock); + + /* Decrement kstat handle count */ + LDISTAT_BUMPDOWN(handle_count); + /* IOKit or vnode */ + switch (lhp->lh_type) { + case LDI_TYPE_IOKIT: + /* Decrement kstat handle count and free iokit_tsd */ + LDISTAT_BUMPDOWN(handle_count_iokit); + handle_free_iokit(lhp); + break; + + case LDI_TYPE_VNODE: + /* Decrement kstat handle count and free vnode_tsd */ + LDISTAT_BUMPDOWN(handle_count_vnode); + handle_free_vnode(lhp); + break; + default: + dprintf("%s invalid handle type\n", __func__); + break; + } + + /* Deallocate handle */ + dprintf("%s freeing %p\n", __func__, lhp); + kmem_free(lhp, sizeof (struct ldi_handle)); + lhp = 0; +} + +/* + * Remove handle from list, decrementing counters + */ +static void +handle_remove_locked(struct ldi_handle *lhp) +{ + int index; + + ASSERT3U(lhp, !=, NULL); + index = LH_HASH(lhp->lh_dev); + ASSERT(MUTEX_HELD(&ldi_handle_hash_lock[index])); + + /* Remove from list, update handle count */ + if (list_link_active(&lhp->lh_node)) + list_remove(&ldi_handle_hash_list[index], lhp); + OSDecrementAtomic(&ldi_handle_hash_count); +} + +void +handle_remove(struct ldi_handle *lhp) +{ + int index = LH_HASH(lhp->lh_dev); + + mutex_enter(&ldi_handle_hash_lock[index]); + handle_remove_locked(lhp); + mutex_exit(&ldi_handle_hash_lock[index]); +} + +/* Protected by hash list lock */ +static void +handle_release_locked(struct ldi_handle *lhp) +{ + boolean_t lastrelease = B_FALSE; + +#ifdef DEBUG + ASSERT3U(lhp, !=, NULL); + int index = LH_HASH(lhp->lh_dev); + ASSERT(MUTEX_HELD(&ldi_handle_hash_lock[index])); +#endif + + if (lhp->lh_ref != 0) { + lhp->lh_ref--; + LDISTAT_BUMPDOWN(handle_refs); + } else { + dprintf("%s with 0 refs\n", __func__); + } + + dprintf("%s %x remaining holds\n", __func__, lhp->lh_ref); + + /* If last open ref was dropped */ + lastrelease = (lhp->lh_ref == 0); + + if (lastrelease) { + dprintf("%s removing handle %p from list\n", __func__, lhp); + handle_remove_locked(lhp); + dprintf("%s freeing handle %p\n", __func__, lhp); + handle_free(lhp); + } +} + +/* Protected by hash list lock */ +void +handle_release(struct ldi_handle *lhp) +{ + int index; + + ASSERT3U(lhp, !=, NULL); + index = LH_HASH(lhp->lh_dev); + + mutex_enter(&ldi_handle_hash_lock[index]); + handle_release_locked(lhp); + mutex_exit(&ldi_handle_hash_lock[index]); +} + +/* + * Add new handle to list. + */ +static struct ldi_handle * +handle_add_locked(struct ldi_handle *lhp) +{ + struct ldi_handle *retlhp; + int index = 0; + + ASSERT3U(lhp, !=, NULL); + ASSERT3U(lhp->lh_dev, !=, 0); + + /* Lock should be held */ + index = LH_HASH(lhp->lh_dev); + ASSERT(MUTEX_HELD(&ldi_handle_hash_lock[index])); + + /* Search for existing handle */ + if ((retlhp = handle_find_locked(lhp->lh_dev, lhp->lh_fmode, + B_TRUE)) != NULL) { + dprintf("%s found handle %p\n", __func__, retlhp); + return (retlhp); + } + + /* Insert into list */ + list_insert_head(&ldi_handle_hash_list[index], lhp); + + /* Update handle count */ + OSIncrementAtomic(&ldi_handle_hash_count); + + /* Return success */ + return (lhp); +} + +/* + * Caller should check if returned handle is the same and free new + * handle if an existing handle was returned + */ +struct ldi_handle * +handle_add(struct ldi_handle *lhp) +{ + struct ldi_handle *retlhp; + int index; + + ASSERT3U(lhp, !=, NULL); + index = LH_HASH(lhp->lh_dev); + + mutex_enter(&ldi_handle_hash_lock[index]); + retlhp = handle_add_locked(lhp); + mutex_exit(&ldi_handle_hash_lock[index]); + + return (retlhp); +} + +/* + * Returns a handle with 1 reference and status Closed + */ +#ifdef illumos +static struct ldi_handle * +handle_alloc(vnode_t *vp, struct ldi_ident_t *li) +#else /* illumos */ +struct ldi_handle * +handle_alloc_common(uint_t type, dev_t device, int fmode) +#endif /* !illumos */ +{ + struct ldi_handle *new_lh; + size_t len; + + /* Validate arguments */ + if ((type != LDI_TYPE_IOKIT && type != LDI_TYPE_VNODE) || + device == 0 || fmode == 0) { + dprintf("%s Invalid type %d, device %d, or fmode %d\n", + __func__, type, device, fmode); + return (NULL); + } + + /* Allocate and verify */ + len = sizeof (struct ldi_handle); + if (NULL == (new_lh = (struct ldi_handle *)kmem_alloc(len, + KM_SLEEP))) { + dprintf("%s couldn't allocate ldi_handle\n", __func__); + return (NULL); + } +#ifdef LDI_ZERO + /* Clear the struct for safety */ + memset(new_lh, 0, len); +#endif + + /* Create handle lock */ + mutex_init(&new_lh->lh_lock, NULL, MUTEX_DEFAULT, NULL); + /* And condvar */ + cv_init(&new_lh->lh_cv, NULL, CV_DEFAULT, NULL); + + /* + * Set the handle type, which dictates the type of device pointer + * and buffers used for the lifetime of the ldi_handle + */ + new_lh->lh_type = type; + /* Set dev_t (major/minor) device number */ + new_lh->lh_dev = device; + + /* Clear list head */ + new_lh->lh_node.list_next = NULL; + new_lh->lh_node.list_prev = NULL; + + /* Initialize with 1 handle ref and 0 open refs */ + new_lh->lh_ref = 1; + new_lh->lh_openref = 0; + + /* Clear type-specific device data */ + new_lh->lh_tsd.iokit_tsd = 0; + /* No need to clear vnode_tsd in union */ + new_lh->lh_notifier = 0; + + /* Assign fmode */ + new_lh->lh_fmode = fmode; + + /* Alloc in status Closed */ + new_lh->lh_status = LDI_STATUS_CLOSED; + + /* Increment kstats */ + LDISTAT_BUMP(handle_count); + LDISTAT_BUMP(handle_refs); + if (type == LDI_TYPE_IOKIT) { + LDISTAT_BUMP(handle_count_iokit); + } else if (type == LDI_TYPE_VNODE) { + LDISTAT_BUMP(handle_count_vnode); + } + + return (new_lh); +} + +static void +handle_set_open_locked(struct ldi_handle *lhp) +{ + ASSERT3U(lhp, !=, NULL); + ASSERT(MUTEX_HELD(&lhp->lh_lock)); + + /* Increment number of open clients */ + lhp->lh_openref++; + + /* Increment kstats */ + if (lhp->lh_fmode & FWRITE) { + LDISTAT_BUMP(handle_open_rw); + } else { + LDISTAT_BUMP(handle_open_ro); + } +} + +#if 0 +static void +handle_set_open(struct ldi_handle *lhp) +{ + ASSERT3U(lhp, !=, NULL); + + mutex_enter(&lhp->lh_lock); + handle_set_open_locked(lhp); + mutex_exit(&lhp->lh_lock); +} +#endif + +static void +handle_clear_open_locked(struct ldi_handle *lhp) +{ + ASSERT3U(lhp, !=, NULL); + ASSERT(MUTEX_HELD(&lhp->lh_lock)); + + /* Decrement number of open clients */ + if (lhp->lh_openref == 0) { + dprintf("%s with 0 open refs\n", __func__); + return; + } + + /* Decrement kstats */ + lhp->lh_openref--; + if (lhp->lh_fmode & FWRITE) { + LDISTAT_BUMPDOWN(handle_open_rw); + } else { + LDISTAT_BUMPDOWN(handle_open_ro); + } +} + +#if 0 +static inline void +handle_clear_open(struct ldi_handle *lhp) +{ + ASSERT3U(lhp, !=, NULL); + ASSERT3U(lhp->lh_dev, !=, 0); + ASSERT3U(lhp->lh_openref, !=, 0); + + mutex_enter(&lhp->lh_lock); + handle_clear_open_locked(lhp, lhp->lh_fmode); + mutex_exit(&lhp->lh_lock); +} +#endif + +static int +handle_close(struct ldi_handle *lhp) +{ +#ifdef DEBUG + int openrefs; +#endif + int error = EINVAL; + + ASSERT3U(lhp, !=, NULL); + ASSERT3U(lhp->lh_ref, !=, 0); + ASSERT3U(lhp->lh_openref, !=, 0); + ASSERT(lhp->lh_type == LDI_TYPE_IOKIT || + lhp->lh_type == LDI_TYPE_VNODE); + + /* Take lock */ + mutex_enter(&lhp->lh_lock); + + /* + * Possible statuses: + * Online with one or more openref + * Offline due to IOMedia termination, one or more openref remain + * Impossible or programming error: + * Closing and Closed should only be set with 0 openref + * Opening should have 0 openref so far, and clients should not be + * calling ldi_close + */ + switch (lhp->lh_status) { + case LDI_STATUS_ONLINE: + if (lhp->lh_openref == 0) { + /* Unlock and return error */ + mutex_exit(&lhp->lh_lock); + /* Shouldn't happen */ + dprintf("%s status Online with 0 openrefs\n", + __func__); + return (ENXIO); + } + + /* If multiple open refs are held */ + if (lhp->lh_openref > 1) { + goto drop_openref; + } + + /* Otherwise open with last open ref */ + /* change status to closing and proceed */ + handle_status_change_locked(lhp, LDI_STATUS_CLOSING); + /* Unlock and exit loop */ + mutex_exit(&lhp->lh_lock); + goto do_close; + + case LDI_STATUS_OFFLINE: + if (lhp->lh_openref == 0) { + /* Unlock and return error */ + mutex_exit(&lhp->lh_lock); + /* Shouldn't happen */ + dprintf("%s status Offline with 0 openrefs\n", + __func__); + return (ENXIO); + } + + /* + * Otherwise the device was marked missing and clients need + * to drop openrefs until it can be released. + */ + goto drop_openref; + + default: + mutex_exit(&lhp->lh_lock); + dprintf("%s invalid handle status %d\n", + __func__, lhp->lh_status); + return (ENXIO); + } + +drop_openref: + /* Just decrement open refs/stats */ + handle_clear_open_locked(lhp); +#ifdef DEBUG + /* Save openrefs to report after unlock */ + openrefs = lhp->lh_openref; +#endif + mutex_exit(&lhp->lh_lock); + +#ifdef DEBUG + dprintf("%s has %d remaining openrefs\n", __func__, openrefs); +#endif + return (0); + +do_close: + /* Remove notification handler */ + if (lhp->lh_notifier) { + error = handle_remove_notifier(lhp); + if (error) { + dprintf("%s lhp %p notifier %p error %d %s\n", + __func__, lhp, lhp->lh_notifier, error, + "couldn't be removed"); + /* Proceeds with close */ + } + } + + /* IOMedia or vnode */ + switch (lhp->lh_type) { + case LDI_TYPE_IOKIT: + error = handle_close_iokit(lhp); + /* Preserve error for return */ + break; + case LDI_TYPE_VNODE: + error = handle_close_vnode(lhp); + /* Preserve error for return */ + break; + } + +#ifdef DEBUG + if (error != 0) { + /* We will still set the handle to Closed status */ + dprintf("%s error %d from handle_close_{type}\n", + __func__, error); + } +#endif + + /* Take lock to drop openref and set status */ + mutex_enter(&lhp->lh_lock); + handle_clear_open_locked(lhp); + handle_status_change_locked(lhp, LDI_STATUS_CLOSED); + + /* Wake any waiting opens and unlock */ + cv_signal(&lhp->lh_cv); + mutex_exit(&lhp->lh_lock); + +dprintf("%s returning %d\n", __func__, error); + return (error); +} + +ldi_status_t +handle_open_start(struct ldi_handle *lhp) +{ + ASSERT3U(lhp, !=, NULL); + ASSERT3U(lhp->lh_ref, !=, 0); + + /* Take lock */ + mutex_enter(&lhp->lh_lock); + /* Loop if the handle is in opening or closing status */ + do { + /* XXX Needs sleep timeout */ + switch (lhp->lh_status) { + case LDI_STATUS_ONLINE: + /* Increment readonly / readwrite count */ + handle_set_open_locked(lhp); + mutex_exit(&lhp->lh_lock); + + /* Success */ + return (LDI_STATUS_ONLINE); + + case LDI_STATUS_CLOSED: + /* Not yet open, change status to opening and proceed */ + handle_status_change_locked(lhp, LDI_STATUS_OPENING); + + /* Unlock and exit loop */ + mutex_exit(&lhp->lh_lock); + /* Return success */ + return (LDI_STATUS_OPENING); + + case LDI_STATUS_OPENING: + case LDI_STATUS_CLOSING: + /* Open or close in progress, sleep until signaled */ + dprintf("%s sleeping on lock\n", __func__); + cv_wait(&lhp->lh_cv, &lhp->lh_lock); + continue; + default: + mutex_exit(&lhp->lh_lock); + dprintf("%s invalid handle status %d\n", + __func__, lhp->lh_status); + return (LDI_STATUS_OFFLINE); + } + } while (1); + + /* Shouldn't reach this */ + return (LDI_STATUS_CLOSED); +} + +void +handle_open_done(struct ldi_handle *lhp, ldi_status_t new_status) +{ + ASSERT3U(lhp, !=, NULL); + ASSERT3U(lhp->lh_status, ==, LDI_STATUS_OPENING); + + /* Lock to change status */ + mutex_enter(&lhp->lh_lock); + + if (new_status != LDI_STATUS_ONLINE) { + /* Set status, issues event */ + handle_status_change_locked(lhp, LDI_STATUS_CLOSED); + } else { + /* Increment open count and fmode */ + handle_set_open_locked(lhp); + /* Set status, issues event */ + handle_status_change_locked(lhp, LDI_STATUS_ONLINE); + } + + /* Wake any waiting opens and unlock */ + cv_signal(&lhp->lh_cv); + mutex_exit(&lhp->lh_lock); + + /* + * Flush out any old buffers remaining from + * a previous use, only if opening read-write. + */ + if (new_status == LDI_STATUS_ONLINE && + (lhp->lh_fmode & FWRITE) && + ldi_sync((ldi_handle_t)lhp) != 0) { + dprintf("%s ldi_sync failed\n", __func__); + } +} + +/* + * Release all remaining handles (during ldi_fini) + * Unless something went wrong, all handles should + * be closed and have zero references. + */ +static void +handle_hash_release() +{ + struct ldi_handle *lhp; + int index, refs, j; + + for (index = 0; index < LH_HASH_SZ; index++) { + mutex_enter(&ldi_handle_hash_lock[index]); + if (!list_empty(&ldi_handle_hash_list[index])) { + dprintf("%s still have LDI handle(s) in list %d\n", + __func__, index); + } + + /* Iterate over the list */ + while ((lhp = list_head(&ldi_handle_hash_list[index]))) { + /* remove from list to deallocate */ + list_remove(&ldi_handle_hash_list[index], lhp); + + /* Update handle count */ + OSDecrementAtomic(&ldi_handle_hash_count); + + dprintf("%s releasing %p with %u refs and status %d\n", + __func__, lhp, lhp->lh_ref, lhp->lh_status); + /* release holds */ + refs = lhp->lh_ref; + for (j = 0; j < refs; j++) { + handle_release_locked(lhp); + } + lhp = 0; + } + + list_destroy(&ldi_handle_hash_list[index]); + mutex_exit(&ldi_handle_hash_lock[index]); + mutex_destroy(&ldi_handle_hash_lock[index]); + } +} + +/* + * LDI Event functions + */ +char * +ldi_ev_get_type(ldi_ev_cookie_t cookie) +{ + int i; + struct ldi_ev_cookie *cookie_impl = (struct ldi_ev_cookie *)cookie; + + for (i = 0; ldi_ev_cookies[i].ck_evname != NULL; i++) { + if (&ldi_ev_cookies[i] == cookie_impl) { + LDI_EVTRC((CE_NOTE, "ldi_ev_get_type: LDI: %s", + ldi_ev_cookies[i].ck_evname)); + return (ldi_ev_cookies[i].ck_evname); + } + } + + return ("UNKNOWN EVENT"); +} + +static int +ldi_native_cookie(ldi_ev_cookie_t cookie) +{ + int i; + struct ldi_ev_cookie *cookie_impl = (struct ldi_ev_cookie *)cookie; + + for (i = 0; ldi_ev_cookies[i].ck_evname != NULL; i++) { + if (&ldi_ev_cookies[i] == cookie_impl) { + LDI_EVTRC((CE_NOTE, "ldi_native_cookie: native LDI")); + return (1); + } + } + + LDI_EVTRC((CE_NOTE, "ldi_native_cookie: is NDI")); + return (0); +} + +static ldi_ev_cookie_t +ldi_get_native_cookie(const char *evname) +{ + int i; + + for (i = 0; ldi_ev_cookies[i].ck_evname != NULL; i++) { + if (strcmp(ldi_ev_cookies[i].ck_evname, evname) == 0) { + LDI_EVTRC((CE_NOTE, "ldi_get_native_cookie: found")); + return ((ldi_ev_cookie_t)&ldi_ev_cookies[i]); + } + } + + LDI_EVTRC((CE_NOTE, "ldi_get_native_cookie: NOT found")); + return (NULL); +} + +/* + * ldi_ev_lock() needs to be recursive, since layered drivers may call + * other LDI interfaces (such as ldi_close() from within the context of + * a notify callback. Since the notify callback is called with the + * ldi_ev_lock() held and ldi_close() also grabs ldi_ev_lock, the lock needs + * to be recursive. + */ +static void +ldi_ev_lock(void) +{ + LDI_EVTRC((CE_NOTE, "ldi_ev_lock: entered")); + + mutex_enter(&ldi_ev_callback_list.le_lock); + if (ldi_ev_callback_list.le_thread == curthread) { + ASSERT(ldi_ev_callback_list.le_busy >= 1); + ldi_ev_callback_list.le_busy++; + } else { + while (ldi_ev_callback_list.le_busy) + cv_wait(&ldi_ev_callback_list.le_cv, + &ldi_ev_callback_list.le_lock); + ASSERT(ldi_ev_callback_list.le_thread == NULL); + ldi_ev_callback_list.le_busy = 1; + ldi_ev_callback_list.le_thread = curthread; + } + mutex_exit(&ldi_ev_callback_list.le_lock); + + LDI_EVTRC((CE_NOTE, "ldi_ev_lock: exit")); +} + +static void +ldi_ev_unlock(void) +{ + LDI_EVTRC((CE_NOTE, "ldi_ev_unlock: entered")); + mutex_enter(&ldi_ev_callback_list.le_lock); + ASSERT(ldi_ev_callback_list.le_thread == curthread); + ASSERT(ldi_ev_callback_list.le_busy >= 1); + + ldi_ev_callback_list.le_busy--; + if (ldi_ev_callback_list.le_busy == 0) { + ldi_ev_callback_list.le_thread = NULL; + cv_signal(&ldi_ev_callback_list.le_cv); + } + mutex_exit(&ldi_ev_callback_list.le_lock); + LDI_EVTRC((CE_NOTE, "ldi_ev_unlock: exit")); +} + +int +ldi_ev_get_cookie(ldi_handle_t lh, char *evname, ldi_ev_cookie_t *cookiep) +{ + ldi_ev_cookie_t tcookie; + + LDI_EVDBG((CE_NOTE, "ldi_ev_get_cookie: entered: evname=%s", + evname ? evname : "")); + + if (lh == NULL || evname == NULL || + strlen(evname) == 0 || cookiep == NULL) { + LDI_EVDBG((CE_NOTE, "ldi_ev_get_cookie: invalid args")); + return (LDI_EV_FAILURE); + } + + *cookiep = NULL; + + /* + * First check if it is a LDI native event + */ + tcookie = ldi_get_native_cookie(evname); + if (tcookie) { + LDI_EVDBG((CE_NOTE, "ldi_ev_get_cookie: got native cookie")); + *cookiep = tcookie; + return (LDI_EV_SUCCESS); + } + + return (LDI_EV_FAILURE); +} + +int +ldi_ev_register_callbacks(ldi_handle_t lh, ldi_ev_cookie_t cookie, + ldi_ev_callback_t *callb, void *arg, ldi_callback_id_t *id) +{ + struct ldi_handle *lhp = (struct ldi_handle *)lh; + ldi_ev_callback_impl_t *lecp; + + if (lh == NULL || cookie == NULL || callb == NULL || id == NULL) { + LDI_EVDBG((CE_NOTE, "ldi_ev_register_callbacks: Invalid args")); + return (LDI_EV_FAILURE); + } + + if (callb->cb_vers != LDI_EV_CB_VERS) { + LDI_EVDBG((CE_NOTE, "ldi_ev_register_callbacks: Invalid vers")); + return (LDI_EV_FAILURE); + } + + if (callb->cb_notify == NULL && callb->cb_finalize == NULL) { + LDI_EVDBG((CE_NOTE, "ldi_ev_register_callbacks: NULL callb")); + return (LDI_EV_FAILURE); + } + + *id = 0; + + lecp = kmem_zalloc(sizeof (ldi_ev_callback_impl_t), KM_SLEEP); + + ldi_ev_lock(); + + /* + * Add the notify/finalize callback to the LDI's list of callbacks. + */ + lecp->lec_lhp = lhp; + + lecp->lec_dev = lhp->lh_dev; + lecp->lec_spec = S_IFBLK; + + lecp->lec_notify = callb->cb_notify; + lecp->lec_finalize = callb->cb_finalize; + lecp->lec_arg = arg; + lecp->lec_cookie = cookie; + + lecp->lec_id = (void *)(uintptr_t)(++ldi_ev_id_pool); + + list_insert_tail(&ldi_ev_callback_list.le_head, lecp); + + *id = (ldi_callback_id_t)lecp->lec_id; + + ldi_ev_unlock(); + + LDI_EVDBG((CE_NOTE, "ldi_ev_register_callbacks: registered " + "notify/finalize")); + + return (LDI_EV_SUCCESS); +} + +static int +ldi_ev_device_match(ldi_ev_callback_impl_t *lecp, __unused dev_info_t *dip, + dev_t dev, int spec_type) +{ + ASSERT(lecp); + ASSERT(dev != DDI_DEV_T_NONE); + ASSERT(dev != NODEV); + ASSERT((dev == DDI_DEV_T_ANY && spec_type == 0) || + (spec_type == S_IFCHR || spec_type == S_IFBLK)); + ASSERT(lecp->lec_spec == S_IFCHR || lecp->lec_spec == S_IFBLK); + ASSERT(lecp->lec_dev != DDI_DEV_T_ANY); + ASSERT(lecp->lec_dev != DDI_DEV_T_NONE); + ASSERT(lecp->lec_dev != NODEV); + + if (dev != DDI_DEV_T_ANY) { + if (dev != lecp->lec_dev || spec_type != lecp->lec_spec) + return (0); + } + + LDI_EVTRC((CE_NOTE, "ldi_ev_device_match: MATCH dev=%d", + (uint32_t)dev)); + + return (1); +} + +/* + * LDI framework function to post a "notify" event to all layered drivers + * that have registered for that event + * + * Returns: + * LDI_EV_SUCCESS - registered callbacks allow event + * LDI_EV_FAILURE - registered callbacks block event + * LDI_EV_NONE - No matching LDI callbacks + * + * This function is *not* to be called by layered drivers. It is for I/O + * framework code in Solaris, such as the I/O retire code and DR code + * to call while servicing a device event such as offline or degraded. + */ +int +ldi_invoke_notify(__unused dev_info_t *dip, dev_t dev, int spec_type, + char *event, void *ev_data) +{ + ldi_ev_callback_impl_t *lecp; + list_t *listp; + int ret; + char *lec_event; + + ASSERT(dev != DDI_DEV_T_NONE); + ASSERT(dev != NODEV); + ASSERT((dev == DDI_DEV_T_ANY && spec_type == 0) || + (spec_type == S_IFCHR || spec_type == S_IFBLK)); + ASSERT(event); + + LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): entered: dip=%p, ev=%s", + (void *)dip, event)); + + ret = LDI_EV_NONE; + ldi_ev_lock(); + + VERIFY(ldi_ev_callback_list.le_walker_next == NULL); + listp = &ldi_ev_callback_list.le_head; + for (lecp = list_head(listp); lecp; lecp = + ldi_ev_callback_list.le_walker_next) { + ldi_ev_callback_list.le_walker_next = list_next(listp, lecp); + + /* Check if matching device */ + if (!ldi_ev_device_match(lecp, dip, dev, spec_type)) + continue; + + if (lecp->lec_lhp == NULL) { + /* + * Consumer has unregistered the handle and so + * is no longer interested in notify events. + */ + LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): No LDI " + "handle, skipping")); + continue; + } + + if (lecp->lec_notify == NULL) { + LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): No notify " + "callback. skipping")); + continue; /* not interested in notify */ + } + + /* + * Check if matching event + */ + lec_event = ldi_ev_get_type(lecp->lec_cookie); + if (strcmp(event, lec_event) != 0) { + LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): Not matching" + " event {%s,%s}. skipping", event, lec_event)); + continue; + } + + lecp->lec_lhp->lh_flags |= LH_FLAGS_NOTIFY; + if (lecp->lec_notify((ldi_handle_t)lecp->lec_lhp, + lecp->lec_cookie, lecp->lec_arg, ev_data) != + LDI_EV_SUCCESS) { + ret = LDI_EV_FAILURE; + LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): notify" + " FAILURE")); + break; + } + + /* We have a matching callback that allows the event to occur */ + ret = LDI_EV_SUCCESS; + + LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): 1 consumer success")); + } + + if (ret != LDI_EV_FAILURE) + goto out; + +#ifdef __APPLE__ + dprintf("%s offline notify failed, shouldn't happen\n", __func__); + goto out; +#endif +#ifdef illumos + LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): undoing notify")); + + /* + * Undo notifies already sent + */ + lecp = list_prev(listp, lecp); + VERIFY(ldi_ev_callback_list.le_walker_prev == NULL); + for (; lecp; lecp = ldi_ev_callback_list.le_walker_prev) { + ldi_ev_callback_list.le_walker_prev = list_prev(listp, lecp); + + /* + * Check if matching device + */ + if (!ldi_ev_device_match(lecp, dip, dev, spec_type)) + continue; + + if (lecp->lec_finalize == NULL) { + LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): no finalize, " + "skipping")); + continue; /* not interested in finalize */ + } + + /* + * it is possible that in response to a notify event a + * layered driver closed its LDI handle so it is ok + * to have a NULL LDI handle for finalize. The layered + * driver is expected to maintain state in its "arg" + * parameter to keep track of the closed device. + */ + + /* Check if matching event */ + lec_event = ldi_ev_get_type(lecp->lec_cookie); + if (strcmp(event, lec_event) != 0) { + LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): not matching " + "event: %s,%s, skipping", event, lec_event)); + continue; + } + + LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): calling finalize")); + + lecp->lec_finalize(lecp->lec_lhp, lecp->lec_cookie, + LDI_EV_FAILURE, lecp->lec_arg, ev_data); + + /* + * If LDI native event and LDI handle closed in context + * of notify, NULL out the finalize callback as we have + * already called the 1 finalize above allowed in this situation + */ + if (lecp->lec_lhp == NULL && + ldi_native_cookie(lecp->lec_cookie)) { + LDI_EVDBG((CE_NOTE, + "ldi_invoke_notify(): NULL-ing finalize after " + "calling 1 finalize following ldi_close")); + lecp->lec_finalize = NULL; + } + } +#endif /* illumos */ + +out: + ldi_ev_callback_list.le_walker_next = NULL; + ldi_ev_callback_list.le_walker_prev = NULL; + ldi_ev_unlock(); + + if (ret == LDI_EV_NONE) { + LDI_EVDBG((CE_NOTE, "ldi_invoke_notify(): no matching " + "LDI callbacks")); + } + + return (ret); +} + +/* + * LDI framework function to invoke "finalize" callbacks for all layered + * drivers that have registered callbacks for that event. + * + * This function is *not* to be called by layered drivers. It is for I/O + * framework code in Solaris, such as the I/O retire code and DR code + * to call while servicing a device event such as offline or degraded. + */ +void +ldi_invoke_finalize(__unused dev_info_t *dip, dev_t dev, int spec_type, + char *event, int ldi_result, void *ev_data) +{ + ldi_ev_callback_impl_t *lecp; + list_t *listp; + char *lec_event; + int found = 0; + + ASSERT(dev != DDI_DEV_T_NONE); + ASSERT(dev != NODEV); + ASSERT((dev == DDI_DEV_T_ANY && spec_type == 0) || + (spec_type == S_IFCHR || spec_type == S_IFBLK)); + ASSERT(event); + ASSERT(ldi_result == LDI_EV_SUCCESS || ldi_result == LDI_EV_FAILURE); + + LDI_EVDBG((CE_NOTE, "ldi_invoke_finalize(): entered: dip=%p, result=%d" + " event=%s", (void *)dip, ldi_result, event)); + + ldi_ev_lock(); + VERIFY(ldi_ev_callback_list.le_walker_next == NULL); + listp = &ldi_ev_callback_list.le_head; + for (lecp = list_head(listp); lecp; lecp = + ldi_ev_callback_list.le_walker_next) { + ldi_ev_callback_list.le_walker_next = list_next(listp, lecp); + + if (lecp->lec_finalize == NULL) { + LDI_EVDBG((CE_NOTE, "ldi_invoke_finalize(): No " + "finalize. Skipping")); + continue; /* Not interested in finalize */ + } + + /* + * Check if matching device + */ + if (!ldi_ev_device_match(lecp, dip, dev, spec_type)) + continue; + + /* + * It is valid for the LDI handle to be NULL during finalize. + * The layered driver may have done an LDI close in the notify + * callback. + */ + + /* + * Check if matching event + */ + lec_event = ldi_ev_get_type(lecp->lec_cookie); + if (strcmp(event, lec_event) != 0) { + LDI_EVDBG((CE_NOTE, "ldi_invoke_finalize(): Not " + "matching event {%s,%s}. Skipping", + event, lec_event)); + continue; + } + + LDI_EVDBG((CE_NOTE, "ldi_invoke_finalize(): calling finalize")); + + found = 1; + + lecp->lec_finalize((ldi_handle_t)lecp->lec_lhp, + lecp->lec_cookie, ldi_result, lecp->lec_arg, + ev_data); + + /* + * If LDI native event and LDI handle closed in context + * of notify, NULL out the finalize callback as we have + * already called the 1 finalize above allowed in this situation + */ + if (lecp->lec_lhp == NULL && + ldi_native_cookie(lecp->lec_cookie)) { + LDI_EVDBG((CE_NOTE, + "ldi_invoke_finalize(): NULLing finalize after " + "calling 1 finalize following ldi_close")); + lecp->lec_finalize = NULL; + } + } + ldi_ev_callback_list.le_walker_next = NULL; + ldi_ev_unlock(); + + if (found) + return; + + LDI_EVDBG((CE_NOTE, "ldi_invoke_finalize(): no matching callbacks")); +} + +int +ldi_ev_remove_callbacks(ldi_callback_id_t id) +{ + ldi_ev_callback_impl_t *lecp; + ldi_ev_callback_impl_t *next; + ldi_ev_callback_impl_t *found; + list_t *listp; + + if (id == 0) { + cmn_err(CE_WARN, "ldi_ev_remove_callbacks: Invalid ID 0"); + return (LDI_EV_FAILURE); + } + + LDI_EVDBG((CE_NOTE, "ldi_ev_remove_callbacks: entered: id=%p", + (void *)id)); + + ldi_ev_lock(); + + listp = &ldi_ev_callback_list.le_head; + next = found = NULL; + for (lecp = list_head(listp); lecp; lecp = next) { + next = list_next(listp, lecp); + if (lecp->lec_id == id) { + VERIFY(found == NULL); + + /* + * If there is a walk in progress, shift that walk + * along to the next element so that we can remove + * this one. This allows us to unregister an arbitrary + * number of callbacks from within a callback. + * + * See the struct definition (in sunldi_impl.h) for + * more information. + */ + if (ldi_ev_callback_list.le_walker_next == lecp) + ldi_ev_callback_list.le_walker_next = next; + if (ldi_ev_callback_list.le_walker_prev == lecp) + ldi_ev_callback_list.le_walker_prev = list_prev( + listp, ldi_ev_callback_list.le_walker_prev); + + list_remove(listp, lecp); + found = lecp; + } + } + ldi_ev_unlock(); + + if (found == NULL) { + cmn_err(CE_WARN, "No LDI event handler for id (%p)", + (void *)id); + return (LDI_EV_SUCCESS); + } + + LDI_EVDBG((CE_NOTE, "ldi_ev_remove_callbacks: removed " + "LDI native callbacks")); + kmem_free(found, sizeof (ldi_ev_callback_impl_t)); + + return (LDI_EV_SUCCESS); +} +/* + * XXX End LDI Events + */ + +/* Client interface, find IOMedia from dev_t, alloc and open handle */ +int +ldi_open_by_dev(dev_t device, __unused int otyp, int fmode, + __unused cred_t *cred, ldi_handle_t *lhp, + __unused ldi_ident_t ident) +{ + int error = EINVAL; + + dprintf("%s dev_t %d fmode %d\n", __func__, device, fmode); + + /* Validate arguments */ + if (!lhp || device == 0) { + dprintf("%s missing argument %p %d\n", + __func__, lhp, device); + return (EINVAL); + } + /* In debug build, be loud if we potentially leak a handle */ + ASSERT3U(*((struct ldi_handle **)lhp), ==, NULL); + + /* Try to open by media */ + error = ldi_open_media_by_dev(device, fmode, lhp); + + /* Pass error from open */ + return (error); +} + +/* Client interface, find dev_t and IOMedia/vnode, alloc and open handle */ +int +ldi_open_by_name(char *path, int fmode, __unused cred_t *cred, + ldi_handle_t *lhp, __unused ldi_ident_t li) +{ + dev_t device = 0; + int error = EINVAL; + + dprintf("%s dev_t %d fmode %d\n", __func__, device, fmode); + + /* Validate arguments */ + if (!lhp || !path) { + dprintf("%s %s %p %s %d\n", __func__, + "missing lhp or path", lhp, path, fmode); + return (EINVAL); + } + /* In debug build, be loud if we potentially leak a handle */ + ASSERT3U(*((struct ldi_handle **)lhp), ==, NULL); + + /* Validate active open modes */ + if (!ldi_use_iokit_from_path && !ldi_use_dev_from_path && + !ldi_use_vnode_from_path) { + dprintf("%s no valid modes to open device\n", __func__); + return (EINVAL); + } + + /* Try to open IOMedia by path */ + if (ldi_use_iokit_from_path) { + error = ldi_open_media_by_path(path, fmode, lhp); + + /* Error check open */ + if (!error) { + return (0); + } else { + dprintf("%s ldi_open_media_by_path failed\n", + __func__); + /* Not fatal, retry by dev_t or vnode */ + } + } + + /* Get dev_t from path, try to open IOMedia by dev */ + if (ldi_use_dev_from_path) { + /* Uses vnode_lookup */ + device = dev_from_path(path); + if (device == 0) { + dprintf("%s dev_from_path failed %s\n", + __func__, path); + /* + * Both media_from_dev and vnode_from_path will fail + * if dev_from_path fails, since it uses vnode_lookup. + */ + return (ENODEV); + } + + if (ldi_use_iokit_from_dev) { + /* Searches for matching IOMedia */ + error = ldi_open_media_by_dev(device, fmode, lhp); + if (!error) { + return (0); + } else { + dprintf("%s ldi_open_media_by_dev failed %d\n", + __func__, device); + /* Not fatal, retry as vnode */ + } + } + } + + if (!ldi_use_vnode_from_path) { + return (EINVAL); + } + + /* Try to open vnode by path */ + error = ldi_open_vnode_by_path(path, device, fmode, lhp); + if (error) { + dprintf("%s ldi_open_vnode_by_path failed %d\n", __func__, + error); + } + + return (error); +} + +/* Client interface, wrapper for handle_close */ +int +ldi_close(ldi_handle_t lh, int fmode, __unused cred_t *cred) +{ + struct ldi_handle *handlep = (struct ldi_handle *)lh; + int error = EINVAL; + + ASSERT3U(handlep, !=, NULL); + ASSERT3U(handlep->lh_ref, !=, 0); + ASSERT3U(handlep->lh_fmode, ==, fmode); + + dprintf("%s dev_t %d fmode %d\n", __func__, handlep->lh_dev, fmode); + + /* Remove event callbacks */ + boolean_t notify = B_FALSE; + list_t *listp; + ldi_ev_callback_impl_t *lecp; + + /* + * Search the event callback list for callbacks with this + * handle. There are 2 cases + * 1. Called in the context of a notify. The handle consumer + * is releasing its hold on the device to allow a reconfiguration + * of the device. Simply NULL out the handle and the notify callback. + * The finalize callback is still available so that the consumer + * knows of the final disposition of the device. + * 2. Not called in the context of notify. NULL out the handle as well + * as the notify and finalize callbacks. Since the consumer has + * closed the handle, we assume it is not interested in the + * notify and finalize callbacks. + */ + ldi_ev_lock(); + + if (handlep->lh_flags & LH_FLAGS_NOTIFY) + notify = B_TRUE; + listp = &ldi_ev_callback_list.le_head; + for (lecp = list_head(listp); lecp; lecp = list_next(listp, lecp)) { + if (lecp->lec_lhp != handlep) + continue; + lecp->lec_lhp = NULL; + lecp->lec_notify = NULL; + LDI_EVDBG((CE_NOTE, "ldi_close: NULLed lh and notify")); + if (!notify) { + LDI_EVDBG((CE_NOTE, "ldi_close: NULLed finalize")); + lecp->lec_finalize = NULL; + } + } + + if (notify) + handlep->lh_flags &= ~LH_FLAGS_NOTIFY; + ldi_ev_unlock(); + + /* Close device if only one openref, or just decrement openrefs */ + if ((error = handle_close(handlep)) != 0) { + dprintf("%s error from handle_close: %d\n", + __func__, error); + } + + /* Decrement lh_ref, if last ref then remove and free */ + handle_release(handlep); + handlep = 0; + + /* XXX clear pointer arg, and return success? */ + lh = (ldi_handle_t)0; + return (0); + // return (error); +} + +/* + * Client interface, must be in LDI_STATUS_ONLINE + */ +int +ldi_get_size(ldi_handle_t lh, uint64_t *dev_size) +{ + struct ldi_handle *handlep = (struct ldi_handle *)lh; + int error; + + /* + * Ensure we have an LDI handle, and a valid dev_size and/or + * blocksize pointer. Caller must pass at least one of these. + */ + if (!handlep || !dev_size) { + dprintf("%s handle %p\n", __func__, handlep); + dprintf("%s dev_size %p\n", __func__, dev_size); + return (EINVAL); + } + + /* + * Must be in LDI_STATUS_ONLINE + * IOMedia can return getSize without being opened, but vnode + * devices must be opened first. + * Rather than have support differing behaviors, require that + * handle is open to retrieve the size. + */ + if (handlep->lh_status != LDI_STATUS_ONLINE) { + dprintf("%s device not online\n", __func__); + return (ENODEV); + } + + /* IOMedia or vnode */ + switch (handlep->lh_type) { + case LDI_TYPE_IOKIT: + error = handle_get_size_iokit(handlep, dev_size); + return (error); + + case LDI_TYPE_VNODE: + error = handle_get_size_vnode(handlep, dev_size); + return (error); + } + + /* Default case, shouldn't reach this */ + dprintf("%s invalid lh_type %d\n", __func__, + handlep->lh_type); + return (EINVAL); +} + +/* + * Must be in LDI_STATUS_ONLINE + * XXX Needs async callback + */ +int +ldi_sync(ldi_handle_t lh) +{ + struct ldi_handle *handlep = (struct ldi_handle *)lh; + int error; + + /* Ensure we have an LDI handle */ + if (!handlep) { + dprintf("%s no handle\n", __func__); + return (EINVAL); + } + + /* Must be in LDI_STATUS_ONLINE */ + if (handlep->lh_status != LDI_STATUS_ONLINE) { + dprintf("%s device not online\n", __func__); + return (ENODEV); + } + + /* IOMedia or vnode */ + switch (handlep->lh_type) { + case LDI_TYPE_IOKIT: + error = handle_sync_iokit(handlep); + return (error); + + case LDI_TYPE_VNODE: + error = handle_sync_vnode(handlep); + return (error); + } + + /* Default case, shouldn't reach this */ + dprintf("%s invalid lh_type %d\n", __func__, + handlep->lh_type); + return (EINVAL); +} + +int +ldi_ioctl(ldi_handle_t lh, int cmd, intptr_t arg, + __unused int mode, __unused cred_t *cr, __unused int *rvalp) +{ + struct ldi_handle *handlep = (struct ldi_handle *)lh; + int error = EINVAL; + struct dk_callback *dkc; + + switch (cmd) { + /* Flush write cache */ + case DKIOCFLUSHWRITECACHE: + /* IOMedia or vnode */ + switch (handlep->lh_type) { + case LDI_TYPE_IOKIT: + error = handle_sync_iokit(handlep); + break; + + case LDI_TYPE_VNODE: + error = handle_sync_vnode(handlep); + break; + + default: + error = ENOTSUP; + } + + if (!arg) { + return (error); + } + + dkc = (struct dk_callback *)arg; + /* Issue completion callback if set */ + if (dkc->dkc_callback) { + (*dkc->dkc_callback)(dkc->dkc_cookie, error); + } + + return (error); + + /* Set or clear write cache enabled */ + case DKIOCSETWCE: + /* + * There doesn't seem to be a way to do this by vnode, + * so we need to be able to locate an IOMedia and an + * IOBlockStorageDevice provider. + */ + return (handle_set_wce_iokit(handlep, (int *)arg)); + + /* Get media blocksize and block count */ + case DKIOCGMEDIAINFO: + /* IOMedia or vnode */ + switch (handlep->lh_type) { + case LDI_TYPE_IOKIT: + return (handle_get_media_info_iokit(handlep, + (struct dk_minfo *)arg)); + + case LDI_TYPE_VNODE: + return (handle_get_media_info_vnode(handlep, + (struct dk_minfo *)arg)); + + default: + return (ENOTSUP); + } + + /* Get media logical/physical blocksize and block count */ + case DKIOCGMEDIAINFOEXT: + /* IOMedia or vnode */ + switch (handlep->lh_type) { + case LDI_TYPE_IOKIT: + return (handle_get_media_info_ext_iokit(handlep, + (struct dk_minfo_ext *)arg)); + + case LDI_TYPE_VNODE: + return (handle_get_media_info_ext_vnode(handlep, + (struct dk_minfo_ext *)arg)); + + default: + return (ENOTSUP); + } + + /* Check device status */ + case DKIOCSTATE: + /* IOMedia or vnode */ + switch (handlep->lh_type) { + case LDI_TYPE_IOKIT: + return (handle_check_media_iokit(handlep, + (int *)arg)); + + case LDI_TYPE_VNODE: + return (handle_check_media_vnode(handlep, + (int *)arg)); + + default: + return (ENOTSUP); + } + + case DKIOCISSOLIDSTATE: + /* IOMedia or vnode */ + switch (handlep->lh_type) { + case LDI_TYPE_IOKIT: + return (handle_is_solidstate_iokit(handlep, + (int *)arg)); + + case LDI_TYPE_VNODE: + return (handle_is_solidstate_vnode(handlep, + (int *)arg)); + + default: + return (ENOTSUP); + } + + case DKIOCGETBOOTINFO: + /* IOMedia or vnode */ + switch (handlep->lh_type) { + case LDI_TYPE_IOKIT: + return (handle_get_bootinfo_iokit(handlep, + (struct io_bootinfo *)arg)); + + case LDI_TYPE_VNODE: + return (handle_get_bootinfo_vnode(handlep, + (struct io_bootinfo *)arg)); + + default: + return (ENOTSUP); + } + + case DKIOCGETFEATURES: /* UNMAP? */ + /* IOMedia or vnode */ + switch (handlep->lh_type) { + case LDI_TYPE_IOKIT: + return (handle_features_iokit(handlep, + (uint32_t *)arg)); + + case LDI_TYPE_VNODE: + return (handle_features_vnode(handlep, + (uint32_t *)arg)); + + default: + return (ENOTSUP); + } + + case DKIOCFREE: /* UNMAP */ + /* IOMedia or vnode */ + switch (handlep->lh_type) { + case LDI_TYPE_IOKIT: + return (handle_unmap_iokit(handlep, + (dkioc_free_list_ext_t *)arg)); + + case LDI_TYPE_VNODE: + return (handle_unmap_vnode(handlep, + (dkioc_free_list_ext_t *)arg)); + + default: + return (ENOTSUP); + } + + default: + return (ENOTSUP); + } +} + +/* + * Must already have handle_open called on lh. + */ +int +ldi_strategy(ldi_handle_t lh, ldi_buf_t *lbp) +{ + struct ldi_handle *handlep = (struct ldi_handle *)lh; + int error = EINVAL; + + /* Verify arguments */ + if (!handlep || !lbp || lbp->b_bcount == 0) { + dprintf("%s missing something...\n", __func__); + dprintf("handlep [%p]\n", handlep); + dprintf("lbp [%p]\n", lbp); + if (lbp) { + dprintf("lbp->b_bcount %llu\n", + lbp->b_bcount); + } + return (EINVAL); + } + + /* Check instantaneous value of handle status */ + if (handlep->lh_status != LDI_STATUS_ONLINE) { + dprintf("%s device not online\n", __func__); + return (ENODEV); + } + + /* IOMedia or vnode */ + /* Issue type-specific buf_strategy, preserve error */ + switch (handlep->lh_type) { + case LDI_TYPE_IOKIT: + error = buf_strategy_iokit(lbp, handlep); + break; + case LDI_TYPE_VNODE: + error = buf_strategy_vnode(lbp, handlep); + break; + default: + dprintf("%s invalid lh_type %d\n", __func__, handlep->lh_type); + return (EINVAL); + } + + return (error); +} + +/* Client interface to get an LDI buffer */ +ldi_buf_t * +ldi_getrbuf(int flags) +{ +/* Example: bp = getrbuf(KM_SLEEP); */ + ldi_buf_t *lbp; + + /* Allocate with requested flags */ + lbp = kmem_alloc(sizeof (ldi_buf_t), flags); + /* Verify allocation */ + if (!lbp) { + return (NULL); + } + + ldi_bioinit(lbp); + + return (lbp); +} + +/* Client interface to release an LDI buffer */ +void +ldi_freerbuf(ldi_buf_t *lbp) +{ + if (!lbp) { + return; + } + + /* Deallocate */ + kmem_free(lbp, sizeof (ldi_buf_t)); +} + +void +ldi_bioinit(ldi_buf_t *lbp) +{ +#ifdef LDI_ZERO + /* Zero the new buffer struct */ + memset(lbp, 0, sizeof (ldi_buf_t)); +#endif + + /* Initialize defaults */ + lbp->b_un.b_addr = 0; + lbp->b_flags = 0; + lbp->b_bcount = 0; + lbp->b_bufsize = 0; + lbp->b_lblkno = 0; + lbp->b_resid = 0; + lbp->b_error = 0; + lbp->b_private = NULL; +} + +/* + * IOKit C++ functions + */ +int +ldi_init(void *provider) +{ + int index; + + /* Allocate kstat pointer */ + ldi_ksp = kstat_create("zfs", 0, "ldi", "darwin", KSTAT_TYPE_NAMED, + sizeof (ldi_stats) / sizeof (kstat_named_t), KSTAT_FLAG_VIRTUAL); + + if (ldi_ksp == NULL) { + dprintf("%s couldn't register kstats\n", __func__); + return (ENOMEM); + } + + /* Register kstats */ + ldi_ksp->ks_data = &ldi_stats; + kstat_install(ldi_ksp); + + /* Register sysctls */ + sysctl_register_oid(&sysctl__ldi); + sysctl_register_oid(&sysctl__ldi_debug); + sysctl_register_oid(&sysctl__ldi_debug_use_iokit_from_path); + sysctl_register_oid(&sysctl__ldi_debug_use_iokit_from_dev); + sysctl_register_oid(&sysctl__ldi_debug_use_dev_from_path); + sysctl_register_oid(&sysctl__ldi_debug_use_vnode_from_path); + + /* Create handle hash lists and locks */ + ldi_handle_hash_count = 0; + for (index = 0; index < LH_HASH_SZ; index++) { + mutex_init(&ldi_handle_hash_lock[index], NULL, + MUTEX_DEFAULT, NULL); + list_create(&ldi_handle_hash_list[index], + sizeof (struct ldi_handle), + offsetof(struct ldi_handle, lh_node)); + } + + /* + * Initialize the LDI event subsystem + */ + mutex_init(&ldi_ev_callback_list.le_lock, NULL, MUTEX_DEFAULT, NULL); + cv_init(&ldi_ev_callback_list.le_cv, NULL, CV_DEFAULT, NULL); + ldi_ev_callback_list.le_busy = 0; + ldi_ev_callback_list.le_thread = NULL; + ldi_ev_callback_list.le_walker_next = NULL; + ldi_ev_callback_list.le_walker_prev = NULL; + list_create(&ldi_ev_callback_list.le_head, + sizeof (ldi_ev_callback_impl_t), + offsetof(ldi_ev_callback_impl_t, lec_list)); + + return (0); +} + +void +ldi_fini() +{ + /* + * Teardown the LDI event subsystem + */ + ldi_ev_lock(); +#ifdef DEBUG + if (ldi_ev_callback_list.le_busy != 1 || + ldi_ev_callback_list.le_thread != curthread || + ldi_ev_callback_list.le_walker_next != NULL || + ldi_ev_callback_list.le_walker_prev != NULL) { + dprintf("%s still has %s %llu %s %p %s %p %s %p\n", __func__, + "le_busy", ldi_ev_callback_list.le_busy, + "le_thread", ldi_ev_callback_list.le_thread, + "le_walker_next", ldi_ev_callback_list.le_walker_next, + "le_walker_prev", ldi_ev_callback_list.le_walker_prev); + } +#endif + list_destroy(&ldi_ev_callback_list.le_head); + ldi_ev_unlock(); +#ifdef DEBUG + ldi_ev_callback_list.le_busy = 0; + ldi_ev_callback_list.le_thread = NULL; + ldi_ev_callback_list.le_walker_next = NULL; + ldi_ev_callback_list.le_walker_prev = NULL; +#endif + + cv_destroy(&ldi_ev_callback_list.le_cv); + mutex_destroy(&ldi_ev_callback_list.le_lock); + + if (ldi_handle_hash_count != 0) { + dprintf("%s ldi_handle_hash_count %llu\n", __func__, + ldi_handle_hash_count); + } + + /* Destroy handle hash lists and locks */ + handle_hash_release(); + + /* Unregister sysctls */ + sysctl_unregister_oid(&sysctl__ldi_debug_use_iokit_from_path); + sysctl_unregister_oid(&sysctl__ldi_debug_use_iokit_from_dev); + sysctl_unregister_oid(&sysctl__ldi_debug_use_dev_from_path); + sysctl_unregister_oid(&sysctl__ldi_debug_use_vnode_from_path); + sysctl_unregister_oid(&sysctl__ldi_debug); + sysctl_unregister_oid(&sysctl__ldi); + + /* Unregister kstats */ + if (ldi_ksp != NULL) { + kstat_delete(ldi_ksp); + ldi_ksp = NULL; + } + + if (ldi_handle_hash_count != 0) { + dprintf("%s handle_hash_count still %llu\n", __func__, + ldi_handle_hash_count); + } +} diff --git a/module/os/macos/zfs/ldi_vnode.c b/module/os/macos/zfs/ldi_vnode.c new file mode 100644 index 000000000000..5270dd5fab76 --- /dev/null +++ b/module/os/macos/zfs/ldi_vnode.c @@ -0,0 +1,1020 @@ +/* + * 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) 1994, 2010, Oracle and/or its affiliates. All rights reserved. + */ +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +/* + * Copyright (c) 2013, Joyent, Inc. All rights reserved. + */ +/* + * Copyright (c) 2015, Evan Susarret. All rights reserved. + */ +/* + * Portions of this document are copyright Oracle and Joyent. + * OS X implementation of ldi_ named functions for ZFS written by + * Evan Susarret in 2015. + */ + +/* + * ZFS internal + */ +#include + +/* + * LDI Includes + */ +#include + +/* Debug prints */ + +#define ldi_log(fmt, ...) do { \ + dprintf(fmt, __VA_ARGS__); \ + /* delay(hz>>1); */ \ +_NOTE(CONSTCOND) } while (0) + +struct _handle_vnode { + vnode_t *devvp; + char *vd_readlinkname; +}; /* 16b */ + +#define LH_VNODE(lhp) lhp->lh_tsd.vnode_tsd->devvp + +void +handle_free_vnode(struct ldi_handle *lhp) +{ + if (!lhp) { + dprintf("%s missing lhp\n", __func__); + return; + } + + if (!lhp->lh_tsd.vnode_tsd) { + dprintf("%s missing vnode_tsd\n", __func__); + return; + } + + kmem_free(lhp->lh_tsd.vnode_tsd, sizeof (struct _handle_vnode)); + lhp->lh_tsd.vnode_tsd = 0; +} + + +/* Returns handle with lock still held */ +struct ldi_handle * +handle_alloc_vnode(dev_t device, int fmode) +{ + struct ldi_handle *lhp, *retlhp; + + /* Search for existing handle */ + if ((retlhp = handle_find(device, fmode, B_TRUE)) != NULL) { + dprintf("%s found handle before alloc\n", __func__); + return (retlhp); + } + + /* Validate arguments */ + if (device == 0 || fmode == 0) { + dprintf("%s missing dev_t %d or fmode %d\n", + __func__, device, fmode); + return (NULL); + } + + /* Allocate LDI vnode handle */ + if ((lhp = handle_alloc_common(LDI_TYPE_VNODE, device, + fmode)) == NULL) { + dprintf("%s couldn't allocate lhp\n", __func__); + return (NULL); + } + + /* Allocate and clear type-specific device data */ + lhp->lh_tsd.vnode_tsd = (struct _handle_vnode *)kmem_alloc( + sizeof (struct _handle_vnode), KM_SLEEP); + LH_VNODE(lhp) = 0; + + /* Add the handle to the list, or return match */ + if ((retlhp = handle_add(lhp)) == NULL) { + dprintf("%s handle_add failed\n", __func__); + handle_release(lhp); + return (NULL); + } + + /* Check if new or found handle was returned */ + if (retlhp != lhp) { + dprintf("%s found handle after alloc\n", __func__); + handle_release(lhp); + lhp = 0; + } + + return (retlhp); +} + +int +handle_close_vnode(struct ldi_handle *lhp) +{ + vfs_context_t context; + int error = EINVAL; + + ASSERT3U(lhp, !=, NULL); + ASSERT3U(lhp->lh_type, ==, LDI_TYPE_VNODE); + ASSERT3U(LH_VNODE(lhp), !=, NULL); + ASSERT3U(lhp->lh_status, ==, LDI_STATUS_CLOSING); + +#ifdef DEBUG + /* Validate vnode and context */ + if (LH_VNODE(lhp) == NULLVP) { + dprintf("%s missing vnode\n", __func__); + return (ENODEV); + } +#endif + + context = vfs_context_create(spl_vfs_context_kernel()); + if (!context) { + dprintf("%s couldn't create VFS context\n", __func__); + return (ENOMEM); + } + + /* Take an iocount on devvp vnode. */ + error = vnode_getwithref(LH_VNODE(lhp)); + if (error) { + dprintf("%s vnode_getwithref error %d\n", + __func__, error); + /* If getwithref failed, we can't call vnode_close. */ + LH_VNODE(lhp) = NULLVP; + vfs_context_rele(context); + return (ENODEV); + } + /* All code paths from here must vnode_put. */ + + /* For read-write, clear mountedon flag and wait for writes */ + if (lhp->lh_fmode & FWRITE) { + /* Wait for writes to complete */ + error = vnode_waitforwrites(LH_VNODE(lhp), 0, 0, 0, + "ldi::handle_close_vnode"); + if (error != 0) { + dprintf("%s waitforwrites returned %d\n", + __func__, error); + } + } + + /* Drop usecount */ + vnode_rele(LH_VNODE(lhp)); + + /* Drop iocount and refcount */ + error = vnode_close(LH_VNODE(lhp), + (lhp->lh_fmode & FWRITE ? FWASWRITTEN : 0), + context); + /* Preserve error from vnode_close */ + + /* Clear handle devvp vnode pointer */ + LH_VNODE(lhp) = NULLVP; + /* Drop VFS context */ + vfs_context_rele(context); + + if (error) { + dprintf("%s vnode_close error %d\n", + __func__, error); + } + /* Return error from close */ + return (error); +} + +static int +handle_open_vnode(struct ldi_handle *lhp, char *path) +{ + vfs_context_t context; + int error = EINVAL; + + ASSERT3U(lhp, !=, NULL); + ASSERT3U(path, !=, NULL); + ASSERT3U(lhp->lh_type, ==, LDI_TYPE_VNODE); + ASSERT3U(lhp->lh_status, ==, LDI_STATUS_OPENING); + + /* Validate path string */ + if (!path || strlen(path) <= 1) { + dprintf("%s missing path\n", __func__); + return (EINVAL); + } + + /* Allocate and validate context */ + context = vfs_context_create(spl_vfs_context_kernel()); + if (!context) { + dprintf("%s couldn't create VFS context\n", __func__); + return (ENOMEM); + } + + /* Try to open the device by path (takes iocount) */ + error = vnode_open(path, lhp->lh_fmode, 0, 0, + &(LH_VNODE(lhp)), context); + + if (error) { + dprintf("%s vnode_open error %d\n", __func__, error); + /* Return error from vnode_open */ + return (error); + } + + /* Increase usecount, saving error. */ + error = vnode_ref(LH_VNODE(lhp)); + if (error != 0) { + dprintf("%s couldn't vnode_ref\n", __func__); + vnode_close(LH_VNODE(lhp), lhp->lh_fmode, context); + /* Return error from vnode_ref */ + return (error); + } + + /* Verify vnode refers to a block device */ + if (!vnode_isblk(LH_VNODE(lhp))) { + dprintf("%s %s is not a block device\n", + __func__, path); + vnode_rele(LH_VNODE(lhp)); + vnode_close(LH_VNODE(lhp), lhp->lh_fmode, context); + return (ENOTBLK); + } + + /* Drop iocount on vnode (still has usecount) */ + vnode_put(LH_VNODE(lhp)); + /* Drop VFS context */ + vfs_context_rele(context); + + return (0); +} + +int +handle_get_size_vnode(struct ldi_handle *lhp, uint64_t *dev_size) +{ + vfs_context_t context; + uint64_t blkcnt = 0; + uint32_t blksize = 0; + int error = EINVAL; + +#ifdef DEBUG + if (!lhp || !dev_size) { + dprintf("%s missing lhp or dev_size\n", __func__); + return (EINVAL); + } + + /* Validate vnode */ + if (LH_VNODE(lhp) == NULLVP) { + dprintf("%s missing vnode\n", __func__); + return (ENODEV); + } +#endif + + /* Allocate and validate context */ + context = vfs_context_create(spl_vfs_context_kernel()); + if (!context) { + dprintf("%s couldn't create VFS context\n", __func__); + return (ENOMEM); + } + + /* Take an iocount on devvp vnode. */ + error = vnode_getwithref(LH_VNODE(lhp)); + if (error) { + dprintf("%s vnode_getwithref error %d\n", + __func__, error); + vfs_context_rele(context); + return (ENODEV); + } + /* All code paths from here must vnode_put. */ + + /* Fetch the blocksize */ + error = VNOP_IOCTL(LH_VNODE(lhp), DKIOCGETBLOCKSIZE, + (caddr_t)&blksize, 0, context); + error = (blksize == 0 ? ENODEV : error); + + /* Fetch the block count */ + error = (error ? error : VNOP_IOCTL(LH_VNODE(lhp), + DKIOCGETBLOCKCOUNT, (caddr_t)&blkcnt, + 0, context)); + error = (blkcnt == 0 ? ENODEV : error); + + /* Release iocount on vnode (still has usecount) */ + vnode_put(LH_VNODE(lhp)); + /* Drop VFS context */ + vfs_context_rele(context); + + /* Cast both to 64-bit then multiply */ + *dev_size = ((uint64_t)blksize * (uint64_t)blkcnt); + if (*dev_size == 0) { + dprintf("%s invalid blksize %u or blkcnt %llu\n", + __func__, blksize, blkcnt); + return (ENODEV); + } + return (0); +} + +int +handle_get_dev_path_vnode(struct ldi_handle *lhp, char *path, int len) +{ + vfs_context_t context; + int error; + + if (!lhp || !path || len == 0) { + dprintf("%s missing argument\n", __func__); + return (EINVAL); + } + + /* Validate vnode */ + if (LH_VNODE(lhp) == NULLVP) { + dprintf("%s missing vnode\n", __func__); + return (ENODEV); + } + + /* Allocate and validate context */ + context = vfs_context_create(spl_vfs_context_kernel()); + if (!context) { + dprintf("%s couldn't create VFS context\n", __func__); + return (ENOMEM); + } + + /* Take an iocount on devvp vnode. */ + error = vnode_getwithref(LH_VNODE(lhp)); + if (error) { + dprintf("%s vnode_getwithref error %d\n", + __func__, error); + vfs_context_rele(context); + return (ENODEV); + } + /* All code paths from here must vnode_put. */ + + if ((error = VNOP_IOCTL(LH_VNODE(lhp), DKIOCGETFIRMWAREPATH, + (caddr_t)path, len, context)) != 0) { + dprintf("%s VNOP_IOCTL error %d\n", __func__, error); + /* Preserve error to return */ + } + + /* Drop iocount on vnode (still has usecount) */ + vnode_put(LH_VNODE(lhp)); + /* Drop VFS context */ + vfs_context_rele(context); + +if (error == 0) dprintf("%s got device path [%s]\n", __func__, path); + return (error); +} + +int +handle_get_bootinfo_vnode(struct ldi_handle *lhp, + struct io_bootinfo *bootinfo) +{ + int error; + + if (!lhp || !bootinfo) { + dprintf("%s missing argument\n", __func__); +printf("%s missing argument\n", __func__); + return (EINVAL); + } + + if ((error = handle_get_size_vnode(lhp, + &bootinfo->dev_size)) != 0 || + (error = handle_get_dev_path_vnode(lhp, bootinfo->dev_path, + sizeof (bootinfo->dev_path))) != 0) { + dprintf("%s get size or dev_path error %d\n", + __func__, error); + } + + return (error); +} + +int +handle_sync_vnode(struct ldi_handle *lhp) +{ + vfs_context_t context; + int error = EINVAL; + +#ifdef DEBUG + if (!lhp) { + dprintf("%s missing lhp\n", __func__); + return (EINVAL); + } + + /* Validate vnode */ + if (LH_VNODE(lhp) == NULLVP) { + dprintf("%s missing vnode\n", __func__); + return (ENODEV); + } +#endif + + /* Allocate and validate context */ + context = vfs_context_create(spl_vfs_context_kernel()); + if (!context) { + dprintf("%s couldn't create VFS context\n", __func__); + return (ENOMEM); + } + + /* Take an iocount on devvp vnode. */ + error = vnode_getwithref(LH_VNODE(lhp)); + if (error) { + dprintf("%s vnode_getwithref error %d\n", + __func__, error); + vfs_context_rele(context); + return (ENODEV); + } + /* All code paths from here must vnode_put. */ + + /* + * Flush out any old buffers remaining from a previous use. + * buf_invalidateblks flushes UPL buffers, VNOP_FSYNC informs + * the disk device to flush write buffers to disk. + */ + error = buf_invalidateblks(LH_VNODE(lhp), BUF_WRITE_DATA, 0, 0); + + error = (error ? error : VNOP_FSYNC(LH_VNODE(lhp), + MNT_WAIT, context)); + + /* Release iocount on vnode (still has usecount) */ + vnode_put(LH_VNODE(lhp)); + /* Drop VFS context */ + vfs_context_rele(context); + + if (error) { + dprintf("%s buf_invalidateblks or VNOP_FSYNC error %d\n", + __func__, error); + return (ENOTSUP); + } + return (0); +} + +/* vnode_lookup, find dev_t info */ +dev_t +dev_from_path(char *path) +{ + vfs_context_t context; + vnode_t *devvp = NULLVP; + dev_t device; + int error = EINVAL; + +#ifdef DEBUG + /* Validate path */ + if (path == 0 || strlen(path) <= 1 || path[0] != '/') { + dprintf("%s invalid path provided\n", __func__); + return (0); + } +#endif + + /* Allocate and validate context */ + context = vfs_context_create(spl_vfs_context_kernel()); + if (!context) { + dprintf("%s couldn't create VFS context\n", __func__); + return (0); + } + + /* Try to lookup the vnode by path */ + error = vnode_lookup(path, 0, &devvp, context); + if (error || devvp == NULLVP) { + dprintf("%s vnode_lookup failed %d\n", __func__, error); + vfs_context_rele(context); + return (0); + } + + /* Get the rdev of this vnode */ + device = vnode_specrdev(devvp); + + /* Drop iocount on devvp */ + vnode_put(devvp); + /* Drop vfs_context */ + vfs_context_rele(context); + +#ifdef DEBUG + /* Validate dev_t */ + if (device == 0) { + dprintf("%s invalid device\n", __func__); + } +#endif + + /* Return 0 or valid dev_t */ + return (device); +} + +/* Completion handler for vnode strategy */ +static void +ldi_vnode_io_intr(buf_t bp, void *arg) +{ + ldi_buf_t *lbp = (ldi_buf_t *)arg; + + ASSERT3U(bp, !=, NULL); + ASSERT3U(lbp, !=, NULL); + + /* Copyout error and resid */ + lbp->b_error = buf_error(bp); + lbp->b_resid = buf_resid(bp); + +#ifdef DEBUG + if (lbp->b_error || lbp->b_resid != 0) { + dprintf("%s io error %d resid %llu\n", __func__, + lbp->b_error, lbp->b_resid); + } +#endif + + /* Teardown */ + buf_free(bp); + + /* Call original completion function */ + if (lbp->b_iodone) { + lbp->b_iodone(lbp); + } +} + +int +buf_strategy_vnode(ldi_buf_t *lbp, struct ldi_handle *lhp) +{ + buf_t bp = 0; + int error = EINVAL; + + ASSERT3U(lbp, !=, NULL); + ASSERT3U(lhp, !=, NULL); + +#ifdef DEBUG + if (!lbp || !lhp) { + dprintf("%s missing lbp or lhp\n", __func__); + return (EINVAL); + } + if (lhp->lh_status != LDI_STATUS_ONLINE) { + dprintf("%s handle is not Online\n", __func__); + return (ENODEV); + } + + /* Validate vnode */ + if (LH_VNODE(lhp) == NULLVP) { + dprintf("%s missing vnode\n", __func__); + return (ENODEV); + } +#endif + + /* Allocate and verify buf_t */ + if (NULL == (bp = buf_alloc(LH_VNODE(lhp)))) { + dprintf("%s couldn't allocate buf_t\n", __func__); + return (ENOMEM); + } + + /* Setup buffer */ + buf_setflags(bp, B_NOCACHE | (lbp->b_flags & B_READ ? + B_READ : B_WRITE) | + (lbp->b_flags & (B_PASSIVE | B_PHYS | B_RAW)) | + ((lbp->b_iodone == NULL) ? 0: B_ASYNC)); + buf_setcount(bp, lbp->b_bcount); + buf_setdataptr(bp, (uintptr_t)lbp->b_un.b_addr); + buf_setblkno(bp, lbp->b_lblkno); + buf_setlblkno(bp, lbp->b_lblkno); + buf_setsize(bp, lbp->b_bufsize); + + /* For asynchronous IO */ + if (lbp->b_iodone != NULL) { + buf_setcallback(bp, &ldi_vnode_io_intr, lbp); + } + + /* Recheck instantaneous value of handle status */ + if (lhp->lh_status != LDI_STATUS_ONLINE) { + dprintf("%s device not online\n", __func__); + buf_free(bp); + return (ENODEV); + } + + /* Take an iocount on devvp vnode. */ + error = vnode_getwithref(LH_VNODE(lhp)); + if (error) { + dprintf("%s vnode_getwithref error %d\n", + __func__, error); + buf_free(bp); + return (ENODEV); + } + /* All code paths from here must vnode_put. */ + + if (!(lbp->b_flags & B_READ)) { + /* Does not return an error status */ + vnode_startwrite(LH_VNODE(lhp)); + } + + /* Issue the IO, preserving error */ + error = VNOP_STRATEGY(bp); + + if (error) { + dprintf("%s VNOP_STRATEGY error %d\n", + __func__, error); + /* Reclaim write count on vnode */ + if (!(lbp->b_flags & B_READ)) { + vnode_writedone(LH_VNODE(lhp)); + } + vnode_put(LH_VNODE(lhp)); + buf_free(bp); + return (EIO); + } + + /* Release iocount on vnode (still has usecount) */ + vnode_put(LH_VNODE(lhp)); + + /* For synchronous IO, call completion */ + if (lbp->b_iodone == NULL) { + ldi_vnode_io_intr(bp, (void*)lbp); + } + + /* Pass error from VNOP_STRATEGY */ + return (error); +} + +/* Client interface, alloc and open vnode handle by pathname */ +int +ldi_open_vnode_by_path(char *path, dev_t device, + int fmode, ldi_handle_t *lhp) +{ + struct ldi_handle *retlhp; + ldi_status_t status; + int error = EIO; + + /* Validate arguments */ + if (!path || strlen(path) <= 1 || device == 0 || !lhp) { + dprintf("%s invalid argument %p %d %p\n", __func__, + path, device, lhp); + if (path) { + dprintf("*path string is %s\n", path); + } + return (EINVAL); + } + /* In debug build, be loud if we potentially leak a handle */ + ASSERT3U(*(struct ldi_handle **)lhp, ==, NULL); + + /* Allocate handle with path */ + retlhp = handle_alloc_vnode(device, fmode); + if (retlhp == NULL) { + dprintf("%s couldn't allocate vnode handle\n", __func__); + return (ENOMEM); + } + + /* Mark the handle as Opening, or increment openref */ + status = handle_open_start(retlhp); + if (status == LDI_STATUS_ONLINE) { + dprintf("%s already online, refs %d, openrefs %d\n", __func__, + retlhp->lh_ref, retlhp->lh_openref); + /* Cast retlhp and assign to lhp (may be 0) */ + *lhp = (ldi_handle_t)retlhp; + /* Successfully incremented open ref in open_start */ + return (0); + } + + /* If state is now Opening, try to open device by vnode */ + if (status != LDI_STATUS_OPENING || + (error = handle_open_vnode(retlhp, path)) != 0) { + dprintf("%s Couldn't open handle\n", __func__); + handle_open_done(retlhp, LDI_STATUS_CLOSED); + handle_release(retlhp); + retlhp = 0; + return ((error == EACCES) ? EROFS:EIO); + } + handle_open_done(retlhp, LDI_STATUS_ONLINE); + + /* Register for disk notifications */ + handle_register_notifier(retlhp); + + /* Cast retlhp and assign to lhp (may be 0) */ + *lhp = (ldi_handle_t)retlhp; + /* Pass error from open */ + return (error); +} + +int +handle_get_media_info_vnode(struct ldi_handle *lhp, + struct dk_minfo *dkm) +{ + vfs_context_t context; + uint32_t blksize; + uint64_t blkcount; + int error; + +#ifdef DEBUG + if (!lhp || !dkm) { + dprintf("%s missing lhp or dkm\n", __func__); + return (EINVAL); + } + if (lhp->lh_status != LDI_STATUS_ONLINE) { + dprintf("%s handle is not Online\n", __func__); + return (ENODEV); + } + + /* Validate vnode */ + if (LH_VNODE(lhp) == NULLVP) { + dprintf("%s missing vnode\n", __func__); + return (ENODEV); + } +#endif + + /* Allocate and validate context */ + context = vfs_context_create(spl_vfs_context_kernel()); + if (!context) { + dprintf("%s couldn't create VFS context\n", __func__); + return (0); + } + + /* Take an iocount on devvp vnode. */ + error = vnode_getwithref(LH_VNODE(lhp)); + if (error) { + dprintf("%s vnode_getwithref error %d\n", + __func__, error); + vfs_context_rele(context); + return (ENODEV); + } + /* All code paths from here must vnode_put. */ + + /* Get the blocksize and block count */ + error = VNOP_IOCTL(LH_VNODE(lhp), DKIOCGETBLOCKSIZE, + (caddr_t)&blksize, 0, context); + error = (error ? error : VNOP_IOCTL(LH_VNODE(lhp), + DKIOCGETBLOCKCOUNT, (caddr_t)&blkcount, + 0, context)); + + /* Release iocount on vnode (still has usecount) */ + vnode_put(LH_VNODE(lhp)); + /* Drop vfs_context */ + vfs_context_rele(context); + + if (error) { + dkm->dki_capacity = 0; + dkm->dki_lbsize = 0; + return (error); + } + + /* If successful, set return values */ + dkm->dki_capacity = blkcount; + dkm->dki_lbsize = blksize; + return (0); +} + +int +handle_get_media_info_ext_vnode(struct ldi_handle *lhp, + struct dk_minfo_ext *dkmext) +{ + vfs_context_t context; + uint32_t blksize, pblksize; + uint64_t blkcount; + int error; + +#ifdef DEBUG + if (!lhp || !dkmext) { + dprintf("%s missing lhp or dkmext\n", __func__); + return (EINVAL); + } + if (lhp->lh_status != LDI_STATUS_ONLINE) { + dprintf("%s handle is not Online\n", __func__); + return (ENODEV); + } + + /* Validate vnode and context */ + if (LH_VNODE(lhp) == NULLVP) { + dprintf("%s missing vnode or context\n", __func__); + return (ENODEV); + } +#endif + + /* Allocate and validate context */ + context = vfs_context_create(spl_vfs_context_kernel()); + if (!context) { + dprintf("%s couldn't create VFS context\n", __func__); + return (0); + } + + /* Take an iocount on devvp vnode. */ + error = vnode_getwithref(LH_VNODE(lhp)); + if (error) { + dprintf("%s vnode_getwithref error %d\n", + __func__, error); + vfs_context_rele(context); + return (ENODEV); + } + /* All code paths from here must vnode_put. */ + + /* Get the blocksize, physical blocksize, and block count */ + error = VNOP_IOCTL(LH_VNODE(lhp), DKIOCGETBLOCKSIZE, + (caddr_t)&blksize, 0, context); + error = (error ? error : VNOP_IOCTL(LH_VNODE(lhp), + DKIOCGETPHYSICALBLOCKSIZE, (caddr_t)&pblksize, + 0, context)); + error = (error ? error : VNOP_IOCTL(LH_VNODE(lhp), + DKIOCGETBLOCKCOUNT, (caddr_t)&blkcount, + 0, context)); + + /* Release iocount on vnode (still has usecount) */ + vnode_put(LH_VNODE(lhp)); + /* Drop vfs_context */ + vfs_context_rele(context); + + if (error) { + dkmext->dki_capacity = 0; + dkmext->dki_lbsize = 0; + dkmext->dki_pbsize = 0; + return (error); + } + + /* If successful, set return values */ + dkmext->dki_capacity = blkcount; + dkmext->dki_lbsize = blksize; + dkmext->dki_pbsize = pblksize; + return (0); +} + +int +handle_check_media_vnode(struct ldi_handle *lhp, int *status) +{ + if (!lhp || !status) { + dprintf("%s missing lhp or invalid status\n", __func__); + return (EINVAL); + } + + /* Validate vnode and context */ + if (LH_VNODE(lhp) == NULLVP) { + dprintf("%s missing vnode\n", __func__); + return (ENODEV); + } + + /* XXX As yet unsupported */ + return (ENOTSUP); + + /* Check if the device is available and responding */ + return (0); +} + +int +handle_is_solidstate_vnode(struct ldi_handle *lhp, int *isssd) +{ + vfs_context_t context; + int error; + + if (!lhp || !isssd) { + dprintf("%s missing lhp or invalid status\n", __func__); + return (EINVAL); + } + + /* Validate vnode */ + if (LH_VNODE(lhp) == NULLVP) { + dprintf("%s missing vnode\n", __func__); + return (ENODEV); + } + + /* Allocate and validate context */ + context = vfs_context_create(spl_vfs_context_kernel()); + if (!context) { + dprintf("%s couldn't create VFS context\n", __func__); + return (ENOMEM); + } + + /* Take an iocount on devvp vnode. */ + error = vnode_getwithref(LH_VNODE(lhp)); + if (error) { + dprintf("%s vnode_getwithref error %d\n", + __func__, error); + vfs_context_rele(context); + return (ENODEV); + } + /* All code paths from here must vnode_put. */ + + error = VNOP_IOCTL(LH_VNODE(lhp), DKIOCISSOLIDSTATE, + (caddr_t)isssd, 0, context); + + /* Release iocount on vnode (still has usecount) */ + vnode_put(LH_VNODE(lhp)); + /* Drop vfs_context */ + vfs_context_rele(context); + + return (error); +} + +int +handle_features_vnode(struct ldi_handle *lhp, + uint32_t *features) +{ + vfs_context_t context; + int error; + +#ifdef DEBUG + if (lhp->lh_status != LDI_STATUS_ONLINE) { + dprintf("%s handle is not Online\n", __func__); + return (ENODEV); + } + + /* Validate vnode */ + if (LH_VNODE(lhp) == NULLVP) { + dprintf("%s missing vnode\n", __func__); + return (ENODEV); + } +#endif + + /* Allocate and validate context */ + context = vfs_context_create(spl_vfs_context_kernel()); + if (!context) { + dprintf("%s couldn't create VFS context\n", __func__); + return (0); + } + + /* Take an iocount on devvp vnode. */ + error = vnode_getwithref(LH_VNODE(lhp)); + if (error) { + dprintf("%s vnode_getwithref error %d\n", + __func__, error); + vfs_context_rele(context); + return (ENODEV); + } + + /* All code paths from here must vnode_put. */ + + error = VNOP_IOCTL(LH_VNODE(lhp), DKIOCGETFEATURES, + (caddr_t)features, 0, context); + + if (error) { + printf("%s: 0x%x\n", __func__, error); + } + + /* Release iocount on vnode (still has usecount) */ + vnode_put(LH_VNODE(lhp)); + /* Drop vfs_context */ + vfs_context_rele(context); + + return (error); +} + +int +handle_unmap_vnode(struct ldi_handle *lhp, + dkioc_free_list_ext_t *dkm) +{ + vfs_context_t context; + int error; + +#ifdef DEBUG + if (!lhp || !dkm) { + dprintf("%s missing lhp or dkm\n", __func__); + return (EINVAL); + } + if (lhp->lh_status != LDI_STATUS_ONLINE) { + dprintf("%s handle is not Online\n", __func__); + return (ENODEV); + } + + /* Validate vnode */ + if (LH_VNODE(lhp) == NULLVP) { + dprintf("%s missing vnode\n", __func__); + return (ENODEV); + } +#endif + + /* Allocate and validate context */ + context = vfs_context_create(spl_vfs_context_kernel()); + if (!context) { + dprintf("%s couldn't create VFS context\n", __func__); + return (0); + } + + /* Take an iocount on devvp vnode. */ + error = vnode_getwithref(LH_VNODE(lhp)); + if (error) { + dprintf("%s vnode_getwithref error %d\n", + __func__, error); + vfs_context_rele(context); + return (ENODEV); + } + /* All code paths from here must vnode_put. */ + + /* We need to convert illumos' dkioc_free_list_t to dk_unmap_t */ + /* We only support 1 entry now */ + dk_unmap_t dkun = { 0 }; + dk_extent_t ext; + dkun.extentsCount = 1; + dkun.extents = &ext; + ext.offset = dkm->dfle_start; + ext.length = dkm->dfle_length; + + /* + * dkm->dfl_flags vs dkun.options + * #define DF_WAIT_SYNC 0x00000001 Wait for full write-out of free. + * #define _DK_UNMAP_INITIALIZE 0x00000100 + */ + + /* issue unmap */ + error = VNOP_IOCTL(LH_VNODE(lhp), DKIOCUNMAP, + (caddr_t)&dkun, 0, context); + + if (error) { + dprintf("%s unmap: 0x%x for off %llx size %llx\n", __func__, + error, ext.offset, ext.length); + } + + /* Release iocount on vnode (still has usecount) */ + vnode_put(LH_VNODE(lhp)); + /* Drop vfs_context */ + vfs_context_rele(context); + + return (error); +} diff --git a/module/os/macos/zfs/policy.c b/module/os/macos/zfs/policy.c new file mode 100644 index 000000000000..5525302266c7 --- /dev/null +++ b/module/os/macos/zfs/policy.c @@ -0,0 +1,354 @@ +/* + * 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) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright 2013, Joyent, Inc. All rights reserved. + * Copyright (C) 2016 Lawrence Livermore National Security, LLC. + * + * For Linux the vast majority of this enforcement is already handled via + * the standard Linux VFS permission checks. However certain administrative + * commands which bypass the standard mechanisms may need to make use of + * this functionality. + */ + +#include +#include +#include + +/* + * The passed credentials cannot be directly verified because Linux only + * provides and interface to check the *current* process credentials. In + * order to handle this the capable() test is only run when the passed + * credentials match the current process credentials or the kcred. In + * all other cases this function must fail and return the passed err. + */ +static int +priv_policy_ns(const cred_t *cr, int capability, boolean_t all, int err, + struct user_namespace *ns) +{ + ASSERT3S(all, ==, B_FALSE); + + if (cr != CRED() && (cr != kcred)) + return (err); + +#if defined(CONFIG_USER_NS) + if (!(ns ? ns_capable(ns, capability) : capable(capability))) +#else + if (!capable(capability)) +#endif + return (err); + + return (0); +} + +static int +priv_policy(const cred_t *cr, int capability, boolean_t all, int err) +{ + return (priv_policy_ns(cr, capability, all, err, NULL)); +} + +static int +priv_policy_user(const cred_t *cr, int capability, boolean_t all, int err) +{ + /* + * All priv_policy_user checks are preceded by kuid/kgid_has_mapping() + * checks. If we cannot do them, we shouldn't be using ns_capable() + * since we don't know whether the affected files are valid in our + * namespace. + */ +#if defined(CONFIG_USER_NS) + return (priv_policy_ns(cr, capability, all, err, cr->user_ns)); +#else + return (priv_policy_ns(cr, capability, all, err, NULL)); +#endif +} + +/* + * Checks for operations that are either client-only or are used by + * both clients and servers. + */ +int +secpolicy_nfs(const cred_t *cr) +{ + return (priv_policy(cr, CAP_SYS_ADMIN, B_FALSE, EPERM)); +} + +/* + * Catch all system configuration. + */ +int +secpolicy_sys_config(const cred_t *cr, boolean_t checkonly) +{ + return (priv_policy(cr, CAP_SYS_ADMIN, B_FALSE, EPERM)); +} + +/* + * Like secpolicy_vnode_access() but we get the actual wanted mode and the + * current mode of the file, not the missing bits. + * + * Enforced in the Linux VFS. + */ +int +secpolicy_vnode_access2(const cred_t *cr, struct inode *ip, uid_t owner, + mode_t curmode, mode_t wantmode) +{ + return (0); +} + +/* + * This is a special routine for ZFS; it is used to determine whether + * any of the privileges in effect allow any form of access to the + * file. There's no reason to audit this or any reason to record + * this. More work is needed to do the "KPLD" stuff. + */ +int +secpolicy_vnode_any_access(const cred_t *cr, struct inode *ip, uid_t owner) +{ + if (crgetfsuid(cr) == owner) + return (0); + + if (inode_owner_or_capable(ip)) + return (0); + +#if defined(CONFIG_USER_NS) + if (!kuid_has_mapping(cr->user_ns, SUID_TO_KUID(owner))) + return (EPERM); +#endif + + if (priv_policy_user(cr, CAP_DAC_OVERRIDE, B_FALSE, EPERM) == 0) + return (0); + + if (priv_policy_user(cr, CAP_DAC_READ_SEARCH, B_FALSE, EPERM) == 0) + return (0); + + return (EPERM); +} + +/* + * Determine if subject can chown owner of a file. + */ +int +secpolicy_vnode_chown(const cred_t *cr, uid_t owner) +{ + if (crgetfsuid(cr) == owner) + return (0); + +#if defined(CONFIG_USER_NS) + if (!kuid_has_mapping(cr->user_ns, SUID_TO_KUID(owner))) + return (EPERM); +#endif + + return (priv_policy_user(cr, CAP_FOWNER, B_FALSE, EPERM)); +} + +/* + * Determine if subject can change group ownership of a file. + */ +int +secpolicy_vnode_create_gid(const cred_t *cr) +{ + return (priv_policy(cr, CAP_SETGID, B_FALSE, EPERM)); +} + +/* + * Policy determines whether we can remove an entry from a directory, + * regardless of permission bits. + */ +int +secpolicy_vnode_remove(const cred_t *cr) +{ + return (priv_policy(cr, CAP_FOWNER, B_FALSE, EPERM)); +} + +/* + * Determine that subject can modify the mode of a file. allzone privilege + * needed when modifying root owned object. + */ +int +secpolicy_vnode_setdac(const cred_t *cr, uid_t owner) +{ + if (crgetfsuid(cr) == owner) + return (0); + +#if defined(CONFIG_USER_NS) + if (!kuid_has_mapping(cr->user_ns, SUID_TO_KUID(owner))) + return (EPERM); +#endif + + return (priv_policy_user(cr, CAP_FOWNER, B_FALSE, EPERM)); +} + +/* + * Are we allowed to retain the set-uid/set-gid bits when + * changing ownership or when writing to a file? + * "issuid" should be true when set-uid; only in that case + * root ownership is checked (setgid is assumed). + * + * Enforced in the Linux VFS. + */ +int +secpolicy_vnode_setid_retain(const cred_t *cr, boolean_t issuidroot) +{ + return (priv_policy_user(cr, CAP_FSETID, B_FALSE, EPERM)); +} + +/* + * Determine that subject can set the file setgid flag. + */ +int +secpolicy_vnode_setids_setgids(const cred_t *cr, gid_t gid) +{ +#if defined(CONFIG_USER_NS) + if (!kgid_has_mapping(cr->user_ns, SGID_TO_KGID(gid))) + return (EPERM); +#endif + if (crgetfsgid(cr) != gid && !groupmember(gid, cr)) + return (priv_policy_user(cr, CAP_FSETID, B_FALSE, EPERM)); + + return (0); +} + +/* + * Determine if the subject can inject faults in the ZFS fault injection + * framework. Requires all privileges. + */ +int +secpolicy_zinject(const cred_t *cr) +{ + return (priv_policy(cr, CAP_SYS_ADMIN, B_FALSE, EACCES)); +} + +/* + * Determine if the subject has permission to manipulate ZFS datasets + * (not pools). Equivalent to the SYS_MOUNT privilege. + */ +int +secpolicy_zfs(const cred_t *cr) +{ + return (priv_policy(cr, CAP_SYS_ADMIN, B_FALSE, EACCES)); +} + +void +secpolicy_setid_clear(vattr_t *vap, cred_t *cr) +{ + if ((vap->va_mode & (S_ISUID | S_ISGID)) != 0 && + secpolicy_vnode_setid_retain(cr, + (vap->va_mode & S_ISUID) != 0 && + (vap->va_mask & AT_UID) != 0 && vap->va_uid == 0) != 0) { + vap->va_mask |= AT_MODE; + vap->va_mode &= ~(S_ISUID|S_ISGID); + } +} + +/* + * Determine that subject can set the file setid flags. + */ +static int +secpolicy_vnode_setid_modify(const cred_t *cr, uid_t owner) +{ + if (crgetfsuid(cr) == owner) + return (0); + +#if defined(CONFIG_USER_NS) + if (!kuid_has_mapping(cr->user_ns, SUID_TO_KUID(owner))) + return (EPERM); +#endif + + return (priv_policy_user(cr, CAP_FSETID, B_FALSE, EPERM)); +} + +/* + * Determine that subject can make a file a "sticky". + * + * Enforced in the Linux VFS. + */ +static int +secpolicy_vnode_stky_modify(const cred_t *cr) +{ + return (0); +} + +int +secpolicy_setid_setsticky_clear(struct inode *ip, vattr_t *vap, + const vattr_t *ovap, cred_t *cr) +{ + int error; + + if ((vap->va_mode & S_ISUID) != 0 && + (error = secpolicy_vnode_setid_modify(cr, + ovap->va_uid)) != 0) { + return (error); + } + + /* + * Check privilege if attempting to set the + * sticky bit on a non-directory. + */ + if (!S_ISDIR(ip->i_mode) && (vap->va_mode & S_ISVTX) != 0 && + secpolicy_vnode_stky_modify(cr) != 0) { + vap->va_mode &= ~S_ISVTX; + } + + /* + * Check for privilege if attempting to set the + * group-id bit. + */ + if ((vap->va_mode & S_ISGID) != 0 && + secpolicy_vnode_setids_setgids(cr, ovap->va_gid) != 0) { + vap->va_mode &= ~S_ISGID; + } + + return (0); +} + +/* + * Check privileges for setting xvattr attributes + */ +int +secpolicy_xvattr(xvattr_t *xvap, uid_t owner, cred_t *cr, mode_t type) +{ + return (secpolicy_vnode_chown(cr, owner)); +} + +/* + * Check privileges for setattr attributes. + * + * Enforced in the Linux VFS. + */ +int +secpolicy_vnode_setattr(cred_t *cr, struct inode *ip, struct vattr *vap, + const struct vattr *ovap, int flags, + int unlocked_access(void *, int, cred_t *), void *node) +{ + return (0); +} + +/* + * Check privileges for links. + * + * Enforced in the Linux VFS. + */ +int +secpolicy_basic_link(const cred_t *cr) +{ + return (0); +} diff --git a/module/os/macos/zfs/qat.c b/module/os/macos/zfs/qat.c new file mode 100644 index 000000000000..08613b3a2042 --- /dev/null +++ b/module/os/macos/zfs/qat.c @@ -0,0 +1,105 @@ +/* + * 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 + */ + +#if defined(_KERNEL) && defined(HAVE_QAT) +#include +#include + +qat_stats_t qat_stats = { + { "comp_requests", KSTAT_DATA_UINT64 }, + { "comp_total_in_bytes", KSTAT_DATA_UINT64 }, + { "comp_total_out_bytes", KSTAT_DATA_UINT64 }, + { "decomp_requests", KSTAT_DATA_UINT64 }, + { "decomp_total_in_bytes", KSTAT_DATA_UINT64 }, + { "decomp_total_out_bytes", KSTAT_DATA_UINT64 }, + { "dc_fails", KSTAT_DATA_UINT64 }, + { "encrypt_requests", KSTAT_DATA_UINT64 }, + { "encrypt_total_in_bytes", KSTAT_DATA_UINT64 }, + { "encrypt_total_out_bytes", KSTAT_DATA_UINT64 }, + { "decrypt_requests", KSTAT_DATA_UINT64 }, + { "decrypt_total_in_bytes", KSTAT_DATA_UINT64 }, + { "decrypt_total_out_bytes", KSTAT_DATA_UINT64 }, + { "crypt_fails", KSTAT_DATA_UINT64 }, + { "cksum_requests", KSTAT_DATA_UINT64 }, + { "cksum_total_in_bytes", KSTAT_DATA_UINT64 }, + { "cksum_fails", KSTAT_DATA_UINT64 }, +}; + +static kstat_t *qat_ksp = NULL; + +CpaStatus +qat_mem_alloc_contig(void **pp_mem_addr, Cpa32U size_bytes) +{ + *pp_mem_addr = kmalloc(size_bytes, GFP_KERNEL); + if (*pp_mem_addr == NULL) + return (CPA_STATUS_RESOURCE); + return (CPA_STATUS_SUCCESS); +} + +void +qat_mem_free_contig(void **pp_mem_addr) +{ + if (*pp_mem_addr != NULL) { + kfree(*pp_mem_addr); + *pp_mem_addr = NULL; + } +} + +int +qat_init(void) +{ + qat_ksp = kstat_create("zfs", 0, "qat", "misc", + KSTAT_TYPE_NAMED, sizeof (qat_stats) / sizeof (kstat_named_t), + KSTAT_FLAG_VIRTUAL); + if (qat_ksp != NULL) { + qat_ksp->ks_data = &qat_stats; + kstat_install(qat_ksp); + } + + /* + * Just set the disable flag when qat init failed, qat can be + * turned on again in post-process after zfs module is loaded, e.g.: + * echo 0 > /sys/module/zfs/parameters/zfs_qat_compress_disable + */ + if (qat_dc_init() != 0) + zfs_qat_compress_disable = 1; + + if (qat_cy_init() != 0) { + zfs_qat_checksum_disable = 1; + zfs_qat_encrypt_disable = 1; + } + + return (0); +} + +void +qat_fini(void) +{ + if (qat_ksp != NULL) { + kstat_delete(qat_ksp); + qat_ksp = NULL; + } + + qat_cy_fini(); + qat_dc_fini(); +} + +#endif diff --git a/module/os/macos/zfs/qat_compress.c b/module/os/macos/zfs/qat_compress.c new file mode 100644 index 000000000000..ad3ead3b16e3 --- /dev/null +++ b/module/os/macos/zfs/qat_compress.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 + */ + +#if defined(_KERNEL) && defined(HAVE_QAT) +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Max instances in a QAT device, each instance is a channel to submit + * jobs to QAT hardware, this is only for pre-allocating instance and + * session arrays; the actual number of instances are defined in the + * QAT driver's configuration file. + */ +#define QAT_DC_MAX_INSTANCES 48 + +/* + * ZLIB head and foot size + */ +#define ZLIB_HEAD_SZ 2 +#define ZLIB_FOOT_SZ 4 + +static CpaInstanceHandle dc_inst_handles[QAT_DC_MAX_INSTANCES]; +static CpaDcSessionHandle session_handles[QAT_DC_MAX_INSTANCES]; +static CpaBufferList **buffer_array[QAT_DC_MAX_INSTANCES]; +static Cpa16U num_inst = 0; +static Cpa32U inst_num = 0; +static boolean_t qat_dc_init_done = B_FALSE; +int zfs_qat_compress_disable = 0; + +boolean_t +qat_dc_use_accel(size_t s_len) +{ + return (!zfs_qat_compress_disable && + qat_dc_init_done && + s_len >= QAT_MIN_BUF_SIZE && + s_len <= QAT_MAX_BUF_SIZE); +} + +static void +qat_dc_callback(void *p_callback, CpaStatus status) +{ + if (p_callback != NULL) + complete((struct completion *)p_callback); +} + +static void +qat_dc_clean(void) +{ + Cpa16U buff_num = 0; + Cpa16U num_inter_buff_lists = 0; + + for (Cpa16U i = 0; i < num_inst; i++) { + cpaDcStopInstance(dc_inst_handles[i]); + QAT_PHYS_CONTIG_FREE(session_handles[i]); + /* free intermediate buffers */ + if (buffer_array[i] != NULL) { + cpaDcGetNumIntermediateBuffers( + dc_inst_handles[i], &num_inter_buff_lists); + for (buff_num = 0; buff_num < num_inter_buff_lists; + buff_num++) { + CpaBufferList *buffer_inter = + buffer_array[i][buff_num]; + if (buffer_inter->pBuffers) { + QAT_PHYS_CONTIG_FREE( + buffer_inter->pBuffers->pData); + QAT_PHYS_CONTIG_FREE( + buffer_inter->pBuffers); + } + QAT_PHYS_CONTIG_FREE( + buffer_inter->pPrivateMetaData); + QAT_PHYS_CONTIG_FREE(buffer_inter); + } + } + } + + num_inst = 0; + qat_dc_init_done = B_FALSE; +} + +int +qat_dc_init(void) +{ + CpaStatus status = CPA_STATUS_SUCCESS; + Cpa32U sess_size = 0; + Cpa32U ctx_size = 0; + Cpa16U num_inter_buff_lists = 0; + Cpa16U buff_num = 0; + Cpa32U buff_meta_size = 0; + CpaDcSessionSetupData sd = {0}; + + if (qat_dc_init_done) + return (0); + + status = cpaDcGetNumInstances(&num_inst); + if (status != CPA_STATUS_SUCCESS) + return (-1); + + /* if the user has configured no QAT compression units just return */ + if (num_inst == 0) + return (0); + + if (num_inst > QAT_DC_MAX_INSTANCES) + num_inst = QAT_DC_MAX_INSTANCES; + + status = cpaDcGetInstances(num_inst, &dc_inst_handles[0]); + if (status != CPA_STATUS_SUCCESS) + return (-1); + + for (Cpa16U i = 0; i < num_inst; i++) { + cpaDcSetAddressTranslation(dc_inst_handles[i], + (void*)virt_to_phys); + + status = cpaDcBufferListGetMetaSize(dc_inst_handles[i], + 1, &buff_meta_size); + + if (status == CPA_STATUS_SUCCESS) + status = cpaDcGetNumIntermediateBuffers( + dc_inst_handles[i], &num_inter_buff_lists); + + if (status == CPA_STATUS_SUCCESS && num_inter_buff_lists != 0) + status = QAT_PHYS_CONTIG_ALLOC(&buffer_array[i], + num_inter_buff_lists * + sizeof (CpaBufferList *)); + + for (buff_num = 0; buff_num < num_inter_buff_lists; + buff_num++) { + if (status == CPA_STATUS_SUCCESS) + status = QAT_PHYS_CONTIG_ALLOC( + &buffer_array[i][buff_num], + sizeof (CpaBufferList)); + + if (status == CPA_STATUS_SUCCESS) + status = QAT_PHYS_CONTIG_ALLOC( + &buffer_array[i][buff_num]-> + pPrivateMetaData, + buff_meta_size); + + if (status == CPA_STATUS_SUCCESS) + status = QAT_PHYS_CONTIG_ALLOC( + &buffer_array[i][buff_num]->pBuffers, + sizeof (CpaFlatBuffer)); + + if (status == CPA_STATUS_SUCCESS) { + /* + * implementation requires an intermediate + * buffer approximately twice the size of + * output buffer, which is 2x max buffer + * size here. + */ + status = QAT_PHYS_CONTIG_ALLOC( + &buffer_array[i][buff_num]->pBuffers-> + pData, 2 * QAT_MAX_BUF_SIZE); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + buffer_array[i][buff_num]->numBuffers = 1; + buffer_array[i][buff_num]->pBuffers-> + dataLenInBytes = 2 * QAT_MAX_BUF_SIZE; + } + } + + status = cpaDcStartInstance(dc_inst_handles[i], + num_inter_buff_lists, buffer_array[i]); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + sd.compLevel = CPA_DC_L1; + sd.compType = CPA_DC_DEFLATE; + sd.huffType = CPA_DC_HT_FULL_DYNAMIC; + sd.sessDirection = CPA_DC_DIR_COMBINED; + sd.sessState = CPA_DC_STATELESS; + sd.deflateWindowSize = 7; + sd.checksum = CPA_DC_ADLER32; + status = cpaDcGetSessionSize(dc_inst_handles[i], + &sd, &sess_size, &ctx_size); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + QAT_PHYS_CONTIG_ALLOC(&session_handles[i], sess_size); + if (session_handles[i] == NULL) + goto fail; + + status = cpaDcInitSession(dc_inst_handles[i], + session_handles[i], + &sd, NULL, qat_dc_callback); + if (status != CPA_STATUS_SUCCESS) + goto fail; + } + + qat_dc_init_done = B_TRUE; + return (0); +fail: + qat_dc_clean(); + return (-1); +} + +void +qat_dc_fini(void) +{ + if (!qat_dc_init_done) + return; + + qat_dc_clean(); +} + +/* + * The "add" parameter is an additional buffer which is passed + * to QAT as a scratch buffer alongside the destination buffer + * in case the "compressed" data ends up being larger than the + * original source data. This is necessary to prevent QAT from + * generating buffer overflow warnings for incompressible data. + */ +static int +qat_compress_impl(qat_compress_dir_t dir, char *src, int src_len, + char *dst, int dst_len, char *add, int add_len, size_t *c_len) +{ + CpaInstanceHandle dc_inst_handle; + CpaDcSessionHandle session_handle; + CpaBufferList *buf_list_src = NULL; + CpaBufferList *buf_list_dst = NULL; + CpaFlatBuffer *flat_buf_src = NULL; + CpaFlatBuffer *flat_buf_dst = NULL; + Cpa8U *buffer_meta_src = NULL; + Cpa8U *buffer_meta_dst = NULL; + Cpa32U buffer_meta_size = 0; + CpaDcRqResults dc_results; + CpaStatus status = CPA_STATUS_FAIL; + Cpa32U hdr_sz = 0; + Cpa32U compressed_sz; + Cpa32U num_src_buf = (src_len >> PAGE_SHIFT) + 2; + Cpa32U num_dst_buf = (dst_len >> PAGE_SHIFT) + 2; + Cpa32U num_add_buf = (add_len >> PAGE_SHIFT) + 2; + Cpa32U bytes_left; + Cpa32U dst_pages = 0; + Cpa32U adler32 = 0; + char *data; + struct page *page; + struct page **in_pages = NULL; + struct page **out_pages = NULL; + struct page **add_pages = NULL; + Cpa32U page_off = 0; + struct completion complete; + Cpa32U page_num = 0; + Cpa16U i; + + /* + * We increment num_src_buf and num_dst_buf by 2 to allow + * us to handle non page-aligned buffer addresses and buffers + * whose sizes are not divisible by PAGE_SIZE. + */ + Cpa32U src_buffer_list_mem_size = sizeof (CpaBufferList) + + (num_src_buf * sizeof (CpaFlatBuffer)); + Cpa32U dst_buffer_list_mem_size = sizeof (CpaBufferList) + + ((num_dst_buf + num_add_buf) * sizeof (CpaFlatBuffer)); + + status = QAT_PHYS_CONTIG_ALLOC(&in_pages, + num_src_buf * sizeof (struct page *)); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + status = QAT_PHYS_CONTIG_ALLOC(&out_pages, + num_dst_buf * sizeof (struct page *)); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + status = QAT_PHYS_CONTIG_ALLOC(&add_pages, + num_add_buf * sizeof (struct page *)); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + i = (Cpa32U)atomic_inc_32_nv(&inst_num) % num_inst; + dc_inst_handle = dc_inst_handles[i]; + session_handle = session_handles[i]; + + cpaDcBufferListGetMetaSize(dc_inst_handle, num_src_buf, + &buffer_meta_size); + status = QAT_PHYS_CONTIG_ALLOC(&buffer_meta_src, buffer_meta_size); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + cpaDcBufferListGetMetaSize(dc_inst_handle, num_dst_buf + num_add_buf, + &buffer_meta_size); + status = QAT_PHYS_CONTIG_ALLOC(&buffer_meta_dst, buffer_meta_size); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + /* build source buffer list */ + status = QAT_PHYS_CONTIG_ALLOC(&buf_list_src, src_buffer_list_mem_size); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + flat_buf_src = (CpaFlatBuffer *)(buf_list_src + 1); + + buf_list_src->pBuffers = flat_buf_src; /* always point to first one */ + + /* build destination buffer list */ + status = QAT_PHYS_CONTIG_ALLOC(&buf_list_dst, dst_buffer_list_mem_size); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + flat_buf_dst = (CpaFlatBuffer *)(buf_list_dst + 1); + + buf_list_dst->pBuffers = flat_buf_dst; /* always point to first one */ + + buf_list_src->numBuffers = 0; + buf_list_src->pPrivateMetaData = buffer_meta_src; + bytes_left = src_len; + data = src; + page_num = 0; + while (bytes_left > 0) { + page_off = ((long)data & ~PAGE_MASK); + page = qat_mem_to_page(data); + in_pages[page_num] = page; + flat_buf_src->pData = kmap(page) + page_off; + flat_buf_src->dataLenInBytes = + min((long)PAGE_SIZE - page_off, (long)bytes_left); + + bytes_left -= flat_buf_src->dataLenInBytes; + data += flat_buf_src->dataLenInBytes; + flat_buf_src++; + buf_list_src->numBuffers++; + page_num++; + } + + buf_list_dst->numBuffers = 0; + buf_list_dst->pPrivateMetaData = buffer_meta_dst; + bytes_left = dst_len; + data = dst; + page_num = 0; + while (bytes_left > 0) { + page_off = ((long)data & ~PAGE_MASK); + page = qat_mem_to_page(data); + flat_buf_dst->pData = kmap(page) + page_off; + out_pages[page_num] = page; + flat_buf_dst->dataLenInBytes = + min((long)PAGE_SIZE - page_off, (long)bytes_left); + + bytes_left -= flat_buf_dst->dataLenInBytes; + data += flat_buf_dst->dataLenInBytes; + flat_buf_dst++; + buf_list_dst->numBuffers++; + page_num++; + dst_pages++; + } + + /* map additional scratch pages into the destination buffer list */ + bytes_left = add_len; + data = add; + page_num = 0; + while (bytes_left > 0) { + page_off = ((long)data & ~PAGE_MASK); + page = qat_mem_to_page(data); + flat_buf_dst->pData = kmap(page) + page_off; + add_pages[page_num] = page; + flat_buf_dst->dataLenInBytes = + min((long)PAGE_SIZE - page_off, (long)bytes_left); + + bytes_left -= flat_buf_dst->dataLenInBytes; + data += flat_buf_dst->dataLenInBytes; + flat_buf_dst++; + buf_list_dst->numBuffers++; + page_num++; + } + + init_completion(&complete); + + if (dir == QAT_COMPRESS) { + QAT_STAT_BUMP(comp_requests); + QAT_STAT_INCR(comp_total_in_bytes, src_len); + + cpaDcGenerateHeader(session_handle, + buf_list_dst->pBuffers, &hdr_sz); + buf_list_dst->pBuffers->pData += hdr_sz; + buf_list_dst->pBuffers->dataLenInBytes -= hdr_sz; + status = cpaDcCompressData( + dc_inst_handle, session_handle, + buf_list_src, buf_list_dst, + &dc_results, CPA_DC_FLUSH_FINAL, + &complete); + if (status != CPA_STATUS_SUCCESS) { + goto fail; + } + + /* we now wait until the completion of the operation. */ + wait_for_completion(&complete); + + if (dc_results.status != CPA_STATUS_SUCCESS) { + status = CPA_STATUS_FAIL; + goto fail; + } + + compressed_sz = dc_results.produced; + if (compressed_sz + hdr_sz + ZLIB_FOOT_SZ > dst_len) { + status = CPA_STATUS_INCOMPRESSIBLE; + goto fail; + } + + flat_buf_dst = (CpaFlatBuffer *)(buf_list_dst + 1); + /* move to the last page */ + flat_buf_dst += (compressed_sz + hdr_sz) >> PAGE_SHIFT; + + /* no space for gzip footer in the last page */ + if (((compressed_sz + hdr_sz) % PAGE_SIZE) + + ZLIB_FOOT_SZ > PAGE_SIZE) { + status = CPA_STATUS_INCOMPRESSIBLE; + goto fail; + } + + /* jump to the end of the buffer and append footer */ + flat_buf_dst->pData = + (char *)((unsigned long)flat_buf_dst->pData & PAGE_MASK) + + ((compressed_sz + hdr_sz) % PAGE_SIZE); + flat_buf_dst->dataLenInBytes = ZLIB_FOOT_SZ; + + dc_results.produced = 0; + status = cpaDcGenerateFooter(session_handle, + flat_buf_dst, &dc_results); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + *c_len = compressed_sz + dc_results.produced + hdr_sz; + QAT_STAT_INCR(comp_total_out_bytes, *c_len); + } else { + ASSERT3U(dir, ==, QAT_DECOMPRESS); + QAT_STAT_BUMP(decomp_requests); + QAT_STAT_INCR(decomp_total_in_bytes, src_len); + + buf_list_src->pBuffers->pData += ZLIB_HEAD_SZ; + buf_list_src->pBuffers->dataLenInBytes -= ZLIB_HEAD_SZ; + status = cpaDcDecompressData(dc_inst_handle, session_handle, + buf_list_src, buf_list_dst, &dc_results, CPA_DC_FLUSH_FINAL, + &complete); + + if (CPA_STATUS_SUCCESS != status) { + status = CPA_STATUS_FAIL; + goto fail; + } + + /* we now wait until the completion of the operation. */ + wait_for_completion(&complete); + + if (dc_results.status != CPA_STATUS_SUCCESS) { + status = CPA_STATUS_FAIL; + goto fail; + } + + /* verify adler checksum */ + adler32 = *(Cpa32U *)(src + dc_results.consumed + ZLIB_HEAD_SZ); + if (adler32 != BSWAP_32(dc_results.checksum)) { + status = CPA_STATUS_FAIL; + goto fail; + } + *c_len = dc_results.produced; + QAT_STAT_INCR(decomp_total_out_bytes, *c_len); + } + +fail: + if (status != CPA_STATUS_SUCCESS && status != CPA_STATUS_INCOMPRESSIBLE) + QAT_STAT_BUMP(dc_fails); + + if (in_pages) { + for (page_num = 0; + page_num < buf_list_src->numBuffers; + page_num++) { + kunmap(in_pages[page_num]); + } + QAT_PHYS_CONTIG_FREE(in_pages); + } + + if (out_pages) { + for (page_num = 0; page_num < dst_pages; page_num++) { + kunmap(out_pages[page_num]); + } + QAT_PHYS_CONTIG_FREE(out_pages); + } + + if (add_pages) { + for (page_num = 0; + page_num < buf_list_dst->numBuffers - dst_pages; + page_num++) { + kunmap(add_pages[page_num]); + } + QAT_PHYS_CONTIG_FREE(add_pages); + } + + QAT_PHYS_CONTIG_FREE(buffer_meta_src); + QAT_PHYS_CONTIG_FREE(buffer_meta_dst); + QAT_PHYS_CONTIG_FREE(buf_list_src); + QAT_PHYS_CONTIG_FREE(buf_list_dst); + + return (status); +} + +/* + * Entry point for QAT accelerated compression / decompression. + */ +int +qat_compress(qat_compress_dir_t dir, char *src, int src_len, + char *dst, int dst_len, size_t *c_len) +{ + int ret; + size_t add_len = 0; + void *add = NULL; + + if (dir == QAT_COMPRESS) { + add_len = dst_len; + add = zio_data_buf_alloc(add_len); + } + + ret = qat_compress_impl(dir, src, src_len, dst, + dst_len, add, add_len, c_len); + + if (dir == QAT_COMPRESS) + zio_data_buf_free(add, add_len); + + return (ret); +} + +static int +param_set_qat_compress(const char *val, zfs_kernel_param_t *kp) +{ + int ret; + int *pvalue = kp->arg; + ret = param_set_int(val, kp); + if (ret) + return (ret); + /* + * zfs_qat_compress_disable = 0: enable qat compress + * try to initialize qat instance if it has not been done + */ + if (*pvalue == 0 && !qat_dc_init_done) { + ret = qat_dc_init(); + if (ret != 0) { + zfs_qat_compress_disable = 1; + return (ret); + } + } + return (ret); +} + +module_param_call(zfs_qat_compress_disable, param_set_qat_compress, + param_get_int, &zfs_qat_compress_disable, 0644); +MODULE_PARM_DESC(zfs_qat_compress_disable, "Enable/Disable QAT compression"); + +#endif diff --git a/module/os/macos/zfs/qat_crypt.c b/module/os/macos/zfs/qat_crypt.c new file mode 100644 index 000000000000..18b6e38d1a6e --- /dev/null +++ b/module/os/macos/zfs/qat_crypt.c @@ -0,0 +1,630 @@ +/* + * 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 file represents the QAT implementation of checksums and encryption. + * Internally, QAT shares the same cryptographic instances for both of these + * operations, so the code has been combined here. QAT data compression uses + * compression instances, so that code is separated into qat_compress.c + */ + +#if defined(_KERNEL) && defined(HAVE_QAT) +#include +#include +#include +#include +#include +#include +#include "lac/cpa_cy_im.h" +#include "lac/cpa_cy_common.h" +#include + +/* + * Max instances in a QAT device, each instance is a channel to submit + * jobs to QAT hardware, this is only for pre-allocating instances + * and session arrays; the actual number of instances are defined in + * the QAT driver's configure file. + */ +#define QAT_CRYPT_MAX_INSTANCES 48 + +#define MAX_PAGE_NUM 1024 + +static Cpa32U inst_num = 0; +static Cpa16U num_inst = 0; +static CpaInstanceHandle cy_inst_handles[QAT_CRYPT_MAX_INSTANCES]; +static boolean_t qat_cy_init_done = B_FALSE; +int zfs_qat_encrypt_disable = 0; +int zfs_qat_checksum_disable = 0; + +typedef struct cy_callback { + CpaBoolean verify_result; + struct completion complete; +} cy_callback_t; + +static void +symcallback(void *p_callback, CpaStatus status, const CpaCySymOp operation, + void *op_data, CpaBufferList *buf_list_dst, CpaBoolean verify) +{ + cy_callback_t *cb = p_callback; + + if (cb != NULL) { + /* indicate that the function has been called */ + cb->verify_result = verify; + complete(&cb->complete); + } +} + +boolean_t +qat_crypt_use_accel(size_t s_len) +{ + return (!zfs_qat_encrypt_disable && + qat_cy_init_done && + s_len >= QAT_MIN_BUF_SIZE && + s_len <= QAT_MAX_BUF_SIZE); +} + +boolean_t +qat_checksum_use_accel(size_t s_len) +{ + return (!zfs_qat_checksum_disable && + qat_cy_init_done && + s_len >= QAT_MIN_BUF_SIZE && + s_len <= QAT_MAX_BUF_SIZE); +} + +void +qat_cy_clean(void) +{ + for (Cpa16U i = 0; i < num_inst; i++) + cpaCyStopInstance(cy_inst_handles[i]); + + num_inst = 0; + qat_cy_init_done = B_FALSE; +} + +int +qat_cy_init(void) +{ + CpaStatus status = CPA_STATUS_FAIL; + + if (qat_cy_init_done) + return (0); + + status = cpaCyGetNumInstances(&num_inst); + if (status != CPA_STATUS_SUCCESS) + return (-1); + + /* if the user has configured no QAT encryption units just return */ + if (num_inst == 0) + return (0); + + if (num_inst > QAT_CRYPT_MAX_INSTANCES) + num_inst = QAT_CRYPT_MAX_INSTANCES; + + status = cpaCyGetInstances(num_inst, &cy_inst_handles[0]); + if (status != CPA_STATUS_SUCCESS) + return (-1); + + for (Cpa16U i = 0; i < num_inst; i++) { + status = cpaCySetAddressTranslation(cy_inst_handles[i], + (void *)virt_to_phys); + if (status != CPA_STATUS_SUCCESS) + goto error; + + status = cpaCyStartInstance(cy_inst_handles[i]); + if (status != CPA_STATUS_SUCCESS) + goto error; + } + + qat_cy_init_done = B_TRUE; + return (0); + +error: + qat_cy_clean(); + return (-1); +} + +void +qat_cy_fini(void) +{ + if (!qat_cy_init_done) + return; + + qat_cy_clean(); +} + +static CpaStatus +qat_init_crypt_session_ctx(qat_encrypt_dir_t dir, CpaInstanceHandle inst_handle, + CpaCySymSessionCtx **cy_session_ctx, crypto_key_t *key, + Cpa64U crypt, Cpa32U aad_len) +{ + CpaStatus status = CPA_STATUS_SUCCESS; + Cpa32U ctx_size; + Cpa32U ciper_algorithm; + Cpa32U hash_algorithm; + CpaCySymSessionSetupData sd = { 0 }; + + if (zio_crypt_table[crypt].ci_crypt_type == ZC_TYPE_CCM) { + return (CPA_STATUS_FAIL); + } else { + ciper_algorithm = CPA_CY_SYM_CIPHER_AES_GCM; + hash_algorithm = CPA_CY_SYM_HASH_AES_GCM; + } + + sd.cipherSetupData.cipherAlgorithm = ciper_algorithm; + sd.cipherSetupData.pCipherKey = key->ck_data; + sd.cipherSetupData.cipherKeyLenInBytes = key->ck_length / 8; + sd.hashSetupData.hashAlgorithm = hash_algorithm; + sd.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_AUTH; + sd.hashSetupData.digestResultLenInBytes = ZIO_DATA_MAC_LEN; + sd.hashSetupData.authModeSetupData.aadLenInBytes = aad_len; + sd.sessionPriority = CPA_CY_PRIORITY_NORMAL; + sd.symOperation = CPA_CY_SYM_OP_ALGORITHM_CHAINING; + sd.digestIsAppended = CPA_FALSE; + sd.verifyDigest = CPA_FALSE; + + if (dir == QAT_ENCRYPT) { + sd.cipherSetupData.cipherDirection = + CPA_CY_SYM_CIPHER_DIRECTION_ENCRYPT; + sd.algChainOrder = + CPA_CY_SYM_ALG_CHAIN_ORDER_HASH_THEN_CIPHER; + } else { + ASSERT3U(dir, ==, QAT_DECRYPT); + sd.cipherSetupData.cipherDirection = + CPA_CY_SYM_CIPHER_DIRECTION_DECRYPT; + sd.algChainOrder = + CPA_CY_SYM_ALG_CHAIN_ORDER_CIPHER_THEN_HASH; + } + + status = cpaCySymSessionCtxGetSize(inst_handle, &sd, &ctx_size); + if (status != CPA_STATUS_SUCCESS) + return (status); + + status = QAT_PHYS_CONTIG_ALLOC(cy_session_ctx, ctx_size); + if (status != CPA_STATUS_SUCCESS) + return (status); + + status = cpaCySymInitSession(inst_handle, symcallback, &sd, + *cy_session_ctx); + if (status != CPA_STATUS_SUCCESS) { + QAT_PHYS_CONTIG_FREE(*cy_session_ctx); + return (status); + } + + return (CPA_STATUS_SUCCESS); +} + +static CpaStatus +qat_init_checksum_session_ctx(CpaInstanceHandle inst_handle, + CpaCySymSessionCtx **cy_session_ctx, Cpa64U cksum) +{ + CpaStatus status = CPA_STATUS_SUCCESS; + Cpa32U ctx_size; + Cpa32U hash_algorithm; + CpaCySymSessionSetupData sd = { 0 }; + + /* + * ZFS's SHA512 checksum is actually SHA512/256, which uses + * a different IV from standard SHA512. QAT does not support + * SHA512/256, so we can only support SHA256. + */ + if (cksum == ZIO_CHECKSUM_SHA256) + hash_algorithm = CPA_CY_SYM_HASH_SHA256; + else + return (CPA_STATUS_FAIL); + + sd.sessionPriority = CPA_CY_PRIORITY_NORMAL; + sd.symOperation = CPA_CY_SYM_OP_HASH; + sd.hashSetupData.hashAlgorithm = hash_algorithm; + sd.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_PLAIN; + sd.hashSetupData.digestResultLenInBytes = sizeof (zio_cksum_t); + sd.digestIsAppended = CPA_FALSE; + sd.verifyDigest = CPA_FALSE; + + status = cpaCySymSessionCtxGetSize(inst_handle, &sd, &ctx_size); + if (status != CPA_STATUS_SUCCESS) + return (status); + + status = QAT_PHYS_CONTIG_ALLOC(cy_session_ctx, ctx_size); + if (status != CPA_STATUS_SUCCESS) + return (status); + + status = cpaCySymInitSession(inst_handle, symcallback, &sd, + *cy_session_ctx); + if (status != CPA_STATUS_SUCCESS) { + QAT_PHYS_CONTIG_FREE(*cy_session_ctx); + return (status); + } + + return (CPA_STATUS_SUCCESS); +} + +static CpaStatus +qat_init_cy_buffer_lists(CpaInstanceHandle inst_handle, uint32_t nr_bufs, + CpaBufferList *src, CpaBufferList *dst) +{ + CpaStatus status = CPA_STATUS_SUCCESS; + Cpa32U meta_size = 0; + + status = cpaCyBufferListGetMetaSize(inst_handle, nr_bufs, &meta_size); + if (status != CPA_STATUS_SUCCESS) + return (status); + + status = QAT_PHYS_CONTIG_ALLOC(&src->pPrivateMetaData, meta_size); + if (status != CPA_STATUS_SUCCESS) + goto error; + + if (src != dst) { + status = QAT_PHYS_CONTIG_ALLOC(&dst->pPrivateMetaData, + meta_size); + if (status != CPA_STATUS_SUCCESS) + goto error; + } + + return (CPA_STATUS_SUCCESS); + +error: + QAT_PHYS_CONTIG_FREE(src->pPrivateMetaData); + if (src != dst) + QAT_PHYS_CONTIG_FREE(dst->pPrivateMetaData); + + return (status); +} + +int +qat_crypt(qat_encrypt_dir_t dir, uint8_t *src_buf, uint8_t *dst_buf, + uint8_t *aad_buf, uint32_t aad_len, uint8_t *iv_buf, uint8_t *digest_buf, + crypto_key_t *key, uint64_t crypt, uint32_t enc_len) +{ + CpaStatus status = CPA_STATUS_SUCCESS; + Cpa16U i; + CpaInstanceHandle cy_inst_handle; + Cpa16U nr_bufs = (enc_len >> PAGE_SHIFT) + 2; + Cpa32U bytes_left = 0; + Cpa8S *data = NULL; + CpaCySymSessionCtx *cy_session_ctx = NULL; + cy_callback_t cb; + CpaCySymOpData op_data = { 0 }; + CpaBufferList src_buffer_list = { 0 }; + CpaBufferList dst_buffer_list = { 0 }; + CpaFlatBuffer *flat_src_buf_array = NULL; + CpaFlatBuffer *flat_src_buf = NULL; + CpaFlatBuffer *flat_dst_buf_array = NULL; + CpaFlatBuffer *flat_dst_buf = NULL; + struct page *in_pages[MAX_PAGE_NUM]; + struct page *out_pages[MAX_PAGE_NUM]; + Cpa32U in_page_num = 0; + Cpa32U out_page_num = 0; + Cpa32U in_page_off = 0; + Cpa32U out_page_off = 0; + + if (dir == QAT_ENCRYPT) { + QAT_STAT_BUMP(encrypt_requests); + QAT_STAT_INCR(encrypt_total_in_bytes, enc_len); + } else { + QAT_STAT_BUMP(decrypt_requests); + QAT_STAT_INCR(decrypt_total_in_bytes, enc_len); + } + + i = (Cpa32U)atomic_inc_32_nv(&inst_num) % num_inst; + cy_inst_handle = cy_inst_handles[i]; + + status = qat_init_crypt_session_ctx(dir, cy_inst_handle, + &cy_session_ctx, key, crypt, aad_len); + if (status != CPA_STATUS_SUCCESS) { + /* don't count CCM as a failure since it's not supported */ + if (zio_crypt_table[crypt].ci_crypt_type == ZC_TYPE_GCM) + QAT_STAT_BUMP(crypt_fails); + return (status); + } + + /* + * We increment nr_bufs by 2 to allow us to handle non + * page-aligned buffer addresses and buffers whose sizes + * are not divisible by PAGE_SIZE. + */ + status = qat_init_cy_buffer_lists(cy_inst_handle, nr_bufs, + &src_buffer_list, &dst_buffer_list); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + status = QAT_PHYS_CONTIG_ALLOC(&flat_src_buf_array, + nr_bufs * sizeof (CpaFlatBuffer)); + if (status != CPA_STATUS_SUCCESS) + goto fail; + status = QAT_PHYS_CONTIG_ALLOC(&flat_dst_buf_array, + nr_bufs * sizeof (CpaFlatBuffer)); + if (status != CPA_STATUS_SUCCESS) + goto fail; + status = QAT_PHYS_CONTIG_ALLOC(&op_data.pDigestResult, + ZIO_DATA_MAC_LEN); + if (status != CPA_STATUS_SUCCESS) + goto fail; + status = QAT_PHYS_CONTIG_ALLOC(&op_data.pIv, + ZIO_DATA_IV_LEN); + if (status != CPA_STATUS_SUCCESS) + goto fail; + if (aad_len > 0) { + status = QAT_PHYS_CONTIG_ALLOC(&op_data.pAdditionalAuthData, + aad_len); + if (status != CPA_STATUS_SUCCESS) + goto fail; + memcpy(op_data.pAdditionalAuthData, aad_buf, aad_len); + } + + bytes_left = enc_len; + data = src_buf; + flat_src_buf = flat_src_buf_array; + while (bytes_left > 0) { + in_page_off = ((long)data & ~PAGE_MASK); + in_pages[in_page_num] = qat_mem_to_page(data); + flat_src_buf->pData = kmap(in_pages[in_page_num]) + in_page_off; + flat_src_buf->dataLenInBytes = + min((long)PAGE_SIZE - in_page_off, (long)bytes_left); + data += flat_src_buf->dataLenInBytes; + bytes_left -= flat_src_buf->dataLenInBytes; + flat_src_buf++; + in_page_num++; + } + src_buffer_list.pBuffers = flat_src_buf_array; + src_buffer_list.numBuffers = in_page_num; + + bytes_left = enc_len; + data = dst_buf; + flat_dst_buf = flat_dst_buf_array; + while (bytes_left > 0) { + out_page_off = ((long)data & ~PAGE_MASK); + out_pages[out_page_num] = qat_mem_to_page(data); + flat_dst_buf->pData = kmap(out_pages[out_page_num]) + + out_page_off; + flat_dst_buf->dataLenInBytes = + min((long)PAGE_SIZE - out_page_off, (long)bytes_left); + data += flat_dst_buf->dataLenInBytes; + bytes_left -= flat_dst_buf->dataLenInBytes; + flat_dst_buf++; + out_page_num++; + } + dst_buffer_list.pBuffers = flat_dst_buf_array; + dst_buffer_list.numBuffers = out_page_num; + + op_data.sessionCtx = cy_session_ctx; + op_data.packetType = CPA_CY_SYM_PACKET_TYPE_FULL; + op_data.cryptoStartSrcOffsetInBytes = 0; + op_data.messageLenToCipherInBytes = 0; + op_data.hashStartSrcOffsetInBytes = 0; + op_data.messageLenToHashInBytes = 0; + op_data.messageLenToCipherInBytes = enc_len; + op_data.ivLenInBytes = ZIO_DATA_IV_LEN; + memcpy(op_data.pIv, iv_buf, ZIO_DATA_IV_LEN); + /* if dir is QAT_DECRYPT, copy digest_buf to pDigestResult */ + if (dir == QAT_DECRYPT) + memcpy(op_data.pDigestResult, digest_buf, ZIO_DATA_MAC_LEN); + + cb.verify_result = CPA_FALSE; + init_completion(&cb.complete); + status = cpaCySymPerformOp(cy_inst_handle, &cb, &op_data, + &src_buffer_list, &dst_buffer_list, NULL); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + /* we now wait until the completion of the operation. */ + wait_for_completion(&cb.complete); + + if (cb.verify_result == CPA_FALSE) { + status = CPA_STATUS_FAIL; + goto fail; + } + + if (dir == QAT_ENCRYPT) { + /* if dir is QAT_ENCRYPT, save pDigestResult to digest_buf */ + memcpy(digest_buf, op_data.pDigestResult, ZIO_DATA_MAC_LEN); + QAT_STAT_INCR(encrypt_total_out_bytes, enc_len); + } else { + QAT_STAT_INCR(decrypt_total_out_bytes, enc_len); + } + +fail: + if (status != CPA_STATUS_SUCCESS) + QAT_STAT_BUMP(crypt_fails); + + for (i = 0; i < in_page_num; i++) + kunmap(in_pages[i]); + for (i = 0; i < out_page_num; i++) + kunmap(out_pages[i]); + + cpaCySymRemoveSession(cy_inst_handle, cy_session_ctx); + if (aad_len > 0) + QAT_PHYS_CONTIG_FREE(op_data.pAdditionalAuthData); + QAT_PHYS_CONTIG_FREE(op_data.pIv); + QAT_PHYS_CONTIG_FREE(op_data.pDigestResult); + QAT_PHYS_CONTIG_FREE(src_buffer_list.pPrivateMetaData); + QAT_PHYS_CONTIG_FREE(dst_buffer_list.pPrivateMetaData); + QAT_PHYS_CONTIG_FREE(cy_session_ctx); + QAT_PHYS_CONTIG_FREE(flat_src_buf_array); + QAT_PHYS_CONTIG_FREE(flat_dst_buf_array); + + return (status); +} + +int +qat_checksum(uint64_t cksum, uint8_t *buf, uint64_t size, zio_cksum_t *zcp) +{ + CpaStatus status; + Cpa16U i; + CpaInstanceHandle cy_inst_handle; + Cpa16U nr_bufs = (size >> PAGE_SHIFT) + 2; + Cpa32U bytes_left = 0; + Cpa8S *data = NULL; + CpaCySymSessionCtx *cy_session_ctx = NULL; + cy_callback_t cb; + Cpa8U *digest_buffer = NULL; + CpaCySymOpData op_data = { 0 }; + CpaBufferList src_buffer_list = { 0 }; + CpaFlatBuffer *flat_src_buf_array = NULL; + CpaFlatBuffer *flat_src_buf = NULL; + struct page *in_pages[MAX_PAGE_NUM]; + Cpa32U page_num = 0; + Cpa32U page_off = 0; + + QAT_STAT_BUMP(cksum_requests); + QAT_STAT_INCR(cksum_total_in_bytes, size); + + i = (Cpa32U)atomic_inc_32_nv(&inst_num) % num_inst; + cy_inst_handle = cy_inst_handles[i]; + + status = qat_init_checksum_session_ctx(cy_inst_handle, + &cy_session_ctx, cksum); + if (status != CPA_STATUS_SUCCESS) { + /* don't count unsupported checksums as a failure */ + if (cksum == ZIO_CHECKSUM_SHA256 || + cksum == ZIO_CHECKSUM_SHA512) + QAT_STAT_BUMP(cksum_fails); + return (status); + } + + /* + * We increment nr_bufs by 2 to allow us to handle non + * page-aligned buffer addresses and buffers whose sizes + * are not divisible by PAGE_SIZE. + */ + status = qat_init_cy_buffer_lists(cy_inst_handle, nr_bufs, + &src_buffer_list, &src_buffer_list); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + status = QAT_PHYS_CONTIG_ALLOC(&flat_src_buf_array, + nr_bufs * sizeof (CpaFlatBuffer)); + if (status != CPA_STATUS_SUCCESS) + goto fail; + status = QAT_PHYS_CONTIG_ALLOC(&digest_buffer, + sizeof (zio_cksum_t)); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + bytes_left = size; + data = buf; + flat_src_buf = flat_src_buf_array; + while (bytes_left > 0) { + page_off = ((long)data & ~PAGE_MASK); + in_pages[page_num] = qat_mem_to_page(data); + flat_src_buf->pData = kmap(in_pages[page_num]) + page_off; + flat_src_buf->dataLenInBytes = + min((long)PAGE_SIZE - page_off, (long)bytes_left); + data += flat_src_buf->dataLenInBytes; + bytes_left -= flat_src_buf->dataLenInBytes; + flat_src_buf++; + page_num++; + } + src_buffer_list.pBuffers = flat_src_buf_array; + src_buffer_list.numBuffers = page_num; + + op_data.sessionCtx = cy_session_ctx; + op_data.packetType = CPA_CY_SYM_PACKET_TYPE_FULL; + op_data.hashStartSrcOffsetInBytes = 0; + op_data.messageLenToHashInBytes = size; + op_data.pDigestResult = digest_buffer; + + cb.verify_result = CPA_FALSE; + init_completion(&cb.complete); + status = cpaCySymPerformOp(cy_inst_handle, &cb, &op_data, + &src_buffer_list, &src_buffer_list, NULL); + if (status != CPA_STATUS_SUCCESS) + goto fail; + + /* we now wait until the completion of the operation. */ + wait_for_completion(&cb.complete); + + if (cb.verify_result == CPA_FALSE) { + status = CPA_STATUS_FAIL; + goto fail; + } + + memcpy(zcp, digest_buffer, sizeof (zio_cksum_t)); + +fail: + if (status != CPA_STATUS_SUCCESS) + QAT_STAT_BUMP(cksum_fails); + + for (i = 0; i < page_num; i++) + kunmap(in_pages[i]); + + cpaCySymRemoveSession(cy_inst_handle, cy_session_ctx); + QAT_PHYS_CONTIG_FREE(digest_buffer); + QAT_PHYS_CONTIG_FREE(src_buffer_list.pPrivateMetaData); + QAT_PHYS_CONTIG_FREE(cy_session_ctx); + QAT_PHYS_CONTIG_FREE(flat_src_buf_array); + + return (status); +} + +static int +param_set_qat_encrypt(const char *val, zfs_kernel_param_t *kp) +{ + int ret; + int *pvalue = kp->arg; + ret = param_set_int(val, kp); + if (ret) + return (ret); + /* + * zfs_qat_encrypt_disable = 0: enable qat encrypt + * try to initialize qat instance if it has not been done + */ + if (*pvalue == 0 && !qat_cy_init_done) { + ret = qat_cy_init(); + if (ret != 0) { + zfs_qat_encrypt_disable = 1; + return (ret); + } + } + return (ret); +} + +static int +param_set_qat_checksum(const char *val, zfs_kernel_param_t *kp) +{ + int ret; + int *pvalue = kp->arg; + ret = param_set_int(val, kp); + if (ret) + return (ret); + /* + * set_checksum_param_ops = 0: enable qat checksum + * try to initialize qat instance if it has not been done + */ + if (*pvalue == 0 && !qat_cy_init_done) { + ret = qat_cy_init(); + if (ret != 0) { + zfs_qat_checksum_disable = 1; + return (ret); + } + } + return (ret); +} + +module_param_call(zfs_qat_encrypt_disable, param_set_qat_encrypt, + param_get_int, &zfs_qat_encrypt_disable, 0644); +MODULE_PARM_DESC(zfs_qat_encrypt_disable, "Enable/Disable QAT encryption"); + +module_param_call(zfs_qat_checksum_disable, param_set_qat_checksum, + param_get_int, &zfs_qat_checksum_disable, 0644); +MODULE_PARM_DESC(zfs_qat_checksum_disable, "Enable/Disable QAT checksumming"); + +#endif diff --git a/module/os/macos/zfs/spa_misc_os.c b/module/os/macos/zfs/spa_misc_os.c new file mode 100644 index 000000000000..14aff0a100ee --- /dev/null +++ b/module/os/macos/zfs/spa_misc_os.c @@ -0,0 +1,113 @@ +/* + * 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, 2019 by Delphix. All rights reserved. + * Copyright 2015 Nexenta Systems, Inc. All rights reserved. + * Copyright (c) 2014 Spectra Logic Corporation, All rights reserved. + * Copyright 2013 Saso Kiselkov. All rights reserved. + * Copyright (c) 2017 Datto Inc. + * Copyright (c) 2017, Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "zfs_prop.h" + +const char * +spa_history_zone(void) +{ + return ("macos"); +} + +void +spa_import_os(spa_t *spa) +{ + int haslock = 0; + int error; + + haslock = mutex_owned(&spa_namespace_lock); + + /* Increase open refcount */ + spa_open_ref(spa, FTAG); + + if (haslock) { + mutex_exit(&spa_namespace_lock); + } + + /* Create IOKit pool proxy */ + if ((error = spa_iokit_pool_proxy_create(spa)) != 0) { + printf("%s spa_iokit_pool_proxy_create error %d\n", + __func__, error); + /* spa_create succeeded, ignore proxy error */ + } + + /* Cache vdev info, needs open ref above, and pool proxy */ + + if (error == 0 && (error = zfs_boot_update_bootinfo(spa)) != 0) { + printf("%s update_bootinfo error %d\n", __func__, error); + /* create succeeded, ignore error from bootinfo */ + } + + /* Drop open refcount */ + if (haslock) { + mutex_enter(&spa_namespace_lock); + } + + spa_close(spa, FTAG); +} + +void +spa_export_os(spa_t *spa) +{ + /* Remove IOKit pool proxy */ + spa_iokit_pool_proxy_destroy(spa); +} + +void +spa_activate_os(spa_t *spa) +{ + /* spa_t *spa = (spa_t *)arg; */ + /* Lock kext in kernel while mounted */ + OSKextRetainKextWithLoadTag(OSKextGetCurrentLoadTag()); +} + +void +spa_deactivate_os(spa_t *spa) +{ + /* spa_t *spa = (spa_t *)arg; */ + OSKextReleaseKextWithLoadTag(OSKextGetCurrentLoadTag()); +} diff --git a/module/os/macos/zfs/sysctl_os.c b/module/os/macos/zfs/sysctl_os.c new file mode 100644 index 000000000000..370fb6945130 --- /dev/null +++ b/module/os/macos/zfs/sysctl_os.c @@ -0,0 +1,914 @@ +/* + * 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. + * + */ + +/* + * The file "os/macos/spl/sys/mod_os.h" handles mapping Linux ZFS_MODULE_ + * tunables into sysctl - like FreeBSD does. However, on macOS we + * have to additionally call sysctl_register_oid() for each struct. + * We can not use "linker sets" on macOS, due to kernel restrictions, + * so mod_os.h defines a "__attribute__((constructor))" function + * for each parameter, each called when kext loads. + * Similarly, all destructor functions call sysctl_unregister_oid(). + * + * When new ZFS_MODULE_PARAMS() add a new parameter, they should + * show automatically. However, if a new branch is added, like that + * of .zfs."vdev". or .zfs."condense". then matching lines are + * required in sysctl_os_init() and sysctl_os_fini(). + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +/* Remove this when merged with upstream */ +#ifndef MIN_ARC_MAX +#define MIN_ARC_MAX DMU_MAX_ACCESS +#endif + +/* BEGIN CSTYLED */ + +/* + * We want, from old OSX: + * kstat.zfs.darwin.tunable.zfs_condense_indirect_commit_entry_delay_ms + * OpenZFS defines + * ZFS_MODULE_PARAM(zfs_condense, zfs_condense_, indirect_commit_entry_delay_ms, INT, ZMOD_RW, + * _sysctl__kstat_zfs_darwin_tunable_zfs_condense_indirect_commit_entry_delay_ms + * + * We build the entire tree under _tunables. Then at init() time + * we fetch the sysctl_node for "kstat.zfs.darwin.tunable" and + * set that as parent. This so the sysctls can coexist between + * spl-kstat.c and this file. + */ +SYSCTL_DECL(_tunable); + +SYSCTL_NODE( , OID_AUTO, tunable, CTLFLAG_RW, 0, ""); +SYSCTL_NODE(_tunable, OID_AUTO, zfs, + CTLFLAG_RD | CTLFLAG_LOCKED, + 0, ""); + +SYSCTL_NODE(_tunable, OID_AUTO, zfs_arc, CTLFLAG_RW, 0, "ZFS adaptive replacement cache"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_brt, CTLFLAG_RW, 0, "ZFS Block Reference Table"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_condense, CTLFLAG_RW, 0, "ZFS condense"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_dbuf, CTLFLAG_RW, 0, "ZFS disk buf cache"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_dbuf_cache, CTLFLAG_RW, 0, "ZFS disk buf cache"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_deadman, CTLFLAG_RW, 0, "ZFS deadman"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_dedup, CTLFLAG_RW, 0, "ZFS dedup"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_l2arc, CTLFLAG_RW, 0, "ZFS l2arc"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_livelist, CTLFLAG_RW, 0, "ZFS livelist"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_lua, CTLFLAG_RW, 0, "ZFS lua"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_metaslab, CTLFLAG_RW, 0, "ZFS metaslab"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_mg, CTLFLAG_RW, 0, "ZFS metaslab group"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_multihost, CTLFLAG_RW, 0, "ZFS multihost protection"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_prefetch, CTLFLAG_RW, 0, "ZFS prefetch"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_reconstruct, CTLFLAG_RW, 0, "ZFS reconstruct"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_recv, CTLFLAG_RW, 0, "ZFS receive"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_send, CTLFLAG_RW, 0, "ZFS send"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_spa, CTLFLAG_RW, 0, "ZFS space allocation"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_trim, CTLFLAG_RW, 0, "ZFS TRIM"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_txg, CTLFLAG_RW, 0, "ZFS transaction group"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_vdev, CTLFLAG_RW, 0, "ZFS VDEV"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_vnops, CTLFLAG_RW, 0, "ZFS VNOPS"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_zevent, CTLFLAG_RW, 0, "ZFS event"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_zil, CTLFLAG_RW, 0, "ZFS ZIL"); +SYSCTL_NODE(_tunable, OID_AUTO, zfs_zio, CTLFLAG_RW, 0, "ZFS ZIO"); + +SYSCTL_NODE(_tunable_zfs_livelist, OID_AUTO, condense, CTLFLAG_RW, 0, + "ZFS livelist condense"); + +SYSCTL_NODE(_tunable_zfs_vdev, OID_AUTO, cache, CTLFLAG_RW, 0, "ZFS VDEV Cache"); +SYSCTL_NODE(_tunable_zfs_vdev, OID_AUTO, file, CTLFLAG_RW, 0, "ZFS VDEV file"); +SYSCTL_NODE(_tunable_zfs_vdev, OID_AUTO, mirror, CTLFLAG_RD, 0, + "ZFS VDEV mirror"); + +void sysctl_os_init(void) +{ + /* Ask spl-kstat.c for (parent of) "tunable" sysctl node to attach to */ + struct sysctl_oid_list *parent; + parent = spl_kstat_find_oid("zfs", "darwin"); + if (parent != NULL) + sysctl__tunable.oid_parent = parent; + + sysctl_register_oid(&sysctl__tunable); + sysctl_register_oid(&sysctl__tunable_zfs); + + sysctl_register_oid(&sysctl__tunable_zfs_arc); + sysctl_register_oid(&sysctl__tunable_zfs_brt); + sysctl_register_oid(&sysctl__tunable_zfs_condense); + sysctl_register_oid(&sysctl__tunable_zfs_dbuf); + sysctl_register_oid(&sysctl__tunable_zfs_dbuf_cache); + sysctl_register_oid(&sysctl__tunable_zfs_deadman); + sysctl_register_oid(&sysctl__tunable_zfs_dedup); + sysctl_register_oid(&sysctl__tunable_zfs_l2arc); + sysctl_register_oid(&sysctl__tunable_zfs_livelist); + sysctl_register_oid(&sysctl__tunable_zfs_lua); + sysctl_register_oid(&sysctl__tunable_zfs_metaslab); + sysctl_register_oid(&sysctl__tunable_zfs_mg); + sysctl_register_oid(&sysctl__tunable_zfs_multihost); + sysctl_register_oid(&sysctl__tunable_zfs_prefetch); + sysctl_register_oid(&sysctl__tunable_zfs_reconstruct); + sysctl_register_oid(&sysctl__tunable_zfs_recv); + sysctl_register_oid(&sysctl__tunable_zfs_send); + sysctl_register_oid(&sysctl__tunable_zfs_spa); + sysctl_register_oid(&sysctl__tunable_zfs_trim); + sysctl_register_oid(&sysctl__tunable_zfs_txg); + sysctl_register_oid(&sysctl__tunable_zfs_vdev); + sysctl_register_oid(&sysctl__tunable_zfs_vnops); + sysctl_register_oid(&sysctl__tunable_zfs_zevent); + sysctl_register_oid(&sysctl__tunable_zfs_zil); + sysctl_register_oid(&sysctl__tunable_zfs_zio); + sysctl_register_oid(&sysctl__tunable_zfs_livelist_condense); + sysctl_register_oid(&sysctl__tunable_zfs_vdev_cache); + sysctl_register_oid(&sysctl__tunable_zfs_vdev_file); + sysctl_register_oid(&sysctl__tunable_zfs_vdev_mirror); +} + +void sysctl_os_fini(void) +{ + sysctl_unregister_oid(&sysctl__tunable_zfs_vdev_mirror); + sysctl_unregister_oid(&sysctl__tunable_zfs_vdev_file); + sysctl_unregister_oid(&sysctl__tunable_zfs_vdev_cache); + sysctl_unregister_oid(&sysctl__tunable_zfs_livelist_condense); + sysctl_unregister_oid(&sysctl__tunable_zfs_zio); + sysctl_unregister_oid(&sysctl__tunable_zfs_zil); + sysctl_unregister_oid(&sysctl__tunable_zfs_zevent); + sysctl_unregister_oid(&sysctl__tunable_zfs_vnops); + sysctl_unregister_oid(&sysctl__tunable_zfs_vdev); + sysctl_unregister_oid(&sysctl__tunable_zfs_txg); + sysctl_unregister_oid(&sysctl__tunable_zfs_trim); + sysctl_unregister_oid(&sysctl__tunable_zfs_spa); + sysctl_unregister_oid(&sysctl__tunable_zfs_send); + sysctl_unregister_oid(&sysctl__tunable_zfs_recv); + sysctl_unregister_oid(&sysctl__tunable_zfs_reconstruct); + sysctl_unregister_oid(&sysctl__tunable_zfs_prefetch); + sysctl_unregister_oid(&sysctl__tunable_zfs_multihost); + sysctl_unregister_oid(&sysctl__tunable_zfs_mg); + sysctl_unregister_oid(&sysctl__tunable_zfs_metaslab); + sysctl_unregister_oid(&sysctl__tunable_zfs_lua); + sysctl_unregister_oid(&sysctl__tunable_zfs_livelist); + sysctl_unregister_oid(&sysctl__tunable_zfs_l2arc); + sysctl_unregister_oid(&sysctl__tunable_zfs_dedup); + sysctl_unregister_oid(&sysctl__tunable_zfs_deadman); + sysctl_unregister_oid(&sysctl__tunable_zfs_dbuf_cache); + sysctl_unregister_oid(&sysctl__tunable_zfs_dbuf); + sysctl_unregister_oid(&sysctl__tunable_zfs_condense); + sysctl_unregister_oid(&sysctl__tunable_zfs_brt); + sysctl_unregister_oid(&sysctl__tunable_zfs_arc); + sysctl_unregister_oid(&sysctl__tunable_zfs); + sysctl_unregister_oid(&sysctl__tunable); +} + +extern arc_state_t ARC_anon; +extern arc_state_t ARC_mru; +extern arc_state_t ARC_mru_ghost; +extern arc_state_t ARC_mfu; +extern arc_state_t ARC_mfu_ghost; +extern arc_state_t ARC_l2c_only; + +/* + * minimum lifespan of a prefetch block in clock ticks + * (initialized in arc_init()) + */ + +/* arc.c */ + +int +param_set_arc_max(ZFS_MODULE_PARAM_ARGS) +{ + uint64_t val; + int err; + + val = zfs_arc_max; + err = sysctl_handle_quad(oidp, &val, 0, req); + if (err != 0 || req->newptr == (user_addr_t) NULL) + return (SET_ERROR(err)); + + if (val != 0 && (val < MIN_ARC_MAX || val <= arc_c_min || + val >= arc_all_memory())) + return (SET_ERROR(EINVAL)); + + zfs_arc_max = val; + arc_tuning_update(B_TRUE); + + /* Update the sysctl to the tuned value */ + if (val != 0) + zfs_arc_max = arc_c_max; + + return (0); +} + +int +param_set_arc_min(ZFS_MODULE_PARAM_ARGS) +{ + uint64_t val; + int err; + + val = zfs_arc_min; + err = sysctl_handle_quad(oidp, &val, 0, req); + if (err != 0 || req->newptr == (user_addr_t) NULL) + return (SET_ERROR(err)); + + if (val != 0 && (val < 2ULL << SPA_MAXBLOCKSHIFT || val > arc_c_max)) + return (SET_ERROR(EINVAL)); + + zfs_arc_min = val; + arc_tuning_update(B_TRUE); + + /* Update the sysctl to the tuned value */ + if (val != 0) + zfs_arc_min = arc_c_min; + + return (0); +} + +/* legacy compat */ +extern uint64_t l2arc_write_max; /* def max write size */ +extern uint64_t l2arc_write_boost; /* extra warmup write */ +extern uint64_t l2arc_headroom; /* # of dev writes */ +extern uint64_t l2arc_headroom_boost; +extern uint64_t l2arc_feed_secs; /* interval seconds */ +extern uint64_t l2arc_feed_min_ms; /* min interval msecs */ +extern int l2arc_noprefetch; /* don't cache prefetch bufs */ +extern int l2arc_feed_again; /* turbo warmup */ +extern int l2arc_norw; /* no reads during writes */ + +static int +param_get_arc_state_size(ZFS_MODULE_PARAM_ARGS) +{ + arc_state_t *state = (arc_state_t *)arg1; + int64_t val; + + val = zfs_refcount_count(&state->arcs_size[ARC_BUFC_DATA]) + + zfs_refcount_count(&state->arcs_size[ARC_BUFC_METADATA]); + return (sysctl_handle_quad(oidp, &val, 0, req)); +} + +extern arc_state_t ARC_anon; + +SYSCTL_PROC(_tunable, OID_AUTO, anon_size, + CTLTYPE_S64 | CTLFLAG_RD, + &ARC_anon, 0, param_get_arc_state_size, "Q", + "size of anonymous state"); +SYSCTL_UQUAD(_tunable, OID_AUTO, anon_metadata_esize, CTLFLAG_RD, + &ARC_anon.arcs_esize[ARC_BUFC_METADATA].rc_count, 0, + "size of evictable metadata in anonymous state"); +SYSCTL_UQUAD(_tunable, OID_AUTO, anon_data_esize, CTLFLAG_RD, + &ARC_anon.arcs_esize[ARC_BUFC_DATA].rc_count, 0, + "size of evictable data in anonymous state"); + +extern arc_state_t ARC_mru_ghost; + +SYSCTL_PROC(_tunable, OID_AUTO, mru_ghost_size, + CTLTYPE_S64 | CTLFLAG_RD, + &ARC_mru_ghost, 0, param_get_arc_state_size, "Q", + "size of mru ghost state"); +SYSCTL_UQUAD(_tunable, OID_AUTO, mru_ghost_metadata_esize, CTLFLAG_RD, + &ARC_mru_ghost.arcs_esize[ARC_BUFC_METADATA].rc_count, 0, + "size of evictable metadata in mru ghost state"); +SYSCTL_UQUAD(_tunable, OID_AUTO, mru_ghost_data_esize, CTLFLAG_RD, + &ARC_mru_ghost.arcs_esize[ARC_BUFC_DATA].rc_count, 0, + "size of evictable data in mru ghost state"); + +extern arc_state_t ARC_mfu; + +SYSCTL_PROC(_tunable, OID_AUTO, mfu_size, + CTLTYPE_S64 | CTLFLAG_RD | CTLFLAG_MPSAFE, + &ARC_mfu, 0, param_get_arc_state_size, "Q", + "size of mfu state"); +SYSCTL_UQUAD(_tunable, OID_AUTO, mfu_metadata_esize, CTLFLAG_RD, + &ARC_mfu.arcs_esize[ARC_BUFC_METADATA].rc_count, 0, + "size of evictable metadata in mfu state"); +SYSCTL_UQUAD(_tunable, OID_AUTO, mfu_data_esize, CTLFLAG_RD, + &ARC_mfu.arcs_esize[ARC_BUFC_DATA].rc_count, 0, + "size of evictable data in mfu state"); + +extern arc_state_t ARC_mfu_ghost; + +SYSCTL_PROC(_tunable, OID_AUTO, mfu_ghost_size, + CTLTYPE_S64 | CTLFLAG_RD | CTLFLAG_MPSAFE, + &ARC_mfu_ghost, 0, param_get_arc_state_size, "Q", + "size of mfu ghost state"); +SYSCTL_UQUAD(_tunable, OID_AUTO, mfu_ghost_metadata_esize, CTLFLAG_RD, + &ARC_mfu_ghost.arcs_esize[ARC_BUFC_METADATA].rc_count, 0, + "size of evictable metadata in mfu ghost state"); +SYSCTL_UQUAD(_tunable, OID_AUTO, mfu_ghost_data_esize, CTLFLAG_RD, + &ARC_mfu_ghost.arcs_esize[ARC_BUFC_DATA].rc_count, 0, + "size of evictable data in mfu ghost state"); + +extern arc_state_t ARC_uncached; + +SYSCTL_PROC(_tunable, OID_AUTO, uncached_size, + CTLTYPE_S64 | CTLFLAG_RD | CTLFLAG_MPSAFE, + &ARC_uncached, 0, param_get_arc_state_size, "Q", + "size of uncached state"); +SYSCTL_UQUAD(_tunable, OID_AUTO, uncached_metadata_esize, CTLFLAG_RD, + &ARC_uncached.arcs_esize[ARC_BUFC_METADATA].rc_count, 0, + "size of evictable metadata in uncached state"); +SYSCTL_UQUAD(_tunable, OID_AUTO, uncached_data_esize, CTLFLAG_RD, + &ARC_uncached.arcs_esize[ARC_BUFC_DATA].rc_count, 0, + "size of evictable data in uncached state"); + +extern arc_state_t ARC_l2c_only; + +SYSCTL_PROC(_tunable, OID_AUTO, l2c_only_size, + CTLTYPE_S64 | CTLFLAG_RD | CTLFLAG_MPSAFE, + &ARC_l2c_only, 0, param_get_arc_state_size, "Q", + "size of l2c_only state"); + +/* dbuf.c */ + +/* dmu.c */ + +/* dmu_zfetch.c */ + +static int +sysctl_tunable_arc_no_grow_shift(ZFS_MODULE_PARAM_ARGS) +{ + int err, val; + + val = arc_no_grow_shift; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == (user_addr_t) NULL) + return (err); + + if (val < 0 || val >= arc_shrink_shift) + return (EINVAL); + + arc_no_grow_shift = val; + return (0); +} + +SYSCTL_PROC(_tunable, OID_AUTO, arc_no_grow_shift, + CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, NULL, sizeof (int), + sysctl_tunable_arc_no_grow_shift, "I", + "log2(fraction of ARC which must be free to allow growing)"); + +int +param_set_arc_u64(ZFS_MODULE_PARAM_ARGS) +{ + int err; + + err = sysctl_handle_quad(oidp, arg1, 0, req); + if (err != 0 || req->newptr == (user_addr_t) NULL) + return (err); + + arc_tuning_update(B_TRUE); + + return (0); +} + +int +param_set_arc_int(ZFS_MODULE_PARAM_ARGS) +{ + int err; + + err = sysctl_handle_int(oidp, arg1, 0, req); + if (err != 0 || req->newptr == (user_addr_t) NULL) + return (err); + + arc_tuning_update(B_TRUE); + + return (0); +} + +SYSCTL_PROC(_tunable, OID_AUTO, arc_min, + CTLTYPE_ULONG | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, + &zfs_arc_min, sizeof (zfs_arc_min), param_set_arc_min, "LU", + "min arc size (LEGACY)"); +SYSCTL_PROC(_tunable, OID_AUTO, arc_max, + CTLTYPE_ULONG | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, + &zfs_arc_max, sizeof (zfs_arc_max), param_set_arc_max, "LU", + "max arc size (LEGACY)"); + +/* dbuf.c */ + + +/* dmu.c */ + +/* dmu_zfetch.c */ +SYSCTL_NODE(_tunable, OID_AUTO, zfetch, CTLFLAG_RW, 0, "ZFS ZFETCH (LEGACY)"); + +/* max bytes to prefetch per stream (default 8MB) */ +extern uint32_t zfetch_max_distance; +SYSCTL_UINT(_tunable_zfetch, OID_AUTO, max_distance, CTLFLAG_RWTUN, + &zfetch_max_distance, 0, "Max bytes to prefetch per stream (LEGACY)"); + +/* max bytes to prefetch indirects for per stream (default 64MB) */ +extern uint32_t zfetch_max_idistance; +SYSCTL_UINT(_tunable_zfetch, OID_AUTO, max_idistance, CTLFLAG_RWTUN, + &zfetch_max_idistance, 0, + "Max bytes to prefetch indirects for per stream (LEGACY)"); + +/* dsl_pool.c */ + +/* dnode.c */ +extern int zfs_default_bs; +SYSCTL_INT(_tunable, OID_AUTO, default_bs, CTLFLAG_RWTUN, + &zfs_default_bs, 0, "Default dnode block shift"); + +extern int zfs_default_ibs; +SYSCTL_INT(_tunable, OID_AUTO, default_ibs, CTLFLAG_RWTUN, + &zfs_default_ibs, 0, "Default dnode indirect block shift"); + + +/* dsl_scan.c */ + +/* metaslab.c */ + +int +param_set_active_allocator(ZFS_MODULE_PARAM_ARGS) +{ + char buf[16]; + int rc; + + if (req->newptr == (user_addr_t)NULL) + strlcpy(buf, zfs_active_allocator, sizeof (buf)); + + rc = sysctl_handle_string(oidp, buf, sizeof (buf), req); + if (rc || req->newptr == (user_addr_t)NULL) + return (rc); + if (strcmp(buf, zfs_active_allocator) == 0) + return (0); + + return (param_set_active_allocator_common(buf)); +} + +/* + * In pools where the log space map feature is not enabled we touch + * multiple metaslabs (and their respective space maps) with each + * transaction group. Thus, we benefit from having a small space map + * block size since it allows us to issue more I/O operations scattered + * around the disk. So a sane default for the space map block size + * is 8~16K. + */ +extern int zfs_metaslab_sm_blksz_no_log; +SYSCTL_INT(_tunable_zfs_metaslab, OID_AUTO, sm_blksz_no_log, CTLFLAG_RDTUN, + &zfs_metaslab_sm_blksz_no_log, 0, + "Block size for space map in pools with log space map disabled. " + "Power of 2 and greater than 4096."); + +/* + * When the log space map feature is enabled, we accumulate a lot of + * changes per metaslab that are flushed once in a while so we benefit + * from a bigger block size like 128K for the metaslab space maps. + */ +extern int zfs_metaslab_sm_blksz_with_log; +SYSCTL_INT(_tunable_zfs_metaslab, OID_AUTO, sm_blksz_with_log, CTLFLAG_RDTUN, + &zfs_metaslab_sm_blksz_with_log, 0, + "Block size for space map in pools with log space map enabled. " + "Power of 2 and greater than 4096."); + +/* + * The in-core space map representation is more compact than its on-disk form. + * The zfs_condense_pct determines how much more compact the in-core + * space map representation must be before we compact it on-disk. + * Values should be greater than or equal to 100. + */ +extern int zfs_condense_pct; +SYSCTL_INT(_tunable, OID_AUTO, condense_pct, CTLFLAG_RWTUN, + &zfs_condense_pct, 0, + "Condense on-disk spacemap when it is more than this many percents" + " of in-memory counterpart"); + +extern int zfs_remove_max_segment; +SYSCTL_INT(_tunable, OID_AUTO, remove_max_segment, CTLFLAG_RWTUN, + &zfs_remove_max_segment, 0, "Largest contiguous segment ZFS will attempt to" + " allocate when removing a device"); + +extern int zfs_removal_suspend_progress; +SYSCTL_INT(_tunable, OID_AUTO, removal_suspend_progress, CTLFLAG_RWTUN, + &zfs_removal_suspend_progress, 0, "Ensures certain actions can happen while" + " in the middle of a removal"); + + +/* + * Minimum size which forces the dynamic allocator to change + * it's allocation strategy. Once the space map cannot satisfy + * an allocation of this size then it switches to using more + * aggressive strategy (i.e search by size rather than offset). + */ +extern uint64_t metaslab_df_alloc_threshold; +SYSCTL_QUAD(_tunable_zfs_metaslab, OID_AUTO, df_alloc_threshold, CTLFLAG_RWTUN, + &metaslab_df_alloc_threshold, +#ifndef __APPLE__ + 0, +#endif + "Minimum size which forces the dynamic allocator to change it's allocation strategy"); + +/* + * The minimum free space, in percent, which must be available + * in a space map to continue allocations in a first-fit fashion. + * Once the space map's free space drops below this level we dynamically + * switch to using best-fit allocations. + */ +extern int metaslab_df_free_pct; +SYSCTL_INT(_tunable_zfs_metaslab, OID_AUTO, df_free_pct, CTLFLAG_RWTUN, + &metaslab_df_free_pct, 0, + "The minimum free space, in percent, which must be available in a " + "space map to continue allocations in a first-fit fashion"); + +/* spa.c */ +extern int zfs_ccw_retry_interval; +SYSCTL_INT(_tunable, OID_AUTO, ccw_retry_interval, CTLFLAG_RWTUN, + &zfs_ccw_retry_interval, 0, + "Configuration cache file write, retry after failure, interval (seconds)"); + +extern uint64_t zfs_max_missing_tvds_cachefile; +SYSCTL_UQUAD(_tunable, OID_AUTO, max_missing_tvds_cachefile, CTLFLAG_RWTUN, + &zfs_max_missing_tvds_cachefile, 0, + "allow importing pools with missing top-level vdevs in cache file"); + +extern uint64_t zfs_max_missing_tvds_scan; +SYSCTL_UQUAD(_tunable, OID_AUTO, max_missing_tvds_scan, CTLFLAG_RWTUN, + &zfs_max_missing_tvds_scan, 0, + "allow importing pools with missing top-level vdevs during scan"); + +/* spa_misc.c */ +extern int zfs_flags; +static int +sysctl_tunable_debug_flags(ZFS_MODULE_PARAM_ARGS) +{ + int err, val; + + val = zfs_flags; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == (user_addr_t) NULL) + return (err); + + /* + * ZFS_DEBUG_MODIFY must be enabled prior to boot so all + * arc buffers in the system have the necessary additional + * checksum data. However, it is safe to disable at any + * time. + */ + if (!(zfs_flags & ZFS_DEBUG_MODIFY)) + val &= ~ZFS_DEBUG_MODIFY; + zfs_flags = val; + + return (0); +} + +SYSCTL_PROC(_tunable, OID_AUTO, debugflags, + CTLTYPE_UINT | CTLFLAG_MPSAFE | CTLFLAG_RWTUN, NULL, 0, + sysctl_tunable_debug_flags, "IU", "Debug flags for ZFS testing."); + +int +param_set_deadman_synctime(ZFS_MODULE_PARAM_ARGS) +{ + unsigned long val; + int err; + + val = zfs_deadman_synctime_ms; + err = sysctl_handle_long(oidp, &val, 0, req); + if (err != 0 || req->newptr == (user_addr_t) NULL) + return (err); + zfs_deadman_synctime_ms = val; + + spa_set_deadman_synctime(MSEC2NSEC(zfs_deadman_synctime_ms)); + + return (0); +} + +int +param_set_deadman_ziotime(ZFS_MODULE_PARAM_ARGS) +{ + unsigned long val; + int err; + + val = zfs_deadman_ziotime_ms; + err = sysctl_handle_long(oidp, &val, 0, req); + if (err != 0 || req->newptr == (user_addr_t) NULL) + return (err); + zfs_deadman_ziotime_ms = val; + + spa_set_deadman_ziotime(MSEC2NSEC(zfs_deadman_synctime_ms)); + + return (0); +} + +int +param_set_deadman_failmode(ZFS_MODULE_PARAM_ARGS) +{ + char buf[16]; + int rc; + + if (req->newptr == (user_addr_t) NULL) + strlcpy(buf, zfs_deadman_failmode, sizeof (buf)); + + rc = sysctl_handle_string(oidp, buf, sizeof (buf), req); + if (rc || req->newptr == (user_addr_t) NULL) + return (rc); + if (strcmp(buf, zfs_deadman_failmode) == 0) + return (0); + if (!strcmp(buf, "wait")) + zfs_deadman_failmode = "wait"; + if (!strcmp(buf, "continue")) + zfs_deadman_failmode = "continue"; + if (!strcmp(buf, "panic")) + zfs_deadman_failmode = "panic"; + return (-param_set_deadman_failmode_common(buf)); +} + + +/* spacemap.c */ +extern int space_map_ibs; +SYSCTL_INT(_tunable, OID_AUTO, space_map_ibs, CTLFLAG_RWTUN, + &space_map_ibs, 0, "Space map indirect block shift"); + + +/* vdev.c */ +int +param_set_min_auto_ashift(ZFS_MODULE_PARAM_ARGS) +{ + uint64_t val; + int err; + + val = zfs_vdev_min_auto_ashift; + err = sysctl_handle_quad(oidp, &val, 0, req); + if (err != 0 || req->newptr == (user_addr_t) NULL) + return (SET_ERROR(err)); + + if (val < ASHIFT_MIN || val > zfs_vdev_max_auto_ashift) + return (SET_ERROR(EINVAL)); + + zfs_vdev_min_auto_ashift = val; + + return (0); +} + +int +param_set_max_auto_ashift(ZFS_MODULE_PARAM_ARGS) +{ + uint64_t val; + int err; + + val = zfs_vdev_max_auto_ashift; + err = sysctl_handle_quad(oidp, &val, 0, req); + if (err != 0 || req->newptr == (user_addr_t) NULL) + return (SET_ERROR(err)); + + if (val > ASHIFT_MAX || val < zfs_vdev_min_auto_ashift) + return (SET_ERROR(EINVAL)); + + zfs_vdev_max_auto_ashift = val; + + return (0); +} + +SYSCTL_PROC(_tunable, OID_AUTO, min_auto_ashift, + CTLTYPE_U64 | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, + &zfs_vdev_min_auto_ashift, sizeof (zfs_vdev_min_auto_ashift), + param_set_min_auto_ashift, "QU", + "Min ashift used when creating new top-level vdev. (LEGACY)"); +SYSCTL_PROC(_tunable, OID_AUTO, max_auto_ashift, + CTLTYPE_U64 | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, + &zfs_vdev_max_auto_ashift, sizeof (zfs_vdev_max_auto_ashift), + param_set_max_auto_ashift, "QU", + "Max ashift used when optimizing for logical -> physical sector size on " + "new top-level vdevs. (LEGACY)"); + +/* + * Since the DTL space map of a vdev is not expected to have a lot of + * entries, we default its block size to 4K. + */ +extern int zfs_vdev_dtl_sm_blksz; +SYSCTL_INT(_tunable, OID_AUTO, dtl_sm_blksz, CTLFLAG_RDTUN, + &zfs_vdev_dtl_sm_blksz, 0, + "Block size for DTL space map. Power of 2 and greater than 4096."); + +/* + * vdev-wide space maps that have lots of entries written to them at + * the end of each transaction can benefit from a higher I/O bandwidth + * (e.g. vdev_obsolete_sm), thus we default their block size to 128K. + */ +extern int zfs_vdev_standard_sm_blksz; +SYSCTL_INT(_tunable, OID_AUTO, standard_sm_blksz, CTLFLAG_RDTUN, + &zfs_vdev_standard_sm_blksz, 0, + "Block size for standard space map. Power of 2 and greater than 4096."); + +extern int vdev_validate_skip; +SYSCTL_INT(_tunable, OID_AUTO, validate_skip, CTLFLAG_RDTUN, + &vdev_validate_skip, 0, + "Enable to bypass vdev_validate()."); + + +/* vdev_cache.c */ + +/* vdev_mirror.c */ +/* + * The load configuration settings below are tuned by default for + * the case where all devices are of the same rotational type. + * + * If there is a mixture of rotating and non-rotating media, setting + * non_rotating_seek_inc to 0 may well provide better results as it + * will direct more reads to the non-rotating vdevs which are more + * likely to have a higher performance. + */ + + +/* vdev_queue.c */ +#define ZFS_VDEV_QUEUE_KNOB_MIN(name) \ +extern uint32_t zfs_vdev_ ## name ## _min_active; \ +SYSCTL_UINT(_tunable_vdev, OID_AUTO, name ## _min_active, CTLFLAG_RWTUN,\ + &zfs_vdev_ ## name ## _min_active, 0, \ + "Initial number of I/O requests of type " #name \ + " active for each device"); + +#define ZFS_VDEV_QUEUE_KNOB_MAX(name) \ +extern uint32_t zfs_vdev_ ## name ## _max_active; \ +SYSCTL_UINT(_tunable_vdev, OID_AUTO, name ## _max_active, CTLFLAG_RWTUN, \ + &zfs_vdev_ ## name ## _max_active, 0, \ + "Maximum number of I/O requests of type " #name \ + " active for each device"); + + +#undef ZFS_VDEV_QUEUE_KNOB + +extern uint32_t zfs_vdev_max_active; +SYSCTL_UINT(_tunable, OID_AUTO, top_maxinflight, CTLFLAG_RWTUN, + &zfs_vdev_max_active, 0, + "The maximum number of I/Os of all types active for each device. (LEGACY)"); + +/*extern uint64_t zfs_multihost_history; +SYSCTL_UQUAD(_tunable, OID_AUTO, multihost_history, CTLFLAG_RWTUN, + &zfs_multihost_history, 0, + "Historical staticists for the last N multihost updates");*/ + +#ifdef notyet +SYSCTL_INT(_tunable_vdev, OID_AUTO, trim_on_init, CTLFLAG_RW, + &vdev_trim_on_init, 0, "Enable/disable full vdev trim on initialisation"); +#endif + +int +param_set_slop_shift(ZFS_MODULE_PARAM_ARGS) +{ + int val; + int err; + + val = *(int *)arg1; + + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == (user_addr_t) NULL) + return (err); + + if (val < 1 || val > 31) + return (EINVAL); + + *(int *)arg1 = val; + + return (0); +} + +int +param_set_multihost_interval(ZFS_MODULE_PARAM_ARGS) +{ + int err; + + err = sysctl_handle_long(oidp, arg1, 0, req); + if (err != 0 || req->newptr == (user_addr_t) NULL) + return (err); + + if (spa_mode_global != SPA_MODE_UNINIT) + mmp_signal_all_threads(); + + return (0); +} + +/* + * macOS specific tunables. + */ + +extern unsigned int zfs_vnop_ignore_negatives; +extern unsigned int zfs_vnop_ignore_positives; +extern unsigned int zfs_vnop_create_negatives; +extern unsigned int zfs_vnop_skip_unlinked_drain; +extern uint64_t zfs_vfs_sync_paranoia; +extern uint64_t zfs_iokit_sync_paranoia; +extern uint64_t vnop_num_vnodes; +extern uint64_t vnop_num_reclaims; +extern int zfs_vnop_force_formd_normalized_output; + +extern int zfs_expire_snapshot; +extern int zfs_admin_snapshot; +extern int zfs_auto_snapshot; + +extern unsigned int zfs_disable_spotlight; +extern unsigned int zfs_disable_trashes; + +/* u64 just to sure it works */ +static uint64_t kstat_spa_version = SPA_VERSION; +static uint64_t kstat_zpl_version = ZPL_VERSION; +ZFS_MODULE_IMPL( , kstat_spa_version, spa_version, ULLONG, ZMOD_RD, + "SPA version"); +ZFS_MODULE_IMPL( , kstat_zpl_version, zpl_version, ULLONG, ZMOD_RD, + "ZPL version"); +ZFS_MODULE_IMPL( , vnop_num_vnodes, active_vnodes, ULLONG, ZMOD_RD, + "Num active vnodes"); +ZFS_MODULE_IMPL( , vnop_num_reclaims, reclaim_nodes, ULLONG, ZMOD_RD, + "Num reclaimed vnodes"); +ZFS_MODULE_IMPL( , zfs_vnop_ignore_negatives, ignore_negatives, + UINT, ZMOD_RW, "Ignore negative cached names"); +ZFS_MODULE_IMPL( , zfs_vnop_ignore_positives, ignore_positives, + UINT, ZMOD_RW, "Ignore positives cached names"); +ZFS_MODULE_IMPL( , zfs_vnop_create_negatives, create_negatives, + UINT, ZMOD_RW, "Create negative cached names on ENOENT"); +ZFS_MODULE_IMPL( , zfs_vnop_force_formd_normalized_output, + force_formd_normalized, UINT, ZMOD_RW, "Force FormD normalize"); +ZFS_MODULE_IMPL( , zfs_vnop_skip_unlinked_drain, skip_unlinked_drain, + UINT, ZMOD_RW, "Do not call unlinked_drain on import"); +ZFS_MODULE_IMPL( , zfs_vfs_sync_paranoia, use_system_sync, + ULLONG, ZMOD_RW, "Extra sync paranoia"); +ZFS_MODULE_IMPL( , zfs_iokit_sync_paranoia, do_iokit_sync, + ULLONG, ZMOD_RW, "IOKit should do more synchronizing"); +ZFS_MODULE_IMPL( , zfs_expire_snapshot, zfs_expire_snapshot, + UINT, ZMOD_RW, "Seconds until auto snapshot unmount"); +ZFS_MODULE_IMPL( , zfs_admin_snapshot, zfs_admin_snapshot, + UINT, ZMOD_RW, "Allow mkdir/rmdir in .zfs/snapshot"); +ZFS_MODULE_IMPL( , zfs_auto_snapshot, zfs_auto_snapshot, + UINT, ZMOD_RW, "Automatically mount snapshots on access"); +ZFS_MODULE_IMPL( , zfs_disable_spotlight, zfs_disable_spotlight, + UINT, ZMOD_RW, "Forcefully stop spotlight"); +ZFS_MODULE_IMPL( , zfs_disable_trashes, zfs_disable_trashes, + UINT, ZMOD_RW, "Forcefully stop .Trashes"); diff --git a/module/os/macos/zfs/trace.c b/module/os/macos/zfs/trace.c new file mode 100644 index 000000000000..0c9990e8547b --- /dev/null +++ b/module/os/macos/zfs/trace.c @@ -0,0 +1,50 @@ +/* + * 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 + */ +/* + * Each Linux tracepoints subsystem must define CREATE_TRACE_POINTS in one + * (and only one) C file, so this dummy file exists for that purpose. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CREATE_TRACE_POINTS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/module/os/macos/zfs/vdev_disk.c b/module/os/macos/zfs/vdev_disk.c new file mode 100644 index 000000000000..c7d457b56661 --- /dev/null +++ b/module/os/macos/zfs/vdev_disk.c @@ -0,0 +1,995 @@ +/* + * 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 + */ +/* + * Based on Apple MacZFS source code + * Copyright (c) 2014,2016 by Jorgen Lundman. All rights reserved. + * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2016 by Delphix. All rights reserved. + * Copyright 2016 Nexenta Systems, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Virtual device vector for disks. + */ + +static taskq_t *vdev_disk_taskq_asyncr; +static taskq_t *vdev_disk_taskq_asyncw; +static taskq_t *vdev_disk_taskq_default; +static taskq_t *vdev_disk_taskq_scrub; +static taskq_t *vdev_disk_taskq_stack; + +_Atomic unsigned int spl_lowest_vdev_disk_stack_remaining = UINT_MAX; + +/* XXX leave extern if declared elsewhere - originally was in zfs_ioctl.c */ +ldi_ident_t zfs_li; + +static void vdev_disk_close(vdev_t *); + +extern unsigned int spl_split_stack_below; + +extern uint64_t zfs_iokit_sync_paranoia; + +typedef struct vdev_disk_ldi_cb { + list_node_t lcb_next; + ldi_callback_id_t lcb_id; +} vdev_disk_ldi_cb_t; + +static void +vdev_disk_alloc(vdev_t *vd) +{ + vdev_disk_t *dvd; + + dvd = vd->vdev_tsd = kmem_zalloc(sizeof (vdev_disk_t), KM_SLEEP); + + /* + * Create the LDI event callback list. + */ + list_create(&dvd->vd_ldi_cbs, sizeof (vdev_disk_ldi_cb_t), + offsetof(vdev_disk_ldi_cb_t, lcb_next)); +} + +static void +vdev_disk_free(vdev_t *vd) +{ + vdev_disk_t *dvd = vd->vdev_tsd; + vdev_disk_ldi_cb_t *lcb; + + if (dvd == NULL) + return; + + /* + * We have already closed the LDI handle. Clean up the LDI event + * callbacks and free vd->vdev_tsd. + */ + while ((lcb = list_head(&dvd->vd_ldi_cbs)) != NULL) { + list_remove(&dvd->vd_ldi_cbs, lcb); + (void) ldi_ev_remove_callbacks(lcb->lcb_id); + kmem_free(lcb, sizeof (vdev_disk_ldi_cb_t)); + } + list_destroy(&dvd->vd_ldi_cbs); + kmem_free(dvd, sizeof (vdev_disk_t)); + vd->vdev_tsd = NULL; +} + +static int +vdev_disk_off_notify(ldi_handle_t lh, ldi_ev_cookie_t ecookie, void *arg, + void *ev_data) +{ + vdev_t *vd = (vdev_t *)arg; + vdev_disk_t *dvd = vd->vdev_tsd; + + /* + * Ignore events other than offline. + */ + if (strcmp(ldi_ev_get_type(ecookie), LDI_EV_OFFLINE) != 0) + return (LDI_EV_SUCCESS); + + /* + * All LDI handles must be closed for the state change to succeed, so + * call on vdev_disk_close() to do this. + * + * We inform vdev_disk_close that it is being called from offline + * notify context so it will defer cleanup of LDI event callbacks and + * freeing of vd->vdev_tsd to the offline finalize or a reopen. + */ + dvd->vd_ldi_offline = B_TRUE; + vdev_disk_close(vd); + + /* + * Now that the device is closed, request that the spa_async_thread + * mark the device as REMOVED and notify FMA of the removal. + */ + zfs_post_remove(vd->vdev_spa, vd); + vd->vdev_remove_wanted = B_TRUE; + spa_async_request(vd->vdev_spa, SPA_ASYNC_REMOVE); + + return (LDI_EV_SUCCESS); +} + +static void +vdev_disk_off_finalize(ldi_handle_t lh, ldi_ev_cookie_t ecookie, + int ldi_result, void *arg, void *ev_data) +{ + vdev_t *vd = (vdev_t *)arg; + + /* + * Ignore events other than offline. + */ + if (strcmp(ldi_ev_get_type(ecookie), LDI_EV_OFFLINE) != 0) + return; + + /* + * We have already closed the LDI handle in notify. + * Clean up the LDI event callbacks and free vd->vdev_tsd. + */ + vdev_disk_free(vd); + /* + * Request that the vdev be reopened if the offline state change was + * unsuccessful. + */ + if (ldi_result != LDI_EV_SUCCESS) { + vd->vdev_probe_wanted = B_TRUE; + spa_async_request(vd->vdev_spa, SPA_ASYNC_PROBE); + } +} + +static ldi_ev_callback_t vdev_disk_off_callb = { + .cb_vers = LDI_EV_CB_VERS, + .cb_notify = vdev_disk_off_notify, + .cb_finalize = vdev_disk_off_finalize +}; + +/* + * We want to be loud in DEBUG kernels when DKIOCGMEDIAINFOEXT fails, or when + * even a fallback to DKIOCGMEDIAINFO fails. + */ +#ifdef DEBUG +#define VDEV_DEBUG(...) cmn_err(CE_NOTE, __VA_ARGS__) +#else +#define VDEV_DEBUG(...) /* Nothing... */ +#endif + +static int +vdev_disk_open(vdev_t *vd, uint64_t *psize, uint64_t *max_psize, + uint64_t *logical_ashift, uint64_t *physical_ashift) +{ + spa_t *spa = vd->vdev_spa; + vdev_disk_t *dvd = vd->vdev_tsd; + ldi_ev_cookie_t ecookie; + vdev_disk_ldi_cb_t *lcb; + union { + struct dk_minfo_ext ude; + struct dk_minfo ud; + } dks; + struct dk_minfo_ext *dkmext = &dks.ude; + struct dk_minfo *dkm = &dks.ud; + int error; + uint64_t __maybe_unused capacity = 0, blksz = 0, pbsize; + int isssd; + + /* + * We must have a pathname, and it must be absolute. + */ + if (vd->vdev_path == NULL || vd->vdev_path[0] != '/') { + vd->vdev_stat.vs_aux = VDEV_AUX_BAD_LABEL; + return (SET_ERROR(EINVAL)); + } + + /* + * Reopen the device if it's not currently open. Otherwise, + * just update the physical size of the device. + */ + if (dvd != NULL) { + if (dvd->vd_ldi_offline && dvd->vd_lh == NULL) { + /* + * If we are opening a device in its offline notify + * context, the LDI handle was just closed. Clean + * up the LDI event callbacks and free vd->vdev_tsd. + */ + vdev_disk_free(vd); + } else { + ASSERT(vd->vdev_reopening); + goto skip_open; + } + } + + /* + * Create vd->vdev_tsd. + */ + vdev_disk_alloc(vd); + dvd = vd->vdev_tsd; + + /* + * When opening a disk device, we want to preserve the user's original + * intent. We always want to open the device by the path the user gave + * us, even if it is one of multiple paths to the same device. But we + * also want to be able to survive disks being removed/recabled. + * Therefore the sequence of opening devices is: + * + * 1. Try opening the device by path. For legacy pools without the + * 'whole_disk' property, attempt to fix the path by appending 's0'. + * + * 2. If the devid of the device matches the stored value, return + * success. + * + * 3. Otherwise, the device may have moved. Try opening the device + * by the devid instead. + */ + + error = EINVAL; /* presume failure */ + + if (vd->vdev_path != NULL) { + + /* + * If we have not yet opened the device, try to open it by the + * specified path. + */ + if (error != 0) { + error = ldi_open_by_name(vd->vdev_path, spa_mode(spa), + kcred, &dvd->vd_lh, zfs_li); + } + + /* + * If we succeeded in opening the device, but 'vdev_wholedisk' + * is not yet set, then this must be a slice. + */ + if (error == 0 && vd->vdev_wholedisk == -1ULL) + vd->vdev_wholedisk = 0; + } + + /* + * If all else fails, then try opening by physical path (if available) + * or the logical path (if we failed due to the devid check). While not + * as reliable as the devid, this will give us something, and the higher + * level vdev validation will prevent us from opening the wrong device. + */ + if (error) { + + /* + * Note that we don't support the legacy auto-wholedisk support + * as above. This hasn't been used in a very long time and we + * don't need to propagate its oddities to this edge condition. + */ + if (error && vd->vdev_path != NULL) + error = ldi_open_by_name(vd->vdev_path, spa_mode(spa), + kcred, &dvd->vd_lh, zfs_li); + } + + if (error) { + vd->vdev_stat.vs_aux = VDEV_AUX_OPEN_FAILED; + vdev_dbgmsg(vd, "vdev_disk_open: failed to open [error=%d]", + error); + return (error); + } + + /* + * Register callbacks for the LDI offline event. + */ + if (ldi_ev_get_cookie(dvd->vd_lh, LDI_EV_OFFLINE, &ecookie) == + LDI_EV_SUCCESS) { + lcb = kmem_zalloc(sizeof (vdev_disk_ldi_cb_t), KM_SLEEP); + list_insert_tail(&dvd->vd_ldi_cbs, lcb); + (void) ldi_ev_register_callbacks(dvd->vd_lh, ecookie, + &vdev_disk_off_callb, (void *) vd, &lcb->lcb_id); + } + + /* We openeded, reset offline */ + /* macOS is unsure why we have to set this when disk comes back */ + vd->vdev_remove_wanted = B_FALSE; + +skip_open: + + /* + * Determine the actual size of the device. + */ + if (ldi_get_size(dvd->vd_lh, psize) != 0) { + vd->vdev_stat.vs_aux = VDEV_AUX_OPEN_FAILED; + vdev_dbgmsg(vd, "vdev_disk_open: failed to get size"); + return (SET_ERROR(EINVAL)); + } + + *max_psize = *psize; + + /* + * Determine the device's minimum transfer size. + * If the ioctl isn't supported, assume DEV_BSIZE. + */ + if ((error = ldi_ioctl(dvd->vd_lh, DKIOCGMEDIAINFOEXT, + (intptr_t)dkmext, FKIOCTL, kcred, NULL)) == 0) { + capacity = dkmext->dki_capacity - 1; + blksz = dkmext->dki_lbsize; + pbsize = dkmext->dki_pbsize; + } else if ((error = ldi_ioctl(dvd->vd_lh, DKIOCGMEDIAINFO, + (intptr_t)dkm, FKIOCTL, kcred, NULL)) == 0) { + VDEV_DEBUG( + "vdev_disk_open(\"%s\"): fallback to DKIOCGMEDIAINFO\n", + vd->vdev_path); + capacity = dkm->dki_capacity - 1; + blksz = dkm->dki_lbsize; + pbsize = blksz; + } else { + VDEV_DEBUG("vdev_disk_open(\"%s\"): " + "both DKIOCGMEDIAINFO{,EXT} calls failed, %d\n", + vd->vdev_path, error); + pbsize = DEV_BSIZE; + } + + *physical_ashift = highbit64(MAX(pbsize, + SPA_MINBLOCKSIZE)) - 1; + + *logical_ashift = highbit64(MAX(pbsize, + SPA_MINBLOCKSIZE)) - 1; + + if (vd->vdev_wholedisk == 1) { + int wce = 1; + + /* + * Since we own the whole disk, try to enable disk write + * caching. We ignore errors because it's OK if we can't do it. + */ + (void) ldi_ioctl(dvd->vd_lh, DKIOCSETWCE, (intptr_t)&wce, + FKIOCTL, kcred, NULL); + } + + /* + * Clear the nowritecache bit, so that on a vdev_reopen() we will + * try again. + */ + vd->vdev_nowritecache = B_FALSE; + + /* Inform the ZIO pipeline that we are non-rotational */ + vd->vdev_nonrot = B_FALSE; + if (ldi_ioctl(dvd->vd_lh, DKIOCISSOLIDSTATE, (intptr_t)&isssd, + FKIOCTL, kcred, NULL) == 0) { + vd->vdev_nonrot = (isssd ? B_TRUE : B_FALSE); + } + + // Assume no TRIM + vd->vdev_has_trim = B_FALSE; + uint32_t features; + if (ldi_ioctl(dvd->vd_lh, DKIOCGETFEATURES, (intptr_t)&features, + FKIOCTL, kcred, NULL) == 0) { + if (features & DK_FEATURE_UNMAP) + vd->vdev_has_trim = B_TRUE; + } + + /* Set when device reports it supports secure TRIM. */ + // No secure trim in Apple yet. + vd->vdev_has_securetrim = B_FALSE; + + return (0); +} + +static void +vdev_disk_close(vdev_t *vd) +{ + vdev_disk_t *dvd = vd->vdev_tsd; + + if (vd->vdev_reopening || dvd == NULL) + return; + + if (dvd->vd_lh != NULL) { + (void) ldi_close(dvd->vd_lh, spa_mode(vd->vdev_spa), kcred); + dvd->vd_lh = NULL; + } + + vd->vdev_delayed_close = B_FALSE; + /* + * If we closed the LDI handle due to an offline notify from LDI, + * don't free vd->vdev_tsd or unregister the callbacks here; + * the offline finalize callback or a reopen will take care of it. + */ + if (dvd->vd_ldi_offline) + return; + + vdev_disk_free(vd); +} + +int +vdev_disk_physio(vdev_t *vd, caddr_t data, + size_t size, uint64_t offset, int flags, boolean_t isdump) +{ + vdev_disk_t *dvd = vd->vdev_tsd; + + /* + * If the vdev is closed, it's likely in the REMOVED or FAULTED state. + * Nothing to be done here but return failure. + */ + if (dvd == NULL || (dvd->vd_ldi_offline && dvd->vd_lh == NULL)) + return (EIO); + + ASSERT(vd->vdev_ops == &vdev_disk_ops); + + return (vdev_disk_ldi_physio(dvd->vd_lh, data, size, offset, flags)); +} + +int +vdev_disk_ldi_physio(ldi_handle_t vd_lh, caddr_t data, + size_t size, uint64_t offset, int flags) +{ + ldi_buf_t *bp; + int error = 0; + + if (vd_lh == NULL) + return (SET_ERROR(EINVAL)); + + ASSERT(flags & B_READ || flags & B_WRITE); + + bp = getrbuf(KM_SLEEP); + bp->b_flags = flags | B_BUSY | B_NOCACHE; + bp->b_bcount = size; + bp->b_un.b_addr = (void *)data; + bp->b_lblkno = lbtodb(offset); + bp->b_bufsize = size; + + error = ldi_strategy(vd_lh, bp); + ASSERT(error == 0); + + if ((error = biowait(bp)) == 0 && bp->b_resid != 0) + error = SET_ERROR(EIO); + freerbuf(bp); + + return (error); +} + +static void +vdev_disk_io_intr(ldi_buf_t *bp) +{ + zio_t *zio = (zio_t *)bp->b_private; + + /* + * The rest of the zio stack only deals with EIO, ECKSUM, and ENXIO. + * Rather than teach the rest of the stack about other error + * possibilities (EFAULT, etc), we normalize the error value here. + */ + zio->io_error = (geterror(bp) != 0 ? EIO : 0); + + if (zio->io_error == 0 && bp->b_resid != 0) + zio->io_error = SET_ERROR(EIO); + + if (zio->io_type == ZIO_TYPE_READ) { + abd_return_buf_copy(zio->io_abd, bp->b_un.b_addr, + zio->io_size); + } else { + abd_return_buf(zio->io_abd, bp->b_un.b_addr, + zio->io_size); + } + + zio_delay_interrupt(zio); +} + +static void +vdev_disk_ioctl_free(zio_t *zio) +{ + kmem_free(zio->io_vsd, sizeof (struct dk_callback)); +} + +static const zio_vsd_ops_t vdev_disk_vsd_ops = { + .vsd_free = vdev_disk_ioctl_free, +}; + +static void +vdev_disk_ioctl_done(void *zio_arg, int error) +{ + zio_t *zio = zio_arg; + + zio->io_error = error; + + zio_interrupt(zio); +} + +static void +vdev_disk_io_strategy(void *arg) +{ + zio_t *zio = (zio_t *)arg; + vdev_t *vd = zio->io_vd; + vdev_disk_t *dvd = vd->vdev_tsd; + ldi_buf_t *bp = NULL; + int flags = 0; + int error = 0; + + ASSERT(zio->io_abd != NULL); + ASSERT(zio->io_size != 0); + + bp = &zio->macos.zm_buf; + bioinit(bp); + + switch (zio->io_type) { + + case ZIO_TYPE_WRITE: + if (zio->io_priority == ZIO_PRIORITY_SYNC_WRITE) { + flags = B_WRITE; + if (zfs_iokit_sync_paranoia != 0) + flags |= B_FUA; + } else { + flags = B_WRITE | B_ASYNC; + } + + bp->b_un.b_addr = + abd_borrow_buf_copy(zio->io_abd, zio->io_size); + break; + + case ZIO_TYPE_READ: + if (zio->io_priority == ZIO_PRIORITY_SYNC_READ) { + flags = B_READ; + } else if (zio->io_priority == ZIO_PRIORITY_SCRUB) { + /* + * Signal using B_THROTTLED_IO. + * This is safe because our path through + * IOKit doesn't really use these flags and + * the flag is valid but depracted in the + * vnode path. + */ + flags = B_READ | B_ASYNC | B_THROTTLED_IO; + } else { + flags = B_READ | B_ASYNC; + } + + bp->b_un.b_addr = + abd_borrow_buf(zio->io_abd, zio->io_size); + break; + + default: + panic("unknown zio->io_type"); + } + + /* Stop OSX from also caching our data */ + flags |= B_NOCACHE | B_PASSIVE | B_BUSY; + + bp->b_flags = flags; + bp->b_bcount = zio->io_size; + bp->b_lblkno = lbtodb(zio->io_offset); + bp->b_bufsize = zio->io_size; + bp->b_iodone = (int (*)(ldi_buf_t *))vdev_disk_io_intr; + bp->b_private = (void *)zio; + + error = ldi_strategy(dvd->vd_lh, bp); + if (error != 0) { + dprintf("%s error from ldi_strategy %d\n", __func__, error); + zio->io_error = SET_ERROR(EIO); + zio_execute(zio); + } +} + +static void +vdev_disk_io_start(zio_t *zio) +{ + vdev_t *vd = zio->io_vd; + vdev_disk_t *dvd = vd->vdev_tsd; + struct dk_callback *dkc; + int error = 0; + + /* + * If the vdev is closed, it's likely in the REMOVED or FAULTED state. + * Nothing to be done here but return failure. + */ + if (dvd == NULL || (dvd->vd_ldi_offline && dvd->vd_lh == NULL)) { + zio->io_error = ENXIO; + zio_interrupt(zio); + return; + } + + switch (zio->io_type) { + case ZIO_TYPE_IOCTL: + + if (!vdev_readable(vd)) { + zio->io_error = SET_ERROR(ENXIO); + zio_interrupt(zio); + return; + } + + switch (zio->io_cmd) { + case DKIOCFLUSHWRITECACHE: + + if (zfs_nocacheflush) + break; + + if (vd->vdev_nowritecache) { + zio->io_error = SET_ERROR(ENOTSUP); + break; + } + + zio->io_vsd = dkc = kmem_alloc(sizeof (*dkc), KM_SLEEP); + zio->io_vsd_ops = &vdev_disk_vsd_ops; + + dkc->dkc_callback = vdev_disk_ioctl_done; + dkc->dkc_flag = FLUSH_VOLATILE; + dkc->dkc_cookie = zio; + + error = ldi_ioctl(dvd->vd_lh, zio->io_cmd, + (uintptr_t)dkc, FKIOCTL, kcred, NULL); + + if (error == 0) { + /* + * The ioctl will be done asychronously, + * and will call vdev_disk_ioctl_done() + * upon completion. + */ + return; + } + + zio->io_error = error; + + break; + + default: + zio->io_error = SET_ERROR(ENOTSUP); + } /* io_cmd */ + + zio_execute(zio); + return; + + case ZIO_TYPE_TRIM: + { + dkioc_free_list_ext_t dfle; + dfle.dfle_start = zio->io_offset; + dfle.dfle_length = zio->io_size; + zio->io_error = ldi_ioctl(dvd->vd_lh, DKIOCFREE, + (uintptr_t)&dfle, FKIOCTL, kcred, NULL); + zio_interrupt(zio); + return; + } + + case ZIO_TYPE_WRITE: + case ZIO_TYPE_READ: + break; + + default: + zio->io_error = SET_ERROR(ENOTSUP); + zio_execute(zio); + return; + } /* io_type */ + + ASSERT(zio->io_type == ZIO_TYPE_READ || zio->io_type == ZIO_TYPE_WRITE); + + zio->io_target_timestamp = zio_handle_io_delay(zio); + + /* + * Check stack remaining, record lowest. If below + * threshold start IO on vdev_disk_taskq_stack. + */ + const vm_offset_t r = OSKernelStackRemaining(); + + if (r < spl_lowest_vdev_disk_stack_remaining) + spl_lowest_vdev_disk_stack_remaining = r; + + if (r < spl_split_stack_below) { + VERIFY3U(taskq_dispatch(vdev_disk_taskq_stack, + vdev_disk_io_strategy, + zio, TQ_SLEEP), !=, 0); + return; + } + + /* + * dispatch async or scrub reads on appropriate taskq, + * dispatch async writes on appropriate taskq, + * do everything else on this thread + */ + if (zio->io_type == ZIO_TYPE_READ) { + if (zio->io_priority == ZIO_PRIORITY_ASYNC_READ) { + VERIFY3U(taskq_dispatch(vdev_disk_taskq_asyncr, + vdev_disk_io_strategy, + zio, TQ_SLEEP), !=, 0); + return; + } else if (zio->io_priority == ZIO_PRIORITY_SCRUB) { + VERIFY3U(taskq_dispatch(vdev_disk_taskq_scrub, + vdev_disk_io_strategy, + zio, TQ_SLEEP), !=, 0); + return; + } + /* fallthrough */ + } else if (zio->io_type == ZIO_TYPE_WRITE) { + if (zio->io_priority == ZIO_PRIORITY_ASYNC_WRITE) { + VERIFY3U(taskq_dispatch(vdev_disk_taskq_asyncw, + vdev_disk_io_strategy, + zio, TQ_SLEEP), !=, 0); + return; + } + /* fallthrough */ + } + + /* default taskq for everything else */ + VERIFY3U(taskq_dispatch(vdev_disk_taskq_default, + vdev_disk_io_strategy, + zio, TQ_SLEEP), !=, 0); +} + +static void +vdev_disk_io_done(zio_t *zio) +{ + vdev_t *vd = zio->io_vd; + + /* + * If the device returned EIO, then attempt a DKIOCSTATE ioctl to see if + * the device has been removed. If this is the case, then we trigger an + * asynchronous removal of the device. Otherwise, probe the device and + * make sure it's still accessible. + */ + if (zio->io_error == EIO && !vd->vdev_remove_wanted) { + vdev_disk_t *dvd = vd->vdev_tsd; + int state = DKIO_NONE; + + if (ldi_ioctl(dvd->vd_lh, DKIOCSTATE, (intptr_t)&state, + FKIOCTL, kcred, NULL) == 0 && state != DKIO_INSERTED) { + /* + * We post the resource as soon as possible, instead of + * when the async removal actually happens, because the + * DE is using this information to discard previous I/O + * errors. + */ + zfs_post_remove(zio->io_spa, vd); + vd->vdev_remove_wanted = B_TRUE; + spa_async_request(zio->io_spa, SPA_ASYNC_REMOVE); + } else if (!vd->vdev_delayed_close) { + vd->vdev_delayed_close = B_TRUE; + } + } +} + +static void +vdev_disk_hold(vdev_t *vd) +{ + ASSERT(spa_config_held(vd->vdev_spa, SCL_STATE, RW_WRITER)); + + /* We must have a pathname, and it must be absolute. */ + if (vd->vdev_path == NULL || vd->vdev_path[0] != '/') + return; + + /* + * Only prefetch path and devid info if the device has + * never been opened. + */ + if (vd->vdev_tsd != NULL) + return; + +} + +static void +vdev_disk_rele(vdev_t *vd) +{ + ASSERT(spa_config_held(vd->vdev_spa, SCL_STATE, RW_WRITER)); + + /* XXX: Implement me as a vnode rele for the device */ +} + +vdev_ops_t vdev_disk_ops = { + .vdev_op_init = NULL, + .vdev_op_fini = NULL, + .vdev_op_open = vdev_disk_open, + .vdev_op_close = vdev_disk_close, + .vdev_op_asize = vdev_default_asize, + .vdev_op_min_asize = vdev_default_min_asize, + .vdev_op_min_alloc = NULL, + .vdev_op_io_start = vdev_disk_io_start, + .vdev_op_io_done = vdev_disk_io_done, + .vdev_op_state_change = NULL, + .vdev_op_need_resilver = NULL, + .vdev_op_hold = vdev_disk_hold, + .vdev_op_rele = vdev_disk_rele, + .vdev_op_remap = NULL, + .vdev_op_xlate = vdev_default_xlate, + .vdev_op_rebuild_asize = NULL, + .vdev_op_metaslab_init = NULL, + .vdev_op_config_generate = NULL, + .vdev_op_nparity = NULL, + .vdev_op_ndisks = NULL, + .vdev_op_type = VDEV_TYPE_DISK, /* name of this vdev type */ + .vdev_op_leaf = B_TRUE /* leaf vdev */ +}; + +void +vdev_disk_init(void) +{ + + /* + * The "cpus" variable generates tq_nthreads, the number of threads in + * each taskq, reported in + * kstat.unix.taskq.vdev_disk_taskq_{name}.threads. + * + * Fundamentally, this can be 1. It works for all of these queues. + * For synchronous I/O that is close to what we want, as a best-effort + * towards serving our callers in FIFO order. ZFS does a good job of + * ordering calls to vdev_disk_io_start(). + * + * However, our use of IOKit is fundamentally asynchronous. Therefore + * we cannot guarantee that a set of taskq jobs A, B, C get callbacks + * (vdev_disk_io_done) in the same order. Consequently we are + * interested in kstat...{name}.maxtasks, which represents the + * greatest observed difference between the number of tasks in the + * taskq and the number of completed tasks. With more threads, that + * number drops. + * + * What this means is that as "cpus" is increased, IOKit is likely to + * have more inflight zfs-generated IOs to queue when there is system + * contention that not directly visible to our kext. There is a + * trade-off between the benefits of lower-level queue management + * (which varies in intelligence) and CPU. + * + * With heavy IO loads this trade-off can be seen in Activity Monitor + * and spindump: higher CPU (spindump exposes the cpu time of each + * of these taskq threads), and the relative smoothness of disk I/O. + * + * These taskq_create priorities are high compared to userland -- and + * IOKit activity and other system dynamics may temporarily boost the + * dispatched IO to even higher priority -- during heavy IO we risk + * starving userland of CPU with many busy high-priority threads + * generating CPU use in IOKit and below. + * + * On all systems, therefore, we cap threads below max_cpus, and on + * Apple Silicon we deflate further. + */ + + const int cpus = MAX(1, max_ncpus - num_ecores - 2); + + /* + * Keep vdev_disk_taskq_stack as in-order as we can, and use + * defclsyspri, since this can be any type of ZIO. This taskq + * is relatively rarely used, and is mainly to track when stacks + * are too large. + */ + + vdev_disk_taskq_stack = taskq_create("vdev_disk_taskq_stack", + 1, defclsyspri, 1, + INT_MAX, TASKQ_PREPOPULATE); + + VERIFY(vdev_disk_taskq_stack); + + /* + * Slightly reduce thread priority of async write threads relative to + * aysnc reads; these come in bursts as TXGs are closed out. + */ + + vdev_disk_taskq_asyncw = taskq_create("vdev_disk_taskq_asyncw", + cpus, defclsyspri - 5, 1, + INT_MAX, TASKQ_PREPOPULATE); + + VERIFY(vdev_disk_taskq_asyncw); + + vdev_disk_taskq_asyncr = taskq_create("vdev_disk_taskq_asyncr", + cpus, defclsyspri - 4, 1, + INT_MAX, TASKQ_PREPOPULATE); + + VERIFY(vdev_disk_taskq_asyncr); + + /* + * Reads (including prefetches) for scans ("scrubs", however these can + * also be issued during resilvers) are asynchronous and + * low-priority. They tend to generate the highest amount of work in + * IOKit and below. There is always significant (and with some + * checksums, serious) CPU activity done when the IO completes. New + * style scrubbing and resilvering do heroic efforts to sequentialize + * these IOs. Therefore scrub_cpus should be the lowest number that + * does not wreck scrub/resilver throughput. Empirically this appears + * to be 1. Increasing beyond that starves userland of CPU for no + * good purpose. + */ + + const int scrub_cpus = 1; + + vdev_disk_taskq_scrub = taskq_create("vdev_disk_taskq_scrub", + scrub_cpus, DSL_SCAN_ISS_SYSPRI, 1, + INT_MAX, TASKQ_PREPOPULATE); + + VERIFY(vdev_disk_taskq_scrub); + + vdev_disk_taskq_default = taskq_create("vdev_disk_taskq_default", + cpus, defclsyspri, 1, + INT_MAX, TASKQ_PREPOPULATE); + + VERIFY(vdev_disk_taskq_default); +} + +void +vdev_disk_fini(void) +{ + taskq_destroy(vdev_disk_taskq_default); + taskq_destroy(vdev_disk_taskq_scrub); + taskq_destroy(vdev_disk_taskq_asyncr); + taskq_destroy(vdev_disk_taskq_asyncw); + taskq_destroy(vdev_disk_taskq_stack); +} + +/* + * Given the root disk device devid or pathname, read the label from + * the device, and construct a configuration nvlist. + */ +int +vdev_disk_read_rootlabel(char *devpath, char *devid, nvlist_t **config) +{ + ldi_handle_t vd_lh; + vdev_label_t *label; + uint64_t s, size; + int l; + int error = -1; + + /* + * Read the device label and build the nvlist. + */ + + /* Apple: Error will be -1 at this point, allowing open_by_name */ + error = -1; + vd_lh = 0; /* Dismiss compiler warning */ + + if (error && (error = ldi_open_by_name(devpath, FREAD, kcred, &vd_lh, + zfs_li))) + return (error); + + if (ldi_get_size(vd_lh, &s)) { + (void) ldi_close(vd_lh, FREAD, kcred); + return (SET_ERROR(EIO)); + } + + size = P2ALIGN_TYPED(s, sizeof (vdev_label_t), uint64_t); + label = kmem_alloc(sizeof (vdev_label_t), KM_SLEEP); + + *config = NULL; + for (l = 0; l < VDEV_LABELS; l++) { + uint64_t offset, state, txg = 0; + + /* read vdev label */ + offset = vdev_label_offset(size, l, 0); + if (vdev_disk_ldi_physio(vd_lh, (caddr_t)label, + VDEV_SKIP_SIZE + VDEV_PHYS_SIZE, offset, B_READ) != 0) + continue; + + if (nvlist_unpack(label->vl_vdev_phys.vp_nvlist, + sizeof (label->vl_vdev_phys.vp_nvlist), config, 0) != 0) { + *config = NULL; + continue; + } + + if (nvlist_lookup_uint64(*config, ZPOOL_CONFIG_POOL_STATE, + &state) != 0 || state >= POOL_STATE_DESTROYED) { + nvlist_free(*config); + *config = NULL; + continue; + } + + if (nvlist_lookup_uint64(*config, ZPOOL_CONFIG_POOL_TXG, + &txg) != 0 || txg == 0) { + nvlist_free(*config); + *config = NULL; + continue; + } + + break; + } + + kmem_free(label, sizeof (vdev_label_t)); + (void) ldi_close(vd_lh, FREAD, kcred); + if (*config == NULL) + error = SET_ERROR(EIDRM); + + return (error); +} diff --git a/module/os/macos/zfs/vdev_file.c b/module/os/macos/zfs/vdev_file.c new file mode 100644 index 000000000000..24a32b1eb547 --- /dev/null +++ b/module/os/macos/zfs/vdev_file.c @@ -0,0 +1,462 @@ +/* + * 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, 2016 by Delphix. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Virtual device vector for files. + */ + +static taskq_t *vdev_file_taskq; + +/* + * By default, the logical/physical ashift for file vdevs is set to + * SPA_MINBLOCKSHIFT (9). This allows all file vdevs to use 512B (1 << 9) + * blocksizes. Users may opt to change one or both of these for testing + * or performance reasons. Care should be taken as these values will + * impact the vdev_ashift setting which can only be set at vdev creation + * time. + */ +static unsigned long vdev_file_logical_ashift = SPA_MINBLOCKSHIFT; +static unsigned long vdev_file_physical_ashift = SPA_MINBLOCKSHIFT; + +static void +vdev_file_hold(vdev_t *vd) +{ + ASSERT(vd->vdev_path != NULL); +} + +static void +vdev_file_rele(vdev_t *vd) +{ + ASSERT(vd->vdev_path != NULL); +} + +static mode_t +vdev_file_open_mode(spa_mode_t spa_mode) +{ + mode_t mode = 0; + + if ((spa_mode & SPA_MODE_READ) && (spa_mode & SPA_MODE_WRITE)) { + mode = O_RDWR; + } else if (spa_mode & SPA_MODE_READ) { + mode = O_RDONLY; + } else if (spa_mode & SPA_MODE_WRITE) { + mode = O_WRONLY; + } + + return (mode | O_LARGEFILE); +} + +static int +vdev_file_open(vdev_t *vd, uint64_t *psize, uint64_t *max_psize, + uint64_t *logical_ashift, uint64_t *physical_ashift) +{ + vdev_file_t *vf; + zfs_file_t *fp; + zfs_file_attr_t zfa; + int error = 0; + + dprintf("vdev_file_open %p\n", vd->vdev_tsd); + + /* + * Rotational optimizations only make sense on block devices. + */ + vd->vdev_nonrot = B_TRUE; + + /* + * Allow TRIM on file based vdevs. This may not always be supported, + * since it depends on your kernel version and underlying filesystem + * type but it is always safe to attempt. + */ + vd->vdev_has_trim = B_TRUE; + + /* + * Disable secure TRIM on file based vdevs. There is no way to + * request this behavior from the underlying filesystem. + */ + vd->vdev_has_securetrim = B_FALSE; + + /* + * We must have a pathname, and it must be absolute. + */ + if (vd->vdev_path == NULL || vd->vdev_path[0] != '/') { + vd->vdev_stat.vs_aux = VDEV_AUX_BAD_LABEL; + return (SET_ERROR(EINVAL)); + } + + /* + * Reopen the device if it's not currently open. Otherwise, + * just update the physical size of the device. + */ +#ifdef _KERNEL + if (vd->vdev_tsd != NULL) { + vf = vd->vdev_tsd; + if (vf->vf_file != NULL) { + ASSERT(vd->vdev_reopening); + vf = vd->vdev_tsd; + goto skip_open; + } + } +#endif + + if (vd->vdev_tsd == NULL) + vf = vd->vdev_tsd = kmem_zalloc(sizeof (vdev_file_t), KM_SLEEP); + + ASSERT(vd->vdev_path != NULL && vd->vdev_path[0] == '/'); + + error = zfs_file_open(vd->vdev_path, + vdev_file_open_mode(spa_mode(vd->vdev_spa)), 0, &fp); + + if (error) { + vd->vdev_stat.vs_aux = VDEV_AUX_OPEN_FAILED; + return (error); + } + + vf->vf_file = fp; + + /* + * Make sure it's a regular file. + */ + if (zfs_file_getattr(fp, &zfa)) { + return (SET_ERROR(ENODEV)); + } + +skip_open: + /* + * Determine the physical size of the file. + */ + error = zfs_file_getattr(vf->vf_file, &zfa); + + if (error) { + vd->vdev_stat.vs_aux = VDEV_AUX_OPEN_FAILED; + return (error); + } + + *max_psize = *psize = zfa.zfa_size; + *logical_ashift = vdev_file_logical_ashift; + *physical_ashift = vdev_file_physical_ashift; + + return (0); +} + +static void +vdev_file_close(vdev_t *vd) +{ + vdev_file_t *vf = vd->vdev_tsd; + + if (vd->vdev_reopening || vf == NULL) + return; + + if (vf->vf_file != NULL) { + zfs_file_close(vf->vf_file); + } + + vd->vdev_delayed_close = B_FALSE; + kmem_free(vf, sizeof (vdev_file_t)); + vd->vdev_tsd = NULL; +} + +static void +vdev_file_io_strategy(void *arg) +{ + zio_t *zio = (zio_t *)arg; + vdev_t *vd = zio->io_vd; + vdev_file_t *vf = vd->vdev_tsd; + ssize_t resid; + loff_t off; + void *data; + ssize_t size; + int err; + + off = zio->io_offset; + size = zio->io_size; + resid = 0; + + if (zio->io_type == ZIO_TYPE_READ) { + data = + abd_borrow_buf(zio->io_abd, size); + err = zfs_file_pread(vf->vf_file, data, size, off, &resid); + abd_return_buf_copy(zio->io_abd, data, size); + } else { + data = + abd_borrow_buf_copy(zio->io_abd, size); + err = zfs_file_pwrite(vf->vf_file, data, size, off, &resid); + abd_return_buf(zio->io_abd, data, size); + } + + zio->io_error = (err != 0 ? EIO : 0); + + if (zio->io_error == 0 && resid != 0) + zio->io_error = SET_ERROR(ENOSPC); + + zio_delay_interrupt(zio); +} + +static void +vdev_file_io_start(zio_t *zio) +{ + vdev_t *vd = zio->io_vd; + vdev_file_t *vf = vd->vdev_tsd; + +#ifdef CLOSE_ON_UNMOUNT + if (vf != NULL && vf->vf_file == NULL) { + zfs_file_t *fp = NULL; + int error; + + error = zfs_file_open(vd->vdev_path, + vdev_file_open_mode(spa_mode(vd->vdev_spa)), 0, &fp); + + if (error == 0) { + atomic_cas_ptr(&vf->vf_file, NULL, fp); + if (vf->vf_file != fp) + zfs_file_close(fp); /* We lost */ + } + + if (vf->vf_file == NULL) { + zio->io_error = SET_ERROR(EIO); + zio_delay_interrupt(zio); + return; + } + } +#endif + + if (zio->io_type == ZIO_TYPE_IOCTL) { + + if (!vdev_readable(vd)) { + zio->io_error = SET_ERROR(ENXIO); + zio_interrupt(zio); + return; + } + + switch (zio->io_cmd) { + case DKIOCFLUSHWRITECACHE: + zio->io_error = zfs_file_fsync(vf->vf_file, + O_SYNC|O_DSYNC); + break; + default: + zio->io_error = SET_ERROR(ENOTSUP); + } + + zio_execute(zio); + return; + } else if (zio->io_type == ZIO_TYPE_TRIM) { + int mode = 0; + + ASSERT3U(zio->io_size, !=, 0); + + /* XXX FreeBSD has no fallocate routine in file ops */ + zio->io_error = zfs_file_fallocate(vf->vf_file, + mode, zio->io_offset, zio->io_size); + zio_execute(zio); + return; + } + + ASSERT(zio->io_type == ZIO_TYPE_READ || zio->io_type == ZIO_TYPE_WRITE); + zio->io_target_timestamp = zio_handle_io_delay(zio); + + VERIFY3U(taskq_dispatch(vdev_file_taskq, vdev_file_io_strategy, zio, + TQ_SLEEP), !=, 0); +} + + +static void +vdev_file_io_done(zio_t *zio) +{ +} + +vdev_ops_t vdev_file_ops = { + .vdev_op_init = NULL, + .vdev_op_fini = NULL, + .vdev_op_open = vdev_file_open, + .vdev_op_close = vdev_file_close, + .vdev_op_asize = vdev_default_asize, + .vdev_op_min_asize = vdev_default_min_asize, + .vdev_op_min_alloc = NULL, + .vdev_op_io_start = vdev_file_io_start, + .vdev_op_io_done = vdev_file_io_done, + .vdev_op_state_change = NULL, + .vdev_op_need_resilver = NULL, + .vdev_op_hold = vdev_file_hold, + .vdev_op_rele = vdev_file_rele, + .vdev_op_remap = NULL, + .vdev_op_xlate = vdev_default_xlate, + .vdev_op_rebuild_asize = NULL, + .vdev_op_metaslab_init = NULL, + .vdev_op_config_generate = NULL, + .vdev_op_nparity = NULL, + .vdev_op_ndisks = NULL, + .vdev_op_type = VDEV_TYPE_FILE, /* name of this vdev type */ + .vdev_op_leaf = B_TRUE /* leaf vdev */ +}; + +extern void vdev_disk_init(void); +extern void vdev_disk_fini(void); + +void +vdev_file_init(void) +{ + vdev_file_taskq = taskq_create("vdev_file_taskq", 100, minclsyspri, + max_ncpus, INT_MAX, TASKQ_PREPOPULATE | TASKQ_THREADS_CPU_PCT); + + VERIFY(vdev_file_taskq); + + vdev_disk_init(); +} + +void +vdev_file_fini(void) +{ + vdev_disk_fini(); + + taskq_destroy(vdev_file_taskq); +} + +#ifdef CLOSE_ON_UNMOUNT +/* + * This sucks! + * + * So vdev_file opens the file on an underlying file-system, which + * means calling vnode_open("diskimage"), this will then hold a + * v_usecount during the time that the pool is open (imported). + * When it comes time to "reboot" the system, it only issues + * unmount to all mounted file-systems. This is not an export. + * The pool still has v_usecount on "diskimage", so vflush() of + * that file-system can not complete, and we hang waiting forever + * for the usecount to go down (.. and all file-systems to be unmounted) + * + * Sadly Apple has left us no way to know when the system is in + * reboot/shutdown. + * + * So now, any unmount request for a dataset, it will run through + * all the vdevs (in that pool), and if the vdev is one of "vdev_file", + * we will close the underlying opened file. We do this "dirty". + * + * The next call to vdev_file_io_start() will notice the file is + * not open, and reopen it. + * + * In theory, vdev_file pools are not common, and unmounting is not + * common, so the penalty might not be too bad. Compared to reboots + * that hang. + * + * vdev_disk gets away with it, as all open disks are in /dev/ mount, + * which is not unmounted at reboot. (virtual filesystem). + * + * Linux and FreeBSD appear to get away with it by using + * file-descriptor equivalent opens, which is flushed at the start of + * reboots. Apple will not let us open files this way from within the + * kernel. + * + */ +static uint32_t vdev_file_close_on_unmount = 1; +ZFS_MODULE_PARAM(zfs_vdev_file, vdev_file_, close_on_unmount, UINT, ZMOD_RW, + "close vdevs on unmount to avoid reboot hang"); + +static void +vdev_file_close_all_impl(vdev_t *vd) +{ + if (vd->vdev_ops->vdev_op_leaf) { + if (vd->vdev_ops == &vdev_file_ops) { + vdev_file_t *vf; + vf = vd->vdev_tsd; + if (vf != NULL) { + zfs_file_t *fp = vf->vf_file; + if (fp != NULL) { + atomic_cas_ptr(&vf->vf_file, fp, NULL); + if (vf->vf_file == NULL) { + zfs_file_close(fp); + printf("closed '%s' " + "(close_on_unmount)\n", + vd->vdev_path); + } + } + } + } + return; + } + + for (int c = 0; c < vd->vdev_children; c++) + vdev_file_close_all_impl(vd->vdev_child[c]); +} + +void +vdev_file_close_all(objset_t *os) +{ + spa_t *spa = dmu_objset_spa(os); + if (!vdev_file_close_on_unmount) + return; + spa_config_enter(spa, SCL_VDEV, FTAG, RW_READER); + vdev_file_close_all_impl(spa->spa_root_vdev); + spa_config_exit(spa, SCL_VDEV, FTAG); +} +#endif + +/* + * From userland we access disks just like files. + */ +#ifndef _KERNEL + +vdev_ops_t vdev_disk_ops = { + .vdev_op_init = NULL, + .vdev_op_fini = NULL, + .vdev_op_open = vdev_file_open, + .vdev_op_close = vdev_file_close, + .vdev_op_asize = vdev_default_asize, + .vdev_op_min_asize = vdev_default_min_asize, + .vdev_op_min_alloc = NULL, + .vdev_op_io_start = vdev_file_io_start, + .vdev_op_io_done = vdev_file_io_done, + .vdev_op_state_change = NULL, + .vdev_op_need_resilver = NULL, + .vdev_op_hold = vdev_file_hold, + .vdev_op_rele = vdev_file_rele, + .vdev_op_remap = NULL, + .vdev_op_xlate = vdev_default_xlate, + .vdev_op_rebuild_asize = NULL, + .vdev_op_metaslab_init = NULL, + .vdev_op_config_generate = NULL, + .vdev_op_nparity = NULL, + .vdev_op_ndisks = NULL, + .vdev_op_type = VDEV_TYPE_DISK, /* name of this vdev type */ + .vdev_op_leaf = B_TRUE /* leaf vdev */ +}; + +#endif + +ZFS_MODULE_PARAM(zfs_vdev_file, vdev_file_, logical_ashift, ULONG, ZMOD_RW, + "Logical ashift for file-based devices"); +ZFS_MODULE_PARAM(zfs_vdev_file, vdev_file_, physical_ashift, ULONG, ZMOD_RW, + "Physical ashift for file-based devices"); diff --git a/module/os/macos/zfs/zfs_acl.c b/module/os/macos/zfs/zfs_acl.c new file mode 100644 index 000000000000..f1d441c69d5c --- /dev/null +++ b/module/os/macos/zfs/zfs_acl.c @@ -0,0 +1,2784 @@ +/* + * 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 by Delphix. All rights reserved. + * Copyright 2017 Nexenta Systems, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef enum vtype vtype_t; + +#define ALLOW ACE_ACCESS_ALLOWED_ACE_TYPE +#define DENY ACE_ACCESS_DENIED_ACE_TYPE +#define MAX_ACE_TYPE ACE_SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE +#define MIN_ACE_TYPE ALLOW + +#define OWNING_GROUP (ACE_GROUP|ACE_IDENTIFIER_GROUP) +#define EVERYONE_ALLOW_MASK (ACE_READ_ACL|ACE_READ_ATTRIBUTES | \ + ACE_READ_NAMED_ATTRS|ACE_SYNCHRONIZE) +#define EVERYONE_DENY_MASK (ACE_WRITE_ACL|ACE_WRITE_OWNER | \ + ACE_WRITE_ATTRIBUTES|ACE_WRITE_NAMED_ATTRS) +#define OWNER_ALLOW_MASK (ACE_WRITE_ACL | ACE_WRITE_OWNER | \ + ACE_WRITE_ATTRIBUTES|ACE_WRITE_NAMED_ATTRS) + +#define ZFS_CHECKED_MASKS (ACE_READ_ACL|ACE_READ_ATTRIBUTES|ACE_READ_DATA| \ + ACE_READ_NAMED_ATTRS|ACE_WRITE_DATA|ACE_WRITE_ATTRIBUTES| \ + ACE_WRITE_NAMED_ATTRS|ACE_APPEND_DATA|ACE_EXECUTE|ACE_WRITE_OWNER| \ + ACE_WRITE_ACL|ACE_DELETE|ACE_DELETE_CHILD|ACE_SYNCHRONIZE) + +#define WRITE_MASK_DATA (ACE_WRITE_DATA|ACE_APPEND_DATA|ACE_WRITE_NAMED_ATTRS) +#define WRITE_MASK_ATTRS (ACE_WRITE_ACL|ACE_WRITE_OWNER|ACE_WRITE_ATTRIBUTES| \ + ACE_DELETE|ACE_DELETE_CHILD) +#define WRITE_MASK (WRITE_MASK_DATA|WRITE_MASK_ATTRS) + +#define OGE_CLEAR (ACE_READ_DATA|ACE_LIST_DIRECTORY|ACE_WRITE_DATA| \ + ACE_ADD_FILE|ACE_APPEND_DATA|ACE_ADD_SUBDIRECTORY|ACE_EXECUTE) + +#define OKAY_MASK_BITS (ACE_READ_DATA|ACE_LIST_DIRECTORY|ACE_WRITE_DATA| \ + ACE_ADD_FILE|ACE_APPEND_DATA|ACE_ADD_SUBDIRECTORY|ACE_EXECUTE) + +#define ALL_INHERIT (ACE_FILE_INHERIT_ACE|ACE_DIRECTORY_INHERIT_ACE | \ + ACE_NO_PROPAGATE_INHERIT_ACE|ACE_INHERIT_ONLY_ACE|ACE_INHERITED_ACE) + +#define RESTRICTED_CLEAR (ACE_WRITE_ACL|ACE_WRITE_OWNER) + +#define V4_ACL_WIDE_FLAGS (ZFS_ACL_AUTO_INHERIT|ZFS_ACL_DEFAULTED|\ + ZFS_ACL_PROTECTED) + +#define ZFS_ACL_WIDE_FLAGS (V4_ACL_WIDE_FLAGS|ZFS_ACL_TRIVIAL|ZFS_INHERIT_ACE|\ + ZFS_ACL_OBJ_ACE) + +#define ALL_MODE_EXECS (S_IXUSR | S_IXGRP | S_IXOTH) + +static uint16_t +zfs_ace_v0_get_type(void *acep) +{ + return (((zfs_oldace_t *)acep)->z_type); +} + +static uint16_t +zfs_ace_v0_get_flags(void *acep) +{ + return (((zfs_oldace_t *)acep)->z_flags); +} + +static uint32_t +zfs_ace_v0_get_mask(void *acep) +{ + return (((zfs_oldace_t *)acep)->z_access_mask); +} + +static uint64_t +zfs_ace_v0_get_who(void *acep) +{ + return (((zfs_oldace_t *)acep)->z_fuid); +} + +static void +zfs_ace_v0_set_type(void *acep, uint16_t type) +{ + ((zfs_oldace_t *)acep)->z_type = type; +} + +static void +zfs_ace_v0_set_flags(void *acep, uint16_t flags) +{ + ((zfs_oldace_t *)acep)->z_flags = flags; +} + +static void +zfs_ace_v0_set_mask(void *acep, uint32_t mask) +{ + ((zfs_oldace_t *)acep)->z_access_mask = mask; +} + +static void +zfs_ace_v0_set_who(void *acep, uint64_t who) +{ + ((zfs_oldace_t *)acep)->z_fuid = who; +} + +static size_t +zfs_ace_v0_size(void *acep) +{ + return (sizeof (zfs_oldace_t)); +} + +static size_t +zfs_ace_v0_abstract_size(void) +{ + return (sizeof (zfs_oldace_t)); +} + +static int +zfs_ace_v0_mask_off(void) +{ + return (offsetof(zfs_oldace_t, z_access_mask)); +} + +static int +zfs_ace_v0_data(void *acep, void **datap) +{ + *datap = NULL; + return (0); +} + +static acl_ops_t zfs_acl_v0_ops = { + zfs_ace_v0_get_mask, + zfs_ace_v0_set_mask, + zfs_ace_v0_get_flags, + zfs_ace_v0_set_flags, + zfs_ace_v0_get_type, + zfs_ace_v0_set_type, + zfs_ace_v0_get_who, + zfs_ace_v0_set_who, + zfs_ace_v0_size, + zfs_ace_v0_abstract_size, + zfs_ace_v0_mask_off, + zfs_ace_v0_data +}; + +static uint16_t +zfs_ace_fuid_get_type(void *acep) +{ + return (((zfs_ace_hdr_t *)acep)->z_type); +} + +static uint16_t +zfs_ace_fuid_get_flags(void *acep) +{ + return (((zfs_ace_hdr_t *)acep)->z_flags); +} + +static uint32_t +zfs_ace_fuid_get_mask(void *acep) +{ + return (((zfs_ace_hdr_t *)acep)->z_access_mask); +} + +static uint64_t +zfs_ace_fuid_get_who(void *args) +{ + uint16_t entry_type; + zfs_ace_t *acep = args; + + entry_type = acep->z_hdr.z_flags & ACE_TYPE_FLAGS; + + if (entry_type == ACE_OWNER || entry_type == OWNING_GROUP || + entry_type == ACE_EVERYONE) + return (-1); + return (((zfs_ace_t *)acep)->z_fuid); +} + +static void +zfs_ace_fuid_set_type(void *acep, uint16_t type) +{ + ((zfs_ace_hdr_t *)acep)->z_type = type; +} + +static void +zfs_ace_fuid_set_flags(void *acep, uint16_t flags) +{ + ((zfs_ace_hdr_t *)acep)->z_flags = flags; +} + +static void +zfs_ace_fuid_set_mask(void *acep, uint32_t mask) +{ + ((zfs_ace_hdr_t *)acep)->z_access_mask = mask; +} + +static void +zfs_ace_fuid_set_who(void *arg, uint64_t who) +{ + zfs_ace_t *acep = arg; + + uint16_t entry_type = acep->z_hdr.z_flags & ACE_TYPE_FLAGS; + + if (entry_type == ACE_OWNER || entry_type == OWNING_GROUP || + entry_type == ACE_EVERYONE) + return; + acep->z_fuid = who; +} + +static size_t +zfs_ace_fuid_size(void *acep) +{ + zfs_ace_hdr_t *zacep = acep; + uint16_t entry_type; + + switch (zacep->z_type) { + case ACE_ACCESS_ALLOWED_OBJECT_ACE_TYPE: + case ACE_ACCESS_DENIED_OBJECT_ACE_TYPE: + case ACE_SYSTEM_AUDIT_OBJECT_ACE_TYPE: + case ACE_SYSTEM_ALARM_OBJECT_ACE_TYPE: + return (sizeof (zfs_object_ace_t)); + case ALLOW: + case DENY: + entry_type = + (((zfs_ace_hdr_t *)acep)->z_flags & ACE_TYPE_FLAGS); + if (entry_type == ACE_OWNER || + entry_type == OWNING_GROUP || + entry_type == ACE_EVERYONE) + return (sizeof (zfs_ace_hdr_t)); + /*FALLTHROUGH*/ + default: + return (sizeof (zfs_ace_t)); + } +} + +static size_t +zfs_ace_fuid_abstract_size(void) +{ + return (sizeof (zfs_ace_hdr_t)); +} + +static int +zfs_ace_fuid_mask_off(void) +{ + return (offsetof(zfs_ace_hdr_t, z_access_mask)); +} + +static int +zfs_ace_fuid_data(void *acep, void **datap) +{ + zfs_ace_t *zacep = acep; + zfs_object_ace_t *zobjp; + + switch (zacep->z_hdr.z_type) { + case ACE_ACCESS_ALLOWED_OBJECT_ACE_TYPE: + case ACE_ACCESS_DENIED_OBJECT_ACE_TYPE: + case ACE_SYSTEM_AUDIT_OBJECT_ACE_TYPE: + case ACE_SYSTEM_ALARM_OBJECT_ACE_TYPE: + zobjp = acep; + *datap = (caddr_t)zobjp + sizeof (zfs_ace_t); + return (sizeof (zfs_object_ace_t) - sizeof (zfs_ace_t)); + default: + *datap = NULL; + return (0); + } +} + +static acl_ops_t zfs_acl_fuid_ops = { + zfs_ace_fuid_get_mask, + zfs_ace_fuid_set_mask, + zfs_ace_fuid_get_flags, + zfs_ace_fuid_set_flags, + zfs_ace_fuid_get_type, + zfs_ace_fuid_set_type, + zfs_ace_fuid_get_who, + zfs_ace_fuid_set_who, + zfs_ace_fuid_size, + zfs_ace_fuid_abstract_size, + zfs_ace_fuid_mask_off, + zfs_ace_fuid_data +}; + +/* + * The following three functions are provided for compatibility with + * older ZPL version in order to determine if the file use to have + * an external ACL and what version of ACL previously existed on the + * file. Would really be nice to not need this, sigh. + */ +uint64_t +zfs_external_acl(znode_t *zp) +{ + zfs_acl_phys_t acl_phys; + int error; + + if (zp->z_is_sa) + return (0); + + /* + * Need to deal with a potential + * race where zfs_sa_upgrade could cause + * z_isa_sa to change. + * + * If the lookup fails then the state of z_is_sa should have + * changed. + */ + + if ((error = sa_lookup(zp->z_sa_hdl, SA_ZPL_ZNODE_ACL(zp->z_zfsvfs), + &acl_phys, sizeof (acl_phys))) == 0) + return (acl_phys.z_acl_extern_obj); + else { + /* + * after upgrade the SA_ZPL_ZNODE_ACL should have been + * removed + */ + VERIFY(zp->z_is_sa && error == ENOENT); + return (0); + } +} + +/* + * Determine size of ACL in bytes + * + * This is more complicated than it should be since we have to deal + * with old external ACLs. + */ +static int +zfs_acl_znode_info(znode_t *zp, int *aclsize, int *aclcount, + zfs_acl_phys_t *aclphys) +{ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + uint64_t acl_count; + int size; + int error; + + ASSERT(MUTEX_HELD(&zp->z_acl_lock)); + if (zp->z_is_sa) { + if ((error = sa_size(zp->z_sa_hdl, SA_ZPL_DACL_ACES(zfsvfs), + &size)) != 0) + return (error); + *aclsize = size; + if ((error = sa_lookup(zp->z_sa_hdl, SA_ZPL_DACL_COUNT(zfsvfs), + &acl_count, sizeof (acl_count))) != 0) + return (error); + *aclcount = acl_count; + } else { + if ((error = sa_lookup(zp->z_sa_hdl, SA_ZPL_ZNODE_ACL(zfsvfs), + aclphys, sizeof (*aclphys))) != 0) + return (error); + + if (aclphys->z_acl_version == ZFS_ACL_VERSION_INITIAL) { + *aclsize = ZFS_ACL_SIZE(aclphys->z_acl_size); + *aclcount = aclphys->z_acl_size; + } else { + *aclsize = aclphys->z_acl_size; + *aclcount = aclphys->z_acl_count; + } + } + return (0); +} + +int +zfs_znode_acl_version(znode_t *zp) +{ + zfs_acl_phys_t acl_phys; + + if (zp->z_is_sa) + return (ZFS_ACL_VERSION_FUID); + else { + int error; + + /* + * Need to deal with a potential + * race where zfs_sa_upgrade could cause + * z_isa_sa to change. + * + * If the lookup fails then the state of z_is_sa should have + * changed. + */ + if ((error = sa_lookup(zp->z_sa_hdl, + SA_ZPL_ZNODE_ACL(zp->z_zfsvfs), + &acl_phys, sizeof (acl_phys))) == 0) + return (acl_phys.z_acl_version); + else { + /* + * After upgrade SA_ZPL_ZNODE_ACL should have + * been removed. + */ + VERIFY(zp->z_is_sa && error == ENOENT); + return (ZFS_ACL_VERSION_FUID); + } + } +} + +static int +zfs_acl_version(int version) +{ + if (version < ZPL_VERSION_FUID) + return (ZFS_ACL_VERSION_INITIAL); + else + return (ZFS_ACL_VERSION_FUID); +} + +static int +zfs_acl_version_zp(znode_t *zp) +{ + return (zfs_acl_version(zp->z_zfsvfs->z_version)); +} + +zfs_acl_t * +zfs_acl_alloc(int vers) +{ + zfs_acl_t *aclp; + + aclp = kmem_zalloc(sizeof (zfs_acl_t), KM_SLEEP); + list_create(&aclp->z_acl, sizeof (zfs_acl_node_t), + offsetof(zfs_acl_node_t, z_next)); + aclp->z_version = vers; + if (vers == ZFS_ACL_VERSION_FUID) + aclp->z_ops = &zfs_acl_fuid_ops; + else + aclp->z_ops = &zfs_acl_v0_ops; + return (aclp); +} + +zfs_acl_node_t * +zfs_acl_node_alloc(size_t bytes) +{ + zfs_acl_node_t *aclnode; + + aclnode = kmem_zalloc(sizeof (zfs_acl_node_t), KM_SLEEP); + if (bytes) { + aclnode->z_acldata = kmem_alloc(bytes, KM_SLEEP); + aclnode->z_allocdata = aclnode->z_acldata; + aclnode->z_allocsize = bytes; + aclnode->z_size = bytes; + } + + return (aclnode); +} + +static void +zfs_acl_node_free(zfs_acl_node_t *aclnode) +{ + if (aclnode->z_allocsize) + kmem_free(aclnode->z_allocdata, aclnode->z_allocsize); + kmem_free(aclnode, sizeof (zfs_acl_node_t)); +} + +static void +zfs_acl_release_nodes(zfs_acl_t *aclp) +{ + zfs_acl_node_t *aclnode; + + while ((aclnode = list_head(&aclp->z_acl))) { + list_remove(&aclp->z_acl, aclnode); + zfs_acl_node_free(aclnode); + } + aclp->z_acl_count = 0; + aclp->z_acl_bytes = 0; +} + +void +zfs_acl_free(zfs_acl_t *aclp) +{ + zfs_acl_release_nodes(aclp); + list_destroy(&aclp->z_acl); + kmem_free(aclp, sizeof (zfs_acl_t)); +} + +static boolean_t +zfs_acl_valid_ace_type(uint_t type, uint_t flags) +{ + uint16_t entry_type; + + switch (type) { + case ALLOW: + case DENY: + case ACE_SYSTEM_AUDIT_ACE_TYPE: + case ACE_SYSTEM_ALARM_ACE_TYPE: + entry_type = flags & ACE_TYPE_FLAGS; + return (entry_type == ACE_OWNER || + entry_type == OWNING_GROUP || + entry_type == ACE_EVERYONE || entry_type == 0 || + entry_type == ACE_IDENTIFIER_GROUP); + default: + if (type >= MIN_ACE_TYPE && type <= MAX_ACE_TYPE) + return (B_TRUE); + } + return (B_FALSE); +} + +static boolean_t +zfs_ace_valid(vtype_t obj_type, zfs_acl_t *aclp, uint16_t type, uint16_t iflags) +{ + /* + * first check type of entry + */ + + if (!zfs_acl_valid_ace_type(type, iflags)) + return (B_FALSE); + + switch (type) { + case ACE_ACCESS_ALLOWED_OBJECT_ACE_TYPE: + case ACE_ACCESS_DENIED_OBJECT_ACE_TYPE: + case ACE_SYSTEM_AUDIT_OBJECT_ACE_TYPE: + case ACE_SYSTEM_ALARM_OBJECT_ACE_TYPE: + if (aclp->z_version < ZFS_ACL_VERSION_FUID) + return (B_FALSE); + aclp->z_hints |= ZFS_ACL_OBJ_ACE; + } + + /* + * next check inheritance level flags + */ + + if (obj_type == VDIR && + (iflags & (ACE_FILE_INHERIT_ACE|ACE_DIRECTORY_INHERIT_ACE))) + aclp->z_hints |= ZFS_INHERIT_ACE; + + if (iflags & (ACE_INHERIT_ONLY_ACE|ACE_NO_PROPAGATE_INHERIT_ACE)) { + if ((iflags & (ACE_FILE_INHERIT_ACE| + ACE_DIRECTORY_INHERIT_ACE)) == 0) { + return (B_FALSE); + } + } + + return (B_TRUE); +} + +static void * +zfs_acl_next_ace(zfs_acl_t *aclp, void *start, uint64_t *who, + uint32_t *access_mask, uint16_t *iflags, uint16_t *type) +{ + zfs_acl_node_t *aclnode; + + ASSERT(aclp); + + if (start == NULL) { + aclnode = list_head(&aclp->z_acl); + if (aclnode == NULL) + return (NULL); + + aclp->z_next_ace = aclnode->z_acldata; + aclp->z_curr_node = aclnode; + aclnode->z_ace_idx = 0; + } + + aclnode = aclp->z_curr_node; + + if (aclnode == NULL) + return (NULL); + + if (aclnode->z_ace_idx >= aclnode->z_ace_count) { + aclnode = list_next(&aclp->z_acl, aclnode); + if (aclnode == NULL) + return (NULL); + else { + aclp->z_curr_node = aclnode; + aclnode->z_ace_idx = 0; + aclp->z_next_ace = aclnode->z_acldata; + } + } + + if (aclnode->z_ace_idx < aclnode->z_ace_count) { + void *acep = aclp->z_next_ace; + size_t ace_size; + + /* + * Make sure we don't overstep our bounds + */ + ace_size = aclp->z_ops->ace_size(acep); + + if (((caddr_t)acep + ace_size) > + ((caddr_t)aclnode->z_acldata + aclnode->z_size)) { + return (NULL); + } + + *iflags = aclp->z_ops->ace_flags_get(acep); + *type = aclp->z_ops->ace_type_get(acep); + *access_mask = aclp->z_ops->ace_mask_get(acep); + *who = aclp->z_ops->ace_who_get(acep); + aclp->z_next_ace = (caddr_t)aclp->z_next_ace + ace_size; + aclnode->z_ace_idx++; + + return ((void *)acep); + } + return (NULL); +} + +static uint64_t +zfs_ace_walk(void *datap, uint64_t cookie, int aclcnt, + uint16_t *flags, uint16_t *type, uint32_t *mask) +{ + zfs_acl_t *aclp = datap; + zfs_ace_hdr_t *acep = (zfs_ace_hdr_t *)(uintptr_t)cookie; + uint64_t who; + + acep = zfs_acl_next_ace(aclp, acep, &who, mask, + flags, type); + return ((uint64_t)(uintptr_t)acep); +} + +/* + * Copy ACE to internal ZFS format. + * While processing the ACL each ACE will be validated for correctness. + * ACE FUIDs will be created later. + */ +static int +zfs_copy_ace_2_fuid(zfsvfs_t *zfsvfs, vtype_t obj_type, zfs_acl_t *aclp, + void *datap, zfs_ace_t *z_acl, uint64_t aclcnt, size_t *size, + zfs_fuid_info_t **fuidp, cred_t *cr) +{ + int i; + uint16_t entry_type; + zfs_ace_t *aceptr = z_acl; + ace_t *acep = datap; + zfs_object_ace_t *zobjacep; + ace_object_t *aceobjp; + + for (i = 0; i != aclcnt; i++) { + aceptr->z_hdr.z_access_mask = acep->a_access_mask; + aceptr->z_hdr.z_flags = acep->a_flags; + aceptr->z_hdr.z_type = acep->a_type; + entry_type = aceptr->z_hdr.z_flags & ACE_TYPE_FLAGS; + if (entry_type != ACE_OWNER && entry_type != OWNING_GROUP && + entry_type != ACE_EVERYONE) { + aceptr->z_fuid = zfs_fuid_create(zfsvfs, acep->a_who, + cr, (entry_type == 0) ? + ZFS_ACE_USER : ZFS_ACE_GROUP, fuidp); + } + + /* + * Make sure ACE is valid + */ + if (zfs_ace_valid(obj_type, aclp, aceptr->z_hdr.z_type, + aceptr->z_hdr.z_flags) != B_TRUE) + return (SET_ERROR(EINVAL)); + + switch (acep->a_type) { + case ACE_ACCESS_ALLOWED_OBJECT_ACE_TYPE: + case ACE_ACCESS_DENIED_OBJECT_ACE_TYPE: + case ACE_SYSTEM_AUDIT_OBJECT_ACE_TYPE: + case ACE_SYSTEM_ALARM_OBJECT_ACE_TYPE: + zobjacep = (zfs_object_ace_t *)aceptr; + aceobjp = (ace_object_t *)acep; + + memcpy(zobjacep->z_object_type, aceobjp->a_obj_type, + sizeof (aceobjp->a_obj_type)); + memcpy( + zobjacep->z_inherit_type, + aceobjp->a_inherit_obj_type, + sizeof (aceobjp->a_inherit_obj_type)); + acep = (ace_t *)((caddr_t)acep + sizeof (ace_object_t)); + break; + default: + acep = (ace_t *)((caddr_t)acep + sizeof (ace_t)); + } + + aceptr = (zfs_ace_t *)((caddr_t)aceptr + + aclp->z_ops->ace_size(aceptr)); + } + + *size = (caddr_t)aceptr - (caddr_t)z_acl; + + return (0); +} + +static int +zfs_copy_ace_2_oldace(vtype_t obj_type, zfs_acl_t *aclp, ace_t *acep, + zfs_oldace_t *z_acl, int aclcnt, size_t *size) +{ + int i; + zfs_oldace_t *aceptr = z_acl; + + for (i = 0; i != aclcnt; i++, aceptr++) { + aceptr->z_access_mask = acep[i].a_access_mask; + aceptr->z_type = acep[i].a_type; + aceptr->z_flags = acep[i].a_flags; + aceptr->z_fuid = acep[i].a_who; + /* + * Make sure ACE is valid + */ + if (zfs_ace_valid(obj_type, aclp, aceptr->z_type, + aceptr->z_flags) != B_TRUE) + return (SET_ERROR(EINVAL)); + } + *size = (caddr_t)aceptr - (caddr_t)z_acl; + return (0); +} + +/* + * convert old ACL format to new + */ +void +zfs_acl_xform(znode_t *zp, zfs_acl_t *aclp, cred_t *cr) +{ + zfs_oldace_t *oldaclp; + int i; + uint16_t type, iflags; + uint32_t access_mask; + uint64_t who; + void *cookie = NULL; + zfs_acl_node_t *newaclnode; + + ASSERT(aclp->z_version == ZFS_ACL_VERSION_INITIAL); + /* + * First create the ACE in a contiguous piece of memory + * for zfs_copy_ace_2_fuid(). + * + * We only convert an ACL once, so this won't happen + * everytime. + */ + oldaclp = kmem_alloc(sizeof (zfs_oldace_t) * aclp->z_acl_count, + KM_SLEEP); + i = 0; + while ((cookie = zfs_acl_next_ace(aclp, cookie, &who, + &access_mask, &iflags, &type))) { + oldaclp[i].z_flags = iflags; + oldaclp[i].z_type = type; + oldaclp[i].z_fuid = who; + oldaclp[i++].z_access_mask = access_mask; + } + + newaclnode = zfs_acl_node_alloc(aclp->z_acl_count * + sizeof (zfs_object_ace_t)); + aclp->z_ops = &zfs_acl_fuid_ops; + VERIFY(zfs_copy_ace_2_fuid(zp->z_zfsvfs, vnode_vtype(ZTOV(zp)), aclp, + oldaclp, newaclnode->z_acldata, aclp->z_acl_count, + &newaclnode->z_size, NULL, cr) == 0); + newaclnode->z_ace_count = aclp->z_acl_count; + aclp->z_version = ZFS_ACL_VERSION; + kmem_free(oldaclp, aclp->z_acl_count * sizeof (zfs_oldace_t)); + + /* + * Release all previous ACL nodes + */ + + zfs_acl_release_nodes(aclp); + + list_insert_head(&aclp->z_acl, newaclnode); + + aclp->z_acl_bytes = newaclnode->z_size; + aclp->z_acl_count = newaclnode->z_ace_count; + +} + +/* + * Convert unix access mask to v4 access mask + */ +static uint32_t +zfs_unix_to_v4(uint32_t access_mask) +{ + uint32_t new_mask = 0; + + if (access_mask & S_IXOTH) + new_mask |= ACE_EXECUTE; + if (access_mask & S_IWOTH) + new_mask |= ACE_WRITE_DATA; + if (access_mask & S_IROTH) + new_mask |= ACE_READ_DATA; + return (new_mask); +} + +static void +zfs_set_ace(zfs_acl_t *aclp, void *acep, uint32_t access_mask, + uint16_t access_type, uint64_t fuid, uint16_t entry_type) +{ + uint16_t type = entry_type & ACE_TYPE_FLAGS; + + aclp->z_ops->ace_mask_set(acep, access_mask); + aclp->z_ops->ace_type_set(acep, access_type); + aclp->z_ops->ace_flags_set(acep, entry_type); + if ((type != ACE_OWNER && type != OWNING_GROUP && + type != ACE_EVERYONE)) + aclp->z_ops->ace_who_set(acep, fuid); +} + +/* + * Determine mode of file based on ACL. + */ +uint64_t +zfs_mode_compute(uint64_t fmode, zfs_acl_t *aclp, + uint64_t *pflags, uint64_t fuid, uint64_t fgid) +{ + int entry_type; + mode_t mode; + mode_t seen = 0; + zfs_ace_hdr_t *acep = NULL; + uint64_t who; + uint16_t iflags, type; + uint32_t access_mask; + boolean_t an_exec_denied = B_FALSE; + + mode = (fmode & (S_IFMT | S_ISUID | S_ISGID | S_ISVTX)); + + while ((acep = zfs_acl_next_ace(aclp, acep, &who, + &access_mask, &iflags, &type))) { + + if (!zfs_acl_valid_ace_type(type, iflags)) + continue; + + entry_type = (iflags & ACE_TYPE_FLAGS); + + /* + * Skip over any inherit_only ACEs + */ + if (iflags & ACE_INHERIT_ONLY_ACE) + continue; + + /* + * Apple has unusual expectations to emulate hfs in that the + * mode is not updated: + * -rw-r--r-- 1 root wheel 0 Nov 12 12:39 file.txt + * chmod +a "root allow execute" file.txt + * ZFS: -rwxr--r--+ 1 root wheel 0 Nov 12 12:39 file.txt + * HFS: -rw-r--r--+ 1 root wheel 0 Nov 12 12:39 file.txt + * 0: user:root allow execute + */ +#ifdef __APPLE__ + if (entry_type == ACE_OWNER) { +#else + if (entry_type == ACE_OWNER || (entry_type == 0 && + who == fuid)) { +#endif + if ((access_mask & ACE_READ_DATA) && + (!(seen & S_IRUSR))) { + seen |= S_IRUSR; + if (type == ALLOW) { + mode |= S_IRUSR; + } + } + if ((access_mask & ACE_WRITE_DATA) && + (!(seen & S_IWUSR))) { + seen |= S_IWUSR; + if (type == ALLOW) { + mode |= S_IWUSR; + } + } + if ((access_mask & ACE_EXECUTE) && + (!(seen & S_IXUSR))) { + seen |= S_IXUSR; + if (type == ALLOW) { + mode |= S_IXUSR; + } + } +#ifdef __APPLE__ + } else if (entry_type == OWNING_GROUP) { +#else + } else if (entry_type == OWNING_GROUP || + (entry_type == ACE_IDENTIFIER_GROUP && who == fgid)) { +#endif + if ((access_mask & ACE_READ_DATA) && + (!(seen & S_IRGRP))) { + seen |= S_IRGRP; + if (type == ALLOW) { + mode |= S_IRGRP; + } + } + if ((access_mask & ACE_WRITE_DATA) && + (!(seen & S_IWGRP))) { + seen |= S_IWGRP; + if (type == ALLOW) { + mode |= S_IWGRP; + } + } + if ((access_mask & ACE_EXECUTE) && + (!(seen & S_IXGRP))) { + seen |= S_IXGRP; + if (type == ALLOW) { + mode |= S_IXGRP; + } + } + } else if (entry_type == ACE_EVERYONE) { + if ((access_mask & ACE_READ_DATA)) { + if (!(seen & S_IRUSR)) { + seen |= S_IRUSR; + if (type == ALLOW) { + mode |= S_IRUSR; + } + } + if (!(seen & S_IRGRP)) { + seen |= S_IRGRP; + if (type == ALLOW) { + mode |= S_IRGRP; + } + } + if (!(seen & S_IROTH)) { + seen |= S_IROTH; + if (type == ALLOW) { + mode |= S_IROTH; + } + } + } + if ((access_mask & ACE_WRITE_DATA)) { + if (!(seen & S_IWUSR)) { + seen |= S_IWUSR; + if (type == ALLOW) { + mode |= S_IWUSR; + } + } + if (!(seen & S_IWGRP)) { + seen |= S_IWGRP; + if (type == ALLOW) { + mode |= S_IWGRP; + } + } + if (!(seen & S_IWOTH)) { + seen |= S_IWOTH; + if (type == ALLOW) { + mode |= S_IWOTH; + } + } + } + if ((access_mask & ACE_EXECUTE)) { + if (!(seen & S_IXUSR)) { + seen |= S_IXUSR; + if (type == ALLOW) { + mode |= S_IXUSR; + } + } + if (!(seen & S_IXGRP)) { + seen |= S_IXGRP; + if (type == ALLOW) { + mode |= S_IXGRP; + } + } + if (!(seen & S_IXOTH)) { + seen |= S_IXOTH; + if (type == ALLOW) { + mode |= S_IXOTH; + } + } + } + } else { + /* + * Only care if this IDENTIFIER_GROUP or + * USER ACE denies execute access to someone, + * mode is not affected + */ + if ((access_mask & ACE_EXECUTE) && type == DENY) + an_exec_denied = B_TRUE; + } + } + + /* + * Failure to allow is effectively a deny, so execute permission + * is denied if it was never mentioned or if we explicitly + * weren't allowed it. + */ + if (!an_exec_denied && + ((seen & ALL_MODE_EXECS) != ALL_MODE_EXECS || + (mode & ALL_MODE_EXECS) != ALL_MODE_EXECS)) + an_exec_denied = B_TRUE; + + if (an_exec_denied) + *pflags &= ~ZFS_NO_EXECS_DENIED; + else + *pflags |= ZFS_NO_EXECS_DENIED; + + return (mode); +} + +/* + * Read an external acl object. If the intent is to modify, always + * create a new acl and leave any cached acl in place. + */ +int +zfs_acl_node_read(znode_t *zp, boolean_t have_lock, zfs_acl_t **aclpp, + boolean_t will_modify) +{ + zfs_acl_t *aclp; + int aclsize = 0; + int acl_count = 0; + zfs_acl_node_t *aclnode; + zfs_acl_phys_t znode_acl; + int version; + int error; + + ASSERT(MUTEX_HELD(&zp->z_acl_lock)); + + if (zp->z_acl_cached && !will_modify) { + *aclpp = zp->z_acl_cached; + return (0); + } + + version = zfs_znode_acl_version(zp); + + if ((error = zfs_acl_znode_info(zp, &aclsize, + &acl_count, &znode_acl)) != 0) { + goto done; + } + + aclp = zfs_acl_alloc(version); + + aclp->z_acl_count = acl_count; + aclp->z_acl_bytes = aclsize; + + aclnode = zfs_acl_node_alloc(aclsize); + aclnode->z_ace_count = aclp->z_acl_count; + aclnode->z_size = aclsize; + + if (!zp->z_is_sa) { + if (znode_acl.z_acl_extern_obj) { + error = dmu_read(zp->z_zfsvfs->z_os, + znode_acl.z_acl_extern_obj, 0, aclnode->z_size, + aclnode->z_acldata, DMU_READ_PREFETCH); + } else { + memcpy(aclnode->z_acldata, znode_acl.z_ace_data, + aclnode->z_size); + } + } else { + error = sa_lookup(zp->z_sa_hdl, SA_ZPL_DACL_ACES(zp->z_zfsvfs), + aclnode->z_acldata, aclnode->z_size); + } + + if (error != 0) { + zfs_acl_free(aclp); + zfs_acl_node_free(aclnode); + /* convert checksum errors into IO errors */ + if (error == ECKSUM) + error = SET_ERROR(EIO); + goto done; + } + + list_insert_head(&aclp->z_acl, aclnode); + + *aclpp = aclp; + if (!will_modify) + zp->z_acl_cached = aclp; +done: + return (error); +} + +void +zfs_acl_data_locator(void **dataptr, uint32_t *length, uint32_t buflen, + boolean_t start, void *userdata) +{ + zfs_acl_locator_cb_t *cb = (zfs_acl_locator_cb_t *)userdata; + + if (start) { + cb->cb_acl_node = list_head(&cb->cb_aclp->z_acl); + } else { + cb->cb_acl_node = list_next(&cb->cb_aclp->z_acl, + cb->cb_acl_node); + } + *dataptr = cb->cb_acl_node->z_acldata; + *length = cb->cb_acl_node->z_size; +} + +int +zfs_acl_chown_setattr(znode_t *zp) +{ + int error; + zfs_acl_t *aclp; + + ASSERT(MUTEX_HELD(&zp->z_acl_lock)); + + if ((error = zfs_acl_node_read(zp, B_TRUE, &aclp, B_FALSE)) == 0) + zp->z_mode = zfs_mode_compute(zp->z_mode, aclp, + &zp->z_pflags, zp->z_uid, zp->z_gid); + + /* + * Some ZFS implementations (ZEVO) create neither a ZNODE_ACL + * nor a DACL_ACES SA in which case ENOENT is returned from + * zfs_acl_node_read() when the SA can't be located. + * Allow chown/chgrp to succeed in these cases rather than + * returning an error that makes no sense in the context of + * the caller. + */ + if (error == ENOENT) + return (0); + + return (error); +} + +/* + * common code for setting ACLs. + * + * This function is called from zfs_mode_update, zfs_perm_init, and zfs_setacl. + * zfs_setacl passes a non-NULL inherit pointer (ihp) to indicate that it's + * already checked the acl and knows whether to inherit. + */ +int +zfs_aclset_common(znode_t *zp, zfs_acl_t *aclp, cred_t *cr, dmu_tx_t *tx) +{ + int error; + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + dmu_object_type_t otype; + zfs_acl_locator_cb_t locate = { 0 }; + uint64_t mode; + sa_bulk_attr_t bulk[5]; + uint64_t ctime[2]; + int count = 0; + zfs_acl_phys_t acl_phys; + + mode = zp->z_mode; + + mode = zfs_mode_compute(mode, aclp, &zp->z_pflags, + zp->z_uid, zp->z_gid); + + zp->z_mode = mode; + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MODE(zfsvfs), NULL, + &mode, sizeof (mode)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, + &zp->z_pflags, sizeof (zp->z_pflags)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, + &ctime, sizeof (ctime)); + + if (zp->z_acl_cached) { + zfs_acl_free(zp->z_acl_cached); + zp->z_acl_cached = NULL; + } + + /* + * Upgrade needed? + */ + if (!zfsvfs->z_use_fuids) { + otype = DMU_OT_OLDACL; + } else { + if ((aclp->z_version == ZFS_ACL_VERSION_INITIAL) && + (zfsvfs->z_version >= ZPL_VERSION_FUID)) + zfs_acl_xform(zp, aclp, cr); + ASSERT(aclp->z_version >= ZFS_ACL_VERSION_FUID); + otype = DMU_OT_ACL; + } + + /* + * Arrgh, we have to handle old on disk format + * as well as newer (preferred) SA format. + */ + + if (zp->z_is_sa) { /* the easy case, just update the ACL attribute */ + locate.cb_aclp = aclp; + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_DACL_ACES(zfsvfs), + zfs_acl_data_locator, &locate, aclp->z_acl_bytes); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_DACL_COUNT(zfsvfs), + NULL, &aclp->z_acl_count, sizeof (uint64_t)); + } else { /* Painful legacy way */ + zfs_acl_node_t *aclnode; + uint64_t off = 0; + uint64_t aoid; + + if ((error = sa_lookup(zp->z_sa_hdl, SA_ZPL_ZNODE_ACL(zfsvfs), + &acl_phys, sizeof (acl_phys))) != 0) + return (error); + + aoid = acl_phys.z_acl_extern_obj; + + if (aclp->z_acl_bytes > ZFS_ACE_SPACE) { + /* + * If ACL was previously external and we are now + * converting to new ACL format then release old + * ACL object and create a new one. + */ + if (aoid && + aclp->z_version != acl_phys.z_acl_version) { + error = dmu_object_free(zfsvfs->z_os, aoid, tx); + if (error) + return (error); + aoid = 0; + } + if (aoid == 0) { + aoid = dmu_object_alloc(zfsvfs->z_os, + otype, aclp->z_acl_bytes, + otype == DMU_OT_ACL ? + DMU_OT_SYSACL : DMU_OT_NONE, + otype == DMU_OT_ACL ? + DN_OLD_MAX_BONUSLEN : 0, tx); + } else { + (void) dmu_object_set_blocksize(zfsvfs->z_os, + aoid, aclp->z_acl_bytes, 0, tx); + } + acl_phys.z_acl_extern_obj = aoid; + for (aclnode = list_head(&aclp->z_acl); aclnode; + aclnode = list_next(&aclp->z_acl, aclnode)) { + if (aclnode->z_ace_count == 0) + continue; + dmu_write(zfsvfs->z_os, aoid, off, + aclnode->z_size, aclnode->z_acldata, tx); + off += aclnode->z_size; + } + } else { + void *start = acl_phys.z_ace_data; + /* + * Migrating back embedded? + */ + if (acl_phys.z_acl_extern_obj) { + error = dmu_object_free(zfsvfs->z_os, + acl_phys.z_acl_extern_obj, tx); + if (error) + return (error); + acl_phys.z_acl_extern_obj = 0; + } + + for (aclnode = list_head(&aclp->z_acl); aclnode; + aclnode = list_next(&aclp->z_acl, aclnode)) { + if (aclnode->z_ace_count == 0) + continue; + memcpy(start, aclnode->z_acldata, + aclnode->z_size); + start = (caddr_t)start + aclnode->z_size; + } + } + /* + * If Old version then swap count/bytes to match old + * layout of znode_acl_phys_t. + */ + if (aclp->z_version == ZFS_ACL_VERSION_INITIAL) { + acl_phys.z_acl_size = aclp->z_acl_count; + acl_phys.z_acl_count = aclp->z_acl_bytes; + } else { + acl_phys.z_acl_size = aclp->z_acl_bytes; + acl_phys.z_acl_count = aclp->z_acl_count; + } + acl_phys.z_acl_version = aclp->z_version; + + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_ZNODE_ACL(zfsvfs), NULL, + &acl_phys, sizeof (acl_phys)); + } + + /* + * Replace ACL wide bits, but first clear them. + */ + zp->z_pflags &= ~ZFS_ACL_WIDE_FLAGS; + + zp->z_pflags |= aclp->z_hints; + + if (ace_trivial_common(aclp, 0, zfs_ace_walk) == 0) + zp->z_pflags |= ZFS_ACL_TRIVIAL; + + zfs_tstamp_update_setup(zp, STATE_CHANGED, NULL, ctime); + return (sa_bulk_update(zp->z_sa_hdl, bulk, count, tx)); +} + +static void +zfs_acl_chmod(vtype_t vtype, uint64_t mode, boolean_t split, boolean_t trim, + zfs_acl_t *aclp) +{ + void *acep = NULL; + uint64_t who; + int new_count, new_bytes; + int ace_size; + int entry_type; + uint16_t iflags, type; + uint32_t access_mask; + zfs_acl_node_t *newnode; + size_t abstract_size = aclp->z_ops->ace_abstract_size(); + void *zacep; + boolean_t isdir; + trivial_acl_t masks; + + new_count = new_bytes = 0; + + isdir = (vtype == VDIR); + + acl_trivial_access_masks((mode_t)mode, isdir, &masks); + + newnode = zfs_acl_node_alloc((abstract_size * 6) + aclp->z_acl_bytes); + + zacep = newnode->z_acldata; + if (masks.allow0) { + zfs_set_ace(aclp, zacep, masks.allow0, ALLOW, -1, ACE_OWNER); + zacep = (void *)((uintptr_t)zacep + abstract_size); + new_count++; + new_bytes += abstract_size; + } + if (masks.deny1) { + zfs_set_ace(aclp, zacep, masks.deny1, DENY, -1, ACE_OWNER); + zacep = (void *)((uintptr_t)zacep + abstract_size); + new_count++; + new_bytes += abstract_size; + } + if (masks.deny2) { + zfs_set_ace(aclp, zacep, masks.deny2, DENY, -1, OWNING_GROUP); + zacep = (void *)((uintptr_t)zacep + abstract_size); + new_count++; + new_bytes += abstract_size; + } + + while ((acep = zfs_acl_next_ace(aclp, acep, &who, &access_mask, + &iflags, &type))) { + entry_type = (iflags & ACE_TYPE_FLAGS); + /* + * ACEs used to represent the file mode may be divided + * into an equivalent pair of inherit-only and regular + * ACEs, if they are inheritable. + * Skip regular ACEs, which are replaced by the new mode. + */ + if (split && (entry_type == ACE_OWNER || + entry_type == OWNING_GROUP || + entry_type == ACE_EVERYONE)) { + if (!isdir || !(iflags & + (ACE_FILE_INHERIT_ACE|ACE_DIRECTORY_INHERIT_ACE))) + continue; + /* + * We preserve owner@, group@, or @everyone + * permissions, if they are inheritable, by + * copying them to inherit_only ACEs. This + * prevents inheritable permissions from being + * altered along with the file mode. + */ + iflags |= ACE_INHERIT_ONLY_ACE; + } + + /* + * If this ACL has any inheritable ACEs, mark that in + * the hints (which are later masked into the pflags) + * so create knows to do inheritance. + */ + if (isdir && (iflags & + (ACE_FILE_INHERIT_ACE|ACE_DIRECTORY_INHERIT_ACE))) + aclp->z_hints |= ZFS_INHERIT_ACE; + + if ((type != ALLOW && type != DENY) || + (iflags & ACE_INHERIT_ONLY_ACE)) { + switch (type) { + case ACE_ACCESS_ALLOWED_OBJECT_ACE_TYPE: + case ACE_ACCESS_DENIED_OBJECT_ACE_TYPE: + case ACE_SYSTEM_AUDIT_OBJECT_ACE_TYPE: + case ACE_SYSTEM_ALARM_OBJECT_ACE_TYPE: + aclp->z_hints |= ZFS_ACL_OBJ_ACE; + break; + } + } else { + /* + * Limit permissions granted by ACEs to be no greater + * than permissions of the requested group mode. + * Applies when the "aclmode" property is set to + * "groupmask". + */ + if ((type == ALLOW) && trim) + access_mask &= masks.group; + } + zfs_set_ace(aclp, zacep, access_mask, type, who, iflags); + ace_size = aclp->z_ops->ace_size(acep); + zacep = (void *)((uintptr_t)zacep + ace_size); + new_count++; + new_bytes += ace_size; + } + zfs_set_ace(aclp, zacep, masks.owner, ALLOW, -1, ACE_OWNER); + zacep = (void *)((uintptr_t)zacep + abstract_size); + zfs_set_ace(aclp, zacep, masks.group, ALLOW, -1, OWNING_GROUP); + zacep = (void *)((uintptr_t)zacep + abstract_size); + zfs_set_ace(aclp, zacep, masks.everyone, ALLOW, -1, ACE_EVERYONE); + + new_count += 3; + new_bytes += abstract_size * 3; + zfs_acl_release_nodes(aclp); + aclp->z_acl_count = new_count; + aclp->z_acl_bytes = new_bytes; + newnode->z_ace_count = new_count; + newnode->z_size = new_bytes; + list_insert_tail(&aclp->z_acl, newnode); +} + +int +zfs_acl_chmod_setattr(znode_t *zp, zfs_acl_t **aclp, uint64_t mode) +{ + int error = 0; + + mutex_enter(&zp->z_acl_lock); + if (zp->z_zfsvfs->z_acl_mode == ZFS_ACL_DISCARD) + *aclp = zfs_acl_alloc(zfs_acl_version_zp(zp)); + else + error = zfs_acl_node_read(zp, B_TRUE, aclp, B_TRUE); + + if (error == 0) { + (*aclp)->z_hints = zp->z_pflags & V4_ACL_WIDE_FLAGS; + zfs_acl_chmod(vnode_vtype(ZTOV(zp)), mode, B_TRUE, + (zp->z_zfsvfs->z_acl_mode == ZFS_ACL_GROUPMASK), *aclp); + } + mutex_exit(&zp->z_acl_lock); + return (error); +} + +/* + * Should ACE be inherited? + */ +static int +zfs_ace_can_use(vtype_t vtype, uint16_t acep_flags) +{ + int iflags = (acep_flags & 0xf); + + if ((vtype == VDIR) && (iflags & ACE_DIRECTORY_INHERIT_ACE)) + return (1); + else if (iflags & ACE_FILE_INHERIT_ACE) + return (!((vtype == VDIR) && + (iflags & ACE_NO_PROPAGATE_INHERIT_ACE))); + return (0); +} + +/* + * inherit inheritable ACEs from parent + */ +static zfs_acl_t * +zfs_acl_inherit(zfsvfs_t *zfsvfs, vtype_t vtype, zfs_acl_t *paclp, + uint64_t mode, boolean_t *need_chmod) +{ + void *pacep = NULL; + void *acep; + zfs_acl_node_t *aclnode; + zfs_acl_t *aclp = NULL; + uint64_t who; + uint32_t access_mask; + uint16_t iflags, newflags, type; + size_t ace_size; + void *data1, *data2; + size_t data1sz, data2sz; + uint_t aclinherit; + boolean_t isdir = (vtype == VDIR); + boolean_t isreg = (vtype == VREG); + + *need_chmod = B_TRUE; + + aclp = zfs_acl_alloc(paclp->z_version); + aclinherit = zfsvfs->z_acl_inherit; + if (aclinherit == ZFS_ACL_DISCARD || vtype == VLNK) + return (aclp); + + while ((pacep = zfs_acl_next_ace(paclp, pacep, &who, + &access_mask, &iflags, &type))) { + + /* + * don't inherit bogus ACEs + */ + if (!zfs_acl_valid_ace_type(type, iflags)) + continue; + + /* + * Check if ACE is inheritable by this vnode + */ + if ((aclinherit == ZFS_ACL_NOALLOW && type == ALLOW) || + !zfs_ace_can_use(vtype, iflags)) + continue; + + /* + * If owner@, group@, or everyone@ inheritable + * then zfs_acl_chmod() isn't needed. + */ + if ((aclinherit == ZFS_ACL_PASSTHROUGH || + aclinherit == ZFS_ACL_PASSTHROUGH_X) && + ((iflags & (ACE_OWNER|ACE_EVERYONE)) || + ((iflags & OWNING_GROUP) == OWNING_GROUP)) && + (isreg || (isdir && (iflags & ACE_DIRECTORY_INHERIT_ACE)))) + *need_chmod = B_FALSE; + + /* + * Strip inherited execute permission from file if + * not in mode + */ + if (aclinherit == ZFS_ACL_PASSTHROUGH_X && type == ALLOW && + !isdir && ((mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0)) { + access_mask &= ~ACE_EXECUTE; + } + + /* + * Strip write_acl and write_owner from permissions + * when inheriting an ACE + */ + if (aclinherit == ZFS_ACL_RESTRICTED && type == ALLOW) { + access_mask &= ~RESTRICTED_CLEAR; + } + + ace_size = aclp->z_ops->ace_size(pacep); + aclnode = zfs_acl_node_alloc(ace_size); + list_insert_tail(&aclp->z_acl, aclnode); + acep = aclnode->z_acldata; + + zfs_set_ace(aclp, acep, access_mask, type, + who, iflags|ACE_INHERITED_ACE); + + /* + * Copy special opaque data if any + */ + if ((data1sz = paclp->z_ops->ace_data(pacep, &data1)) != 0) { + VERIFY((data2sz = aclp->z_ops->ace_data(acep, + &data2)) == data1sz); + memcpy(data2, data1, data2sz); + } + + aclp->z_acl_count++; + aclnode->z_ace_count++; + aclp->z_acl_bytes += aclnode->z_size; + newflags = aclp->z_ops->ace_flags_get(acep); + + /* + * If ACE is not to be inherited further, or if the vnode is + * not a directory, remove all inheritance flags + */ + if (!isdir || (iflags & ACE_NO_PROPAGATE_INHERIT_ACE)) { + newflags &= ~ALL_INHERIT; + aclp->z_ops->ace_flags_set(acep, + newflags|ACE_INHERITED_ACE); + continue; + } + + /* + * This directory has an inheritable ACE + */ + aclp->z_hints |= ZFS_INHERIT_ACE; + + /* + * If only FILE_INHERIT is set then turn on + * inherit_only + */ + if ((iflags & (ACE_FILE_INHERIT_ACE | + ACE_DIRECTORY_INHERIT_ACE)) == ACE_FILE_INHERIT_ACE) { + newflags |= ACE_INHERIT_ONLY_ACE; + aclp->z_ops->ace_flags_set(acep, + newflags|ACE_INHERITED_ACE); + } else { + newflags &= ~ACE_INHERIT_ONLY_ACE; + aclp->z_ops->ace_flags_set(acep, + newflags|ACE_INHERITED_ACE); + } + } + if (zfsvfs->z_acl_mode == ZFS_ACL_RESTRICTED && + aclp->z_acl_count != 0) { + *need_chmod = B_FALSE; + } + + return (aclp); +} + +/* + * Create file system object initial permissions + * including inheritable ACEs. + * Also, create FUIDs for owner and group. + */ +int +zfs_acl_ids_create(znode_t *dzp, int flag, vattr_t *vap, cred_t *cr, + vsecattr_t *vsecp, zfs_acl_ids_t *acl_ids, zidmap_t *mnt_ns) +{ + int error; + zfsvfs_t *zfsvfs = dzp->z_zfsvfs; + zfs_acl_t *paclp; + gid_t gid; + boolean_t need_chmod = B_TRUE; + boolean_t trim = B_FALSE; + boolean_t inherited = B_FALSE; + + memset(acl_ids, 0, sizeof (zfs_acl_ids_t)); + acl_ids->z_mode = MAKEIMODE(vap->va_type, vap->va_mode); + + if (vsecp) + if ((error = zfs_vsec_2_aclp(zfsvfs, vap->va_type, vsecp, cr, + &acl_ids->z_fuidp, &acl_ids->z_aclp)) != 0) + return (error); + /* + * Determine uid and gid. + */ + if ((flag & IS_ROOT_NODE) || zfsvfs->z_replay || + ((flag & IS_XATTR) && (vap->va_type == VDIR))) { + acl_ids->z_fuid = zfs_fuid_create(zfsvfs, + (uint64_t)vap->va_uid, cr, + ZFS_OWNER, &acl_ids->z_fuidp); + acl_ids->z_fgid = zfs_fuid_create(zfsvfs, + (uint64_t)vap->va_gid, cr, + ZFS_GROUP, &acl_ids->z_fuidp); + gid = vap->va_gid; + } else { + acl_ids->z_fuid = zfs_fuid_create_cred(zfsvfs, ZFS_OWNER, + cr, &acl_ids->z_fuidp); + acl_ids->z_fgid = 0; + if (vap->va_mask & ATTR_GID) { + acl_ids->z_fgid = zfs_fuid_create(zfsvfs, + (uint64_t)vap->va_gid, + cr, ZFS_GROUP, &acl_ids->z_fuidp); + gid = vap->va_gid; + if (acl_ids->z_fgid != dzp->z_gid && + !groupmember(vap->va_gid, cr) && + secpolicy_vnode_create_gid(cr) != 0) + acl_ids->z_fgid = 0; + } + if (acl_ids->z_fgid == 0) { + const char *domain; + uint32_t rid; + + acl_ids->z_fgid = dzp->z_gid; + gid = zfs_fuid_map_id(zfsvfs, acl_ids->z_fgid, + cr, ZFS_GROUP); + + if (zfsvfs->z_use_fuids && + IS_EPHEMERAL(acl_ids->z_fgid)) { + domain = + zfs_fuid_idx_domain(&zfsvfs->z_fuid_idx, + FUID_INDEX(acl_ids->z_fgid)); + rid = FUID_RID(acl_ids->z_fgid); + zfs_fuid_node_add(&acl_ids->z_fuidp, + domain, rid, FUID_INDEX(acl_ids->z_fgid), + acl_ids->z_fgid, ZFS_GROUP); + } + } + } + + /* + * If we're creating a directory, and the parent directory has the + * set-GID bit set, set in on the new directory. + * Otherwise, if the user is neither privileged nor a member of the + * file's new group, clear the file's set-GID bit. + */ + + if (!(flag & IS_ROOT_NODE) && (dzp->z_mode & S_ISGID) && + (vap->va_type == VDIR)) { + acl_ids->z_mode |= S_ISGID; + } else { + if ((acl_ids->z_mode & S_ISGID) && + secpolicy_vnode_setids_setgids(ZTOV(dzp), cr, gid) != 0) + acl_ids->z_mode &= ~S_ISGID; + } + + if (acl_ids->z_aclp == NULL) { + mutex_enter(&dzp->z_acl_lock); + if (!(flag & IS_ROOT_NODE) && + (dzp->z_pflags & ZFS_INHERIT_ACE) && + !(dzp->z_pflags & ZFS_XATTR)) { + VERIFY0(zfs_acl_node_read(dzp, B_TRUE, + &paclp, B_FALSE)); + acl_ids->z_aclp = zfs_acl_inherit(zfsvfs, + vap->va_type, paclp, acl_ids->z_mode, &need_chmod); + inherited = B_TRUE; + } else { + acl_ids->z_aclp = + zfs_acl_alloc(zfs_acl_version_zp(dzp)); + acl_ids->z_aclp->z_hints |= ZFS_ACL_TRIVIAL; + } + mutex_exit(&dzp->z_acl_lock); + + if (need_chmod) { + if (vap->va_type == VDIR) + acl_ids->z_aclp->z_hints |= + ZFS_ACL_AUTO_INHERIT; + + if (zfsvfs->z_acl_mode == ZFS_ACL_GROUPMASK && + zfsvfs->z_acl_inherit != ZFS_ACL_PASSTHROUGH && + zfsvfs->z_acl_inherit != ZFS_ACL_PASSTHROUGH_X) + trim = B_TRUE; + zfs_acl_chmod(vap->va_type, acl_ids->z_mode, B_FALSE, + trim, acl_ids->z_aclp); + } + } + + if (inherited || vsecp) { + acl_ids->z_mode = zfs_mode_compute(acl_ids->z_mode, + acl_ids->z_aclp, &acl_ids->z_aclp->z_hints, + acl_ids->z_fuid, acl_ids->z_fgid); + if (ace_trivial_common(acl_ids->z_aclp, 0, zfs_ace_walk) == 0) + acl_ids->z_aclp->z_hints |= ZFS_ACL_TRIVIAL; + } + + return (0); +} + +/* + * Free ACL and fuid_infop, but not the acl_ids structure + */ +void +zfs_acl_ids_free(zfs_acl_ids_t *acl_ids) +{ + if (acl_ids->z_aclp) + zfs_acl_free(acl_ids->z_aclp); + if (acl_ids->z_fuidp) + zfs_fuid_info_free(acl_ids->z_fuidp); + acl_ids->z_aclp = NULL; + acl_ids->z_fuidp = NULL; +} + +boolean_t +zfs_acl_ids_overquota(zfsvfs_t *zv, zfs_acl_ids_t *acl_ids, uint64_t projid) +{ + return (zfs_id_overquota(zv, DMU_USERUSED_OBJECT, acl_ids->z_fuid) || + zfs_id_overquota(zv, DMU_GROUPUSED_OBJECT, acl_ids->z_fgid) || + (projid != ZFS_DEFAULT_PROJID && projid != ZFS_INVALID_PROJID && + zfs_id_overquota(zv, DMU_PROJECTUSED_OBJECT, projid))); +} + +/* + * Retrieve a file's ACL + */ +int +zfs_getacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, + cred_t *cr) +{ + (void) zp; (void) vsecp; (void) skipaclchk; (void) cr; + panic("Did not expect %s to be called.", __func__); +} + +int +zfs_macos_getacl(znode_t *zp, struct kauth_acl **aclpp, boolean_t skipaclchk, + cred_t *cr) +{ + zfs_acl_t *aclp; + kauth_acl_t k_acl; + u_int32_t ace_flags = 0; + kauth_ace_rights_t rights = 0; + guid_t *guidp; + uint64_t who; + uint32_t access_mask; + uint16_t flags; + uint16_t type; + int i; + int error; + void *zacep = NULL; + + mutex_enter(&zp->z_acl_lock); + + error = zfs_acl_node_read(zp, B_FALSE, &aclp, B_TRUE); + if (error != 0) { + mutex_exit(&zp->z_acl_lock); + return (error); + } + if ((k_acl = kauth_acl_alloc(aclp->z_acl_count)) == NULL) { + mutex_exit(&zp->z_acl_lock); + *aclpp = (kauth_acl_t)KAUTH_FILESEC_NONE; + return (ENOMEM); + } + + dprintf("acl_count %llu\n", aclp->z_acl_count); + + k_acl->acl_entrycount = aclp->z_acl_count; + k_acl->acl_flags = 0; + *aclpp = k_acl; + + /* + * Translate Open Solaris ACEs to Mac OS X ACLs + */ + i = 0; + while ((zacep = zfs_acl_next_ace(aclp, zacep, + &who, &access_mask, &flags, &type))) { + rights = 0; + ace_flags = 0; + + guidp = &k_acl->acl_ace[i].ace_applicable; + + if (flags & ACE_OWNER) { +#if HIDE_TRIVIAL_ACL + continue; +#endif + who = -1; + nfsacl_set_wellknown(KAUTH_WKG_OWNER, guidp); + } else if ((flags & OWNING_GROUP) == OWNING_GROUP) { +#if HIDE_TRIVIAL_ACL + continue; +#endif + who = -1; + nfsacl_set_wellknown(KAUTH_WKG_GROUP, guidp); + } else if (flags & ACE_EVERYONE) { +#if HIDE_TRIVIAL_ACL + continue; +#endif + who = -1; + nfsacl_set_wellknown(KAUTH_WKG_EVERYBODY, guidp); + /* Try to get a guid from our uid */ + } else { + + dprintf( + "ZFS: trying to map uid %llu flags %x type %x\n", + who, flags, type); + + if (flags & OWNING_GROUP) { + if (kauth_cred_gid2guid(who, guidp) == 0) { + dprintf("ZFS: appears to be a group\n"); + } + } else if (kauth_cred_uid2guid(who, guidp) == 0) { + dprintf("ZFS: appears to be a user\n"); + } else { + dprintf("ZFS: Unable to map\n"); + memset(guidp, 0, sizeof (guid_t)); + } + } + + // access_mask = aclp->z_acl[i].a_access_mask; + if (access_mask & ACE_READ_DATA) + rights |= KAUTH_VNODE_READ_DATA; + if (access_mask & ACE_WRITE_DATA) + rights |= KAUTH_VNODE_WRITE_DATA; + if (access_mask & ACE_APPEND_DATA) + rights |= KAUTH_VNODE_APPEND_DATA; + if (access_mask & ACE_READ_NAMED_ATTRS) + rights |= KAUTH_VNODE_READ_EXTATTRIBUTES; + if (access_mask & ACE_WRITE_NAMED_ATTRS) + rights |= KAUTH_VNODE_WRITE_EXTATTRIBUTES; + if (access_mask & ACE_EXECUTE) + rights |= KAUTH_VNODE_EXECUTE; + if (access_mask & ACE_DELETE_CHILD) + rights |= KAUTH_VNODE_DELETE_CHILD; + if (access_mask & ACE_READ_ATTRIBUTES) + rights |= KAUTH_VNODE_READ_ATTRIBUTES; + if (access_mask & ACE_WRITE_ATTRIBUTES) + rights |= KAUTH_VNODE_WRITE_ATTRIBUTES; + if (access_mask & ACE_DELETE) + rights |= KAUTH_VNODE_DELETE; + if (access_mask & ACE_READ_ACL) + rights |= KAUTH_VNODE_READ_SECURITY; + if (access_mask & ACE_WRITE_ACL) + rights |= KAUTH_VNODE_WRITE_SECURITY; + if (access_mask & ACE_WRITE_OWNER) + rights |= KAUTH_VNODE_TAKE_OWNERSHIP; + if (access_mask & ACE_SYNCHRONIZE) + rights |= KAUTH_VNODE_SYNCHRONIZE; + k_acl->acl_ace[i].ace_rights = rights; + + // flags = aclp->z_acl[i].a_flags; + if (flags & ACE_FILE_INHERIT_ACE) + ace_flags |= KAUTH_ACE_FILE_INHERIT; + if (flags & ACE_DIRECTORY_INHERIT_ACE) + ace_flags |= KAUTH_ACE_DIRECTORY_INHERIT; + if (flags & ACE_NO_PROPAGATE_INHERIT_ACE) + ace_flags |= KAUTH_ACE_LIMIT_INHERIT; + if (flags & ACE_INHERIT_ONLY_ACE) + ace_flags |= KAUTH_ACE_ONLY_INHERIT; + + // type = aclp->z_acl[i].a_type; + switch (type) { + case ACE_ACCESS_ALLOWED_ACE_TYPE: + ace_flags |= KAUTH_ACE_PERMIT; + break; + case ACE_ACCESS_DENIED_ACE_TYPE: + ace_flags |= KAUTH_ACE_DENY; + break; + case ACE_SYSTEM_AUDIT_ACE_TYPE: + ace_flags |= KAUTH_ACE_AUDIT; + break; + case ACE_SYSTEM_ALARM_ACE_TYPE: + ace_flags |= KAUTH_ACE_ALARM; + break; + } + k_acl->acl_ace[i].ace_flags = ace_flags; + i++; + } + k_acl->acl_entrycount = i; + mutex_exit(&zp->z_acl_lock); + + zfs_acl_free(aclp); + + return (0); +} + +int +zfs_addacl_trivial(znode_t *zp, ace_t *aces, int *nentries, int seen_type) +{ + zfs_acl_t *aclp; + uint64_t who; + uint32_t access_mask; + uint16_t flags; + uint16_t type; + int i; + int error; + void *zacep = NULL; + + mutex_enter(&zp->z_acl_lock); + + error = zfs_acl_node_read(zp, B_FALSE, &aclp, B_TRUE); + if (error != 0) { + mutex_exit(&zp->z_acl_lock); + return (error); + } + + dprintf("ondisk acl_count %llu\n", aclp->z_acl_count); + + // Start at the end + i = *nentries; + + /* + * Translate Open Solaris ACEs to Mac OS X ACLs + */ + while ((zacep = zfs_acl_next_ace(aclp, zacep, + &who, &access_mask, &flags, &type))) { + + if (flags & ACE_OWNER) { + if (seen_type & ACE_OWNER) continue; + seen_type |= ACE_OWNER; + who = -1; + } else if ((flags & OWNING_GROUP) == OWNING_GROUP) { + if (seen_type & ACE_GROUP) continue; + seen_type |= ACE_GROUP; + who = -1; + } else if (flags & ACE_EVERYONE) { + if (seen_type & ACE_EVERYONE) continue; + seen_type |= ACE_EVERYONE; + who = -1; + /* Try to get a guid from our uid */ + } else { + // Only deal with the trivials + continue; + } + + aces[i].a_who = who; + aces[i].a_access_mask = access_mask; + aces[i].a_flags = flags; + aces[i].a_type = type; + + dprintf("zfs: adding entry %d for type %x sizeof %lu\n", + i, type, sizeof (aces[i])); + i++; + } + + *nentries = i; + mutex_exit(&zp->z_acl_lock); + + zfs_acl_free(aclp); + + return (0); +} + +int +zfs_vsec_2_aclp(zfsvfs_t *zfsvfs, vtype_t obj_type, + vsecattr_t *vsecp, cred_t *cr, zfs_fuid_info_t **fuidp, zfs_acl_t **zaclp) +{ + zfs_acl_t *aclp; + zfs_acl_node_t *aclnode; + int aclcnt = vsecp->vsa_aclcnt; + int error; + + if (vsecp->vsa_aclcnt > MAX_ACL_ENTRIES || vsecp->vsa_aclcnt <= 0) + return (SET_ERROR(EINVAL)); + + aclp = zfs_acl_alloc(zfs_acl_version(zfsvfs->z_version)); + + aclp->z_hints = 0; + aclnode = zfs_acl_node_alloc(aclcnt * sizeof (zfs_object_ace_t)); + if (aclp->z_version == ZFS_ACL_VERSION_INITIAL) { + if ((error = zfs_copy_ace_2_oldace(obj_type, aclp, + (ace_t *)vsecp->vsa_aclentp, aclnode->z_acldata, + aclcnt, &aclnode->z_size)) != 0) { + zfs_acl_free(aclp); + zfs_acl_node_free(aclnode); + return (error); + } + } else { + if ((error = zfs_copy_ace_2_fuid(zfsvfs, obj_type, aclp, + vsecp->vsa_aclentp, aclnode->z_acldata, aclcnt, + &aclnode->z_size, fuidp, cr)) != 0) { + zfs_acl_free(aclp); + zfs_acl_node_free(aclnode); + return (error); + } + } + aclp->z_acl_bytes = aclnode->z_size; + aclnode->z_ace_count = aclcnt; + aclp->z_acl_count = aclcnt; + list_insert_head(&aclp->z_acl, aclnode); + + /* + * If flags are being set then add them to z_hints + */ + if (vsecp->vsa_mask & VSA_ACE_ACLFLAGS) { + if (vsecp->vsa_aclflags & ACL_PROTECTED) + aclp->z_hints |= ZFS_ACL_PROTECTED; + if (vsecp->vsa_aclflags & ACL_DEFAULTED) + aclp->z_hints |= ZFS_ACL_DEFAULTED; + if (vsecp->vsa_aclflags & ACL_AUTO_INHERIT) + aclp->z_hints |= ZFS_ACL_AUTO_INHERIT; + } + + *zaclp = aclp; + + return (0); +} + +/* + * Set a file's ACL + */ +int +zfs_setacl(znode_t *zp, vsecattr_t *vsecp, boolean_t skipaclchk, cred_t *cr) +{ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + zilog_t *zilog = zfsvfs->z_log; + // ulong_t mask = vsecp->vsa_mask & (VSA_ACE | VSA_ACECNT); + dmu_tx_t *tx; + int error; + zfs_acl_t *aclp; + zfs_fuid_info_t *fuidp = NULL; + boolean_t fuid_dirtied; + uint64_t acl_obj; + + // if (mask == 0) + // return (SET_ERROR(ENOSYS)); + + if (zp->z_pflags & ZFS_IMMUTABLE) + return (SET_ERROR(EPERM)); + + if ((error = zfs_zaccess(zp, ACE_WRITE_ACL, 0, skipaclchk, cr, NULL))) + return (error); + + error = zfs_vsec_2_aclp(zfsvfs, vnode_vtype(ZTOV(zp)), vsecp, cr, + &fuidp, &aclp); + if (error) + return (error); + + /* + * If ACL wide flags aren't being set then preserve any + * existing flags. + */ + // if (!(vsecp->vsa_mask & VSA_ACE_ACLFLAGS)) { + // aclp->z_hints |= + // (zp->z_pflags & V4_ACL_WIDE_FLAGS); + // } +top: + mutex_enter(&zp->z_acl_lock); + + tx = dmu_tx_create(zfsvfs->z_os); + + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE); + + fuid_dirtied = zfsvfs->z_fuid_dirty; + if (fuid_dirtied) + zfs_fuid_txhold(zfsvfs, tx); + + /* + * If old version and ACL won't fit in bonus and we aren't + * upgrading then take out necessary DMU holds + */ + + if ((acl_obj = zfs_external_acl(zp)) != 0) { + if (zfsvfs->z_version >= ZPL_VERSION_FUID && + zfs_znode_acl_version(zp) <= ZFS_ACL_VERSION_INITIAL) { + dmu_tx_hold_free(tx, acl_obj, 0, + DMU_OBJECT_END); + dmu_tx_hold_write(tx, DMU_NEW_OBJECT, 0, + aclp->z_acl_bytes); + } else { + dmu_tx_hold_write(tx, acl_obj, 0, aclp->z_acl_bytes); + } + } else if (!zp->z_is_sa && aclp->z_acl_bytes > ZFS_ACE_SPACE) { + dmu_tx_hold_write(tx, DMU_NEW_OBJECT, 0, aclp->z_acl_bytes); + } + + zfs_sa_upgrade_txholds(tx, zp); + error = dmu_tx_assign(tx, TXG_NOWAIT); + if (error) { + mutex_exit(&zp->z_acl_lock); + + if (error == ERESTART) { + dmu_tx_wait(tx); + dmu_tx_abort(tx); + goto top; + } + dmu_tx_abort(tx); + zfs_acl_free(aclp); + return (error); + } + + error = zfs_aclset_common(zp, aclp, cr, tx); + ASSERT(error == 0); + ASSERT(zp->z_acl_cached == NULL); + zp->z_acl_cached = aclp; + + if (fuid_dirtied) + zfs_fuid_sync(zfsvfs, tx); + + zfs_log_acl(zilog, tx, zp, vsecp, fuidp); + + if (fuidp) + zfs_fuid_info_free(fuidp); + dmu_tx_commit(tx); + mutex_exit(&zp->z_acl_lock); + + return (error); +} + +/* + * Check accesses of interest (AoI) against attributes of the dataset + * such as read-only. Returns zero if no AoI conflict with dataset + * attributes, otherwise an appropriate errno is returned. + */ +static int +zfs_zaccess_dataset_check(znode_t *zp, uint32_t v4_mode) +{ + if ((v4_mode & WRITE_MASK) && + (vfs_isrdonly(zp->z_zfsvfs->z_vfs)) && + (!IS_DEVVP(ZTOV(zp)) || + (IS_DEVVP(ZTOV(zp)) && (v4_mode & WRITE_MASK_ATTRS)))) { + return (SET_ERROR(EROFS)); + } + + /* + * Intentionally allow ZFS_READONLY through here. + * See zfs_zaccess_common(). + */ + if ((v4_mode & WRITE_MASK_DATA) && + (zp->z_pflags & ZFS_IMMUTABLE)) { + return (SET_ERROR(EPERM)); + } + + /* + * In FreeBSD we allow to modify directory's content is ZFS_NOUNLINK + * (sunlnk) is set. We just don't allow directory removal, which is + * handled in zfs_zaccess_delete(). + */ + if ((v4_mode & ACE_DELETE) && + (zp->z_pflags & ZFS_NOUNLINK)) { + return (EPERM); + } + + if (((v4_mode & (ACE_READ_DATA|ACE_EXECUTE)) && + (zp->z_pflags & ZFS_AV_QUARANTINED))) { + return (SET_ERROR(EACCES)); + } + + return (0); +} + +/* + * The primary usage of this function is to loop through all of the + * ACEs in the znode, determining what accesses of interest (AoI) to + * the caller are allowed or denied. The AoI are expressed as bits in + * the working_mode parameter. As each ACE is processed, bits covered + * by that ACE are removed from the working_mode. This removal + * facilitates two things. The first is that when the working mode is + * empty (= 0), we know we've looked at all the AoI. The second is + * that the ACE interpretation rules don't allow a later ACE to undo + * something granted or denied by an earlier ACE. Removing the + * discovered access or denial enforces this rule. At the end of + * processing the ACEs, all AoI that were found to be denied are + * placed into the working_mode, giving the caller a mask of denied + * accesses. Returns: + * 0 if all AoI granted + * EACCESS if the denied mask is non-zero + * other error if abnormal failure (e.g., IO error) + * + * A secondary usage of the function is to determine if any of the + * AoI are granted. If an ACE grants any access in + * the working_mode, we immediately short circuit out of the function. + * This mode is chosen by setting anyaccess to B_TRUE. The + * working_mode is not a denied access mask upon exit if the function + * is used in this manner. + */ +static int +zfs_zaccess_aces_check(znode_t *zp, uint32_t *working_mode, + boolean_t anyaccess, cred_t *cr) +{ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + zfs_acl_t *aclp; + int error; + uid_t uid = crgetuid(cr); + uint64_t who; + uint16_t type, iflags; + uint16_t entry_type; + uint32_t access_mask; + uint32_t deny_mask = 0; + zfs_ace_hdr_t *acep = NULL; + boolean_t checkit; + uid_t gowner; + uid_t fowner; + + zfs_fuid_map_ids(zp, cr, &fowner, &gowner); + + mutex_enter(&zp->z_acl_lock); + + error = zfs_acl_node_read(zp, B_TRUE, &aclp, B_FALSE); + if (error != 0) { + mutex_exit(&zp->z_acl_lock); + return (error); + } + + ASSERT(zp->z_acl_cached); + + while ((acep = zfs_acl_next_ace(aclp, acep, &who, &access_mask, + &iflags, &type))) { + uint32_t mask_matched; + + if (!zfs_acl_valid_ace_type(type, iflags)) + continue; + + if (vnode_vtype(ZTOV(zp)) == VDIR && + (iflags & ACE_INHERIT_ONLY_ACE)) + continue; + + /* Skip ACE if it does not affect any AoI */ + mask_matched = (access_mask & *working_mode); + if (!mask_matched) + continue; + + entry_type = (iflags & ACE_TYPE_FLAGS); + + checkit = B_FALSE; + + switch (entry_type) { + case ACE_OWNER: + if (uid == fowner) + checkit = B_TRUE; + break; + case OWNING_GROUP: + who = gowner; + /*FALLTHROUGH*/ + case ACE_IDENTIFIER_GROUP: + checkit = zfs_groupmember(zfsvfs, who, cr); + break; + case ACE_EVERYONE: + checkit = B_TRUE; + break; + + /* USER Entry */ + default: + if (entry_type == 0) { + uid_t newid; + + newid = zfs_fuid_map_id(zfsvfs, who, cr, + ZFS_ACE_USER); + if (newid != UID_NOBODY && + uid == newid) + checkit = B_TRUE; + break; + } else { + mutex_exit(&zp->z_acl_lock); + return (SET_ERROR(EIO)); + } + } + + if (checkit) { + if (type == DENY) { + DTRACE_PROBE3(zfs__ace__denies, + znode_t *, zp, + zfs_ace_hdr_t *, acep, + uint32_t, mask_matched); + deny_mask |= mask_matched; + } else { + DTRACE_PROBE3(zfs__ace__allows, + znode_t *, zp, + zfs_ace_hdr_t *, acep, + uint32_t, mask_matched); + if (anyaccess) { + mutex_exit(&zp->z_acl_lock); + return (0); + } + } + *working_mode &= ~mask_matched; + } + + /* Are we done? */ + if (*working_mode == 0) + break; + } + + mutex_exit(&zp->z_acl_lock); + + /* Put the found 'denies' back on the working mode */ + if (deny_mask) { + *working_mode |= deny_mask; + return (SET_ERROR(EACCES)); + } else if (*working_mode) { + return (-1); + } + + return (0); +} + +/* + * Return true if any access whatsoever granted, we don't actually + * care what access is granted. + */ +boolean_t +zfs_has_access(znode_t *zp, cred_t *cr) +{ + uint32_t have = ACE_ALL_PERMS; + + if (zfs_zaccess_aces_check(zp, &have, B_TRUE, cr) != 0) { + uid_t owner; + + owner = zfs_fuid_map_id(zp->z_zfsvfs, zp->z_uid, cr, ZFS_OWNER); + return (secpolicy_vnode_any_access(cr, ZTOV(zp), owner) == 0); + } + return (B_TRUE); +} + +static int +zfs_zaccess_common(znode_t *zp, uint32_t v4_mode, uint32_t *working_mode, + boolean_t *check_privs, boolean_t skipaclchk, cred_t *cr, + zidmap_t *mnt_ns) +{ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + int err; + + *working_mode = v4_mode; + *check_privs = B_TRUE; + + /* + * Short circuit empty requests + */ + if (v4_mode == 0 || zfsvfs->z_replay) { + *working_mode = 0; + return (0); + } + + if ((err = zfs_zaccess_dataset_check(zp, v4_mode)) != 0) { + *check_privs = B_FALSE; + return (err); + } + + /* + * The caller requested that the ACL check be skipped. This + * would only happen if the caller checked VOP_ACCESS() with a + * 32 bit ACE mask and already had the appropriate permissions. + */ + if (skipaclchk) { + *working_mode = 0; + return (0); + } + + /* + * Note: ZFS_READONLY represents the "DOS R/O" attribute. + * When that flag is set, we should behave as if write access + * were not granted by anything in the ACL. In particular: + * We _must_ allow writes after opening the file r/w, then + * setting the DOS R/O attribute, and writing some more. + * (Similar to how you can write after fchmod(fd, 0444).) + * + * Therefore ZFS_READONLY is ignored in the dataset check + * above, and checked here as if part of the ACL check. + * Also note: DOS R/O is ignored for directories. + */ + if ((v4_mode & WRITE_MASK_DATA) && + (vnode_vtype(ZTOV(zp)) != VDIR) && + (zp->z_pflags & ZFS_READONLY)) { + return (SET_ERROR(EPERM)); + } + + return (zfs_zaccess_aces_check(zp, working_mode, B_FALSE, cr)); +} + +static int +zfs_zaccess_append(znode_t *zp, uint32_t *working_mode, boolean_t *check_privs, + cred_t *cr, zidmap_t *mnt_ns) +{ + if (*working_mode != ACE_WRITE_DATA) + return (SET_ERROR(EACCES)); + + return (zfs_zaccess_common(zp, ACE_APPEND_DATA, working_mode, + check_privs, B_FALSE, cr, mnt_ns)); +} + +/* + * Check if VEXEC is allowed. + * + * This routine is based on zfs_fastaccesschk_execute which has slowpath + * calling zfs_zaccess. This would be incorrect on FreeBSD (see + * zfs_freebsd_access for the difference). Thus this variant let's the + * caller handle the slowpath (if necessary). + * + * On top of that we perform a lockless check for ZFS_NO_EXECS_DENIED. + * + * Safe access to znode_t is provided by the vnode lock. + */ +int +zfs_fastaccesschk_execute(znode_t *zdp, cred_t *cr) +{ + boolean_t is_attr; + + if (zdp->z_pflags & ZFS_AV_QUARANTINED) + return (1); + + is_attr = ((zdp->z_pflags & ZFS_XATTR) && + (vnode_vtype(ZTOV(zdp)) == VDIR)); + if (is_attr) + return (1); + + if (zdp->z_pflags & ZFS_NO_EXECS_DENIED) + return (0); + + return (1); +} + + +/* + * Determine whether Access should be granted/denied. + * + * The least priv subsystem is always consulted as a basic privilege + * can define any form of access. + */ +int +zfs_zaccess(znode_t *zp, int mode, int flags, boolean_t skipaclchk, cred_t *cr, + zidmap_t *mnt_ns) +{ + uint32_t working_mode; + int error; + int is_attr; + boolean_t check_privs; + znode_t *xzp = NULL; + znode_t *check_zp = zp; + mode_t needed_bits; + uid_t owner; + + is_attr = ((zp->z_pflags & ZFS_XATTR) && + (vnode_vtype(ZTOV(zp)) == VDIR)); + +#ifdef __APPLE__ + /* + * In APPLE, we don't care about permissions of individual ADS. + * Note that not checking them is not just an optimization - without + * this shortcut, EA operations may bogusly fail with EACCES. + */ + if (zp->z_pflags & ZFS_XATTR) + return (0); +#else + /* + * If attribute then validate against base file + */ + if (is_attr) { + uint64_t parent; + + if ((error = sa_lookup(zp->z_sa_hdl, + SA_ZPL_PARENT(zp->z_zfsvfs), &parent, + sizeof (parent))) != 0) + return (error); + + if ((error = zfs_zget(zp->z_zfsvfs, + parent, &xzp)) != 0) { + return (error); + } + + check_zp = xzp; + + /* + * fixup mode to map to xattr perms + */ + + if (mode & (ACE_WRITE_DATA|ACE_APPEND_DATA)) { + mode &= ~(ACE_WRITE_DATA|ACE_APPEND_DATA); + mode |= ACE_WRITE_NAMED_ATTRS; + } + + if (mode & (ACE_READ_DATA|ACE_EXECUTE)) { + mode &= ~(ACE_READ_DATA|ACE_EXECUTE); + mode |= ACE_READ_NAMED_ATTRS; + } + } +#endif + + owner = zfs_fuid_map_id(zp->z_zfsvfs, zp->z_uid, cr, ZFS_OWNER); + /* + * Map the bits required to the standard vnode flags VREAD|VWRITE|VEXEC + * in needed_bits. Map the bits mapped by working_mode (currently + * missing) in missing_bits. + * Call secpolicy_vnode_access2() with (needed_bits & ~checkmode), + * needed_bits. + */ + needed_bits = 0; + + working_mode = mode; + if ((working_mode & (ACE_READ_ACL|ACE_READ_ATTRIBUTES)) && + owner == crgetuid(cr)) + working_mode &= ~(ACE_READ_ACL|ACE_READ_ATTRIBUTES); + + if (working_mode & (ACE_READ_DATA|ACE_READ_NAMED_ATTRS| + ACE_READ_ACL|ACE_READ_ATTRIBUTES|ACE_SYNCHRONIZE)) + needed_bits |= VREAD; + if (working_mode & (ACE_WRITE_DATA|ACE_WRITE_NAMED_ATTRS| + ACE_APPEND_DATA|ACE_WRITE_ATTRIBUTES|ACE_SYNCHRONIZE)) + needed_bits |= VWRITE; + if (working_mode & ACE_EXECUTE) + needed_bits |= VEXEC; + + if ((error = zfs_zaccess_common(check_zp, mode, &working_mode, + &check_privs, skipaclchk, cr, mnt_ns)) == 0) { + if (is_attr) + VN_RELE(ZTOV(xzp)); + return (secpolicy_vnode_access2(cr, ZTOV(zp), owner, + needed_bits, needed_bits)); + } + + if (error && !check_privs) { + if (is_attr) + VN_RELE(ZTOV(xzp)); + return (error); + } + + if (error && (flags & V_APPEND)) { + error = zfs_zaccess_append(zp, &working_mode, &check_privs, cr, + mnt_ns); + } + + if (error && check_privs) { + mode_t checkmode = 0; + vnode_t *check_vp = ZTOV(check_zp); + + /* + * First check for implicit owner permission on + * read_acl/read_attributes + */ + + error = 0; + ASSERT(working_mode != 0); + + if ((working_mode & (ACE_READ_ACL|ACE_READ_ATTRIBUTES) && + owner == crgetuid(cr))) + working_mode &= ~(ACE_READ_ACL|ACE_READ_ATTRIBUTES); + + if (working_mode & (ACE_READ_DATA|ACE_READ_NAMED_ATTRS| + ACE_READ_ACL|ACE_READ_ATTRIBUTES|ACE_SYNCHRONIZE)) + checkmode |= VREAD; + if (working_mode & (ACE_WRITE_DATA|ACE_WRITE_NAMED_ATTRS| + ACE_APPEND_DATA|ACE_WRITE_ATTRIBUTES|ACE_SYNCHRONIZE)) + checkmode |= VWRITE; + if (working_mode & ACE_EXECUTE) + checkmode |= VEXEC; + + error = secpolicy_vnode_access2(cr, check_vp, owner, + needed_bits & ~checkmode, needed_bits); + + if (error == 0 && (working_mode & ACE_WRITE_OWNER)) + error = secpolicy_vnode_chown(check_vp, cr, owner); + if (error == 0 && (working_mode & ACE_WRITE_ACL)) + error = secpolicy_vnode_setdac(check_vp, cr, owner); + + if (error == 0 && (working_mode & + (ACE_DELETE|ACE_DELETE_CHILD))) + error = secpolicy_vnode_remove(check_vp, cr); + + if (error == 0 && (working_mode & ACE_SYNCHRONIZE)) { + error = secpolicy_vnode_chown(check_vp, cr, owner); + } + if (error == 0) { + /* + * See if any bits other than those already checked + * for are still present. If so then return EACCES + */ + if (working_mode & ~(ZFS_CHECKED_MASKS)) { + error = SET_ERROR(EACCES); + } + } + } else if (error == 0) { + error = secpolicy_vnode_access2(cr, ZTOV(zp), owner, + needed_bits, needed_bits); + } + + + if (is_attr) + VN_RELE(ZTOV(xzp)); + + return (error); +} + +/* + * Translate traditional unix VREAD/VWRITE/VEXEC mode into + * NFSv4-style ZFS ACL format and call zfs_zaccess() + */ +int +zfs_zaccess_rwx(znode_t *zp, mode_t mode, int flags, cred_t *cr, + zidmap_t *mnt_ns) +{ + return (zfs_zaccess(zp, zfs_unix_to_v4(mode >> 6), flags, B_FALSE, cr, + mnt_ns)); +} + +/* + * Access function for secpolicy_vnode_setattr + */ +int +zfs_zaccess_unix(void *zp, int mode, cred_t *cr) +{ + int v4_mode = zfs_unix_to_v4(mode >> 6); + + return (zfs_zaccess(zp, v4_mode, 0, B_FALSE, cr, NULL)); +} + +static int +zfs_delete_final_check(znode_t *zp, znode_t *dzp, + mode_t available_perms, cred_t *cr) +{ + int error; + uid_t downer; + + downer = zfs_fuid_map_id(dzp->z_zfsvfs, dzp->z_uid, cr, ZFS_OWNER); + + error = secpolicy_vnode_access2(cr, ZTOV(dzp), + downer, available_perms, VWRITE|VEXEC); + + if (error == 0) + error = zfs_sticky_remove_access(dzp, zp, cr); + + return (error); +} + +uint64_t zfs_write_implies_delete_child = 1; + +/* + * Determine whether Access should be granted/deny, without + * consulting least priv subsystem. + * + * The following chart is the recommended NFSv4 enforcement for + * ability to delete an object. + * + * ------------------------------------------------------- + * | Parent Dir | Target Object Permissions | + * | permissions | | + * ------------------------------------------------------- + * | | ACL Allows | ACL Denies| Delete | + * | | Delete | Delete | unspecified| + * ------------------------------------------------------- + * | ACL Allows | Permit | Permit | Permit | + * | DELETE_CHILD | | + * ------------------------------------------------------- + * | ACL Denies | Permit | Deny | Deny | + * | DELETE_CHILD | | | | + * ------------------------------------------------------- + * | ACL specifies | | | | + * | only allow | Permit | Permit | Permit | + * | write and | | | | + * | execute | | | | + * ------------------------------------------------------- + * | ACL denies | | | | + * | write and | Permit | Deny | Deny | + * | execute | | | | + * ------------------------------------------------------- + * ^ + * | + * No search privilege, can't even look up file? + * + */ +int +zfs_zaccess_delete(znode_t *dzp, znode_t *zp, cred_t *cr, zidmap_t *mnt_ns) +{ + uint32_t dzp_working_mode = 0; + uint32_t zp_working_mode = 0; + int dzp_error, zp_error; + mode_t available_perms; + boolean_t dzpcheck_privs = B_TRUE; + boolean_t zpcheck_privs = B_TRUE; + + /* + * We want specific DELETE permissions to + * take precedence over WRITE/EXECUTE. We don't + * want an ACL such as this to mess us up. + * user:joe:write_data:deny,user:joe:delete:allow + * + * However, deny permissions may ultimately be overridden + * by secpolicy_vnode_access(). + * + * We will ask for all of the necessary permissions and then + * look at the working modes from the directory and target object + * to determine what was found. + */ + + if (zp->z_pflags & (ZFS_IMMUTABLE | ZFS_NOUNLINK)) + return (SET_ERROR(EPERM)); + + /* + * First row + * If the directory permissions allow the delete, we are done. + */ + if ((dzp_error = zfs_zaccess_common(dzp, ACE_DELETE_CHILD, + &dzp_working_mode, &dzpcheck_privs, B_FALSE, cr, mnt_ns)) == 0) + return (0); + + /* + * If target object has delete permission then we are done + */ + if ((zp_error = zfs_zaccess_common(zp, ACE_DELETE, &zp_working_mode, + &zpcheck_privs, B_FALSE, cr, mnt_ns)) == 0) + return (0); + + ASSERT(dzp_error && zp_error); + + if (!dzpcheck_privs) + return (dzp_error); + if (!zpcheck_privs) + return (zp_error); + + /* + * Second row + * + * If directory returns EACCES then delete_child was denied + * due to deny delete_child. In this case send the request through + * secpolicy_vnode_remove(). We don't use zfs_delete_final_check() + * since that *could* allow the delete based on write/execute permission + * and we want delete permissions to override write/execute. + */ + + if (dzp_error == EACCES) { + /* XXXPJD: s/dzp/zp/ ? */ + return (secpolicy_vnode_remove(ZTOV(dzp), cr)); + } + /* + * Third Row + * only need to see if we have write/execute on directory. + */ + + dzp_error = zfs_zaccess_common(dzp, ACE_EXECUTE|ACE_WRITE_DATA, + &dzp_working_mode, &dzpcheck_privs, B_FALSE, cr, mnt_ns); + + if (dzp_error != 0 && !dzpcheck_privs) + return (dzp_error); + + /* + * Fourth row + */ + + available_perms = (dzp_working_mode & ACE_WRITE_DATA) ? 0 : VWRITE; + available_perms |= (dzp_working_mode & ACE_EXECUTE) ? 0 : VEXEC; + + return (zfs_delete_final_check(zp, dzp, available_perms, cr)); + +} + +int +zfs_zaccess_rename(znode_t *sdzp, znode_t *szp, znode_t *tdzp, + znode_t *tzp, cred_t *cr, zidmap_t *mnt_ns) +{ + int add_perm; + int error; + + if (szp->z_pflags & ZFS_AV_QUARANTINED) + return (SET_ERROR(EACCES)); + + add_perm = (vnode_vtype(ZTOV(szp)) == VDIR) ? + ACE_ADD_SUBDIRECTORY : ACE_ADD_FILE; + + /* + * Rename permissions are combination of delete permission + + * add file/subdir permission. + * + * BSD operating systems also require write permission + * on the directory being moved from one parent directory + * to another. + */ + if (vnode_vtype(ZTOV(szp)) == VDIR && ZTOV(sdzp) != ZTOV(tdzp)) { + if ((error = zfs_zaccess(szp, ACE_WRITE_DATA, 0, B_FALSE, cr, + mnt_ns))) + return (error); + } + + /* + * first make sure we do the delete portion. + * + * If that succeeds then check for add_file/add_subdir permissions + */ + + if ((error = zfs_zaccess_delete(sdzp, szp, cr, mnt_ns))) + return (error); + + /* + * If we have a tzp, see if we can delete it? + */ + if (tzp && (error = zfs_zaccess_delete(tdzp, tzp, cr, mnt_ns))) + return (error); + + /* + * Now check for add permissions + */ + error = zfs_zaccess(tdzp, add_perm, 0, B_FALSE, cr, mnt_ns); + + return (error); +} diff --git a/module/os/macos/zfs/zfs_boot.cpp b/module/os/macos/zfs/zfs_boot.cpp new file mode 100644 index 000000000000..84a1608c0e2e --- /dev/null +++ b/module/os/macos/zfs/zfs_boot.cpp @@ -0,0 +1,2981 @@ +/* + * 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) 2015, Evan Susarret. All rights reserved. + */ +/* + * ZFS boot utils + * + * While loading the kext, check if early boot and zfs-boot + * kernel flag. + * Allocate pool_list (and lock). + * Register matching notification zfs_boot_probe_disk to check + * IOMediaBSDClient devices as they are published (or matched?), + * passing pool_list (automatically calls handler for all + * existing devices). + * Dispatch zfs_boot_import_thread on zfs_boot_taskq. + * + * In notification handler zfs_boot_probe_disk: + * Check provider IOMedia for: + * 1 Leaf node and whole disk. + * 2 Leaf node and type ZFS. + * 3 Leaf node and type FreeBSD-ZFS. + * Check IOMedia meets minimum size or bail. + * Allocate char* buffer. + * Call vdev_disk_read_rootlabel. + * XXX Alternately: + * Alloc and prep IOMemoryDescriptor. + * Open IOMedia device (read-only). + * Try to read vdev label from device. + * Close IOMedia device. + * Release IOMemoryDescriptor (data is in buffer). + * XXX + * If label was read, try to generate a config from label. + * Check pool name matches zfs-boot or bail. + * Check pool status. + * Update this vdev's path and set status. + * Set other vdevs to missing status. + * Check-in config in thread-safe manner: + * Take pool_list lock. + * If config not found, insert new config, or update existing. + * Unlock pool_list. + * If found config is complete, wake import thread. + * + * In vdev_disk_read_rootlabel: + * Use vdev_disk_physio to read label. + * If label was read, try to unpack. + * Return label or failure. + * + * In vdev_disk_physio: + * Open device (read-only) using vnop/VOP. + * Try to read vdev label from device. + * Close device using vnop/VOP. + * + * In zfs_boot_import_thread: + * Loop checking for work and sleeping on lock between loops. + * Take pool_list lock and check for work. + * Attempt to import root pool using spa_import_rootpool. + * If successful, remove notification handler (waits for + * all tasks). + * Empty and deallocate pool_list (and lock). + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +int dsl_dsobj_to_dsname(char *pname, uint64_t obj, char *buf); + +} /* extern "C" */ + +static taskq_t *zfs_boot_taskq; + +#include +#include +#include +#include + +#if defined(DEBUG) || defined(ZFS_DEBUG) +#define DSTATIC +#else +#define DSTATIC static +#endif + +#ifndef verify +#define verify(EX) (void)((EX) || \ + (printf("%s, %s, %d, %s\n", #EX, __FILE__, __LINE__, __func__), 0)) +#endif /* verify */ + +/* Most of this is only built when configured with --enable-boot */ + +/* block size is 512 B, count is 512 M blocks */ +#define ZFS_BOOT_DEV_BSIZE (UInt64)(1<<9) +#define ZFS_BOOT_DEV_BCOUNT (UInt64)(2<<29) +#define ZFS_BOOT_DATASET_NAME_KEY "zfs_dataset_name" +#define ZFS_BOOT_DATASET_UUID_KEY "zfs_dataset_uuid" +#define ZFS_BOOT_DATASET_RDONLY_KEY "zfs_dataset_rdonly" +#define ZFS_MOUNTROOT_RETRIES 50 +#define ZFS_BOOTLOG_DELAY 100 + +/* + * C functions for boot-time vdev discovery + */ + +/* + * 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; + uint64_t complete; +} 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; + uint64_t pool_guid; + char *pool_name; + OSSet *new_disks; + OSSet *disks; + kmutex_t lock; + kcondvar_t cv; + IOService *zfs_hl; + IONotifier *notifier; + _Atomic UInt64 terminating; +} pool_list_t; + +#define ZFS_BOOT_ACTIVE 0x1 +#define ZFS_BOOT_TERMINATING 0x2 +#define ZFS_BOOT_INVALID 0x99 + +#define ZFS_BOOT_PREALLOC_SET 5 + +#if 0 +static ZFSBootDevice *bootdev = 0; +#endif +static pool_list_t *zfs_boot_pool_list = 0; + +DSTATIC char * +zfs_boot_get_devid(const char *path) +{ + /* + * XXX Unavailable interface + * + * If we implement one in spl, it could + * simplify import when device paths + * have changed (e.g. USB pools). + * + * Could use ldi DeviceTree path, or + * IOService path if not in DTPlane. + */ + return (NULL); +} + +/* + * Go through and fix up any path and/or devid information for the given vdev + * configuration. + * + * Copied from libzfs_import.c + */ +DSTATIC int +zfs_boot_fix_paths(nvlist_t *nv, name_entry_t *names) +{ + nvlist_t **child; + uint_t c, children; + uint64_t guid; + name_entry_t *ne, *best; + const char *path; + char *devid; + + if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN, + &child, &children) == 0) { + for (c = 0; c < children; c++) + if (zfs_boot_fix_paths(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 more 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); + + if ((devid = zfs_boot_get_devid(best->ne_name)) == NULL) { + (void) nvlist_remove_all(nv, ZPOOL_CONFIG_DEVID); + } else { + if (nvlist_add_string(nv, ZPOOL_CONFIG_DEVID, devid) != 0) { + spa_strfree(devid); + return (-1); + } + spa_strfree(devid); + } + + return (0); +} + +/* + * Add the given configuration to the list of known devices. + * + * Copied from libzfs_import.c + * diffs: kmem_alloc, kmem_free with size + */ +DSTATIC int +zfs_boot_add_config(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; + + dprintf("%s %p [%s] %d %d %p\n", __func__, + pl, path, order, num_labels, config); + + /* + * 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 = (name_entry_t *)kmem_alloc( + sizeof (name_entry_t), KM_SLEEP)) == NULL) { + return (-1); + } + memset(ne, 0, sizeof (name_entry_t)); + + if ((ne->ne_name = spa_strdup(path)) == NULL) { + kmem_free(ne, sizeof (name_entry_t)); + 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) { + nvlist_free(config); + 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 = (pool_entry_t *)kmem_alloc( + sizeof (pool_entry_t), KM_SLEEP)) == NULL) { + nvlist_free(config); + return (-1); + } + memset(pe, 0, sizeof (pool_entry_t)); + 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 = (vdev_entry_t *)kmem_alloc( + sizeof (vdev_entry_t), KM_SLEEP)) == NULL) { + nvlist_free(config); + return (-1); + } + memset(ve, 0, sizeof (vdev_entry_t)); + 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 = (config_entry_t *)kmem_alloc( + sizeof (config_entry_t), KM_SLEEP)) == NULL) { + nvlist_free(config); + return (-1); + } + memset(ce, 0, sizeof (config_entry_t)); + ce->ce_txg = txg; + ce->ce_config = config; + ce->ce_next = ve->ve_configs; + ve->ve_configs = ce; + } else { + nvlist_free(config); + } + + /* + * 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 = (name_entry_t *)kmem_alloc( + sizeof (name_entry_t), KM_SLEEP)) == NULL) { + return (-1); + } + memset(ne, 0, sizeof (name_entry_t)); + + if ((ne->ne_name = spa_strdup(path)) == NULL) { + kmem_free(ne, sizeof (name_entry_t)); + 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); +} + +/* + * libzfs_import used the libzfs handle and a zfs + * command to issue tryimport in-kernel via ioctl. + * This should leave config as-is, and return nvl. + * Since zfs_boot is already in-kernel, duplicate + * config into nvl, and call spa_tryimport on it. + */ +DSTATIC nvlist_t * +zfs_boot_refresh_config(nvlist_t *config) +{ + nvlist_t *nvl = 0; + + /* tryimport does not free config, and returns new nvl or null */ + nvl = spa_tryimport(config); + return (nvl); +} + +/* + * Determine if the vdev id is a hole in the namespace. + */ +DSTATIC boolean_t +zfs_boot_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. + */ +DSTATIC nvlist_t * +zfs_boot_get_configs(pool_list_t *pl, boolean_t active_ok) +{ + 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; + const 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; +#if 0 + boolean_t isactive; +#endif + 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 + * pool txg (if available) + * comment (if available) + * pool state + * hostid (if available) + * hostname (if available) + */ + uint64_t state, version, pool_txg; + const 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_uint64(tmp, + ZPOOL_CONFIG_POOL_TXG, &pool_txg) == 0) + fnvlist_add_uint64(config, + ZPOOL_CONFIG_POOL_TXG, pool_txg); + + 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 = (nvlist_t **)kmem_alloc((id + 1) * + sizeof (nvlist_t *), KM_SLEEP); + if (newchild == NULL) + goto nomem; + + for (c = 0; c < children; c++) + newchild[c] = child[c]; + + kmem_free(child, children * + sizeof (nvlist_t *)); + 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 = (nvlist_t **)kmem_alloc((max_id) * + sizeof (nvlist_t *), KM_SLEEP); + if (newchild == NULL) + goto nomem; + + for (c = 0; c < children; c++) + newchild[c] = child[c]; + + kmem_free(child, children * + sizeof (nvlist_t *)); + 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 || + !zfs_boot_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]); + kmem_free(child, children * sizeof (nvlist_t *)); + 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 (zfs_boot_fix_paths(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; + +#if 0 +/* + * For root-pool import, no pools are active yet. + * Pool name and guid were looked up from the config and only used here. + * (Later we lookup the pool name for a separate test). + */ + /* + * 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 (zfs_boot_pool_active(name, guid, &isactive) != 0) + goto error; + + if (isactive) { + nvlist_free(config); + config = NULL; + continue; + } +#endif + + if ((nvl = zfs_boot_refresh_config(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 (zfs_boot_fix_paths(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 (zfs_boot_fix_paths(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: +#ifdef DEBUG + printf("zfs_boot_get_configs failed to allocate memory\n"); +#endif + if (config) nvlist_free(config); + if (ret) nvlist_free(ret); + for (c = 0; c < children; c++) + nvlist_free(child[c]); + if (children > 0) { + kmem_free(child, children * sizeof (nvlist_t *)); + } + /* + * libzfs_import simply calls free(child), we need to + * pass kmem_free the size of the array. Array is + * allocated above as (children * sizeof nvlist_t*). + */ + + return (NULL); +} + +/* + * Return the offset of the given label. + */ +DSTATIC uint64_t +zfs_boot_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 an IOMedia, 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. + */ +DSTATIC int +zfs_boot_read_label(IOService *zfs_hl, IOMedia *media, + nvlist_t **config, int *num_labels) +{ + IOMemoryDescriptor *buffer = NULL; + uint64_t mediaSize; + uint64_t nread = 0; + vdev_label_t *label; + nvlist_t *expected_config = NULL; + uint64_t expected_guid = 0, size, labelsize; + int l, count = 0; + IOReturn ret; + + *config = NULL; + + /* Verify IOMedia pointer and device size */ + if (!media || (mediaSize = media->getSize()) == 0) { + dprintf("%s couldn't get media or size\n", __func__); + return (-1); + } + + /* Determine vdev label size and aligned vdev size */ + labelsize = sizeof (vdev_label_t); + size = P2ALIGN_TYPED(mediaSize, labelsize, uint64_t); + + /* Allocate a buffer to read labels into */ + label = (vdev_label_t *)kmem_alloc(labelsize, KM_SLEEP); + if (!label) { + dprintf("%s couldn't allocate label for read\n", __func__); + return (-1); + } + + /* Allocate a memory descriptor with the label pointer */ + buffer = IOMemoryDescriptor::withAddress((void*)label, labelsize, + kIODirectionIn); + + /* Verify buffer was allocated */ + if (!buffer || (buffer->getLength() != labelsize)) { + dprintf("%s couldn't allocate buffer for read\n", __func__); + goto error; + } + + /* Open the device for reads */ + if (false == media->IOMedia::open(zfs_hl, 0, + kIOStorageAccessReader)) { + dprintf("%s media open failed\n", __func__); + goto error; + } + + /* Read all four vdev labels */ + for (l = 0; l < VDEV_LABELS; l++) { + uint64_t state, guid, txg; + + /* Zero the label buffer */ + memset(label, 0, labelsize); + + /* Prepare the buffer for IO */ + buffer->prepare(kIODirectionIn); + + /* Read a label from the specified offset */ + ret = media->IOMedia::read(zfs_hl, + zfs_boot_label_offset(size, l), + buffer, 0, &nread); + + /* Call the buffer completion */ + buffer->complete(); + + /* Skip failed reads, try next label */ + if (ret != kIOReturnSuccess) { + dprintf("%s media->read failed\n", __func__); + continue; + } + + /* Skip incomplete reads, try next label */ + if (nread < labelsize) { + dprintf("%s nread %llu / %llu\n", + __func__, nread, labelsize); + continue; + } + + /* Skip invalid labels that can't be unpacked */ + if (nvlist_unpack(label->vl_vdev_phys.vp_nvlist, + sizeof (label->vl_vdev_phys.vp_nvlist), config, 0) != 0) + continue; + + /* Verify GUID */ + if (nvlist_lookup_uint64(*config, ZPOOL_CONFIG_GUID, + &guid) != 0 || guid == 0) { + dprintf("%s nvlist_lookup guid failed %llu\n", + __func__, guid); + nvlist_free(*config); + continue; + } + + /* Verify vdev state */ + if (nvlist_lookup_uint64(*config, ZPOOL_CONFIG_POOL_STATE, + &state) != 0 || state > POOL_STATE_L2CACHE) { + dprintf("%s nvlist_lookup state failed %llu\n", + __func__, state); + nvlist_free(*config); + continue; + } + + /* Verify txg number */ + if (state != POOL_STATE_SPARE && state != POOL_STATE_L2CACHE && + (nvlist_lookup_uint64(*config, ZPOOL_CONFIG_POOL_TXG, + &txg) != 0 || txg == 0)) { + dprintf("%s nvlist_lookup txg failed %llu\n", + __func__, txg); + nvlist_free(*config); + continue; + } + + /* Increment count for first match, or if guid matches */ + if (expected_guid) { + if (expected_guid == guid) + count++; + + nvlist_free(*config); + } else { + expected_config = *config; + expected_guid = guid; + count++; + } + } + + /* Close IOMedia */ + media->close(zfs_hl); + + /* Copy out the config and number of labels */ + if (num_labels != NULL) + *num_labels = count; + + kmem_free(label, labelsize); + buffer->release(); + *config = expected_config; + + return (0); + + +error: + /* Clean up */ + if (buffer) { + buffer->release(); + buffer = 0; + } + if (label) { + kmem_free(label, labelsize); + label = 0; + } + + return (-1); +} + +DSTATIC bool +zfs_boot_probe_media(void* target, void* refCon, + IOService* newService, __unused IONotifier* notifier) +{ + IOMedia *media = 0; + OSObject *isLeaf = 0; + OSString *ospath = 0; + uint64_t mediaSize = 0; + pool_list_t *pools = (pool_list_t *)refCon; + + /* Verify pool list can be cast */ + if (!pools) { + dprintf("%s invalid refCon\n", __func__); + return (false); + } + /* Should never happen */ + if (!newService) { + printf("%s %s\n", "zfs_boot_probe_media", + "called with null newService"); + return (false); + } + + /* Abort early */ + if (pools->terminating != ZFS_BOOT_ACTIVE) { + dprintf("%s terminating 1\n", __func__); + return (false); + } + + /* Validate pool name */ + if (!pools->pool_name || strlen(pools->pool_name) == 0) { + dprintf("%s no pool name specified\n", __func__); + return (false); + } + + /* Get the parent IOMedia device */ + media = OSDynamicCast(IOMedia, newService->getProvider()); + + if (!media) { + dprintf("%s couldn't be cast as IOMedia\n", + __func__); + return (false); + } + + isLeaf = media->getProperty(kIOMediaLeafKey); + if (!isLeaf) { + dprintf("%s skipping non-leaf\n", __func__); + goto out; + } + + mediaSize = media->getSize(); + if (mediaSize < SPA_MINDEVSIZE) { + dprintf("%s skipping device with size %llu\n", + __func__, mediaSize); + goto out; + } + + ospath = OSDynamicCast(OSString, media->getProperty( + kIOBSDNameKey, gIOServicePlane, + kIORegistryIterateRecursively)); + if (!ospath || (ospath->getLength() == 0)) { + dprintf("%s skipping device with no bsd disk node\n", + __func__); + goto out; + } + + /* Abort early */ + if (pools->terminating != ZFS_BOOT_ACTIVE) { + dprintf("%s terminating 2\n", __func__); + goto out; + } + + /* Take pool_list lock */ + mutex_enter(&pools->lock); + + /* Abort early */ + if (pools->terminating != ZFS_BOOT_ACTIVE) { + dprintf("%s terminating 3\n", __func__); + /* Unlock the pool list lock */ + mutex_exit(&pools->lock); + goto out; + } + + /* Add this IOMedia to the disk set */ + pools->disks->setObject(media); + + /* Unlock the pool list lock */ + mutex_exit(&pools->lock); + + /* Wakeup zfs_boot_import_thread */ + cv_signal(&pools->cv); + +out: + media = 0; + return (true); +} + +DSTATIC bool +zfs_boot_probe_disk(pool_list_t *pools, IOMedia *media) +{ + OSString *ospath, *uuid; + char *path = 0; + const char *pname; + const char prefix[] = "/private/var/run/disk/by-id/media-"; + uint64_t this_guid; + int num_labels, err, len = 0; + nvlist_t *config; + boolean_t matched = B_FALSE; + + dprintf("%s: with %s media\n", __func__, + (media ? "valid" : "missing")); + ASSERT3U(media, !=, NULL); + + /* Verify pool list can be cast */ + if (!pools) { + dprintf("%s missing pool_list\n", __func__); + return (false); + } + + /* Abort early */ + if (pools->terminating != ZFS_BOOT_ACTIVE) { + dprintf("%s terminating 1\n", __func__); + return (false); + } + + /* Validate pool name */ + if (!pools->pool_name || strlen(pools->pool_name) == 0) { + dprintf("%s no pool name specified\n", __func__); + return (false); + } + + /* Try to get a UUID from the media */ + uuid = OSDynamicCast(OSString, media->getProperty(kIOMediaUUIDKey)); + if (uuid && uuid->getLength() != 0) { + /* Allocate room for prefix, UUID, and null terminator */ + len = (strlen(prefix) + uuid->getLength()) + 1; + + path = (char *)kmem_alloc(len, KM_SLEEP); + if (!path) { + dprintf("%s couldn't allocate path\n", __func__); + return (false); + } + + snprintf(path, len, "%s%s", prefix, uuid->getCStringNoCopy()); + uuid = 0; + } else { + /* Get the BSD name as a C string */ + ospath = OSDynamicCast(OSString, media->getProperty( + kIOBSDNameKey, gIOServicePlane, + kIORegistryIterateRecursively)); + if (!ospath || (ospath->getLength() == 0)) { + dprintf("%s skipping device with no bsd disk node\n", + __func__); + return (false); + } + + /* Allocate room for "/dev/" + "diskNsN" + '\0' */ + len = (strlen("/dev/") + ospath->getLength() + 1); + path = (char *)kmem_alloc(len, KM_SLEEP); + if (!path) { + dprintf("%s couldn't allocate path\n", __func__); + return (false); + } + + /* "/dev/" is 5 characters, plus null character */ + snprintf(path, len, "/dev/%s", ospath->getCStringNoCopy()); + ospath = 0; + } + dprintf("%s path [%s]\n", __func__, (path ? path : "")); + + /* Read vdev labels, if any */ + err = zfs_boot_read_label(pools->zfs_hl, media, + &config, &num_labels); + + /* Skip disks with no labels */ + if (err != 0 || num_labels == 0 || !config) { + goto out; + } + + /* Lookup pool name */ + if (pools->pool_name != NULL && + (nvlist_lookup_string(config, ZPOOL_CONFIG_POOL_NAME, + &pname) == 0)) { + /* Compare with pool_name */ + if (strlen(pools->pool_name) == strlen(pname) && + strncmp(pools->pool_name, pname, + strlen(pname)) == 0) { + printf("%s matched pool %s\n", + __func__, pname); + matched = B_TRUE; + } + /* Compare with pool_guid */ + } else if (pools->pool_guid != 0) { + matched = nvlist_lookup_uint64(config, + ZPOOL_CONFIG_POOL_GUID, + &this_guid) == 0 && + pools->pool_guid == this_guid; + } + + /* Skip non-matches */ + if (!matched) { + nvlist_free(config); + config = NULL; + goto out; + } + + /* + * Add this config to the pool list. + * Always assigns order 1 since all disks are + * referenced by /private/var/run/disk/by-id/ paths. + */ + dprintf("%s: add_config %s\n", __func__, path); + if (zfs_boot_add_config(pools, path, 1, + num_labels, config) != 0) { + printf("%s couldn't add config to pool list\n", + __func__); + } + +out: + /* Clean up */ + if (path && len > 0) { + kmem_free(path, len); + } + return (true); +} + +DSTATIC void +zfs_boot_free() +{ + pool_entry_t *pe, *penext; + vdev_entry_t *ve, *venext; + config_entry_t *ce, *cenext; + name_entry_t *ne, *nenext; + pool_list_t *pools = zfs_boot_pool_list; + + /* Verify pool list can be cast */ + if (!pools) { + dprintf("%s: no pool_list to clear\n", __func__); + return; + } + + /* Clear global ptr */ + zfs_boot_pool_list = 0; + + pools->terminating = ZFS_BOOT_TERMINATING; + + /* Remove IONotifier (waits for tasks to complete) */ + if (pools->notifier) { + pools->notifier->remove(); + pools->notifier = 0; + } + + /* Release the lock */ + mutex_destroy(&pools->lock); + + /* Release the disk set */ + if (pools->disks) { + pools->disks->flushCollection(); + pools->disks->release(); + pools->disks = 0; + } + + /* Clear the zfs IOService handle */ + if (pools->zfs_hl) { + pools->zfs_hl = 0; + } + + /* Free the pool_name string */ + if (pools->pool_name) { + kmem_free(pools->pool_name, strlen(pools->pool_name) + 1); + pools->pool_name = 0; + } + + /* Clear the pool config list */ + for (pe = pools->pools; pe != NULL; pe = penext) { + /* Clear the vdev list */ + penext = pe->pe_next; + for (ve = pe->pe_vdevs; ve != NULL; ve = venext) { + /* Clear the vdev config list */ + venext = ve->ve_next; + for (ce = ve->ve_configs; ce != NULL; ce = cenext) { + cenext = ce->ce_next; + if (ce->ce_config) + nvlist_free(ce->ce_config); + kmem_free(ce, sizeof (config_entry_t)); + } + kmem_free(ve, sizeof (vdev_entry_t)); + } + kmem_free(pe, sizeof (pool_entry_t)); + } + pools->pools = 0; + + /* Clear the vdev name list */ + for (ne = pools->names; ne != NULL; ne = nenext) { + nenext = ne->ne_next; + if (ne->ne_name) + spa_strfree(ne->ne_name); + kmem_free(ne, sizeof (name_entry_t)); + } + pools->names = 0; + + /* Finally, free the pool list struct */ + kmem_free(pools, sizeof (pool_list_t)); + pools = 0; +} + +void +zfs_boot_fini() +{ + pool_list_t *pools = zfs_boot_pool_list; + + if (!pools) { + printf("%s no pool_list to clear\n", __func__); + return; + } + + /* Set terminating flag */ + if (false == OSCompareAndSwap64(ZFS_BOOT_ACTIVE, + ZFS_BOOT_TERMINATING, &(pools->terminating))) { + printf("%s already terminating? %llu\n", + __func__, pools->terminating); + } + + /* Wakeup zfs_boot_import_thread */ + cv_signal(&pools->cv); + + taskq_wait(zfs_boot_taskq); + taskq_destroy(zfs_boot_taskq); + + /* Clean up */ + pools = 0; +} + +#define kBootUUIDKey "boot-uuid" +#define kBootUUIDMediaKey "boot-uuid-media" + +DSTATIC int +zfs_boot_publish_bootfs(IOService *zfs_hl, pool_list_t *pools) +{ + ZFSDataset *dataset = NULL; + IOMedia *media; + IOService *resourceService = NULL; + OSDictionary *properties = NULL; + spa_t *spa = NULL; + char *zfs_bootfs = NULL; + uint64_t bootfs = 0ULL; + int error, len = ZFS_MAX_DATASET_NAME_LEN; + + dprintf("%s\n", __func__); + if (!zfs_hl || !pools) { + dprintf("%s missing argument\n", __func__); + return (EINVAL); + } + +#if 0 + ZFSPool *pool_proxy = NULL; + if (bootdev) { + dprintf("%s bootdev already set\n", __func__); + return (EBUSY); + } +#endif + + zfs_bootfs = (char *)kmem_alloc(len, KM_SLEEP); + if (!zfs_bootfs) { + printf("%s string alloc failed\n", __func__); + return (ENOMEM); + } + zfs_bootfs[0] = '\0'; + + mutex_enter(&spa_namespace_lock); + spa = spa_next(NULL); + if (spa) { + bootfs = spa_bootfs(spa); + } + if (bootfs == 0) { + mutex_exit(&spa_namespace_lock); + dprintf("%s no bootfs, nothing to do\n", __func__); + kmem_free(zfs_bootfs, len); + return (0); + } + +#if 0 + /* Get pool proxy */ + if (!spa->spa_iokit_proxy || + (pool_proxy = spa->spa_iokit_proxy->proxy) == NULL) { + mutex_exit(&spa_namespace_lock); + dprintf("%s no spa_pool_proxy\n", __func__); + kmem_free(zfs_bootfs, len); + return (0); + } +#endif + + error = dsl_dsobj_to_dsname(spa_name(spa), + spa_bootfs(spa), zfs_bootfs); + mutex_exit(&spa_namespace_lock); + + if (error != 0) { + dprintf("%s bootfs to name failed\n", __func__); + kmem_free(zfs_bootfs, len); + return (ENODEV); + } + + printf("%s: publishing bootfs [%s]\n", __func__, zfs_bootfs); + + /* Create prop dict for the proxy, with 6 or more keys */ + if ((properties = OSDictionary::withCapacity(6)) == NULL) { + dprintf("%s prop dict allocation failed\n", __func__); + kmem_free(zfs_bootfs, len); + return (ENOMEM); + } + + /* Set Content Hint and Content */ + do { + const OSSymbol *partUUID; + + /* ZFS (BF01) partition type */ + if ((partUUID = OSSymbol::withCString( + "6A898CC3-1DD2-11B2-99A6-080020736631")) == NULL) { + dprintf("%s couldn't make partUUID\n", __func__); + break; + // kmem_free(zfs_bootfs, len); + // return (ENOMEM); + } + + /* Assign ZFS partiton UUID to both */ + if (properties->setObject(kIOMediaContentKey, + partUUID) == false || + properties->setObject(kIOMediaContentHintKey, + partUUID) == false) { + dprintf("%s content hint failed\n", __func__); + // kmem_free(zfs_bootfs, len); + // return (ENOMEM); + } + partUUID->release(); + } while (0); + + /* XXX Set dataset name, rdonly, and UUID */ + do { + OSString *nameStr; + OSString *uuidStr; + char uuid_cstr[UUID_PRINTABLE_STRING_LENGTH]; + uuid_t uuid; + + memset(uuid, 0, sizeof (uuid_t)); + memset(uuid_cstr, 0, UUID_PRINTABLE_STRING_LENGTH); + + zfs_vfs_uuid_gen(zfs_bootfs, uuid); + zfs_vfs_uuid_unparse(uuid, uuid_cstr); + + nameStr = OSString::withCString(zfs_bootfs); + uuidStr = OSString::withCString(uuid_cstr); + + if (properties->setObject(ZFS_BOOT_DATASET_NAME_KEY, + nameStr) == false || + properties->setObject(ZFS_BOOT_DATASET_UUID_KEY, + uuidStr) == false || + properties->setObject(ZFS_BOOT_DATASET_RDONLY_KEY, + kOSBooleanFalse) == false) { + dprintf("ZFSBootDevice::%s couldn't setup" + "property dict\n", __func__); + nameStr->release(); + uuidStr->release(); + kmem_free(zfs_bootfs, len); + return (ENOMEM); + } + nameStr->release(); + uuidStr->release(); + } while (0); + + /* Create proxy device */ + error = zfs_osx_proxy_create(zfs_bootfs); + if (error == 0) { + dataset = zfs_osx_proxy_get(zfs_bootfs); + } + /* Done with this string */ + kmem_free(zfs_bootfs, len); + zfs_bootfs = 0; + + if (!dataset) { + printf("%s: couldn't create proxy device\n", + __func__); + return (ENXIO); + } + + media = OSDynamicCast(IOMedia, dataset); + if (!media) { + printf("%s: couldn't cast proxy media\n", + __func__); + dataset->release(); + return (ENXIO); + } + +#if 0 + bootdev = new ZFSBootDevice; + + if (!bootdev) { + printf("%s: couldn't create boot device\n", __func__); + return (ENOMEM); + } + + if (bootdev->init(properties) == false) { + printf("%s init failed\n", __func__); + properties->release(); + bootdev->release(); + bootdev = 0; + return (ENXIO); + } + properties->release(); + properties = 0; + + if (bootdev->attach(pool_proxy) == false) { + printf("%s attach failed\n", __func__); + bootdev->release(); + bootdev = 0; + return (ENXIO); + } + + /* Technically should start but this doesn't do much */ + if (bootdev->start(pool_proxy) == false) { + printf("%s start failed\n", __func__); + bootdev->detach(pool_proxy); + bootdev->release(); + bootdev = 0; + return (ENXIO); + } + + /* Get matching started */ + bootdev->registerService(kIOServiceAsynchronous); + // bootdev->registerService(kIOServiceSynchronous); + + do { + if (bootdev->getClient() != 0) { + media = OSDynamicCast(IOMedia, + bootdev->getClient()->getClient()); + if (media) { + media->retain(); + break; + } + } + + /* Sleep until media is available */ + /* + * XXX Should use waitForServiceMatching or IONotifier + */ + IOSleep(200); + } while (!media); + + if (!media) { + /* XXX currently unreachable */ + printf("%s couldn't get bootdev media\n", __func__); + return (ENXIO); + } +#endif + + resourceService = IOService::getResourceService(); + if (!resourceService) { + dprintf("%s missing resource service\n", __func__); + /* Handle error */ + media->release(); + return (ENXIO); + } + +#if 1 + /* XXX publish an IOMedia as the BootUUIDMedia resource */ + /* uses same method as AppleFileSystemDriver */ + + /* Set IOMedia UUID */ + /* XXX skip (moved get uuid below) */ + // media->setProperty(kIOMediaUUIDKey, uuid); + /* Publish this IOMedia as the boot-uuid-media */ + IOService::publishResource(kBootUUIDMediaKey, media); + + /* Drop retain from earlier */ + media->release(); + /* Remove boot-uuid key so AppleFileSystem stops matching */ + resourceService->removeProperty(kBootUUIDKey); +#else + OSString *uuid = 0; + /* Get the current boot-uuid string */ + uuid = OSDynamicCast(OSString, + resourceService->getProperty(kBootUUIDKey, gIOServicePlane)); + if (!uuid) { + dprintf("%s missing boot-uuid IOResource\n", __func__); + /* Handle error */ + return (ENXIO); + } + printf("%s: got boot-uuid %s\n", __func__, uuid->getCStringNoCopy()); + + /* XXX Or use below and let AppleFileSystemDriver match it */ + /* Leaves the Apple_Boot content hint (at least for now) */ + media->setProperty(kIOMediaContentHintKey, "Apple_Boot"); + media->setProperty(kIOMediaUUIDKey, uuid); + /* Register for notifications (not matching) */ + media->registerService(kIOServiceAsynchronous); + /* Drop retain from earlier */ + media->release(); +#endif + + printf("%s done\n", __func__); + return (0); +} + +DSTATIC void +zfs_boot_import_thread(void *arg) +{ + nvlist_t *configs, *nv, *newnv; + nvpair_t *elem; + IOService *zfs_hl = 0; + OSSet *disks, *new_set = 0; + OSCollectionIterator *iter = 0; + OSObject *next; + IOMedia *media; + pool_list_t *pools = (pool_list_t *)arg; + uint64_t pool_state; + boolean_t pool_imported = B_FALSE; + int error = EINVAL; + + /* Verify pool list coult be cast */ + ASSERT3U(pools, !=, 0); + if (!pools) { + printf("%s %p %s\n", "zfs_boot_import_thread", + arg, "couldn't be cast as pool_list_t*"); + return; + } + + /* Abort early */ + if (pools->terminating != ZFS_BOOT_ACTIVE) { + dprintf("%s terminating 1\n", __func__); + goto out_unlocked; + } + + new_set = OSSet::withCapacity(1); + /* To swap with pools->disks while locked */ + if (!new_set) { + dprintf("%s couldn't allocate new_set\n", __func__); + goto out_unlocked; + } + + /* Take pool list lock */ + mutex_enter(&pools->lock); + + zfs_hl = pools->zfs_hl; + + /* Check for work, then sleep on the lock */ + do { + /* Abort early */ + if (pools->terminating != ZFS_BOOT_ACTIVE) { + dprintf("%s terminating 2\n", __func__); + goto out_locked; + } + + /* Check for work */ + if (pools->disks->getCount() == 0) { + dprintf("%s no disks to check\n", __func__); + goto next_locked; + } + + /* Swap full set with a new empty one */ + ASSERT3U(new_set, !=, 0); + disks = pools->disks; + pools->disks = new_set; + new_set = 0; + + /* Release pool list lock */ + mutex_exit(&pools->lock); + + /* Create an iterator over the objects in the set */ + iter = OSCollectionIterator::withCollection(disks); + + /* couldn't be initialized */ + if (!iter) { + dprintf("%s %s %d %s\n", "zfs_boot_import_thread", + "couldn't get iterator from collection", + disks->getCount(), "disks skipped"); + + /* Merge disks back into pools->disks */ + mutex_enter(&pools->lock); + pools->disks->merge(disks); + mutex_exit(&pools->lock); + + /* Swap 'disks' back to new_set */ + disks->flushCollection(); + new_set = disks; + disks = 0; + + continue; + } + + /* Iterate over all disks */ + while ((next = iter->getNextObject()) != NULL) { + /* Cast each IOMedia object */ + media = OSDynamicCast(IOMedia, next); + + if (!iter->isValid()) { + /* Oh gosh, need to start over */ + iter->reset(); + continue; + } + + if (!media) { + dprintf("%s couldn't cast IOMedia\n", + __func__); + continue; + } + + /* Check this IOMedia device for a vdev label */ + if (!zfs_boot_probe_disk(pools, media)) { + dprintf("%s couldn't probe disk\n", + __func__); + continue; + } + } + + /* Clean up */ + media = 0; + iter->release(); + iter = 0; + + /* Swap 'disks' back to new_set */ + disks->flushCollection(); + new_set = disks; + disks = 0; + + /* Abort early */ + if (pools->terminating != ZFS_BOOT_ACTIVE) { + dprintf("%s terminating 3\n", __func__); + goto out_unlocked; + } + + mutex_enter(&pools->lock); + /* Check for work */ + if (pools->disks->getCount() != 0) { + dprintf("%s more disks available, looping\n", __func__); + continue; + } + /* Release pool list lock */ + mutex_exit(&pools->lock); + + /* Generate a list of pool configs to import */ + configs = zfs_boot_get_configs(pools, B_TRUE); + + /* Abort early */ + if (pools->terminating != ZFS_BOOT_ACTIVE) { + dprintf("%s terminating 4\n", __func__); + goto out_unlocked; + } + + /* Iterate over the nvlists (stored as nvpairs in nvlist) */ + elem = NULL; + while ((elem = nvlist_next_nvpair(configs, + elem)) != NULL) { + /* Cast the nvpair back to nvlist */ + nv = NULL; + verify(nvpair_value_nvlist(elem, &nv) == 0); + + /* Check vdev state */ + verify(nvlist_lookup_uint64(nv, + ZPOOL_CONFIG_POOL_STATE, + &pool_state) == 0); + if (pool_state == POOL_STATE_DESTROYED) { + dprintf("%s skipping destroyed pool\n", + __func__); + continue; + } + + /* Abort early */ + if (pools->terminating != ZFS_BOOT_ACTIVE) { + dprintf("%s terminating 5\n", __func__); + goto out_unlocked; + } + + /* Try import */ + newnv = spa_tryimport(nv); + nvlist_free(nv); + nv = 0; + if (newnv) { + dprintf("%s newnv: %p\n", __func__, newnv); + + /* Stop probing disks */ + if (pools->notifier) + pools->notifier->disable(); + + /* Do import */ + pool_imported = (spa_import(pools->pool_name, + newnv, 0, 0) == 0); + nvlist_free(newnv); + newnv = 0; + // pool_imported = spa_import_rootpool(nv); + } else { + dprintf("%s no newnv returned\n", __func__); + } + + dprintf("%s spa_import returned %d\n", __func__, + pool_imported); + + if (pool_imported) { + /* Get bootfs and publish IOMedia */ + error = zfs_boot_publish_bootfs(zfs_hl, pools); + if (error != 0) { + dprintf("%s publish bootfs error %d\n", + __func__, error); + } + + goto out_unlocked; + } else { + /* Resume notifications */ + if (pools->notifier) + pools->notifier->enable(true); + } + } + + /* Retake pool list lock */ + mutex_enter(&pools->lock); + +next_locked: + /* Check for work */ + if (pools->disks->getCount() != 0) { + continue; + } + + /* Abort early */ + if (pools->terminating != ZFS_BOOT_ACTIVE) { + dprintf("%s terminating 6\n", __func__); + goto out_locked; + } + + dprintf("%s sleeping on lock\n", __func__); + /* Sleep on lock, thread is resumed with lock held */ + cv_timedwait_sig(&pools->cv, &pools->lock, + ddi_get_lbolt() + hz); + + /* Loop forever */ + } while (true); + +out_locked: + /* Unlock pool list lock */ + mutex_exit(&pools->lock); + +out_unlocked: + /* Cleanup new_set */ + if (new_set) { + new_set->flushCollection(); + new_set->release(); + new_set = 0; + } + + /* Teardown pool list, lock, etc */ + zfs_boot_free(); + + return; /* taskq_dispatch */ +#if 0 + thread_exit(); /* thread_create */ +#endif +} + +DSTATIC bool +zfs_boot_check_mountroot(char **pool_name, uint64_t *pool_guid) +{ + /* + * Check if the kext is loading during early boot + * and/or check if root is mounted (IORegistry?) + * Use PE Boot Args to determine the root pool name. + */ + char *zfs_boot; + char *split; + uint64_t len; + bool result = false; + uint64_t uptime = 0; + + + if (!pool_name || !pool_guid) { + dprintf("%s %s\n", __func__, + "invalid pool_name or pool_guid ptr"); + return (false); + } + + /* XXX Ugly hack to determine if this is early boot */ + /* + * Could just check if boot-uuid (or rd= or rootdev=) + * are set, and abort otherwise + * IOResource "boot-uuid" only published before root is + * mounted, or "boot-uuid-media" once discovered + */ + clock_get_uptime(&uptime); /* uptime since boot in nanoseconds */ + dprintf("%s uptime: %llu\n", __func__, uptime); + + /* 3 billion nanoseconds ~= 3 seconds */ + // if (uptime >= 3LLU<<30) { + /* 60 billion nanoseconds ~= 60 seconds */ + if (uptime >= 7LLU<<33) { + dprintf("%s %s\n", __func__, "Already booted"); + /* + * Start the getrootdir() from working, the vfs_start() call + * isn't called until first mount, which is too late for + * spa_async_dispatch(). + */ + return (false); + } else { + dprintf("%s %s\n", __func__, "Boot time"); + } + + zfs_boot = (char *)kmem_alloc(ZFS_MAX_DATASET_NAME_LEN, KM_SLEEP); + + if (!zfs_boot) { + dprintf("%s couldn't allocate zfs_boot\n", __func__); + return (false); + } + + result = PE_parse_boot_argn("zfs_boot", zfs_boot, + ZFS_MAX_DATASET_NAME_LEN); + // dprintf( "Raw zfs_boot: [%llu] {%s}\n", + // (uint64_t)strlen(zfs_boot), zfs_boot); + + result = (result && (zfs_boot != 0) && strlen(zfs_boot) > 0); + + if (!result) { + result = PE_parse_boot_argn("rd", zfs_boot, + MAXPATHLEN); + result = (result && (zfs_boot != 0) && + strlen(zfs_boot) > 0 && + strncmp(zfs_boot, "zfs:", 4)); + // dprintf("Raw rd: [%llu] {%s}\n", + // (uint64_t)strlen(zfs_boot), zfs_boot ); + } + if (!result) { + result = PE_parse_boot_argn("rootdev", zfs_boot, + MAXPATHLEN); + result = (result && (zfs_boot != 0) && + strlen(zfs_boot) > 0 && + strncmp(zfs_boot, "zfs:", 4)); + // dprintf("Raw rootdev: [%llu] {%s}\n", + // (uint64_t)strlen(zfs_boot), zfs_boot ); + } + + /* + * XXX To Do - parse zpool_guid boot arg + */ + *pool_guid = 0; + + if (result) { + /* Check for first slash in zfs_boot */ + split = strchr(zfs_boot, '/'); + if (split) { + /* copy pool name up to first slash */ + len = (split - zfs_boot); + } else { + /* or copy whole string */ + len = strlen(zfs_boot); + } + + *pool_name = (char *)kmem_alloc(len+1, KM_SLEEP); + snprintf(*pool_name, len+1, "%s", zfs_boot); + + dprintf("Got zfs_boot: [%llu] {%s}->{%s}\n", + *pool_guid, zfs_boot, *pool_name); + } else { + dprintf("%s\n", "No zfs_boot\n"); + pool_name = 0; + } + + kmem_free(zfs_boot, ZFS_MAX_DATASET_NAME_LEN); + zfs_boot = 0; + return (result); +} + +bool +zfs_boot_init(IOService *zfs_hl) +{ + IONotifier *notifier = 0; + pool_list_t *pools = 0; + char *pool_name = 0; + uint64_t pool_guid = 0; + + zfs_boot_pool_list = 0; + + if (!zfs_hl) { + dprintf("%s: No zfs_hl provided\n", __func__); + return (false); + } + + if (!zfs_boot_check_mountroot(&pool_name, &pool_guid) || + (!pool_name && pool_guid == 0)) { + /* + * kext is not being loaded during early-boot, + * or no pool is specified for import. + */ + dprintf("%s: check failed\n", __func__); + return (true); + } + + pools = (pool_list_t *)kmem_alloc(sizeof (pool_list_t), + KM_SLEEP); + if (!pools) { + goto error; + } + memset(pools, 0, sizeof (pool_list_t)); + + if ((pools->disks = OSSet::withCapacity( + ZFS_BOOT_PREALLOC_SET)) == NULL) { + /* Fail if memory couldn't be allocated */ + goto error; + } + + /* create the zfs_boot taskq */ + + zfs_boot_taskq = + taskq_create("zfs_boot_taskq", 100, defclsyspri, + max_ncpus, INT_MAX, + TASKQ_PREPOPULATE | TASKQ_THREADS_CPU_PCT); + + VERIFY(zfs_boot_taskq); + + /* create the lock and cv early, before notifier */ + mutex_init(&pools->lock, NULL, MUTEX_DEFAULT, NULL); + cv_init(&pools->cv, NULL, CV_DEFAULT, NULL); + + pools->pools = 0; + pools->names = 0; + pools->pool_guid = pool_guid; + pools->pool_name = pool_name; + pools->zfs_hl = zfs_hl; + /* and finally hit the _Atomic to spread the above */ + pools->terminating = ZFS_BOOT_ACTIVE; + + notifier = IOService::addMatchingNotification( + gIOFirstPublishNotification, IOService::serviceMatching( + "IOMediaBSDClient"), zfs_boot_probe_media, + zfs_hl, pools, 0); + + if (!notifier) { + /* Fail if memory couldn't be allocated */ + goto error; + } + pools->notifier = notifier; + + /* Finally, start the import thread */ + VERIFY3U(taskq_dispatch(zfs_boot_taskq, zfs_boot_import_thread, + (void*)pools, TQ_SLEEP), !=, 0); + +#if 0 +/* Alternate method of scheduling the import thread */ + (void) thread_create(NULL, 0, zfs_boot_import_thread, + pools, 0, &p0, + TS_RUN, minclsyspri); +#endif + + zfs_boot_pool_list = pools; + + return (true); + +error: + if (pools) { + if (pools->disks) { + pools->disks->flushCollection(); + pools->disks->release(); + pools->disks = 0; + } + kmem_free(pools, sizeof (pool_list_t)); + pools = 0; + } + return (false); +} + +/* Include these functions in all builds */ + +/* + * zfs_boot_update_bootinfo_vdev_leaf + * Inputs: spa: valid pool spa pointer. vd: valid vdev pointer. + * Return: 0 on success, positive integer errno on failure. + * Callers: zfs_boot_update_bootinfo_vdev + * + * called by bootinfo_vdev with each leaf vdev. + */ +DSTATIC int +zfs_boot_update_bootinfo_vdev_leaf(OSArray *array, vdev_t *vd) +{ + OSDictionary *dict; + OSString *dev_str; + OSNumber *dev_size; + vdev_disk_t *dvd; + struct io_bootinfo *info; + int error; + + /* Validate inputs */ + if (!array || !vd) { + dprintf("%s missing argument\n", __func__); + return (EINVAL); + } + + /* Should be called with leaf vdev */ + if (!vd->vdev_ops->vdev_op_leaf) { + dprintf("%s not a leaf vdev\n", __func__); + return (EINVAL); + } + + /* Skip hole vdevs */ + if (vd->vdev_ishole) { + dprintf("%s skipping hole in namespace\n", __func__); + return (0); + } + + /* No info available if missing */ + if (strcmp(vd->vdev_ops->vdev_op_type, VDEV_TYPE_MISSING) == 0) { + dprintf("%s skipping missing vdev\n", __func__); + return (0); + } + + /* Must be a disk, not a file */ + if (strcmp(vd->vdev_ops->vdev_op_type, VDEV_TYPE_DISK) != 0) { + dprintf("%s skipping non-disk vdev\n", __func__); + return (0); + } + + /* Skip obviously non-bootable vdevs */ + if (vd->vdev_islog || + vd->vdev_isl2cache || vd->vdev_isspare) { + dprintf("%s skipping non-bootable\n", __func__); + return (0); + } + + /* Get vdev type-specific data */ + dvd = (vdev_disk_t *)vd->vdev_tsd; + if (!dvd || !dvd->vd_lh) { + dprintf("%s missing dvd or ldi handle\n", __func__); + return (0); + } + + /* Allocate an ldi io_bootinfo struct */ + info = (struct io_bootinfo *)kmem_alloc( + sizeof (struct io_bootinfo), KM_SLEEP); + if (!info) { + dprintf("%s info alloc failed\n", __func__); + return (ENOMEM); + } + memset(info, 0, sizeof (struct io_bootinfo)); + + /* Ask the vdev handle to fill in the info */ + error = ldi_ioctl(dvd->vd_lh, DKIOCGETBOOTINFO, + (intptr_t)info, 0, 0, 0); + if (error != 0) { + dprintf("%s ioctl error %d\n", __func__, error); + kmem_free(info, sizeof (struct io_bootinfo)); + return (EIO); + } + + /* Allocate dictionary to hold the keys */ + if ((dict = OSDictionary::withCapacity(2)) == NULL) { + dprintf("%s dictionary alloc failed\n", __func__); + kmem_free(info, sizeof (struct io_bootinfo)); + return (ENOMEM); + } + + /* Keys are path (string) and size (number) */ + dev_str = OSString::withCString(info->dev_path); + dev_size = OSNumber::withNumber(info->dev_size, + (8 * sizeof (info->dev_size))); + kmem_free(info, sizeof (struct io_bootinfo)); + info = 0; + + /* Add keys to dictionary or bail */ + if (!dev_str || !dev_size || + dict->setObject(kIOBootDevicePathKey, + dev_str) == false || + dict->setObject(kIOBootDeviceSizeKey, + dev_size) == false) { + dprintf("%s dictionary setup failed\n", __func__); + if (dev_str) dev_str->release(); + if (dev_size) dev_size->release(); + dict->release(); + dict = 0; + return (ENOMEM); + } + dev_str->release(); + dev_str = 0; + dev_size->release(); + dev_size = 0; + + /* Add dict to array */ + if (array->setObject(dict) == false) { + dprintf("%s couldn't set bootinfo\n", __func__); + dict->release(); + dict = 0; + return (ENOMEM); + } + dict->release(); + dict = 0; + + return (0); +} + +/* + * zfs_boot_update_bootinfo_vdev + * Inputs: spa: valid pool spa pointer. vd: valid vdev pointer. + * Return: 0 on success, positive integer errno on failure. + * Callers: zfs_boot_update_bootinfo + * + * called by bootinfo with root vdev, and recursively calls + * itself while iterating over children (vdevs only have a + * few levels of nesting at most). + */ +DSTATIC int +zfs_boot_update_bootinfo_vdev(OSArray *array, vdev_t *vd) +{ + int c, error; + + /* Validate inputs */ + if (!array || !vd) { + dprintf("%s missing argument\n", __func__); + return (EINVAL); + } + + /* Skip obviously non-bootable vdevs */ + if (vd->vdev_islog || + vd->vdev_isl2cache || vd->vdev_isspare) { + dprintf("%s skipping non-bootable\n", __func__); + return (0); + } + + /* Process leaf vdevs */ + if (vd->vdev_ops->vdev_op_leaf) { + error = zfs_boot_update_bootinfo_vdev_leaf(array, vd); + if (error) + dprintf("%s bootinfo_vdev_leaf error %d\n", + __func__, error); + return (error); + } + + /* Iterate over child vdevs */ + for (c = 0; c < vd->vdev_children; c++) { + if (vd->vdev_child[c] == NULL) { + dprintf("%s hole in vdev namespace\n", __func__); + continue; + } + + /* Recursion */ + error = zfs_boot_update_bootinfo_vdev(array, + vd->vdev_child[c]); + if (error != 0) { + dprintf("%s bootinfo_vdev_leaf error %d\n", + __func__, error); + return (error); + } + } + + return (0); +} + +extern "C" { + +/* + * zfs_boot_update_bootinfo + * Inputs: spa: valid pool spa pointer. + * Return: 0 on success, positive integer errno on failure. + * Callers: spa_open_common, spa_vdev_add, spa_vdev_remove, + * spa_vdev_attach, spa_vdev_detach. + * + * Called from spa.c on changes to the vdev layout. This + * information is assigned to the pool proxy so all zvols + * and datasets will retrieve the property through IOKit + * since it is retrieved via recursion. + * (see bless-105/Misc/BLCreateBooterInformationDictionary.c). + * If IOBootDevice property is needed for each dataset and + * zvol, we can revisit this and assign/update on all of + * these (already implemented a prototype that worked fine). + * + * Note: bootinfo is only collected for data vdevs. + * XXX We only want boot helpers there, unless there is a + * compelling argument for log, cache, or spares having + * boot helpers. + */ +int +zfs_boot_update_bootinfo(spa_t *spa) +{ + ZFSPool *pool_proxy; + OSArray *array; + int error; + + if (!spa) { + dprintf("%s missing spa\n", __func__); + return (EINVAL); + } + + /* XXX Could count vdevs first? */ + if ((array = OSArray::withCapacity(1)) == NULL) { + dprintf("%s allocation failed\n", __func__); + return (ENOMEM); + } + + /* Grab necessary locks */ + mutex_enter(&spa_namespace_lock); + spa_open_ref(spa, FTAG); + + /* Get pool proxy */ + if (!spa->spa_iokit_proxy || + (pool_proxy = spa->spa_iokit_proxy->proxy) == NULL) { + spa_close(spa, FTAG); + mutex_exit(&spa_namespace_lock); + dprintf("%s no spa_pool_proxy\n", __func__); + return (0); + } + /* Avoid it disappearing from under us */ + pool_proxy->retain(); + + /* Don't need to hold this throughout */ + mutex_exit(&spa_namespace_lock); + + /* vdev state lock only requires an spa open ref */ + spa_vdev_state_enter(spa, SCL_NONE); + + /* Iterate over all vdevs */ + if ((error = zfs_boot_update_bootinfo_vdev(array, + spa->spa_root_vdev)) != 0) { + dprintf("%s bootinfo_vdev error %d\n", + __func__, error); + + /* Drop locks */ + (void) spa_vdev_state_exit(spa, NULL, 0); + mutex_enter(&spa_namespace_lock); + spa_close(spa, FTAG); + mutex_exit(&spa_namespace_lock); + array->release(); + pool_proxy->release(); + return (error); + } + + /* Release locks, passing NULL vd (no change) */ + error = spa_vdev_state_exit(spa, NULL, 0); + if (error != 0) { + dprintf("%s spa_vdev_state_exit error %d\n", + __func__, error); + } + + /* setProperty adds a retain */ + pool_proxy->setProperty(kIOBootDeviceKey, array); + pool_proxy->release(); + array->release(); + + /* Drop locks */ + mutex_enter(&spa_namespace_lock); + spa_close(spa, FTAG); + mutex_exit(&spa_namespace_lock); + return (0); +} + +} /* extern "C" */ + +#if 0 +#ifdef ZFS_BOOT +/* Remainder only needed for boot */ + +#define DPRINTF_FUNC() dprintf("%s\n", __func__) + +#pragma mark - ZFSBootDevice + +OSDefineMetaClassAndStructors(ZFSBootDevice, IOBlockStorageDevice); +char ZFSBootDevice::vendorString[4] = "ZFS"; +char ZFSBootDevice::revisionString[4] = "0.1"; +char ZFSBootDevice::infoString[12] = "ZFS dataset"; + +#if 0 +int +zfs_boot_get_path(char *path, int len) +{ + OSString *disk = 0; + + if (!path || len == 0) { + dprintf("%s: invalid argument\n", __func__); + return (-1); + } + + if (bootdev) { + disk = OSDynamicCast(OSString, + bootdev->getProperty(kIOBSDNameKey, gIOServicePlane, + kIORegistryIterateRecursively)); + } + + if (disk) { + snprintf(path, len, "/dev/%s", disk->getCStringNoCopy()); + return (0); + } + + return (-1); +} +#endif + +bool +ZFSBootDevice::init(OSDictionary *properties) +{ + /* Allocate dictionaries and symbols */ + OSDictionary *pdict = OSDictionary::withCapacity(2); + OSDictionary *ddict = OSDictionary::withCapacity(4); + const OSSymbol *virtualSymbol = OSSymbol::withCString( + kIOPropertyPhysicalInterconnectTypeVirtual); + const OSSymbol *ramSymbol = OSSymbol::withCString( + kIOPropertyInterconnectRAMKey); + const OSSymbol *ssdSymbol = OSSymbol::withCString( + kIOPropertyMediumTypeSolidStateKey); + OSNumber *physSize = OSNumber::withNumber((uint32_t)4096, 32); + OSNumber *logSize = OSNumber::withNumber((uint32_t)512, 32); + const OSSymbol *vendorSymbol = 0; + const OSSymbol *revisionSymbol = 0; + const OSSymbol *blankSymbol = 0; + OSBoolean *rdonly = 0; + OSString *str = 0; + const char *cstr = 0; + bool ret = false; + + DPRINTF_FUNC(); + + /* Validate allocations */ + if (!pdict || !ddict || !virtualSymbol || !ramSymbol || + !ssdSymbol || !physSize || !logSize) { + dprintf("ZFSBootDevice::%s allocation failed\n", __func__); + goto error; + } + + /* Init class statics every time an instance inits */ + /* Shared across instances, but doesn't hurt to reprint */ + snprintf(vendorString, strlen("ZFS")+1, "ZFS"); + snprintf(revisionString, strlen("0.1")+1, "0.1"); + snprintf(infoString, strlen("ZFS dataset")+1, "ZFS dataset"); + + /* For IORegistry keys, cache OSSymbols for class statics */ + /* Leverages OSSymbol cahce pool to reuse across instances */ + vendorSymbol = OSSymbol::withCString(vendorString); + revisionSymbol = OSSymbol::withCString(revisionString); + blankSymbol = OSSymbol::withCString(""); + if (!vendorSymbol || !revisionSymbol || !blankSymbol) { + dprintf("ZFSBootDevice::%s class symbols failed\n", __func__); + goto error; + } + + /* Call super init */ + if (IOBlockStorageDevice::init(properties) == false) { + dprintf("ZFSBootDevice::%s device init failed\n", __func__); + goto error; + } + + /* Set class private vars */ + productString = NULL; + isReadOnly = false; // XXX should really be true initially + + /* Set Protocol Characteristics */ + if (pdict->setObject(kIOPropertyPhysicalInterconnectLocationKey, + ramSymbol) == false || + pdict->setObject(kIOPropertyPhysicalInterconnectTypeKey, + virtualSymbol) == false) { + dprintf("%s pdict set properties failed\n", __func__); + goto error; + } + setProperty(kIOPropertyProtocolCharacteristicsKey, pdict); + + /* Set Device Characteristics */ + if (ddict->setObject(kIOPropertyVendorNameKey, + vendorSymbol) == false || + ddict->setObject(kIOPropertyProductRevisionLevelKey, + revisionSymbol) == false || + ddict->setObject(kIOPropertyProductSerialNumberKey, + blankSymbol) == false || + ddict->setObject(kIOPropertyPhysicalBlockSizeKey, + physSize) == false || + ddict->setObject(kIOPropertyLogicalBlockSizeKey, + logSize) == false || + ddict->setObject(kIOPropertyMediumTypeKey, + ssdSymbol) == false) { + dprintf("%s ddict set properties failed\n", __func__); + goto error; + } + setProperty(kIOPropertyDeviceCharacteristicsKey, ddict); + + /* Check for passed in readonly status */ + if (properties && (rdonly = OSDynamicCast(OSBoolean, + properties->getObject(ZFS_BOOT_DATASET_RDONLY_KEY))) != NULL) { + /* Got the boolean */ + isReadOnly = rdonly->getValue(); + dprintf("ZFSBootDevice %s set %s\n", __func__, + (isReadOnly ? "readonly" : "readwrite")); + } + + /* Check for passed in dataset UUID */ + if (properties && (str = OSDynamicCast(OSString, + properties->getObject(ZFS_BOOT_DATASET_UUID_KEY))) != NULL && + (cstr = str->getCStringNoCopy()) != NULL) { + /* Got the string, try to set UUID */ + str->retain(); + if (ddict->setObject("Dataset UUID", str) == false) { + dprintf("ZFSBootDevice::%s failed UUID [%s]\n", + __func__, cstr); + str->release(); + goto error; + } + dprintf("ZFSBootDevice::%s set UUID [%s]\n", + __func__, cstr); + str->release(); + } + + /* Check for passed in dataset name */ + if (properties && (str = OSDynamicCast(OSString, + properties->getObject(ZFS_BOOT_DATASET_NAME_KEY))) != NULL && + (cstr = str->getCStringNoCopy()) != NULL) { + /* Got the string, try to set name */ + str->retain(); + if (setDatasetName(cstr) == false) { + /* Unlikely */ + dprintf("ZFSBootDevice %s couldn't setup dataset" + " name property [%s]\n", __func__, cstr); + str->release(); + goto error; + } + + dprintf("ZFSBootDevice %s set dataset name [%s]\n", + __func__, cstr); + str->release(); + } else { + if (setDatasetName("invalid") == false) { + dprintf("ZFSBootDevice::%s setDatasetName failed\n", + __func__); + goto error; + } + dprintf("ZFSBootDevice %s set name [invalid]\n", __func__); + } + + /* Success */ + ret = true; + +error: + if (pdict) pdict->release(); + if (ddict) ddict->release(); + if (virtualSymbol) virtualSymbol->release(); + if (ramSymbol) ramSymbol->release(); + if (ssdSymbol) ssdSymbol->release(); + if (physSize) physSize->release(); + if (logSize) logSize->release(); + if (vendorSymbol) vendorSymbol->release(); + if (revisionSymbol) revisionSymbol->release(); + if (blankSymbol) blankSymbol->release(); + return (ret); +} + +void +ZFSBootDevice::free() +{ + char *pstring = (char *)productString; + productString = 0; + + if (pstring) kmem_free(pstring, strlen(pstring) + 1); + + IOBlockStorageDevice::free(); +} + +#if 0 +bool +ZFSBootDevice::attach(IOService *provider) +{ + DPRINTF_FUNC(); + // return (IOMedia::attach(provider)); + return (IOBlockStorageDevice::attach(provider)); +} + +void +ZFSBootDevice::detach(IOService *provider) +{ + DPRINTF_FUNC(); + // IOMedia::detach(provider); + IOBlockStorageDevice::detach(provider); +} + +bool +ZFSBootDevice::start(IOService *provider) +{ + DPRINTF_FUNC(); + // return (IOMedia::start(provider)); + return (IOBlockStorageDevice::start(provider)); +} + +void +ZFSBootDevice::stop(IOService *provider) +{ + DPRINTF_FUNC(); + // IOMedia::stop(provider); + IOBlockStorageDevice::stop(provider); +} + +IOService* +ZFSBootDevice::probe(IOService *provider, SInt32 *score) +{ + DPRINTF_FUNC(); + // return (IOMedia::probe(provider, score)); + return (IOBlockStorageDevice::probe(provider, score)); +} +#endif + +IOReturn +ZFSBootDevice::doSynchronizeCache(void) +{ + dprintf("ZFSBootDevice %s\n", __func__); + return (kIOReturnSuccess); +} + +IOReturn +ZFSBootDevice::doAsyncReadWrite(IOMemoryDescriptor *buffer, + UInt64 block, UInt64 nblks, + IOStorageAttributes *attributes, + IOStorageCompletion *completion) +{ + char zero[ZFS_BOOT_DEV_BSIZE]; + size_t len, cur, off = 0; + + DPRINTF_FUNC(); + + if (!buffer) { + IOStorage::complete(completion, kIOReturnError, 0); + return (kIOReturnSuccess); + } + + /* Read vs. write */ + if (buffer->getDirection() == kIODirectionIn) { + /* Zero the read buffer */ + memset(zero, 0, ZFS_BOOT_DEV_BSIZE); + len = buffer->getLength(); + while (len > 0) { + cur = (len > ZFS_BOOT_DEV_BSIZE ? + ZFS_BOOT_DEV_BSIZE : len); + buffer->writeBytes(/* offset */ off, + /* buf */ zero, /* length */ cur); + off += cur; + len -= cur; + } + // dprintf("%s: read: %llu %llu\n", + // __func__, block, nblks); + IOStorage::complete(completion, kIOReturnSuccess, + buffer->getLength()); + return (kIOReturnSuccess); + } + + if (buffer->getDirection() != kIODirectionOut) { + dprintf("%s invalid direction %d\n", __func__, + buffer->getDirection()); + IOStorage::complete(completion, kIOReturnError, 0); + return (kIOReturnSuccess); + } + + /* + * XXX For now this just returns error for all writes. + * If it turns out that mountroot/bdevvp try to + * verify writable status by reading a block and writing + * it back to disk, lie and say it succeeded. + */ + dprintf("%s: write: %llu %llu\n", __func__, block, nblks); + IOStorage::complete(completion, kIOReturnError, 0); + return (kIOReturnSuccess); +} + +IOReturn +ZFSBootDevice::doEjectMedia() +{ + DPRINTF_FUNC(); + /* XXX Called at shutdown, maybe return success? */ + return (kIOReturnError); +} + +IOReturn +ZFSBootDevice::doFormatMedia(UInt64 byteCapacity) +{ + DPRINTF_FUNC(); + /* XXX shouldn't need it */ + return (kIOReturnError); + // return (kIOReturnSuccess); +} + +UInt32 +ZFSBootDevice::doGetFormatCapacities(UInt64 *capacities, + UInt32 capacitiesMaxCount) const +{ + DPRINTF_FUNC(); + if (capacities && capacitiesMaxCount > 0) { + capacities[0] = (ZFS_BOOT_DEV_BSIZE * ZFS_BOOT_DEV_BCOUNT); + dprintf("ZFSBootDevice %s: capacity %llu\n", + __func__, capacities[0]); + } + + /* Always inform caller of capacity count */ + return (1); +} + +/* Assign dataset name from null-terminated string */ +bool +ZFSBootDevice::setDatasetName(const char *dsname) +{ + OSDictionary *dict; + OSString *dsstr; + char *newname, *oldname; + size_t len; + + DPRINTF_FUNC(); + + /* Validate arguments */ + if (!dsname || (len = strnlen(dsname, + ZFS_MAX_DATASET_NAME_LEN)) == 0) { + dprintf("%s: missing argument\n", __func__); + return (false); + } + + /* Truncate too-long names (shouldn't happen) */ + if (len == ZFS_MAX_DATASET_NAME_LEN && + dsname[ZFS_MAX_DATASET_NAME_LEN] != '\0') { + dprintf("%s: dsname too long [%s]\n", + __func__, dsname); + /* XXX Just truncate the name */ + len--; + } + + /* Allocate room for name plus null char */ + newname = (char *)kmem_alloc(len+1, KM_SLEEP); + if (!newname) { + dprintf("ZFSBootDevice::%s string alloc failed\n", __func__); + return (false); + } + snprintf(newname, len+1, "%s", dsname); + newname[len] = '\0'; /* just in case */ + + /* Save an OSString copy for IORegistry */ + dsstr = OSString::withCString(newname); + if (!dsstr) { + dprintf("ZFSBootDevice::%s OSString failed\n", __func__); + kmem_free(newname, len+1); + return (false); + } + + /* Swap into class private var */ + oldname = productString; + productString = newname; + newname = 0; + if (oldname) { + kmem_free(oldname, strlen(oldname)+1); + oldname = 0; + } + + /* Get and clone device characteristics prop dict */ + if ((dict = OSDynamicCast(OSDictionary, + getProperty(kIOPropertyDeviceCharacteristicsKey))) == NULL || + (dict = OSDictionary::withDictionary(dict)) == NULL) { + dprintf("%s couldn't clone prop dict\n", __func__); + /* Should only happen during initialization */ + } + + if (dict) { + /* Copy string, add to dictionary, and replace prop dict */ + if (dict->setObject(kIOPropertyProductNameKey, + dsstr) == false || + setProperty(kIOPropertyDeviceCharacteristicsKey, + dict) == false) { + dprintf("%s couldn't set name\n", __func__); + dsstr->release(); + dict->release(); + return (false); + } + dict->release(); + dict = 0; + } + + /* Finally, set the IORegistryEntry/IOService name */ + setName(dsstr->getCStringNoCopy()); + dsstr->release(); + + return (true); +} + +/* Returns full dataset name from instance private var */ +char * +ZFSBootDevice::getProductString() +{ + dprintf("ZFSBootDevice %s [%s]\n", productString); + /* Return class private string */ + return (productString); +} + +/* Returns readonly status from instance private var */ +IOReturn +ZFSBootDevice::reportWriteProtection(bool *isWriteProtected) +{ + DPRINTF_FUNC(); + if (isWriteProtected) *isWriteProtected = isReadOnly; + return (kIOReturnSuccess); +} + +/* These return class static string for all instances */ +char * +ZFSBootDevice::getVendorString() +{ + dprintf("ZFSBootDevice %s [%s]\n", vendorString); + /* Return class static string */ + return (vendorString); +} +char * +ZFSBootDevice::getRevisionString() +{ + dprintf("ZFSBootDevice %s [%s]\n", revisionString); + /* Return class static string */ + return (revisionString); +} +char * +ZFSBootDevice::getAdditionalDeviceInfoString() +{ + dprintf("ZFSBootDevice %s [%s]\n", infoString); + /* Return class static string */ + return (infoString); +} + +/* Always return media present and unchanged */ +IOReturn +ZFSBootDevice::reportMediaState(bool *mediaPresent, + bool *changedState) +{ + DPRINTF_FUNC(); + if (mediaPresent) *mediaPresent = true; + if (changedState) *changedState = false; + return (kIOReturnSuccess); +} + +/* Always report nonremovable and nonejectable */ +IOReturn +ZFSBootDevice::reportRemovability(bool *isRemoveable) +{ + DPRINTF_FUNC(); + if (isRemoveable) *isRemoveable = false; + return (kIOReturnSuccess); +} +IOReturn +ZFSBootDevice::reportEjectability(bool *isEjectable) +{ + DPRINTF_FUNC(); + if (isEjectable) *isEjectable = false; + return (kIOReturnSuccess); +} + +/* Always report 512b blocksize */ +IOReturn +ZFSBootDevice::reportBlockSize(UInt64 *blockSize) +{ + DPRINTF_FUNC(); + if (!blockSize) + return (kIOReturnError); + + *blockSize = ZFS_BOOT_DEV_BSIZE; + return (kIOReturnSuccess); +} + +/* XXX Calculate from dev_bcount, should get size from objset */ +/* XXX Can issue message kIOMessageMediaParametersHaveChanged to update */ +IOReturn +ZFSBootDevice::reportMaxValidBlock(UInt64 *maxBlock) +{ + DPRINTF_FUNC(); + if (!maxBlock) + return (kIOReturnError); + + // *maxBlock = 0; + *maxBlock = ZFS_BOOT_DEV_BCOUNT - 1; + dprintf("ZFSBootDevice %s: maxBlock %llu\n", __func__, *maxBlock); + + return (kIOReturnSuccess); +} +#endif /* ZFS_BOOT */ +#endif /* 0 */ diff --git a/module/os/macos/zfs/zfs_ctldir.c b/module/os/macos/zfs/zfs_ctldir.c new file mode 100644 index 000000000000..0449d4584896 --- /dev/null +++ b/module/os/macos/zfs/zfs_ctldir.c @@ -0,0 +1,1726 @@ +/* + * 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 Jorgen Lundman. All rights reserved. + */ + +/* + * ZFS control directory (a.k.a. ".zfs") + * + * This directory provides a common location for all ZFS meta-objects. + * Currently, this is only the 'snapshot' and 'shares' directory, but this may + * expand in the future. The elements are built dynamically, as the hierarchy + * does not actually exist on disk. + * + * For 'snapshot', we don't want to have all snapshots always mounted, because + * this would take up a huge amount of space in /etc/mnttab. We have three + * types of objects: + * + * ctldir ------> snapshotdir -------> snapshot + * | + * | + * V + * mounted fs + * + * The 'snapshot' node contains just enough information to lookup '..' and act + * as a mountpoint for the snapshot. Whenever we lookup a specific snapshot, we + * perform an automount of the underlying filesystem and return the + * corresponding vnode. + * + * All mounts are handled automatically by an user mode helper which invokes + * the mount procedure. Unmounts are handled by allowing the mount + * point to expire so the kernel may automatically unmount it. + * + * The '.zfs', '.zfs/snapshot', and all directories created under + * '.zfs/snapshot' (ie: '.zfs/snapshot/') all share the same + * zfsvfs_t as the head filesystem (what '.zfs' lives under). + * + * File systems mounted on top of the '.zfs/snapshot/' paths + * (ie: snapshots) are complete ZFS filesystems and have their own unique + * zfsvfs_t. However, the fsid reported by these mounts will be the same + * as that used by the parent zfsvfs_t to make NFS happy. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "zfs_namecheck.h" + +extern kmem_cache_t *znode_cache; +extern uint64_t vnop_num_vnodes; + +/* + * Apple differences; + * + * We don't have 'shares' directory, so only 'snapshot' is relevant. + * + * We can not issue mount from kernel, so involve zed. + * - see zfs_ctldir_snapdir.c + * + * All vnodes point to znode_t, no special case nodes. + */ + +/* List of zfsctl mounts waiting to be mounted */ +static kmutex_t zfsctl_mounts_lock; +static list_t zfsctl_mounts_list; +struct zfsctl_mounts_waiting { + kmutex_t zcm_lock; + kcondvar_t zcm_cv; + list_node_t zcm_node; + struct vnode *zcm_vnode; + char zcm_name[ZFS_MAX_DATASET_NAME_LEN]; +}; +typedef struct zfsctl_mounts_waiting zfsctl_mounts_waiting_t; + +/* + * Control Directory Tunables (.zfs) + */ +int zfs_expire_snapshot = ZFSCTL_EXPIRE_SNAPSHOT; +int zfs_admin_snapshot = 1; +int zfs_auto_snapshot = 1; + +static kmutex_t zfsctl_unmount_lock; +static kcondvar_t zfsctl_unmount_cv; +static boolean_t zfsctl_unmount_thread_exit; + +static kmutex_t zfsctl_unmount_list_lock; +static list_t zfsctl_unmount_list; + +struct zfsctl_unmount_delay { + char *se_name; /* full snapshot name */ + spa_t *se_spa; /* pool spa */ + struct vnode *se_vnode; + time_t se_time; + list_node_t se_nodelink; +}; +typedef struct zfsctl_unmount_delay zfsctl_unmount_delay_t; + +/* + * We need to remember the pid,tid of processes involved with unmount + * so they do not trigger mount due to it. This feels a little hacky + * so there is reoom for improvement here. + */ +#define IGNORE_MAX 5 +static uint32_t ignore_next = 0; +static kthread_t *ignore_lookups_tid[IGNORE_MAX] = {NULL}; +static pid_t ignore_lookups_pid[IGNORE_MAX] = {0}; +static clock_t ignore_lookups_time[IGNORE_MAX] = {0}; +#define IGNORE_NEXT do { \ + if (atomic_inc_32_nv(&ignore_next) >= IGNORE_MAX) ignore_next = 0; \ + } while (0) + +#define IGNORE_ADD(pid, tid, time) do { \ + int i = ignore_next; \ + ignore_lookups_pid[i] = (pid); \ + ignore_lookups_tid[i] = (tid); \ + ignore_lookups_time[i] = (time); \ + IGNORE_NEXT; \ + } while (0) + +#define IGNORE_FIND_CLEAR(pid, tid, time) do { \ + for (int i = 0; i < IGNORE_MAX; i++) { \ + if (ignore_lookups_pid[i] == (pid) && \ + ignore_lookups_tid[i] == (tid)) { \ + ignore_lookups_pid[i] = 0; \ + ignore_lookups_tid[i] = 0; \ + ignore_lookups_time[i] = 0; \ + } \ + } \ + } while (0) + +#define IGNORE_FIND_SETTIME(pid, tid, time) do { \ + for (int i = 0; i < IGNORE_MAX; i++) \ + if (ignore_lookups_pid[i] == (pid) && \ + ignore_lookups_tid[i] == (tid)) { \ + ignore_lookups_time[i] = (time); \ + } \ + } while (0) + +/* + * Check if the given vnode is a part of the virtual .zfs directory. + */ +boolean_t +zfsctl_is_node(struct vnode *vp) +{ + return (VTOZ(vp)->z_is_ctldir); +} + +typedef int (**vnode_operations)(void *); + + +/* + * Allocate a new vnode with the passed id and ops. + */ +static struct vnode * +zfsctl_vnode_alloc(zfsvfs_t *zfsvfs, uint64_t id, + char *name) +{ + timestruc_t now; + struct vnode *vp = NULL; + znode_t *zp = NULL; + struct vnode_fsparam vfsp; + + dprintf("%s id %llu name '%s'\n", __func__, id, name); + + zp = kmem_cache_alloc(znode_cache, KM_SLEEP); + gethrestime(&now); + ASSERT3P(zp->z_dirlocks, ==, NULL); + ASSERT3P(zp->z_acl_cached, ==, NULL); + ASSERT3P(zp->z_xattr_cached, ==, NULL); + zp->z_zfsvfs = zfsvfs; + zp->z_id = id; + zp->z_unlinked = B_FALSE; + zp->z_atime_dirty = B_FALSE; + zp->z_zn_prefetch = B_FALSE; + zp->z_is_sa = B_FALSE; + zp->z_is_mapped = B_FALSE; + zp->z_is_ctldir = B_TRUE; + zp->z_sa_hdl = NULL; + zp->z_blksz = 0; + zp->z_seq = 0; + zp->z_mapcnt = 0; + zp->z_size = 0; + zp->z_pflags = 0; + zp->z_mode = 0; + zp->z_sync_cnt = 0; + zp->z_gen = 0; + zp->z_mode = (S_IFDIR | (S_IRWXU|S_IRWXG|S_IRWXO)); + zp->z_uid = 0; + zp->z_gid = 0; + ZFS_TIME_ENCODE(&now, zp->z_atime); + + zp->z_snap_mount_time = 0; /* Allow automount attempt */ + + strlcpy(zp->z_name_cache, name, sizeof (zp->z_name_cache)); + + memset(&vfsp, 0, sizeof (vfsp)); + vfsp.vnfs_str = "zfs"; + vfsp.vnfs_mp = zfsvfs->z_vfs; + vfsp.vnfs_vtype = IFTOVT((mode_t)zp->z_mode); + vfsp.vnfs_fsnode = zp; + vfsp.vnfs_flags = VNFS_ADDFSREF | VNFS_CANTCACHE; + + dprintf("%s zp %p with vp %p zfsvfs %p vfs %p: vtype %u: '%s'\n", + __func__, + zp, vp, zfsvfs, zfsvfs->z_vfs, vfsp.vnfs_vtype, name); + + /* + * This creates a vnode with VSYSTEM set, this is so that unmount's + * vflush() (called before our vfs_unmount) will pass (and not block + * waiting for the usercount ref to be released). We then release the + * VROOT vnode in zfsctl_destroy, and release the usercount ref. + * Because of this, we need to call vnode_recycle() ourselves in destroy + */ + if (id == ZFSCTL_INO_ROOT) + vfsp.vnfs_marksystem = 1; + + vfsp.vnfs_vops = zfs_ctldirops; + + while (vnode_create(VNCREATE_FLAVOR, VCREATESIZE, &vfsp, &vp) != 0) { + kpreempt(KPREEMPT_SYNC); + } + atomic_inc_64(&vnop_num_vnodes); + + dprintf("Assigned zp %p with vp %p zfsvfs %p\n", zp, vp, zp->z_zfsvfs); + + vnode_settag(vp, VT_ZFS); + + zp->z_vid = vnode_vid(vp); + zp->z_vnode = vp; + + mutex_enter(&zfsvfs->z_znodes_lock); + list_insert_tail(&zfsvfs->z_all_znodes, zp); + membar_producer(); + if (id < zfsvfs->z_ctldir_startid) + zfsvfs->z_ctldir_startid = id; + mutex_exit(&zfsvfs->z_znodes_lock); + + return (vp); +} + +/* + * Lookup the vnode with given id, it will be allocated if needed. + */ +static struct vnode * +zfsctl_vnode_lookup(zfsvfs_t *zfsvfs, uint64_t id, + char *name) +{ + struct vnode *ip = NULL; + int error = 0; + + dprintf("%s: looking for id %llu name '%s'\n", __func__, + id, name); + + while (ip == NULL) { + + error = zfs_vfs_vget(zfsvfs->z_vfs, id, &ip, NULL); + if (error == 0 && ip != NULL) + break; + + /* May fail due to concurrent zfsctl_vnode_alloc() */ + ip = zfsctl_vnode_alloc(zfsvfs, id, name); + } + + dprintf("%s: returning with %p\n", __func__, ip); + return (ip); +} + +/* + * Create the '.zfs' directory. This directory is cached as part of the VFS + * structure. This results in a hold on the zfsvfs_t. The code in zfs_umount() + * therefore checks against a vfs_count of 2 instead of 1. This reference + * is removed when the ctldir is destroyed in the unmount. All other entities + * under the '.zfs' directory are created dynamically as needed. + * + * Because the dynamically created '.zfs' directory entries assume the use + * of 64-bit vnode numbers this support must be disabled on 32-bit systems. + */ +int +zfsctl_create(zfsvfs_t *zfsvfs) +{ + ASSERT(zfsvfs->z_ctldir == NULL); + + dprintf("%s\n", __func__); + + /* Create root node, tagged with VSYSTEM - see above */ + zfsvfs->z_ctldir = zfsctl_vnode_alloc(zfsvfs, ZFSCTL_INO_ROOT, + ZFS_CTLDIR_NAME); + + if (zfsvfs->z_ctldir == NULL) + return (SET_ERROR(ENOENT)); + + vnode_ref(zfsvfs->z_ctldir); + VN_RELE(zfsvfs->z_ctldir); + + dprintf("%s: done %p\n", __func__, zfsvfs->z_ctldir); + + return (0); +} + +/* + * Destroy the '.zfs' directory or remove a snapshot from + * zfs_snapshots_by_name. Only called when the filesystem is unmounted. + */ +void +zfsctl_destroy(zfsvfs_t *zfsvfs) +{ + if (zfsvfs->z_ctldir) { + if (VN_HOLD(zfsvfs->z_ctldir) == 0) { + vnode_rele(zfsvfs->z_ctldir); + /* Because tagged VSYSTEM, we manually call recycle */ + vnode_recycle(zfsvfs->z_ctldir); + VN_RELE(zfsvfs->z_ctldir); + } + zfsvfs->z_ctldir = NULL; + } +} + +/* + * Construct a full dataset name in full_name: "pool/dataset@snap_name" + */ +static int +zfsctl_snapshot_name(zfsvfs_t *zfsvfs, const char *snap_name, int len, + char *full_name) +{ + objset_t *os = zfsvfs->z_os; + + if (zfs_component_namecheck(snap_name, NULL, NULL) != 0) + return (SET_ERROR(EILSEQ)); + + dmu_objset_name(os, full_name); + if ((strlen(full_name) + 1 + strlen(snap_name)) >= len) + return (SET_ERROR(ENAMETOOLONG)); + + (void) strcat(full_name, "@"); + (void) strcat(full_name, snap_name); + + return (0); +} + +/* + * Given a snapshot name, fetch the dataset name to make dataset@snap + */ +int +zfsctl_snapshot_unmount_name(zfsvfs_t *zfsvfs, const char *name, char *snapname) +{ + char real[ZFS_MAX_DATASET_NAME_LEN]; + const char *nameptr = name; + int error; + + if (zfsvfs->z_case == ZFS_CASE_INSENSITIVE) { + error = dmu_snapshot_realname(zfsvfs->z_os, name, + real, ZFS_MAX_DATASET_NAME_LEN, NULL); + if (error == 0) { + nameptr = real; + } else if (error != ENOTSUP) { + return (error); + } + } + + error = zfsctl_snapshot_name(zfsvfs, nameptr, + ZFS_MAX_DATASET_NAME_LEN, snapname); + return (error); +} + +/* + * Given a root znode, retrieve the associated .zfs directory. + * Add a hold to the vnode and return it. + */ +struct vnode * +zfsctl_root(znode_t *zp) +{ + ASSERT(zfs_has_ctldir(zp)); + VN_HOLD(ZTOZSB(zp)->z_ctldir); + return (ZTOZSB(zp)->z_ctldir); +} + + +struct vnode * +zfs_root_dotdot(struct vnode *vp) +{ + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = ZTOZSB(zp); + znode_t *rootzp = NULL; + struct vnode *retvp = NULL; + + dprintf("%s: for id %llu\n", __func__, zp->z_id); + + if (zp->z_id == ZFSCTL_INO_ROOT) + zfs_zget(zfsvfs, zfsvfs->z_root, &rootzp); + else if (zp->z_id == ZFSCTL_INO_SNAPDIR) + retvp = zfsctl_root(zp); + else + retvp = zfsctl_vnode_lookup(zfsvfs, ZFSCTL_INO_SNAPDIR, + "snapshot"); + + if (rootzp != NULL) + retvp = ZTOV(rootzp); + + dprintf("%s: for id %llu -> vp %p\n", __func__, zp->z_id, retvp); + return (retvp); +} + +static void +zfsctl_delay_if_mounting(struct vnode *vp) +{ + zfsctl_mounts_waiting_t *zcm = NULL; + + dprintf("%s: is_empty %d\n", __func__, + list_is_empty(&zfsctl_mounts_list)); + + /* Quick check for NULL - probably OK outside mutex */ + if (list_is_empty(&zfsctl_mounts_list)) + return; + + /* + * Things to wait on ... + * See if there is a mount happening for our "vp". If we find it + * we also wait for signal. + */ +again: + mutex_enter(&zfsctl_mounts_lock); + for (zcm = list_head(&zfsctl_mounts_list); + zcm; + zcm = list_next(&zfsctl_mounts_list, zcm)) { + if (zcm->zcm_vnode == vp) + break; + } + mutex_exit(&zfsctl_mounts_lock); + + if (zcm != NULL) { + /* + * It would be tempting to call cv_timedwait() here as well but + * the mounter will mutex_destroy after releasing. As this is + * an unusual situation we don't expect to happen very often, + * we go with inelegant sleep + */ + dprintf("Delaying due to mount in progress: found '%s'\n", + zcm->zcm_name); + delay(hz/4); + goto again; + } +} + +/* + * Special case the handling of "..". + */ +int +zfsctl_root_lookup(struct vnode *dvp, char *name, struct vnode **vpp, + int flags, int *direntflags, struct componentname *realpnp) +{ + znode_t *dzp = VTOZ(dvp); + zfsvfs_t *zfsvfs = ZTOZSB(dzp); + int error = 0; + uint64_t id = ZFSCTL_INO_ROOT; + + dprintf("%s: '%s' cn_nameiop 0x%x\n", __func__, name, + realpnp != NULL ? realpnp->cn_nameiop : 0); + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + if (strcmp(name, ".") == 0) { + error = VN_HOLD(dvp); + if (error == 0) + *vpp = dvp; + } else if (strcmp(name, "..") == 0) { + *vpp = zfs_root_dotdot(dvp); + } else if (strcmp(name, ZFS_SNAPDIR_NAME) == 0) { + *vpp = zfsctl_vnode_lookup(zfsvfs, ZFSCTL_INO_SNAPDIR, + name); + } else { + + error = dmu_snapshot_lookup(zfsvfs->z_os, name, &id); + if (error != 0) + goto fail; + + *vpp = zfsctl_vnode_lookup(zfsvfs, ZFSCTL_INO_SHARES - id, + name); + + if (*vpp == NULL) + goto fail; + + /* + * If the request is for DELETE, it may be from rmdir of + * snapshot. if so, we must make sure it is unmounted, + * or it will fail before calling us (EBUSY). + */ + + if (realpnp != NULL && realpnp->cn_nameiop == DELETE) { + if (vnode_mountedhere(*vpp) != NULL) { + char snapname[ZFS_MAX_DATASET_NAME_LEN]; + error = zfsctl_snapshot_unmount_name(zfsvfs, + name, snapname); + if (error != 0) + goto out; + error = zfsctl_snapshot_unmount_node(dvp, + snapname, MNT_FORCE); + } + + } else { + + /* + * Not DELETE - Check if we need to mount it + */ + if (vnode_mountedhere(*vpp) == NULL) { + + /* If usecount here is > 1 we will hang */ + if (vnode_isinuse(*vpp, 1)) + goto out; + + error = zfsctl_snapshot_mount(*vpp, 0); + + } + } // Not DELETE + } // lookup snapdir + +fail: + if (*vpp == NULL) + error = SET_ERROR(ENOENT); + +out: + /* If we are to return ERESTART, but we took a hold, release it */ + if (error == ERESTART) { + /* Dont return ERESTART, fopen doesn't like it */ + error = 0; + if (*vpp != NULL) { + /* Make "sure" mount thread goes first */ + delay(hz >> 1); + } + } + + zfs_exit(zfsvfs, FTAG); + dprintf("lookup exit: %d with vpp %p\n", error, *vpp); + return (error); +} + +int +zfsctl_vnop_lookup(struct vnop_lookup_args *ap) +#if 0 + struct vnop_lookup_args { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + vfs_context_t a_context; + }; +#endif +{ + int direntflags = 0; + int error; + struct componentname *cnp = ap->a_cnp; + char *filename = NULL; + int filename_num_bytes = 0; + + /* + * Darwin uses namelen as an optimisation, for example it can be + * set to 5 for the string "alpha/beta" to look up "alpha". In this + * case we need to copy it out to null-terminate. + */ + if (cnp->cn_nameptr[cnp->cn_namelen] != 0) { + filename_num_bytes = cnp->cn_namelen + 1; + filename = (char *)kmem_alloc(filename_num_bytes, KM_SLEEP); + memcpy(filename, cnp->cn_nameptr, cnp->cn_namelen); + filename[cnp->cn_namelen] = '\0'; + } + + error = zfsctl_root_lookup(ap->a_dvp, + filename ? filename : cnp->cn_nameptr, + ap->a_vpp, /* flags */ 0, &direntflags, cnp); + + /* If we are to create a directory, change error code for XNU */ + if ((error == ENOENT) && + (cnp->cn_flags & ISLASTCN)) { + if ((cnp->cn_nameiop == CREATE) || + (cnp->cn_nameiop == RENAME)) + error = EJUSTRETURN; + } + + if (filename != NULL) + kmem_free(filename, filename_num_bytes); + + return (error); +} + +/* Quick output function for readdir */ +#define DIRENT_RECLEN(namelen, ext) \ + ((ext) ? \ + ((sizeof (struct direntry) + (namelen) - (MAXPATHLEN-1) + 7) & ~7) \ + : \ + ((sizeof (struct dirent) - (NAME_MAX+1)) + (((namelen)+1 + 7) &~ 7))) + +static int zfsctl_dir_emit(const char *name, uint64_t id, enum vtype type, + struct vnop_readdir_args *ap, uint64_t **next) +{ + ZFS_UIO_INIT_XNU(uio, ap->a_uio); + boolean_t extended = (ap->a_flags & VNODE_READDIR_EXTENDED); + struct direntry *eodp; /* Extended */ + struct dirent *odp; /* Standard */ + int namelen; + void *buf; + int error = 0; + ushort_t reclen; + + dprintf("%s '%s'\n", __func__, name); + + namelen = strlen(name); + reclen = DIRENT_RECLEN(namelen, extended); + + if (reclen > zfs_uio_resid(uio)) + return (EINVAL); + + buf = kmem_zalloc(reclen, KM_SLEEP); + + if (extended) { + eodp = buf; + + /* + * NOTE: d_seekoff is the offset for the *next* entry - + * so poke in the previous struct with this id + */ + eodp->d_seekoff = zfs_uio_offset(uio) + 1; + + eodp->d_ino = id; + eodp->d_type = type; + (void) memcpy(eodp->d_name, name, namelen + 1); + eodp->d_namlen = namelen; + eodp->d_reclen = reclen; + + } else { + odp = buf; + + odp->d_ino = id; + odp->d_type = type; + (void) memcpy(odp->d_name, name, namelen + 1); + odp->d_namlen = namelen; + odp->d_reclen = reclen; + + } + + /* Copyout this entry */ + error = zfs_uiomove(buf, (long)reclen, UIO_READ, uio); + + kmem_free(buf, reclen); + return (error); +} + +int +zfsctl_vnop_readdir_root(struct vnop_readdir_args *ap) +#if 0 + struct vnop_readdir_args { + struct vnode a_vp; + struct uio *a_uio; + int a_flags; + int *a_eofflag; + int *a_numdirent; + vfs_context_t a_context; + }; +#endif +{ + int error = 0; + uint64_t *next = NULL; + int entries = 0; + uint64_t offset; + ZFS_UIO_INIT_XNU(uio, ap->a_uio); + znode_t *zp = VTOZ(ap->a_vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + + dprintf("%s\n", __func__); + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + *ap->a_numdirent = 0; + + offset = zfs_uio_offset(uio); + + while (offset < 3 && error == 0) { + + switch (offset) { + case 0: /* "." */ + error = zfsctl_dir_emit(".", ZFSCTL_INO_ROOT, + DT_DIR, ap, &next); + break; + + case 1: /* ".." */ + error = zfsctl_dir_emit("..", 2, + DT_DIR, ap, &next); + break; + + case 2: + error = zfsctl_dir_emit(ZFS_SNAPDIR_NAME, + ZFSCTL_INO_SNAPDIR, DT_DIR, ap, &next); + break; + } + + if (error == ENOENT) { + dprintf("end of snapshots reached\n"); + break; + } + + if (error != 0) { + dprintf("emit error\n"); + break; + } + + entries++; + offset++; + zfs_uio_setoffset(uio, offset); + } + + zfs_uio_setoffset(uio, offset); + + /* Finished without error? Set EOF */ + if (offset >= 3 && error == 0) { + *ap->a_eofflag = 1; + dprintf("Setting eof\n"); + } + + *ap->a_numdirent = entries; + dprintf("Returning %d entries\n", entries); + + zfs_exit(zfsvfs, FTAG); + + return (error); +} + +int +zfsctl_vnop_readdir_snapdir(struct vnop_readdir_args *ap) +#if 0 + struct vnop_readdir_args { + struct vnode a_vp; + struct uio *a_uio; + int a_flags; + int *a_eofflag; + int *a_numdirent; + vfs_context_t a_context; + }; +#endif +{ + int error = 0; + uint64_t *next = NULL; + int entries = 0; + uint64_t offset; + ZFS_UIO_INIT_XNU(uio, ap->a_uio); + boolean_t case_conflict; + uint64_t id; + char snapname[MAXNAMELEN]; + znode_t *zp = VTOZ(ap->a_vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + + dprintf("%s\n", __func__); + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + *ap->a_numdirent = 0; + + offset = zfs_uio_offset(uio); + + while (error == 0) { + + switch (offset) { + case 0: /* "." */ + error = zfsctl_dir_emit(".", ZFSCTL_INO_SNAPDIR, + DT_DIR, ap, &next); + break; + + case 1: /* ".." */ + error = zfsctl_dir_emit("..", ZFSCTL_INO_ROOT, + DT_DIR, ap, &next); + break; + + default: + dsl_pool_config_enter(dmu_objset_pool(zfsvfs->z_os), + FTAG); + error = dmu_snapshot_list_next(zfsvfs->z_os, + MAXNAMELEN, snapname, &id, &offset, &case_conflict); + dsl_pool_config_exit(dmu_objset_pool(zfsvfs->z_os), + FTAG); + if (error) + break; + + error = zfsctl_dir_emit(snapname, + ZFSCTL_INO_SHARES - id, DT_DIR, ap, &next); + break; + } + + if (error != 0) { + dprintf("emit error\n"); + break; + } + + entries++; + offset++; + zfs_uio_setoffset(uio, offset); + } + + zfs_uio_setoffset(uio, offset); + + /* Finished without error? Set EOF */ + if (error == ENOENT) { + *ap->a_eofflag = 1; + dprintf("Setting eof\n"); + error = 0; + } + + *ap->a_numdirent = entries; + dprintf("Returning %d entries\n", entries); + + zfs_exit(zfsvfs, FTAG); + + return (error); +} + + +/* We need to spit out a valid "." ".." entries for mount to work */ +int +zfsctl_vnop_readdir_snapdirs(struct vnop_readdir_args *ap) +#if 0 + struct vnop_readdir_args { + struct vnode a_vp; + struct uio *a_uio; + int a_flags; + int *a_eofflag; + int *a_numdirent; + vfs_context_t a_context; + }; +#endif +{ + int error = 0; + uint64_t *next = NULL; + int entries = 0; + uint64_t offset; + ZFS_UIO_INIT_XNU(uio, ap->a_uio); + znode_t *zp = VTOZ(ap->a_vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + *ap->a_numdirent = 0; + + offset = zfs_uio_offset(uio); + + dprintf("%s: for id %llu: offset %llu\n", __func__, + zp->z_id, offset); + + while (error == 0) { + + switch (offset) { + case 0: /* "." */ + error = zfsctl_dir_emit(".", ZFSCTL_INO_SNAPDIR, + DT_DIR, ap, &next); + break; + + case 1: /* ".." */ + error = zfsctl_dir_emit("..", ZFSCTL_INO_ROOT, + DT_DIR, ap, &next); + break; + + default: + error = ENOENT; + break; + } + + if (error != 0) { + dprintf("emit error\n"); + break; + } + + entries++; + offset++; + zfs_uio_setoffset(uio, offset); + } + + zfs_uio_setoffset(uio, offset); + + /* Finished without error? Set EOF */ + if (error == ENOENT) { + *ap->a_eofflag = 1; + dprintf("Setting eof\n"); + error = 0; + } + + *ap->a_numdirent = entries; + dprintf("Returning %d entries\n", entries); + + zfs_exit(zfsvfs, FTAG); + + return (error); +} + +int +zfsctl_vnop_readdir(struct vnop_readdir_args *ap) +#if 0 + struct vnop_readdir_args { + struct vnode a_vp; + struct uio *a_uio; + int a_flags; + int *a_eofflag; + int *a_numdirent; + vfs_context_t a_context; + }; +#endif +{ + znode_t *zp = VTOZ(ap->a_vp); + + dprintf("%s\n", __func__); + + /* Which directory are we to output? */ + switch (zp->z_id) { + case ZFSCTL_INO_ROOT: + return (zfsctl_vnop_readdir_root(ap)); + case ZFSCTL_INO_SNAPDIR: + return (zfsctl_vnop_readdir_snapdir(ap)); + default: + return (zfsctl_vnop_readdir_snapdirs(ap)); + } + return (EINVAL); +} + +int +zfsctl_vnop_getattr(struct vnop_getattr_args *ap) +#if 0 + struct vnop_getattr_args { + struct vnode *a_vp; + struct vnode_vattr *a_vap; + vfs_context_t a_context; + }; +#endif +{ + vattr_t *vap = ap->a_vap; + struct vnode *vp = ap->a_vp; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + timestruc_t now; + int error; + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + gethrestime(&now); + + if (VATTR_IS_ACTIVE(vap, va_rdev)) + VATTR_RETURN(vap, va_rdev, zfsvfs->z_rdev); + if (VATTR_IS_ACTIVE(vap, va_nlink)) + VATTR_RETURN(vap, va_nlink, + vnode_isdir(vp) ? zp->z_size : zp->z_links); + if (VATTR_IS_ACTIVE(vap, va_total_size)) + VATTR_RETURN(vap, va_total_size, 512); + if (VATTR_IS_ACTIVE(vap, va_total_alloc)) + VATTR_RETURN(vap, va_total_alloc, 512); + if (VATTR_IS_ACTIVE(vap, va_data_size)) + VATTR_RETURN(vap, va_data_size, 0); + if (VATTR_IS_ACTIVE(vap, va_data_alloc)) + VATTR_RETURN(vap, va_data_alloc, 0); + if (VATTR_IS_ACTIVE(vap, va_iosize)) + VATTR_RETURN(vap, va_iosize, 512); + if (VATTR_IS_ACTIVE(vap, va_uid)) + VATTR_RETURN(vap, va_uid, 0); + if (VATTR_IS_ACTIVE(vap, va_gid)) + VATTR_RETURN(vap, va_gid, 0); + if (VATTR_IS_ACTIVE(vap, va_mode)) + VATTR_RETURN(vap, va_mode, S_IFDIR | + S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + if (VATTR_IS_ACTIVE(vap, va_flags)) + VATTR_RETURN(vap, va_flags, zfs_getbsdflags(zp)); + + if (VATTR_IS_ACTIVE(vap, va_acl)) { + VATTR_RETURN(vap, va_uuuid, kauth_null_guid); + VATTR_RETURN(vap, va_guuid, kauth_null_guid); + VATTR_RETURN(vap, va_acl, NULL); + } + + // crtime, atime, mtime, ctime, btime + uint64_t timez[2]; + timez[0] = zfsvfs->z_mount_time; + timez[1] = 0; + + if (VATTR_IS_ACTIVE(vap, va_create_time)) { + ZFS_TIME_DECODE(&vap->va_create_time, timez); + VATTR_SET_SUPPORTED(vap, va_create_time); + } + if (VATTR_IS_ACTIVE(vap, va_access_time)) { + ZFS_TIME_DECODE(&vap->va_access_time, timez); + VATTR_SET_SUPPORTED(vap, va_access_time); + } + if (VATTR_IS_ACTIVE(vap, va_modify_time)) { + ZFS_TIME_DECODE(&vap->va_modify_time, timez); + VATTR_SET_SUPPORTED(vap, va_modify_time); + } + if (VATTR_IS_ACTIVE(vap, va_change_time)) { + ZFS_TIME_DECODE(&vap->va_change_time, timez); + VATTR_SET_SUPPORTED(vap, va_change_time); + } + if (VATTR_IS_ACTIVE(vap, va_backup_time)) { + ZFS_TIME_DECODE(&vap->va_backup_time, timez); + VATTR_SET_SUPPORTED(vap, va_backup_time); + } + if (VATTR_IS_ACTIVE(vap, va_addedtime)) { + ZFS_TIME_DECODE(&vap->va_addedtime, timez); + VATTR_SET_SUPPORTED(vap, va_addedtime); + } + + if (VATTR_IS_ACTIVE(vap, va_fileid)) + VATTR_RETURN(vap, va_fileid, zp->z_id); + if (VATTR_IS_ACTIVE(vap, va_linkid)) + VATTR_RETURN(vap, va_linkid, zp->z_id); + if (VATTR_IS_ACTIVE(vap, va_parentid)) { + switch (zp->z_id) { + case ZFSCTL_INO_ROOT: + // ".zfs" parent is mount, 2 on osx + VATTR_RETURN(vap, va_parentid, 2); + break; + case ZFSCTL_INO_SNAPDIR: + // ".zfs/snapshot" parent is ".zfs" + VATTR_RETURN(vap, va_parentid, ZFSCTL_INO_ROOT); + break; + default: + // ".zfs/snapshot/$name" parent ".zfs/snapshot" + VATTR_RETURN(vap, va_parentid, + ZFSCTL_INO_SNAPDIR); + break; + } + } + if (VATTR_IS_ACTIVE(vap, va_fsid)) + VATTR_RETURN(vap, va_fsid, zfsvfs->z_rdev); + + if (VATTR_IS_ACTIVE(vap, va_filerev)) + VATTR_RETURN(vap, va_filerev, 0); + if (VATTR_IS_ACTIVE(vap, va_gen)) + VATTR_RETURN(vap, va_gen, zp->z_gen); + if (VATTR_IS_ACTIVE(vap, va_type)) + VATTR_RETURN(vap, va_type, vnode_vtype(ZTOV(zp))); + if (VATTR_IS_ACTIVE(vap, va_name)) { + strlcpy(vap->va_name, zp->z_name_cache, MAXPATHLEN); + VATTR_SET_SUPPORTED(vap, va_name); + } + + /* Don't include '.' and '..' in the number of entries */ + if (VATTR_IS_ACTIVE(vap, va_nchildren) && vnode_isdir(vp)) { + VATTR_RETURN(vap, va_nchildren, + zp->z_links > 3 ? zp->z_links-2 : 1); + } + if (VATTR_IS_ACTIVE(vap, va_dirlinkcount) && vnode_isdir(vp)) + VATTR_RETURN(vap, va_dirlinkcount, 1); + +#ifdef VNODE_ATTR_va_fsid64 + if (VATTR_IS_ACTIVE(vap, va_fsid64)) { + vap->va_fsid64.val[0] = + vfs_statfs(zfsvfs->z_vfs)->f_fsid.val[0]; + vap->va_fsid64.val[1] = vfs_typenum(zfsvfs->z_vfs); + VATTR_SET_SUPPORTED(vap, va_fsid64); + } +#endif + + zfs_exit(zfsvfs, FTAG); + + return (0); +} + +int +zfsctl_vnop_access(struct vnop_access_args *ap) +{ + int accmode = ap->a_action; + dprintf("zfsctl_access\n"); + + if (accmode & VWRITE) + return (EACCES); + return (0); +} + +int +zfsctl_vnop_open(struct vnop_open_args *ap) +{ + int flags = ap->a_mode; + struct vnode *vp = ap->a_vp; + znode_t *zp = VTOZ(vp); + + if (flags & FWRITE) + return (EACCES); + + if (zp->z_id == ZFSCTL_INO_SNAPDIR) { + IGNORE_ADD(getpid(), curthread, gethrtime()); + dprintf("Setting to ignore thread %p for mounts\n", + ignore_lookups_tid); + return (zfsctl_snapshot_mount(ap->a_vp, 0)); + } else { + /* If we are to list anything but ".zfs" we should clear */ + dprintf("Clearing ignore thread %p for mounts\n", + ignore_lookups_tid); + IGNORE_FIND_CLEAR(getpid(), curthread, gethrtime()); + } + return (0); +} + +int +zfsctl_vnop_close(struct vnop_close_args *ap) +{ + struct vnode *vp = ap->a_vp; + znode_t *zp = VTOZ(vp); + + if (zp->z_id == ZFSCTL_INO_SNAPDIR) { + dprintf("%s: refreshing tid time\n", __func__); + IGNORE_FIND_SETTIME(getpid(), curthread, gethrtime()); + } + return (0); +} + +int +zfsctl_vnop_inactive(struct vnop_inactive_args *ap) +{ + dprintf("%s\n", __func__); + return (0); +} + +int +zfsctl_vnop_reclaim(struct vnop_reclaim_args *ap) +{ + struct vnode *vp = ap->a_vp; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + + dprintf("%s vp %p\n", __func__, vp); + vnode_removefsref(vp); /* ADDREF from vnode_create */ + vnode_clearfsnode(vp); /* vp->v_data = NULL */ + + mutex_enter(&zfsvfs->z_znodes_lock); + if (list_link_active(&zp->z_link_node)) { + list_remove(&zfsvfs->z_all_znodes, zp); + } + mutex_exit(&zfsvfs->z_znodes_lock); + + zp->z_vnode = NULL; + kmem_cache_free(znode_cache, zp); + + return (0); +} + +int +zfsctl_snapshot_mount(struct vnode *vp, int flags) +{ + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + int ret = 0; + int error; + + /* + * If we are here for a snapdirs directory, attempt to get zed + * to mount the snapshot for the user. If successful, forward the + * vnop_open() to them (ourselves). + * Use a timeout in case zed is not running. + */ + + dprintf("%s: entry: id %llu: pid %d tid %p: auto %u\n", + __func__, zp->z_id, getpid(), curthread, zfs_auto_snapshot); + + if (zfs_auto_snapshot != 1) { + dprintf("%s: zfs_auto_snapshot disabled\n", __func__); + IGNORE_ADD(getpid(), curthread, gethrtime()); + return (0); + } + + for (int i = 0; i < IGNORE_MAX; i++) + if (ignore_lookups_pid[i] == getpid() || + ignore_lookups_tid[i] == curthread) { + dprintf("Ignore thread set for %p (us) or pid %d\n", + curthread, getpid()); + if (gethrtime() - ignore_lookups_time[i] < SEC2NSEC(1)) + return (0); + dprintf("But expired, so ignoring the ignore\n"); + /* expired? clear it. */ + ignore_lookups_pid[i] = 0; + ignore_lookups_tid[i] = 0; + ignore_lookups_time[i] = 0; + } + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + if (((zp->z_id >= zfsvfs->z_ctldir_startid) && + (zp->z_id <= ZFSCTL_INO_SNAPDIRS))) { + hrtime_t now; + now = gethrtime(); + + /* + * If z_snap_mount_time is set, check if it is old enough to + * retry, if so, set z_snap_mount_time to zero. + */ + if (now - zp->z_snap_mount_time > SEC2NSEC(10)) + atomic_cas_64((uint64_t *)&zp->z_snap_mount_time, + (uint64_t)zp->z_snap_mount_time, + 0ULL); + + /* + * Attempt mount, make sure only to issue one request, by + * attempting to CAS in current time in place of zero. + */ + if (atomic_cas_64((uint64_t *)&zp->z_snap_mount_time, 0ULL, + (uint64_t)now) == 0ULL) { + char full_name[ZFS_MAX_DATASET_NAME_LEN]; + + /* First! */ + ret = zfsctl_snapshot_name(zfsvfs, zp->z_name_cache, + ZFS_MAX_DATASET_NAME_LEN, full_name); + + if (ret == 0) { + zfsctl_mounts_waiting_t *zcm; + + /* Create condvar to wait for mount to happen */ + + zcm = kmem_zalloc( + sizeof (zfsctl_mounts_waiting_t), KM_SLEEP); + mutex_init(&zcm->zcm_lock, NULL, MUTEX_DEFAULT, + NULL); + cv_init(&zcm->zcm_cv, NULL, CV_DEFAULT, NULL); + strlcpy(zcm->zcm_name, full_name, + sizeof (zcm->zcm_name)); + /* To match in lookup mount delay */ + zcm->zcm_vnode = vp; + + dprintf("%s: requesting mount for '%s'\n", + __func__, full_name); + + mutex_enter(&zfsctl_mounts_lock); + list_insert_tail(&zfsctl_mounts_list, zcm); + mutex_exit(&zfsctl_mounts_lock); + + mutex_enter(&zcm->zcm_lock); + zfs_ereport_snapshot_post( + FM_RESOURCE_ZFS_SNAPSHOT_MOUNT, + dmu_objset_spa(zfsvfs->z_os), full_name); + + /* Now we wait hoping zed comes back to us */ + ret = cv_timedwait(&zcm->zcm_cv, &zcm->zcm_lock, + ddi_get_lbolt() + SEC_TO_TICK(6)); + + dprintf("%s: finished waiting %d\n", + __func__, ret); + + mutex_exit(&zcm->zcm_lock); + + mutex_enter(&zfsctl_mounts_lock); + list_remove(&zfsctl_mounts_list, zcm); + mutex_exit(&zfsctl_mounts_lock); + + mutex_destroy(&zcm->zcm_lock); + cv_destroy(&zcm->zcm_cv); + + kmem_free(zcm, + sizeof (zfsctl_mounts_waiting_t)); + + /* + * If we mounted, make it re-open it so + * the process that issued the access will + * see the mounted content + */ + if (ret >= 0) { + /* Remove the cache entry */ + ret = ERESTART; + } + } + } + } + + zfs_exit(zfsvfs, FTAG); + + /* If this thread didn't mount, but a mount is in progress, wait */ + if (ret != ERESTART) { + zfsctl_delay_if_mounting(vp); + } + + return (ret); +} + +/* Called whenever zfs_vfs_mount() is called with a snapshot */ +void +zfsctl_mount_signal(char *osname, boolean_t mounting) +{ + zfsctl_mounts_waiting_t *zcm; + struct vnode *root_vnode = NULL; + + dprintf("%s: %s looking for snapshot '%s'\n", __func__, + mounting ? "mounting" : "unmounting", osname); + + mutex_enter(&zfsctl_mounts_lock); + for (zcm = list_head(&zfsctl_mounts_list); + zcm; + zcm = list_next(&zfsctl_mounts_list, zcm)) { + if (strncmp(zcm->zcm_name, osname, sizeof (zcm->zcm_name)) == 0) + break; + } + mutex_exit(&zfsctl_mounts_lock); + + /* Is there someone to wake up? */ + if (zcm != NULL) { + mutex_enter(&zcm->zcm_lock); + root_vnode = zcm->zcm_vnode; + cv_signal(&zcm->zcm_cv); + mutex_exit(&zcm->zcm_lock); + dprintf("%s: mount waiter found and signalled\n", __func__); + } + + zfsctl_unmount_delay_t *zcu; + + /* Add or remove mount to/from list of active mounts */ + + if (mounting) { + /* Add active mounts to the list */ + zcu = kmem_alloc(sizeof (zfsctl_unmount_delay_t), KM_SLEEP); + zcu->se_name = kmem_strdup(osname); + zcu->se_time = gethrestime_sec(); + zcu->se_vnode = root_vnode; + list_link_init(&zcu->se_nodelink); + + mutex_enter(&zfsctl_unmount_list_lock); + list_insert_tail(&zfsctl_unmount_list, zcu); + mutex_exit(&zfsctl_unmount_list_lock); + + } else { + /* Unmounting */ + mutex_enter(&zfsctl_unmount_list_lock); + for (zcu = list_head(&zfsctl_unmount_list); + zcu != NULL; + zcu = list_next(&zfsctl_unmount_list, zcu)) { + if (strcmp(osname, zcu->se_name) == 0) { + + if (zcu->se_vnode != NULL) { + znode_t *zp = VTOZ(zcu->se_vnode); + dprintf("unmount: autounmount pause\n"); + zp->z_snap_mount_time = gethrtime(); + } + + list_remove(&zfsctl_unmount_list, zcu); + kmem_strfree(zcu->se_name); + kmem_free(zcu, sizeof (zfsctl_unmount_delay_t)); + break; + } + } + mutex_exit(&zfsctl_unmount_list_lock); + } +} + +int +zfsctl_snapshot_unmount_node(struct vnode *vp, const char *full_name, + int flags) +{ + znode_t *zp = VTOZ(vp); + int error; + + dprintf("%s\n", __func__); + + if (zp == NULL) + return (ENOENT); + + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + int ret = ENOENT; + /* + * If we are here for a snapdirs directory, attempt to get zed + * to unmount the snapshot for the user. If successful, forward the + * vnop_open() to them (ourselves). + * Use a timeout in case zed is not running. + */ + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + if (zp->z_id == ZFSCTL_INO_SNAPDIR || + zfsvfs->z_root == zp->z_id) { + hrtime_t now; + now = gethrtime(); + + /* + * If z_snap_mount_time is set, check if it is old enough to + * retry, if so, set z_snap_mount_time to zero. + */ + if (now - zp->z_snap_mount_time > SEC2NSEC(10)) + atomic_cas_64((uint64_t *)&zp->z_snap_mount_time, + (uint64_t)zp->z_snap_mount_time, + 0ULL); + + /* + * Attempt unmount, make sure only to issue one request, by + * attempting to CAS in current time in place of zero. + */ + if (atomic_cas_64((uint64_t *)&zp->z_snap_mount_time, 0ULL, + (uint64_t)now) == 0ULL) { + + ret = 0; + + /* First! */ + + if (ret == 0) { + zfsctl_mounts_waiting_t *zcm; + + /* Create condvar to wait for mount to happen */ + + zcm = kmem_zalloc( + sizeof (zfsctl_mounts_waiting_t), KM_SLEEP); + mutex_init(&zcm->zcm_lock, NULL, MUTEX_DEFAULT, + NULL); + cv_init(&zcm->zcm_cv, NULL, CV_DEFAULT, NULL); + strlcpy(zcm->zcm_name, full_name, + sizeof (zcm->zcm_name)); + + dprintf("%s: requesting unmount for '%s'\n", + __func__, full_name); + + mutex_enter(&zfsctl_mounts_lock); + list_insert_tail(&zfsctl_mounts_list, zcm); + mutex_exit(&zfsctl_mounts_lock); + + mutex_enter(&zcm->zcm_lock); + zfs_ereport_snapshot_post( + FM_RESOURCE_ZFS_SNAPSHOT_UNMOUNT, + dmu_objset_spa(zfsvfs->z_os), full_name); + + /* Now we wait hoping zed comes back to us */ + ret = cv_timedwait(&zcm->zcm_cv, &zcm->zcm_lock, + ddi_get_lbolt() + (hz * 3)); + + dprintf("%s: finished waiting %d\n", + __func__, ret); + + mutex_exit(&zcm->zcm_lock); + + mutex_enter(&zfsctl_mounts_lock); + list_remove(&zfsctl_mounts_list, zcm); + mutex_exit(&zfsctl_mounts_lock); + + mutex_destroy(&zcm->zcm_lock); + cv_destroy(&zcm->zcm_cv); + kmem_free(zcm, + sizeof (zfsctl_mounts_waiting_t)); + + /* Allow mounts to happen immediately */ + // zp->z_snap_mount_time = 0; + + /* + * If we unmounted, alert caller + */ + if (ret >= 0) + ret = ERESTART; + + } + } + } + + zfs_exit(zfsvfs, FTAG); + + return (ret); +} + +int +zfsctl_snapshot_unmount(const char *snapname, int flags) +{ + znode_t *rootzp; + zfsvfs_t *zfsvfs; + + dprintf("%s\n", __func__); + + if (strchr(snapname, '@') == NULL) + return (0); + + int err = getzfsvfs(snapname, &zfsvfs); + if (err != 0) { + ASSERT3P(zfsvfs, ==, NULL); + return (0); + } + ASSERT(!dsl_pool_config_held(dmu_objset_pool(zfsvfs->z_os))); + + err = zfs_zget(zfsvfs, zfsvfs->z_root, &rootzp); + vfs_unbusy(zfsvfs->z_vfs); + + if (err == 0) { + zfsctl_snapshot_unmount_node(ZTOV(rootzp), snapname, flags); + VN_RELE(ZTOV(rootzp)); + } + + return (0); +} + +int +zfsctl_vnop_mkdir(struct vnop_mkdir_args *ap) +#if 0 + struct vnop_mkdir_args { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + struct vnode_vattr *a_vap; + vfs_context_t a_context; + }; +#endif +{ + cred_t *cr = (cred_t *)vfs_context_ucred((ap)->a_context); + znode_t *dzp = VTOZ(ap->a_dvp); + zfsvfs_t *zfsvfs = dzp->z_zfsvfs; + char *dsname = NULL; + int error; + + if (zfs_admin_snapshot == 0) + return (SET_ERROR(EACCES)); + + /* Make sure mkdir is directly in snapdir and nowhere else */ + if (dzp->z_id != ZFSCTL_INO_SNAPDIR) + return (SET_ERROR(EROFS)); + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + dsname = kmem_alloc(ZFS_MAX_DATASET_NAME_LEN, KM_SLEEP); + + if (zfs_component_namecheck(ap->a_cnp->cn_nameptr, NULL, NULL) != 0) { + error = SET_ERROR(EILSEQ); + goto out; + } + + dmu_objset_name(zfsvfs->z_os, dsname); + + error = zfs_secpolicy_snapshot_perms(dsname, cr); + if (error != 0) + goto out; + + if (error == 0) { + error = dmu_objset_snapshot_one(dsname, ap->a_cnp->cn_nameptr); + if (error != 0) + goto out; + + error = zfsctl_root_lookup(ap->a_dvp, ap->a_cnp->cn_nameptr, + ap->a_vpp, 0, NULL, NULL); + } + +out: + if (dsname != NULL) + kmem_free(dsname, ZFS_MAX_DATASET_NAME_LEN); + + zfs_exit(zfsvfs, FTAG); + return (error); +} + +int +zfsctl_vnop_rmdir(struct vnop_rmdir_args *ap) +#if 0 + struct vnop_rmdir_args { + struct vnode *a_dvp; + struct vnode *a_vp; + struct componentname *a_cnp; + vfs_context_t a_context; + }; +#endif +{ + cred_t *cr = (cred_t *)vfs_context_ucred((ap)->a_context); + znode_t *dzp = VTOZ(ap->a_dvp); + zfsvfs_t *zfsvfs = dzp->z_zfsvfs; + char snapname[ZFS_MAX_DATASET_NAME_LEN]; + char *name = ap->a_cnp->cn_nameptr; + int error; + + dprintf("%s: '%s'\n", __func__, name); + + if (zfs_admin_snapshot == 0) + return (SET_ERROR(EACCES)); + + /* Make sure rmdir is directly in snapdir and nowhere else */ + if (dzp->z_id != ZFSCTL_INO_SNAPDIR) + return (SET_ERROR(EROFS)); + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + error = zfsctl_snapshot_unmount_name(zfsvfs, name, snapname); + + if (error == 0) + error = zfs_secpolicy_destroy_perms(snapname, cr); + if (error != 0) + goto out; + + error = zfsctl_snapshot_unmount_node(ap->a_vp, snapname, MNT_FORCE); + + if ((error == 0) || (error == ENOENT)) { + error = dsl_destroy_snapshot(snapname, B_FALSE); + + /* Destroy the vnode */ + if (ap->a_vp != NULL) { + dprintf("%s: releasing vp\n", __func__); + vnode_recycle(ap->a_vp); + } + } + +out: + zfs_exit(zfsvfs, FTAG); + return (error); +} + +static void +zfsctl_unmount_thread(void *notused) +{ + callb_cpr_t cpr; + zfsctl_unmount_delay_t *zcu; + time_t now; + CALLB_CPR_INIT(&cpr, &zfsctl_unmount_lock, callb_generic_cpr, FTAG); + + dprintf("%s is alive\n", __func__); + + mutex_enter(&zfsctl_unmount_lock); + while (!zfsctl_unmount_thread_exit) { + + CALLB_CPR_SAFE_BEGIN(&cpr); + (void) cv_timedwait(&zfsctl_unmount_cv, + &zfsctl_unmount_lock, ddi_get_lbolt() + (hz<<6)); + CALLB_CPR_SAFE_END(&cpr, &zfsctl_unmount_lock); + + if (!zfsctl_unmount_thread_exit) { + /* + * Loop all active mounts, if any are older + * than ZFSCTL_EXPIRE_SNAPSHOT, then we update + * their timestamp and attempt unmount. + */ + now = gethrestime_sec(); + mutex_enter(&zfsctl_unmount_list_lock); + for (zcu = list_head(&zfsctl_unmount_list); + zcu != NULL; + zcu = list_next(&zfsctl_unmount_list, zcu)) { + if ((now > zcu->se_time) && + ((now - zcu->se_time) > + zfs_expire_snapshot)) { + zcu->se_time = now; + zfsctl_snapshot_unmount(zcu->se_name, + 0); + } + } + mutex_exit(&zfsctl_unmount_list_lock); + } + } + + zfsctl_unmount_thread_exit = FALSE; + cv_broadcast(&zfsctl_unmount_cv); + CALLB_CPR_EXIT(&cpr); + dprintf("ZFS: zfsctl_unmount thread exit\n"); + thread_exit(); +} + +/* + * Initialize the various pieces we'll need to create and manipulate .zfs + * directories. Currently this is unused but available. + */ +void +zfsctl_init(void) +{ + mutex_init(&zfsctl_mounts_lock, NULL, MUTEX_DEFAULT, NULL); + list_create(&zfsctl_mounts_list, sizeof (zfsctl_mounts_waiting_t), + offsetof(zfsctl_mounts_waiting_t, zcm_node)); + + mutex_init(&zfsctl_unmount_list_lock, NULL, MUTEX_DEFAULT, NULL); + list_create(&zfsctl_unmount_list, sizeof (zfsctl_unmount_delay_t), + offsetof(zfsctl_unmount_delay_t, se_nodelink)); + + mutex_init(&zfsctl_unmount_lock, NULL, MUTEX_DEFAULT, NULL); + cv_init(&zfsctl_unmount_cv, NULL, CV_DEFAULT, NULL); + zfsctl_unmount_thread_exit = FALSE; + + (void) thread_create(NULL, 0, zfsctl_unmount_thread, NULL, 0, &p0, + TS_RUN, minclsyspri); +} + +/* + * Cleanup the various pieces we needed for .zfs directories. In particular + * ensure the expiry timer is canceled safely. + */ +void +zfsctl_fini(void) +{ + mutex_destroy(&zfsctl_mounts_lock); + list_destroy(&zfsctl_mounts_list); + + mutex_destroy(&zfsctl_unmount_list_lock); + list_destroy(&zfsctl_unmount_list); + + mutex_enter(&zfsctl_unmount_lock); + zfsctl_unmount_thread_exit = TRUE; + while (zfsctl_unmount_thread_exit) { + cv_signal(&zfsctl_unmount_cv); + cv_wait(&zfsctl_unmount_cv, &zfsctl_unmount_lock); + } + mutex_exit(&zfsctl_unmount_lock); + + mutex_destroy(&zfsctl_unmount_lock); + cv_destroy(&zfsctl_unmount_cv); +} + +module_param(zfs_admin_snapshot, int, 0644); +MODULE_PARM_DESC(zfs_admin_snapshot, "Enable mkdir/rmdir/mv in .zfs/snapshot"); + +module_param(zfs_expire_snapshot, int, 0644); +MODULE_PARM_DESC(zfs_expire_snapshot, "Seconds to expire .zfs/snapshot"); diff --git a/module/os/macos/zfs/zfs_debug.c b/module/os/macos/zfs/zfs_debug.c new file mode 100644 index 000000000000..2c4938c96824 --- /dev/null +++ b/module/os/macos/zfs/zfs_debug.c @@ -0,0 +1,263 @@ +/* + * 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 (c) 2012, 2014 by Delphix. All rights reserved. + */ + +#include + +typedef struct zfs_dbgmsg { + list_node_t zdm_node; + time_t zdm_timestamp; + int zdm_size; + char zdm_msg[1]; /* variable length allocation */ +} zfs_dbgmsg_t; + +list_t zfs_dbgmsgs; +int zfs_dbgmsg_size; +kmutex_t zfs_dbgmsgs_lock; +int zfs_dbgmsg_maxsize = 4<<20; /* 4MB */ +kstat_t *zfs_dbgmsg_kstat; + +int zfs_dbgmsg_enable = 1; + +static int +zfs_dbgmsg_headers(char *buf, size_t size) +{ + (void) snprintf(buf, size, "%-12s %-8s\n", "timestamp", "message"); + + return (0); +} + +static int +zfs_dbgmsg_data(char *buf, size_t size, void *data) +{ + zfs_dbgmsg_t *zdm = (zfs_dbgmsg_t *)data; + + (void) snprintf(buf, size, "%-12llu %-s\n", + (u_longlong_t)zdm->zdm_timestamp, zdm->zdm_msg); + + return (0); +} + +static void * +zfs_dbgmsg_addr(kstat_t *ksp, loff_t n) +{ + zfs_dbgmsg_t *zdm = (zfs_dbgmsg_t *)ksp->ks_private; + + ASSERT(MUTEX_HELD(&zfs_dbgmsgs_lock)); + + if (n == 0) + ksp->ks_private = list_head(&zfs_dbgmsgs); + else if (zdm) + ksp->ks_private = list_next(&zfs_dbgmsgs, zdm); + + return (ksp->ks_private); +} + +static void +zfs_dbgmsg_purge(int max_size) +{ + zfs_dbgmsg_t *zdm; + int size; + + ASSERT(MUTEX_HELD(&zfs_dbgmsgs_lock)); + + while (zfs_dbgmsg_size > max_size) { + zdm = list_remove_head(&zfs_dbgmsgs); + if (zdm == NULL) + return; + + size = zdm->zdm_size; + kmem_free(zdm, size); + zfs_dbgmsg_size -= size; + } +} + +static int +zfs_dbgmsg_update(kstat_t *ksp, int rw) +{ + if (rw == KSTAT_WRITE) + zfs_dbgmsg_purge(0); + + return (0); +} + +/* + * Debug logging is enabled by default for production kernel builds. + * The overhead for this is negligible and the logs can be valuable when + * debugging. For non-production user space builds all debugging except + * logging is enabled since performance is no longer a concern. + */ +void +zfs_dbgmsg_init(void) +{ + list_create(&zfs_dbgmsgs, sizeof (zfs_dbgmsg_t), + offsetof(zfs_dbgmsg_t, zdm_node)); + mutex_init(&zfs_dbgmsgs_lock, NULL, MUTEX_DEFAULT, NULL); + + zfs_dbgmsg_kstat = kstat_create("zfs", 0, "dbgmsg", "misc", + KSTAT_TYPE_RAW, 0, KSTAT_FLAG_VIRTUAL); + if (zfs_dbgmsg_kstat) { + zfs_dbgmsg_kstat->ks_lock = &zfs_dbgmsgs_lock; + zfs_dbgmsg_kstat->ks_ndata = UINT32_MAX; + zfs_dbgmsg_kstat->ks_private = NULL; + zfs_dbgmsg_kstat->ks_update = zfs_dbgmsg_update; + kstat_set_raw_ops(zfs_dbgmsg_kstat, zfs_dbgmsg_headers, + zfs_dbgmsg_data, zfs_dbgmsg_addr); + kstat_install(zfs_dbgmsg_kstat); + } +} + +void +zfs_dbgmsg_fini(void) +{ + zfs_dbgmsg_t *zdm; + + if (zfs_dbgmsg_kstat) + kstat_delete(zfs_dbgmsg_kstat); + + while ((zdm = list_remove_head(&zfs_dbgmsgs)) != NULL) { + int size = sizeof (zfs_dbgmsg_t) + strlen(zdm->zdm_msg); + kmem_free(zdm, size); + zfs_dbgmsg_size -= size; + } + mutex_destroy(&zfs_dbgmsgs_lock); + ASSERT0(zfs_dbgmsg_size); +} + +void +__set_error(const char *file, const char *func, int line, int err) +{ + /* + * To enable this: + * + * $ echo 512 >/sys/module/zfs/parameters/zfs_flags + */ + if (zfs_flags & ZFS_DEBUG_SET_ERROR) + __dprintf(B_FALSE, file, func, line, "error %d", err); +} + +/* + * Print these messages by running: + * echo ::zfs_dbgmsg | mdb -k + * + * Monitor these messages by running: + * dtrace -qn 'zfs-dbgmsg{printf("%s\n", stringof(arg0))}' + * + * When used with libzpool, monitor with: + * dtrace -qn 'zfs$pid::zfs_dbgmsg:probe1{printf("%s\n", copyinstr(arg1))}' + */ + +/* + * MacOS X's dtrace doesn't handle the PROBEs, so + * we have a utility function that we can watch with + * sudo dtrace -qn '__zfs_dbgmsg:entry{printf("%s\n", stringof(arg0));}' + */ +noinline void +__zfs_dbgmsg(char *buf) +{ + int size = sizeof (zfs_dbgmsg_t) + strlen(buf); + zfs_dbgmsg_t *zdm = kmem_zalloc(size, KM_SLEEP); + zdm->zdm_size = size; + zdm->zdm_timestamp = gethrestime_sec(); + strlcpy(zdm->zdm_msg, buf, size); + + mutex_enter(&zfs_dbgmsgs_lock); + list_insert_tail(&zfs_dbgmsgs, zdm); + zfs_dbgmsg_size += size; + zfs_dbgmsg_purge(MAX(zfs_dbgmsg_maxsize, 0)); + mutex_exit(&zfs_dbgmsgs_lock); +} + +void +__dprintf(boolean_t dprint, const char *file, const char *func, + int line, const char *fmt, ...) +{ + int size, i; + va_list adx; + char *buf, *nl; + char *prefix = (dprint) ? "dprintf: " : ""; + const char *newfile; + + /* + * Get rid of annoying prefix to filename. + */ + newfile = strrchr(file, '/'); + if (newfile != NULL) { + newfile = newfile + 1; /* Get rid of leading / */ + } else { + newfile = file; + } + + va_start(adx, fmt); + size = vsnprintf(NULL, 0, fmt, adx); + va_end(adx); + + size += snprintf(NULL, 0, "%s%s:%d:%s(): ", prefix, newfile, line, + func); + + size++; /* null byte in the "buf" string */ + + /* + * There is one byte of string in sizeof (zfs_dbgmsg_t), used + * for the terminating null. + */ + buf = kmem_alloc(size, KM_SLEEP); + + va_start(adx, fmt); + i = snprintf(buf, size + 1, "%s%s:%d:%s(): ", + prefix, newfile, line, func); + (void) vsnprintf(buf + i, size -i + 1, fmt, adx); + va_end(adx); + + /* + * Get rid of trailing newline for dprintf logs. + */ + if (dprint && buf[0] != '\0') { + nl = &buf[strlen(buf) - 1]; + if (*nl == '\n') + *nl = '\0'; + } + + DTRACE_PROBE1(zfs__dbgmsg, char *, zdm->zdm_msg); + + __zfs_dbgmsg(buf); + + /* Also emit string to log/console */ + printf("%s\n", buf); + + kmem_free(buf, size); +} + +void +zfs_dbgmsg_print(const char *tag) +{ + zfs_dbgmsg_t *zdm; + + (void) printf("ZFS_DBGMSG(%s):\n", tag); + mutex_enter(&zfs_dbgmsgs_lock); + for (zdm = list_head(&zfs_dbgmsgs); zdm; + zdm = list_next(&zfs_dbgmsgs, zdm)) + (void) printf("%s\n", zdm->zdm_msg); + mutex_exit(&zfs_dbgmsgs_lock); +} diff --git a/module/os/macos/zfs/zfs_dir.c b/module/os/macos/zfs/zfs_dir.c new file mode 100644 index 000000000000..74bbebdc7fa0 --- /dev/null +++ b/module/os/macos/zfs/zfs_dir.c @@ -0,0 +1,1219 @@ +/* + * 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, 2016 by Delphix. All rights reserved. + * Copyright 2017 Nexenta Systems, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * zfs_match_find() is used by zfs_dirent_lock() to perform zap lookups + * of names after deciding which is the appropriate lookup interface. + */ +static int +zfs_match_find(zfsvfs_t *zfsvfs, znode_t *dzp, char *name, matchtype_t mt, + boolean_t update, int *deflags, struct componentname *rpnp, uint64_t *zoid) +{ + boolean_t conflict = B_FALSE; + int error; + + if (zfsvfs->z_norm) { + size_t bufsz = 0; + char *buf = NULL; + + if (rpnp) { + buf = rpnp->cn_nameptr; + bufsz = rpnp->cn_pnlen; + } + + /* + * In the non-mixed case we only expect there would ever + * be one match, but we need to use the normalizing lookup. + */ + error = zap_lookup_norm(zfsvfs->z_os, dzp->z_id, name, 8, 1, + zoid, mt, buf, bufsz, &conflict); + } else { + error = zap_lookup(zfsvfs->z_os, dzp->z_id, name, 8, 1, zoid); + } + + /* + * Allow multiple entries provided the first entry is + * the object id. Non-zpl consumers may safely make + * use of the additional space. + * + * XXX: This should be a feature flag for compatibility + */ + if (error == EOVERFLOW) + error = 0; + + if (zfsvfs->z_norm && !error && deflags) + *deflags = conflict ? ED_CASE_CONFLICT : 0; + + *zoid = ZFS_DIRENT_OBJ(*zoid); + + return (error); +} + +/* + * Lock a directory entry. A dirlock on protects that name + * in dzp's directory zap object. As long as you hold a dirlock, you can + * assume two things: (1) dzp cannot be reaped, and (2) no other thread + * can change the zap entry for (i.e. link or unlink) this name. + * + * Input arguments: + * dzp - znode for directory + * name - name of entry to lock + * flag - ZNEW: if the entry already exists, fail with EEXIST. + * ZEXISTS: if the entry does not exist, fail with ENOENT. + * ZSHARED: allow concurrent access with other ZSHARED callers. + * ZXATTR: we want dzp's xattr directory + * ZCILOOK: On a mixed sensitivity file system, + * this lookup should be case-insensitive. + * ZCIEXACT: On a purely case-insensitive file system, + * this lookup should be case-sensitive. + * ZRENAMING: we are locking for renaming, force narrow locks + * ZHAVELOCK: Don't grab the z_name_lock for this call. The + * current thread already holds it. + * + * Output arguments: + * zpp - pointer to the znode for the entry (NULL if there isn't one) + * dlpp - pointer to the dirlock for this entry (NULL on error) + * direntflags - (case-insensitive lookup only) + * flags if multiple case-sensitive matches exist in directory + * realpnp - (case-insensitive lookup only) + * actual name matched within the directory + * + * Return value: 0 on success or errno on failure. + * + * NOTE: Always checks for, and rejects, '.' and '..'. + * NOTE: For case-insensitive file systems we take wide locks (see below), + * but return znode pointers to a single match. + */ +int +zfs_dirent_lock(zfs_dirlock_t **dlpp, znode_t *dzp, char *name, znode_t **zpp, + int flag, int *direntflags, struct componentname *realpnp) +{ + zfsvfs_t *zfsvfs = ZTOZSB(dzp); + zfs_dirlock_t *dl; + boolean_t update; + matchtype_t mt = 0; + uint64_t zoid; + int error = 0; + int cmpflags; + + *zpp = NULL; + *dlpp = NULL; + + /* + * Verify that we are not trying to lock '.', '..', or '.zfs' + */ + if ((name[0] == '.' && + (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) || + (zfs_has_ctldir(dzp) && strcmp(name, ZFS_CTLDIR_NAME) == 0)) + return (SET_ERROR(EEXIST)); + + /* + * Case sensitivity and normalization preferences are set when + * the file system is created. These are stored in the + * zfsvfs->z_case and zfsvfs->z_norm fields. These choices + * affect what vnodes can be cached in the DNLC, how we + * perform zap lookups, and the "width" of our dirlocks. + * + * A normal dirlock locks a single name. Note that with + * normalization a name can be composed multiple ways, but + * when normalized, these names all compare equal. A wide + * dirlock locks multiple names. We need these when the file + * system is supporting mixed-mode access. It is sometimes + * necessary to lock all case permutations of file name at + * once so that simultaneous case-insensitive/case-sensitive + * behaves as rationally as possible. + */ + + /* + * When matching we may need to normalize & change case according to + * FS settings. + * + * Note that a normalized match is necessary for a case insensitive + * filesystem when the lookup request is not exact because normalization + * can fold case independent of normalizing code point sequences. + * + * See the table above zfs_dropname(). + */ + if (zfsvfs->z_norm != 0) { + mt = MT_NORMALIZE; + + /* + * Determine if the match needs to honor the case specified in + * lookup, and if so keep track of that so that during + * normalization we don't fold case. + */ + if ((zfsvfs->z_case == ZFS_CASE_INSENSITIVE && + (flag & ZCIEXACT)) || + (zfsvfs->z_case == ZFS_CASE_MIXED && !(flag & ZCILOOK))) { + mt |= MT_MATCH_CASE; + } + } + + /* + * Only look in or update the DNLC if we are looking for the + * name on a file system that does not require normalization + * or case folding. We can also look there if we happen to be + * on a non-normalizing, mixed sensitivity file system IF we + * are looking for the exact name. + * + * Maybe can add TO-UPPERed version of name to dnlc in ci-only + * case for performance improvement? + */ + update = !zfsvfs->z_norm || + (zfsvfs->z_case == ZFS_CASE_MIXED && + !(zfsvfs->z_norm & ~U8_TEXTPREP_TOUPPER) && !(flag & ZCILOOK)); + + /* + * ZRENAMING indicates we are in a situation where we should + * take narrow locks regardless of the file system's + * preferences for normalizing and case folding. This will + * prevent us deadlocking trying to grab the same wide lock + * twice if the two names happen to be case-insensitive + * matches. + */ + if (flag & ZRENAMING) + cmpflags = 0; + else + cmpflags = zfsvfs->z_norm; + + /* + * Wait until there are no locks on this name. + * + * Don't grab the lock if it is already held. However, cannot + * have both ZSHARED and ZHAVELOCK together. + */ + ASSERT(!(flag & ZSHARED) || !(flag & ZHAVELOCK)); + if (!(flag & ZHAVELOCK)) + rw_enter(&dzp->z_name_lock, RW_READER); + + mutex_enter(&dzp->z_lock); + for (;;) { + if (dzp->z_unlinked && !(flag & ZXATTR)) { + mutex_exit(&dzp->z_lock); + if (!(flag & ZHAVELOCK)) + rw_exit(&dzp->z_name_lock); + return (SET_ERROR(ENOENT)); + } + for (dl = dzp->z_dirlocks; dl != NULL; dl = dl->dl_next) { + if ((u8_strcmp(name, dl->dl_name, 0, cmpflags, + U8_UNICODE_LATEST, &error) == 0) || error != 0) + break; + } + if (error != 0) { + mutex_exit(&dzp->z_lock); + if (!(flag & ZHAVELOCK)) + rw_exit(&dzp->z_name_lock); + return (SET_ERROR(ENOENT)); + } + if (dl == NULL) { + /* + * Allocate a new dirlock and add it to the list. + */ + dl = kmem_alloc(sizeof (zfs_dirlock_t), KM_SLEEP); + cv_init(&dl->dl_cv, NULL, CV_DEFAULT, NULL); + dl->dl_name = name; + dl->dl_sharecnt = 0; + dl->dl_namelock = 0; + dl->dl_namesize = 0; + dl->dl_dzp = dzp; + dl->dl_next = dzp->z_dirlocks; + dzp->z_dirlocks = dl; + break; + } + if ((flag & ZSHARED) && dl->dl_sharecnt != 0) + break; + cv_wait(&dl->dl_cv, &dzp->z_lock); + } + + /* + * If the z_name_lock was NOT held for this dirlock record it. + */ + if (flag & ZHAVELOCK) + dl->dl_namelock = 1; + + if ((flag & ZSHARED) && ++dl->dl_sharecnt > 1 && dl->dl_namesize == 0) { + /* + * We're the second shared reference to dl. Make a copy of + * dl_name in case the first thread goes away before we do. + * Note that we initialize the new name before storing its + * pointer into dl_name, because the first thread may load + * dl->dl_name at any time. It'll either see the old value, + * which belongs to it, or the new shared copy; either is OK. + */ + dl->dl_namesize = strlen(dl->dl_name) + 1; + name = kmem_alloc(dl->dl_namesize, KM_SLEEP); + memcpy(name, dl->dl_name, dl->dl_namesize); + dl->dl_name = name; + } + + mutex_exit(&dzp->z_lock); + + /* + * We have a dirlock on the name. (Note that it is the dirlock, + * not the dzp's z_lock, that protects the name in the zap object.) + * See if there's an object by this name; if so, put a hold on it. + */ + if (flag & ZXATTR) { + error = sa_lookup(dzp->z_sa_hdl, SA_ZPL_XATTR(zfsvfs), &zoid, + sizeof (zoid)); + if (error == 0) + error = (zoid == 0 ? SET_ERROR(ENOENT) : 0); + } else { + error = zfs_match_find(zfsvfs, dzp, name, mt, + update, direntflags, realpnp, &zoid); + } + if (error) { + if (error != ENOENT || (flag & ZEXISTS)) { + zfs_dirent_unlock(dl); + return (error); + } + } else { + if (flag & ZNEW) { + zfs_dirent_unlock(dl); + return (SET_ERROR(EEXIST)); + } + error = zfs_zget(zfsvfs, zoid, zpp); + if (error) { + zfs_dirent_unlock(dl); + return (error); + } + } + + *dlpp = dl; + + return (0); +} + +/* + * Unlock this directory entry and wake anyone who was waiting for it. + */ +void +zfs_dirent_unlock(zfs_dirlock_t *dl) +{ + znode_t *dzp = dl->dl_dzp; + zfs_dirlock_t **prev_dl, *cur_dl; + + mutex_enter(&dzp->z_lock); + + if (!dl->dl_namelock) + rw_exit(&dzp->z_name_lock); + + if (dl->dl_sharecnt > 1) { + dl->dl_sharecnt--; + mutex_exit(&dzp->z_lock); + return; + } + prev_dl = &dzp->z_dirlocks; + while ((cur_dl = *prev_dl) != dl) + prev_dl = &cur_dl->dl_next; + *prev_dl = dl->dl_next; + cv_broadcast(&dl->dl_cv); + mutex_exit(&dzp->z_lock); + + if (dl->dl_namesize != 0) + kmem_free(dl->dl_name, dl->dl_namesize); + cv_destroy(&dl->dl_cv); + kmem_free(dl, sizeof (*dl)); +} + +/* + * Look up an entry in a directory. + * + * NOTE: '.' and '..' are handled as special cases because + * no directory entries are actually stored for them. If this is + * the root of a filesystem, then '.zfs' is also treated as a + * special pseudo-directory. + */ +int +zfs_dirlook(znode_t *dzp, char *name, znode_t **zpp, int flags, + int *deflg, struct componentname *rpnp) +{ + zfs_dirlock_t *dl; + znode_t *zp; + struct vnode *vp; + int error = 0; + uint64_t parent; + + if (name[0] == 0 || (name[0] == '.' && name[1] == 0)) { + *zpp = dzp; + zhold(*zpp); + } else if (name[0] == '.' && name[1] == '.' && name[2] == 0) { + zfsvfs_t *zfsvfs = ZTOZSB(dzp); + + /* + * If we are a snapshot mounted under .zfs, return + * the inode pointer for the snapshot directory. + */ + if ((error = sa_lookup(dzp->z_sa_hdl, + SA_ZPL_PARENT(zfsvfs), &parent, sizeof (parent))) != 0) + return (error); + + if (parent == dzp->z_id && zfsvfs->z_parent != zfsvfs) { + error = zfsctl_root_lookup(zfsvfs->z_parent->z_ctldir, + "snapshot", &vp, 0, NULL, NULL); + if (error == 0) + *zpp = VTOZ(vp); + return (error); + } + rw_enter(&dzp->z_parent_lock, RW_READER); + error = zfs_zget(zfsvfs, parent, &zp); + if (error == 0) + *zpp = zp; + rw_exit(&dzp->z_parent_lock); + } else if (zfs_has_ctldir(dzp) && strcmp(name, ZFS_CTLDIR_NAME) == 0) { + vp = zfsctl_root(dzp); + if (vp != NULL) + *zpp = VTOZ(vp); + else + error = ENOENT; + } else { + int zf; + + zf = ZEXISTS | ZSHARED; + if (flags & FIGNORECASE) + zf |= ZCILOOK; + + error = zfs_dirent_lock(&dl, dzp, name, &zp, zf, deflg, rpnp); + if (error == 0) { + *zpp = zp; + zfs_dirent_unlock(dl); + dzp->z_zn_prefetch = B_TRUE; /* enable prefetching */ + } + rpnp = NULL; + } + + if ((flags & FIGNORECASE) && rpnp && !error) + (void) strlcpy(rpnp->cn_nameptr, name, rpnp->cn_pnlen); + + return (error); +} + +/* + * unlinked Set (formerly known as the "delete queue") Error Handling + * + * When dealing with the unlinked set, we dmu_tx_hold_zap(), but we + * don't specify the name of the entry that we will be manipulating. We + * also fib and say that we won't be adding any new entries to the + * unlinked set, even though we might (this is to lower the minimum file + * size that can be deleted in a full filesystem). So on the small + * chance that the nlink list is using a fat zap (ie. has more than + * 2000 entries), we *may* not pre-read a block that's needed. + * Therefore it is remotely possible for some of the assertions + * regarding the unlinked set below to fail due to i/o error. On a + * nondebug system, this will result in the space being leaked. + */ +void +zfs_unlinked_add(znode_t *zp, dmu_tx_t *tx) +{ + zfsvfs_t *zfsvfs = ZTOZSB(zp); + + ASSERT(zp->z_unlinked); + + VERIFY3U(0, ==, + zap_add_int(zfsvfs->z_os, zfsvfs->z_unlinkedobj, zp->z_id, tx)); + + dataset_kstats_update_nunlinks_kstat(&zfsvfs->z_kstat, 1); +} + +/* + * Clean up any znodes that had no links when we either crashed or + * (force) umounted the file system. + */ +static void +zfs_unlinked_drain_task(void *arg) +{ + zfsvfs_t *zfsvfs = arg; + zap_cursor_t zc; + zap_attribute_t zap; + dmu_object_info_t doi; + znode_t *zp; + int error; + + /* + * Iterate over the contents of the unlinked set. + */ + for (zap_cursor_init(&zc, zfsvfs->z_os, zfsvfs->z_unlinkedobj); + zap_cursor_retrieve(&zc, &zap) == 0 && + zfsvfs->z_drain_state == ZFS_DRAIN_RUNNING; + zap_cursor_advance(&zc)) { + + /* + * See what kind of object we have in list + */ + + error = dmu_object_info(zfsvfs->z_os, + zap.za_first_integer, &doi); + if (error != 0) + continue; + + ASSERT((doi.doi_type == DMU_OT_PLAIN_FILE_CONTENTS) || + (doi.doi_type == DMU_OT_DIRECTORY_CONTENTS)); + /* + * We need to re-mark these list entries for deletion, + * so we pull them back into core and set zp->z_unlinked. + */ + error = zfs_zget(zfsvfs, zap.za_first_integer, &zp); + + /* + * We may pick up znodes that are already marked for deletion. + * This could happen during the purge of an extended attribute + * directory. All we need to do is skip over them, since they + * are already in the system marked z_unlinked. + */ + if (error != 0) + continue; + + zp->z_unlinked = B_TRUE; + + /* + * zrele() decrements the znode's ref count and may cause + * it to be synchronously freed. We interrupt freeing + * of this znode by checking the return value of + * dmu_objset_zfs_unmounting() in dmu_free_long_range() + * when an unmount is requested. + */ + zrele(zp); + ASSERT3B(zfsvfs->z_unmounted, ==, B_FALSE); + } + zap_cursor_fini(&zc); + + mutex_enter(&zfsvfs->z_drain_lock); + zfsvfs->z_drain_state = ZFS_DRAIN_SHUTDOWN; + cv_broadcast(&zfsvfs->z_drain_cv); + mutex_exit(&zfsvfs->z_drain_lock); +} + +/* + * Sets z_draining then tries to dispatch async unlinked drain. + * If that fails executes synchronous unlinked drain. + */ +void +zfs_unlinked_drain(zfsvfs_t *zfsvfs) +{ + ASSERT3B(zfsvfs->z_unmounted, ==, B_FALSE); + + mutex_enter(&zfsvfs->z_drain_lock); + ASSERT(zfsvfs->z_drain_state == ZFS_DRAIN_SHUTDOWN); + zfsvfs->z_drain_state = ZFS_DRAIN_RUNNING; + mutex_exit(&zfsvfs->z_drain_lock); + + if (taskq_dispatch( + dsl_pool_unlinked_drain_taskq(dmu_objset_pool(zfsvfs->z_os)), + zfs_unlinked_drain_task, zfsvfs, TQ_SLEEP) == 0) { + zfs_dbgmsg("async zfs_unlinked_drain dispatch failed"); + zfs_unlinked_drain_task(zfsvfs); + } +} + +/* + * Wait for the unlinked drain taskq task to stop. This will interrupt the + * unlinked set processing if it is in progress. + */ +void +zfs_unlinked_drain_stop_wait(zfsvfs_t *zfsvfs) +{ + ASSERT3B(zfsvfs->z_unmounted, ==, B_FALSE); + + mutex_enter(&zfsvfs->z_drain_lock); + while (zfsvfs->z_drain_state != ZFS_DRAIN_SHUTDOWN) { + zfsvfs->z_drain_state = ZFS_DRAIN_SHUTDOWN_REQ; + cv_wait(&zfsvfs->z_drain_cv, &zfsvfs->z_drain_lock); + } + mutex_exit(&zfsvfs->z_drain_lock); +} + +/* + * Delete the entire contents of a directory. Return a count + * of the number of entries that could not be deleted. If we encounter + * an error, return a count of at least one so that the directory stays + * in the unlinked set. + * + * NOTE: this function assumes that the directory is inactive, + * so there is no need to lock its entries before deletion. + * Also, it assumes the directory contents is *only* regular + * files. + */ +static int +zfs_purgedir(znode_t *dzp) +{ + zap_cursor_t zc; + zap_attribute_t zap; + znode_t *xzp; + dmu_tx_t *tx; + zfsvfs_t *zfsvfs = ZTOZSB(dzp); + zfs_dirlock_t dl; + int skipped = 0; + int error; + + for (zap_cursor_init(&zc, zfsvfs->z_os, dzp->z_id); + (error = zap_cursor_retrieve(&zc, &zap)) == 0; + zap_cursor_advance(&zc)) { + error = zfs_zget_ext(zfsvfs, + ZFS_DIRENT_OBJ(zap.za_first_integer), &xzp, + ZGET_FLAG_ASYNC); + if (error) { + skipped += 1; + continue; + } + + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_sa(tx, dzp->z_sa_hdl, B_FALSE); + dmu_tx_hold_zap(tx, dzp->z_id, FALSE, zap.za_name); + dmu_tx_hold_sa(tx, xzp->z_sa_hdl, B_FALSE); + dmu_tx_hold_zap(tx, zfsvfs->z_unlinkedobj, FALSE, NULL); + /* Is this really needed ? */ + zfs_sa_upgrade_txholds(tx, xzp); + dmu_tx_mark_netfree(tx); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + /* Be aware this is not the "normal" zfs_zrele_async */ + zfs_znode_asyncput(xzp); + skipped += 1; + continue; + } + memset(&dl, 0, sizeof (dl)); + dl.dl_dzp = dzp; + dl.dl_name = zap.za_name; + + error = zfs_link_destroy(&dl, xzp, tx, 0, NULL); + if (error) + skipped += 1; + dmu_tx_commit(tx); + + /* Be aware this is not the "normal" zfs_zrele_async() */ + zfs_znode_asyncput(xzp); + } + zap_cursor_fini(&zc); + if (error != ENOENT) + skipped += 1; + return (skipped); +} + +void +zfs_rmnode(znode_t *zp) +{ + zfsvfs_t *zfsvfs = ZTOZSB(zp); + objset_t *os = zfsvfs->z_os; + znode_t *xzp = NULL; + dmu_tx_t *tx; + uint64_t acl_obj; + uint64_t xattr_obj; + int error; + + /* + * If this is an attribute directory, purge its contents. + */ + if (S_ISDIR(zp->z_mode) && (zp->z_pflags & ZFS_XATTR)) { + if (zfs_purgedir(zp) != 0) { + /* + * Not enough space to delete some xattrs. + * Leave it in the unlinked set. + */ + zfs_znode_dmu_fini(zp); + + return; + } + } + + /* + * Free up all the data in the file. We don't do this for directories + * because we need truncate and remove to be in the same tx, like in + * zfs_znode_delete(). Otherwise, if we crash here we'll end up with + * an inconsistent truncated zap object in the delete queue. Note a + * truncated file is harmless since it only contains user data. + */ + if (S_ISREG(zp->z_mode)) { + error = dmu_free_long_range(os, zp->z_id, 0, DMU_OBJECT_END); + if (error) { + /* + * Not enough space or we were interrupted by unmount. + * Leave the file in the unlinked set. + */ + zfs_znode_dmu_fini(zp); + return; + } + } + + /* + * If the file has extended attributes, we're going to unlink + * the xattr dir. + */ + error = sa_lookup(zp->z_sa_hdl, SA_ZPL_XATTR(zfsvfs), + &xattr_obj, sizeof (xattr_obj)); + if (error == 0 && xattr_obj) { + error = zfs_zget_ext(zfsvfs, xattr_obj, &xzp, + ZGET_FLAG_ASYNC); + ASSERT(error == 0); + } + + acl_obj = zfs_external_acl(zp); + + /* + * Set up the final transaction. + */ + tx = dmu_tx_create(os); + dmu_tx_hold_free(tx, zp->z_id, 0, DMU_OBJECT_END); + dmu_tx_hold_zap(tx, zfsvfs->z_unlinkedobj, FALSE, NULL); + if (xzp) { + dmu_tx_hold_zap(tx, zfsvfs->z_unlinkedobj, TRUE, NULL); + dmu_tx_hold_sa(tx, xzp->z_sa_hdl, B_FALSE); + } + if (acl_obj) + dmu_tx_hold_free(tx, acl_obj, 0, DMU_OBJECT_END); + + zfs_sa_upgrade_txholds(tx, zp); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + /* + * Not enough space to delete the file. Leave it in the + * unlinked set, leaking it until the fs is remounted (at + * which point we'll call zfs_unlinked_drain() to process it). + */ + dmu_tx_abort(tx); + zfs_znode_dmu_fini(zp); + goto out; + } + + if (xzp) { + ASSERT(error == 0); + mutex_enter(&xzp->z_lock); + xzp->z_unlinked = B_TRUE; /* mark xzp for deletion */ + xzp->z_links = 0; /* no more links to it */ + VERIFY(0 == sa_update(xzp->z_sa_hdl, SA_ZPL_LINKS(zfsvfs), + &xzp->z_links, sizeof (xzp->z_links), tx)); + mutex_exit(&xzp->z_lock); + zfs_unlinked_add(xzp, tx); + } + + mutex_enter(&os->os_dsl_dataset->ds_dir->dd_activity_lock); + + /* + * Remove this znode from the unlinked set. If a has rollback has + * occurred while a file is open and unlinked. Then when the file + * is closed post rollback it will not exist in the rolled back + * version of the unlinked object. + */ + error = zap_remove_int(zfsvfs->z_os, zfsvfs->z_unlinkedobj, + zp->z_id, tx); + VERIFY(error == 0 || error == ENOENT); + + uint64_t count; + if (zap_count(os, zfsvfs->z_unlinkedobj, &count) == 0 && count == 0) { + cv_broadcast(&os->os_dsl_dataset->ds_dir->dd_activity_cv); + } + + mutex_exit(&os->os_dsl_dataset->ds_dir->dd_activity_lock); + + dataset_kstats_update_nunlinked_kstat(&zfsvfs->z_kstat, 1); + + zfs_znode_delete(zp, tx); + + dmu_tx_commit(tx); +out: + + /* Be aware this is not the "normal" zfs_zrele_async() */ + if (xzp) + zfs_znode_asyncput(xzp); +} + +static uint64_t +zfs_dirent(znode_t *zp, uint64_t mode) +{ + uint64_t de = zp->z_id; + + if (ZTOZSB(zp)->z_version >= ZPL_VERSION_DIRENT_TYPE) + de |= IFTODT(mode) << 60; + return (de); +} + +/* + * Link zp into dl. Can fail in the following cases : + * - if zp has been unlinked. + * - if the number of entries with the same hash (aka. colliding entries) + * exceed the capacity of a leaf-block of fatzap and splitting of the + * leaf-block does not help. + */ +int +zfs_link_create(zfs_dirlock_t *dl, znode_t *zp, dmu_tx_t *tx, int flag) +{ + znode_t *dzp = dl->dl_dzp; + zfsvfs_t *zfsvfs = ZTOZSB(zp); + uint64_t value; + int zp_is_dir = S_ISDIR(zp->z_mode); + sa_bulk_attr_t bulk[5]; + uint64_t mtime[2], ctime[2]; + int count = 0; + int error; + + mutex_enter(&zp->z_lock); + + if (!(flag & ZRENAMING)) { + if (zp->z_unlinked) { /* no new links to unlinked zp */ + ASSERT(!(flag & (ZNEW | ZEXISTS))); + mutex_exit(&zp->z_lock); + return (SET_ERROR(ENOENT)); + } + zp->z_links++; + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_LINKS(zfsvfs), + NULL, &zp->z_links, sizeof (zp->z_links)); + } + + value = zfs_dirent(zp, zp->z_mode); + error = zap_add(ZTOZSB(zp)->z_os, dzp->z_id, dl->dl_name, 8, 1, + &value, tx); + + /* + * zap_add could fail to add the entry if it exceeds the capacity of the + * leaf-block and zap_leaf_split() failed to help. + * The caller of this routine is responsible for failing the transaction + * which will rollback the SA updates done above. + */ + if (error != 0) { + mutex_exit(&zp->z_lock); + return (error); + } + + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_PARENT(zfsvfs), NULL, + &dzp->z_id, sizeof (dzp->z_id)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, + &zp->z_pflags, sizeof (zp->z_pflags)); + + if (!(flag & ZNEW)) { + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, + ctime, sizeof (ctime)); + zfs_tstamp_update_setup(zp, STATE_CHANGED, mtime, + ctime); + } + error = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); + ASSERT(error == 0); + + mutex_exit(&zp->z_lock); + + mutex_enter(&dzp->z_lock); + dzp->z_size++; + if (zp_is_dir) + dzp->z_links++; + count = 0; + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_SIZE(zfsvfs), NULL, + &dzp->z_size, sizeof (dzp->z_size)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_LINKS(zfsvfs), NULL, + &dzp->z_links, sizeof (dzp->z_links)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, + mtime, sizeof (mtime)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, + ctime, sizeof (ctime)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, + &dzp->z_pflags, sizeof (dzp->z_pflags)); + zfs_tstamp_update_setup(dzp, CONTENT_MODIFIED, mtime, ctime); + error = sa_bulk_update(dzp->z_sa_hdl, bulk, count, tx); + ASSERT(error == 0); + mutex_exit(&dzp->z_lock); + + return (0); +} + +/* + * The match type in the code for this function should conform to: + * + * ------------------------------------------------------------------------ + * fs type | z_norm | lookup type | match type + * ---------|-------------|-------------|---------------------------------- + * CS !norm | 0 | 0 | 0 (exact) + * CS norm | formX | 0 | MT_NORMALIZE + * CI !norm | upper | !ZCIEXACT | MT_NORMALIZE + * CI !norm | upper | ZCIEXACT | MT_NORMALIZE | MT_MATCH_CASE + * CI norm | upper|formX | !ZCIEXACT | MT_NORMALIZE + * CI norm | upper|formX | ZCIEXACT | MT_NORMALIZE | MT_MATCH_CASE + * CM !norm | upper | !ZCILOOK | MT_NORMALIZE | MT_MATCH_CASE + * CM !norm | upper | ZCILOOK | MT_NORMALIZE + * CM norm | upper|formX | !ZCILOOK | MT_NORMALIZE | MT_MATCH_CASE + * CM norm | upper|formX | ZCILOOK | MT_NORMALIZE + * + * Abbreviations: + * CS = Case Sensitive, CI = Case Insensitive, CM = Case Mixed + * upper = case folding set by fs type on creation (U8_TEXTPREP_TOUPPER) + * formX = unicode normalization form set on fs creation + */ +static int +zfs_dropname(zfs_dirlock_t *dl, znode_t *zp, znode_t *dzp, dmu_tx_t *tx, + int flag) +{ + int error; + + if (ZTOZSB(zp)->z_norm) { + matchtype_t mt = MT_NORMALIZE; + + if ((ZTOZSB(zp)->z_case == ZFS_CASE_INSENSITIVE && + (flag & ZCIEXACT)) || + (ZTOZSB(zp)->z_case == ZFS_CASE_MIXED && + !(flag & ZCILOOK))) { + mt |= MT_MATCH_CASE; + } + + error = zap_remove_norm(ZTOZSB(zp)->z_os, dzp->z_id, + dl->dl_name, mt, tx); + } else { + error = zap_remove(ZTOZSB(zp)->z_os, dzp->z_id, dl->dl_name, + tx); + } + + return (error); +} + +/* + * Unlink zp from dl, and mark zp for deletion if this was the last link. Can + * fail if zp is a mount point (EBUSY) or a non-empty directory (ENOTEMPTY). + * If 'unlinkedp' is NULL, we put unlinked znodes on the unlinked list. + * If it's non-NULL, we use it to indicate whether the znode needs deletion, + * and it's the caller's job to do it. + */ +int +zfs_link_destroy(zfs_dirlock_t *dl, znode_t *zp, dmu_tx_t *tx, int flag, + boolean_t *unlinkedp) +{ + znode_t *dzp = dl->dl_dzp; + zfsvfs_t *zfsvfs = ZTOZSB(dzp); + int zp_is_dir = S_ISDIR(zp->z_mode); + boolean_t unlinked = B_FALSE; + sa_bulk_attr_t bulk[5]; + uint64_t mtime[2], ctime[2]; + int count = 0; + int error; + + if (!(flag & ZRENAMING)) { + mutex_enter(&zp->z_lock); + + if (zp_is_dir && !zfs_dirempty(zp)) { + mutex_exit(&zp->z_lock); + return (SET_ERROR(ENOTEMPTY)); + } + + /* + * If we get here, we are going to try to remove the object. + * First try removing the name from the directory; if that + * fails, return the error. + */ + error = zfs_dropname(dl, zp, dzp, tx, flag); + if (error != 0) { + mutex_exit(&zp->z_lock); + return (error); + } + + if (zp->z_links <= zp_is_dir) { + zfs_panic_recover("zfs: link count on %lu is %u, " + "should be at least %u", zp->z_id, + (int)zp->z_links, zp_is_dir + 1); + zp->z_links = zp_is_dir + 1; + } + if (--zp->z_links == zp_is_dir) { + zp->z_unlinked = B_TRUE; + zp->z_links = 0; + unlinked = B_TRUE; + } else { + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), + NULL, &ctime, sizeof (ctime)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), + NULL, &zp->z_pflags, sizeof (zp->z_pflags)); + zfs_tstamp_update_setup(zp, STATE_CHANGED, mtime, + ctime); + } + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_LINKS(zfsvfs), + NULL, &zp->z_links, sizeof (zp->z_links)); + error = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); + count = 0; + ASSERT(error == 0); + mutex_exit(&zp->z_lock); + } else { + error = zfs_dropname(dl, zp, dzp, tx, flag); + if (error != 0) + return (error); + } + + mutex_enter(&dzp->z_lock); + dzp->z_size--; /* one dirent removed */ + if (zp_is_dir) + dzp->z_links--; /* ".." link from zp */ + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_LINKS(zfsvfs), + NULL, &dzp->z_links, sizeof (dzp->z_links)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_SIZE(zfsvfs), + NULL, &dzp->z_size, sizeof (dzp->z_size)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), + NULL, ctime, sizeof (ctime)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), + NULL, mtime, sizeof (mtime)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), + NULL, &dzp->z_pflags, sizeof (dzp->z_pflags)); + zfs_tstamp_update_setup(dzp, CONTENT_MODIFIED, mtime, ctime); + error = sa_bulk_update(dzp->z_sa_hdl, bulk, count, tx); + ASSERT(error == 0); + mutex_exit(&dzp->z_lock); + + if (unlinkedp != NULL) + *unlinkedp = unlinked; + else if (unlinked) + zfs_unlinked_add(zp, tx); + + return (0); +} + +/* + * Indicate whether the directory is empty. Works with or without z_lock + * held, but can only be consider a hint in the latter case. Returns true + * if only "." and ".." remain and there's no work in progress. + * + * The internal ZAP size, rather than zp->z_size, needs to be checked since + * some consumers (Lustre) do not strictly maintain an accurate SA_ZPL_SIZE. + */ +boolean_t +zfs_dirempty(znode_t *dzp) +{ + zfsvfs_t *zfsvfs = ZTOZSB(dzp); + uint64_t count; + int error; + + if (dzp->z_dirlocks != NULL) + return (B_FALSE); + + error = zap_count(zfsvfs->z_os, dzp->z_id, &count); + if (error != 0 || count != 0) + return (B_FALSE); + + return (B_TRUE); +} + +int +zfs_make_xattrdir(znode_t *zp, vattr_t *vap, znode_t **xzpp, cred_t *cr) +{ + zfsvfs_t *zfsvfs = ZTOZSB(zp); + znode_t *xzp; + dmu_tx_t *tx; + int error; + zfs_acl_ids_t acl_ids; + boolean_t fuid_dirtied; +#ifdef DEBUG + uint64_t parent; +#endif + + *xzpp = NULL; + + if ((error = zfs_zaccess(zp, ACE_WRITE_NAMED_ATTRS, 0, B_FALSE, cr, + NULL))) + return (error); + + if ((error = zfs_acl_ids_create(zp, IS_XATTR, vap, cr, NULL, + &acl_ids, NULL)) != 0) + return (error); + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, zp->z_projid)) { + zfs_acl_ids_free(&acl_ids); + return (SET_ERROR(EDQUOT)); + } + + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_sa_create(tx, acl_ids.z_aclp->z_acl_bytes + + ZFS_SA_BASE_ATTR_SIZE); + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE); + dmu_tx_hold_zap(tx, DMU_NEW_OBJECT, FALSE, NULL); + fuid_dirtied = zfsvfs->z_fuid_dirty; + if (fuid_dirtied) + zfs_fuid_txhold(zfsvfs, tx); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + zfs_acl_ids_free(&acl_ids); + dmu_tx_abort(tx); + return (error); + } + zfs_mknode(zp, vap, tx, cr, IS_XATTR, &xzp, &acl_ids); + + if (fuid_dirtied) + zfs_fuid_sync(zfsvfs, tx); + +#ifdef DEBUG + error = sa_lookup(xzp->z_sa_hdl, SA_ZPL_PARENT(zfsvfs), + &parent, sizeof (parent)); + ASSERT(error == 0 && parent == zp->z_id); +#endif + + VERIFY(0 == sa_update(zp->z_sa_hdl, SA_ZPL_XATTR(zfsvfs), &xzp->z_id, + sizeof (xzp->z_id), tx)); + + if (!zp->z_unlinked) + (void) zfs_log_create(zfsvfs->z_log, tx, TX_MKXATTR, zp, + xzp, "", NULL, acl_ids.z_fuidp, vap); + + zfs_acl_ids_free(&acl_ids); + dmu_tx_commit(tx); + +#ifdef __APPLE__ + /* + * OS X - attach the vnode _after_ committing the transaction + */ + zfs_znode_getvnode(xzp, zfsvfs); +#endif + + *xzpp = xzp; + + return (0); +} + +/* + * Return a znode for the extended attribute directory for zp. + * ** If the directory does not already exist, it is created ** + * + * IN: zp - znode to obtain attribute directory from + * cr - credentials of caller + * flags - flags from the VOP_LOOKUP call + * + * OUT: xipp - pointer to extended attribute znode + * + * RETURN: 0 on success + * error number on failure + */ +int +zfs_get_xattrdir(znode_t *zp, znode_t **xzpp, cred_t *cr, int flags) +{ + zfsvfs_t *zfsvfs = ZTOZSB(zp); + znode_t *xzp; + zfs_dirlock_t *dl; + vattr_t va; + int error; +top: + error = zfs_dirent_lock(&dl, zp, "", &xzp, ZXATTR, NULL, NULL); + if (error) + return (error); + + if (xzp != NULL) { + *xzpp = xzp; + zfs_dirent_unlock(dl); + return (0); + } + + if (!(flags & CREATE_XATTR_DIR)) { + zfs_dirent_unlock(dl); + return (SET_ERROR(ENOENT)); + } + + if (zfs_is_readonly(zfsvfs)) { + zfs_dirent_unlock(dl); + return (SET_ERROR(EROFS)); + } + + /* + * The ability to 'create' files in an attribute + * directory comes from the write_xattr permission on the base file. + * + * The ability to 'search' an attribute directory requires + * read_xattr permission on the base file. + * + * Once in a directory the ability to read/write attributes + * is controlled by the permissions on the attribute file. + */ + va.va_mask = ATTR_TYPE | ATTR_MODE | ATTR_UID | ATTR_GID; + va.va_type = VDIR; + va.va_mode = S_IFDIR | S_ISVTX | 0777; + zfs_fuid_map_ids(zp, cr, &va.va_uid, &va.va_gid); + + error = zfs_make_xattrdir(zp, &va, xzpp, cr); + zfs_dirent_unlock(dl); + + if (error == ERESTART) { + /* NB: we already did dmu_tx_wait() if necessary */ + goto top; + } + + return (error); +} + +/* + * Decide whether it is okay to remove within a sticky directory. + * + * In sticky directories, write access is not sufficient; + * you can remove entries from a directory only if: + * + * you own the directory, + * you own the entry, + * you have write access to the entry, + * or you are privileged (checked in secpolicy...). + * + * The function returns 0 if remove access is granted. + */ +int +zfs_sticky_remove_access(znode_t *zdp, znode_t *zp, cred_t *cr) +{ + uid_t uid; + uid_t downer; + uid_t fowner; + zfsvfs_t *zfsvfs = ZTOZSB(zdp); + + if (zfsvfs->z_replay) + return (0); + + if ((zdp->z_mode & S_ISVTX) == 0) + return (0); + + downer = zfs_fuid_map_id(zfsvfs, zdp->z_uid, cr, ZFS_OWNER); + fowner = zfs_fuid_map_id(zfsvfs, zp->z_uid, cr, ZFS_OWNER); + + if ((uid = crgetuid(cr)) == downer || uid == fowner || + (vnode_isreg(ZTOV(zp)) && zfs_zaccess(zp, ACE_WRITE_DATA, + 0, B_FALSE, cr, NULL) == 0)) + return (0); + else + return (secpolicy_vnode_remove(ZTOV(zp), cr)); +} diff --git a/module/os/macos/zfs/zfs_file_os.c b/module/os/macos/zfs/zfs_file_os.c new file mode 100644 index 000000000000..96959505db08 --- /dev/null +++ b/module/os/macos/zfs/zfs_file_os.c @@ -0,0 +1,458 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#define FILE_FD_NOTUSED -1 + +/* + * 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. + */ +noinline int +zfs_file_open(const char *path, int flags, int mode, zfs_file_t **fpp) +{ + struct vnode *vp = NULL; + vfs_context_t vctx; + int error; + + if (!(flags & O_CREAT) && (flags & O_WRONLY)) + flags |= O_EXCL; + + vctx = vfs_context_create((vfs_context_t)0); + error = vnode_open(path, flags, mode, 0, &vp, vctx); + if (error == 0 && vp != NULL) { + zfs_file_t *zf; + zf = (zfs_file_t *)kmem_zalloc(sizeof (zfs_file_t), KM_SLEEP); + zf->f_vnode = vp; + zf->f_fd = FILE_FD_NOTUSED; + + /* Optional, implemented O_APPEND: set offset to file size. */ + if (flags & O_APPEND) + zf->f_ioflags |= IO_APPEND; + + /* O_TRUNC is broken */ + if (flags & O_TRUNC) { + struct vnode_attr va; + + VATTR_INIT(&va); + VATTR_SET(&va, va_data_size, 0); + error = vnode_setattr(vp, &va, vctx); + } + + *fpp = zf; + } + (void) vfs_context_rele(vctx); + + return (error); +} + +void +zfs_file_close(zfs_file_t *fp) +{ + vfs_context_t vctx; + vctx = vfs_context_create((vfs_context_t)0); + vnode_close(fp->f_vnode, fp->f_writes ? FWASWRITTEN : 0, vctx); + (void) vfs_context_rele(vctx); + + kmem_free(fp, sizeof (zfs_file_t)); +} + +static int +zfs_file_write_impl(zfs_file_t *fp, const void *buf, size_t count, + loff_t *off, ssize_t *resid) +{ + int error; + ssize_t local_resid = count; + + /* If we came with a 'fd' use it, as it can handle pipes. */ +again: + if (fp->f_fd == FILE_FD_NOTUSED) + error = zfs_vn_rdwr(UIO_WRITE, fp->f_vnode, (caddr_t)buf, count, + *off, UIO_SYSSPACE, fp->f_ioflags, RLIM64_INFINITY, + kcred, &local_resid); + else + error = spl_vn_rdwr(UIO_WRITE, fp, (caddr_t)buf, count, + *off, UIO_SYSSPACE, fp->f_ioflags, RLIM64_INFINITY, + kcred, &local_resid); + + /* + * We need to handle partial writes and restarts. The test + * zfs_send/zfs_send_sparse is really good at triggering this. + */ + if (error == EAGAIN) { + /* + * No progress at all, sleep a bit so we don't busycpu + * Unfortunately, pipe_select() and fo_select(), are static, + * and VNOP_SELECT is not exported. So we have no choice + * but to static sleep until APPLE exports something for us + */ + if (local_resid == count) + IOSleepWithLeeway(2, 1); + + buf += count - local_resid; + *off += count - local_resid; + count -= count - local_resid; + goto again; + } + + if (error != 0) + return (SET_ERROR(error)); + + fp->f_writes = 1; + + if (resid != NULL) + *resid = local_resid; + else if (local_resid != 0) + return (SET_ERROR(EIO)); + + *off += count - local_resid; + + return (0); +} + +/* + * 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) +{ + loff_t off = fp->f_offset; + ssize_t rc; + + rc = zfs_file_write_impl(fp, buf, count, &off, resid); + if (rc == 0) + fp->f_offset = off; + + return (SET_ERROR(rc)); +} + +/* + * 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 off, + ssize_t *resid) +{ + return (zfs_file_write_impl(fp, buf, count, &off, resid)); +} + +static ssize_t +zfs_file_read_impl(zfs_file_t *fp, void *buf, size_t count, loff_t *off, + ssize_t *resid) +{ + int error; + ssize_t local_resid = count; + + /* If we have realvp, it's faster to call its spl_vn_rdwr */ +again: + if (fp->f_fd == FILE_FD_NOTUSED) + error = zfs_vn_rdwr(UIO_READ, fp->f_vnode, buf, count, + *off, UIO_SYSSPACE, 0, RLIM64_INFINITY, + kcred, &local_resid); + else + error = spl_vn_rdwr(UIO_READ, fp, buf, count, + *off, UIO_SYSSPACE, 0, RLIM64_INFINITY, + kcred, &local_resid); + + /* + * We need to handle partial reads and restarts. + */ + if (error == EAGAIN) { + /* No progress at all, sleep a bit so we don't busycpu */ + if (local_resid == count) + IOSleepWithLeeway(2, 1); + buf += count - local_resid; + *off += count - local_resid; + count -= count - local_resid; + goto again; + } + + if (error) + return (SET_ERROR(error)); + + *off += count - local_resid; + if (resid != NULL) + *resid = local_resid; + + return (SET_ERROR(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) +{ + loff_t off = fp->f_offset; + int rc; + + rc = zfs_file_read_impl(fp, buf, count, &off, resid); + if (rc == 0) + fp->f_offset = off; + return (rc); +} + +/* + * 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 read) + * + * 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) +{ + return (zfs_file_read_impl(fp, buf, count, &off, resid)); +} + +/* + * 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) +{ + if (*offp < 0 || *offp > MAXOFFSET_T) + return (EINVAL); + + switch (whence) { + case SEEK_SET: + fp->f_offset = *offp; + break; + case SEEK_CUR: + fp->f_offset += *offp; + *offp = fp->f_offset; + break; + case SEEK_END: + /* Implement this if eventually needed: get filesize */ + VERIFY0(whence == SEEK_END); + break; + } + + 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 *filp, zfs_file_attr_t *zfattr) +{ + vfs_context_t vctx; + int rc; + vattr_t vap; + + VATTR_INIT(&vap); + VATTR_WANTED(&vap, va_size); + VATTR_WANTED(&vap, va_mode); + + vctx = vfs_context_create((vfs_context_t)0); + rc = vnode_getattr(filp->f_vnode, &vap, vctx); + (void) vfs_context_rele(vctx); + + if (rc) + return (rc); + + zfattr->zfa_size = vap.va_size; + zfattr->zfa_mode = vap.va_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 *filp, int flags) +{ + vfs_context_t vctx; + int rc; + + vctx = vfs_context_create((vfs_context_t)0); + rc = VNOP_FSYNC(filp->f_vnode, (flags == FSYNC), vctx); + (void) vfs_context_rele(vctx); + return (rc); +} + +/* + * 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) +{ + int rc; + struct flock flck = { 0 }; + + flck.l_type = F_FREESP; + flck.l_start = offset; + flck.l_len = len; + flck.l_whence = 0; + + rc = VOP_SPACE(fp->f_vnode, F_FREESP, &flck, + 0, 0, kcred, NULL); + + return (rc); +} + +/* + * Request current file pointer offset + * + * fp - pointer to file + * + * Returns current file offset. + */ +loff_t +zfs_file_off(zfs_file_t *fp) +{ + return (fp->f_offset); +} + +/* + * Request file pointer private data + * + * fp - pointer to file + * + * Returns pointer to file private data. + */ +extern kmutex_t zfsdev_state_lock; +dev_t zfsdev_get_dev(void); + +void * +zfs_file_private(zfs_file_t *fp) +{ + dev_t dev; + void *zs; + + dev = zfsdev_get_dev(); + dprintf("%s: fetching dev x%x\n", __func__, dev); + if (dev == 0) + return (NULL); + + mutex_enter(&zfsdev_state_lock); + zs = zfsdev_get_state(minor(dev), ZST_ALL); + mutex_exit(&zfsdev_state_lock); + dprintf("%s: searching minor %d %p\n", __func__, minor(dev), zs); + + return (zs); +} + +/* + * unlink file + * + * path - fully qualified file path + * + * Returns 0 on success. + * + * OPTIONAL + */ +int +zfs_file_unlink(const char *path) +{ + return (EOPNOTSUPP); +} + +/* + * Get reference to file pointer + * + * fd - input file descriptor + * + * Returns pointer to file struct or NULL + */ +zfs_file_t * +zfs_file_get(int fd) +{ + return (getf(fd)); +} + +/* + * Drop reference to file pointer + * + * fp - input file struct pointer + */ +void +zfs_file_put(zfs_file_t *fp) +{ + releasefp(fp); +} diff --git a/module/os/macos/zfs/zfs_fuid_os.c b/module/os/macos/zfs/zfs_fuid_os.c new file mode 100644 index 000000000000..8d6e9b9c54cd --- /dev/null +++ b/module/os/macos/zfs/zfs_fuid_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 + */ +/* + * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#ifdef _KERNEL +#include +#include +#include +#endif +#include + +uint64_t +zfs_fuid_create_cred(zfsvfs_t *zfsvfs, zfs_fuid_type_t type, + cred_t *cr, zfs_fuid_info_t **fuidp) +{ + uid_t id; + + VERIFY(type == ZFS_OWNER || type == ZFS_GROUP); + + id = (type == ZFS_OWNER) ? crgetuid(cr) : crgetgid(cr); + + if (IS_EPHEMERAL(id)) + return ((type == ZFS_OWNER) ? UID_NOBODY : GID_NOBODY); + + return ((uint64_t)id); +} diff --git a/module/os/macos/zfs/zfs_ioctl_os.c b/module/os/macos/zfs/zfs_ioctl_os.c new file mode 100644 index 000000000000..5251208c358f --- /dev/null +++ b/module/os/macos/zfs/zfs_ioctl_os.c @@ -0,0 +1,488 @@ +/* + * 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, 2020 Jorgen Lundman + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +int zfs_major = 0; +int zfs_bmajor = 0; +static void *zfs_devnode = NULL; +#define ZFS_MAJOR -24 + +boolean_t +zfs_vfs_held(zfsvfs_t *zfsvfs) +{ + return (zfsvfs->z_vfs != NULL); +} + +int +zfs_vfs_ref(zfsvfs_t **zfvp) +{ + int error = 0; + + if (*zfvp == NULL || (*zfvp)->z_vfs == NULL) + return (SET_ERROR(ESRCH)); + + error = vfs_busy((*zfvp)->z_vfs, LK_NOWAIT); + if (error != 0) { + *zfvp = NULL; + error = SET_ERROR(ESRCH); + } + return (error); +} + +void +zfs_vfs_rele(zfsvfs_t *zfsvfs) +{ + vfs_unbusy(zfsvfs->z_vfs); +} + +static uint_t zfsdev_private_tsd; + +dev_t +zfsdev_get_dev(void) +{ + return ((dev_t)(uintptr_t)tsd_get(zfsdev_private_tsd)); +} + +/* We can't set ->private method, so this function does nothing */ +void +zfsdev_private_set_state(void *priv, zfsdev_state_t *zs) +{ + zfsdev_state_t **actual_zs = (zfsdev_state_t **)priv; + if (actual_zs != NULL) + *actual_zs = zs; +} + +/* Loop all zs looking for matching dev_t */ +zfsdev_state_t * +zfsdev_private_get_state(void *priv) +{ + dev_t dev = (dev_t)(uintptr_t)priv; + zfsdev_state_t *zs; + mutex_enter(&zfsdev_state_lock); + zs = zfsdev_get_state(dev, ZST_ALL); + mutex_exit(&zfsdev_state_lock); + return (zs); +} + +static int +zfsdev_open(dev_t dev, int flags, int devtype, struct proc *p) +{ + int error; + zfsdev_state_t *actual_zs = NULL; + + mutex_enter(&zfsdev_state_lock); + + /* + * Check if the minor already exists, something that zfsdev_state_init() + * does internally, but it doesn't know of the minor we are to use. + * This should never happen, so use ASSERT() + */ + ASSERT3P(zfsdev_get_state(minor(dev), ZST_ALL), ==, NULL); + + error = zfsdev_state_init((void *)&actual_zs); + /* + * We are given the minor to use, so we set it here. We can't use + * zfsdev_private_set_state() as it is called before zfsdev_state_init() + * sets the minor. Also, since zfsdev_state_init() doesn't return zs + * nor the minor they pick, we ab/use "priv" to return it to us. + * Maybe we should change zfsdev_state_init() instead of this dance, + * either to take 'minor' to use, or, to return zs. + */ + if (error == 0 && actual_zs != NULL) + actual_zs->zs_minor = minor(dev); + mutex_exit(&zfsdev_state_lock); + + /* Store this dev_t in tsd, so zfs_get_private() can retrieve it */ + tsd_set(zfsdev_private_tsd, (void *)(uintptr_t)dev); + + return (error); +} + +static int +zfsdev_release(dev_t dev, int flags, int devtype, struct proc *p) +{ + /* zfsdev_state_destroy() doesn't check for NULL, so pre-lookup here */ + void *priv; + + priv = (void *)(uintptr_t)minor(dev); + zfsdev_state_t *zs = zfsdev_private_get_state(priv); + if (zs != NULL) + zfsdev_state_destroy(priv); + return (0); +} + +/* !static : so we can dtrace */ +int +zfsdev_ioctl(dev_t dev, ulong_t cmd, caddr_t arg, __unused int xflag, + struct proc *p) +{ + uint_t len, vecnum; + zfs_iocparm_t *zit; + zfs_cmd_t *zc; + int error, rc; + user_addr_t uaddr; + + /* Translate XNU ioctl to enum table: */ + len = IOCPARM_LEN(cmd); + vecnum = cmd - _IOWR('Z', ZFS_IOC_FIRST, zfs_iocparm_t); + zit = (void *)arg; + uaddr = (user_addr_t)zit->zfs_cmd; + + if (len != sizeof (zfs_iocparm_t)) { + /* + * printf("len %d vecnum: %d sizeof (zfs_cmd_t) %lu\n", + * len, vecnum, sizeof (zfs_cmd_t)); + */ + /* + * We can get plenty raw ioctl()s here, for exaple open() will + * cause spec_open() to issue DKIOCGETTHROTTLEMASK. + */ + return (EINVAL); + } + + zc = kmem_zalloc(sizeof (zfs_cmd_t), KM_SLEEP); + + if (copyin(uaddr, zc, sizeof (zfs_cmd_t))) { + error = SET_ERROR(EFAULT); + goto out; + } + + error = zfsdev_ioctl_common(vecnum, zc, 0); + + rc = copyout(zc, uaddr, sizeof (*zc)); + + if (error == 0 && rc != 0) + error = -SET_ERROR(EFAULT); + + /* + * OSX must return(0) or XNU doesn't copyout(). Save the real + * rc to userland + */ + zit->zfs_ioc_error = error; + error = 0; + +out: + kmem_free(zc, sizeof (zfs_cmd_t)); + return (error); + +} + +/* for spa_iokit_dataset_proxy_create */ +#include +#include + + +static int +zfs_secpolicy_os_none(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr) +{ + return (0); +} + +static const zfs_ioc_key_t zfs_keys_proxy_dataset[] = { + {ZPOOL_CONFIG_POOL_NAME, DATA_TYPE_STRING, 0}, +}; + +static int +zfs_ioc_osx_proxy_dataset(const char *unused, nvlist_t *innvl, + nvlist_t *outnvl) +{ + int error; + const char *osname = NULL; + char value[MAXPATHLEN * 2]; + + if (nvlist_lookup_string(innvl, + ZPOOL_CONFIG_POOL_NAME, &osname) != 0) + return (EINVAL); + + /* XXX Get osname */ + + /* Create new virtual disk, and return /dev/disk name */ + error = zfs_osx_proxy_create(osname); + + if (error == 0) + error = zfs_osx_proxy_get_bsdname(osname, + value, sizeof (value)); + + if (error == 0) { + + fnvlist_add_string(outnvl, ZPOOL_CONFIG_POOL_NAME, osname); + fnvlist_add_string(outnvl, ZPOOL_CONFIG_PATH, value); + + printf("%s: Created virtual disk '%s' for '%s'\n", __func__, + value, osname); + } + + return (error); +} + +static const zfs_ioc_key_t zfs_keys_proxy_remove[] = { + {ZPOOL_CONFIG_POOL_NAME, DATA_TYPE_STRING, 0}, +}; + +static int +zfs_ioc_osx_proxy_remove(const char *unused, nvlist_t *innvl, + nvlist_t *outnvl) +{ + const char *osname = NULL; + + if (nvlist_lookup_string(innvl, + ZPOOL_CONFIG_POOL_NAME, &osname) != 0) + return (EINVAL); + + zfs_osx_proxy_remove(osname); + + return (0); +} + +void +zfs_ioctl_init_os(void) +{ + /* APPLE Specific ioctls */ + zfs_ioctl_register("proxy_dataset", ZFS_IOC_PROXY_DATASET, + zfs_ioc_osx_proxy_dataset, zfs_secpolicy_os_none, + NO_NAME, POOL_CHECK_NONE, + B_FALSE, B_FALSE, zfs_keys_proxy_dataset, + ARRAY_SIZE(zfs_keys_proxy_dataset)); + zfs_ioctl_register("proxy_remove", ZFS_IOC_PROXY_REMOVE, + zfs_ioc_osx_proxy_remove, zfs_secpolicy_config, + NO_NAME, POOL_CHECK_NONE, + B_FALSE, B_FALSE, zfs_keys_proxy_remove, + ARRAY_SIZE(zfs_keys_proxy_remove)); +} + +/* ioctl handler for block device. Relay to zvol */ +static int +zfsdev_bioctl(dev_t dev, ulong_t cmd, caddr_t data, + __unused int flag, struct proc *p) +{ + return (zvol_os_ioctl(dev, cmd, data, 1, NULL, NULL)); +} + +static struct bdevsw zfs_bdevsw = { + .d_open = zvol_os_open, + .d_close = zvol_os_close, + .d_strategy = zvol_os_strategy, + .d_ioctl = zfsdev_bioctl, /* block ioctl handler */ + .d_dump = eno_dump, + .d_psize = zvol_os_get_volume_blocksize, + .d_type = D_DISK, +}; + +static struct cdevsw zfs_cdevsw = { + .d_open = zfsdev_open, + .d_close = zfsdev_release, + .d_read = zvol_os_read, + .d_write = zvol_os_write, + .d_ioctl = zfsdev_ioctl, + .d_stop = eno_stop, + .d_reset = eno_reset, + .d_ttys = NULL, + .d_select = eno_select, + .d_mmap = eno_mmap, + .d_strategy = eno_strat, + .d_reserved_1 = eno_getc, + .d_reserved_2 = eno_putc, + .d_type = D_DISK +}; + +#ifdef ZFS_DEBUG +#define ZFS_DEBUG_STR " (DEBUG mode)" +#else +#define ZFS_DEBUG_STR "" +#endif + +static int +openzfs_init_os(void) +{ + return (0); +} + +static void +openzfs_fini_os(void) +{ +} + + + +/* + * This is an identical copy of zfsdev_minor_alloc() except we check if + * 'last_minor + 0' is available instead of 'last_minor + 1'. The latter + * will cycle through minors unnecessarily, when it 'often' is available + * again. Which gives us unattractive things like; + * crw-rw-rw- 1 root wheel 34, 0x0000213A May 31 14:42 /dev/zfs + */ +static minor_t +zfsdev_minor_alloc_os(void) +{ + static minor_t last_minor = 1; + minor_t m; + + ASSERT(MUTEX_HELD(&zfsdev_state_lock)); + + for (m = last_minor; m != last_minor - 1; m++) { + if (m > ZFSDEV_MAX_MINOR) + m = 1; + if (zfsdev_get_state(m, ZST_ALL) == NULL) { + last_minor = m; + return (m); + } + } + + return (0); +} + +/* Callback to create a unique minor for each open */ +static int +zfs_devfs_clone(__unused dev_t dev, int action) +{ + static minor_t minorx; + + if (action == DEVFS_CLONE_ALLOC) { + mutex_enter(&zfsdev_state_lock); + minorx = zfsdev_minor_alloc_os(); + mutex_exit(&zfsdev_state_lock); + return (minorx); + } + return (-1); +} + +int +zfsdev_attach(void) +{ + dev_t dev; + + zfs_bmajor = bdevsw_add(-1, &zfs_bdevsw); + zfs_major = cdevsw_add_with_bdev(-1, &zfs_cdevsw, zfs_bmajor); + + if (zfs_major < 0) { + printf("ZFS: zfs_attach() failed to allocate a major number\n"); + return (-1); + } + + dev = makedev(zfs_major, 0); /* Get the device number */ + zfs_devnode = devfs_make_node_clone(dev, DEVFS_CHAR, UID_ROOT, + GID_WHEEL, 0666, zfs_devfs_clone, "zfs"); + if (!zfs_devnode) { + printf("ZFS: devfs_make_node() failed\n"); + return (-1); + } + + int err = 0; + if ((err = zcommon_init()) != 0) + goto zcommon_failed; + if ((err = icp_init()) != 0) + goto icp_failed; + if ((err = zstd_init()) != 0) + goto zstd_failed; + if ((err = openzfs_init_os()) != 0) + goto openzfs_os_failed; + + tsd_create(&zfsdev_private_tsd, NULL); + + sysctl_os_init(); + + return (0); + +openzfs_os_failed: + zstd_fini(); +zstd_failed: + icp_fini(); +icp_failed: + zcommon_fini(); +zcommon_failed: + return (err); +} + +void +zfsdev_detach(void) +{ + sysctl_os_fini(); + + tsd_destroy(&zfsdev_private_tsd); + + openzfs_fini_os(); + zstd_fini(); + icp_fini(); + zcommon_fini(); + + if (zfs_devnode) { + devfs_remove(zfs_devnode); + zfs_devnode = NULL; + } + if (zfs_major) { + (void) cdevsw_remove(zfs_major, &zfs_cdevsw); + zfs_major = 0; + } +} + +/* Update the VFS's cache of mountpoint properties */ +void +zfs_ioctl_update_mount_cache(const char *dsname) +{ + zfsvfs_t *zfsvfs; + + if (getzfsvfs(dsname, &zfsvfs) == 0) { + /* insert code here */ + zfs_vfs_rele(zfsvfs); + } + /* + * Ignore errors; we can't do anything useful if either getzfsvfs or + * VFS_STATFS fails. + */ +} + +uint64_t +zfs_max_nvlist_src_size_os(void) +{ + if (zfs_max_nvlist_src_size != 0) + return (zfs_max_nvlist_src_size); + + return (KMALLOC_MAX_SIZE); +} diff --git a/module/os/macos/zfs/zfs_osx.cpp b/module/os/macos/zfs/zfs_osx.cpp new file mode 100644 index 000000000000..390a3b3359b3 --- /dev/null +++ b/module/os/macos/zfs/zfs_osx.cpp @@ -0,0 +1,309 @@ +/* + * 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-2020, Jorgen Lundman. All rights reserved. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +// Define the superclass. +#define super IOService + +OSDefineMetaClassAndStructors(org_openzfsonosx_zfs_zvol, IOService) + +extern "C" { + +#include +#include +#include + +extern SInt32 zfs_active_fs_count; + +#ifdef DEBUG +#define ZFS_DEBUG_STR " (DEBUG mode)" +#else +#define ZFS_DEBUG_STR "" +#endif + +static char spl_gitrev[64] = ZFS_META_VERSION "-" ZFS_META_RELEASE; + +SYSCTL_DECL(_zfs); +SYSCTL_NODE(, OID_AUTO, zfs, CTLFLAG_RD, 0, ""); +SYSCTL_STRING(_zfs, OID_AUTO, kext_version, + CTLFLAG_RD | CTLFLAG_LOCKED, + spl_gitrev, 0, "ZFS KEXT Version"); + + +extern kern_return_t _start(kmod_info_t *ki, void *data); +extern kern_return_t _stop(kmod_info_t *ki, void *data); + +__attribute__((visibility("default"))) KMOD_EXPLICIT_DECL(org.openzfsonosx.zfs, + "1.0.0", _start, _stop) +kmod_start_func_t *_realmain = 0; +kmod_stop_func_t *_antimain = 0; +int _kext_apple_cc = __APPLE_CC__; + +} // Extern "C" + +bool +org_openzfsonosx_zfs_zvol::init(OSDictionary* dict) +{ + bool res; + + /* Need an OSSet for open clients */ + _openClients = OSSet::withCapacity(1); + if (_openClients == NULL) { + dprintf("client OSSet failed"); + return (false); + } + + res = super::init(dict); + + // IOLog("ZFS::init\n"); + return (res); +} + +void +org_openzfsonosx_zfs_zvol::free(void) +{ + OSSafeReleaseNULL(_openClients); + + // IOLog("ZFS::free\n"); + super::free(); +} + +bool +org_openzfsonosx_zfs_zvol::isOpen(const IOService *forClient) const +{ + bool ret; + ret = IOService::isOpen(forClient); + return (ret); +} + +bool +org_openzfsonosx_zfs_zvol::handleOpen(IOService *client, + IOOptionBits options, void *arg) +{ + bool ret = true; + + dprintf(""); + + _openClients->setObject(client); + ret = _openClients->containsObject(client); + + return (ret); +} + +bool +org_openzfsonosx_zfs_zvol::handleIsOpen(const IOService *client) const +{ + bool ret; + + dprintf(""); + + ret = _openClients->containsObject(client); + + return (ret); +} + +void +org_openzfsonosx_zfs_zvol::handleClose(IOService *client, + IOOptionBits options) +{ + dprintf(""); + + if (_openClients->containsObject(client) == false) { + dprintf("not open"); + } + + _openClients->removeObject(client); +} + +IOService* +org_openzfsonosx_zfs_zvol::probe(IOService *provider, SInt32 *score) +{ + IOService *res = super::probe(provider, score); + return (res); +} + + +/* + * + * ************************************************************************ + * + * Kernel Module Load + * + * ************************************************************************ + * + */ + +bool +org_openzfsonosx_zfs_zvol::start(IOService *provider) +{ + bool res = super::start(provider); + + IOLog("ZFS: Loading module ... \n"); + + if (!res) + return (res); + + /* Fire up all SPL modules and threads */ + spl_start(NULL, NULL); + + /* registerService() allows zconfigd to match against the service */ + this->registerService(); + + /* + * hostid is left as 0 on OSX, and left to be set if developers wish to + * use it. If it is 0, we will hash the hardware.uuid into a 32 bit + * value and set the hostid. + */ + if (!zone_get_hostid(NULL)) { + uint32_t myhostid = 0; + IORegistryEntry *ioregroot = + IORegistryEntry::getRegistryRoot(); + if (ioregroot) { + IORegistryEntry *macmodel = + ioregroot->getChildEntry(gIOServicePlane); + + if (macmodel) { + OSObject *ioplatformuuidobj; + ioplatformuuidobj = + macmodel->getProperty(kIOPlatformUUIDKey); + if (ioplatformuuidobj) { + OSString *ioplatformuuidstr = + OSDynamicCast(OSString, + ioplatformuuidobj); + + myhostid = fnv_32a_str( + ioplatformuuidstr-> + getCStringNoCopy(), + FNV1_32A_INIT); + + sysctlbyname("kern.hostid", NULL, NULL, + &myhostid, sizeof (myhostid)); + printf("ZFS: hostid set to %08x from " + "UUID '%s'\n", myhostid, + ioplatformuuidstr-> + getCStringNoCopy()); + } + } + } + } + + /* Register ZFS KEXT Version sysctl - separate to kstats */ + sysctl_register_oid(&sysctl__zfs); + sysctl_register_oid(&sysctl__zfs_kext_version); + + /* Init LDI */ + int error = 0; + error = ldi_init(NULL); + if (error) { + IOLog("%s ldi_init error %d\n", __func__, error); + goto failure; + } + + /* Start ZFS itself */ + zfs_kmod_init(); + + /* Register fs with XNU */ + zfs_vfsops_init(); + + /* + * When is the best time to start the system_taskq? It is strictly + * speaking not used by SPL, but by ZFS. ZFS should really start it? + */ + system_taskq_init(); + + res = zfs_boot_init((IOService *)this); + + printf("ZFS: Loaded module v%s-%s%s, " + "ZFS pool version %s, ZFS filesystem version %s\n", + ZFS_META_VERSION, ZFS_META_RELEASE, ZFS_DEBUG_STR, + SPA_VERSION_STRING, ZPL_VERSION_STRING); + + return (true); + +failure: + spl_stop(NULL, NULL); + sysctl_unregister_oid(&sysctl__zfs_kext_version); + sysctl_unregister_oid(&sysctl__zfs); + return (false); +} + +/* Here we are, at the end of all things */ +void +org_openzfsonosx_zfs_zvol::stop(IOService *provider) +{ + + zfs_boot_fini(); + + IOLog("ZFS: Attempting to unload ...\n"); + + super::stop(provider); + + zfs_vfsops_fini(); + + zfs_kmod_fini(); + + system_taskq_fini(); + + ldi_fini(); + + sysctl_unregister_oid(&sysctl__zfs_kext_version); + sysctl_unregister_oid(&sysctl__zfs); + + spl_stop(NULL, NULL); + + printf("ZFS: Unloaded module v%s-%s%s\n", + ZFS_META_VERSION, ZFS_META_RELEASE, ZFS_DEBUG_STR); + + /* + * There is no way to ensure all threads have actually got to the + * thread_exit() call, before we exit here (and XNU unloads all + * memory for the KEXT). So we increase the odds of that happening + * by delaying a little bit before we return to XNU. Quite possibly + * the worst "solution" but Apple has not given any good options. + */ + delay(hz*5); +} diff --git a/module/os/macos/zfs/zfs_racct.c b/module/os/macos/zfs/zfs_racct.c new file mode 100644 index 000000000000..7b5d510c303f --- /dev/null +++ b/module/os/macos/zfs/zfs_racct.c @@ -0,0 +1,32 @@ +/* + * 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 + +void +zfs_racct_read(uint64_t size, uint64_t iops) +{ +} + +void +zfs_racct_write(uint64_t size, uint64_t iops) +{ +} diff --git a/module/os/macos/zfs/zfs_vfsops.c b/module/os/macos/zfs/zfs_vfsops.c new file mode 100644 index 000000000000..ee3d21f0d7c3 --- /dev/null +++ b/module/os/macos/zfs/zfs_vfsops.c @@ -0,0 +1,3051 @@ +/* + * 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 Pawel Jakub Dawidek . + * Copyright (c) 2012, 2015 by Delphix. All rights reserved. + * Copyright 2016 Nexenta Systems, Inc. All rights reserved. + */ + +/* Portions Copyright 2010 Robert Milkowski */ +/* Portions Copyright 2013,2020 Jorgen Lundman */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "zfs_comutil.h" + +#include +#include +#include +#include +#include +#include + +unsigned int zfs_vnop_skip_unlinked_drain = 0; + +int zfs_module_start(kmod_info_t *ki, void *data); +int zfs_module_stop(kmod_info_t *ki, void *data); +extern int getzfsvfs(const char *dsname, zfsvfs_t **zfvp); + +void arc_os_init(void); +void arc_os_fini(void); + +/* + * AVL tree of hardlink entries, which we need to map for Finder. The va_linkid + * needs to be unique for each hardlink target, as well as, return the znode + * in vget(va_linkid). Unfortunately, the va_linkid is 32bit (lost in the + * syscall translation to userland struct). We sort the AVL tree by + * -> directory id + * -> z_id + * -> name + * + */ +static int hardlinks_compare(const void *arg1, const void *arg2) +{ + const hardlinks_t *node1 = arg1; + const hardlinks_t *node2 = arg2; + int value; + if (node1->hl_parent > node2->hl_parent) + return (1); + if (node1->hl_parent < node2->hl_parent) + return (-1); + if (node1->hl_fileid > node2->hl_fileid) + return (1); + if (node1->hl_fileid < node2->hl_fileid) + return (-1); + + value = strncmp(node1->hl_name, node2->hl_name, PATH_MAX); + if (value < 0) + return (-1); + if (value > 0) + return (1); + return (0); +} + +/* + * Lookup same information from linkid, to get at parentid, objid and name + */ +static int hardlinks_compare_linkid(const void *arg1, const void *arg2) +{ + const hardlinks_t *node1 = arg1; + const hardlinks_t *node2 = arg2; + if (node1->hl_linkid > node2->hl_linkid) + return (1); + if (node1->hl_linkid < node2->hl_linkid) + return (-1); + return (0); +} + +extern int +zfs_obtain_xattr(znode_t *, const char *, mode_t, cred_t *, vnode_t **, int); + + +/* + * We need to keep a count of active fs's. + * This is necessary to prevent our kext + * from being unloaded after a umount -f + */ +uint32_t zfs_active_fs_count = 0; + +extern void zfs_ioctl_init(void); +extern void zfs_ioctl_fini(void); + +int +zfs_is_readonly(zfsvfs_t *zfsvfs) +{ + return (!!(vfs_isrdonly(zfsvfs->z_vfs))); +} + +/* + * The OS sync ignored by default, as ZFS handles internal periodic + * syncs. (As per illumos) Unfortunately, we can not tell the difference + * of when users run "sync" by hand. Sync is called on umount though. + */ +uint64_t zfs_vfs_sync_paranoia = 0; + +/* + * IOKit wil use a barrier sync if this is zero, which may be a performance + * gain, at the risk of not doing sync correctly on devices which do not + * support barrier sync (see ioreg -l | grep Barrier). + */ +uint64_t zfs_iokit_sync_paranoia = 1; + +int +zfs_vfs_sync(struct mount *vfsp, __unused int waitfor, + __unused vfs_context_t context) +{ + int error; + /* + * Data integrity is job one. We don't want a compromised kernel + * writing to the storage pool, so we never sync during panic. + */ + if (spl_panicstr()) + return (0); + + /* Check if sysctl setting wants sync - and we are not unmounting */ + if (zfs_vfs_sync_paranoia == 0 && + !vfs_isunmount(vfsp)) + return (0); + + if (vfsp != NULL) { + /* + * Sync a specific filesystem. + */ + zfsvfs_t *zfsvfs = vfs_fsprivate(vfsp); + dsl_pool_t *dp; + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + dp = dmu_objset_pool(zfsvfs->z_os); + + /* + * If the system is shutting down, then skip any + * filesystems which may exist on a suspended pool. + */ + if (spl_system_inshutdown() && spa_suspended(dp->dp_spa)) { + zfs_exit(zfsvfs, FTAG); + return (0); + } + + if (zfsvfs->z_log != NULL) + zil_commit(zfsvfs->z_log, 0); + + zfs_exit(zfsvfs, FTAG); + + } else { + /* + * Sync all ZFS filesystems. This is what happens when you + * run sync(1M). Unlike other filesystems, ZFS honors the + * request by waiting for all pools to commit all dirty data. + */ + spa_sync_allpools(); + } + + return (0); +} + +static void +atime_changed_cb(void *arg, uint64_t newval) +{ + zfsvfs_t *zfsvfs = arg; + + if (newval == B_TRUE) { + zfsvfs->z_atime = B_TRUE; + vfs_clearflags(zfsvfs->z_vfs, (uint64_t)MNT_NOATIME); + } else { + zfsvfs->z_atime = B_FALSE; + vfs_setflags(zfsvfs->z_vfs, (uint64_t)MNT_NOATIME); + } +} + +static void +xattr_changed_cb(void *arg, uint64_t newval) +{ + zfsvfs_t *zfsvfs = arg; + + /* + * Apple does have MNT_NOUSERXATTR mount option, but unfortunately + * the VFS layer returns EACCESS if xattr access is attempted. + * Finder etc, will do so, even if filesystem capabilities is set + * without xattr, rendering the mount option useless. We no longer + * set it, and handle xattrs being disabled internally. + */ + + if (newval == ZFS_XATTR_OFF) { + zfsvfs->z_xattr = B_FALSE; + // vfs_setflags(zfsvfs->z_vfs, (uint64_t)MNT_NOUSERXATTR); + } else { + zfsvfs->z_xattr = B_TRUE; + // vfs_clearflags(zfsvfs->z_vfs, (uint64_t)MNT_NOUSERXATTR); + + if (newval == ZFS_XATTR_SA) + zfsvfs->z_xattr_sa = B_TRUE; + else + zfsvfs->z_xattr_sa = B_FALSE; + } +} + +static void +blksz_changed_cb(void *arg, uint64_t newval) +{ + zfsvfs_t *zfsvfs = arg; + ASSERT3U(newval, <=, spa_maxblocksize(dmu_objset_spa(zfsvfs->z_os))); + ASSERT3U(newval, >=, SPA_MINBLOCKSIZE); + ASSERT(ISP2(newval)); + + zfsvfs->z_max_blksz = newval; + // zfsvfs->z_vfs->mnt_stat.f_iosize = newval; +} + +static void +readonly_changed_cb(void *arg, uint64_t newval) +{ + zfsvfs_t *zfsvfs = arg; + if (newval == B_TRUE) { + vfs_setflags(zfsvfs->z_vfs, (uint64_t)MNT_RDONLY); + } else { + vfs_clearflags(zfsvfs->z_vfs, (uint64_t)MNT_RDONLY); + } +} + +static void +devices_changed_cb(void *arg, uint64_t newval) +{ + zfsvfs_t *zfsvfs = arg; + if (newval == B_FALSE) { + vfs_setflags(zfsvfs->z_vfs, (uint64_t)MNT_NODEV); + } else { + vfs_clearflags(zfsvfs->z_vfs, (uint64_t)MNT_NODEV); + } +} + +static void +setuid_changed_cb(void *arg, uint64_t newval) +{ + zfsvfs_t *zfsvfs = arg; + if (newval == B_FALSE) { + vfs_setflags(zfsvfs->z_vfs, (uint64_t)MNT_NOSUID); + } else { + vfs_clearflags(zfsvfs->z_vfs, (uint64_t)MNT_NOSUID); + } +} + +static void +exec_changed_cb(void *arg, uint64_t newval) +{ + zfsvfs_t *zfsvfs = arg; + if (newval == B_FALSE) { + vfs_setflags(zfsvfs->z_vfs, (uint64_t)MNT_NOEXEC); + } else { + vfs_clearflags(zfsvfs->z_vfs, (uint64_t)MNT_NOEXEC); + } +} + +static void +snapdir_changed_cb(void *arg, uint64_t newval) +{ + zfsvfs_t *zfsvfs = arg; + zfsvfs->z_show_ctldir = newval; + cache_purgevfs(zfsvfs->z_vfs, FALSE); +} + +static void +vscan_changed_cb(void *arg, uint64_t newval) +{ + // zfsvfs_t *zfsvfs = arg; + // zfsvfs->z_vscan = newval; +} + +static void +acl_mode_changed_cb(void *arg, uint64_t newval) +{ + zfsvfs_t *zfsvfs = arg; + zfsvfs->z_acl_mode = newval; +} + +static void +acl_inherit_changed_cb(void *arg, uint64_t newval) +{ + zfsvfs_t *zfsvfs = arg; + + zfsvfs->z_acl_inherit = newval; +} + +static void +finderbrowse_changed_cb(void *arg, uint64_t newval) +{ + zfsvfs_t *zfsvfs = arg; + if (newval == B_FALSE) { + vfs_setflags(zfsvfs->z_vfs, (uint64_t)MNT_DONTBROWSE); + } else { + vfs_clearflags(zfsvfs->z_vfs, (uint64_t)MNT_DONTBROWSE); + } +} +static void +ignoreowner_changed_cb(void *arg, uint64_t newval) +{ + zfsvfs_t *zfsvfs = arg; + if (newval == B_FALSE) { + vfs_clearflags(zfsvfs->z_vfs, (uint64_t)MNT_IGNORE_OWNERSHIP); + } else { + vfs_setflags(zfsvfs->z_vfs, (uint64_t)MNT_IGNORE_OWNERSHIP); + } +} + +static void +mimic_changed_cb(void *arg, uint64_t newval) +{ + zfsvfs_t *zfsvfs = arg; + struct vfsstatfs *vfsstatfs; + vfsstatfs = vfs_statfs(zfsvfs->z_vfs); + + zfsvfs->z_mimic = newval; + + switch (newval) { + default: + case ZFS_MIMIC_OFF: + strlcpy(vfsstatfs->f_fstypename, "zfs", MFSTYPENAMELEN); + break; + case ZFS_MIMIC_HFS: + strlcpy(vfsstatfs->f_fstypename, "hfs", MFSTYPENAMELEN); + break; + case ZFS_MIMIC_APFS: + strlcpy(vfsstatfs->f_fstypename, "apfs", + MFSTYPENAMELEN); + break; + } +} + +static int +zfs_register_callbacks(struct mount *vfsp) +{ + struct dsl_dataset *ds = NULL; + + objset_t *os = NULL; + zfsvfs_t *zfsvfs = NULL; + boolean_t readonly = B_FALSE; + boolean_t do_readonly = B_FALSE; + boolean_t setuid = B_FALSE; + boolean_t do_setuid = B_FALSE; + boolean_t exec = B_FALSE; + boolean_t do_exec = B_FALSE; + boolean_t devices = B_FALSE; + boolean_t do_devices = B_FALSE; + boolean_t xattr = B_FALSE; + boolean_t do_xattr = B_FALSE; + boolean_t atime = B_FALSE; + boolean_t do_atime = B_FALSE; + boolean_t finderbrowse = B_FALSE; + boolean_t do_finderbrowse = B_FALSE; + boolean_t ignoreowner = B_FALSE; + boolean_t do_ignoreowner = B_FALSE; + int error = 0; + + ASSERT(vfsp); + zfsvfs = vfs_fsprivate(vfsp); + ASSERT(zfsvfs); + os = zfsvfs->z_os; + + /* + * This function can be called for a snapshot when we update snapshot's + * mount point, which isn't really supported. + */ + if (dmu_objset_is_snapshot(os)) + return (EOPNOTSUPP); + + /* + * The act of registering our callbacks will destroy any mount + * options we may have. In order to enable temporary overrides + * of mount options, we stash away the current values and + * restore them after we register the callbacks. + */ +#define vfs_optionisset(X, Y, Z) (vfs_flags(X)&(Y)) + + if (vfs_optionisset(vfsp, MNT_RDONLY, NULL) || + !spa_writeable(dmu_objset_spa(os))) { + readonly = B_TRUE; + do_readonly = B_TRUE; + } + if (vfs_optionisset(vfsp, MNT_NODEV, NULL)) { + devices = B_FALSE; + do_devices = B_TRUE; + } + /* xnu SETUID, not IllumOS SUID */ + if (vfs_optionisset(vfsp, MNT_NOSUID, NULL)) { + setuid = B_FALSE; + do_setuid = B_TRUE; + } + if (vfs_optionisset(vfsp, MNT_NOEXEC, NULL)) { + exec = B_FALSE; + do_exec = B_TRUE; + } + if (vfs_optionisset(vfsp, MNT_NOUSERXATTR, NULL)) { + xattr = B_FALSE; + do_xattr = B_TRUE; + } + if (vfs_optionisset(vfsp, MNT_NOATIME, NULL)) { + atime = B_FALSE; + do_atime = B_TRUE; + } + if (vfs_optionisset(vfsp, MNT_DONTBROWSE, NULL)) { + finderbrowse = B_FALSE; + do_finderbrowse = B_TRUE; + } + if (vfs_optionisset(vfsp, MNT_IGNORE_OWNERSHIP, NULL)) { + ignoreowner = B_TRUE; + do_ignoreowner = B_TRUE; + } + + /* + * nbmand is a special property. It can only be changed at + * mount time. + * + * This is weird, but it is documented to only be changeable + * at mount time. + */ + + /* + * Register property callbacks. + * + * It would probably be fine to just check for i/o error from + * the first prop_register(), but I guess I like to go + * overboard... + */ + ds = dmu_objset_ds(os); + dsl_pool_config_enter(dmu_objset_pool(os), FTAG); + error = dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_ATIME), atime_changed_cb, zfsvfs); + error = error ? error : dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_XATTR), xattr_changed_cb, zfsvfs); + error = error ? error : dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_RECORDSIZE), blksz_changed_cb, zfsvfs); + error = error ? error : dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_READONLY), readonly_changed_cb, zfsvfs); + error = error ? error : dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_DEVICES), devices_changed_cb, zfsvfs); + error = error ? error : dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_SETUID), setuid_changed_cb, zfsvfs); + error = error ? error : dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_EXEC), exec_changed_cb, zfsvfs); + error = error ? error : dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_SNAPDIR), snapdir_changed_cb, zfsvfs); + // This appears to be PROP_PRIVATE, investigate if we want this + // ZOL calls this ACLTYPE + error = error ? error : dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_ACLMODE), acl_mode_changed_cb, zfsvfs); + error = error ? error : dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_ACLINHERIT), acl_inherit_changed_cb, + zfsvfs); + error = error ? error : dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_VSCAN), vscan_changed_cb, zfsvfs); + + error = error ? error : dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_BROWSE), finderbrowse_changed_cb, zfsvfs); + error = error ? error : dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_IGNOREOWNER), + ignoreowner_changed_cb, zfsvfs); + error = error ? error : dsl_prop_register(ds, + zfs_prop_to_name(ZFS_PROP_MIMIC), mimic_changed_cb, zfsvfs); + + dsl_pool_config_exit(dmu_objset_pool(os), FTAG); + if (error) + goto unregister; + + /* + * Invoke our callbacks to restore temporary mount options. + */ + if (do_readonly) + readonly_changed_cb(zfsvfs, readonly); + if (do_setuid) + setuid_changed_cb(zfsvfs, setuid); + if (do_exec) + exec_changed_cb(zfsvfs, exec); + if (do_devices) + devices_changed_cb(zfsvfs, devices); + if (do_xattr) + xattr_changed_cb(zfsvfs, xattr); + if (do_atime) + atime_changed_cb(zfsvfs, atime); + + if (do_finderbrowse) + finderbrowse_changed_cb(zfsvfs, finderbrowse); + if (do_ignoreowner) + ignoreowner_changed_cb(zfsvfs, ignoreowner); + + return (0); + +unregister: + dsl_prop_unregister_all(ds, zfsvfs); + return (error); +} + +/* + * Takes a dataset, a property, a value and that value's setpoint as + * found in the ZAP. Checks if the property has been changed in the vfs. + * If so, val and setpoint will be overwritten with updated content. + * Otherwise, they are left unchanged. + */ +int +zfs_get_temporary_prop(dsl_dataset_t *ds, zfs_prop_t zfs_prop, uint64_t *val, + char *setpoint) +{ + int error; + zfsvfs_t *zfvp; +// mount_t vfsp; + objset_t *os; + uint64_t tmp = *val; + + error = dmu_objset_from_ds(ds, &os); + if (error != 0) + return (error); + + if (dmu_objset_type(os) != DMU_OST_ZFS) + return (EINVAL); + + mutex_enter(&os->os_user_ptr_lock); + zfvp = dmu_objset_get_user(os); + mutex_exit(&os->os_user_ptr_lock); + if (zfvp == NULL) + return (ESRCH); + +// vfsp = zfvp->z_vfs; + + switch (zfs_prop) { + case ZFS_PROP_ATIME: +// if (vfsp->vfs_do_atime) +// tmp = vfsp->vfs_atime; + break; + case ZFS_PROP_RELATIME: +// if (vfsp->vfs_do_relatime) +// tmp = vfsp->vfs_relatime; + break; + case ZFS_PROP_DEVICES: +// if (vfsp->vfs_do_devices) +// tmp = vfsp->vfs_devices; + break; + case ZFS_PROP_EXEC: +// if (vfsp->vfs_do_exec) +// tmp = vfsp->vfs_exec; + break; + case ZFS_PROP_SETUID: +// if (vfsp->vfs_do_setuid) +// tmp = vfsp->vfs_setuid; + break; + case ZFS_PROP_READONLY: +// if (vfsp->vfs_do_readonly) +// tmp = vfsp->vfs_readonly; + break; + case ZFS_PROP_XATTR: +// if (vfsp->vfs_do_xattr) +// tmp = vfsp->vfs_xattr; + break; + case ZFS_PROP_NBMAND: +// if (vfsp->vfs_do_nbmand) +// tmp = vfsp->vfs_nbmand; + break; + default: + return (ENOENT); + } + + if (tmp != *val) { + (void) strlcpy(setpoint, "temporary", ZFS_MAX_DATASET_NAME_LEN); + *val = tmp; + } + return (0); +} + + +/* + * Associate this zfsvfs with the given objset, which must be owned. + * This will cache a bunch of on-disk state from the objset in the + * zfsvfs. + */ +static int +zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os) +{ + int error; + uint64_t val; + + zfsvfs->z_max_blksz = SPA_OLD_MAXBLOCKSIZE; + zfsvfs->z_show_ctldir = ZFS_SNAPDIR_VISIBLE; + zfsvfs->z_os = os; + + /* Volume status "all ok" */ + zfsvfs->z_notification_conditions = 0; + zfsvfs->z_freespace_notify_warninglimit = 0; + zfsvfs->z_freespace_notify_dangerlimit = 0; + zfsvfs->z_freespace_notify_desiredlevel = 0; + + error = zfs_get_zplprop(os, ZFS_PROP_VERSION, &zfsvfs->z_version); + if (error != 0) + return (error); + if (zfsvfs->z_version > + zfs_zpl_version_map(spa_version(dmu_objset_spa(os)))) { + (void) printf("Can't mount a version %lld file system " + "on a version %lld pool\n. Pool must be upgraded to mount " + "this file system.\n", (u_longlong_t)zfsvfs->z_version, + (u_longlong_t)spa_version(dmu_objset_spa(os))); + return (SET_ERROR(ENOTSUP)); + } + error = zfs_get_zplprop(os, ZFS_PROP_NORMALIZE, &val); + if (error != 0) + return (error); + zfsvfs->z_norm = (int)val; + + error = zfs_get_zplprop(os, ZFS_PROP_UTF8ONLY, &val); + if (error != 0) + return (error); + zfsvfs->z_utf8 = (val != 0); + + error = zfs_get_zplprop(os, ZFS_PROP_CASE, &val); + if (error != 0) + return (error); + zfsvfs->z_case = (uint_t)val; + + error = zfs_get_zplprop(os, ZFS_PROP_ACLMODE, &val); + if (error != 0) + return (error); + zfsvfs->z_acl_mode = (uint_t)val; + + zfs_get_zplprop(os, ZFS_PROP_LASTUNMOUNT, &val); + zfsvfs->z_last_unmount_time = val; + + /* + * Fold case on file systems that are always or sometimes case + * insensitive. + */ + if (zfsvfs->z_case == ZFS_CASE_INSENSITIVE || + zfsvfs->z_case == ZFS_CASE_MIXED) + zfsvfs->z_norm |= U8_TEXTPREP_TOUPPER; + + zfsvfs->z_use_fuids = USE_FUIDS(zfsvfs->z_version, zfsvfs->z_os); + zfsvfs->z_use_sa = USE_SA(zfsvfs->z_version, zfsvfs->z_os); + + uint64_t sa_obj = 0; + if (zfsvfs->z_use_sa) { + /* should either have both of these objects or none */ + error = zap_lookup(os, MASTER_NODE_OBJ, ZFS_SA_ATTRS, 8, 1, + &sa_obj); + + if (error != 0) + return (error); + + error = zfs_get_zplprop(os, ZFS_PROP_XATTR, &val); + if ((error == 0) && (val == ZFS_XATTR_SA)) + zfsvfs->z_xattr_sa = B_TRUE; + } + + error = zap_lookup(os, MASTER_NODE_OBJ, ZFS_ROOT_OBJ, 8, 1, + &zfsvfs->z_root); + if (error != 0) + return (error); + ASSERT(zfsvfs->z_root != 0); + + error = zap_lookup(os, MASTER_NODE_OBJ, ZFS_UNLINKED_SET, 8, 1, + &zfsvfs->z_unlinkedobj); + if (error != 0) + return (error); + + error = zap_lookup(os, MASTER_NODE_OBJ, + zfs_userquota_prop_prefixes[ZFS_PROP_USERQUOTA], + 8, 1, &zfsvfs->z_userquota_obj); + if (error == ENOENT) + zfsvfs->z_userquota_obj = 0; + else if (error != 0) + return (error); + + error = zap_lookup(os, MASTER_NODE_OBJ, + zfs_userquota_prop_prefixes[ZFS_PROP_GROUPQUOTA], + 8, 1, &zfsvfs->z_groupquota_obj); + if (error == ENOENT) + zfsvfs->z_groupquota_obj = 0; + else if (error != 0) + return (error); + + error = zap_lookup(os, MASTER_NODE_OBJ, + zfs_userquota_prop_prefixes[ZFS_PROP_PROJECTQUOTA], + 8, 1, &zfsvfs->z_projectquota_obj); + if (error == ENOENT) + zfsvfs->z_projectquota_obj = 0; + else if (error != 0) + return (error); + + error = zap_lookup(os, MASTER_NODE_OBJ, + zfs_userquota_prop_prefixes[ZFS_PROP_USEROBJQUOTA], + 8, 1, &zfsvfs->z_userobjquota_obj); + if (error == ENOENT) + zfsvfs->z_userobjquota_obj = 0; + else if (error != 0) + return (error); + + error = zap_lookup(os, MASTER_NODE_OBJ, + zfs_userquota_prop_prefixes[ZFS_PROP_GROUPOBJQUOTA], + 8, 1, &zfsvfs->z_groupobjquota_obj); + if (error == ENOENT) + zfsvfs->z_groupobjquota_obj = 0; + else if (error != 0) + return (error); + + error = zap_lookup(os, MASTER_NODE_OBJ, + zfs_userquota_prop_prefixes[ZFS_PROP_PROJECTOBJQUOTA], + 8, 1, &zfsvfs->z_projectobjquota_obj); + if (error == ENOENT) + zfsvfs->z_projectobjquota_obj = 0; + else if (error != 0) + return (error); + + error = zap_lookup(os, MASTER_NODE_OBJ, ZFS_FUID_TABLES, 8, 1, + &zfsvfs->z_fuid_obj); + if (error == ENOENT) + zfsvfs->z_fuid_obj = 0; + else if (error != 0) + return (error); + + error = zap_lookup(os, MASTER_NODE_OBJ, ZFS_SHARES_DIR, 8, 1, + &zfsvfs->z_shares_dir); + if (error == ENOENT) + zfsvfs->z_shares_dir = 0; + else if (error != 0) + return (error); + + error = sa_setup(os, sa_obj, zfs_attr_table, ZPL_END, + &zfsvfs->z_attr_table); + if (error != 0) + return (error); + + if (zfsvfs->z_version >= ZPL_VERSION_SA) + sa_register_update_callback(os, zfs_sa_upgrade); + + return (0); +} + +int +zfsvfs_create(const char *osname, boolean_t readonly, zfsvfs_t **zfvp) +{ + objset_t *os; + zfsvfs_t *zfsvfs; + int error; + boolean_t ro = (readonly || (strchr(osname, '@') != NULL)); + + zfsvfs = kmem_zalloc(sizeof (zfsvfs_t), KM_SLEEP); + + /* + * We claim to always be readonly so we can open snapshots; + * other ZPL code will prevent us from writing to snapshots. + */ + error = dmu_objset_own(osname, DMU_OST_ZFS, ro, B_TRUE, + zfsvfs, &os); + if (error != 0) { + kmem_free(zfsvfs, sizeof (zfsvfs_t)); + return (error); + } + + error = zfsvfs_create_impl(zfvp, zfsvfs, os); + if (error != 0) { + dmu_objset_disown(os, B_TRUE, zfsvfs); + } + return (error); +} + +int +zfsvfs_create_impl(zfsvfs_t **zfvp, zfsvfs_t *zfsvfs, objset_t *os) +{ + int error; + + zfsvfs->z_vfs = NULL; + zfsvfs->z_parent = zfsvfs; + + mutex_init(&zfsvfs->z_znodes_lock, NULL, MUTEX_DEFAULT, NULL); + mutex_init(&zfsvfs->z_lock, NULL, MUTEX_DEFAULT, NULL); + list_create(&zfsvfs->z_all_znodes, sizeof (znode_t), + offsetof(znode_t, z_link_node)); + + zfsvfs->z_ctldir_startid = ZFSCTL_INO_SNAPDIRS; + + rrm_init(&zfsvfs->z_teardown_lock, B_FALSE); + + rw_init(&zfsvfs->z_teardown_inactive_lock, NULL, RW_DEFAULT, NULL); + rw_init(&zfsvfs->z_fuid_lock, NULL, RW_DEFAULT, NULL); + + int size = MIN(1 << (highbit64(zfs_object_mutex_size) - 1), + ZFS_OBJ_MTX_MAX); + zfsvfs->z_hold_size = size; + zfsvfs->z_hold_trees = vmem_zalloc(sizeof (avl_tree_t) * size, + KM_SLEEP); + zfsvfs->z_hold_locks = vmem_zalloc(sizeof (kmutex_t) * size, KM_SLEEP); + for (int i = 0; i != size; i++) { + avl_create(&zfsvfs->z_hold_trees[i], zfs_znode_hold_compare, + sizeof (znode_hold_t), offsetof(znode_hold_t, zh_node)); + mutex_init(&zfsvfs->z_hold_locks[i], NULL, MUTEX_DEFAULT, NULL); + } + + rw_init(&zfsvfs->z_hardlinks_lock, NULL, RW_DEFAULT, NULL); + avl_create(&zfsvfs->z_hardlinks, hardlinks_compare, + sizeof (hardlinks_t), offsetof(hardlinks_t, hl_node)); + avl_create(&zfsvfs->z_hardlinks_linkid, hardlinks_compare_linkid, + sizeof (hardlinks_t), offsetof(hardlinks_t, hl_node_linkid)); + zfsvfs->z_rdonly = 0; + + mutex_init(&zfsvfs->z_drain_lock, NULL, MUTEX_DEFAULT, NULL); + cv_init(&zfsvfs->z_drain_cv, NULL, CV_DEFAULT, NULL); + + error = zfsvfs_init(zfsvfs, os); + if (error != 0) { + *zfvp = NULL; + kmem_free(zfsvfs, sizeof (zfsvfs_t)); + return (error); + } + + *zfvp = zfsvfs; + return (0); +} + +static int +zfsvfs_setup(zfsvfs_t *zfsvfs, boolean_t mounting) +{ + int error; + boolean_t readonly = vfs_isrdonly(zfsvfs->z_vfs); + + error = zfs_register_callbacks(zfsvfs->z_vfs); + if (error) + return (error); + + /* + * If we are not mounting (ie: online recv), then we don't + * have to worry about replaying the log as we blocked all + * operations out since we closed the ZIL. + */ + if (mounting) { + + ASSERT3P(zfsvfs->z_kstat.dk_kstats, ==, NULL); + dataset_kstats_create(&zfsvfs->z_kstat, zfsvfs->z_os); + if (error) + return (error); + zfsvfs->z_log = zil_open(zfsvfs->z_os, zfs_get_data, + &zfsvfs->z_kstat.dk_zil_sums); + + /* + * During replay we remove the read only flag to + * allow replays to succeed. + */ + + if (readonly != 0) + readonly_changed_cb(zfsvfs, B_FALSE); + else { + zap_stats_t zs; + if (zap_get_stats(zfsvfs->z_os, zfsvfs->z_unlinkedobj, + &zs) == 0) { + dataset_kstats_update_nunlinks_kstat( + &zfsvfs->z_kstat, zs.zs_num_entries); + dprintf_ds(zfsvfs->z_os->os_dsl_dataset, + "num_entries in unlinked set: %llu", + zs.zs_num_entries); + } + + if (!zfs_vnop_skip_unlinked_drain) + zfs_unlinked_drain(zfsvfs); + dsl_dir_t *dd = zfsvfs->z_os->os_dsl_dataset->ds_dir; + dd->dd_activity_cancelled = B_FALSE; + } + + /* + * Parse and replay the intent log. + * + * Because of ziltest, this must be done after + * zfs_unlinked_drain(). (Further note: ziltest + * doesn't use readonly mounts, where + * zfs_unlinked_drain() isn't called.) This is because + * ziltest causes spa_sync() to think it's committed, + * but actually it is not, so the intent log contains + * many txg's worth of changes. + * + * In particular, if object N is in the unlinked set in + * the last txg to actually sync, then it could be + * actually freed in a later txg and then reallocated + * in a yet later txg. This would write a "create + * object N" record to the intent log. Normally, this + * would be fine because the spa_sync() would have + * written out the fact that object N is free, before + * we could write the "create object N" intent log + * record. + * + * But when we are in ziltest mode, we advance the "open + * txg" without actually spa_sync()-ing the changes to + * disk. So we would see that object N is still + * allocated and in the unlinked set, and there is an + * intent log record saying to allocate it. + */ + if (spa_writeable(dmu_objset_spa(zfsvfs->z_os))) { + if (zil_replay_disable) { + zil_destroy(zfsvfs->z_log, B_FALSE); + } else { + zfsvfs->z_replay = B_TRUE; + zil_replay(zfsvfs->z_os, zfsvfs, + zfs_replay_vector); + zfsvfs->z_replay = B_FALSE; + } + } + + /* restore readonly bit */ + if (readonly != 0) + readonly_changed_cb(zfsvfs, B_TRUE); + } else { + ASSERT3P(zfsvfs->z_kstat.dk_kstats, !=, NULL); + zfsvfs->z_log = zil_open(zfsvfs->z_os, zfs_get_data, + &zfsvfs->z_kstat.dk_zil_sums); + } + + /* + * Set the objset user_ptr to track its zfsvfs. + */ + mutex_enter(&zfsvfs->z_os->os_user_ptr_lock); + dmu_objset_set_user(zfsvfs->z_os, zfsvfs); + mutex_exit(&zfsvfs->z_os->os_user_ptr_lock); + + return (0); +} + +extern krwlock_t zfsvfs_lock; /* in zfs_znode.c */ + +void +zfsvfs_free(zfsvfs_t *zfsvfs) +{ + int i, size = zfsvfs->z_hold_size; + + zfs_fuid_destroy(zfsvfs); + + cv_destroy(&zfsvfs->z_drain_cv); + mutex_destroy(&zfsvfs->z_drain_lock); + mutex_destroy(&zfsvfs->z_znodes_lock); + mutex_destroy(&zfsvfs->z_lock); + list_destroy(&zfsvfs->z_all_znodes); + rrm_destroy(&zfsvfs->z_teardown_lock); + rw_destroy(&zfsvfs->z_teardown_inactive_lock); + rw_destroy(&zfsvfs->z_fuid_lock); + + for (i = 0; i != size; i++) { + avl_destroy(&zfsvfs->z_hold_trees[i]); + mutex_destroy(&zfsvfs->z_hold_locks[i]); + } + kmem_free(zfsvfs->z_hold_trees, sizeof (avl_tree_t) * size); + kmem_free(zfsvfs->z_hold_locks, sizeof (kmutex_t) * size); + + dprintf("ZFS: Unloading hardlink AVLtree: %lu\n", + avl_numnodes(&zfsvfs->z_hardlinks)); + void *cookie = NULL; + hardlinks_t *hardlink; + rw_destroy(&zfsvfs->z_hardlinks_lock); + while ((hardlink = avl_destroy_nodes(&zfsvfs->z_hardlinks_linkid, + &cookie))) { + } + cookie = NULL; + while ((hardlink = avl_destroy_nodes(&zfsvfs->z_hardlinks, &cookie))) { + kmem_free(hardlink, sizeof (*hardlink)); + } + avl_destroy(&zfsvfs->z_hardlinks); + avl_destroy(&zfsvfs->z_hardlinks_linkid); + + dataset_kstats_destroy(&zfsvfs->z_kstat); + kmem_free(zfsvfs, sizeof (zfsvfs_t)); + dprintf("-zfsvfs_free\n"); +} + +static void +zfs_set_fuid_feature(zfsvfs_t *zfsvfs) +{ + zfsvfs->z_use_fuids = USE_FUIDS(zfsvfs->z_version, zfsvfs->z_os); + if (zfsvfs->z_vfs) { +#if 0 + if (zfsvfs->z_use_fuids) { + vfs_set_feature(zfsvfs->z_vfs, VFSFT_XVATTR); + vfs_set_feature(zfsvfs->z_vfs, VFSFT_SYSATTR_VIEWS); + vfs_set_feature(zfsvfs->z_vfs, VFSFT_ACEMASKONACCESS); + vfs_set_feature(zfsvfs->z_vfs, VFSFT_ACLONCREATE); + vfs_set_feature(zfsvfs->z_vfs, VFSFT_ACCESS_FILTER); + vfs_set_feature(zfsvfs->z_vfs, VFSFT_REPARSE); + } else { + vfs_clear_feature(zfsvfs->z_vfs, VFSFT_XVATTR); + vfs_clear_feature(zfsvfs->z_vfs, VFSFT_SYSATTR_VIEWS); + vfs_clear_feature(zfsvfs->z_vfs, VFSFT_ACEMASKONACCESS); + vfs_clear_feature(zfsvfs->z_vfs, VFSFT_ACLONCREATE); + vfs_clear_feature(zfsvfs->z_vfs, VFSFT_ACCESS_FILTER); + vfs_clear_feature(zfsvfs->z_vfs, VFSFT_REPARSE); + } +#endif + } + zfsvfs->z_use_sa = USE_SA(zfsvfs->z_version, zfsvfs->z_os); +} + + +static int +zfs_domount(struct mount *vfsp, dev_t mount_dev, char *osname, + vfs_context_t ctx) +{ + int error = 0; + zfsvfs_t *zfsvfs; + uint64_t mimic = 0; + struct timeval tv; + boolean_t readonly = B_FALSE; + + ASSERT(vfsp); + ASSERT(osname); + + if (vfs_flags(vfsp) & MNT_RDONLY) + readonly = B_TRUE; + + error = zfsvfs_create(osname, readonly, &zfsvfs); + if (error) + return (error); + + zfsvfs->z_vfs = vfsp; + zfsvfs->z_rdev = mount_dev; + + /* HFS sets this prior to mounting */ + vfs_setflags(vfsp, (uint64_t)((unsigned int)MNT_DOVOLFS)); + /* Advisory locking should be handled at the VFS layer */ + vfs_setlocklocal(vfsp); + + /* + * Record the mount time (for Spotlight) + */ + microtime(&tv); + zfsvfs->z_mount_time = tv.tv_sec; + + vfs_setfsprivate(vfsp, zfsvfs); + + /* + * The fsid is 64 bits, composed of an 8-bit fs type, which + * separates our fsid from any other filesystem types, and a + * 56-bit objset unique ID. The objset unique ID is unique to + * all objsets open on this system, provided by unique_create(). + * The 8-bit fs type must be put in the low bits of fsid[1] + * because that's where other Solaris filesystems put it. + */ + + error = dsl_prop_get_integer(osname, "com.apple.mimic", &mimic, NULL); + if (zfsvfs->z_rdev) { + struct vfsstatfs *vfsstatfs; + vfsstatfs = vfs_statfs(vfsp); + vfsstatfs->f_fsid.val[0] = zfsvfs->z_rdev; + vfsstatfs->f_fsid.val[1] = vfs_typenum(vfsp); + } else { + // Otherwise, ask VFS to give us a random unique one. + vfs_getnewfsid(vfsp); + struct vfsstatfs *vfsstatfs; + vfsstatfs = vfs_statfs(vfsp); + zfsvfs->z_rdev = vfsstatfs->f_fsid.val[0]; + } + + /* + * If we are readonly (ie, waiting for rootmount) we need to reply + * honestly, so launchd runs fsck_zfs and mount_zfs + */ + mimic_changed_cb(zfsvfs, mimic); + + /* + * Set features for file system. + */ + zfs_set_fuid_feature(zfsvfs); + + if (dmu_objset_is_snapshot(zfsvfs->z_os)) { + uint64_t pval; + char fsname[ZFS_MAX_DATASET_NAME_LEN]; + zfsvfs_t *fs_zfsvfs; + + dmu_fsname(osname, fsname); + error = getzfsvfs(fsname, &fs_zfsvfs); + if (error == 0) { + if (fs_zfsvfs->z_unmounted) + error = SET_ERROR(EINVAL); + vfs_unbusy(fs_zfsvfs->z_vfs); + } + if (error) { + printf("file system '%s' is unmounted : error %d\n", + fsname, + error); + goto out; + } + + atime_changed_cb(zfsvfs, B_FALSE); + readonly_changed_cb(zfsvfs, B_TRUE); + if ((error = dsl_prop_get_integer(osname, "xattr", &pval, + NULL))) + goto out; + xattr_changed_cb(zfsvfs, pval); + zfsvfs->z_issnap = B_TRUE; + zfsvfs->z_os->os_sync = ZFS_SYNC_DISABLED; + + mutex_enter(&zfsvfs->z_os->os_user_ptr_lock); + dmu_objset_set_user(zfsvfs->z_os, zfsvfs); + mutex_exit(&zfsvfs->z_os->os_user_ptr_lock); + + zfsctl_mount_signal(osname, B_TRUE); + + } else { + if ((error = zfsvfs_setup(zfsvfs, B_TRUE))) + goto out; + } + + vfs_setflags(vfsp, (uint64_t)((unsigned int)MNT_JOURNALED)); + + if ((vfs_flags(vfsp) & MNT_ROOTFS) != 0) { + /* Root FS */ + vfs_clearflags(vfsp, + (uint64_t)((unsigned int)MNT_UNKNOWNPERMISSIONS)); + vfs_clearflags(vfsp, + (uint64_t)((unsigned int)MNT_IGNORE_OWNERSHIP)); + } + +#if 1 // Want .zfs or not + if (!zfsvfs->z_issnap) { + zfsctl_create(zfsvfs); + } +#endif + +out: + if (error) { + vfs_setfsprivate(vfsp, NULL); + dmu_objset_disown(zfsvfs->z_os, B_TRUE, zfsvfs); + zfsvfs_free(zfsvfs); + } else { + atomic_inc_32(&zfs_active_fs_count); + } + + return (error); +} + +void +zfs_unregister_callbacks(zfsvfs_t *zfsvfs) +{ + objset_t *os = zfsvfs->z_os; + + /* + * Unregister properties. + */ + if (!dmu_objset_is_snapshot(os)) { + dsl_prop_unregister_all(dmu_objset_ds(os), zfsvfs); + } +} + +/* + * zfs_vfs_mountroot + * Given a device vnode created by vfs_mountroot bdevvp, + * and with the root pool already imported, root mount the + * dataset specified in the pool's bootfs property. + * + * Inputs: + * mp: VFS mount struct + * devvp: device vnode, currently only used to retrieve the + * dev_t for the fsid. Could vnode_get, vnode_ref, vnode_put, + * with matching get/rele/put in zfs_vfs_umount, but this is + * already done by XNU as well. + * ctx: VFS context, unused. + * + * Return: + * 0 on success, positive int on failure. + */ +int +zfs_vfs_mountroot(struct mount *mp, struct vnode *devvp, vfs_context_t ctx) +{ + /* + * static int zfsrootdone = 0; + */ + zfsvfs_t *zfsvfs = NULL; + spa_t *spa = 0; + char *zfs_bootfs = 0; + dev_t dev = 0; + int error = EINVAL; + + printf("ZFS: %s\n", __func__); + ASSERT(mp); + ASSERT(devvp); + ASSERT(ctx); + if (!mp || !devvp | !ctx) { + cmn_err(CE_NOTE, "%s: missing one of mp %p devvp %p" + " or ctx %p", __func__, mp, devvp, ctx); + return (EINVAL); + } + + /* Look up bootfs variable from pool here */ + zfs_bootfs = kmem_alloc(MAXPATHLEN, KM_SLEEP); + if (!zfs_bootfs) { + cmn_err(CE_NOTE, "%s: bootfs alloc failed", + __func__); + return (ENOMEM); + } + + mutex_enter(&spa_namespace_lock); + spa = spa_next(NULL); + if (!spa) { + mutex_exit(&spa_namespace_lock); + cmn_err(CE_NOTE, "%s: no pool available", + __func__); + goto out; + } + + error = dsl_dsobj_to_dsname(spa_name(spa), + spa_bootfs(spa), zfs_bootfs); + if (error != 0) { + mutex_exit(&spa_namespace_lock); + cmn_err(CE_NOTE, "%s: bootfs to name error %d", + __func__, error); + goto out; + } + mutex_exit(&spa_namespace_lock); + + /* + * By setting the dev_t value in the mount vfsp, + * mount_zfs will be called with the /dev/diskN + * proxy, but we can leave the dataset name in + * the mountedfrom field + */ + dev = vnode_specrdev(devvp); + + dprintf("Setting readonly\n"); + + if ((error = zfs_domount(mp, dev, zfs_bootfs, ctx)) != 0) { + printf("zfs_domount: error %d", error); + goto out; + } + + zfsvfs = (zfsvfs_t *)vfs_fsprivate(mp); + ASSERT(zfsvfs); + if (!zfsvfs) { + cmn_err(CE_NOTE, "missing zfsvfs"); + goto out; + } + + /* Set this mount to read-only */ + zfsvfs->z_rdonly = 1; + + /* + * Due to XNU mount flags, readonly gets set off for a short + * while, which means mimic will kick in if enabled. But we need + * to reply with true "zfs" until root has been remounted RW, so + * that launchd tries to run mount_zfs instead of mount_hfs + */ + mimic_changed_cb(zfsvfs, ZFS_MIMIC_OFF); + + /* + * Leave rootvp held. The root file system is never unmounted. + * + * XXX APPLE + * xnu will in fact call vfs_unmount on the root filesystem + * during shutdown/reboot. + */ + +out: + + if (zfs_bootfs) { + kmem_free(zfs_bootfs, MAXPATHLEN); + } + return (error); + +} + +int +zfs_vfs_mount(struct mount *vfsp, vnode_t *mvp /* devvp */, + user_addr_t data, vfs_context_t context) +{ + char *osname = NULL; + char *options = NULL; + int error = 0; + int __maybe_unused rdonly = 0; + int mflag = 0; + char *proxy = NULL; + struct zfs_mount_args mnt_args; + size_t osnamelen = 0; + uint32_t cmdflags = 0; + + cmdflags = (uint32_t)vfs_flags(vfsp) & MNT_CMDFLAGS; + rdonly = vfs_isrdonly(vfsp); + + if (!data) { + /* + * From 10.12, if you set VFS_TBLCANMOUNTROOT, XNU will + * call vfs_mountroot if set (and we can not set it), OR + * call vfs_mount if not set. Since data is always passed NULL + * in this case, we know we are supposed to call mountroot. + */ + dprintf("ZFS: vfs_mount -> vfs_mountroot\n"); + return (zfs_vfs_mountroot(vfsp, mvp, context)); + } + + /* + * Get the objset name (the "special" mount argument). + */ + if (data) { + + // Clear the struct, so that "flags" is null if only given path. + memset(&mnt_args, 0, sizeof (mnt_args)); + + osname = kmem_alloc(MAXPATHLEN, KM_SLEEP); + + if (vfs_context_is64bit(context)) { + if ((error = ddi_copyin((void *)data, + (caddr_t)&mnt_args, sizeof (mnt_args), 0))) { + dprintf("%s: error on mnt_args copyin %d\n", + __func__, error); + goto out; + } + } else { + user32_addr_t tmp; + if ((error = ddi_copyin((void *)data, + (caddr_t)&tmp, sizeof (tmp), 0))) { + printf("%s: error on mnt_args copyin32 %d\n", + __func__, error); + goto out; + } + /* munge into LP64 addr */ + mnt_args.fspec = (char *)CAST_USER_ADDR_T(tmp); + } + + // Copy over the string + if ((error = ddi_copyinstr((const void *)mnt_args.fspec, osname, + MAXPATHLEN, &osnamelen))) { + dprintf("%s: error on osname copyin %d\n", + __func__, error); + if (!mvp) + goto out; + } + } + + proxy = kmem_alloc(MAXPATHLEN, KM_SLEEP); + *proxy = 0; + + /* + * Translate /dev/disk path into dataset name + * After this; + * "proxy" will have "/dev/disk" (IF given) + * "osname" has the dataset name as usual + */ + if (strncmp(osname, "/dev/disk", 9) == 0) { + strlcpy(proxy, osname, MAXPATHLEN); + error = zfs_osx_proxy_get_osname(osname, + osname, MAXPATHLEN); + if (error != 0) { + printf("%s couldn't get dataset from %s\n", + __func__, osname); + error = ENOENT; + goto out; + } + dprintf("%s got new osname %s\n", __func__, osname); + } + + if (mnt_args.struct_size == sizeof (mnt_args)) { + mflag = mnt_args.mflag; + options = kmem_alloc(mnt_args.optlen, KM_SLEEP); + error = ddi_copyin((const void *)mnt_args.optptr, + (caddr_t)options, mnt_args.optlen, 0); + } + + if (mflag & MS_RDONLY) { + dprintf("%s: adding MNT_RDONLY\n", __func__); + cmdflags |= MNT_RDONLY; + } + + if (mflag & MS_OVERLAY) { + dprintf("%s: adding MNT_UNION\n", __func__); + cmdflags |= MNT_UNION; + } + + if (mflag & MS_FORCE) { + dprintf("%s: adding MNT_FORCE\n", __func__); + cmdflags |= MNT_FORCE; + } + + if (mflag & MS_REMOUNT) { + dprintf("%s: adding MNT_UPDATE on MS_REMOUNT\n", __func__); + cmdflags |= MNT_UPDATE; + } + + vfs_setflags(vfsp, (uint64_t)cmdflags); + + /* + * When doing a remount, we simply refresh our temporary properties + * according to those options set in the current VFS options. + */ + if (cmdflags & MNT_UPDATE) { + + error = 0; + // Used after fsck + if (cmdflags & MNT_RELOAD) { + goto out; + } + + /* refresh mount options */ + zfsvfs_t *zfsvfs = vfs_fsprivate(vfsp); + + if (zfsvfs != NULL) { + if (zfsvfs->z_rdonly == 0 && + (cmdflags & MNT_RDONLY || + vfs_isrdonly(vfsp))) { + /* downgrade */ + dprintf("%s: downgrade requested\n", __func__); + zfsvfs->z_rdonly = 1; + readonly_changed_cb(zfsvfs, B_TRUE); + zfs_unregister_callbacks(zfsvfs); + error = zfs_register_callbacks(vfsp); + if (error) { + dprintf("%s: remount returned %d", + __func__, error); + } + } + + if (vfs_iswriteupgrade(vfsp)) { + /* upgrade */ + dprintf("%s: upgrade requested\n", __func__); + zfsvfs->z_rdonly = 0; + readonly_changed_cb(zfsvfs, B_FALSE); + zfs_unregister_callbacks(zfsvfs); + error = zfs_register_callbacks(vfsp); + if (error) { + dprintf("%s: remount returned %d", + __func__, error); + } + } + } + goto out; + } + + if (vfs_fsprivate(vfsp) != NULL) { + dprintf("already mounted\n"); + error = 0; + goto out; + } + + error = zfs_domount(vfsp, 0, osname, context); + if (error) { + dprintf("%s: zfs_domount returned %d\n", + __func__, error); + goto out; + } + +out: + + if (error == 0) { + + /* Indicate to VFS that we support ACLs. */ + vfs_setextendedsecurity(vfsp); + + // Set /dev/disk name if we have one, otherwise, datasetname + vfs_mountedfrom(vfsp, proxy && *proxy ? proxy : osname); + + } + + if (error) + dprintf("zfs_vfs_mount: error %d\n", error); + + if (osname) + kmem_free(osname, MAXPATHLEN); + + if (proxy) + kmem_free(proxy, MAXPATHLEN); + + if (options) + kmem_free(options, mnt_args.optlen); + + return (error); +} + + +int +zfs_vfs_getattr(struct mount *mp, struct vfs_attr *fsap, + __unused vfs_context_t context) +{ + zfsvfs_t *zfsvfs = vfs_fsprivate(mp); + uint64_t refdbytes, availbytes, usedobjs, availobjs; + uint64_t log_blksize; + uint64_t log_blkcnt; + int error; + + // dprintf("vfs_getattr\n"); + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + /* + * Finder will show the old/incorrect size, we can force a sync of the + * pool to make it correct, but that has side effects which are + * undesirable. + */ + /* txg_wait_synced(dmu_objset_pool(zfsvfs->z_os), 0); */ + + dmu_objset_space(zfsvfs->z_os, + &refdbytes, &availbytes, &usedobjs, &availobjs); + + VFSATTR_RETURN(fsap, f_objcount, usedobjs); + VFSATTR_RETURN(fsap, f_maxobjcount, 0x7fffffffffffffff); + /* + * Carbon depends on f_filecount and f_dircount so + * make up some values based on total objects. + */ + VFSATTR_RETURN(fsap, f_filecount, usedobjs - (usedobjs / 4)); + VFSATTR_RETURN(fsap, f_dircount, usedobjs / 4); + + /* + * Model after HFS in working out if we should use the legacy size + * 512, or go to 4096. Note that XNU only likes those two + * blocksizes, so we don't use the ZFS recordsize + */ + log_blkcnt = (u_int64_t)((refdbytes + availbytes) >> SPA_MINBLOCKSHIFT); + log_blksize = (log_blkcnt > 0x000000007fffffff) ? + 4096 : (1 << SPA_MINBLOCKSHIFT); + + /* + * The underlying storage pool actually uses multiple block sizes. + * We report the fragsize as the smallest block size we support, + * and we report our blocksize as the filesystem's maximum blocksize. + */ + VFSATTR_RETURN(fsap, f_bsize, log_blksize); + VFSATTR_RETURN(fsap, f_iosize, zfsvfs->z_max_blksz); + + /* + * The following report "total" blocks of various kinds in the + * file system, but reported in terms of f_frsize - the + * "fragment" size. + */ + VFSATTR_RETURN(fsap, f_blocks, + (u_int64_t)((refdbytes + availbytes) / log_blksize)); + VFSATTR_RETURN(fsap, f_bfree, (u_int64_t)(availbytes / log_blksize)); + VFSATTR_RETURN(fsap, f_bavail, fsap->f_bfree); + VFSATTR_RETURN(fsap, f_bused, fsap->f_blocks - fsap->f_bfree); + + /* + * statvfs() should really be called statufs(), because it assumes + * static metadata. ZFS doesn't preallocate files, so the best + * we can do is report the max that could possibly fit in f_files, + * and that minus the number actually used in f_ffree. + * For f_ffree, report the smaller of the number of object available + * and the number of blocks (each object will take at least a block). + */ + VFSATTR_RETURN(fsap, f_ffree, (u_int64_t)MIN(availobjs, fsap->f_bfree)); + VFSATTR_RETURN(fsap, f_files, fsap->f_ffree + usedobjs); + + if (VFSATTR_IS_ACTIVE(fsap, f_fsid)) { + fsap->f_fsid.val[0] = zfsvfs->z_rdev; + fsap->f_fsid.val[1] = vfs_typenum(mp); + VFSATTR_SET_SUPPORTED(fsap, f_fsid); + } + if (VFSATTR_IS_ACTIVE(fsap, f_capabilities)) { + fsap->f_capabilities.capabilities[VOL_CAPABILITIES_FORMAT] = + VOL_CAP_FMT_PERSISTENTOBJECTIDS | + VOL_CAP_FMT_HARDLINKS | // ZFS + VOL_CAP_FMT_SPARSE_FILES | // ZFS + VOL_CAP_FMT_2TB_FILESIZE | // ZFS + VOL_CAP_FMT_JOURNAL | VOL_CAP_FMT_JOURNAL_ACTIVE | // ZFS + VOL_CAP_FMT_SYMBOLICLINKS | // msdos.. + // ZFS has root times just fine + /* VOL_CAP_FMT_NO_ROOT_TIMES | */ + // Ask XNU to remember zero-runs, instead of writing + // zeros to it. + VOL_CAP_FMT_ZERO_RUNS | + VOL_CAP_FMT_CASE_PRESERVING | + VOL_CAP_FMT_FAST_STATFS | + VOL_CAP_FMT_PATH_FROM_ID | + VOL_CAP_FMT_64BIT_OBJECT_IDS | + /* VOL_CAP_FMT_DECMPFS_COMPRESSION | */ + VOL_CAP_FMT_HIDDEN_FILES; + + fsap->f_capabilities.capabilities[VOL_CAPABILITIES_INTERFACES] = + VOL_CAP_INT_ATTRLIST | // ZFS + VOL_CAP_INT_NFSEXPORT | // ZFS + VOL_CAP_INT_EXTENDED_SECURITY | // ZFS +#if NAMEDSTREAMS + VOL_CAP_INT_NAMEDSTREAMS | // ZFS +#endif + VOL_CAP_INT_EXTENDED_ATTR | // ZFS + VOL_CAP_INT_VOL_RENAME | // msdos.. + VOL_CAP_INT_ADVLOCK | + // ZFS does not yet have exchangedata (it's in a branch) + /* VOL_CAP_INT_EXCHANGEDATA| */ + // ZFS does not yet have copyfile + /* VOL_CAP_INT_COPYFILE| */ + // ZFS does not yet have allocate + /* VOL_CAP_INT_ALLOCATE| */ + VOL_CAP_INT_FLOCK; + + fsap->f_capabilities.capabilities[VOL_CAPABILITIES_RESERVED1] = + 0; + fsap->f_capabilities.capabilities[VOL_CAPABILITIES_RESERVED2] = + 0; + + /* + * This is the list of valid capabilities at time of + * compile. The valid list should have them all defined + * and the "capability" list above should enable only + * those we have implemented + */ + fsap->f_capabilities.valid[VOL_CAPABILITIES_FORMAT] = + VOL_CAP_FMT_PERSISTENTOBJECTIDS | + VOL_CAP_FMT_SYMBOLICLINKS | + VOL_CAP_FMT_HARDLINKS | + VOL_CAP_FMT_JOURNAL | + VOL_CAP_FMT_JOURNAL_ACTIVE | + VOL_CAP_FMT_NO_ROOT_TIMES | + VOL_CAP_FMT_SPARSE_FILES | + VOL_CAP_FMT_ZERO_RUNS | + VOL_CAP_FMT_CASE_SENSITIVE | + VOL_CAP_FMT_CASE_PRESERVING | + VOL_CAP_FMT_FAST_STATFS | + VOL_CAP_FMT_2TB_FILESIZE | + VOL_CAP_FMT_OPENDENYMODES | + VOL_CAP_FMT_PATH_FROM_ID | + VOL_CAP_FMT_64BIT_OBJECT_IDS | + VOL_CAP_FMT_NO_VOLUME_SIZES | + VOL_CAP_FMT_DECMPFS_COMPRESSION | + VOL_CAP_FMT_HIDDEN_FILES; + fsap->f_capabilities.valid[VOL_CAPABILITIES_INTERFACES] = + VOL_CAP_INT_SEARCHFS | + VOL_CAP_INT_ATTRLIST | + VOL_CAP_INT_NFSEXPORT | + VOL_CAP_INT_READDIRATTR | + VOL_CAP_INT_EXCHANGEDATA | + VOL_CAP_INT_COPYFILE | + VOL_CAP_INT_ALLOCATE | + VOL_CAP_INT_VOL_RENAME | + VOL_CAP_INT_ADVLOCK | + VOL_CAP_INT_FLOCK | + VOL_CAP_INT_EXTENDED_ATTR | + VOL_CAP_INT_USERACCESS | +#if NAMEDSTREAMS + VOL_CAP_INT_NAMEDSTREAMS | +#endif + VOL_CAP_INT_MANLOCK; + + fsap->f_capabilities.valid[VOL_CAPABILITIES_RESERVED1] = 0; + fsap->f_capabilities.valid[VOL_CAPABILITIES_RESERVED2] = 0; + + /* Check if we are case-sensitive */ + if (zfsvfs->z_case == ZFS_CASE_SENSITIVE) + fsap->f_capabilities.capabilities[ + VOL_CAPABILITIES_FORMAT] |= + VOL_CAP_FMT_CASE_SENSITIVE; + + /* Check if xattr is enabled */ + if (zfsvfs->z_xattr == B_TRUE) { + fsap->f_capabilities.capabilities[ + VOL_CAPABILITIES_INTERFACES] |= + VOL_CAP_INT_EXTENDED_ATTR; + } + + if (zfsvfs->z_mimic != ZFS_MIMIC_OFF) { + fsap->f_capabilities.capabilities[ + VOL_CAPABILITIES_FORMAT] |= + VOL_CAP_FMT_DECMPFS_COMPRESSION; + } + + VFSATTR_SET_SUPPORTED(fsap, f_capabilities); + } + + if (VFSATTR_IS_ACTIVE(fsap, f_attributes)) { + fsap->f_attributes.validattr.commonattr = + ATTR_CMN_NAME | + ATTR_CMN_DEVID | + ATTR_CMN_FSID | + ATTR_CMN_OBJTYPE | + ATTR_CMN_OBJTAG | + ATTR_CMN_OBJID | + ATTR_CMN_OBJPERMANENTID | + ATTR_CMN_PAROBJID | + /* ATTR_CMN_SCRIPT | */ + ATTR_CMN_CRTIME | + ATTR_CMN_MODTIME | + ATTR_CMN_CHGTIME | + ATTR_CMN_ACCTIME | + /* ATTR_CMN_BKUPTIME | */ + ATTR_CMN_FNDRINFO | + ATTR_CMN_OWNERID | + ATTR_CMN_GRPID | + ATTR_CMN_ACCESSMASK | + ATTR_CMN_FLAGS | + ATTR_CMN_USERACCESS | + ATTR_CMN_EXTENDED_SECURITY | + ATTR_CMN_UUID | + ATTR_CMN_GRPUUID | +#ifdef ATTR_CMN_DOCUMENT_ID + ATTR_CMN_DOCUMENT_ID | +#endif +#ifdef ATTR_CMN_GEN_COUNT + ATTR_CMN_GEN_COUNT | +#endif + 0; + fsap->f_attributes.validattr.volattr = + ATTR_VOL_FSTYPE | + ATTR_VOL_SIGNATURE | + ATTR_VOL_SIZE | + ATTR_VOL_SPACEFREE | + ATTR_VOL_SPACEAVAIL | + ATTR_VOL_MINALLOCATION | + ATTR_VOL_ALLOCATIONCLUMP | + ATTR_VOL_IOBLOCKSIZE | + ATTR_VOL_OBJCOUNT | + ATTR_VOL_FILECOUNT | + ATTR_VOL_DIRCOUNT | + ATTR_VOL_MAXOBJCOUNT | + /* ATTR_VOL_MOUNTPOINT | */ + ATTR_VOL_NAME | + ATTR_VOL_MOUNTFLAGS | + /* ATTR_VOL_MOUNTEDDEVICE | */ + /* ATTR_VOL_ENCODINGSUSED | */ + ATTR_VOL_CAPABILITIES | + ATTR_VOL_ATTRIBUTES; + fsap->f_attributes.validattr.dirattr = + ATTR_DIR_LINKCOUNT | + ATTR_DIR_ENTRYCOUNT | + ATTR_DIR_MOUNTSTATUS; + fsap->f_attributes.validattr.fileattr = + ATTR_FILE_LINKCOUNT | + ATTR_FILE_TOTALSIZE | + ATTR_FILE_ALLOCSIZE | + /* ATTR_FILE_IOBLOCKSIZE */ + ATTR_FILE_DEVTYPE | + /* ATTR_FILE_FORKCOUNT */ + /* ATTR_FILE_FORKLIST */ + ATTR_FILE_DATALENGTH | + ATTR_FILE_DATAALLOCSIZE | + ATTR_FILE_RSRCLENGTH | + ATTR_FILE_RSRCALLOCSIZE; + fsap->f_attributes.validattr.forkattr = 0; + fsap->f_attributes.nativeattr.commonattr = + ATTR_CMN_NAME | + ATTR_CMN_DEVID | + ATTR_CMN_FSID | + ATTR_CMN_OBJTYPE | + ATTR_CMN_OBJTAG | + ATTR_CMN_OBJID | + ATTR_CMN_OBJPERMANENTID | + ATTR_CMN_PAROBJID | + /* ATTR_CMN_SCRIPT | */ + ATTR_CMN_CRTIME | + ATTR_CMN_MODTIME | + /* ATTR_CMN_CHGTIME | */ /* Supported but not native */ + ATTR_CMN_ACCTIME | + /* ATTR_CMN_BKUPTIME | */ + /* ATTR_CMN_FNDRINFO | */ + ATTR_CMN_OWNERID | /* Supported but not native */ + ATTR_CMN_GRPID | /* Supported but not native */ + ATTR_CMN_ACCESSMASK | /* Supported but not native */ + ATTR_CMN_FLAGS | + ATTR_CMN_USERACCESS | + ATTR_CMN_EXTENDED_SECURITY | + ATTR_CMN_UUID | + ATTR_CMN_GRPUUID | +#ifdef ATTR_CMN_DOCUMENT_ID + ATTR_CMN_DOCUMENT_ID | +#endif +#ifdef ATTR_CMN_GEN_COUNT + ATTR_CMN_GEN_COUNT | +#endif + 0; + fsap->f_attributes.nativeattr.volattr = + ATTR_VOL_FSTYPE | + ATTR_VOL_SIGNATURE | + ATTR_VOL_SIZE | + ATTR_VOL_SPACEFREE | + ATTR_VOL_SPACEAVAIL | + ATTR_VOL_MINALLOCATION | + ATTR_VOL_ALLOCATIONCLUMP | + ATTR_VOL_IOBLOCKSIZE | + ATTR_VOL_OBJCOUNT | + ATTR_VOL_FILECOUNT | + ATTR_VOL_DIRCOUNT | + ATTR_VOL_MAXOBJCOUNT | + /* ATTR_VOL_MOUNTPOINT | */ + ATTR_VOL_NAME | + ATTR_VOL_MOUNTFLAGS | + /* ATTR_VOL_MOUNTEDDEVICE | */ + /* ATTR_VOL_ENCODINGSUSED */ + ATTR_VOL_CAPABILITIES | + ATTR_VOL_ATTRIBUTES; + fsap->f_attributes.nativeattr.dirattr = 0; + fsap->f_attributes.nativeattr.fileattr = + /* ATTR_FILE_LINKCOUNT | */ /* Supported but not native */ + ATTR_FILE_TOTALSIZE | + ATTR_FILE_ALLOCSIZE | + /* ATTR_FILE_IOBLOCKSIZE */ + ATTR_FILE_DEVTYPE | + /* ATTR_FILE_FORKCOUNT */ + /* ATTR_FILE_FORKLIST */ + ATTR_FILE_DATALENGTH | + ATTR_FILE_DATAALLOCSIZE | + ATTR_FILE_RSRCLENGTH | + ATTR_FILE_RSRCALLOCSIZE; + fsap->f_attributes.nativeattr.forkattr = 0; + + VFSATTR_SET_SUPPORTED(fsap, f_attributes); + } + if (VFSATTR_IS_ACTIVE(fsap, f_create_time)) { + char osname[MAXNAMELEN]; + uint64_t value; + + // Get dataset name + dmu_objset_name(zfsvfs->z_os, osname); + dsl_prop_get_integer(osname, "CREATION", + &value, NULL); + fsap->f_create_time.tv_sec = value; + fsap->f_create_time.tv_nsec = 0; + VFSATTR_SET_SUPPORTED(fsap, f_create_time); + } + if (VFSATTR_IS_ACTIVE(fsap, f_modify_time)) { + timestruc_t now; + uint64_t mtime[2]; + + gethrestime(&now); + ZFS_TIME_ENCODE(&now, mtime); + // fsap->f_modify_time = mtime; + ZFS_TIME_DECODE(&fsap->f_modify_time, mtime); + + VFSATTR_SET_SUPPORTED(fsap, f_modify_time); + } + /* + * For Carbon compatibility, pretend to support this legacy/unused + * attribute + */ + if (VFSATTR_IS_ACTIVE(fsap, f_backup_time)) { + fsap->f_backup_time.tv_sec = 0; + fsap->f_backup_time.tv_nsec = 0; + VFSATTR_SET_SUPPORTED(fsap, f_backup_time); + } + + if (VFSATTR_IS_ACTIVE(fsap, f_vol_name)) { + char osname[MAXNAMELEN], *slash; + dmu_objset_name(zfsvfs->z_os, osname); + + slash = strrchr(osname, '/'); + if (slash) { + /* Advance past last slash */ + slash += 1; + } else { + /* Copy whole osname (pool root) */ + slash = osname; + } + strlcpy(fsap->f_vol_name, slash, MAXPATHLEN); + + VFSATTR_SET_SUPPORTED(fsap, f_vol_name); + dprintf("vfs_getattr: volume name '%s'\n", fsap->f_vol_name); + } + + /* If we are mimicking, we need userland know we are really ZFS */ + if (zfsvfs->z_mimic != ZFS_MIMIC_OFF) { + VFSATTR_RETURN(fsap, f_fssubtype, + zfsvfs->z_case == ZFS_CASE_SENSITIVE ? 2 : 0); + } else { + // 0x83 or 0x81 HFS + JOURNAL and optional CASESENSITIVE + VFSATTR_RETURN(fsap, f_fssubtype, + zfsvfs->z_case == ZFS_CASE_SENSITIVE ? 0x83 : 0x81); + } + /* + * According to joshade over at + * https://github.com/joshado/liberate-applefileserver/blob/ + * master/liberate.m + * the following values need to be returned for it to be considered + * by Apple's AFS. + */ + VFSATTR_RETURN(fsap, f_signature, 0x482b); /* "H+" in ascii */ + VFSATTR_RETURN(fsap, f_carbon_fsid, 0); + // Make up a UUID here, based on the name + if (VFSATTR_IS_ACTIVE(fsap, f_uuid)) { + + char osname[MAXNAMELEN]; + int error; + // Get dataset name + dmu_objset_name(zfsvfs->z_os, osname); + dprintf("%s: osname [%s]\n", __func__, osname); + + if ((error = zfs_vfs_uuid_gen(osname, + fsap->f_uuid)) != 0) { + dprintf("%s uuid_gen error %d\n", __func__, error); + } else { + /* return f_uuid in fsap */ + VFSATTR_SET_SUPPORTED(fsap, f_uuid); + } + } + + uint64_t missing = 0; + missing = (fsap->f_active ^ (fsap->f_active & fsap->f_supported)); + if (missing != 0) { + dprintf("%s: asked %08llx reply %08llx missing %08llx\n", + __func__, fsap->f_active, fsap->f_supported, + missing); + } + + zfs_exit(zfsvfs, FTAG); + + return (0); +} + +int +zfs_vnode_lock(vnode_t *vp, int flags) +{ + int error; + + ASSERT(vp != NULL); + + error = vn_lock(vp, flags); + return (error); +} + +/* + * The ARC has requested that the filesystem drop entries from the dentry + * and inode caches. This can occur when the ARC needs to free meta data + * blocks but can't because they are all pinned by entries in these caches. + */ + +/* Get vnode for the root object of this mount */ +int +zfs_vfs_root(struct mount *mp, vnode_t **vpp, __unused vfs_context_t context) +{ + zfsvfs_t *zfsvfs = vfs_fsprivate(mp); + znode_t *rootzp = NULL; + int error; + + if (!zfsvfs) { + struct vfsstatfs *stat = 0; + if (mp) stat = vfs_statfs(mp); + if (stat) + dprintf("%s mp on %s from %s\n", __func__, + stat->f_mntonname, stat->f_mntfromname); + dprintf("%s no zfsvfs yet for mp\n", __func__); + return (EINVAL); + } + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + error = zfs_zget(zfsvfs, zfsvfs->z_root, &rootzp); + if (error == 0) + *vpp = ZTOV(rootzp); + else + *vpp = NULL; + + zfs_exit(zfsvfs, FTAG); + + if (error == 0 && *vpp != NULL) + if (vnode_vtype(*vpp) != VDIR) { + panic("%s: not a directory\n", __func__); + } + + return (error); +} + +/* + * Teardown the zfsvfs::z_os. + * + * Note, if 'unmounting' is FALSE, we return with the 'z_teardown_lock' + * and 'z_teardown_inactive_lock' held. + */ +static int +zfsvfs_teardown(zfsvfs_t *zfsvfs, boolean_t unmounting) +{ + znode_t *zp; + /* + * We have experienced deadlocks with dmu_recv_end happening between + * suspend_fs() and resume_fs(). Clearly something is not quite ready + * so we will wait for pools to be synced first. + * This is considered a temporary solution until we can work out + * the full issue. + */ + + zfs_unlinked_drain_stop_wait(zfsvfs); + + /* + * If someone has not already unmounted this file system, + * drain the iput_taskq to ensure all active references to the + * zfs_sb_t have been handled only then can it be safely destroyed. + */ + if (zfsvfs->z_os) { + /* + * If we're unmounting we have to wait for the list to + * drain completely. + * + * If we're not unmounting there's no guarantee the list + * will drain completely, but iputs run from the taskq + * may add the parents of dir-based xattrs to the taskq + * so we want to wait for these. + * + * We can safely read z_nr_znodes without locking because the + * VFS has already blocked operations which add to the + * z_all_znodes list and thus increment z_nr_znodes. + */ + int round = 0; + while (!list_empty(&zfsvfs->z_all_znodes)) { + taskq_wait_outstanding(dsl_pool_zrele_taskq( + dmu_objset_pool(zfsvfs->z_os)), 0); + if (++round > 1 && !unmounting) + break; + break; /* Only loop once - osx can get stuck */ + } + } + + /* best effort before lock held - see below */ + cache_purgevfs(zfsvfs->z_parent->z_vfs, TRUE); + + rrm_enter(&zfsvfs->z_teardown_lock, RW_WRITER, FTAG); + + if (!unmounting) { + /* + * We purge the parent filesystem's vfsp as the parent + * filesystem and all of its snapshots have their vnode's + * v_vfsp set to the parent's filesystem's vfsp. Note, + * 'z_parent' is self referential for non-snapshots. + */ + /* + * This can deadlock, for example from rollback, as we + * hold teardown lock above and vfs_iterate() will call + * into ZFS when releasing nodes e.g. zfs_vnop_pageout() + */ + /* cache_purgevfs(zfsvfs->z_parent->z_vfs); */ + } + + /* + * Close the zil. NB: Can't close the zil while zfs_inactive + * threads are blocked as zil_close can call zfs_inactive. + */ + if (zfsvfs->z_log) { + zil_close(zfsvfs->z_log); + zfsvfs->z_log = NULL; + } + + rw_enter(&zfsvfs->z_teardown_inactive_lock, RW_WRITER); + + /* + * If we are not unmounting (ie: online recv) and someone already + * unmounted this file system while we were doing the switcheroo, + * or a reopen of z_os failed then just bail out now. + */ + if (!unmounting && (zfsvfs->z_unmounted || zfsvfs->z_os == NULL)) { + rw_exit(&zfsvfs->z_teardown_inactive_lock); + rrm_exit(&zfsvfs->z_teardown_lock, FTAG); + return (SET_ERROR(EIO)); + } + /* + * At this point there are no VFS ops active, and any new VFS ops + * will fail with EIO since we have z_teardown_lock for writer (only + * relevant for forced unmount). + * + * Release all holds on dbufs. We also grab an extra reference to all + * the remaining inodes so that the kernel does not attempt to free + * any inodes of a suspended fs. This can cause deadlocks since the + * zfs_resume_fs() process may involve starting threads, which might + * attempt to free unreferenced inodes to free up memory for the new + * thread. + */ + if (!unmounting) { + mutex_enter(&zfsvfs->z_znodes_lock); + for (zp = list_head(&zfsvfs->z_all_znodes); zp != NULL; + zp = list_next(&zfsvfs->z_all_znodes, zp)) { + if (zp->z_sa_hdl) + zfs_znode_dmu_fini(zp); + if (VN_HOLD(ZTOV(zp)) == 0) { + vnode_ref(ZTOV(zp)); + zp->z_suspended = B_TRUE; + VN_RELE(ZTOV(zp)); + } + } + mutex_exit(&zfsvfs->z_znodes_lock); + } + + /* + * If we are unmounting, set the unmounted flag and let new VFS ops + * unblock. zfs_inactive will have the unmounted behavior, and all + * other VFS ops will fail with EIO. + */ + if (unmounting) { + zfsvfs->z_unmounted = B_TRUE; + rw_exit(&zfsvfs->z_teardown_inactive_lock); + rrm_exit(&zfsvfs->z_teardown_lock, FTAG); + } + + /* + * z_os will be NULL if there was an error in attempting to reopen + * zfsvfs, so just return as the properties had already been + * unregistered and cached data had been evicted before. + */ + if (zfsvfs->z_os == NULL) + return (0); + + /* + * Unregister properties. + */ + zfs_unregister_callbacks(zfsvfs); + + /* + * Evict cached data + */ + /* + * Evict cached data. We must write out any dirty data before + * disowning the dataset. + */ + objset_t *os = zfsvfs->z_os; + boolean_t os_dirty = B_FALSE; + for (int t = 0; t < TXG_SIZE; t++) { + if (dmu_objset_is_dirty(os, t)) { + os_dirty = B_TRUE; + break; + } + } + if (!zfs_is_readonly(zfsvfs) && os_dirty) { + txg_wait_synced(dmu_objset_pool(zfsvfs->z_os), 0); + } + dmu_objset_evict_dbufs(zfsvfs->z_os); + dsl_dir_t *dd = os->os_dsl_dataset->ds_dir; + dsl_dir_cancel_waiters(dd); + + return (0); +} + +int +zfs_vfs_unmount(struct mount *mp, int mntflags, vfs_context_t context) +{ + zfsvfs_t *zfsvfs = vfs_fsprivate(mp); + objset_t *os; + char osname[MAXNAMELEN]; + int ret; + /* cred_t *cr = (cred_t *)vfs_context_ucred(context); */ + int destroyed_zfsctl = 0; + + dprintf("%s\n", __func__); + + zfs_unlinked_drain_stop_wait(zfsvfs); + + /* Save osname for later */ + dmu_objset_name(zfsvfs->z_os, osname); + + /* + * We might skip the sync called in the unmount path, since + * zfs_vfs_sync() is generally ignoring xnu's calls, and alas, + * mount_isforce() is set AFTER that sync call, so we can not + * detect unmount is inflight. But why not just sync now, it + * is safe. Optionally, sync if (mount_isforce()); + */ + spa_sync_allpools(); + + /* + * We purge the parent filesystem's vfsp as the parent filesystem + * and all of its snapshots have their vnode's v_vfsp set to the + * parent's filesystem's vfsp. Note, 'z_parent' is self + * referential for non-snapshots. + */ + cache_purgevfs(zfsvfs->z_parent->z_vfs, TRUE); + + /* + * Unmount any snapshots mounted under .zfs before unmounting the + * dataset itself. + * + * Unfortunately, XNU will check for mounts in preflight, and + * simply not call us at all if snapshots are mounted. + * We expect userland to unmount snapshots now. + */ + + ret = vflush(mp, NULLVP, SKIPSYSTEM); + + if (mntflags & MNT_FORCE) { + /* + * Mark file system as unmounted before calling + * vflush(FORCECLOSE). This way we ensure no future vnops + * will be called and risk operating on DOOMED vnodes. + */ + rrm_enter(&zfsvfs->z_teardown_lock, RW_WRITER, FTAG); + zfsvfs->z_unmounted = B_TRUE; + rrm_exit(&zfsvfs->z_teardown_lock, FTAG); + } + + /* + * We must release ctldir before vflush on osx. + */ + if (zfsvfs->z_ctldir != NULL) { + destroyed_zfsctl = 1; + zfsctl_destroy(zfsvfs); + } + + /* + * Flush all the files. + */ + ret = vflush(mp, NULLVP, + (mntflags & MNT_FORCE) ? FORCECLOSE|SKIPSYSTEM : SKIPSYSTEM); + + if ((ret != 0) && !(mntflags & MNT_FORCE)) { + if (destroyed_zfsctl) + zfsctl_create(zfsvfs); + return (ret); + } + + /* If we are ourselves a snapshot */ + if (dmu_objset_is_snapshot(zfsvfs->z_os)) { + /* Wake up anyone waiting for unmount */ + zfsctl_mount_signal(osname, B_FALSE); + } + + if (!vfs_isrdonly(zfsvfs->z_vfs) && + spa_writeable(dmu_objset_spa(zfsvfs->z_os)) && + !(mntflags & MNT_FORCE)) { + /* Update the last-unmount time for Spotlight's next mount */ + timestruc_t now; + dmu_tx_t *tx; + int error; + uint64_t value; + + dprintf("ZFS: '%s' Updating spotlight LASTUNMOUNT property\n", + osname); + + gethrestime(&now); + zfsvfs->z_last_unmount_time = now.tv_sec; + + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_zap(tx, MASTER_NODE_OBJ, TRUE, NULL); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + } else { + value = zfsvfs->z_last_unmount_time; + error = zap_update(zfsvfs->z_os, MASTER_NODE_OBJ, + zfs_prop_to_name(ZFS_PROP_LASTUNMOUNT), + 8, 1, + &value, tx); + dmu_tx_commit(tx); + } + dprintf("ZFS: '%s' set lastunmount to 0x%lx (%d)\n", + osname, zfsvfs->z_last_unmount_time, error); + } + + /* + * Last chance to dump unreferenced system files. + */ + (void) vflush(mp, NULLVP, FORCECLOSE); + + VERIFY(zfsvfs_teardown(zfsvfs, B_TRUE) == 0); + os = zfsvfs->z_os; + +#ifdef CLOSE_ON_UNMOUNT + /* See rant in vdev_file.c */ + extern void vdev_file_close_all(objset_t *); + vdev_file_close_all(os); +#endif + + /* + * z_os will be NULL if there was an error in + * attempting to reopen zfsvfs. + */ + if (os != NULL) { + /* + * Unset the objset user_ptr. + */ + mutex_enter(&os->os_user_ptr_lock); + dmu_objset_set_user(os, NULL); + mutex_exit(&os->os_user_ptr_lock); + + /* + * Finally release the objset + */ + dmu_objset_disown(os, B_TRUE, zfsvfs); + } + + zfs_freevfs(zfsvfs->z_vfs); + + return (0); +} + +static int +zfs_vget_internal(zfsvfs_t *zfsvfs, ino64_t ino, vnode_t **vpp) +{ + znode_t *zp; + int err; + + dprintf("vget get %llu\n", ino); + + /* + * Check to see if we expect to find this in the hardlink avl tree of + * hashes. Use the MSB set high as indicator. + */ + hardlinks_t *findnode = NULL; + if ((1ULL<<31) & ino) { + hardlinks_t *searchnode; + avl_index_t loc; + + searchnode = kmem_alloc(sizeof (hardlinks_t), KM_SLEEP); + + dprintf("ZFS: vget looking for (%llx,%llu)\n", ino, ino); + + searchnode->hl_linkid = ino; + + rw_enter(&zfsvfs->z_hardlinks_lock, RW_READER); + findnode = avl_find(&zfsvfs->z_hardlinks_linkid, searchnode, + &loc); + rw_exit(&zfsvfs->z_hardlinks_lock); + + kmem_free(searchnode, sizeof (hardlinks_t)); + + if (findnode) { + dprintf("ZFS: vget found (%llu, %llu, %u): '%s'\n", + findnode->hl_parent, + findnode->hl_fileid, findnode->hl_linkid, + findnode->hl_name); + // Lookup the actual zp instead + ino = findnode->hl_fileid; + } // findnode + } // MSB set + + + /* We can not be locked during zget. */ + if (!ino) { + dprintf("%s: setting ino from %lld to 2\n", __func__, ino); + ino = 2; + } + + err = zfs_zget(zfsvfs, ino, &zp); + + if (err) { + dprintf("zget failed %d\n", err); + return (err); + } + + /* Don't expose EA objects! */ + if (zp->z_pflags & ZFS_XATTR) { + err = ENOENT; + goto out; + } + if (zp->z_unlinked) { + err = EINVAL; + goto out; + } + + *vpp = ZTOV(zp); + + err = zfs_vnode_lock(*vpp, 0 /* flags */); + + /* + * Spotlight requires that vap->va_name() is set when returning + * from vfs_vget, so that vfs_getrealpath() can succeed in returning + * a path to mds. + */ + char *name = kmem_alloc(MAXPATHLEN + 2, KM_SLEEP); + + /* Root can't lookup in ZAP */ + if (zp->z_id == zfsvfs->z_root) { + + dmu_objset_name(zfsvfs->z_os, name); + dprintf("vget: set root '%s'\n", name); + vnode_update_identity(*vpp, NULL, name, + strlen(name), 0, VNODE_UPDATE_NAME); + + } else { + uint64_t parent; + + // if its a hardlink cache + if (findnode) { + + dprintf("vget: updating vnode to '%s' parent %llu\n", + findnode->hl_name, findnode->hl_parent); + + vnode_update_identity(*vpp, + NULL, findnode->hl_name, + strlen(findnode->hl_name), 0, + VNODE_UPDATE_NAME|VNODE_UPDATE_PARENT); + mutex_enter(&zp->z_lock); + strlcpy(zp->z_name_cache, findnode->hl_name, PATH_MAX); + zp->z_finder_parentid = findnode->hl_parent; + mutex_exit(&zp->z_lock); + + + // If we already have the name, cached in zfs_vnop_lookup + } else if (zp->z_name_cache[0]) { + dprintf("vget: cached name '%s'\n", zp->z_name_cache); + vnode_update_identity(*vpp, NULL, zp->z_name_cache, + strlen(zp->z_name_cache), 0, + VNODE_UPDATE_NAME); + + /* If needed, if findnode is set, update the parentid */ + + } else { + + /* Lookup name from ID, grab parent */ + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_PARENT(zfsvfs), + &parent, sizeof (parent)) == 0); + + if (zap_value_search(zfsvfs->z_os, parent, zp->z_id, + ZFS_DIRENT_OBJ(-1ULL), name) == 0) { + + dprintf("vget: set name '%s'\n", name); + vnode_update_identity(*vpp, NULL, name, + strlen(name), 0, + VNODE_UPDATE_NAME); + } else { + dprintf("vget: unable to get name for %llu\n", + zp->z_id); + } // !zap_search + } + } // rootid + + kmem_free(name, MAXPATHLEN + 2); + +out: + + if (err != 0) { + VN_RELE(ZTOV(zp)); + *vpp = NULL; + } + + return (err); +} + +/* + * Get a vnode from a file id (ignoring the generation) + * + * Use by NFS Server (readdirplus) and VFS (build_path) + */ +int +zfs_vfs_vget(struct mount *mp, ino64_t ino, vnode_t **vpp, + __unused vfs_context_t context) +{ + zfsvfs_t *zfsvfs = vfs_fsprivate(mp); + int error; + + dprintf("%s: %llu\n", __func__, ino); + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + /* We also need to handle (.zfs) and (.zfs/snapshot). */ + if ((ino == ZFSCTL_INO_ROOT) && (zfsvfs->z_ctldir != NULL)) { + if (VN_HOLD(zfsvfs->z_ctldir) == 0) { + znode_t *zp = VTOZ(zfsvfs->z_ctldir); + *vpp = zfsvfs->z_ctldir; + dprintf(".zfs returned: id %llu\n", zp->z_id); + error = 0; + } else { + error = ENOENT; + } + zfs_exit(zfsvfs, FTAG); + return (error); + } + + /* + * This one is trickier, we have no reference to it, but it is + * in the all list. A little expensive to search list, but at + * least "snapshot" is infrequently accessed + * We also need to check if it is a ".zfs/snapshot/$name" entry - + * luckily we keep the "lowest" ID seen, so we only need to check + * when it is in the range. + */ + if (zfsvfs->z_ctldir != NULL) { + + /* + * Either it is the snapdir itself, or one of the snapshot + * directories inside it + */ + if ((ino == ZFSCTL_INO_SNAPDIR) || + ((ino >= zfsvfs->z_ctldir_startid) && + (ino <= ZFSCTL_INO_SNAPDIRS))) { + znode_t *zp; + + mutex_enter(&zfsvfs->z_znodes_lock); + for (zp = list_head(&zfsvfs->z_all_znodes); zp; + zp = list_next(&zfsvfs->z_all_znodes, zp)) { + if (zp->z_id == ino) + break; + } + mutex_exit(&zfsvfs->z_znodes_lock); + + dprintf(".zfs/%llu returned\n", zp ? zp->z_id : 0); + + error = ENOENT; + if (zp != NULL) { + if (VN_HOLD(ZTOV(zp)) == 0) { + *vpp = ZTOV(zp); + error = 0; + } + } + + zfs_exit(zfsvfs, FTAG); + return (error); + } + } + + /* + * On Mac OS X we always export the root directory id as 2. + * So we don't expect to see the real root directory id + * from zfs_vfs_vget KPI (unless of course the real id was + * already 2). + */ + ino = INO_XNUTOZFS(ino, zfsvfs->z_root); + + error = zfs_vget_internal(zfsvfs, ino, vpp); + + dprintf("%s: return %d: %llu\n", __func__, + error, error == 0 ? VTOZ(*vpp)->z_id : 0); + + zfs_exit(zfsvfs, FTAG); + return (error); +} + +int +zfs_vfs_setattr(__unused struct mount *mp, __unused struct vfs_attr *fsap, + __unused vfs_context_t context) +{ + // 10a286 bits has an implementation of this: to set volume name. + return (ENOTSUP); +} + +/* + * NFS Server File Handle File ID + */ +typedef struct zfs_zfid { + uint8_t zf_object[8]; /* obj[i] = obj >> (8 * i) */ + uint8_t zf_gen[8]; /* gen[i] = gen >> (8 * i) */ +} zfs_zfid_t; + +/* + * File handle to vnode pointer + */ +int +zfs_vfs_fhtovp(struct mount *mp, int fhlen, unsigned char *fhp, + vnode_t **vpp, __unused vfs_context_t context) +{ + dprintf("%s\n", __func__); + zfsvfs_t *zfsvfs = vfs_fsprivate(mp); + zfs_zfid_t *zfid = (zfs_zfid_t *)fhp; + znode_t *zp; + uint64_t obj_num = 0; + uint64_t fid_gen = 0; + int i; + int error; + + *vpp = NULL; + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + if (fhlen < sizeof (zfs_zfid_t)) { + error = EINVAL; + goto out; + } + + /* + * Grab the object and gen numbers in an endian neutral manner + */ + for (i = 0; i < sizeof (zfid->zf_object); i++) + obj_num |= ((uint64_t)zfid->zf_object[i]) << (8 * i); + + for (i = 0; i < sizeof (zfid->zf_gen); i++) + fid_gen |= ((uint64_t)zfid->zf_gen[i]) << (8 * i); + + obj_num = INO_XNUTOZFS(obj_num, zfsvfs->z_root); + + if ((error = zfs_zget(zfsvfs, obj_num, &zp))) { + goto out; + } + + if (zp->z_gen == 0) + zp->z_gen = 1; + + if (zp->z_unlinked || zp->z_gen != fid_gen) { + vnode_put(ZTOV(zp)); + error = EINVAL; + goto out; + } + *vpp = ZTOV(zp); +out: + zfs_exit(zfsvfs, FTAG); + return (error); +} + +/* + * Vnode pointer to File handle + * + * XXX Do we want to check the DSL sharenfs property? + */ +int +zfs_vfs_vptofh(vnode_t *vp, int *fhlenp, unsigned char *fhp, + __unused vfs_context_t context) +{ + dprintf("%s\n", __func__); + zfsvfs_t *zfsvfs = vfs_fsprivate(vnode_mount(vp)); + zfs_zfid_t *zfid = (zfs_zfid_t *)fhp; + znode_t *zp = VTOZ(vp); + uint64_t obj_num; + uint64_t zp_gen; + int i; + int error; + + if (*fhlenp < sizeof (zfs_zfid_t)) { + return (EOVERFLOW); + } + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + obj_num = INO_ZFSTOXNU(zp->z_id, zfsvfs->z_root); + zp_gen = zp->z_gen; + if (zp_gen == 0) + zp_gen = 1; + + /* + * Store the object and gen numbers in an endian neutral manner + */ + for (i = 0; i < sizeof (zfid->zf_object); i++) + zfid->zf_object[i] = (uint8_t)(obj_num >> (8 * i)); + + for (i = 0; i < sizeof (zfid->zf_gen); i++) + zfid->zf_gen[i] = (uint8_t)(zp_gen >> (8 * i)); + + *fhlenp = sizeof (zfs_zfid_t); + + zfs_exit(zfsvfs, FTAG); + return (0); +} + +/* + * Block out VOPs and close zfsvfs_t::z_os + * + * Note, if successful, then we return with the 'z_teardown_lock' and + * 'z_teardown_inactive_lock' write held. We leave ownership of the underlying + * dataset and objset intact so that they can be atomically handed off during + * a subsequent rollback or recv operation and the resume thereafter. + */ +int +zfs_suspend_fs(zfsvfs_t *zfsvfs) +{ + int error; + + if ((error = zfsvfs_teardown(zfsvfs, B_FALSE)) != 0) + return (error); + + return (0); +} + +/* + * Reopen zfsvfs_t::z_os and release VOPs. + */ +int +zfs_resume_fs(zfsvfs_t *zfsvfs, dsl_dataset_t *ds) +{ + int err, __maybe_unused err2; + znode_t *zp; + + ASSERT(RRM_WRITE_HELD(&zfsvfs->z_teardown_lock)); + ASSERT(RW_WRITE_HELD(&zfsvfs->z_teardown_inactive_lock)); + + /* + * We already own this, so just update the objset_t, as the one we + * had before may have been evicted. + */ + objset_t *os; + VERIFY3P(ds->ds_owner, ==, zfsvfs); + VERIFY(dsl_dataset_long_held(ds)); + dsl_pool_t *dp = spa_get_dsl(dsl_dataset_get_spa(ds)); + dsl_pool_config_enter(dp, FTAG); + VERIFY0(dmu_objset_from_ds(ds, &os)); + dsl_pool_config_exit(dp, FTAG); + + err = zfsvfs_init(zfsvfs, os); + if (err != 0) + goto bail; + + ds->ds_dir->dd_activity_cancelled = B_FALSE; + VERIFY(zfsvfs_setup(zfsvfs, B_FALSE) == 0); + + zfs_set_fuid_feature(zfsvfs); + + /* + * Attempt to re-establish all the active inodes with their + * dbufs. If a zfs_rezget() fails, then we unhash the inode + * and mark it stale. This prevents a collision if a new + * inode/object is created which must use the same inode + * number. The stale inode will be be released when the + * VFS prunes the dentry holding the remaining references + * on the stale inode. + */ + mutex_enter(&zfsvfs->z_znodes_lock); + for (zp = list_head(&zfsvfs->z_all_znodes); zp; + zp = list_next(&zfsvfs->z_all_znodes, zp)) { + err2 = zfs_rezget(zp); + + /* see comment in zfs_suspend_fs() */ + if (zp->z_suspended) { + struct vnode *vp = ZTOV(zp); + if (vp != NULL && vnode_getwithref(vp) == 0) { + vnode_rele(vp); + zfs_zrele_async(zp); + zp->z_suspended = B_FALSE; + } + } + } + mutex_exit(&zfsvfs->z_znodes_lock); + + if (!vfs_isrdonly(zfsvfs->z_vfs) && !zfsvfs->z_unmounted) { + /* + * zfs_suspend_fs() could have interrupted freeing + * of dnodes. We need to restart this freeing so + * that we don't "leak" the space. + */ + zfs_unlinked_drain(zfsvfs); + } + + cache_purgevfs(zfsvfs->z_parent->z_vfs, TRUE); + + /* Also issue an FSEvent so Finder updates */ + zfs_findernotify_refresh(zfsvfs->z_parent->z_vfs); + + +bail: + /* release the VFS ops */ + rw_exit(&zfsvfs->z_teardown_inactive_lock); + rrm_exit(&zfsvfs->z_teardown_lock, FTAG); + + if (err) { + /* + * Since we couldn't setup the sa framework, try to force + * unmount this file system. + */ + if (zfsvfs->z_os) + zfs_vfs_unmount(zfsvfs->z_vfs, 0, NULL); + } + return (err); +} + + +void +zfs_freevfs(struct mount *vfsp) +{ + zfsvfs_t *zfsvfs = vfs_fsprivate(vfsp); + + dprintf("+freevfs\n"); + + vfs_setfsprivate(vfsp, NULL); + + zfsvfs_free(zfsvfs); + + atomic_dec_32(&zfs_active_fs_count); + dprintf("-freevfs\n"); +} + +struct fromname_struct { + char *oldname; + char *newname; +}; +typedef struct fromname_struct fromname_t; + +static int +zfsvfs_update_fromname_callback(mount_t mp, void *arg) +{ + fromname_t *frna = (fromname_t *)arg; + struct vfsstatfs *vsf = vfs_statfs(mp); + + if (strncmp(vsf->f_mntfromname, frna->oldname, + sizeof (vsf->f_mntfromname)) == 0) { + vfs_mountedfrom(mp, frna->newname); + return (VFS_RETURNED_DONE); + } + + return (VFS_RETURNED); +} + +void +zfsvfs_update_fromname(const char *oldname, const char *newname) +{ + fromname_t frna; + + // find oldname's vfsp + // vfs_mountedfrom(vfsp, newname); + frna.oldname = (char *)oldname; + frna.newname = (char *)newname; + vfs_iterate(0, zfsvfs_update_fromname_callback, (void *)&frna); +} + +void +zfs_init(void) +{ + + printf("ZFS filesystem version: " ZPL_VERSION_STRING "\n"); + + /* + * Initialize .zfs directory structures + */ + zfsctl_init(); + + /* + * Initialize znode cache, vnode ops, etc... + */ + zfs_znode_init(); + + dmu_objset_register_type(DMU_OST_ZFS, zpl_get_file_info); + + /* Start arc_os - reclaim thread */ + arc_os_init(); + +} + +void +zfs_fini(void) +{ + arc_os_fini(); + zfsctl_fini(); + zfs_znode_fini(); +} + +int +zfs_busy(void) +{ + return (zfs_active_fs_count != 0); +} + +/* + * Release VOPs and unmount a suspended filesystem. + */ +int +zfs_end_fs(zfsvfs_t *zfsvfs, dsl_dataset_t *ds) +{ + ASSERT(RRM_WRITE_HELD(&zfsvfs->z_teardown_lock)); + ASSERT(RW_WRITE_HELD(&zfsvfs->z_teardown_inactive_lock)); + + /* + * We already own this, so just hold and rele it to update the + * objset_t, as the one we had before may have been evicted. + */ + objset_t *os; + VERIFY3P(ds->ds_owner, ==, zfsvfs); + VERIFY(dsl_dataset_long_held(ds)); + dsl_pool_t *dp = spa_get_dsl(dsl_dataset_get_spa(ds)); + dsl_pool_config_enter(dp, FTAG); + VERIFY0(dmu_objset_from_ds(ds, &os)); + dsl_pool_config_exit(dp, FTAG); + zfsvfs->z_os = os; + + /* release the VOPs */ + rw_exit(&zfsvfs->z_teardown_inactive_lock); + rrm_exit(&zfsvfs->z_teardown_lock, FTAG); + + /* + * Try to force unmount this file system. + */ + zfs_vfs_unmount(zfsvfs->z_vfs, 0, NULL); + zfsvfs->z_unmounted = B_TRUE; + return (0); +} + +int +zfs_set_version(zfsvfs_t *zfsvfs, uint64_t newvers) +{ + int error; + objset_t *os = zfsvfs->z_os; + dmu_tx_t *tx; + + if (newvers < ZPL_VERSION_INITIAL || newvers > ZPL_VERSION) + return (SET_ERROR(EINVAL)); + + if (newvers < zfsvfs->z_version) + return (SET_ERROR(EINVAL)); + + if (zfs_spa_version_map(newvers) > + spa_version(dmu_objset_spa(zfsvfs->z_os))) + return (SET_ERROR(ENOTSUP)); + + tx = dmu_tx_create(os); + dmu_tx_hold_zap(tx, MASTER_NODE_OBJ, B_FALSE, ZPL_VERSION_STR); + if (newvers >= ZPL_VERSION_SA && !zfsvfs->z_use_sa) { + dmu_tx_hold_zap(tx, MASTER_NODE_OBJ, B_TRUE, + ZFS_SA_ATTRS); + dmu_tx_hold_zap(tx, DMU_NEW_OBJECT, FALSE, NULL); + } + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + return (error); + } + + error = zap_update(os, MASTER_NODE_OBJ, ZPL_VERSION_STR, + 8, 1, &newvers, tx); + + if (error) { + dmu_tx_commit(tx); + return (error); + } + + if (newvers >= ZPL_VERSION_SA && !zfsvfs->z_use_sa) { + uint64_t sa_obj; + + ASSERT3U(spa_version(dmu_objset_spa(zfsvfs->z_os)), >=, + SPA_VERSION_SA); + sa_obj = zap_create(os, DMU_OT_SA_MASTER_NODE, + DMU_OT_NONE, 0, tx); + + error = zap_add(os, MASTER_NODE_OBJ, + ZFS_SA_ATTRS, 8, 1, &sa_obj, tx); + ASSERT(error == 0); + + VERIFY(0 == sa_set_sa_object(os, sa_obj)); + sa_register_update_callback(os, zfs_sa_upgrade); + } + + spa_history_log_internal(dmu_objset_spa(os), "upgrade", tx, + "oldver=%llu newver=%llu dataset = %llu", zfsvfs->z_version, + newvers, dmu_objset_id(os)); + + dmu_tx_commit(tx); + + zfsvfs->z_version = newvers; + os->os_version = newvers; + + zfs_set_fuid_feature(zfsvfs); + + return (0); +} + +/* + * Read a property stored within the master node. + */ +int +zfs_get_zplprop(objset_t *os, zfs_prop_t prop, uint64_t *value) +{ + uint64_t *cached_copy = NULL; + + /* + * Figure out where in the objset_t the cached copy would live, if it + * is available for the requested property. + */ + if (os != NULL) { + switch (prop) { + case ZFS_PROP_VERSION: + cached_copy = &os->os_version; + break; + case ZFS_PROP_NORMALIZE: + cached_copy = &os->os_normalization; + break; + case ZFS_PROP_UTF8ONLY: + cached_copy = &os->os_utf8only; + break; + case ZFS_PROP_CASE: + cached_copy = &os->os_casesensitivity; + break; + default: + break; + } + } + if (cached_copy != NULL && *cached_copy != OBJSET_PROP_UNINITIALIZED) { + *value = *cached_copy; + return (0); + } + + /* + * If the property wasn't cached, look up the file system's value for + * the property. For the version property, we look up a slightly + * different string. + */ + const char *pname; + int error = ENOENT; + if (prop == ZFS_PROP_VERSION) { + pname = ZPL_VERSION_STR; + } else { + pname = zfs_prop_to_name(prop); + } + + if (os != NULL) { + ASSERT3U(os->os_phys->os_type, ==, DMU_OST_ZFS); + error = zap_lookup(os, MASTER_NODE_OBJ, pname, 8, 1, value); + } + + if (error == ENOENT) { + /* No value set, use the default value */ + switch (prop) { + case ZFS_PROP_VERSION: + *value = ZPL_VERSION; + break; + case ZFS_PROP_NORMALIZE: + case ZFS_PROP_UTF8ONLY: + *value = 0; + break; + case ZFS_PROP_CASE: + *value = ZFS_CASE_SENSITIVE; + break; + case ZFS_PROP_ACLMODE: + *value = ZFS_ACLTYPE_OFF; + break; + default: + return (error); + } + error = 0; + } + + /* + * If one of the methods for getting the property value above worked, + * copy it into the objset_t's cache. + */ + if (error == 0 && cached_copy != NULL) { + *cached_copy = *value; + } + + return (error); +} + +/* + * Return true if the coresponding vfs's unmounted flag is set. + * Otherwise return false. + * If this function returns true we know VFS unmount has been initiated. + */ +boolean_t +zfs_get_vfs_flag_unmounted(objset_t *os) +{ + zfsvfs_t *zfvp; + boolean_t unmounted = B_FALSE; + + ASSERT(dmu_objset_type(os) == DMU_OST_ZFS); + + mutex_enter(&os->os_user_ptr_lock); + zfvp = dmu_objset_get_user(os); + if (zfvp != NULL && zfvp->z_vfs != NULL && + (vfs_isunmount(zfvp->z_vfs))) + unmounted = B_TRUE; + mutex_exit(&os->os_user_ptr_lock); + + return (unmounted); +} diff --git a/module/os/macos/zfs/zfs_vnops_os.c b/module/os/macos/zfs/zfs_vnops_os.c new file mode 100644 index 000000000000..a42f93b4e3e3 --- /dev/null +++ b/module/os/macos/zfs/zfs_vnops_os.c @@ -0,0 +1,3814 @@ +/* + * 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) 2015 by Chunwei Chen. All rights reserved. + * Copyright 2017 Nexenta Systems, Inc. + * Copyright (c) 2013, 2020 Jorgen Lundman + */ + +/* Portions Copyright 2007 Jeremy Teo */ +/* Portions Copyright 2010 Robert Milkowski */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int zfs_vnop_force_formd_normalized_output = 0; /* disabled by default */ + +/* + * Programming rules. + * + * Each vnode op performs some logical unit of work. To do this, the ZPL must + * properly lock its in-core state, create a DMU transaction, do the work, + * record this work in the intent log (ZIL), commit the DMU transaction, + * and wait for the intent log to commit if it is a synchronous operation. + * Moreover, the vnode ops must work in both normal and log replay context. + * The ordering of events is important to avoid deadlocks and references + * to freed memory. The example below illustrates the following Big Rules: + * + * (1) A check must be made in each zfs thread for a mounted file system. + * This is done avoiding races using ZFS_ENTER(zfsvfs). + * A ZFS_EXIT(zfsvfs) is needed before all returns. Any znodes + * must be checked with ZFS_VERIFY_ZP(zp). Both of these macros + * can return EIO from the calling function. + * + * (2) zrele() should always be the last thing except for zil_commit() + * (if necessary) and ZFS_EXIT(). This is for 3 reasons: + * First, if it's the last reference, the vnode/znode + * can be freed, so the zp may point to freed memory. Second, the last + * reference will call zfs_zinactive(), which may induce a lot of work -- + * pushing cached pages (which acquires range locks) and syncing out + * cached atime changes. Third, zfs_zinactive() may require a new tx, + * which could deadlock the system if you were already holding one. + * If you must call zrele() within a tx then use zfs_zrele_async(). + * + * (3) All range locks must be grabbed before calling dmu_tx_assign(), + * as they can span dmu_tx_assign() calls. + * + * (4) If ZPL locks are held, pass TXG_NOWAIT as the second argument to + * dmu_tx_assign(). This is critical because we don't want to block + * while holding locks. + * + * If no ZPL locks are held (aside from ZFS_ENTER()), use TXG_WAIT. This + * reduces lock contention and CPU usage when we must wait (note that if + * throughput is constrained by the storage, nearly every transaction + * must wait). + * + * Note, in particular, that if a lock is sometimes acquired before + * the tx assigns, and sometimes after (e.g. z_lock), then failing + * to use a non-blocking assign can deadlock the system. The scenario: + * + * Thread A has grabbed a lock before calling dmu_tx_assign(). + * Thread B is in an already-assigned tx, and blocks for this lock. + * Thread A calls dmu_tx_assign(TXG_WAIT) and blocks in txg_wait_open() + * forever, because the previous txg can't quiesce until B's tx commits. + * + * If dmu_tx_assign() returns ERESTART and zfsvfs->z_assign is TXG_NOWAIT, + * then drop all locks, call dmu_tx_wait(), and try again. On subsequent + * calls to dmu_tx_assign(), pass TXG_NOTHROTTLE in addition to TXG_NOWAIT, + * to indicate that this operation has already called dmu_tx_wait(). + * This will ensure that we don't retry forever, waiting a short bit + * each time. + * + * (5) If the operation succeeded, generate the intent log entry for it + * before dropping locks. This ensures that the ordering of events + * in the intent log matches the order in which they actually occurred. + * During ZIL replay the zfs_log_* functions will update the sequence + * number to indicate the zil transaction has replayed. + * + * (6) At the end of each vnode op, the DMU tx must always commit, + * regardless of whether there were any errors. + * + * (7) After dropping all locks, invoke zil_commit(zilog, foid) + * to ensure that synchronous semantics are provided when necessary. + * + * In general, this is how things should be ordered in each vnode op: + * + * if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + * return (error); + * top: + * zfs_dirent_lock(&dl, ...) // lock directory entry (may igrab()) + * rw_enter(...); // grab any other locks you need + * tx = dmu_tx_create(...); // get DMU tx + * dmu_tx_hold_*(); // hold each object you might modify + * error = dmu_tx_assign(tx, (waited ? TXG_NOTHROTTLE : 0) | TXG_NOWAIT); + * if (error) { + * rw_exit(...); // drop locks + * zfs_dirent_unlock(dl); // unlock directory entry + * zrele(...); // release held znodes + * if (error == ERESTART) { + * waited = B_TRUE; + * dmu_tx_wait(tx); + * dmu_tx_abort(tx); + * goto top; + * } + * dmu_tx_abort(tx); // abort DMU tx + * zfs_exit(zfsvfs, FTAG); // finished in zfs + * return (error); // really out of space + * } + * error = do_real_work(); // do whatever this VOP does + * if (error == 0) + * zfs_log_*(...); // on success, make ZIL entry + * dmu_tx_commit(tx); // commit DMU tx -- error or not + * rw_exit(...); // drop locks + * zfs_dirent_unlock(dl); // unlock directory entry + * zrele(...); // release held znodes + * zil_commit(zilog, foid); // synchronous when necessary + * zfs_exit(zfsvfs, FTAG); // finished in zfs + * return (error); // done, report error + */ + +/* + * Virus scanning is unsupported. It would be possible to add a hook + * here to performance the required virus scan. This could be done + * entirely in the kernel or potentially as an update to invoke a + * scanning utility. + */ +static int +zfs_vscan(struct vnode *vp, cred_t *cr, int async) +{ + return (0); +} + +int +zfs_open(struct vnode *vp, int mode, int flag, cred_t *cr) +{ + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = ITOZSB(vp); + int error; + + if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) + return (error); + + /* Honor ZFS_APPENDONLY file attribute */ + if ((mode & FWRITE) && (zp->z_pflags & ZFS_APPENDONLY) && + ((flag & O_APPEND) == 0)) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EPERM)); + } + + /* Virus scan eligible files on open */ + if (!zfs_has_ctldir(zp) && zfsvfs->z_vscan && S_ISREG(zp->z_mode) && + !(zp->z_pflags & ZFS_AV_QUARANTINED) && zp->z_size > 0) { + if (zfs_vscan(vp, cr, 0) != 0) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EACCES)); + } + } + + /* Keep a count of the synchronous opens in the znode */ + if (flag & (FSYNC | FDSYNC)) + atomic_inc_32(&zp->z_sync_cnt); + + zfs_exit(zfsvfs, FTAG); + return (0); +} + +int +zfs_close(struct vnode *vp, int flag, cred_t *cr) +{ + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = ITOZSB(vp); + int error; + + if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) + return (error); + + /* Decrement the synchronous opens in the znode */ + if (flag & (FSYNC | FDSYNC)) + atomic_dec_32(&zp->z_sync_cnt); + + if (!zfs_has_ctldir(zp) && zfsvfs->z_vscan && S_ISREG(zp->z_mode) && + !(zp->z_pflags & ZFS_AV_QUARANTINED) && zp->z_size > 0) + VERIFY(zfs_vscan(vp, cr, 1) == 0); + + zfs_exit(zfsvfs, FTAG); + return (0); +} + +#if defined(_KERNEL) +/* + * When a file is memory mapped, we must keep the IO data synchronized + * between the DMU cache and the memory mapped pages. What this means: + * + * On Write: If we find a memory mapped page, we write to *both* + * the page and the dmu buffer. + */ +void +update_pages(znode_t *zp, int64_t start, int len, + objset_t *os) +{ + int error = 0; + vm_offset_t vaddr = 0; + upl_t upl; + upl_page_info_t *pl = NULL; + int upl_size; + int upl_page; + off_t off; + + off = start & (PAGE_SIZE - 1); + start &= ~PAGE_MASK; + + upl_size = (off + len + (PAGE_SIZE - 1)) & ~PAGE_MASK; + + // dprintf("update_pages: start 0x%llx len 0x%llx: 1st off x%llx\n", + // start, len, off); + /* + * Create a UPL for the current range and map its + * page list into the kernel virtual address space. + */ + error = ubc_create_upl(ZTOV(zp), start, upl_size, &upl, &pl, + UPL_FILE_IO | UPL_SET_LITE); + if ((error != KERN_SUCCESS) || !upl) { + printf("ZFS: update_pages failed to ubc_create_upl: %d\n", + error); + return; + } + + if (ubc_upl_map(upl, &vaddr) != KERN_SUCCESS) { + printf("ZFS: update_pages failed to ubc_upl_map: %d\n", + error); + (void) ubc_upl_abort(upl, UPL_ABORT_FREE_ON_EMPTY); + return; + } + for (upl_page = 0; len > 0; ++upl_page) { + uint64_t nbytes = MIN(PAGESIZE - off, len); + /* + * We don't want a new page to "appear" in the middle of + * the file update (because it may not get the write + * update data), so we grab a lock to block + * zfs_getpage(). + */ + rw_enter(&zp->z_map_lock, RW_WRITER); + if (pl && upl_valid_page(pl, upl_page)) { + rw_exit(&zp->z_map_lock); + (void) dmu_read(os, zp->z_id, start+off, nbytes, + (void *)(vaddr+off), DMU_READ_PREFETCH); + + } else { // !upl_valid_page + rw_exit(&zp->z_map_lock); + } + vaddr += PAGE_SIZE; + start += PAGE_SIZE; + len -= nbytes; + off = 0; + } + + /* + * Unmap the page list and free the UPL. + */ + (void) ubc_upl_unmap(upl); + /* + * We want to abort here since due to dmu_write() + * we effectively didn't dirty any pages. + */ + (void) ubc_upl_abort(upl, UPL_ABORT_FREE_ON_EMPTY); +} + +/* + * When a file is memory mapped, we must keep the IO data synchronized + * between the DMU cache and the memory mapped pages. What this means: + * + * On Read: We "read" preferentially from memory mapped pages, + * else we default from the dmu buffer. + * + * NOTE: We will always "break up" the IO into PAGESIZE uiomoves when + * the file is memory mapped. + */ +int +mappedread(struct znode *zp, int nbytes, zfs_uio_t *uio) +{ + objset_t *os = zp->z_zfsvfs->z_os; + int len = nbytes; + int error = 0; + vm_offset_t vaddr = 0; + upl_t upl; + upl_page_info_t *pl = NULL; + off_t upl_start; + int upl_size; + int upl_page; + off_t off; + + upl_start = zfs_uio_offset(uio); + off = upl_start & PAGE_MASK; + upl_start &= ~PAGE_MASK; + upl_size = (off + nbytes + (PAGE_SIZE - 1)) & ~PAGE_MASK; + + /* + * Create a UPL for the current range and map its + * page list into the kernel virtual address space. + */ + error = ubc_create_upl(ZTOV(zp), upl_start, upl_size, &upl, &pl, + UPL_FILE_IO | UPL_SET_LITE); + if ((error != KERN_SUCCESS) || !upl) { + return (EIO); + } + + if (ubc_upl_map(upl, &vaddr) != KERN_SUCCESS) { + (void) ubc_upl_abort(upl, UPL_ABORT_FREE_ON_EMPTY); + return (ENOMEM); + } + + for (upl_page = 0; len > 0; ++upl_page) { + uint64_t bytes = MIN(PAGE_SIZE - off, len); + if (pl && upl_valid_page(pl, upl_page)) { + zfs_uio_setrw(uio, UIO_READ); + error = zfs_uiomove((caddr_t)vaddr + off, bytes, + UIO_READ, uio); + } else { + error = dmu_read_uio(os, zp->z_id, uio, bytes); + } + + vaddr += PAGE_SIZE; + len -= bytes; + off = 0; + if (error) + break; + } + + /* + * Unmap the page list and free the UPL. + */ + (void) ubc_upl_unmap(upl); + (void) ubc_upl_abort(upl, UPL_ABORT_FREE_ON_EMPTY); + + return (error); +} +#endif /* _KERNEL */ + +unsigned long zfs_delete_blocks = DMU_MAX_DELETEBLKCNT; + +/* + * Write the bytes to a file. + * + * IN: zp - znode of file to be written to + * data - bytes to write + * len - number of bytes to write + * pos - offset to start writing at + * + * OUT: resid - remaining bytes to write + * + * RETURN: 0 if success + * positive error code if failure + * + * Timestamps: + * zp - ctime|mtime updated if byte count > 0 + */ +int +zfs_write_simple(znode_t *zp, const void *data, size_t len, + loff_t pos, size_t *presid) +{ + int error = 0; + ssize_t resid; + + error = zfs_vn_rdwr(UIO_WRITE, ZTOV(zp), __DECONST(void *, data), len, + pos, UIO_SYSSPACE, IO_SYNC, RLIM64_INFINITY, NOCRED, &resid); + + if (error) { + return (SET_ERROR(error)); + } else if (presid == NULL) { + if (resid != 0) { + error = SET_ERROR(EIO); + } + } else { + *presid = resid; + } + return (error); +} + +/* + * Drop a reference on the passed inode asynchronously. This ensures + * that the caller will never drop the last reference on an inode in + * the current context. Doing so while holding open a tx could result + * in a deadlock if iput_final() re-enters the filesystem code. + */ +void +zfs_zrele_async(znode_t *zp) +{ + struct vnode *vp = ZTOV(zp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + objset_t *os = zfsvfs->z_os; + + ASSERT(os != NULL); + + if (vp == NULL) + return; + + /* If iocount > 1, AND, vp is set (not async_get) */ + if (vnode_iocount(vp) > 1) { + VN_RELE(vp); + return; + } + + ASSERT3P(vp, !=, NULL); + + VERIFY(taskq_dispatch(dsl_pool_zrele_taskq(dmu_objset_pool(os)), + (task_func_t *)vnode_put, vp, TQ_SLEEP) != TASKQID_INVALID); +} + +/* + * Lookup an entry in a directory, or an extended attribute directory. + * If it exists, return a held inode reference for it. + * + * IN: zdp - znode of directory to search. + * nm - name of entry to lookup. + * flags - LOOKUP_XATTR set if looking for an attribute. + * cr - credentials of caller. + * direntflags - directory lookup flags + * realpnp - returned pathname. + * + * OUT: zpp - znode of located entry, NULL if not found. + * + * RETURN: 0 on success, error code on failure. + * + * Timestamps: + * NA + */ +int +zfs_lookup(znode_t *zdp, char *nm, znode_t **zpp, int flags, + cred_t *cr, int *direntflags, struct componentname *realpnp) +{ + zfsvfs_t *zfsvfs = ZTOZSB(zdp); + int error = 0; + + if ((error = zfs_enter_verify_zp(zfsvfs, zdp, FTAG)) != 0) + return (error); + + *zpp = NULL; + + /* + * OsX has separate vnops for XATTR activity + */ + if (flags & LOOKUP_XATTR) { + /* + * We don't allow recursive attributes.. + * Maybe someday we will. + */ + if (zdp->z_pflags & ZFS_XATTR) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EINVAL)); + } + + if ((error = zfs_get_xattrdir(zdp, zpp, cr, flags))) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + + /* + * Do we have permission to get into attribute directory? + */ + + if ((error = zfs_zaccess(*zpp, ACE_EXECUTE, 0, + B_FALSE, cr, NULL))) { + zrele(*zpp); + *zpp = NULL; + } + + zfs_exit(zfsvfs, FTAG); + return (error); + } + + if (!S_ISDIR(zdp->z_mode)) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(ENOTDIR)); + } + + /* + * Check accessibility of directory. + */ + + if ((error = zfs_zaccess(zdp, ACE_EXECUTE, 0, B_FALSE, cr, NULL))) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + + if (zfsvfs->z_utf8 && u8_validate(nm, strlen(nm), + NULL, U8_VALIDATE_ENTIRE, &error) < 0) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EILSEQ)); + } + + error = zfs_dirlook(zdp, nm, zpp, flags, direntflags, realpnp); + + zfs_exit(zfsvfs, FTAG); + return (error); +} + +/* + * Attempt to create a new entry in a directory. If the entry + * already exists, truncate the file if permissible, else return + * an error. Return the ip of the created or trunc'd file. + * + * IN: dzp - znode of directory to put new file entry in. + * name - name of new file entry. + * vap - attributes of new file. + * excl - flag indicating exclusive or non-exclusive mode. + * mode - mode to open file with. + * cr - credentials of caller. + * flag - file flag. + * vsecp - ACL to be set + * + * OUT: zpp - znode of created or trunc'd entry. + * + * RETURN: 0 on success, error code on failure. + * + * Timestamps: + * dzp - ctime|mtime updated if new entry created + * zp - ctime|mtime always, atime if new + */ + +int +zfs_create(znode_t *dzp, char *name, vattr_t *vap, int excl, + int mode, znode_t **zpp, cred_t *cr, int flag, vsecattr_t *vsecp, + zidmap_t *mnt_ns) +{ + znode_t *zp = NULL; + zfsvfs_t *zfsvfs = ZTOZSB(dzp); + zilog_t *zilog; + objset_t *os; + zfs_dirlock_t *dl; + dmu_tx_t *tx; + int error; + uid_t uid; + gid_t gid; + zfs_acl_ids_t acl_ids; + boolean_t fuid_dirtied; + boolean_t have_acl = B_FALSE; + boolean_t waited = B_FALSE; + + /* + * If we have an ephemeral id, ACL, or XVATTR then + * make sure file system is at proper version + */ + + gid = crgetgid(cr); + uid = crgetuid(cr); + + if (zfsvfs->z_use_fuids == B_FALSE && + (vsecp || IS_EPHEMERAL(uid) || IS_EPHEMERAL(gid))) + return (SET_ERROR(EINVAL)); + + if (name == NULL) + return (SET_ERROR(EINVAL)); + + if ((error = zfs_enter_verify_zp(zfsvfs, dzp, FTAG)) != 0) + return (error); + os = zfsvfs->z_os; + zilog = zfsvfs->z_log; + + if (zfsvfs->z_utf8 && u8_validate(name, strlen(name), + NULL, U8_VALIDATE_ENTIRE, &error) < 0) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EILSEQ)); + } + + if (vap->va_mask & ATTR_XVATTR) { + if ((error = secpolicy_xvattr(vap, + crgetuid(cr), cr, vap->va_mode)) != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + } + +top: + *zpp = NULL; + if (*name == '\0') { + /* + * Null component name refers to the directory itself. + */ + zhold(dzp); + zp = dzp; + dl = NULL; + error = 0; + } else { + /* possible igrab(zp) */ + int zflg = 0; + + if (flag & FIGNORECASE) + zflg |= ZCILOOK; + + error = zfs_dirent_lock(&dl, dzp, name, &zp, zflg, + NULL, NULL); + if (error) { + if (have_acl) + zfs_acl_ids_free(&acl_ids); + if (strcmp(name, "..") == 0) + error = SET_ERROR(EISDIR); + zfs_exit(zfsvfs, FTAG); + return (error); + } + } + + if (zp == NULL) { + uint64_t txtype; + uint64_t projid = ZFS_DEFAULT_PROJID; + + /* + * Create a new file object and update the directory + * to reference it. + */ + if ((error = zfs_zaccess(dzp, ACE_ADD_FILE, 0, B_FALSE, cr, + NULL))) { + if (have_acl) + zfs_acl_ids_free(&acl_ids); + goto out; + } + + /* + * We only support the creation of regular files in + * extended attribute directories. + */ + + if ((dzp->z_pflags & ZFS_XATTR) && !S_ISREG(vap->va_mode)) { + if (have_acl) + zfs_acl_ids_free(&acl_ids); + error = SET_ERROR(EINVAL); + goto out; + } + + if (!have_acl && (error = zfs_acl_ids_create(dzp, 0, vap, + cr, vsecp, &acl_ids, NULL)) != 0) + goto out; + have_acl = B_TRUE; + + if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) + projid = zfs_inherit_projid(dzp); + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, projid)) { + zfs_acl_ids_free(&acl_ids); + error = SET_ERROR(EDQUOT); + goto out; + } + + tx = dmu_tx_create(os); + + dmu_tx_hold_sa_create(tx, acl_ids.z_aclp->z_acl_bytes + + ZFS_SA_BASE_ATTR_SIZE); + + fuid_dirtied = zfsvfs->z_fuid_dirty; + if (fuid_dirtied) + zfs_fuid_txhold(zfsvfs, tx); + dmu_tx_hold_zap(tx, dzp->z_id, TRUE, name); + dmu_tx_hold_sa(tx, dzp->z_sa_hdl, B_FALSE); + if (!zfsvfs->z_use_sa && + acl_ids.z_aclp->z_acl_bytes > ZFS_ACE_SPACE) { + dmu_tx_hold_write(tx, DMU_NEW_OBJECT, + 0, acl_ids.z_aclp->z_acl_bytes); + } + + error = dmu_tx_assign(tx, + (waited ? TXG_NOTHROTTLE : 0) | TXG_NOWAIT); + if (error) { + zfs_dirent_unlock(dl); + if (error == ERESTART) { + waited = B_TRUE; + dmu_tx_wait(tx); + dmu_tx_abort(tx); + goto top; + } + zfs_acl_ids_free(&acl_ids); + dmu_tx_abort(tx); + zfs_exit(zfsvfs, FTAG); + return (error); + } + + zfs_mknode(dzp, vap, tx, cr, 0, &zp, &acl_ids); + + error = zfs_link_create(dl, zp, tx, ZNEW); + if (error != 0) { + /* + * Since, we failed to add the directory entry for it, + * delete the newly created dnode. + */ + zfs_znode_delete(zp, tx); + zfs_acl_ids_free(&acl_ids); + dmu_tx_commit(tx); + + /* + * Failed, have zp but on OsX we don't have a vp, as it + * would have been attached below, and we've cleared out + * zp, signal then not to call zrele() on it. + */ + if (ZTOV(zp) == NULL) { + zfs_znode_free(zp); + zp = NULL; + } + + goto out; + } + + if (fuid_dirtied) + zfs_fuid_sync(zfsvfs, tx); + + txtype = zfs_log_create_txtype(Z_FILE, vsecp, vap); + if (flag & FIGNORECASE) + txtype |= TX_CI; + zfs_log_create(zilog, tx, txtype, dzp, zp, name, + vsecp, acl_ids.z_fuidp, vap); + zfs_acl_ids_free(&acl_ids); + dmu_tx_commit(tx); + + /* + * OS X - attach the vnode _after_ committing the transaction + */ + zfs_znode_getvnode(zp, zfsvfs); + + } else { + int aflags = (flag & O_APPEND) ? V_APPEND : 0; + + if (have_acl) + zfs_acl_ids_free(&acl_ids); + have_acl = B_FALSE; + + /* + * A directory entry already exists for this name. + */ + /* + * Can't truncate an existing file if in exclusive mode. + */ + if (excl) { + error = SET_ERROR(EEXIST); + goto out; + } + /* + * Can't open a directory for writing. + */ + if (S_ISDIR(zp->z_mode)) { + error = SET_ERROR(EISDIR); + goto out; + } + /* + * Verify requested access to file. + */ + if (mode && (error = zfs_zaccess_rwx(zp, mode, aflags, cr, + NULL))) + goto out; + + mutex_enter(&dzp->z_lock); + dzp->z_seq++; + mutex_exit(&dzp->z_lock); + + /* + * Truncate regular files if requested. + */ + if (S_ISREG(zp->z_mode) && + (vap->va_mask & ATTR_SIZE) && (vap->va_size == 0)) { + /* we can't hold any locks when calling zfs_freesp() */ + if (dl) { + zfs_dirent_unlock(dl); + dl = NULL; + } + error = zfs_freesp(zp, 0, 0, mode, TRUE); + } + } +out: + + if (dl) + zfs_dirent_unlock(dl); + + if (error) { + if (zp) + zrele(zp); + } else { + *zpp = zp; + } + + if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS) + zil_commit(zilog, 0); + + zfs_exit(zfsvfs, FTAG); + return (error); +} + +/* + * Remove an entry from a directory. + * + * IN: dzp - znode of directory to remove entry from. + * name - name of entry to remove. + * cr - credentials of caller. + * flags - case flags. + * + * RETURN: 0 if success + * error code if failure + * + * Timestamps: + * dzp - ctime|mtime + * ip - ctime (if nlink > 0) + */ + +uint64_t null_xattr = 0; + +int +zfs_remove(znode_t *dzp, char *name, cred_t *cr, int flags) +{ + znode_t *zp; + znode_t *xzp; + zfsvfs_t *zfsvfs = ZTOZSB(dzp); + zilog_t *zilog; + uint64_t acl_obj, xattr_obj; + uint64_t xattr_obj_unlinked = 0; + uint64_t obj = 0; + uint64_t links; + zfs_dirlock_t *dl; + dmu_tx_t *tx; + boolean_t may_delete_now, delete_now = FALSE; + boolean_t unlinked, toobig = FALSE; + uint64_t txtype; + struct componentname *realnmp = NULL; + struct componentname realnm = { 0 }; + int error; + int zflg = ZEXISTS; + boolean_t waited = B_FALSE; + + if (name == NULL) + return (SET_ERROR(EINVAL)); + + if ((error = zfs_enter_verify_zp(zfsvfs, dzp, FTAG)) != 0) + return (error); + zilog = zfsvfs->z_log; + + if (flags & FIGNORECASE) { + zflg |= ZCILOOK; + + realnm.cn_nameptr = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + realnm.cn_namelen = MAXPATHLEN; + realnmp = &realnm; + } + +top: + xattr_obj = 0; + xzp = NULL; + /* + * Attempt to lock directory; fail if entry doesn't exist. + */ + if ((error = zfs_dirent_lock(&dl, dzp, name, &zp, zflg, + NULL, realnmp))) { + if (realnmp) + kmem_free(realnm.cn_nameptr, realnm.cn_namelen); + zfs_exit(zfsvfs, FTAG); + return (error); + } + + if ((error = zfs_zaccess_delete(dzp, zp, cr, NULL))) { + goto out; + } + + /* + * Need to use rmdir for removing directories. + */ + if (S_ISDIR(zp->z_mode)) { + error = SET_ERROR(EPERM); + goto out; + } + + mutex_enter(&zp->z_lock); + may_delete_now = vnode_iocount(ZTOV(zp)) == 1 && + !(zp->z_is_mapped); + mutex_exit(&zp->z_lock); + + /* + * We may delete the znode now, or we may put it in the unlinked set; + * it depends on whether we're the last link, and on whether there are + * other holds on the inode. So we dmu_tx_hold() the right things to + * allow for either case. + */ + obj = zp->z_id; + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_zap(tx, dzp->z_id, FALSE, name); + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); + zfs_sa_upgrade_txholds(tx, zp); + zfs_sa_upgrade_txholds(tx, dzp); + if (may_delete_now) { + toobig = zp->z_size > zp->z_blksz * zfs_delete_blocks; + /* if the file is too big, only hold_free a token amount */ + dmu_tx_hold_free(tx, zp->z_id, 0, + (toobig ? DMU_MAX_ACCESS : DMU_OBJECT_END)); + } + + /* are there any extended attributes? */ + error = sa_lookup(zp->z_sa_hdl, SA_ZPL_XATTR(zfsvfs), + &xattr_obj, sizeof (xattr_obj)); + if (error == 0 && xattr_obj) { + error = zfs_zget(zfsvfs, xattr_obj, &xzp); + ASSERT0(error); + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE); + dmu_tx_hold_sa(tx, xzp->z_sa_hdl, B_FALSE); + } + + mutex_enter(&zp->z_lock); + if ((acl_obj = zfs_external_acl(zp)) != 0 && may_delete_now) + dmu_tx_hold_free(tx, acl_obj, 0, DMU_OBJECT_END); + mutex_exit(&zp->z_lock); + + /* charge as an update -- would be nice not to charge at all */ + dmu_tx_hold_zap(tx, zfsvfs->z_unlinkedobj, FALSE, NULL); + + /* + * Mark this transaction as typically resulting in a net free of space + */ + dmu_tx_mark_netfree(tx); + + error = dmu_tx_assign(tx, (waited ? TXG_NOTHROTTLE : 0) | TXG_NOWAIT); + if (error) { + zfs_dirent_unlock(dl); + if (error == ERESTART) { + waited = B_TRUE; + dmu_tx_wait(tx); + dmu_tx_abort(tx); + zrele(zp); + if (xzp) + zrele(xzp); + goto top; + } + if (realnmp) + kmem_free(realnm.cn_nameptr, realnm.cn_namelen); + dmu_tx_abort(tx); + zrele(zp); + if (xzp) + zrele(xzp); + zfs_exit(zfsvfs, FTAG); + return (error); + } + + /* + * Remove the directory entry. + */ + error = zfs_link_destroy(dl, zp, tx, zflg, &unlinked); + + if (error) { + dmu_tx_commit(tx); + goto out; + } + + if (unlinked) { + /* + * Hold z_lock so that we can make sure that the ACL obj + * hasn't changed. Could have been deleted due to + * zfs_sa_upgrade(). + */ + mutex_enter(&zp->z_lock); + (void) sa_lookup(zp->z_sa_hdl, SA_ZPL_XATTR(zfsvfs), + &xattr_obj_unlinked, sizeof (xattr_obj_unlinked)); + delete_now = may_delete_now && !toobig && + vnode_iocount(ZTOV(zp)) == 1 && + !(zp->z_is_mapped) && xattr_obj == xattr_obj_unlinked && + zfs_external_acl(zp) == acl_obj; + } + + if (delete_now) { + if (xattr_obj_unlinked) { + mutex_enter(&xzp->z_lock); + xzp->z_unlinked = B_TRUE; + links = 0; + error = sa_update(xzp->z_sa_hdl, SA_ZPL_LINKS(zfsvfs), + &links, sizeof (links), tx); + ASSERT3U(error, ==, 0); + mutex_exit(&xzp->z_lock); + zfs_unlinked_add(xzp, tx); + + if (zp->z_is_sa) + error = sa_remove(zp->z_sa_hdl, + SA_ZPL_XATTR(zfsvfs), tx); + else + error = sa_update(zp->z_sa_hdl, + SA_ZPL_XATTR(zfsvfs), &null_xattr, + sizeof (uint64_t), tx); + ASSERT0(error); + } + /* + * Add to the unlinked set because a new reference could be + * taken concurrently resulting in a deferred destruction. + */ + zfs_unlinked_add(zp, tx); + mutex_exit(&zp->z_lock); + } else if (unlinked) { + mutex_exit(&zp->z_lock); + zfs_unlinked_add(zp, tx); + } + + txtype = TX_REMOVE; + if (flags & FIGNORECASE) + txtype |= TX_CI; + zfs_log_remove(zilog, tx, txtype, dzp, name, obj, unlinked); + + dmu_tx_commit(tx); +out: + if (realnmp) + kmem_free(realnm.cn_nameptr, realnm.cn_namelen); + + zfs_dirent_unlock(dl); + + if (delete_now) + zrele(zp); + else + zfs_zrele_async(zp); + + if (xzp) + zfs_zrele_async(xzp); + + if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS) + zil_commit(zilog, 0); + + zfs_exit(zfsvfs, FTAG); + return (error); +} + +/* + * Create a new directory and insert it into dzp using the name + * provided. Return a pointer to the inserted directory. + * + * IN: dzp - znode of directory to add subdir to. + * dirname - name of new directory. + * vap - attributes of new directory. + * cr - credentials of caller. + * flags - case flags. + * vsecp - ACL to be set + * + * OUT: zpp - znode of created directory. + * + * RETURN: 0 if success + * error code if failure + * + * Timestamps: + * dzp - ctime|mtime updated + * zpp - ctime|mtime|atime updated + */ +int +zfs_mkdir(znode_t *dzp, char *dirname, vattr_t *vap, znode_t **zpp, + cred_t *cr, int flags, vsecattr_t *vsecp, zidmap_t *mnt_ns) +{ + znode_t *zp; + zfsvfs_t *zfsvfs = ZTOZSB(dzp); + zilog_t *zilog; + zfs_dirlock_t *dl; + uint64_t txtype; + dmu_tx_t *tx; + int error; + int zf = ZNEW; + uid_t uid; + gid_t gid = crgetgid(cr); + zfs_acl_ids_t acl_ids; + boolean_t fuid_dirtied; + boolean_t waited = B_FALSE; + + ASSERT(S_ISDIR(vap->va_mode)); + + /* + * If we have an ephemeral id, ACL, or XVATTR then + * make sure file system is at proper version + */ + + uid = crgetuid(cr); + if (zfsvfs->z_use_fuids == B_FALSE && + (vsecp || IS_EPHEMERAL(uid) || IS_EPHEMERAL(gid))) + return (SET_ERROR(EINVAL)); + + if (dirname == NULL) + return (SET_ERROR(EINVAL)); + + if ((error = zfs_enter_verify_zp(zfsvfs, dzp, FTAG)) != 0) + return (error); + zilog = zfsvfs->z_log; + + if (dzp->z_pflags & ZFS_XATTR) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EINVAL)); + } + + if (zfsvfs->z_utf8 && u8_validate(dirname, + strlen(dirname), NULL, U8_VALIDATE_ENTIRE, &error) < 0) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EILSEQ)); + } + if (flags & FIGNORECASE) + zf |= ZCILOOK; + + if (vap->va_mask & ATTR_XVATTR) { + if ((error = secpolicy_xvattr(vap, + crgetuid(cr), cr, vap->va_mode)) != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + } + + if ((error = zfs_acl_ids_create(dzp, 0, vap, cr, + vsecp, &acl_ids, mnt_ns)) != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + /* + * First make sure the new directory doesn't exist. + * + * Existence is checked first to make sure we don't return + * EACCES instead of EEXIST which can cause some applications + * to fail. + */ +top: + *zpp = NULL; + + if ((error = zfs_dirent_lock(&dl, dzp, dirname, &zp, zf, + NULL, NULL))) { + zfs_acl_ids_free(&acl_ids); + zfs_exit(zfsvfs, FTAG); + return (error); + } + + if ((error = zfs_zaccess(dzp, ACE_ADD_SUBDIRECTORY, 0, B_FALSE, cr, + mnt_ns))) { + zfs_acl_ids_free(&acl_ids); + zfs_dirent_unlock(dl); + zfs_exit(zfsvfs, FTAG); + return (error); + } + + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, zfs_inherit_projid(dzp))) { + zfs_acl_ids_free(&acl_ids); + zfs_dirent_unlock(dl); + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EDQUOT)); + } + + /* + * Add a new entry to the directory. + */ + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_zap(tx, dzp->z_id, TRUE, dirname); + dmu_tx_hold_zap(tx, DMU_NEW_OBJECT, FALSE, NULL); + fuid_dirtied = zfsvfs->z_fuid_dirty; + if (fuid_dirtied) + zfs_fuid_txhold(zfsvfs, tx); + if (!zfsvfs->z_use_sa && acl_ids.z_aclp->z_acl_bytes > ZFS_ACE_SPACE) { + dmu_tx_hold_write(tx, DMU_NEW_OBJECT, 0, + acl_ids.z_aclp->z_acl_bytes); + } + + dmu_tx_hold_sa_create(tx, acl_ids.z_aclp->z_acl_bytes + + ZFS_SA_BASE_ATTR_SIZE); + + error = dmu_tx_assign(tx, (waited ? TXG_NOTHROTTLE : 0) | TXG_NOWAIT); + if (error) { + zfs_dirent_unlock(dl); + if (error == ERESTART) { + waited = B_TRUE; + dmu_tx_wait(tx); + dmu_tx_abort(tx); + goto top; + } + zfs_acl_ids_free(&acl_ids); + dmu_tx_abort(tx); + zfs_exit(zfsvfs, FTAG); + return (error); + } + + /* + * Create new node. + */ + zfs_mknode(dzp, vap, tx, cr, 0, &zp, &acl_ids); + + /* + * Now put new name in parent dir. + */ + error = zfs_link_create(dl, zp, tx, ZNEW); + if (error != 0) { + zfs_znode_delete(zp, tx); + goto out; + } + + if (fuid_dirtied) + zfs_fuid_sync(zfsvfs, tx); + + *zpp = zp; + + txtype = zfs_log_create_txtype(Z_DIR, vsecp, vap); + if (flags & FIGNORECASE) + txtype |= TX_CI; + zfs_log_create(zilog, tx, txtype, dzp, zp, dirname, vsecp, + acl_ids.z_fuidp, vap); + +out: + zfs_acl_ids_free(&acl_ids); + + dmu_tx_commit(tx); + /* + * OS X - attach the vnode _after_ committing the transaction + */ + zfs_znode_getvnode(zp, zfsvfs); + + zfs_dirent_unlock(dl); + + if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS) + zil_commit(zilog, 0); + + if (error != 0) { + zrele(zp); + } else { + } + zfs_exit(zfsvfs, FTAG); + return (error); +} + +/* + * Remove a directory subdir entry. If the current working + * directory is the same as the subdir to be removed, the + * remove will fail. + * + * IN: dzp - znode of directory to remove from. + * name - name of directory to be removed. + * cwd - inode of current working directory. + * cr - credentials of caller. + * flags - case flags + * + * RETURN: 0 on success, error code on failure. + * + * Timestamps: + * dzp - ctime|mtime updated + */ +int +zfs_rmdir(znode_t *dzp, char *name, znode_t *cwd, cred_t *cr, + int flags) +{ + znode_t *zp; + zfsvfs_t *zfsvfs = ZTOZSB(dzp); + zilog_t *zilog; + zfs_dirlock_t *dl; + dmu_tx_t *tx; + int error; + int zflg = ZEXISTS; + boolean_t waited = B_FALSE; + + if (name == NULL) + return (SET_ERROR(EINVAL)); + + if ((error = zfs_enter_verify_zp(zfsvfs, dzp, FTAG)) != 0) + return (error); + zilog = zfsvfs->z_log; + + if (flags & FIGNORECASE) + zflg |= ZCILOOK; +top: + zp = NULL; + + /* + * Attempt to lock directory; fail if entry doesn't exist. + */ + if ((error = zfs_dirent_lock(&dl, dzp, name, &zp, zflg, + NULL, NULL))) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + + if ((error = zfs_zaccess_delete(dzp, zp, cr, NULL))) { + goto out; + } + + if (ZTOTYPE(zp) != VDIR) { + error = SET_ERROR(ENOTDIR); + goto out; + } + + if (zp == cwd) { + error = SET_ERROR(EINVAL); + goto out; + } + + /* + * Grab a lock on the directory to make sure that no one is + * trying to add (or lookup) entries while we are removing it. + */ + rw_enter(&zp->z_name_lock, RW_WRITER); + + /* + * Grab a lock on the parent pointer to make sure we play well + * with the treewalk and directory rename code. + */ + rw_enter(&zp->z_parent_lock, RW_WRITER); + + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_zap(tx, dzp->z_id, FALSE, name); + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); + dmu_tx_hold_zap(tx, zfsvfs->z_unlinkedobj, FALSE, NULL); + zfs_sa_upgrade_txholds(tx, zp); + zfs_sa_upgrade_txholds(tx, dzp); + dmu_tx_mark_netfree(tx); + error = dmu_tx_assign(tx, (waited ? TXG_NOTHROTTLE : 0) | TXG_NOWAIT); + if (error) { + rw_exit(&zp->z_parent_lock); + rw_exit(&zp->z_name_lock); + zfs_dirent_unlock(dl); + if (error == ERESTART) { + waited = B_TRUE; + dmu_tx_wait(tx); + dmu_tx_abort(tx); + zrele(zp); + goto top; + } + dmu_tx_abort(tx); + zrele(zp); + zfs_exit(zfsvfs, FTAG); + return (error); + } + + error = zfs_link_destroy(dl, zp, tx, zflg, NULL); + + if (error == 0) { + uint64_t txtype = TX_RMDIR; + if (flags & FIGNORECASE) + txtype |= TX_CI; + zfs_log_remove(zilog, tx, txtype, dzp, name, ZFS_NO_OBJECT, + B_FALSE); + } + + dmu_tx_commit(tx); + + rw_exit(&zp->z_parent_lock); + rw_exit(&zp->z_name_lock); +out: + zfs_dirent_unlock(dl); + + zrele(zp); + + if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS) + zil_commit(zilog, 0); + + zfs_exit(zfsvfs, FTAG); + return (error); +} + +/* + * Read directory entries from the given directory cursor position and emit + * name and position for each entry. + * + * IN: ip - inode of directory to read. + * ctx - directory entry context. + * cr - credentials of caller. + * + * RETURN: 0 if success + * error code if failure + * + * Timestamps: + * ip - atime updated + * + * Note that the low 4 bits of the cookie returned by zap is always zero. + * This allows us to use the low range for "special" directory entries: + * We use 0 for '.', and 1 for '..'. If this is the root of the filesystem, + * we use the offset 2 for the '.zfs' directory. + */ +int +zfs_readdir(vnode_t *vp, zfs_uio_t *uio, cred_t *cr, int *eofp, + int flags, int *a_numdirent) +{ + + znode_t *zp = VTOZ(vp); + boolean_t extended = (flags & VNODE_READDIR_EXTENDED); + struct direntry *eodp; /* Extended */ + struct dirent *odp; /* Standard */ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + objset_t *os; + caddr_t outbuf; + size_t bufsize; + zap_cursor_t zc; + zap_attribute_t zap; + uint_t bytes_wanted; + uint64_t offset; /* must be unsigned; checks for < 1 */ + uint64_t parent; + int local_eof; + int outcount; + int error = 0; + uint8_t prefetch; + uint8_t type; + int numdirent = 0; + char *bufptr; + + if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) + return (error); + + if ((error = sa_lookup(zp->z_sa_hdl, SA_ZPL_PARENT(zfsvfs), + &parent, sizeof (parent))) != 0) + goto out; + + /* + * If we are not given an eof variable, + * use a local one. + */ + if (eofp == NULL) + eofp = &local_eof; + + /* + * Check for valid iov_len. + */ + if (zfs_uio_iovlen(uio, 0) <= 0) { + error = EINVAL; + goto out; + } + + /* + * Quit if directory has been removed (posix) + */ + if ((*eofp = zp->z_unlinked) != 0) { + goto out; + } + + error = 0; + os = zfsvfs->z_os; + offset = zfs_uio_offset(uio); + prefetch = zp->z_zn_prefetch; + + /* + * Initialize the iterator cursor. + */ + if (offset <= 3) { + /* + * Start iteration from the beginning of the directory. + */ + zap_cursor_init(&zc, os, zp->z_id); + } else { + /* + * The offset is a serialized cursor. + */ + zap_cursor_init_serialized(&zc, os, zp->z_id, offset); + } + + /* + * Get space to change directory entries into fs independent format. + */ + bytes_wanted = zfs_uio_iovlen(uio, 0); + bufsize = (size_t)bytes_wanted; + outbuf = kmem_alloc(bufsize, KM_SLEEP); + bufptr = (char *)outbuf; + + /* + * Transform to file-system independent format + */ + + outcount = 0; + while (outcount < bytes_wanted) { + ino64_t objnum; + ushort_t reclen; + uint64_t *next = NULL; + size_t namelen; + int force_formd_normalized_output; + size_t nfdlen; + + /* + * Special case `.', `..', and `.zfs'. + */ + if (offset == 0) { + (void) strlcpy(zap.za_name, ".", MAXNAMELEN); + zap.za_normalization_conflict = 0; + objnum = (zp->z_id == zfsvfs->z_root) ? 2 : zp->z_id; + type = DT_DIR; + } else if (offset == 1) { + (void) strlcpy(zap.za_name, "..", MAXNAMELEN); + zap.za_normalization_conflict = 0; + objnum = (parent == zfsvfs->z_root) ? 2 : parent; + objnum = (zp->z_id == zfsvfs->z_root) ? 1 : objnum; + type = DT_DIR; + } else if (offset == 2 && zfs_show_ctldir(zp)) { + (void) strlcpy(zap.za_name, ZFS_CTLDIR_NAME, + MAXNAMELEN); + zap.za_normalization_conflict = 0; + objnum = ZFSCTL_INO_ROOT; + type = DT_DIR; + } else { + + /* + * Grab next entry. + */ + if ((error = zap_cursor_retrieve(&zc, &zap))) { + if ((*eofp = (error == ENOENT)) != 0) + break; + else + goto update; + } + + /* + * Allow multiple entries provided the first entry is + * the object id. Non-zpl consumers may safely make + * use of the additional space. + * + * XXX: This should be a feature flag for compatibility + */ + if (zap.za_integer_length != 8 || + zap.za_num_integers != 1) { + cmn_err(CE_WARN, "zap_readdir: bad directory " + "entry, obj = %lld, offset = %lld\n", + (u_longlong_t)zp->z_id, + (u_longlong_t)offset); + error = SET_ERROR(ENXIO); + goto update; + } + + objnum = ZFS_DIRENT_OBJ(zap.za_first_integer); + /* + * MacOS X can extract the object type here such as: + * uint8_t type = ZFS_DIRENT_TYPE(zap.za_first_integer); + */ + type = ZFS_DIRENT_TYPE(zap.za_first_integer); + + } + + /* emit start */ + +#define DIRENT_RECLEN(namelen, ext) \ + ((ext) ? \ + ((sizeof (struct direntry) + (namelen) - (MAXPATHLEN-1) + 7) & ~7) \ + : \ + ((sizeof (struct dirent) - (NAME_MAX+1)) + (((namelen)+1 + 7) &~ 7))) + + /* + * Check if name will fit. + * + * Note: non-ascii names may expand (3x) when converted to NFD + */ + namelen = strlen(zap.za_name); + + /* sysctl to force formD normalization of vnop output */ + if (zfs_vnop_force_formd_normalized_output && + !is_ascii_str(zap.za_name)) + force_formd_normalized_output = 1; + else + force_formd_normalized_output = 0; + + if (force_formd_normalized_output) + namelen = MIN(extended ? MAXPATHLEN-1 : MAXNAMLEN, + namelen * 3); + + reclen = DIRENT_RECLEN(namelen, extended); + + /* + * Will this entry fit in the buffer? + */ + if (outcount + reclen > bufsize) { + /* + * Did we manage to fit anything in the buffer? + */ + if (!outcount) { + error = (EINVAL); + goto update; + } + break; + } + + if (extended) { + /* + * Add extended flag entry: + */ + eodp = (struct direntry *)bufptr; + /* NOTE: d_seekoff is the offset for the *next* entry */ + next = &(eodp->d_seekoff); + eodp->d_ino = INO_ZFSTOXNU(objnum, zfsvfs->z_root); + eodp->d_type = type; + + /* + * Mac OS X: non-ascii names are UTF-8 NFC on disk + * so convert to NFD before exporting them. + */ + namelen = strlen(zap.za_name); + if (!force_formd_normalized_output || + utf8_normalizestr((const u_int8_t *)zap.za_name, + namelen, (u_int8_t *)eodp->d_name, &nfdlen, + MAXPATHLEN-1, UTF_DECOMPOSED) != 0) { + /* ASCII or normalization failed, copy zap */ + if ((namelen > 0)) + (void) memcpy(eodp->d_name, zap.za_name, + namelen + 1); + } else { + /* Normalization succeeded (in buffer) */ + namelen = nfdlen; + } + eodp->d_namlen = namelen; + eodp->d_reclen = reclen = + DIRENT_RECLEN(namelen, extended); + + } else { + /* + * Add normal entry: + */ + + odp = (struct dirent *)bufptr; + odp->d_ino = INO_ZFSTOXNU(objnum, zfsvfs->z_root); + odp->d_type = type; + + /* + * Mac OS X: non-ascii names are UTF-8 NFC on disk + * so convert to NFD before exporting them. + */ + namelen = strlen(zap.za_name); + if (!force_formd_normalized_output || + utf8_normalizestr((const u_int8_t *)zap.za_name, + namelen, (u_int8_t *)odp->d_name, &nfdlen, + MAXNAMLEN, UTF_DECOMPOSED) != 0) { + /* ASCII or normalization failed, copy zap */ + if ((namelen > 0)) + (void) memcpy(odp->d_name, zap.za_name, + namelen + 1); + } else { + /* Normalization succeeded (in buffer). */ + namelen = nfdlen; + } + odp->d_namlen = namelen; + odp->d_reclen = reclen = + DIRENT_RECLEN(namelen, extended); + } + + outcount += reclen; + bufptr += reclen; + numdirent++; + + ASSERT(outcount <= bufsize); + + /* emit done */ + + /* Prefetch znode */ + if (prefetch) + dmu_prefetch(os, objnum, 0, 0, 0, + ZIO_PRIORITY_SYNC_READ); + + /* + * Move to the next entry, fill in the previous offset. + */ + if (offset > 2 || (offset == 2 && !zfs_show_ctldir(zp))) { + zap_cursor_advance(&zc); + offset = zap_cursor_serialize(&zc); + } else { + offset += 1; + } + + if (extended) + *next = offset; + } + zp->z_zn_prefetch = B_FALSE; /* a lookup will re-enable pre-fetching */ + + /* All done, copy temporary buffer to userland */ + if ((error = zfs_uiomove(outbuf, (long)outcount, UIO_READ, uio))) { + /* + * Reset the pointer. + */ + offset = zfs_uio_offset(uio); + } + + +update: + zap_cursor_fini(&zc); + if (outbuf) { + kmem_free(outbuf, bufsize); + } + + if (error == ENOENT) + error = 0; + + zfs_uio_setoffset(uio, offset); + if (a_numdirent) + *a_numdirent = numdirent; + +out: + zfs_exit(zfsvfs, FTAG); + + dprintf("-zfs_readdir: num %d\n", numdirent); + + return (error); +} + +static ulong_t zfs_fsync_sync_cnt = 4; + +/* Explore if we can use zfs/zfs_vnops.c's zfs_fsync() */ +int +zfs_fsync(znode_t *zp, int syncflag, cred_t *cr) +{ + zfsvfs_t *zfsvfs = ZTOZSB(zp); + vnode_t *vp = ZTOV(zp); + int error; + + if (zp->z_is_mapped /* && !(syncflag & FNODSYNC) */ && + vnode_isreg(vp) && !vnode_isswap(vp)) { + cluster_push(vp, /* waitdata ? IO_SYNC : */ 0); + } + + (void) tsd_set(zfs_fsyncer_key, (void *)zfs_fsync_sync_cnt); + + if (zfsvfs->z_os->os_sync != ZFS_SYNC_DISABLED && + !vnode_isrecycled(ZTOV(zp))) { + if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) + return (error); + zil_commit(zfsvfs->z_log, zp->z_id); + zfs_exit(zfsvfs, FTAG); + } + tsd_set(zfs_fsyncer_key, NULL); + + return (0); +} + +/* + * Get the requested file attributes and place them in the provided + * vattr structure. + * + * IN: vp - vnode of file. + * vap - va_mask identifies requested attributes. + * If ATTR_XVATTR set, then optional attrs are requested + * flags - ATTR_NOACLCHECK (CIFS server context) + * cr - credentials of caller. + * ct - caller context + * + * OUT: vap - attribute values. + * + * RETURN: 0 (always succeeds) + */ +int +zfs_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr, + caller_context_t *ct) +{ + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + int error = 0; + uint64_t links; + uint64_t mtime[2], ctime[2], crtime[2], rdev; + xvattr_t *xvap = (xvattr_t *)vap; /* vap may be an xvattr_t * */ + xoptattr_t *xoap = NULL; + boolean_t skipaclchk = /* (flags&ATTR_NOACLCHECK)?B_TRUE: */ B_FALSE; + sa_bulk_attr_t bulk[4]; + int count = 0; + + VERIFY3P(zp->z_zfsvfs, ==, vfs_fsprivate(vnode_mount(vp))); + + if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) + return (error); + + zfs_fuid_map_ids(zp, cr, &vap->va_uid, &vap->va_gid); + + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, &mtime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, &ctime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CRTIME(zfsvfs), NULL, &crtime, 16); + if (vnode_isblk(vp) || vnode_ischr(vp)) + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_RDEV(zfsvfs), NULL, + &rdev, 8); + + if ((error = sa_bulk_lookup(zp->z_sa_hdl, bulk, count)) != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + + /* + * If ACL is trivial don't bother looking for ACE_READ_ATTRIBUTES. + * Also, if we are the owner don't bother, since owner should + * always be allowed to read basic attributes of file. + */ + if (!(zp->z_pflags & ZFS_ACL_TRIVIAL) && + (vap->va_uid != crgetuid(cr))) { + if ((error = zfs_zaccess(zp, ACE_READ_ATTRIBUTES, 0, + skipaclchk, cr, NULL))) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + } + + /* + * Return all attributes. It's cheaper to provide the answer + * than to determine whether we were asked the question. + */ + + mutex_enter(&zp->z_lock); + vap->va_type = IFTOVT(zp->z_mode); + vap->va_mode = zp->z_mode & ~S_IFMT; + vap->va_nodeid = INO_ZFSTOXNU(zp->z_id, zfsvfs->z_root); + if (vnode_isvroot((vp)) && zfs_show_ctldir(zp)) + links = zp->z_links + 1; + else + links = zp->z_links; + vap->va_nlink = MIN(links, LINK_MAX); /* nlink_t limit! */ + vap->va_size = zp->z_size; + if (vnode_isblk(vp) || vnode_ischr(vp)) + vap->va_rdev = zfs_cmpldev(rdev); + + vap->va_flags = 0; /* FreeBSD: Reset chflags(2) flags. */ + + /* + * Add in any requested optional attributes and the create time. + * Also set the corresponding bits in the returned attribute bitmap. + */ + if ((xoap = xva_getxoptattr(xvap)) != NULL && zfsvfs->z_use_fuids) { + if (XVA_ISSET_REQ(xvap, XAT_ARCHIVE)) { + xoap->xoa_archive = + ((zp->z_pflags & ZFS_ARCHIVE) != 0); + XVA_SET_RTN(xvap, XAT_ARCHIVE); + } + + if (XVA_ISSET_REQ(xvap, XAT_READONLY)) { + xoap->xoa_readonly = + ((zp->z_pflags & ZFS_READONLY) != 0); + XVA_SET_RTN(xvap, XAT_READONLY); + } + + if (XVA_ISSET_REQ(xvap, XAT_SYSTEM)) { + xoap->xoa_system = + ((zp->z_pflags & ZFS_SYSTEM) != 0); + XVA_SET_RTN(xvap, XAT_SYSTEM); + } + + if (XVA_ISSET_REQ(xvap, XAT_HIDDEN)) { + xoap->xoa_hidden = + ((zp->z_pflags & ZFS_HIDDEN) != 0); + XVA_SET_RTN(xvap, XAT_HIDDEN); + } + + if (XVA_ISSET_REQ(xvap, XAT_NOUNLINK)) { + xoap->xoa_nounlink = + ((zp->z_pflags & ZFS_NOUNLINK) != 0); + XVA_SET_RTN(xvap, XAT_NOUNLINK); + } + + if (XVA_ISSET_REQ(xvap, XAT_IMMUTABLE)) { + xoap->xoa_immutable = + ((zp->z_pflags & ZFS_IMMUTABLE) != 0); + XVA_SET_RTN(xvap, XAT_IMMUTABLE); + } + + if (XVA_ISSET_REQ(xvap, XAT_APPENDONLY)) { + xoap->xoa_appendonly = + ((zp->z_pflags & ZFS_APPENDONLY) != 0); + XVA_SET_RTN(xvap, XAT_APPENDONLY); + } + + if (XVA_ISSET_REQ(xvap, XAT_NODUMP)) { + xoap->xoa_nodump = + ((zp->z_pflags & ZFS_NODUMP) != 0); + XVA_SET_RTN(xvap, XAT_NODUMP); + } + + if (XVA_ISSET_REQ(xvap, XAT_OPAQUE)) { + xoap->xoa_opaque = + ((zp->z_pflags & ZFS_OPAQUE) != 0); + XVA_SET_RTN(xvap, XAT_OPAQUE); + } + + if (XVA_ISSET_REQ(xvap, XAT_AV_QUARANTINED)) { + xoap->xoa_av_quarantined = + ((zp->z_pflags & ZFS_AV_QUARANTINED) != 0); + XVA_SET_RTN(xvap, XAT_AV_QUARANTINED); + } + + if (XVA_ISSET_REQ(xvap, XAT_AV_MODIFIED)) { + xoap->xoa_av_modified = + ((zp->z_pflags & ZFS_AV_MODIFIED) != 0); + XVA_SET_RTN(xvap, XAT_AV_MODIFIED); + } + + if (XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP) && + vnode_isreg(vp)) { + zfs_sa_get_scanstamp(zp, xvap); + } + if (XVA_ISSET_REQ(xvap, XAT_CREATETIME)) { + uint64_t times[2]; + + (void) sa_lookup(zp->z_sa_hdl, SA_ZPL_CRTIME(zfsvfs), + times, sizeof (times)); + ZFS_TIME_DECODE(&xoap->xoa_createtime, times); + XVA_SET_RTN(xvap, XAT_CREATETIME); + } + + if (XVA_ISSET_REQ(xvap, XAT_REPARSE)) { + xoap->xoa_reparse = ((zp->z_pflags & ZFS_REPARSE) != 0); + XVA_SET_RTN(xvap, XAT_REPARSE); + } + if (XVA_ISSET_REQ(xvap, XAT_GEN)) { + xoap->xoa_generation = zp->z_gen; + XVA_SET_RTN(xvap, XAT_GEN); + } + + if (XVA_ISSET_REQ(xvap, XAT_OFFLINE)) { + xoap->xoa_offline = + ((zp->z_pflags & ZFS_OFFLINE) != 0); + XVA_SET_RTN(xvap, XAT_OFFLINE); + } + + if (XVA_ISSET_REQ(xvap, XAT_SPARSE)) { + xoap->xoa_sparse = + ((zp->z_pflags & ZFS_SPARSE) != 0); + XVA_SET_RTN(xvap, XAT_SPARSE); + } + } + + ZFS_TIME_DECODE(&vap->va_atime, zp->z_atime); + ZFS_TIME_DECODE(&vap->va_mtime, mtime); + ZFS_TIME_DECODE(&vap->va_ctime, ctime); + ZFS_TIME_DECODE(&vap->va_crtime, crtime); + + mutex_exit(&zp->z_lock); + + /* + * If we are told to ignore owners, we scribble over the + * uid and gid here unless root. + */ + if (((unsigned int)vfs_flags(zfsvfs->z_vfs)) & MNT_IGNORE_OWNERSHIP) { + if (kauth_cred_getuid(cr) != 0) { + vap->va_uid = UNKNOWNUID; + vap->va_gid = UNKNOWNGID; + } + } + + zfs_exit(zfsvfs, FTAG); + return (0); +} + +/* + * Set the file attributes to the values contained in the + * vattr structure. + * + * IN: zp - znode of file to be modified. + * vap - new attribute values. + * If AT_XVATTR set, then optional attrs are being set + * flags - ATTR_UTIME set if non-default time values provided. + * - ATTR_NOACLCHECK (CIFS context only). + * cr - credentials of caller. + * ct - caller context + * + * RETURN: 0 on success, error code on failure. + * + * Timestamps: + * vp - ctime updated, mtime updated if size changed. + */ +int +zfs_setattr(znode_t *zp, vattr_t *vap, int flags, cred_t *cr, + zidmap_t *mnt_ns) +{ + vnode_t *vp = ZTOV(zp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + objset_t *os = zfsvfs->z_os; + zilog_t *zilog; + dmu_tx_t *tx; + vattr_t oldva; + xvattr_t tmpxvattr; + uint64_t mask = vap->va_mask; /* Upstream is 32 bit */ + uint64_t saved_mask = 0; + uint64_t saved_mode; + int trim_mask = 0; + uint64_t new_mode; + uint64_t new_uid, new_gid; + uint64_t xattr_obj; + uint64_t mtime[2], ctime[2], crtime[2]; + uint64_t projid = ZFS_INVALID_PROJID; + znode_t *attrzp; + int need_policy = FALSE; + int err, err2; + zfs_fuid_info_t *fuidp = NULL; + xvattr_t *xvap = (xvattr_t *)vap; /* vap may be an xvattr_t * */ + xoptattr_t *xoap; + zfs_acl_t *aclp; + boolean_t skipaclchk = (flags & ATTR_NOACLCHECK) ? B_TRUE : B_FALSE; + boolean_t fuid_dirtied = B_FALSE; + sa_bulk_attr_t bulk[7], xattr_bulk[7]; + int count = 0, xattr_count = 0; + int error; + + if (mask == 0) + return (0); + + if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) + return (error); + + zilog = zfsvfs->z_log; + + /* + * Make sure that if we have ephemeral uid/gid or xvattr specified + * that file system is at proper version level + */ + + if (zfsvfs->z_use_fuids == B_FALSE && + (((mask & ATTR_UID) && IS_EPHEMERAL(vap->va_uid)) || + ((mask & ATTR_GID) && IS_EPHEMERAL(vap->va_gid)) || + (mask & ATTR_XVATTR))) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EINVAL)); + } + + if (mask & ATTR_SIZE && vnode_vtype(vp) == VDIR) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EISDIR)); + } + + if (mask & ATTR_SIZE && vnode_vtype(vp) != VREG && + vnode_vtype(vp) != VFIFO) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EINVAL)); + } + + /* + * If this is an xvattr_t, then get a pointer to the structure of + * optional attributes. If this is NULL, then we have a vattr_t. + */ + xoap = xva_getxoptattr(xvap); + xva_init(&tmpxvattr); + + /* + * Immutable files can only alter immutable bit and atime + * Apple - we have to turn on ATTR_MODE, which would trip + * this, so we change to allow oldmode == newmode. + */ + if ((zp->z_pflags & ZFS_IMMUTABLE) && + ((mask & (ATTR_SIZE|ATTR_UID|ATTR_GID|ATTR_MTIME + /* |ATTR_MODE */)) || + (vap->va_mode != zp->z_mode) || + ((mask & ATTR_XVATTR) && XVA_ISSET_REQ(xvap, XAT_CREATETIME)))) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EPERM)); + } + + /* + * Note: ZFS_READONLY is handled in zfs_zaccess_common. + */ + + /* + * Verify timestamps doesn't overflow 32 bits. + * ZFS can handle large timestamps, but 32bit syscalls can't + * handle times greater than 2039. This check should be removed + * once large timestamps are fully supported. + * + * macOS: Everything is 64bit and if we return OVERFLOW it + * fails to handle O_EXCL correctly, as atime is used to store + * random unique id to verify creation or not. See Issue #104 + */ +#if 0 + if (mask & (ATTR_ATIME | ATTR_MTIME)) { + if (((mask & ATTR_ATIME) && + TIMESPEC_OVERFLOW(&vap->va_atime)) || + ((mask & ATTR_MTIME) && + TIMESPEC_OVERFLOW(&vap->va_mtime))) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EOVERFLOW)); + } + } +#endif + if (xoap != NULL && (mask & ATTR_XVATTR)) { + +#if 0 + if (XVA_ISSET_REQ(xvap, XAT_CREATETIME) && + TIMESPEC_OVERFLOW(&vap->va_create_time)) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EOVERFLOW)); + } +#endif + + if (XVA_ISSET_REQ(xvap, XAT_PROJID)) { + if (!dmu_objset_projectquota_enabled(os) || + (!S_ISREG(zp->z_mode) && !S_ISDIR(zp->z_mode))) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EOPNOTSUPP)); + } + + projid = xoap->xoa_projid; + if (unlikely(projid == ZFS_INVALID_PROJID)) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EINVAL)); + } + + if (projid == zp->z_projid && zp->z_pflags & ZFS_PROJID) + projid = ZFS_INVALID_PROJID; + else + need_policy = TRUE; + } + + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT) && + (xoap->xoa_projinherit != + ((zp->z_pflags & ZFS_PROJINHERIT) != 0)) && + (!dmu_objset_projectquota_enabled(os) || + (!S_ISREG(zp->z_mode) && !S_ISDIR(zp->z_mode)))) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EOPNOTSUPP)); + } + } + + attrzp = NULL; + aclp = NULL; + + if (zfs_is_readonly(zfsvfs)) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EROFS)); + } + + /* + * First validate permissions + */ + + if (mask & ATTR_SIZE) { + /* + * XXX - Note, we are not providing any open + * mode flags here (like FNDELAY), so we may + * block if there are locks present... this + * should be addressed in openat(). + */ + /* XXX - would it be OK to generate a log record here? */ + err = zfs_freesp(zp, vap->va_size, 0, 0, FALSE); + if (err) { + zfs_exit(zfsvfs, FTAG); + return (err); + } + } + + if (mask & (ATTR_ATIME|ATTR_MTIME) || + ((mask & ATTR_XVATTR) && (XVA_ISSET_REQ(xvap, XAT_HIDDEN) || + XVA_ISSET_REQ(xvap, XAT_READONLY) || + XVA_ISSET_REQ(xvap, XAT_ARCHIVE) || + XVA_ISSET_REQ(xvap, XAT_OFFLINE) || + XVA_ISSET_REQ(xvap, XAT_SPARSE) || + XVA_ISSET_REQ(xvap, XAT_CREATETIME) || + XVA_ISSET_REQ(xvap, XAT_SYSTEM)))) { + need_policy = zfs_zaccess(zp, ACE_WRITE_ATTRIBUTES, 0, + skipaclchk, cr, NULL); + } + + if (mask & (ATTR_UID|ATTR_GID)) { + int idmask = (mask & (ATTR_UID|ATTR_GID)); + int take_owner; + int take_group; + + /* + * NOTE: even if a new mode is being set, + * we may clear S_ISUID/S_ISGID bits. + */ + + if (!(mask & ATTR_MODE)) + vap->va_mode = zp->z_mode; + + /* + * Take ownership or chgrp to group we are a member of + */ + + take_owner = (mask & ATTR_UID) && (vap->va_uid == crgetuid(cr)); + take_group = (mask & ATTR_GID) && + zfs_groupmember(zfsvfs, vap->va_gid, cr); + + /* + * If both ATTR_UID and ATTR_GID are set then take_owner and + * take_group must both be set in order to allow taking + * ownership. + * + * Otherwise, send the check through secpolicy_vnode_setattr() + * + */ + + if (((idmask == (ATTR_UID|ATTR_GID)) && + take_owner && take_group) || + ((idmask == ATTR_UID) && take_owner) || + ((idmask == ATTR_GID) && take_group)) { + if (zfs_zaccess(zp, ACE_WRITE_OWNER, 0, + skipaclchk, cr, NULL) == 0) { + /* + * Remove setuid/setgid for non-privileged users + */ + secpolicy_setid_clear(vap, cr); + trim_mask = (mask & (ATTR_UID|ATTR_GID)); + } else { + need_policy = TRUE; + } + } else { + need_policy = TRUE; + } + } + + oldva.va_mode = zp->z_mode; + zfs_fuid_map_ids(zp, cr, &oldva.va_uid, &oldva.va_gid); + if (mask & ATTR_XVATTR) { + /* + * Update xvattr mask to include only those attributes + * that are actually changing. + * + * the bits will be restored prior to actually setting + * the attributes so the caller thinks they were set. + */ + if (XVA_ISSET_REQ(xvap, XAT_APPENDONLY)) { + if (xoap->xoa_appendonly != + ((zp->z_pflags & ZFS_APPENDONLY) != 0)) { + need_policy = TRUE; + } else { + XVA_CLR_REQ(xvap, XAT_APPENDONLY); + XVA_SET_REQ(&tmpxvattr, XAT_APPENDONLY); + } + } + + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) { + if (xoap->xoa_projinherit != + ((zp->z_pflags & ZFS_PROJINHERIT) != 0)) { + need_policy = TRUE; + } else { + XVA_CLR_REQ(xvap, XAT_PROJINHERIT); + XVA_SET_REQ(&tmpxvattr, XAT_PROJINHERIT); + } + } + + if (XVA_ISSET_REQ(xvap, XAT_NOUNLINK)) { + if (xoap->xoa_nounlink != + ((zp->z_pflags & ZFS_NOUNLINK) != 0)) { + need_policy = TRUE; + } else { + XVA_CLR_REQ(xvap, XAT_NOUNLINK); + XVA_SET_REQ(&tmpxvattr, XAT_NOUNLINK); + } + } + + if (XVA_ISSET_REQ(xvap, XAT_IMMUTABLE)) { + if (xoap->xoa_immutable != + ((zp->z_pflags & ZFS_IMMUTABLE) != 0)) { + need_policy = TRUE; + } else { + XVA_CLR_REQ(xvap, XAT_IMMUTABLE); + XVA_SET_REQ(&tmpxvattr, XAT_IMMUTABLE); + } + } + + if (XVA_ISSET_REQ(xvap, XAT_NODUMP)) { + if (xoap->xoa_nodump != + ((zp->z_pflags & ZFS_NODUMP) != 0)) { + need_policy = TRUE; + } else { + XVA_CLR_REQ(xvap, XAT_NODUMP); + XVA_SET_REQ(&tmpxvattr, XAT_NODUMP); + } + } + + if (XVA_ISSET_REQ(xvap, XAT_AV_MODIFIED)) { + if (xoap->xoa_av_modified != + ((zp->z_pflags & ZFS_AV_MODIFIED) != 0)) { + need_policy = TRUE; + } else { + XVA_CLR_REQ(xvap, XAT_AV_MODIFIED); + XVA_SET_REQ(&tmpxvattr, XAT_AV_MODIFIED); + } + } + + if (XVA_ISSET_REQ(xvap, XAT_AV_QUARANTINED)) { + if ((vnode_vtype(vp) != VREG && + xoap->xoa_av_quarantined) || + xoap->xoa_av_quarantined != + ((zp->z_pflags & ZFS_AV_QUARANTINED) != 0)) { + need_policy = TRUE; + } else { + XVA_CLR_REQ(xvap, XAT_AV_QUARANTINED); + XVA_SET_REQ(&tmpxvattr, XAT_AV_QUARANTINED); + } + } + + if (XVA_ISSET_REQ(xvap, XAT_REPARSE)) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EPERM)); + } + + if (need_policy == FALSE && + (XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP) || + XVA_ISSET_REQ(xvap, XAT_OPAQUE))) { + need_policy = TRUE; + } + } + + if (mask & ATTR_MODE) { + if (zfs_zaccess(zp, ACE_WRITE_ACL, 0, skipaclchk, cr, + NULL) == 0) { + err = secpolicy_setid_setsticky_clear(vp, vap, + &oldva, cr); + if (err) { + zfs_exit(zfsvfs, FTAG); + return (err); + } + trim_mask |= ATTR_MODE; + } else { + need_policy = TRUE; + } + } + + if (need_policy) { + /* + * If trim_mask is set then take ownership + * has been granted or write_acl is present and user + * has the ability to modify mode. In that case remove + * UID|GID and or MODE from mask so that + * secpolicy_vnode_setattr() doesn't revoke it. + */ + + if (trim_mask) { + saved_mask = vap->va_mask; + vap->va_mask &= ~trim_mask; + if (trim_mask & ATTR_MODE) { + /* + * Save the mode, as secpolicy_vnode_setattr() + * will overwrite it with ova.va_mode. + */ + saved_mode = vap->va_mode; + } + } + err = secpolicy_vnode_setattr(cr, vp, vap, &oldva, flags, + (int (*)(void *, int, cred_t *))zfs_zaccess_unix, zp); + if (err) { + zfs_exit(zfsvfs, FTAG); + return (err); + } + + if (trim_mask) { + vap->va_mask |= saved_mask; + if (trim_mask & ATTR_MODE) { + /* + * Recover the mode after + * secpolicy_vnode_setattr(). + */ + vap->va_mode = saved_mode; + } + } + } + + /* + * secpolicy_vnode_setattr, or take ownership may have + * changed va_mask + */ + mask = vap->va_mask; + + if ((mask & (ATTR_UID | ATTR_GID)) || projid != ZFS_INVALID_PROJID) { + err = sa_lookup(zp->z_sa_hdl, SA_ZPL_XATTR(zfsvfs), + &xattr_obj, sizeof (xattr_obj)); + + if (err == 0 && xattr_obj) { + err = zfs_zget(zp->z_zfsvfs, xattr_obj, &attrzp); + if (err) + goto out2; + } + if (mask & ATTR_UID) { + new_uid = zfs_fuid_create(zfsvfs, + (uint64_t)vap->va_uid, cr, ZFS_OWNER, &fuidp); + if (new_uid != zp->z_uid && + zfs_id_overquota(zfsvfs, DMU_USERUSED_OBJECT, + new_uid)) { + if (attrzp) + zrele(attrzp); + err = SET_ERROR(EDQUOT); + goto out2; + } + } + + if (mask & ATTR_GID) { + new_gid = zfs_fuid_create(zfsvfs, (uint64_t)vap->va_gid, + cr, ZFS_GROUP, &fuidp); + if (new_gid != zp->z_gid && + zfs_id_overquota(zfsvfs, DMU_GROUPUSED_OBJECT, + new_gid)) { + if (attrzp) + zrele(attrzp); + err = SET_ERROR(EDQUOT); + goto out2; + } + } + + if (projid != ZFS_INVALID_PROJID && + zfs_id_overquota(zfsvfs, DMU_PROJECTUSED_OBJECT, projid)) { + if (attrzp) + zrele(attrzp); + err = SET_ERROR(EDQUOT); + goto out2; + } + } + tx = dmu_tx_create(os); + + /* + * Translate XNU ACL to ZFS ACL + */ + if (VATTR_IS_ACTIVE(vap, va_acl)) { + vsecattr_t vsecattr; + int seen_type = 0; + int aclbsize; /* size of acl list in bytes */ + ace_t *aaclp; + struct kauth_acl *kauth; + + printf("special xnu \n"); + + if ((vap->va_acl != (kauth_acl_t)KAUTH_FILESEC_NONE) && + (vap->va_acl->acl_entrycount > 0) && + (vap->va_acl->acl_entrycount != KAUTH_FILESEC_NOACL)) { + + vsecattr.vsa_mask = VSA_ACE; + kauth = vap->va_acl; + +#if HIDE_TRIVIAL_ACL + /* + * We might have to add 3 trivial acls, + * depending on what was handed to us. + */ + aclbsize = (3 + kauth->acl_entrycount) * sizeof (ace_t); + dprintf("%d ACLs, adding 3\n", kauth->acl_entrycount); +#else + aclbsize = kauth->acl_entrycount * sizeof (ace_t); + dprintf("%d ACLs\n", kauth->acl_entrycount); +#endif + + vsecattr.vsa_aclentp = kmem_zalloc(aclbsize, KM_SLEEP); + aaclp = vsecattr.vsa_aclentp; + vsecattr.vsa_aclentsz = aclbsize; + +#if HIDE_TRIVIAL_ACL + /* + * Add in the trivials, keep "seen_type" as a bit + * pattern of which trivials we have seen + */ + seen_type = 0; + dprintf("aces_from_acl %d entries\n", + kauth->acl_entrycount); + aces_from_acl(vsecattr.vsa_aclentp, + &vsecattr.vsa_aclcnt, kauth, &seen_type); + + /* Add in trivials at end, based on the "seen_type". */ + zfs_addacl_trivial(zp, vsecattr.vsa_aclentp, + &vsecattr.vsa_aclcnt, seen_type); + dprintf("together at last: %d\n", vsecattr.vsa_aclcnt); +#else + aces_from_acl(vsecattr.vsa_aclentp, + &vsecattr.vsa_aclcnt, kauth); +#endif + error = zfs_setacl(zp, &vsecattr, B_TRUE, cr); + kmem_free(aaclp, aclbsize); + + } else { + + seen_type = 0; + vsecattr.vsa_mask = VSA_ACE; + vsecattr.vsa_aclcnt = 0; + aclbsize = (3) * sizeof (ace_t); + vsecattr.vsa_aclentp = kmem_zalloc(aclbsize, KM_SLEEP); + aaclp = vsecattr.vsa_aclentp; + vsecattr.vsa_aclentsz = aclbsize; + /* Clearing, we need to pass in the trivials only */ + zfs_addacl_trivial(zp, vsecattr.vsa_aclentp, + &vsecattr.vsa_aclcnt, seen_type); + + if ((error = zfs_setacl(zp, &vsecattr, B_TRUE, cr))) + dprintf("setattr: setacl failed: %d\n", error); + + kmem_free(aaclp, aclbsize); + + } /* blank ACL? */ + } /* VATTR_IS_ACTIVE(vap, va_acl) */ + + + if (mask & ATTR_MODE) { + uint64_t pmode = zp->z_mode; + uint64_t acl_obj; + + /* APPLE - strip flags if NOT acl */ + new_mode = pmode; + if (!VATTR_IS_ACTIVE(vap, va_acl)) { + new_mode = (pmode & S_IFMT) | (vap->va_mode & ~S_IFMT); + } + + if (zp->z_zfsvfs->z_acl_mode == ZFS_ACL_RESTRICTED && + !(zp->z_pflags & ZFS_ACL_TRIVIAL)) { + err = SET_ERROR(EPERM); + goto out; + } + + if ((err = zfs_acl_chmod_setattr(zp, &aclp, new_mode))) + goto out; + + if (!zp->z_is_sa && ((acl_obj = zfs_external_acl(zp)) != 0)) { + /* + * Are we upgrading ACL from old V0 format + * to V1 format? + */ + if (zfsvfs->z_version >= ZPL_VERSION_FUID && + zfs_znode_acl_version(zp) == + ZFS_ACL_VERSION_INITIAL) { + dmu_tx_hold_free(tx, acl_obj, 0, + DMU_OBJECT_END); + dmu_tx_hold_write(tx, DMU_NEW_OBJECT, + 0, aclp->z_acl_bytes); + } else { + dmu_tx_hold_write(tx, acl_obj, 0, + aclp->z_acl_bytes); + } + } else if (!zp->z_is_sa && aclp->z_acl_bytes > ZFS_ACE_SPACE) { + dmu_tx_hold_write(tx, DMU_NEW_OBJECT, + 0, aclp->z_acl_bytes); + } + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE); + } else { + if (((mask & ATTR_XVATTR) && + XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP)) || + (projid != ZFS_INVALID_PROJID && + !(zp->z_pflags & ZFS_PROJID))) + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE); + else + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); + } + + if (attrzp) { + dmu_tx_hold_sa(tx, attrzp->z_sa_hdl, B_FALSE); + } + + fuid_dirtied = zfsvfs->z_fuid_dirty; + if (fuid_dirtied) + zfs_fuid_txhold(zfsvfs, tx); + + zfs_sa_upgrade_txholds(tx, zp); + + err = dmu_tx_assign(tx, TXG_WAIT); + if (err) + goto out; + + count = 0; + /* + * Set each attribute requested. + * We group settings according to the locks they need to acquire. + * + * Note: you cannot set ctime directly, although it will be + * updated as a side-effect of calling this function. + */ + + if (projid != ZFS_INVALID_PROJID && !(zp->z_pflags & ZFS_PROJID)) { + /* + * For the existed object that is upgraded from old system, + * its on-disk layout has no slot for the project ID attribute. + * But quota accounting logic needs to access related slots by + * offset directly. So we need to adjust old objects' layout + * to make the project ID to some unified and fixed offset. + */ + if (attrzp) + err = sa_add_projid(attrzp->z_sa_hdl, tx, projid); + if (err == 0) + err = sa_add_projid(zp->z_sa_hdl, tx, projid); + + if (unlikely(err == EEXIST)) + err = 0; + else if (err != 0) + goto out; + else + projid = ZFS_INVALID_PROJID; + } + + if (mask & (ATTR_UID|ATTR_GID|ATTR_MODE)) + mutex_enter(&zp->z_acl_lock); + + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, + &zp->z_pflags, sizeof (zp->z_pflags)); + + if (attrzp) { + if (mask & (ATTR_UID|ATTR_GID|ATTR_MODE)) + mutex_enter(&attrzp->z_acl_lock); + SA_ADD_BULK_ATTR(xattr_bulk, xattr_count, + SA_ZPL_FLAGS(zfsvfs), NULL, &attrzp->z_pflags, + sizeof (attrzp->z_pflags)); + if (projid != ZFS_INVALID_PROJID) { + attrzp->z_projid = projid; + SA_ADD_BULK_ATTR(xattr_bulk, xattr_count, + SA_ZPL_PROJID(zfsvfs), NULL, &attrzp->z_projid, + sizeof (attrzp->z_projid)); + } + } + + if (mask & (ATTR_UID|ATTR_GID)) { + + if (mask & ATTR_UID) { + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_UID(zfsvfs), NULL, + &new_uid, sizeof (new_uid)); + zp->z_uid = new_uid; + if (attrzp) { + SA_ADD_BULK_ATTR(xattr_bulk, xattr_count, + SA_ZPL_UID(zfsvfs), NULL, &new_uid, + sizeof (new_uid)); + attrzp->z_uid = new_uid; + } + } + + if (mask & ATTR_GID) { + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GID(zfsvfs), + NULL, &new_gid, sizeof (new_gid)); + zp->z_gid = new_gid; + if (attrzp) { + SA_ADD_BULK_ATTR(xattr_bulk, xattr_count, + SA_ZPL_GID(zfsvfs), NULL, &new_gid, + sizeof (new_gid)); + attrzp->z_gid = new_gid; + } + } + if (!(mask & ATTR_MODE)) { + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MODE(zfsvfs), + NULL, &new_mode, sizeof (new_mode)); + new_mode = zp->z_mode; + } + err = zfs_acl_chown_setattr(zp); + ASSERT(err == 0); + if (attrzp) { + err = zfs_acl_chown_setattr(attrzp); + ASSERT(err == 0); + } + } + + if (mask & ATTR_MODE) { + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MODE(zfsvfs), NULL, + &new_mode, sizeof (new_mode)); + zp->z_mode = new_mode; + + /* + * Mode change needs to trigger corresponding update to + * trivial ACLs. ACL change already does this, and another + * call to zfs_aclset_common would overwrite our explicit + * ACL changes. APPLE + */ + if (!VATTR_IS_ACTIVE(vap, va_acl)) { + ASSERT3U((uintptr_t)aclp, !=, 0); + err = zfs_aclset_common(zp, aclp, cr, tx); + ASSERT0(err); + if (zp->z_acl_cached) + zfs_acl_free(zp->z_acl_cached); + zp->z_acl_cached = aclp; + aclp = NULL; + } + } + + if (mask & ATTR_ATIME) { + ZFS_TIME_ENCODE(&vap->va_atime, zp->z_atime); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_ATIME(zfsvfs), NULL, + &zp->z_atime, sizeof (zp->z_atime)); + } + + if (mask & ATTR_MTIME) { + ZFS_TIME_ENCODE(&vap->va_mtime, mtime); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, + mtime, sizeof (mtime)); + } + + if (mask & ATTR_CRTIME) { + ZFS_TIME_ENCODE(&vap->va_crtime, crtime); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CRTIME(zfsvfs), NULL, + crtime, sizeof (crtime)); + } + + if (projid != ZFS_INVALID_PROJID) { + zp->z_projid = projid; + SA_ADD_BULK_ATTR(bulk, count, + SA_ZPL_PROJID(zfsvfs), NULL, &zp->z_projid, + sizeof (zp->z_projid)); + } + + /* XXX - shouldn't this be done *before* the ATIME/MTIME checks? */ + if (mask & ATTR_SIZE && !(mask & ATTR_MTIME)) { + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), + NULL, mtime, sizeof (mtime)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, + &ctime, sizeof (ctime)); + zfs_tstamp_update_setup(zp, CONTENT_MODIFIED, mtime, ctime); + } else if (mask != 0) { + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, + &ctime, sizeof (ctime)); + zfs_tstamp_update_setup(zp, STATE_CHANGED, mtime, ctime); + if (attrzp) { + SA_ADD_BULK_ATTR(xattr_bulk, xattr_count, + SA_ZPL_CTIME(zfsvfs), NULL, + &ctime, sizeof (ctime)); + zfs_tstamp_update_setup(attrzp, STATE_CHANGED, + mtime, ctime); + } + } + + /* + * Do this after setting timestamps to prevent timestamp + * update from toggling bit + */ + + if (xoap && (mask & ATTR_XVATTR)) { + + if (XVA_ISSET_REQ(xvap, XAT_CREATETIME)) + xoap->xoa_createtime = vap->va_create_time; + /* + * restore trimmed off masks + * so that return masks can be set for caller. + */ + + if (XVA_ISSET_REQ(&tmpxvattr, XAT_APPENDONLY)) { + XVA_SET_REQ(xvap, XAT_APPENDONLY); + } + if (XVA_ISSET_REQ(&tmpxvattr, XAT_NOUNLINK)) { + XVA_SET_REQ(xvap, XAT_NOUNLINK); + } + if (XVA_ISSET_REQ(&tmpxvattr, XAT_IMMUTABLE)) { + XVA_SET_REQ(xvap, XAT_IMMUTABLE); + } + if (XVA_ISSET_REQ(&tmpxvattr, XAT_NODUMP)) { + XVA_SET_REQ(xvap, XAT_NODUMP); + } + if (XVA_ISSET_REQ(&tmpxvattr, XAT_AV_MODIFIED)) { + XVA_SET_REQ(xvap, XAT_AV_MODIFIED); + } + if (XVA_ISSET_REQ(&tmpxvattr, XAT_AV_QUARANTINED)) { + XVA_SET_REQ(xvap, XAT_AV_QUARANTINED); + } + if (XVA_ISSET_REQ(&tmpxvattr, XAT_PROJINHERIT)) { + XVA_SET_REQ(xvap, XAT_PROJINHERIT); + } + + if (XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP)) + ASSERT(vnode_isreg(vp)); + + zfs_xvattr_set(zp, xvap, tx); + } + + if (fuid_dirtied) + zfs_fuid_sync(zfsvfs, tx); + + if (mask != 0) + zfs_log_setattr(zilog, tx, TX_SETATTR, zp, vap, mask, fuidp); + + if (mask & (ATTR_UID|ATTR_GID|ATTR_MODE)) + mutex_exit(&zp->z_acl_lock); + + if (attrzp) { + if (mask & (ATTR_UID|ATTR_GID|ATTR_MODE)) + mutex_exit(&attrzp->z_acl_lock); + } +out: + if (err == 0 && attrzp) { + err2 = sa_bulk_update(attrzp->z_sa_hdl, xattr_bulk, + xattr_count, tx); + ASSERT(err2 == 0); + } + + if (attrzp) + zrele(attrzp); + + if (aclp) + zfs_acl_free(aclp); + + if (fuidp) { + zfs_fuid_info_free(fuidp); + fuidp = NULL; + } + + if (err) { + dmu_tx_abort(tx); + } else { + err2 = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); + dmu_tx_commit(tx); + } + +out2: + if (os->os_sync == ZFS_SYNC_ALWAYS) + zil_commit(zilog, 0); + + zfs_exit(zfsvfs, FTAG); + return (err); +} + +typedef struct zfs_zlock { + krwlock_t *zl_rwlock; /* lock we acquired */ + znode_t *zl_znode; /* znode we held */ + struct zfs_zlock *zl_next; /* next in list */ +} zfs_zlock_t; + +/* + * Drop locks and release vnodes that were held by zfs_rename_lock(). + */ +static void +zfs_rename_unlock(zfs_zlock_t **zlpp) +{ + zfs_zlock_t *zl; + + while ((zl = *zlpp) != NULL) { + if (zl->zl_znode != NULL) + zfs_zrele_async(zl->zl_znode); + rw_exit(zl->zl_rwlock); + *zlpp = zl->zl_next; + kmem_free(zl, sizeof (*zl)); + } +} + +/* + * Search back through the directory tree, using the ".." entries. + * Lock each directory in the chain to prevent concurrent renames. + * Fail any attempt to move a directory into one of its own descendants. + * XXX - z_parent_lock can overlap with map or grow locks + */ +static int +zfs_rename_lock(znode_t *szp, znode_t *tdzp, znode_t *sdzp, zfs_zlock_t **zlpp) +{ + zfs_zlock_t *zl; + znode_t *zp = tdzp; + uint64_t rootid = ZTOZSB(zp)->z_root; + uint64_t oidp = zp->z_id; + krwlock_t *rwlp = &szp->z_parent_lock; + krw_t rw = RW_WRITER; + + /* + * First pass write-locks szp and compares to zp->z_id. + * Later passes read-lock zp and compare to zp->z_parent. + */ + do { + if (!rw_tryenter(rwlp, rw)) { + /* + * Another thread is renaming in this path. + * Note that if we are a WRITER, we don't have any + * parent_locks held yet. + */ + if (rw == RW_READER && zp->z_id > szp->z_id) { + /* + * Drop our locks and restart + */ + zfs_rename_unlock(&zl); + *zlpp = NULL; + zp = tdzp; + oidp = zp->z_id; + rwlp = &szp->z_parent_lock; + rw = RW_WRITER; + continue; + } else { + /* + * Wait for other thread to drop its locks + */ + rw_enter(rwlp, rw); + } + } + + zl = kmem_alloc(sizeof (*zl), KM_SLEEP); + zl->zl_rwlock = rwlp; + zl->zl_znode = NULL; + zl->zl_next = *zlpp; + *zlpp = zl; + + if (oidp == szp->z_id) /* We're a descendant of szp */ + return (SET_ERROR(EINVAL)); + + if (oidp == rootid) /* We've hit the top */ + return (0); + + if (rw == RW_READER) { /* i.e. not the first pass */ + int error = zfs_zget(ZTOZSB(zp), oidp, &zp); + if (error) + return (error); + zl->zl_znode = zp; + } + (void) sa_lookup(zp->z_sa_hdl, SA_ZPL_PARENT(ZTOZSB(zp)), + &oidp, sizeof (oidp)); + rwlp = &zp->z_parent_lock; + rw = RW_READER; + + } while (zp->z_id != sdzp->z_id); + + return (0); +} + +/* + * Move an entry from the provided source directory to the target + * directory. Change the entry name as indicated. + * + * IN: sdzp - Source directory containing the "old entry". + * snm - Old entry name. + * tdzp - Target directory to contain the "new entry". + * tnm - New entry name. + * cr - credentials of caller. + * flags - case flags + * + * RETURN: 0 on success, error code on failure. + * + * Timestamps: + * sdzp,tdzp - ctime|mtime updated + */ +int +zfs_rename(znode_t *sdzp, char *snm, znode_t *tdzp, char *tnm, + cred_t *cr, int flags, uint64_t rflags, vattr_t *wo_vap, + zidmap_t *userns) +{ + znode_t *szp, *tzp; + zfsvfs_t *zfsvfs = ZTOZSB(sdzp); + zilog_t *zilog; + uint64_t addtime[2]; + zfs_dirlock_t *sdl, *tdl; + dmu_tx_t *tx; + zfs_zlock_t *zl; + int cmp, serr, terr; + int error = 0; + int zflg = 0; + boolean_t waited = B_FALSE; + + if (snm == NULL || tnm == NULL) + return (SET_ERROR(EINVAL)); + + if ((error = zfs_enter_verify_zp(zfsvfs, sdzp, FTAG)) != 0) + return (error); + zilog = zfsvfs->z_log; + + /* + * We check i_sb because snapshots and the ctldir must have different + * super blocks. + */ + // Can't we use zp->z_zfsvfs in place of zp->vp->v_vfs ? + if (VTOM(ZTOV(tdzp)) != VTOM(ZTOV(sdzp)) || + zfsctl_is_node(ZTOV(tdzp))) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EXDEV)); + } + + if (zfsvfs->z_utf8 && u8_validate(tnm, + strlen(tnm), NULL, U8_VALIDATE_ENTIRE, &error) < 0) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EILSEQ)); + } + + if (flags & FIGNORECASE) + zflg |= ZCILOOK; + +top: + szp = NULL; + tzp = NULL; + zl = NULL; + + /* + * This is to prevent the creation of links into attribute space + * by renaming a linked file into/outof an attribute directory. + * See the comment in zfs_link() for why this is considered bad. + */ + if ((tdzp->z_pflags & ZFS_XATTR) != (sdzp->z_pflags & ZFS_XATTR)) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EINVAL)); + } + + /* + * Lock source and target directory entries. To prevent deadlock, + * a lock ordering must be defined. We lock the directory with + * the smallest object id first, or if it's a tie, the one with + * the lexically first name. + */ + if (sdzp->z_id < tdzp->z_id) { + cmp = -1; + } else if (sdzp->z_id > tdzp->z_id) { + cmp = 1; + } else { + /* + * First compare the two name arguments without + * considering any case folding. + */ + int nofold = (zfsvfs->z_norm & ~U8_TEXTPREP_TOUPPER); + + cmp = u8_strcmp(snm, tnm, 0, nofold, U8_UNICODE_LATEST, &error); + ASSERT(error == 0 || !zfsvfs->z_utf8); + if (cmp == 0) { + /* + * POSIX: "If the old argument and the new argument + * both refer to links to the same existing file, + * the rename() function shall return successfully + * and perform no other action." + */ + zfs_exit(zfsvfs, FTAG); + return (0); + } + /* + * If the file system is case-folding, then we may + * have some more checking to do. A case-folding file + * system is either supporting mixed case sensitivity + * access or is completely case-insensitive. Note + * that the file system is always case preserving. + * + * In mixed sensitivity mode case sensitive behavior + * is the default. FIGNORECASE must be used to + * explicitly request case insensitive behavior. + * + * If the source and target names provided differ only + * by case (e.g., a request to rename 'tim' to 'Tim'), + * we will treat this as a special case in the + * case-insensitive mode: as long as the source name + * is an exact match, we will allow this to proceed as + * a name-change request. + */ + if ((zfsvfs->z_case == ZFS_CASE_INSENSITIVE || + (zfsvfs->z_case == ZFS_CASE_MIXED && + flags & FIGNORECASE)) && + u8_strcmp(snm, tnm, 0, zfsvfs->z_norm, U8_UNICODE_LATEST, + &error) == 0) { + /* + * case preserving rename request, require exact + * name matches + */ + zflg |= ZCIEXACT; + zflg &= ~ZCILOOK; + } + } + + /* + * If the source and destination directories are the same, we should + * grab the z_name_lock of that directory only once. + */ + if (sdzp == tdzp) { + zflg |= ZHAVELOCK; + rw_enter(&sdzp->z_name_lock, RW_READER); + } + + if (cmp < 0) { + serr = zfs_dirent_lock(&sdl, sdzp, snm, &szp, + ZEXISTS | zflg, NULL, NULL); + terr = zfs_dirent_lock(&tdl, + tdzp, tnm, &tzp, ZRENAMING | zflg, NULL, NULL); + } else { + terr = zfs_dirent_lock(&tdl, + tdzp, tnm, &tzp, zflg, NULL, NULL); + serr = zfs_dirent_lock(&sdl, + sdzp, snm, &szp, ZEXISTS | ZRENAMING | zflg, + NULL, NULL); + } + + if (serr) { + /* + * Source entry invalid or not there. + */ + if (!terr) { + zfs_dirent_unlock(tdl); + if (tzp) + zrele(tzp); + } + + if (sdzp == tdzp) + rw_exit(&sdzp->z_name_lock); + + if (strcmp(snm, ".") == 0 || strcmp(snm, "..") == 0) + serr = EINVAL; + zfs_exit(zfsvfs, FTAG); + return (serr); + } + if (terr) { + zfs_dirent_unlock(sdl); + zrele(szp); + + if (sdzp == tdzp) + rw_exit(&sdzp->z_name_lock); + + if (strcmp(tnm, "..") == 0) + terr = EINVAL; + zfs_exit(zfsvfs, FTAG); + return (terr); + } + + /* + * If we are using project inheritance, means if the directory has + * ZFS_PROJINHERIT set, then its descendant directories will inherit + * not only the project ID, but also the ZFS_PROJINHERIT flag. Under + * such case, we only allow renames into our tree when the project + * IDs are the same. + */ + if (tdzp->z_pflags & ZFS_PROJINHERIT && + tdzp->z_projid != szp->z_projid) { + error = SET_ERROR(EXDEV); + goto out; + } + + /* + * Must have write access at the source to remove the old entry + * and write access at the target to create the new entry. + * Note that if target and source are the same, this can be + * done in a single check. + */ + + if ((error = zfs_zaccess_rename(sdzp, szp, tdzp, tzp, cr, userns))) + goto out; + + if (S_ISDIR(szp->z_mode)) { + /* + * Check to make sure rename is valid. + * Can't do a move like this: /usr/a/b to /usr/a/b/c/d + */ + if ((error = zfs_rename_lock(szp, tdzp, sdzp, &zl))) + goto out; + } + + /* + * Does target exist? + */ + if (tzp) { + /* + * Source and target must be the same type. + */ + if (S_ISDIR(szp->z_mode)) { + if (!S_ISDIR(tzp->z_mode)) { + error = SET_ERROR(ENOTDIR); + goto out; + } + } else { + if (S_ISDIR(tzp->z_mode)) { + error = SET_ERROR(EISDIR); + goto out; + } + } + /* + * POSIX dictates that when the source and target + * entries refer to the same file object, rename + * must do nothing and exit without error. + */ + if (szp->z_id == tzp->z_id) { + error = 0; + goto out; + } + +#if defined(MAC_OS_X_VERSION_10_12) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) + /* If renamex(VFS_RENAME_EXCL) is used, error out */ + if (flags & VFS_RENAME_EXCL) { + error = EEXIST; + goto out; + } +#endif + + } + + tx = dmu_tx_create(zfsvfs->z_os); +#ifdef __APPLE__ + /* ADDTIME might grow SA */ + dmu_tx_hold_sa(tx, szp->z_sa_hdl, B_TRUE); +#else + dmu_tx_hold_sa(tx, szp->z_sa_hdl, B_FALSE); +#endif + dmu_tx_hold_sa(tx, sdzp->z_sa_hdl, B_FALSE); + dmu_tx_hold_zap(tx, sdzp->z_id, FALSE, snm); + dmu_tx_hold_zap(tx, tdzp->z_id, TRUE, tnm); + if (sdzp != tdzp) { + dmu_tx_hold_sa(tx, tdzp->z_sa_hdl, B_FALSE); + zfs_sa_upgrade_txholds(tx, tdzp); + } + if (tzp) { + dmu_tx_hold_sa(tx, tzp->z_sa_hdl, B_FALSE); + zfs_sa_upgrade_txholds(tx, tzp); + } + + zfs_sa_upgrade_txholds(tx, szp); + dmu_tx_hold_zap(tx, zfsvfs->z_unlinkedobj, FALSE, NULL); + error = dmu_tx_assign(tx, (waited ? TXG_NOTHROTTLE : 0) | TXG_NOWAIT); + if (error) { + if (zl != NULL) + zfs_rename_unlock(&zl); + zfs_dirent_unlock(sdl); + zfs_dirent_unlock(tdl); + + if (sdzp == tdzp) + rw_exit(&sdzp->z_name_lock); + + if (error == ERESTART) { + waited = B_TRUE; + dmu_tx_wait(tx); + dmu_tx_abort(tx); + zrele(szp); + if (tzp) + zrele(tzp); + goto top; + } + dmu_tx_abort(tx); + zrele(szp); + if (tzp) + zrele(tzp); + zfs_exit(zfsvfs, FTAG); + return (error); + } + + if (tzp) /* Attempt to remove the existing target */ + error = zfs_link_destroy(tdl, tzp, tx, zflg, NULL); + + if (error == 0) { + error = zfs_link_create(tdl, szp, tx, ZRENAMING); + if (error == 0) { + szp->z_pflags |= ZFS_AV_MODIFIED; + if (tdzp->z_pflags & ZFS_PROJINHERIT) + szp->z_pflags |= ZFS_PROJINHERIT; + + error = sa_update(szp->z_sa_hdl, SA_ZPL_FLAGS(zfsvfs), + (void *)&szp->z_pflags, sizeof (uint64_t), tx); + ASSERT0(error); + +#ifdef __APPLE__ + /* + * If we moved an entry into a different directory + * (sdzp != tdzp) then we also need to update ADDEDTIME + * (ADDTIME) property for FinderInfo. We are already + * inside error == 0 conditional + */ + if ((sdzp != tdzp) && + zfsvfs->z_use_sa == B_TRUE) { + timestruc_t now; + gethrestime(&now); + ZFS_TIME_ENCODE(&now, addtime); + error = sa_update(szp->z_sa_hdl, + SA_ZPL_ADDTIME(zfsvfs), (void *)&addtime, + sizeof (addtime), tx); + } +#endif + + error = zfs_link_destroy(sdl, szp, tx, ZRENAMING, NULL); + if (error == 0) { + zfs_log_rename(zilog, tx, TX_RENAME | + (flags & FIGNORECASE ? TX_CI : 0), sdzp, + sdl->dl_name, tdzp, tdl->dl_name, szp); + + } else { + /* + * At this point, we have successfully created + * the target name, but have failed to remove + * the source name. Since the create was done + * with the ZRENAMING flag, there are + * complications; for one, the link count is + * wrong. The easiest way to deal with this + * is to remove the newly created target, and + * return the original error. This must + * succeed; fortunately, it is very unlikely to + * fail, since we just created it. + */ + VERIFY3U(zfs_link_destroy(tdl, szp, tx, + ZRENAMING, NULL), ==, 0); + } + } else { + /* + * If we had removed the existing target, subsequent + * call to zfs_link_create() to add back the same entry + * but, the new dnode (szp) should not fail. + */ + ASSERT(tzp == NULL); + } + } + + if (error == 0) { + /* + * Update cached name - for vget, and access + * without calling vnop_lookup first - it is + * easier to clear it out and let getattr + * look it up if needed. + */ + if (tzp) { + mutex_enter(&tzp->z_lock); + tzp->z_name_cache[0] = 0; + mutex_exit(&tzp->z_lock); + } + if (szp) { + mutex_enter(&szp->z_lock); + szp->z_name_cache[0] = 0; + mutex_exit(&szp->z_lock); + } + } + + dmu_tx_commit(tx); +out: + if (zl != NULL) + zfs_rename_unlock(&zl); + + zfs_dirent_unlock(sdl); + zfs_dirent_unlock(tdl); + + if (sdzp == tdzp) + rw_exit(&sdzp->z_name_lock); + + zrele(szp); + if (tzp) { + zrele(tzp); + } + + if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS) + zil_commit(zilog, 0); + + zfs_exit(zfsvfs, FTAG); + return (error); +} + +/* + * Insert the indicated symbolic reference entry into the directory. + * + * IN: dzp - Directory to contain new symbolic link. + * name - Name of directory entry in dip. + * vap - Attributes of new entry. + * link - Name for new symlink entry. + * cr - credentials of caller. + * flags - case flags + * + * OUT: zpp - Znode for new symbolic link. + * + * RETURN: 0 on success, error code on failure. + * + * Timestamps: + * dip - ctime|mtime updated + */ +int +zfs_symlink(znode_t *dzp, char *name, vattr_t *vap, char *link, + znode_t **zpp, cred_t *cr, int flags, zidmap_t *mnt_ns) +{ + znode_t *zp; + zfs_dirlock_t *dl; + dmu_tx_t *tx; + zfsvfs_t *zfsvfs = ZTOZSB(dzp); + zilog_t *zilog; + uint64_t len = strlen(link); + int error; + int zflg = ZNEW; + zfs_acl_ids_t acl_ids; + boolean_t fuid_dirtied; + uint64_t txtype = TX_SYMLINK; + boolean_t waited = B_FALSE; + + ASSERT(S_ISLNK(vap->va_mode)); + + if (name == NULL) + return (SET_ERROR(EINVAL)); + + if ((error = zfs_enter_verify_zp(zfsvfs, dzp, FTAG)) != 0) + return (error); + zilog = zfsvfs->z_log; + + if (zfsvfs->z_utf8 && u8_validate(name, strlen(name), + NULL, U8_VALIDATE_ENTIRE, &error) < 0) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EILSEQ)); + } + if (flags & FIGNORECASE) + zflg |= ZCILOOK; + + if (len > MAXPATHLEN) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(ENAMETOOLONG)); + } + + if ((error = zfs_acl_ids_create(dzp, 0, + vap, cr, NULL, &acl_ids, mnt_ns)) != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } +top: + *zpp = NULL; + + /* + * Attempt to lock directory; fail if entry already exists. + */ + error = zfs_dirent_lock(&dl, dzp, name, &zp, zflg, NULL, NULL); + if (error) { + zfs_acl_ids_free(&acl_ids); + zfs_exit(zfsvfs, FTAG); + return (error); + } + + if ((error = zfs_zaccess(dzp, ACE_ADD_FILE, 0, B_FALSE, cr, mnt_ns))) { + zfs_acl_ids_free(&acl_ids); + zfs_dirent_unlock(dl); + zfs_exit(zfsvfs, FTAG); + return (error); + } + + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, ZFS_DEFAULT_PROJID)) { + zfs_acl_ids_free(&acl_ids); + zfs_dirent_unlock(dl); + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EDQUOT)); + } + tx = dmu_tx_create(zfsvfs->z_os); + fuid_dirtied = zfsvfs->z_fuid_dirty; + dmu_tx_hold_write(tx, DMU_NEW_OBJECT, 0, MAX(1, len)); + dmu_tx_hold_zap(tx, dzp->z_id, TRUE, name); + dmu_tx_hold_sa_create(tx, acl_ids.z_aclp->z_acl_bytes + + ZFS_SA_BASE_ATTR_SIZE + len); + dmu_tx_hold_sa(tx, dzp->z_sa_hdl, B_FALSE); + if (!zfsvfs->z_use_sa && acl_ids.z_aclp->z_acl_bytes > ZFS_ACE_SPACE) { + dmu_tx_hold_write(tx, DMU_NEW_OBJECT, 0, + acl_ids.z_aclp->z_acl_bytes); + } + if (fuid_dirtied) + zfs_fuid_txhold(zfsvfs, tx); + error = dmu_tx_assign(tx, (waited ? TXG_NOTHROTTLE : 0) | TXG_NOWAIT); + if (error) { + zfs_dirent_unlock(dl); + if (error == ERESTART) { + waited = B_TRUE; + dmu_tx_wait(tx); + dmu_tx_abort(tx); + goto top; + } + zfs_acl_ids_free(&acl_ids); + dmu_tx_abort(tx); + zfs_exit(zfsvfs, FTAG); + return (error); + } + + /* + * Create a new object for the symlink. + * for version 4 ZPL datsets the symlink will be an SA attribute + */ + zfs_mknode(dzp, vap, tx, cr, 0, &zp, &acl_ids); + + if (fuid_dirtied) + zfs_fuid_sync(zfsvfs, tx); + + mutex_enter(&zp->z_lock); + if (zp->z_is_sa) + error = sa_update(zp->z_sa_hdl, SA_ZPL_SYMLINK(zfsvfs), + link, len, tx); + else + zfs_sa_symlink(zp, link, len, tx); + mutex_exit(&zp->z_lock); + + zp->z_size = len; + (void) sa_update(zp->z_sa_hdl, SA_ZPL_SIZE(zfsvfs), + &zp->z_size, sizeof (zp->z_size), tx); + /* + * Insert the new object into the directory. + */ + error = zfs_link_create(dl, zp, tx, ZNEW); + if (error != 0) { + zfs_znode_delete(zp, tx); + } else { + if (flags & FIGNORECASE) + txtype |= TX_CI; + zfs_log_symlink(zilog, tx, txtype, dzp, zp, name, link); + } + + zfs_acl_ids_free(&acl_ids); + + dmu_tx_commit(tx); + + zfs_dirent_unlock(dl); + + /* + * OS X - attach the vnode _after_ committing the transaction + */ + zfs_znode_getvnode(zp, zfsvfs); + + if (error == 0) { + *zpp = zp; + + if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS) + zil_commit(zilog, 0); + } else { + zrele(zp); + } + + zfs_exit(zfsvfs, FTAG); + return (error); +} + +/* + * Return, in the buffer contained in the provided uio structure, + * the symbolic path referred to by ip. + * + * IN: ip - inode of symbolic link + * uio - structure to contain the link path. + * cr - credentials of caller. + * + * RETURN: 0 if success + * error code if failure + * + * Timestamps: + * ip - atime updated + */ +int +zfs_readlink(struct vnode *vp, zfs_uio_t *uio, cred_t *cr) +{ + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = ITOZSB(vp); + int error; + + if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) + return (error); + + mutex_enter(&zp->z_lock); + if (zp->z_is_sa) + error = sa_lookup_uio(zp->z_sa_hdl, + SA_ZPL_SYMLINK(zfsvfs), uio); + else + error = zfs_sa_readlink(zp, uio); + mutex_exit(&zp->z_lock); + + zfs_exit(zfsvfs, FTAG); + return (error); +} + +/* + * Insert a new entry into directory tdzp referencing szp. + * + * IN: tdzp - Directory to contain new entry. + * szp - znode of new entry. + * name - name of new entry. + * cr - credentials of caller. + * flags - case flags. + * + * RETURN: 0 if success + * error code if failure + * + * Timestamps: + * tdzp - ctime|mtime updated + * szp - ctime updated + */ +int +zfs_link(znode_t *tdzp, znode_t *szp, char *name, cred_t *cr, + int flags) +{ + struct vnode *svp = ZTOV(szp); + znode_t *tzp; + zfsvfs_t *zfsvfs = ZTOZSB(tdzp); + zilog_t *zilog; + zfs_dirlock_t *dl; + dmu_tx_t *tx; + int error; + int zf = ZNEW; + uint64_t parent; + uid_t owner; + boolean_t waited = B_FALSE; + boolean_t is_tmpfile = 0; + + ASSERT(S_ISDIR(tdzp->z_mode)); + + if (name == NULL) + return (SET_ERROR(EINVAL)); + + if ((error = zfs_enter_verify_zp(zfsvfs, tdzp, FTAG)) != 0) + return (error); + + zilog = zfsvfs->z_log; + +#ifdef __APPLE__ + if (VTOM(svp) != VTOM(ZTOV(tdzp))) { + zfs_exit(zfsvfs, FTAG); + return (EXDEV); + } +#endif + + /* + * POSIX dictates that we return EPERM here. + * Better choices include ENOTSUP or EISDIR. + */ + if (vnode_isdir(svp)) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EPERM)); + } + + if ((error = zfs_verify_zp(szp)) != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + + /* + * If we are using project inheritance, means if the directory has + * ZFS_PROJINHERIT set, then its descendant directories will inherit + * not only the project ID, but also the ZFS_PROJINHERIT flag. Under + * such case, we only allow hard link creation in our tree when the + * project IDs are the same. + */ + if (tdzp->z_pflags & ZFS_PROJINHERIT && + tdzp->z_projid != szp->z_projid) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EXDEV)); + } + + /* Prevent links to .zfs/shares files */ + + if ((error = sa_lookup(szp->z_sa_hdl, SA_ZPL_PARENT(zfsvfs), + &parent, sizeof (uint64_t))) != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + if (parent == zfsvfs->z_shares_dir) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EPERM)); + } + + if (zfsvfs->z_utf8 && u8_validate(name, + strlen(name), NULL, U8_VALIDATE_ENTIRE, &error) < 0) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EILSEQ)); + } + if (flags & FIGNORECASE) + zf |= ZCILOOK; + + /* + * We do not support links between attributes and non-attributes + * because of the potential security risk of creating links + * into "normal" file space in order to circumvent restrictions + * imposed in attribute space. + */ + if ((szp->z_pflags & ZFS_XATTR) != (tdzp->z_pflags & ZFS_XATTR)) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EINVAL)); + } + + owner = zfs_fuid_map_id(zfsvfs, KUID_TO_SUID(szp->z_uid), + cr, ZFS_OWNER); + if (owner != crgetuid(cr) && secpolicy_basic_link(cr) != 0) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EPERM)); + } + + if ((error = zfs_zaccess(tdzp, ACE_ADD_FILE, 0, B_FALSE, cr, NULL))) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + +top: + /* + * Attempt to lock directory; fail if entry already exists. + */ + error = zfs_dirent_lock(&dl, tdzp, name, &tzp, zf, NULL, NULL); + if (error) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_sa(tx, szp->z_sa_hdl, B_FALSE); + dmu_tx_hold_zap(tx, tdzp->z_id, TRUE, name); + if (is_tmpfile) + dmu_tx_hold_zap(tx, zfsvfs->z_unlinkedobj, FALSE, NULL); + + zfs_sa_upgrade_txholds(tx, szp); + zfs_sa_upgrade_txholds(tx, tdzp); + error = dmu_tx_assign(tx, (waited ? TXG_NOTHROTTLE : 0) | TXG_NOWAIT); + if (error) { + zfs_dirent_unlock(dl); + if (error == ERESTART) { + waited = B_TRUE; + dmu_tx_wait(tx); + dmu_tx_abort(tx); + goto top; + } + dmu_tx_abort(tx); + zfs_exit(zfsvfs, FTAG); + return (error); + } + + error = zfs_link_create(dl, szp, tx, 0); + + if (error == 0) { + uint64_t txtype = TX_LINK; + if (flags & FIGNORECASE) + txtype |= TX_CI; + zfs_log_link(zilog, tx, txtype, tdzp, szp, name); + } else if (is_tmpfile) { + /* restore z_unlinked since when linking failed */ + szp->z_unlinked = B_TRUE; + } + + dmu_tx_commit(tx); + + zfs_dirent_unlock(dl); + + if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS) + zil_commit(zilog, 0); + + zfs_exit(zfsvfs, FTAG); + return (error); +} + +void +zfs_inactive(struct vnode *vp) +{ + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = ITOZSB(vp); + int error; + + rw_enter(&zfsvfs->z_teardown_inactive_lock, RW_READER); + if (zp->z_sa_hdl == NULL) { + /* + * The fs has been unmounted, or we did a + * suspend/resume and this file no longer exists. + */ + rw_exit(&zfsvfs->z_teardown_inactive_lock); + vnode_recycle(vp); + return; + } + + if (zp->z_unlinked) { + /* + * Fast path to recycle a vnode of a removed file. + */ + rw_exit(&zfsvfs->z_teardown_inactive_lock); + vnode_recycle(vp); + return; + } + + if (zp->z_atime_dirty && zp->z_unlinked == 0) { + dmu_tx_t *tx = dmu_tx_create(zfsvfs->z_os); + + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); + zfs_sa_upgrade_txholds(tx, zp); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + } else { + (void) sa_update(zp->z_sa_hdl, SA_ZPL_ATIME(zfsvfs), + (void *)&zp->z_atime, sizeof (zp->z_atime), tx); + zp->z_atime_dirty = 0; + dmu_tx_commit(tx); + } + } + rw_exit(&zfsvfs->z_teardown_inactive_lock); +} + +/* + * Free or allocate space in a file. Currently, this function only + * supports the `F_FREESP' command. However, this command is somewhat + * misnamed, as its functionality includes the ability to allocate as + * well as free space. + * + * IN: zp - znode of file to free data in. + * cmd - action to take (only F_FREESP supported). + * bfp - section of file to free/alloc. + * flag - current file open mode flags. + * offset - current file offset. + * cr - credentials of caller. + * + * RETURN: 0 on success, error code on failure. + * + * Timestamps: + * zp - ctime|mtime updated + */ +int +zfs_space(znode_t *zp, int cmd, flock64_t *bfp, int flag, + offset_t offset, cred_t *cr) +{ + zfsvfs_t *zfsvfs = ZTOZSB(zp); + uint64_t off, len; + int error; + + if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) + return (error); + + if (cmd != F_FREESP) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EINVAL)); + } + + /* + * Callers might not be able to detect properly that we are read-only, + * so check it explicitly here. + */ + if (zfs_is_readonly(zfsvfs)) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EROFS)); + } + + if (bfp->l_len < 0) { + zfs_exit(zfsvfs, FTAG); + return (SET_ERROR(EINVAL)); + } + + /* + * Permissions aren't checked on Solaris because on this OS + * zfs_space() can only be called with an opened file handle. + * On Linux we can get here through truncate_range() which + * operates directly on inodes, so we need to check access rights. + */ + if ((error = zfs_zaccess(zp, ACE_WRITE_DATA, 0, B_FALSE, cr, NULL))) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + + off = bfp->l_start; + len = bfp->l_len; /* 0 means from off to end of file */ + + error = zfs_freesp(zp, off, len, flag, TRUE); + + zfs_exit(zfsvfs, FTAG); + return (error); +} diff --git a/module/os/macos/zfs/zfs_vnops_osx.c b/module/os/macos/zfs/zfs_vnops_osx.c new file mode 100644 index 000000000000..3f2ca8156123 --- /dev/null +++ b/module/os/macos/zfs/zfs_vnops_osx.c @@ -0,0 +1,5250 @@ +/* + * 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 Will Andrews + * Copyright (c) 2013, 2020 Jorgen Lundman + */ + +/* + * XXX GENERAL COMPATIBILITY ISSUES + * + * 'name' is a common argument, but in OS X (and FreeBSD), we need to pass + * the componentname pointer, so other things can use them. We should + * change the 'name' argument to be an opaque name pointer, and define + * OS-dependent macros that yield the desired results when needed. + * + * On OS X, VFS performs access checks before calling anything, so + * zfs_zaccess_* calls are not used. Not true on FreeBSD, though. Perhaps + * those calls should be conditionally #if 0'd? + * + * On OS X, VFS & I/O objects are often opaque, e.g. uio_t and struct vnode + * require using functions to access elements of an object. Should convert + * the Solaris code to use macros on other platforms. + * + * OS X and FreeBSD appear to use similar zfs-vfs interfaces; see Apple's + * comment in zfs_remove() about the fact that VFS holds the last ref while + * in Solaris it's the ZFS code that does. On FreeBSD, the code Apple + * refers to here results in a panic if the branch is actually taken. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + + + +#ifdef _KERNEL +#include +#include +unsigned int zfs_vnop_ignore_negatives = 0; +unsigned int zfs_vnop_ignore_positives = 0; +unsigned int zfs_vnop_create_negatives = 1; +unsigned int zfs_disable_spotlight = 0; +unsigned int zfs_disable_trashes = 0; +#endif + +#define DECLARE_CRED(ap) \ + cred_t *cr = (cred_t *)vfs_context_ucred((ap)->a_context) +#define DECLARE_CONTEXT(ap) \ + caller_context_t *ct = (caller_context_t *)(ap)->a_context +#define DECLARE_CRED_AND_CONTEXT(ap) \ + DECLARE_CRED(ap); \ + DECLARE_CONTEXT(ap) + +/* Empty FinderInfo struct */ +static u_int32_t emptyfinfo[8] = {0}; + +/* vnop_lookup needs path buffer each time */ +static kmem_cache_t *vnop_lookup_cache = NULL; + +/* + * zfs vfs operations. + */ +static struct vfsops zfs_vfsops_template = { + zfs_vfs_mount, + zfs_vfs_start, + zfs_vfs_unmount, + zfs_vfs_root, + zfs_vfs_quotactl, + zfs_vfs_getattr, + zfs_vfs_sync, + zfs_vfs_vget, + zfs_vfs_fhtovp, + zfs_vfs_vptofh, + zfs_vfs_init, + zfs_vfs_sysctl, + zfs_vfs_setattr, +#if defined(MAC_OS_X_VERSION_10_12) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) + NULL, /* vfs_ioctl */ + NULL, /* vfs_vget_snapdir */ + NULL +#else + {NULL} +#endif +}; + +#define ZFS_VNOP_TBL_CNT 6 + +static struct vnodeopv_desc *zfs_vnodeop_opv_desc_list[ZFS_VNOP_TBL_CNT] = +{ + &zfs_dvnodeop_opv_desc, + &zfs_fvnodeop_opv_desc, + &zfs_symvnodeop_opv_desc, + &zfs_xdvnodeop_opv_desc, + &zfs_fifonodeop_opv_desc, + &zfs_ctldir_opv_desc, +}; + +static vfstable_t zfs_vfsconf; + +int +zfs_vnop_removexattr_int(zfsvfs_t *zfsvfs, znode_t *zp, const char *name, + cred_t *cr); + +int +zfs_vfs_init(__unused struct vfsconf *vfsp) +{ + return (0); +} + +int +zfs_vfs_start(__unused struct mount *mp, __unused int flags, + __unused vfs_context_t context) +{ + return (0); +} + +int +zfs_vfs_quotactl(__unused struct mount *mp, __unused int cmds, + __unused uid_t uid, __unused caddr_t datap, __unused vfs_context_t context) +{ + dprintf("%s ENOTSUP\n", __func__); + return (ENOTSUP); +} + +static kmutex_t zfs_findernotify_lock; +static kcondvar_t zfs_findernotify_thread_cv; +static boolean_t zfs_findernotify_thread_exit; + +#define VNODE_EVENT_ATTRIB 0x00000008 + +static int +zfs_findernotify_callback(mount_t mp, __unused void *arg) +{ + vfs_context_t kernelctx = NULL; + struct vnode *rootvp, *vp; + znode_t *zp = NULL; + + /* + * Since potentially other filesystems could be using "our" + * fssubtype, and we don't always announce as "zfs" due to + * hfs-mimic requirements, we have to make extra care here to + * make sure this "mp" really is ZFS. + */ + zfsvfs_t *zfsvfs; + + zfsvfs = vfs_fsprivate(mp); + + /* As set in vfs_fsadd() below */ + char tname[MFSNAMELEN] = { 0 }; + vfs_name(mp, tname); + if (strncmp(tname, "zfs", MFSNAMELEN) != 0) + return (SET_ERROR(VFS_RETURNED)); + + /* + * The first entry in struct zfsvfs is the vfs ptr, so they + * should be equal if it is ZFS + */ + if (!zfsvfs || + (mp != zfsvfs->z_vfs)) + return (SET_ERROR(VFS_RETURNED)); + + /* Guard against unmount */ + if (zfs_enter(zfsvfs, FTAG) != 0) + return (SET_ERROR(VFS_RETURNED)); + + /* Check if space usage has changed enough to bother updating */ + uint64_t refdbytes, availbytes, usedobjs, availobjs; + uint64_t delta; + dmu_objset_space(zfsvfs->z_os, + &refdbytes, &availbytes, &usedobjs, &availobjs); + if (availbytes >= zfsvfs->z_findernotify_space) { + delta = availbytes - zfsvfs->z_findernotify_space; + } else { + delta = zfsvfs->z_findernotify_space - availbytes; + } + +#define ZFS_FINDERNOTIFY_THRESHOLD (1ULL<<20) +#define ZFS_FINDERNOTIFY_REFRESH 1ULL + + boolean_t refresh = B_FALSE; + + if (zfsvfs->z_findernotify_space == ZFS_FINDERNOTIFY_REFRESH) + refresh = B_TRUE; + + /* Under the limit ? (and not a refresh) */ + if (!refresh && delta <= ZFS_FINDERNOTIFY_THRESHOLD) goto out; + + /* Over threashold, so we will notify finder, remember value */ + zfsvfs->z_findernotify_space = availbytes; + + /* If old value is zero (first run), don't bother */ + if (availbytes == delta) + goto out; + + dprintf("ZFS: findernotify %p space delta %llu %s\n", mp, delta, + refresh ? "(forced refresh)" : ""); + + // Grab the root zp + if (zfs_zget(zfsvfs, zfsvfs->z_root, &zp) == 0) { + + rootvp = ZTOV(zp); + + struct componentname cn; + char *tmpname; + + /* To refresh finder space values, use fsevents, otherwise . */ + if (refresh) + tmpname = "."; + else + tmpname = ".fseventsd"; + + kernelctx = vfs_context_create((vfs_context_t)0); + + memset(&cn, 0, sizeof (cn)); + cn.cn_nameiop = LOOKUP; + cn.cn_flags = ISLASTCN; + // cn.cn_context = kernelctx; + cn.cn_pnbuf = tmpname; + cn.cn_pnlen = sizeof (tmpname); + cn.cn_nameptr = cn.cn_pnbuf; + cn.cn_namelen = strlen(tmpname); + + // Attempt to lookup .Trashes + if (!VOP_LOOKUP(rootvp, &vp, &cn, kernelctx)) { + + // Send the event to wake up Finder + struct vnode_attr vattr; + // Also calls VATTR_INIT + spl_vfs_get_notify_attributes(&vattr); + // Fill in vap + vnode_getattr(vp, &vattr, kernelctx); + // Send event + spl_vnode_notify(vp, VNODE_EVENT_ATTRIB, + &vattr); + // Cleanup vp + vnode_put(vp); + + } // VNOP_LOOKUP + + // Cleanup rootvp + vnode_put(rootvp); + + (void) vfs_context_rele(kernelctx); + + } // VFS_ROOT + +out: + zfs_exit(zfsvfs, FTAG); + + return (SET_ERROR(VFS_RETURNED)); +} + +static void +zfs_findernotify_thread(void *notused) +{ + callb_cpr_t cpr; + + dprintf("ZFS: findernotify thread start\n"); + CALLB_CPR_INIT(&cpr, &zfs_findernotify_lock, callb_generic_cpr, FTAG); + + mutex_enter(&zfs_findernotify_lock); + while (!zfs_findernotify_thread_exit) { + + /* Sleep 32 seconds */ + CALLB_CPR_SAFE_BEGIN(&cpr); + (void) cv_timedwait(&zfs_findernotify_thread_cv, + &zfs_findernotify_lock, ddi_get_lbolt() + (hz<<5)); + CALLB_CPR_SAFE_END(&cpr, &zfs_findernotify_lock); + + if (!zfs_findernotify_thread_exit) + vfs_iterate(LK_NOWAIT, zfs_findernotify_callback, NULL); + + } + + zfs_findernotify_thread_exit = FALSE; + cv_broadcast(&zfs_findernotify_thread_cv); + CALLB_CPR_EXIT(&cpr); /* drops arc_reclaim_lock */ + dprintf("ZFS: findernotify thread exit\n"); + thread_exit(); +} + + +/* Uses the thread as not to block in vfs_iterate() */ +void +zfs_findernotify_refresh(struct mount *mp) +{ + zfsvfs_t *zfsvfs = vfs_fsprivate(mp); + + dprintf("%s\n", __func__); + + /* Make sure it thinks delta needs updating */ + zfsvfs->z_findernotify_space = ZFS_FINDERNOTIFY_REFRESH; + + /* Wake up thread */ + cv_broadcast(&zfs_findernotify_thread_cv); + +} + +void +zfs_start_notify_thread(void) +{ + mutex_init(&zfs_findernotify_lock, NULL, MUTEX_DEFAULT, NULL); + cv_init(&zfs_findernotify_thread_cv, NULL, CV_DEFAULT, NULL); + zfs_findernotify_thread_exit = FALSE; + (void) thread_create(NULL, 0, zfs_findernotify_thread, NULL, 0, &p0, + TS_RUN, minclsyspri); +} + + +void +zfs_stop_notify_thread(void) +{ + mutex_enter(&zfs_findernotify_lock); + zfs_findernotify_thread_exit = TRUE; + /* + * The reclaim thread will set arc_reclaim_thread_exit back to + * FALSE when it is finished exiting; we're waiting for that. + */ + while (zfs_findernotify_thread_exit) { + cv_signal(&zfs_findernotify_thread_cv); + cv_wait(&zfs_findernotify_thread_cv, &zfs_findernotify_lock); + } + mutex_exit(&zfs_findernotify_lock); + mutex_destroy(&zfs_findernotify_lock); + cv_destroy(&zfs_findernotify_thread_cv); +} + +int +zfs_vfs_sysctl(int *name, __unused uint_t namelen, user_addr_t oldp, + size_t *oldlenp, user_addr_t newp, size_t newlen, + __unused vfs_context_t context) +{ +#if 0 + int error; + switch (name[0]) { + case ZFS_SYSCTL_FOOTPRINT: { + zfs_footprint_stats_t *footprint; + size_t copyinsize; + size_t copyoutsize; + int max_caches; + int act_caches; + + if (newp) { + return (EINVAL); + } + if (!oldp) { + *oldlenp = sizeof (zfs_footprint_stats_t); + return (0); + } + copyinsize = *oldlenp; + if (copyinsize < sizeof (zfs_footprint_stats_t)) { + *oldlenp = sizeof (zfs_footprint_stats_t); + return (ENOMEM); + } + footprint = kmem_zalloc(copyinsize, KM_SLEEP); + + max_caches = copyinsize - sizeof (zfs_footprint_stats_t); + max_caches += sizeof (kmem_cache_stats_t); + max_caches /= sizeof (kmem_cache_stats_t); + + footprint->version = ZFS_FOOTPRINT_VERSION; + + footprint->memory_stats.current = zfs_footprint.current; + footprint->memory_stats.target = zfs_footprint.target; + footprint->memory_stats.highest = zfs_footprint.highest; + footprint->memory_stats.maximum = zfs_footprint.maximum; + + arc_get_stats(&footprint->arc_stats); + + kmem_cache_stats(&footprint->cache_stats[0], max_caches, + &act_caches); + footprint->caches_count = act_caches; + footprint->thread_count = zfs_threads; + + copyoutsize = sizeof (zfs_footprint_stats_t) + + ((act_caches - 1) * sizeof (kmem_cache_stats_t)); + + error = ddi_copyout(footprint, oldp, copyoutsize, 0); + + kmem_free(footprint, copyinsize); + + return (error); + } + + case ZFS_SYSCTL_CONFIG_DEBUGMSG: + error = sysctl_int(oldp, oldlenp, newp, newlen, + &zfs_msg_buf_enabled); + return (error); + + case ZFS_SYSCTL_CONFIG_zdprintf: +#ifdef ZFS_DEBUG + error = sysctl_int(oldp, oldlenp, newp, newlen, + &zfs_zdprintf_enabled); +#else + error = ENOTSUP; +#endif + return (error); + } +#endif + return (ENOTSUP); +} + +/* + * All these functions could be declared as 'static' but to assist with + * dtrace debugging, we do not. + */ + +int +zfs_vnop_open(struct vnop_open_args *ap) +#if 0 + struct vnop_open_args { + struct vnode *a_vp; + int a_mode; + vfs_context_t a_context; + }; +#endif +{ + DECLARE_CRED(ap); + int err = 0; + + err = zfs_open(ap->a_vp, ap->a_mode, 0, cr); + + if (err) dprintf("zfs_open() failed %d\n", err); + return (err); +} + +int +zfs_vnop_close(struct vnop_close_args *ap) +#if 0 + struct vnop_close_args { + struct vnode *a_vp; + int a_fflag; + vfs_context_t a_context; + }; +#endif +{ +// int count = 1; +// int offset = 0; +// DECLARE_CRED_AND_CONTEXT(ap); + DECLARE_CRED(ap); + + return (zfs_close(ap->a_vp, ap->a_fflag, cr)); +} + +int +zfs_vnop_ioctl(struct vnop_ioctl_args *ap) +#if 0 + struct vnop_ioctl_args { + struct vnode *a_vp; + ulong_t a_command; + caddr_t a_data; + int a_fflag; + kauth_cred_t a_cred; + struct proc *a_p; + }; +#endif +{ + /* OS X has no use for zfs_ioctl(). */ + znode_t *zp = VTOZ(ap->a_vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + int error = 0; + DECLARE_CRED_AND_CONTEXT(ap); + + dprintf("vnop_ioctl %08lx: VTYPE %d\n", ap->a_command, + vnode_vtype(ZTOV(zp))); + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + if (IFTOVT((mode_t)zp->z_mode) == VFIFO) { + dprintf("ZFS: FIFO ioctl %02lx ('%lu' + %lu)\n", + ap->a_command, (ap->a_command&0xff00)>>8, + ap->a_command&0xff); + error = fifo_ioctl(ap); + error = 0; + zfs_exit(zfsvfs, FTAG); + goto out; + } + + if ((IFTOVT((mode_t)zp->z_mode) == VBLK) || + (IFTOVT((mode_t)zp->z_mode) == VCHR)) { + dprintf("ZFS: spec ioctl %02lx ('%lu' + %lu)\n", + ap->a_command, (ap->a_command&0xff00)>>8, + ap->a_command&0xff); + error = spec_ioctl(ap); + zfs_exit(zfsvfs, FTAG); + goto out; + } + zfs_exit(zfsvfs, FTAG); + + switch (ap->a_command) { + + /* ioctl supported by ZFS and POSIX */ + + case F_FULLFSYNC: + dprintf("%s F_FULLFSYNC\n", __func__); +#ifdef F_BARRIERFSYNC + case F_BARRIERFSYNC: + dprintf("%s F_BARRIERFSYNC\n", __func__); +#endif + error = zfs_fsync(VTOZ(ap->a_vp), /* flag */0, cr); + break; + + case F_CHKCLEAN: + dprintf("%s F_CHKCLEAN\n", __func__); + /* + * normally calls http://fxr.watson.org/fxr/source/bsd/ + * vfs/vfs_cluster.c?v=xnu-2050.18.24#L5839 + */ + /* XXX Why don't we? */ + off_t fsize = zp->z_size; + error = is_file_clean(ap->a_vp, fsize); + break; + + case F_RDADVISE: + dprintf("%s F_RDADVISE\n", __func__); + uint64_t file_size; + struct radvisory *ra; + int len; + + ra = (struct radvisory *)(ap->a_data); + + file_size = zp->z_size; + len = ra->ra_count; + + /* XXX Check request size */ + if (ra->ra_offset > file_size) { + dprintf("invalid request offset\n"); + error = EFBIG; + break; + } + + if ((ra->ra_offset + len) > file_size) { + len = file_size - ra->ra_offset; + dprintf("%s truncating F_RDADVISE from" + " %08x -> %08x\n", __func__, + ra->ra_count, len); + } + + /* + * Rather than advisory_read (which calls + * cluster_io->VNOP_BLOCKMAP), prefetch + * the level 0 metadata and level 1 data + * at the requested offset + length. + */ + // error = advisory_read(ap->a_vp, file_size, + // ra->ra_offset, len); + dmu_prefetch(zfsvfs->z_os, zp->z_id, + 0, 0, 0, ZIO_PRIORITY_SYNC_READ); + dmu_prefetch(zfsvfs->z_os, zp->z_id, + 1, ra->ra_offset, len, + ZIO_PRIORITY_SYNC_READ); +#if 0 + { + const char *name = vnode_getname(ap->a_vp); + printf("%s F_RDADVISE: prefetch issued for " + "[%s](0x%016llx) (0x%016llx 0x%08x)\n", __func__, + (name ? name : ""), zp->z_id, + ra->ra_offset, len); + if (name) vnode_putname(name); + } +#endif + + break; + + case SPOTLIGHT_GET_MOUNT_TIME: + case SPOTLIGHT_IOC_GET_MOUNT_TIME: + case SPOTLIGHT_FSCTL_GET_MOUNT_TIME: + dprintf("%s SPOTLIGHT_GET_MOUNT_TIME\n", __func__); + *(uint32_t *)ap->a_data = zfsvfs->z_mount_time; + break; + case SPOTLIGHT_GET_UNMOUNT_TIME: + dprintf("%s SPOTLIGHT_GET_UNMOUNT_TIME\n", __func__); + *(uint32_t *)ap->a_data = zfsvfs->z_last_unmount_time; + break; + case SPOTLIGHT_FSCTL_GET_LAST_MTIME: + case SPOTLIGHT_IOC_GET_LAST_MTIME: + dprintf("%s SPOTLIGHT_FSCTL_GET_LAST_MTIME\n", + __func__); + *(uint32_t *)ap->a_data = zfsvfs->z_last_unmount_time; + break; + + case HFS_SET_ALWAYS_ZEROFILL: + dprintf("%s HFS_SET_ALWAYS_ZEROFILL\n", __func__); + /* Required by Spotlight search */ + break; + case HFS_EXT_BULKACCESS_FSCTL: + dprintf("%s HFS_EXT_BULKACCESS_FSCTL\n", __func__); + /* Required by Spotlight search */ + break; + +#ifdef FSIOC_FIOSEEKHOLE + case FSIOC_FIOSEEKHOLE: + case FSCTL_FIOSEEKHOLE: + { + loff_t off; + off = *(loff_t *)ap->a_data; + /* offset parameter is in/out */ + error = zfs_holey(zp, SEEK_HOLE, &off); + if (error) + break; + *(loff_t *)ap->a_data = off; + break; + } +#endif + +#ifdef FSIOC_FIOSEEKDATA + case FSIOC_FIOSEEKDATA: + case FSCTL_FIOSEEKDATA: + { + loff_t off; + off = *(loff_t *)ap->a_data; + /* offset parameter is in/out */ + error = zfs_holey(zp, SEEK_DATA, &off); + if (error) + break; + *(loff_t *)ap->a_data = off; + break; + } +#endif + + + /* ioctl required to simulate HFS mimic behavior */ + case 0x80005802: + dprintf("%s 0x80005802 unknown\n", __func__); + /* unknown - from subsystem read, 'X', 2 */ + break; + + case HFS_GETPATH: + case HFSIOC_GETPATH: + dprintf("%s HFS_GETPATH\n", __func__); + { + struct vfsstatfs *vfsp; + struct vnode *file_vp; + ino64_t cnid; + int outlen; + char *bufptr; + int flags = 0; + + /* Caller must be owner of file system. */ + vfsp = vfs_statfs(zfsvfs->z_vfs); + if (proc_suser(current_proc()) && + kauth_cred_getuid((kauth_cred_t)cr) != + vfsp->f_owner) { + error = EACCES; + goto out; + } + /* Target vnode must be file system's root. */ + if (!vnode_isvroot(ap->a_vp)) { + error = EINVAL; + goto out; + } + + /* We are passed a string containing inode # */ + bufptr = (char *)ap->a_data; + cnid = strtoul(bufptr, NULL, 10); + if (ap->a_fflag & HFS_GETPATH_VOLUME_RELATIVE) { + flags |= BUILDPATH_VOLUME_RELATIVE; + } + + if ((error = zfs_vfs_vget(zfsvfs->z_vfs, cnid, + &file_vp, (vfs_context_t)ct))) { + goto out; + } + + error = spl_build_path(file_vp, bufptr, + MAXPATHLEN, &outlen, flags, + (vfs_context_t)ct); + + vnode_put(file_vp); + + dprintf("ZFS: HFS_GETPATH done %d : '%s'\n", + error, error ? "" : bufptr); + } + break; + + case HFS_TRANSFER_DOCUMENT_ID: + case HFSIOC_TRANSFER_DOCUMENT_ID: + dprintf("%s HFS_TRANSFER_DOCUMENT_ID\n", __func__); + { + u_int32_t to_fd = *(u_int32_t *)ap->a_data; + file_t *to_fp; + struct vnode *to_vp; + znode_t *to_zp; + + to_fp = getf(to_fd); + if (to_fp == NULL) { + error = EBADF; + goto out; + } + + to_vp = getf_vnode(to_fp); + + if ((error = vnode_getwithref(to_vp))) { + releasef(to_fd); + goto out; + } + + /* Confirm it is inside our mount */ + if (((zfsvfs_t *)vfs_fsprivate( + vnode_mount(to_vp))) != zfsvfs) { + error = EXDEV; + goto transfer_out; + } + + to_zp = VTOZ(to_vp); + + /* Source should have UF_TRACKED */ + if (!(zp->z_pflags & ZFS_TRACKED)) { + dprintf("ZFS: source is not TRACKED\n"); + error = EINVAL; + /* dest should NOT have UF_TRACKED */ + } else if (to_zp->z_pflags & ZFS_TRACKED) { + dprintf("ZFS: dest already TRACKED\n"); + error = EEXIST; + /* should be valid types */ + } else if ( + (IFTOVT((mode_t)zp->z_mode) == VDIR) || + (IFTOVT((mode_t)zp->z_mode) == VREG) || + (IFTOVT((mode_t)zp->z_mode) == VLNK)) { + /* + * Make sure source has a document id + * - although it can't + */ + if (!zp->z_document_id) + zfs_setattr_generate_id(zp, 0, + NULL); + + /* transfer over */ + to_zp->z_document_id = + zp->z_document_id; + zp->z_document_id = 0; + to_zp->z_pflags |= ZFS_TRACKED; + zp->z_pflags &= ~ZFS_TRACKED; + + /* Commit to disk */ + zfs_setattr_set_documentid(to_zp, + B_TRUE); + zfs_setattr_set_documentid(zp, + B_TRUE); /* also update flags */ + dprintf("ZFS: Moved docid %u from " + "id %llu to id %llu\n", + to_zp->z_document_id, zp->z_id, + to_zp->z_id); + } +transfer_out: + vnode_put(to_vp); + releasef(to_fd); + } + break; + + + case F_MAKECOMPRESSED: + dprintf("%s F_MAKECOMPRESSED\n", __func__); + /* + * Not entirely sure what this does, but HFS comments + * include: "Make the file compressed; truncate & + * toggle BSD bits" + * makes compressed copy of allocated blocks + * shortens file to new length + * sets BSD bits to indicate per-file compression + * + * On HFS, locks cnode and compresses its data. ZFS + * inband compression makes this obsolete. + */ + if (vfs_isrdonly(zfsvfs->z_vfs) || + !spa_writeable(dmu_objset_spa(zfsvfs->z_os))) { + error = EROFS; + goto out; + } + + /* Are there any other usecounts/FDs? */ + if (vnode_isinuse(ap->a_vp, 1)) { + error = EBUSY; + goto out; + } + + if (zp->z_pflags & ZFS_IMMUTABLE) { + error = EINVAL; + goto out; + } + + /* Return failure */ + error = EINVAL; + break; + + case HFS_PREV_LINK: + case HFS_NEXT_LINK: + case HFSIOC_PREV_LINK: + case HFSIOC_NEXT_LINK: + dprintf("%s HFS_PREV/NEXT_LINK\n", __func__); + { + /* + * Find sibling linkids with hardlinks. a_data points + * to the "current" linkid, and look up either prev + * or next (a_command) linkid. Return in a_data. + */ + uint32_t linkfileid; + struct vfsstatfs *vfsp; + /* Caller must be owner of file system. */ + vfsp = vfs_statfs(zfsvfs->z_vfs); + if ((kauth_cred_getuid(cr) == 0) && + kauth_cred_getuid(cr) != vfsp->f_owner) { + error = EACCES; + goto out; + } + /* Target vnode must be file system's root. */ + if (!vnode_isvroot(ap->a_vp)) { + error = EINVAL; + goto out; + } + linkfileid = *(uint32_t *)ap->a_data; + if (linkfileid < 16) { /* kHFSFirstUserCatalogNodeID */ + error = EINVAL; + goto out; + } + + /* + * Attempt to find the linkid in the hardlink_link + * AVL tree. If found, call to get prev or next. + */ + hardlinks_t *searchnode, *findnode, *sibling; + avl_index_t loc; + + searchnode = kmem_zalloc(sizeof (hardlinks_t), + KM_SLEEP); + searchnode->hl_linkid = linkfileid; + + rw_enter(&zfsvfs->z_hardlinks_lock, RW_READER); + findnode = avl_find(&zfsvfs->z_hardlinks_linkid, + searchnode, &loc); + kmem_free(searchnode, sizeof (hardlinks_t)); + + if (!findnode) { + rw_exit(&zfsvfs->z_hardlinks_lock); + *(uint32_t *)ap->a_data = 0; + dprintf("ZFS: HFS_NEXT_LINK/HFS_PREV_LINK %u " + "not found\n", linkfileid); + goto out; + } + + if (ap->a_command != HFS_NEXT_LINK) { + + while ((sibling = + AVL_NEXT(&zfsvfs->z_hardlinks_linkid, + findnode)) != NULL) { + if (findnode->hl_fileid == + sibling->hl_fileid) + break; + } + + } else { + + while ((sibling = + AVL_PREV(&zfsvfs->z_hardlinks_linkid, + findnode)) != NULL) { + if (findnode->hl_fileid == + sibling->hl_fileid) + break; + } + + } + rw_exit(&zfsvfs->z_hardlinks_lock); + + dprintf("ZFS: HFS_%s_LINK %u sibling %u\n", + (ap->a_command != HFS_NEXT_LINK) ? "NEXT" : "PREV", + linkfileid, + sibling ? sibling->hl_linkid : 0); + + // Did we get a new node? + if (sibling == NULL) { + *(uint32_t *)ap->a_data = 0; + goto out; + } + + *(uint32_t *)ap->a_data = sibling->hl_linkid; + error = 0; + } + break; + + case HFS_RESIZE_PROGRESS: + case HFSIOC_RESIZE_PROGRESS: + dprintf("%s HFS_RESIZE_PROGRESS\n", __func__); + /* fail as if requested of non-root fs */ + error = EINVAL; + break; + + case HFS_RESIZE_VOLUME: + case HFSIOC_RESIZE_VOLUME: + dprintf("%s HFS_RESIZE_VOLUME\n", __func__); + /* fail as if requested of non-root fs */ + error = EINVAL; + break; + + case HFS_CHANGE_NEXT_ALLOCATION: + case HFSIOC_CHANGE_NEXT_ALLOCATION: + dprintf("%s HFS_CHANGE_NEXT_ALLOCATION\n", __func__); + /* fail as if requested of non-root fs */ + error = EINVAL; + break; + + case HFS_CHANGE_NEXTCNID: + case HFSIOC_CHANGE_NEXTCNID: + dprintf("%s HFS_CHANGE_NEXTCNID\n", __func__); + /* FIXME : fail as though read only */ + error = EROFS; + break; + + case F_FREEZE_FS: + dprintf("%s F_FREEZE_FS\n", __func__); + /* Dont support freeze */ + error = ENOTSUP; + break; + + case F_THAW_FS: + dprintf("%s F_THAW_FS\n", __func__); + /* dont support fail as though insufficient privilege */ + error = EACCES; + break; + + case HFS_BULKACCESS_FSCTL: + case HFSIOC_BULKACCESS: + dprintf("%s HFS_BULKACCESS_FSCTL\n", __func__); + /* Respond as if HFS_STANDARD flag is set */ + error = EINVAL; + break; + + case HFS_FSCTL_GET_VERY_LOW_DISK: + case HFSIOC_GET_VERY_LOW_DISK: + dprintf("%s HFS_FSCTL_GET_VERY_LOW_DISK\n", __func__); + *(uint32_t *)ap->a_data = + zfsvfs->z_freespace_notify_dangerlimit; + break; + + case HFS_FSCTL_SET_VERY_LOW_DISK: + case HFSIOC_SET_VERY_LOW_DISK: + dprintf("%s HFS_FSCTL_SET_VERY_LOW_DISK\n", __func__); + if (*(uint32_t *)ap->a_data >= + zfsvfs->z_freespace_notify_warninglimit) { + error = EINVAL; + } else { + zfsvfs->z_freespace_notify_dangerlimit = + *(uint32_t *)ap->a_data; + } + break; + + case HFS_FSCTL_GET_LOW_DISK: + case HFSIOC_GET_LOW_DISK: + dprintf("%s HFS_FSCTL_GET_LOW_DISK\n", __func__); + *(uint32_t *)ap->a_data = + zfsvfs->z_freespace_notify_warninglimit; + break; + + case HFS_FSCTL_SET_LOW_DISK: + case HFSIOC_SET_LOW_DISK: + dprintf("%s HFS_FSCTL_SET_LOW_DISK\n", __func__); + if (*(uint32_t *)ap->a_data >= + zfsvfs->z_freespace_notify_desiredlevel || + *(uint32_t *)ap->a_data <= + zfsvfs->z_freespace_notify_dangerlimit) { + error = EINVAL; + } else { + zfsvfs->z_freespace_notify_warninglimit = + *(uint32_t *)ap->a_data; + } + break; + + case HFS_FSCTL_GET_DESIRED_DISK: + case HFSIOC_GET_DESIRED_DISK: + dprintf("%s HFS_FSCTL_GET_DESIRED_DISK\n", __func__); + *(uint32_t *)ap->a_data = + zfsvfs->z_freespace_notify_desiredlevel; + break; + + case HFS_FSCTL_SET_DESIRED_DISK: + case HFSIOC_SET_DESIRED_DISK: + dprintf("%s HFS_FSCTL_SET_DESIRED_DISK\n", __func__); + if (*(uint32_t *)ap->a_data <= + zfsvfs->z_freespace_notify_warninglimit) { + error = EINVAL; + } else { + zfsvfs->z_freespace_notify_desiredlevel = + *(uint32_t *)ap->a_data; + } + break; + + case HFS_VOLUME_STATUS: + case HFSIOC_VOLUME_STATUS: + dprintf("%s HFS_VOLUME_STATUS\n", __func__); + /* For now we always reply "all ok" */ + *(uint32_t *)ap->a_data = + zfsvfs->z_notification_conditions; + break; + + case HFS_SET_BOOT_INFO: + dprintf("%s HFS_SET_BOOT_INFO\n", __func__); + /* + * ZFS booting is not supported, mimic selection + * of a non-root HFS volume + */ + *(uint32_t *)ap->a_data = 0; + error = EINVAL; + break; + case HFS_GET_BOOT_INFO: + { + u_int32_t vcbFndrInfo[8]; + dprintf("%s HFS_GET_BOOT_INFO\n", __func__); + /* + * ZFS booting is not supported, mimic selection + * of a non-root HFS volume + */ + memset(vcbFndrInfo, 0, sizeof (vcbFndrInfo)); + struct vfsstatfs *vfsstatfs; + vfsstatfs = vfs_statfs(zfsvfs->z_vfs); + vcbFndrInfo[6] = vfsstatfs->f_fsid.val[0]; + vcbFndrInfo[7] = vfsstatfs->f_fsid.val[1]; + memcpy(ap->a_data, vcbFndrInfo, + sizeof (vcbFndrInfo)); + } + break; + case HFS_MARK_BOOT_CORRUPT: + dprintf("%s HFS_MARK_BOOT_CORRUPT\n", __func__); + /* + * ZFS booting is not supported, mimic selection + * of a non-root HFS volume + */ + *(uint32_t *)ap->a_data = 0; + error = EINVAL; + break; + + case HFS_FSCTL_GET_JOURNAL_INFO: + case HFSIOC_GET_JOURNAL_INFO: + dprintf("%s HFS_FSCTL_GET_JOURNAL_INFO\n", __func__); + /* + * XXX We're setting the mount as 'Journaled' + * so this might conflict + * Respond as though journal is empty/disabled + */ + { + struct hfs_journal_info *jip; + jip = (struct hfs_journal_info *)ap->a_data; + jip->jstart = 0; + jip->jsize = 0; + } + break; + + case HFS_DISABLE_METAZONE: + dprintf("%s HFS_DISABLE_METAZONE\n", __func__); + /* fail as though insufficient privs */ + error = EACCES; + break; + +#ifdef HFS_GET_FSINFO + case HFS_GET_FSINFO: + case HFSIOC_GET_FSINFO: + dprintf("%s HFS_GET_FSINFO\n", __func__); + break; +#endif + +#ifdef HFS_REPIN_HOTFILE_STATE + case HFS_REPIN_HOTFILE_STATE: + case HFSIOC_REPIN_HOTFILE_STATE: + dprintf("%s HFS_REPIN_HOTFILE_STATE\n", __func__); + break; +#endif + +#ifdef HFS_SET_HOTFILE_STATE + case HFS_SET_HOTFILE_STATE: + case HFSIOC_SET_HOTFILE_STATE: + dprintf("%s HFS_SET_HOTFILE_STATE\n", __func__); + break; +#endif + +#ifdef APFSIOC_GET_NEAR_LOW_DISK + case APFSIOC_GET_NEAR_LOW_DISK: + dprintf("%s APFSIOC_GET_NEAR_LOW_DISK\n", __func__); + *(uint32_t *)ap->a_data = + zfsvfs->z_freespace_notify_warninglimit; + break; +#endif + +#ifdef APFSIOC_SET_NEAR_LOW_DISK + case APFSIOC_SET_NEAR_LOW_DISK: + dprintf("%s APFSIOC_SET_NEAR_LOW_DISK\n", __func__); + if (*(uint32_t *)ap->a_data >= + zfsvfs->z_freespace_notify_desiredlevel || + *(uint32_t *)ap->a_data <= + zfsvfs->z_freespace_notify_dangerlimit) { + error = EINVAL; + } else { + zfsvfs->z_freespace_notify_warninglimit = + *(uint32_t *)ap->a_data; + } + break; +#endif + + /* End HFS mimic ioctl */ + + default: + dprintf("%s: Unknown ioctl %02lx ('%lu' + %lu)\n", + __func__, ap->a_command, (ap->a_command&0xff00)>>8, + ap->a_command&0xff); + error = ENOTTY; + } + +out: + if (error) { + dprintf("%s: failing ioctl: %02lx ('%lu' + %lu) returned %d\n", + __func__, ap->a_command, (ap->a_command&0xff00)>>8, + ap->a_command&0xff, error); + } + + return (error); +} + + +int +zfs_vnop_read(struct vnop_read_args *ap) +#if 0 + struct vnop_read_args { + struct vnode *a_vp; + struct uio *a_uio; + int a_ioflag; + vfs_context_t a_context; + }; +#endif +{ + int ioflag = zfs_ioflags(ap->a_ioflag); + int error; + /* uint64_t resid; */ +// DECLARE_CRED_AND_CONTEXT(ap); + DECLARE_CRED(ap); + ZFS_UIO_INIT_XNU(uio, ap->a_uio); + + /* resid = uio_resid(ap->a_uio); */ + error = zfs_read(VTOZ(ap->a_vp), uio, ioflag, cr); + + if (error) dprintf("vnop_read %d\n", error); + return (error); +} + +int +zfs_vnop_write(struct vnop_write_args *ap) +#if 0 + struct vnop_write_args { + struct vnode *a_vp; + struct uio *a_uio; + int a_ioflag; + vfs_context_t a_context; + }; +#endif +{ + int ioflag = zfs_ioflags(ap->a_ioflag); + int error; + DECLARE_CRED(ap); + ZFS_UIO_INIT_XNU(uio, ap->a_uio); + + // dprintf("zfs_vnop_write(vp %p, offset 0x%llx size 0x%llx\n", + // ap->a_vp, uio_offset(ap->a_uio), uio_resid(ap->a_uio)); + + error = zfs_write(VTOZ(ap->a_vp), uio, ioflag, cr); + + /* + * Mac OS X: pageout requires that the UBC file size be current. + * Possibly, we could update it only if size has changed. + */ + + /* if (tx_bytes != 0) { */ + if (!error) { + ubc_setsize(ap->a_vp, VTOZ(ap->a_vp)->z_size); + } else { + dprintf("%s error %d\n", __func__, error); + } + + return (error); +} + +int +zfs_vnop_access(struct vnop_access_args *ap) +#if 0 + struct vnop_access_args { + struct vnodeop_desc *a_desc; + struct vnode a_vp; + int a_action; + vfs_context_t a_context; + }; +#endif +{ + int error = ENOTSUP; + int action = ap->a_action; + int mode = 0; + DECLARE_CRED(ap); + + /* + * KAUTH_VNODE_READ_EXTATTRIBUTES, as well? + * KAUTH_VNODE_WRITE_EXTATTRIBUTES + */ + if (action & KAUTH_VNODE_READ_DATA) + mode |= VREAD; + if (action & KAUTH_VNODE_WRITE_DATA) + mode |= VWRITE; + if (action & KAUTH_VNODE_EXECUTE) + mode |= VEXEC; + + dprintf("vnop_access: action %04x -> mode %04x\n", action, mode); + error = zfs_access(VTOZ(ap->a_vp), mode, 0, cr); + + if (error) dprintf("%s: error %d\n", __func__, error); + return (error); +} + + +/* + * hard link references? + * Read the comment in zfs_getattr_znode_unlocked for the reason + * for this hackery. Since getattr(VA_NAME) is extremely common + * call in OSX, we opt to always save the name. We need to be careful + * as zfs_dirlook can return ctldir node as well (".zfs"). + * Hardlinks also need to be able to return the correct parentid. + */ +static void zfs_cache_name(struct vnode *vp, struct vnode *dvp, char *filename) +{ + znode_t *zp; + + if (vp == NULL) + return; + + // Only cache files, or we might end up caching "." + if (!vnode_isreg(vp)) + return; + + zp = VTOZ(vp); + + // If hardlink, remember the parentid. + if (zp != NULL) { + if (((zp->z_links > 1) || (zp->z_finder_hardlink)) && + (IFTOVT((mode_t)zp->z_mode) == VREG) && dvp) { + zp->z_finder_parentid = VTOZ(dvp)->z_id; + } + } + + if (!filename || + !filename[0] || + zfsctl_is_node(vp) || + !VTOZ(vp)) + return; + + mutex_enter(&zp->z_lock); + + strlcpy(zp->z_name_cache, filename, + MAXPATHLEN); + + mutex_exit(&zp->z_lock); +} + + +int +zfs_vnop_lookup(struct vnop_lookup_args *ap) +#if 0 + struct vnop_lookup_args { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + vfs_context_t a_context; + }; +#endif +{ + struct componentname *cnp = ap->a_cnp; + DECLARE_CRED(ap); + int error; + int negative_cache = 0; + znode_t *zp = NULL; + int direntflags = 0; + char *filename = NULL; + int filename_num_bytes = 0; + + *ap->a_vpp = NULL; /* In case we return an error */ + + /* + * Darwin uses namelen as an optimisation, for example it can be + * set to 5 for the string "alpha/beta" to look up "alpha". In this + * case we need to copy it out to null-terminate. + * Since cn2 below needs it to be separate to given cnp, we always + * allocate it. + */ + // filename_num_bytes = cnp->cn_namelen + 1; + // filename = (char *)kmem_alloc(filename_num_bytes, KM_SLEEP); + filename_num_bytes = MAXPATHLEN; + filename = kmem_cache_alloc(vnop_lookup_cache, KM_SLEEP); + memcpy(filename, cnp->cn_nameptr, cnp->cn_namelen); + filename[cnp->cn_namelen] = '\0'; + +#if 1 + /* + * cache_lookup() returns 0 for no-entry + * -1 for cache found (a_vpp set) + * ENOENT for negative cache + */ + error = cache_lookup(ap->a_dvp, ap->a_vpp, cnp); + if (error) { + /* We found a cache entry, positive or negative. */ + if (error == -1) { /* Positive entry? */ + if (!zfs_vnop_ignore_positives) { + error = 0; + goto exit; /* Positive cache, return it */ + } + /* Release iocount held by cache_lookup */ + vnode_put(*ap->a_vpp); + } + /* Negatives are only followed if not CREATE, from HFS+. */ + + if (cnp->cn_nameiop != CREATE) { + if (!zfs_vnop_ignore_negatives) { + goto exit; /* Negative cache hit */ + } + negative_cache = 1; + } + } +#endif + + dprintf("+vnop_lookup '%s' %s\n", filename, + negative_cache ? "negative_cache":""); + + /* + * 'cnp' passed to us is 'readonly' as XNU does not expect a return + * name, but most likely expects it correct in getattr. + */ + struct componentname cn2; + cn2.cn_nameptr = filename; + cn2.cn_namelen = cnp->cn_namelen; + cn2.cn_pnlen = filename_num_bytes; + cn2.cn_nameiop = cnp->cn_nameiop; + cn2.cn_flags = cnp->cn_flags; + + error = zfs_lookup(VTOZ(ap->a_dvp), filename, &zp, /* flags */ 0, cr, + &direntflags, &cn2); + + /* flags can be LOOKUP_XATTR | FIGNORECASE */ + if (error == 0) + *ap->a_vpp = ZTOV(zp); + else if (error == ENOTSUP) // formD return for not enough space + error = ENAMETOOLONG; + +#if 1 + /* + * It appears that VFS layer adds negative cache entries for us, so + * we do not need to add them here, or they are duplicated. + */ + if (!negative_cache) { + if ((error == ENOENT) && zfs_vnop_create_negatives) { + if ((ap->a_cnp->cn_nameiop == CREATE || + ap->a_cnp->cn_nameiop == RENAME) && + (cnp->cn_flags & ISLASTCN)) { + error = EJUSTRETURN; + goto exit; + } + /* Insert name into cache (non-existent) */ + if ((cnp->cn_flags & MAKEENTRY) && + ap->a_cnp->cn_nameiop != CREATE) { + cache_enter(ap->a_dvp, NULL, ap->a_cnp); + dprintf("Negative-cache made for '%s'\n", + filename); + } + } /* ENOENT */ + } +#endif + +exit: + + // If cache_lookup() found it, set zp to it. + if (*ap->a_vpp != NULL && zp == NULL) + zp = VTOZ(*ap->a_vpp); + + if (error == 0 && (zp != NULL)) + zfs_cache_name(*ap->a_vpp, ap->a_dvp, filename); + + dprintf("-vnop_lookup %d : dvp %llu '%s'\n", error, + VTOZ(ap->a_dvp)->z_id, filename); + + // kmem_free(filename, filename_num_bytes); + kmem_cache_free(vnop_lookup_cache, filename); + + return (error); +} + +int +zfs_vnop_create(struct vnop_create_args *ap) +#if 0 + struct vnop_create_args { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + struct vnode_vattr *a_vap; + vfs_context_t a_context; + }; +#endif +{ + struct componentname *cnp = ap->a_cnp; + vattr_t *vap = ap->a_vap; + DECLARE_CRED(ap); + vcexcl_t excl; + int mode = 0; /* FIXME */ + int error; + znode_t *zp = NULL; + + dprintf("vnop_create: '%s'\n", cnp->cn_nameptr); + + /* + * extern int zfs_create(struct vnode *dvp, char *name, vattr_t *vap, + * int excl, int mode, struct vnode **vpp, cred_t *cr); + */ + excl = (vap->va_vaflags & VA_EXCLUSIVE) ? EXCL : NONEXCL; + + /* + * This comment is from HFS: hfs_vnops.c + * Note that our [xnu] NFS server code does not set the + * VA_EXCLUSIVE flag so you cannot assume that callers don't want + * EEXIST errors if it's not set. The common case, where users + * are calling open with the O_CREAT mode, is handled in VFS; when + * we return EEXIST, it will loop and do the look-up again. + */ + excl = EXCL; + + dprintf("*** %s: with %x: %s: mode supplied %o: UTIME_NULL is %s\n", + __func__, excl, + excl ? "EXCL" : "NONEXCL", + vap->va_mode, + vap->va_vaflags & VA_UTIMES_NULL ? "set" : "not set"); + + if (VATTR_IS_ACTIVE(vap, va_access_time)) { + ZFS_TIME_ENCODE(&vap->va_access_time, zp->z_atime); + } + + + error = zfs_create(VTOZ(ap->a_dvp), cnp->cn_nameptr, vap, excl, mode, + &zp, cr, 0, NULL, NULL); + if (!error) { + cache_purge_negatives(ap->a_dvp); + *ap->a_vpp = ZTOV(zp); + + // Also tell XNU what VAPs we handled. + if (VATTR_IS_ACTIVE(vap, va_mode)) + VATTR_SET_SUPPORTED(vap, va_mode); + if (VATTR_IS_ACTIVE(vap, va_data_size)) + VATTR_SET_SUPPORTED(vap, va_data_size); + if (VATTR_IS_ACTIVE(vap, va_type)) + VATTR_SET_SUPPORTED(vap, va_type); + if (VATTR_IS_ACTIVE(vap, va_uid)) + VATTR_SET_SUPPORTED(vap, va_uid); + if (VATTR_IS_ACTIVE(vap, va_gid)) + VATTR_SET_SUPPORTED(vap, va_gid); + if (VATTR_IS_ACTIVE(vap, va_flags)) + VATTR_SET_SUPPORTED(vap, va_flags); + if (VATTR_IS_ACTIVE(vap, va_create_time)) + VATTR_SET_SUPPORTED(vap, va_create_time); + if (VATTR_IS_ACTIVE(vap, va_access_time)) + VATTR_SET_SUPPORTED(vap, va_access_time); + if (VATTR_IS_ACTIVE(vap, va_modify_time)) + VATTR_SET_SUPPORTED(vap, va_modify_time); + if (VATTR_IS_ACTIVE(vap, va_change_time)) + VATTR_SET_SUPPORTED(vap, va_change_time); + if (VATTR_IS_ACTIVE(vap, va_backup_time)) + VATTR_SET_SUPPORTED(vap, va_backup_time); + + uint64_t missing = 0; + missing = + (vap->va_active ^ (vap->va_active & vap->va_supported)); + if (missing != 0) { + dprintf("%s: asked %08llx replied %08llx " + " missing %08llx\n", __func__, + vap->va_active, vap->va_supported, + missing); + } + + /* We need to update name here for NFS; issue #104 */ + vnode_update_identity(*ap->a_vpp, NULL, + (const char *)ap->a_cnp->cn_nameptr, + ap->a_cnp->cn_namelen, 0, + VNODE_UPDATE_NAME); + + } + + return (error); +} + + +static int zfs_remove_hardlink(struct vnode *vp, struct vnode *dvp, char *name) +{ + /* + * Because we store hash of hardlinks in an AVLtree, we need to remove + * any entries in it upon deletion. Since it is complicated to know + * if an entry was a hardlink, we simply check if the avltree has the + * name. + */ + hardlinks_t *searchnode, *findnode; + avl_index_t loc; + + if (!vp || !VTOZ(vp)) + return (1); + if (!dvp || !VTOZ(dvp)) + return (1); + znode_t *zp = VTOZ(vp); + znode_t *dzp = VTOZ(dvp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + int ishardlink = 0; + + ishardlink = ((zp->z_links > 1) && + (IFTOVT((mode_t)zp->z_mode) == VREG)) ? 1 : 0; + if (zp->z_finder_hardlink) + ishardlink = 1; + + if (!ishardlink) + return (0); + + dprintf("ZFS: removing hash (%llu,%llu,'%s')\n", + dzp->z_id, zp->z_id, name); + + // Attempt to remove from hardlink avl, if its there + searchnode = kmem_zalloc(sizeof (hardlinks_t), KM_SLEEP); + searchnode->hl_parent = dzp->z_id; + searchnode->hl_fileid = zp->z_id; + strlcpy(searchnode->hl_name, name, PATH_MAX); + + rw_enter(&zfsvfs->z_hardlinks_lock, RW_READER); + findnode = avl_find(&zfsvfs->z_hardlinks, searchnode, &loc); + rw_exit(&zfsvfs->z_hardlinks_lock); + kmem_free(searchnode, sizeof (hardlinks_t)); + + // Found it? remove it + if (findnode) { + rw_enter(&zfsvfs->z_hardlinks_lock, RW_WRITER); + avl_remove(&zfsvfs->z_hardlinks, findnode); + avl_remove(&zfsvfs->z_hardlinks_linkid, findnode); + rw_exit(&zfsvfs->z_hardlinks_lock); + kmem_free(findnode, sizeof (*findnode)); + dprintf("ZFS: removed hash '%s'\n", name); + mutex_enter(&zp->z_lock); + zp->z_name_cache[0] = 0; + zp->z_finder_parentid = 0; + mutex_exit(&zp->z_lock); + return (1); + } + return (0); +} + + +static int zfs_rename_hardlink(struct vnode *vp, struct vnode *tvp, + struct vnode *fdvp, struct vnode *tdvp, + char *from, char *to) +{ + /* + * Because we store hash of hardlinks in an AVLtree, we need to update + * any entries in it upon rename. Since it is complicated to know + * if an entry was a hardlink, we simply check if the avltree has the + * name. + */ + hardlinks_t *searchnode, *findnode, *delnode; + avl_index_t loc; + uint64_t parent_fid, parent_tid; + int ishardlink = 0; + + if (!vp || !VTOZ(vp)) + return (0); + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + + ishardlink = ((zp->z_links > 1) && + (IFTOVT((mode_t)zp->z_mode) == VREG)) ? 1 : 0; + if (zp->z_finder_hardlink) + ishardlink = 1; + + if (!ishardlink) + return (0); + + if (!fdvp || !VTOZ(fdvp)) + return (0); + parent_fid = VTOZ(fdvp)->z_id; + + if (!tdvp || !VTOZ(tdvp)) { + parent_tid = parent_fid; + } else { + parent_tid = VTOZ(tdvp)->z_id; + } + + dprintf("ZFS: looking to rename hardlinks (%llu,%llu,%s)\n", + parent_fid, zp->z_id, from); + + + // Attempt to remove from hardlink avl, if its there + searchnode = kmem_zalloc(sizeof (hardlinks_t), KM_SLEEP); + searchnode->hl_parent = parent_fid; + searchnode->hl_fileid = zp->z_id; + strlcpy(searchnode->hl_name, from, PATH_MAX); + + rw_enter(&zfsvfs->z_hardlinks_lock, RW_READER); + findnode = avl_find(&zfsvfs->z_hardlinks, searchnode, &loc); + rw_exit(&zfsvfs->z_hardlinks_lock); + + // Found it? update it + if (findnode) { + + rw_enter(&zfsvfs->z_hardlinks_lock, RW_WRITER); + + // Technically, we do not need to re-do the _linkid AVL here. + avl_remove(&zfsvfs->z_hardlinks, findnode); + avl_remove(&zfsvfs->z_hardlinks_linkid, findnode); + + // If we already have a hashid for "to" and the rename + // presumably unlinked it, we need to remove it first. + searchnode->hl_parent = parent_tid; + strlcpy(searchnode->hl_name, to, PATH_MAX); + delnode = avl_find(&zfsvfs->z_hardlinks, searchnode, &loc); + if (delnode) { + dprintf("ZFS: apparently %llu:'%s' exists, deleting\n", + parent_tid, to); + avl_remove(&zfsvfs->z_hardlinks, delnode); + avl_remove(&zfsvfs->z_hardlinks_linkid, delnode); + kmem_free(delnode, sizeof (*delnode)); + } + + dprintf("ZFS: renamed hash %llu (%llu:'%s' to %llu:'%s'): %s\n", + zp->z_id, + parent_fid, from, + parent_tid, to, + delnode ? "deleted":""); + + // Update source node to new hash, and name. + findnode->hl_parent = parent_tid; + strlcpy(findnode->hl_name, to, PATH_MAX); + // zp->z_finder_parentid = parent_tid; + + avl_add(&zfsvfs->z_hardlinks, findnode); + avl_add(&zfsvfs->z_hardlinks_linkid, findnode); + + rw_exit(&zfsvfs->z_hardlinks_lock); + kmem_free(searchnode, sizeof (hardlinks_t)); + + return (1); + } + + kmem_free(searchnode, sizeof (hardlinks_t)); + return (0); +} + + +int +zfs_vnop_remove(struct vnop_remove_args *ap) +#if 0 + struct vnop_remove_args { + struct vnode *a_dvp; + struct vnode *a_vp; + struct componentname *a_cnp; + int a_flags; + vfs_context_t a_context; + }; +#endif +{ +// DECLARE_CRED_AND_CONTEXT(ap); + DECLARE_CRED(ap); + int error; + + dprintf("vnop_remove: %p (%s)\n", ap->a_vp, ap->a_cnp->cn_nameptr); + + /* + * extern int zfs_remove ( struct vnode *dvp, char *name, cred_t *cr, + * caller_context_t *ct, int flags); + */ + error = zfs_remove(VTOZ(ap->a_dvp), ap->a_cnp->cn_nameptr, cr, + /* flags */0); + if (!error) { + cache_purge(ap->a_vp); + + zfs_remove_hardlink(ap->a_vp, + ap->a_dvp, + ap->a_cnp->cn_nameptr); + } else { + dprintf("%s error %d\n", __func__, error); + } + + return (error); +} + +int +zfs_vnop_mkdir(struct vnop_mkdir_args *ap) +#if 0 + struct vnop_mkdir_args { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + struct vnode_vattr *a_vap; + vfs_context_t a_context; + }; +#endif +{ +// DECLARE_CRED_AND_CONTEXT(ap); + DECLARE_CRED(ap); + int error; + + dprintf("vnop_mkdir '%s'\n", ap->a_cnp->cn_nameptr); + + if (zfs_disable_spotlight) { + /* Let's deny OS X fseventd */ + if (ap->a_cnp->cn_nameptr && + strcmp(ap->a_cnp->cn_nameptr, ".fseventsd") == 0) + return (EINVAL); + /* spotlight */ + if (ap->a_cnp->cn_nameptr && + strcmp(ap->a_cnp->cn_nameptr, ".Spotlight-V100") == 0) + return (EINVAL); + } + if (zfs_disable_trashes) { + /* Let's deny OS X .trashes */ + if (ap->a_cnp->cn_nameptr && + strcmp(ap->a_cnp->cn_nameptr, ".Trashes") == 0) + return (EINVAL); + } + /* + * extern int zfs_mkdir(struct vnode *dvp, char *dirname, vattr_t *vap, + * struct vnode **vpp, cred_t *cr, caller_context_t *ct, int flags, + * vsecattr_t *vsecp); + */ + znode_t *zp = NULL; + ap->a_vap->va_mode |= S_IFDIR; + error = zfs_mkdir(VTOZ(ap->a_dvp), ap->a_cnp->cn_nameptr, ap->a_vap, + &zp, cr, /* flags */0, /* vsecp */NULL, NULL); + if (!error) { + *ap->a_vpp = ZTOV(zp); + cache_purge_negatives(ap->a_dvp); + vnode_update_identity(*ap->a_vpp, ap->a_dvp, + (const char *)ap->a_cnp->cn_nameptr, ap->a_cnp->cn_namelen, + 0, VNODE_UPDATE_NAME); + + VERIFY3P(zp->z_zfsvfs, ==, + vfs_fsprivate(vnode_mount(*ap->a_vpp))); + + + } else { + dprintf("%s error %d\n", __func__, error); + } + + return (error); +} + +int +zfs_vnop_rmdir(struct vnop_rmdir_args *ap) +#if 0 + struct vnop_rmdir_args { + struct vnode *a_dvp; + struct vnode *a_vp; + struct componentname *a_cnp; + vfs_context_t a_context; + }; +#endif +{ +// DECLARE_CRED_AND_CONTEXT(ap); + DECLARE_CRED(ap); + int error; + + dprintf("vnop_rmdir\n"); + + /* + * extern int zfs_rmdir(struct vnode *dvp, char *name, + * struct vnode *cwd, cred_t *cr, caller_context_t *ct, int flags); + */ + error = zfs_rmdir(VTOZ(ap->a_dvp), ap->a_cnp->cn_nameptr, + /* cwd */NULL, cr, /* flags */0); + if (!error) { + cache_purge(ap->a_vp); + } else { + dprintf("%s error %d\n", __func__, error); + } + + return (error); +} + +int +zfs_vnop_readdir(struct vnop_readdir_args *ap) +#if 0 + struct vnop_readdir_args { + struct vnode a_vp; + struct uio *a_uio; + int a_flags; + int *a_eofflag; + int *a_numdirent; + vfs_context_t a_context; + }; +#endif +{ + int error; + DECLARE_CRED(ap); + ZFS_UIO_INIT_XNU(uio, ap->a_uio); + + dprintf("+readdir: %p\n", ap->a_vp); + + /* + * XXX This interface needs vfs_has_feature. + * XXX zfs_readdir() also needs to grow support for passing back the + * number of entries (OS X/FreeBSD) and cookies (FreeBSD). However, + * it should be the responsibility of the OS caller to malloc/free + * space for that. + */ + + /* + * extern int zfs_readdir(struct vnode *vp, uio_t *uio, cred_t *cr, + * int *eofp, int flags, int *a_numdirent); + */ + *ap->a_numdirent = 0; + + error = zfs_readdir(ap->a_vp, uio, cr, ap->a_eofflag, ap->a_flags, + ap->a_numdirent); + + /* .zfs dirs can be completely empty */ + if (*ap->a_numdirent == 0) + *ap->a_numdirent = 2; /* . and .. */ + + if (error) { + dprintf("-readdir %d (nument %d)\n", error, *ap->a_numdirent); + } + return (error); +} + +int +zfs_vnop_fsync(struct vnop_fsync_args *ap) +#if 0 + struct vnop_fsync_args { + struct vnode *a_vp; + int a_waitfor; + vfs_context_t a_context; + }; +#endif +{ + znode_t *zp = VTOZ(ap->a_vp); + zfsvfs_t *zfsvfs; +// DECLARE_CRED_AND_CONTEXT(ap); + DECLARE_CRED(ap); + int err; + + /* + * Check if this znode has already been synced, freed, and recycled + * by znode_pageout_func. + * + * XXX What is this? Substitute for Illumos vn_has_cached_data()? + */ + if (zp == NULL) + return (0); + + zfsvfs = zp->z_zfsvfs; + + if (!zfsvfs) + return (0); + + /* + * If we come here via vnode_create()->vclean() we can not end up in + * zil_commit() or we will deadlock. But we know that vnop_reclaim will + * be called next, so we just return success. + */ + if (vnode_isrecycled(ap->a_vp)) + return (0); + + err = zfs_fsync(VTOZ(ap->a_vp), /* flag */0, cr); + + if (err) dprintf("%s err %d\n", __func__, err); + + return (err); +} + +int +zfs_vnop_getattr(struct vnop_getattr_args *ap) +#if 0 + struct vnop_getattr_args { + struct vnode *a_vp; + struct vnode_vattr *a_vap; + vfs_context_t a_context; + }; +#endif +{ + int error; + DECLARE_CRED_AND_CONTEXT(ap); + + /* dprintf("+vnop_getattr zp %p vp %p\n", VTOZ(ap->a_vp), ap->a_vp); */ + + /* If they want ADDEDTIME, make sure to ask for CRTIME */ + if (VATTR_IS_ACTIVE(ap->a_vap, va_addedtime)) + VATTR_WANTED(ap->a_vap, va_create_time); + + error = zfs_getattr(ap->a_vp, ap->a_vap, /* flags */0, cr, ct); + + if (error == 0) { + error = zfs_getattr_znode_unlocked(ap->a_vp, ap->a_vap); + } + if (error) + dprintf("-vnop_getattr '%p' %d\n", (ap->a_vp), error); + + return (error); +} + +int +zfs_vnop_setattr(struct vnop_setattr_args *ap) +#if 0 + struct vnop_setattr_args { + struct vnode *a_vp; + struct vnode_vattr *a_vap; + vfs_context_t a_context; + }; +#endif +{ + DECLARE_CRED(ap); + int error = 0; + znode_t *zp = VTOZ(ap->a_vp); + vattr_t *vap = ap->a_vap; + xvattr_t xva; /* va_flags */ + + if (VATTR_IS_ACTIVE(vap, va_access_time)) { + ZFS_TIME_ENCODE(&vap->va_access_time, zp->z_atime); + } + /* + * Both 'flags' and 'acl' can come to setattr, but without 'mode' set. + * However, ZFS assumes 'mode' is also set. We need to look up 'mode' in + * this case. + */ + if ((VATTR_IS_ACTIVE(vap, va_flags) || VATTR_IS_ACTIVE(vap, va_acl)) && + !VATTR_IS_ACTIVE(vap, va_mode)) { + uint64_t mode; + + dprintf("fetching MODE for FLAGS or ACL\n"); + + if ((error = zfs_enter_verify_zp(zp->z_zfsvfs, zp, FTAG)) != 0) + return (error); + (void) sa_lookup(zp->z_sa_hdl, SA_ZPL_MODE(zp->z_zfsvfs), &mode, + sizeof (mode)); + + VATTR_RETURN(vap, va_mode, mode); + VATTR_WANTED(vap, va_mode); + + zfs_exit(zp->z_zfsvfs, FTAG); + } + if (VATTR_IS_ACTIVE(vap, va_flags)) { + + /* + * If TRACKED is wanted, and not previously set, + * go set DocumentID + */ + if ((vap->va_flags & UF_TRACKED) && + !(zp->z_pflags & ZFS_TRACKED)) { + zfs_setattr_generate_id(zp, 0, NULL); + /* flags updated in vnops */ + zfs_setattr_set_documentid(zp, B_FALSE); + } + +#ifndef DECMPFS_XATTR_NAME +#define DECMPFS_XATTR_NAME "com.apple.decmpfs" +#endif + + /* If they are trying to turn on compression.. */ + if (vap->va_flags & UF_COMPRESSED) { + dprintf("setattr trying to set COMPRESSED!\n"); + + vap->va_flags &= ~UF_COMPRESSED; + + if (zp->z_size == 0) { + dprintf("zero-length file, returning EINVAL\n"); + return (EINVAL); + } + + zp->z_skip_truncate_undo_decmpfs = B_TRUE; + + /* delete the xattr, can be either of 2 names */ + error = zpl_xattr_set(ap->a_vp, DECMPFS_XATTR_NAME, + NULL, 0, cr); + dprintf("del xattr '%s': %d\n", DECMPFS_XATTR_NAME, + error); + error = zpl_xattr_set(ap->a_vp, XATTR_RESOURCEFORK_NAME, + NULL, 0, cr); + dprintf("del xattr '%s': %d\n", XATTR_RESOURCEFORK_NAME, + error); + + if (error != 0) + dprintf("setattr failed to delete xattr?!\n"); + + } + + /* Map OS X file flags to zfs file flags */ + error = zfs_setbsdflags(zp, vap->va_flags, &xva); + if (!error) { + memcpy(&xva.xva_vattr, vap, sizeof (vattr_t)); + /* memcpy overwrote */ + xva.xva_vattr.va_mask |= ATTR_XVATTR; + vap = (vattr_t *)&xva; + } + } + + /* + * If z_skip_truncate_undo_decmpfs is set, and they are trying to + * va_size == 0 (truncate), we undo the decmpfs work here. This is + * because we can not stop (no error, or !feature works) macOS from + * using decmpfs. + */ + if ((VATTR_IS_ACTIVE(vap, va_total_size) || + VATTR_IS_ACTIVE(vap, va_data_size)) && + vap->va_size == 0 && + zp->z_skip_truncate_undo_decmpfs) { + zp->z_skip_truncate_undo_decmpfs = B_FALSE; + + dprintf("setattr setsize with compress attempted\n"); + + /* Successfully deleted the XATTR - skip truncate */ + VATTR_CLEAR_ACTIVE(vap, va_total_size); + VATTR_CLEAR_ACTIVE(vap, va_data_size); + + } + + error = zfs_setattr(VTOZ(ap->a_vp), vap, /* flag */0, cr, + NULL); + + dprintf("vnop_setattr: called on vp %p with mask %llx, err=%d\n", + ap->a_vp, vap->va_mask, error); + + if (!error) { + /* If successful, tell OS X which fields ZFS set. */ + if (VATTR_IS_ACTIVE(vap, va_data_size)) { + dprintf("ZFS: setattr new size %llx %llx\n", + vap->va_size, ubc_getsize(ap->a_vp)); + ubc_setsize(ap->a_vp, vap->va_size); + VATTR_SET_SUPPORTED(vap, va_data_size); + } + if (VATTR_IS_ACTIVE(vap, va_mode)) + VATTR_SET_SUPPORTED(vap, va_mode); + if (VATTR_IS_ACTIVE(vap, va_acl)) + VATTR_SET_SUPPORTED(vap, va_acl); + if (VATTR_IS_ACTIVE(vap, va_uid)) + VATTR_SET_SUPPORTED(vap, va_uid); + if (VATTR_IS_ACTIVE(vap, va_gid)) + VATTR_SET_SUPPORTED(vap, va_gid); + if (VATTR_IS_ACTIVE(vap, va_access_time)) + VATTR_SET_SUPPORTED(vap, va_access_time); + if (VATTR_IS_ACTIVE(vap, va_modify_time)) + VATTR_SET_SUPPORTED(vap, va_modify_time); + if (VATTR_IS_ACTIVE(vap, va_change_time)) + VATTR_SET_SUPPORTED(vap, va_change_time); + if (VATTR_IS_ACTIVE(vap, va_create_time)) + VATTR_SET_SUPPORTED(vap, va_create_time); + if (VATTR_IS_ACTIVE(vap, va_backup_time)) + VATTR_SET_SUPPORTED(vap, va_backup_time); + if (VATTR_IS_ACTIVE(vap, va_flags)) { + VATTR_SET_SUPPORTED(vap, va_flags); + /* Copy everything back from xvattr */ + /* Don't leak to XNU */ + xva.xva_vattr.va_mask &= ~ATTR_XVATTR; + vap = ap->a_vap; + memcpy(vap, &xva.xva_vattr, sizeof (vattr_t)); + } + + /* + * If we are told to ignore owners, we scribble over the uid + * and gid here unless root. + */ + if (((unsigned int)vfs_flags(zp->z_zfsvfs->z_vfs)) & + MNT_IGNORE_OWNERSHIP) { + if (kauth_cred_getuid(cr) != 0) { + vap->va_uid = UNKNOWNUID; + vap->va_gid = UNKNOWNGID; + } + } + } + +#if 1 + uint64_t missing = 0; + missing = (vap->va_active ^ (vap->va_active & vap->va_supported)); + if (missing != 0) { + dprintf("vnop_setattr:: asked %08llx replied %08llx " + "missing %08llx\n", vap->va_active, + vap->va_supported, missing); + } +#endif + + if (error) + dprintf("ZFS: vnop_setattr return failure %d\n", error); + return (error); +} + +int +zfs_vnop_rename(struct vnop_rename_args *ap) +#if 0 + struct vnop_rename_args { + struct vnode *a_fdvp; + struct vnode *a_fvp; + struct componentname *a_fcnp; + struct vnode *a_tdvp; + struct vnode *a_tvp; + struct componentname *a_tcnp; + vfs_context_t a_context; + }; +#endif +{ +// DECLARE_CRED_AND_CONTEXT(ap); + DECLARE_CRED(ap); + int error; + + dprintf("vnop_rename\n"); + + /* + * extern int zfs_rename(struct vnode *sdvp, char *snm, + * struct vnode *tdvp, char *tnm, cred_t *cr, caller_context_t *ct, + * int flags); + */ + error = zfs_rename(VTOZ(ap->a_fdvp), ap->a_fcnp->cn_nameptr, + VTOZ(ap->a_tdvp), ap->a_tcnp->cn_nameptr, cr, /* flags */0, + /* rflags */ 0, NULL, NULL); + + if (!error) { + cache_purge_negatives(ap->a_fdvp); + cache_purge_negatives(ap->a_tdvp); + cache_purge(ap->a_fvp); + + zfs_rename_hardlink(ap->a_fvp, ap->a_tvp, + ap->a_fdvp, ap->a_tdvp, + ap->a_fcnp->cn_nameptr, + ap->a_tcnp->cn_nameptr); + if (ap->a_tvp) { + cache_purge(ap->a_tvp); + } + +#ifdef __APPLE__ + /* + * After a rename, the VGET path /.vol/$fsid/$ino fails for + * a short period on hardlinks (until someone calls lookup). + * So until we can figure out exactly why this is, we drive + * a lookup here to ensure that vget will work + * (Finder/Spotlight). + */ + if (ap->a_fvp && VTOZ(ap->a_fvp) && + VTOZ(ap->a_fvp)->z_finder_hardlink) { + struct vnode *vp; + if (VOP_LOOKUP(ap->a_tdvp, &vp, ap->a_tcnp, + spl_vfs_context_kernel()) == 0) + vnode_put(vp); + } +#endif + + } + + if (error) dprintf("%s: error %d\n", __func__, error); + return (error); +} + +#if defined(MAC_OS_X_VERSION_10_12) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) +int +zfs_vnop_renamex(struct vnop_renamex_args *ap) +#if 0 + struct vnop_renamex_args { + struct vnode *a_fdvp; + struct vnode *a_fvp; + struct componentname *a_fcnp; + struct vnode *a_tdvp; + struct vnode *a_tvp; + struct componentname *a_tcnp; + struct vnode_attr *a_vap; // Reserved for future use + vfs_rename_flags_t a_flags; + vfs_context_t a_context; + }; +#endif +{ + DECLARE_CRED(ap); + int error; + + dprintf("vnop_renamex\n"); + + /* + * extern int zfs_rename(struct vnode *sdvp, char *snm, + * struct vnode *tdvp, char *tnm, cred_t *cr, caller_context_t *ct, + * int flags); + * + * Currently, hfs only supports one flag, VFS_RENAME_EXCL, so + * we will do the same. Since zfs_rename() only has logic for + * FIGNORECASE, passing VFS_RENAME_EXCL should be ok, if a bit + * hacky. + */ + error = zfs_rename(VTOZ(ap->a_fdvp), ap->a_fcnp->cn_nameptr, + VTOZ(ap->a_tdvp), ap->a_tcnp->cn_nameptr, cr, + (ap->a_flags&VFS_RENAME_EXCL), 0, NULL, NULL); + + if (!error) { + cache_purge_negatives(ap->a_fdvp); + cache_purge_negatives(ap->a_tdvp); + cache_purge(ap->a_fvp); + + zfs_rename_hardlink(ap->a_fvp, ap->a_tvp, + ap->a_fdvp, ap->a_tdvp, + ap->a_fcnp->cn_nameptr, + ap->a_tcnp->cn_nameptr); + if (ap->a_tvp) { + cache_purge(ap->a_tvp); + } + +#ifdef __APPLE__ + /* + * After a rename, the VGET path /.vol/$fsid/$ino fails for + * a short period on hardlinks (until someone calls lookup). + * So until we can figure out exactly why this is, we drive + * a lookup here to ensure that vget will work + * (Finder/Spotlight). + */ + if (ap->a_fvp && VTOZ(ap->a_fvp) && + VTOZ(ap->a_fvp)->z_finder_hardlink) { + struct vnode *vp; + if (VOP_LOOKUP(ap->a_tdvp, &vp, ap->a_tcnp, + spl_vfs_context_kernel()) == 0) + vnode_put(vp); + } +#endif + + } + + if (error) dprintf("%s: error %d\n", __func__, error); + return (error); +} +#endif // vnop_renamex_args + +int +zfs_vnop_symlink(struct vnop_symlink_args *ap) +#if 0 + struct vnop_symlink_args { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + struct vnode_vattr *a_vap; + char *a_target; + vfs_context_t a_context; + }; +#endif +{ + DECLARE_CRED(ap); + int error; + + dprintf("vnop_symlink\n"); + + /* + * extern int zfs_symlink(struct vnode *dvp, struct vnode **vpp, + * char *name, vattr_t *vap, char *link, cred_t *cr); + */ + + /* OS X doesn't need to set vap->va_mode? */ + znode_t *zp = NULL; + ap->a_vap->va_mode |= S_IFLNK; + error = zfs_symlink(VTOZ(ap->a_dvp), ap->a_cnp->cn_nameptr, + ap->a_vap, ap->a_target, &zp, cr, 0, NULL); + if (!error) { + *ap->a_vpp = ZTOV(zp); + cache_purge_negatives(ap->a_dvp); + vnode_update_identity(*ap->a_vpp, NULL, + (const char *)ap->a_cnp->cn_nameptr, + ap->a_cnp->cn_namelen, 0, + VNODE_UPDATE_NAME); + } else { + dprintf("%s: error %d\n", __func__, error); + } + /* XXX zfs_attach_vnode()? */ + return (error); +} + + +int +zfs_vnop_readlink(struct vnop_readlink_args *ap) +#if 0 + struct vnop_readlink_args { + struct vnode *vp; + struct uio *uio; + vfs_context_t a_context; + }; +#endif +{ +// DECLARE_CRED_AND_CONTEXT(ap); + DECLARE_CRED(ap); + ZFS_UIO_INIT_XNU(uio, ap->a_uio); + + dprintf("vnop_readlink\n"); + + /* + * extern int zfs_readlink(struct vnode *vp, uio_t *uio, cred_t *cr, + * caller_context_t *ct); + */ + return (zfs_readlink(ap->a_vp, uio, cr)); +} + +int +zfs_vnop_link(struct vnop_link_args *ap) +#if 0 + struct vnop_link_args { + struct vnode *a_vp; + struct vnode *a_tdvp; + struct componentname *a_cnp; + vfs_context_t a_context; + }; +#endif +{ +// DECLARE_CRED_AND_CONTEXT(ap); + DECLARE_CRED(ap); + int error; + + dprintf("vnop_link\n"); + + /* XXX Translate this inside zfs_link() instead. */ + if (vnode_mount(ap->a_vp) != vnode_mount(ap->a_tdvp)) { + dprintf("%s: vp and tdvp on different mounts\n", __func__); + return (EXDEV); + } + + /* + * XXX Understand why Apple made this comparison in so many places where + * others do not. + */ + if (ap->a_cnp->cn_namelen >= ZAP_MAXNAMELEN) { + dprintf("%s: name too long %d\n", __func__, + ap->a_cnp->cn_namelen); + return (ENAMETOOLONG); + } + + /* + * extern int zfs_link(struct vnode *tdvp, struct vnode *svp, + * char *name, cred_t *cr, caller_context_t *ct, int flags); + */ + error = zfs_link(VTOZ(ap->a_tdvp), VTOZ(ap->a_vp), + ap->a_cnp->cn_nameptr, cr, 0); + if (!error) { + // Set source vnode to multipath too, zfs_get_vnode() + // handles the target + vnode_setmultipath(ap->a_vp); + cache_purge(ap->a_vp); + cache_purge_negatives(ap->a_tdvp); + vnode_update_identity(ap->a_vp, NULL, + (const char *)ap->a_cnp->cn_nameptr, + ap->a_cnp->cn_namelen, 0, + VNODE_UPDATE_NAME); + } else { + dprintf("%s error %d\n", __func__, error); + } + + return (error); +} + +int +zfs_vnop_pagein(struct vnop_pagein_args *ap) +#if 0 + struct vnop_pagein_args { + struct vnode *a_vp; + upl_t a_pl; + vm_offset_t a_pl_offset; + off_t a_foffset; + size_t a_size; + int a_flags; + vfs_context_t a_context; + }; +#endif +{ + /* XXX Crib this from the Apple zfs_vnops.c. */ + struct vnode *vp = ap->a_vp; + offset_t off = ap->a_f_offset; + size_t len = ap->a_size; + upl_t upl = ap->a_pl; + vm_offset_t upl_offset = ap->a_pl_offset; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + caddr_t vaddr = NULL; + /* vm_offset_t vaddr = NULL; */ + int flags = ap->a_flags; + int need_unlock = 0; + int error = 0; + uint64_t file_sz; + + dprintf("+vnop_pagein: %p/%p off 0x%llx size 0x%lx filesz 0x%llx\n", + zp, vp, off, len, zp->z_size); + + if (upl == (upl_t)NULL) + panic("zfs_vnop_pagein: no upl!"); + + if (len <= 0) { + dprintf("zfs_vnop_pagein: invalid size %ld", len); + if (!(flags & UPL_NOCOMMIT)) + (void) ubc_upl_abort(upl, 0); + return (EINVAL); + } + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + file_sz = zp->z_size; + + /* ASSERT(zp->z_dbuf_held && zp->z_phys); */ + /* can't fault passed EOF */ + if ((off < 0) || (off >= file_sz) || + (len & PAGE_MASK) || (upl_offset & PAGE_MASK)) { + dprintf("passed EOF or size error\n"); + zfs_exit(zfsvfs, FTAG); + if (!(flags & UPL_NOCOMMIT)) + ubc_upl_abort_range(upl, upl_offset, len, + (UPL_ABORT_ERROR | UPL_ABORT_FREE_ON_EMPTY)); + return (EFAULT); + } + + /* + * If we already own the lock, then we must be page faulting in the + * middle of a write to this file (i.e., we are writing to this file + * using data from a mapped region of the file). + */ + if (!rw_write_held(&zp->z_map_lock)) { + rw_enter(&zp->z_map_lock, RW_WRITER); + need_unlock = TRUE; + } + + + if (ubc_upl_map(upl, (vm_offset_t *)&vaddr) != KERN_SUCCESS) { + dprintf("zfs_vnop_pagein: failed to ubc_upl_map"); + if (!(flags & UPL_NOCOMMIT)) + (void) ubc_upl_abort(upl, 0); + if (need_unlock) + rw_exit(&zp->z_map_lock); + zfs_exit(zfsvfs, FTAG); + return (ENOMEM); + } + + dprintf("vaddr %p with upl_off 0x%lx\n", vaddr, upl_offset); + vaddr += upl_offset; + + /* Can't read beyond EOF - but we need to zero those extra bytes. */ + if (off + len > file_sz) { + uint64_t newend = file_sz - off; + + dprintf("ZFS: pagein zeroing offset 0x%llx for 0x%llx bytes.\n", + newend, len - newend); + memset(&vaddr[newend], 0, len - newend); + len = newend; + } + /* + * Fill pages with data from the file. + */ + while (len > 0) { + uint64_t readlen; + + readlen = MIN(PAGESIZE, len); + + dprintf("pagein from off 0x%llx len 0x%llx into " + "address %p (len 0x%lx)\n", + off, readlen, vaddr, len); + + error = dmu_read(zp->z_zfsvfs->z_os, zp->z_id, off, readlen, + (void *)vaddr, DMU_READ_PREFETCH); + if (error) { + printf("zfs_vnop_pagein: dmu_read err %d\n", error); + break; + } + off += readlen; + vaddr += readlen; + len -= readlen; + } + ubc_upl_unmap(upl); + + if (!(flags & UPL_NOCOMMIT)) { + if (error) + ubc_upl_abort_range(upl, upl_offset, ap->a_size, + (UPL_ABORT_ERROR | UPL_ABORT_FREE_ON_EMPTY)); + else + ubc_upl_commit_range(upl, upl_offset, ap->a_size, + (UPL_COMMIT_CLEAR_DIRTY | + UPL_COMMIT_FREE_ON_EMPTY)); + } + ZFS_ACCESSTIME_STAMP(zfsvfs, zp); + + /* + * We can't grab the range lock for the page as reader which would stop + * truncation as this leads to deadlock. So we need to recheck the file + * size. + */ + if (ap->a_f_offset >= file_sz) + error = EFAULT; + if (need_unlock) + rw_exit(&zp->z_map_lock); + + zfs_exit(zfsvfs, FTAG); + if (error) dprintf("%s error %d\n", __func__, error); + return (error); +} + + + + +static int +zfs_pageout(zfsvfs_t *zfsvfs, znode_t *zp, upl_t upl, vm_offset_t upl_offset, + offset_t off, size_t size, int flags) +{ + dmu_tx_t *tx; + zfs_locked_range_t *lr; + uint64_t filesz; + int err = 0; + size_t len = size; + + dprintf("+vnop_pageout: %p/%p off 0x%llx len 0x%lx upl_off 0x%lx: " + "blksz 0x%x, z_size 0x%llx upl %p flags 0x%x\n", zp, ZTOV(zp), + off, len, upl_offset, zp->z_blksz, + zp->z_size, upl, flags); + + if (upl == (upl_t)NULL) { + dprintf("ZFS: vnop_pageout: failed on NULL upl\n"); + return (EINVAL); + } + + if ((err = zfs_enter(zfsvfs, FTAG)) != 0) { + if (!(flags & UPL_NOCOMMIT)) + (void) ubc_upl_abort(upl, + UPL_ABORT_DUMP_PAGES|UPL_ABORT_FREE_ON_EMPTY); + dprintf("ZFS: vnop_pageout: abort on z_unmounted\n"); + zfs_exit(zfsvfs, FTAG); + return (EIO); + } + + + /* ASSERT(zp->z_dbuf_held); */ /* field no longer present in znode. */ + + if (len <= 0) { + if (!(flags & UPL_NOCOMMIT)) + (void) ubc_upl_abort(upl, + UPL_ABORT_DUMP_PAGES|UPL_ABORT_FREE_ON_EMPTY); + err = EINVAL; + goto exit; + } + if (vnode_vfsisrdonly(ZTOV(zp))) { + if (!(flags & UPL_NOCOMMIT)) + ubc_upl_abort_range(upl, upl_offset, len, + UPL_ABORT_FREE_ON_EMPTY); + err = EROFS; + goto exit; + } + + filesz = zp->z_size; /* get consistent copy of zp_size */ + + if (off < 0 || off >= filesz || (off & PAGE_MASK_64) || + (len & PAGE_MASK)) { + if (!(flags & UPL_NOCOMMIT)) + ubc_upl_abort_range(upl, upl_offset, len, + UPL_ABORT_FREE_ON_EMPTY); + err = EINVAL; + goto exit; + } + + uint64_t pgsize = roundup(filesz, PAGESIZE); + + /* Any whole pages beyond the end of the file while we abort */ + if ((size + off) > pgsize) { + printf("ZFS: pageout abort outside pages (rounded 0x%llx > " + "UPLlen 0x%llx\n", pgsize, size + off); + ubc_upl_abort_range(upl, pgsize, + pgsize - (size + off), + UPL_ABORT_FREE_ON_EMPTY); + } + + dprintf("ZFS: starting with size %lx\n", len); + +top: + lr = zfs_rangelock_enter(&zp->z_rangelock, off, len, RL_WRITER); + /* + * can't push pages passed end-of-file + */ + filesz = zp->z_size; + if (off >= filesz) { + /* ignore all pages */ + err = 0; + goto out; + } else if (off + len > filesz) { +#if 0 + int npages = btopr(filesz - off); + page_t *trunc; + + page_list_break(&pp, &trunc, npages); + /* ignore pages past end of file */ + if (trunc) + pvn_write_done(trunc, flags); +#endif + len = filesz - off; + } + + tx = dmu_tx_create(zfsvfs->z_os); + if (!tx) { + dprintf("ZFS: zfs_vnops_osx: NULL TX encountered!\n"); + if (!(flags & UPL_NOCOMMIT)) + ubc_upl_abort_range(upl, upl_offset, len, + UPL_ABORT_FREE_ON_EMPTY); + err = EINVAL; + goto exit; + } + dmu_tx_hold_write(tx, zp->z_id, off, len); + + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); + zfs_sa_upgrade_txholds(tx, zp); + err = dmu_tx_assign(tx, TXG_WAIT); + if (err != 0) { + if (err == ERESTART) { + zfs_rangelock_exit(lr); + dmu_tx_wait(tx); + dmu_tx_abort(tx); + goto top; + } + dmu_tx_abort(tx); + goto out; + } + + caddr_t va; + + if (ubc_upl_map(upl, (vm_offset_t *)&va) != KERN_SUCCESS) { + err = EINVAL; + goto out; + } + + va += upl_offset; + while (len >= PAGESIZE) { + ssize_t sz = PAGESIZE; + + dprintf("pageout: dmu_write off 0x%llx size 0x%lx\n", off, sz); + + dmu_write(zfsvfs->z_os, zp->z_id, off, sz, va, tx); + va += sz; + off += sz; + len -= sz; + } + + /* + * The last, possibly partial block needs to have the data zeroed that + * would extend past the size of the file. + */ + if (len > 0) { + ssize_t sz = len; + + dprintf("pageout: dmu_writeX off 0x%llx size 0x%lx\n", off, sz); + dmu_write(zfsvfs->z_os, zp->z_id, off, sz, va, tx); + + va += sz; + off += sz; + len -= sz; + + /* + * Zero out the remainder of the PAGE that didn't fit within + * the file size. + */ + // memset(va, 0, PAGESIZE-sz); + // dprintf("zero last 0x%lx bytes.\n", PAGESIZE-sz); + + } + ubc_upl_unmap(upl); + + if (err == 0) { + uint64_t mtime[2], ctime[2]; + sa_bulk_attr_t bulk[3]; + int count = 0; + + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, + &mtime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, + &ctime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, + &zp->z_pflags, 8); + zfs_tstamp_update_setup(zp, CONTENT_MODIFIED, mtime, ctime); + err = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); + ASSERT0(err); + zfs_log_write(zfsvfs->z_log, tx, TX_WRITE, zp, off, len, 0, + NULL, NULL); + } + dmu_tx_commit(tx); + +out: + zfs_rangelock_exit(lr); + if (flags & UPL_IOSYNC) + zil_commit(zfsvfs->z_log, zp->z_id); + + if (!(flags & UPL_NOCOMMIT)) { + if (err) + ubc_upl_abort_range(upl, upl_offset, size, + (UPL_ABORT_ERROR | UPL_ABORT_FREE_ON_EMPTY)); + else + ubc_upl_commit_range(upl, upl_offset, size, + (UPL_COMMIT_CLEAR_DIRTY | + UPL_COMMIT_FREE_ON_EMPTY)); + } +exit: + zfs_exit(zfsvfs, FTAG); + if (err) dprintf("%s err %d\n", __func__, err); + return (err); +} + + + +int +zfs_vnop_pageout(struct vnop_pageout_args *ap) +#if 0 + struct vnop_pageout_args { + struct vnode *a_vp; + upl_t a_pl; + vm_offset_t a_pl_offset; + off_t a_foffset; + size_t a_size; + int a_flags; + vfs_context_t a_context; + }; +#endif +{ + struct vnode *vp = ap->a_vp; + int flags = ap->a_flags; + upl_t upl = ap->a_pl; + vm_offset_t upl_offset = ap->a_pl_offset; + size_t len = ap->a_size; + offset_t off = ap->a_f_offset; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = NULL; + int error; + + if (!zp || !zp->z_zfsvfs) { + if (!(flags & UPL_NOCOMMIT)) + ubc_upl_abort(upl, + (UPL_ABORT_DUMP_PAGES|UPL_ABORT_FREE_ON_EMPTY)); + dprintf("ZFS: vnop_pageout: null zp or zfsvfs\n"); + return (ENXIO); + } + + zfsvfs = zp->z_zfsvfs; + + dprintf("+vnop_pageout: off 0x%llx len 0x%lx upl_off 0x%lx: " + "blksz 0x%x, z_size 0x%llx\n", off, len, upl_offset, zp->z_blksz, + zp->z_size); + + /* + * XXX Crib this too, although Apple uses parts of zfs_putapage(). + * Break up that function into smaller bits so it can be reused. + */ + error = zfs_pageout(zfsvfs, zp, upl, upl_offset, ap->a_f_offset, + len, flags); + + return (error); +} + + +static int bluster_pageout(zfsvfs_t *zfsvfs, znode_t *zp, upl_t upl, + upl_offset_t upl_offset, off_t f_offset, int size, + uint64_t filesize, int flags, caddr_t vaddr, + dmu_tx_t *tx) +{ + int io_size; + int rounded_size; + off_t max_size; + int is_clcommit = 0; + + if ((flags & UPL_NOCOMMIT) == 0) + is_clcommit = 1; + + /* + * If they didn't specify any I/O, then we are done... + * we can't issue an abort because we don't know how + * big the upl really is + */ + if (size <= 0) { + dprintf("%s invalid size %d\n", __func__, size); + return (EINVAL); + } + + if (vnode_vfsisrdonly(ZTOV(zp))) { + if (is_clcommit) + ubc_upl_abort_range(upl, upl_offset, size, + UPL_ABORT_FREE_ON_EMPTY); + dprintf("%s: readonly fs\n", __func__); + return (EROFS); + } + + /* + * can't page-in from a negative offset + * or if we're starting beyond the EOF + * or if the file offset isn't page aligned + * or the size requested isn't a multiple of PAGE_SIZE + */ + if (f_offset < 0 || f_offset >= filesize || + (f_offset & PAGE_MASK_64) || (size & PAGE_MASK)) { + if (is_clcommit) + ubc_upl_abort_range(upl, upl_offset, size, + UPL_ABORT_FREE_ON_EMPTY); + dprintf("%s: invalid offset or size\n", __func__); + return (EINVAL); + } + max_size = filesize - f_offset; + + if (size < max_size) + io_size = size; + else + io_size = max_size; + + rounded_size = (io_size + (PAGE_SIZE - 1)) & ~PAGE_MASK; + + if (size > rounded_size) { + if (is_clcommit) + ubc_upl_abort_range(upl, upl_offset + rounded_size, + size - rounded_size, UPL_ABORT_FREE_ON_EMPTY); + } + +#if 1 + if (f_offset + size > filesize) { + dprintf("ZFS: lowering size %u to %llu\n", + size, f_offset > filesize ? 0 : filesize - f_offset); + if (f_offset > filesize) + size = 0; + else + size = filesize - f_offset; + } +#endif + + dmu_write(zfsvfs->z_os, zp->z_id, f_offset, size, + &vaddr[upl_offset], tx); + + return (0); +} + + + + +/* + * In V2 of vnop_pageout, we are given a NULL upl, so that we can + * grab the file locks first, then request the upl to lock down pages. + */ +int +zfs_vnop_pageoutv2(struct vnop_pageout_args *ap) +#if 0 + struct vnop_pageout_args { + struct vnode *a_vp; + upl_t a_pl; + vm_offset_t a_pl_offset; + off_t a_foffset; + size_t a_size; + int a_flags; + vfs_context_t a_context; + }; +#endif +{ + struct vnode *vp = ap->a_vp; + int a_flags = ap->a_flags; + vm_offset_t a_pl_offset = ap->a_pl_offset; + size_t a_size = ap->a_size; + upl_t upl = ap->a_pl; + upl_page_info_t *pl; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = NULL; + int error = 0; + uint64_t filesize; + zfs_locked_range_t *lr; + dmu_tx_t *tx; + caddr_t vaddr = NULL; + int merror = 0; + + /* + * We can still get into this function as non-v2 style, by the default + * pager (ie, swap - when we eventually support it) + */ + if (upl) { + dprintf("ZFS: Relaying vnop_pageoutv2 to vnop_pageout\n"); + return (zfs_vnop_pageout(ap)); + } + + if (!zp || !zp->z_zfsvfs) { + dprintf("ZFS: vnop_pageout: null zp or zfsvfs\n"); + return (ENXIO); + } + + if (ZTOV(zp) == NULL) { + dprintf("ZFS: vnop_pageout: null vp\n"); + return (ENXIO); + } + + // XNU can call us with iocount == 0 && usecount == 0. Grab + // a ref now so the vp doesn't reclaim while we are in here. + if (vnode_get(ZTOV(zp)) != 0) { + dprintf("ZFS: vnop_pageout: vnode_ref failed.\n"); + return (ENXIO); + } + + mutex_enter(&zp->z_lock); + + sa_handle_t *z_sa_hdl; + z_sa_hdl = zp->z_sa_hdl; + if (!z_sa_hdl) { + mutex_exit(&zp->z_lock); + vnode_put(ZTOV(zp)); + dprintf("ZFS: vnop_pageout: null sa_hdl\n"); + return (ENXIO); + } + + zfsvfs = zp->z_zfsvfs; + + mutex_exit(&zp->z_lock); + + if (error) { + dprintf("ZFS: %s: can't hold_sa: %d\n", __func__, error); + vnode_put(ZTOV(zp)); + return (ENXIO); + } + + dprintf("+vnop_pageout2: off 0x%llx len 0x%lx upl_off 0x%lx: " + "blksz 0x%x, z_size 0x%llx\n", ap->a_f_offset, a_size, + a_pl_offset, zp->z_blksz, + zp->z_size); + + + /* Start the pageout request */ + /* + * We can't leave this function without either calling upl_commit or + * upl_abort. So use the non-error version. + */ + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) { + dprintf("ZFS: vnop_pageoutv2: abort on z_unmounted\n"); + zfsvfs = NULL; + error = EIO; + goto exit_abort; + } + if (vfs_flags(zfsvfs->z_vfs) & MNT_RDONLY) { + dprintf("ZFS: vnop_pageoutv2: readonly\n"); + error = EROFS; + goto exit_abort; + } + + lr = zfs_rangelock_enter(&zp->z_rangelock, ap->a_f_offset, a_size, + RL_WRITER); + + /* Grab UPL now */ + int request_flags; + + /* + * we're in control of any UPL we commit + * make sure someone hasn't accidentally passed in UPL_NOCOMMIT + */ + a_flags &= ~UPL_NOCOMMIT; + a_pl_offset = 0; + + if (a_flags & UPL_MSYNC) { + request_flags = UPL_UBC_MSYNC | UPL_RET_ONLY_DIRTY; + } else { + request_flags = UPL_UBC_PAGEOUT | UPL_RET_ONLY_DIRTY; + } + + error = ubc_create_upl(vp, ap->a_f_offset, ap->a_size, &upl, &pl, + request_flags); + if (error || (upl == NULL)) { + dprintf("ZFS: Failed to create UPL! %d\n", error); + goto pageout_done; + } + + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_write(tx, zp->z_id, ap->a_f_offset, ap->a_size); + + // NULL z_sa_hdl + if (z_sa_hdl != NULL) + dmu_tx_hold_sa(tx, z_sa_hdl, B_FALSE); + + zfs_sa_upgrade_txholds(tx, zp); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error != 0) { + dmu_tx_abort(tx); + ubc_upl_abort(upl, (UPL_ABORT_ERROR|UPL_ABORT_FREE_ON_EMPTY)); + goto pageout_done; + } + + off_t f_offset; + int64_t offset; + int64_t isize; + int64_t pg_index; + + filesize = zp->z_size; /* get consistent copy of zp_size */ + + isize = ap->a_size; + f_offset = ap->a_f_offset; + + /* + * Scan from the back to find the last page in the UPL, so that we + * aren't looking at a UPL that may have already been freed by the + * preceding aborts/completions. + */ + for (pg_index = ((isize) / PAGE_SIZE); pg_index > 0; ) { + if (upl_page_present(pl, --pg_index)) + break; + if (pg_index == 0) { + dprintf("ZFS: failed on pg_index\n"); + dmu_tx_commit(tx); + ubc_upl_abort_range(upl, 0, isize, + UPL_ABORT_FREE_ON_EMPTY); + goto pageout_done; + } + } + + dprintf("ZFS: isize %llu pg_index %llu\n", isize, pg_index); + /* + * initialize the offset variables before we touch the UPL. + * a_f_offset is the position into the file, in bytes + * offset is the position into the UPL, in bytes + * pg_index is the pg# of the UPL we're operating on. + * isize is the offset into the UPL of the last non-clean page. + */ + isize = ((pg_index + 1) * PAGE_SIZE); + + offset = 0; + pg_index = 0; + while (isize > 0) { + int64_t xsize; + int64_t num_of_pages; + + // printf("isize %d for page %d\n", isize, pg_index); + + if (!upl_page_present(pl, pg_index)) { + /* + * we asked for RET_ONLY_DIRTY, so it's possible + * to get back empty slots in the UPL. + * just skip over them + */ + f_offset += PAGE_SIZE; + offset += PAGE_SIZE; + isize -= PAGE_SIZE; + pg_index++; + + continue; + } + if (!upl_dirty_page(pl, pg_index)) { + /* + * hfs has a call to panic here, but we trigger this + * *a lot* so unsure what is going on + */ + dprintf("zfs_vnop_pageoutv2: unforeseen clean page " + "@ index %lld for UPL %p\n", pg_index, upl); + f_offset += PAGE_SIZE; + offset += PAGE_SIZE; + isize -= PAGE_SIZE; + pg_index++; + continue; + } + + /* + * We know that we have at least one dirty page. + * Now checking to see how many in a row we have + */ + num_of_pages = 1; + xsize = isize - PAGE_SIZE; + + while (xsize > 0) { + if (!upl_dirty_page(pl, pg_index + num_of_pages)) + break; + num_of_pages++; + xsize -= PAGE_SIZE; + } + xsize = num_of_pages * PAGE_SIZE; + + if (!vnode_isswap(vp)) { + off_t end_of_range; + + end_of_range = f_offset + xsize - 1; + if (end_of_range >= filesize) { + end_of_range = (off_t)(filesize - 1); + } + } + + // Map it if needed + if (!vaddr) { + if ((ubc_upl_map(upl, (vm_offset_t *)&vaddr) != + KERN_SUCCESS) || vaddr == NULL) { + error = EINVAL; + vaddr = NULL; + dprintf("ZFS: unable to map\n"); + goto out; + } + dprintf("ZFS: Mapped %p\n", vaddr); + } + + + dprintf("ZFS: bluster offset %lld fileoff %lld size %lld " + "filesize %lld\n", offset, f_offset, xsize, filesize); + merror = bluster_pageout(zfsvfs, zp, upl, offset, f_offset, + xsize, filesize, a_flags, vaddr, tx); + /* remember the first error */ + if ((error == 0) && (merror)) + error = merror; + + f_offset += xsize; + offset += xsize; + isize -= xsize; + pg_index += num_of_pages; + } // while isize + + /* finish off transaction */ + if (error == 0) { + uint64_t mtime[2], ctime[2]; + sa_bulk_attr_t bulk[3]; + int count = 0; + + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, + &mtime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, + &ctime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, + &zp->z_pflags, 8); + zfs_tstamp_update_setup(zp, CONTENT_MODIFIED, mtime, ctime); + zfs_log_write(zfsvfs->z_log, tx, TX_WRITE, zp, ap->a_f_offset, + a_size, 0, NULL, NULL); + } + dmu_tx_commit(tx); + + // unmap + if (vaddr) { + ubc_upl_unmap(upl); + vaddr = NULL; + } +out: + zfs_rangelock_exit(lr); + if (a_flags & UPL_IOSYNC) + zil_commit(zfsvfs->z_log, zp->z_id); + + if (error) + ubc_upl_abort(upl, (UPL_ABORT_ERROR|UPL_ABORT_FREE_ON_EMPTY)); + else + ubc_upl_commit_range(upl, 0, a_size, UPL_COMMIT_FREE_ON_EMPTY); + + upl = NULL; + + vnode_put(ZTOV(zp)); + + zfs_exit(zfsvfs, FTAG); + if (error) + dprintf("ZFS: pageoutv2 failed %d\n", error); + return (error); + +pageout_done: + zfs_rangelock_exit(lr); + +exit_abort: + dprintf("ZFS: pageoutv2 aborted %d\n", error); + // VERIFY(ubc_create_upl(vp, off, len, &upl, &pl, flags) == 0); + // ubc_upl_abort(upl, UPL_ABORT_FREE_ON_EMPTY); + + vnode_put(ZTOV(zp)); + + if (zfsvfs) + zfs_exit(zfsvfs, FTAG); + return (error); +} + + + + + + +int +zfs_vnop_mmap(struct vnop_mmap_args *ap) +#if 0 + struct vnop_mmap_args { + struct vnode *a_vp; + int a_fflags; + kauth_cred_t a_cred; + struct proc *a_p; + }; +#endif +{ + struct vnode *vp = ap->a_vp; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs; + int error = 0; + + if (!zp) + return (ENODEV); + + zfsvfs = zp->z_zfsvfs; + + dprintf("+vnop_mmap: %p\n", ap->a_vp); + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + goto out; + + if (!vnode_isreg(vp)) { + error = ENODEV; + goto out; + } + + mutex_enter(&zp->z_lock); + zp->z_is_mapped = 1; + mutex_exit(&zp->z_lock); + +out: + zfs_exit(zfsvfs, FTAG); + dprintf("-vnop_mmap\n"); + return (error); +} + +int +zfs_vnop_mnomap(struct vnop_mnomap_args *ap) +#if 0 + struct vnop_mnomap_args { + struct vnode *a_vp; + int a_fflags; + kauth_cred_t a_cred; + struct proc *a_p; + }; +#endif +{ + struct vnode *vp = ap->a_vp; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + int error = 0; + + dprintf("+vnop_mnomap: %p\n", ap->a_vp); + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + goto out; + + if (!vnode_isreg(vp)) { + error = ENODEV; + goto out; + } + mutex_enter(&zp->z_lock); + /* + * If a file as been mmaped even once, it needs to keep "z_is_mapped" + * high because it will potentially keep pages in the UPL cache we need + * to update on writes. We can either drop the UPL pages here, or simply + * keep updating both places on zfs_write(). + */ + /* zp->z_is_mapped = 0; */ + mutex_exit(&zp->z_lock); + +out: + zfs_exit(zfsvfs, FTAG); + dprintf("-vnop_mnomap\n"); + return (error); +} + +int +zfs_vnop_inactive(struct vnop_inactive_args *ap) +#if 0 + struct vnop_inactive_args { + struct vnode *a_vp; + vfs_context_t a_context; + }; +#endif +{ + struct vnode *vp = ap->a_vp; + zfs_inactive(vp); + return (0); +} + + + +#ifdef _KERNEL +uint64_t vnop_num_reclaims = 0; +uint64_t vnop_num_vnodes = 0; +#endif + + +int +zfs_vnop_reclaim(struct vnop_reclaim_args *ap) +#if 0 + struct vnop_reclaim_args { + struct vnode *a_vp; + vfs_context_t a_context; + }; +#endif +{ + /* + * Care needs to be taken here, we may already have called reclaim + * from vnop_inactive, if so, very little needs to be done. + */ + + struct vnode *vp = ap->a_vp; + znode_t *zp = NULL; + zfsvfs_t *zfsvfs = NULL; + + /* Destroy the vm object and flush associated pages. */ +#ifndef __APPLE__ + vnode_destroy_vobject(vp); +#endif + + /* Already been released? */ + zp = VTOZ(vp); + ASSERT(zp != NULL); + dprintf("+vnop_reclaim zp %p/%p type %d\n", zp, vp, vnode_vtype(vp)); + if (!zp) goto out; + + zfsvfs = zp->z_zfsvfs; + + if (!zfsvfs) { + dprintf("ZFS: vnop_reclaim with zfsvfs == NULL\n"); + return (0); + } + + if (zfsctl_is_node(vp)) { + dprintf("ZFS: vnop_reclaim with ctldir node\n"); + return (0); + } + + ZTOV(zp) = NULL; + + /* + * Purge old data structures associated with the denode. + */ + vnode_clearfsnode(vp); /* vp->v_data = NULL */ + vnode_removefsref(vp); /* ADDREF from vnode_create */ + atomic_dec_64(&vnop_num_vnodes); + + dprintf("+vnop_reclaim zp %p/%p unlinked %d unmount " + "%d sa_hdl %p\n", zp, vp, zp->z_unlinked, + zfsvfs->z_unmounted, zp->z_sa_hdl); + + rw_enter(&zfsvfs->z_teardown_inactive_lock, RW_READER); + if (zp->z_sa_hdl == NULL) { + zfs_znode_free(zp); + } else { + zfs_zinactive(zp); + zfs_znode_free(zp); + } + rw_exit(&zfsvfs->z_teardown_inactive_lock); + +#ifdef _KERNEL + atomic_inc_64(&vnop_num_reclaims); +#endif + +out: + return (0); +} + + + + + +int +zfs_vnop_mknod(struct vnop_mknod_args *ap) +#if 0 + struct vnop_mknod_args { + struct vnode *a_dvp; + struct vnode **a_vpp; + struct componentname *a_cnp; + struct vnode_vattr *vap; + vfs_context_t a_context; + }; +#endif +{ + struct vnop_create_args create_ap; + int error; + + dprintf("%s\n", __func__); + + memset(&create_ap, 0, sizeof (struct vnop_create_args)); + + create_ap.a_dvp = ap->a_dvp; + create_ap.a_vpp = ap->a_vpp; + create_ap.a_cnp = ap->a_cnp; + create_ap.a_vap = ap->a_vap; + create_ap.a_context = ap->a_context; + + error = zfs_vnop_create(&create_ap); + if (error) dprintf("%s error %d\n", __func__, error); + return (error); +} + +int +zfs_vnop_allocate(struct vnop_allocate_args *ap) +#if 0 + struct vnop_allocate_args { + struct vnode *a_vp; + off_t a_length; + u_int32_t a_flags; + off_t *a_bytesallocated; + off_t a_offset; + vfs_context_t a_context; + }; +#endif +{ + struct vnode *vp = ap->a_vp; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs; + uint64_t wantedsize = 0, filesize = 0; + int error = 0; + + dprintf("%s %llu %d %llu %llu: '%s'\n", __func__, ap->a_length, + ap->a_flags, (ap->a_bytesallocated ? *ap->a_bytesallocated : 0), + ap->a_offset, zp->z_name_cache); + + /* + * This code has been reverted: + * https://github.com/openzfsonosx/zfs/issues/631 + * Most likely not correctly aligned, and too-large offsets. + */ + return (0); + + if (!zp || !zp->z_sa_hdl) + return (ENODEV); + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + +// *ap->a_bytesallocated = 0; + + if (!vnode_isreg(vp)) { + zfs_exit(zfsvfs, FTAG); + return (ENODEV); + } + + filesize = zp->z_size; + wantedsize = ap->a_length; + + if (ap->a_flags & ALLOCATEFROMPEOF) + wantedsize += filesize; + else if (ap->a_flags & ALLOCATEFROMVOL) + /* blockhint = ap->a_offset / blocksize */ // yeah, no idea + dprintf("%s: help, allocatefromvolume set?\n", __func__); + + dprintf("%s: filesize %llu wantedsize %llu\n", __func__, + filesize, wantedsize); + + // If we are extending + if (wantedsize > filesize) { + + error = zfs_freesp(zp, wantedsize, 0, FWRITE, B_TRUE); + + // If we are truncating, Apple claims this code is never called. + } else if (wantedsize < filesize) { + + dprintf("%s: file shrinking branch taken?\n", __func__); + + } + + if (!error) { + *(ap->a_bytesallocated) = wantedsize - filesize; + } + + zfs_exit(zfsvfs, FTAG); + dprintf("-%s: %d\n", __func__, error); + return (error); +} + +int +zfs_vnop_whiteout(struct vnop_whiteout_args *ap) +#if 0 + struct vnop_whiteout_args { + struct vnode *a_dvp; + struct componentname *a_cnp; + int a_flags; + vfs_context_t a_context; + }; +#endif +{ + dprintf("vnop_whiteout: ENOTSUP\n"); + + return (ENOTSUP); +} + +int +zfs_vnop_pathconf(struct vnop_pathconf_args *ap) +#if 0 + struct vnop_pathconf_args { + struct vnode *a_vp; + int a_name; + register_t *a_retval; + vfs_context_t a_context; + }; +#endif +{ + int32_t *valp = ap->a_retval; + int error = 0; + + dprintf("+vnop_pathconf a_name %d\n", ap->a_name); + + switch (ap->a_name) { + case _PC_LINK_MAX: + *valp = INT_MAX; + break; + case _PC_PIPE_BUF: + *valp = PIPE_BUF; + break; + case _PC_CHOWN_RESTRICTED: + *valp = 200112; /* POSIX */ + break; + case _PC_NO_TRUNC: + *valp = 200112; /* POSIX */ + break; + case _PC_NAME_MAX: + case _PC_NAME_CHARS_MAX: + *valp = ZAP_MAXNAMELEN - 1; /* 255 */ + break; + case _PC_PATH_MAX: + case _PC_SYMLINK_MAX: + *valp = PATH_MAX; /* 1024 */ + break; + case _PC_CASE_SENSITIVE: + { + znode_t *zp = VTOZ(ap->a_vp); + *valp = 1; + if (zp && zp->z_zfsvfs) { + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + *valp = (zfsvfs->z_case == ZFS_CASE_SENSITIVE) ? 1 : 0; + } + } + break; + case _PC_CASE_PRESERVING: + *valp = 1; + break; +/* + * OS X 10.6 does not define this. + */ +#ifndef _PC_XATTR_SIZE_BITS +#define _PC_XATTR_SIZE_BITS 26 +#endif +/* + * Even though ZFS has 64 bit limit on XATTR size, there would appear to be a + * limit in SMB2 that the bit size returned has to be 18, or we will get an + * error from most XATTR calls (STATUS_ALLOTTED_SPACE_EXCEEDED). + */ +#ifndef AD_XATTR_SIZE_BITS +#define AD_XATTR_SIZE_BITS 18 +#endif + case _PC_XATTR_SIZE_BITS: + *valp = AD_XATTR_SIZE_BITS; + break; + case _PC_FILESIZEBITS: + *valp = 64; + break; + default: + printf("ZFS: unknown pathconf %d called.\n", ap->a_name); + error = EINVAL; + } + + if (error) dprintf("%s vp %p : %d\n", __func__, ap->a_vp, error); + return (error); +} + +int +zfs_vnop_getxattr(struct vnop_getxattr_args *ap) +#if 0 + struct vnop_getxattr_args { + struct vnodeop_desc *a_desc; + struct vnode *a_vp; + char *a_name; + struct uio *a_uio; + size_t *a_size; + int a_options; + vfs_context_t a_context; + }; +#endif +{ + DECLARE_CRED(ap); + struct vnode *vp = ap->a_vp; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + ZFS_UIO_INIT_XNU(uio, ap->a_uio); + zfs_uio_t local_uio = { 0 }; + struct iovec iov; + u_int32_t local_finderinfo[8] = {0}; + boolean_t is_finderinfo = B_FALSE; + int error = 0; + uint64_t resid = ap->a_uio ? zfs_uio_resid(uio) : 0; + ssize_t retsize; + + dprintf("%s: vp %p: '%s'\n", __func__, ap->a_vp, ap->a_name); + + /* xattrs disabled? */ + if (zfsvfs->z_xattr == B_FALSE) + return (ENOTSUP); + + /* + * We need to do some special work on the finderinfo xattr in XNU. + * So it is better to read it into local memory, modify and copyout + * at the end. "resid" is set if we are going to read the value in, + * ie, not the a_uio == NULL case to read the size required. + */ + if (resid && + memcmp(XATTR_FINDERINFO_NAME, ap->a_name, + sizeof (XATTR_FINDERINFO_NAME)) == 0) { + + /* Must be 32 bytes */ + if (resid != sizeof (emptyfinfo)) { + return (ERANGE); + } + + iov.iov_base = (void *)local_finderinfo; + iov.iov_len = resid; + + zfs_uio_iovec_init(&local_uio, &iov, 1, 0, UIO_SYSSPACE, + resid, 0); + + is_finderinfo = B_TRUE; + } + + + error = zpl_xattr_get(vp, ap->a_name, + is_finderinfo ? &local_uio : uio, &retsize, cr); + + if (error != 0) + return (error); + + if (ap->a_size) + *ap->a_size = retsize; + + if (is_finderinfo) { + + /* According to HFS zero out some fields */ + finderinfo_update((uint8_t *)local_finderinfo, zp); + + /* If FinderInfo is empty > it doesn't exist */ + if (memcmp(emptyfinfo, local_finderinfo, + sizeof (emptyfinfo)) == 0) { + error = ENOATTR; + } else { + error = zfs_uiomove(local_finderinfo, resid, + UIO_READ, uio); + } + } + + + dprintf("%s: return 0 size %ld\n", __func__, retsize); + + return (0); +} + +int +zfs_vnop_setxattr(struct vnop_setxattr_args *ap) +#if 0 + struct vnop_setxattr_args { + struct vnodeop_desc *a_desc; + struct vnode *a_vp; + char *a_name; + struct uio *a_uio; + int a_options; + vfs_context_t a_context; + }; +#endif +{ + DECLARE_CRED(ap); + ZFS_UIO_INIT_XNU(uio, ap->a_uio); + struct vnode *vp = ap->a_vp; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + int error = 0; + zfs_uio_t local_uio = { 0 }; + struct iovec iov; + u_int32_t local_finderinfo[8] = {0}; + boolean_t is_finderinfo = B_FALSE; + + dprintf("+setxattr vp %p '%s' (enabled: %d) resid %lu\n", ap->a_vp, + ap->a_name, zfsvfs->z_xattr, zfs_uio_resid(uio)); + + /* xattrs disabled? */ + if (zfsvfs->z_xattr == B_FALSE) + return (ENOTSUP); + + if (ap->a_name == NULL || ap->a_name[0] == '\0') + return (EINVAL); /* invalid name */ + + if (strlen(ap->a_name) >= ZAP_MAXNAMELEN) + return (ENAMETOOLONG); + + if (memcmp(XATTR_RESOURCEFORK_NAME, ap->a_name, + sizeof (XATTR_RESOURCEFORK_NAME)) == 0) { + if (!vnode_isreg(vp)) + return (SET_ERROR(EPERM)); + } + + /* + * We need to do special work on the finderinfo when writing, so + * copyin to local buffer, and modify before passing to lower + */ + if (memcmp(XATTR_FINDERINFO_NAME, ap->a_name, + sizeof (XATTR_FINDERINFO_NAME)) == 0) { + + /* Must be 32 bytes */ + if (zfs_uio_resid(uio) != sizeof (emptyfinfo)) { + return (ERANGE); + } + + /* copyin finderinfo from userland */ + error = zfs_uiomove(local_finderinfo, sizeof (local_finderinfo), + UIO_WRITE, uio); + + /* According to HFS zero out some fields */ + finderinfo_update((uint8_t *)local_finderinfo, zp); + + /* If FinderInfo is empty > it doesn't exist - don't write */ + if (memcmp(emptyfinfo, local_finderinfo, + sizeof (emptyfinfo)) == 0) { + + /* But if there was one, delete it */ + (void) zpl_xattr_set(vp, ap->a_name, NULL, 0, cr); + + /* Pretend we wrote it fine. */ + return (0); + + } + + /* We read finderinfo, and possibly modified it, change uio */ + iov.iov_base = (void *)local_finderinfo; + iov.iov_len = sizeof (local_finderinfo); + + zfs_uio_iovec_init(&local_uio, &iov, 1, 0, UIO_SYSSPACE, + sizeof (local_finderinfo), 0); + + is_finderinfo = B_TRUE; + } + + error = zpl_xattr_set(vp, ap->a_name, + is_finderinfo ? &local_uio : uio, + ap->a_options, cr); + + dprintf("zpl_xattr_set(%s) returned %d\n", ap->a_name, error); + + if (error < 0) + return (-error); + + return (error); +} + +int +zfs_vnop_removexattr(struct vnop_removexattr_args *ap) +#if 0 + struct vnop_removexattr_args { + struct vnodeop_desc *a_desc; + struct vnode *a_vp; + char *a_name; + int a_options; + vfs_context_t a_context; + }; +#endif +{ + DECLARE_CRED(ap); + struct vnode *vp = ap->a_vp; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + int error = 0; + + dprintf("+removexattr vp %p '%s'\n", ap->a_vp, ap->a_name); + + /* xattrs disabled? */ + if (zfsvfs->z_xattr == B_FALSE) + return (ENOTSUP); + + error = zpl_xattr_set(vp, ap->a_name, NULL, 0, cr); + + if (error < 0) + return (-error); + + return (error); + +} + +int +zfs_vnop_listxattr(struct vnop_listxattr_args *ap) +#if 0 + struct vnop_listxattr_args { + struct vnodeop_desc *a_desc; + vnode_t a_vp; + uio_t a_uio; + size_t *a_size; + int a_options; + vfs_context_t a_context; + }; +#endif +{ + DECLARE_CRED(ap); + ZFS_UIO_INIT_XNU(uio, ap->a_uio); + struct vnode *vp = ap->a_vp; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + int error = 0; + ssize_t retsize; + + dprintf("+listxattr vp %p: resid %lu\n", ap->a_vp, zfs_uio_resid(uio)); + + /* xattrs disabled? */ + if (zfsvfs->z_xattr == B_FALSE) { + return (EINVAL); + } + + /* + * Call the Linux helper functions to deal with the xattr. + * Note they return negative errors; ie -ERANGE + * Positive for size of xattr. + */ + error = zpl_xattr_list(vp, uio, &retsize, cr); + + if (error != 0) + return (error); + + if (ap->a_size != NULL) + *ap->a_size = retsize; + + dprintf("%s: size %lu\n", __func__, retsize); + return (0); +} + +#ifdef HAVE_NAMED_STREAMS +int +zfs_vnop_getnamedstream(struct vnop_getnamedstream_args *ap) +#if 0 + struct vnop_getnamedstream_args { + struct vnode *a_vp; + struct vnode **a_svpp; + char *a_name; + }; +#endif +{ + DECLARE_CRED(ap); + struct vnode *vp = ap->a_vp; + struct vnode **svpp = ap->a_svpp; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + struct componentname cn = { 0 }; + int error = 0; + znode_t *xdzp = NULL; + znode_t *xzp = NULL; + + dprintf("+getnamedstream vp %p '%s': op %u\n", ap->a_vp, ap->a_name, + ap->a_operation); + + *svpp = NULLVP; + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + /* + * Mac OS X only supports the "com.apple.ResourceFork" stream. + */ + if (memcmp(XATTR_RESOURCEFORK_NAME, ap->a_name, + sizeof (XATTR_RESOURCEFORK_NAME)) != 0) { + error = ENOATTR; + goto out; + } + + /* Only regular files */ + if (!vnode_isreg(vp)) { + error = EPERM; + goto out; + } + + /* Grab the hidden attribute directory vnode. */ + if ((error = zfs_get_xattrdir(zp, &xdzp, cr, 0)) != 0) + goto out; + + const char *prefixed_name = zpl_xattr_prefixname(ap->a_name); + + cn.cn_namelen = strlen(prefixed_name) + 1; + cn.cn_nameptr = (char *)kmem_zalloc(cn.cn_namelen, KM_SLEEP); + + /* Lookup the attribute name. */ + if ((error = zfs_dirlook(xdzp, (char *)prefixed_name, &xzp, 0, NULL, + &cn))) { + if (error == ENOENT) + error = SET_ERROR(ENOATTR); + } else { + *svpp = ZTOV(xzp); + + /* + * We have to update the name or mac_vnode_check_open() + * rejects it, hfs sets to name of "file/..namedfork/rsrc" + * But is it enough to just set it to RSRCFORKSPEC ? + */ + vnode_update_identity(ZTOV(xzp), vp, + (const char *)ap->a_name, + strlen(ap->a_name), + 0, VNODE_UPDATE_NAME); + } + + kmem_free(prefixed_name, strlen(prefixed_name)); + kmem_free(cn.cn_nameptr, cn.cn_namelen); + +out: + if (xdzp) + zrele(xdzp); + + zfs_exit(zfsvfs, FTAG); + if (error) dprintf("%s vp %p: error %d\n", __func__, ap->a_vp, error); + return (SET_ERROR(error)); +} + +int +zfs_vnop_makenamedstream(struct vnop_makenamedstream_args *ap) +#if 0 + struct vnop_makenamedstream_args { + struct vnode *a_vp; + struct vnode **a_svpp; + char *a_name; + }; +#endif +{ + DECLARE_CRED(ap); + struct vnode *vp = ap->a_vp; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + struct componentname cn; + struct vnode_attr vattr; + int error = 0; + znode_t *xdzp = NULL; + znode_t *xzp = NULL; + + dprintf("+makenamedstream vp %p: '%s'\n", ap->a_vp, ap->a_name); + + *ap->a_svpp = NULLVP; + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + /* Only regular files can have a resource fork stream. */ + if (!vnode_isreg(vp)) { + error = EPERM; + goto out; + } + + /* + * Mac OS X only supports the "com.apple.ResourceFork" stream. + */ + if (memcmp(XATTR_RESOURCEFORK_NAME, ap->a_name, + sizeof (XATTR_RESOURCEFORK_NAME)) != 0) { + error = ENOATTR; + goto out; + } + + /* Grab the hidden attribute directory vnode. */ + if ((error = zfs_get_xattrdir(zp, &xdzp, cr, CREATE_XATTR_DIR))) + goto out; + + const char *prefixed_name = zpl_xattr_prefixname(ap->a_name); + + memset(&cn, 0, sizeof (cn)); + cn.cn_nameiop = CREATE; + cn.cn_flags = ISLASTCN; + cn.cn_nameptr = (char *)prefixed_name; + cn.cn_namelen = strlen(cn.cn_nameptr); + + VATTR_INIT(&vattr); + VATTR_SET(&vattr, va_type, VREG); + VATTR_SET(&vattr, va_mode, + S_IFREG | + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + + error = zfs_create(xdzp, (char *)prefixed_name, &vattr, NONEXCL, + VTOZ(vp)->z_mode, &xzp, cr, 0, NULL, NULL); + + if (error == 0) { + *ap->a_svpp = ZTOV(xzp); + /* It is interesting we don't need to set name here */ + } + + kmem_free(prefixed_name, strlen(prefixed_name)); + +out: + if (xdzp) + zrele(xdzp); + + zfs_exit(zfsvfs, FTAG); + if (error) dprintf("%s vp %p: error %d\n", __func__, ap->a_vp, error); + return (error); +} + +int +zfs_vnop_removenamedstream(struct vnop_removenamedstream_args *ap) +#if 0 + struct vnop_removenamedstream_args { + struct vnode *a_vp; + struct vnode **a_svpp; + char *a_name; + }; +#endif +{ + struct vnode *svp = ap->a_svp; + znode_t *zp = VTOZ(svp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + int error = 0; + + dprintf("zfs_vnop_removenamedstream: %p '%s'\n", + svp, ap->a_name); + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + /* + * Mac OS X only supports the "com.apple.ResourceFork" stream. + */ + if (memcmp(XATTR_RESOURCEFORK_NAME, ap->a_name, + sizeof (XATTR_RESOURCEFORK_NAME)) != 0) { + error = ENOATTR; + goto out; + } + + /* ### MISING CODE ### */ + /* + * It turns out that even though APPLE uses makenamedstream() to + * create a stream, for example compression, they use vnop_removexattr + * to delete it, so this appears not in use. + */ + dprintf("zfs_vnop_removenamedstream\n"); + error = EPERM; +out: + zfs_exit(zfsvfs, FTAG); + return (error); +} +#endif /* HAVE_NAMED_STREAMS */ + +/* + * The Darwin kernel's HFS+ appears to implement this by two methods, + * + * if (ap->a_options & FSOPT_EXCHANGE_DATA_ONLY) is set + * ** Copy the data of the files over (including rsrc) + * + * if not set + * ** exchange FileID between the two nodes, copy over vnode information + * like that of *time records, uid/gid, flags, mode, linkcount, + * finderinfo, c_desc, c_attr, c_flag, and cache_purge(). + * + * This call is deprecated in 10.8 + */ +int +zfs_vnop_exchange(struct vnop_exchange_args *ap) +#if 0 + struct vnop_exchange_args { + struct vnode *a_fvp; + struct vnode *a_tvp; + int a_options; + vfs_context_t a_context; + }; +#endif +{ + vnode_t *fvp = ap->a_fvp; + vnode_t *tvp = ap->a_tvp; + znode_t *fzp; + zfsvfs_t *zfsvfs; + int error = 0; + + /* The files must be on the same volume. */ + if (vnode_mount(fvp) != vnode_mount(tvp)) { + dprintf("%s fvp and tvp not in same mountpoint\n", + __func__); + return (EXDEV); + } + + if (fvp == tvp) { + dprintf("%s fvp == tvp\n", __func__); + return (EINVAL); + } + + /* Only normal files can be exchanged. */ + if (!vnode_isreg(fvp) || !vnode_isreg(tvp)) { + dprintf("%s fvp or tvp is not a regular file\n", + __func__); + return (EINVAL); + } + + fzp = VTOZ(fvp); + zfsvfs = fzp->z_zfsvfs; + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + /* ADD MISSING CODE HERE */ + + zfs_exit(zfsvfs, FTAG); + printf("vnop_exchange: ENOTSUP\n"); + return (ENOTSUP); +} + +int +zfs_vnop_revoke(struct vnop_revoke_args *ap) +#if 0 + struct vnop_revoke_args { + struct vnode *a_vp; + int a_flags; + vfs_context_t a_context; + }; +#endif +{ + return (vn_revoke(ap->a_vp, ap->a_flags, ap->a_context)); +} + +int +zfs_vnop_blktooff(struct vnop_blktooff_args *ap) +#if 0 + struct vnop_blktooff_args { + struct vnode *a_vp; + daddr64_t a_lblkno; + off_t *a_offset; + }; +#endif +{ + dprintf("vnop_blktooff: 0\n"); + return (ENOTSUP); +} + +int +zfs_vnop_offtoblk(struct vnop_offtoblk_args *ap) +#if 0 + struct vnop_offtoblk_args { + struct vnode *a_vp; + off_t a_offset; + daddr64_t *a_lblkno; + }; +#endif +{ + dprintf("+vnop_offtoblk\n"); + return (ENOTSUP); +} + +int +zfs_vnop_blockmap(struct vnop_blockmap_args *ap) +#if 0 + struct vnop_blockmap_args { + struct vnode *a_vp; + off_t a_foffset; + size_t a_size; + daddr64_t *a_bpn; + size_t *a_run; + void *a_poff; + int a_flags; +}; +#endif +{ + dprintf("+vnop_blockmap\n"); + return (ENOTSUP); + +#if 0 + znode_t *zp; + zfsvfs_t *zfsvfs; + + ASSERT(ap); + ASSERT(ap->a_vp); + ASSERT(ap->a_size); + + if (!ap->a_bpn) { + return (0); + } + + if (vnode_isdir(ap->a_vp)) { + return (ENOTSUP); + } + + zp = VTOZ(ap->a_vp); + if (!zp) + return (ENODEV); + + zfsvfs = zp->z_zfsvfs; + if (!zfsvfs) + return (ENODEV); + + /* Return full request size as contiguous */ + if (ap->a_run) { + // *ap->a_run = ap->a_size; + *ap->a_run = 0; + } + if (ap->a_poff) { + *((int *)(ap->a_poff)) = 0; + /* + * returning offset of -1 asks the + * caller to zero the ranges + */ + // *((int *)(ap->a_poff)) = -1; + } + *ap->a_bpn = 0; +// *ap->a_bpn = (daddr64_t)(ap->a_foffset / zfsvfs->z_max_blksz); + + dprintf("%s ret %lu %d %llu\n", __func__, + ap->a_size, *((int *)(ap->a_poff)), *((uint64_t *)(ap->a_bpn))); + + return (0); +#endif +} + +int +zfs_vnop_strategy(struct vnop_strategy_args *ap) +#if 0 + struct vnop_strategy_args { + struct buf *a_bp; + }; +#endif +{ + dprintf("vnop_strategy: 0\n"); + return (ENOTSUP); +} + +int +zfs_vnop_select(struct vnop_select_args *ap) +#if 0 + struct vnop_select_args { + struct vnode *a_vp; + int a_which; + int a_fflags; + kauth_cred_t a_cred; + void *a_wql; + struct proc *a_p; + }; +#endif +{ + dprintf("vnop_select: 1\n"); + return (1); +} + +#ifdef WITH_READDIRATTR +int +zfs_vnop_readdirattr(struct vnop_readdirattr_args *ap) +#if 0 + struct vnop_readdirattr_args { + struct vnodeop_desc *a_desc; + struct vnode *a_vp; + struct attrlist *a_alist; + struct uio *a_uio; + ulong_t a_maxcount; + ulong_t a_options; + ulong_t *a_newstate; + int *a_eofflag; + ulong_t *a_actualcount; + vfs_context_t a_context; + }; +#endif +{ + struct vnode *vp = ap->a_vp; + struct attrlist *alp = ap->a_alist; + struct uio *uio = ap->a_uio; + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + zap_cursor_t zc; + zap_attribute_t zap; + attrinfo_t attrinfo; + int maxcount = ap->a_maxcount; + uint64_t offset = (uint64_t)uio_offset(uio); + u_int32_t fixedsize; + u_int32_t maxsize; + u_int32_t attrbufsize; + void *attrbufptr = NULL; + void *attrptr; + void *varptr; /* variable-length storage area */ + boolean_t user64 = vfs_context_is64bit(ap->a_context); + int prefetch = 0; + int error = 0; + +#if 0 + dprintf("+vnop_readdirattr\n"); +#endif + + *(ap->a_actualcount) = 0; + *(ap->a_eofflag) = 0; + + /* + * Check for invalid options or invalid uio. + */ + if (((ap->a_options & ~(FSOPT_NOINMEMUPDATE | FSOPT_NOFOLLOW)) != 0) || + (uio_resid(uio) <= 0) || (maxcount <= 0)) { + dprintf("%s invalid argument\n"); + return (EINVAL); + } + /* + * Reject requests for unsupported attributes. + */ + if ((alp->bitmapcount != ZFS_ATTR_BIT_MAP_COUNT) || + (alp->commonattr & ~ZFS_ATTR_CMN_VALID) || + (alp->dirattr & ~ZFS_ATTR_DIR_VALID) || + (alp->fileattr & ~ZFS_ATTR_FILE_VALID) || + (alp->volattr != 0 || alp->forkattr != 0)) { + dprintf("%s unsupported attr\n"); + return (EINVAL); + } + /* + * Check if we should prefetch znodes + */ + if ((alp->commonattr & ~ZFS_DIR_ENT_ATTRS) || + (alp->dirattr != 0) || (alp->fileattr != 0)) { + prefetch = TRUE; + } + + /* + * Setup a buffer to hold the packed attributes. + */ + fixedsize = sizeof (u_int32_t) + getpackedsize(alp, user64); + maxsize = fixedsize; + if (alp->commonattr & ATTR_CMN_NAME) + maxsize += ZAP_MAXNAMELEN + 1; + attrbufptr = (void*)kmem_zalloc(maxsize, KM_SLEEP); + if (attrbufptr == NULL) { + dprintf("%s kmem_zalloc failed\n"); + return (ENOMEM); + } + attrptr = attrbufptr; + varptr = (char *)attrbufptr + fixedsize; + + attrinfo.ai_attrlist = alp; + attrinfo.ai_varbufend = (char *)attrbufptr + maxsize; + attrinfo.ai_context = ap->a_context; + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (error); + + /* + * Initialize the zap iterator cursor. + */ + + if (offset <= 3) { + /* + * Start iteration from the beginning of the directory. + */ + zap_cursor_init(&zc, zfsvfs->z_os, zp->z_id); + } else { + /* + * The offset is a serialized cursor. + */ + zap_cursor_init_serialized(&zc, zfsvfs->z_os, zp->z_id, offset); + } + + while (1) { + ino64_t objnum; + enum vtype vtype = VNON; + znode_t *tmp_zp = NULL; + + /* + * Note that the low 4 bits of the cookie returned by zap is + * always zero. This allows us to use the low nibble for + * "special" entries: + * We use 0 for '.', and 1 for '..' (ignored here). + * If this is the root of the filesystem, we use the offset 2 + * for the *'.zfs' directory. + */ + if (offset <= 1) { + offset = 2; + continue; + } else if (offset == 2 && zfs_show_ctldir(zp)) { + (void) strlcpy(zap.za_name, ZFS_CTLDIR_NAME, + MAXNAMELEN); + objnum = ZFSCTL_INO_ROOT; + vtype = VDIR; + } else { + /* + * Grab next entry. + */ + if ((error = zap_cursor_retrieve(&zc, &zap))) { + *(ap->a_eofflag) = (error == ENOENT); + goto update; + } + + if (zap.za_integer_length != 8 || + zap.za_num_integers != 1) { + error = ENXIO; + goto update; + } + + objnum = ZFS_DIRENT_OBJ(zap.za_first_integer); + vtype = DTTOVT(ZFS_DIRENT_TYPE(zap.za_first_integer)); + /* Check if vtype is MIA */ + if ((vtype == 0) && !prefetch && (alp->dirattr || + alp->fileattr || + (alp->commonattr & ATTR_CMN_OBJTYPE))) { + prefetch = 1; + } + } + + /* Grab znode if required */ + if (prefetch) { + dmu_prefetch(zfsvfs->z_os, objnum, 0, 0); + if ((error = zfs_zget(zfsvfs, objnum, &tmp_zp)) == 0) { + if (vtype == VNON) { + /* SA_LOOKUP? */ + vtype = IFTOVT(tmp_zp->z_mode); + } + } else { + tmp_zp = NULL; + error = ENXIO; + goto skip_entry; + /* + * Currently ".zfs" entry is skipped, as we have + * no methods to pack that into the attrs (all + * helper functions take znode_t *, and .zfs is + * not a znode_t *). Add dummy .zfs code here if + * it is desirable to show .zfs in Finder. + */ + } + } + + /* + * Setup for the next item's attribute list + */ + *((u_int32_t *)attrptr) = 0; /* byte count slot */ + attrptr = ((u_int32_t *)attrptr) + 1; /* fixed attr start */ + attrinfo.ai_attrbufpp = &attrptr; + attrinfo.ai_varbufpp = &varptr; + + /* + * Pack entries into attribute buffer. + */ + if (alp->commonattr) { + commonattrpack(&attrinfo, zfsvfs, tmp_zp, zap.za_name, + objnum, vtype, user64); + } + if (alp->dirattr && vtype == VDIR) { + dirattrpack(&attrinfo, tmp_zp); + } + if (alp->fileattr && vtype != VDIR) { + fileattrpack(&attrinfo, zfsvfs, tmp_zp); + } + /* All done with tmp znode. */ + if (prefetch && tmp_zp) { + vnode_put(ZTOV(tmp_zp)); + tmp_zp = NULL; + } + attrbufsize = ((char *)varptr - (char *)attrbufptr); + + /* + * Make sure there's enough buffer space remaining. + */ + if (uio_resid(uio) < 0 || + attrbufsize > (u_int32_t)uio_resid(uio)) { + break; + } else { + *((u_int32_t *)attrbufptr) = attrbufsize; + error = uiomove((caddr_t)attrbufptr, attrbufsize, + UIO_READ, uio); + if (error != 0) + break; + attrptr = attrbufptr; + /* Point to variable-length storage */ + varptr = (char *)attrbufptr + fixedsize; + *(ap->a_actualcount) += 1; + + /* + * Move to the next entry, fill in the previous offset. + */ + skip_entry: + if ((offset > 2) || ((offset == 2) && + !zfs_show_ctldir(zp))) { + zap_cursor_advance(&zc); + offset = zap_cursor_serialize(&zc); + } else { + offset += 1; + } + + /* Termination checks */ + if (--maxcount <= 0 || uio_resid(uio) < 0 || + (u_int32_t)uio_resid(uio) < (fixedsize + + ZAP_AVENAMELEN)) { + break; + } + } + } +update: + zap_cursor_fini(&zc); + + if (attrbufptr) { + kmem_free(attrbufptr, maxsize); + } + if (error == ENOENT) { + error = 0; + } + ZFS_ACCESSTIME_STAMP(zfsvfs, zp); + + /* XXX newstate TBD */ + *ap->a_newstate = zp->z_atime[0] + zp->z_atime[1]; + uio_setoffset(uio, offset); + + zfs_exit(zfsvfs, FTAG); + dprintf("-readdirattr: error %d\n", error); + return (error); +} +#endif + + +#ifdef WITH_SEARCHFS +int +zfs_vnop_searchfs(struct vnop_searchfs_args *ap) +#if 0 + struct vnop_searchfs_args { + struct vnodeop_desc *a_desc; + struct vnode *a_vp; + void *a_searchparams1; + void *a_searchparams2; + struct attrlist *a_searchattrs; + ulong_t a_maxmatches; + struct timeval *a_timelimit; + struct attrlist *a_returnattrs; + ulong_t *a_nummatches; + ulong_t a_scriptcode; + ulong_t a_options; + struct uio *a_uio; + struct searchstate *a_searchstate; + vfs_context_t a_context; + }; +#endif +{ + printf("vnop_searchfs called, type %d\n", vnode_vtype(ap->a_vp)); + + *(ap->a_nummatches) = 0; + + return (ENOTSUP); +} +#endif + +#ifdef VNODE_CLONEFILE_DEFAULT +int +zfs_vnop_clonefile(struct vnop_clonefile_args *ap) +#if 0 + struct vnop_clone_args { + struct vnodeop_desc *a_desc; + vnode_t a_fvp; + vnode_t a_dvp; + vnode_t *a_vpp; + struct componentname *a_cnp; + struct vnode_attr *a_vap; + uint32_t a_flags; + vfs_context_t a_context; + /* funcptr only set for VDIR. VREG it is set to NULL, see XNU */ + int (*a_dir_clone_authorizer)(/* Authorization callback */ + struct vnode_attr *vap, /* attribute to be authorized */ + kauth_action_t action, + struct vnode_attr *dvap, /* target directory attr */ + vnode_t sdvp, /* src dir vnode pointer (optional) */ + mount_t mp, /* mount point of filesystem */ + dir_clone_authorizer_op_t vattr_op, + uint32_t flags, /* needs value passed to a_flags */ + vfs_context_t ctx, /* As passed to VNOP */ + void *reserved); /* Always NULL */ + void *a_reserved; /* Currently unused */ + }; +#endif +{ + DECLARE_CRED(ap); + struct vnode *invp = ap->a_fvp; + znode_t *inzp = NULL; + znode_t *outzp = NULL; + uint64_t inoff = 0ULL; + uint64_t outoff = 0ULL; + uint64_t len = 0ULL; + int error; + struct componentname *cnp = ap->a_cnp; + vattr_t *vap = ap->a_vap; + int mode = 0; /* FIXME */ + + dprintf("+vnop_clonefile\n"); + + if (invp == NULL) + return (SET_ERROR(EINVAL)); + + inzp = VTOZ(invp); + if (inzp == NULL) + return (SET_ERROR(EINVAL)); + + /* XNU clonefile allows DIR and REG */ + if (!vnode_isreg(invp)) + return (SET_ERROR(EINVAL)); + + /* + * It seems FreeBSD is given two vnodes to open/existing files, + * and calls to clone between them. + * XNU gives us the source vnode, and the name of the dest, so + * we create a new entry like zfs_create(). + */ + error = zfs_create(VTOZ(ap->a_dvp), cnp->cn_nameptr, vap, NONEXCL, mode, + &outzp, cr, 0, NULL, NULL); + + if (error != 0) + return (SET_ERROR(error)); + + /* + * Created entry, fix things up like zfs_create. + * Any failure exit out of here should release outzp + */ + + cache_purge_negatives(ap->a_dvp); + + // Also tell XNU what VAPs we handled. + if (VATTR_IS_ACTIVE(vap, va_mode)) + VATTR_SET_SUPPORTED(vap, va_mode); + if (VATTR_IS_ACTIVE(vap, va_data_size)) + VATTR_SET_SUPPORTED(vap, va_data_size); + if (VATTR_IS_ACTIVE(vap, va_type)) + VATTR_SET_SUPPORTED(vap, va_type); + if (VATTR_IS_ACTIVE(vap, va_uid)) + VATTR_SET_SUPPORTED(vap, va_uid); + if (VATTR_IS_ACTIVE(vap, va_gid)) + VATTR_SET_SUPPORTED(vap, va_gid); + if (VATTR_IS_ACTIVE(vap, va_flags)) + VATTR_SET_SUPPORTED(vap, va_flags); + if (VATTR_IS_ACTIVE(vap, va_acl)) + VATTR_SET_SUPPORTED(vap, va_acl); + if (VATTR_IS_ACTIVE(vap, va_create_time)) + VATTR_SET_SUPPORTED(vap, va_create_time); + if (VATTR_IS_ACTIVE(vap, va_access_time)) + VATTR_SET_SUPPORTED(vap, va_access_time); + if (VATTR_IS_ACTIVE(vap, va_modify_time)) + VATTR_SET_SUPPORTED(vap, va_modify_time); + if (VATTR_IS_ACTIVE(vap, va_change_time)) + VATTR_SET_SUPPORTED(vap, va_change_time); + if (VATTR_IS_ACTIVE(vap, va_backup_time)) + VATTR_SET_SUPPORTED(vap, va_backup_time); + + /* Print out any missing VAP flags, so we can fix them */ + uint64_t missing = 0; + missing = + (vap->va_active ^ (vap->va_active & vap->va_supported)); + if (missing != 0) { + dprintf("%s: asked %08llx replied %08llx " + " missing %08llx\n", __func__, + vap->va_active, vap->va_supported, + missing); + } + + + /* Back to clonefile work */ + + + len = inzp->z_size; + + + /* + * zfs_clone_range(znode_t *inzp, uint64_t *inoffp, znode_t *outzp, + * uint64_t *outoffp, uint64_t *lenp, cred_t *cr) + */ + error = zfs_clone_range(inzp, &inoff, outzp, &outoff, + &len, cr); + + if (error == 0) { + *ap->a_vpp = ZTOV(outzp); + } else { + if (outzp != NULL) + zrele(outzp); + + /* clonefile failed, leave a 0-len file, or remove it ? */ + zfs_remove(VTOZ(ap->a_dvp), ap->a_cnp->cn_nameptr, cr, 0); + } + + dprintf("-vnop_clonefile: %d\n", error); + return (error); +} +#endif + +/* + * Predeclare these here so that the compiler assumes that this is an "old + * style" function declaration that does not include arguments so that we won't + * get type mismatch errors in the initializations that follow. + */ +static int zfs_inval(void); +static int zfs_isdir(void); + +static int +zfs_inval() +{ + dprintf("ZFS: Bad vnop: returning EINVAL\n"); + return (EINVAL); +} + +static int +zfs_isdir() +{ + dprintf("ZFS: Bad vnop: returning EISDIR\n"); + return (EISDIR); +} + + +#define VOPFUNC int (*)(void *) + +/* Directory vnode operations template */ +int (**zfs_dvnodeops) (void *); +struct vnodeopv_entry_desc zfs_dvnodeops_template[] = { + {&vnop_default_desc, (VOPFUNC)vn_default_error }, + {&vnop_lookup_desc, (VOPFUNC)zfs_vnop_lookup}, + {&vnop_create_desc, (VOPFUNC)zfs_vnop_create}, + {&vnop_whiteout_desc, (VOPFUNC)zfs_vnop_whiteout}, + {&vnop_mknod_desc, (VOPFUNC)zfs_vnop_mknod}, + {&vnop_open_desc, (VOPFUNC)zfs_vnop_open}, + {&vnop_close_desc, (VOPFUNC)zfs_vnop_close}, + {&vnop_access_desc, (VOPFUNC)zfs_vnop_access}, + {&vnop_getattr_desc, (VOPFUNC)zfs_vnop_getattr}, + {&vnop_setattr_desc, (VOPFUNC)zfs_vnop_setattr}, + {&vnop_read_desc, (VOPFUNC)zfs_isdir}, + {&vnop_write_desc, (VOPFUNC)zfs_isdir}, + {&vnop_ioctl_desc, (VOPFUNC)zfs_vnop_ioctl}, + {&vnop_select_desc, (VOPFUNC)zfs_isdir}, + {&vnop_bwrite_desc, (VOPFUNC)zfs_isdir}, + {&vnop_fsync_desc, (VOPFUNC)zfs_vnop_fsync}, + {&vnop_remove_desc, (VOPFUNC)zfs_vnop_remove}, + {&vnop_link_desc, (VOPFUNC)zfs_vnop_link}, + {&vnop_rename_desc, (VOPFUNC)zfs_vnop_rename}, +#if defined(MAC_OS_X_VERSION_10_12) && \ + (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) + {&vnop_renamex_desc, (VOPFUNC)zfs_vnop_renamex}, +#endif + {&vnop_mkdir_desc, (VOPFUNC)zfs_vnop_mkdir}, + {&vnop_rmdir_desc, (VOPFUNC)zfs_vnop_rmdir}, + {&vnop_symlink_desc, (VOPFUNC)zfs_vnop_symlink}, + {&vnop_readdir_desc, (VOPFUNC)zfs_vnop_readdir}, + {&vnop_inactive_desc, (VOPFUNC)zfs_vnop_inactive}, + {&vnop_reclaim_desc, (VOPFUNC)zfs_vnop_reclaim}, + {&vnop_pathconf_desc, (VOPFUNC)zfs_vnop_pathconf}, + {&vnop_revoke_desc, (VOPFUNC)zfs_vnop_revoke}, + {&vnop_getxattr_desc, (VOPFUNC)zfs_vnop_getxattr}, + {&vnop_setxattr_desc, (VOPFUNC)zfs_vnop_setxattr}, + {&vnop_removexattr_desc, (VOPFUNC)zfs_vnop_removexattr}, + {&vnop_listxattr_desc, (VOPFUNC)zfs_vnop_listxattr}, +#ifdef WITH_READDIRATTR + {&vnop_readdirattr_desc, (VOPFUNC)zfs_vnop_readdirattr}, +#endif +#ifdef WITH_SEARCHFS + {&vnop_searchfs_desc, (VOPFUNC)zfs_vnop_searchfs}, +#endif +#ifdef VNODE_CLONEFILE_DEFAULT + {&vnop_clonefile_desc, (VOPFUNC)zfs_vnop_clonefile}, +#endif + {NULL, (VOPFUNC)NULL } +}; +struct vnodeopv_desc zfs_dvnodeop_opv_desc = +{ &zfs_dvnodeops, zfs_dvnodeops_template }; + +/* Regular file vnode operations template */ +int (**zfs_fvnodeops) (void *); +struct vnodeopv_entry_desc zfs_fvnodeops_template[] = { + {&vnop_default_desc, (VOPFUNC)vn_default_error }, + {&vnop_whiteout_desc, (VOPFUNC)zfs_vnop_whiteout}, + {&vnop_open_desc, (VOPFUNC)zfs_vnop_open}, + {&vnop_close_desc, (VOPFUNC)zfs_vnop_close}, + {&vnop_access_desc, (VOPFUNC)zfs_vnop_access}, + {&vnop_getattr_desc, (VOPFUNC)zfs_vnop_getattr}, + {&vnop_setattr_desc, (VOPFUNC)zfs_vnop_setattr}, + {&vnop_read_desc, (VOPFUNC)zfs_vnop_read}, + {&vnop_write_desc, (VOPFUNC)zfs_vnop_write}, + {&vnop_ioctl_desc, (VOPFUNC)zfs_vnop_ioctl}, + {&vnop_select_desc, (VOPFUNC)zfs_vnop_select}, + {&vnop_fsync_desc, (VOPFUNC)zfs_vnop_fsync}, + {&vnop_inactive_desc, (VOPFUNC)zfs_vnop_inactive}, + {&vnop_reclaim_desc, (VOPFUNC)zfs_vnop_reclaim}, + {&vnop_pathconf_desc, (VOPFUNC)zfs_vnop_pathconf}, + {&vnop_bwrite_desc, (VOPFUNC)zfs_inval}, + {&vnop_pagein_desc, (VOPFUNC)zfs_vnop_pagein}, +#if HAVE_PAGEOUT_V2 + {&vnop_pageout_desc, (VOPFUNC)zfs_vnop_pageoutv2}, +#else + {&vnop_pageout_desc, (VOPFUNC)zfs_vnop_pageout}, +#endif + {&vnop_mmap_desc, (VOPFUNC)zfs_vnop_mmap}, + {&vnop_mnomap_desc, (VOPFUNC)zfs_vnop_mnomap}, + {&vnop_blktooff_desc, (VOPFUNC)zfs_vnop_blktooff}, + {&vnop_offtoblk_desc, (VOPFUNC)zfs_vnop_offtoblk}, + {&vnop_blockmap_desc, (VOPFUNC)zfs_vnop_blockmap}, + {&vnop_strategy_desc, (VOPFUNC)zfs_vnop_strategy}, + {&vnop_allocate_desc, (VOPFUNC)zfs_vnop_allocate}, + {&vnop_revoke_desc, (VOPFUNC)zfs_vnop_revoke}, + {&vnop_exchange_desc, (VOPFUNC)zfs_vnop_exchange}, + {&vnop_getxattr_desc, (VOPFUNC)zfs_vnop_getxattr}, + {&vnop_setxattr_desc, (VOPFUNC)zfs_vnop_setxattr}, + {&vnop_removexattr_desc, (VOPFUNC)zfs_vnop_removexattr}, + {&vnop_listxattr_desc, (VOPFUNC)zfs_vnop_listxattr}, +#ifdef HAVE_NAMED_STREAMS + {&vnop_getnamedstream_desc, (VOPFUNC)zfs_vnop_getnamedstream}, + {&vnop_makenamedstream_desc, (VOPFUNC)zfs_vnop_makenamedstream}, + {&vnop_removenamedstream_desc, (VOPFUNC)zfs_vnop_removenamedstream}, +#endif +#ifdef WITH_SEARCHFS + {&vnop_searchfs_desc, (VOPFUNC)zfs_vnop_searchfs}, +#endif +#ifdef VNODE_CLONEFILE_DEFAULT + {&vnop_clonefile_desc, (VOPFUNC)zfs_vnop_clonefile}, +#endif + {NULL, (VOPFUNC)NULL } +}; +struct vnodeopv_desc zfs_fvnodeop_opv_desc = +{ &zfs_fvnodeops, zfs_fvnodeops_template }; + +/* Symbolic link vnode operations template */ +int (**zfs_symvnodeops) (void *); +struct vnodeopv_entry_desc zfs_symvnodeops_template[] = { + {&vnop_default_desc, (VOPFUNC)vn_default_error }, + {&vnop_open_desc, (VOPFUNC)zfs_vnop_open}, + {&vnop_close_desc, (VOPFUNC)zfs_vnop_close}, + {&vnop_access_desc, (VOPFUNC)zfs_vnop_access}, + {&vnop_getattr_desc, (VOPFUNC)zfs_vnop_getattr}, + {&vnop_setattr_desc, (VOPFUNC)zfs_vnop_setattr}, + {&vnop_ioctl_desc, (VOPFUNC)zfs_vnop_ioctl}, + {&vnop_readlink_desc, (VOPFUNC)zfs_vnop_readlink}, + {&vnop_inactive_desc, (VOPFUNC)zfs_vnop_inactive}, + {&vnop_reclaim_desc, (VOPFUNC)zfs_vnop_reclaim}, + {&vnop_pathconf_desc, (VOPFUNC)zfs_vnop_pathconf}, + {&vnop_revoke_desc, (VOPFUNC)zfs_vnop_revoke}, + {&vnop_getxattr_desc, (VOPFUNC)zfs_vnop_getxattr}, + {&vnop_setxattr_desc, (VOPFUNC)zfs_vnop_setxattr}, + {&vnop_removexattr_desc, (VOPFUNC)zfs_vnop_removexattr}, + {&vnop_listxattr_desc, (VOPFUNC)zfs_vnop_listxattr}, + {NULL, (VOPFUNC)NULL } +}; +struct vnodeopv_desc zfs_symvnodeop_opv_desc = +{ &zfs_symvnodeops, zfs_symvnodeops_template }; + +/* Extended attribtue directory vnode operations template */ +int (**zfs_xdvnodeops) (void *); +struct vnodeopv_entry_desc zfs_xdvnodeops_template[] = { + {&vnop_default_desc, (VOPFUNC)vn_default_error }, + {&vnop_lookup_desc, (VOPFUNC)zfs_vnop_lookup}, + {&vnop_create_desc, (VOPFUNC)zfs_vnop_create}, + {&vnop_whiteout_desc, (VOPFUNC)zfs_vnop_whiteout}, + {&vnop_mknod_desc, (VOPFUNC)zfs_inval}, + {&vnop_open_desc, (VOPFUNC)zfs_vnop_open}, + {&vnop_close_desc, (VOPFUNC)zfs_vnop_close}, + {&vnop_access_desc, (VOPFUNC)zfs_vnop_access}, + {&vnop_getattr_desc, (VOPFUNC)zfs_vnop_getattr}, + {&vnop_setattr_desc, (VOPFUNC)zfs_vnop_setattr}, + {&vnop_read_desc, (VOPFUNC)zfs_vnop_read}, + {&vnop_write_desc, (VOPFUNC)zfs_vnop_write}, + {&vnop_ioctl_desc, (VOPFUNC)zfs_vnop_ioctl}, + {&vnop_select_desc, (VOPFUNC)zfs_vnop_select}, + {&vnop_fsync_desc, (VOPFUNC)zfs_vnop_fsync}, + {&vnop_remove_desc, (VOPFUNC)zfs_vnop_remove}, + {&vnop_link_desc, (VOPFUNC)zfs_vnop_link}, + {&vnop_rename_desc, (VOPFUNC)zfs_vnop_rename}, + {&vnop_mkdir_desc, (VOPFUNC)zfs_inval}, + {&vnop_rmdir_desc, (VOPFUNC)zfs_vnop_rmdir}, + {&vnop_symlink_desc, (VOPFUNC)zfs_inval}, + {&vnop_readdir_desc, (VOPFUNC)zfs_vnop_readdir}, + {&vnop_inactive_desc, (VOPFUNC)zfs_vnop_inactive}, + {&vnop_reclaim_desc, (VOPFUNC)zfs_vnop_reclaim}, + {&vnop_pathconf_desc, (VOPFUNC)zfs_vnop_pathconf}, + {NULL, (VOPFUNC)NULL } +}; +struct vnodeopv_desc zfs_xdvnodeop_opv_desc = +{ &zfs_xdvnodeops, zfs_xdvnodeops_template }; + +/* Error vnode operations template */ +int (**zfs_evnodeops) (void *); +struct vnodeopv_entry_desc zfs_evnodeops_template[] = { + {&vnop_default_desc, (VOPFUNC)vn_default_error }, + {&vnop_inactive_desc, (VOPFUNC)zfs_vnop_inactive}, + {&vnop_reclaim_desc, (VOPFUNC)zfs_vnop_reclaim}, + {&vnop_pathconf_desc, (VOPFUNC)zfs_vnop_pathconf}, + {NULL, (VOPFUNC)NULL } +}; +struct vnodeopv_desc zfs_evnodeop_opv_desc = +{ &zfs_evnodeops, zfs_evnodeops_template }; + +int (**zfs_fifonodeops)(void *); +struct vnodeopv_entry_desc zfs_fifonodeops_template[] = { + { &vnop_default_desc, (VOPFUNC)vn_default_error }, + { &vnop_lookup_desc, (VOPFUNC)fifo_lookup }, + { &vnop_create_desc, (VOPFUNC)fifo_create }, + { &vnop_mknod_desc, (VOPFUNC)fifo_mknod }, + { &vnop_open_desc, (VOPFUNC)fifo_open }, + { &vnop_close_desc, (VOPFUNC)fifo_close }, + { &vnop_getattr_desc, (VOPFUNC)zfs_vnop_getattr }, + { &vnop_setattr_desc, (VOPFUNC)zfs_vnop_setattr }, + { &vnop_read_desc, (VOPFUNC)fifo_read }, + { &vnop_write_desc, (VOPFUNC)fifo_write }, + { &vnop_ioctl_desc, (VOPFUNC)fifo_ioctl }, + { &vnop_select_desc, (VOPFUNC)fifo_select }, + { &vnop_revoke_desc, (VOPFUNC)fifo_revoke }, + { &vnop_mmap_desc, (VOPFUNC)fifo_mmap }, + { &vnop_fsync_desc, (VOPFUNC)zfs_vnop_fsync }, + { &vnop_remove_desc, (VOPFUNC)fifo_remove }, + { &vnop_link_desc, (VOPFUNC)fifo_link }, + { &vnop_rename_desc, (VOPFUNC)fifo_rename }, + { &vnop_mkdir_desc, (VOPFUNC)fifo_mkdir }, + { &vnop_rmdir_desc, (VOPFUNC)fifo_rmdir }, + { &vnop_symlink_desc, (VOPFUNC)fifo_symlink }, + { &vnop_readdir_desc, (VOPFUNC)fifo_readdir }, + { &vnop_readlink_desc, (VOPFUNC)fifo_readlink }, + { &vnop_inactive_desc, (VOPFUNC)zfs_vnop_inactive }, + { &vnop_reclaim_desc, (VOPFUNC)zfs_vnop_reclaim }, + { &vnop_strategy_desc, (VOPFUNC)fifo_strategy }, + { &vnop_pathconf_desc, (VOPFUNC)fifo_pathconf }, + { &vnop_advlock_desc, (VOPFUNC)err_advlock }, + { &vnop_bwrite_desc, (VOPFUNC)zfs_inval }, + { &vnop_pagein_desc, (VOPFUNC)zfs_vnop_pagein }, +#if HAVE_PAGEOUT_V2 + { &vnop_pageout_desc, (VOPFUNC)zfs_vnop_pageoutv2 }, +#else + { &vnop_pageout_desc, (VOPFUNC)zfs_vnop_pageout }, +#endif + { &vnop_copyfile_desc, (VOPFUNC)err_copyfile }, + { &vnop_blktooff_desc, (VOPFUNC)zfs_vnop_blktooff }, + { &vnop_offtoblk_desc, (VOPFUNC)zfs_vnop_offtoblk }, + { &vnop_blockmap_desc, (VOPFUNC)zfs_vnop_blockmap }, + { &vnop_getxattr_desc, (VOPFUNC)zfs_vnop_getxattr}, + { &vnop_setxattr_desc, (VOPFUNC)zfs_vnop_setxattr}, + { &vnop_removexattr_desc, (VOPFUNC)zfs_vnop_removexattr}, + { &vnop_listxattr_desc, (VOPFUNC)zfs_vnop_listxattr}, + { (struct vnodeop_desc *)NULL, (VOPFUNC)NULL } +}; +struct vnodeopv_desc zfs_fifonodeop_opv_desc = + { &zfs_fifonodeops, zfs_fifonodeops_template }; + + +/* + * .zfs/snapdir vnops + */ +int (**zfs_ctldirops) (void *); +struct vnodeopv_entry_desc zfs_ctldir_template[] = { + {&vnop_default_desc, (VOPFUNC)vn_default_error }, + {&vnop_lookup_desc, (VOPFUNC)zfsctl_vnop_lookup}, + {&vnop_getattr_desc, (VOPFUNC)zfsctl_vnop_getattr}, + {&vnop_readdir_desc, (VOPFUNC)zfsctl_vnop_readdir}, + {&vnop_mkdir_desc, (VOPFUNC)zfsctl_vnop_mkdir}, + {&vnop_rmdir_desc, (VOPFUNC)zfsctl_vnop_rmdir}, + /* We also need to define these for the top ones to work */ + {&vnop_open_desc, (VOPFUNC)zfsctl_vnop_open}, + {&vnop_close_desc, (VOPFUNC)zfsctl_vnop_close}, + {&vnop_access_desc, (VOPFUNC)zfsctl_vnop_access}, + {&vnop_inactive_desc, (VOPFUNC)zfsctl_vnop_inactive}, + {&vnop_reclaim_desc, (VOPFUNC)zfsctl_vnop_reclaim}, + {&vnop_revoke_desc, (VOPFUNC)err_revoke}, + {&vnop_fsync_desc, (VOPFUNC)nop_fsync}, + {NULL, (VOPFUNC)NULL } +}; +struct vnodeopv_desc zfs_ctldir_opv_desc = +{ &zfs_ctldirops, zfs_ctldir_template }; + +/* + * Get new vnode for znode. + * + * This function uses zp->z_zfsvfs, zp->z_mode, zp->z_flags, zp->z_id and sets + * zp->z_vnode and zp->z_vid. + */ +int +zfs_znode_getvnode(znode_t *zp, zfsvfs_t *zfsvfs) +{ + struct vnode_fsparam vfsp; + struct vnode *vp = NULL; + + dprintf("getvnode zp %p with vp %p zfsvfs %p vfs %p\n", zp, vp, + zfsvfs, zfsvfs->z_vfs); + + if (zp->z_vnode) + panic("zp %p vnode already set\n", zp->z_vnode); + + memset(&vfsp, 0, sizeof (vfsp)); + vfsp.vnfs_str = "zfs"; + vfsp.vnfs_mp = zfsvfs->z_vfs; + vfsp.vnfs_vtype = IFTOVT((mode_t)zp->z_mode); + vfsp.vnfs_fsnode = zp; + vfsp.vnfs_flags = VNFS_ADDFSREF; + + /* Tag root directory */ + if (zp->z_id == zfsvfs->z_root) { + vfsp.vnfs_markroot = 1; + } + + switch (vfsp.vnfs_vtype) { + case VDIR: + if (zp->z_pflags & ZFS_XATTR) { + vfsp.vnfs_vops = zfs_xdvnodeops; + } else { + vfsp.vnfs_vops = zfs_dvnodeops; + } + zp->z_zn_prefetch = B_TRUE; /* z_prefetch default is enabled */ + break; + case VBLK: + case VCHR: + { + uint64_t rdev; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_RDEV(zfsvfs), + &rdev, sizeof (rdev)) == 0); + + vfsp.vnfs_rdev = zfs_cmpldev(rdev); + } + /* FALLTHROUGH */ + case VSOCK: + vfsp.vnfs_vops = zfs_fvnodeops; + break; + case VFIFO: + vfsp.vnfs_vops = zfs_fifonodeops; + break; + case VREG: + vfsp.vnfs_vops = zfs_fvnodeops; + vfsp.vnfs_filesize = zp->z_size; + break; + case VLNK: + vfsp.vnfs_vops = zfs_symvnodeops; +#if 0 + vfsp.vnfs_filesize = ???; +#endif + break; + default: + vfsp.vnfs_vops = zfs_fvnodeops; + printf("ZFS: Warning, error-vnops selected: vtype %d\n", + vfsp.vnfs_vtype); + break; + } + + /* + * vnode_create() has a habit of calling both vnop_reclaim() and + * vnop_fsync(), which can create havok as we are already holding locks. + */ + + while (vnode_create(VNCREATE_FLAVOR, VCREATESIZE, &vfsp, &vp) != 0) { + kpreempt(KPREEMPT_SYNC); + } + atomic_inc_64(&vnop_num_vnodes); + + dprintf("Assigned zp %p with vp %p zfsvfs %p\n", zp, vp, zp->z_zfsvfs); + + /* + * Unfortunately, when it comes to IOCTL_GET_BOOT_INFO and getting + * the volume finderinfo, XNU checks the tags, and only acts on + * HFS. So we have to set it to HFS on the root. It is pretty gross + * but until XNU adds supporting code.. + * We no longer use tags in ZFS. + */ + if (zp->z_id == zfsvfs->z_root) + vnode_settag(vp, VT_HFS); + else + vnode_settag(vp, VT_ZFS); + + zp->z_vid = vnode_vid(vp); + zp->z_vnode = vp; + + /* + * OS X Finder is hardlink agnostic, so we need to mark vp's that + * are hardlinks, so that it forces a lookup each time, ignoring + * the name cache. + */ + if ((zp->z_links > 1) && (IFTOVT((mode_t)zp->z_mode) == VREG)) + vnode_setmultipath(vp); + + return (0); +} + + +/* + * Called by taskq, to call zfs_znode_getvnode( vnode_create( - and + * attach vnode to znode. + */ +void +zfs_znode_asyncgetvnode_impl(void *arg) +{ + znode_t *zp = (znode_t *)arg; + VERIFY3P(zp, !=, NULL); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + VERIFY3P(zfsvfs, !=, NULL); + + // Attach vnode, done as different thread + zfs_znode_getvnode(zp, zfsvfs); + + // Wake up anyone blocked on us + mutex_enter(&zp->z_attach_lock); + taskq_init_ent(&zp->z_attach_taskq); + cv_broadcast(&zp->z_attach_cv); + mutex_exit(&zp->z_attach_lock); + +} + + +/* + * If the znode's vnode is not yet attached (zp->z_vnode == NULL) + * we call taskq_wait to wait for it to complete. + * We guarantee znode has a vnode at the return of function only + * when return is "0". On failure to wait, it returns -1, and caller + * may consider waiting by other means. + */ +int +zfs_znode_asyncwait(zfsvfs_t *zfsvfs, znode_t *zp) +{ + int ret = -1; + int error = 0; + + if (zp == NULL) + return (ret); + + if (zfsvfs == NULL) + return (ret); + + if ((error = zfs_enter(zfsvfs, FTAG)) != 0) + return (ret); + + if (zfsvfs->z_os == NULL) + goto out; + + // Work out if we need to block, that is, we have + // no vnode AND a taskq was launched. Unsure if we should + // look inside taskqent node like this. + mutex_enter(&zp->z_attach_lock); + if (zp->z_vnode == NULL && + zp->z_attach_taskq.tqent_func != NULL) { + // We need to block and wait for taskq to finish. + cv_wait(&zp->z_attach_cv, &zp->z_attach_lock); + ret = 0; + } + mutex_exit(&zp->z_attach_lock); + +out: + zfs_exit(zfsvfs, FTAG); + return (ret); +} + +/* + * Called in place of VN_RELE() for the places that uses ZGET_FLAG_ASYNC. + */ +void +zfs_znode_asyncput_impl(znode_t *zp) +{ + // Make sure the other thread finished zfs_znode_getvnode(); + // This may block, if waiting is required. + zfs_znode_asyncwait(zp->z_zfsvfs, zp); + + // Safe to release now that it is attached. + VN_RELE(ZTOV(zp)); + +} + +/* + * Called in place of VN_RELE() for the places that uses ZGET_FLAG_ASYNC, + * where we also taskq it - as we come from reclaim. + */ +void +zfs_znode_asyncput(znode_t *zp) +{ + dsl_pool_t *dp = dmu_objset_pool(zp->z_zfsvfs->z_os); + taskq_t *tq = dsl_pool_zrele_taskq(dp); + vnode_t *vp = ZTOV(zp); + + VERIFY3P(tq, !=, NULL); + + /* If iocount > 1, AND, vp is set (not async_get) */ + if (vp != NULL && vnode_iocount(vp) > 1) { + VN_RELE(vp); + return; + } + + VERIFY(taskq_dispatch( + (taskq_t *)tq, + (task_func_t *)zfs_znode_asyncput_impl, zp, TQ_SLEEP) != 0); +} + +/* + * Attach a new vnode to the znode asynchronically. We do this using + * a taskq to call it, and then wait to release the iocount. + * Called of zget_ext(..., ZGET_FLAG_ASYNC); will use + * zfs_znode_asyncput(zp) instead of VN_RELE(vp). + */ +int +zfs_znode_asyncgetvnode(znode_t *zp, zfsvfs_t *zfsvfs) +{ + VERIFY(zp != NULL); + VERIFY(zfsvfs != NULL); + + // We should not have a vnode here. + VERIFY3P(ZTOV(zp), ==, NULL); + + dsl_pool_t *dp = dmu_objset_pool(zfsvfs->z_os); + taskq_t *tq = dsl_pool_zrele_taskq(dp); + VERIFY3P(tq, !=, NULL); + + mutex_enter(&zp->z_attach_lock); + taskq_dispatch_ent(tq, + (task_func_t *)zfs_znode_asyncgetvnode_impl, + zp, + TQ_SLEEP, + &zp->z_attach_taskq); + mutex_exit(&zp->z_attach_lock); + return (0); +} + + + +/* + * Maybe these should live in vfsops + */ +int +zfs_vfsops_init(void) +{ + struct vfs_fsentry vfe; + + vnop_lookup_cache = kmem_cache_create("zfs_vnop_lookup", + MAXPATHLEN, 0, NULL, NULL, NULL, NULL, NULL, 0); + + /* Start thread to notify Finder of changes */ + zfs_start_notify_thread(); + + vfe.vfe_vfsops = &zfs_vfsops_template; + vfe.vfe_vopcnt = ZFS_VNOP_TBL_CNT; + vfe.vfe_opvdescs = zfs_vnodeop_opv_desc_list; + + strlcpy(vfe.vfe_fsname, "zfs", MFSNAMELEN); + + /* + * Note: must set VFS_TBLGENERICMNTARGS with VFS_TBLLOCALVOL + * to suppress local mount argument handling. + */ + vfe.vfe_flags = VFS_TBLTHREADSAFE | VFS_TBLNOTYPENUM | VFS_TBLLOCALVOL | + VFS_TBL64BITREADY | VFS_TBLNATIVEXATTR | VFS_TBLGENERICMNTARGS | + VFS_TBLREADDIR_EXTENDED; + +#if HAVE_PAGEOUT_V2 + vfe.vfe_flags |= VFS_TBLVNOP_PAGEOUTV2; +#endif + +#ifdef VFS_TBLCANMOUNTROOT // From 10.12 + vfe.vfe_flags |= VFS_TBLCANMOUNTROOT; +#endif + + vfe.vfe_reserv[0] = 0; + vfe.vfe_reserv[1] = 0; + + if (vfs_fsadd(&vfe, &zfs_vfsconf) != 0) + return (KERN_FAILURE); + else + return (KERN_SUCCESS); +} + +int +zfs_vfsops_fini(void) +{ + + zfs_stop_notify_thread(); + + if (vnop_lookup_cache) + kmem_cache_destroy(vnop_lookup_cache); + vnop_lookup_cache = NULL; + + return (vfs_fsremove(zfs_vfsconf)); +} diff --git a/module/os/macos/zfs/zfs_vnops_osx_lib.c b/module/os/macos/zfs/zfs_vnops_osx_lib.c new file mode 100644 index 000000000000..20cc7f8e1f43 --- /dev/null +++ b/module/os/macos/zfs/zfs_vnops_osx_lib.c @@ -0,0 +1,2151 @@ +/* + * 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 Will Andrews + * Copyright (c) 2013, 2020 Jorgen Lundman + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +extern int zfs_vnop_force_formd_normalized_output; /* disabled by default */ + +static uint32_t zfs_hardlink_sequence = 1ULL<<31; + +/* + * Unfortunately Apple defines "KAUTH_VNODE_ACCESS (1<<31)" which + * generates: "warning: signed shift result (0x80000000) sets the + * sign bit of the shift expression's type ('int') and becomes negative." + * So until they fix their define, we override it here. + */ + +#if KAUTH_VNODE_ACCESS == 0x80000000 +#undef KAUTH_VNODE_ACCESS +#define KAUTH_VNODE_ACCESS (1ULL<<31) +#endif + + + +int zfs_hardlink_addmap(znode_t *zp, uint64_t parentid, uint32_t linkid); + +/* Originally from illumos:uts/common/sys/vfs.h */ +typedef uint64_t vfs_feature_t; +#define VFSFT_XVATTR 0x100000001 /* Supports xvattr for attrs */ +#define VFSFT_CASEINSENSITIVE 0x100000002 /* Supports case-insensitive */ +#define VFSFT_NOCASESENSITIVE 0x100000004 /* NOT case-sensitive */ +#define VFSFT_DIRENTFLAGS 0x100000008 /* Supports dirent flags */ +#define VFSFT_ACLONCREATE 0x100000010 /* Supports ACL on create */ +#define VFSFT_ACEMASKONACCESS 0x100000020 /* Can use ACEMASK for access */ +#define VFSFT_SYSATTR_VIEWS 0x100000040 /* Supports sysattr view i/f */ +#define VFSFT_ACCESS_FILTER 0x100000080 /* dirents filtered by access */ +#define VFSFT_REPARSE 0x100000100 /* Supports reparse point */ +#define VFSFT_ZEROCOPY_SUPPORTED 0x100000200 /* Supports loaning buffers */ + +/* + * fnv_32a_str - perform a 32 bit Fowler/Noll/Vo FNV-1a hash on a string + * + * input: + * str - string to hash + * hval - previous hash value or 0 if first call + * + * returns: + * 32 bit hash as a static hash type + * + * NOTE: To use the recommended 32 bit FNV-1a hash, use FNV1_32A_INIT as the + * hval arg on the first call to either fnv_32a_buf() or fnv_32a_str(). + */ +uint32_t +fnv_32a_str(const char *str, uint32_t hval) +{ + unsigned char *s = (unsigned char *)str; /* unsigned string */ + + /* + * FNV-1a hash each octet in the buffer + */ + while (*s) { + + /* xor the bottom with the current octet */ + hval ^= (uint32_t)*s++; + + /* multiply by the 32 bit FNV magic prime mod 2^32 */ +#if defined(NO_FNV_GCC_OPTIMIZATION) + hval *= FNV_32_PRIME; +#else + hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + + (hval<<24); +#endif + } + + /* return our new hash value */ + return (hval); +} + +/* + * fnv_32a_buf - perform a 32 bit Fowler/Noll/Vo FNV-1a hash on a buffer + * + * input: + * buf- start of buffer to hash + * len- length of buffer in octets + * hval- previous hash value or 0 if first call + * + * returns: + * 32 bit hash as a static hash type + * + * NOTE: To use the recommended 32 bit FNV-1a hash, use FNV1_32A_INIT as the + * hval arg on the first call to either fnv_32a_buf() or fnv_32a_str(). + */ +uint32_t +fnv_32a_buf(void *buf, size_t len, uint32_t hval) +{ + unsigned char *bp = (unsigned char *)buf; /* start of buffer */ + unsigned char *be = bp + len; /* beyond end of buffer */ + + /* + * FNV-1a hash each octet in the buffer + */ + while (bp < be) { + + /* xor the bottom with the current octet */ + hval ^= (uint32_t)*bp++; + + /* multiply by the 32 bit FNV magic prime mod 2^32 */ +#if defined(NO_FNV_GCC_OPTIMIZATION) + hval *= FNV_32_PRIME; +#else + hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + + (hval<<24); +#endif + } + + /* return our new hash value */ + return (hval); +} + +int +zfs_getattr_znode_unlocked(struct vnode *vp, vattr_t *vap) +{ + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + int error = 0; + uint64_t parent; + sa_bulk_attr_t bulk[4]; + int count = 0; +#ifdef VNODE_ATTR_va_addedtime + uint64_t addtime[2] = { 0 }; +#endif + int ishardlink = 0; + + // printf("getattr_osx\n"); + + if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) + return (error); + + // If wanted, return NULL guids + if (VATTR_IS_ACTIVE(vap, va_uuuid)) + VATTR_RETURN(vap, va_uuuid, kauth_null_guid); + if (VATTR_IS_ACTIVE(vap, va_guuid)) + VATTR_RETURN(vap, va_guuid, kauth_null_guid); + +#if 0 // Issue #192 + if (VATTR_IS_ACTIVE(vap, va_uuuid)) { + kauth_cred_uid2guid(zp->z_uid, &vap->va_uuuid); + VATTR_RETURN(vap, va_uuuid, vap->va_uuuid); + } + if (VATTR_IS_ACTIVE(vap, va_guuid)) { + kauth_cred_gid2guid(zp->z_gid, &vap->va_guuid); + VATTR_RETURN(vap, va_guuid, vap->va_guuid); + } +#endif + + // But if we are to check acl, can fill in guids + if (VATTR_IS_ACTIVE(vap, va_acl)) { + // dprintf("Calling getacl\n"); + if ((error = zfs_macos_getacl(zp, &vap->va_acl, B_TRUE, + NULL))) { + // dprintf("zfs_getacl returned error %d\n", error); + error = 0; + } else { + VATTR_SET_SUPPORTED(vap, va_acl); + } + } + + mutex_enter(&zp->z_lock); + + ishardlink = ((zp->z_links > 1) && + (IFTOVT((mode_t)zp->z_mode) == VREG)) ? 1 : 0; + if (zp->z_finder_hardlink == TRUE) + ishardlink = 1; + else if (ishardlink) + zp->z_finder_hardlink = TRUE; + + /* Work out which SA we need to fetch */ + + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_PARENT(zfsvfs), NULL, &parent, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, + &zp->z_pflags, 8); + + /* + * Unfortunately, sa_bulk_lookup does not let you handle optional + * SA entries - so have to look up the optionals individually. + */ + error = sa_bulk_lookup(zp->z_sa_hdl, bulk, count); + if (error) { + dprintf("ZFS: Warning: getattr failed sa_bulk_lookup: %d, " + "parent %llu, flags %llu\n", error, parent, zp->z_pflags); + mutex_exit(&zp->z_lock); + zfs_exit(zfsvfs, FTAG); + return (0); + } + + /* + * On Mac OS X we always export the root directory id as 2 + */ + vap->va_fileid = INO_ZFSTOXNU(zp->z_id, zfsvfs->z_root); + + vap->va_data_size = zp->z_size; + vap->va_total_size = zp->z_size; + if (zp->z_gen == 0) + zp->z_gen = 1; + vap->va_gen = zp->z_gen; + +#if defined(DEBUG) || defined(ZFS_DEBUG) + if (zp->z_gen != 0) + dprintf("%s: va_gen %lld -> 0\n", __func__, zp->z_gen); +#endif + + vap->va_nlink = zp->z_links; + + /* + * Carbon compatibility, pretend to support this legacy attribute + */ + if (VATTR_IS_ACTIVE(vap, va_backup_time)) { + vap->va_backup_time.tv_sec = 0; + vap->va_backup_time.tv_nsec = 0; + VATTR_SET_SUPPORTED(vap, va_backup_time); + } + + vap->va_flags = zfs_getbsdflags(zp); + + /* + * On Mac OS X we always export the root directory id as 2 + * and its parent as 1 + */ + if (zp->z_id == zfsvfs->z_root) + vap->va_parentid = 1; + else if (parent == zfsvfs->z_root) + vap->va_parentid = 2; + else + vap->va_parentid = INO_ZFSTOXNU(parent, zfsvfs->z_root); + + // Hardlinks: Return cached parentid, make it 2 if root. + if (ishardlink && zp->z_finder_parentid) + vap->va_parentid = + INO_ZFSTOXNU(zp->z_finder_parentid, zfsvfs->z_root); + + vap->va_iosize = zp->z_blksz ? zp->z_blksz : zfsvfs->z_max_blksz; + // vap->va_iosize = 512; + if (VATTR_IS_ACTIVE(vap, va_iosize)) + VATTR_SET_SUPPORTED(vap, va_iosize); + + /* Don't include '.' and '..' in the number of entries */ + if (VATTR_IS_ACTIVE(vap, va_nchildren) && vnode_isdir(vp)) { + VATTR_RETURN(vap, va_nchildren, vap->va_nlink - 2); + } + + /* + * va_dirlinkcount is the count of directory hard links. When a file + * system does not support ATTR_DIR_LINKCOUNT, xnu will default to 1. + * Since we claim to support ATTR_DIR_LINKCOUNT both as valid and as + * native, we'll just return 1. We set 1 for this value in dirattrpack + * as well. If in the future ZFS actually supports directory hard links, + * we can return a real value. + */ + if (VATTR_IS_ACTIVE(vap, va_dirlinkcount) /* && vnode_isdir(vp) */) { + VATTR_RETURN(vap, va_dirlinkcount, 1); + } + + + if (VATTR_IS_ACTIVE(vap, va_data_alloc) || + VATTR_IS_ACTIVE(vap, va_total_alloc)) { + uint32_t blksize; + u_longlong_t nblks; + sa_object_size(zp->z_sa_hdl, &blksize, &nblks); + vap->va_data_alloc = (uint64_t)512LL * (uint64_t)nblks; + vap->va_total_alloc = vap->va_data_alloc; + vap->va_supported |= VNODE_ATTR_va_data_alloc | + VNODE_ATTR_va_total_alloc; + } + + if (VATTR_IS_ACTIVE(vap, va_name)) { + vap->va_name[0] = 0; + + if (!vnode_isvroot(vp)) { + + /* + * Finder (Carbon) relies on getattr returning the + * correct name for hardlinks to work, so we store the + * lookup name in vnop_lookup if file references are + * high, then set the return name here. + * If we also want ATTR_CMN_* lookups to work, we need + * to set a unique va_linkid for each entry, and based + * on the linkid in the lookup, return the correct name. + * It is set in zfs_vnop_lookup(). + * Since zap_value_search is a slow call, we only use + * it if we have not cached the name in vnop_lookup. + */ + + // Cached name, from vnop_lookup + if (ishardlink && + zp->z_name_cache[0]) { + + strlcpy(vap->va_name, zp->z_name_cache, + MAXPATHLEN); + VATTR_SET_SUPPORTED(vap, va_name); + + } else if (zp->z_name_cache[0]) { + + strlcpy(vap->va_name, zp->z_name_cache, + MAXPATHLEN); + VATTR_SET_SUPPORTED(vap, va_name); + + } else { + + // Go find the name. + if (zap_value_search(zfsvfs->z_os, parent, + zp->z_id, ZFS_DIRENT_OBJ(-1ULL), + vap->va_name) == 0) { + VATTR_SET_SUPPORTED(vap, va_name); + // Might as well keep this name too. + strlcpy(zp->z_name_cache, vap->va_name, + MAXPATHLEN); + } // zap_value_search + + } + + dprintf("getattr: %p return name '%s':%04llx\n", vp, + vap->va_name, vap->va_linkid); + + + } else { + /* + * The vroot objects must return a unique name for + * Finder to be able to distringuish between mounts. + * For this reason we simply return the fullname, + * from the statfs mountedfrom + * + * dataset mountpoint + * foo /bar + * As we used to return "foo" to ATTR_CMN_NAME of + * "/bar" we change this to return "bar" as expected. + */ + char *r, *osname; + osname = vfs_statfs(zfsvfs->z_vfs)->f_mntonname; + r = strrchr(osname, '/'); + strlcpy(vap->va_name, + r ? &r[1] : osname, + MAXPATHLEN); + VATTR_SET_SUPPORTED(vap, va_name); + dprintf("getattr root returning '%s'\n", vap->va_name); + } + } + + if (VATTR_IS_ACTIVE(vap, va_linkid)) { + + /* + * Apple needs a little extra care with HARDLINKs. All hardlink + * targets return the same va_fileid (POSIX) but also return + * a unique va_linkid. This we generate by hashing the (unique) + * name and store as va_linkid. However, Finder will call + * vfs_vget() with linkid and expect to receive the correct link + * target, so we need to add it to the AVL z_hardlinks. + */ + if (ishardlink) { + hardlinks_t *searchnode, *findnode; + avl_index_t loc; + + // If we don't have a linkid, make one. + searchnode = kmem_alloc(sizeof (hardlinks_t), KM_SLEEP); + searchnode->hl_parent = + INO_XNUTOZFS(vap->va_parentid, zfsvfs->z_root); + searchnode->hl_fileid = zp->z_id; + strlcpy(searchnode->hl_name, zp->z_name_cache, + PATH_MAX); + + rw_enter(&zfsvfs->z_hardlinks_lock, RW_READER); + findnode = avl_find(&zfsvfs->z_hardlinks, searchnode, + &loc); + rw_exit(&zfsvfs->z_hardlinks_lock); + kmem_free(searchnode, sizeof (hardlinks_t)); + + if (!findnode) { + uint32_t id; + + id = atomic_inc_32_nv(&zfs_hardlink_sequence); + + zfs_hardlink_addmap(zp, vap->va_parentid, id); + if (VATTR_IS_ACTIVE(vap, va_linkid)) + VATTR_RETURN(vap, va_linkid, id); + + } else { + VATTR_RETURN(vap, va_linkid, + findnode->hl_linkid); + } + + } else { // !ishardlink - use same as fileid + + VATTR_RETURN(vap, va_linkid, vap->va_fileid); + + } + + } // active linkid + + if (VATTR_IS_ACTIVE(vap, va_filerev)) { + VATTR_RETURN(vap, va_filerev, 0); + } + if (VATTR_IS_ACTIVE(vap, va_fsid)) { + VATTR_RETURN(vap, va_fsid, zfsvfs->z_rdev); + } + if (VATTR_IS_ACTIVE(vap, va_type)) { + VATTR_RETURN(vap, va_type, vnode_vtype(ZTOV(zp))); + } + if (VATTR_IS_ACTIVE(vap, va_encoding)) { + VATTR_RETURN(vap, va_encoding, kTextEncodingMacUnicode); + } +#ifdef VNODE_ATTR_va_addedtime + /* + * ADDEDTIME should come from finderinfo according to hfs_attrlist.c + * in ZFS we can use crtime, and add logic to getxattr finderinfo to + * copy the ADDEDTIME into the structure. See vnop_getxattr + */ + if (VATTR_IS_ACTIVE(vap, va_addedtime)) { + if (sa_lookup(zp->z_sa_hdl, SA_ZPL_ADDTIME(zfsvfs), + &addtime, sizeof (addtime)) != 0) { + /* + * Lookup the ADDTIME if it exists, if not, use CRTIME. + * We add CRTIME to WANTED in zfs_vnop_getattr() + * so we know we have the value here. + */ + vap->va_addedtime.tv_sec = vap->va_crtime.tv_sec; + vap->va_addedtime.tv_nsec = vap->va_crtime.tv_nsec; + } else { + ZFS_TIME_DECODE(&vap->va_addedtime, addtime); + } + VATTR_SET_SUPPORTED(vap, va_addedtime); + } +#endif +#ifdef VNODE_ATTR_va_fsid64 + if (VATTR_IS_ACTIVE(vap, va_fsid64)) { + vap->va_fsid64.val[0] = + vfs_statfs(zfsvfs->z_vfs)->f_fsid.val[0]; + vap->va_fsid64.val[1] = vfs_typenum(zfsvfs->z_vfs); + VATTR_SET_SUPPORTED(vap, va_fsid64); + } +#endif +#ifdef VNODE_ATTR_va_write_gencount + if (VATTR_IS_ACTIVE(vap, va_write_gencount)) { + if (!zp->z_write_gencount) + atomic_inc_64(&zp->z_write_gencount); + VATTR_RETURN(vap, va_write_gencount, + (uint32_t)zp->z_write_gencount); + } +#endif + +#ifdef VNODE_ATTR_va_document_id + if (VATTR_IS_ACTIVE(vap, va_document_id)) { + + if (!zp->z_document_id) { + zfs_setattr_generate_id(zp, parent, vap->va_name); + } + + VATTR_RETURN(vap, va_document_id, zp->z_document_id); + } +#endif /* VNODE_ATTR_va_document_id */ + +#ifdef VNODE_ATTR_va_devid + if (VATTR_IS_ACTIVE(vap, va_devid)) { + VATTR_RETURN(vap, va_devid, + vfs_statfs(zfsvfs->z_vfs)->f_fsid.val[0]); + } +#endif /* VNODE_ATTR_va_document_id */ + + if (ishardlink) { + dprintf("ZFS:getattr(%s,%llu,%llu) parent %llu: cache_parent " + "%llu: va_nlink %llu\n", VATTR_IS_ACTIVE(vap, va_name) ? + vap->va_name : zp->z_name_cache, + vap->va_fileid, + VATTR_IS_ACTIVE(vap, va_linkid) ? vap->va_linkid : 0, + vap->va_parentid, + zp->z_finder_parentid, + vap->va_nlink); + } + + /* A bunch of vattrs are handled inside zfs_getattr() */ + if (VATTR_IS_ACTIVE(vap, va_mode)) + VATTR_SET_SUPPORTED(vap, va_mode); + if (VATTR_IS_ACTIVE(vap, va_nlink)) + VATTR_SET_SUPPORTED(vap, va_nlink); + if (VATTR_IS_ACTIVE(vap, va_uid)) + VATTR_SET_SUPPORTED(vap, va_uid); + if (VATTR_IS_ACTIVE(vap, va_gid)) + VATTR_SET_SUPPORTED(vap, va_gid); + if (VATTR_IS_ACTIVE(vap, va_fileid)) + VATTR_SET_SUPPORTED(vap, va_fileid); + if (VATTR_IS_ACTIVE(vap, va_data_size)) + VATTR_SET_SUPPORTED(vap, va_data_size); + if (VATTR_IS_ACTIVE(vap, va_total_size)) + VATTR_SET_SUPPORTED(vap, va_total_size); + if (VATTR_IS_ACTIVE(vap, va_rdev)) + VATTR_SET_SUPPORTED(vap, va_rdev); + if (VATTR_IS_ACTIVE(vap, va_gen)) + VATTR_SET_SUPPORTED(vap, va_gen); + if (VATTR_IS_ACTIVE(vap, va_create_time)) + VATTR_SET_SUPPORTED(vap, va_create_time); + if (VATTR_IS_ACTIVE(vap, va_access_time)) + VATTR_SET_SUPPORTED(vap, va_access_time); + if (VATTR_IS_ACTIVE(vap, va_modify_time)) + VATTR_SET_SUPPORTED(vap, va_modify_time); + if (VATTR_IS_ACTIVE(vap, va_change_time)) + VATTR_SET_SUPPORTED(vap, va_change_time); + if (VATTR_IS_ACTIVE(vap, va_backup_time)) + VATTR_SET_SUPPORTED(vap, va_backup_time); + if (VATTR_IS_ACTIVE(vap, va_flags)) + VATTR_SET_SUPPORTED(vap, va_flags); + if (VATTR_IS_ACTIVE(vap, va_parentid)) + VATTR_SET_SUPPORTED(vap, va_parentid); + + uint64_t missing = 0; + missing = (vap->va_active ^ (vap->va_active & vap->va_supported)); + if (missing != 0) { + dprintf("vnop_getattr:: asked %08llx replied %08llx " + " missing %08llx\n", + vap->va_active, vap->va_supported, + missing); + } + + mutex_exit(&zp->z_lock); + + zfs_exit(zfsvfs, FTAG); + return (error); +} + +boolean_t +vfs_has_feature(vfs_t *vfsp, vfs_feature_t vfsft) +{ + + switch (vfsft) { + case VFSFT_CASEINSENSITIVE: + case VFSFT_NOCASESENSITIVE: + return (B_TRUE); + default: + return (B_FALSE); + } +} + +int +zfs_access_native_mode(struct vnode *vp, int *mode, cred_t *cr, + caller_context_t *ct) +{ + int accmode = *mode & (VREAD|VWRITE|VEXEC /* |VAPPEND */); + int error = 0; + int flag = 0; // FIXME + + if (accmode != 0) + error = zfs_access(VTOZ(vp), accmode, flag, cr); + + *mode &= ~(accmode); + + return (error); +} + +int +zfs_ioflags(int ap_ioflag) +{ + int flags = 0; + + if (ap_ioflag & IO_APPEND) + flags |= FAPPEND; + if (ap_ioflag & IO_NDELAY) + flags |= FNONBLOCK; + if (ap_ioflag & IO_SYNC) + flags |= (FSYNC | FDSYNC | FRSYNC); + + return (flags); +} + +int +zfs_vnop_ioctl_fullfsync(struct vnode *vp, vfs_context_t ct, zfsvfs_t *zfsvfs) +{ + int error; + + error = zfs_fsync(VTOZ(vp), /* syncflag */ 0, NULL); + if (error) + return (error); + + if (zfsvfs->z_log != NULL) + zil_commit(zfsvfs->z_log, 0); + else + txg_wait_synced(dmu_objset_pool(zfsvfs->z_os), 0); + return (0); +} + + +/* + * Nobody seems to use xvattr to get the extended flags, + * so we'll have a direct convert as well. + */ +uint32_t +zfs_getbsdflags(znode_t *zp) +{ + uint32_t bsdflags = 0; + uint64_t zflags = zp->z_pflags; + + if (zflags & ZFS_NODUMP) + bsdflags |= UF_NODUMP; + if (zflags & ZFS_UIMMUTABLE) + bsdflags |= UF_IMMUTABLE; + if (zflags & ZFS_UAPPENDONLY) + bsdflags |= UF_APPEND; + if (zflags & ZFS_OPAQUE) + bsdflags |= UF_OPAQUE; + if (zflags & ZFS_HIDDEN) + bsdflags |= UF_HIDDEN; + if (zflags & ZFS_TRACKED) + bsdflags |= UF_TRACKED; + + if (zflags & ZFS_NOUNLINK) + bsdflags |= SF_NOUNLINK; + if (zflags & ZFS_SIMMUTABLE) + bsdflags |= SF_IMMUTABLE; + if (zflags & ZFS_SAPPENDONLY) + bsdflags |= SF_APPEND; + /* + * Due to every file getting archive set automatically, and OSX + * don't let you move/copy it as a user, we disable archive connection + * for now + * if (zflags & ZFS_ARCHIVE) + * bsdflags |= SF_ARCHIVED; + */ + dprintf("getbsd changing zfs %08llx to osx %08x\n", + zflags, bsdflags); + return (bsdflags); +} + +int +zfs_setbsdflags(znode_t *zp, uint32_t ioctl_flags, xvattr_t *xva) +{ + uint64_t zfs_flags; + xoptattr_t *xoap; + + zfs_flags = zp->z_pflags; + + xva_init(xva); + xoap = xva_getxoptattr(xva); + +#define FLAG_CHANGE(zflag, iflag, xflag, xfield) do { \ + if (((ioctl_flags & (iflag)) && !(zfs_flags & (zflag))) || \ + ((zfs_flags & (zflag)) && !(ioctl_flags & (iflag)))) { \ + XVA_SET_REQ(xva, (xflag)); \ + (xfield) = ((ioctl_flags & (iflag)) != 0); \ + } \ + } while (0) + + FLAG_CHANGE(ZFS_IMMUTABLE, UF_IMMUTABLE, + XAT_IMMUTABLE, xoap->xoa_immutable); + FLAG_CHANGE(ZFS_APPENDONLY, UF_APPEND, + XAT_APPENDONLY, xoap->xoa_appendonly); + FLAG_CHANGE(ZFS_NODUMP, UF_NODUMP, + XAT_NODUMP, xoap->xoa_nodump); + FLAG_CHANGE(ZFS_HIDDEN, UF_HIDDEN, + XAT_HIDDEN, xoap->xoa_hidden); + FLAG_CHANGE(ZFS_OPAQUE, UF_OPAQUE, + XAT_OPAQUE, xoap->xoa_opaque); + FLAG_CHANGE(ZFS_TRACKED, UF_TRACKED, + XAT_TRACKED, xoap->xoa_tracked); + + FLAG_CHANGE(ZFS_SAPPENDONLY, SF_APPEND, + XAT_SAPPENDONLY, xoap->xoa_sappendonly); + FLAG_CHANGE(ZFS_SIMMUTABLE, SF_IMMUTABLE, + XAT_SIMMUTABLE, xoap->xoa_simmutable); + FLAG_CHANGE(ZFS_ARCHIVE, SF_ARCHIVED, + XAT_ARCHIVE, xoap->xoa_archive); + FLAG_CHANGE(ZFS_NOUNLINK, SF_NOUNLINK, + XAT_NOUNLINK, xoap->xoa_nounlink); + // FLAG_CHANGE(ZFS_READONLY, XAT_READONLY, xoap->xoa_readonly); + // FLAG_CHANGE(ZFS_SYSTEM, XAT_SYSTEM, xoap->xoa_system); + // FLAG_CHANGE(ZFS_REPARSE, XAT_REPARSE, xoap->xoa_reparse); + // FLAG_CHANGE(ZFS_OFFLINE, XAT_OFFLINE, xoap->xoa_offline); + // FLAG_CHANGE(ZFS_SPARSE, XAT_SPARSE, xoap->xoa_sparse); +#undef FLAG_CHANGE + + return (0); +} + +/* + * Lookup/Create an extended attribute entry. + * + * Input arguments: + * dzp - znode for hidden attribute directory + * name - name of attribute + * flag - ZNEW: if the entry already exists, fail with EEXIST. + * ZEXISTS: if the entry does not exist, fail with ENOENT. + * + * Output arguments: + * vpp - pointer to the vnode for the entry (NULL if there isn't one) + * + * Return value: 0 on success or errno value on failure. + */ +int +zpl_obtain_xattr(znode_t *dzp, const char *name, mode_t mode, cred_t *cr, + vnode_t **vpp, int flag) +{ + znode_t *xzp = NULL; + zfsvfs_t *zfsvfs = dzp->z_zfsvfs; + zilog_t *zilog; + zfs_dirlock_t *dl; + dmu_tx_t *tx; + struct vnode_attr vattr; + int error; + struct componentname cn = { 0 }; + zfs_acl_ids_t acl_ids; + + /* zfs_dirent_lock() expects a component name */ + + if ((error = zfs_enter_verify_zp(zfsvfs, dzp, FTAG)) != 0) + return (error); + + zilog = zfsvfs->z_log; + + VATTR_INIT(&vattr); + VATTR_SET(&vattr, va_type, VREG); + VATTR_SET(&vattr, va_mode, mode & ~S_IFMT); + + if ((error = zfs_acl_ids_create(dzp, 0, + &vattr, cr, NULL, &acl_ids, NULL)) != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + + cn.cn_namelen = strlen(name)+1; + cn.cn_nameptr = (char *)kmem_zalloc(cn.cn_namelen, KM_SLEEP); + +top: + /* Lock the attribute entry name. */ + if ((error = zfs_dirent_lock(&dl, dzp, (char *)name, &xzp, flag, + NULL, &cn))) { + goto out; + } + /* If the name already exists, we're done. */ + if (xzp != NULL) { + zfs_dirent_unlock(dl); + goto out; + } + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_sa(tx, dzp->z_sa_hdl, B_FALSE); + dmu_tx_hold_zap(tx, dzp->z_id, TRUE, (char *)name); + dmu_tx_hold_zap(tx, DMU_NEW_OBJECT, FALSE, NULL); + +#if 1 // FIXME + if (dzp->z_pflags & ZFS_INHERIT_ACE) { + dmu_tx_hold_write(tx, DMU_NEW_OBJECT, 0, SPA_MAXBLOCKSIZE); + } +#endif + zfs_sa_upgrade_txholds(tx, dzp); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + zfs_dirent_unlock(dl); + if (error == ERESTART) { + dmu_tx_wait(tx); + dmu_tx_abort(tx); + goto top; + } + dmu_tx_abort(tx); + goto out; + } + + zfs_mknode(dzp, &vattr, tx, cr, 0, &xzp, &acl_ids); + + /* + * ASSERT(xzp->z_id == zoid); + */ + (void) zfs_link_create(dl, xzp, tx, ZNEW); + zfs_log_create(zilog, tx, TX_CREATE, dzp, xzp, (char *)name, + NULL /* vsecp */, 0 /* acl_ids.z_fuidp */, &vattr); + dmu_tx_commit(tx); + + /* + * OS X - attach the vnode _after_ committing the transaction + */ + zfs_znode_getvnode(xzp, zfsvfs); + + zfs_dirent_unlock(dl); +out: + zfs_acl_ids_free(&acl_ids); + if (cn.cn_nameptr) + kmem_free(cn.cn_nameptr, cn.cn_namelen); + + /* The REPLACE error if doesn't exist is ENOATTR */ + if ((flag & ZEXISTS) && (error == ENOENT)) + error = ENOATTR; + + if (xzp) + *vpp = ZTOV(xzp); + + zfs_exit(zfsvfs, FTAG); + return (error); +} + +/* + * ace_trivial: + * determine whether an ace_t acl is trivial + * + * Trivialness implies that the acl is composed of only + * owner, group, everyone entries. ACL can't + * have read_acl denied, and write_owner/write_acl/write_attributes + * can only be owner@ entry. + */ +int +ace_trivial_common(void *acep, int aclcnt, + uint64_t (*walk)(void *, uint64_t, int aclcnt, + uint16_t *, uint16_t *, uint32_t *)) +{ + uint16_t flags; + uint32_t mask; + uint16_t type; + uint64_t cookie = 0; + + while ((cookie = walk(acep, cookie, aclcnt, &flags, &type, &mask))) { + switch (flags & ACE_TYPE_FLAGS) { + case ACE_OWNER: + case ACE_GROUP|ACE_IDENTIFIER_GROUP: + case ACE_EVERYONE: + break; + default: + return (1); + + } + + if (flags & (ACE_FILE_INHERIT_ACE| + ACE_DIRECTORY_INHERIT_ACE|ACE_NO_PROPAGATE_INHERIT_ACE| + ACE_INHERIT_ONLY_ACE)) + return (1); + + /* + * Special check for some special bits + * + * Don't allow anybody to deny reading basic + * attributes or a files ACL. + */ + if ((mask & (ACE_READ_ACL|ACE_READ_ATTRIBUTES)) && + (type == ACE_ACCESS_DENIED_ACE_TYPE)) + return (1); + + /* + * Delete permission is never set by default + */ + if (mask & ACE_DELETE) + return (1); + + /* + * Child delete permission should be accompanied by write + */ + if ((mask & ACE_DELETE_CHILD) && !(mask & ACE_WRITE_DATA)) + return (1); + /* + * only allow owner@ to have + * write_acl/write_owner/write_attributes/write_xattr/ + */ + + if (type == ACE_ACCESS_ALLOWED_ACE_TYPE && + (!(flags & ACE_OWNER) && (mask & + (ACE_WRITE_OWNER|ACE_WRITE_ACL| ACE_WRITE_ATTRIBUTES| + ACE_WRITE_NAMED_ATTRS)))) + return (1); + + } + + return (0); +} + + +void +acl_trivial_access_masks(mode_t mode, boolean_t isdir, trivial_acl_t *masks) +{ + uint32_t read_mask = ACE_READ_DATA; + uint32_t write_mask = ACE_WRITE_DATA|ACE_APPEND_DATA; + uint32_t execute_mask = ACE_EXECUTE; + + if (isdir) + write_mask |= ACE_DELETE_CHILD; + + masks->deny1 = 0; + if (!(mode & S_IRUSR) && (mode & (S_IRGRP|S_IROTH))) + masks->deny1 |= read_mask; + if (!(mode & S_IWUSR) && (mode & (S_IWGRP|S_IWOTH))) + masks->deny1 |= write_mask; + if (!(mode & S_IXUSR) && (mode & (S_IXGRP|S_IXOTH))) + masks->deny1 |= execute_mask; + + masks->deny2 = 0; + if (!(mode & S_IRGRP) && (mode & S_IROTH)) + masks->deny2 |= read_mask; + if (!(mode & S_IWGRP) && (mode & S_IWOTH)) + masks->deny2 |= write_mask; + if (!(mode & S_IXGRP) && (mode & S_IXOTH)) + masks->deny2 |= execute_mask; + + masks->allow0 = 0; + if ((mode & S_IRUSR) && (!(mode & S_IRGRP) && (mode & S_IROTH))) + masks->allow0 |= read_mask; + if ((mode & S_IWUSR) && (!(mode & S_IWGRP) && (mode & S_IWOTH))) + masks->allow0 |= write_mask; + if ((mode & S_IXUSR) && (!(mode & S_IXGRP) && (mode & S_IXOTH))) + masks->allow0 |= execute_mask; + + masks->owner = ACE_WRITE_ATTRIBUTES|ACE_WRITE_OWNER|ACE_WRITE_ACL| + ACE_WRITE_NAMED_ATTRS|ACE_READ_ACL|ACE_READ_ATTRIBUTES| + ACE_READ_NAMED_ATTRS|ACE_SYNCHRONIZE; + if (mode & S_IRUSR) + masks->owner |= read_mask; + if (mode & S_IWUSR) + masks->owner |= write_mask; + if (mode & S_IXUSR) + masks->owner |= execute_mask; + + masks->group = ACE_READ_ACL|ACE_READ_ATTRIBUTES|ACE_READ_NAMED_ATTRS| + ACE_SYNCHRONIZE; + if (mode & S_IRGRP) + masks->group |= read_mask; + if (mode & S_IWGRP) + masks->group |= write_mask; + if (mode & S_IXGRP) + masks->group |= execute_mask; + + masks->everyone = ACE_READ_ACL|ACE_READ_ATTRIBUTES|ACE_READ_NAMED_ATTRS| + ACE_SYNCHRONIZE; + if (mode & S_IROTH) + masks->everyone |= read_mask; + if (mode & S_IWOTH) + masks->everyone |= write_mask; + if (mode & S_IXOTH) + masks->everyone |= execute_mask; +} + +void commonattrpack(attrinfo_t *aip, zfsvfs_t *zfsvfs, znode_t *zp, + const char *name, ino64_t objnum, enum vtype vtype, + boolean_t user64) +{ + attrgroup_t commonattr = aip->ai_attrlist->commonattr; + void *attrbufptr = *aip->ai_attrbufpp; + void *varbufptr = *aip->ai_varbufpp; + struct mount *mp = zfsvfs->z_vfs; + cred_t *cr = (cred_t *)vfs_context_ucred(aip->ai_context); + finderinfo_t finderinfo; + + /* + * We should probably combine all the sa_lookup into a bulk + * lookup operand. + */ + + finderinfo.fi_flags = 0; + + if (ATTR_CMN_NAME & commonattr) { + nameattrpack(aip, name, strlen(name)); + attrbufptr = *aip->ai_attrbufpp; + varbufptr = *aip->ai_varbufpp; + } + if (ATTR_CMN_DEVID & commonattr) { + *((dev_t *)attrbufptr) = vfs_statfs(mp)->f_fsid.val[0]; + attrbufptr = ((dev_t *)attrbufptr) + 1; + } + if (ATTR_CMN_FSID & commonattr) { + *((fsid_t *)attrbufptr) = vfs_statfs(mp)->f_fsid; + attrbufptr = ((fsid_t *)attrbufptr) + 1; + } + if (ATTR_CMN_OBJTYPE & commonattr) { + *((fsobj_type_t *)attrbufptr) = vtype; + attrbufptr = ((fsobj_type_t *)attrbufptr) + 1; + } + if (ATTR_CMN_OBJTAG & commonattr) { + *((fsobj_tag_t *)attrbufptr) = VT_ZFS; + attrbufptr = ((fsobj_tag_t *)attrbufptr) + 1; + } + /* + * Note: ATTR_CMN_OBJID is lossy (only 32 bits). + */ + if ((ATTR_CMN_OBJID | ATTR_CMN_OBJPERMANENTID) & commonattr) { + u_int32_t fileid; + /* + * On Mac OS X we always export the root directory id as 2 + */ + fileid = (objnum == zfsvfs->z_root) ? 2 : objnum; + + if (ATTR_CMN_OBJID & commonattr) { + ((fsobj_id_t *)attrbufptr)->fid_objno = fileid; + ((fsobj_id_t *)attrbufptr)->fid_generation = 0; + attrbufptr = ((fsobj_id_t *)attrbufptr) + 1; + } + if (ATTR_CMN_OBJPERMANENTID & commonattr) { + ((fsobj_id_t *)attrbufptr)->fid_objno = fileid; + ((fsobj_id_t *)attrbufptr)->fid_generation = 0; + attrbufptr = ((fsobj_id_t *)attrbufptr) + 1; + } + } + /* + * Note: ATTR_CMN_PAROBJID is lossy (only 32 bits). + */ + if (ATTR_CMN_PAROBJID & commonattr) { + uint64_t parentid; + + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_PARENT(zfsvfs), + &parentid, sizeof (parentid)) == 0); + + /* + * On Mac OS X we always export the root + * directory id as 2 and its parent as 1 + */ + if (zp && zp->z_id == zfsvfs->z_root) + parentid = 1; + else if (parentid == zfsvfs->z_root) + parentid = 2; + + ASSERT(parentid != 0); + + ((fsobj_id_t *)attrbufptr)->fid_objno = (uint32_t)parentid; + ((fsobj_id_t *)attrbufptr)->fid_generation = 0; + attrbufptr = ((fsobj_id_t *)attrbufptr) + 1; + } + if (ATTR_CMN_SCRIPT & commonattr) { + *((text_encoding_t *)attrbufptr) = kTextEncodingMacUnicode; + attrbufptr = ((text_encoding_t *)attrbufptr) + 1; + } + if (ATTR_CMN_CRTIME & commonattr) { + uint64_t times[2]; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_CRTIME(zfsvfs), + times, sizeof (times)) == 0); + if (user64) { + ZFS_TIME_DECODE((timespec_user64_t *)attrbufptr, + times); + attrbufptr = ((timespec_user64_t *)attrbufptr) + 1; + } else { + ZFS_TIME_DECODE((timespec_user32_t *)attrbufptr, + times); + attrbufptr = ((timespec_user32_t *)attrbufptr) + 1; + } + } + if (ATTR_CMN_MODTIME & commonattr) { + uint64_t times[2]; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_MTIME(zfsvfs), + times, sizeof (times)) == 0); + if (user64) { + ZFS_TIME_DECODE((timespec_user64_t *)attrbufptr, + times); + attrbufptr = ((timespec_user64_t *)attrbufptr) + 1; + } else { + ZFS_TIME_DECODE((timespec_user32_t *)attrbufptr, + times); + attrbufptr = ((timespec_user32_t *)attrbufptr) + 1; + } + } + if (ATTR_CMN_CHGTIME & commonattr) { + uint64_t times[2]; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_CTIME(zfsvfs), + times, sizeof (times)) == 0); + if (user64) { + ZFS_TIME_DECODE((timespec_user64_t *)attrbufptr, + times); + attrbufptr = ((timespec_user64_t *)attrbufptr) + 1; + } else { + ZFS_TIME_DECODE((timespec_user32_t *)attrbufptr, + times); + attrbufptr = ((timespec_user32_t *)attrbufptr) + 1; + } + } + if (ATTR_CMN_ACCTIME & commonattr) { + uint64_t times[2]; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_ATIME(zfsvfs), + times, sizeof (times)) == 0); + if (user64) { + ZFS_TIME_DECODE((timespec_user64_t *)attrbufptr, + times); + attrbufptr = ((timespec_user64_t *)attrbufptr) + 1; + } else { + ZFS_TIME_DECODE((timespec_user32_t *)attrbufptr, + times); + attrbufptr = ((timespec_user32_t *)attrbufptr) + 1; + } + } + if (ATTR_CMN_BKUPTIME & commonattr) { + /* legacy attribute -- just pass zero */ + if (user64) { + ((timespec_user64_t *)attrbufptr)->tv_sec = 0; + ((timespec_user64_t *)attrbufptr)->tv_nsec = 0; + attrbufptr = ((timespec_user64_t *)attrbufptr) + 1; + } else { + ((timespec_user32_t *)attrbufptr)->tv_sec = 0; + ((timespec_user32_t *)attrbufptr)->tv_nsec = 0; + attrbufptr = ((timespec_user32_t *)attrbufptr) + 1; + } + } + if (ATTR_CMN_FNDRINFO & commonattr) { + uint64_t val; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_FLAGS(zfsvfs), + &val, sizeof (val)) == 0); + getfinderinfo(zp, cr, &finderinfo); + /* Shadow ZFS_HIDDEN to Finder Info's invisible bit */ + if (val & ZFS_HIDDEN) { + finderinfo.fi_flags |= + OSSwapHostToBigConstInt16(kIsInvisible); + } + memcpy(attrbufptr, &finderinfo, sizeof (finderinfo)); + attrbufptr = (char *)attrbufptr + 32; + } + if (ATTR_CMN_OWNERID & commonattr) { + uint64_t val; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_UID(zfsvfs), + &val, sizeof (val)) == 0); + *((uid_t *)attrbufptr) = val; + attrbufptr = ((uid_t *)attrbufptr) + 1; + } + if (ATTR_CMN_GRPID & commonattr) { + uint64_t val; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_GID(zfsvfs), + &val, sizeof (val)) == 0); + *((gid_t *)attrbufptr) = val; + attrbufptr = ((gid_t *)attrbufptr) + 1; + } + if (ATTR_CMN_ACCESSMASK & commonattr) { + uint64_t val; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_MODE(zfsvfs), + &val, sizeof (val)) == 0); + *((u_int32_t *)attrbufptr) = val; + attrbufptr = ((u_int32_t *)attrbufptr) + 1; + } + if (ATTR_CMN_FLAGS & commonattr) { + // TODO, sa_lookup of ZPL_FLAGS + u_int32_t flags = zfs_getbsdflags(zp); + + /* Shadow Finder Info's invisible bit to UF_HIDDEN */ + if ((ATTR_CMN_FNDRINFO & commonattr) && + (OSSwapBigToHostInt16(finderinfo.fi_flags) & kIsInvisible)) + flags |= UF_HIDDEN; + + *((u_int32_t *)attrbufptr) = flags; + attrbufptr = ((u_int32_t *)attrbufptr) + 1; + } + if (ATTR_CMN_USERACCESS & commonattr) { + u_int32_t user_access = 0; + uint64_t val; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_FLAGS(zfsvfs), + &val, sizeof (val)) == 0); + + user_access = getuseraccess(zp, aip->ai_context); + + /* Also consider READ-ONLY file system. */ + if (vfs_flags(mp) & MNT_RDONLY) { + user_access &= ~W_OK; + } + + /* Locked objects are not writable either */ + if ((val & ZFS_IMMUTABLE) && + (vfs_context_suser(aip->ai_context) != 0)) { + user_access &= ~W_OK; + } + + *((u_int32_t *)attrbufptr) = user_access; + attrbufptr = ((u_int32_t *)attrbufptr) + 1; + } + if (ATTR_CMN_FILEID & commonattr) { + /* + * On Mac OS X we always export the root directory id as 2 + */ + if (objnum == zfsvfs->z_root) + objnum = 2; + + *((u_int64_t *)attrbufptr) = objnum; + attrbufptr = ((u_int64_t *)attrbufptr) + 1; + } + if (ATTR_CMN_PARENTID & commonattr) { + uint64_t parentid; + + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_PARENT(zfsvfs), + &parentid, sizeof (parentid)) == 0); + + /* + * On Mac OS X we always export the root + * directory id as 2 and its parent as 1 + */ + if (zp && zp->z_id == zfsvfs->z_root) + parentid = 1; + else if (parentid == zfsvfs->z_root) + parentid = 2; + + ASSERT(parentid != 0); + + *((u_int64_t *)attrbufptr) = parentid; + attrbufptr = ((u_int64_t *)attrbufptr) + 1; + } + + *aip->ai_attrbufpp = attrbufptr; + *aip->ai_varbufpp = varbufptr; +} + +void +dirattrpack(attrinfo_t *aip, znode_t *zp) +{ + attrgroup_t dirattr = aip->ai_attrlist->dirattr; + void *attrbufptr = *aip->ai_attrbufpp; + + if (ATTR_DIR_LINKCOUNT & dirattr) { + *((u_int32_t *)attrbufptr) = 1; /* no dir hard links */ + attrbufptr = ((u_int32_t *)attrbufptr) + 1; + } + if (ATTR_DIR_ENTRYCOUNT & dirattr) { + uint64_t val; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_SIZE(zp->z_zfsvfs), + &val, sizeof (val)) == 0); + *((u_int32_t *)attrbufptr) = (uint32_t)val; + attrbufptr = ((u_int32_t *)attrbufptr) + 1; + } + if (ATTR_DIR_MOUNTSTATUS & dirattr && zp) { + vnode_t *vp = ZTOV(zp); + + if (vp != NULL && vnode_mountedhere(vp) != NULL) + *((u_int32_t *)attrbufptr) = DIR_MNTSTATUS_MNTPOINT; + else + *((u_int32_t *)attrbufptr) = 0; + attrbufptr = ((u_int32_t *)attrbufptr) + 1; + } + *aip->ai_attrbufpp = attrbufptr; +} + +void +fileattrpack(attrinfo_t *aip, zfsvfs_t *zfsvfs, znode_t *zp) +{ + attrgroup_t fileattr = aip->ai_attrlist->fileattr; + void *attrbufptr = *aip->ai_attrbufpp; + void *varbufptr = *aip->ai_varbufpp; + uint64_t allocsize = 0; + cred_t *cr = (cred_t *)vfs_context_ucred(aip->ai_context); + + if ((ATTR_FILE_ALLOCSIZE | ATTR_FILE_DATAALLOCSIZE) & fileattr && zp) { + uint32_t blksize; + u_longlong_t nblks; + + sa_object_size(zp->z_sa_hdl, &blksize, &nblks); + allocsize = (uint64_t)512LL * (uint64_t)nblks; + } + if (ATTR_FILE_LINKCOUNT & fileattr) { + uint64_t val; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_LINKS(zfsvfs), + &val, sizeof (val)) == 0); + *((u_int32_t *)attrbufptr) = val; + attrbufptr = ((u_int32_t *)attrbufptr) + 1; + } + if (ATTR_FILE_TOTALSIZE & fileattr) { + uint64_t val; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_SIZE(zfsvfs), + &val, sizeof (val)) == 0); + *((off_t *)attrbufptr) = val; + attrbufptr = ((off_t *)attrbufptr) + 1; + } + if (ATTR_FILE_ALLOCSIZE & fileattr) { + *((off_t *)attrbufptr) = allocsize; + attrbufptr = ((off_t *)attrbufptr) + 1; + } + if (ATTR_FILE_IOBLOCKSIZE & fileattr && zp) { + *((u_int32_t *)attrbufptr) = + zp->z_blksz ? zp->z_blksz : zfsvfs->z_max_blksz; + attrbufptr = ((u_int32_t *)attrbufptr) + 1; + } + if (ATTR_FILE_DEVTYPE & fileattr) { + uint64_t mode, val = 0; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_MODE(zfsvfs), + &mode, sizeof (mode)) == 0); + sa_lookup(zp->z_sa_hdl, SA_ZPL_RDEV(zfsvfs), + &val, sizeof (val)); + if (S_ISBLK(mode) || S_ISCHR(mode)) + *((u_int32_t *)attrbufptr) = (u_int32_t)val; + else + *((u_int32_t *)attrbufptr) = 0; + attrbufptr = ((u_int32_t *)attrbufptr) + 1; + } + if (ATTR_FILE_DATALENGTH & fileattr) { + uint64_t val; + VERIFY(sa_lookup(zp->z_sa_hdl, SA_ZPL_SIZE(zfsvfs), + &val, sizeof (val)) == 0); + *((off_t *)attrbufptr) = val; + attrbufptr = ((off_t *)attrbufptr) + 1; + } + if (ATTR_FILE_DATAALLOCSIZE & fileattr) { + *((off_t *)attrbufptr) = allocsize; + attrbufptr = ((off_t *)attrbufptr) + 1; + } + if ((ATTR_FILE_RSRCLENGTH | ATTR_FILE_RSRCALLOCSIZE) & fileattr) { + uint64_t rsrcsize = 0; + uint64_t xattr; + + if (!sa_lookup(zp->z_sa_hdl, SA_ZPL_XATTR(zfsvfs), + &xattr, sizeof (xattr)) && + xattr) { + znode_t *xdzp = NULL, *xzp = NULL; + struct componentname cn = { 0 }; + char *name = NULL; + + name = spa_strdup(XATTR_RESOURCEFORK_NAME); + cn.cn_namelen = strlen(name)+1; + cn.cn_nameptr = kmem_zalloc(cn.cn_namelen, KM_SLEEP); + + /* Grab the hidden attribute directory vnode. */ + if (zfs_get_xattrdir(zp, &xdzp, cr, 0) == 0 && + zfs_dirlook(xdzp, name, &xzp, 0, NULL, + &cn) == 0) { + rsrcsize = xzp->z_size; + } + spa_strfree(name); + kmem_free(cn.cn_nameptr, cn.cn_namelen); + + if (xzp) + zrele(xzp); + if (xdzp) + zrele(xdzp); + } + if (ATTR_FILE_RSRCLENGTH & fileattr) { + *((off_t *)attrbufptr) = rsrcsize; + attrbufptr = ((off_t *)attrbufptr) + 1; + } + if (ATTR_FILE_RSRCALLOCSIZE & fileattr) { + *((off_t *)attrbufptr) = roundup(rsrcsize, 512); + attrbufptr = ((off_t *)attrbufptr) + 1; + } + } + *aip->ai_attrbufpp = attrbufptr; + *aip->ai_varbufpp = varbufptr; +} + +void +nameattrpack(attrinfo_t *aip, const char *name, int namelen) +{ + void *varbufptr; + struct attrreference *attr_refptr; + u_int32_t attrlen; + size_t nfdlen, freespace; + int force_formd_normalized_output; + + varbufptr = *aip->ai_varbufpp; + attr_refptr = (struct attrreference *)(*aip->ai_attrbufpp); + + freespace = (char *)aip->ai_varbufend - (char *)varbufptr; + /* + * Mac OS X: non-ascii names are UTF-8 NFC on disk + * so convert to NFD before exporting them. + */ + + if (zfs_vnop_force_formd_normalized_output && + !is_ascii_str(name)) + force_formd_normalized_output = 1; + else + force_formd_normalized_output = 0; + + namelen = strlen(name); + if (!force_formd_normalized_output || + utf8_normalizestr((const u_int8_t *)name, namelen, + (u_int8_t *)varbufptr, &nfdlen, + freespace, UTF_DECOMPOSED) != 0) { + /* ASCII or normalization failed, just copy zap name. */ + strncpy((char *)varbufptr, name, MIN(freespace, namelen+1)); + } else { + /* Normalization succeeded (already in buffer). */ + namelen = nfdlen; + } + attrlen = namelen + 1; + attr_refptr->attr_dataoffset = (char *)varbufptr - (char *)attr_refptr; + attr_refptr->attr_length = attrlen; + /* + * Advance beyond the space just allocated and + * round up to the next 4-byte boundary: + */ + varbufptr = ((char *)varbufptr) + attrlen + ((4 - (attrlen & 3)) & 3); + ++attr_refptr; + + *aip->ai_attrbufpp = attr_refptr; + *aip->ai_varbufpp = varbufptr; +} + +int +getpackedsize(struct attrlist *alp, boolean_t user64) +{ + attrgroup_t attrs; + int timespecsize; + int size = 0; + + timespecsize = user64 ? sizeof (timespec_user64_t) : + sizeof (timespec_user32_t); + + if ((attrs = alp->commonattr) != 0) { + if (attrs & ATTR_CMN_NAME) + size += sizeof (struct attrreference); + if (attrs & ATTR_CMN_DEVID) + size += sizeof (dev_t); + if (attrs & ATTR_CMN_FSID) + size += sizeof (fsid_t); + if (attrs & ATTR_CMN_OBJTYPE) + size += sizeof (fsobj_type_t); + if (attrs & ATTR_CMN_OBJTAG) + size += sizeof (fsobj_tag_t); + if (attrs & ATTR_CMN_OBJID) + size += sizeof (fsobj_id_t); + if (attrs & ATTR_CMN_OBJPERMANENTID) + size += sizeof (fsobj_id_t); + if (attrs & ATTR_CMN_PAROBJID) + size += sizeof (fsobj_id_t); + if (attrs & ATTR_CMN_SCRIPT) + size += sizeof (text_encoding_t); + if (attrs & ATTR_CMN_CRTIME) + size += timespecsize; + if (attrs & ATTR_CMN_MODTIME) + size += timespecsize; + if (attrs & ATTR_CMN_CHGTIME) + size += timespecsize; + if (attrs & ATTR_CMN_ACCTIME) + size += timespecsize; + if (attrs & ATTR_CMN_BKUPTIME) + size += timespecsize; + if (attrs & ATTR_CMN_FNDRINFO) + size += 32 * sizeof (u_int8_t); + if (attrs & ATTR_CMN_OWNERID) + size += sizeof (uid_t); + if (attrs & ATTR_CMN_GRPID) + size += sizeof (gid_t); + if (attrs & ATTR_CMN_ACCESSMASK) + size += sizeof (u_int32_t); + if (attrs & ATTR_CMN_FLAGS) + size += sizeof (u_int32_t); + if (attrs & ATTR_CMN_USERACCESS) + size += sizeof (u_int32_t); + if (attrs & ATTR_CMN_FILEID) + size += sizeof (u_int64_t); + if (attrs & ATTR_CMN_PARENTID) + size += sizeof (u_int64_t); + /* + * Also add: + * ATTR_CMN_GEN_COUNT (|FSOPT_ATTR_CMN_EXTENDED) + * ATTR_CMN_DOCUMENT_ID (|FSOPT_ATTR_CMN_EXTENDED) + * ATTR_CMN_EXTENDED_SECURITY + * ATTR_CMN_UUID + * ATTR_CMN_GRPUUID + * ATTR_CMN_FULLPATH + * ATTR_CMN_ADDEDTIME + * ATTR_CMN_ERROR + * ATTR_CMN_DATA_PROTECT_FLAGS + */ + } + if ((attrs = alp->dirattr) != 0) { + if (attrs & ATTR_DIR_LINKCOUNT) + size += sizeof (u_int32_t); + if (attrs & ATTR_DIR_ENTRYCOUNT) + size += sizeof (u_int32_t); + if (attrs & ATTR_DIR_MOUNTSTATUS) + size += sizeof (u_int32_t); + } + if ((attrs = alp->fileattr) != 0) { + if (attrs & ATTR_FILE_LINKCOUNT) + size += sizeof (u_int32_t); + if (attrs & ATTR_FILE_TOTALSIZE) + size += sizeof (off_t); + if (attrs & ATTR_FILE_ALLOCSIZE) + size += sizeof (off_t); + if (attrs & ATTR_FILE_IOBLOCKSIZE) + size += sizeof (u_int32_t); + if (attrs & ATTR_FILE_DEVTYPE) + size += sizeof (u_int32_t); + if (attrs & ATTR_FILE_DATALENGTH) + size += sizeof (off_t); + if (attrs & ATTR_FILE_DATAALLOCSIZE) + size += sizeof (off_t); + if (attrs & ATTR_FILE_RSRCLENGTH) + size += sizeof (off_t); + if (attrs & ATTR_FILE_RSRCALLOCSIZE) + size += sizeof (off_t); + } + return (size); +} + + +void +getfinderinfo(znode_t *zp, cred_t *cr, finderinfo_t *fip) +{ + znode_t *xdzp = NULL; + znode_t *xzp = NULL; + struct uio *auio = NULL; + struct componentname cn = { 0 }; + int error; + uint64_t xattr = 0; + char *name = NULL; + + if (sa_lookup(zp->z_sa_hdl, SA_ZPL_XATTR(zp->z_zfsvfs), + &xattr, sizeof (xattr)) || + (xattr == 0)) { + goto nodata; + } + + // Change this to internal uio ? + auio = uio_create(1, 0, UIO_SYSSPACE, UIO_READ); + if (auio == NULL) { + goto nodata; + } + uio_addiov(auio, CAST_USER_ADDR_T(fip), sizeof (finderinfo_t)); + + /* + * Grab the hidden attribute directory vnode. + * + * XXX - switch to embedded Finder Info when it becomes available + */ + if ((error = zfs_get_xattrdir(zp, &xdzp, cr, 0))) { + goto out; + } + + name = spa_strdup(XATTR_FINDERINFO_NAME); + cn.cn_namelen = strlen(name)+1; + cn.cn_nameptr = kmem_zalloc(cn.cn_namelen, KM_SLEEP); + + if ((error = zfs_dirlook(xdzp, name, &xzp, 0, NULL, &cn))) { + goto out; + } + + ZFS_UIO_INIT_XNU(uio, auio); + error = dmu_read_uio(zp->z_zfsvfs->z_os, xzp->z_id, uio, + sizeof (finderinfo_t)); +out: + if (name) + spa_strfree(name); + if (cn.cn_nameptr) + kmem_free(cn.cn_nameptr, cn.cn_namelen); + if (auio) + uio_free(auio); + if (xzp) + zrele(xzp); + if (xdzp) + zrele(xdzp); + if (error == 0) + return; +nodata: + memset(fip, 0, sizeof (finderinfo_t)); +} + +#define KAUTH_DIR_WRITE (KAUTH_VNODE_ACCESS | KAUTH_VNODE_ADD_FILE | \ + KAUTH_VNODE_ADD_SUBDIRECTORY | \ + KAUTH_VNODE_DELETE_CHILD) + +#define KAUTH_DIR_READ (KAUTH_VNODE_ACCESS | KAUTH_VNODE_LIST_DIRECTORY) + +#define KAUTH_DIR_EXECUTE (KAUTH_VNODE_ACCESS | KAUTH_VNODE_SEARCH) + +#define KAUTH_FILE_WRITE (KAUTH_VNODE_ACCESS | KAUTH_VNODE_WRITE_DATA) + +#define KAUTH_FILE_READ (KAUTH_VNODE_ACCESS | KAUTH_VNODE_READ_DATA) + +#define KAUTH_FILE_EXECUTE (KAUTH_VNODE_ACCESS | KAUTH_VNODE_EXECUTE) + +/* + * Compute the same user access value as getattrlist(2) + */ +u_int32_t +getuseraccess(znode_t *zp, vfs_context_t ctx) +{ + vnode_t *vp; + u_int32_t user_access = 0; + zfs_acl_phys_t acl_phys; + int error; + /* Only take the expensive vnode_authorize path when we have an ACL */ + + error = sa_lookup(zp->z_sa_hdl, SA_ZPL_ZNODE_ACL(zp->z_zfsvfs), + &acl_phys, sizeof (acl_phys)); + + if (error || acl_phys.z_acl_count == 0) { + kauth_cred_t cred = vfs_context_ucred(ctx); + uint64_t obj_uid; + uint64_t obj_mode; + + /* User id 0 (root) always gets access. */ + if (!vfs_context_suser(ctx)) { + return (R_OK | W_OK | X_OK); + } + + sa_lookup(zp->z_sa_hdl, SA_ZPL_UID(zp->z_zfsvfs), + &obj_uid, sizeof (obj_uid)); + sa_lookup(zp->z_sa_hdl, SA_ZPL_MODE(zp->z_zfsvfs), + &obj_mode, sizeof (obj_mode)); + + // obj_uid = pzp->zp_uid; + obj_mode = obj_mode & MODEMASK; + if (obj_uid == UNKNOWNUID) { + obj_uid = kauth_cred_getuid(cred); + } + if ((obj_uid == kauth_cred_getuid(cred)) || + (obj_uid == UNKNOWNUID)) { + return (((u_int32_t)obj_mode & S_IRWXU) >> 6); + } + /* Otherwise, settle for 'others' access. */ + return ((u_int32_t)obj_mode & S_IRWXO); + } + vp = ZTOV(zp); + if (vnode_isdir(vp)) { + if (vnode_authorize(vp, NULLVP, KAUTH_DIR_WRITE, ctx) == 0) + user_access |= W_OK; + if (vnode_authorize(vp, NULLVP, KAUTH_DIR_READ, ctx) == 0) + user_access |= R_OK; + if (vnode_authorize(vp, NULLVP, KAUTH_DIR_EXECUTE, ctx) == 0) + user_access |= X_OK; + } else { + if (vnode_authorize(vp, NULLVP, KAUTH_FILE_WRITE, ctx) == 0) + user_access |= W_OK; + if (vnode_authorize(vp, NULLVP, KAUTH_FILE_READ, ctx) == 0) + user_access |= R_OK; + if (vnode_authorize(vp, NULLVP, KAUTH_FILE_EXECUTE, ctx) == 0) + user_access |= X_OK; + } + return (user_access); +} + + + +static unsigned char fingerprint[] = {0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, + 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef}; + +/* + * Convert "Well Known" GUID to enum type. + */ +int +kauth_wellknown_guid(guid_t *guid) +{ + uint32_t last = 0; + + if (memcmp(fingerprint, guid->g_guid, sizeof (fingerprint))) + return (KAUTH_WKG_NOT); + + last = BE_32(*((u_int32_t *)&guid->g_guid[12])); + + switch (last) { + case 0x0c: + return (KAUTH_WKG_EVERYBODY); + case 0x0a: + return (KAUTH_WKG_OWNER); + case 0x10: + return (KAUTH_WKG_GROUP); + case 0xFFFFFFFE: + return (KAUTH_WKG_NOBODY); + } + + return (KAUTH_WKG_NOT); +} + + +/* + * Set GUID to "well known" guid, based on enum type + */ +void +nfsacl_set_wellknown(int wkg, guid_t *guid) +{ + /* + * All WKGs begin with the same 12 bytes. + */ + memcpy((void *)guid, fingerprint, 12); + /* + * The final 4 bytes are our code (in network byte order). + */ + switch (wkg) { + case 4: + *((u_int32_t *)&guid->g_guid[12]) = BE_32(0x0000000c); + break; + case 3: + *((u_int32_t *)&guid->g_guid[12]) = BE_32(0xfffffffe); + break; + case 1: + *((u_int32_t *)&guid->g_guid[12]) = BE_32(0x0000000a); + break; + case 2: + *((u_int32_t *)&guid->g_guid[12]) = BE_32(0x00000010); + }; +} + + +/* + * Convert Darwin ACL list, into ZFS ACL "aces" list. + */ +void +aces_from_acl(ace_t *aces, int *nentries, struct kauth_acl *k_acl, + int *seen_type) +{ + int i; + ace_t *ace; + guid_t *guidp; + kauth_ace_rights_t ace_rights; + uid_t who; + uint32_t mask = 0; + uint16_t flags = 0; + uint16_t type = 0; + u_int32_t ace_flags; + int wkg; + int err = 0; + + *nentries = k_acl->acl_entrycount; + + // memset(aces, 0, sizeof (*aces) * *nentries); + + // *nentries = aclp->acl_cnt; + + for (i = 0; i < *nentries; i++) { + // entry = &(aclp->acl_entry[i]); + + flags = 0; + mask = 0; + + ace = &(aces[i]); + + /* Note Mac OS X GUID is a 128-bit identifier */ + guidp = &k_acl->acl_ace[i].ace_applicable; + + who = -1; + wkg = kauth_wellknown_guid(guidp); + + switch (wkg) { + case KAUTH_WKG_OWNER: + flags |= ACE_OWNER; + if (seen_type) *seen_type |= ACE_OWNER; + break; + case KAUTH_WKG_GROUP: + flags |= ACE_GROUP|ACE_IDENTIFIER_GROUP; + if (seen_type) *seen_type |= ACE_GROUP; + break; + case KAUTH_WKG_EVERYBODY: + flags |= ACE_EVERYONE; + if (seen_type) *seen_type |= ACE_EVERYONE; + break; + + case KAUTH_WKG_NOBODY: + default: + /* Try to get a uid from supplied guid */ + err = kauth_cred_guid2uid(guidp, &who); + if (err) { + err = kauth_cred_guid2gid(guidp, &who); + if (!err) { + flags |= ACE_IDENTIFIER_GROUP; + } + } + if (err) { + *nentries = 0; + return; + } + + } // switch + + ace->a_who = who; + + ace_rights = k_acl->acl_ace[i].ace_rights; + if (ace_rights & KAUTH_VNODE_READ_DATA) + mask |= ACE_READ_DATA; + if (ace_rights & KAUTH_VNODE_WRITE_DATA) + mask |= ACE_WRITE_DATA; + if (ace_rights & KAUTH_VNODE_APPEND_DATA) + mask |= ACE_APPEND_DATA; + if (ace_rights & KAUTH_VNODE_READ_EXTATTRIBUTES) + mask |= ACE_READ_NAMED_ATTRS; + if (ace_rights & KAUTH_VNODE_WRITE_EXTATTRIBUTES) + mask |= ACE_WRITE_NAMED_ATTRS; + if (ace_rights & KAUTH_VNODE_EXECUTE) + mask |= ACE_EXECUTE; + if (ace_rights & KAUTH_VNODE_DELETE_CHILD) + mask |= ACE_DELETE_CHILD; + if (ace_rights & KAUTH_VNODE_READ_ATTRIBUTES) + mask |= ACE_READ_ATTRIBUTES; + if (ace_rights & KAUTH_VNODE_WRITE_ATTRIBUTES) + mask |= ACE_WRITE_ATTRIBUTES; + if (ace_rights & KAUTH_VNODE_DELETE) + mask |= ACE_DELETE; + if (ace_rights & KAUTH_VNODE_READ_SECURITY) + mask |= ACE_READ_ACL; + if (ace_rights & KAUTH_VNODE_WRITE_SECURITY) + mask |= ACE_WRITE_ACL; + if (ace_rights & KAUTH_VNODE_TAKE_OWNERSHIP) + mask |= ACE_WRITE_OWNER; + if (ace_rights & KAUTH_VNODE_SYNCHRONIZE) + mask |= ACE_SYNCHRONIZE; + ace->a_access_mask = mask; + + ace_flags = k_acl->acl_ace[i].ace_flags; + if (ace_flags & KAUTH_ACE_FILE_INHERIT) + flags |= ACE_FILE_INHERIT_ACE; + if (ace_flags & KAUTH_ACE_DIRECTORY_INHERIT) + flags |= ACE_DIRECTORY_INHERIT_ACE; + if (ace_flags & KAUTH_ACE_LIMIT_INHERIT) + flags |= ACE_NO_PROPAGATE_INHERIT_ACE; + if (ace_flags & KAUTH_ACE_ONLY_INHERIT) + flags |= ACE_INHERIT_ONLY_ACE; + ace->a_flags = flags; + + switch (ace_flags & KAUTH_ACE_KINDMASK) { + case KAUTH_ACE_PERMIT: + type = ACE_ACCESS_ALLOWED_ACE_TYPE; + break; + case KAUTH_ACE_DENY: + type = ACE_ACCESS_DENIED_ACE_TYPE; + break; + case KAUTH_ACE_AUDIT: + type = ACE_SYSTEM_AUDIT_ACE_TYPE; + break; + case KAUTH_ACE_ALARM: + type = ACE_SYSTEM_ALARM_ACE_TYPE; + break; + } + ace->a_type = type; + dprintf(" ACL: %d type %04x, mask %04x, flags %04x, who %d\n", + i, type, mask, flags, who); + } + +} + +void +finderinfo_update(uint8_t *finderinfo, znode_t *zp) +{ + zfsvfs_t *zfsvfs; + u_int8_t *finfo = NULL; + + if (zp == NULL) + return; + + zfsvfs = zp->z_zfsvfs; + if (zfsvfs == NULL) + return; + + if (zfsvfs->z_mimic != ZFS_MIMIC_HFS) + return; + + /* Advance finfo by 16 bytes to the 2nd half of the finderinfo */ + finfo = (u_int8_t *)finderinfo + 16; + + /* Don't expose a symlink's private type/creator. */ + if (IFTOVT((mode_t)zp->z_mode) == VLNK) { + struct FndrFileInfo *fip; + + fip = (struct FndrFileInfo *)finderinfo; + fip->fdType = 0; + fip->fdCreator = 0; + } + + /* hfs_xattr.c hfs_zero_hidden_fields() */ + if ((IFTOVT((mode_t)zp->z_mode) == VREG) || + (IFTOVT((mode_t)zp->z_mode) == VLNK)) { + struct FndrExtendedFileInfo *extinfo = + (struct FndrExtendedFileInfo *)finfo; + extinfo->document_id = 0; + extinfo->date_added = 0; + extinfo->write_gen_counter = 0; + } + + if (IFTOVT((mode_t)zp->z_mode) == VDIR) { + struct FndrExtendedDirInfo *extinfo = + (struct FndrExtendedDirInfo *)finfo; + extinfo->document_id = 0; + extinfo->date_added = 0; + extinfo->write_gen_counter = 0; + } + +} + +/* + * Document ID. Persistant IDs that can survive "safe saving". + * 'revisiond' appears to use fchflags(UF_TRACKED) on files/dirs + * that it wishes to use DocumentIDs with. Here, we will lookup + * if an entry already has a DocumentID stored in SA, but if not, + * hash the DocumentID for (PARENTID + filename) and return it. + * In vnop_setattr for UF_TRACKED, we will store the DocumentID to + * disk. + * Although it is not entirely clear which situations we should handle + * we do handle: + * + * Case 1: + * "file.txt" gets chflag(UF_TRACKED) and DocumentID set. + * "file.txt" is renamed to "file.tmp". DocumentID is kept. + * "file.txt" is re-created, DocumentID remains same, but not saved. + * + * Case 2: + * "file.txt" gets chflag(UF_TRACKED) and DocumentID set. + * "file.txt" is moved to another directory. DocumentID is kept. + * + * It is interesting to note that HFS+ has "tombstones" which is + * created when a UF_TRACKED entry is unlinked, or, renamed. + * Then if a new entry is created with same PARENT+name, and matching + * tombstone is found, will inherit the DocumentID, and UF_TRACKED flag. + * + * We may need to implement this as well. + * + * If "name" or "parent" is known, pass it along, or it needs to look it up. + * + */ +void +zfs_setattr_generate_id(znode_t *zp, uint64_t val, char *name) +{ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + char *nameptr = NULL; + char *filename = NULL; + uint64_t parent = val; + int error = 0; + uint64_t docid = 0; + + if (!zp->z_document_id && zp->z_sa_hdl) { + + error = sa_lookup(zp->z_sa_hdl, SA_ZPL_DOCUMENTID(zfsvfs), + &docid, sizeof (docid)); + if (!error && docid) { + zp->z_document_id = docid; + return; + } + + /* Have name? */ + if (name && *name) { + nameptr = name; + } else { + /* Do we have parent? */ + if (!parent) { + VERIFY(sa_lookup(zp->z_sa_hdl, + SA_ZPL_PARENT(zfsvfs), &parent, + sizeof (parent)) == 0); + } + /* Lookup filename */ + filename = kmem_zalloc(MAXPATHLEN + 2, KM_SLEEP); + if (zap_value_search(zfsvfs->z_os, parent, zp->z_id, + ZFS_DIRENT_OBJ(-1ULL), filename) == 0) { + + nameptr = filename; + // Might as well keep this name too. + strlcpy(zp->z_name_cache, filename, + MAXPATHLEN); + } + } + + zp->z_document_id = fnv_32a_buf(&parent, sizeof (parent), + FNV1_32A_INIT); + if (nameptr) + zp->z_document_id = + fnv_32a_str(nameptr, zp->z_document_id); + + if (filename) + kmem_free(filename, MAXPATHLEN + 2); + } // !document_id +} + +/* + * setattr asked for UF_TRACKED to be set, which means we will make sure + * we have a hash made (includes getting filename) and stored in SA. + */ +int +zfs_setattr_set_documentid(znode_t *zp, boolean_t update_flags) +{ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + int error = 0; + dmu_tx_t *tx; + int count = 0; + sa_bulk_attr_t bulk[2]; + + dprintf("ZFS: vnop_setattr(UF_TRACKED) obj %llu : documentid %08u\n", + zp->z_id, + zp->z_document_id); + + /* Write the new documentid to SA */ + if ((zfsvfs->z_use_sa == B_TRUE) && + !vfs_isrdonly(zfsvfs->z_vfs) && + spa_writeable(dmu_objset_spa(zfsvfs->z_os))) { + + uint64_t docid = zp->z_document_id; // 32->64 + + if (update_flags == B_TRUE) { + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), + NULL, &zp->z_pflags, 8); + } + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_DOCUMENTID(zfsvfs), NULL, + &docid, sizeof (docid)); + + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE); + + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + } else { + error = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); + dmu_tx_commit(tx); + } + + if (error) + dprintf("ZFS: sa_update(SA_ZPL_DOCUMENTID) failed %d\n", + error); + + } // if z_use_sa && !readonly + + return (error); +} + +int +zfs_hardlink_addmap(znode_t *zp, uint64_t parentid, uint32_t linkid) +{ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + hardlinks_t *searchnode, *findnode; + avl_index_t loc; + + if (zp->z_name_cache[0] == 0) { + dprintf("Addmap: skipping id %llu due to no name.\n", + zp->z_id); + return (0); + } + + dprintf("Addmap('%s' parentid %llu linkid %u (ZFS parentid %llu)\n", + zp->z_name_cache, parentid, linkid, + INO_XNUTOZFS(parentid, zfsvfs->z_root)); + parentid = INO_XNUTOZFS(parentid, zfsvfs->z_root); + + if (linkid == 0) + linkid = atomic_inc_32_nv(&zfs_hardlink_sequence); + + searchnode = kmem_alloc(sizeof (hardlinks_t), KM_SLEEP); + searchnode->hl_parent = parentid; + searchnode->hl_fileid = zp->z_id; + strlcpy(searchnode->hl_name, zp->z_name_cache, PATH_MAX); + + rw_enter(&zfsvfs->z_hardlinks_lock, RW_WRITER); + findnode = avl_find(&zfsvfs->z_hardlinks, searchnode, &loc); + kmem_free(searchnode, sizeof (hardlinks_t)); + if (!findnode) { + // Add hash entry + zp->z_finder_hardlink = TRUE; + findnode = kmem_alloc(sizeof (hardlinks_t), KM_SLEEP); + + findnode->hl_parent = parentid; + findnode->hl_fileid = zp->z_id; + strlcpy(findnode->hl_name, zp->z_name_cache, PATH_MAX); + + findnode->hl_linkid = linkid; + + avl_add(&zfsvfs->z_hardlinks, findnode); + avl_add(&zfsvfs->z_hardlinks_linkid, findnode); + dprintf("ZFS: Inserted new hardlink node (%llu,%llu,'%s') " + "<-> (%x,%u)\n", + findnode->hl_parent, + findnode->hl_fileid, findnode->hl_name, + findnode->hl_linkid, findnode->hl_linkid); + } + rw_exit(&zfsvfs->z_hardlinks_lock); + + return (findnode ? 1 : 0); +} + +/* dst buffer must be at least UUID_PRINTABLE_STRING_LENGTH bytes */ + +int +zfs_vfs_uuid_unparse(uuid_t uuid, char *dst) +{ + if (!uuid || !dst) { + dprintf("%s missing argument\n", __func__); + return (EINVAL); + } + + snprintf(dst, UUID_PRINTABLE_STRING_LENGTH, "%02X%02X%02X%02X-" + "%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15]); + + return (0); +} + +int +zfs_vfs_uuid_gen(const char *osname, uuid_t uuid) +{ + MD5_CTX md5c; + /* namespace (generated by uuidgen) */ + /* 50670853-FBD2-4EC3-9802-73D847BF7E62 */ + char namespace[16] = {0x50, 0x67, 0x08, 0x53, /* - */ + 0xfb, 0xd2, /* - */ 0x4e, 0xc3, /* - */ + 0x98, 0x02, /* - */ + 0x73, 0xd8, 0x47, 0xbf, 0x7e, 0x62}; + + /* Validate arguments */ + if (!osname || !uuid || strlen(osname) == 0) { + dprintf("%s missing argument\n", __func__); + return (EINVAL); + } + + /* + * UUID version 3 (MD5) namespace variant: + * hash namespace (uuid) together with name + */ + MD5Init(&md5c); + MD5Update(&md5c, &namespace, sizeof (namespace)); + MD5Update(&md5c, osname, strlen(osname)); + MD5Final(uuid, &md5c); + + /* + * To make UUID version 3, twiddle a few bits: + * xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx + * [uint32]-[uin-t32]-[uin-t32][uint32] + * M should be 0x3 to indicate uuid v3 + * N should be 0x8, 0x9, 0xa, or 0xb + */ + uuid[6] = (uuid[6] & 0x0F) | 0x30; + uuid[8] = (uuid[8] & 0x3F) | 0x80; + + /* Print all caps */ + // dprintf("%s UUIDgen: [%s](%ld)->" + dprintf("%s UUIDgen: [%s](%ld) -> " + "[%02X%02X%02X%02X-%02X%02X-%02X%02X-" + "%02X%02X-%02X%02X%02X%02X%02X%02X]\n", + __func__, osname, strlen(osname), + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15]); + + return (0); +} + +int +uio_prefaultpages(ssize_t n, struct uio *uio) +{ + return (0); +} diff --git a/module/os/macos/zfs/zfs_vnops_osx_xattr.c b/module/os/macos/zfs/zfs_vnops_osx_xattr.c new file mode 100644 index 000000000000..3725ba0bce5c --- /dev/null +++ b/module/os/macos/zfs/zfs_vnops_osx_xattr.c @@ -0,0 +1,801 @@ +/* + * 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, Lawrence Livermore National Security, LLC. + * + * Extended attributes (xattr) on Solaris are implemented as files + * which exist in a hidden xattr directory. These extended attributes + * can be accessed using the attropen() system call which opens + * the extended attribute. It can then be manipulated just like + * a standard file descriptor. This has a couple advantages such + * as practically no size limit on the file, and the extended + * attributes permissions may differ from those of the parent file. + * This interface is really quite clever, but it's also completely + * different than what is supported on Linux. It also comes with a + * steep performance penalty when accessing small xattrs because they + * are not stored with the parent file. + * + * Under Linux extended attributes are manipulated by the system + * calls getxattr(2), setxattr(2), and listxattr(2). They consider + * extended attributes to be name/value pairs where the name is a + * NULL terminated string. The name must also include one of the + * following namespace prefixes: + * + * user - No restrictions and is available to user applications. + * trusted - Restricted to kernel and root (CAP_SYS_ADMIN) use. + * system - Used for access control lists (system.nfs4_acl, etc). + * security - Used by SELinux to store a files security context. + * + * The value under Linux to limited to 65536 bytes of binary data. + * In practice, individual xattrs tend to be much smaller than this + * and are typically less than 100 bytes. A good example of this + * are the security.selinux xattrs which are less than 100 bytes and + * exist for every file when xattr labeling is enabled. + * + * The Linux xattr implementation has been written to take advantage of + * this typical usage. When the dataset property 'xattr=sa' is set, + * then xattrs will be preferentially stored as System Attributes (SA). + * This allows tiny xattrs (~100 bytes) to be stored with the dnode and + * up to 64k of xattrs to be stored in the spill block. If additional + * xattr space is required, which is unlikely under Linux, they will + * be stored using the traditional directory approach. + * + * This optimization results in roughly a 3x performance improvement + * when accessing xattrs because it avoids the need to perform a seek + * for every xattr value. When multiple xattrs are stored per-file + * the performance improvements are even greater because all of the + * xattrs stored in the spill block will be cached. + * + * However, by default SA based xattrs are disabled in the Linux port + * to maximize compatibility with other implementations. If you do + * enable SA based xattrs then they will not be visible on platforms + * which do not support this feature. + * + * NOTE: One additional consequence of the xattr directory implementation + * is that when an extended attribute is manipulated an inode is created. + * This inode will exist in the Linux inode cache but there will be no + * associated entry in the dentry cache which references it. This is + * safe but it may result in some confusion. Enabling SA based xattrs + * largely avoids the issue except in the overflow case. + */ +/* + * Large Block Coment about differences on XNU goes here, etc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define XATTR_USER_PREFIX "macos:" +#define XATTR_USER_PREFIX_LEN strlen("macos:") + +enum xattr_permission { + XAPERM_DENY, + XAPERM_ALLOW, + XAPERM_COMPAT, +}; + +static unsigned int zfs_xattr_compat = 0; + +static enum xattr_permission +zpl_xattr_permission(struct vnode *dvp, zfs_uio_t *uio, const char *name, + int name_len) +{ + if (xattr_protected(name)) + return (XAPERM_DENY); + return (zfs_xattr_compat ? XAPERM_COMPAT : XAPERM_ALLOW); +} + +/* + * Determine is a given xattr name should be visible and if so copy it + * in to the provided buffer (xf->buf). + */ +static int +zpl_xattr_filldir(struct vnode *dvp, zfs_uio_t *uio, const char *name, + int name_len) +{ + enum xattr_permission perm; + + /* Check permissions using the per-namespace list xattr handler. */ + perm = zpl_xattr_permission(dvp, uio, name, name_len); + if (perm == XAPERM_DENY) + return (0); + + /* If it starts with "macos:", skip past it. */ + if (perm != XAPERM_COMPAT) { + if (name_len >= XATTR_USER_PREFIX_LEN && + strncmp(XATTR_USER_PREFIX, name, + XATTR_USER_PREFIX_LEN) == 0) { + name += XATTR_USER_PREFIX_LEN; + name_len -= XATTR_USER_PREFIX_LEN; + } + } + + /* When resid is 0 only calculate the required size. */ + if (uio == NULL || zfs_uio_resid(uio) == 0) { + zfs_uio_setoffset(uio, zfs_uio_offset(uio) + + name_len + 1); + return (0); + } + + if (name_len + 1 > zfs_uio_resid(uio)) + return (ERANGE); + + zfs_uiomove((void *)name, name_len + 1, UIO_READ, uio); + + return (0); +} + +/* + * Read as many directory entry names as will fit in to the provided buffer, + * or when no buffer is provided calculate the required buffer size. + */ +static int +zpl_xattr_readdir(struct vnode *dxip, struct vnode *dvp, zfs_uio_t *uio) +{ + zap_cursor_t zc; + zap_attribute_t zap; + int error; + + zap_cursor_init(&zc, ITOZSB(dxip)->z_os, ITOZ(dxip)->z_id); + + while ((error = zap_cursor_retrieve(&zc, &zap)) == 0) { + + if (zap.za_integer_length != 8 || zap.za_num_integers != 1) { + error = ENXIO; + break; + } + + error = zpl_xattr_filldir(dvp, uio, + zap.za_name, strlen(zap.za_name)); + + if (error) + break; + + zap_cursor_advance(&zc); + } + + zap_cursor_fini(&zc); + + if (error == ENOENT) + error = 0; + + return (error); +} + +static ssize_t +zpl_xattr_list_dir(struct vnode *dvp, zfs_uio_t *uio, cred_t *cr) +{ + struct vnode *dxip = NULL; + znode_t *dxzp; + int error; + + /* Lookup the xattr directory */ + error = zfs_lookup(ITOZ(dvp), NULL, &dxzp, LOOKUP_XATTR, + cr, NULL, NULL); + if (error) { + if (error == ENOENT) + error = 0; + return (error); + } + + dxip = ZTOI(dxzp); + error = zpl_xattr_readdir(dxip, dvp, uio); + VN_RELE(dxip); + + return (error); +} + +static ssize_t +zpl_xattr_list_sa(struct vnode *dvp, zfs_uio_t *uio) +{ + znode_t *zp = ITOZ(dvp); + nvpair_t *nvp = NULL; + int error = 0; + + mutex_enter(&zp->z_lock); + if (zp->z_xattr_cached == NULL) + error = zfs_sa_get_xattr(zp); + mutex_exit(&zp->z_lock); + + if (error) + return (error); + + ASSERT(zp->z_xattr_cached); + + while ((nvp = nvlist_next_nvpair(zp->z_xattr_cached, nvp)) != NULL) { + ASSERT3U(nvpair_type(nvp), ==, DATA_TYPE_BYTE_ARRAY); + + error = zpl_xattr_filldir(dvp, uio, nvpair_name(nvp), + strlen(nvpair_name(nvp))); + if (error) + return (error); + } + + return (0); +} + +int +zpl_xattr_list(struct vnode *dvp, zfs_uio_t *uio, ssize_t *size, cred_t *cr) +{ + znode_t *zp = ITOZ(dvp); + zfsvfs_t *zfsvfs = ZTOZSB(zp); + int error = 0; + + if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) + return (error); + rw_enter(&zp->z_xattr_lock, RW_READER); + + if (zfsvfs->z_use_sa && zp->z_is_sa) { + error = zpl_xattr_list_sa(dvp, uio); + if (error) + goto out; + } + + error = zpl_xattr_list_dir(dvp, uio, cr); + if (error) + goto out; + + if (size) + *size = zfs_uio_offset(uio); + +out: + + rw_exit(&zp->z_xattr_lock); + zfs_exit(zfsvfs, FTAG); + + return (error); +} + +static int +zpl_xattr_get_dir(struct vnode *ip, const char *name, zfs_uio_t *uio, + ssize_t *size, cred_t *cr) +{ + znode_t *dxzp = NULL; + znode_t *xzp = NULL; + int error; + + /* Lookup the xattr directory */ + error = zfs_lookup(ITOZ(ip), NULL, &dxzp, LOOKUP_XATTR, + cr, NULL, NULL); + if (error) + goto out; + + if (size) + *size = 0; // NA + + /* Lookup a specific xattr name in the directory */ + error = zfs_lookup(dxzp, (char *)name, &xzp, 0, cr, NULL, NULL); + if (error) + goto out; + + if (size) + *size = xzp->z_size; + + if (uio == NULL || zfs_uio_resid(uio) == 0) + goto out; + + if (zfs_uio_resid(uio) < xzp->z_size) { + error = ERANGE; + goto out; + } + + error = zfs_read(xzp, uio, 0, cr); + + if (size) + *size = xzp->z_size - zfs_uio_resid(uio); + +out: + if (xzp) + zrele(xzp); + + if (dxzp) + zrele(dxzp); + + return (error); +} + +static int +zpl_xattr_get_sa(struct vnode *ip, const char *name, zfs_uio_t *uio, + ssize_t *size) +{ + znode_t *zp = ITOZ(ip); + uchar_t *nv_value; + uint_t nv_size; + int error = 0; + + ASSERT(RW_LOCK_HELD(&zp->z_xattr_lock)); + + if (size) + *size = 0; // NA + + mutex_enter(&zp->z_lock); + if (zp->z_xattr_cached == NULL) + error = zfs_sa_get_xattr(zp); + mutex_exit(&zp->z_lock); + + if (error) + return (error); + + ASSERT(zp->z_xattr_cached); + error = nvlist_lookup_byte_array(zp->z_xattr_cached, name, + &nv_value, &nv_size); + if (error) + return (error); + + if (size) + *size = nv_size; + + if (uio == NULL || zfs_uio_resid(uio) == 0) + return (0); + + if (zfs_uio_resid(uio) < nv_size) + return (ERANGE); + + zfs_uiomove(nv_value, nv_size, UIO_READ, uio); + + if (size) + *size = nv_size - zfs_uio_resid(uio); + + return (0); +} + +static int +__zpl_xattr_get(struct vnode *ip, const char *name, zfs_uio_t *uio, + ssize_t *retsize, cred_t *cr) +{ + znode_t *zp = ITOZ(ip); + zfsvfs_t *zfsvfs = ZTOZSB(zp); + int error; + + ASSERT(RW_LOCK_HELD(&zp->z_xattr_lock)); + + if (zfsvfs->z_use_sa && zp->z_is_sa) { + error = zpl_xattr_get_sa(ip, name, uio, retsize); + if (error != ENOENT) + goto out; + } + + error = zpl_xattr_get_dir(ip, name, uio, retsize, cr); + +out: + if (error == ENOENT) + error = ENOATTR; + + return (error); +} + +#define XATTR_NOENT 0x0 +#define XATTR_IN_SA 0x1 +#define XATTR_IN_DIR 0x2 +/* check where the xattr resides */ +static int +__zpl_xattr_where(struct vnode *ip, const char *name, int *where, cred_t *cr) +{ + znode_t *zp = ITOZ(ip); + zfsvfs_t *zfsvfs = ZTOZSB(zp); + int error; + ssize_t retsize; + + ASSERT(where); + ASSERT(RW_LOCK_HELD(&zp->z_xattr_lock)); + + *where = XATTR_NOENT; + if (zfsvfs->z_use_sa && zp->z_is_sa) { + error = zpl_xattr_get_sa(ip, name, NULL, &retsize); + if (error == 0) + *where |= XATTR_IN_SA; + else if (error != ENOENT) + return (error); + } + + error = zpl_xattr_get_dir(ip, name, NULL, &retsize, cr); + if (error == 0) + *where |= XATTR_IN_DIR; + else if (error != ENOENT) + return (error); + + if (*where == (XATTR_IN_SA|XATTR_IN_DIR)) + cmn_err(CE_WARN, "ZFS: inode %p has xattr \"%s\"" + " in both SA and dir", ip, name); + if (*where == XATTR_NOENT) + error = ENOATTR; + else + error = 0; + + return (error); +} + +int +zpl_xattr_get(struct vnode *ip, const char *name, zfs_uio_t *uio, + ssize_t *retsize, cred_t *cr) +{ + znode_t *zp = ITOZ(ip); + zfsvfs_t *zfsvfs = ZTOZSB(zp); + int error; + + if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) + return (error); + + rw_enter(&zp->z_xattr_lock, RW_READER); + /* + * Try to look up the name with the namespace prefix first for + * compatibility with xattrs from this platform. If that fails, + * try again without the namespace prefix for compatibility with + * other platforms. + */ + char *xattr_name = kmem_asprintf("%s%s", XATTR_USER_PREFIX, name); + error = __zpl_xattr_get(ip, xattr_name, uio, retsize, cr); + kmem_strfree(xattr_name); + + if (error == ENOATTR) + error = __zpl_xattr_get(ip, name, uio, retsize, cr); + + rw_exit(&zp->z_xattr_lock); + zfs_exit(zfsvfs, FTAG); + + return (error); +} + +static int +zpl_xattr_set_dir(struct vnode *ip, const char *name, zfs_uio_t *uio, + int flags, cred_t *cr) +{ + znode_t *dxzp = NULL; + znode_t *xzp = NULL; + int lookup_flags, error; + const int xattr_mode = S_IFREG | 0644; + + /* + * Lookup the xattr directory. When we're adding an entry pass + * CREATE_XATTR_DIR to ensure the xattr directory is created. + * When removing an entry this flag is not passed to avoid + * unnecessarily creating a new xattr directory. + */ + lookup_flags = LOOKUP_XATTR; + if (uio != NULL) + lookup_flags |= CREATE_XATTR_DIR; + + error = zfs_lookup(ITOZ(ip), NULL, &dxzp, lookup_flags, + cr, NULL, NULL); + if (error) + goto out; + + /* Lookup a specific xattr name in the directory */ + error = zfs_lookup(dxzp, (char *)name, &xzp, 0, cr, NULL, NULL); + + if (error && (error != ENOENT)) + goto out; + + error = 0; + + /* Remove a specific name xattr when value is set to NULL. */ + if (uio == NULL) { + if (xzp) + error = zfs_remove(dxzp, (char *)name, cr, 0); + + goto out; + } + + /* Lookup failed create a new xattr. */ + if (xzp == NULL) { + struct vnode_attr vattr; + /* Set va_size and 0 - skip zfs_freesp below? */ + VATTR_INIT(&vattr); + VATTR_SET(&vattr, va_type, VREG); + VATTR_SET(&vattr, va_mode, xattr_mode); + VATTR_SET(&vattr, va_uid, crgetfsuid(cr)); + VATTR_SET(&vattr, va_gid, crgetfsgid(cr)); + + error = zfs_create(dxzp, (char *)name, &vattr, 0, 0644, &xzp, + cr, 0, NULL, NULL); + if (error) + goto out; + } + + ASSERT(xzp != NULL); + + error = zfs_freesp(xzp, 0, 0, xattr_mode, TRUE); + if (error) + goto out; + + error = zfs_write(xzp, uio, 0, cr); + +out: + if (error == 0) { + /* + * ip->z_ctime = current_time(ip); + * zfs_mark_inode_dirty(ip); + */ + } + + if (xzp) + zrele(xzp); + + if (dxzp) + zrele(dxzp); + + if (error == ENOENT) + error = ENOATTR; + + return (error); +} + +static int +zpl_xattr_set_sa(struct vnode *ip, const char *name, zfs_uio_t *uio, + int flags, cred_t *cr) +{ + znode_t *zp = ITOZ(ip); + nvlist_t *nvl; + size_t sa_size; + int error = 0; + void *buf = NULL; + int len = 0; + size_t used; + int allocated = 0; + + mutex_enter(&zp->z_lock); + if (zp->z_xattr_cached == NULL) + error = zfs_sa_get_xattr(zp); + mutex_exit(&zp->z_lock); + + if (error) + return (error); + + /* + * We have to be careful not to "consume" the uio, + * in the error cases, as it is to be used next in + * xattr=dir. + * This only supports "one iovec" for the data + */ + if (uio != NULL) { + buf = zfs_uio_iovbase(uio, 0); + len = zfs_uio_iovlen(uio, 0); + } + + ASSERT(zp->z_xattr_cached); + nvl = zp->z_xattr_cached; + + if (uio == NULL) { + error = nvlist_remove(nvl, name, DATA_TYPE_BYTE_ARRAY); + if (error == ENOENT) + error = zpl_xattr_set_dir(ip, name, NULL, flags, cr); + } else { + /* Limited to 32k to keep nvpair memory allocations small */ + if (zfs_uio_resid(uio) > DXATTR_MAX_ENTRY_SIZE) + return (EFBIG); + + /* Prevent the DXATTR SA from consuming the entire SA region */ + error = nvlist_size(nvl, &sa_size, NV_ENCODE_XDR); + if (error) + return (error); + + if (sa_size > DXATTR_MAX_SA_SIZE) + return (EFBIG); + + /* + * Allocate memory to copyin, which is a shame as nvlist + * will also allocate memory to hold it. Could consider a + * nvlist_add_byte_array_uio() so the memcpy(KM_SLEEP); + * zfs_uiomove(buf, ) uses uiomove() + * instead. + */ + if (zfs_uio_segflg(uio) != UIO_SYSSPACE) { + allocated = 1; + buf = kmem_alloc(len, KM_SLEEP); + /* Don't consume uio yet; uiocopy, not uiomove */ + zfs_uiocopy(buf, len, UIO_WRITE, uio, &used); + } + + error = nvlist_add_byte_array(nvl, name, + (uchar_t *)buf, len); + + /* Free this after zfs_sa_set_xattr() */ + + } + + /* + * Update the SA for additions, modifications, and removals. On + * error drop the inconsistent cached version of the nvlist, it + * will be reconstructed from the ARC when next accessed. + */ + if (error == 0) { + error = zfs_sa_set_xattr(zp, name, buf, len); + } + + if (allocated == 1) + kmem_free(buf, len); + + if (error) { + nvlist_free(nvl); + zp->z_xattr_cached = NULL; + } else if (uio != NULL) { + /* Finally consume uio */ + zfs_uio_advance(uio, len); + } + + return (error); +} + +static int +_zpl_xattr_set(struct vnode *ip, const char *name, zfs_uio_t *uio, int flags, + cred_t *cr) +{ + znode_t *zp = ITOZ(ip); + zfsvfs_t *zfsvfs = ZTOZSB(zp); + int where; + int error; + + if ((error = zfs_enter_verify_zp(zfsvfs, zp, FTAG)) != 0) + goto out1; + rw_enter(&zp->z_xattr_lock, RW_WRITER); + + /* + * Before setting the xattr check to see if it already exists. + * This is done to ensure the following optional flags are honored. + * + * XATTR_CREATE: fail if xattr already exists + * XATTR_REPLACE: fail if xattr does not exist + * + * We also want to know if it resides in sa or dir, so we can make + * sure we don't end up with duplicate in both places. + */ + + error = __zpl_xattr_where(ip, name, &where, cr); + if (error != 0) { + if (error != ENOATTR) + goto out; + if (flags & XATTR_REPLACE) + goto out; + + /* The xattr to be removed already doesn't exist */ + error = 0; + } else { + error = EEXIST; + if (flags & XATTR_CREATE) + goto out; + } + + /* + * Preferentially store the xattr as a SA for better performance + * We must not do this for "com.apple.ResourceFork" as it can + * be opened as a vnode. + */ + if (zfsvfs->z_use_sa && zp->z_is_sa && + (zfsvfs->z_xattr_sa || + (uio == NULL && where & XATTR_IN_SA))) { + error = zpl_xattr_set_sa(ip, name, uio, flags, cr); + if (error == 0) { + /* + * Successfully put into SA, we need to clear the one + * in dir. + */ + if (where & XATTR_IN_DIR) + zpl_xattr_set_dir(ip, name, NULL, 0, cr); + goto out; + } + } + + error = zpl_xattr_set_dir(ip, name, uio, flags, cr); + + /* + * Successfully put into dir, we need to clear the one in SA. + */ + if (error == 0 && (where & XATTR_IN_SA)) + zpl_xattr_set_sa(ip, name, NULL, 0, cr); +out: + rw_exit(&zp->z_xattr_lock); + zfs_exit(zfsvfs, FTAG); +out1: + + return (error); +} + +/* + * Return an allocated (caller free()s) string potentially prefixed + * based on the xattr_compat tunable. + */ +const char * +zpl_xattr_prefixname(const char *name) +{ + if (zfs_xattr_compat) { + return (spa_strdup(name)); + } else { + return (kmem_asprintf("%s%s", XATTR_USER_PREFIX, name)); + } +} + +int +zpl_xattr_set(struct vnode *ip, const char *name, zfs_uio_t *uio, int flags, + cred_t *cr) +{ + int error; + + /* + * Remove alternate compat version of the xattr so we only set the + * version specified by the zfs_xattr_compat tunable. + * + * The following flags must be handled correctly: + * + * XATTR_CREATE: fail if xattr already exists + * XATTR_REPLACE: fail if xattr does not exist + */ + + char *prefixed_name = kmem_asprintf("%s%s", XATTR_USER_PREFIX, name); + const char *clear_name, *set_name; + if (zfs_xattr_compat) { + clear_name = prefixed_name; + set_name = name; + } else { + clear_name = name; + set_name = prefixed_name; + } + + /* + * Clear the old value with the alternative name format, if it exists. + */ + error = _zpl_xattr_set(ip, clear_name, NULL, flags, cr); + + /* + * XATTR_CREATE was specified and we failed to clear the xattr + * because it already exists. Stop here. + */ + if (error == EEXIST) + goto out; + + /* + * If XATTR_REPLACE was specified and we succeeded to clear + * an xattr, we don't need to replace anything when setting + * the new value. If we failed with -ENODATA that's fine, + * there was nothing to be cleared and we can ignore the error. + */ + if (error == 0) + flags &= ~XATTR_REPLACE; + + /* + * Special case for macOS com.apple.ResourceFork + */ + if (memcmp(XATTR_RESOURCEFORK_NAME, name, + sizeof (XATTR_RESOURCEFORK_NAME)) == 0) { + /* Clear any SA */ + error = zpl_xattr_set_sa(ip, set_name, NULL, flags, cr); + /* Always create DIR */ + error = zpl_xattr_set_dir(ip, set_name, uio, flags, cr); + goto out; + } + + /* + * Set the new value with the configured name format. + */ + error = _zpl_xattr_set(ip, set_name, uio, flags, cr); +out: + kmem_strfree(prefixed_name); + return (error); +} + + +ZFS_MODULE_PARAM(zfs, zfs_, xattr_compat, UINT, ZMOD_RW, + "Use legacy ZFS xattr naming for writing new user namespace xattrs"); diff --git a/module/os/macos/zfs/zfs_znode.c b/module/os/macos/zfs/zfs_znode.c new file mode 100644 index 000000000000..af15502c0572 --- /dev/null +++ b/module/os/macos/zfs/zfs_znode.c @@ -0,0 +1,2369 @@ +/* + * 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. + * Portions Copyright 2007-2009 Apple Inc. All rights reserved. + * Use is subject to license terms. + * Copyright (c) 2012, 2018 by Delphix. All rights reserved. + */ + +/* Portions Copyright 2007 Jeremy Teo */ +/* Portions Copyright 2011 Martin Matuska */ +/* Portions Copyright 2013 Jorgen Lundman */ + +#ifdef _KERNEL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif /* _KERNEL */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "zfs_prop.h" +#include "zfs_comutil.h" + +/* Used by fstat(1). */ +#ifndef __APPLE__ +SYSCTL_INT(_debug_sizeof, OID_AUTO, znode, CTLFLAG_RD, 0, sizeof (znode_t), + "sizeof (znode_t)"); +#endif +void +zfs_release_sa_handle(sa_handle_t *hdl, dmu_buf_t *db, void *tag); + +/* + * Functions needed for userland (ie: libzpool) are not put under + * #ifdef_KERNEL; the rest of the functions have dependencies + * (such as VFS logic) that will not compile easily in userland. + */ +#ifdef _KERNEL +/* + * This is used by the test suite so that it can delay znodes from being + * freed in order to inspect the unlinked set. + */ +static int zfs_unlink_suspend_progress = 0; + +/* + * This callback is invoked when acquiring a RL_WRITER or RL_APPEND lock on + * z_rangelock. It will modify the offset and length of the lock to reflect + * znode-specific information, and convert RL_APPEND to RL_WRITER. This is + * called with the rangelock_t's rl_lock held, which avoids races. + */ + +kmem_cache_t *znode_cache = NULL; +static kmem_cache_t *znode_hold_cache = NULL; +unsigned int zfs_object_mutex_size = ZFS_OBJ_MTX_SZ; + +/* + * This callback is invoked when acquiring a RL_WRITER or RL_APPEND lock on + * z_rangelock. It will modify the offset and length of the lock to reflect + * znode-specific information, and convert RL_APPEND to RL_WRITER. This is + * called with the rangelock_t's rl_lock held, which avoids races. + */ +static void +zfs_rangelock_cb(zfs_locked_range_t *new, void *arg) +{ + znode_t *zp = arg; + + /* + * If in append mode, convert to writer and lock starting at the + * current end of file. + */ + if (new->lr_type == RL_APPEND) { + new->lr_offset = zp->z_size; + new->lr_type = RL_WRITER; + } + + /* + * If we need to grow the block size then lock the whole file range. + */ + uint64_t end_size = MAX(zp->z_size, new->lr_offset + new->lr_length); + if (end_size > zp->z_blksz && (!ISP2(zp->z_blksz) || + zp->z_blksz < zp->z_zfsvfs->z_max_blksz)) { + new->lr_offset = 0; + new->lr_length = UINT64_MAX; + } +} + +#if 0 // unused function +static void +znode_evict_error(dmu_buf_t *dbuf, void *user_ptr) +{ + /* + * We should never drop all dbuf refs without first clearing + * the eviction callback. + */ + panic("evicting znode %p\n", user_ptr); +} +#endif + +extern struct vop_vector zfs_vnodeops; +extern struct vop_vector zfs_fifoops; +extern struct vop_vector zfs_shareops; + +/* + * XXX: We cannot use this function as a cache constructor, because + * there is one global cache for all file systems and we need + * to pass vfsp here, which is not possible, because argument + * 'cdrarg' is defined at kmem_cache_create() time. + */ +static int +zfs_znode_cache_constructor(void *buf, void *arg, int kmflags) +{ + znode_t *zp = buf; + + list_link_init(&zp->z_link_node); + + mutex_init(&zp->z_lock, NULL, MUTEX_DEFAULT, NULL); + rw_init(&zp->z_map_lock, NULL, RW_DEFAULT, NULL); + rw_init(&zp->z_parent_lock, NULL, RW_DEFAULT, NULL); + rw_init(&zp->z_name_lock, NULL, RW_DEFAULT, NULL); + mutex_init(&zp->z_acl_lock, NULL, MUTEX_DEFAULT, NULL); + rw_init(&zp->z_xattr_lock, NULL, RW_DEFAULT, NULL); + zfs_rangelock_init(&zp->z_rangelock, zfs_rangelock_cb, zp); + + mutex_init(&zp->z_attach_lock, NULL, MUTEX_DEFAULT, NULL); + cv_init(&zp->z_attach_cv, NULL, CV_DEFAULT, NULL); + + zp->z_dirlocks = NULL; + zp->z_acl_cached = NULL; + zp->z_xattr_cached = NULL; + zp->z_xattr_parent = 0; + zp->z_skip_truncate_undo_decmpfs = B_FALSE; + return (0); +} + +static void +zfs_znode_cache_destructor(void *buf, void *arg) +{ + znode_t *zp = buf; + + ASSERT(ZTOV(zp) == NULL); + ASSERT(!list_link_active(&zp->z_link_node)); + mutex_destroy(&zp->z_lock); + rw_destroy(&zp->z_map_lock); + rw_destroy(&zp->z_parent_lock); + rw_destroy(&zp->z_name_lock); + mutex_destroy(&zp->z_acl_lock); + rw_destroy(&zp->z_xattr_lock); + zfs_rangelock_fini(&zp->z_rangelock); + mutex_destroy(&zp->z_attach_lock); + cv_destroy(&zp->z_attach_cv); + + ASSERT(zp->z_dirlocks == NULL); + ASSERT(zp->z_acl_cached == NULL); + ASSERT(zp->z_xattr_cached == NULL); +} + +static int +zfs_znode_hold_cache_constructor(void *buf, void *arg, int kmflags) +{ + znode_hold_t *zh = buf; + + mutex_init(&zh->zh_lock, NULL, MUTEX_DEFAULT, NULL); + zh->zh_refcount = 0; + zh->zh_obj = ZFS_NO_OBJECT; + + return (0); +} + +static void +zfs_znode_hold_cache_destructor(void *buf, void *arg) +{ + znode_hold_t *zh = buf; + + mutex_destroy(&zh->zh_lock); +} + +void +zfs_znode_init(void) +{ + /* + * Initialize zcache. The KMC_SLAB hint is used in order that it be + * backed by kmalloc() when on the Linux slab in order that any + * wait_on_bit() operations on the related inode operate properly. + */ + ASSERT(znode_cache == NULL); + znode_cache = kmem_cache_create("zfs_znode_cache", + sizeof (znode_t), 0, + zfs_znode_cache_constructor, + zfs_znode_cache_destructor, NULL, NULL, + NULL, 0); + + ASSERT(znode_hold_cache == NULL); + znode_hold_cache = kmem_cache_create("zfs_znode_hold_cache", + sizeof (znode_hold_t), 0, zfs_znode_hold_cache_constructor, + zfs_znode_hold_cache_destructor, NULL, NULL, NULL, 0); +} + +void +zfs_znode_fini(void) +{ + /* + * Cleanup zcache + */ + if (znode_cache) + kmem_cache_destroy(znode_cache); + znode_cache = NULL; + + if (znode_hold_cache) + kmem_cache_destroy(znode_hold_cache); + znode_hold_cache = NULL; +} + +/* + * The zfs_znode_hold_enter() / zfs_znode_hold_exit() functions are used to + * serialize access to a znode and its SA buffer while the object is being + * created or destroyed. This kind of locking would normally reside in the + * znode itself but in this case that's impossible because the znode and SA + * buffer may not yet exist. Therefore the locking is handled externally + * with an array of mutexs and AVLs trees which contain per-object locks. + * + * In zfs_znode_hold_enter() a per-object lock is created as needed, inserted + * in to the correct AVL tree and finally the per-object lock is held. In + * zfs_znode_hold_exit() the process is reversed. The per-object lock is + * released, removed from the AVL tree and destroyed if there are no waiters. + * + * This scheme has two important properties: + * + * 1) No memory allocations are performed while holding one of the z_hold_locks. + * This ensures evict(), which can be called from direct memory reclaim, will + * never block waiting on a z_hold_locks which just happens to have hashed + * to the same index. + * + * 2) All locks used to serialize access to an object are per-object and never + * shared. This minimizes lock contention without creating a large number + * of dedicated locks. + * + * On the downside it does require znode_lock_t structures to be frequently + * allocated and freed. However, because these are backed by a kmem cache + * and very short lived this cost is minimal. + */ +int +zfs_znode_hold_compare(const void *a, const void *b) +{ + const znode_hold_t *zh_a = (const znode_hold_t *)a; + const znode_hold_t *zh_b = (const znode_hold_t *)b; + + return (TREE_CMP(zh_a->zh_obj, zh_b->zh_obj)); +} + +boolean_t +zfs_znode_held(zfsvfs_t *zfsvfs, uint64_t obj) +{ + znode_hold_t *zh, search; + int i = ZFS_OBJ_HASH(zfsvfs, obj); + boolean_t held; + + search.zh_obj = obj; + + mutex_enter(&zfsvfs->z_hold_locks[i]); + zh = avl_find(&zfsvfs->z_hold_trees[i], &search, NULL); + held = (zh && MUTEX_HELD(&zh->zh_lock)) ? B_TRUE : B_FALSE; + mutex_exit(&zfsvfs->z_hold_locks[i]); + + return (held); +} + +znode_hold_t * +zfs_znode_hold_enter(zfsvfs_t *zfsvfs, uint64_t obj) +{ + znode_hold_t *zh, *zh_new, search; + int i = ZFS_OBJ_HASH(zfsvfs, obj); + boolean_t found = B_FALSE; + + zh_new = kmem_cache_alloc(znode_hold_cache, KM_SLEEP); + zh_new->zh_obj = obj; + search.zh_obj = obj; + + mutex_enter(&zfsvfs->z_hold_locks[i]); + zh = avl_find(&zfsvfs->z_hold_trees[i], &search, NULL); + if (likely(zh == NULL)) { + zh = zh_new; + avl_add(&zfsvfs->z_hold_trees[i], zh); + } else { + ASSERT3U(zh->zh_obj, ==, obj); + found = B_TRUE; + } + zh->zh_refcount++; + ASSERT3S(zh->zh_refcount, >, 0); + mutex_exit(&zfsvfs->z_hold_locks[i]); + + if (found == B_TRUE) + kmem_cache_free(znode_hold_cache, zh_new); + + ASSERT(MUTEX_NOT_HELD(&zh->zh_lock)); + mutex_enter(&zh->zh_lock); + + return (zh); +} + +void +zfs_znode_hold_exit(zfsvfs_t *zfsvfs, znode_hold_t *zh) +{ + int i = ZFS_OBJ_HASH(zfsvfs, zh->zh_obj); + boolean_t remove = B_FALSE; + + ASSERT(zfs_znode_held(zfsvfs, zh->zh_obj)); + mutex_exit(&zh->zh_lock); + + mutex_enter(&zfsvfs->z_hold_locks[i]); + ASSERT3S(zh->zh_refcount, >, 0); + if (--zh->zh_refcount == 0) { + avl_remove(&zfsvfs->z_hold_trees[i], zh); + remove = B_TRUE; + } + mutex_exit(&zfsvfs->z_hold_locks[i]); + + if (remove == B_TRUE) + kmem_cache_free(znode_hold_cache, zh); +} + +int +zfs_create_share_dir(zfsvfs_t *zfsvfs, dmu_tx_t *tx) +{ + int error = 0; +#if 0 // FIXME, uses vnode struct, not ptr + zfs_acl_ids_t acl_ids; + vattr_t vattr; + znode_t *sharezp; + struct vnode *vp, *vnode; + znode_t *zp; + + vattr.va_mask = ATTR_MODE|ATTR_UID|ATTR_GID|ATTR_TYPE; + vattr.va_type = VDIR; + vattr.va_mode = S_IFDIR|0555; + vattr.va_uid = crgetuid(kcred); + vattr.va_gid = crgetgid(kcred); + + sharezp = kmem_cache_alloc(znode_cache, KM_SLEEP); + sharezp->z_unlinked = 0; + sharezp->z_atime_dirty = 0; + sharezp->z_zfsvfs = zfsvfs; + sharezp->z_is_sa = zfsvfs->z_use_sa; + + sharezp->z_vnode = vnode; + vnode.v_data = sharezp; + + vp = ZTOV(sharezp); + vp->v_type = VDIR; + + VERIFY(0 == zfs_acl_ids_create(sharezp, IS_ROOT_NODE, &vattr, + kcred, NULL, &acl_ids)); + zfs_mknode(sharezp, &vattr, tx, kcred, IS_ROOT_NODE, &zp, &acl_ids); + ASSERT3P(zp, ==, sharezp); + POINTER_INVALIDATE(&sharezp->z_zfsvfs); + error = zap_add(zfsvfs->z_os, MASTER_NODE_OBJ, + ZFS_SHARES_DIR, 8, 1, &sharezp->z_id, tx); + zfsvfs->z_shares_dir = sharezp->z_id; + + zfs_acl_ids_free(&acl_ids); + ZTOV(sharezp)->v_data = NULL; + ZTOV(sharezp)->v_count = 0; + ZTOV(sharezp)->v_holdcnt = 0; + zp->z_vnode = NULL; + sa_handle_destroy(sharezp->z_sa_hdl); + sharezp->z_vnode = NULL; + kmem_cache_free(znode_cache, sharezp); +#endif + return (error); +} + +/* + * define a couple of values we need available + * for both 64 and 32 bit environments. + */ +#ifndef NBITSMINOR64 +#define NBITSMINOR64 32 +#endif +#ifndef MAXMAJ64 +#define MAXMAJ64 0xffffffffUL +#endif +#ifndef MAXMIN64 +#define MAXMIN64 0xffffffffUL +#endif + +/* + * Create special expldev for ZFS private use. + * Can't use standard expldev since it doesn't do + * what we want. The standard expldev() takes a + * dev32_t in LP64 and expands it to a long dev_t. + * We need an interface that takes a dev32_t in ILP32 + * and expands it to a long dev_t. + */ +static uint64_t +zfs_expldev(dev_t dev) +{ + return (((uint64_t)major(dev) << NBITSMINOR64) | minor(dev)); +} +/* + * Special cmpldev for ZFS private use. + * Can't use standard cmpldev since it takes + * a long dev_t and compresses it to dev32_t in + * LP64. We need to do a compaction of a long dev_t + * to a dev32_t in ILP32. + */ +dev_t +zfs_cmpldev(uint64_t dev) +{ + return (makedev((dev >> NBITSMINOR64), (dev & MAXMIN64))); +} + +static void +zfs_znode_sa_init(zfsvfs_t *zfsvfs, znode_t *zp, + dmu_buf_t *db, dmu_object_type_t obj_type, + sa_handle_t *sa_hdl) +{ + ASSERT(zfs_znode_held(zfsvfs, zp->z_id)); + + mutex_enter(&zp->z_lock); + + ASSERT(zp->z_sa_hdl == NULL); + ASSERT(zp->z_acl_cached == NULL); + if (sa_hdl == NULL) { + VERIFY(0 == sa_handle_get_from_db(zfsvfs->z_os, db, zp, + SA_HDL_SHARED, &zp->z_sa_hdl)); + } else { + zp->z_sa_hdl = sa_hdl; + sa_set_userp(sa_hdl, zp); + } + + zp->z_is_sa = (obj_type == DMU_OT_SA) ? B_TRUE : B_FALSE; + + mutex_exit(&zp->z_lock); +} + +void +zfs_znode_dmu_fini(znode_t *zp) +{ + ASSERT(zfs_znode_held(ZTOZSB(zp), zp->z_id) || zp->z_unlinked || + RW_WRITE_HELD(&ZTOZSB(zp)->z_teardown_inactive_lock)); + + sa_handle_destroy(zp->z_sa_hdl); + zp->z_sa_hdl = NULL; +} + +#if 0 // Until we need it ? +static void +zfs_vnode_destroy(struct vnode *vp) +{ + znode_t *zp = VTOZ(vp); + zfsvfs_t *zfsvfs = ZTOZSB(zp); + + if (vp != NULL) { + znode_t *zp = VTOZ(vp); + + if (zp != NULL) { + mutex_enter(&zfsvfs->z_znodes_lock); + if (list_link_active(&zp->z_link_node)) { + list_remove(&zfsvfs->z_all_znodes, zp); + } + mutex_exit(&zfsvfs->z_znodes_lock); + + if (zp->z_acl_cached) { + zfs_acl_free(zp->z_acl_cached); + zp->z_acl_cached = NULL; + } + + if (zp->z_xattr_cached) { + nvlist_free(zp->z_xattr_cached); + zp->z_xattr_cached = NULL; + } + + kmem_cache_free(znode_cache, zp); + } + + vnode_clearfsnode(vp); + vnode_put(vp); + vnode_recycle(vp); + } +} +#endif + +/* + * Construct a new znode/vnode and intialize. + * + * This does not do a call to dmu_set_user() that is + * up to the caller to do, in case you don't want to + * return the znode + */ +static znode_t * +zfs_znode_alloc(zfsvfs_t *zfsvfs, dmu_buf_t *db, int blksz, + dmu_object_type_t obj_type, sa_handle_t *hdl) +{ + znode_t *zp; + struct vnode __maybe_unused *vp; + uint64_t mode; + uint64_t parent; + sa_bulk_attr_t bulk[11]; + int count = 0; + uint64_t projid = ZFS_DEFAULT_PROJID; + + zp = kmem_cache_alloc(znode_cache, KM_SLEEP); + + ASSERT(zp->z_dirlocks == NULL); + ASSERT(!POINTER_IS_VALID(zp->z_zfsvfs)); + + /* + * Defer setting z_zfsvfs until the znode is ready to be a candidate for + * the zfs_znode_move() callback. + */ + zp->z_vnode = NULL; + zp->z_sa_hdl = NULL; + zp->z_unlinked = 0; + zp->z_atime_dirty = 0; + zp->z_mapcnt = 0; + zp->z_id = db->db_object; + zp->z_blksz = blksz; + zp->z_seq = 0x7A4653; + zp->z_sync_cnt = 0; + + zp->z_is_mapped = 0; + zp->z_is_ctldir = 0; + zp->z_vid = 0; + zp->z_uid = 0; + zp->z_gid = 0; + zp->z_size = 0; + zp->z_name_cache[0] = 0; + zp->z_finder_parentid = 0; + zp->z_finder_hardlink = FALSE; + + taskq_init_ent(&zp->z_attach_taskq); + + vp = ZTOV(zp); /* Does nothing in OSX */ + + zfs_znode_sa_init(zfsvfs, zp, db, obj_type, hdl); + + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MODE(zfsvfs), NULL, &mode, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GEN(zfsvfs), NULL, &zp->z_gen, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_SIZE(zfsvfs), NULL, + &zp->z_size, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_LINKS(zfsvfs), NULL, + &zp->z_links, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, + &zp->z_pflags, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_PARENT(zfsvfs), NULL, &parent, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_ATIME(zfsvfs), NULL, + &zp->z_atime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_UID(zfsvfs), NULL, + &zp->z_uid, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GID(zfsvfs), NULL, + &zp->z_gid, 8); + + if (sa_bulk_lookup(zp->z_sa_hdl, bulk, count) != 0 || zp->z_gen == 0 || + (dmu_objset_projectquota_enabled(zfsvfs->z_os) && + (zp->z_pflags & ZFS_PROJID) && + sa_lookup(zp->z_sa_hdl, SA_ZPL_PROJID(zfsvfs), &projid, 8) != 0)) { + if (hdl == NULL) + sa_handle_destroy(zp->z_sa_hdl); + zp->z_sa_hdl = NULL; + printf("znode_alloc: sa_bulk_lookup failed - aborting\n"); + kmem_cache_free(znode_cache, zp); + return (NULL); + } + + zp->z_projid = projid; + zp->z_mode = mode; + + mutex_enter(&zfsvfs->z_znodes_lock); + list_insert_tail(&zfsvfs->z_all_znodes, zp); + membar_producer(); + /* + * Everything else must be valid before assigning z_zfsvfs makes the + * znode eligible for zfs_znode_move(). + */ + zp->z_zfsvfs = zfsvfs; + mutex_exit(&zfsvfs->z_znodes_lock); + + return (zp); +} + + +static uint64_t empty_xattr; +static uint64_t pad[4]; +static zfs_acl_phys_t acl_phys; +/* + * Create a new DMU object to hold a zfs znode. + * + * IN: dzp - parent directory for new znode + * vap - file attributes for new znode + * tx - dmu transaction id for zap operations + * cr - credentials of caller + * flag - flags: + * IS_ROOT_NODE - new object will be root + * IS_XATTR - new object is an attribute + * bonuslen - length of bonus buffer + * setaclp - File/Dir initial ACL + * fuidp - Tracks fuid allocation. + * + * OUT: zpp - allocated znode + * + * OS X implementation notes: + * + * The caller of zfs_mknode() is expected to call zfs_znode_getvnode() + * AFTER the dmu_tx_commit() is performed. This prevents deadlocks + * since vnode_create can indirectly attempt to clean a dirty vnode. + * + * The current list of callers includes: + * zfs_vnop_create + * zfs_vnop_mkdir + * zfs_vnop_symlink + * zfs_obtain_xattr + * zfs_make_xattrdir + */ +void +zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, + uint_t flag, znode_t **zpp, zfs_acl_ids_t *acl_ids) +{ + uint64_t crtime[2], atime[2], mtime[2], ctime[2]; + uint64_t mode, size, links, parent, pflags; + uint64_t projid = ZFS_DEFAULT_PROJID; + uint64_t dzp_pflags = 0; + uint64_t rdev = 0; + zfsvfs_t *zfsvfs = dzp->z_zfsvfs; + dmu_buf_t *db; + timestruc_t now; + uint64_t gen, obj; + int bonuslen; + int dnodesize; + sa_handle_t *sa_hdl; + dmu_object_type_t obj_type; + sa_bulk_attr_t *sa_attrs; + int cnt = 0; + zfs_acl_locator_cb_t locate = { 0 }; + int err = 0; + znode_hold_t *zh; + + ASSERT(vap && (vap->va_mask & (ATTR_TYPE|ATTR_MODE)) == + (ATTR_TYPE|ATTR_MODE)); + + if (zfsvfs->z_replay) { + obj = vap->va_nodeid; + now = vap->va_ctime; /* see zfs_replay_create() */ + gen = vap->va_nblocks; /* ditto */ + dnodesize = vap->va_fsid; /* ditto */ + } else { + obj = 0; + gethrestime(&now); + gen = dmu_tx_get_txg(tx); + dnodesize = dmu_objset_dnodesize(zfsvfs->z_os); + } + + if (dnodesize == 0) + dnodesize = DNODE_MIN_SIZE; + + obj_type = zfsvfs->z_use_sa ? DMU_OT_SA : DMU_OT_ZNODE; + bonuslen = (obj_type == DMU_OT_SA) ? + DN_BONUS_SIZE(dnodesize) : ZFS_OLD_ZNODE_PHYS_SIZE; + + /* + * Create a new DMU object. + */ + /* + * There's currently no mechanism for pre-reading the blocks that will + * be needed to allocate a new object, so we accept the small chance + * that there will be an i/o error and we will fail one of the + * assertions below. + */ + if (vap->va_type == VDIR) { + if (zfsvfs->z_replay) { + VERIFY0(zap_create_claim_norm_dnsize(zfsvfs->z_os, obj, + zfsvfs->z_norm, DMU_OT_DIRECTORY_CONTENTS, + obj_type, bonuslen, dnodesize, tx)); + } else { + obj = zap_create_norm_dnsize(zfsvfs->z_os, + zfsvfs->z_norm, DMU_OT_DIRECTORY_CONTENTS, + obj_type, bonuslen, dnodesize, tx); + } + } else { + if (zfsvfs->z_replay) { + VERIFY0(dmu_object_claim_dnsize(zfsvfs->z_os, obj, + DMU_OT_PLAIN_FILE_CONTENTS, 0, + obj_type, bonuslen, dnodesize, tx)); + } else { + obj = dmu_object_alloc_dnsize(zfsvfs->z_os, + DMU_OT_PLAIN_FILE_CONTENTS, 0, + obj_type, bonuslen, dnodesize, tx); + } + } + + zh = zfs_znode_hold_enter(zfsvfs, obj); + VERIFY0(sa_buf_hold(zfsvfs->z_os, obj, NULL, &db)); + + /* + * If this is the root, fix up the half-initialized parent pointer + * to reference the just-allocated physical data area. + */ + if (flag & IS_ROOT_NODE) { + dzp->z_id = obj; + } else { + dzp_pflags = dzp->z_pflags; + } + + /* + * If parent is an xattr, so am I. + */ + if (dzp_pflags & ZFS_XATTR) { + flag |= IS_XATTR; + } + + if (zfsvfs->z_use_fuids) + pflags = ZFS_ARCHIVE | ZFS_AV_MODIFIED; + else + pflags = 0; + + if (vap->va_type == VDIR) { + size = 2; /* contents ("." and "..") */ + links = (flag & (IS_ROOT_NODE | IS_XATTR)) ? 2 : 1; + } else { + size = links = 0; + } + + if (vap->va_type == VBLK || vap->va_type == VCHR) { + rdev = zfs_expldev(vap->va_rdev); + } + + parent = dzp->z_id; + mode = acl_ids->z_mode; + if (flag & IS_XATTR) + pflags |= ZFS_XATTR; + + if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) { + /* + * With ZFS_PROJID flag, we can easily know whether there is + * project ID stored on disk or not. See zfs_space_delta_cb(). + */ + if (obj_type != DMU_OT_ZNODE && + dmu_objset_projectquota_enabled(zfsvfs->z_os)) + pflags |= ZFS_PROJID; + + /* + * Inherit project ID from parent if required. + */ + projid = zfs_inherit_projid(dzp); + if (dzp->z_pflags & ZFS_PROJINHERIT) + pflags |= ZFS_PROJINHERIT; + } + + /* + * No execs denied will be deterimed when zfs_mode_compute() is called. + */ + pflags |= acl_ids->z_aclp->z_hints & + (ZFS_ACL_TRIVIAL|ZFS_INHERIT_ACE|ZFS_ACL_AUTO_INHERIT| + ZFS_ACL_DEFAULTED|ZFS_ACL_PROTECTED); + + ZFS_TIME_ENCODE(&now, crtime); + ZFS_TIME_ENCODE(&now, ctime); + + if (vap->va_mask & ATTR_ATIME) { + ZFS_TIME_ENCODE(&vap->va_atime, atime); + } else { + ZFS_TIME_ENCODE(&now, atime); + } + + if (vap->va_mask & ATTR_MTIME) { + ZFS_TIME_ENCODE(&vap->va_mtime, mtime); + } else { + ZFS_TIME_ENCODE(&now, mtime); + } + + /* Now add in all of the "SA" attributes */ + VERIFY(0 == sa_handle_get_from_db(zfsvfs->z_os, db, NULL, SA_HDL_SHARED, + &sa_hdl)); + + /* + * Setup the array of attributes to be replaced/set on the new file + * + * order for DMU_OT_ZNODE is critical since it needs to be constructed + * in the old znode_phys_t format. Don't change this ordering + */ + sa_attrs = kmem_alloc(sizeof (sa_bulk_attr_t) * ZPL_END, KM_SLEEP); + + if (obj_type == DMU_OT_ZNODE) { + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_ATIME(zfsvfs), + NULL, &atime, 16); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_MTIME(zfsvfs), + NULL, &mtime, 16); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_CTIME(zfsvfs), + NULL, &ctime, 16); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_CRTIME(zfsvfs), + NULL, &crtime, 16); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_GEN(zfsvfs), + NULL, &gen, 8); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_MODE(zfsvfs), + NULL, &mode, 8); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_SIZE(zfsvfs), + NULL, &size, 8); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_PARENT(zfsvfs), + NULL, &parent, 8); + } else { + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_MODE(zfsvfs), + NULL, &mode, 8); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_SIZE(zfsvfs), + NULL, &size, 8); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_GEN(zfsvfs), + NULL, &gen, 8); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_UID(zfsvfs), + NULL, &acl_ids->z_fuid, 8); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_GID(zfsvfs), + NULL, &acl_ids->z_fgid, 8); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_PARENT(zfsvfs), + NULL, &parent, 8); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_FLAGS(zfsvfs), + NULL, &pflags, 8); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_ATIME(zfsvfs), + NULL, &atime, 16); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_MTIME(zfsvfs), + NULL, &mtime, 16); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_CTIME(zfsvfs), + NULL, &ctime, 16); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_CRTIME(zfsvfs), + NULL, &crtime, 16); + } + + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_LINKS(zfsvfs), NULL, &links, 8); + + if (obj_type == DMU_OT_ZNODE) { + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_XATTR(zfsvfs), NULL, + &empty_xattr, 8); + } else if (dmu_objset_projectquota_enabled(zfsvfs->z_os) && + pflags & ZFS_PROJID) { + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_PROJID(zfsvfs), + NULL, &projid, 8); + } + if (obj_type == DMU_OT_ZNODE || + (vap->va_type == VBLK || vap->va_type == VCHR)) { + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_RDEV(zfsvfs), + NULL, &rdev, 8); + + } + if (obj_type == DMU_OT_ZNODE) { + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_FLAGS(zfsvfs), + NULL, &pflags, 8); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_UID(zfsvfs), NULL, + &acl_ids->z_fuid, 8); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_GID(zfsvfs), NULL, + &acl_ids->z_fgid, 8); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_PAD(zfsvfs), NULL, pad, + sizeof (uint64_t) * 4); + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_ZNODE_ACL(zfsvfs), NULL, + &acl_phys, sizeof (zfs_acl_phys_t)); + } else if (acl_ids->z_aclp->z_version >= ZFS_ACL_VERSION_FUID) { + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_DACL_COUNT(zfsvfs), NULL, + &acl_ids->z_aclp->z_acl_count, 8); + locate.cb_aclp = acl_ids->z_aclp; + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_DACL_ACES(zfsvfs), + zfs_acl_data_locator, &locate, + acl_ids->z_aclp->z_acl_bytes); + mode = zfs_mode_compute(mode, acl_ids->z_aclp, &pflags, + acl_ids->z_fuid, acl_ids->z_fgid); + } + + VERIFY(sa_replace_all_by_template(sa_hdl, sa_attrs, cnt, tx) == 0); + + if (!(flag & IS_ROOT_NODE)) { + /* + * We must not hold any locks while calling vnode_create inside + * zfs_znode_alloc(), as it may call either of vnop_reclaim, or + * vnop_fsync. If it is not enough to just release ZFS_OBJ_HOLD + * we will have to attach the vnode after the dmu_commit like + * maczfs does, in each vnop caller. + */ + do { + *zpp = zfs_znode_alloc(zfsvfs, db, 0, obj_type, sa_hdl); + } while (*zpp == NULL); + + VERIFY(*zpp != NULL); + VERIFY(dzp != NULL); + } else { + /* + * If we are creating the root node, the "parent" we + * passed in is the znode for the root. + */ + *zpp = dzp; + + (*zpp)->z_sa_hdl = sa_hdl; + } + + (*zpp)->z_pflags = pflags; + (*zpp)->z_mode = mode; + (*zpp)->z_dnodesize = dnodesize; + (*zpp)->z_projid = projid; + + if (vap->va_mask & ATTR_XVATTR) + zfs_xvattr_set(*zpp, (xvattr_t *)vap, tx); + + if (obj_type == DMU_OT_ZNODE || + acl_ids->z_aclp->z_version < ZFS_ACL_VERSION_FUID) { + err = zfs_aclset_common(*zpp, acl_ids->z_aclp, cr, tx); + ASSERT(err == 0); + } + + kmem_free(sa_attrs, sizeof (sa_bulk_attr_t) * ZPL_END); + zfs_znode_hold_exit(zfsvfs, zh); +} + +/* + * Update in-core attributes. It is assumed the caller will be doing an + * sa_bulk_update to push the changes out. + */ +void +zfs_xvattr_set(znode_t *zp, xvattr_t *xvap, dmu_tx_t *tx) +{ + xoptattr_t *xoap; + + xoap = xva_getxoptattr(xvap); + ASSERT(xoap); + + if (XVA_ISSET_REQ(xvap, XAT_CREATETIME)) { + uint64_t times[2]; + ZFS_TIME_ENCODE(&xoap->xoa_createtime, times); + (void) sa_update(zp->z_sa_hdl, SA_ZPL_CRTIME(zp->z_zfsvfs), + ×, sizeof (times), tx); + XVA_SET_RTN(xvap, XAT_CREATETIME); + } + if (XVA_ISSET_REQ(xvap, XAT_READONLY)) { + ZFS_ATTR_SET(zp, ZFS_READONLY, xoap->xoa_readonly, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_READONLY); + } + if (XVA_ISSET_REQ(xvap, XAT_HIDDEN)) { + ZFS_ATTR_SET(zp, ZFS_HIDDEN, xoap->xoa_hidden, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_HIDDEN); + } + if (XVA_ISSET_REQ(xvap, XAT_SYSTEM)) { + ZFS_ATTR_SET(zp, ZFS_SYSTEM, xoap->xoa_system, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_SYSTEM); + } + if (XVA_ISSET_REQ(xvap, XAT_ARCHIVE)) { + ZFS_ATTR_SET(zp, ZFS_ARCHIVE, xoap->xoa_archive, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_ARCHIVE); + } + if (XVA_ISSET_REQ(xvap, XAT_IMMUTABLE)) { + ZFS_ATTR_SET(zp, ZFS_IMMUTABLE, xoap->xoa_immutable, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_IMMUTABLE); + } + if (XVA_ISSET_REQ(xvap, XAT_NOUNLINK)) { + ZFS_ATTR_SET(zp, ZFS_NOUNLINK, xoap->xoa_nounlink, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_NOUNLINK); + } + if (XVA_ISSET_REQ(xvap, XAT_APPENDONLY)) { + ZFS_ATTR_SET(zp, ZFS_APPENDONLY, xoap->xoa_appendonly, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_APPENDONLY); + } + if (XVA_ISSET_REQ(xvap, XAT_NODUMP)) { + ZFS_ATTR_SET(zp, ZFS_NODUMP, xoap->xoa_nodump, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_NODUMP); + } + if (XVA_ISSET_REQ(xvap, XAT_OPAQUE)) { + ZFS_ATTR_SET(zp, ZFS_OPAQUE, xoap->xoa_opaque, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_OPAQUE); + } + if (XVA_ISSET_REQ(xvap, XAT_AV_QUARANTINED)) { + ZFS_ATTR_SET(zp, ZFS_AV_QUARANTINED, + xoap->xoa_av_quarantined, zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_AV_QUARANTINED); + } + if (XVA_ISSET_REQ(xvap, XAT_AV_MODIFIED)) { + ZFS_ATTR_SET(zp, ZFS_AV_MODIFIED, xoap->xoa_av_modified, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_AV_MODIFIED); + } + if (XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP)) { + zfs_sa_set_scanstamp(zp, xvap, tx); + XVA_SET_RTN(xvap, XAT_AV_SCANSTAMP); + } + if (XVA_ISSET_REQ(xvap, XAT_REPARSE)) { + ZFS_ATTR_SET(zp, ZFS_REPARSE, xoap->xoa_reparse, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_REPARSE); + } + if (XVA_ISSET_REQ(xvap, XAT_OFFLINE)) { + ZFS_ATTR_SET(zp, ZFS_OFFLINE, xoap->xoa_offline, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_OFFLINE); + } + if (XVA_ISSET_REQ(xvap, XAT_SPARSE)) { + ZFS_ATTR_SET(zp, ZFS_SPARSE, xoap->xoa_sparse, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_SPARSE); + } + + /* macOS */ + if (XVA_ISSET_REQ(xvap, XAT_TRACKED)) { + ZFS_ATTR_SET(zp, ZFS_TRACKED, xoap->xoa_tracked, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_TRACKED); + } + if (XVA_ISSET_REQ(xvap, XAT_SAPPENDONLY)) { + ZFS_ATTR_SET(zp, ZFS_SAPPENDONLY, xoap->xoa_sappendonly, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_SAPPENDONLY); + } + if (XVA_ISSET_REQ(xvap, XAT_SIMMUTABLE)) { + ZFS_ATTR_SET(zp, ZFS_SIMMUTABLE, xoap->xoa_simmutable, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_SIMMUTABLE); + } +} + +int +zfs_zget(zfsvfs_t *zfsvfs, uint64_t obj_num, znode_t **zpp) +{ + return (zfs_zget_ext(zfsvfs, obj_num, zpp, 0)); +} + +int +zfs_zget_ext(zfsvfs_t *zfsvfs, uint64_t obj_num, znode_t **zpp, + int flags) +{ + dmu_object_info_t doi; + dmu_buf_t *db; + znode_t *zp; + znode_hold_t *zh; + struct vnode *vp = NULL; + sa_handle_t *hdl; + uint32_t vid; + int err; + + dprintf("+zget %llu\n", obj_num); + + *zpp = NULL; + +again: + zh = zfs_znode_hold_enter(zfsvfs, obj_num); + + err = sa_buf_hold(zfsvfs->z_os, obj_num, NULL, &db); + if (err) { + zfs_znode_hold_exit(zfsvfs, zh); + return (err); + } + + dmu_object_info_from_db(db, &doi); + if (doi.doi_bonus_type != DMU_OT_SA && + (doi.doi_bonus_type != DMU_OT_ZNODE || + (doi.doi_bonus_type == DMU_OT_ZNODE && + doi.doi_bonus_size < sizeof (znode_phys_t)))) { + sa_buf_rele(db, NULL); + zfs_znode_hold_exit(zfsvfs, zh); + return (SET_ERROR(EINVAL)); + } + + hdl = dmu_buf_get_user(db); + if (hdl != NULL) { + zp = sa_get_userdata(hdl); + + + /* + * Since "SA" does immediate eviction we + * should never find a sa handle that doesn't + * know about the znode. + */ + ASSERT3P(zp, !=, NULL); + + mutex_enter(&zp->z_lock); + + /* + * Since zp may disappear after we unlock below, + * we save a copy of vp and it's vid + */ + vid = zp->z_vid; + vp = ZTOV(zp); + + /* + * Since we do immediate eviction of the z_dbuf, we + * should never find a dbuf with a znode that doesn't + * know about the dbuf. + */ + ASSERT3U(zp->z_id, ==, obj_num); + + /* + * OS X can return the znode when the file is unlinked + * in order to support the sync of open-unlinked files + */ + if (!(flags & ZGET_FLAG_UNLINKED) && zp->z_unlinked) { + mutex_exit(&zp->z_lock); + sa_buf_rele(db, NULL); + zfs_znode_hold_exit(zfsvfs, zh); + return (ENOENT); + } + + mutex_exit(&zp->z_lock); + sa_buf_rele(db, NULL); + zfs_znode_hold_exit(zfsvfs, zh); + + /* + * We are racing zfs_znode_getvnode() and we got here first, we + * need to let it get ahead + */ + if (!vp) { + + // Wait until attached, if we can. + if ((flags & ZGET_FLAG_ASYNC) && + zfs_znode_asyncwait(zfsvfs, zp) == 0) { + dprintf("%s: waited on z_vnode OK\n", __func__); + } else { + dprintf("%s: async racing attach\n", __func__); + // Could be zp is being torn down, idle a bit, + // and retry. This branch is rarely executed. + kpreempt(KPREEMPT_SYNC); + } + goto again; + } + + /* + * Due to vnode_create() -> zfs_fsync() -> zil_commit() -> + * zget() -> vnode_getwithvid() -> deadlock. Unsure why + * vnode_getwithvid() ends up sleeping in msleep() but + * vnode_get() does not. + * As we can deadlock here using vnode_getwithvid() we will use + * the simpler vnode_get() in the ASYNC cases. We verify the + * vids match below. + */ +#if 0 +// Let's try just using vnode_get() for now, avoid zfs_get_data #ifdef + if ((flags & ZGET_FLAG_ASYNC)) + err = vnode_get(vp); + else + err = vnode_getwithvid(vp, vid); +#else + err = vnode_get(vp); +#endif + + if (err != 0) { + dprintf("ZFS: vnode_get() returned %d\n", err); + kpreempt(KPREEMPT_SYNC); + goto again; + } + + /* + * Since we had to drop all of our locks above, make sure + * that we have the vnode and znode we had before. + */ + mutex_enter(&zp->z_lock); + if ((vid != zp->z_vid) || (vp != ZTOV(zp))) { + mutex_exit(&zp->z_lock); + /* + * Release the wrong vp from vnode_getwithvid(). + */ + VN_RELE(vp); + dprintf("ZFS: the vids do not match part 1\n"); + goto again; + } + if (vnode_vid(vp) != zp->z_vid) + dprintf("ZFS: the vids do not match\n"); + mutex_exit(&zp->z_lock); + + *zpp = zp; + + return (0); + } // if vnode != NULL + + /* + * Not found create new znode/vnode + * but only if file exists. + * + * There is a small window where zfs_vget() could + * find this object while a file create is still in + * progress. This is checked for in zfs_znode_alloc() + * + * if zfs_znode_alloc() fails it will drop the hold on the + * bonus buffer. + */ + + zp = NULL; + zp = zfs_znode_alloc(zfsvfs, db, doi.doi_data_block_size, + doi.doi_bonus_type, NULL); + if (zp == NULL) { + err = SET_ERROR(ENOENT); + zfs_znode_hold_exit(zfsvfs, zh); + dprintf("zget returning %d\n", err); + return (err); + } + + dprintf("zget create: %llu setting to %p\n", obj_num, zp); + *zpp = zp; + + // Spawn taskq to attach while we are locked + if (flags & ZGET_FLAG_ASYNC) { + zfs_znode_asyncgetvnode(zp, zfsvfs); + } + + zfs_znode_hold_exit(zfsvfs, zh); + + /* Attach a vnode to our new znode */ + if (!(flags & ZGET_FLAG_ASYNC)) { + zfs_znode_getvnode(zp, zfsvfs); + } + + dprintf("zget returning %d\n", err); + return (err); +} + + +int +zfs_rezget(znode_t *zp) +{ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + dmu_object_info_t doi; + dmu_buf_t *db; + struct vnode *vp; + uint64_t obj_num = zp->z_id; + uint64_t mode, size; + sa_bulk_attr_t bulk[8]; + int err; + int count = 0; + uint64_t gen; + uint64_t projid = ZFS_DEFAULT_PROJID; + znode_hold_t *zh; + + if (zp->z_is_ctldir) + return (0); + + zh = zfs_znode_hold_enter(zfsvfs, obj_num); + + mutex_enter(&zp->z_acl_lock); + if (zp->z_acl_cached) { + zfs_acl_free(zp->z_acl_cached); + zp->z_acl_cached = NULL; + } + mutex_exit(&zp->z_acl_lock); + + rw_enter(&zp->z_xattr_lock, RW_WRITER); + if (zp->z_xattr_cached) { + nvlist_free(zp->z_xattr_cached); + zp->z_xattr_cached = NULL; + } + + rw_exit(&zp->z_xattr_lock); + + ASSERT(zp->z_sa_hdl == NULL); + err = sa_buf_hold(zfsvfs->z_os, obj_num, NULL, &db); + if (err) { + zfs_znode_hold_exit(zfsvfs, zh); + return (err); + } + + dmu_object_info_from_db(db, &doi); + if (doi.doi_bonus_type != DMU_OT_SA && + (doi.doi_bonus_type != DMU_OT_ZNODE || + (doi.doi_bonus_type == DMU_OT_ZNODE && + doi.doi_bonus_size < sizeof (znode_phys_t)))) { + sa_buf_rele(db, NULL); + zfs_znode_hold_exit(zfsvfs, zh); + return (SET_ERROR(EINVAL)); + } + + zfs_znode_sa_init(zfsvfs, zp, db, doi.doi_bonus_type, NULL); + size = zp->z_size; + + /* reload cached values */ + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GEN(zfsvfs), NULL, + &gen, sizeof (gen)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_SIZE(zfsvfs), NULL, + &zp->z_size, sizeof (zp->z_size)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_LINKS(zfsvfs), NULL, + &zp->z_links, sizeof (zp->z_links)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, + &zp->z_pflags, + sizeof (zp->z_pflags)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_ATIME(zfsvfs), NULL, + &zp->z_atime, sizeof (zp->z_atime)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_UID(zfsvfs), NULL, + &zp->z_uid, sizeof (zp->z_uid)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GID(zfsvfs), NULL, + &zp->z_gid, sizeof (zp->z_gid)); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MODE(zfsvfs), NULL, + &mode, sizeof (mode)); + + if (sa_bulk_lookup(zp->z_sa_hdl, bulk, count)) { + zfs_znode_dmu_fini(zp); + zfs_znode_hold_exit(zfsvfs, zh); + return (SET_ERROR(EIO)); + } + + if (dmu_objset_projectquota_enabled(zfsvfs->z_os)) { + err = sa_lookup(zp->z_sa_hdl, SA_ZPL_PROJID(zfsvfs), + &projid, 8); + if (err != 0 && err != ENOENT) { + zfs_znode_dmu_fini(zp); + zfs_znode_hold_exit(zfsvfs, zh); + return (SET_ERROR(err)); + } + } + + zp->z_projid = projid; + zp->z_mode = mode; + + if (gen != zp->z_gen) { + zfs_znode_dmu_fini(zp); + zfs_znode_hold_exit(zfsvfs, zh); + return (SET_ERROR(EIO)); + } + + /* + * XXXPJD: Not sure how is that possible, but under heavy + * zfs recv -F load it happens that z_gen is the same, but + * vnode type is different than znode type. This would mean + * that for example regular file was replaced with directory + * which has the same object number. + */ + vp = ZTOV(zp); + if (vp != NULL && + vnode_vtype(vp) != IFTOVT((mode_t)zp->z_mode)) { + zfs_znode_dmu_fini(zp); + zfs_znode_hold_exit(zfsvfs, zh); + return (EIO); + } + + zp->z_blksz = doi.doi_data_block_size; + if (vp != NULL) { + vn_pages_remove(vp, 0, 0); + if (zp->z_size != size) + vnode_pager_setsize(vp, zp->z_size); + } + + /* + * If the file has zero links, then it has been unlinked on the send + * side and it must be in the received unlinked set. + * We call zfs_znode_dmu_fini() now to prevent any accesses to the + * stale data and to prevent automatical removal of the file in + * zfs_zinactive(). The file will be removed either when it is removed + * on the send side and the next incremental stream is received or + * when the unlinked set gets processed. + */ + zp->z_unlinked = (zp->z_links == 0); + if (zp->z_unlinked) + zfs_znode_dmu_fini(zp); + + zfs_znode_hold_exit(zfsvfs, zh); + + return (0); +} + +void +zfs_znode_delete(znode_t *zp, dmu_tx_t *tx) +{ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + objset_t *os = zfsvfs->z_os; + uint64_t obj = zp->z_id; + uint64_t acl_obj = zfs_external_acl(zp); + znode_hold_t *zh; + + zh = zfs_znode_hold_enter(zfsvfs, obj); + if (acl_obj) { + VERIFY(!zp->z_is_sa); + VERIFY(0 == dmu_object_free(os, acl_obj, tx)); + } + VERIFY(0 == dmu_object_free(os, obj, tx)); + zfs_znode_dmu_fini(zp); + zfs_znode_hold_exit(zfsvfs, zh); +} + +void +zfs_zinactive(znode_t *zp) +{ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + uint64_t z_id = zp->z_id; + znode_hold_t *zh; + + ASSERT(zp->z_sa_hdl); + + /* + * Don't allow a zfs_zget() while were trying to release this znode + */ + zh = zfs_znode_hold_enter(zfsvfs, z_id); + + mutex_enter(&zp->z_lock); + + /* + * If this was the last reference to a file with no links, remove + * the file from the file system unless the file system is mounted + * read-only. That can happen, for example, if the file system was + * originally read-write, the file was opened, then unlinked and + * the file system was made read-only before the file was finally + * closed. The file will remain in the unlinked set. + */ + if (zp->z_unlinked) { + ASSERT(!zfsvfs->z_issnap); + + if (!(vfs_isrdonly(zfsvfs->z_vfs)) && + !zfs_unlink_suspend_progress) { + mutex_exit(&zp->z_lock); + zfs_znode_hold_exit(zfsvfs, zh); + zfs_rmnode(zp); + return; + } + } + + mutex_exit(&zp->z_lock); + zfs_znode_dmu_fini(zp); + + zfs_znode_hold_exit(zfsvfs, zh); +} + +void +zfs_znode_free(znode_t *zp) +{ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + + mutex_enter(&zfsvfs->z_znodes_lock); + zp->z_vnode = NULL; + zp->z_zfsvfs = NULL; + POINTER_INVALIDATE(&zp->z_zfsvfs); + list_remove(&zfsvfs->z_all_znodes, zp); /* XXX */ + mutex_exit(&zfsvfs->z_znodes_lock); + + if (zp->z_acl_cached) { + zfs_acl_free(zp->z_acl_cached); + zp->z_acl_cached = NULL; + } + + if (zp->z_xattr_cached) { + nvlist_free(zp->z_xattr_cached); + zp->z_xattr_cached = NULL; + } + + ASSERT(zp->z_sa_hdl == NULL); + + kmem_cache_free(znode_cache, zp); +} + + +/* + * Prepare to update znode time stamps. + * + * IN: zp - znode requiring timestamp update + * flag - ATTR_MTIME, ATTR_CTIME, ATTR_ATIME flags + * have_tx - true of caller is creating a new txg + * + * OUT: zp - new atime (via underlying inode's i_atime) + * mtime - new mtime + * ctime - new ctime + * + * NOTE: The arguments are somewhat redundant. The following condition + * is always true: + * + * have_tx == !(flag & ATTR_ATIME) + */ +void +zfs_tstamp_update_setup_ext(znode_t *zp, uint_t flag, uint64_t mtime[2], + uint64_t ctime[2], boolean_t have_tx) +{ + timestruc_t now; + + gethrestime(&now); + + if (have_tx) { /* will sa_bulk_update happen really soon? */ + zp->z_atime_dirty = 0; + zp->z_seq++; + } else { + zp->z_atime_dirty = 1; + } + + if (flag & ATTR_ATIME) { + ZFS_TIME_ENCODE(&now, zp->z_atime); + } + + if (flag & ATTR_MTIME) { + ZFS_TIME_ENCODE(&now, mtime); + if (zp->z_zfsvfs->z_use_fuids) { + zp->z_pflags |= (ZFS_ARCHIVE | + ZFS_AV_MODIFIED); + } + } + + if (flag & ATTR_CTIME) { + ZFS_TIME_ENCODE(&now, ctime); + if (zp->z_zfsvfs->z_use_fuids) + zp->z_pflags |= ZFS_ARCHIVE; + } +} + +void +zfs_tstamp_update_setup(znode_t *zp, uint_t flag, uint64_t mtime[2], + uint64_t ctime[2]) +{ + zfs_tstamp_update_setup_ext(zp, flag, mtime, ctime, B_TRUE); +} + +/* + * Grow the block size for a file. + * + * IN: zp - znode of file to free data in. + * size - requested block size + * tx - open transaction. + * + * NOTE: this function assumes that the znode is write locked. + */ +void +zfs_grow_blocksize(znode_t *zp, uint64_t size, dmu_tx_t *tx) +{ + int error; + u_longlong_t dummy; + + if (size <= zp->z_blksz) + return; + /* + * If the file size is already greater than the current blocksize, + * we will not grow. If there is more than one block in a file, + * the blocksize cannot change. + */ + if (zp->z_blksz && zp->z_size > zp->z_blksz) + return; + + error = dmu_object_set_blocksize(zp->z_zfsvfs->z_os, + zp->z_id, + size, 0, tx); + + if (error == ENOTSUP) + return; + ASSERT(error == 0); + + /* What blocksize did we actually get? */ + dmu_object_size_from_db(sa_get_db(zp->z_sa_hdl), &zp->z_blksz, &dummy); +} + +#ifdef sun +/* + * This is a dummy interface used when pvn_vplist_dirty() should *not* + * be calling back into the fs for a putpage(). E.g.: when truncating + * a file, the pages being "thrown away* don't need to be written out. + */ +static int +zfs_no_putpage(struct vnode *vp, page_t *pp, u_offset_t *offp, size_t *lenp, + int flags, cred_t *cr) +{ + ASSERT(0); + return (0); +} +#endif /* sun */ + +/* + * Increase the file length + * + * IN: zp - znode of file to free data in. + * end - new end-of-file + * + * RETURN: 0 on success, error code on failure + */ +static int +zfs_extend(znode_t *zp, uint64_t end) +{ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + dmu_tx_t *tx; + zfs_locked_range_t *lr; + uint64_t newblksz; + int error; + + /* + * We will change zp_size, lock the whole file. + */ + lr = zfs_rangelock_enter(&zp->z_rangelock, 0, UINT64_MAX, RL_WRITER); + + /* + * Nothing to do if file already at desired length. + */ + if (end <= zp->z_size) { + zfs_rangelock_exit(lr); + return (0); + } + + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); + zfs_sa_upgrade_txholds(tx, zp); + if (end > zp->z_blksz && + (!ISP2(zp->z_blksz) || zp->z_blksz < zfsvfs->z_max_blksz)) { + /* + * We are growing the file past the current block size. + */ + if (zp->z_blksz > zp->z_zfsvfs->z_max_blksz) { + /* + * File's blocksize is already larger than the + * "recordsize" property. Only let it grow to + * the next power of 2. + */ + ASSERT(!ISP2(zp->z_blksz)); + newblksz = MIN(end, 1 << highbit64(zp->z_blksz)); + } else { + newblksz = MIN(end, zp->z_zfsvfs->z_max_blksz); + } + dmu_tx_hold_write(tx, zp->z_id, 0, newblksz); + } else { + newblksz = 0; + } + + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + zfs_rangelock_exit(lr); + return (error); + } + + if (newblksz) + zfs_grow_blocksize(zp, newblksz, tx); + + zp->z_size = end; + + VERIFY(0 == sa_update(zp->z_sa_hdl, SA_ZPL_SIZE(zp->z_zfsvfs), + &zp->z_size, + sizeof (zp->z_size), tx)); + + vnode_pager_setsize(ZTOV(zp), end); + + zfs_rangelock_exit(lr); + + dmu_tx_commit(tx); + + return (0); +} + + +/* + * Free space in a file. + * + * IN: zp - znode of file to free data in. + * off - start of section to free. + * len - length of section to free. + * + * RETURN: 0 on success, error code on failure + */ +static int +zfs_free_range(znode_t *zp, uint64_t off, uint64_t len) +{ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + zfs_locked_range_t *lr; + int error; + + /* + * Lock the range being freed. + */ + lr = zfs_rangelock_enter(&zp->z_rangelock, off, len, RL_WRITER); + + /* + * Nothing to do if file already at desired length. + */ + if (off >= zp->z_size) { + zfs_rangelock_exit(lr); + return (0); + } + + if (off + len > zp->z_size) + len = zp->z_size - off; + + error = dmu_free_long_range(zfsvfs->z_os, zp->z_id, off, len); + + if (error == 0) { + /* + * In FreeBSD we cannot free block in the middle of a file, + * but only at the end of a file, so this code path should + * never happen. + */ + vnode_pager_setsize(ZTOV(zp), off); + } + +#ifdef _LINUX + /* + * Zero partial page cache entries. This must be done under a + * range lock in order to keep the ARC and page cache in sync. + */ + if (zp->z_is_mapped) { + loff_t first_page, last_page, page_len; + loff_t first_page_offset, last_page_offset; + + /* first possible full page in hole */ + first_page = (off + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT; + /* last page of hole */ + last_page = (off + len) >> PAGE_CACHE_SHIFT; + + /* offset of first_page */ + first_page_offset = first_page << PAGE_CACHE_SHIFT; + /* offset of last_page */ + last_page_offset = last_page << PAGE_CACHE_SHIFT; + + /* truncate whole pages */ + if (last_page_offset > first_page_offset) { + truncate_inode_pages_range(ZTOI(zp)->i_mapping, + first_page_offset, last_page_offset - 1); + } + + /* truncate sub-page ranges */ + if (first_page > last_page) { + /* entire punched area within a single page */ + zfs_zero_partial_page(zp, off, len); + } else { + /* beginning of punched area at the end of a page */ + page_len = first_page_offset - off; + if (page_len > 0) + zfs_zero_partial_page(zp, off, page_len); + + /* end of punched area at the beginning of a page */ + page_len = off + len - last_page_offset; + if (page_len > 0) + zfs_zero_partial_page(zp, last_page_offset, + page_len); + } + } +#endif + zfs_rangelock_exit(lr); + + return (error); +} + +/* + * Truncate a file + * + * IN: zp - znode of file to free data in. + * end - new end-of-file. + * + * RETURN: 0 on success, error code on failure + */ +static int +zfs_trunc(znode_t *zp, uint64_t end) +{ + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + struct vnode *vp = ZTOV(zp); + dmu_tx_t *tx; + zfs_locked_range_t *lr; + int error; + sa_bulk_attr_t bulk[2]; + int count = 0; + /* + * We will change zp_size, lock the whole file. + */ + lr = zfs_rangelock_enter(&zp->z_rangelock, 0, UINT64_MAX, RL_WRITER); + + /* + * Nothing to do if file already at desired length. + */ + if (end >= zp->z_size) { + zfs_rangelock_exit(lr); + return (0); + } + + error = dmu_free_long_range(zfsvfs->z_os, zp->z_id, end, + DMU_OBJECT_END); + if (error) { + zfs_rangelock_exit(lr); + return (error); + } + + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); + zfs_sa_upgrade_txholds(tx, zp); + dmu_tx_mark_netfree(tx); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + zfs_rangelock_exit(lr); + return (error); + } + + zp->z_size = end; + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_SIZE(zfsvfs), + NULL, &zp->z_size, sizeof (zp->z_size)); + + if (end == 0) { + zp->z_pflags &= ~ZFS_SPARSE; + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), + NULL, &zp->z_pflags, 8); + } + VERIFY(sa_bulk_update(zp->z_sa_hdl, bulk, count, tx) == 0); + + dmu_tx_commit(tx); + + /* + * Clear any mapped pages in the truncated region. This has to + * happen outside of the transaction to avoid the possibility of + * a deadlock with someone trying to push a page that we are + * about to invalidate. + */ + vnode_pager_setsize(vp, end); + + zfs_rangelock_exit(lr); + + return (0); +} + +/* + * Free space in a file + * + * IN: zp - znode of file to free data in. + * off - start of range + * len - end of range (0 => EOF) + * flag - current file open mode flags. + * log - TRUE if this action should be logged + * + * RETURN: 0 on success, error code on failure + */ +int +zfs_freesp(znode_t *zp, uint64_t off, uint64_t len, int flag, boolean_t log) +{ +// struct vnode *vp = ZTOV(zp); + dmu_tx_t *tx; + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + zilog_t *zilog = zfsvfs->z_log; + uint64_t mode; + uint64_t mtime[2], ctime[2]; + sa_bulk_attr_t bulk[3]; + int count = 0; + int error; + + if (vnode_isfifo(ZTOV(zp))) + return (0); + + if ((error = sa_lookup(zp->z_sa_hdl, SA_ZPL_MODE(zfsvfs), &mode, + sizeof (mode))) != 0) + return (error); + + if (off > zp->z_size) { + error = zfs_extend(zp, off+len); + if (error == 0 && log) + goto log; + goto out; + } + + if (len == 0) { + error = zfs_trunc(zp, off); + } else { + if ((error = zfs_free_range(zp, off, len)) == 0 && + off + len > zp->z_size) + error = zfs_extend(zp, off+len); + } + if (error || !log) + goto out; +log: + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); + zfs_sa_upgrade_txholds(tx, zp); + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + goto out; + } + + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, mtime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, ctime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), + NULL, &zp->z_pflags, 8); + zfs_tstamp_update_setup(zp, CONTENT_MODIFIED, mtime, ctime); + error = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); + ASSERT(error == 0); + + zfs_log_truncate(zilog, tx, TX_TRUNCATE, zp, off, len); + + dmu_tx_commit(tx); + + error = 0; + +out: + + return (error); +} + +void +zfs_create_fs(objset_t *os, cred_t *cr, nvlist_t *zplprops, dmu_tx_t *tx) +{ + zfsvfs_t *zfsvfs; + uint64_t moid, obj, sa_obj, version; + uint64_t sense = ZFS_CASE_SENSITIVE; + uint64_t norm = 0; + nvpair_t *elem; + int size; + int error; + int i; + znode_t *rootzp = NULL; + vattr_t vattr; + znode_t *zp; + zfs_acl_ids_t acl_ids; + + /* + * First attempt to create master node. + */ + /* + * In an empty objset, there are no blocks to read and thus + * there can be no i/o errors (which we assert below). + */ + moid = MASTER_NODE_OBJ; + error = zap_create_claim(os, moid, DMU_OT_MASTER_NODE, + DMU_OT_NONE, 0, tx); + ASSERT(error == 0); + + /* + * Set starting attributes. + */ + version = zfs_zpl_version_map(spa_version(dmu_objset_spa(os))); + elem = NULL; + while ((elem = nvlist_next_nvpair(zplprops, elem)) != NULL) { + /* For the moment we expect all zpl props to be uint64_ts */ + uint64_t val; + const char *name; + + ASSERT(nvpair_type(elem) == DATA_TYPE_UINT64); + VERIFY(nvpair_value_uint64(elem, &val) == 0); + name = nvpair_name(elem); + if (strcmp(name, zfs_prop_to_name(ZFS_PROP_VERSION)) == 0) { + if (val < version) + version = val; + } else { + error = zap_update(os, moid, name, 8, 1, &val, tx); + } + ASSERT(error == 0); + if (strcmp(name, zfs_prop_to_name(ZFS_PROP_NORMALIZE)) == 0) + norm = val; + else if (strcmp(name, zfs_prop_to_name(ZFS_PROP_CASE)) == 0) + sense = val; + } + ASSERT(version != 0); + error = zap_update(os, moid, ZPL_VERSION_STR, 8, 1, &version, tx); + + /* + * Create zap object used for SA attribute registration + */ + + if (version >= ZPL_VERSION_SA) { + sa_obj = zap_create(os, DMU_OT_SA_MASTER_NODE, + DMU_OT_NONE, 0, tx); + error = zap_add(os, moid, ZFS_SA_ATTRS, 8, 1, &sa_obj, tx); + ASSERT(error == 0); + } else { + sa_obj = 0; + } + /* + * Create a delete queue. + */ + obj = zap_create(os, DMU_OT_UNLINKED_SET, DMU_OT_NONE, 0, tx); + + error = zap_add(os, moid, ZFS_UNLINKED_SET, 8, 1, &obj, tx); + ASSERT(error == 0); + + /* + * Create root znode. Create minimal znode/vnode/zfsvfs + * to allow zfs_mknode to work. + */ + VATTR_NULL(&vattr); + vattr.va_mask = ATTR_MODE|ATTR_UID|ATTR_GID|ATTR_TYPE; + vattr.va_type = VDIR; + vattr.va_mode = S_IFDIR|0755; + vattr.va_uid = crgetuid(cr); + vattr.va_gid = crgetgid(cr); + + rootzp = kmem_cache_alloc(znode_cache, KM_SLEEP); + ASSERT(!POINTER_IS_VALID(rootzp->z_zfsvfs)); + rootzp->z_unlinked = 0; + rootzp->z_atime_dirty = 0; + rootzp->z_is_sa = USE_SA(version, os); + rootzp->z_projid = ZFS_DEFAULT_PROJID; + + rootzp->z_vnode = NULL; +#ifndef __APPLE__ + vnode.v_type = VDIR; + vnode.v_data = rootzp; + rootzp->z_vnode = &vnode; +#endif + + zfsvfs = kmem_alloc(sizeof (zfsvfs_t), KM_SLEEP); +#ifdef __APPLE__ + memset(zfsvfs, 0, sizeof (zfsvfs_t)); +#endif + zfsvfs->z_os = os; + zfsvfs->z_parent = zfsvfs; + zfsvfs->z_version = version; + zfsvfs->z_use_fuids = USE_FUIDS(version, os); + zfsvfs->z_use_sa = USE_SA(version, os); + zfsvfs->z_norm = norm; + + error = sa_setup(os, sa_obj, zfs_attr_table, ZPL_END, + &zfsvfs->z_attr_table); + + ASSERT(error == 0); + + /* + * Fold case on file systems that are always or sometimes case + * insensitive. + */ + if (sense == ZFS_CASE_INSENSITIVE || sense == ZFS_CASE_MIXED) + zfsvfs->z_norm |= U8_TEXTPREP_TOUPPER; + + mutex_init(&zfsvfs->z_znodes_lock, NULL, MUTEX_DEFAULT, NULL); + list_create(&zfsvfs->z_all_znodes, sizeof (znode_t), + offsetof(znode_t, z_link_node)); + + size = MIN(1 << (highbit64(zfs_object_mutex_size)-1), ZFS_OBJ_MTX_MAX); + zfsvfs->z_hold_size = size; + zfsvfs->z_hold_trees = kmem_zalloc(sizeof (avl_tree_t) * size, + KM_SLEEP); + zfsvfs->z_hold_locks = kmem_zalloc(sizeof (kmutex_t) * size, KM_SLEEP); + for (i = 0; i != size; i++) { + avl_create(&zfsvfs->z_hold_trees[i], zfs_znode_hold_compare, + sizeof (znode_hold_t), offsetof(znode_hold_t, zh_node)); + mutex_init(&zfsvfs->z_hold_locks[i], NULL, MUTEX_DEFAULT, NULL); + } + + rootzp->z_zfsvfs = zfsvfs; + VERIFY(0 == zfs_acl_ids_create(rootzp, IS_ROOT_NODE, &vattr, + cr, NULL, &acl_ids, NULL)); + zfs_mknode(rootzp, &vattr, tx, cr, IS_ROOT_NODE, &zp, &acl_ids); + ASSERT3P(zp, ==, rootzp); + error = zap_add(os, moid, ZFS_ROOT_OBJ, 8, 1, &rootzp->z_id, tx); + ASSERT(error == 0); + zfs_acl_ids_free(&acl_ids); + POINTER_INVALIDATE(&rootzp->z_zfsvfs); + + sa_handle_destroy(rootzp->z_sa_hdl); + rootzp->z_sa_hdl = NULL; + rootzp->z_vnode = NULL; + kmem_cache_free(znode_cache, rootzp); + + for (i = 0; i != size; i++) { + avl_destroy(&zfsvfs->z_hold_trees[i]); + mutex_destroy(&zfsvfs->z_hold_locks[i]); + } + + /* + * Create shares directory + */ + + error = zfs_create_share_dir(zfsvfs, tx); + + ASSERT(error == 0); + + list_destroy(&zfsvfs->z_all_znodes); + mutex_destroy(&zfsvfs->z_znodes_lock); + + kmem_free(zfsvfs->z_hold_trees, sizeof (avl_tree_t) * size); + kmem_free(zfsvfs->z_hold_locks, sizeof (kmutex_t) * size); + + kmem_free(zfsvfs, sizeof (zfsvfs_t)); +} + +#endif /* _KERNEL */ + +static int +zfs_sa_setup(objset_t *osp, sa_attr_type_t **sa_table) +{ + uint64_t sa_obj = 0; + int error; + + error = zap_lookup(osp, MASTER_NODE_OBJ, ZFS_SA_ATTRS, 8, 1, &sa_obj); + if (error != 0 && error != ENOENT) + return (error); + + error = sa_setup(osp, sa_obj, zfs_attr_table, ZPL_END, sa_table); + return (error); +} +static int +zfs_grab_sa_handle(objset_t *osp, uint64_t obj, sa_handle_t **hdlp, + dmu_buf_t **db, void *tag) +{ + dmu_object_info_t doi; + int error; + + if ((error = sa_buf_hold(osp, obj, tag, db)) != 0) + return (error); + + dmu_object_info_from_db(*db, &doi); + if (((doi.doi_bonus_type != DMU_OT_SA) && + (doi.doi_bonus_type != DMU_OT_ZNODE)) || + ((doi.doi_bonus_type == DMU_OT_ZNODE) && + (doi.doi_bonus_size < sizeof (znode_phys_t)))) { + sa_buf_rele(*db, tag); + return (SET_ERROR(ENOTSUP)); + } + + error = sa_handle_get(osp, obj, NULL, SA_HDL_PRIVATE, hdlp); + if (error != 0) { + sa_buf_rele(*db, tag); + return (error); + } + return (0); +} + +void +zfs_release_sa_handle(sa_handle_t *hdl, dmu_buf_t *db, void *tag) +{ + sa_handle_destroy(hdl); + sa_buf_rele(db, tag); +} + +/* + * Given an object number, return its parent object number and whether + * or not the object is an extended attribute directory. + */ +static int +zfs_obj_to_pobj(objset_t *osp, sa_handle_t *hdl, sa_attr_type_t *sa_table, + uint64_t *pobjp, int *is_xattrdir) +{ + uint64_t parent; + uint64_t pflags; + uint64_t mode; + uint64_t parent_mode; + sa_bulk_attr_t bulk[3]; + sa_handle_t *sa_hdl; + dmu_buf_t *sa_db; + int count = 0; + int error; + + SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_PARENT], NULL, + &parent, sizeof (parent)); + SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_FLAGS], NULL, + &pflags, sizeof (pflags)); + SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_MODE], NULL, + &mode, sizeof (mode)); + + if ((error = sa_bulk_lookup(hdl, bulk, count)) != 0) + return (error); + + /* + * When a link is removed its parent pointer is not changed and will + * be invalid. There are two cases where a link is removed but the + * file stays around, when it goes to the delete queue and when there + * are additional links. + */ + error = zfs_grab_sa_handle(osp, parent, &sa_hdl, &sa_db, FTAG); + if (error != 0) + return (error); + + error = sa_lookup(sa_hdl, ZPL_MODE, &parent_mode, sizeof (parent_mode)); + zfs_release_sa_handle(sa_hdl, sa_db, FTAG); + if (error != 0) + return (error); + + *is_xattrdir = ((pflags & ZFS_XATTR) != 0) && S_ISDIR(mode); + + /* + * Extended attributes can be applied to files, directories, etc. + * Otherwise the parent must be a directory. + */ + if (!*is_xattrdir && !S_ISDIR(parent_mode)) + return ((EINVAL)); + + *pobjp = parent; + + return (0); +} + +/* + * Given an object number, return some zpl level statistics + */ +static int +zfs_obj_to_stats_impl(sa_handle_t *hdl, sa_attr_type_t *sa_table, + zfs_stat_t *sb) +{ + sa_bulk_attr_t bulk[4]; + int count = 0; + + SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_MODE], NULL, + &sb->zs_mode, sizeof (sb->zs_mode)); + SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_GEN], NULL, + &sb->zs_gen, sizeof (sb->zs_gen)); + SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_LINKS], NULL, + &sb->zs_links, sizeof (sb->zs_links)); + SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_CTIME], NULL, + &sb->zs_ctime, sizeof (sb->zs_ctime)); + + return (sa_bulk_lookup(hdl, bulk, count)); +} + +static int +zfs_obj_to_path_impl(objset_t *osp, uint64_t obj, sa_handle_t *hdl, + sa_attr_type_t *sa_table, char *buf, int len) +{ + sa_handle_t *sa_hdl; + sa_handle_t *prevhdl = NULL; + dmu_buf_t *prevdb = NULL; + dmu_buf_t *sa_db = NULL; + char *path = buf + len - 1; + int error; + + *path = '\0'; + sa_hdl = hdl; + + uint64_t deleteq_obj; + VERIFY0(zap_lookup(osp, MASTER_NODE_OBJ, + ZFS_UNLINKED_SET, sizeof (uint64_t), 1, &deleteq_obj)); + error = zap_lookup_int(osp, deleteq_obj, obj); + if (error == 0) { + return (ESTALE); + } else if (error != ENOENT) { + return (error); + } + error = 0; + + for (;;) { + uint64_t pobj = 0; + char component[MAXNAMELEN + 2]; + size_t complen; + int is_xattrdir = 0; + + if (prevdb) + zfs_release_sa_handle(prevhdl, prevdb, FTAG); + + if ((error = zfs_obj_to_pobj(osp, sa_hdl, sa_table, &pobj, + &is_xattrdir)) != 0) + break; + + if (pobj == obj) { + if (path[0] != '/') + *--path = '/'; + break; + } + + component[0] = '/'; + if (is_xattrdir) { + (void) snprintf(component + 1, MAXNAMELEN+1, + ""); + } else { + error = zap_value_search(osp, pobj, obj, + ZFS_DIRENT_OBJ(-1ULL), + component + 1); + if (error != 0) + break; + } + + complen = strlen(component); + path -= complen; + ASSERT(path >= buf); + memcpy(path, component, complen); + obj = pobj; + + if (sa_hdl != hdl) { + prevhdl = sa_hdl; + prevdb = sa_db; + } + error = zfs_grab_sa_handle(osp, obj, &sa_hdl, &sa_db, FTAG); + if (error != 0) { + sa_hdl = prevhdl; + sa_db = prevdb; + break; + } + } + + if (sa_hdl != NULL && sa_hdl != hdl) { + ASSERT(sa_db != NULL); + zfs_release_sa_handle(sa_hdl, sa_db, FTAG); + } + + if (error == 0) + (void) memmove(buf, path, buf + len - path); + + return (error); +} + +int +zfs_obj_to_path(objset_t *osp, uint64_t obj, char *buf, int len) +{ + sa_attr_type_t *sa_table; + sa_handle_t *hdl; + dmu_buf_t *db; + int error; + + error = zfs_sa_setup(osp, &sa_table); + if (error != 0) + return (error); + + error = zfs_grab_sa_handle(osp, obj, &hdl, &db, FTAG); + if (error != 0) + return (error); + + error = zfs_obj_to_path_impl(osp, obj, hdl, sa_table, buf, len); + + zfs_release_sa_handle(hdl, db, FTAG); + return (error); +} + +int +zfs_obj_to_stats(objset_t *osp, uint64_t obj, zfs_stat_t *sb, + char *buf, int len) +{ + char *path = buf + len - 1; + sa_attr_type_t *sa_table; + sa_handle_t *hdl; + dmu_buf_t *db; + int error; + + *path = '\0'; + + error = zfs_sa_setup(osp, &sa_table); + if (error != 0) + return (error); + + error = zfs_grab_sa_handle(osp, obj, &hdl, &db, FTAG); + if (error != 0) + return (error); + + error = zfs_obj_to_stats_impl(hdl, sa_table, sb); + if (error != 0) { + zfs_release_sa_handle(hdl, db, FTAG); + return (error); + } + + error = zfs_obj_to_path_impl(osp, obj, hdl, sa_table, buf, len); + + zfs_release_sa_handle(hdl, db, FTAG); + return (error); +} + +void +zfs_znode_update_vfs(znode_t *zp) +{ + ubc_setsize(ZTOV(zp), zp->z_size); +} + +ZFS_MODULE_PARAM(zfs, zfs_, unlink_suspend_progress, UINT, ZMOD_RW, + "Set to prevent async unlinks "); diff --git a/module/os/macos/zfs/zio_crypt.c b/module/os/macos/zfs/zio_crypt.c new file mode 100644 index 000000000000..2e416ccdfa5f --- /dev/null +++ b/module/os/macos/zfs/zio_crypt.c @@ -0,0 +1,2080 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * This file is responsible for handling all of the details of generating + * encryption parameters and performing encryption and authentication. + * + * BLOCK ENCRYPTION PARAMETERS: + * Encryption /Authentication Algorithm Suite (crypt): + * The encryption algorithm, mode, and key length we are going to use. We + * currently support AES in either GCM or CCM modes with 128, 192, and 256 bit + * keys. All authentication is currently done with SHA512-HMAC. + * + * Plaintext: + * The unencrypted data that we want to encrypt. + * + * Initialization Vector (IV): + * An initialization vector for the encryption algorithms. This is used to + * "tweak" the encryption algorithms so that two blocks of the same data are + * encrypted into different ciphertext outputs, thus obfuscating block patterns. + * The supported encryption modes (AES-GCM and AES-CCM) require that an IV is + * never reused with the same encryption key. This value is stored unencrypted + * and must simply be provided to the decryption function. We use a 96 bit IV + * (as recommended by NIST) for all block encryption. For non-dedup blocks we + * derive the IV randomly. The first 64 bits of the IV are stored in the second + * word of DVA[2] and the remaining 32 bits are stored in the upper 32 bits of + * blk_fill. This is safe because encrypted blocks can't use the upper 32 bits + * of blk_fill. We only encrypt level 0 blocks, which normally have a fill count + * of 1. The only exception is for DMU_OT_DNODE objects, where the fill count of + * level 0 blocks is the number of allocated dnodes in that block. The on-disk + * format supports at most 2^15 slots per L0 dnode block, because the maximum + * block size is 16MB (2^24). In either case, for level 0 blocks this number + * will still be smaller than UINT32_MAX so it is safe to store the IV in the + * top 32 bits of blk_fill, while leaving the bottom 32 bits of the fill count + * for the dnode code. + * + * Master key: + * This is the most important secret data of an encrypted dataset. It is used + * along with the salt to generate that actual encryption keys via HKDF. We + * do not use the master key to directly encrypt any data because there are + * theoretical limits on how much data can actually be safely encrypted with + * any encryption mode. The master key is stored encrypted on disk with the + * user's wrapping key. Its length is determined by the encryption algorithm. + * For details on how this is stored see the block comment in dsl_crypt.c + * + * Salt: + * Used as an input to the HKDF function, along with the master key. We use a + * 64 bit salt, stored unencrypted in the first word of DVA[2]. Any given salt + * can be used for encrypting many blocks, so we cache the current salt and the + * associated derived key in zio_crypt_t so we do not need to derive it again + * needlessly. + * + * Encryption Key: + * A secret binary key, generated from an HKDF function used to encrypt and + * decrypt data. + * + * Message Authentication Code (MAC) + * The MAC is an output of authenticated encryption modes such as AES-GCM and + * AES-CCM. Its purpose is to ensure that an attacker cannot modify encrypted + * data on disk and return garbage to the application. Effectively, it is a + * checksum that can not be reproduced by an attacker. We store the MAC in the + * second 128 bits of blk_cksum, leaving the first 128 bits for a truncated + * regular checksum of the ciphertext which can be used for scrubbing. + * + * OBJECT AUTHENTICATION: + * Some object types, such as DMU_OT_MASTER_NODE cannot be encrypted because + * they contain some info that always needs to be readable. To prevent this + * data from being altered, we authenticate this data using SHA512-HMAC. This + * will produce a MAC (similar to the one produced via encryption) which can + * be used to verify the object was not modified. HMACs do not require key + * rotation or IVs, so we can keep up to the full 3 copies of authenticated + * data. + * + * ZIL ENCRYPTION: + * ZIL blocks have their bp written to disk ahead of the associated data, so we + * cannot store the MAC there as we normally do. For these blocks the MAC is + * stored in the embedded checksum within the zil_chain_t header. The salt and + * IV are generated for the block on bp allocation instead of at encryption + * time. In addition, ZIL blocks have some pieces that must be left in plaintext + * for claiming even though all of the sensitive user data still needs to be + * encrypted. The function zio_crypt_init_uios_zil() handles parsing which + * pieces of the block need to be encrypted. All data that is not encrypted is + * authenticated using the AAD mechanisms that the supported encryption modes + * provide for. In order to preserve the semantics of the ZIL for encrypted + * datasets, the ZIL is not protected at the objset level as described below. + * + * DNODE ENCRYPTION: + * Similarly to ZIL blocks, the core part of each dnode_phys_t needs to be left + * in plaintext for scrubbing and claiming, but the bonus buffers might contain + * sensitive user data. The function zio_crypt_init_uios_dnode() handles parsing + * which pieces of the block need to be encrypted. For more details about + * dnode authentication and encryption, see zio_crypt_init_uios_dnode(). + * + * OBJECT SET AUTHENTICATION: + * Up to this point, everything we have encrypted and authenticated has been + * at level 0 (or -2 for the ZIL). If we did not do any further work the + * on-disk format would be susceptible to attacks that deleted or rearranged + * the order of level 0 blocks. Ideally, the cleanest solution would be to + * maintain a tree of authentication MACs going up the bp tree. However, this + * presents a problem for raw sends. Send files do not send information about + * indirect blocks so there would be no convenient way to transfer the MACs and + * they cannot be recalculated on the receive side without the master key which + * would defeat one of the purposes of raw sends in the first place. Instead, + * for the indirect levels of the bp tree, we use a regular SHA512 of the MACs + * from the level below. We also include some portable fields from blk_prop such + * as the lsize and compression algorithm to prevent the data from being + * misinterpreted. + * + * At the objset level, we maintain 2 separate 256 bit MACs in the + * objset_phys_t. The first one is "portable" and is the logical root of the + * MAC tree maintained in the metadnode's bps. The second, is "local" and is + * used as the root MAC for the user accounting objects, which are also not + * transferred via "zfs send". The portable MAC is sent in the DRR_BEGIN payload + * of the send file. The useraccounting code ensures that the useraccounting + * info is not present upon a receive, so the local MAC can simply be cleared + * out at that time. For more info about objset_phys_t authentication, see + * zio_crypt_do_objset_hmacs(). + * + * CONSIDERATIONS FOR DEDUP: + * In order for dedup to work, blocks that we want to dedup with one another + * need to use the same IV and encryption key, so that they will have the same + * ciphertext. Normally, one should never reuse an IV with the same encryption + * key or else AES-GCM and AES-CCM can both actually leak the plaintext of both + * blocks. In this case, however, since we are using the same plaintext as + * well all that we end up with is a duplicate of the original ciphertext we + * already had. As a result, an attacker with read access to the raw disk will + * be able to tell which blocks are the same but this information is given away + * by dedup anyway. In order to get the same IVs and encryption keys for + * equivalent blocks of data we use an HMAC of the plaintext. We use an HMAC + * here so that a reproducible checksum of the plaintext is never available to + * the attacker. The HMAC key is kept alongside the master key, encrypted on + * disk. The first 64 bits of the HMAC are used in place of the random salt, and + * the next 96 bits are used as the IV. As a result of this mechanism, dedup + * will only work within a clone family since encrypted dedup requires use of + * the same master and HMAC keys. + */ + +/* + * After encrypting many blocks with the same key we may start to run up + * against the theoretical limits of how much data can securely be encrypted + * with a single key using the supported encryption modes. The most obvious + * limitation is that our risk of generating 2 equivalent 96 bit IVs increases + * the more IVs we generate (which both GCM and CCM modes strictly forbid). + * This risk actually grows surprisingly quickly over time according to the + * Birthday Problem. With a total IV space of 2^(96 bits), and assuming we have + * generated n IVs with a cryptographically secure RNG, the approximate + * probability p(n) of a collision is given as: + * + * p(n) ~= e^(-n*(n-1)/(2*(2^96))) + * + * [http://www.math.cornell.edu/~mec/2008-2009/TianyiZheng/Birthday.html] + * + * Assuming that we want to ensure that p(n) never goes over 1 / 1 trillion + * we must not write more than 398,065,730 blocks with the same encryption key. + * Therefore, we rotate our keys after 400,000,000 blocks have been written by + * generating a new random 64 bit salt for our HKDF encryption key generation + * function. + */ +#define ZFS_KEY_MAX_SALT_USES_DEFAULT 400000000 +#define ZFS_CURRENT_MAX_SALT_USES \ + (MIN(zfs_key_max_salt_uses, ZFS_KEY_MAX_SALT_USES_DEFAULT)) +static unsigned long zfs_key_max_salt_uses = ZFS_KEY_MAX_SALT_USES_DEFAULT; + +typedef struct blkptr_auth_buf { + uint64_t bab_prop; /* blk_prop - portable mask */ + uint8_t bab_mac[ZIO_DATA_MAC_LEN]; /* MAC from blk_cksum */ + uint64_t bab_pad; /* reserved for future use */ +} blkptr_auth_buf_t; + +const zio_crypt_info_t zio_crypt_table[ZIO_CRYPT_FUNCTIONS] = { + {"", ZC_TYPE_NONE, 0, "inherit"}, + {"", ZC_TYPE_NONE, 0, "on"}, + {"", ZC_TYPE_NONE, 0, "off"}, + {SUN_CKM_AES_CCM, ZC_TYPE_CCM, 16, "aes-128-ccm"}, + {SUN_CKM_AES_CCM, ZC_TYPE_CCM, 24, "aes-192-ccm"}, + {SUN_CKM_AES_CCM, ZC_TYPE_CCM, 32, "aes-256-ccm"}, + {SUN_CKM_AES_GCM, ZC_TYPE_GCM, 16, "aes-128-gcm"}, + {SUN_CKM_AES_GCM, ZC_TYPE_GCM, 24, "aes-192-gcm"}, + {SUN_CKM_AES_GCM, ZC_TYPE_GCM, 32, "aes-256-gcm"} +}; + +void +zio_crypt_key_destroy(zio_crypt_key_t *key) +{ + rw_destroy(&key->zk_salt_lock); + + /* free crypto templates */ + crypto_destroy_ctx_template(key->zk_current_tmpl); + crypto_destroy_ctx_template(key->zk_hmac_tmpl); + + /* zero out sensitive data */ + memset(key, 0, sizeof (zio_crypt_key_t)); +} + +int +zio_crypt_key_init(uint64_t crypt, zio_crypt_key_t *key) +{ + int ret; + crypto_mechanism_t mech; + uint_t keydata_len; + + ASSERT(key != NULL); + ASSERT3U(crypt, <, ZIO_CRYPT_FUNCTIONS); + + keydata_len = zio_crypt_table[crypt].ci_keylen; + memset(key, 0, sizeof (zio_crypt_key_t)); + rw_init(&key->zk_salt_lock, NULL, RW_DEFAULT, NULL); + + /* fill keydata buffers and salt with random data */ + ret = random_get_bytes((uint8_t *)&key->zk_guid, sizeof (uint64_t)); + if (ret != 0) + goto error; + + ret = random_get_bytes(key->zk_master_keydata, keydata_len); + if (ret != 0) + goto error; + + ret = random_get_bytes(key->zk_hmac_keydata, SHA512_HMAC_KEYLEN); + if (ret != 0) + goto error; + + ret = random_get_bytes(key->zk_salt, ZIO_DATA_SALT_LEN); + if (ret != 0) + goto error; + + /* derive the current key from the master key */ + ret = hkdf_sha512(key->zk_master_keydata, keydata_len, NULL, 0, + key->zk_salt, ZIO_DATA_SALT_LEN, key->zk_current_keydata, + keydata_len); + if (ret != 0) + goto error; + + /* initialize keys for the ICP */ + key->zk_current_key.ck_data = key->zk_current_keydata; + key->zk_current_key.ck_length = CRYPTO_BYTES2BITS(keydata_len); + + key->zk_hmac_key.ck_data = &key->zk_hmac_key; + key->zk_hmac_key.ck_length = CRYPTO_BYTES2BITS(SHA512_HMAC_KEYLEN); + + /* + * Initialize the crypto templates. It's ok if this fails because + * this is just an optimization. + */ + mech.cm_type = crypto_mech2id(zio_crypt_table[crypt].ci_mechname); + ret = crypto_create_ctx_template(&mech, &key->zk_current_key, + &key->zk_current_tmpl); + if (ret != CRYPTO_SUCCESS) + key->zk_current_tmpl = NULL; + + mech.cm_type = crypto_mech2id(SUN_CKM_SHA512_HMAC); + ret = crypto_create_ctx_template(&mech, &key->zk_hmac_key, + &key->zk_hmac_tmpl); + if (ret != CRYPTO_SUCCESS) + key->zk_hmac_tmpl = NULL; + + key->zk_crypt = crypt; + key->zk_version = ZIO_CRYPT_KEY_CURRENT_VERSION; + key->zk_salt_count = 0; + + return (0); + +error: + zio_crypt_key_destroy(key); + return (ret); +} + +static int +zio_crypt_key_change_salt(zio_crypt_key_t *key) +{ + int ret = 0; + uint8_t salt[ZIO_DATA_SALT_LEN]; + crypto_mechanism_t mech; + uint_t keydata_len = zio_crypt_table[key->zk_crypt].ci_keylen; + + /* generate a new salt */ + ret = random_get_bytes(salt, ZIO_DATA_SALT_LEN); + if (ret != 0) + goto error; + + rw_enter(&key->zk_salt_lock, RW_WRITER); + + /* someone beat us to the salt rotation, just unlock and return */ + if (key->zk_salt_count < ZFS_CURRENT_MAX_SALT_USES) + goto out_unlock; + + /* derive the current key from the master key and the new salt */ + ret = hkdf_sha512(key->zk_master_keydata, keydata_len, NULL, 0, + salt, ZIO_DATA_SALT_LEN, key->zk_current_keydata, keydata_len); + if (ret != 0) + goto out_unlock; + + /* assign the salt and reset the usage count */ + memcpy(key->zk_salt, salt, ZIO_DATA_SALT_LEN); + key->zk_salt_count = 0; + + /* destroy the old context template and create the new one */ + crypto_destroy_ctx_template(key->zk_current_tmpl); + ret = crypto_create_ctx_template(&mech, &key->zk_current_key, + &key->zk_current_tmpl); + if (ret != CRYPTO_SUCCESS) + key->zk_current_tmpl = NULL; + + rw_exit(&key->zk_salt_lock); + + return (0); + +out_unlock: + rw_exit(&key->zk_salt_lock); +error: + return (ret); +} + +/* See comment above zfs_key_max_salt_uses definition for details */ +int +zio_crypt_key_get_salt(zio_crypt_key_t *key, uint8_t *salt) +{ + int ret; + boolean_t salt_change; + + rw_enter(&key->zk_salt_lock, RW_READER); + + memcpy(salt, key->zk_salt, ZIO_DATA_SALT_LEN); + salt_change = (atomic_inc_64_nv(&key->zk_salt_count) >= + ZFS_CURRENT_MAX_SALT_USES); + + rw_exit(&key->zk_salt_lock); + + if (salt_change) { + ret = zio_crypt_key_change_salt(key); + if (ret != 0) + goto error; + } + + return (0); + +error: + return (ret); +} + +/* + * This function handles all encryption and decryption in zfs. When + * encrypting it expects puio to reference the plaintext and cuio to + * reference the ciphertext. cuio must have enough space for the + * ciphertext + room for a MAC. datalen should be the length of the + * plaintext / ciphertext alone. + */ +static int +zio_do_crypt_uio(boolean_t encrypt, uint64_t crypt, crypto_key_t *key, + crypto_ctx_template_t tmpl, uint8_t *ivbuf, uint_t datalen, + zfs_uio_t *puio, zfs_uio_t *cuio, uint8_t *authbuf, uint_t auth_len) +{ + int ret; + crypto_data_t plaindata, cipherdata; + CK_AES_CCM_PARAMS ccmp; + CK_AES_GCM_PARAMS gcmp; + crypto_mechanism_t mech; + zio_crypt_info_t crypt_info; + uint_t plain_full_len, maclen; + + ASSERT3U(crypt, <, ZIO_CRYPT_FUNCTIONS); + + /* lookup the encryption info */ + crypt_info = zio_crypt_table[crypt]; + + /* the mac will always be the last iovec_t in the cipher uio */ + maclen = cuio->uio_iov[cuio->uio_iovcnt - 1].iov_len; + + ASSERT(maclen <= ZIO_DATA_MAC_LEN); + + /* setup encryption mechanism (same as crypt) */ + mech.cm_type = crypto_mech2id(crypt_info.ci_mechname); + + /* + * Strangely, the ICP requires that plain_full_len must include + * the MAC length when decrypting, even though the UIO does not + * need to have the extra space allocated. + */ + if (encrypt) { + plain_full_len = datalen; + } else { + plain_full_len = datalen + maclen; + } + + /* + * setup encryption params (currently only AES CCM and AES GCM + * are supported) + */ + if (crypt_info.ci_crypt_type == ZC_TYPE_CCM) { + ccmp.ulNonceSize = ZIO_DATA_IV_LEN; + ccmp.ulAuthDataSize = auth_len; + ccmp.authData = authbuf; + ccmp.ulMACSize = maclen; + ccmp.nonce = ivbuf; + ccmp.ulDataSize = plain_full_len; + + mech.cm_param = (char *)(&ccmp); + mech.cm_param_len = sizeof (CK_AES_CCM_PARAMS); + } else { + gcmp.ulIvLen = ZIO_DATA_IV_LEN; + gcmp.ulIvBits = CRYPTO_BYTES2BITS(ZIO_DATA_IV_LEN); + gcmp.ulAADLen = auth_len; + gcmp.pAAD = authbuf; + gcmp.ulTagBits = CRYPTO_BYTES2BITS(maclen); + gcmp.pIv = ivbuf; + + mech.cm_param = (char *)(&gcmp); + mech.cm_param_len = sizeof (CK_AES_GCM_PARAMS); + } + + /* populate the cipher and plain data structs. */ + plaindata.cd_format = CRYPTO_DATA_UIO; + plaindata.cd_offset = 0; + plaindata.cd_uio = puio; + plaindata.cd_length = plain_full_len; + + cipherdata.cd_format = CRYPTO_DATA_UIO; + cipherdata.cd_offset = 0; + cipherdata.cd_uio = cuio; + cipherdata.cd_length = datalen + maclen; + + /* perform the actual encryption */ + if (encrypt) { + ret = crypto_encrypt(&mech, &plaindata, key, tmpl, &cipherdata); + if (ret != CRYPTO_SUCCESS) { + ret = SET_ERROR(EIO); + goto error; + } + } else { + ret = crypto_decrypt(&mech, &cipherdata, key, tmpl, &plaindata); + if (ret != CRYPTO_SUCCESS) { + ASSERT3U(ret, ==, CRYPTO_INVALID_MAC); + ret = SET_ERROR(ECKSUM); + goto error; + } + } + + return (0); + +error: + return (ret); +} + +int +zio_crypt_key_wrap(crypto_key_t *cwkey, zio_crypt_key_t *key, uint8_t *iv, + uint8_t *mac, uint8_t *keydata_out, uint8_t *hmac_keydata_out) +{ + int ret; + zfs_uio_t puio, cuio; + uint64_t aad[3]; + iovec_t plain_iovecs[2], cipher_iovecs[3]; + uint64_t crypt = key->zk_crypt; + uint_t enc_len, keydata_len, aad_len; + + ASSERT3U(crypt, <, ZIO_CRYPT_FUNCTIONS); + + keydata_len = zio_crypt_table[crypt].ci_keylen; + + /* generate iv for wrapping the master and hmac key */ + ret = random_get_pseudo_bytes(iv, WRAPPING_IV_LEN); + if (ret != 0) + goto error; + + /* initialize zfs_uio_ts */ + plain_iovecs[0].iov_base = key->zk_master_keydata; + plain_iovecs[0].iov_len = keydata_len; + plain_iovecs[1].iov_base = key->zk_hmac_keydata; + plain_iovecs[1].iov_len = SHA512_HMAC_KEYLEN; + + cipher_iovecs[0].iov_base = keydata_out; + cipher_iovecs[0].iov_len = keydata_len; + cipher_iovecs[1].iov_base = hmac_keydata_out; + cipher_iovecs[1].iov_len = SHA512_HMAC_KEYLEN; + cipher_iovecs[2].iov_base = mac; + cipher_iovecs[2].iov_len = WRAPPING_MAC_LEN; + + /* + * Although we don't support writing to the old format, we do + * support rewrapping the key so that the user can move and + * quarantine datasets on the old format. + */ + if (key->zk_version == 0) { + aad_len = sizeof (uint64_t); + aad[0] = LE_64(key->zk_guid); + } else { + ASSERT3U(key->zk_version, ==, ZIO_CRYPT_KEY_CURRENT_VERSION); + aad_len = sizeof (uint64_t) * 3; + aad[0] = LE_64(key->zk_guid); + aad[1] = LE_64(crypt); + aad[2] = LE_64(key->zk_version); + } + + enc_len = zio_crypt_table[crypt].ci_keylen + SHA512_HMAC_KEYLEN; + puio.uio_iov = plain_iovecs; + puio.uio_iovcnt = 2; + puio.uio_segflg = UIO_SYSSPACE; + cuio.uio_iov = cipher_iovecs; + cuio.uio_iovcnt = 3; + cuio.uio_segflg = UIO_SYSSPACE; + + /* encrypt the keys and store the resulting ciphertext and mac */ + ret = zio_do_crypt_uio(B_TRUE, crypt, cwkey, NULL, iv, enc_len, + &puio, &cuio, (uint8_t *)aad, aad_len); + if (ret != 0) + goto error; + + return (0); + +error: + return (ret); +} + +int +zio_crypt_key_unwrap(crypto_key_t *cwkey, uint64_t crypt, uint64_t version, + uint64_t guid, uint8_t *keydata, uint8_t *hmac_keydata, uint8_t *iv, + uint8_t *mac, zio_crypt_key_t *key) +{ + crypto_mechanism_t mech; + zfs_uio_t puio, cuio; + uint64_t aad[3]; + iovec_t plain_iovecs[2], cipher_iovecs[3]; + uint_t enc_len, keydata_len, aad_len; + int ret; + + ASSERT3U(crypt, <, ZIO_CRYPT_FUNCTIONS); + + rw_init(&key->zk_salt_lock, NULL, RW_DEFAULT, NULL); + + keydata_len = zio_crypt_table[crypt].ci_keylen; + + /* initialize zfs_uio_ts */ + plain_iovecs[0].iov_base = key->zk_master_keydata; + plain_iovecs[0].iov_len = keydata_len; + plain_iovecs[1].iov_base = key->zk_hmac_keydata; + plain_iovecs[1].iov_len = SHA512_HMAC_KEYLEN; + + cipher_iovecs[0].iov_base = keydata; + cipher_iovecs[0].iov_len = keydata_len; + cipher_iovecs[1].iov_base = hmac_keydata; + cipher_iovecs[1].iov_len = SHA512_HMAC_KEYLEN; + cipher_iovecs[2].iov_base = mac; + cipher_iovecs[2].iov_len = WRAPPING_MAC_LEN; + + if (version == 0) { + aad_len = sizeof (uint64_t); + aad[0] = LE_64(guid); + } else { + ASSERT3U(version, ==, ZIO_CRYPT_KEY_CURRENT_VERSION); + aad_len = sizeof (uint64_t) * 3; + aad[0] = LE_64(guid); + aad[1] = LE_64(crypt); + aad[2] = LE_64(version); + } + + enc_len = keydata_len + SHA512_HMAC_KEYLEN; + puio.uio_iov = plain_iovecs; + puio.uio_segflg = UIO_SYSSPACE; + puio.uio_iovcnt = 2; + cuio.uio_iov = cipher_iovecs; + cuio.uio_iovcnt = 3; + cuio.uio_segflg = UIO_SYSSPACE; + + /* decrypt the keys and store the result in the output buffers */ + ret = zio_do_crypt_uio(B_FALSE, crypt, cwkey, NULL, iv, enc_len, + &puio, &cuio, (uint8_t *)aad, aad_len); + if (ret != 0) + goto error; + + /* generate a fresh salt */ + ret = random_get_bytes(key->zk_salt, ZIO_DATA_SALT_LEN); + if (ret != 0) + goto error; + + /* derive the current key from the master key */ + ret = hkdf_sha512(key->zk_master_keydata, keydata_len, NULL, 0, + key->zk_salt, ZIO_DATA_SALT_LEN, key->zk_current_keydata, + keydata_len); + if (ret != 0) + goto error; + + /* initialize keys for ICP */ + key->zk_current_key.ck_data = key->zk_current_keydata; + key->zk_current_key.ck_length = CRYPTO_BYTES2BITS(keydata_len); + + key->zk_hmac_key.ck_data = key->zk_hmac_keydata; + key->zk_hmac_key.ck_length = CRYPTO_BYTES2BITS(SHA512_HMAC_KEYLEN); + + /* + * Initialize the crypto templates. It's ok if this fails because + * this is just an optimization. + */ + mech.cm_type = crypto_mech2id(zio_crypt_table[crypt].ci_mechname); + ret = crypto_create_ctx_template(&mech, &key->zk_current_key, + &key->zk_current_tmpl); + if (ret != CRYPTO_SUCCESS) + key->zk_current_tmpl = NULL; + + mech.cm_type = crypto_mech2id(SUN_CKM_SHA512_HMAC); + ret = crypto_create_ctx_template(&mech, &key->zk_hmac_key, + &key->zk_hmac_tmpl); + if (ret != CRYPTO_SUCCESS) + key->zk_hmac_tmpl = NULL; + + key->zk_crypt = crypt; + key->zk_version = version; + key->zk_guid = guid; + key->zk_salt_count = 0; + + return (0); + +error: + zio_crypt_key_destroy(key); + return (ret); +} + +int +zio_crypt_generate_iv(uint8_t *ivbuf) +{ + int ret; + + /* randomly generate the IV */ + ret = random_get_pseudo_bytes(ivbuf, ZIO_DATA_IV_LEN); + if (ret != 0) + goto error; + + return (0); + +error: + memset(ivbuf, 0, ZIO_DATA_IV_LEN); + return (ret); +} + +int +zio_crypt_do_hmac(zio_crypt_key_t *key, uint8_t *data, uint_t datalen, + uint8_t *digestbuf, uint_t digestlen) +{ + int ret; + crypto_mechanism_t mech; + crypto_data_t in_data, digest_data; + uint8_t raw_digestbuf[SHA512_DIGEST_LENGTH]; + + ASSERT3U(digestlen, <=, SHA512_DIGEST_LENGTH); + + /* initialize sha512-hmac mechanism and crypto data */ + mech.cm_type = crypto_mech2id(SUN_CKM_SHA512_HMAC); + mech.cm_param = NULL; + mech.cm_param_len = 0; + + /* initialize the crypto data */ + in_data.cd_format = CRYPTO_DATA_RAW; + in_data.cd_offset = 0; + in_data.cd_length = datalen; + in_data.cd_raw.iov_base = (char *)data; + in_data.cd_raw.iov_len = in_data.cd_length; + + digest_data.cd_format = CRYPTO_DATA_RAW; + digest_data.cd_offset = 0; + digest_data.cd_length = SHA512_DIGEST_LENGTH; + digest_data.cd_raw.iov_base = (char *)raw_digestbuf; + digest_data.cd_raw.iov_len = digest_data.cd_length; + + /* generate the hmac */ + ret = crypto_mac(&mech, &in_data, &key->zk_hmac_key, key->zk_hmac_tmpl, + &digest_data); + if (ret != CRYPTO_SUCCESS) { + ret = SET_ERROR(EIO); + goto error; + } + + memcpy(digestbuf, raw_digestbuf, digestlen); + + return (0); + +error: + memset(digestbuf, 0, digestlen); + return (ret); +} + +int +zio_crypt_generate_iv_salt_dedup(zio_crypt_key_t *key, uint8_t *data, + uint_t datalen, uint8_t *ivbuf, uint8_t *salt) +{ + int ret; + uint8_t digestbuf[SHA512_DIGEST_LENGTH]; + + ret = zio_crypt_do_hmac(key, data, datalen, + digestbuf, SHA512_DIGEST_LENGTH); + if (ret != 0) + return (ret); + + memcpy(salt, digestbuf, ZIO_DATA_SALT_LEN); + memcpy(ivbuf, digestbuf + ZIO_DATA_SALT_LEN, ZIO_DATA_IV_LEN); + + return (0); +} + +/* + * The following functions are used to encode and decode encryption parameters + * into blkptr_t and zil_header_t. The ICP wants to use these parameters as + * byte strings, which normally means that these strings would not need to deal + * with byteswapping at all. However, both blkptr_t and zil_header_t may be + * byteswapped by lower layers and so we must "undo" that byteswap here upon + * decoding and encoding in a non-native byteorder. These functions require + * that the byteorder bit is correct before being called. + */ +void +zio_crypt_encode_params_bp(blkptr_t *bp, uint8_t *salt, uint8_t *iv) +{ + uint64_t val64; + uint32_t val32; + + ASSERT(BP_IS_ENCRYPTED(bp)); + + if (!BP_SHOULD_BYTESWAP(bp)) { + memcpy(&bp->blk_dva[2].dva_word[0], salt, sizeof (uint64_t)); + memcpy(&bp->blk_dva[2].dva_word[1], iv, sizeof (uint64_t)); + memcpy(&val32, iv + sizeof (uint64_t), sizeof (uint32_t)); + BP_SET_IV2(bp, val32); + } else { + memcpy(&val64, salt, sizeof (uint64_t)); + bp->blk_dva[2].dva_word[0] = BSWAP_64(val64); + + memcpy(&val64, iv, sizeof (uint64_t)); + bp->blk_dva[2].dva_word[1] = BSWAP_64(val64); + + memcpy(&val32, iv + sizeof (uint64_t), sizeof (uint32_t)); + BP_SET_IV2(bp, BSWAP_32(val32)); + } +} + +void +zio_crypt_decode_params_bp(const blkptr_t *bp, uint8_t *salt, uint8_t *iv) +{ + uint64_t val64; + uint32_t val32; + + ASSERT(BP_IS_PROTECTED(bp)); + + /* for convenience, so callers don't need to check */ + if (BP_IS_AUTHENTICATED(bp)) { + memset(salt, 0, ZIO_DATA_SALT_LEN); + memset(iv, 0, ZIO_DATA_IV_LEN); + return; + } + + if (!BP_SHOULD_BYTESWAP(bp)) { + memcpy(salt, &bp->blk_dva[2].dva_word[0], sizeof (uint64_t)); + memcpy(iv, &bp->blk_dva[2].dva_word[1], sizeof (uint64_t)); + + val32 = (uint32_t)BP_GET_IV2(bp); + memcpy(iv + sizeof (uint64_t), &val32, sizeof (uint32_t)); + } else { + val64 = BSWAP_64(bp->blk_dva[2].dva_word[0]); + memcpy(salt, &val64, sizeof (uint64_t)); + + val64 = BSWAP_64(bp->blk_dva[2].dva_word[1]); + memcpy(iv, &val64, sizeof (uint64_t)); + + val32 = BSWAP_32((uint32_t)BP_GET_IV2(bp)); + memcpy(iv + sizeof (uint64_t), &val32, sizeof (uint32_t)); + } +} + +void +zio_crypt_encode_mac_bp(blkptr_t *bp, uint8_t *mac) +{ + uint64_t val64; + + ASSERT(BP_USES_CRYPT(bp)); + ASSERT3U(BP_GET_TYPE(bp), !=, DMU_OT_OBJSET); + + if (!BP_SHOULD_BYTESWAP(bp)) { + memcpy(&bp->blk_cksum.zc_word[2], mac, sizeof (uint64_t)); + memcpy(&bp->blk_cksum.zc_word[3], mac + sizeof (uint64_t), + sizeof (uint64_t)); + } else { + memcpy(&val64, mac, sizeof (uint64_t)); + bp->blk_cksum.zc_word[2] = BSWAP_64(val64); + + memcpy(&val64, mac + sizeof (uint64_t), sizeof (uint64_t)); + bp->blk_cksum.zc_word[3] = BSWAP_64(val64); + } +} + +void +zio_crypt_decode_mac_bp(const blkptr_t *bp, uint8_t *mac) +{ + uint64_t val64; + + ASSERT(BP_USES_CRYPT(bp) || BP_IS_HOLE(bp)); + + /* for convenience, so callers don't need to check */ + if (BP_GET_TYPE(bp) == DMU_OT_OBJSET) { + memset(mac, 0, ZIO_DATA_MAC_LEN); + return; + } + + if (!BP_SHOULD_BYTESWAP(bp)) { + memcpy(mac, &bp->blk_cksum.zc_word[2], sizeof (uint64_t)); + memcpy(mac + sizeof (uint64_t), &bp->blk_cksum.zc_word[3], + sizeof (uint64_t)); + } else { + val64 = BSWAP_64(bp->blk_cksum.zc_word[2]); + memcpy(mac, &val64, sizeof (uint64_t)); + + val64 = BSWAP_64(bp->blk_cksum.zc_word[3]); + memcpy(mac + sizeof (uint64_t), &val64, sizeof (uint64_t)); + } +} + +void +zio_crypt_encode_mac_zil(void *data, uint8_t *mac) +{ + zil_chain_t *zilc = data; + + memcpy(&zilc->zc_eck.zec_cksum.zc_word[2], mac, sizeof (uint64_t)); + memcpy(&zilc->zc_eck.zec_cksum.zc_word[3], mac + sizeof (uint64_t), + sizeof (uint64_t)); +} + +void +zio_crypt_decode_mac_zil(const void *data, uint8_t *mac) +{ + /* + * The ZIL MAC is embedded in the block it protects, which will + * not have been byteswapped by the time this function has been called. + * As a result, we don't need to worry about byteswapping the MAC. + */ + const zil_chain_t *zilc = data; + + memcpy(mac, &zilc->zc_eck.zec_cksum.zc_word[2], sizeof (uint64_t)); + memcpy(mac + sizeof (uint64_t), &zilc->zc_eck.zec_cksum.zc_word[3], + sizeof (uint64_t)); +} + +/* + * This routine takes a block of dnodes (src_abd) and copies only the bonus + * buffers to the same offsets in the dst buffer. datalen should be the size + * of both the src_abd and the dst buffer (not just the length of the bonus + * buffers). + */ +void +zio_crypt_copy_dnode_bonus(abd_t *src_abd, uint8_t *dst, uint_t datalen) +{ + uint_t i, max_dnp = datalen >> DNODE_SHIFT; + uint8_t *src; + dnode_phys_t *dnp, *sdnp, *ddnp; + + src = abd_borrow_buf_copy(src_abd, datalen); + + sdnp = (dnode_phys_t *)src; + ddnp = (dnode_phys_t *)dst; + + for (i = 0; i < max_dnp; i += sdnp[i].dn_extra_slots + 1) { + dnp = &sdnp[i]; + if (dnp->dn_type != DMU_OT_NONE && + DMU_OT_IS_ENCRYPTED(dnp->dn_bonustype) && + dnp->dn_bonuslen != 0) { + memcpy(DN_BONUS(&ddnp[i]), DN_BONUS(dnp), + DN_MAX_BONUS_LEN(dnp)); + } + } + + abd_return_buf(src_abd, src, datalen); +} + +/* + * This function decides what fields from blk_prop are included in + * the on-disk various MAC algorithms. + */ +static void +zio_crypt_bp_zero_nonportable_blkprop(blkptr_t *bp, uint64_t version) +{ + /* + * Version 0 did not properly zero out all non-portable fields + * as it should have done. We maintain this code so that we can + * do read-only imports of pools on this version. + */ + if (version == 0) { + BP_SET_DEDUP(bp, 0); + BP_SET_CHECKSUM(bp, 0); + BP_SET_PSIZE(bp, SPA_MINBLOCKSIZE); + return; + } + + ASSERT3U(version, ==, ZIO_CRYPT_KEY_CURRENT_VERSION); + + /* + * The hole_birth feature might set these fields even if this bp + * is a hole. We zero them out here to guarantee that raw sends + * will function with or without the feature. + */ + if (BP_IS_HOLE(bp)) { + bp->blk_prop = 0ULL; + return; + } + + /* + * At L0 we want to verify these fields to ensure that data blocks + * can not be reinterpreted. For instance, we do not want an attacker + * to trick us into returning raw lz4 compressed data to the user + * by modifying the compression bits. At higher levels, we cannot + * enforce this policy since raw sends do not convey any information + * about indirect blocks, so these values might be different on the + * receive side. Fortunately, this does not open any new attack + * vectors, since any alterations that can be made to a higher level + * bp must still verify the correct order of the layer below it. + */ + if (BP_GET_LEVEL(bp) != 0) { + BP_SET_BYTEORDER(bp, 0); + BP_SET_COMPRESS(bp, 0); + + /* + * psize cannot be set to zero or it will trigger + * asserts, but the value doesn't really matter as + * long as it is constant. + */ + BP_SET_PSIZE(bp, SPA_MINBLOCKSIZE); + } + + BP_SET_DEDUP(bp, 0); + BP_SET_CHECKSUM(bp, 0); +} + +static void +zio_crypt_bp_auth_init(uint64_t version, boolean_t should_bswap, blkptr_t *bp, + blkptr_auth_buf_t *bab, uint_t *bab_len) +{ + blkptr_t tmpbp = *bp; + + if (should_bswap) + byteswap_uint64_array(&tmpbp, sizeof (blkptr_t)); + + ASSERT(BP_USES_CRYPT(&tmpbp) || BP_IS_HOLE(&tmpbp)); + ASSERT0(BP_IS_EMBEDDED(&tmpbp)); + + zio_crypt_decode_mac_bp(&tmpbp, bab->bab_mac); + + /* + * We always MAC blk_prop in LE to ensure portability. This + * must be done after decoding the mac, since the endianness + * will get zero'd out here. + */ + zio_crypt_bp_zero_nonportable_blkprop(&tmpbp, version); + bab->bab_prop = LE_64(tmpbp.blk_prop); + bab->bab_pad = 0ULL; + + /* version 0 did not include the padding */ + *bab_len = sizeof (blkptr_auth_buf_t); + if (version == 0) + *bab_len -= sizeof (uint64_t); +} + +static int +zio_crypt_bp_do_hmac_updates(crypto_context_t ctx, uint64_t version, + boolean_t should_bswap, blkptr_t *bp) +{ + int ret; + uint_t bab_len; + blkptr_auth_buf_t bab; + crypto_data_t cd; + + zio_crypt_bp_auth_init(version, should_bswap, bp, &bab, &bab_len); + cd.cd_format = CRYPTO_DATA_RAW; + cd.cd_offset = 0; + cd.cd_length = bab_len; + cd.cd_raw.iov_base = (char *)&bab; + cd.cd_raw.iov_len = cd.cd_length; + + ret = crypto_mac_update(ctx, &cd); + if (ret != CRYPTO_SUCCESS) { + ret = SET_ERROR(EIO); + goto error; + } + + return (0); + +error: + return (ret); +} + +static void +zio_crypt_bp_do_indrect_checksum_updates(SHA2_CTX *ctx, uint64_t version, + boolean_t should_bswap, blkptr_t *bp) +{ + uint_t bab_len; + blkptr_auth_buf_t bab; + + zio_crypt_bp_auth_init(version, should_bswap, bp, &bab, &bab_len); + SHA2Update(ctx, &bab, bab_len); +} + +static void +zio_crypt_bp_do_aad_updates(uint8_t **aadp, uint_t *aad_len, uint64_t version, + boolean_t should_bswap, blkptr_t *bp) +{ + uint_t bab_len; + blkptr_auth_buf_t bab; + + zio_crypt_bp_auth_init(version, should_bswap, bp, &bab, &bab_len); + memcpy(*aadp, &bab, bab_len); + *aadp += bab_len; + *aad_len += bab_len; +} + +static int +zio_crypt_do_dnode_hmac_updates(crypto_context_t ctx, uint64_t version, + boolean_t should_bswap, dnode_phys_t *dnp) +{ + int ret, i; + dnode_phys_t *adnp, tmp_dncore; + size_t dn_core_size = offsetof(dnode_phys_t, dn_blkptr); + boolean_t le_bswap = (should_bswap == ZFS_HOST_BYTEORDER); + crypto_data_t cd; + + cd.cd_format = CRYPTO_DATA_RAW; + cd.cd_offset = 0; + + /* + * Authenticate the core dnode (masking out non-portable bits). + * We only copy the first 64 bytes we operate on to avoid the overhead + * of copying 512-64 unneeded bytes. The compiler seems to be fine + * with that. + */ + memcpy(&tmp_dncore, dnp, dn_core_size); + adnp = &tmp_dncore; + + if (le_bswap) { + adnp->dn_datablkszsec = BSWAP_16(adnp->dn_datablkszsec); + adnp->dn_bonuslen = BSWAP_16(adnp->dn_bonuslen); + adnp->dn_maxblkid = BSWAP_64(adnp->dn_maxblkid); + adnp->dn_used = BSWAP_64(adnp->dn_used); + } + adnp->dn_flags &= DNODE_CRYPT_PORTABLE_FLAGS_MASK; + adnp->dn_used = 0; + + cd.cd_length = dn_core_size; + cd.cd_raw.iov_base = (char *)adnp; + cd.cd_raw.iov_len = cd.cd_length; + + ret = crypto_mac_update(ctx, &cd); + if (ret != CRYPTO_SUCCESS) { + ret = SET_ERROR(EIO); + goto error; + } + + for (i = 0; i < dnp->dn_nblkptr; i++) { + ret = zio_crypt_bp_do_hmac_updates(ctx, version, + should_bswap, &dnp->dn_blkptr[i]); + if (ret != 0) + goto error; + } + + if (dnp->dn_flags & DNODE_FLAG_SPILL_BLKPTR) { + ret = zio_crypt_bp_do_hmac_updates(ctx, version, + should_bswap, DN_SPILL_BLKPTR(dnp)); + if (ret != 0) + goto error; + } + + return (0); + +error: + return (ret); +} + +/* + * objset_phys_t blocks introduce a number of exceptions to the normal + * authentication process. objset_phys_t's contain 2 separate HMACS for + * protecting the integrity of their data. The portable_mac protects the + * metadnode. This MAC can be sent with a raw send and protects against + * reordering of data within the metadnode. The local_mac protects the user + * accounting objects which are not sent from one system to another. + * + * In addition, objset blocks are the only blocks that can be modified and + * written to disk without the key loaded under certain circumstances. During + * zil_claim() we need to be able to update the zil_header_t to complete + * claiming log blocks and during raw receives we need to write out the + * portable_mac from the send file. Both of these actions are possible + * because these fields are not protected by either MAC so neither one will + * need to modify the MACs without the key. However, when the modified blocks + * are written out they will be byteswapped into the host machine's native + * endianness which will modify fields protected by the MAC. As a result, MAC + * calculation for objset blocks works slightly differently from other block + * types. Where other block types MAC the data in whatever endianness is + * written to disk, objset blocks always MAC little endian version of their + * values. In the code, should_bswap is the value from BP_SHOULD_BYTESWAP() + * and le_bswap indicates whether a byteswap is needed to get this block + * into little endian format. + */ +static int +zio_crypt_do_objset_hmacs_impl(zio_crypt_key_t *key, void *data, + uint_t datalen, boolean_t should_bswap, uint8_t *portable_mac, + uint8_t *local_mac, boolean_t skip_projectquota) +{ + int ret; + crypto_mechanism_t mech; + crypto_context_t ctx; + crypto_data_t cd; + objset_phys_t *osp = data; + uint64_t intval; + boolean_t le_bswap = (should_bswap == ZFS_HOST_BYTEORDER); + uint8_t raw_portable_mac[SHA512_DIGEST_LENGTH]; + uint8_t raw_local_mac[SHA512_DIGEST_LENGTH]; + + /* initialize HMAC mechanism */ + mech.cm_type = crypto_mech2id(SUN_CKM_SHA512_HMAC); + mech.cm_param = NULL; + mech.cm_param_len = 0; + + cd.cd_format = CRYPTO_DATA_RAW; + cd.cd_offset = 0; + + /* calculate the portable MAC from the portable fields and metadnode */ + ret = crypto_mac_init(&mech, &key->zk_hmac_key, NULL, &ctx); + if (ret != CRYPTO_SUCCESS) { + ret = SET_ERROR(EIO); + goto error; + } + + /* add in the os_type */ + intval = (le_bswap) ? osp->os_type : BSWAP_64(osp->os_type); + cd.cd_length = sizeof (uint64_t); + cd.cd_raw.iov_base = (char *)&intval; + cd.cd_raw.iov_len = cd.cd_length; + + ret = crypto_mac_update(ctx, &cd); + if (ret != CRYPTO_SUCCESS) { + ret = SET_ERROR(EIO); + goto error; + } + + /* add in the portable os_flags */ + intval = osp->os_flags; + if (should_bswap) + intval = BSWAP_64(intval); + intval &= OBJSET_CRYPT_PORTABLE_FLAGS_MASK; + if (!ZFS_HOST_BYTEORDER) + intval = BSWAP_64(intval); + + cd.cd_length = sizeof (uint64_t); + cd.cd_raw.iov_base = (char *)&intval; + cd.cd_raw.iov_len = cd.cd_length; + + ret = crypto_mac_update(ctx, &cd); + if (ret != CRYPTO_SUCCESS) { + ret = SET_ERROR(EIO); + goto error; + } + + /* add in fields from the metadnode */ + ret = zio_crypt_do_dnode_hmac_updates(ctx, key->zk_version, + should_bswap, &osp->os_meta_dnode); + if (ret) + goto error; + + /* store the final digest in a temporary buffer and copy what we need */ + cd.cd_length = SHA512_DIGEST_LENGTH; + cd.cd_raw.iov_base = (char *)raw_portable_mac; + cd.cd_raw.iov_len = cd.cd_length; + + ret = crypto_mac_final(ctx, &cd); + if (ret != CRYPTO_SUCCESS) { + ret = SET_ERROR(EIO); + goto error; + } + + memcpy(portable_mac, raw_portable_mac, ZIO_OBJSET_MAC_LEN); + + /* + * This is necessary here as we check next whether + * OBJSET_FLAG_USERACCOUNTING_COMPLETE is set in order to + * decide if the local_mac should be zeroed out. That flag will always + * be set by dmu_objset_id_quota_upgrade_cb() and + * dmu_objset_userspace_upgrade_cb() if useraccounting has been + * completed. + */ + intval = osp->os_flags; + if (should_bswap) + intval = BSWAP_64(intval); + boolean_t uacct_incomplete = + !(intval & OBJSET_FLAG_USERACCOUNTING_COMPLETE); + + /* + * The local MAC protects the user, group and project accounting. + * If these objects are not present, the local MAC is zeroed out. + */ + if (uacct_incomplete || + (datalen >= OBJSET_PHYS_SIZE_V3 && + osp->os_userused_dnode.dn_type == DMU_OT_NONE && + osp->os_groupused_dnode.dn_type == DMU_OT_NONE && + osp->os_projectused_dnode.dn_type == DMU_OT_NONE) || + (datalen >= OBJSET_PHYS_SIZE_V2 && + osp->os_userused_dnode.dn_type == DMU_OT_NONE && + osp->os_groupused_dnode.dn_type == DMU_OT_NONE) || + (datalen <= OBJSET_PHYS_SIZE_V1)) { + memset(local_mac, 0, ZIO_OBJSET_MAC_LEN); + return (0); + } + + /* calculate the local MAC from the userused and groupused dnodes */ + ret = crypto_mac_init(&mech, &key->zk_hmac_key, NULL, &ctx); + if (ret != CRYPTO_SUCCESS) { + ret = SET_ERROR(EIO); + goto error; + } + + /* add in the non-portable os_flags */ + intval = osp->os_flags; + if (should_bswap) + intval = BSWAP_64(intval); + intval &= ~OBJSET_CRYPT_PORTABLE_FLAGS_MASK; + if (!ZFS_HOST_BYTEORDER) + intval = BSWAP_64(intval); + + cd.cd_length = sizeof (uint64_t); + cd.cd_raw.iov_base = (char *)&intval; + cd.cd_raw.iov_len = cd.cd_length; + + ret = crypto_mac_update(ctx, &cd); + if (ret != CRYPTO_SUCCESS) { + ret = SET_ERROR(EIO); + goto error; + } + + /* add in fields from the user accounting dnodes */ + if (osp->os_userused_dnode.dn_type != DMU_OT_NONE) { + ret = zio_crypt_do_dnode_hmac_updates(ctx, key->zk_version, + should_bswap, &osp->os_userused_dnode); + if (ret) + goto error; + } + + if (osp->os_groupused_dnode.dn_type != DMU_OT_NONE) { + ret = zio_crypt_do_dnode_hmac_updates(ctx, key->zk_version, + should_bswap, &osp->os_groupused_dnode); + if (ret) + goto error; + } + + /* + * Unfortunate side-effect of macOS port getting crypto before + * projectquota. Luckily, if we just let it mount, by generating the + * old style local_mac, "generate" calls will upgrade to "proper". + */ + if (!skip_projectquota) { + if (osp->os_projectused_dnode.dn_type != DMU_OT_NONE && + datalen >= OBJSET_PHYS_SIZE_V3) { + ret = zio_crypt_do_dnode_hmac_updates(ctx, + key->zk_version, should_bswap, + &osp->os_projectused_dnode); + if (ret) + goto error; + } + } + + /* store the final digest in a temporary buffer and copy what we need */ + cd.cd_length = SHA512_DIGEST_LENGTH; + cd.cd_raw.iov_base = (char *)raw_local_mac; + cd.cd_raw.iov_len = cd.cd_length; + + ret = crypto_mac_final(ctx, &cd); + if (ret != CRYPTO_SUCCESS) { + ret = SET_ERROR(EIO); + goto error; + } + + memcpy(local_mac, raw_local_mac, ZIO_OBJSET_MAC_LEN); + + return (0); + +error: + memset(portable_mac, 0, ZIO_OBJSET_MAC_LEN); + memset(local_mac, 0, ZIO_OBJSET_MAC_LEN); + return (ret); +} + +int +zio_crypt_do_objset_hmacs(zio_crypt_key_t *key, void *data, uint_t datalen, + boolean_t should_bswap, uint8_t *portable_mac, uint8_t *local_mac) +{ + return (zio_crypt_do_objset_hmacs_impl(key, data, datalen, should_bswap, + portable_mac, local_mac, FALSE)); +} + +#ifdef _KERNEL +int +zio_crypt_do_objset_hmacs_errata1(zio_crypt_key_t *key, void *data, + uint_t datalen, boolean_t should_bswap, uint8_t *portable_mac, + uint8_t *local_mac) +{ + dprintf("trying errata1 work-around\n"); + return (zio_crypt_do_objset_hmacs_impl(key, data, datalen, should_bswap, + portable_mac, local_mac, TRUE)); +} +#endif + +static void +zio_crypt_destroy_uio(zfs_uio_t *uio) +{ + if (uio->uio_iov) + kmem_free((void *)uio->uio_iov, + uio->uio_iovcnt * sizeof (iovec_t)); +} + +/* + * This function parses an uncompressed indirect block and returns a checksum + * of all the portable fields from all of the contained bps. The portable + * fields are the MAC and all of the fields from blk_prop except for the dedup, + * checksum, and psize bits. For an explanation of the purpose of this, see + * the comment block on object set authentication. + */ +static int +zio_crypt_do_indirect_mac_checksum_impl(boolean_t generate, void *buf, + uint_t datalen, uint64_t version, boolean_t byteswap, uint8_t *cksum) +{ + blkptr_t *bp; + int i, epb = datalen >> SPA_BLKPTRSHIFT; + SHA2_CTX ctx; + uint8_t digestbuf[SHA512_DIGEST_LENGTH]; + + /* checksum all of the MACs from the layer below */ + SHA2Init(SHA512, &ctx); + for (i = 0, bp = buf; i < epb; i++, bp++) { + zio_crypt_bp_do_indrect_checksum_updates(&ctx, version, + byteswap, bp); + } + SHA2Final(digestbuf, &ctx); + + if (generate) { + memcpy(cksum, digestbuf, ZIO_DATA_MAC_LEN); + return (0); + } + + if (memcmp(digestbuf, cksum, ZIO_DATA_MAC_LEN) != 0) + return (SET_ERROR(ECKSUM)); + + return (0); +} + +int +zio_crypt_do_indirect_mac_checksum(boolean_t generate, void *buf, + uint_t datalen, boolean_t byteswap, uint8_t *cksum) +{ + int ret; + + /* + * Unfortunately, callers of this function will not always have + * easy access to the on-disk format version. This info is + * normally found in the DSL Crypto Key, but the checksum-of-MACs + * is expected to be verifiable even when the key isn't loaded. + * Here, instead of doing a ZAP lookup for the version for each + * zio, we simply try both existing formats. + */ + ret = zio_crypt_do_indirect_mac_checksum_impl(generate, buf, + datalen, ZIO_CRYPT_KEY_CURRENT_VERSION, byteswap, cksum); + if (ret == ECKSUM) { + ASSERT(!generate); + ret = zio_crypt_do_indirect_mac_checksum_impl(generate, + buf, datalen, 0, byteswap, cksum); + } + + return (ret); +} + +int +zio_crypt_do_indirect_mac_checksum_abd(boolean_t generate, abd_t *abd, + uint_t datalen, boolean_t byteswap, uint8_t *cksum) +{ + int ret; + void *buf; + + buf = abd_borrow_buf_copy(abd, datalen); + ret = zio_crypt_do_indirect_mac_checksum(generate, buf, datalen, + byteswap, cksum); + abd_return_buf(abd, buf, datalen); + + return (ret); +} + +/* + * Special case handling routine for encrypting / decrypting ZIL blocks. + * We do not check for the older ZIL chain because the encryption feature + * was not available before the newer ZIL chain was introduced. The goal + * here is to encrypt everything except the blkptr_t of a lr_write_t and + * the zil_chain_t header. Everything that is not encrypted is authenticated. + */ +static int +zio_crypt_init_uios_zil(boolean_t encrypt, uint8_t *plainbuf, + uint8_t *cipherbuf, uint_t datalen, boolean_t byteswap, zfs_uio_t *puio, + zfs_uio_t *cuio, uint_t *enc_len, uint8_t **authbuf, uint_t *auth_len, + boolean_t *no_crypt) +{ + int ret; + uint64_t txtype, lr_len; + uint_t nr_src, nr_dst, crypt_len; + uint_t aad_len = 0, nr_iovecs = 0, total_len = 0; + iovec_t *src_iovecs = NULL, *dst_iovecs = NULL; + uint8_t *src, *dst, *slrp, *dlrp, *blkend, *aadp; + zil_chain_t *zilc; + lr_t *lr; + uint8_t *aadbuf = zio_buf_alloc(datalen); + + /* cipherbuf always needs an extra iovec for the MAC */ + if (encrypt) { + src = plainbuf; + dst = cipherbuf; + nr_src = 0; + nr_dst = 1; + } else { + src = cipherbuf; + dst = plainbuf; + nr_src = 1; + nr_dst = 0; + } + memset(dst, 0, datalen); + + /* find the start and end record of the log block */ + zilc = (zil_chain_t *)src; + slrp = src + sizeof (zil_chain_t); + aadp = aadbuf; + blkend = src + ((byteswap) ? BSWAP_64(zilc->zc_nused) : zilc->zc_nused); + + /* calculate the number of encrypted iovecs we will need */ + for (; slrp < blkend; slrp += lr_len) { + lr = (lr_t *)slrp; + + if (!byteswap) { + txtype = lr->lrc_txtype; + lr_len = lr->lrc_reclen; + } else { + txtype = BSWAP_64(lr->lrc_txtype); + lr_len = BSWAP_64(lr->lrc_reclen); + } + + nr_iovecs++; + if (txtype == TX_WRITE && lr_len != sizeof (lr_write_t)) + nr_iovecs++; + } + + nr_src += nr_iovecs; + nr_dst += nr_iovecs; + + /* allocate the iovec arrays */ + if (nr_src != 0) { + src_iovecs = kmem_alloc(nr_src * sizeof (iovec_t), KM_SLEEP); + if (src_iovecs == NULL) { + ret = SET_ERROR(ENOMEM); + goto error; + } + } + + if (nr_dst != 0) { + dst_iovecs = kmem_alloc(nr_dst * sizeof (iovec_t), KM_SLEEP); + if (dst_iovecs == NULL) { + ret = SET_ERROR(ENOMEM); + goto error; + } + } + + /* + * Copy the plain zil header over and authenticate everything except + * the checksum that will store our MAC. If we are writing the data + * the embedded checksum will not have been calculated yet, so we don't + * authenticate that. + */ + memcpy(dst, src, sizeof (zil_chain_t)); + memcpy(aadp, src, sizeof (zil_chain_t) - sizeof (zio_eck_t)); + aadp += sizeof (zil_chain_t) - sizeof (zio_eck_t); + aad_len += sizeof (zil_chain_t) - sizeof (zio_eck_t); + + /* loop over records again, filling in iovecs */ + nr_iovecs = 0; + slrp = src + sizeof (zil_chain_t); + dlrp = dst + sizeof (zil_chain_t); + + for (; slrp < blkend; slrp += lr_len, dlrp += lr_len) { + lr = (lr_t *)slrp; + + if (!byteswap) { + txtype = lr->lrc_txtype; + lr_len = lr->lrc_reclen; + } else { + txtype = BSWAP_64(lr->lrc_txtype); + lr_len = BSWAP_64(lr->lrc_reclen); + } + + /* copy the common lr_t */ + memcpy(dlrp, slrp, sizeof (lr_t)); + memcpy(aadp, slrp, sizeof (lr_t)); + aadp += sizeof (lr_t); + aad_len += sizeof (lr_t); + + ASSERT3P(src_iovecs, !=, NULL); + ASSERT3P(dst_iovecs, !=, NULL); + + /* + * If this is a TX_WRITE record we want to encrypt everything + * except the bp if exists. If the bp does exist we want to + * authenticate it. + */ + if (txtype == TX_WRITE) { + crypt_len = sizeof (lr_write_t) - + sizeof (lr_t) - sizeof (blkptr_t); + src_iovecs[nr_iovecs].iov_base = slrp + sizeof (lr_t); + src_iovecs[nr_iovecs].iov_len = crypt_len; + dst_iovecs[nr_iovecs].iov_base = dlrp + sizeof (lr_t); + dst_iovecs[nr_iovecs].iov_len = crypt_len; + + /* copy the bp now since it will not be encrypted */ + memcpy( + dlrp + sizeof (lr_write_t) - sizeof (blkptr_t), + slrp + sizeof (lr_write_t) - sizeof (blkptr_t), + sizeof (blkptr_t)); + memcpy(aadp, + slrp + sizeof (lr_write_t) - sizeof (blkptr_t), + sizeof (blkptr_t)); + aadp += sizeof (blkptr_t); + aad_len += sizeof (blkptr_t); + nr_iovecs++; + total_len += crypt_len; + + if (lr_len != sizeof (lr_write_t)) { + crypt_len = lr_len - sizeof (lr_write_t); + src_iovecs[nr_iovecs].iov_base = + slrp + sizeof (lr_write_t); + src_iovecs[nr_iovecs].iov_len = crypt_len; + dst_iovecs[nr_iovecs].iov_base = + dlrp + sizeof (lr_write_t); + dst_iovecs[nr_iovecs].iov_len = crypt_len; + nr_iovecs++; + total_len += crypt_len; + } + } else { + crypt_len = lr_len - sizeof (lr_t); + src_iovecs[nr_iovecs].iov_base = slrp + sizeof (lr_t); + src_iovecs[nr_iovecs].iov_len = crypt_len; + dst_iovecs[nr_iovecs].iov_base = dlrp + sizeof (lr_t); + dst_iovecs[nr_iovecs].iov_len = crypt_len; + nr_iovecs++; + total_len += crypt_len; + } + } + + *no_crypt = (nr_iovecs == 0); + *enc_len = total_len; + *authbuf = aadbuf; + *auth_len = aad_len; + + if (encrypt) { + puio->uio_iov = src_iovecs; + puio->uio_iovcnt = nr_src; + cuio->uio_iov = dst_iovecs; + cuio->uio_iovcnt = nr_dst; + } else { + puio->uio_iov = dst_iovecs; + puio->uio_iovcnt = nr_dst; + cuio->uio_iov = src_iovecs; + cuio->uio_iovcnt = nr_src; + } + + return (0); + +error: + zio_buf_free(aadbuf, datalen); + if (src_iovecs != NULL) + kmem_free(src_iovecs, nr_src * sizeof (iovec_t)); + if (dst_iovecs != NULL) + kmem_free(dst_iovecs, nr_dst * sizeof (iovec_t)); + + *enc_len = 0; + *authbuf = NULL; + *auth_len = 0; + *no_crypt = B_FALSE; + puio->uio_iov = NULL; + puio->uio_iovcnt = 0; + cuio->uio_iov = NULL; + cuio->uio_iovcnt = 0; + return (ret); +} + +/* + * Special case handling routine for encrypting / decrypting dnode blocks. + */ +static int +zio_crypt_init_uios_dnode(boolean_t encrypt, uint64_t version, + uint8_t *plainbuf, uint8_t *cipherbuf, uint_t datalen, boolean_t byteswap, + zfs_uio_t *puio, zfs_uio_t *cuio, uint_t *enc_len, uint8_t **authbuf, + uint_t *auth_len, boolean_t *no_crypt) +{ + int ret; + uint_t nr_src, nr_dst, crypt_len; + uint_t aad_len = 0, nr_iovecs = 0, total_len = 0; + uint_t i, j, max_dnp = datalen >> DNODE_SHIFT; + iovec_t *src_iovecs = NULL, *dst_iovecs = NULL; + uint8_t *src, *dst, *aadp; + dnode_phys_t *dnp, *adnp, *sdnp, *ddnp; + uint8_t *aadbuf = zio_buf_alloc(datalen); + + if (encrypt) { + src = plainbuf; + dst = cipherbuf; + nr_src = 0; + nr_dst = 1; + } else { + src = cipherbuf; + dst = plainbuf; + nr_src = 1; + nr_dst = 0; + } + + sdnp = (dnode_phys_t *)src; + ddnp = (dnode_phys_t *)dst; + aadp = aadbuf; + + /* + * Count the number of iovecs we will need to do the encryption by + * counting the number of bonus buffers that need to be encrypted. + */ + for (i = 0; i < max_dnp; i += sdnp[i].dn_extra_slots + 1) { + /* + * This block may still be byteswapped. However, all of the + * values we use are either uint8_t's (for which byteswapping + * is a noop) or a * != 0 check, which will work regardless + * of whether or not we byteswap. + */ + if (sdnp[i].dn_type != DMU_OT_NONE && + DMU_OT_IS_ENCRYPTED(sdnp[i].dn_bonustype) && + sdnp[i].dn_bonuslen != 0) { + nr_iovecs++; + } + } + + nr_src += nr_iovecs; + nr_dst += nr_iovecs; + + if (nr_src != 0) { + src_iovecs = kmem_alloc(nr_src * sizeof (iovec_t), KM_SLEEP); + if (src_iovecs == NULL) { + ret = SET_ERROR(ENOMEM); + goto error; + } + } + + if (nr_dst != 0) { + dst_iovecs = kmem_alloc(nr_dst * sizeof (iovec_t), KM_SLEEP); + if (dst_iovecs == NULL) { + ret = SET_ERROR(ENOMEM); + goto error; + } + } + + nr_iovecs = 0; + + /* + * Iterate through the dnodes again, this time filling in the uios + * we allocated earlier. We also concatenate any data we want to + * authenticate onto aadbuf. + */ + for (i = 0; i < max_dnp; i += sdnp[i].dn_extra_slots + 1) { + dnp = &sdnp[i]; + + /* copy over the core fields and blkptrs (kept as plaintext) */ + memcpy(&ddnp[i], dnp, + (uint8_t *)DN_BONUS(dnp) - (uint8_t *)dnp); + + if (dnp->dn_flags & DNODE_FLAG_SPILL_BLKPTR) { + memcpy(DN_SPILL_BLKPTR(&ddnp[i]), DN_SPILL_BLKPTR(dnp), + sizeof (blkptr_t)); + } + + /* + * Handle authenticated data. We authenticate everything in + * the dnode that can be brought over when we do a raw send. + * This includes all of the core fields as well as the MACs + * stored in the bp checksums and all of the portable bits + * from blk_prop. We include the dnode padding here in case it + * ever gets used in the future. Some dn_flags and dn_used are + * not portable so we mask those out values out of the + * authenticated data. + */ + crypt_len = offsetof(dnode_phys_t, dn_blkptr); + memcpy(aadp, dnp, crypt_len); + adnp = (dnode_phys_t *)aadp; + adnp->dn_flags &= DNODE_CRYPT_PORTABLE_FLAGS_MASK; + adnp->dn_used = 0; + aadp += crypt_len; + aad_len += crypt_len; + + for (j = 0; j < dnp->dn_nblkptr; j++) { + zio_crypt_bp_do_aad_updates(&aadp, &aad_len, + version, byteswap, &dnp->dn_blkptr[j]); + } + + if (dnp->dn_flags & DNODE_FLAG_SPILL_BLKPTR) { + zio_crypt_bp_do_aad_updates(&aadp, &aad_len, + version, byteswap, DN_SPILL_BLKPTR(dnp)); + } + + /* + * If this bonus buffer needs to be encrypted, we prepare an + * iovec_t. The encryption / decryption functions will fill + * this in for us with the encrypted or decrypted data. + * Otherwise we add the bonus buffer to the authenticated + * data buffer and copy it over to the destination. The + * encrypted iovec extends to DN_MAX_BONUS_LEN(dnp) so that + * we can guarantee alignment with the AES block size + * (128 bits). + */ + crypt_len = DN_MAX_BONUS_LEN(dnp); + if (dnp->dn_type != DMU_OT_NONE && + DMU_OT_IS_ENCRYPTED(dnp->dn_bonustype) && + dnp->dn_bonuslen != 0) { + ASSERT3U(nr_iovecs, <, nr_src); + ASSERT3U(nr_iovecs, <, nr_dst); + ASSERT3P(src_iovecs, !=, NULL); + ASSERT3P(dst_iovecs, !=, NULL); + src_iovecs[nr_iovecs].iov_base = DN_BONUS(dnp); + src_iovecs[nr_iovecs].iov_len = crypt_len; + dst_iovecs[nr_iovecs].iov_base = DN_BONUS(&ddnp[i]); + dst_iovecs[nr_iovecs].iov_len = crypt_len; + + nr_iovecs++; + total_len += crypt_len; + } else { + memcpy(DN_BONUS(&ddnp[i]), DN_BONUS(dnp), crypt_len); + memcpy(aadp, DN_BONUS(dnp), crypt_len); + aadp += crypt_len; + aad_len += crypt_len; + } + } + + *no_crypt = (nr_iovecs == 0); + *enc_len = total_len; + *authbuf = aadbuf; + *auth_len = aad_len; + + if (encrypt) { + puio->uio_iov = src_iovecs; + puio->uio_iovcnt = nr_src; + cuio->uio_iov = dst_iovecs; + cuio->uio_iovcnt = nr_dst; + } else { + puio->uio_iov = dst_iovecs; + puio->uio_iovcnt = nr_dst; + cuio->uio_iov = src_iovecs; + cuio->uio_iovcnt = nr_src; + } + + return (0); + +error: + zio_buf_free(aadbuf, datalen); + if (src_iovecs != NULL) + kmem_free(src_iovecs, nr_src * sizeof (iovec_t)); + if (dst_iovecs != NULL) + kmem_free(dst_iovecs, nr_dst * sizeof (iovec_t)); + + *enc_len = 0; + *authbuf = NULL; + *auth_len = 0; + *no_crypt = B_FALSE; + puio->uio_iov = NULL; + puio->uio_iovcnt = 0; + cuio->uio_iov = NULL; + cuio->uio_iovcnt = 0; + return (ret); +} + +static int +zio_crypt_init_uios_normal(boolean_t encrypt, uint8_t *plainbuf, + uint8_t *cipherbuf, uint_t datalen, zfs_uio_t *puio, zfs_uio_t *cuio, + uint_t *enc_len) +{ + (void) encrypt; + int ret; + uint_t nr_plain = 1, nr_cipher = 2; + iovec_t *plain_iovecs = NULL, *cipher_iovecs = NULL; + + /* allocate the iovecs for the plain and cipher data */ + plain_iovecs = kmem_alloc(nr_plain * sizeof (iovec_t), + KM_SLEEP); + if (!plain_iovecs) { + ret = SET_ERROR(ENOMEM); + goto error; + } + + cipher_iovecs = kmem_alloc(nr_cipher * sizeof (iovec_t), + KM_SLEEP); + if (!cipher_iovecs) { + ret = SET_ERROR(ENOMEM); + goto error; + } + + plain_iovecs[0].iov_base = plainbuf; + plain_iovecs[0].iov_len = datalen; + cipher_iovecs[0].iov_base = cipherbuf; + cipher_iovecs[0].iov_len = datalen; + + *enc_len = datalen; + puio->uio_iov = plain_iovecs; + puio->uio_iovcnt = nr_plain; + cuio->uio_iov = cipher_iovecs; + cuio->uio_iovcnt = nr_cipher; + + return (0); + +error: + if (plain_iovecs != NULL) + kmem_free(plain_iovecs, nr_plain * sizeof (iovec_t)); + if (cipher_iovecs != NULL) + kmem_free(cipher_iovecs, nr_cipher * sizeof (iovec_t)); + + *enc_len = 0; + puio->uio_iov = NULL; + puio->uio_iovcnt = 0; + cuio->uio_iov = NULL; + cuio->uio_iovcnt = 0; + return (ret); +} + +/* + * This function builds up the plaintext (puio) and ciphertext (cuio) uios so + * that they can be used for encryption and decryption by zio_do_crypt_uio(). + * Most blocks will use zio_crypt_init_uios_normal(), with ZIL and dnode blocks + * requiring special handling to parse out pieces that are to be encrypted. The + * authbuf is used by these special cases to store additional authenticated + * data (AAD) for the encryption modes. + */ +static int +zio_crypt_init_uios(boolean_t encrypt, uint64_t version, dmu_object_type_t ot, + uint8_t *plainbuf, uint8_t *cipherbuf, uint_t datalen, boolean_t byteswap, + uint8_t *mac, zfs_uio_t *puio, zfs_uio_t *cuio, uint_t *enc_len, + uint8_t **authbuf, uint_t *auth_len, boolean_t *no_crypt) +{ + int ret; + iovec_t *mac_iov; + + ASSERT(DMU_OT_IS_ENCRYPTED(ot) || ot == DMU_OT_NONE); + + /* route to handler */ + switch (ot) { + case DMU_OT_INTENT_LOG: + ret = zio_crypt_init_uios_zil(encrypt, plainbuf, cipherbuf, + datalen, byteswap, puio, cuio, enc_len, authbuf, auth_len, + no_crypt); + break; + case DMU_OT_DNODE: + ret = zio_crypt_init_uios_dnode(encrypt, version, plainbuf, + cipherbuf, datalen, byteswap, puio, cuio, enc_len, authbuf, + auth_len, no_crypt); + break; + default: + ret = zio_crypt_init_uios_normal(encrypt, plainbuf, cipherbuf, + datalen, puio, cuio, enc_len); + *authbuf = NULL; + *auth_len = 0; + *no_crypt = B_FALSE; + break; + } + + if (ret != 0) + goto error; + + /* populate the uios */ + puio->uio_segflg = UIO_SYSSPACE; + cuio->uio_segflg = UIO_SYSSPACE; + + mac_iov = ((iovec_t *)&cuio->uio_iov[cuio->uio_iovcnt - 1]); + mac_iov->iov_base = mac; + mac_iov->iov_len = ZIO_DATA_MAC_LEN; + + return (0); + +error: + return (ret); +} + +/* + * Primary encryption / decryption entrypoint for zio data. + */ +int +zio_do_crypt_data(boolean_t encrypt, zio_crypt_key_t *key, + dmu_object_type_t ot, boolean_t byteswap, uint8_t *salt, uint8_t *iv, + uint8_t *mac, uint_t datalen, uint8_t *plainbuf, uint8_t *cipherbuf, + boolean_t *no_crypt) +{ + int ret; + boolean_t locked = B_FALSE; + uint64_t crypt = key->zk_crypt; + uint_t keydata_len = zio_crypt_table[crypt].ci_keylen; + uint_t enc_len, auth_len; + zfs_uio_t puio, cuio; + uint8_t enc_keydata[MASTER_KEY_MAX_LEN]; + crypto_key_t tmp_ckey, *ckey = NULL; + crypto_ctx_template_t tmpl; + uint8_t *authbuf = NULL; + + /* + * If the needed key is the current one, just use it. Otherwise we + * need to generate a temporary one from the given salt + master key. + * If we are encrypting, we must return a copy of the current salt + * so that it can be stored in the blkptr_t. + */ + rw_enter(&key->zk_salt_lock, RW_READER); + locked = B_TRUE; + + if (memcmp(salt, key->zk_salt, ZIO_DATA_SALT_LEN) == 0) { + ckey = &key->zk_current_key; + tmpl = key->zk_current_tmpl; + } else { + rw_exit(&key->zk_salt_lock); + locked = B_FALSE; + + ret = hkdf_sha512(key->zk_master_keydata, keydata_len, NULL, 0, + salt, ZIO_DATA_SALT_LEN, enc_keydata, keydata_len); + if (ret != 0) + goto error; + + tmp_ckey.ck_data = enc_keydata; + tmp_ckey.ck_length = CRYPTO_BYTES2BITS(keydata_len); + + ckey = &tmp_ckey; + tmpl = NULL; + } + + /* + * Attempt to use QAT acceleration if we can. We currently don't + * do this for metadnode and ZIL blocks, since they have a much + * more involved buffer layout and the qat_crypt() function only + * works in-place. + */ + if (qat_crypt_use_accel(datalen) && + ot != DMU_OT_INTENT_LOG && ot != DMU_OT_DNODE) { + uint8_t *srcbuf, *dstbuf; + + if (encrypt) { + srcbuf = plainbuf; + dstbuf = cipherbuf; + } else { + srcbuf = cipherbuf; + dstbuf = plainbuf; + } + + ret = qat_crypt((encrypt) ? QAT_ENCRYPT : QAT_DECRYPT, srcbuf, + dstbuf, NULL, 0, iv, mac, ckey, key->zk_crypt, datalen); + if (ret == CPA_STATUS_SUCCESS) { + if (locked) { + rw_exit(&key->zk_salt_lock); + locked = B_FALSE; + } + + return (0); + } + /* If the hardware implementation fails fall back to software */ + } + + memset(&puio, 0, sizeof (zfs_uio_t)); + memset(&cuio, 0, sizeof (zfs_uio_t)); + + /* create uios for encryption */ + ret = zio_crypt_init_uios(encrypt, key->zk_version, ot, plainbuf, + cipherbuf, datalen, byteswap, mac, &puio, &cuio, &enc_len, + &authbuf, &auth_len, no_crypt); + if (ret != 0) + goto error; + + /* perform the encryption / decryption in software */ + ret = zio_do_crypt_uio(encrypt, key->zk_crypt, ckey, tmpl, iv, enc_len, + &puio, &cuio, authbuf, auth_len); + if (ret != 0) + goto error; + + if (locked) { + rw_exit(&key->zk_salt_lock); + locked = B_FALSE; + } + + if (authbuf != NULL) + zio_buf_free(authbuf, datalen); + if (ckey == &tmp_ckey) + memset(enc_keydata, 0, keydata_len); + zio_crypt_destroy_uio(&puio); + zio_crypt_destroy_uio(&cuio); + + return (0); + +error: + if (locked) + rw_exit(&key->zk_salt_lock); + if (authbuf != NULL) + zio_buf_free(authbuf, datalen); + if (ckey == &tmp_ckey) + memset(enc_keydata, 0, keydata_len); + zio_crypt_destroy_uio(&puio); + zio_crypt_destroy_uio(&cuio); + + return (ret); +} + +/* + * Simple wrapper around zio_do_crypt_data() to work with abd's instead of + * linear buffers. + */ +int +zio_do_crypt_abd(boolean_t encrypt, zio_crypt_key_t *key, dmu_object_type_t ot, + boolean_t byteswap, uint8_t *salt, uint8_t *iv, uint8_t *mac, + uint_t datalen, abd_t *pabd, abd_t *cabd, boolean_t *no_crypt) +{ + int ret; + void *ptmp, *ctmp; + + if (encrypt) { + ptmp = abd_borrow_buf_copy(pabd, datalen); + ctmp = abd_borrow_buf(cabd, datalen); + } else { + ptmp = abd_borrow_buf(pabd, datalen); + ctmp = abd_borrow_buf_copy(cabd, datalen); + } + + ret = zio_do_crypt_data(encrypt, key, ot, byteswap, salt, iv, mac, + datalen, ptmp, ctmp, no_crypt); + if (ret != 0) + goto error; + + if (encrypt) { + abd_return_buf(pabd, ptmp, datalen); + abd_return_buf_copy(cabd, ctmp, datalen); + } else { + abd_return_buf_copy(pabd, ptmp, datalen); + abd_return_buf(cabd, ctmp, datalen); + } + + return (0); + +error: + if (encrypt) { + abd_return_buf(pabd, ptmp, datalen); + abd_return_buf_copy(cabd, ctmp, datalen); + } else { + abd_return_buf_copy(pabd, ptmp, datalen); + abd_return_buf(cabd, ctmp, datalen); + } + + return (ret); +} + +#if defined(_KERNEL) +/* BEGIN CSTYLED */ +module_param(zfs_key_max_salt_uses, ulong, 0644); +MODULE_PARM_DESC(zfs_key_max_salt_uses, "Max number of times a salt value " + "can be used for generating encryption keys before it is rotated"); +/* END CSTYLED */ +#endif diff --git a/module/os/macos/zfs/zvolIO.cpp b/module/os/macos/zfs/zvolIO.cpp new file mode 100644 index 000000000000..c951d4127fb9 --- /dev/null +++ b/module/os/macos/zfs/zvolIO.cpp @@ -0,0 +1,1369 @@ +/* + * 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-2020, Jorgen Lundman. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * ZVOL Device + */ + +// Define the superclass +#define super IOBlockStorageDevice + +#define ZVOL_BSIZE DEV_BSIZE + +static const char *ZVOL_PRODUCT_NAME_PREFIX = "ZVOL "; + +/* Wrapper for zvol_state pointer to IOKit device */ +typedef struct zvol_iokit { + org_openzfsonosx_zfs_zvol_device *dev; +} zvol_iokit_t; + +OSDefineMetaClassAndStructors(org_openzfsonosx_zfs_zvol_device, + IOBlockStorageDevice) + +bool +org_openzfsonosx_zfs_zvol_device::init(zvol_state_t *c_zv, + OSDictionary *properties) +{ + zvol_iokit_t *iokitdev = NULL; + + dprintf("zvolIO_device:init\n"); + + if (!c_zv || c_zv->zv_zso->zvo_iokitdev != NULL) { + dprintf("zvol %s invalid c_zv\n", __func__); + return (false); + } + + if ((iokitdev = (zvol_iokit_t *)kmem_alloc(sizeof (zvol_iokit_t), + KM_SLEEP)) == NULL) { + printf("zvol %s wrapper alloc failed\n", __func__); + return (false); + } + + if (super::init(properties) == false) { + printf("zvol %s super init failed\n", __func__); + kmem_free(iokitdev, sizeof (zvol_iokit_t)); + return (false); + } + + /* Store reference to zvol_state_t in the iokitdev */ + zv = c_zv; + /* Store reference to iokitdev in zvol_state_t */ + iokitdev->dev = this; + + /* Assign to zv once completely initialized */ + zv->zv_zso->zvo_iokitdev = iokitdev; + + /* Apply the name from the full dataset path */ + if (strlen(zv->zv_name) != 0) { + setName(zv->zv_name); + } + + return (true); +} + +bool +org_openzfsonosx_zfs_zvol_device::attach(IOService *provider) +{ + OSDictionary *protocolCharacteristics = 0; + OSDictionary *deviceCharacteristics = 0; + OSDictionary *storageFeatures = 0; + OSBoolean *unmapFeature = 0; + const OSSymbol *propSymbol = 0; + OSString *dataString = 0; + OSNumber *dataNumber = 0; + char product_name[MAXPATHLEN + 1]; + + if (!provider) { + dprintf("ZVOL attach missing provider\n"); + return (false); + } + + if (super::attach(provider) == false) + return (false); + + /* + * We want to set some additional properties for ZVOLs, in + * particular, "Virtual Device", and type "File" + * (or is Internal better?) + * + * Finally "Generic" type. + * + * These properties are defined in *protocol* characteristics + */ + + protocolCharacteristics = OSDictionary::withCapacity(3); + + if (!protocolCharacteristics) { + IOLog("failed to create dict for protocolCharacteristics.\n"); + return (true); + } + + propSymbol = OSSymbol::withCString( + kIOPropertyPhysicalInterconnectTypeVirtual); + + if (!propSymbol) { + IOLog("could not create interconnect type string\n"); + return (true); + } + protocolCharacteristics->setObject( + kIOPropertyPhysicalInterconnectTypeKey, propSymbol); + + propSymbol->release(); + propSymbol = 0; + + propSymbol = OSSymbol::withCString(kIOPropertyInterconnectFileKey); + if (!propSymbol) { + IOLog("could not create interconnect location string\n"); + return (true); + } + protocolCharacteristics->setObject( + kIOPropertyPhysicalInterconnectLocationKey, propSymbol); + + propSymbol->release(); + propSymbol = 0; + + setProperty(kIOPropertyProtocolCharacteristicsKey, + protocolCharacteristics); + + protocolCharacteristics->release(); + protocolCharacteristics = 0; + + /* + * We want to set some additional properties for ZVOLs, in + * particular, physical block size (volblocksize) of the + * underlying ZVOL, and 'logical' block size presented by + * the virtual disk. Also set physical bytes per sector. + * + * These properties are defined in *device* characteristics + */ + + deviceCharacteristics = OSDictionary::withCapacity(3); + + if (!deviceCharacteristics) { + IOLog("failed to create dict for deviceCharacteristics.\n"); + return (true); + } + + /* Set this device to be an SSD, for priority and VM paging */ + propSymbol = OSSymbol::withCString( + kIOPropertyMediumTypeSolidStateKey); + if (!propSymbol) { + IOLog("could not create medium type string\n"); + return (true); + } + deviceCharacteristics->setObject(kIOPropertyMediumTypeKey, + propSymbol); + + propSymbol->release(); + propSymbol = 0; + + /* Set logical block size to ZVOL_BSIZE (512b) */ + dataNumber = OSNumber::withNumber(ZVOL_BSIZE, + 8 * sizeof (ZVOL_BSIZE)); + + deviceCharacteristics->setObject(kIOPropertyLogicalBlockSizeKey, + dataNumber); + + dprintf("logicalBlockSize %llu\n", + dataNumber->unsigned64BitValue()); + + dataNumber->release(); + dataNumber = 0; + + /* Set physical block size to match volblocksize property */ + dataNumber = OSNumber::withNumber(zv->zv_volblocksize, + 8 * sizeof (zv->zv_volblocksize)); + + deviceCharacteristics->setObject(kIOPropertyPhysicalBlockSizeKey, + dataNumber); + + dprintf("physicalBlockSize %llu\n", + dataNumber->unsigned64BitValue()); + + dataNumber->release(); + dataNumber = 0; + + /* Set physical bytes per sector to match volblocksize property */ + dataNumber = OSNumber::withNumber((uint64_t)(zv->zv_volblocksize), + 8 * sizeof (uint64_t)); + + deviceCharacteristics->setObject(kIOPropertyBytesPerPhysicalSectorKey, + dataNumber); + + dprintf("physicalBytesPerSector %llu\n", + dataNumber->unsigned64BitValue()); + + dataNumber->release(); + dataNumber = 0; + + /* Publish the Device / Media name */ + (void) snprintf(product_name, sizeof (product_name), "%s%s", + ZVOL_PRODUCT_NAME_PREFIX, zv->zv_name); + dataString = OSString::withCString(product_name); + deviceCharacteristics->setObject(kIOPropertyProductNameKey, dataString); + dataString->release(); + dataString = 0; + + /* Apply these characteristics */ + setProperty(kIOPropertyDeviceCharacteristicsKey, + deviceCharacteristics); + + deviceCharacteristics->release(); + deviceCharacteristics = 0; + + /* + * ZVOL unmap support + * + * These properties are defined in IOStorageFeatures + */ + + storageFeatures = OSDictionary::withCapacity(1); + if (!storageFeatures) { + IOLog("failed to create dictionary for storageFeatures.\n"); + return (true); + } + + /* Set unmap feature */ + unmapFeature = OSBoolean::withBoolean(true); + storageFeatures->setObject(kIOStorageFeatureUnmap, unmapFeature); + unmapFeature->release(); + unmapFeature = 0; + + /* Apply these storage features */ + setProperty(kIOStorageFeaturesKey, storageFeatures); + storageFeatures->release(); + storageFeatures = 0; + + + /* + * Set transfer limits: + * + * Maximum transfer size (bytes) + * Maximum transfer block count + * Maximum transfer block size (bytes) + * Maximum transfer segment count + * Maximum transfer segment size (bytes) + * Minimum transfer segment size (bytes) + * + * We will need to establish safe defaults for all / per volblocksize + * + * Example: setProperty(kIOMinimumSegmentAlignmentByteCountKey, 1, 1); + */ + + /* + * Finally "Generic" type, set as a device property. Tried setting this + * to the string "ZVOL" however the OS does not recognize it as a block + * storage device. This would probably be possible by extending the + * IOBlockStorage Device / Driver relationship. + */ + + setProperty(kIOBlockStorageDeviceTypeKey, + kIOBlockStorageDeviceTypeGeneric); + + return (true); +} + +int +org_openzfsonosx_zfs_zvol_device::renameDevice(void) +{ + OSDictionary *deviceDict; + OSString *nameStr; + char *newstr; + int len; + + /* Length of string and null terminating character */ + len = strlen(ZVOL_PRODUCT_NAME_PREFIX) + strlen(zv->zv_name) + 1; + newstr = (char *)kmem_alloc(len, KM_SLEEP); + if (!newstr) { + dprintf("%s string alloc failed\n", __func__); + return (ENOMEM); + } + + /* Append prefix and dsl name */ + snprintf(newstr, len, "%s%s", ZVOL_PRODUCT_NAME_PREFIX, zv->zv_name); + nameStr = OSString::withCString(newstr); + kmem_free(newstr, len); + + if (!nameStr) { + dprintf("%s couldn't allocate name string\n", __func__); + return (ENOMEM); + } + + /* Fetch current device characteristics dictionary */ + deviceDict = OSDynamicCast(OSDictionary, + getProperty(kIOPropertyDeviceCharacteristicsKey)); + if (!deviceDict || (deviceDict = + OSDictionary::withDictionary(deviceDict)) == NULL) { + dprintf("couldn't clone device characteristics\n"); + /* Allocate new dict */ + if (!deviceDict && + (deviceDict = OSDictionary::withCapacity(1)) == NULL) { + dprintf("%s OSDictionary alloc failed\n", __func__); + nameStr->release(); + return (ENOMEM); + } + + } + + /* Add or replace the product name */ + if (deviceDict->setObject(kIOPropertyProductNameKey, + nameStr) == false) { + dprintf("%s couldn't set product name\n", __func__); + nameStr->release(); + deviceDict->release(); + return (ENXIO); + } + nameStr->release(); + nameStr = 0; + + /* Set IORegistry property */ + if (setProperty(kIOPropertyDeviceCharacteristicsKey, + deviceDict) == false) { + dprintf("%s couldn't set IORegistry property\n", __func__); + deviceDict->release(); + return (ENXIO); + } + deviceDict->release(); + deviceDict = 0; + + /* Apply the name from the full dataset path */ + setName(zv->zv_name); + + return (0); +} + +int +org_openzfsonosx_zfs_zvol_device::offlineDevice(void) +{ + IOService *client; + + if ((client = this->getClient()) == NULL) { + return (ENOENT); + } + + /* Ask IOBlockStorageDevice to offline media */ + if (client->message(kIOMessageMediaStateHasChanged, + this, (void *)kIOMediaStateOffline) != kIOReturnSuccess) { + dprintf("%s failed\n", __func__); + return (ENXIO); + } + + return (0); +} + +int +org_openzfsonosx_zfs_zvol_device::onlineDevice(void) +{ + IOService *client; + + if ((client = this->getClient()) == NULL) { + return (ENOENT); + } + + /* Ask IOBlockStorageDevice to online media */ + if (client->message(kIOMessageMediaStateHasChanged, + this, (void *)kIOMediaStateOnline) != kIOReturnSuccess) { + dprintf("%s failed\n", __func__); + return (ENXIO); + } + + return (0); +} + +int +org_openzfsonosx_zfs_zvol_device::refreshDevice(void) +{ + IOService *client; + + if ((client = this->getClient()) == NULL) { + return (ENOENT); + } + + /* Ask IOBlockStorageDevice to reset the media params */ + if (client->message(kIOMessageMediaParametersHaveChanged, + this) != kIOReturnSuccess) { + dprintf("%s failed\n", __func__); + return (ENXIO); + } + + return (0); +} + +int +org_openzfsonosx_zfs_zvol_device::getBSDName(void) +{ + IORegistryEntry *ioregdevice = 0; + OSObject *bsdnameosobj = 0; + OSString* bsdnameosstr = 0; + + ioregdevice = OSDynamicCast(IORegistryEntry, this); + + if (!ioregdevice) + return (-1); + + bsdnameosobj = ioregdevice->getProperty(kIOBSDNameKey, + gIOServicePlane, kIORegistryIterateRecursively); + + if (!bsdnameosobj) + return (-1); + + bsdnameosstr = OSDynamicCast(OSString, bsdnameosobj); + + IOLog("zvol: bsd name is '%s'\n", + bsdnameosstr->getCStringNoCopy()); + + if (!zv) + return (-1); + + zv->zv_zso->zvo_bsdname[0] = 'r'; // for 'rdiskX'. + strlcpy(&zv->zv_zso->zvo_bsdname[1], + bsdnameosstr->getCStringNoCopy(), + sizeof (zv->zv_zso->zvo_bsdname)-1); + /* + * IOLog("name assigned '%s'\n", zv->zv_zso->zvo_bsdname); + */ + + return (0); +} + +void +org_openzfsonosx_zfs_zvol_device::detach(IOService *provider) +{ + super::detach(provider); +} + +void +org_openzfsonosx_zfs_zvol_device::clearState(void) +{ + zv = NULL; +} + +bool +org_openzfsonosx_zfs_zvol_device::handleOpen(IOService *client, + IOOptionBits options, void *argument) +{ + IOStorageAccess access = (uintptr_t)argument; + bool ret = false; + int openflags = 0; + + if (super::handleOpen(client, options, argument) == false) + return (false); + + /* Device terminating? */ + if (zv == NULL || + zv->zv_zso == NULL || + zv->zv_zso->zvo_iokitdev == NULL) + return (false); + + if (access & kIOStorageAccessReaderWriter) { + openflags = FWRITE | ZVOL_EXCL; + } else { + openflags = FREAD; + } + + /* + * Don't use 'zv' until it has been verified by zvol_os_open_zv() + * and returned as opened, then it holds an open count and can be + * used. + */ + + if (zvol_os_open_zv(zv, zv->zv_zso->zvo_openflags, 0, NULL) == 0) { + ret = true; + } + + if (ret) + zv->zv_zso->zvo_openflags = openflags; + + + dprintf("Open %s (openflags %llx)\n", (ret ? "done" : "failed"), + ret ? zv->zv_zso->zvo_openflags : 0); + + if (ret == false) + super::handleClose(client, options); + + return (ret); +} + +void +org_openzfsonosx_zfs_zvol_device::handleClose(IOService *client, + IOOptionBits options) +{ + super::handleClose(client, options); + + /* Terminating ? */ + if (zv == NULL || + zv->zv_zso == NULL || + zv->zv_zso->zvo_iokitdev == NULL) + return; + + zvol_os_close_zv(zv, zv->zv_zso->zvo_openflags, 0, NULL); + +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::doAsyncReadWrite( + IOMemoryDescriptor *buffer, UInt64 block, UInt64 nblks, + IOStorageAttributes *attributes, IOStorageCompletion *completion) +{ + IODirection direction; + IOByteCount actualByteCount; + + // Return errors for incoming I/O if we have been terminated. + if (isInactive() == true) { + dprintf("asyncReadWrite notActive fail\n"); + return (kIOReturnNotAttached); + } + + // These variables are set in zvol_first_open(), which should have been + // called already. + if (!zv->zv_dn) { + dprintf("asyncReadWrite no zvol dnode\n"); + return (kIOReturnNotAttached); + } + + // Ensure the start block is within the disk capacity. + if ((block)*(ZVOL_BSIZE) >= zv->zv_volsize) { + dprintf("asyncReadWrite start block outside volume\n"); + return (kIOReturnBadArgument); + } + + // Shorten the read, if beyond the end + if (((block + nblks)*(ZVOL_BSIZE)) > zv->zv_volsize) { + dprintf("asyncReadWrite block shortening needed\n"); + return (kIOReturnBadArgument); + } + + // Get the buffer direction, whether this is a read or a write. + direction = buffer->getDirection(); + if ((direction != kIODirectionIn) && (direction != kIODirectionOut)) { + dprintf("asyncReadWrite kooky direction\n"); + return (kIOReturnBadArgument); + } + + // dprintf("%s offset @block %llu numblocks %llu: blksz %u\n", + // direction == kIODirectionIn ? "Read" : "Write", + // block, nblks, (ZVOL_BSIZE)); + + /* Perform the read or write operation through the transport driver. */ + actualByteCount = (nblks*(ZVOL_BSIZE)); + + /* Make sure we don't go away while the command is being executed */ + /* Open should be holding a retain */ + + struct iovec iov; + iov.iov_base = (void *)buffer; + iov.iov_len = actualByteCount; + zfs_uio_t uio; + zfs_uio_iovec_func_init(&uio, &iov, 1, block*(ZVOL_BSIZE), + (zfs_uio_seg_t)UIO_FUNCSPACE, actualByteCount, 0, zvolIO_strategy); + + if (direction == kIODirectionIn) { + zvol_os_read_zv(zv, &uio); + } else { + zvol_os_write_zv(zv, &uio); + } + + if (zfs_uio_resid(&uio) != 0) + printf("Read/Write operation failed\n"); + + // Call the completion function. + (completion->action)(completion->target, completion->parameter, + kIOReturnSuccess, actualByteCount); + + return (kIOReturnSuccess); +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::doDiscard(UInt64 block, UInt64 nblks) +{ + dprintf("doDiscard called with block, nblks (%llu, %llu)\n", + block, nblks); + uint64_t bytes = 0; + uint64_t off = 0; + + /* Convert block/nblks to offset/bytes */ + off = block * ZVOL_BSIZE; + bytes = nblks * ZVOL_BSIZE; + dprintf("calling zvol_unmap with offset, bytes (%llu, %llu)\n", + off, bytes); + + if (zvol_os_unmap(zv, off, bytes) == 0) + return (kIOReturnSuccess); + else + return (kIOReturnError); +} + + +IOReturn +org_openzfsonosx_zfs_zvol_device::doUnmap(IOBlockStorageDeviceExtent *extents, + UInt32 extentsCount, UInt32 options = 0) +{ + UInt32 i = 0; + IOReturn result; + + dprintf("doUnmap called with (%u) extents and options (%u)\n", + (uint32_t)extentsCount, (uint32_t)options); + + if (options > 0 || !extents) { + return (kIOReturnUnsupported); + } + + for (i = 0; i < extentsCount; i++) { + + result = doDiscard(extents[i].blockStart, + extents[i].blockCount); + + if (result != kIOReturnSuccess) { + return (result); + } + } + + return (kIOReturnSuccess); +} + +UInt32 +org_openzfsonosx_zfs_zvol_device::doGetFormatCapacities(UInt64* capacities, + UInt32 capacitiesMaxCount) const +{ + dprintf("formatCap\n"); + + /* + * Ensure that the array is sufficient to hold all our formats + * (we require one element). + */ + if ((capacities != NULL) && (capacitiesMaxCount < 1)) + return (0); + /* Error, return an array size of 0. */ + + /* + * The caller may provide a NULL array if it wishes to query the number + * of formats that we support. + */ + if (capacities != NULL) + capacities[0] = zv->zv_volsize; + + dprintf("returning capacity[0] size %llu\n", zv->zv_volsize); + + return (1); +} + +char * +org_openzfsonosx_zfs_zvol_device::getProductString(void) +{ + dprintf("getProduct %p\n", zv); + + if (zv) + return (zv->zv_name); + + return ((char *)"ZVolume"); +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::reportBlockSize(UInt64 *blockSize) +{ + dprintf("reportBlockSize %llu\n", *blockSize); + + if (blockSize) *blockSize = (ZVOL_BSIZE); + + return (kIOReturnSuccess); +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::reportMaxValidBlock(UInt64 *maxBlock) +{ + dprintf("reportMaxValidBlock %llu\n", *maxBlock); + + if (maxBlock) *maxBlock = ((zv->zv_volsize / (ZVOL_BSIZE)) - 1); + + return (kIOReturnSuccess); +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::reportMediaState(bool *mediaPresent, + bool *changedState) +{ + dprintf("reportMediaState\n"); + if (mediaPresent) *mediaPresent = true; + if (changedState) *changedState = false; + return (kIOReturnSuccess); +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::reportPollRequirements(bool *pollRequired, + bool *pollIsExpensive) +{ + dprintf("reportPollReq\n"); + if (pollRequired) *pollRequired = false; + if (pollIsExpensive) *pollIsExpensive = false; + return (kIOReturnSuccess); +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::reportRemovability(bool *isRemovable) +{ + dprintf("reportRemova\n"); + if (isRemovable) *isRemovable = false; + return (kIOReturnSuccess); +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::doEjectMedia(void) +{ + dprintf("ejectMedia\n"); +/* XXX */ + // Only 10.6 needs special work to eject + // if ((version_major == 10) && (version_minor == 8)) + // destroyBlockStorageDevice(zvol); + // } + + return (kIOReturnError); + return (kIOReturnSuccess); +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::doFormatMedia(UInt64 byteCapacity) +{ + dprintf("doFormat\n"); + return (kIOReturnSuccess); +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::doLockUnlockMedia(bool doLock) +{ + dprintf("doLockUnlock\n"); + return (kIOReturnSuccess); +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::doSynchronizeCache(void) +{ + dprintf("doSync\n"); + if (zv && zv->zv_zilog) { + zil_commit(zv->zv_zilog, ZVOL_OBJ); + } + return (kIOReturnSuccess); +} + +char * +org_openzfsonosx_zfs_zvol_device::getVendorString(void) +{ + dprintf("getVendor\n"); + return ((char *)"ZVOL"); +} + +char * +org_openzfsonosx_zfs_zvol_device::getRevisionString(void) +{ + dprintf("getRevision\n"); + return ((char *)ZFS_META_VERSION); +} + +char * +org_openzfsonosx_zfs_zvol_device::getAdditionalDeviceInfoString(void) +{ + dprintf("getAdditional\n"); + return ((char *)"ZFS Volume"); +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::reportEjectability(bool *isEjectable) +{ + dprintf("reportEjecta\n"); + /* + * Which do we prefer? If you eject it, you can't get volume back until + * you import it again. + */ + + if (isEjectable) *isEjectable = false; + return (kIOReturnSuccess); +} + +/* XXX deprecated function */ +IOReturn +org_openzfsonosx_zfs_zvol_device::reportLockability(bool *isLockable) +{ + dprintf("reportLocka\n"); + if (isLockable) *isLockable = true; + return (kIOReturnSuccess); +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::reportWriteProtection(bool *isWriteProtected) +{ + dprintf("reportWritePro: %d\n", *isWriteProtected); + + if (!isWriteProtected) + return (kIOReturnSuccess); + + if (zv && (zv->zv_flags & ZVOL_RDONLY)) + *isWriteProtected = true; + else + *isWriteProtected = false; + + return (kIOReturnSuccess); +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::getWriteCacheState(bool *enabled) +{ + dprintf("getCacheState\n"); + if (enabled) *enabled = true; + return (kIOReturnSuccess); +} + +IOReturn +org_openzfsonosx_zfs_zvol_device::setWriteCacheState(bool enabled) +{ + dprintf("setWriteCache\n"); + return (kIOReturnSuccess); +} + +extern "C" { + +/* C interfaces */ +int +zvolCreateNewDevice(zvol_state_t *zv) +{ + org_openzfsonosx_zfs_zvol_device *zvol; + ZFSPool *pool_proxy; + spa_t *spa; + dprintf("%s\n", __func__); + + /* We must have a valid zvol_state_t */ + if (!zv || !zv->zv_objset) { + dprintf("%s missing zv or objset\n", __func__); + return (EINVAL); + } + + /* We need the spa to get the pool proxy */ + if ((spa = dmu_objset_spa(zv->zv_objset)) == NULL) { + dprintf("%s couldn't get spa\n", __func__); + return (EINVAL); + } + if (spa->spa_iokit_proxy == NULL || + (pool_proxy = spa->spa_iokit_proxy->proxy) == NULL) { + dprintf("%s missing IOKit pool proxy\n", __func__); + return (EINVAL); + } + + zvol = new org_openzfsonosx_zfs_zvol_device; + + /* Validate creation, initialize and attach */ + if (!zvol || zvol->init(zv) == false || + zvol->attach(pool_proxy) == false) { + dprintf("%s device creation failed\n", __func__); + if (zvol) zvol->release(); + return (ENOMEM); + } + /* Start the service */ + if (zvol->start(pool_proxy) == false) { + dprintf("%s device start failed\n", __func__); + zvol->detach(pool_proxy); + zvol->release(); + return (ENXIO); + } + + /* Open pool_proxy provider */ + if (pool_proxy->open(zvol) == false) { + dprintf("%s open provider failed\n", __func__); + zvol->stop(pool_proxy); + zvol->detach(pool_proxy); + zvol->release(); + return (ENXIO); + } + /* Is retained by provider */ + zvol->release(); + zvol = 0; + + return (0); +} + + +/* + * Sometimes we need to wait for zvol name to show up. + * 0 means success - if ret_service is given, service is returned. + * Caller should release() + * > 0 means error of some kind + * -1 means timeout. + */ +static int +zvolWaitForName(char *name, char *vendor, uint64_t timeout, + IOService **ret_service) +{ + OSDictionary *matching; + IOService *service = 0; + OSString *nameStr = 0; + char str[MAXNAMELEN]; + + snprintf(str, MAXNAMELEN, "%s %s Media", + vendor, name); + if ((nameStr = OSString::withCString(str)) == NULL) { + dprintf("%s problem with name string\n", __func__); + return (ENOMEM); + } + + matching = IOService::serviceMatching("IOMedia"); + if (!matching || !matching->setObject(gIONameMatchKey, nameStr)) { + dprintf("%s couldn't get matching dictionary\n", __func__); + nameStr->release(); + return (ENOMEM); + } + + /* Wait for upper layer BSD client */ + printf("%s waiting for IOMedia\n", __func__); + + /* Wait for up to `timeout` */ + service = IOService::waitForMatchingService(matching, timeout); + dprintf("%s %s service\n", __func__, (service ? "got" : "no")); + + nameStr->release(); + matching->release(); + + if (!service) + return (SET_ERROR(-1)); + + if (ret_service != 0) + *ret_service = service; + else + service->release(); + + return (0); +} + +int +zvolRegisterDevice(zvol_state_t *zv) +{ + org_openzfsonosx_zfs_zvol_device *zvol; + IOService *service = 0; + IOMedia *media = 0; + OSString *bsdName = 0; + int ret = ENOENT; + + if (!zv || !zv->zv_zso->zvo_iokitdev || zv->zv_name[0] == 0) { + dprintf("%s missing zv, iokitdev, or name\n", __func__); + return (SET_ERROR(EINVAL)); + } + + if ((zvol = zv->zv_zso->zvo_iokitdev->dev) == NULL) { + dprintf("%s couldn't get zvol device\n", __func__); + return (SET_ERROR(EINVAL)); + } + + if (!zvol->getVendorString()) { + return (SET_ERROR(EINVAL)); + } + + /* Register device for service matching */ + zvol->registerService(kIOServiceAsynchronous); + + if (zvolWaitForName(zv->zv_name, zvol->getVendorString(), + (5ULL * kSecondScale), &service) != 0) { + dprintf("%s couldn't get matching dictionary\n", __func__); + return (SET_ERROR(ENOMEM)); + } + + if (!service) { + dprintf("%s couldn't get matching service\n", __func__); + return (SET_ERROR(ENOENT)); + } + + dprintf("%s casting to IOMedia\n", __func__); + media = OSDynamicCast(IOMedia, service); + + if (!media) { + dprintf("%s no IOMedia\n", __func__); + service->release(); + return (SET_ERROR(ENOENT)); + } + + dprintf("%s getting IOBSDNameKey\n", __func__); + bsdName = OSDynamicCast(OSString, + media->getProperty(kIOBSDNameKey)); + + if (bsdName) { + const char *str = bsdName->getCStringNoCopy(); + dprintf("%s Got bsd name [%s]\n", + __func__, str); + zv->zv_zso->zvo_bsdname[0] = 'r'; + snprintf(zv->zv_zso->zvo_bsdname+1, + sizeof (zv->zv_zso->zvo_bsdname)-1, + "%s", str); + dprintf("%s zvol bsdname set to %s\n", __func__, + zv->zv_zso->zvo_bsdname); + zvol_add_symlink(zv, zv->zv_zso->zvo_bsdname+1, + zv->zv_zso->zvo_bsdname); + ret = 0; + } else { + dprintf("%s couldn't get BSD Name\n", __func__); + } + + /* Release retain held by waitForMatchingService */ + service->release(); + + dprintf("%s complete\n", __func__); + return (ret); +} + +/* Struct passed in will be freed before returning */ +void * +zvolRemoveDevice(zvol_state_t *zv) +{ + zvol_iokit_t *iokitdev = zv->zv_zso->zvo_iokitdev; + org_openzfsonosx_zfs_zvol_device *zvol; + dprintf("%s\n", __func__); + + if (!iokitdev) { + dprintf("%s missing argument\n", __func__); + return (NULL); + } + + zvol = iokitdev->dev; + + /* Free the wrapper struct */ + kmem_free(iokitdev, sizeof (zvol_iokit_t)); + + if (zvol == NULL) { + dprintf("%s couldn't get IOKit handle\n", __func__); + return (NULL); + } + + /* Mark us as terminating */ + zvol->clearState(); + + return (zvol); +} + +/* + * zvolRemoveDevice continued.. + * terminate() will block and we can deadlock, so it is issued as a + * separate thread. Done from zvol_os.c as it is easier in C. + */ +int +zvolRemoveDeviceTerminate(void *arg) +{ + org_openzfsonosx_zfs_zvol_device *zvol = + (org_openzfsonosx_zfs_zvol_device *)arg; + + IOLog("zvolRemoveDeviceTerminate\n"); + + /* Terminate */ + if (zvol->terminate(kIOServiceTerminate|kIOServiceSynchronous| + kIOServiceRequired) == false) { + IOLog("%s terminate failed\n", __func__); + } + + return (0); +} + +/* Called with zv->zv_name already updated */ +int +zvolRenameDevice(zvol_state_t *zv) +{ + org_openzfsonosx_zfs_zvol_device *zvol = NULL; + int error; + + if (!zv || strnlen(zv->zv_name, 1) == 0) { + dprintf("%s missing argument\n", __func__); + return (EINVAL); + } + + if ((zvol = zv->zv_zso->zvo_iokitdev->dev) == NULL) { + dprintf("%s couldn't get zvol device\n", __func__); + return (EINVAL); + } + + /* Set IORegistry name and property */ + if ((error = zvol->renameDevice()) != 0) { + dprintf("%s renameDevice error %d\n", __func__, error); + return (error); + } + + /* + * XXX This works, but if there is a volume mounted on + * the zvol at the time it is uncleanly ejected. + * We just need to add diskutil unmount to `zfs rename`, + * like zpool export. + */ + /* Inform clients of this device that name has changed */ + if (zvolWaitForName(zv->zv_name, zvol->getVendorString(), + (2ULL * kSecondScale), NULL) != 0) + dprintf("wait for rename failed.\n"); + + + if (zvol->offlineDevice() != 0 || + zvol->onlineDevice() != 0) { + dprintf("%s media reset failed\n", __func__); + return (ENXIO); + } + + return (0); +} + +/* Called with zvol volsize already updated */ +int +zvolSetVolsize(zvol_state_t *zv) +{ + org_openzfsonosx_zfs_zvol_device *zvol; + int error; + + dprintf("%s\n", __func__); + + if (!zv || !zv->zv_zso->zvo_iokitdev) { + dprintf("%s invalid zvol\n", __func__); + return (EINVAL); + } + + /* Cast to correct type */ + if ((zvol = zv->zv_zso->zvo_iokitdev->dev) == NULL) { + dprintf("%s couldn't cast IOKit handle\n", __func__); + return (ENXIO); + } + /* + * XXX This works fine, even if volume is mounted, + * but only tested expanding the zvol and only with + * GPT/APM/MBR partition map (not volume on whole-zvol). + */ + /* Inform clients of this device that size has changed */ + if ((error = zvol->refreshDevice()) != 0) { + dprintf("%s refreshDevice error %d\n", __func__, error); + return (error); + } + + return (0); +} + + +size_t zvolIO_strategy(char *addr, uint64_t offset, + size_t len, zfs_uio_rw_t rw, const void *privptr) +{ + IOMemoryDescriptor *iomem = (IOMemoryDescriptor *)privptr; + + if (rw == UIO_READ) + return (iomem->writeBytes(offset, (void *)addr, len)); + else + return (iomem->readBytes(offset, (void *)addr, len)); +} + +boolean_t +zvol_os_is_zvol_impl(const char *path) +{ + OSDictionary *matchDict = 0; + OSString *bsdName = NULL; + OSString *uuid = NULL; + const char *substr = 0; + bool ret = B_FALSE; + + dprintf("%s: processing '%s'\n", __func__, path); + + /* Validate path */ + if (path == 0 || strlen(path) <= 1) { + dprintf("%s no path provided\n", __func__); + return (SET_ERROR(ret)); + } + /* Translate /dev/diskN and InvariantDisks paths */ + if (strncmp(path, "/dev/", 5) != 0 && + strncmp(path, "/var/run/disk/by-id/", 20) != 0 && + strncmp(path, "/private/var/run/disk/by-id/", 28) != 0) { + dprintf("%s Unrecognized path %s\n", __func__, path); + return (SET_ERROR(ret)); + } + + /* Validate path and alloc bsdName */ + if (strncmp(path, "/dev/", 5) == 0) { + char disk[MAXPATHLEN]; + + /* substr starts after '/dev/' */ + substr = path + 5; + + strlcpy(disk, substr, MAXPATHLEN); + + /* For zvol_is_zvol, we want root disk, not slice. */ + if (tolower(disk[0]) == 'd' && + tolower(disk[1]) == 'i' && + tolower(disk[2]) == 's' && + tolower(disk[3]) == 'k') { + char *r = &disk[4]; + while (isdigit(*r)) r++; + if (tolower(*r) == 's') + *r = 0; + } + + /* Get diskN from /dev/diskN or /dev/rdiskN */ + if (strncmp(disk, "disk", 4) == 0) { + bsdName = OSString::withCString(disk); + } else if (strncmp(disk, "rdisk", 5) == 0) { + bsdName = OSString::withCString(disk + 1); + } + } else if (strncmp(path, "/var/run/disk/by-id/", 20) == 0 || + strncmp(path, "/private/var/run/disk/by-id/", 28) == 0) { + /* InvariantDisks paths */ + + /* substr starts after '/by-id/' */ + substr = path + 20; + if (strncmp(path, "/private", 8) == 0) substr += 8; + + /* Handle media UUID, skip volume UUID or device GUID */ + if (strncmp(substr, "media-", 6) == 0) { + /* Lookup IOMedia with UUID */ + uuid = OSString::withCString(substr+strlen("media-")); + } else if (strncmp(substr, "volume-", 7) == 0) { + /* + * volume-UUID is specified by DiskArbitration + * when a Filesystem bundle is able to probe + * the media and retrieve/generate a UUID for + * it's contents. + * So while we could use this and have zfs.util + * probe for vdev GUID (and pool GUID) and + * generate a UUID, we would need to do the same + * here to find the disk, possibly probing + * devices to get the vdev GUID in the process. + */ + dprintf("%s Unsupported volume-UUID path %s\n", + __func__, path); + } else if (strncmp(substr, "device-", 7) == 0) { + /* Lookup IOMedia with device GUID */ + /* + * XXX Not sure when this is used, no devices + * seem to be presented this way. + */ + dprintf("%s Unsupported device-GUID path %s\n", + __func__, path); + } else { + dprintf("%s unrecognized path %s\n", __func__, path); + } + /* by-path and by-serial are handled separately */ + } + + if (!bsdName && !uuid) { + dprintf("%s Invalid path %s\n", __func__, path); + return (SET_ERROR(ret)); + } + + /* Match on IOMedia by BSD disk name */ + matchDict = IOService::serviceMatching("IOMedia"); + if (!matchDict) { + dprintf("%s couldn't get matching dictionary\n", __func__); + if (bsdName) bsdName->release(); + if (uuid) uuid->release(); + return (SET_ERROR(ret)); + } + if (bsdName) { + + matchDict->setObject(kIOBSDNameKey, bsdName); + + } else if (uuid) { + if (matchDict->setObject(kIOMediaUUIDKey, uuid) == false) { + dprintf("%s couldn't setup UUID matching" + " dictionary\n", __func__); + matchDict->release(); + matchDict = 0; + } + } else { + dprintf("%s missing matching property\n", __func__); + matchDict->release(); + matchDict = 0; + } + + if (bsdName) bsdName->release(); + if (uuid) uuid->release(); + + if (matchDict == 0) + return (SET_ERROR(ret)); + + OSIterator *iter = 0; + OSObject *obj = 0; + IOMedia *media = 0; + + iter = IOService::getMatchingServices(matchDict); + + matchDict->release(); + + if (!iter) { + dprintf("%s No iterator from getMatchingServices\n", + __func__); + return (SET_ERROR(ret)); + } + + /* Get first object from iterator */ + while ((obj = iter->getNextObject()) != NULL) { + if ((media = OSDynamicCast(IOMedia, obj)) == NULL) { + obj = 0; + continue; + } + if (media->isFormatted() == false) { + obj = 0; + media = 0; + continue; + } + + media->retain(); + break; + } + + if (!media) { + dprintf("%s no match found\n", __func__); + iter->release(); + return (SET_ERROR(ret)); + } + + iter->release(); + iter = 0; + + // IOMedia from here on out. + + const char *name; + + name = media->getName(); + + if (strncmp(name, ZVOL_PRODUCT_NAME_PREFIX, + strlen(ZVOL_PRODUCT_NAME_PREFIX)) == 0) + ret = B_TRUE; + + media->release(); + + return (SET_ERROR(ret)); +} + + +} /* extern "C" */ diff --git a/module/os/macos/zfs/zvol_os.c b/module/os/macos/zfs/zvol_os.c new file mode 100644 index 000000000000..dc7a67b4de3b --- /dev/null +++ b/module/os/macos/zfs/zvol_os.c @@ -0,0 +1,1179 @@ +/* + * 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 Jorgen Lundman. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static uint32_t zvol_major = ZVOL_MAJOR; + +unsigned int zvol_request_sync = 0; +unsigned int zvol_prefetch_bytes = (128 * 1024); +unsigned long zvol_max_discard_blocks = 16384; +unsigned int zvol_threads = 8; + +taskq_t *zvol_taskq; + +/* Until we can find a solution that works for us too */ +extern list_t zvol_state_list; + +extern unsigned int spl_split_stack_below; +_Atomic unsigned int spl_lowest_zvol_stack_remaining = UINT_MAX; + +typedef struct zv_request { + zvol_state_t *zv_zv; + + union { + void (*zv_func)(zvol_state_t *, void *); + int (*zv_ifunc)(zvol_state_t *, void *); + }; + void *zv_arg; + int zv_rv; + + /* Used with zv_ifunc to wait for completion */ + kmutex_t zv_lock; + kcondvar_t zv_cv; + + taskq_ent_t zv_ent; +} zv_request_t; + +#define ZVOL_LOCK_HELD (1<<0) +#define ZVOL_LOCK_SPA (1<<1) +#define ZVOL_LOCK_SUSPEND (1<<2) + +static void +zvol_os_spawn_cb(void *param) +{ + zv_request_t *zvr = (zv_request_t *)param; + zvr->zv_func(zvr->zv_zv, zvr->zv_arg); + kmem_free(zvr, sizeof (zv_request_t)); +} + +static void +zvol_os_spawn(zvol_state_t *zv, + void (*func)(zvol_state_t *, void *), void *arg) +{ + zv_request_t *zvr; + zvr = kmem_alloc(sizeof (zv_request_t), KM_SLEEP); + zvr->zv_zv = zv; + zvr->zv_arg = arg; + zvr->zv_func = func; + + taskq_init_ent(&zvr->zv_ent); + + taskq_dispatch_ent(zvol_taskq, + zvol_os_spawn_cb, zvr, 0, &zvr->zv_ent); +} + +static void +zvol_os_spawn_wait_cb(void *param) +{ + zv_request_t *zvr = (zv_request_t *)param; + + zvr->zv_rv = zvr->zv_ifunc(zvr->zv_zv, zvr->zv_arg); + + zvr->zv_func = NULL; + mutex_enter(&zvr->zv_lock); + cv_broadcast(&zvr->zv_cv); + mutex_exit(&zvr->zv_lock); +} + +static int +zvol_os_spawn_wait(zvol_state_t *zv, + int (*func)(zvol_state_t *, void *), void *arg) +{ + int rv; + zv_request_t *zvr; + zvr = kmem_alloc(sizeof (zv_request_t), KM_SLEEP); + zvr->zv_zv = zv; + zvr->zv_arg = arg; + zvr->zv_ifunc = func; + + taskq_init_ent(&zvr->zv_ent); + cv_init(&zvr->zv_cv, NULL, CV_DEFAULT, NULL); + mutex_init(&zvr->zv_lock, NULL, MUTEX_DEFAULT, NULL); + + mutex_enter(&zvr->zv_lock); + + taskq_dispatch_ent(zvol_taskq, + zvol_os_spawn_wait_cb, zvr, 0, &zvr->zv_ent); + + /* Make sure it ran, by waiting */ + cv_wait(&zvr->zv_cv, &zvr->zv_lock); + mutex_exit(&zvr->zv_lock); + + mutex_destroy(&zvr->zv_lock); + cv_destroy(&zvr->zv_cv); + + VERIFY3P(zvr->zv_ifunc, ==, NULL); + rv = zvr->zv_rv; + kmem_free(zvr, sizeof (zv_request_t)); + return (rv); +} + +/* + * Given a path, return TRUE if path is a ZVOL. + */ +boolean_t +zvol_os_is_zvol(const char *device) +{ + if (device == NULL) + return (B_FALSE); + return (zvol_os_is_zvol_impl(device)); +} + +/* + * Make sure zv is still in the list (not freed) and if it is + * grab the locks in the correct order. We can not access "zv" + * until we know it exists in the list. (may be freed memory) + * Can we rely on list_link_active() instead of looping list? + * Return value: + * 0 : not found. No locks. + * ZVOL_LOCK_HELD : found and zv->zv_state_lock held + * |ZVOL_LOCK_SPA : spa_namespace_lock held + * |ZVOL_LOCK_SUSPEND : zv->zv_state_lock held + * call zvol_os_verify_lock_exit() to release + */ +static int +zvol_os_verify_and_lock(zvol_state_t *node, boolean_t takesuspend) +{ + zvol_state_t *zv; + int ret = ZVOL_LOCK_HELD; + +retry: + rw_enter(&zvol_state_lock, RW_READER); + for (zv = list_head(&zvol_state_list); zv != NULL; + zv = list_next(&zvol_state_list, zv)) { + + /* Until we find the node ... */ + if (zv != node) + continue; + + /* If this is to be first open, deal with spa_namespace */ + if (zv->zv_open_count == 0 && + !mutex_owned(&spa_namespace_lock)) { + /* + * We need to guarantee that the namespace lock is held + * to avoid spurious failures in zvol_first_open. + */ + ret |= ZVOL_LOCK_SPA; + if (!mutex_tryenter(&spa_namespace_lock)) { + rw_exit(&zvol_state_lock); + mutex_enter(&spa_namespace_lock); + /* Sadly, this will restart for loop */ + goto retry; + } + } + + mutex_enter(&zv->zv_state_lock); + + /* + * make sure zvol is not suspended during first open + * (hold zv_suspend_lock) and respect proper lock acquisition + * ordering - zv_suspend_lock before zv_state_lock + */ + if (zv->zv_open_count == 0 || takesuspend) { + ret |= ZVOL_LOCK_SUSPEND; + if (!rw_tryenter(&zv->zv_suspend_lock, RW_READER)) { + mutex_exit(&zv->zv_state_lock); + + /* If we hold spa_namespace, we can deadlock */ + if (ret & ZVOL_LOCK_SPA) { + rw_exit(&zvol_state_lock); + mutex_exit(&spa_namespace_lock); + ret &= ~ZVOL_LOCK_SPA; + dprintf("%s: spa_namespace loop\n", + __func__); + /* Let's not busy loop */ + delay(hz>>2); + goto retry; + } + rw_enter(&zv->zv_suspend_lock, RW_READER); + mutex_enter(&zv->zv_state_lock); + /* check to see if zv_suspend_lock is needed */ + if (zv->zv_open_count != 0) { + rw_exit(&zv->zv_suspend_lock); + ret &= ~ZVOL_LOCK_SUSPEND; + } + } + } + rw_exit(&zvol_state_lock); + + /* Success */ + return (ret); + + } /* for */ + + /* Not found */ + rw_exit(&zvol_state_lock); + + /* It's possible we grabbed spa, but then didn't re-find zv */ + if (ret & ZVOL_LOCK_SPA) + mutex_exit(&spa_namespace_lock); + return (0); +} + +static void +zvol_os_verify_lock_exit(zvol_state_t *zv, int locks) +{ + if (locks & ZVOL_LOCK_SPA) + mutex_exit(&spa_namespace_lock); + mutex_exit(&zv->zv_state_lock); + if (locks & ZVOL_LOCK_SUSPEND) + rw_exit(&zv->zv_suspend_lock); +} + +static void +zvol_os_register_device_cb(zvol_state_t *zv, void *param) +{ + int locks; + + if ((locks = zvol_os_verify_and_lock(zv, zv->zv_open_count == 0)) == 0) + return; + + zvol_os_verify_lock_exit(zv, locks); + + /* This is a bit racy? */ + zvolRegisterDevice(zv); + +} + +int +zvol_os_write(dev_t dev, struct uio *uio, int p) +{ + return (ENOTSUP); +} + +int +zvol_os_read(dev_t dev, struct uio *uio, int p) +{ + return (ENOTSUP); +} + +static int +zvol_os_write_zv_impl(zvol_state_t *zv, void *param) +{ + zfs_uio_t *uio = (zfs_uio_t *)param; + int error = 0; + + if (zv == NULL) + return (ENXIO); + + rw_enter(&zv->zv_suspend_lock, RW_READER); + + /* Some requests are just for flush and nothing else. */ + if (zfs_uio_resid(uio) == 0) { + rw_exit(&zv->zv_suspend_lock); + return (0); + } + + ssize_t start_resid = zfs_uio_resid(uio); + boolean_t sync = (zv->zv_objset->os_sync == ZFS_SYNC_ALWAYS); + + /* + * Open a ZIL if this is the first time we have written to this + * zvol. We protect zv->zv_zilog with zv_suspend_lock rather + * than zv_state_lock so that we don't need to acquire an + * additional lock in this path. + */ + if (zv->zv_zilog == NULL) { + rw_exit(&zv->zv_suspend_lock); + rw_enter(&zv->zv_suspend_lock, RW_WRITER); + if (zv->zv_zilog == NULL) { + zv->zv_zilog = zil_open(zv->zv_objset, + zvol_get_data, NULL); + zv->zv_flags |= ZVOL_WRITTEN_TO; + } + rw_downgrade(&zv->zv_suspend_lock); + } + + zfs_locked_range_t *lr = zfs_rangelock_enter(&zv->zv_rangelock, + zfs_uio_offset(uio), zfs_uio_resid(uio), RL_WRITER); + + uint64_t volsize = zv->zv_volsize; + while (zfs_uio_resid(uio) > 0 && zfs_uio_offset(uio) < volsize) { + uint64_t bytes = MIN(zfs_uio_resid(uio), DMU_MAX_ACCESS >> 1); + uint64_t off = zfs_uio_offset(uio); + dmu_tx_t *tx = dmu_tx_create(zv->zv_objset); + + if (bytes > volsize - off) /* don't write past the end */ + bytes = volsize - off; + + dmu_tx_hold_write_by_dnode(tx, zv->zv_dn, off, bytes); + + /* This will only fail for ENOSPC */ + error = dmu_tx_assign(tx, TXG_WAIT); + if (error) { + dmu_tx_abort(tx); + break; + } + error = dmu_write_uio_dnode(zv->zv_dn, uio, bytes, tx); + if (error == 0) { + zvol_log_write(zv, tx, off, bytes, sync); + } + dmu_tx_commit(tx); + + if (error) + break; + } + zfs_rangelock_exit(lr); + + int64_t nwritten = start_resid - zfs_uio_resid(uio); + dataset_kstats_update_write_kstats(&zv->zv_kstat, nwritten); + + if (sync) + zil_commit(zv->zv_zilog, ZVOL_OBJ); + + rw_exit(&zv->zv_suspend_lock); + + return (error); +} + +int +zvol_os_write_zv(zvol_state_t *zv, zfs_uio_t *uio) +{ + /* Start IO - possibly as taskq */ + const vm_offset_t r = OSKernelStackRemaining(); + + if (r < spl_lowest_zvol_stack_remaining) + spl_lowest_zvol_stack_remaining = r; + + if (zfs_uio_resid(uio) != 0 && + r < spl_split_stack_below) + return (zvol_os_spawn_wait(zv, zvol_os_write_zv_impl, uio)); + + return (zvol_os_write_zv_impl(zv, uio)); +} + +int +zvol_os_read_zv_impl(zvol_state_t *zv, void *param) +{ + zfs_uio_t *uio = (zfs_uio_t *)param; + int error = 0; + + ASSERT3P(zv, !=, NULL); + ASSERT3U(zv->zv_open_count, >, 0); + + ssize_t start_resid = zfs_uio_resid(uio); + + rw_enter(&zv->zv_suspend_lock, RW_READER); + + zfs_locked_range_t *lr = zfs_rangelock_enter(&zv->zv_rangelock, + zfs_uio_offset(uio), zfs_uio_resid(uio), RL_READER); + + uint64_t volsize = zv->zv_volsize; + while (zfs_uio_resid(uio) > 0 && zfs_uio_offset(uio) < volsize) { + uint64_t bytes = MIN(zfs_uio_resid(uio), DMU_MAX_ACCESS >> 1); + + /* don't read past the end */ + if (bytes > volsize - zfs_uio_offset(uio)) + bytes = volsize - zfs_uio_offset(uio); + + error = dmu_read_uio_dnode(zv->zv_dn, uio, bytes); + if (error) { + /* convert checksum errors into IO errors */ + if (error == ECKSUM) + error = SET_ERROR(EIO); + break; + } + } + zfs_rangelock_exit(lr); + + int64_t nread = start_resid - zfs_uio_resid(uio); + dataset_kstats_update_read_kstats(&zv->zv_kstat, nread); + rw_exit(&zv->zv_suspend_lock); + + + return (error); +} + +int +zvol_os_read_zv(zvol_state_t *zv, zfs_uio_t *uio) +{ + /* Start IO - possibly as taskq */ + const vm_offset_t r = OSKernelStackRemaining(); + + if (r < spl_lowest_zvol_stack_remaining) + spl_lowest_zvol_stack_remaining = r; + + if (zfs_uio_resid(uio) != 0 && + r < spl_split_stack_below) + return (zvol_os_spawn_wait(zv, zvol_os_read_zv_impl, uio)); + + return (zvol_os_read_zv_impl(zv, uio)); +} + +int +zvol_os_unmap(zvol_state_t *zv, uint64_t off, uint64_t bytes) +{ + zfs_locked_range_t *lr = NULL; + dmu_tx_t *tx = NULL; + int error = 0; + uint64_t end = off + bytes; + + if (zv == NULL) + return (ENXIO); + + /* + * XNU's wipefs_wipe() will issue one giant unmap for the entire + * device; + * zfs create -V 8g BOOM/vol + * zvolIO doDiscard calling zvol_unmap with offset, bytes (0, 858992) + * Which will both take too long, and is uneccessary. We will ignore + * any unmaps deemed "too large". + */ + if ((off == 0ULL) && + (zv->zv_volsize > (1ULL << 24)) && /* 16Mb slop */ + (bytes >= (zv->zv_volsize - (1ULL << 24)))) + return (0); + + rw_enter(&zv->zv_suspend_lock, RW_READER); + + /* + * Open a ZIL if this is the first time we have written to this + * zvol. We protect zv->zv_zilog with zv_suspend_lock rather + * than zv_state_lock so that we don't need to acquire an + * additional lock in this path. + */ + if (zv->zv_zilog == NULL) { + rw_exit(&zv->zv_suspend_lock); + rw_enter(&zv->zv_suspend_lock, RW_WRITER); + if (zv->zv_zilog == NULL) { + zv->zv_zilog = zil_open(zv->zv_objset, + zvol_get_data, NULL); + zv->zv_flags |= ZVOL_WRITTEN_TO; + } + rw_downgrade(&zv->zv_suspend_lock); + } + + off = P2ROUNDUP(off, zv->zv_volblocksize); + end = P2ALIGN(end, zv->zv_volblocksize); + + if (end > zv->zv_volsize) /* don't write past the end */ + end = zv->zv_volsize; + + if (off >= end) { + /* Return success- caller does not need to know */ + goto out; + } + + bytes = end - off; + lr = zfs_rangelock_enter(&zv->zv_rangelock, off, bytes, RL_WRITER); + + tx = dmu_tx_create(zv->zv_objset); + + dmu_tx_mark_netfree(tx); + + error = dmu_tx_assign(tx, TXG_WAIT); + + if (error) { + dmu_tx_abort(tx); + } else { + + zvol_log_truncate(zv, tx, off, bytes, B_TRUE); + + dmu_tx_commit(tx); + + error = dmu_free_long_range(zv->zv_objset, + ZVOL_OBJ, off, bytes); + } + + zfs_rangelock_exit(lr); + + if (error == 0) { + /* + * If the 'sync' property is set to 'always' then + * treat this as a synchronous operation + * (i.e. commit to zil). + */ + if (zv->zv_objset->os_sync == ZFS_SYNC_ALWAYS) { + zil_commit(zv->zv_zilog, ZVOL_OBJ); + } + } + +out: + rw_exit(&zv->zv_suspend_lock); + return (error); +} + +int +zvol_os_update_volsize(zvol_state_t *zv, uint64_t volsize) +{ + zv->zv_volsize = volsize; + return (0); +} + +static void +zvol_os_clear_private_cb(zvol_state_t *zv, void *param) +{ + zvolRemoveDeviceTerminate(param); +} + +void +zvol_os_clear_private(zvol_state_t *zv) +{ + void *term; + + dprintf("%s\n", __func__); + + /* We can do all removal work, except call terminate. */ + term = zvolRemoveDevice(zv); + if (term == NULL) + return; + + zvol_remove_symlink(zv); + + zv->zv_zso->zvo_iokitdev = NULL; + + /* Call terminate in the background */ + zvol_os_spawn(zv, zvol_os_clear_private_cb, term); + +} + +/* + * Find a zvol_state_t given the full major+minor dev_t. If found, + * return with zv_state_lock taken, otherwise, return (NULL) without + * taking zv_state_lock. + */ +static zvol_state_t * +zvol_os_find_by_dev(dev_t dev) +{ + zvol_state_t *zv; + + rw_enter(&zvol_state_lock, RW_READER); + for (zv = list_head(&zvol_state_list); zv != NULL; + zv = list_next(&zvol_state_list, zv)) { + mutex_enter(&zv->zv_state_lock); + if (zv->zv_zso->zvo_dev == dev) { + rw_exit(&zvol_state_lock); + return (zv); + } + mutex_exit(&zv->zv_state_lock); + } + rw_exit(&zvol_state_lock); + + return (NULL); +} + +void +zvol_os_validate_dev(zvol_state_t *zv) +{ +} + +/* + * Allocate memory for a new zvol_state_t and setup the required + * request queue and generic disk structures for the block device. + */ +static zvol_state_t * +zvol_os_alloc(dev_t dev, const char *name) +{ + zvol_state_t *zv; + struct zvol_state_os *zso; + uint64_t volmode; + + if (dsl_prop_get_integer(name, "volmode", &volmode, NULL) != 0) + return (NULL); + + if (volmode == ZFS_VOLMODE_DEFAULT) + volmode = zvol_volmode; + + if (volmode == ZFS_VOLMODE_NONE) + return (NULL); + + zv = kmem_zalloc(sizeof (zvol_state_t), KM_SLEEP); + zso = kmem_zalloc(sizeof (struct zvol_state_os), KM_SLEEP); + zv->zv_zso = zso; + + list_link_init(&zv->zv_next); + mutex_init(&zv->zv_state_lock, NULL, MUTEX_DEFAULT, NULL); + + zv->zv_open_count = 0; + strlcpy(zv->zv_name, name, MAXNAMELEN); + + zfs_rangelock_init(&zv->zv_rangelock, NULL, NULL); + rw_init(&zv->zv_suspend_lock, NULL, RW_DEFAULT, NULL); + + return (zv); +#if 0 +out_kmem: + kmem_free(zso, sizeof (struct zvol_state_os)); + kmem_free(zv, sizeof (zvol_state_t)); + return (NULL); +#endif +} + +/* + * Cleanup then free a zvol_state_t which was created by zvol_alloc(). + * At this time, the structure is not opened by anyone, is taken off + * the zvol_state_list, and has its private data set to NULL. + * The zvol_state_lock is dropped. + * + */ +void +zvol_os_free(zvol_state_t *zv) +{ + ASSERT(!RW_LOCK_HELD(&zv->zv_suspend_lock)); + ASSERT(!MUTEX_HELD(&zv->zv_state_lock)); + ASSERT(zv->zv_open_count == 0); + + rw_destroy(&zv->zv_suspend_lock); + zfs_rangelock_fini(&zv->zv_rangelock); + + mutex_destroy(&zv->zv_state_lock); + dataset_kstats_destroy(&zv->zv_kstat); + + kmem_free(zv->zv_zso, sizeof (struct zvol_state_os)); + kmem_free(zv, sizeof (zvol_state_t)); +} + +void +zvol_wait_close(zvol_state_t *zv) +{ +} + +/* + * Create a block device minor node and setup the linkage between it + * and the specified volume. Once this function returns the block + * device is live and ready for use. + */ +int +zvol_os_create_minor(const char *name) +{ + zvol_state_t *zv; + objset_t *os; + dmu_object_info_t *doi; + uint64_t volsize; + unsigned minor = 0; + int error = 0; + uint64_t hash = zvol_name_hash(name); + + dprintf("%s\n", __func__); + + if (zvol_inhibit_dev) + return (0); + + // minor? + zv = zvol_find_by_name_hash(name, hash, RW_NONE); + if (zv) { + ASSERT(MUTEX_HELD(&zv->zv_state_lock)); + mutex_exit(&zv->zv_state_lock); + return (SET_ERROR(EEXIST)); + } + + doi = kmem_alloc(sizeof (dmu_object_info_t), KM_SLEEP); + + error = dmu_objset_own(name, DMU_OST_ZVOL, B_TRUE, B_TRUE, FTAG, &os); + if (error) + goto out_doi; + + error = dmu_object_info(os, ZVOL_OBJ, doi); + if (error) + goto out_dmu_objset_disown; + + error = zap_lookup(os, ZVOL_ZAP_OBJ, "size", 8, 1, &volsize); + if (error) + goto out_dmu_objset_disown; + + zv = zvol_os_alloc(makedevice(zvol_major, minor), name); + if (zv == NULL) { + error = SET_ERROR(EAGAIN); + goto out_dmu_objset_disown; + } + zv->zv_hash = hash; + + if (dmu_objset_is_snapshot(os)) + zv->zv_flags |= ZVOL_RDONLY; + + zv->zv_volblocksize = doi->doi_data_block_size; + zv->zv_volsize = volsize; + zv->zv_objset = os; + + // set_capacity(zv->zv_zso->zvo_disk, zv->zv_volsize >> 9); + ASSERT3P(zv->zv_zilog, ==, NULL); + zv->zv_zilog = zil_open(os, zvol_get_data, NULL); + if (spa_writeable(dmu_objset_spa(os))) { + if (zil_replay_disable) + zil_destroy(zv->zv_zilog, B_FALSE); + else + zil_replay(os, zv, zvol_replay_vector); + } + zil_close(zv->zv_zilog); + zv->zv_zilog = NULL; + + dataset_kstats_create(&zv->zv_kstat, zv->zv_objset); + + /* Create the IOKit zvol while owned */ + if ((error = zvolCreateNewDevice(zv)) != 0) { + dprintf("%s zvolCreateNewDevice error %d\n", + __func__, error); + } + + zv->zv_objset = NULL; +out_dmu_objset_disown: + dmu_objset_disown(os, B_TRUE, FTAG); +out_doi: + kmem_free(doi, sizeof (dmu_object_info_t)); + + if (error == 0) { + rw_enter(&zvol_state_lock, RW_WRITER); + zvol_insert(zv); + rw_exit(&zvol_state_lock); + + /* Register (async) IOKit zvol after disown and unlock */ + /* The callback with release the mutex */ + zvol_os_spawn(zv, zvol_os_register_device_cb, NULL); + + } else { + + } + + dprintf("%s complete\n", __func__); + return (error); +} + + +static void zvol_os_rename_device_cb(zvol_state_t *zv, void *param) +{ + int locks; + + if ((locks = zvol_os_verify_and_lock(zv, zv->zv_open_count == 0)) == 0) + return; + + zvol_add_symlink(zv, zv->zv_zso->zvo_bsdname + 1, + zv->zv_zso->zvo_bsdname); + + zvol_os_verify_lock_exit(zv, locks); + zvolRenameDevice(zv); +} + +void +zvol_os_rename_minor(zvol_state_t *zv, const char *newname) +{ + // int readonly = get_disk_ro(zv->zv_zso->zvo_disk); + + ASSERT(RW_LOCK_HELD(&zvol_state_lock)); + ASSERT(MUTEX_HELD(&zv->zv_state_lock)); + + zvol_remove_symlink(zv); + + strlcpy(zv->zv_name, newname, sizeof (zv->zv_name)); + + /* move to new hashtable entry */ + zv->zv_hash = zvol_name_hash(zv->zv_name); + hlist_del(&zv->zv_hlink); + hlist_add_head(&zv->zv_hlink, ZVOL_HT_HEAD(zv->zv_hash)); + + zvol_os_spawn(zv, zvol_os_rename_device_cb, NULL); + + /* + * The block device's read-only state is briefly changed causing + * a KOBJ_CHANGE uevent to be issued. This ensures udev detects + * the name change and fixes the symlinks. This does not change + * ZVOL_RDONLY in zv->zv_flags so the actual read-only state never + * changes. This would normally be done using kobject_uevent() but + * that is a GPL-only symbol which is why we need this workaround. + */ + // set_disk_ro(zv->zv_zso->zvo_disk, !readonly); + // set_disk_ro(zv->zv_zso->zvo_disk, readonly); +} + +void +zvol_os_set_disk_ro(zvol_state_t *zv, int flags) +{ + // set_disk_ro(zv->zv_zso->zvo_disk, flags); +} + +void +zvol_os_set_capacity(zvol_state_t *zv, uint64_t capacity) +{ + // set_capacity(zv->zv_zso->zvo_disk, capacity); +} + +int +zvol_os_open_zv(zvol_state_t *zv, int flag, int otyp, struct proc *p) +{ + int error = 0; + int locks; + + /* + * make sure zvol is not suspended during first open + * (hold zv_suspend_lock) and respect proper lock acquisition + * ordering - zv_suspend_lock before zv_state_lock + */ + if ((locks = zvol_os_verify_and_lock(zv, zv->zv_open_count == 0)) + == 0) { + return (SET_ERROR(ENOENT)); + } + + ASSERT(MUTEX_HELD(&zv->zv_state_lock)); + ASSERT(zv->zv_open_count != 0 || RW_READ_HELD(&zv->zv_suspend_lock)); + + /* + * We often race opens due to DiskArb. So if spa_namespace_lock is + * already held, potentially a zvol_first_open() is already in progress + */ + if (zv->zv_open_count == 0) { + error = zvol_first_open(zv, !(flag & FWRITE)); + if (error) + goto out_mutex; + } + + if ((flag & FWRITE) && (zv->zv_flags & ZVOL_RDONLY)) { + error = EROFS; + goto out_open_count; + } + + zv->zv_open_count++; + + zvol_os_verify_lock_exit(zv, locks); + return (0); + +out_open_count: + if (zv->zv_open_count == 0) + zvol_last_close(zv); + +out_mutex: + zvol_os_verify_lock_exit(zv, locks); + + if (error == EINTR) { + error = ERESTART; + schedule(); + } + return (SET_ERROR(error)); +} + +int +zvol_os_open(dev_t devp, int flag, int otyp, struct proc *p) +{ + zvol_state_t *zv; + int error = 0; + + if (!getminor(devp)) + return (0); + + zv = zvol_os_find_by_dev(devp); + if (zv == NULL) { + return (SET_ERROR(ENXIO)); + } + + error = zvol_os_open_zv(zv, flag, otyp, p); + + mutex_exit(&zv->zv_state_lock); + return (SET_ERROR(error)); +} + +int +zvol_os_close_zv(zvol_state_t *zv, int flag, int otyp, struct proc *p) +{ + int locks; + + if ((locks = zvol_os_verify_and_lock(zv, TRUE)) == 0) + return (SET_ERROR(ENOENT)); + + ASSERT(MUTEX_HELD(&zv->zv_state_lock)); + ASSERT(zv->zv_open_count != 1 || RW_READ_HELD(&zv->zv_suspend_lock)); + + zv->zv_open_count--; + + if (zv->zv_open_count == 0) + zvol_last_close(zv); + + zvol_os_verify_lock_exit(zv, locks); + + return (0); +} + +int +zvol_os_close(dev_t dev, int flag, int otyp, struct proc *p) +{ + zvol_state_t *zv; + int error = 0; + + if (!getminor(dev)) + return (0); + + zv = zvol_os_find_by_dev(dev); + if (zv == NULL) { + return (SET_ERROR(-ENXIO)); + } + + error = zvol_os_close_zv(zv, flag, otyp, p); + + mutex_exit(&zv->zv_state_lock); + return (error); +} + +void +zvol_os_strategy(struct buf *bp) +{ + +} + +int +zvol_os_get_volume_blocksize(dev_t dev) +{ + /* XNU can only handle two sizes. */ + return (DEV_BSIZE); +} + +int +zvol_os_ioctl(dev_t dev, unsigned long cmd, caddr_t data, int isblk, + cred_t *cr, int *rvalp) +{ + int error = 0; + u_int32_t *f; + u_int64_t *o; + zvol_state_t *zv = NULL; + + dprintf("%s\n", __func__); + + if (!getminor(dev)) + return (ENXIO); + + zv = zvol_os_find_by_dev(dev); + + if (zv == NULL) { + dprintf("zv is NULL\n"); + return (ENXIO); + } + + f = (u_int32_t *)data; + o = (u_int64_t *)data; + + switch (cmd) { + + case DKIOCGETMAXBLOCKCOUNTREAD: + dprintf("DKIOCGETMAXBLOCKCOUNTREAD\n"); + *o = 32; + break; + + case DKIOCGETMAXBLOCKCOUNTWRITE: + dprintf("DKIOCGETMAXBLOCKCOUNTWRITE\n"); + *o = 32; + break; + case DKIOCGETMAXSEGMENTCOUNTREAD: + dprintf("DKIOCGETMAXSEGMENTCOUNTREAD\n"); + *o = 32; + break; + + case DKIOCGETMAXSEGMENTCOUNTWRITE: + dprintf("DKIOCGETMAXSEGMENTCOUNTWRITE\n"); + *o = 32; + break; + + case DKIOCGETBLOCKSIZE: + dprintf("DKIOCGETBLOCKSIZE: %llu\n", + zv->zv_volblocksize); + *f = zv->zv_volblocksize; + break; + + case DKIOCSETBLOCKSIZE: + dprintf("DKIOCSETBLOCKSIZE %u\n", (uint32_t)*f); + + if (!isblk) { + /* We can only do this for a block device */ + error = ENODEV; + break; + } + + if (zvol_check_volblocksize(zv->zv_name, + (uint64_t)*f)) { + error = EINVAL; + break; + } + + /* set the new block size */ + zv->zv_volblocksize = (uint64_t)*f; + dprintf("setblocksize changed: %llu\n", + zv->zv_volblocksize); + break; + + case DKIOCISWRITABLE: + dprintf("DKIOCISWRITABLE\n"); + if (zv && (zv->zv_flags & ZVOL_RDONLY)) + *f = 0; + else + *f = 1; + break; +#ifdef DKIOCGETBLOCKCOUNT32 + case DKIOCGETBLOCKCOUNT32: + dprintf("DKIOCGETBLOCKCOUNT32: %lu\n", + (uint32_t)zv->zv_volsize / zv->zv_volblocksize); + *f = (uint32_t)zv->zv_volsize / zv->zv_volblocksize; + break; +#endif + + case DKIOCGETBLOCKCOUNT: + dprintf("DKIOCGETBLOCKCOUNT: %llu\n", + zv->zv_volsize / zv->zv_volblocksize); + *o = (uint64_t)zv->zv_volsize / zv->zv_volblocksize; + break; + + case DKIOCGETBASE: + dprintf("DKIOCGETBASE\n"); + /* + * What offset should we say? + * 0 is ok for FAT but to HFS + */ + *o = zv->zv_volblocksize * 0; + break; + + case DKIOCGETPHYSICALBLOCKSIZE: + dprintf("DKIOCGETPHYSICALBLOCKSIZE\n"); + *f = zv->zv_volblocksize; + break; + +#ifdef DKIOCGETTHROTTLEMASK + case DKIOCGETTHROTTLEMASK: + dprintf("DKIOCGETTHROTTLEMASK\n"); + *o = 0; + break; +#endif + + case DKIOCGETMAXBYTECOUNTREAD: + *o = SPA_MAXBLOCKSIZE; + break; + + case DKIOCGETMAXBYTECOUNTWRITE: + *o = SPA_MAXBLOCKSIZE; + break; +#ifdef DKIOCUNMAP + case DKIOCUNMAP: + dprintf("DKIOCUNMAP\n"); + *f = 1; + break; +#endif + + case DKIOCGETFEATURES: + *f = 0; + break; + +#ifdef DKIOCISSOLIDSTATE + case DKIOCISSOLIDSTATE: + dprintf("DKIOCISSOLIDSTATE\n"); + *f = 0; + break; +#endif + + case DKIOCISVIRTUAL: + *f = 1; + break; + + case DKIOCGETMAXSEGMENTBYTECOUNTREAD: + *o = 32 * zv->zv_volblocksize; + break; + + case DKIOCGETMAXSEGMENTBYTECOUNTWRITE: + *o = 32 * zv->zv_volblocksize; + break; + + case DKIOCSYNCHRONIZECACHE: + dprintf("DKIOCSYNCHRONIZECACHE\n"); + break; + + default: + dprintf("unknown ioctl: ENOTTY\n"); + error = ENOTTY; + break; + } + + mutex_exit(&zv->zv_state_lock); + + return (SET_ERROR(error)); +} + +int +zvol_init(void) +{ + int threads = MIN(MAX(zvol_threads, 1), 1024); + + zvol_taskq = taskq_create(ZVOL_DRIVER, threads, maxclsyspri-4, + threads * 2, INT_MAX, TASKQ_PREPOPULATE); + if (zvol_taskq == NULL) { + return (-ENOMEM); + } + + zvol_init_impl(); + return (0); +} + +void +zvol_fini(void) +{ + zvol_fini_impl(); + taskq_destroy(zvol_taskq); +} + + + +/* + * Due to OS X limitations in /dev, we create a symlink for "/dev/zvol" to + * "/var/run/zfs" (if we can) and for each pool, create the traditional + * ZFS Volume symlinks. + * + * Ie, for ZVOL $POOL/$VOLUME + * BSDName /dev/disk2 /dev/rdisk2 + * /dev/zvol -> /var/run/zfs + * /var/run/zfs/zvol/dsk/$POOL/$VOLUME -> /dev/disk2 + * /var/run/zfs/zvol/rdsk/$POOL/$VOLUME -> /dev/rdisk2 + * + * Note, we do not create symlinks for the partitioned slices. + * + */ + +void +zvol_add_symlink(zvol_state_t *zv, const char *bsd_disk, const char *bsd_rdisk) +{ + zfs_ereport_zvol_post(FM_RESOURCE_ZVOL_CREATE_SYMLINK, + zv->zv_name, bsd_disk, bsd_rdisk); +} + + +void +zvol_remove_symlink(zvol_state_t *zv) +{ + if (!zv || !zv->zv_name[0]) + return; + + zfs_ereport_zvol_post(FM_RESOURCE_ZVOL_REMOVE_SYMLINK, + zv->zv_name, &zv->zv_zso->zvo_bsdname[1], + zv->zv_zso->zvo_bsdname); +} diff --git a/scripts/load_macos.sh b/scripts/load_macos.sh new file mode 100755 index 000000000000..36d2c9bc5338 --- /dev/null +++ b/scripts/load_macos.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# +# Expected to be run from the root of the source tree, as root; +# ./scripts/load_macos.sh +# +# Copies compiled zfs.kext to /tmp/ and prepares the requirements +# for load. +# + +rsync -ar module/os/macos/zfs.kext/ /tmp/zfs.kext/ + +chown -R root:wheel /tmp/zfs.kext + +kextload -v /tmp/zfs.kext || kextutil /tmp/zfs.kext + +# log stream --source --predicate 'sender == "zfs"' --style compact diff --git a/scripts/pkg_macos.sh b/scripts/pkg_macos.sh new file mode 100755 index 000000000000..6b5e761678fa --- /dev/null +++ b/scripts/pkg_macos.sh @@ -0,0 +1,460 @@ +#!/usr/bin/env bash + +# +# 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) 2020 by ilovezfs +# Copyright (c) 2020 by Jorgen Lundman + +# +# A script to produce an installable .pkg for macOS. +# +# Environment variables: +# +# $PKG_CODESIGN_KEY: Set to name of certificate for codesigning, +# as named in Keychain. For example; +# "Developer ID Application: Joergen Lundman (735AM5QEU3)" +# +# $PKG_NOTARIZE_KEY: Set to the notarize key you can create on +# Apple developer pages. For example; +# "awvz-fqoi-cxag-tymn" +# +# $PKG_INSTALL_KEY: Set to the name of certificate for installer +# signing, as named in Keychain, For example; +# "Developer ID Installer: Joergen Lundman (735AM5QEU3)" +# + +BASE_DIR=$(dirname "$0") +SCRIPT_COMMON=common.sh +if [ -f "${BASE_DIR}/${SCRIPT_COMMON}" ]; then + . "${BASE_DIR}/${SCRIPT_COMMON}" +else + echo "Missing helper script ${SCRIPT_COMMON}" && exit 1 +fi + +WORKNAME="macos-root" +WORKDIR="/private/var/tmp/${WORKNAME}" + +# If there are two dots "10.15.4", eat it +OS=$(sw_vers | awk '{if ($1 == "ProductVersion:") print $2;}') +OS=$(echo "$OS" | awk -F . '{if ($1 == 10) print $1"."$2; else print $1}') +RC=$(grep Release: META | awk '$2 ~ /rc/ { print $2}') + +function usage +{ + echo "$0: Create installable pkg for macOS" + echo "" + echo " Options:" + echo " -l skip make install step, using a folder of a previous run" + echo " -L copy and fix external libraries" + exit +} + +while getopts "hlL" opt; do + case $opt in + l ) skip_install=1 ;; + L ) fix_libraries=1 ;; + h ) usage + exit 2 + ;; + * ) echo "Invalid argument: -$OPTARG"; + usage + exit 1 + esac +done + +case ${OS} in + 10.8|10.9|10.10|10.11|10.12|10.13) + unset PKG_NOTARIZE_KEY + echo "No notarize for OS $OS" + ;; +esac + +# pass remaining arguments on +shift $((OPTIND - 1)) + + +echo "" +echo "Creating pkg for macOS installation... " +echo "" + +if [ -z "$PKG_CODESIGN_KEY" ]; then + echo "\$PKG_CODESIGN_KEY not set to certificate name, skipping codesign." +fi + +if [ -z "$PKG_NOTARIZE_KEY" ]; then + echo "\$PKG_NOTARIZE_KEY not set to pass-key, skipping notarize." +fi + +if [ -z "$PKG_INSTALL_KEY" ]; then + echo "\$PKG_INSTALL_KEY not set to certificate name, skipping pkg install signing." +fi + +version=$(awk < META '{if ($1 == "Version:") print $2;}') +prefix="/usr/local" +if [ -f "${BASE_DIR}/../config.status" ]; then + prefix=$(grep 'S\["prefix"\]' "${BASE_DIR}/../config.status" | tr '=' ' ' | awk '{print $2;}' | tr -d '"') +fi + +echo "Version is $version" +echo "Prefix set to $prefix" +echo "RC, if set: $RC" +echo "" + +sleep 3 + +if [ -z $skip_install ]; then + rm -rf ${WORKDIR} || true + + mkdir -p ${WORKDIR} || fail "Unable to create $WORKDIR" + + echo "Running \"make install DESTDIR=$WORKDIR\" ... " + make install DESTDIR="${WORKDIR}" || fail "Make install failed." +fi + +echo "" +echo "make install completed." +echo "" + +# Make an attempt to figure out where "zpool" is installed to, +# repo default is /usr/local/sbin/ but macOS prefers /usr/local/bin +pushd $WORKDIR || fail "failed to create workdir" +file=$(find usr -type f -name zpool) +bindir=$(dirname "$file") +popd || fail "failed to popd" + +codesign_dirs=" +${WORKDIR}/Library/Extensions/zfs.kext/ +" +codesign_files=" +${WORKDIR}/${bindir}/zdb +${WORKDIR}/${bindir}/zed +${WORKDIR}/${bindir}/zfs +${WORKDIR}/${bindir}/zhack +${WORKDIR}/${bindir}/zinject +${WORKDIR}/${bindir}/zpool +${WORKDIR}/${bindir}/zstream +${WORKDIR}/${bindir}/ztest +${WORKDIR}/${bindir}/zfs_ids_to_path +${WORKDIR}/${bindir}/InvariantDisks +${WORKDIR}/${bindir}/zfs_util +${WORKDIR}/${bindir}/zconfigd +${WORKDIR}/${bindir}/zsysctl +${WORKDIR}/${bindir}/mount_zfs +${WORKDIR}/${prefix}/libexec/zfs/zpool_influxdb +${WORKDIR}/${prefix}/lib/libnvpair.a +${WORKDIR}/${prefix}/lib/libuutil.a +${WORKDIR}/${prefix}/lib/libzfs.a +${WORKDIR}/${prefix}/lib/libzpool.a +${WORKDIR}/${prefix}/lib/libzfs_core.a +${WORKDIR}/${prefix}/lib/librt.so.1 +${WORKDIR}/${prefix}/lib/libnvpair.?.dylib +${WORKDIR}/${prefix}/lib/libuutil.?.dylib +${WORKDIR}/${prefix}/lib/libzfs.?.dylib +${WORKDIR}/${prefix}/lib/libzpool.?.dylib +${WORKDIR}/${prefix}/lib/libzfs_core.?.dylib +${WORKDIR}/${prefix}/lib/libzfsbootenv.?.dylib +${WORKDIR}/Library/Filesystems/zfs.fs/Contents/Resources/zfs_util +${WORKDIR}/Library/Filesystems/zfs.fs/Contents/Resources/mount_zfs +" + +codesign_all="$codesign_files $codesign_dirs" + +function fail +{ + echo "$@" + exit 1 +} + +function do_unlock +{ + cert=$1 + + echo "Looking for certificate ${cert} ..." + + keychain=$(security find-certificate -c "${cert}" | awk '{if ($1 == "keychain:") print $2;}'|tr -d '"') + + echo "Unlocking keychain $keychain ..." + security unlock-keychain "${keychain}" || fail "Unable to unlock keychain" + + retval=${keychain} +} + +function do_codesign +{ + failures=0 + + echo "" + + do_unlock "${PKG_CODESIGN_KEY}" + + echo "OS $OS" + if [ x"$OS" == x"10.12" ] || + [ x"$OS" == x"10.11" ] || + [ x"$OS" == x"10.10" ] || + [ x"$OS" == x"10.9" ]; then + extra="" + else + extra="--options runtime" + fi + + for file in ${codesign_all} + do + echo "$file" + codesign --timestamp ${extra} -fvs "${PKG_CODESIGN_KEY}" "${file}" || failures=$((failures+1)) + done + + if [ "$failures" -ne 0 ]; then + echo "codesign phase had $failures issues ..." + exit 1 + fi + + # Look into where mount_zfs umount_zfs fsck_zfs went .. + # also: InvariantDisks +} + +# Delete everything in a directory, keeping a list +# of files. +# argument 1: directory path "/tmp/dir" +# argument 2: keep list "(item1|item2|tem3)" +function delete_and_keep +{ + # Keep only commands that apply to macOS + directory="$1" + keep="$2" + + pushd "${directory}" || fail "Unable to cd to ${directory}" + + shopt -s extglob + + for file in * + do + # shellcheck disable=SC2254 + case "${file}" in + !${keep}) + echo "Deleting non macOS file \"$file\"" + rm -f "${file}" + ;; + *) + # echo "Not deleting $file" + esac + done + shopt -u extglob + popd || fail "failed to popd" +} + +function do_prune +{ + + delete_and_keep "${WORKDIR}/${bindir}/" "(zfs|zpool|zdb|zed|zhack|zinject|zstream|zstreamdump|ztest|InvariantDisks|zfs_util|zconfigd|arc_summary|arcstat|dbufstat|fsck.zfs|zilstat|zfs_ids_to_path|zpool_influxdb|zsysctl|mount_zfs)" + + pushd "${WORKDIR}" || fail "Unable to cd to ${WORKDIR}" + + # Using relative paths here for safety + rm -rf \ +"./${prefix}/share/zfs-macos/runfiles" \ +"./${prefix}/share/zfs-macos/test-runner" \ +"./${prefix}/share/zfs-macos/zfs-tests" \ +"./${prefix}/src" + + popd || fail "failed to popd" +} + +# Find any libraries we link with outside of zfs (and system). +# For example, /usr/local/opt/openssl/lib/libssl.1.1.0.dylib +# and copy them into /usr/local/zfs/lib - then update the paths to +# those libraries in all cmds and libraries. +# To do, handle "/usr/local/zfs/" from ./configure arguments (--prefix) +function copy_fix_libraries +{ + echo "Fixing external libraries ... " + fixlib=$(otool -L ${codesign_files} | egrep '/usr/local/opt/|/opt/local/lib/' |awk '{print $1;}' | grep '\.dylib$' | sort | uniq) + + # Add the libs into codesign list - both to be codesigned, and updated + # between themselves (libssl depends on libcrypt) + # copy over, build array of relative paths to add. + fixlib_relative="" + for lib in $fixlib + do + dir=$(dirname "$lib") + name=$(basename "$lib" .dylib) + echo " working on ${name}.dylib ..." + rsync -ar --include "${name}*" --exclude="*" "${dir}/" "${WORKDIR}/${prefix}/lib/" + fixlib_relative="${fixlib_relative} ${WORKDIR}/${prefix}/lib/${name}.dylib" + done + + echo "Adding in new libraries: " + echo "${fixlib_relative}" + + # Add new libraries + codesign_files="${codesign_files} ${fixlib_relative}" + codesign_all="${codesign_all} ${fixlib_relative}" + + # Fix up paths between binaries and libraries + for lib in $fixlib + do + dir=$(dirname "$lib") + name=$(basename "$lib" .dylib) + + # We could just change $lib into $prefix, which will work for + # zfs libraries. But libssl having libcrypto might have a different + # path, so lookup the source path each time. + for file in $codesign_files + do + chmod u+w "${file}" + src=$(otool -L "$file" | awk '{print $1;}' | grep "${name}.dylib") + install_name_tool -change "${src}" "${prefix}/lib/${name}.dylib" "${file}" + done + done +} + +# Upload .pkg file +# Staple .pkg file +function do_notarize +{ + echo "Uploading PKG to Apple ..." + + TFILE="out-altool.xml" + RFILE="req-altool.xml" + xcrun altool --notarize-app -f my_package_new.pkg --primary-bundle-id org.openzfsonosx.zfs -u lundman@lundman.net -p "$PKG_NOTARIZE_KEY" --output-format xml > ${TFILE} + + GUID=$(/usr/libexec/PlistBuddy -c "Print :notarization-upload:RequestUUID" ${TFILE}) + echo "Uploaded. GUID ${GUID}" + echo "Waiting for Apple to notarize..." + while true + do + sleep 10 + echo "Querying Apple." + + xcrun altool --notarization-info "${GUID}" -u lundman@lundman.net -p "$PKG_NOTARIZE_KEY" --output-format xml > ${RFILE} + status=$(/usr/libexec/PlistBuddy -c "Print :notarization-info:Status" ${RFILE}) + if [ "$status" != "in progress" ]; then + echo "Status: $status ." + break + fi + echo "Status: $status - sleeping ..." + sleep 30 + done + + echo "Stapling PKG ..." + xcrun stapler staple my_package_new.pkg + ret=$? + xcrun stapler validate -v my_package_new.pkg + + if [ $ret != 0 ]; then + echo "Failed to notarize: $ret" + grep "https://" ${RFILE} + exit 1 + fi + +} + +echo "Pruning install area ..." +do_prune + +if [ -n "$fix_libraries" ]; then + copy_fix_libraries + copy_fix_libraries +fi + +if [ -n "$PKG_CODESIGN_KEY" ]; then + do_codesign +fi + +sleep 1 + +echo "Creating pkg ... " + +sign=() + +if [ -n "$PKG_INSTALL_KEY" ]; then + do_unlock "${PKG_INSTALL_KEY}" + #sign=(--sign "$PKG_INSTALL_KEY" --keychain "$retval" --keychain ~/Library/Keychains/login.keychain-db) + echo sign=--sign "$PKG_INSTALL_KEY" --keychain "$retval" + sign=(--sign "$PKG_INSTALL_KEY" --keychain "$retval") +fi + +rm -f my_package.pkg +pkgbuild --root "${WORKDIR}" --identifier org.openzfsonosx.zfs --version "${version}" --scripts "${BASE_DIR}/../contrib/macOS/pkg-scripts/" "${sign[@]}" my_package.pkg + +ret=$? + +echo "pkgbuild result $ret" + +if [ $ret != 0 ]; then + fail "pkgbuild failed" +fi + +friendly=$(awk '/SOFTWARE LICENSE AGREEMENT FOR macOS/' '/System/Library/CoreServices/Setup Assistant.app/Contents/Resources/en.lproj/OSXSoftwareLicense.rtf' | awk -F 'macOS ' '{print $NF}' | tr -d '\\') +if [ -z "$friendly" ]; then + friendly=$(awk '/SOFTWARE LICENSE AGREEMENT FOR OS X/' '/System/Library/CoreServices/Setup Assistant.app/Contents/Resources/en.lproj/OSXSoftwareLicense.rtf' | awk -F 'OS X ' '{print $NF}' | awk '{print substr($0, 0, length($0)-1)}') +fi + +friendly=$(echo "$friendly" | tr ' ' '.') + +# Now fiddle with pkg to make it nicer +productbuild --synthesize --package ./my_package.pkg distribution.xml + +sed < distribution.xml > distribution_new.xml -e \ +"s## Open ZFS on OsX ${version} - ${friendly}-${OS}\\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ + \\ +\\ +\\ +\\ +\\ +#g" + +sed -i "" \ + -e "/SCRIPT_REPLACE/{r ${BASE_DIR}/../contrib/macOS/resources/javascript.js" \ + -e 'd' -e '}' \ + distribution_new.xml + +rm -f my_package_new.pkg +productbuild --distribution distribution_new.xml --resources "${BASE_DIR}/../contrib/macOS/resources/" --scripts "${BASE_DIR}/../contrib/macOS/product-scripts" "${sign[@]}" --package-path ./my_package.pkg my_package_new.pkg + +if [ -n "$PKG_NOTARIZE_KEY" ]; then + SECONDS=0 + do_notarize + echo "Notarize took $SECONDS seconds to complete" +fi + +arch=$(uname -m) +if [ x"$arch" == x"arm64" ]; then + name="OpenZFSonOsX-${version}${RC}-${friendly}-${OS}-${arch}.pkg" +else + name="OpenZFSonOsX-${version}${RC}-${friendly}-${OS}.pkg" +fi + +mv my_package_new.pkg "${name}" +ls -l "${name}" + +# Cleanup +rm -f my_package.pkg distribution.xml distribution_new.xml req-altool.xml out-altool.xml + +echo "All done." From bf89419a902af34cc14a86a6f2ff84e5e041fd89 Mon Sep 17 00:00:00 2001 From: Jorgen Lundman Date: Mon, 23 Oct 2023 09:02:11 +0900 Subject: [PATCH 2/9] 2: module/ changes part2: module/* Signed-off-by: Jorgen Lundman --- module/Kbuild.in | 1 + module/Makefile.bsd | 4 + module/Makefile.in | 33 +- module/icp/algs/aes/aes_impl.c | 40 +- module/icp/algs/aes/aes_impl_aesv8.c | 146 + module/icp/algs/blake3/blake3_impl.c | 16 +- module/icp/algs/modes/gcm.c | 77 +- module/icp/algs/sha2/sha256_impl.c | 9 +- module/icp/algs/sha2/sha512_impl.c | 9 +- module/icp/asm-aarch64/aes/aesv8-armx.S | 3245 +++++++++++++++++ .../icp/asm-aarch64/blake3/b3_aarch64_sse2.S | 45 +- .../icp/asm-aarch64/blake3/b3_aarch64_sse41.S | 81 +- module/icp/asm-aarch64/sha2/sha256-armv8.S | 28 +- module/icp/asm-aarch64/sha2/sha512-armv8.S | 23 +- module/icp/core/kcf_mech_tabs.c | 1 + module/icp/include/aes/aes_impl.h | 4 + module/lua/ldo.c | 2 +- module/lua/setjmp/setjmp_aarch64.S | 16 +- module/nvpair/nvpair.c | 28 + module/zcommon/zfeature_common.c | 9 +- module/zcommon/zfs_fletcher.c | 12 +- module/zcommon/zfs_prop.c | 34 + module/zcommon/zprop_common.c | 3 +- module/zfs/blake3_zfs.c | 5 +- module/zfs/dbuf.c | 23 + module/zfs/dsl_crypt.c | 16 + module/zfs/dsl_scan.c | 5 + module/zfs/spa.c | 25 +- module/zfs/spa_errlog.c | 2 +- module/zfs/spa_misc.c | 2 +- module/zfs/vdev_raidz_math.c | 30 +- module/zfs/zfs_fuid.c | 17 + module/zfs/zfs_ioctl.c | 2 +- module/zfs/zfs_log.c | 22 + module/zfs/zfs_sa.c | 2 + module/zfs/zfs_vnops.c | 11 +- module/zfs/zvol.c | 2 +- module/zstd/include/limits.h | 1 + module/zstd/include/stddef.h | 1 + module/zstd/include/stdint.h | 1 + module/zstd/include/string.h | 1 + module/zstd/lib/common/zstd_internal.h | 8 +- 42 files changed, 3974 insertions(+), 68 deletions(-) create mode 100644 module/icp/algs/aes/aes_impl_aesv8.c create mode 100644 module/icp/asm-aarch64/aes/aesv8-armx.S diff --git a/module/Kbuild.in b/module/Kbuild.in index c132171592a8..946184dff78b 100644 --- a/module/Kbuild.in +++ b/module/Kbuild.in @@ -151,6 +151,7 @@ ICP_OBJS_ARM := \ asm-arm/sha2/sha512-armv7.o ICP_OBJS_ARM64 := \ + asm-aarch64/aes/aes_aesv8.o \ asm-aarch64/blake3/b3_aarch64_sse2.o \ asm-aarch64/blake3/b3_aarch64_sse41.o \ asm-aarch64/sha2/sha256-armv8.o \ diff --git a/module/Makefile.bsd b/module/Makefile.bsd index 0c4d8bfe1159..55341a50a16b 100644 --- a/module/Makefile.bsd +++ b/module/Makefile.bsd @@ -15,6 +15,7 @@ KMOD= openzfs ${SRCDIR}/icp/algs/blake3 \ ${SRCDIR}/icp/algs/edonr \ ${SRCDIR}/icp/algs/sha2 \ + ${SRCDIR}/icp/asm-aarch64/aes \ ${SRCDIR}/icp/asm-aarch64/blake3 \ ${SRCDIR}/icp/asm-aarch64/sha2 \ ${SRCDIR}/icp/asm-arm/sha2 \ @@ -88,6 +89,9 @@ SRCS+= avl.c # icp SRCS+= edonr.c +#icp/asm-aarch64/aes +SRCS+= aesv8-armx.S + #icp/algs/blake3 SRCS+= blake3.c \ blake3_generic.c \ diff --git a/module/Makefile.in b/module/Makefile.in index 9b34b3dfaec7..09d35d87a119 100644 --- a/module/Makefile.in +++ b/module/Makefile.in @@ -10,13 +10,15 @@ uninstall: modules_uninstall data_uninstall check: .PHONY: all distclean maintainer-clean install uninstall check distdir \ - modules modules-Linux modules-FreeBSD modules-unknown \ - clean clean-Linux clean-FreeBSD \ + modules modules-Linux modules-FreeBSD modules-macOS modules-unknown \ + clean clean-Linux clean-FreeBSD clean-macOS \ modules_install modules_install-Linux modules_install-FreeBSD \ - data_install data_install-Linux data_install-FreeBSD \ + modules_install-macOS data_install data_install-Linux \ + data_install-FreeBSD data_install-macOS \ modules_uninstall modules_uninstall-Linux modules_uninstall-FreeBSD \ - data_uninstall data_uninstall-Linux data_uninstall-FreeBSD \ - cppcheck cppcheck-Linux cppcheck-FreeBSD + modules_uninstall-macOS data_uninstall data_uninstall-Linux \ + data_uninstall-FreeBSD data_uninstall-macOS \ + cppcheck cppcheck-Linux cppcheck-FreeBSD cppcheck-macOS # For FreeBSD, use debug options from ./configure if not overridden. export WITH_DEBUG ?= @WITH_DEBUG@ @@ -60,6 +62,9 @@ modules-Linux: modules-FreeBSD: +$(FMAKE) +modules-macOS: + @true + modules-unknown: @true @@ -76,6 +81,9 @@ clean-Linux: clean-FreeBSD: +$(FMAKE) clean +clean-macOS: + @true + clean: clean-@ac_system@ .PHONY: modules_uninstall-Linux-legacy @@ -110,6 +118,9 @@ modules_install-FreeBSD: @# Install the kernel modules +$(FMAKE) install +modules_install-macOS: + @true + modules_install: modules_install-@ac_system@ data_install-Linux: @@ -119,6 +130,9 @@ data_install-Linux: data_install-FreeBSD: @ +data_install-macOS: + @ + data_install: data_install-@ac_system@ modules_uninstall-Linux: modules_uninstall-Linux-legacy @@ -128,6 +142,9 @@ modules_uninstall-Linux: modules_uninstall-Linux-legacy modules_uninstall-FreeBSD: @false +modules_uninstall-macOS: + @false + modules_uninstall: modules_uninstall-@ac_system@ data_uninstall-Linux: @@ -136,6 +153,9 @@ data_uninstall-Linux: data_uninstall-FreeBSD: @ +data_uninstall-macOS: + @ + data_uninstall: data_uninstall-@ac_system@ cppcheck-Linux: @@ -158,6 +178,9 @@ cppcheck-Linux: cppcheck-FreeBSD: @true +cppcheck-macOS: + @true + cppcheck: cppcheck-@ac_system@ distdir: diff --git a/module/icp/algs/aes/aes_impl.c b/module/icp/algs/aes/aes_impl.c index 9daa975226fe..db0c84940630 100644 --- a/module/icp/algs/aes/aes_impl.c +++ b/module/icp/algs/aes/aes_impl.c @@ -233,6 +233,9 @@ static const aes_impl_ops_t *aes_all_impl[] = { #if defined(__x86_64) && defined(HAVE_AES) &aes_aesni_impl, #endif +#if defined(__aarch64__) && defined(HAVE_AESV8) + &aes_aesv8_impl, +#endif }; /* Indicate that benchmark has been completed */ @@ -307,12 +310,21 @@ aes_impl_init(void) if (curr_impl->is_supported()) aes_supp_impl[c++] = (aes_impl_ops_t *)curr_impl; } + aes_supp_impl_cnt = c; /* * Set the fastest implementation given the assumption that the * hardware accelerated version is the fastest. */ +#if defined(__aarch64__) +#if defined(HAVE_AESV8) + if (aes_aesv8_impl.is_supported()) { + memcpy(&aes_fastest_impl, &aes_aesv8_impl, + sizeof (aes_fastest_impl)); + } else +#endif +#endif #if defined(__x86_64) #if defined(HAVE_AES) if (aes_aesni_impl.is_supported()) { @@ -334,6 +346,7 @@ aes_impl_init(void) /* Finish initialization */ atomic_swap_32(&icp_aes_impl, user_sel_impl); aes_impl_initialized = B_TRUE; + } static const struct { @@ -404,14 +417,17 @@ aes_impl_set(const char *val) return (err); } -#if defined(_KERNEL) && defined(__linux__) +#if defined(_KERNEL) +#if defined(__linux__) static int icp_aes_impl_set(const char *val, zfs_kernel_param_t *kp) { return (aes_impl_set(val)); } +#endif +#if defined(__linux__) || defined(__APPLE__) static int icp_aes_impl_get(char *buffer, zfs_kernel_param_t *kp) { @@ -437,6 +453,28 @@ icp_aes_impl_get(char *buffer, zfs_kernel_param_t *kp) return (cnt); } +#endif /* defined(Linux) || defined(APPLE) */ + +#if defined(__APPLE__) +/* get / set function */ +int +param_icp_aes_impl_set(ZFS_MODULE_PARAM_ARGS) +{ + char buf[1024]; /* Linux module string limit */ + int rc = 0; + + /* Always fill in value before calling sysctl_handle_*() */ + if (req->newptr == (user_addr_t)NULL) + (void) icp_aes_impl_get(buf, NULL); + + rc = sysctl_handle_string(oidp, buf, sizeof (buf), req); + if (rc || req->newptr == (user_addr_t)NULL) + return (rc); + + rc = aes_impl_set(buf); + return (rc); +} +#endif /* defined(APPLE) */ module_param_call(icp_aes_impl, icp_aes_impl_set, icp_aes_impl_get, NULL, 0644); diff --git a/module/icp/algs/aes/aes_impl_aesv8.c b/module/icp/algs/aes/aes_impl_aesv8.c new file mode 100644 index 000000000000..b389f9975150 --- /dev/null +++ b/module/icp/algs/aes/aes_impl_aesv8.c @@ -0,0 +1,146 @@ +/* + * 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 https://opensource.org/licenses/CDDL-1.0. + * 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) 2023, Jorgen Lundman + */ + +#define HAVE_AESV8 +#if defined(__aarch64__) && defined(HAVE_AESV8) + +#include +#include +#include + +/* These functions are used to execute AES-V8 instructions: */ +#ifdef OPENSSL_INTERFACE +extern ASMABI int aes_v8_set_encrypt_key(const unsigned char *userKey, + const int bits, AES_KEY *key); +extern ASMABI int aes_v8_set_decrypt_key(const unsigned char *userKey, + const int bits, AES_KEY *key); +extern ASMABI void aes_v8_encrypt(const unsigned char *in, + unsigned char *out, const AES_KEY *key, const unsigned int nround); +extern ASMABI void aes_v8_decrypt(const unsigned char *in, + unsigned char *out, const AES_KEY *key, const unsigned int nround); +#endif + +extern ASMABI int aes_v8_set_encrypt_key(const uint32_t rk[], + uint64_t bits, const uint32_t cipherKey[]); +extern ASMABI int aes_v8_set_decrypt_key(const uint32_t rk[], + uint64_t bits, const uint32_t cipherKey[]); +/* nround $10 (128), $12 (192), $14 (256) */ +extern ASMABI void aes_v8_encrypt(const uint32_t pt[4], + const uint32_t ct[4], const uint32_t rk[], const unsigned int nround); +extern ASMABI void aes_v8_decrypt(const uint32_t ct[4], + const uint32_t pt[4], const uint32_t rk[], const unsigned int nround); + +#define AES_MAXNR 14 +typedef struct aes_key_st { + unsigned int rd_key[4 *(AES_MAXNR + 1)]; + int rounds; + unsigned int pad[3]; +} AES_KEY; + +#include + +/* + * Expand the 32-bit AES cipher key array into the encryption and decryption + * key schedules. + * + * Parameters: + * key AES key schedule to be initialized + * keyarr32 User key + * keyBits AES key size (128, 192, or 256 bits) + */ +static void +aes_aesv8_generate(aes_key_t *key, const uint32_t *keyarr32, int keybits) +{ + kfpu_begin(); + key->nr = aes_v8_set_encrypt_key(keyarr32, keybits, + &(key->encr_ks.ks32[0])); + key->nr = aes_v8_set_decrypt_key(keyarr32, keybits, + &(key->decr_ks.ks32[0])); + kfpu_end(); +} + +/* + * Encrypt one block of data. The block is assumed to be an array + * of four uint32_t values, so copy for alignment (and byte-order + * reversal for little endian systems might be necessary on the + * input and output byte streams. + * The size of the key schedule depends on the number of rounds + * (which can be computed from the size of the key), i.e. 4*(Nr + 1). + * + * Parameters: + * rk Key schedule, of aes_ks_t (60 32-bit integers) + * Nr Number of rounds + * pt Input block (plain text) + * ct Output block (crypto text). Can overlap with pt + */ +static void +aes_aesv8_encrypt(const uint32_t rk[], int Nr, const uint32_t pt[4], + uint32_t ct[4]) +{ + kfpu_begin(); + aes_v8_encrypt(pt, ct, rk, Nr); + kfpu_end(); +} + +/* + * Decrypt one block of data. The block is assumed to be an array + * of four uint32_t values, so copy for alignment (and byte-order + * reversal for little endian systems might be necessary on the + * input and output byte streams. + * The size of the key schedule depends on the number of rounds + * (which can be computed from the size of the key), i.e. 4*(Nr + 1). + * + * Parameters: + * rk Key schedule, of aes_ks_t (60 32-bit integers) + * Nr Number of rounds + * ct Input block (crypto text) + * pt Output block (plain text). Can overlap with pt + */ +static void +aes_aesv8_decrypt(const uint32_t rk[], int Nr, const uint32_t ct[4], + uint32_t pt[4]) +{ + kfpu_begin(); + aes_v8_encrypt(ct, pt, rk, Nr); + kfpu_end(); +} + +static boolean_t +aes_aesv8_will_work(void) +{ + return (kfpu_allowed() && zfs_aesv8_available()); + +} + +const aes_impl_ops_t aes_aesv8_impl = { + .generate = &aes_aesv8_generate, + .encrypt = &aes_aesv8_encrypt, + .decrypt = &aes_aesv8_decrypt, + .is_supported = &aes_aesv8_will_work, + .needs_byteswap = B_FALSE, + .name = "aesv8" +}; + +#endif /* defined(__aarch64__) && defined(HAVE_AESV8) */ diff --git a/module/icp/algs/blake3/blake3_impl.c b/module/icp/algs/blake3/blake3_impl.c index f3f48c2dfa1a..2cd0c67ce8b6 100644 --- a/module/icp/algs/blake3/blake3_impl.c +++ b/module/icp/algs/blake3/blake3_impl.c @@ -30,6 +30,13 @@ #include "blake3_impl.h" +#if defined(__APPLE__) && defined(__aarch64__) +/* Sadly, toolchain sets this, but M1 can't compile it as-is */ +#undef __aarch64__ +#undef HAVE_SSE2 +#undef HAVE_SSE4_1 +#endif + #if defined(__aarch64__) || \ (defined(__x86_64) && defined(HAVE_SSE2)) || \ (defined(__PPC64__) && defined(__LITTLE_ENDIAN__)) @@ -347,7 +354,7 @@ blake3_param_set(const char *val, zfs_kernel_param_t *unused) return (generic_impl_setname(val)); } -#elif defined(__FreeBSD__) +#elif defined(__FreeBSD__) || defined(__APPLE__) #include @@ -357,7 +364,7 @@ blake3_param(ZFS_MODULE_PARAM_ARGS) int err; generic_impl_init(); - if (req->newptr == NULL) { + if ((const void *)req->newptr == NULL) { const uint32_t impl = IMPL_READ(generic_impl_chosen); const int init_buflen = 64; const char *fmt; @@ -379,7 +386,12 @@ blake3_param(ZFS_MODULE_PARAM_ARGS) (void) sbuf_printf(s, fmt, generic_supp_impls[i]->name); } +#ifdef __APPLE__ + err = SYSCTL_OUT(req, s->s_buf, s->s_len); + sbuf_finish(s); +#else err = sbuf_finish(s); +#endif sbuf_delete(s); return (err); diff --git a/module/icp/algs/modes/gcm.c b/module/icp/algs/modes/gcm.c index dd8db6f97460..e10e2738748f 100644 --- a/module/icp/algs/modes/gcm.c +++ b/module/icp/algs/modes/gcm.c @@ -862,6 +862,12 @@ gcm_impl_init(void) * Set the fastest implementation given the assumption that the * hardware accelerated version is the fastest. */ +#if defined(__aarch64__) && defined(HAVE_ARMV8) + if (gcm_armv8_impl.is_supported()) { + memcpy(&gcm_fastest_impl, &gcm_armv8_impl, + sizeof (gcm_fastest_impl)); + } else +#endif #if defined(__x86_64) && defined(HAVE_PCLMULQDQ) if (gcm_pclmulqdq_impl.is_supported()) { memcpy(&gcm_fastest_impl, &gcm_pclmulqdq_impl, @@ -883,7 +889,13 @@ gcm_impl_init(void) if (gcm_avx_will_work()) { #ifdef HAVE_MOVBE if (zfs_movbe_available() == B_TRUE) { +#ifdef __APPLE__ + atomic_swap_32( + (volatile unsigned int *)&gcm_avx_can_use_movbe, + B_TRUE); +#else atomic_swap_32(&gcm_avx_can_use_movbe, B_TRUE); +#endif } #endif if (GCM_IMPL_READ(user_sel_impl) == IMPL_FASTEST) { @@ -985,14 +997,17 @@ gcm_impl_set(const char *val) return (err); } -#if defined(_KERNEL) && defined(__linux__) +#if defined(_KERNEL) +#if defined(__linux__) static int icp_gcm_impl_set(const char *val, zfs_kernel_param_t *kp) { return (gcm_impl_set(val)); } +#endif +#if defined(__linux__) || defined(__APPLE__) static int icp_gcm_impl_get(char *buffer, zfs_kernel_param_t *kp) { @@ -1024,6 +1039,28 @@ icp_gcm_impl_get(char *buffer, zfs_kernel_param_t *kp) return (cnt); } +#endif /* defined(Linux) || defined(APPLE) */ + +#if defined(__APPLE__) +/* get / set function */ +int +param_icp_gcm_impl_set(ZFS_MODULE_PARAM_ARGS) +{ + char buf[1024]; /* Linux module string limit */ + int rc = 0; + + /* Always fill in value before calling sysctl_handle_*() */ + if (req->newptr == (user_addr_t)NULL) + (void) icp_gcm_impl_get(buf, NULL); + + rc = sysctl_handle_string(oidp, buf, sizeof (buf), req); + if (rc || req->newptr == (user_addr_t)NULL) + return (rc); + + rc = gcm_impl_set(buf); + return (rc); +} +#endif /* defined(APPLE) */ module_param_call(icp_gcm_impl, icp_gcm_impl_set, icp_gcm_impl_get, NULL, 0644); @@ -1092,7 +1129,11 @@ static inline void gcm_set_avx(boolean_t val) { if (gcm_avx_will_work() == B_TRUE) { +#ifdef __APPLE__ + atomic_swap_32((volatile unsigned int *)&gcm_use_avx, val); +#else atomic_swap_32(&gcm_use_avx, val); +#endif } } @@ -1543,6 +1584,8 @@ gcm_init_avx(gcm_ctx_t *ctx, const uint8_t *iv, size_t iv_len, } #if defined(_KERNEL) + +#if defined(__linux__) static int icp_gcm_avx_set_chunk_size(const char *buf, zfs_kernel_param_t *kp) { @@ -1563,6 +1606,38 @@ icp_gcm_avx_set_chunk_size(const char *buf, zfs_kernel_param_t *kp) error = param_set_uint(val_rounded, kp); return (error); } +#endif + +#ifdef __APPLE__ +/* Lives in here to have access to GCM macros */ +int +param_icp_gcm_avx_set_chunk_size(ZFS_MODULE_PARAM_ARGS) +{ + unsigned long val; + char buf[16]; + int rc = 0; + + /* Always fill in value before calling sysctl_handle_*() */ + if (req->newptr == (user_addr_t)NULL) + snprintf(buf, sizeof (buf), "%u", gcm_avx_chunk_size); + + rc = sysctl_handle_string(oidp, buf, sizeof (buf), req); + if (rc || req->newptr == (user_addr_t)NULL) + return (rc); + + rc = kstrtoul(buf, 0, &val); + if (rc) + return (rc); + + val = (val / GCM_AVX_MIN_DECRYPT_BYTES) * GCM_AVX_MIN_DECRYPT_BYTES; + + if (val < GCM_AVX_MIN_ENCRYPT_BYTES || val > GCM_AVX_MAX_CHUNK_SIZE) + return (EINVAL); + + gcm_avx_chunk_size = val; + return (rc); +} +#endif module_param_call(icp_gcm_avx_chunk_size, icp_gcm_avx_set_chunk_size, param_get_uint, &gcm_avx_chunk_size, 0644); diff --git a/module/icp/algs/sha2/sha256_impl.c b/module/icp/algs/sha2/sha256_impl.c index 01ce5cbd814c..b791540b8f6b 100644 --- a/module/icp/algs/sha2/sha256_impl.c +++ b/module/icp/algs/sha2/sha256_impl.c @@ -250,7 +250,7 @@ sha256_param_set(const char *val, zfs_kernel_param_t *unused) return (generic_impl_setname(val)); } -#elif defined(__FreeBSD__) +#elif defined(__FreeBSD__) || defined(__APPLE__) #include @@ -260,7 +260,7 @@ sha256_param(ZFS_MODULE_PARAM_ARGS) int err; generic_impl_init(); - if (req->newptr == NULL) { + if ((const void *)req->newptr == NULL) { const uint32_t impl = IMPL_READ(generic_impl_chosen); const int init_buflen = 64; const char *fmt; @@ -282,7 +282,12 @@ sha256_param(ZFS_MODULE_PARAM_ARGS) (void) sbuf_printf(s, fmt, generic_supp_impls[i]->name); } +#ifdef __APPLE__ + err = SYSCTL_OUT(req, s->s_buf, s->s_len); + sbuf_finish(s); +#else err = sbuf_finish(s); +#endif sbuf_delete(s); return (err); diff --git a/module/icp/algs/sha2/sha512_impl.c b/module/icp/algs/sha2/sha512_impl.c index 27b35a639a54..9c5b3bbc6c37 100644 --- a/module/icp/algs/sha2/sha512_impl.c +++ b/module/icp/algs/sha2/sha512_impl.c @@ -225,7 +225,7 @@ sha512_param_set(const char *val, zfs_kernel_param_t *unused) return (generic_impl_setname(val)); } -#elif defined(__FreeBSD__) +#elif defined(__FreeBSD__) || defined(__APPLE__) #include @@ -235,7 +235,7 @@ sha512_param(ZFS_MODULE_PARAM_ARGS) int err; generic_impl_init(); - if (req->newptr == NULL) { + if ((const void *)req->newptr == NULL) { const uint32_t impl = IMPL_READ(generic_impl_chosen); const int init_buflen = 64; const char *fmt; @@ -257,7 +257,12 @@ sha512_param(ZFS_MODULE_PARAM_ARGS) (void) sbuf_printf(s, fmt, generic_supp_impls[i]->name); } +#ifdef __APPLE__ + err = SYSCTL_OUT(req, s->s_buf, s->s_len); + sbuf_finish(s); +#else err = sbuf_finish(s); +#endif sbuf_delete(s); return (err); diff --git a/module/icp/asm-aarch64/aes/aesv8-armx.S b/module/icp/asm-aarch64/aes/aesv8-armx.S new file mode 100644 index 000000000000..f26bf08e778e --- /dev/null +++ b/module/icp/asm-aarch64/aes/aesv8-armx.S @@ -0,0 +1,3245 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + */ + +#include + +#if defined(__aarch64__) + +/* + * + * OpenSSL interface: + * int intel_AES_set_encrypt_key(const unsigned char *userKey, + * const int bits, AES_KEY *key) ; + * int intel_AES_set_decrypt_key(const unsigned char *userKey, + * const int bits, AES_KEY *key) ; + * Return values for above are non-zero on error, 0 on success. + * + * void intel_AES_encrypt(const unsigned char *in, unsigned char *out, + * const AES_KEY *key) ; + * void intel_AES_decrypt(const unsigned char *in, unsigned char *out, + * const AES_KEY *key) ; + * typedef struct aes_key_st { + * unsigned int rd_key[4 *(AES_MAXNR + 1)] ; + * int rounds ; + * unsigned int pad[3] ; + * } AES_KEY ; + * + * 2023/03/17 lundman + * changed to use 4th parameter for NROUNDS + * __AARCH64EB__ for big endian + * + * Make it return Nrounds, delete other methods xts, etc + */ + +SECTION_TEXT +.balign 32 +Lrcon: +.long 0x01,0x01,0x01,0x01 +.long 0x0c0f0e0d,0x0c0f0e0d,0x0c0f0e0d,0x0c0f0e0d // rotate-n-splat +.long 0x1b,0x1b,0x1b,0x1b + +ENTRY_ALIGN(aes_v8_set_encrypt_key, 32) +Lenc_key: + // Armv8.3-A PAuth: even though x30 is pushed to stack it is not popped later. + stp x29,x30,[sp,#-16]! + add x29,sp,#0 + mov x3,#-1 + cmp x0,#0 + b.eq Lenc_key_abort + cmp x2,#0 + b.eq Lenc_key_abort + mov x3,#-2 + cmp w1,#128 + b.lt Lenc_key_abort + cmp w1,#256 + b.gt Lenc_key_abort + tst w1,#0x3f + b.ne Lenc_key_abort + + adr x3,Lrcon + cmp w1,#192 + + eor v0.16b,v0.16b,v0.16b + ld1 {v3.16b},[x0],#16 + mov w1,#8 // reuse w1 + ld1 {v1.4s,v2.4s},[x3],#32 + + b.lt Loop128 + b.eq L192 + b L256 + +.balign 16 +Loop128: + tbl v6.16b,{v3.16b},v2.16b + ext v5.16b,v0.16b,v3.16b,#12 + st1 {v3.4s},[x2],#16 + aese v6.16b,v0.16b + subs w1,w1,#1 + + eor v3.16b,v3.16b,v5.16b + ext v5.16b,v0.16b,v5.16b,#12 + eor v3.16b,v3.16b,v5.16b + ext v5.16b,v0.16b,v5.16b,#12 + eor v6.16b,v6.16b,v1.16b + eor v3.16b,v3.16b,v5.16b + shl v1.16b,v1.16b,#1 + eor v3.16b,v3.16b,v6.16b + b.ne Loop128 + + ld1 {v1.4s},[x3] + + tbl v6.16b,{v3.16b},v2.16b + ext v5.16b,v0.16b,v3.16b,#12 + st1 {v3.4s},[x2],#16 + aese v6.16b,v0.16b + + eor v3.16b,v3.16b,v5.16b + ext v5.16b,v0.16b,v5.16b,#12 + eor v3.16b,v3.16b,v5.16b + ext v5.16b,v0.16b,v5.16b,#12 + eor v6.16b,v6.16b,v1.16b + eor v3.16b,v3.16b,v5.16b + shl v1.16b,v1.16b,#1 + eor v3.16b,v3.16b,v6.16b + + tbl v6.16b,{v3.16b},v2.16b + ext v5.16b,v0.16b,v3.16b,#12 + st1 {v3.4s},[x2],#16 + aese v6.16b,v0.16b + + eor v3.16b,v3.16b,v5.16b + ext v5.16b,v0.16b,v5.16b,#12 + eor v3.16b,v3.16b,v5.16b + ext v5.16b,v0.16b,v5.16b,#12 + eor v6.16b,v6.16b,v1.16b + eor v3.16b,v3.16b,v5.16b + eor v3.16b,v3.16b,v6.16b + st1 {v3.4s},[x2] + add x2,x2,#0x50 + + mov w12,#10 +#ifndef OPENSSL_INTERFACE + mov x3,#10 // Return nround +#endif + b Ldone + +.balign 16 +L192: + ld1 {v4.8b},[x0],#8 + movi v6.16b,#8 // borrow v6.16b + st1 {v3.4s},[x2],#16 + sub v2.16b,v2.16b,v6.16b // adjust the mask + +Loop192: + tbl v6.16b,{v4.16b},v2.16b + ext v5.16b,v0.16b,v3.16b,#12 +#ifdef __AARCH64EB__ + st1 {v4.4s},[x2],#16 + sub x2,x2,#8 +#else + st1 {v4.8b},[x2],#8 +#endif + aese v6.16b,v0.16b + subs w1,w1,#1 + + eor v3.16b,v3.16b,v5.16b + ext v5.16b,v0.16b,v5.16b,#12 + eor v3.16b,v3.16b,v5.16b + ext v5.16b,v0.16b,v5.16b,#12 + eor v3.16b,v3.16b,v5.16b + + dup v5.4s,v3.s[3] + eor v5.16b,v5.16b,v4.16b + eor v6.16b,v6.16b,v1.16b + ext v4.16b,v0.16b,v4.16b,#12 + shl v1.16b,v1.16b,#1 + eor v4.16b,v4.16b,v5.16b + eor v3.16b,v3.16b,v6.16b + eor v4.16b,v4.16b,v6.16b + st1 {v3.4s},[x2],#16 + b.ne Loop192 + + mov w12,#12 + add x2,x2,#0x20 +#ifndef OPENSSL_INTERFACE + mov x3,#12 // Return nround +#endif + b Ldone + +.balign 16 +L256: + ld1 {v4.16b},[x0] + mov w1,#7 + mov w12,#14 + st1 {v3.4s},[x2],#16 + +Loop256: + tbl v6.16b,{v4.16b},v2.16b + ext v5.16b,v0.16b,v3.16b,#12 + st1 {v4.4s},[x2],#16 + aese v6.16b,v0.16b + subs w1,w1,#1 + + eor v3.16b,v3.16b,v5.16b + ext v5.16b,v0.16b,v5.16b,#12 + eor v3.16b,v3.16b,v5.16b + ext v5.16b,v0.16b,v5.16b,#12 + eor v6.16b,v6.16b,v1.16b + eor v3.16b,v3.16b,v5.16b + shl v1.16b,v1.16b,#1 + eor v3.16b,v3.16b,v6.16b + st1 {v3.4s},[x2],#16 +#ifndef OPENSSL_INTERFACE + mov x3,#14 // Return nround +#endif + b.eq Ldone + + dup v6.4s,v3.s[3] // just splat + ext v5.16b,v0.16b,v4.16b,#12 + aese v6.16b,v0.16b + + eor v4.16b,v4.16b,v5.16b + ext v5.16b,v0.16b,v5.16b,#12 + eor v4.16b,v4.16b,v5.16b + ext v5.16b,v0.16b,v5.16b,#12 + eor v4.16b,v4.16b,v5.16b + + eor v4.16b,v4.16b,v6.16b + b Loop256 + +Ldone: + str w12,[x2] +#ifdef OPENSSL_INTERFACE + mov x3,#0 // return value, skip with openzfs +#endif + +Lenc_key_abort: + mov x0,x3 // return value + ldr x29,[sp],#16 + ret + + +ENTRY_ALIGN(aes_v8_set_decrypt_key, 32) + stp x29,x30,[sp,#-16]! + add x29,sp,#0 + bl Lenc_key + + cmp x0,#0 + b.ne Ldec_key_abort + + sub x2,x2,#240 // restore original x2 + mov x4,#-16 + add x0,x2,x12,lsl#4 // end of key schedule + + ld1 {v0.4s},[x2] + ld1 {v1.4s},[x0] + st1 {v0.4s},[x0],x4 + st1 {v1.4s},[x2],#16 + +Loop_imc: + ld1 {v0.4s},[x2] + ld1 {v1.4s},[x0] + aesimc v0.16b,v0.16b + aesimc v1.16b,v1.16b + st1 {v0.4s},[x0],x4 + st1 {v1.4s},[x2],#16 + cmp x0,x2 + b.hi Loop_imc + + ld1 {v0.4s},[x2] + aesimc v0.16b,v0.16b + st1 {v0.4s},[x0] + + eor x0,x0,x0 // return value +Ldec_key_abort: + ldp x29,x30,[sp],#16 + ret + +ENTRY_ALIGN(aes_v8_encrypt, 32) +#ifdef OPENSSL_INTERFACE + ldr w3,[x2,#240] // NumRounds in AESKEY +#endif + // We'll pass numRounds in 4th argument instead, ie x3/w3. + ld1 {v0.4s},[x2],#16 + ld1 {v2.16b},[x0] + sub w3,w3,#2 + ld1 {v1.4s},[x2],#16 + +Loop_enc: + aese v2.16b,v0.16b + aesmc v2.16b,v2.16b + ld1 {v0.4s},[x2],#16 + subs w3,w3,#2 + aese v2.16b,v1.16b + aesmc v2.16b,v2.16b + ld1 {v1.4s},[x2],#16 + b.gt Loop_enc + + aese v2.16b,v0.16b + aesmc v2.16b,v2.16b + ld1 {v0.4s},[x2] + aese v2.16b,v1.16b + eor v2.16b,v2.16b,v0.16b + + st1 {v2.16b},[x1] + ret + +ENTRY_ALIGN(aes_v8_decrypt, 32) +#ifdef OPENSSL_INTERFACE + ldr w3,[x2,#240] // NumRounds in AESKEY +#endif + // We'll pass numRounds in 4th argument instead, ie x3/w3. + ld1 {v0.4s},[x2],#16 + ld1 {v2.16b},[x0] + sub w3,w3,#2 + ld1 {v1.4s},[x2],#16 + +Loop_dec: + aesd v2.16b,v0.16b + aesimc v2.16b,v2.16b + ld1 {v0.4s},[x2],#16 + subs w3,w3,#2 + aesd v2.16b,v1.16b + aesimc v2.16b,v2.16b + ld1 {v1.4s},[x2],#16 + b.gt Loop_dec + + aesd v2.16b,v0.16b + aesimc v2.16b,v2.16b + ld1 {v0.4s},[x2] + aesd v2.16b,v1.16b + eor v2.16b,v2.16b,v0.16b + + st1 {v2.16b},[x1] + ret + +ENTRY_ALIGN(aes_v8_ecb_encrypt, 32) + subs x2,x2,#16 + // Original input data size bigger than 16, jump to big size processing. + b.ne Lecb_big_size + ld1 {v0.16b},[x0] + cmp w4,#0 // en- or decrypting? + ldr w5,[x3,#240] + ld1 {v5.4s,v6.4s},[x3],#32 // load key schedule... + + b.eq Lecb_small_dec + aese v0.16b,v5.16b + aesmc v0.16b,v0.16b + ld1 {v16.4s,v17.4s},[x3],#32 // load key schedule... + aese v0.16b,v6.16b + aesmc v0.16b,v0.16b + subs w5,w5,#10 // if rounds==10, jump to aes-128-ecb processing + b.eq Lecb_128_enc +Lecb_round_loop: + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + ld1 {v16.4s},[x3],#16 // load key schedule... + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + ld1 {v17.4s},[x3],#16 // load key schedule... + subs w5,w5,#2 // bias + b.gt Lecb_round_loop +Lecb_128_enc: + ld1 {v18.4s,v19.4s},[x3],#32 // load key schedule... + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + ld1 {v20.4s,v21.4s},[x3],#32 // load key schedule... + aese v0.16b,v18.16b + aesmc v0.16b,v0.16b + aese v0.16b,v19.16b + aesmc v0.16b,v0.16b + ld1 {v22.4s,v23.4s},[x3],#32 // load key schedule... + aese v0.16b,v20.16b + aesmc v0.16b,v0.16b + aese v0.16b,v21.16b + aesmc v0.16b,v0.16b + ld1 {v7.4s},[x3] + aese v0.16b,v22.16b + aesmc v0.16b,v0.16b + aese v0.16b,v23.16b + eor v0.16b,v0.16b,v7.16b + st1 {v0.16b},[x1] + b Lecb_Final_abort +Lecb_small_dec: + aesd v0.16b,v5.16b + aesimc v0.16b,v0.16b + ld1 {v16.4s,v17.4s},[x3],#32 // load key schedule... + aesd v0.16b,v6.16b + aesimc v0.16b,v0.16b + subs w5,w5,#10 // bias + b.eq Lecb_128_dec +Lecb_dec_round_loop: + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + ld1 {v16.4s},[x3],#16 // load key schedule... + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + ld1 {v17.4s},[x3],#16 // load key schedule... + subs w5,w5,#2 // bias + b.gt Lecb_dec_round_loop +Lecb_128_dec: + ld1 {v18.4s,v19.4s},[x3],#32 // load key schedule... + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + ld1 {v20.4s,v21.4s},[x3],#32 // load key schedule... + aesd v0.16b,v18.16b + aesimc v0.16b,v0.16b + aesd v0.16b,v19.16b + aesimc v0.16b,v0.16b + ld1 {v22.4s,v23.4s},[x3],#32 // load key schedule... + aesd v0.16b,v20.16b + aesimc v0.16b,v0.16b + aesd v0.16b,v21.16b + aesimc v0.16b,v0.16b + ld1 {v7.4s},[x3] + aesd v0.16b,v22.16b + aesimc v0.16b,v0.16b + aesd v0.16b,v23.16b + eor v0.16b,v0.16b,v7.16b + st1 {v0.16b},[x1] + b Lecb_Final_abort +Lecb_big_size: + stp x29,x30,[sp,#-16]! + add x29,sp,#0 + mov x8,#16 + b.lo Lecb_done + csel x8,xzr,x8,eq + + cmp w4,#0 // en- or decrypting? + ldr w5,[x3,#240] + and x2,x2,#-16 + ld1 {v0.16b},[x0],x8 + + ld1 {v16.4s,v17.4s},[x3] // load key schedule... + sub w5,w5,#6 + add x7,x3,x5,lsl#4 // pointer to last 7 round keys + sub w5,w5,#2 + ld1 {v18.4s,v19.4s},[x7],#32 + ld1 {v20.4s,v21.4s},[x7],#32 + ld1 {v22.4s,v23.4s},[x7],#32 + ld1 {v7.4s},[x7] + + add x7,x3,#32 + mov w6,w5 + b.eq Lecb_dec + + ld1 {v1.16b},[x0],#16 + subs x2,x2,#32 // bias + add w6,w5,#2 + orr v3.16b,v1.16b,v1.16b + orr v24.16b,v1.16b,v1.16b + orr v1.16b,v0.16b,v0.16b + b.lo Lecb_enc_tail + + orr v1.16b,v3.16b,v3.16b + ld1 {v24.16b},[x0],#16 + cmp x2,#32 + b.lo Loop3x_ecb_enc + + ld1 {v25.16b},[x0],#16 + ld1 {v26.16b},[x0],#16 + sub x2,x2,#32 // bias + mov w6,w5 + +Loop5x_ecb_enc: + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v24.16b,v16.16b + aesmc v24.16b,v24.16b + aese v25.16b,v16.16b + aesmc v25.16b,v25.16b + aese v26.16b,v16.16b + aesmc v26.16b,v26.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v24.16b,v17.16b + aesmc v24.16b,v24.16b + aese v25.16b,v17.16b + aesmc v25.16b,v25.16b + aese v26.16b,v17.16b + aesmc v26.16b,v26.16b + ld1 {v17.4s},[x7],#16 + b.gt Loop5x_ecb_enc + + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v24.16b,v16.16b + aesmc v24.16b,v24.16b + aese v25.16b,v16.16b + aesmc v25.16b,v25.16b + aese v26.16b,v16.16b + aesmc v26.16b,v26.16b + cmp x2,#0x40 // because Lecb_enc_tail4x + sub x2,x2,#0x50 + + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v24.16b,v17.16b + aesmc v24.16b,v24.16b + aese v25.16b,v17.16b + aesmc v25.16b,v25.16b + aese v26.16b,v17.16b + aesmc v26.16b,v26.16b + csel x6,xzr,x2,gt // borrow x6, w6, "gt" is not typo + mov x7,x3 + + aese v0.16b,v18.16b + aesmc v0.16b,v0.16b + aese v1.16b,v18.16b + aesmc v1.16b,v1.16b + aese v24.16b,v18.16b + aesmc v24.16b,v24.16b + aese v25.16b,v18.16b + aesmc v25.16b,v25.16b + aese v26.16b,v18.16b + aesmc v26.16b,v26.16b + add x0,x0,x6 // x0 is adjusted in such way that + // at exit from the loop v1.16b-v26.16b + // are loaded with last "words" + add x6,x2,#0x60 // because Lecb_enc_tail4x + + aese v0.16b,v19.16b + aesmc v0.16b,v0.16b + aese v1.16b,v19.16b + aesmc v1.16b,v1.16b + aese v24.16b,v19.16b + aesmc v24.16b,v24.16b + aese v25.16b,v19.16b + aesmc v25.16b,v25.16b + aese v26.16b,v19.16b + aesmc v26.16b,v26.16b + + aese v0.16b,v20.16b + aesmc v0.16b,v0.16b + aese v1.16b,v20.16b + aesmc v1.16b,v1.16b + aese v24.16b,v20.16b + aesmc v24.16b,v24.16b + aese v25.16b,v20.16b + aesmc v25.16b,v25.16b + aese v26.16b,v20.16b + aesmc v26.16b,v26.16b + + aese v0.16b,v21.16b + aesmc v0.16b,v0.16b + aese v1.16b,v21.16b + aesmc v1.16b,v1.16b + aese v24.16b,v21.16b + aesmc v24.16b,v24.16b + aese v25.16b,v21.16b + aesmc v25.16b,v25.16b + aese v26.16b,v21.16b + aesmc v26.16b,v26.16b + + aese v0.16b,v22.16b + aesmc v0.16b,v0.16b + aese v1.16b,v22.16b + aesmc v1.16b,v1.16b + aese v24.16b,v22.16b + aesmc v24.16b,v24.16b + aese v25.16b,v22.16b + aesmc v25.16b,v25.16b + aese v26.16b,v22.16b + aesmc v26.16b,v26.16b + + aese v0.16b,v23.16b + ld1 {v2.16b},[x0],#16 + aese v1.16b,v23.16b + ld1 {v3.16b},[x0],#16 + aese v24.16b,v23.16b + ld1 {v27.16b},[x0],#16 + aese v25.16b,v23.16b + ld1 {v28.16b},[x0],#16 + aese v26.16b,v23.16b + ld1 {v29.16b},[x0],#16 + cbz x6,Lecb_enc_tail4x + ld1 {v16.4s},[x7],#16 // re-pre-load rndkey[0] + eor v4.16b,v7.16b,v0.16b + orr v0.16b,v2.16b,v2.16b + eor v5.16b,v7.16b,v1.16b + orr v1.16b,v3.16b,v3.16b + eor v17.16b,v7.16b,v24.16b + orr v24.16b,v27.16b,v27.16b + eor v30.16b,v7.16b,v25.16b + orr v25.16b,v28.16b,v28.16b + eor v31.16b,v7.16b,v26.16b + st1 {v4.16b},[x1],#16 + orr v26.16b,v29.16b,v29.16b + st1 {v5.16b},[x1],#16 + mov w6,w5 + st1 {v17.16b},[x1],#16 + ld1 {v17.4s},[x7],#16 // re-pre-load rndkey[1] + st1 {v30.16b},[x1],#16 + st1 {v31.16b},[x1],#16 + b.hs Loop5x_ecb_enc + + add x2,x2,#0x50 + cbz x2,Lecb_done + + add w6,w5,#2 + subs x2,x2,#0x30 + orr v0.16b,v27.16b,v27.16b + orr v1.16b,v28.16b,v28.16b + orr v24.16b,v29.16b,v29.16b + b.lo Lecb_enc_tail + + b Loop3x_ecb_enc + +.align 4 +Lecb_enc_tail4x: + eor v5.16b,v7.16b,v1.16b + eor v17.16b,v7.16b,v24.16b + eor v30.16b,v7.16b,v25.16b + eor v31.16b,v7.16b,v26.16b + st1 {v5.16b},[x1],#16 + st1 {v17.16b},[x1],#16 + st1 {v30.16b},[x1],#16 + st1 {v31.16b},[x1],#16 + + b Lecb_done +.align 4 +Loop3x_ecb_enc: + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v24.16b,v16.16b + aesmc v24.16b,v24.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v24.16b,v17.16b + aesmc v24.16b,v24.16b + ld1 {v17.4s},[x7],#16 + b.gt Loop3x_ecb_enc + + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v24.16b,v16.16b + aesmc v24.16b,v24.16b + subs x2,x2,#0x30 + csel x6,x2,x6,lo // x6, w6, is zero at this point + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v24.16b,v17.16b + aesmc v24.16b,v24.16b + add x0,x0,x6 // x0 is adjusted in such way that + // at exit from the loop v1.16b-v24.16b + // are loaded with last "words" + mov x7,x3 + aese v0.16b,v20.16b + aesmc v0.16b,v0.16b + aese v1.16b,v20.16b + aesmc v1.16b,v1.16b + aese v24.16b,v20.16b + aesmc v24.16b,v24.16b + ld1 {v2.16b},[x0],#16 + aese v0.16b,v21.16b + aesmc v0.16b,v0.16b + aese v1.16b,v21.16b + aesmc v1.16b,v1.16b + aese v24.16b,v21.16b + aesmc v24.16b,v24.16b + ld1 {v3.16b},[x0],#16 + aese v0.16b,v22.16b + aesmc v0.16b,v0.16b + aese v1.16b,v22.16b + aesmc v1.16b,v1.16b + aese v24.16b,v22.16b + aesmc v24.16b,v24.16b + ld1 {v27.16b},[x0],#16 + aese v0.16b,v23.16b + aese v1.16b,v23.16b + aese v24.16b,v23.16b + ld1 {v16.4s},[x7],#16 // re-pre-load rndkey[0] + add w6,w5,#2 + eor v4.16b,v7.16b,v0.16b + eor v5.16b,v7.16b,v1.16b + eor v24.16b,v24.16b,v7.16b + ld1 {v17.4s},[x7],#16 // re-pre-load rndkey[1] + st1 {v4.16b},[x1],#16 + orr v0.16b,v2.16b,v2.16b + st1 {v5.16b},[x1],#16 + orr v1.16b,v3.16b,v3.16b + st1 {v24.16b},[x1],#16 + orr v24.16b,v27.16b,v27.16b + b.hs Loop3x_ecb_enc + + cmn x2,#0x30 + b.eq Lecb_done + nop + +Lecb_enc_tail: + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v24.16b,v16.16b + aesmc v24.16b,v24.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v24.16b,v17.16b + aesmc v24.16b,v24.16b + ld1 {v17.4s},[x7],#16 + b.gt Lecb_enc_tail + + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v24.16b,v16.16b + aesmc v24.16b,v24.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v24.16b,v17.16b + aesmc v24.16b,v24.16b + aese v1.16b,v20.16b + aesmc v1.16b,v1.16b + aese v24.16b,v20.16b + aesmc v24.16b,v24.16b + cmn x2,#0x20 + aese v1.16b,v21.16b + aesmc v1.16b,v1.16b + aese v24.16b,v21.16b + aesmc v24.16b,v24.16b + aese v1.16b,v22.16b + aesmc v1.16b,v1.16b + aese v24.16b,v22.16b + aesmc v24.16b,v24.16b + aese v1.16b,v23.16b + aese v24.16b,v23.16b + b.eq Lecb_enc_one + eor v5.16b,v7.16b,v1.16b + eor v17.16b,v7.16b,v24.16b + st1 {v5.16b},[x1],#16 + st1 {v17.16b},[x1],#16 + b Lecb_done + +Lecb_enc_one: + eor v5.16b,v7.16b,v24.16b + st1 {v5.16b},[x1],#16 + b Lecb_done +.balign 32 +Lecb_dec: + ld1 {v1.16b},[x0],#16 + subs x2,x2,#32 // bias + add w6,w5,#2 + orr v3.16b,v1.16b,v1.16b + orr v24.16b,v1.16b,v1.16b + orr v1.16b,v0.16b,v0.16b + b.lo Lecb_dec_tail + + orr v1.16b,v3.16b,v3.16b + ld1 {v24.16b},[x0],#16 + cmp x2,#32 + b.lo Loop3x_ecb_dec + + ld1 {v25.16b},[x0],#16 + ld1 {v26.16b},[x0],#16 + sub x2,x2,#32 // bias + mov w6,w5 + +Loop5x_ecb_dec: + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v16.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v16.16b + aesimc v26.16b,v26.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v17.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v17.16b + aesimc v26.16b,v26.16b + ld1 {v17.4s},[x7],#16 + b.gt Loop5x_ecb_dec + + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v16.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v16.16b + aesimc v26.16b,v26.16b + cmp x2,#0x40 // because Lecb_tail4x + sub x2,x2,#0x50 + + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v17.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v17.16b + aesimc v26.16b,v26.16b + csel x6,xzr,x2,gt // borrow x6, w6, "gt" is not typo + mov x7,x3 + + aesd v0.16b,v18.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v18.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v18.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v18.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v18.16b + aesimc v26.16b,v26.16b + add x0,x0,x6 // x0 is adjusted in such way that + // at exit from the loop v1.16b-v26.16b + // are loaded with last "words" + add x6,x2,#0x60 // because Lecb_tail4x + + aesd v0.16b,v19.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v19.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v19.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v19.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v19.16b + aesimc v26.16b,v26.16b + + aesd v0.16b,v20.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v20.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v20.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v20.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v20.16b + aesimc v26.16b,v26.16b + + aesd v0.16b,v21.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v21.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v21.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v21.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v21.16b + aesimc v26.16b,v26.16b + + aesd v0.16b,v22.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v22.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v22.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v22.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v22.16b + aesimc v26.16b,v26.16b + + aesd v0.16b,v23.16b + ld1 {v2.16b},[x0],#16 + aesd v1.16b,v23.16b + ld1 {v3.16b},[x0],#16 + aesd v24.16b,v23.16b + ld1 {v27.16b},[x0],#16 + aesd v25.16b,v23.16b + ld1 {v28.16b},[x0],#16 + aesd v26.16b,v23.16b + ld1 {v29.16b},[x0],#16 + cbz x6,Lecb_tail4x + ld1 {v16.4s},[x7],#16 // re-pre-load rndkey[0] + eor v4.16b,v7.16b,v0.16b + orr v0.16b,v2.16b,v2.16b + eor v5.16b,v7.16b,v1.16b + orr v1.16b,v3.16b,v3.16b + eor v17.16b,v7.16b,v24.16b + orr v24.16b,v27.16b,v27.16b + eor v30.16b,v7.16b,v25.16b + orr v25.16b,v28.16b,v28.16b + eor v31.16b,v7.16b,v26.16b + st1 {v4.16b},[x1],#16 + orr v26.16b,v29.16b,v29.16b + st1 {v5.16b},[x1],#16 + mov w6,w5 + st1 {v17.16b},[x1],#16 + ld1 {v17.4s},[x7],#16 // re-pre-load rndkey[1] + st1 {v30.16b},[x1],#16 + st1 {v31.16b},[x1],#16 + b.hs Loop5x_ecb_dec + + add x2,x2,#0x50 + cbz x2,Lecb_done + + add w6,w5,#2 + subs x2,x2,#0x30 + orr v0.16b,v27.16b,v27.16b + orr v1.16b,v28.16b,v28.16b + orr v24.16b,v29.16b,v29.16b + b.lo Lecb_dec_tail + + b Loop3x_ecb_dec + +.balign 16 +Lecb_tail4x: + eor v5.16b,v7.16b,v1.16b + eor v17.16b,v7.16b,v24.16b + eor v30.16b,v7.16b,v25.16b + eor v31.16b,v7.16b,v26.16b + st1 {v5.16b},[x1],#16 + st1 {v17.16b},[x1],#16 + st1 {v30.16b},[x1],#16 + st1 {v31.16b},[x1],#16 + + b Lecb_done +.balign 16 +Loop3x_ecb_dec: + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + ld1 {v17.4s},[x7],#16 + b.gt Loop3x_ecb_dec + + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + subs x2,x2,#0x30 + csel x6,x2,x6,lo // x6, w6, is zero at this point + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + add x0,x0,x6 // x0 is adjusted in such way that + // at exit from the loop v1.16b-v24.16b + // are loaded with last "words" + mov x7,x3 + aesd v0.16b,v20.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v20.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v20.16b + aesimc v24.16b,v24.16b + ld1 {v2.16b},[x0],#16 + aesd v0.16b,v21.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v21.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v21.16b + aesimc v24.16b,v24.16b + ld1 {v3.16b},[x0],#16 + aesd v0.16b,v22.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v22.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v22.16b + aesimc v24.16b,v24.16b + ld1 {v27.16b},[x0],#16 + aesd v0.16b,v23.16b + aesd v1.16b,v23.16b + aesd v24.16b,v23.16b + ld1 {v16.4s},[x7],#16 // re-pre-load rndkey[0] + add w6,w5,#2 + eor v4.16b,v7.16b,v0.16b + eor v5.16b,v7.16b,v1.16b + eor v24.16b,v24.16b,v7.16b + ld1 {v17.4s},[x7],#16 // re-pre-load rndkey[1] + st1 {v4.16b},[x1],#16 + orr v0.16b,v2.16b,v2.16b + st1 {v5.16b},[x1],#16 + orr v1.16b,v3.16b,v3.16b + st1 {v24.16b},[x1],#16 + orr v24.16b,v27.16b,v27.16b + b.hs Loop3x_ecb_dec + + cmn x2,#0x30 + b.eq Lecb_done + nop + +Lecb_dec_tail: + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + ld1 {v17.4s},[x7],#16 + b.gt Lecb_dec_tail + + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + aesd v1.16b,v20.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v20.16b + aesimc v24.16b,v24.16b + cmn x2,#0x20 + aesd v1.16b,v21.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v21.16b + aesimc v24.16b,v24.16b + aesd v1.16b,v22.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v22.16b + aesimc v24.16b,v24.16b + aesd v1.16b,v23.16b + aesd v24.16b,v23.16b + b.eq Lecb_dec_one + eor v5.16b,v7.16b,v1.16b + eor v17.16b,v7.16b,v24.16b + st1 {v5.16b},[x1],#16 + st1 {v17.16b},[x1],#16 + b Lecb_done + +Lecb_dec_one: + eor v5.16b,v7.16b,v24.16b + st1 {v5.16b},[x1],#16 + +Lecb_done: + ldr x29,[sp],#16 +Lecb_Final_abort: + ret + +ENTRY_ALIGN(aes_v8_cbc_encrypt, 32) + // Armv8.3-A PAuth: even though x30 is pushed to stack it is not popped later. + stp x29,x30,[sp,#-16]! + add x29,sp,#0 + subs x2,x2,#16 + mov x8,#16 + b.lo Lcbc_abort + csel x8,xzr,x8,eq + + cmp w5,#0 // en- or decrypting? + ldr w5,[x3,#240] + and x2,x2,#-16 + ld1 {v6.16b},[x4] + ld1 {v0.16b},[x0],x8 + + ld1 {v16.4s,v17.4s},[x3] // load key schedule... + sub w5,w5,#6 + add x7,x3,x5,lsl#4 // pointer to last 7 round keys + sub w5,w5,#2 + ld1 {v18.4s,v19.4s},[x7],#32 + ld1 {v20.4s,v21.4s},[x7],#32 + ld1 {v22.4s,v23.4s},[x7],#32 + ld1 {v7.4s},[x7] + + add x7,x3,#32 + mov w6,w5 + b.eq Lcbc_dec + + cmp w5,#2 + eor v0.16b,v0.16b,v6.16b + eor v5.16b,v16.16b,v7.16b + b.eq Lcbc_enc128 + + ld1 {v2.4s,v3.4s},[x7] + add x7,x3,#16 + add x6,x3,#16*4 + add x12,x3,#16*5 + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + add x14,x3,#16*6 + add x3,x3,#16*7 + b Lenter_cbc_enc + +.balign 16 +Loop_cbc_enc: + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + st1 {v6.16b},[x1],#16 +Lenter_cbc_enc: + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + aese v0.16b,v2.16b + aesmc v0.16b,v0.16b + ld1 {v16.4s},[x6] + cmp w5,#4 + aese v0.16b,v3.16b + aesmc v0.16b,v0.16b + ld1 {v17.4s},[x12] + b.eq Lcbc_enc192 + + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + ld1 {v16.4s},[x14] + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + ld1 {v17.4s},[x3] + nop + +Lcbc_enc192: + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + subs x2,x2,#16 + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + csel x8,xzr,x8,eq + aese v0.16b,v18.16b + aesmc v0.16b,v0.16b + aese v0.16b,v19.16b + aesmc v0.16b,v0.16b + ld1 {v16.16b},[x0],x8 + aese v0.16b,v20.16b + aesmc v0.16b,v0.16b + eor v16.16b,v16.16b,v5.16b + aese v0.16b,v21.16b + aesmc v0.16b,v0.16b + ld1 {v17.4s},[x7] // re-pre-load rndkey[1] + aese v0.16b,v22.16b + aesmc v0.16b,v0.16b + aese v0.16b,v23.16b + eor v6.16b,v0.16b,v7.16b + b.hs Loop_cbc_enc + + st1 {v6.16b},[x1],#16 + b Lcbc_done + +.balign 32 +Lcbc_enc128: + ld1 {v2.4s,v3.4s},[x7] + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + b Lenter_cbc_enc128 +Loop_cbc_enc128: + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + st1 {v6.16b},[x1],#16 +Lenter_cbc_enc128: + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + subs x2,x2,#16 + aese v0.16b,v2.16b + aesmc v0.16b,v0.16b + csel x8,xzr,x8,eq + aese v0.16b,v3.16b + aesmc v0.16b,v0.16b + aese v0.16b,v18.16b + aesmc v0.16b,v0.16b + aese v0.16b,v19.16b + aesmc v0.16b,v0.16b + ld1 {v16.16b},[x0],x8 + aese v0.16b,v20.16b + aesmc v0.16b,v0.16b + aese v0.16b,v21.16b + aesmc v0.16b,v0.16b + aese v0.16b,v22.16b + aesmc v0.16b,v0.16b + eor v16.16b,v16.16b,v5.16b + aese v0.16b,v23.16b + eor v6.16b,v0.16b,v7.16b + b.hs Loop_cbc_enc128 + + st1 {v6.16b},[x1],#16 + b Lcbc_done +.balign 32 +Lcbc_dec: + ld1 {v24.16b},[x0],#16 + subs x2,x2,#32 // bias + add w6,w5,#2 + orr v3.16b,v0.16b,v0.16b + orr v1.16b,v0.16b,v0.16b + orr v27.16b,v24.16b,v24.16b + b.lo Lcbc_dec_tail + + orr v1.16b,v24.16b,v24.16b + ld1 {v24.16b},[x0],#16 + orr v2.16b,v0.16b,v0.16b + orr v3.16b,v1.16b,v1.16b + orr v27.16b,v24.16b,v24.16b + cmp x2,#32 + b.lo Loop3x_cbc_dec + + ld1 {v25.16b},[x0],#16 + ld1 {v26.16b},[x0],#16 + sub x2,x2,#32 // bias + mov w6,w5 + orr v28.16b,v25.16b,v25.16b + orr v29.16b,v26.16b,v26.16b + +Loop5x_cbc_dec: + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v16.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v16.16b + aesimc v26.16b,v26.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v17.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v17.16b + aesimc v26.16b,v26.16b + ld1 {v17.4s},[x7],#16 + b.gt Loop5x_cbc_dec + + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v16.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v16.16b + aesimc v26.16b,v26.16b + cmp x2,#0x40 // because Lcbc_tail4x + sub x2,x2,#0x50 + + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v17.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v17.16b + aesimc v26.16b,v26.16b + csel x6,xzr,x2,gt // borrow x6, w6, "gt" is not typo + mov x7,x3 + + aesd v0.16b,v18.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v18.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v18.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v18.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v18.16b + aesimc v26.16b,v26.16b + add x0,x0,x6 // x0 is adjusted in such way that + // at exit from the loop v1.16b-v26.16b + // are loaded with last "words" + add x6,x2,#0x60 // because Lcbc_tail4x + + aesd v0.16b,v19.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v19.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v19.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v19.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v19.16b + aesimc v26.16b,v26.16b + + aesd v0.16b,v20.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v20.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v20.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v20.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v20.16b + aesimc v26.16b,v26.16b + + aesd v0.16b,v21.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v21.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v21.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v21.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v21.16b + aesimc v26.16b,v26.16b + + aesd v0.16b,v22.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v22.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v22.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v22.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v22.16b + aesimc v26.16b,v26.16b + + eor v4.16b,v6.16b,v7.16b + aesd v0.16b,v23.16b + eor v5.16b,v2.16b,v7.16b + ld1 {v2.16b},[x0],#16 + aesd v1.16b,v23.16b + eor v17.16b,v3.16b,v7.16b + ld1 {v3.16b},[x0],#16 + aesd v24.16b,v23.16b + eor v30.16b,v27.16b,v7.16b + ld1 {v27.16b},[x0],#16 + aesd v25.16b,v23.16b + eor v31.16b,v28.16b,v7.16b + ld1 {v28.16b},[x0],#16 + aesd v26.16b,v23.16b + orr v6.16b,v29.16b,v29.16b + ld1 {v29.16b},[x0],#16 + cbz x6,Lcbc_tail4x + ld1 {v16.4s},[x7],#16 // re-pre-load rndkey[0] + eor v4.16b,v4.16b,v0.16b + orr v0.16b,v2.16b,v2.16b + eor v5.16b,v5.16b,v1.16b + orr v1.16b,v3.16b,v3.16b + eor v17.16b,v17.16b,v24.16b + orr v24.16b,v27.16b,v27.16b + eor v30.16b,v30.16b,v25.16b + orr v25.16b,v28.16b,v28.16b + eor v31.16b,v31.16b,v26.16b + st1 {v4.16b},[x1],#16 + orr v26.16b,v29.16b,v29.16b + st1 {v5.16b},[x1],#16 + mov w6,w5 + st1 {v17.16b},[x1],#16 + ld1 {v17.4s},[x7],#16 // re-pre-load rndkey[1] + st1 {v30.16b},[x1],#16 + st1 {v31.16b},[x1],#16 + b.hs Loop5x_cbc_dec + + add x2,x2,#0x50 + cbz x2,Lcbc_done + + add w6,w5,#2 + subs x2,x2,#0x30 + orr v0.16b,v27.16b,v27.16b + orr v2.16b,v27.16b,v27.16b + orr v1.16b,v28.16b,v28.16b + orr v3.16b,v28.16b,v28.16b + orr v24.16b,v29.16b,v29.16b + orr v27.16b,v29.16b,v29.16b + b.lo Lcbc_dec_tail + + b Loop3x_cbc_dec + +.balign 16 +Lcbc_tail4x: + eor v5.16b,v4.16b,v1.16b + eor v17.16b,v17.16b,v24.16b + eor v30.16b,v30.16b,v25.16b + eor v31.16b,v31.16b,v26.16b + st1 {v5.16b},[x1],#16 + st1 {v17.16b},[x1],#16 + st1 {v30.16b},[x1],#16 + st1 {v31.16b},[x1],#16 + + b Lcbc_done +.balign 16 +Loop3x_cbc_dec: + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + ld1 {v17.4s},[x7],#16 + b.gt Loop3x_cbc_dec + + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + eor v4.16b,v6.16b,v7.16b + subs x2,x2,#0x30 + eor v5.16b,v2.16b,v7.16b + csel x6,x2,x6,lo // x6, w6, is zero at this point + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + eor v17.16b,v3.16b,v7.16b + add x0,x0,x6 // x0 is adjusted in such way that + // at exit from the loop v1.16b-v24.16b + // are loaded with last "words" + orr v6.16b,v27.16b,v27.16b + mov x7,x3 + aesd v0.16b,v20.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v20.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v20.16b + aesimc v24.16b,v24.16b + ld1 {v2.16b},[x0],#16 + aesd v0.16b,v21.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v21.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v21.16b + aesimc v24.16b,v24.16b + ld1 {v3.16b},[x0],#16 + aesd v0.16b,v22.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v22.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v22.16b + aesimc v24.16b,v24.16b + ld1 {v27.16b},[x0],#16 + aesd v0.16b,v23.16b + aesd v1.16b,v23.16b + aesd v24.16b,v23.16b + ld1 {v16.4s},[x7],#16 // re-pre-load rndkey[0] + add w6,w5,#2 + eor v4.16b,v4.16b,v0.16b + eor v5.16b,v5.16b,v1.16b + eor v24.16b,v24.16b,v17.16b + ld1 {v17.4s},[x7],#16 // re-pre-load rndkey[1] + st1 {v4.16b},[x1],#16 + orr v0.16b,v2.16b,v2.16b + st1 {v5.16b},[x1],#16 + orr v1.16b,v3.16b,v3.16b + st1 {v24.16b},[x1],#16 + orr v24.16b,v27.16b,v27.16b + b.hs Loop3x_cbc_dec + + cmn x2,#0x30 + b.eq Lcbc_done + nop + +Lcbc_dec_tail: + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + ld1 {v17.4s},[x7],#16 + b.gt Lcbc_dec_tail + + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + aesd v1.16b,v20.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v20.16b + aesimc v24.16b,v24.16b + cmn x2,#0x20 + aesd v1.16b,v21.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v21.16b + aesimc v24.16b,v24.16b + eor v5.16b,v6.16b,v7.16b + aesd v1.16b,v22.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v22.16b + aesimc v24.16b,v24.16b + eor v17.16b,v3.16b,v7.16b + aesd v1.16b,v23.16b + aesd v24.16b,v23.16b + b.eq Lcbc_dec_one + eor v5.16b,v5.16b,v1.16b + eor v17.16b,v17.16b,v24.16b + orr v6.16b,v27.16b,v27.16b + st1 {v5.16b},[x1],#16 + st1 {v17.16b},[x1],#16 + b Lcbc_done + +Lcbc_dec_one: + eor v5.16b,v5.16b,v24.16b + orr v6.16b,v27.16b,v27.16b + st1 {v5.16b},[x1],#16 + +Lcbc_done: + st1 {v6.16b},[x4] +Lcbc_abort: + ldr x29,[sp],#16 + ret + +ENTRY_ALIGN(aes_v8_ctr32_encrypt_blocks, 32) + // Armv8.3-A PAuth: even though x30 is pushed to stack it is not popped later. + stp x29,x30,[sp,#-16]! + add x29,sp,#0 + ldr w5,[x3,#240] + + ldr w8, [x4, #12] +#ifdef __AARCH64EB__ + ld1 {v0.16b},[x4] +#else + ld1 {v0.4s},[x4] +#endif + ld1 {v16.4s,v17.4s},[x3] // load key schedule... + sub w5,w5,#4 + mov x12,#16 + cmp x2,#2 + add x7,x3,x5,lsl#4 // pointer to last 5 round keys + sub w5,w5,#2 + ld1 {v20.4s,v21.4s},[x7],#32 + ld1 {v22.4s,v23.4s},[x7],#32 + ld1 {v7.4s},[x7] + add x7,x3,#32 + mov w6,w5 + csel x12,xzr,x12,lo +#ifndef __AARCH64EB__ + rev w8, w8 +#endif + orr v1.16b,v0.16b,v0.16b + add w10, w8, #1 + orr v18.16b,v0.16b,v0.16b + add w8, w8, #2 + orr v6.16b,v0.16b,v0.16b + rev w10, w10 + mov v1.s[3],w10 + b.ls Lctr32_tail + rev w12, w8 + sub x2,x2,#3 // bias + mov v18.s[3],w12 + cmp x2,#32 + b.lo Loop3x_ctr32 + + add w13,w8,#1 + add w14,w8,#2 + orr v24.16b,v0.16b,v0.16b + rev w13,w13 + orr v25.16b,v0.16b,v0.16b + rev w14,w14 + mov v24.s[3],w13 + sub x2,x2,#2 // bias + mov v25.s[3],w14 + add w8,w8,#2 + b Loop5x_ctr32 + +.balign 16 +Loop5x_ctr32: + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v18.16b,v16.16b + aesmc v18.16b,v18.16b + aese v24.16b,v16.16b + aesmc v24.16b,v24.16b + aese v25.16b,v16.16b + aesmc v25.16b,v25.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v18.16b,v17.16b + aesmc v18.16b,v18.16b + aese v24.16b,v17.16b + aesmc v24.16b,v24.16b + aese v25.16b,v17.16b + aesmc v25.16b,v25.16b + ld1 {v17.4s},[x7],#16 + b.gt Loop5x_ctr32 + + mov x7,x3 + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v18.16b,v16.16b + aesmc v18.16b,v18.16b + aese v24.16b,v16.16b + aesmc v24.16b,v24.16b + aese v25.16b,v16.16b + aesmc v25.16b,v25.16b + ld1 {v16.4s},[x7],#16 // re-pre-load rndkey[0] + + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v18.16b,v17.16b + aesmc v18.16b,v18.16b + aese v24.16b,v17.16b + aesmc v24.16b,v24.16b + aese v25.16b,v17.16b + aesmc v25.16b,v25.16b + ld1 {v17.4s},[x7],#16 // re-pre-load rndkey[1] + + aese v0.16b,v20.16b + aesmc v0.16b,v0.16b + add w9,w8,#1 + add w10,w8,#2 + aese v1.16b,v20.16b + aesmc v1.16b,v1.16b + add w12,w8,#3 + add w13,w8,#4 + aese v18.16b,v20.16b + aesmc v18.16b,v18.16b + add w14,w8,#5 + rev w9,w9 + aese v24.16b,v20.16b + aesmc v24.16b,v24.16b + rev w10,w10 + rev w12,w12 + aese v25.16b,v20.16b + aesmc v25.16b,v25.16b + rev w13,w13 + rev w14,w14 + + aese v0.16b,v21.16b + aesmc v0.16b,v0.16b + aese v1.16b,v21.16b + aesmc v1.16b,v1.16b + aese v18.16b,v21.16b + aesmc v18.16b,v18.16b + aese v24.16b,v21.16b + aesmc v24.16b,v24.16b + aese v25.16b,v21.16b + aesmc v25.16b,v25.16b + + aese v0.16b,v22.16b + aesmc v0.16b,v0.16b + ld1 {v2.16b},[x0],#16 + aese v1.16b,v22.16b + aesmc v1.16b,v1.16b + ld1 {v3.16b},[x0],#16 + aese v18.16b,v22.16b + aesmc v18.16b,v18.16b + ld1 {v19.16b},[x0],#16 + aese v24.16b,v22.16b + aesmc v24.16b,v24.16b + ld1 {v26.16b},[x0],#16 + aese v25.16b,v22.16b + aesmc v25.16b,v25.16b + ld1 {v27.16b},[x0],#16 + + aese v0.16b,v23.16b + eor v2.16b,v2.16b,v7.16b + aese v1.16b,v23.16b + eor v3.16b,v3.16b,v7.16b + aese v18.16b,v23.16b + eor v19.16b,v19.16b,v7.16b + aese v24.16b,v23.16b + eor v26.16b,v26.16b,v7.16b + aese v25.16b,v23.16b + eor v27.16b,v27.16b,v7.16b + + eor v2.16b,v2.16b,v0.16b + orr v0.16b,v6.16b,v6.16b + eor v3.16b,v3.16b,v1.16b + orr v1.16b,v6.16b,v6.16b + eor v19.16b,v19.16b,v18.16b + orr v18.16b,v6.16b,v6.16b + eor v26.16b,v26.16b,v24.16b + orr v24.16b,v6.16b,v6.16b + eor v27.16b,v27.16b,v25.16b + orr v25.16b,v6.16b,v6.16b + + st1 {v2.16b},[x1],#16 + mov v0.s[3],w9 + st1 {v3.16b},[x1],#16 + mov v1.s[3],w10 + st1 {v19.16b},[x1],#16 + mov v18.s[3],w12 + st1 {v26.16b},[x1],#16 + mov v24.s[3],w13 + st1 {v27.16b},[x1],#16 + mov v25.s[3],w14 + + mov w6,w5 + cbz x2,Lctr32_done + + add w8,w8,#5 + subs x2,x2,#5 + b.hs Loop5x_ctr32 + + add x2,x2,#5 + sub w8,w8,#5 + + cmp x2,#2 + mov x12,#16 + csel x12,xzr,x12,lo + b.ls Lctr32_tail + + sub x2,x2,#3 // bias + add w8,w8,#3 + b Loop3x_ctr32 + +.balign 16 +Loop3x_ctr32: + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v18.16b,v16.16b + aesmc v18.16b,v18.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v18.16b,v17.16b + aesmc v18.16b,v18.16b + ld1 {v17.4s},[x7],#16 + b.gt Loop3x_ctr32 + + aese v0.16b,v16.16b + aesmc v4.16b,v0.16b + aese v1.16b,v16.16b + aesmc v5.16b,v1.16b + ld1 {v2.16b},[x0],#16 + orr v0.16b,v6.16b,v6.16b + aese v18.16b,v16.16b + aesmc v18.16b,v18.16b + ld1 {v3.16b},[x0],#16 + orr v1.16b,v6.16b,v6.16b + aese v4.16b,v17.16b + aesmc v4.16b,v4.16b + aese v5.16b,v17.16b + aesmc v5.16b,v5.16b + ld1 {v19.16b},[x0],#16 + mov x7,x3 + aese v18.16b,v17.16b + aesmc v17.16b,v18.16b + orr v18.16b,v6.16b,v6.16b + add w9,w8,#1 + aese v4.16b,v20.16b + aesmc v4.16b,v4.16b + aese v5.16b,v20.16b + aesmc v5.16b,v5.16b + eor v2.16b,v2.16b,v7.16b + add w10,w8,#2 + aese v17.16b,v20.16b + aesmc v17.16b,v17.16b + eor v3.16b,v3.16b,v7.16b + add w8,w8,#3 + aese v4.16b,v21.16b + aesmc v4.16b,v4.16b + aese v5.16b,v21.16b + aesmc v5.16b,v5.16b + eor v19.16b,v19.16b,v7.16b + rev w9,w9 + aese v17.16b,v21.16b + aesmc v17.16b,v17.16b + mov v0.s[3], w9 + rev w10,w10 + aese v4.16b,v22.16b + aesmc v4.16b,v4.16b + aese v5.16b,v22.16b + aesmc v5.16b,v5.16b + mov v1.s[3], w10 + rev w12,w8 + aese v17.16b,v22.16b + aesmc v17.16b,v17.16b + mov v18.s[3], w12 + subs x2,x2,#3 + aese v4.16b,v23.16b + aese v5.16b,v23.16b + aese v17.16b,v23.16b + + eor v2.16b,v2.16b,v4.16b + ld1 {v16.4s},[x7],#16 // re-pre-load rndkey[0] + st1 {v2.16b},[x1],#16 + eor v3.16b,v3.16b,v5.16b + mov w6,w5 + st1 {v3.16b},[x1],#16 + eor v19.16b,v19.16b,v17.16b + ld1 {v17.4s},[x7],#16 // re-pre-load rndkey[1] + st1 {v19.16b},[x1],#16 + b.hs Loop3x_ctr32 + + adds x2,x2,#3 + b.eq Lctr32_done + cmp x2,#1 + mov x12,#16 + csel x12,xzr,x12,eq + +Lctr32_tail: + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + ld1 {v17.4s},[x7],#16 + b.gt Lctr32_tail + + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + ld1 {v2.16b},[x0],x12 + aese v0.16b,v20.16b + aesmc v0.16b,v0.16b + aese v1.16b,v20.16b + aesmc v1.16b,v1.16b + ld1 {v3.16b},[x0] + aese v0.16b,v21.16b + aesmc v0.16b,v0.16b + aese v1.16b,v21.16b + aesmc v1.16b,v1.16b + eor v2.16b,v2.16b,v7.16b + aese v0.16b,v22.16b + aesmc v0.16b,v0.16b + aese v1.16b,v22.16b + aesmc v1.16b,v1.16b + eor v3.16b,v3.16b,v7.16b + aese v0.16b,v23.16b + aese v1.16b,v23.16b + + cmp x2,#1 + eor v2.16b,v2.16b,v0.16b + eor v3.16b,v3.16b,v1.16b + st1 {v2.16b},[x1],#16 + b.eq Lctr32_done + st1 {v3.16b},[x1] + +Lctr32_done: + ldr x29,[sp],#16 + ret + +ENTRY_ALIGN(aes_v8_xts_encrypt, 32) + cmp x2,#16 + // Original input data size bigger than 16, jump to big size processing. + b.ne Lxts_enc_big_size + // Encrypt the iv with key2, as the first XEX iv. + ldr w6,[x4,#240] + ld1 {v0.16b},[x4],#16 + ld1 {v6.16b},[x5] + sub w6,w6,#2 + ld1 {v1.16b},[x4],#16 + +Loop_enc_iv_enc: + aese v6.16b,v0.16b + aesmc v6.16b,v6.16b + ld1 {v0.4s},[x4],#16 + subs w6,w6,#2 + aese v6.16b,v1.16b + aesmc v6.16b,v6.16b + ld1 {v1.4s},[x4],#16 + b.gt Loop_enc_iv_enc + + aese v6.16b,v0.16b + aesmc v6.16b,v6.16b + ld1 {v0.4s},[x4] + aese v6.16b,v1.16b + eor v6.16b,v6.16b,v0.16b + + ld1 {v0.16b},[x0] + eor v0.16b,v6.16b,v0.16b + + ldr w6,[x3,#240] + ld1 {v28.4s,v29.4s},[x3],#32 // load key schedule... + + aese v0.16b,v28.16b + aesmc v0.16b,v0.16b + ld1 {v16.4s,v17.4s},[x3],#32 // load key schedule... + aese v0.16b,v29.16b + aesmc v0.16b,v0.16b + subs w6,w6,#10 // if rounds==10, jump to aes-128-xts processing + b.eq Lxts_128_enc +Lxts_enc_round_loop: + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + ld1 {v16.4s},[x3],#16 // load key schedule... + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + ld1 {v17.4s},[x3],#16 // load key schedule... + subs w6,w6,#2 // bias + b.gt Lxts_enc_round_loop +Lxts_128_enc: + ld1 {v18.4s,v19.4s},[x3],#32 // load key schedule... + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + ld1 {v20.4s,v21.4s},[x3],#32 // load key schedule... + aese v0.16b,v18.16b + aesmc v0.16b,v0.16b + aese v0.16b,v19.16b + aesmc v0.16b,v0.16b + ld1 {v22.4s,v23.4s},[x3],#32 // load key schedule... + aese v0.16b,v20.16b + aesmc v0.16b,v0.16b + aese v0.16b,v21.16b + aesmc v0.16b,v0.16b + ld1 {v7.4s},[x3] + aese v0.16b,v22.16b + aesmc v0.16b,v0.16b + aese v0.16b,v23.16b + eor v0.16b,v0.16b,v7.16b + eor v0.16b,v0.16b,v6.16b + st1 {v0.16b},[x1] + b Lxts_enc_final_abort + +.balign 16 +Lxts_enc_big_size: + stp x19,x20,[sp,#-64]! + stp x21,x22,[sp,#48] + stp d8,d9,[sp,#32] + stp d10,d11,[sp,#16] + + // tailcnt store the tail value of length%16. + and x21,x2,#0xf + and x2,x2,#-16 + subs x2,x2,#16 + mov x8,#16 + b.lo Lxts_abort + csel x8,xzr,x8,eq + + // Firstly, encrypt the iv with key2, as the first iv of XEX. + ldr w6,[x4,#240] + ld1 {v0.4s},[x4],#16 + ld1 {v6.16b},[x5] + sub w6,w6,#2 + ld1 {v1.4s},[x4],#16 + +Loop_iv_enc: + aese v6.16b,v0.16b + aesmc v6.16b,v6.16b + ld1 {v0.4s},[x4],#16 + subs w6,w6,#2 + aese v6.16b,v1.16b + aesmc v6.16b,v6.16b + ld1 {v1.4s},[x4],#16 + b.gt Loop_iv_enc + + aese v6.16b,v0.16b + aesmc v6.16b,v6.16b + ld1 {v0.4s},[x4] + aese v6.16b,v1.16b + eor v6.16b,v6.16b,v0.16b + + // The iv for second block + // x9- iv(low), x10 - iv(high) + // the five ivs stored into, v6.16b,v8.16b,v9.16b,v10.16b,v11.16b + fmov x9,d6 + fmov x10,v6.d[1] + mov w19,#0x87 + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr#31 + eor x9,x11,x9,lsl#1 + fmov d8,x9 + fmov v8.d[1],x10 + + ldr w5,[x3,#240] // next starting point + ld1 {v0.16b},[x0],x8 + + ld1 {v16.4s,v17.4s},[x3] // load key schedule... + sub w5,w5,#6 + add x7,x3,x5,lsl#4 // pointer to last 7 round keys + sub w5,w5,#2 + ld1 {v18.4s,v19.4s},[x7],#32 + ld1 {v20.4s,v21.4s},[x7],#32 + ld1 {v22.4s,v23.4s},[x7],#32 + ld1 {v7.4s},[x7] + + add x7,x3,#32 + mov w6,w5 + + // Encryption +Lxts_enc: + ld1 {v24.16b},[x0],#16 + subs x2,x2,#32 // bias + add w6,w5,#2 + orr v3.16b,v0.16b,v0.16b + orr v1.16b,v0.16b,v0.16b + orr v28.16b,v0.16b,v0.16b + orr v27.16b,v24.16b,v24.16b + orr v29.16b,v24.16b,v24.16b + b.lo Lxts_inner_enc_tail + eor v0.16b,v0.16b,v6.16b // before encryption, xor with iv + eor v24.16b,v24.16b,v8.16b + + // The iv for third block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr#31 + eor x9,x11,x9,lsl#1 + fmov d9,x9 + fmov v9.d[1],x10 + + + orr v1.16b,v24.16b,v24.16b + ld1 {v24.16b},[x0],#16 + orr v2.16b,v0.16b,v0.16b + orr v3.16b,v1.16b,v1.16b + eor v27.16b,v24.16b,v9.16b // the third block + eor v24.16b,v24.16b,v9.16b + cmp x2,#32 + b.lo Lxts_outer_enc_tail + + // The iv for fourth block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr#31 + eor x9,x11,x9,lsl#1 + fmov d10,x9 + fmov v10.d[1],x10 + + ld1 {v25.16b},[x0],#16 + // The iv for fifth block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr#31 + eor x9,x11,x9,lsl#1 + fmov d11,x9 + fmov v11.d[1],x10 + + ld1 {v26.16b},[x0],#16 + eor v25.16b,v25.16b,v10.16b // the fourth block + eor v26.16b,v26.16b,v11.16b + sub x2,x2,#32 // bias + mov w6,w5 + b Loop5x_xts_enc + +.balign 16 +Loop5x_xts_enc: + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v24.16b,v16.16b + aesmc v24.16b,v24.16b + aese v25.16b,v16.16b + aesmc v25.16b,v25.16b + aese v26.16b,v16.16b + aesmc v26.16b,v26.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v24.16b,v17.16b + aesmc v24.16b,v24.16b + aese v25.16b,v17.16b + aesmc v25.16b,v25.16b + aese v26.16b,v17.16b + aesmc v26.16b,v26.16b + ld1 {v17.4s},[x7],#16 + b.gt Loop5x_xts_enc + + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v24.16b,v16.16b + aesmc v24.16b,v24.16b + aese v25.16b,v16.16b + aesmc v25.16b,v25.16b + aese v26.16b,v16.16b + aesmc v26.16b,v26.16b + subs x2,x2,#0x50 // because Lxts_enc_tail4x + + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v24.16b,v17.16b + aesmc v24.16b,v24.16b + aese v25.16b,v17.16b + aesmc v25.16b,v25.16b + aese v26.16b,v17.16b + aesmc v26.16b,v26.16b + csel x6,xzr,x2,gt // borrow x6, w6, "gt" is not typo + mov x7,x3 + + aese v0.16b,v18.16b + aesmc v0.16b,v0.16b + aese v1.16b,v18.16b + aesmc v1.16b,v1.16b + aese v24.16b,v18.16b + aesmc v24.16b,v24.16b + aese v25.16b,v18.16b + aesmc v25.16b,v25.16b + aese v26.16b,v18.16b + aesmc v26.16b,v26.16b + add x0,x0,x6 // x0 is adjusted in such way that + // at exit from the loop v1.16b-v26.16b + // are loaded with last "words" + add x6,x2,#0x60 // because Lxts_enc_tail4x + + aese v0.16b,v19.16b + aesmc v0.16b,v0.16b + aese v1.16b,v19.16b + aesmc v1.16b,v1.16b + aese v24.16b,v19.16b + aesmc v24.16b,v24.16b + aese v25.16b,v19.16b + aesmc v25.16b,v25.16b + aese v26.16b,v19.16b + aesmc v26.16b,v26.16b + + aese v0.16b,v20.16b + aesmc v0.16b,v0.16b + aese v1.16b,v20.16b + aesmc v1.16b,v1.16b + aese v24.16b,v20.16b + aesmc v24.16b,v24.16b + aese v25.16b,v20.16b + aesmc v25.16b,v25.16b + aese v26.16b,v20.16b + aesmc v26.16b,v26.16b + + aese v0.16b,v21.16b + aesmc v0.16b,v0.16b + aese v1.16b,v21.16b + aesmc v1.16b,v1.16b + aese v24.16b,v21.16b + aesmc v24.16b,v24.16b + aese v25.16b,v21.16b + aesmc v25.16b,v25.16b + aese v26.16b,v21.16b + aesmc v26.16b,v26.16b + + aese v0.16b,v22.16b + aesmc v0.16b,v0.16b + aese v1.16b,v22.16b + aesmc v1.16b,v1.16b + aese v24.16b,v22.16b + aesmc v24.16b,v24.16b + aese v25.16b,v22.16b + aesmc v25.16b,v25.16b + aese v26.16b,v22.16b + aesmc v26.16b,v26.16b + + eor v4.16b,v7.16b,v6.16b + aese v0.16b,v23.16b + // The iv for first block of one iteration + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr#31 + eor x9,x11,x9,lsl#1 + fmov d6,x9 + fmov v6.d[1],x10 + eor v5.16b,v7.16b,v8.16b + ld1 {v2.16b},[x0],#16 + aese v1.16b,v23.16b + // The iv for second block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr#31 + eor x9,x11,x9,lsl#1 + fmov d8,x9 + fmov v8.d[1],x10 + eor v17.16b,v7.16b,v9.16b + ld1 {v3.16b},[x0],#16 + aese v24.16b,v23.16b + // The iv for third block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr#31 + eor x9,x11,x9,lsl#1 + fmov d9,x9 + fmov v9.d[1],x10 + eor v30.16b,v7.16b,v10.16b + ld1 {v27.16b},[x0],#16 + aese v25.16b,v23.16b + // The iv for fourth block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr#31 + eor x9,x11,x9,lsl#1 + fmov d10,x9 + fmov v10.d[1],x10 + eor v31.16b,v7.16b,v11.16b + ld1 {v28.16b},[x0],#16 + aese v26.16b,v23.16b + + // The iv for fifth block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d11,x9 + fmov v11.d[1],x10 + + ld1 {v29.16b},[x0],#16 + cbz x6,Lxts_enc_tail4x + ld1 {v16.4s},[x7],#16 // re-pre-load rndkey[0] + eor v4.16b,v4.16b,v0.16b + eor v0.16b,v2.16b,v6.16b + eor v5.16b,v5.16b,v1.16b + eor v1.16b,v3.16b,v8.16b + eor v17.16b,v17.16b,v24.16b + eor v24.16b,v27.16b,v9.16b + eor v30.16b,v30.16b,v25.16b + eor v25.16b,v28.16b,v10.16b + eor v31.16b,v31.16b,v26.16b + st1 {v4.16b},[x1],#16 + eor v26.16b,v29.16b,v11.16b + st1 {v5.16b},[x1],#16 + mov w6,w5 + st1 {v17.16b},[x1],#16 + ld1 {v17.4s},[x7],#16 // re-pre-load rndkey[1] + st1 {v30.16b},[x1],#16 + st1 {v31.16b},[x1],#16 + b.hs Loop5x_xts_enc + + + // If left 4 blocks, borrow the five block's processing. + cmn x2,#0x10 + b.ne Loop5x_enc_after + orr v11.16b,v10.16b,v10.16b + orr v10.16b,v9.16b,v9.16b + orr v9.16b,v8.16b,v8.16b + orr v8.16b,v6.16b,v6.16b + fmov x9,d11 + fmov x10,v11.d[1] + eor v0.16b,v6.16b,v2.16b + eor v1.16b,v8.16b,v3.16b + eor v24.16b,v27.16b,v9.16b + eor v25.16b,v28.16b,v10.16b + eor v26.16b,v29.16b,v11.16b + b.eq Loop5x_xts_enc + +Loop5x_enc_after: + add x2,x2,#0x50 + cbz x2,Lxts_enc_done + + add w6,w5,#2 + subs x2,x2,#0x30 + b.lo Lxts_inner_enc_tail + + eor v0.16b,v6.16b,v27.16b + eor v1.16b,v8.16b,v28.16b + eor v24.16b,v29.16b,v9.16b + b Lxts_outer_enc_tail + +.balign 16 +Lxts_enc_tail4x: + add x0,x0,#16 + eor v5.16b,v1.16b,v5.16b + st1 {v5.16b},[x1],#16 + eor v17.16b,v24.16b,v17.16b + st1 {v17.16b},[x1],#16 + eor v30.16b,v25.16b,v30.16b + eor v31.16b,v26.16b,v31.16b + st1 {v30.16b,v31.16b},[x1],#32 + + b Lxts_enc_done +.balign 16 +Lxts_outer_enc_tail: + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v24.16b,v16.16b + aesmc v24.16b,v24.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v24.16b,v17.16b + aesmc v24.16b,v24.16b + ld1 {v17.4s},[x7],#16 + b.gt Lxts_outer_enc_tail + + aese v0.16b,v16.16b + aesmc v0.16b,v0.16b + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v24.16b,v16.16b + aesmc v24.16b,v24.16b + eor v4.16b,v6.16b,v7.16b + subs x2,x2,#0x30 + // The iv for first block + fmov x9,d9 + fmov x10,v9.d[1] + //mov w19,#0x87 + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr#31 + eor x9,x11,x9,lsl#1 + fmov d6,x9 + fmov v6.d[1],x10 + eor v5.16b,v8.16b,v7.16b + csel x6,x2,x6,lo // x6, w6, is zero at this point + aese v0.16b,v17.16b + aesmc v0.16b,v0.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v24.16b,v17.16b + aesmc v24.16b,v24.16b + eor v17.16b,v9.16b,v7.16b + + add x6,x6,#0x20 + add x0,x0,x6 + mov x7,x3 + + aese v0.16b,v20.16b + aesmc v0.16b,v0.16b + aese v1.16b,v20.16b + aesmc v1.16b,v1.16b + aese v24.16b,v20.16b + aesmc v24.16b,v24.16b + aese v0.16b,v21.16b + aesmc v0.16b,v0.16b + aese v1.16b,v21.16b + aesmc v1.16b,v1.16b + aese v24.16b,v21.16b + aesmc v24.16b,v24.16b + aese v0.16b,v22.16b + aesmc v0.16b,v0.16b + aese v1.16b,v22.16b + aesmc v1.16b,v1.16b + aese v24.16b,v22.16b + aesmc v24.16b,v24.16b + aese v0.16b,v23.16b + aese v1.16b,v23.16b + aese v24.16b,v23.16b + ld1 {v27.16b},[x0],#16 + add w6,w5,#2 + ld1 {v16.4s},[x7],#16 // re-pre-load rndkey[0] + eor v4.16b,v4.16b,v0.16b + eor v5.16b,v5.16b,v1.16b + eor v24.16b,v24.16b,v17.16b + ld1 {v17.4s},[x7],#16 // re-pre-load rndkey[1] + st1 {v4.16b},[x1],#16 + st1 {v5.16b},[x1],#16 + st1 {v24.16b},[x1],#16 + cmn x2,#0x30 + b.eq Lxts_enc_done +Lxts_encxor_one: + orr v28.16b,v3.16b,v3.16b + orr v29.16b,v27.16b,v27.16b + nop + +Lxts_inner_enc_tail: + cmn x2,#0x10 + eor v1.16b,v28.16b,v6.16b + eor v24.16b,v29.16b,v8.16b + b.eq Lxts_enc_tail_loop + eor v24.16b,v29.16b,v6.16b +Lxts_enc_tail_loop: + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v24.16b,v16.16b + aesmc v24.16b,v24.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v24.16b,v17.16b + aesmc v24.16b,v24.16b + ld1 {v17.4s},[x7],#16 + b.gt Lxts_enc_tail_loop + + aese v1.16b,v16.16b + aesmc v1.16b,v1.16b + aese v24.16b,v16.16b + aesmc v24.16b,v24.16b + aese v1.16b,v17.16b + aesmc v1.16b,v1.16b + aese v24.16b,v17.16b + aesmc v24.16b,v24.16b + aese v1.16b,v20.16b + aesmc v1.16b,v1.16b + aese v24.16b,v20.16b + aesmc v24.16b,v24.16b + cmn x2,#0x20 + aese v1.16b,v21.16b + aesmc v1.16b,v1.16b + aese v24.16b,v21.16b + aesmc v24.16b,v24.16b + eor v5.16b,v6.16b,v7.16b + aese v1.16b,v22.16b + aesmc v1.16b,v1.16b + aese v24.16b,v22.16b + aesmc v24.16b,v24.16b + eor v17.16b,v8.16b,v7.16b + aese v1.16b,v23.16b + aese v24.16b,v23.16b + b.eq Lxts_enc_one + eor v5.16b,v5.16b,v1.16b + st1 {v5.16b},[x1],#16 + eor v17.16b,v17.16b,v24.16b + orr v6.16b,v8.16b,v8.16b + st1 {v17.16b},[x1],#16 + fmov x9,d8 + fmov x10,v8.d[1] + mov w19,#0x87 + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d6,x9 + fmov v6.d[1],x10 + b Lxts_enc_done + +Lxts_enc_one: + eor v5.16b,v5.16b,v24.16b + orr v6.16b,v6.16b,v6.16b + st1 {v5.16b},[x1],#16 + fmov x9,d6 + fmov x10,v6.d[1] + mov w19,#0x87 + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d6,x9 + fmov v6.d[1],x10 + b Lxts_enc_done +.balign 32 +Lxts_enc_done: + // Process the tail block with cipher stealing. + tst x21,#0xf + b.eq Lxts_abort + + mov x20,x0 + mov x13,x1 + sub x1,x1,#16 +.composite_enc_loop: + subs x21,x21,#1 + ldrb w15,[x1,x21] + ldrb w14,[x20,x21] + strb w15,[x13,x21] + strb w14,[x1,x21] + b.gt .composite_enc_loop +Lxts_enc_load_done: + ld1 {v26.16b},[x1] + eor v26.16b,v26.16b,v6.16b + + // Encrypt the composite block to get the last second encrypted text block + ldr w6,[x3,#240] // load key schedule... + ld1 {v0.16b},[x3],#16 + sub w6,w6,#2 + ld1 {v1.16b},[x3],#16 // load key schedule... +Loop_final_enc: + aese v26.16b,v0.16b + aesmc v26.16b,v26.16b + ld1 {v0.4s},[x3],#16 + subs w6,w6,#2 + aese v26.16b,v1.16b + aesmc v26.16b,v26.16b + ld1 {v1.4s},[x3],#16 + b.gt Loop_final_enc + + aese v26.16b,v0.16b + aesmc v26.16b,v26.16b + ld1 {v0.4s},[x3] + aese v26.16b,v1.16b + eor v26.16b,v26.16b,v0.16b + eor v26.16b,v26.16b,v6.16b + st1 {v26.16b},[x1] + +Lxts_abort: + ldp x21,x22,[sp,#48] + ldp d8,d9,[sp,#32] + ldp d10,d11,[sp,#16] + ldp x19,x20,[sp],#64 +Lxts_enc_final_abort: + ret + +ENTRY_ALIGN(aes_v8_xts_decrypt, 32) + cmp x2,#16 + // Original input data size bigger than 16, jump to big size processing. + b.ne Lxts_dec_big_size + // Encrypt the iv with key2, as the first XEX iv. + ldr w6,[x4,#240] + ld1 {v0.16b},[x4],#16 + ld1 {v6.16b},[x5] + sub w6,w6,#2 + ld1 {v1.16b},[x4],#16 + +Loop_dec_small_iv_enc: + aese v6.16b,v0.16b + aesmc v6.16b,v6.16b + ld1 {v0.4s},[x4],#16 + subs w6,w6,#2 + aese v6.16b,v1.16b + aesmc v6.16b,v6.16b + ld1 {v1.4s},[x4],#16 + b.gt Loop_dec_small_iv_enc + + aese v6.16b,v0.16b + aesmc v6.16b,v6.16b + ld1 {v0.4s},[x4] + aese v6.16b,v1.16b + eor v6.16b,v6.16b,v0.16b + + ld1 {v0.16b},[x0] + eor v0.16b,v6.16b,v0.16b + + ldr w6,[x3,#240] + ld1 {v28.4s,v29.4s},[x3],#32 // load key schedule... + + aesd v0.16b,v28.16b + aesimc v0.16b,v0.16b + ld1 {v16.4s,v17.4s},[x3],#32 // load key schedule... + aesd v0.16b,v29.16b + aesimc v0.16b,v0.16b + subs w6,w6,#10 // bias + b.eq Lxts_128_dec +Lxts_dec_round_loop: + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + ld1 {v16.4s},[x3],#16 // load key schedule... + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + ld1 {v17.4s},[x3],#16 // load key schedule... + subs w6,w6,#2 // bias + b.gt Lxts_dec_round_loop +Lxts_128_dec: + ld1 {v18.4s,v19.4s},[x3],#32 // load key schedule... + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + ld1 {v20.4s,v21.4s},[x3],#32 // load key schedule... + aesd v0.16b,v18.16b + aesimc v0.16b,v0.16b + aesd v0.16b,v19.16b + aesimc v0.16b,v0.16b + ld1 {v22.4s,v23.4s},[x3],#32 // load key schedule... + aesd v0.16b,v20.16b + aesimc v0.16b,v0.16b + aesd v0.16b,v21.16b + aesimc v0.16b,v0.16b + ld1 {v7.4s},[x3] + aesd v0.16b,v22.16b + aesimc v0.16b,v0.16b + aesd v0.16b,v23.16b + eor v0.16b,v0.16b,v7.16b + eor v0.16b,v6.16b,v0.16b + st1 {v0.16b},[x1] + b Lxts_dec_final_abort +Lxts_dec_big_size: + stp x19,x20,[sp,#-64]! + stp x21,x22,[sp,#48] + stp d8,d9,[sp,#32] + stp d10,d11,[sp,#16] + + and x21,x2,#0xf + and x2,x2,#-16 + subs x2,x2,#16 + mov x8,#16 + b.lo Lxts_dec_abort + + // Encrypt the iv with key2, as the first XEX iv + ldr w6,[x4,#240] + ld1 {v0.16b},[x4],#16 + ld1 {v6.16b},[x5] + sub w6,w6,#2 + ld1 {v1.16b},[x4],#16 + +Loop_dec_iv_enc: + aese v6.16b,v0.16b + aesmc v6.16b,v6.16b + ld1 {v0.4s},[x4],#16 + subs w6,w6,#2 + aese v6.16b,v1.16b + aesmc v6.16b,v6.16b + ld1 {v1.4s},[x4],#16 + b.gt Loop_dec_iv_enc + + aese v6.16b,v0.16b + aesmc v6.16b,v6.16b + ld1 {v0.4s},[x4] + aese v6.16b,v1.16b + eor v6.16b,v6.16b,v0.16b + + // The iv for second block + // x9- iv(low), x10 - iv(high) + // the five ivs stored into, v6.16b,v8.16b,v9.16b,v10.16b,v11.16b + fmov x9,d6 + fmov x10,v6.d[1] + mov w19,#0x87 + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d8,x9 + fmov v8.d[1],x10 + + ldr w5,[x3,#240] // load rounds number + + // The iv for third block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d9,x9 + fmov v9.d[1],x10 + + ld1 {v16.4s,v17.4s},[x3] // load key schedule... + sub w5,w5,#6 + add x7,x3,x5,lsl#4 // pointer to last 7 round keys + sub w5,w5,#2 + ld1 {v18.4s,v19.4s},[x7],#32 // load key schedule... + ld1 {v20.4s,v21.4s},[x7],#32 + ld1 {v22.4s,v23.4s},[x7],#32 + ld1 {v7.4s},[x7] + + // The iv for fourth block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d10,x9 + fmov v10.d[1],x10 + + add x7,x3,#32 + mov w6,w5 + b Lxts_dec + + // Decryption +.balign 32 +Lxts_dec: + tst x21,#0xf + b.eq Lxts_dec_begin + subs x2,x2,#16 + csel x8,xzr,x8,eq + ld1 {v0.16b},[x0],#16 + b.lo Lxts_done + sub x0,x0,#16 +Lxts_dec_begin: + ld1 {v0.16b},[x0],x8 + subs x2,x2,#32 // bias + add w6,w5,#2 + orr v3.16b,v0.16b,v0.16b + orr v1.16b,v0.16b,v0.16b + orr v28.16b,v0.16b,v0.16b + ld1 {v24.16b},[x0],#16 + orr v27.16b,v24.16b,v24.16b + orr v29.16b,v24.16b,v24.16b + b.lo Lxts_inner_dec_tail + eor v0.16b,v0.16b,v6.16b // before decryt, xor with iv + eor v24.16b,v24.16b,v8.16b + + orr v1.16b,v24.16b,v24.16b + ld1 {v24.16b},[x0],#16 + orr v2.16b,v0.16b,v0.16b + orr v3.16b,v1.16b,v1.16b + eor v27.16b,v24.16b,v9.16b // third block xox with third iv + eor v24.16b,v24.16b,v9.16b + cmp x2,#32 + b.lo Lxts_outer_dec_tail + + ld1 {v25.16b},[x0],#16 + + // The iv for fifth block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d11,x9 + fmov v11.d[1],x10 + + ld1 {v26.16b},[x0],#16 + eor v25.16b,v25.16b,v10.16b // the fourth block + eor v26.16b,v26.16b,v11.16b + sub x2,x2,#32 // bias + mov w6,w5 + b Loop5x_xts_dec + +.balign 16 +Loop5x_xts_dec: + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v16.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v16.16b + aesimc v26.16b,v26.16b + ld1 {v16.4s},[x7],#16 // load key schedule... + subs w6,w6,#2 + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v17.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v17.16b + aesimc v26.16b,v26.16b + ld1 {v17.4s},[x7],#16 // load key schedule... + b.gt Loop5x_xts_dec + + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v16.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v16.16b + aesimc v26.16b,v26.16b + subs x2,x2,#0x50 // because Lxts_dec_tail4x + + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v17.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v17.16b + aesimc v26.16b,v26.16b + csel x6,xzr,x2,gt // borrow x6, w6, "gt" is not typo + mov x7,x3 + + aesd v0.16b,v18.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v18.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v18.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v18.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v18.16b + aesimc v26.16b,v26.16b + add x0,x0,x6 // x0 is adjusted in such way that + // at exit from the loop v1.16b-v26.16b + // are loaded with last "words" + add x6,x2,#0x60 // because Lxts_dec_tail4x + + aesd v0.16b,v19.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v19.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v19.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v19.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v19.16b + aesimc v26.16b,v26.16b + + aesd v0.16b,v20.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v20.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v20.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v20.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v20.16b + aesimc v26.16b,v26.16b + + aesd v0.16b,v21.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v21.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v21.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v21.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v21.16b + aesimc v26.16b,v26.16b + + aesd v0.16b,v22.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v22.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v22.16b + aesimc v24.16b,v24.16b + aesd v25.16b,v22.16b + aesimc v25.16b,v25.16b + aesd v26.16b,v22.16b + aesimc v26.16b,v26.16b + + eor v4.16b,v7.16b,v6.16b + aesd v0.16b,v23.16b + // The iv for first block of next iteration. + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d6,x9 + fmov v6.d[1],x10 + eor v5.16b,v7.16b,v8.16b + ld1 {v2.16b},[x0],#16 + aesd v1.16b,v23.16b + // The iv for second block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d8,x9 + fmov v8.d[1],x10 + eor v17.16b,v7.16b,v9.16b + ld1 {v3.16b},[x0],#16 + aesd v24.16b,v23.16b + // The iv for third block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d9,x9 + fmov v9.d[1],x10 + eor v30.16b,v7.16b,v10.16b + ld1 {v27.16b},[x0],#16 + aesd v25.16b,v23.16b + // The iv for fourth block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d10,x9 + fmov v10.d[1],x10 + eor v31.16b,v7.16b,v11.16b + ld1 {v28.16b},[x0],#16 + aesd v26.16b,v23.16b + + // The iv for fifth block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d11,x9 + fmov v11.d[1],x10 + + ld1 {v29.16b},[x0],#16 + cbz x6,Lxts_dec_tail4x + ld1 {v16.4s},[x7],#16 // re-pre-load rndkey[0] + eor v4.16b,v4.16b,v0.16b + eor v0.16b,v2.16b,v6.16b + eor v5.16b,v5.16b,v1.16b + eor v1.16b,v3.16b,v8.16b + eor v17.16b,v17.16b,v24.16b + eor v24.16b,v27.16b,v9.16b + eor v30.16b,v30.16b,v25.16b + eor v25.16b,v28.16b,v10.16b + eor v31.16b,v31.16b,v26.16b + st1 {v4.16b},[x1],#16 + eor v26.16b,v29.16b,v11.16b + st1 {v5.16b},[x1],#16 + mov w6,w5 + st1 {v17.16b},[x1],#16 + ld1 {v17.4s},[x7],#16 // re-pre-load rndkey[1] + st1 {v30.16b},[x1],#16 + st1 {v31.16b},[x1],#16 + b.hs Loop5x_xts_dec + + cmn x2,#0x10 + b.ne Loop5x_dec_after + // If x2(x2) equal to -0x10, the left blocks is 4. + // After specially processing, utilize the five blocks processing again. + // It will use the following IVs: v6.16b,v6.16b,v8.16b,v9.16b,v10.16b. + orr v11.16b,v10.16b,v10.16b + orr v10.16b,v9.16b,v9.16b + orr v9.16b,v8.16b,v8.16b + orr v8.16b,v6.16b,v6.16b + fmov x9,d11 + fmov x10,v11.d[1] + eor v0.16b,v6.16b,v2.16b + eor v1.16b,v8.16b,v3.16b + eor v24.16b,v27.16b,v9.16b + eor v25.16b,v28.16b,v10.16b + eor v26.16b,v29.16b,v11.16b + b.eq Loop5x_xts_dec + +Loop5x_dec_after: + add x2,x2,#0x50 + cbz x2,Lxts_done + + add w6,w5,#2 + subs x2,x2,#0x30 + b.lo Lxts_inner_dec_tail + + eor v0.16b,v6.16b,v27.16b + eor v1.16b,v8.16b,v28.16b + eor v24.16b,v29.16b,v9.16b + b Lxts_outer_dec_tail + +.balign 16 +Lxts_dec_tail4x: + add x0,x0,#16 + ld1 {v0.4s},[x0],#16 + eor v5.16b,v1.16b,v4.16b + st1 {v5.16b},[x1],#16 + eor v17.16b,v24.16b,v17.16b + st1 {v17.16b},[x1],#16 + eor v30.16b,v25.16b,v30.16b + eor v31.16b,v26.16b,v31.16b + st1 {v30.16b,v31.16b},[x1],#32 + + b Lxts_done +.balign 16 +Lxts_outer_dec_tail: + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + ld1 {v17.4s},[x7],#16 + b.gt Lxts_outer_dec_tail + + aesd v0.16b,v16.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + eor v4.16b,v6.16b,v7.16b + subs x2,x2,#0x30 + // The iv for first block + fmov x9,d9 + fmov x10,v9.d[1] + mov w19,#0x87 + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d6,x9 + fmov v6.d[1],x10 + eor v5.16b,v8.16b,v7.16b + csel x6,x2,x6,lo // x6, w6, is zero at this point + aesd v0.16b,v17.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + eor v17.16b,v9.16b,v7.16b + // The iv for second block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d8,x9 + fmov v8.d[1],x10 + + add x6,x6,#0x20 + add x0,x0,x6 // x0 is adjusted to the last data + + mov x7,x3 + + // The iv for third block + extr x22,x10,x10,#32 + extr x10,x10,x9,#63 + and w11,w19,w22,asr #31 + eor x9,x11,x9,lsl #1 + fmov d9,x9 + fmov v9.d[1],x10 + + aesd v0.16b,v20.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v20.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v20.16b + aesimc v24.16b,v24.16b + aesd v0.16b,v21.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v21.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v21.16b + aesimc v24.16b,v24.16b + aesd v0.16b,v22.16b + aesimc v0.16b,v0.16b + aesd v1.16b,v22.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v22.16b + aesimc v24.16b,v24.16b + ld1 {v27.16b},[x0],#16 + aesd v0.16b,v23.16b + aesd v1.16b,v23.16b + aesd v24.16b,v23.16b + ld1 {v16.4s},[x7],#16 // re-pre-load rndkey[0] + add w6,w5,#2 + eor v4.16b,v4.16b,v0.16b + eor v5.16b,v5.16b,v1.16b + eor v24.16b,v24.16b,v17.16b + ld1 {v17.4s},[x7],#16 // re-pre-load rndkey[1] + st1 {v4.16b},[x1],#16 + st1 {v5.16b},[x1],#16 + st1 {v24.16b},[x1],#16 + + cmn x2,#0x30 + add x2,x2,#0x30 + b.eq Lxts_done + sub x2,x2,#0x30 + orr v28.16b,v3.16b,v3.16b + orr v29.16b,v27.16b,v27.16b + nop + +Lxts_inner_dec_tail: + // x2 == -0x10 means two blocks left. + cmn x2,#0x10 + eor v1.16b,v28.16b,v6.16b + eor v24.16b,v29.16b,v8.16b + b.eq Lxts_dec_tail_loop + eor v24.16b,v29.16b,v6.16b +Lxts_dec_tail_loop: + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + ld1 {v16.4s},[x7],#16 + subs w6,w6,#2 + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + ld1 {v17.4s},[x7],#16 + b.gt Lxts_dec_tail_loop + + aesd v1.16b,v16.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v16.16b + aesimc v24.16b,v24.16b + aesd v1.16b,v17.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v17.16b + aesimc v24.16b,v24.16b + aesd v1.16b,v20.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v20.16b + aesimc v24.16b,v24.16b + cmn x2,#0x20 + aesd v1.16b,v21.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v21.16b + aesimc v24.16b,v24.16b + eor v5.16b,v6.16b,v7.16b + aesd v1.16b,v22.16b + aesimc v1.16b,v1.16b + aesd v24.16b,v22.16b + aesimc v24.16b,v24.16b + eor v17.16b,v8.16b,v7.16b + aesd v1.16b,v23.16b + aesd v24.16b,v23.16b + b.eq Lxts_dec_one + eor v5.16b,v5.16b,v1.16b + eor v17.16b,v17.16b,v24.16b + orr v6.16b,v9.16b,v9.16b + orr v8.16b,v10.16b,v10.16b + st1 {v5.16b},[x1],#16 + st1 {v17.16b},[x1],#16 + add x2,x2,#16 + b Lxts_done + +Lxts_dec_one: + eor v5.16b,v5.16b,v24.16b + orr v6.16b,v8.16b,v8.16b + orr v8.16b,v9.16b,v9.16b + st1 {v5.16b},[x1],#16 + add x2,x2,#32 + +Lxts_done: + tst x21,#0xf + b.eq Lxts_dec_abort + // Processing the last two blocks with cipher stealing. + mov x7,x3 + cbnz x2,Lxts_dec_1st_done + ld1 {v0.4s},[x0],#16 + + // Decrypt the last second block to get the last plain text block +Lxts_dec_1st_done: + eor v26.16b,v0.16b,v8.16b + ldr w6,[x3,#240] + ld1 {v0.4s},[x3],#16 + sub w6,w6,#2 + ld1 {v1.4s},[x3],#16 +Loop_final_2nd_dec: + aesd v26.16b,v0.16b + aesimc v26.16b,v26.16b + ld1 {v0.4s},[x3],#16 // load key schedule... + subs w6,w6,#2 + aesd v26.16b,v1.16b + aesimc v26.16b,v26.16b + ld1 {v1.4s},[x3],#16 // load key schedule... + b.gt Loop_final_2nd_dec + + aesd v26.16b,v0.16b + aesimc v26.16b,v26.16b + ld1 {v0.4s},[x3] + aesd v26.16b,v1.16b + eor v26.16b,v26.16b,v0.16b + eor v26.16b,v26.16b,v8.16b + st1 {v26.16b},[x1] + + mov x20,x0 + add x13,x1,#16 + + // Composite the tailcnt "16 byte not aligned block" into the last second plain blocks + // to get the last encrypted block. +.composite_dec_loop: + subs x21,x21,#1 + ldrb w15,[x1,x21] + ldrb w14,[x20,x21] + strb w15,[x13,x21] + strb w14,[x1,x21] + b.gt .composite_dec_loop +Lxts_dec_load_done: + ld1 {v26.16b},[x1] + eor v26.16b,v26.16b,v6.16b + + // Decrypt the composite block to get the last second plain text block + ldr w6,[x7,#240] + ld1 {v0.16b},[x7],#16 + sub w6,w6,#2 + ld1 {v1.16b},[x7],#16 +Loop_final_dec: + aesd v26.16b,v0.16b + aesimc v26.16b,v26.16b + ld1 {v0.4s},[x7],#16 // load key schedule... + subs w6,w6,#2 + aesd v26.16b,v1.16b + aesimc v26.16b,v26.16b + ld1 {v1.4s},[x7],#16 // load key schedule... + b.gt Loop_final_dec + + aesd v26.16b,v0.16b + aesimc v26.16b,v26.16b + ld1 {v0.4s},[x7] + aesd v26.16b,v1.16b + eor v26.16b,v26.16b,v0.16b + eor v26.16b,v26.16b,v6.16b + st1 {v26.16b},[x1] + +Lxts_dec_abort: + ldp x21,x22,[sp,#48] + ldp d8,d9,[sp,#32] + ldp d10,d11,[sp,#16] + ldp x19,x20,[sp],#64 + +Lxts_dec_final_abort: + ret + +#endif diff --git a/module/icp/asm-aarch64/blake3/b3_aarch64_sse2.S b/module/icp/asm-aarch64/blake3/b3_aarch64_sse2.S index dc2719d142db..91399aeb28e3 100644 --- a/module/icp/asm-aarch64/blake3/b3_aarch64_sse2.S +++ b/module/icp/asm-aarch64/blake3/b3_aarch64_sse2.S @@ -33,7 +33,9 @@ #if defined(__aarch64__) .text +#ifndef __APPLE__ .section .note.gnu.property,"a",@note +#endif .p2align 3 .word 4 .word 16 @@ -47,7 +49,9 @@ .text .globl zfs_blake3_compress_in_place_sse2 .p2align 2 +#ifndef __APPLE__ .type zfs_blake3_compress_in_place_sse2,@function +#endif zfs_blake3_compress_in_place_sse2: .cfi_startproc hint #25 @@ -79,17 +83,24 @@ zfs_blake3_compress_in_place_sse2: hint #29 ret .Lfunc_end0: - .size zfs_blake3_compress_in_place_sse2, .Lfunc_end0-zfs_blake3_compress_in_place_sse2 +#ifndef __APPLE__ +.size zfs_blake3_compress_in_place_sse2, .Lfunc_end0-zfs_blake3_compress_in_place_sse2 +#endif .cfi_endproc + +#ifndef __APPLE__ .section .rodata.cst16,"aM",@progbits,16 +#endif .p2align 4 .LCPI1_0: .xword -4942790177982912921 .xword -6534734903820487822 .text .p2align 2 +#ifndef __APPLE__ .type compress_pre,@function +#endif compress_pre: .cfi_startproc hint #34 @@ -97,10 +108,18 @@ compress_pre: movi d0, #0x0000ff000000ff ldr q2, [x1] fmov d3, x4 +#ifndef __APPLE__ adrp x8, .LCPI1_0 +#else + adrp x8, .LCPI1_0@PAGE +#endif mov v1.s[1], w5 str q2, [x0] +#ifndef __APPLE__ ldr q4, [x8, :lo12:.LCPI1_0] +#else + ldr q4, [x8, :lo12:.LCPI1_0@PAGEOFF] +#endif add x8, x2, #32 ldr q5, [x1, #16] and v0.8b, v1.8b, v0.8b @@ -546,12 +565,16 @@ compress_pre: stp q0, q1, [x0] ret .Lfunc_end1: +#ifndef __APPLE__ .size compress_pre, .Lfunc_end1-compress_pre +#endif .cfi_endproc .globl zfs_blake3_compress_xof_sse2 .p2align 2 +#ifndef __APPLE__ .type zfs_blake3_compress_xof_sse2,@function +#endif zfs_blake3_compress_xof_sse2: .cfi_startproc hint #25 @@ -591,10 +614,14 @@ zfs_blake3_compress_xof_sse2: hint #29 ret .Lfunc_end2: +#ifndef __APPLE__ .size zfs_blake3_compress_xof_sse2, .Lfunc_end2-zfs_blake3_compress_xof_sse2 +#endif .cfi_endproc +#ifndef __APPLE__ .section .rodata.cst16,"aM",@progbits,16 +#endif .p2align 4 .LCPI3_0: .word 0 @@ -604,7 +631,9 @@ zfs_blake3_compress_xof_sse2: .text .globl zfs_blake3_hash_many_sse2 .p2align 2 +#ifndef __APPLE__ .type zfs_blake3_hash_many_sse2,@function +#endif zfs_blake3_hash_many_sse2: .cfi_startproc hint #25 @@ -650,13 +679,21 @@ zfs_blake3_hash_many_sse2: cmp x1, #4 str x3, [sp, #40] b.lo .LBB3_6 +#ifndef __APPLE__ adrp x8, .LCPI3_0 +#else + adrp x8, .LCPI3_0@PAGE +#endif sbfx w9, w5, #0, #1 mov w10, #44677 mov w11, #62322 movk w10, #47975, lsl #16 movk w11, #15470, lsl #16 +#ifndef __APPLE__ ldr q0, [x8, :lo12:.LCPI3_0] +#else + ldr q0, [x8, :lo12:.LCPI3_0@PAGEOFF] +#endif dup v1.4s, w9 mov w9, #58983 orr w8, w7, w19 @@ -2055,7 +2092,11 @@ zfs_blake3_hash_many_sse2: hint #29 ret .Lfunc_end3: +#ifndef __APPLE__ .size zfs_blake3_hash_many_sse2, .Lfunc_end3-zfs_blake3_hash_many_sse2 +#endif .cfi_endproc +#ifndef __APPLE__ .section ".note.GNU-stack","",@progbits -#endif \ No newline at end of file +#endif +#endif diff --git a/module/icp/asm-aarch64/blake3/b3_aarch64_sse41.S b/module/icp/asm-aarch64/blake3/b3_aarch64_sse41.S index c4c2dfc5bcde..ee4638858286 100644 --- a/module/icp/asm-aarch64/blake3/b3_aarch64_sse41.S +++ b/module/icp/asm-aarch64/blake3/b3_aarch64_sse41.S @@ -33,7 +33,9 @@ #if defined(__aarch64__) .text +#ifndef __APPLE__ .section .note.gnu.property,"a",@note +#endif .p2align 3 .word 4 .word 16 @@ -47,7 +49,9 @@ .text .globl zfs_blake3_compress_in_place_sse41 .p2align 2 +#ifndef __APPLE__ .type zfs_blake3_compress_in_place_sse41,@function +#endif zfs_blake3_compress_in_place_sse41: .cfi_startproc hint #25 @@ -79,10 +83,14 @@ zfs_blake3_compress_in_place_sse41: hint #29 ret .Lfunc_end0: +#ifndef __APPLE__ .size zfs_blake3_compress_in_place_sse41, .Lfunc_end0-zfs_blake3_compress_in_place_sse41 +#endif .cfi_endproc +#ifndef __APPLE__ .section .rodata.cst16,"aM",@progbits,16 +#endif .p2align 4 .LCPI1_0: .xword -4942790177982912921 @@ -123,19 +131,33 @@ zfs_blake3_compress_in_place_sse41: .byte 12 .text .p2align 2 +#ifndef __APPLE__ .type compress_pre,@function +#endif compress_pre: .cfi_startproc hint #34 fmov s1, w3 movi d0, #0x0000ff000000ff ldr q2, [x1] +#ifndef __APPLE__ adrp x8, .LCPI1_0 +#else + adrp x8, .LCPI1_0@PAGE +#endif mov v1.s[1], w5 str q2, [x0] +#ifndef __APPLE__ ldr q4, [x8, :lo12:.LCPI1_0] +#else + ldr q4, [x8, :lo12:.LCPI1_0@PAGEOFF] +#endif ldr q5, [x1, #16] +#ifndef __APPLE__ adrp x8, .LCPI1_1 +#else + adrp x8, .LCPI1_1@PAGE +#endif and v0.8b, v1.8b, v0.8b fmov d1, x4 stp q5, q4, [x0, #16] @@ -146,8 +168,13 @@ compress_pre: add v0.4s, v2.4s, v3.4s uzp2 v2.4s, v6.4s, v7.4s add v16.4s, v0.4s, v5.4s +#ifndef __APPLE__ ldr q0, [x8, :lo12:.LCPI1_1] adrp x8, .LCPI1_2 +#else + ldr q0, [x8, :lo12:.LCPI1_1@PAGEOFF] + adrp x8, .LCPI1_2@PAGE +#endif eor v1.16b, v16.16b, v1.16b add v7.4s, v16.4s, v2.4s tbl v1.16b, { v1.16b }, v0.16b @@ -158,7 +185,11 @@ compress_pre: orr v5.16b, v5.16b, v6.16b add v6.4s, v7.4s, v5.4s eor v7.16b, v1.16b, v6.16b +#ifndef __APPLE__ ldr q1, [x8, :lo12:.LCPI1_2] +#else + ldr q1, [x8, :lo12:.LCPI1_2@PAGEOFF] +#endif add x8, x2, #32 tbl v7.16b, { v7.16b }, v1.16b ld2 { v16.4s, v17.4s }, [x8] @@ -556,12 +587,16 @@ compress_pre: stp q2, q3, [x0] ret .Lfunc_end1: +#ifndef __APPLE__ .size compress_pre, .Lfunc_end1-compress_pre +#endif .cfi_endproc .globl zfs_blake3_compress_xof_sse41 .p2align 2 +#ifndef __APPLE__ .type zfs_blake3_compress_xof_sse41,@function +#endif zfs_blake3_compress_xof_sse41: .cfi_startproc hint #25 @@ -601,10 +636,14 @@ zfs_blake3_compress_xof_sse41: hint #29 ret .Lfunc_end2: +#ifndef __APPLE__ .size zfs_blake3_compress_xof_sse41, .Lfunc_end2-zfs_blake3_compress_xof_sse41 +#endif .cfi_endproc +#ifndef __APPLE__ .section .rodata.cst16,"aM",@progbits,16 +#endif .p2align 4 .LCPI3_0: .word 0 @@ -653,7 +692,9 @@ zfs_blake3_compress_xof_sse41: .text .globl zfs_blake3_hash_many_sse41 .p2align 2 +#ifndef __APPLE__ .type zfs_blake3_hash_many_sse41,@function +#endif zfs_blake3_hash_many_sse41: .cfi_startproc hint #34 @@ -687,25 +728,45 @@ zfs_blake3_hash_many_sse41: .cfi_offset b14, -136 .cfi_offset b15, -144 ldr x8, [sp, #520] +#ifndef __APPLE__ adrp x11, .LCPI3_1 +#else + adrp x11, .LCPI3_1@PAGE +#endif ldrb w9, [sp, #512] +#ifndef __APPLE__ adrp x10, .LCPI3_2 +#else + adrp x10, .LCPI3_2@PAGE +#endif cmp x1, #4 b.lo .LBB3_6 +#ifndef __APPLE__ adrp x12, .LCPI3_0 +#else + adrp x12, .LCPI3_0@PAGE +#endif sbfx w13, w5, #0, #1 mov w15, #58983 mov w16, #44677 movk w15, #27145, lsl #16 movk w16, #47975, lsl #16 +#ifndef __APPLE__ ldr q0, [x12, :lo12:.LCPI3_0] +#else + ldr q0, [x12, :lo12:.LCPI3_0@PAGEOFF] +#endif dup v1.4s, w13 movi v13.4s, #64 mov w13, #62322 mov w14, #62778 orr w12, w7, w6 and v0.16b, v1.16b, v0.16b +#ifndef __APPLE__ ldr q1, [x11, :lo12:.LCPI3_1] +#else + ldr q1, [x11, :lo12:.LCPI3_1@PAGEOFF] +#endif movk w13, #15470, lsl #16 movk w14, #42319, lsl #16 dup v14.4s, w15 @@ -876,7 +937,11 @@ zfs_blake3_hash_many_sse41: ushr v8.4s, v25.4s, #12 shl v25.4s, v25.4s, #20 orr v3.16b, v20.16b, v18.16b +#ifndef __APPLE__ ldr q18, [x10, :lo12:.LCPI3_2] +#else + ldr q18, [x10, :lo12:.LCPI3_2@PAGEOFF] +#endif orr v13.16b, v17.16b, v26.16b orr v24.16b, v24.16b, v29.16b orr v14.16b, v25.16b, v8.16b @@ -1935,11 +2000,21 @@ zfs_blake3_hash_many_sse41: b .LBB3_2 .LBB3_6: cbz x1, .LBB3_14 +#ifndef __APPLE__ adrp x12, .LCPI3_3 ldr q0, [x11, :lo12:.LCPI3_1] +#else + adrp x12, .LCPI3_3@PAGE + ldr q0, [x11, :lo12:.LCPI3_1@PAGEOFF] +#endif orr w11, w7, w6 +#ifndef __APPLE__ ldr q2, [x10, :lo12:.LCPI3_2] ldr q1, [x12, :lo12:.LCPI3_3] +#else + ldr q2, [x10, :lo12:.LCPI3_2@PAGEOFF] + ldr q1, [x12, :lo12:.LCPI3_3@PAGEOFF] +#endif and x12, x5, #0x1 .LBB3_8: movi v3.4s, #64 @@ -2392,7 +2467,11 @@ zfs_blake3_hash_many_sse41: ldp d15, d14, [sp], #144 ret .Lfunc_end3: +#ifndef __APPLE__ .size zfs_blake3_hash_many_sse41, .Lfunc_end3-zfs_blake3_hash_many_sse41 +#endif .cfi_endproc +#ifndef __APPLE__ .section ".note.GNU-stack","",@progbits -#endif \ No newline at end of file +#endif +#endif diff --git a/module/icp/asm-aarch64/sha2/sha256-armv8.S b/module/icp/asm-aarch64/sha2/sha256-armv8.S index 7ae486e4e229..cd7307a4417d 100644 --- a/module/icp/asm-aarch64/sha2/sha256-armv8.S +++ b/module/icp/asm-aarch64/sha2/sha256-armv8.S @@ -19,12 +19,14 @@ * - modified assembly to fit into OpenZFS */ +#include + #if defined(__aarch64__) -.text +SECTION_TEXT -.align 6 -.type .LK256,%object +.balign 64 +SET_OBJ(.LK256) .LK256: .long 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5 .long 0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5 @@ -43,13 +45,17 @@ .long 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208 .long 0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2 .long 0 //terminator -.size .LK256,.-.LK256 +SET_SIZE(.LK256) +#ifdef __APPLE__ +ENTRY_ALIGN(zfs_sha256_block_armv7, 64) +#else .globl zfs_sha256_block_armv7 .type zfs_sha256_block_armv7,%function .align 6 zfs_sha256_block_armv7: hint #34 // bti c +#endif stp x29,x30,[sp,#-128]! add x29,sp,#0 @@ -1010,13 +1016,17 @@ zfs_sha256_block_armv7: ldp x27,x28,[x29,#80] ldp x29,x30,[sp],#128 ret -.size zfs_sha256_block_armv7,.-zfs_sha256_block_armv7 +SET_SIZE(zfs_sha256_block_armv7) +#ifdef __APPLE__ +ENTRY_ALIGN(zfs_sha256_block_armv8, 64) +#else .globl zfs_sha256_block_armv8 .type zfs_sha256_block_armv8,%function .align 6 zfs_sha256_block_armv8: hint #34 // bti c +#endif .Lv8_entry: stp x29,x30,[sp,#-16]! add x29,sp,#0 @@ -1151,13 +1161,17 @@ zfs_sha256_block_armv8: ldr x29,[sp],#16 ret -.size zfs_sha256_block_armv8,.-zfs_sha256_block_armv8 +SET_SIZE(zfs_sha256_block_armv8) +#ifdef __APPLE__ +ENTRY_ALIGN(zfs_sha256_block_neon, 16) +#else .globl zfs_sha256_block_neon .type zfs_sha256_block_neon,%function .align 4 zfs_sha256_block_neon: hint #34 // bti c +#endif .Lneon_entry: stp x29, x30, [sp, #-16]! mov x29, sp @@ -1997,6 +2011,6 @@ zfs_sha256_block_neon: ldr x29,[x29] add sp,sp,#16*4+16 ret -.size zfs_sha256_block_neon,.-zfs_sha256_block_neon +SET_SIZE(zfs_sha256_block_neon) #endif diff --git a/module/icp/asm-aarch64/sha2/sha512-armv8.S b/module/icp/asm-aarch64/sha2/sha512-armv8.S index 9c61eeee4d7b..f147faf3751e 100644 --- a/module/icp/asm-aarch64/sha2/sha512-armv8.S +++ b/module/icp/asm-aarch64/sha2/sha512-armv8.S @@ -18,13 +18,14 @@ * Portions Copyright (c) 2022 Tino Reichardt * - modified assembly to fit into OpenZFS */ +#include #if defined(__aarch64__) -.text +SECTION_TEXT -.align 6 -.type .LK512,%object +.balign 64 +SET_OBJ(.LK512) .LK512: .quad 0x428a2f98d728ae22,0x7137449123ef65cd .quad 0xb5c0fbcfec4d3b2f,0xe9b5dba58189dbbc @@ -67,13 +68,17 @@ .quad 0x4cc5d4becb3e42b6,0x597f299cfc657e2a .quad 0x5fcb6fab3ad6faec,0x6c44198c4a475817 .quad 0 // terminator -.size .LK512,.-.LK512 +SET_SIZE(.LK512) +#ifdef __APPLE__ +ENTRY_ALIGN(zfs_sha512_block_armv7, 64) +#else .globl zfs_sha512_block_armv7 .type zfs_sha512_block_armv7,%function .align 6 zfs_sha512_block_armv7: hint #34 // bti c +#endif stp x29,x30,[sp,#-128]! add x29,sp,#0 @@ -1034,14 +1039,18 @@ zfs_sha512_block_armv7: ldp x27,x28,[x29,#80] ldp x29,x30,[sp],#128 ret -.size zfs_sha512_block_armv7,.-zfs_sha512_block_armv7 +SET_SIZE(zfs_sha512_block_armv7) +#ifdef __APPLE__ +ENTRY_ALIGN(zfs_sha512_block_armv8, 64) +#else .globl zfs_sha512_block_armv8 .type zfs_sha512_block_armv8,%function .align 6 zfs_sha512_block_armv8: hint #34 // bti c +#endif .Lv8_entry: // Armv8.3-A PAuth: even though x30 is pushed to stack it is not popped later stp x29,x30,[sp,#-16]! @@ -1063,7 +1072,7 @@ zfs_sha512_block_armv8: rev64 v23.16b,v23.16b b .Loop_hw -.align 4 +.balign 16 .Loop_hw: ld1 {v24.2d},[x3],#16 subs x2,x2,#1 @@ -1556,5 +1565,5 @@ zfs_sha512_block_armv8: ldr x29,[sp],#16 ret -.size zfs_sha512_block_armv8,.-zfs_sha512_block_armv8 +SET_SIZE(zfs_sha512_block_armv8) #endif diff --git a/module/icp/core/kcf_mech_tabs.c b/module/icp/core/kcf_mech_tabs.c index 41705e84bc4b..96c0c2cfa7fc 100644 --- a/module/icp/core/kcf_mech_tabs.c +++ b/module/icp/core/kcf_mech_tabs.c @@ -170,6 +170,7 @@ kcf_create_mech_entry(kcf_ops_class_t class, const char *mechname) strlcpy(me_tab[i].me_name, mechname, CRYPTO_MAX_MECH_NAME); me_tab[i].me_mechid = KCF_MECHID(class, i); + me_tab[i].me_sw_prov = NULL; /* Add the new mechanism to the hash table */ avl_insert(&kcf_mech_hash, &me_tab[i], where); diff --git a/module/icp/include/aes/aes_impl.h b/module/icp/include/aes/aes_impl.h index 66eb4a6c8fb6..379a735b093c 100644 --- a/module/icp/include/aes/aes_impl.h +++ b/module/icp/include/aes/aes_impl.h @@ -203,6 +203,10 @@ extern ASMABI void aes_decrypt_amd64(const uint32_t rk[], int Nr, #if defined(__x86_64) && defined(HAVE_AES) extern const aes_impl_ops_t aes_aesni_impl; #endif +#define HAVE_AESV8 /* fix me, autoconf */ +#if defined(__aarch64__) && defined(HAVE_AESV8) +extern const aes_impl_ops_t aes_aesv8_impl; +#endif /* * Initializes fastest implementation diff --git a/module/lua/ldo.c b/module/lua/ldo.c index 38bd4e08a73d..b2c2c1e2dd8f 100644 --- a/module/lua/ldo.c +++ b/module/lua/ldo.c @@ -65,7 +65,7 @@ static intptr_t stack_remaining(void) { #ifdef _KERNEL -#ifdef __linux__ +#if defined(__linux__) || defined(__APPLE__) #if defined(__i386__) #define JMP_BUF_CNT 6 #elif defined(__x86_64__) diff --git a/module/lua/setjmp/setjmp_aarch64.S b/module/lua/setjmp/setjmp_aarch64.S index 040ef1821ab0..201e580bed05 100644 --- a/module/lua/setjmp/setjmp_aarch64.S +++ b/module/lua/setjmp/setjmp_aarch64.S @@ -29,20 +29,10 @@ * */ +#include #ifdef __aarch64__ -#define ENTRY(sym) \ - .text; \ - .globl sym; \ - .balign 2; \ - .type sym,#function; \ -sym: - -#define END(sym) \ - .size sym, . - sym - - ENTRY(setjmp) /* Store the stack pointer */ mov x8, sp @@ -59,7 +49,7 @@ ENTRY(setjmp) /* Return value */ mov x0, #0 ret -END(setjmp) +SET_SIZE(setjmp) ENTRY(longjmp) /* Restore the stack pointer */ @@ -77,7 +67,7 @@ ENTRY(longjmp) /* Load the return value */ mov x0, x1 ret -END(longjmp) +SET_SIZE(longjmp) #ifdef __ELF__ .section .note.GNU-stack,"",%progbits diff --git a/module/nvpair/nvpair.c b/module/nvpair/nvpair.c index d9449e47e87a..e2a1cfbf1f65 100644 --- a/module/nvpair/nvpair.c +++ b/module/nvpair/nvpair.c @@ -3253,6 +3253,34 @@ nvs_xdr_nvp_##type(XDR *xdrs, void *ptr) \ return (xdr_##type(xdrs, ptr)); \ } +#elif defined(__APPLE__) && defined(_KERNEL) + +#define NVS_BUILD_XDRPROC_T(type) \ +static bool_t \ +nvs_xdr_nvp_##type(XDR *xdrs, void *ptr) \ +{ \ + return (xdr_##type(xdrs, ptr)); \ +} + +#elif defined(__APPLE__) && defined(_KERNEL) + +#define NVS_BUILD_XDRPROC_T(type) \ +static bool_t \ +nvs_xdr_nvp_##type(XDR *xdrs, void *ptr, ...) \ +{ \ + return (xdr_##type(xdrs, ptr)); \ +} + +#elif defined(__APPLE__) /* mac userland */ + +#define NVS_BUILD_XDRPROC_T(type) \ +static bool_t \ +nvs_xdr_nvp_##type(XDR *xdrs, void *ptr, \ + __unused unsigned int u) \ +{ \ + return (xdr_##type(xdrs, ptr)); \ +} + #elif !defined(_KERNEL) && defined(XDR_CONTROL) /* tirpc */ #define NVS_BUILD_XDRPROC_T(type) \ diff --git a/module/zcommon/zfeature_common.c b/module/zcommon/zfeature_common.c index 2c74d10f43ff..31006efbed39 100644 --- a/module/zcommon/zfeature_common.c +++ b/module/zcommon/zfeature_common.c @@ -175,7 +175,8 @@ struct zfs_mod_supported_features { struct zfs_mod_supported_features * zfs_mod_list_supported(const char *scope) { -#if defined(__FreeBSD__) || defined(_KERNEL) || defined(LIB_ZPOOL_BUILD) +#if defined(__FreeBSD__) || defined(__APPLE__) || \ + defined(_KERNEL) || defined(LIB_ZPOOL_BUILD) (void) scope; return (NULL); #else @@ -236,7 +237,8 @@ zfs_mod_list_supported(const char *scope) void zfs_mod_list_supported_free(struct zfs_mod_supported_features *list) { -#if !defined(__FreeBSD__) && !defined(_KERNEL) && !defined(LIB_ZPOOL_BUILD) +#if !defined(__FreeBSD__) && !defined(__APPLE__) && \ + !defined(_KERNEL) && !defined(LIB_ZPOOL_BUILD) if (list) { tdestroy(list->tree, free); free(list); @@ -310,7 +312,8 @@ zfs_mod_supported_feature(const char *name, * that all features are supported. */ -#if defined(_KERNEL) || defined(LIB_ZPOOL_BUILD) || defined(__FreeBSD__) +#if defined(_KERNEL) || defined(LIB_ZPOOL_BUILD) || \ + defined(__FreeBSD__) || defined(__APPLE__) (void) name, (void) sfeatures; return (B_TRUE); #else diff --git a/module/zcommon/zfs_fletcher.c b/module/zcommon/zfs_fletcher.c index 619ddef0243a..fa8876ca503f 100644 --- a/module/zcommon/zfs_fletcher.c +++ b/module/zcommon/zfs_fletcher.c @@ -188,7 +188,7 @@ static const fletcher_4_ops_t *fletcher_4_impls[] = { #if defined(__x86_64) && defined(HAVE_AVX512BW) &fletcher_4_avx512bw_ops, #endif -#if defined(__aarch64__) && !defined(__FreeBSD__) +#if defined(__aarch64__) && !defined(__FreeBSD__) && !defined(__APPLE__) &fletcher_4_aarch64_neon_ops, #endif }; @@ -947,9 +947,9 @@ fletcher_4_param_set(const char *val, zfs_kernel_param_t *unused) static int fletcher_4_param(ZFS_MODULE_PARAM_ARGS) { - int err; + int err = 0; - if (req->newptr == NULL) { + if ((const void *)req->newptr == NULL) { const uint32_t impl = IMPL_READ(fletcher_4_impl_chosen); const int init_buflen = 64; const char *fmt; @@ -968,9 +968,13 @@ fletcher_4_param(ZFS_MODULE_PARAM_ARGS) fletcher_4_supp_impls[i]->name); } +#ifdef __APPLE__ + err = SYSCTL_OUT(req, s->s_buf, s->s_len); + sbuf_finish(s); +#else err = sbuf_finish(s); +#endif sbuf_delete(s); - return (err); } diff --git a/module/zcommon/zfs_prop.c b/module/zcommon/zfs_prop.c index 29764674a31b..0dfd1ddace0c 100644 --- a/module/zcommon/zfs_prop.c +++ b/module/zcommon/zfs_prop.c @@ -398,6 +398,22 @@ zfs_prop_init(void) struct zfs_mod_supported_features *sfeatures = zfs_mod_list_supported(ZFS_SYSFS_DATASET_PROPERTIES); + /* __APPLE__ */ + static zprop_index_t devdisk_table[] = { + { "poolonly", ZFS_DEVDISK_POOLONLY }, + { "off", ZFS_DEVDISK_OFF }, + { "on", ZFS_DEVDISK_ON }, + { NULL } + }; + + static zprop_index_t mimic_table[] = { + { "off", ZFS_MIMIC_OFF }, + { "hfs", ZFS_MIMIC_HFS }, + { "apfs", ZFS_MIMIC_APFS }, + { NULL } + }; + /* __APPLE__ */ + /* inherit index properties */ zprop_register_index(ZFS_PROP_REDUNDANT_METADATA, "redundant_metadata", ZFS_REDUNDANT_METADATA_ALL, @@ -609,6 +625,24 @@ zfs_prop_init(void) ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK, "[,...]", "RSNAPS", sfeatures); + /* __APPLE__ */ + zprop_register_index(ZFS_PROP_BROWSE, "com.apple.browse", 1, + PROP_INHERIT, ZFS_TYPE_FILESYSTEM, "on | off", + "COM.APPLE.BROWSE", boolean_table, sfeatures); + zprop_register_index(ZFS_PROP_IGNOREOWNER, "com.apple.ignoreowner", 0, + PROP_INHERIT, ZFS_TYPE_FILESYSTEM, "on | off", + "COM.APPLE.IGNOREOWNER", boolean_table, sfeatures); + zprop_register_hidden(ZFS_PROP_LASTUNMOUNT, "COM.APPLE.LASTUNMOUNT", + PROP_TYPE_NUMBER, PROP_READONLY, ZFS_TYPE_DATASET, "LASTUNMOUNT", + B_FALSE, sfeatures); + zprop_register_index(ZFS_PROP_MIMIC, "com.apple.mimic", 0, + PROP_INHERIT, ZFS_TYPE_FILESYSTEM, "off | hfs | apfs", + "COM.APPLE.MIMIC_HFS", mimic_table, sfeatures); + zprop_register_index(ZFS_PROP_DEVDISK, "com.apple.devdisk", 0, + PROP_INHERIT, ZFS_TYPE_FILESYSTEM, "poolonly | on | off", + "COM.APPLE.DEVDISK", devdisk_table, sfeatures); + /* __APPLE__ */ + /* readonly number properties */ zprop_register_number(ZFS_PROP_USED, "used", 0, PROP_READONLY, ZFS_TYPE_DATASET, "", "USED", B_FALSE, sfeatures); diff --git a/module/zcommon/zprop_common.c b/module/zcommon/zprop_common.c index eb635b38ceb5..a6d707df4b76 100644 --- a/module/zcommon/zprop_common.c +++ b/module/zcommon/zprop_common.c @@ -82,7 +82,8 @@ zfs_mod_supported_prop(const char *name, zfs_type_t type, * The equivalent _can_ be done on FreeBSD by way of the sysctl * tree, but this has not been done yet. */ -#if defined(_KERNEL) || defined(LIB_ZPOOL_BUILD) || defined(__FreeBSD__) +#if defined(_KERNEL) || defined(LIB_ZPOOL_BUILD) || \ + defined(__FreeBSD__) || defined(__APPLE__) (void) name, (void) type, (void) sfeatures; return (B_TRUE); #else diff --git a/module/zfs/blake3_zfs.c b/module/zfs/blake3_zfs.c index 7783282b671a..50924534695b 100644 --- a/module/zfs/blake3_zfs.c +++ b/module/zfs/blake3_zfs.c @@ -49,7 +49,7 @@ abd_checksum_blake3_native(abd_t *abd, uint64_t size, const void *ctx_template, { ASSERT(ctx_template != NULL); -#if defined(_KERNEL) +#if defined(_KERNEL) && !(defined(__APPLE__) && defined(__aarch64__)) kpreempt_disable(); BLAKE3_CTX *ctx = blake3_per_cpu_ctx[CPU_SEQID]; #else @@ -60,7 +60,8 @@ abd_checksum_blake3_native(abd_t *abd, uint64_t size, const void *ctx_template, (void) abd_iterate_func(abd, 0, size, blake3_incremental, ctx); Blake3_Final(ctx, (uint8_t *)zcp); -#if defined(_KERNEL) +#if defined(_KERNEL) && !(defined(__APPLE__) && defined(__aarch64__)) + /* To keep conditionals the same */ kpreempt_enable(); #else memset(ctx, 0, sizeof (*ctx)); diff --git a/module/zfs/dbuf.c b/module/zfs/dbuf.c index c0c2692c113a..8a640fc444dc 100644 --- a/module/zfs/dbuf.c +++ b/module/zfs/dbuf.c @@ -3826,7 +3826,9 @@ dbuf_rm_spill(dnode_t *dn, dmu_tx_t *tx) dbuf_free_range(dn, DMU_SPILL_BLKID, DMU_SPILL_BLKID, tx); } +#ifndef __APPLE__ #pragma weak dmu_buf_add_ref = dbuf_add_ref +#endif void dbuf_add_ref(dmu_buf_impl_t *db, const void *tag) { @@ -3834,7 +3836,19 @@ dbuf_add_ref(dmu_buf_impl_t *db, const void *tag) VERIFY3S(holds, >, 1); } +#ifdef __APPLE__ +/* No #pragma weaks here! */ +void +dmu_buf_add_ref(dmu_buf_t *db, const void *tag) +{ + dbuf_add_ref((dmu_buf_impl_t *)db, tag); +} +#endif + +#ifndef __APPLE__ #pragma weak dmu_buf_try_add_ref = dbuf_try_add_ref +#endif + boolean_t dbuf_try_add_ref(dmu_buf_t *db_fake, objset_t *os, uint64_t obj, uint64_t blkid, const void *tag) @@ -3858,6 +3872,15 @@ dbuf_try_add_ref(dmu_buf_t *db_fake, objset_t *os, uint64_t obj, uint64_t blkid, return (result); } +#ifdef __APPLE__ +boolean_t +dmu_buf_try_add_ref(dmu_buf_t *db, objset_t *os, uint64_t object, + uint64_t blkid, const void *tag) +{ + return (dbuf_try_add_ref(db, os, object, blkid, tag)); +} +#endif + /* * If you call dbuf_rele() you had better not be referencing the dnode handle * unless you have some other direct or indirect hold on the dnode. (An indirect diff --git a/module/zfs/dsl_crypt.c b/module/zfs/dsl_crypt.c index 5e6e4e3d6c39..f835669178d5 100644 --- a/module/zfs/dsl_crypt.c +++ b/module/zfs/dsl_crypt.c @@ -2703,6 +2703,22 @@ spa_do_crypt_objset_mac_abd(boolean_t generate, spa_t *spa, uint64_t dsobj, return (0); } +#if defined(__APPLE__) && defined(_KERNEL) + /* + * Unfortunate errata case, see module/os/macos/zfs/zio_crypt.c + * If portable is GOOD, but local_mac is BAD - recompute. + * We were hoping this would not be required after the work + * on the incompatibilities, but users complain they can not + * mount older crypted datasets. + */ + if (memcmp(portable_mac, osp->os_portable_mac, + ZIO_OBJSET_MAC_LEN) == 0 && + memcmp(local_mac, osp->os_local_mac, ZIO_OBJSET_MAC_LEN) != 0) { + ret = zio_crypt_do_objset_hmacs_errata1(&dck->dck_key, buf, + datalen, byteswap, portable_mac, local_mac); + } +#endif + if (memcmp(portable_mac, osp->os_portable_mac, ZIO_OBJSET_MAC_LEN) != 0 || memcmp(local_mac, osp->os_local_mac, ZIO_OBJSET_MAC_LEN) != 0) { diff --git a/module/zfs/dsl_scan.c b/module/zfs/dsl_scan.c index 34012db82dee..0e85d821cf59 100644 --- a/module/zfs/dsl_scan.c +++ b/module/zfs/dsl_scan.c @@ -3488,8 +3488,13 @@ scan_io_queues_run(dsl_scan_t *scn) * scan_io_queues_run_one can occur during spa_sync runs * and that significantly impacts performance. */ +#if defined(__APPLE__) && defined(_KERNEL) + scn->scn_taskq = taskq_create("dsl_scan_iss", nthreads, + DSL_SCAN_ISS_SYSPRI, nthreads, nthreads, TASKQ_PREPOPULATE); +#else scn->scn_taskq = taskq_create("dsl_scan_iss", nthreads, minclsyspri, nthreads, nthreads, TASKQ_PREPOPULATE); +#endif } for (uint64_t i = 0; i < spa->spa_root_vdev->vdev_children; i++) { diff --git a/module/zfs/spa.c b/module/zfs/spa.c index aa97144f16e4..e9921627ae63 100644 --- a/module/zfs/spa.c +++ b/module/zfs/spa.c @@ -1164,6 +1164,27 @@ spa_taskqs_init(spa_t *spa, zio_type_t t, zio_taskq_type_t q) pri++; #elif defined(__FreeBSD__) pri += 4; +#elif defined(__APPLE__) + pri -= 4; +#if defined(_KERNEL) + } else { + /* + * we want to be below maclsyspri for zio + * taskqs on macOS, to avoid starving out + * base=81 (maxclsyspri) kernel tasks when + * doing computation-intensive checksums etc. + */ + pri -= 1; + } + /* macOS cannot handle TASKQ_DYNAMIC zio taskqs */ + + if ((flags & (TASKQ_DC_BATCH|TASKQ_DUTY_CYCLE)) == 0) + flags |= TASKQ_TIMESHARE; + + if (flags & TASKQ_DYNAMIC) { + flags &= ~TASKQ_DYNAMIC; + /* fallthrough to closing brace after #endif */ +#endif #else #error "unknown OS" #endif @@ -6386,12 +6407,12 @@ spa_import(char *pool, nvlist_t *config, nvlist_t *props, uint64_t flags) spa_event_notify(spa, NULL, NULL, ESC_ZFS_POOL_IMPORT); + spa_import_os(spa); + mutex_exit(&spa_namespace_lock); zvol_create_minors_recursive(pool); - spa_import_os(spa); - return (0); } diff --git a/module/zfs/spa_errlog.c b/module/zfs/spa_errlog.c index 5dd08f597f33..fc8336a8209b 100644 --- a/module/zfs/spa_errlog.c +++ b/module/zfs/spa_errlog.c @@ -303,7 +303,7 @@ copyout_entry(const zbookmark_phys_t *zb, void *uaddr, uint64_t *count) return (SET_ERROR(ENOMEM)); *count -= 1; - if (copyout(zb, (char *)uaddr + (*count) * sizeof (zbookmark_phys_t), + if (xcopyout(zb, (char *)uaddr + (*count) * sizeof (zbookmark_phys_t), sizeof (zbookmark_phys_t)) != 0) return (SET_ERROR(EFAULT)); return (0); diff --git a/module/zfs/spa_misc.c b/module/zfs/spa_misc.c index c7472f972cc2..6d6350d9fa85 100644 --- a/module/zfs/spa_misc.c +++ b/module/zfs/spa_misc.c @@ -645,7 +645,7 @@ spa_deadman(void *arg) zfs_dbgmsg("slow spa_sync: started %llu seconds ago, calls %llu", (gethrtime() - spa->spa_sync_starttime) / NANOSEC, (u_longlong_t)++spa->spa_deadman_calls); - if (zfs_deadman_enabled) + if (zfs_deadman_enabled && spa->spa_root_vdev != NULL) vdev_deadman(spa->spa_root_vdev, FTAG); spa->spa_deadman_tqid = taskq_dispatch_delay(system_delay_taskq, diff --git a/module/zfs/vdev_raidz_math.c b/module/zfs/vdev_raidz_math.c index e12b96170f55..5385e2510597 100644 --- a/module/zfs/vdev_raidz_math.c +++ b/module/zfs/vdev_raidz_math.c @@ -61,7 +61,7 @@ static const raidz_impl_ops_t *const raidz_all_maths[] = { #if defined(__x86_64) && defined(HAVE_AVX512BW) /* only x86_64 for now */ &vdev_raidz_avx512bw_impl, #endif -#if defined(__aarch64__) && !defined(__FreeBSD__) +#if defined(__aarch64__) && !defined(__FreeBSD__) && !defined(__APPLE__) &vdev_raidz_aarch64_neon_impl, &vdev_raidz_aarch64_neonx2_impl, #endif @@ -633,14 +633,17 @@ vdev_raidz_impl_set(const char *val) return (err); } -#if defined(_KERNEL) && defined(__linux__) +#if defined(_KERNEL) +#if defined(__linux__) static int zfs_vdev_raidz_impl_set(const char *val, zfs_kernel_param_t *kp) { return (vdev_raidz_impl_set(val)); } +#endif +#if defined(__linux__) || defined(__APPLE__) static int zfs_vdev_raidz_impl_get(char *buffer, zfs_kernel_param_t *kp) { @@ -667,6 +670,29 @@ zfs_vdev_raidz_impl_get(char *buffer, zfs_kernel_param_t *kp) return (cnt); } +#endif /* defined(Linux) || defined(APPLE) */ + +#if defined(__APPLE__) +/* get / set function */ +int +param_zfs_vdev_raidz_impl_set(ZFS_MODULE_PARAM_ARGS) +{ + char buf[1024]; /* Linux module string limit */ + int rc = 0; + + /* Always fill in value before calling sysctl_handle_*() */ + if (req->newptr == (user_addr_t)NULL) + (void) zfs_vdev_raidz_impl_get(buf, NULL); + + rc = sysctl_handle_string(oidp, buf, sizeof (buf), req); + if (rc || req->newptr == (user_addr_t)NULL) + return (rc); + + rc = vdev_raidz_impl_set(buf); + return (rc); +} +#endif /* defined(APPLE) */ + module_param_call(zfs_vdev_raidz_impl, zfs_vdev_raidz_impl_set, zfs_vdev_raidz_impl_get, NULL, 0644); MODULE_PARM_DESC(zfs_vdev_raidz_impl, "Select raidz implementation."); diff --git a/module/zfs/zfs_fuid.c b/module/zfs/zfs_fuid.c index add4241dcc99..301ab70a8339 100644 --- a/module/zfs/zfs_fuid.c +++ b/module/zfs/zfs_fuid.c @@ -409,7 +409,24 @@ zfs_fuid_map_id(zfsvfs_t *zfsvfs, uint64_t fuid, */ return (fuid); } +#elif defined(__APPLE__) +uid_t +zfs_fuid_map_id(zfsvfs_t *zfsvfs, uint64_t fuid, + cred_t *cr, zfs_fuid_type_t type) +{ + uint32_t index = FUID_INDEX(fuid); + const char *domain; + uid_t id; + if (index == 0) + return (fuid); + + domain = zfs_fuid_find_by_idx(zfsvfs, index); + ASSERT(domain != NULL); + + id = UID_NOBODY; + return (id); +} #else uid_t zfs_fuid_map_id(zfsvfs_t *zfsvfs, uint64_t fuid, diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index 2738385e260b..67ded1537b96 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -7027,7 +7027,7 @@ zfs_ioctl_register(const char *name, zfs_ioc_t ioc, zfs_ioc_func_t *func, vec->zvec_nvl_key_count = num_keys; } -static void +void zfs_ioctl_register_pool(zfs_ioc_t ioc, zfs_ioc_legacy_func_t *func, zfs_secpolicy_func_t *secpolicy, boolean_t log_history, zfs_ioc_poolcheck_t pool_check) diff --git a/module/zfs/zfs_log.c b/module/zfs/zfs_log.c index 50325907b0d1..3beff3ce3998 100644 --- a/module/zfs/zfs_log.c +++ b/module/zfs/zfs_log.c @@ -253,6 +253,28 @@ zfs_xattr_owner_unlinked(znode_t *zp) } if (tzp != zp) zrele(tzp); +#elif defined(__APPLE__) + znode_t *tzp = zp; + + /* + * if zp is XATTR node, keep walking up via z_xattr_parent + * until we get the owner + */ + while (tzp->z_pflags & ZFS_XATTR) { + ASSERT3U(tzp->z_xattr_parent, !=, 0); + if (zfs_zget(ZTOZSB(tzp), tzp->z_xattr_parent, + &dzp) != 0) { + unlinked = 1; + break; + } + + if (tzp != zp) + zrele(tzp); + tzp = dzp; + unlinked = tzp->z_unlinked; + } + if (tzp != zp) + zrele(tzp); #else zhold(zp); /* diff --git a/module/zfs/zfs_sa.c b/module/zfs/zfs_sa.c index fb2443b756f8..4d41ff715511 100644 --- a/module/zfs/zfs_sa.c +++ b/module/zfs/zfs_sa.c @@ -67,6 +67,8 @@ const sa_attr_reg_t zfs_attr_table[ZPL_END+1] = { {"ZPL_DACL_ACES", 0, SA_ACL, 0}, {"ZPL_DXATTR", 0, SA_UINT8_ARRAY, 0}, {"ZPL_PROJID", sizeof (uint64_t), SA_UINT64_ARRAY, 0}, + {"ZPL_ADDTIME", sizeof (uint64_t) * 2, SA_UINT64_ARRAY, 0}, + {"ZPL_DOCUMENTID", sizeof (uint64_t), SA_UINT64_ARRAY, 0}, {NULL, 0, 0, 0} }; diff --git a/module/zfs/zfs_vnops.c b/module/zfs/zfs_vnops.c index 40d6c87a754e..ba1ef36e6358 100644 --- a/module/zfs/zfs_vnops.c +++ b/module/zfs/zfs_vnops.c @@ -58,6 +58,8 @@ #include +#ifndef __APPLE__ + static ulong_t zfs_fsync_sync_cnt = 4; int @@ -81,7 +83,7 @@ zfs_fsync(znode_t *zp, int syncflag, cred_t *cr) return (error); } - +#endif #if defined(SEEK_HOLE) && defined(SEEK_DATA) /* @@ -579,7 +581,7 @@ zfs_write(znode_t *zp, zfs_uio_t *uio, int ioflag, cred_t *cr) ASSERT(abuf != NULL); ASSERT(arc_buf_size(abuf) == blksz); if ((error = zfs_uiocopy(abuf->b_data, blksz, - UIO_WRITE, uio, &nbytes))) { + UIO_WRITE, uio, (size_t *)&nbytes))) { dmu_return_arcbuf(abuf); break; } @@ -844,8 +846,13 @@ zfs_get_data(void *arg, uint64_t gen, lr_write_t *lr, char *buf, /* * Nothing to do if the file has been removed */ +#ifndef __APPLE__ if (zfs_zget(zfsvfs, object, &zp) != 0) return (SET_ERROR(ENOENT)); +#else + if (zfs_zget_ext(zfsvfs, object, &zp, ZGET_FLAG_ASYNC) != 0) + return (SET_ERROR(ENOENT)); +#endif if (zp->z_unlinked) { /* * Release the vnode asynchronously as we currently have the diff --git a/module/zfs/zvol.c b/module/zfs/zvol.c index cc11fd80618b..2dbb0710923c 100644 --- a/module/zfs/zvol.c +++ b/module/zfs/zvol.c @@ -90,7 +90,7 @@ unsigned int zvol_inhibit_dev = 0; unsigned int zvol_volmode = ZFS_VOLMODE_GEOM; struct hlist_head *zvol_htable; -static list_t zvol_state_list; +list_t zvol_state_list; krwlock_t zvol_state_lock; typedef enum { diff --git a/module/zstd/include/limits.h b/module/zstd/include/limits.h index 3bf5b67765ae..fdd3e3dc724a 100644 --- a/module/zstd/include/limits.h +++ b/module/zstd/include/limits.h @@ -48,6 +48,7 @@ extern "C" { #elif defined(__linux__) #include #include +#elif defined(__APPLE__) #else #error "Unsupported platform" #endif diff --git a/module/zstd/include/stddef.h b/module/zstd/include/stddef.h index 3f46fb8b033e..5805ab76248d 100644 --- a/module/zstd/include/stddef.h +++ b/module/zstd/include/stddef.h @@ -47,6 +47,7 @@ extern "C" { #include #elif defined(__linux__) #include +#elif defined(__APPLE__) #else #error "Unsupported platform" #endif diff --git a/module/zstd/include/stdint.h b/module/zstd/include/stdint.h index 2d98a556c23e..6ce1f435603c 100644 --- a/module/zstd/include/stdint.h +++ b/module/zstd/include/stdint.h @@ -47,6 +47,7 @@ extern "C" { #include #elif defined(__linux__) #include +#elif defined(__APPLE__) #else #error "Unsupported platform" #endif diff --git a/module/zstd/include/string.h b/module/zstd/include/string.h index 7474e7f1af0f..cd7e13784e01 100644 --- a/module/zstd/include/string.h +++ b/module/zstd/include/string.h @@ -48,6 +48,7 @@ extern "C" { #include /* memcpy, memset */ #elif defined(__linux__) #include /* memcpy, memset */ +#elif defined(__APPLE__) #else #error "Unsupported platform" #endif diff --git a/module/zstd/lib/common/zstd_internal.h b/module/zstd/lib/common/zstd_internal.h index 4a86d186a967..e7d37f5b7939 100644 --- a/module/zstd/lib/common/zstd_internal.h +++ b/module/zstd/lib/common/zstd_internal.h @@ -19,7 +19,7 @@ /*-************************************* * Dependencies ***************************************/ -#if !defined(ZSTD_NO_INTRINSICS) && defined(__ARM_NEON) +#if !defined(ZSTD_NO_INTRINSICS) && defined(__ARM_NEON) && !defined(__APPLE__) #include #endif #include "compiler.h" @@ -227,7 +227,7 @@ static const U32 OF_defaultNormLog = OF_DEFAULTNORMLOG; * Shared functions to include for inlining *********************************************/ static void ZSTD_copy8(void* dst, const void* src) { -#if !defined(ZSTD_NO_INTRINSICS) && defined(__ARM_NEON) +#if !defined(ZSTD_NO_INTRINSICS) && defined(__ARM_NEON) && !defined(__APPLE__) vst1_u8((uint8_t*)dst, vld1_u8((const uint8_t*)src)); #else memcpy(dst, src, 8); @@ -236,7 +236,7 @@ static void ZSTD_copy8(void* dst, const void* src) { #define COPY8(d,s) { ZSTD_copy8(d,s); d+=8; s+=8; } static void ZSTD_copy16(void* dst, const void* src) { -#if !defined(ZSTD_NO_INTRINSICS) && defined(__ARM_NEON) +#if !defined(ZSTD_NO_INTRINSICS) && defined(__ARM_NEON) && !defined(__APPLE__) vst1q_u8((uint8_t*)dst, vld1q_u8((const uint8_t*)src)); #else memcpy(dst, src, 16); @@ -260,7 +260,7 @@ typedef enum { * - ZSTD_overlap_src_before_dst: The src and dst may overlap, but they MUST be at least 8 bytes apart. * The src buffer must be before the dst buffer. */ -MEM_STATIC FORCE_INLINE_ATTR +MEM_STATIC FORCE_INLINE_ATTR void ZSTD_wildcopy(void* dst, const void* src, ptrdiff_t length, ZSTD_overlap_e const ovtype) { ptrdiff_t diff = (BYTE*)dst - (const BYTE*)src; From 12610e7d6a9e177150f68331b71680ec93e1080e Mon Sep 17 00:00:00 2001 From: Jorgen Lundman Date: Mon, 23 Oct 2023 09:04:12 +0900 Subject: [PATCH 3/9] 3: cmd/* and tests/* part3: cmd/* + tests/* Signed-off-by: Jorgen Lundman --- cmd/Makefile.am | 5 +- cmd/arcstat.in | 16 + cmd/os/Makefile.am | 2 + cmd/zdb/zdb.c | 8 +- cmd/zed/Makefile.am | 3 + cmd/zed/agents/fmd_api.h | 2 +- cmd/zed/zed.d/Makefile.am | 12 +- cmd/zed/zed.d/snapshot_mount.sh | 30 + cmd/zed/zed.d/snapshot_unmount.sh | 1 + cmd/zed/zed.d/zvol_create.sh | 28 + cmd/zed/zed.d/zvol_remove.sh | 23 + cmd/zfs/Makefile.am | 4 + cmd/zfs/zfs_main.c | 58 +- cmd/zpool/Makefile.am | 10 + cmd/zpool/zpool_vdev.c | 29 +- tests/runfiles/darwin.run | 0 tests/runfiles/large.run | 822 ++++++++++++++++++ tests/runfiles/macOS-CI.run | 350 ++++++++ tests/zfs-tests/cmd/Makefile.am | 6 + tests/zfs-tests/cmd/checksum/blake3_test.c | 2 + tests/zfs-tests/cmd/dosmode_readonly_write.c | 7 + tests/zfs-tests/cmd/librt/Makefile.am | 12 + tests/zfs-tests/cmd/librt/mach_gettime.c | 28 + tests/zfs-tests/cmd/mmap_seek.c | 4 + tests/zfs-tests/include/blkdev.shlib | 11 +- tests/zfs-tests/include/default.cfg.in | 32 + tests/zfs-tests/include/libtest.shlib | 289 +++++- tests/zfs-tests/include/tunables.cfg | 162 ++-- .../tests/functional/hkdf/hkdf_test.c | 1 + tests/zfs-tests/tests/perf/perf.shlib | 9 + 30 files changed, 1864 insertions(+), 102 deletions(-) create mode 100644 cmd/os/Makefile.am create mode 100644 cmd/zed/zed.d/snapshot_mount.sh create mode 120000 cmd/zed/zed.d/snapshot_unmount.sh create mode 100755 cmd/zed/zed.d/zvol_create.sh create mode 100755 cmd/zed/zed.d/zvol_remove.sh create mode 100644 tests/runfiles/darwin.run create mode 100644 tests/runfiles/large.run create mode 100644 tests/runfiles/macOS-CI.run create mode 100644 tests/zfs-tests/cmd/librt/Makefile.am create mode 100644 tests/zfs-tests/cmd/librt/mach_gettime.c diff --git a/cmd/Makefile.am b/cmd/Makefile.am index 6d6de4adb42a..a6c288274657 100644 --- a/cmd/Makefile.am +++ b/cmd/Makefile.am @@ -98,6 +98,10 @@ SHELLCHECKSCRIPTS += %D%/zvol_wait include $(srcdir)/%D%/zed/Makefile.am endif +if BUILD_MACOS +include $(srcdir)/%D%/zed/Makefile.am +include $(srcdir)/%D%/os/Makefile.am +endif if USING_PYTHON bin_SCRIPTS += arc_summary arcstat dbufstat zilstat @@ -111,6 +115,5 @@ arc_summary: %D%/arc_summary $(AM_V_at)cp $< $@ endif - PHONY += cmd cmd: $(bin_SCRIPTS) $(bin_PROGRAMS) $(sbin_SCRIPTS) $(sbin_PROGRAMS) $(dist_bin_SCRIPTS) $(zfsexec_PROGRAMS) $(mounthelper_PROGRAMS) diff --git a/cmd/arcstat.in b/cmd/arcstat.in index 8df1c62f7e86..6e46b6e233ac 100755 --- a/cmd/arcstat.in +++ b/cmd/arcstat.in @@ -220,7 +220,23 @@ elif sys.platform.startswith('linux'): name, unused, value = s.split() kstat[name] = int(value) +elif sys.platform.startswith('darwin'): + import subprocess + def kstat_update(): + global kstat + + process = subprocess.Popen(['sysctl', 'kstat.zfs.misc.arcstats'], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + kstats = process.communicate()[0].decode('ascii').splitlines() + + kstat = {} + for l in kstats: + items = l.split(':') + name = items[0] + value = items[1].strip() + kstat[name[24:]] = int(value) def detailed_usage(): sys.stderr.write("%s\n" % cmd) diff --git a/cmd/os/Makefile.am b/cmd/os/Makefile.am new file mode 100644 index 000000000000..543ff7142808 --- /dev/null +++ b/cmd/os/Makefile.am @@ -0,0 +1,2 @@ + +include $(srcdir)/%D%/macos/Makefile.am diff --git a/cmd/zdb/zdb.c b/cmd/zdb/zdb.c index 3c282f3fc975..968ce620a106 100644 --- a/cmd/zdb/zdb.c +++ b/cmd/zdb/zdb.c @@ -2189,16 +2189,16 @@ dump_history(spa_t *spa) if (ievent >= ZFS_NUM_LEGACY_HISTORY_EVENTS) goto next; - (void) printf(" %s [internal %s txg:%ju] %s\n", + (void) printf(" %s [internal %s txg:%llu] %s\n", tbuf, zfs_history_event_names[ievent], - fnvlist_lookup_uint64(events[i], + (u_longlong_t)fnvlist_lookup_uint64(events[i], ZPOOL_HIST_TXG), fnvlist_lookup_string(events[i], ZPOOL_HIST_INT_STR)); } else if (nvlist_exists(events[i], ZPOOL_HIST_INT_NAME)) { - (void) printf("%s [txg:%ju] %s", tbuf, - fnvlist_lookup_uint64(events[i], + (void) printf("%s [txg:%llu] %s", tbuf, + (u_longlong_t)fnvlist_lookup_uint64(events[i], ZPOOL_HIST_TXG), fnvlist_lookup_string(events[i], ZPOOL_HIST_INT_NAME)); diff --git a/cmd/zed/Makefile.am b/cmd/zed/Makefile.am index c437ff51dd2b..11f906f59e50 100644 --- a/cmd/zed/Makefile.am +++ b/cmd/zed/Makefile.am @@ -40,7 +40,10 @@ zed_LDADD = \ libnvpair.la \ libuutil.la +if !BUILD_MACOS zed_LDADD += -lrt $(LIBATOMIC_LIBS) $(LIBUDEV_LIBS) $(LIBUUID_LIBS) +endif + zed_LDFLAGS = -pthread dist_noinst_DATA += %D%/agents/README.md diff --git a/cmd/zed/agents/fmd_api.h b/cmd/zed/agents/fmd_api.h index b940d0d395ec..c0fa2d5f2efc 100644 --- a/cmd/zed/agents/fmd_api.h +++ b/cmd/zed/agents/fmd_api.h @@ -105,7 +105,7 @@ typedef struct fmd_stat { uint_t fmds_type; /* statistic type (see above) */ char fmds_desc[64]; /* statistic description */ union { - int bool; /* FMD_TYPE_BOOL */ + int fmds_bool; /* FMD_TYPE_BOOL */ int32_t i32; /* FMD_TYPE_INT32 */ uint32_t ui32; /* FMD_TYPE_UINT32 */ int64_t i64; /* FMD_TYPE_INT64 */ diff --git a/cmd/zed/zed.d/Makefile.am b/cmd/zed/zed.d/Makefile.am index 812558cf6d0f..f8a54b9e4f3b 100644 --- a/cmd/zed/zed.d/Makefile.am +++ b/cmd/zed/zed.d/Makefile.am @@ -14,12 +14,16 @@ dist_zedexec_SCRIPTS = \ %D%/resilver_finish-notify.sh \ %D%/resilver_finish-start-scrub.sh \ %D%/scrub_finish-notify.sh \ + %D%/snapshot_mount.sh \ + %D%/snapshot_unmount.sh \ %D%/statechange-led.sh \ %D%/statechange-notify.sh \ %D%/statechange-slot_off.sh \ %D%/trim_finish-notify.sh \ %D%/vdev_attach-led.sh \ - %D%/vdev_clear-led.sh + %D%/vdev_clear-led.sh \ + %D%/zvol_create.sh \ + %D%/zvol_remove.sh nodist_zedexec_SCRIPTS = \ %D%/history_event-zfs-list-cacher.sh @@ -34,11 +38,15 @@ zedconfdefaults = \ resilver_finish-notify.sh \ resilver_finish-start-scrub.sh \ scrub_finish-notify.sh \ + snapshot_mount.sh \ + snapshot_unmount.sh \ statechange-led.sh \ statechange-notify.sh \ statechange-slot_off.sh \ vdev_attach-led.sh \ - vdev_clear-led.sh + vdev_clear-led.sh \ + zvol_create.sh \ + zvol_remove.sh dist_noinst_DATA += %D%/README diff --git a/cmd/zed/zed.d/snapshot_mount.sh b/cmd/zed/zed.d/snapshot_mount.sh new file mode 100644 index 000000000000..111b692cea2f --- /dev/null +++ b/cmd/zed/zed.d/snapshot_mount.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# shellcheck disable=SC2154 +# +# Helper to mount and unmount snapshots when asked to by kernel. +# +# Mostly used in macOS. +# +set -ef + +[ -f "${ZED_ZEDLET_DIR}/zed.rc" ] && . "${ZED_ZEDLET_DIR}/zed.rc" +. "${ZED_ZEDLET_DIR}/zed-functions.sh" + +[ -n "${ZEVENT_SNAPSHOT_NAME}" ] || exit 1 +[ -n "${ZEVENT_SUBCLASS}" ] || exit 2 + +if [ "${ZEVENT_SUBCLASS}" = "snapshot_mount" ]; then + action="mount" +elif [ "${ZEVENT_SUBCLASS}" = "snapshot_unmount" ]; then + action="unmount" +else + zed_log_err "unsupported event class \"${ZEVENT_SUBCLASS}\"" + exit 3 +fi + +zed_exit_if_ignoring_this_event +zed_check_cmd "${ZFS}" || exit 4 + +"${ZFS}" "${action}" "${ZEVENT_SNAPSHOT_NAME}" + +finished diff --git a/cmd/zed/zed.d/snapshot_unmount.sh b/cmd/zed/zed.d/snapshot_unmount.sh new file mode 120000 index 000000000000..9f74a29e61f4 --- /dev/null +++ b/cmd/zed/zed.d/snapshot_unmount.sh @@ -0,0 +1 @@ +snapshot_mount.sh \ No newline at end of file diff --git a/cmd/zed/zed.d/zvol_create.sh b/cmd/zed/zed.d/zvol_create.sh new file mode 100755 index 000000000000..99f3370e3eab --- /dev/null +++ b/cmd/zed/zed.d/zvol_create.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# shellcheck disable=SC2154 +# +# Log the zevent via syslog. +# + +# Given POOL and DATASET name for ZVOL +# DEVICE_NAME for /dev/disk* +# RAW_DEVICE_NAME for /dev/rdisk* +# Create symlink in +# /var/run/zfs/zvol/dsk/POOL/DATASET -> /dev/disk* +# /var/run/zfs/zvol/rdsk/POOL/DATASET -> /dev/rdisk* + +ZVOL_ROOT="/var/run/zfs/zvol" + +mkdir -p "$(dirname "${ZVOL_ROOT}/rdsk/${ZEVENT_POOL}/${ZEVENT_VOLUME}")" "$(dirname "${ZVOL_ROOT}/dsk/${ZEVENT_POOL}/${ZEVENT_VOLUME}")" + +# Remove them if they already exist. (ln -f is not portable) +rm -f "${ZVOL_ROOT}/rdsk/${ZEVENT_POOL}/${ZEVENT_VOLUME}" "${ZVOL_ROOT}/dsk/${ZEVENT_POOL}/${ZEVENT_VOLUME}" + +ln -s "/dev/${ZEVENT_DEVICE_NAME}" "${ZVOL_ROOT}/dsk/${ZEVENT_POOL}/${ZEVENT_VOLUME}" +ln -s "/dev/${ZEVENT_RAW_NAME}" "${ZVOL_ROOT}/rdsk/${ZEVENT_POOL}/${ZEVENT_VOLUME}" + +logger -t "${ZED_SYSLOG_TAG:=zed}" -p "${ZED_SYSLOG_PRIORITY:=daemon.notice}" \ + eid="${ZEVENT_EID}" class="${ZEVENT_SUBCLASS}" \ + "${ZEVENT_POOL:+pool=$ZEVENT_POOL}/${ZEVENT_VOLUME} symlinked ${ZEVENT_DEVICE_NAME}" + +echo 0 diff --git a/cmd/zed/zed.d/zvol_remove.sh b/cmd/zed/zed.d/zvol_remove.sh new file mode 100755 index 000000000000..1de53af38b68 --- /dev/null +++ b/cmd/zed/zed.d/zvol_remove.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# shellcheck disable=SC2154 +# +# Log the zevent via syslog. +# + +# Given POOL and DATASET name for ZVOL +# DEVICE_NAME for /dev/disk* +# RAW_DEVICE_NAME for /dev/rdisk* +# Create symlink in +# /var/run/zfs/zvol/dsk/POOL/DATASET -> /dev/disk* +# /var/run/zfs/zvol/rdsk/POOL/DATASET -> /dev/rdisk* + +ZVOL_ROOT="/var/run/zfs/zvol" + +rm -f "${ZVOL_ROOT}/rdsk/${ZEVENT_POOL}/${ZEVENT_VOLUME}" "${ZVOL_ROOT}/dsk/${ZEVENT_POOL}/${ZEVENT_VOLUME}" +rmdir "$(dirname "${ZVOL_ROOT}/rdsk/${ZEVENT_POOL}/${ZEVENT_VOLUME}")" "$(dirname "${ZVOL_ROOT}/dsk/${ZEVENT_POOL}/${ZEVENT_VOLUME}")" + +logger -t "${ZED_SYSLOG_TAG:=zed}" -p "${ZED_SYSLOG_PRIORITY:=daemon.notice}" \ + eid="${ZEVENT_EID}" class="${ZEVENT_SUBCLASS}" \ + "${ZEVENT_POOL:+pool=$ZEVENT_POOL}/${ZEVENT_VOLUME} removed symlink" + +echo 0 diff --git a/cmd/zfs/Makefile.am b/cmd/zfs/Makefile.am index 8a3c13a1fcfe..eaf2530e5eea 100644 --- a/cmd/zfs/Makefile.am +++ b/cmd/zfs/Makefile.am @@ -20,3 +20,7 @@ zfs_LDADD += $(LTLIBINTL) if BUILD_FREEBSD zfs_LDADD += -lgeom -ljail endif + +if BUILD_MACOS +zfs_LDFLAGS = -sectcreate __TEXT __info_plist %D%/../zpool/os/macos/Info-zfs.plist +endif diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 9939f206a7f2..f86db5ed8424 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -789,7 +789,7 @@ zfs_mount_and_share(libzfs_handle_t *hdl, const char *dataset, zfs_type_t type) } else if (zfs_share(zhp, NULL) != 0) { (void) fprintf(stderr, gettext("filesystem " "successfully created, but not shared\n")); - ret = 1; + ret = 0; } zfs_commit_shares(NULL); } @@ -1459,6 +1459,9 @@ destroy_callback(zfs_handle_t *zhp, void *data) if (zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT) { cb->cb_snap_count++; fnvlist_add_boolean(cb->cb_batchedsnaps, name); +#ifdef __APPLE__ + zfs_snapshot_unmount(zhp, cb->cb_force ? MS_FORCE : 0); +#endif if (cb->cb_snap_count % 10 == 0 && cb->cb_defer_destroy) { error = destroy_batched(cb); if (error != 0) { @@ -4187,6 +4190,11 @@ zfs_do_rollback(int argc, char **argv) */ ret = zfs_rollback(zhp, snap, force); +#ifdef __APPLE__ + if (ret == 0) + zfs_rollback_os(zhp); +#endif + out: zfs_close(snap); zfs_close(zhp); @@ -7285,6 +7293,39 @@ share_mount(int op, int argc, char **argv) (void) fclose(mnttab); } else { +#if defined(__APPLE__) + /* + * OsX can not mount from kernel, users are expected to mount + * by hand using "zfs mount dataset@snapshot". + */ + zfs_handle_t *zhp; + + if (argc > 1) { + (void) fprintf(stderr, + gettext("too many arguments\n")); + usage(B_FALSE); + } + + if ((zhp = zfs_open(g_zfs, argv[0], + ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT)) == NULL) { + ret = 1; + } else { + + if (zfs_get_type(zhp)&ZFS_TYPE_SNAPSHOT) { + + ret = zfs_snapshot_mount(zhp, options, flags); + + } else { + + ret = share_mount_one(zhp, op, flags, + SA_NO_PROTOCOL, B_TRUE, options); + } + + zfs_close(zhp); + } + +#else // APPLE + zfs_handle_t *zhp; if (argc > 1) { @@ -7302,6 +7343,7 @@ share_mount(int op, int argc, char **argv) zfs_commit_shares(NULL); zfs_close(zhp); } +#endif // !APPLE } free(options); @@ -7673,9 +7715,23 @@ unshare_unmount(int op, int argc, char **argv) return (unshare_unmount_path(op, argv[0], flags, B_FALSE)); +#if defined(__APPLE__) + /* Temporarily, allow mounting snapshots on OS X */ + + if ((zhp = zfs_open(g_zfs, argv[0], + ZFS_TYPE_FILESYSTEM | ZFS_TYPE_SNAPSHOT)) == NULL) + return (1); + + if (zfs_get_type(zhp) & ZFS_TYPE_SNAPSHOT) { + ret = zfs_snapshot_unmount(zhp, flags); + zfs_close(zhp); + return (ret); + } +#else if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_FILESYSTEM)) == NULL) return (1); +#endif verify(zfs_prop_get(zhp, op == OP_SHARE ? ZFS_PROP_SHARENFS : ZFS_PROP_MOUNTPOINT, diff --git a/cmd/zpool/Makefile.am b/cmd/zpool/Makefile.am index d08b8e1791b6..5e8a1959b0b8 100644 --- a/cmd/zpool/Makefile.am +++ b/cmd/zpool/Makefile.am @@ -24,6 +24,10 @@ zpool_SOURCES += \ %D%/os/linux/zpool_vdev_os.c endif +if BUILD_MACOS +zpool_SOURCES += %D%/os/macos/zpool_vdev_os.c +endif + zpool_LDADD = \ libzfs.la \ libzfs_core.la \ @@ -36,6 +40,12 @@ zpool_LDADD += $(LTLIBINTL) if BUILD_FREEBSD zpool_LDADD += -lgeom endif + +if BUILD_MACOS +zpool_LDFLAGS = -sectcreate __TEXT __info_plist %D%/os/macos/Info-zpool.plist +zpool_LDFLAGS += -sectcreate __TEXT __entitlements %D%/os/macos/zpool-entitlements.plist +endif + zpool_LDADD += -lm $(LIBBLKID_LIBS) $(LIBUUID_LIBS) dist_noinst_DATA += %D%/zpool.d/README diff --git a/cmd/zpool/zpool_vdev.c b/cmd/zpool/zpool_vdev.c index 3d0fc089c32f..8557c2eea750 100644 --- a/cmd/zpool/zpool_vdev.c +++ b/cmd/zpool/zpool_vdev.c @@ -66,6 +66,7 @@ #include #include #include +#include #include #include #include @@ -272,6 +273,9 @@ static nvlist_t * make_leaf_vdev(nvlist_t *props, const char *arg, boolean_t is_primary) { char path[MAXPATHLEN]; + char *d, *b; + char *dpath; + const char *bname; struct stat64 statbuf; nvlist_t *vdev = NULL; const char *type = NULL; @@ -307,8 +311,29 @@ make_leaf_vdev(nvlist_t *props, const char *arg, boolean_t is_primary) return (NULL); } - /* After whole disk check restore original passed path */ - strlcpy(path, arg, sizeof (path)); + /* + * After whole disk check restore original passed path and use + * the realpath of the directory. + */ + d = strdup(arg); + b = strdup(arg); + int idx = zfs_dirnamelen(d); + if (idx != -1) + d[idx] = 0; + dpath = d; + bname = zfs_basename(b); + if (realpath(dpath, path) == NULL) { + (void) fprintf(stderr, + gettext("cannot resolve path '%s'\n"), dpath); + free(d); + free(b); + return (NULL); + } + + strlcat(path, "/", sizeof (path)); + strlcat(path, bname, sizeof (path)); + free(d); + free(b); } else if (zpool_is_draid_spare(arg)) { if (!is_primary) { (void) fprintf(stderr, diff --git a/tests/runfiles/darwin.run b/tests/runfiles/darwin.run new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/runfiles/large.run b/tests/runfiles/large.run new file mode 100644 index 000000000000..65de03497fbb --- /dev/null +++ b/tests/runfiles/large.run @@ -0,0 +1,822 @@ +# +# 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. +# + +[DEFAULT] +pre = setup +quiet = False +pre_user = root +user = root +timeout = 600 +post_user = root +post = cleanup +failsafe_user = root +failsafe = callbacks/zfs_failsafe +outputdir = /var/tmp/test_results +tags = ['functional'] + +[tests/functional/alloc_class] +tests = ['alloc_class_001_pos', 'alloc_class_002_neg', 'alloc_class_003_pos', + 'alloc_class_004_pos', 'alloc_class_005_pos', 'alloc_class_006_pos', + 'alloc_class_007_pos', 'alloc_class_008_pos', 'alloc_class_009_pos', + 'alloc_class_010_pos', 'alloc_class_011_neg', 'alloc_class_012_pos', + 'alloc_class_013_pos'] +tags = ['functional', 'alloc_class'] + +[tests/functional/atime] +tests = ['atime_001_pos', 'atime_002_neg', 'root_atime_off', 'root_atime_on'] +tags = ['functional', 'atime'] + +[tests/functional/bootfs] +tests = ['bootfs_001_pos', 'bootfs_002_neg', 'bootfs_003_pos', + 'bootfs_004_neg', +#macOS 10mins timeout +#'bootfs_005_neg', + 'bootfs_006_pos', 'bootfs_007_pos', + 'bootfs_008_pos'] +tags = ['functional', 'bootfs'] + +[tests/functional/btree] +tests = ['btree_positive', 'btree_negative'] +tags = ['functional', 'btree'] +pre = +post = + +[tests/functional/cache] +tests = ['cache_001_pos', 'cache_002_pos', 'cache_003_pos', 'cache_004_neg', + 'cache_005_neg', 'cache_006_pos', 'cache_007_neg', 'cache_008_neg', + 'cache_009_pos', 'cache_010_pos', 'cache_011_pos', 'cache_012_pos'] +tags = ['functional', 'cache'] + +[tests/functional/cachefile] +tests = ['cachefile_001_pos', 'cachefile_002_pos', 'cachefile_003_pos', + 'cachefile_004_pos'] +tags = ['functional', 'cachefile'] + +[tests/functional/casenorm] +tests = ['case_all_values', 'norm_all_values', +# 'mixed_create_failure', + 'sensitive_none_lookup', 'sensitive_none_delete', + 'sensitive_formd_lookup', 'sensitive_formd_delete', + 'insensitive_none_lookup', 'insensitive_none_delete', + 'insensitive_formd_lookup', 'insensitive_formd_delete', + 'mixed_none_lookup', 'mixed_none_lookup_ci', 'mixed_none_delete', + 'mixed_formd_lookup', 'mixed_formd_lookup_ci', 'mixed_formd_delete'] +tags = ['functional', 'casenorm'] + +[tests/functional/channel_program/lua_core] +tests = ['tst.args_to_lua', 'tst.divide_by_zero', 'tst.exists', + 'tst.integer_illegal', 'tst.integer_overflow', 'tst.language_functions_neg', + 'tst.language_functions_pos', 'tst.large_prog', +# 'tst.libraries', + 'tst.memory_limit', 'tst.nested_neg', 'tst.nested_pos', 'tst.nvlist_to_lua', + 'tst.recursive_neg', 'tst.recursive_pos', 'tst.return_large', + 'tst.return_nvlist_neg', 'tst.return_nvlist_pos', + 'tst.return_recursive_table', 'tst.timeout'] +tags = ['functional', 'channel_program', 'lua_core'] + +[tests/functional/channel_program/synctask_core] +tests = ['tst.destroy_fs', 'tst.destroy_snap', 'tst.get_count_and_limit', + 'tst.get_index_props', 'tst.get_mountpoint', 'tst.get_neg', + 'tst.get_number_props', 'tst.get_string_props', 'tst.get_type', + 'tst.get_userquota', 'tst.get_written', 'tst.inherit', 'tst.list_bookmarks', + 'tst.list_children', 'tst.list_clones', 'tst.list_holds', + 'tst.list_snapshots', 'tst.list_system_props', + 'tst.list_user_props', 'tst.parse_args_neg','tst.promote_conflict', + 'tst.promote_multiple', 'tst.promote_simple', 'tst.rollback_mult', + 'tst.rollback_one', 'tst.set_props', 'tst.snapshot_destroy', 'tst.snapshot_neg', + 'tst.snapshot_recursive', 'tst.snapshot_simple', + 'tst.bookmark.create', 'tst.bookmark.copy' +# macOS +# 'tst.terminate_by_signal' + ] +tags = ['functional', 'channel_program', 'synctask_core'] + +[tests/functional/checksum] +tests = ['run_sha2_test', 'run_skein_test', 'filetest_001_pos'] +tags = ['functional', 'checksum'] + +[tests/functional/clean_mirror] +tests = [ 'clean_mirror_001_pos', 'clean_mirror_002_pos', + 'clean_mirror_003_pos', 'clean_mirror_004_pos'] +tags = ['functional', 'clean_mirror'] + +[tests/functional/cli_root/zdb] +tests = ['zdb_002_pos', 'zdb_003_pos', 'zdb_004_pos', 'zdb_005_pos', + 'zdb_006_pos', 'zdb_args_neg', 'zdb_args_pos', 'zdb_checksum', 'zdb_decompress', + 'zdb_object_range_neg', 'zdb_object_range_pos', 'zdb_display_block', + 'zdb_objset_id'] +pre = +post = +tags = ['functional', 'cli_root', 'zdb'] + +[tests/functional/cli_root/zfs] +tests = ['zfs_001_neg', 'zfs_002_pos'] +tags = ['functional', 'cli_root', 'zfs'] + +[tests/functional/cli_root/zfs_bookmark] +tests = ['zfs_bookmark_cliargs'] +tags = ['functional', 'cli_root', 'zfs_bookmark'] + +[tests/functional/cli_root/zfs_change-key] +tests = ['zfs_change-key', 'zfs_change-key_child', 'zfs_change-key_format', + 'zfs_change-key_inherit', 'zfs_change-key_load', 'zfs_change-key_location', + 'zfs_change-key_pbkdf2iters', 'zfs_change-key_clones'] +tags = ['functional', 'cli_root', 'zfs_change-key'] + +[tests/functional/cli_root/zfs_clone] +tests = ['zfs_clone_001_neg', 'zfs_clone_002_pos', 'zfs_clone_003_pos', + 'zfs_clone_004_pos', 'zfs_clone_005_pos', 'zfs_clone_006_pos', + 'zfs_clone_007_pos', 'zfs_clone_008_neg', 'zfs_clone_009_neg', + 'zfs_clone_010_pos', 'zfs_clone_encrypted', 'zfs_clone_deeply_nested'] +tags = ['functional', 'cli_root', 'zfs_clone'] + +[tests/functional/cli_root/zfs_copies] +tests = ['zfs_copies_001_pos', 'zfs_copies_002_pos', 'zfs_copies_003_pos', + 'zfs_copies_004_neg', 'zfs_copies_005_neg', 'zfs_copies_006_pos'] +tags = ['functional', 'cli_root', 'zfs_copies'] + +[tests/functional/cli_root/zfs_create] +tests = ['zfs_create_001_pos', 'zfs_create_002_pos', 'zfs_create_003_pos', + 'zfs_create_004_pos', 'zfs_create_005_pos', 'zfs_create_006_pos', + 'zfs_create_007_pos', 'zfs_create_008_neg', 'zfs_create_009_neg', + 'zfs_create_010_neg', 'zfs_create_011_pos', 'zfs_create_012_pos', + 'zfs_create_013_pos', +# 'zfs_create_014_pos', # ZOL #11051 + 'zfs_create_encrypted', + 'zfs_create_crypt_combos', 'zfs_create_dryrun', 'zfs_create_verbose'] +tags = ['functional', 'cli_root', 'zfs_create'] + +[tests/functional/cli_root/zfs_destroy] +tests = ['zfs_clone_livelist_condense_and_disable', + 'zfs_clone_livelist_condense_races', 'zfs_destroy_001_pos', + 'zfs_destroy_002_pos', 'zfs_destroy_003_pos', + 'zfs_destroy_004_pos', 'zfs_destroy_005_neg', 'zfs_destroy_006_neg', + 'zfs_destroy_007_neg', 'zfs_destroy_008_pos', 'zfs_destroy_009_pos', + 'zfs_destroy_010_pos', 'zfs_destroy_011_pos', 'zfs_destroy_012_pos', + 'zfs_destroy_013_neg', 'zfs_destroy_014_pos', 'zfs_destroy_015_pos', + 'zfs_destroy_016_pos', 'zfs_destroy_clone_livelist', + 'zfs_destroy_dev_removal', 'zfs_destroy_dev_removal_condense'] +tags = ['functional', 'cli_root', 'zfs_destroy'] + +[tests/functional/cli_root/zfs_diff] +tests = ['zfs_diff_changes', 'zfs_diff_cliargs', 'zfs_diff_timestamp', + 'zfs_diff_types', 'zfs_diff_encrypted'] +tags = ['functional', 'cli_root', 'zfs_diff'] + +[tests/functional/cli_root/zfs_get] +tests = ['zfs_get_001_pos', 'zfs_get_002_pos', 'zfs_get_003_pos', + 'zfs_get_004_pos', 'zfs_get_005_neg', 'zfs_get_006_neg', 'zfs_get_007_neg', + 'zfs_get_008_pos', 'zfs_get_009_pos', 'zfs_get_010_neg'] +tags = ['functional', 'cli_root', 'zfs_get'] + +[tests/functional/cli_root/zfs_ids_to_path] +tests = ['zfs_ids_to_path_001_pos'] +tags = ['functional', 'cli_root', 'zfs_ids_to_path'] + +[tests/functional/cli_root/zfs_inherit] +tests = ['zfs_inherit_001_neg', 'zfs_inherit_002_neg', 'zfs_inherit_003_pos', + 'zfs_inherit_mountpoint'] +tags = ['functional', 'cli_root', 'zfs_inherit'] + +[tests/functional/cli_root/zfs_load-key] +tests = ['zfs_load-key', 'zfs_load-key_all', 'zfs_load-key_file', + 'zfs_load-key_location', 'zfs_load-key_noop', 'zfs_load-key_recursive'] +tags = ['functional', 'cli_root', 'zfs_load-key'] + +[tests/functional/cli_root/zfs_mount] +tests = ['zfs_mount_001_pos', 'zfs_mount_002_pos', 'zfs_mount_003_pos', + 'zfs_mount_004_pos', 'zfs_mount_005_pos', 'zfs_mount_007_pos', + 'zfs_mount_009_neg', 'zfs_mount_010_neg', 'zfs_mount_011_neg', + 'zfs_mount_012_pos', 'zfs_mount_all_001_pos', 'zfs_mount_encrypted', + 'zfs_mount_remount', 'zfs_mount_all_fail', 'zfs_mount_all_mountpoints', + 'zfs_mount_test_race'] +tags = ['functional', 'cli_root', 'zfs_mount'] + +[tests/functional/cli_root/zfs_program] +tests = ['zfs_program_json'] +tags = ['functional', 'cli_root', 'zfs_program'] + +[tests/functional/cli_root/zfs_promote] +tests = ['zfs_promote_001_pos', 'zfs_promote_002_pos', 'zfs_promote_003_pos', + 'zfs_promote_004_pos', 'zfs_promote_005_pos', 'zfs_promote_006_neg', + 'zfs_promote_007_neg', 'zfs_promote_008_pos', 'zfs_promote_encryptionroot'] +tags = ['functional', 'cli_root', 'zfs_promote'] + +[tests/functional/cli_root/zfs_property] +tests = ['zfs_written_property_001_pos'] +tags = ['functional', 'cli_root', 'zfs_property'] + +[tests/functional/cli_root/zfs_receive] +tests = ['zfs_receive_001_pos', 'zfs_receive_002_pos', 'zfs_receive_003_pos', + 'zfs_receive_004_neg', 'zfs_receive_005_neg', 'zfs_receive_006_pos', + 'zfs_receive_007_neg', 'zfs_receive_008_pos', 'zfs_receive_009_neg', + 'zfs_receive_010_pos', 'zfs_receive_011_pos', 'zfs_receive_012_pos', + 'zfs_receive_013_pos', 'zfs_receive_014_pos', 'zfs_receive_015_pos', + 'zfs_receive_016_pos', 'receive-o-x_props_override', + 'zfs_receive_from_encrypted', 'zfs_receive_to_encrypted', + 'zfs_receive_raw', 'zfs_receive_raw_incremental', 'zfs_receive_-e', + 'zfs_receive_raw_-d'] +tags = ['functional', 'cli_root', 'zfs_receive'] + +[tests/functional/cli_root/zfs_rename] +tests = ['zfs_rename_001_pos', 'zfs_rename_002_pos', 'zfs_rename_003_pos', + 'zfs_rename_004_neg', 'zfs_rename_005_neg', 'zfs_rename_006_pos', + 'zfs_rename_007_pos', 'zfs_rename_008_pos', 'zfs_rename_009_neg', + 'zfs_rename_010_neg', 'zfs_rename_011_pos', 'zfs_rename_012_neg', + 'zfs_rename_013_pos', 'zfs_rename_014_neg', 'zfs_rename_encrypted_child', + 'zfs_rename_to_encrypted', 'zfs_rename_mountpoint'] +tags = ['functional', 'cli_root', 'zfs_rename'] + +[tests/functional/cli_root/zfs_reservation] +tests = ['zfs_reservation_001_pos', 'zfs_reservation_002_pos'] +tags = ['functional', 'cli_root', 'zfs_reservation'] + +[tests/functional/cli_root/zfs_rollback] +tests = ['zfs_rollback_001_pos', 'zfs_rollback_002_pos', + 'zfs_rollback_003_neg', 'zfs_rollback_004_neg'] +tags = ['functional', 'cli_root', 'zfs_rollback'] + +[tests/functional/cli_root/zfs_send] +tests = ['zfs_send_001_pos', 'zfs_send_002_pos', 'zfs_send_003_pos', + 'zfs_send_004_neg', 'zfs_send_005_pos', 'zfs_send_006_pos', + 'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw', + 'zfs_send_sparse', 'zfs_send-b'] +tags = ['functional', 'cli_root', 'zfs_send'] + +[tests/functional/cli_root/zfs_set] +tests = ['cache_001_pos', 'cache_002_neg', 'canmount_001_pos', + 'canmount_002_pos', 'canmount_003_pos', 'canmount_004_pos', + 'checksum_001_pos', 'compression_001_pos', 'mountpoint_001_pos', + 'mountpoint_002_pos', 'reservation_001_neg', 'user_property_002_pos', + 'share_mount_001_neg', 'snapdir_001_pos', 'onoffs_001_pos', + 'user_property_001_pos', 'user_property_003_neg', 'readonly_001_pos', + 'user_property_004_pos', 'version_001_neg', 'zfs_set_001_neg', + 'zfs_set_002_neg', 'zfs_set_003_neg', 'property_alias_001_pos', + 'mountpoint_003_pos', 'ro_props_001_pos', 'zfs_set_keylocation'] +tags = ['functional', 'cli_root', 'zfs_set'] + +[tests/functional/cli_root/zfs_share] +tests = ['zfs_share_001_pos', 'zfs_share_002_pos', 'zfs_share_003_pos', + 'zfs_share_004_pos', 'zfs_share_005_pos', 'zfs_share_006_pos', + 'zfs_share_007_neg', 'zfs_share_008_neg', 'zfs_share_009_neg', + 'zfs_share_010_neg', 'zfs_share_011_pos'] +tags = ['functional', 'cli_root', 'zfs_share'] + +[tests/functional/cli_root/zfs_snapshot] +tests = ['zfs_snapshot_001_neg', 'zfs_snapshot_002_neg', + 'zfs_snapshot_003_neg', 'zfs_snapshot_004_neg', 'zfs_snapshot_005_neg', + 'zfs_snapshot_006_pos', 'zfs_snapshot_007_neg', 'zfs_snapshot_008_neg', + 'zfs_snapshot_009_pos'] +tags = ['functional', 'cli_root', 'zfs_snapshot'] + +[tests/functional/cli_root/zfs_unload-key] +tests = ['zfs_unload-key', 'zfs_unload-key_all', 'zfs_unload-key_recursive'] +tags = ['functional', 'cli_root', 'zfs_unload-key'] + +[tests/functional/cli_root/zfs_unmount] +tests = ['zfs_unmount_001_pos', 'zfs_unmount_002_pos', 'zfs_unmount_003_pos', + 'zfs_unmount_004_pos', 'zfs_unmount_005_pos', + # KILLED: testpool/c/c/c/c/c/c/c/c/c/c + # 'zfs_unmount_006_pos', + 'zfs_unmount_007_neg', 'zfs_unmount_008_neg', 'zfs_unmount_009_pos', + 'zfs_unmount_all_001_pos', 'zfs_unmount_nested', 'zfs_unmount_unload_keys'] +tags = ['functional', 'cli_root', 'zfs_unmount'] + +[tests/functional/cli_root/zfs_unshare] +tests = ['zfs_unshare_001_pos', 'zfs_unshare_002_pos', 'zfs_unshare_003_pos', + 'zfs_unshare_004_neg', 'zfs_unshare_005_neg', 'zfs_unshare_006_pos', + 'zfs_unshare_007_pos'] +tags = ['functional', 'cli_root', 'zfs_unshare'] + +# macOS - known to panic +[tests/functional/cli_root/zfs_upgrade] +tests = ['zfs_upgrade_001_pos', 'zfs_upgrade_002_pos', 'zfs_upgrade_003_pos', +# 'zfs_upgrade_004_pos', + 'zfs_upgrade_005_pos', 'zfs_upgrade_006_neg', + 'zfs_upgrade_007_neg'] +tags = ['functional', 'cli_root', 'zfs_upgrade'] + +[tests/functional/cli_root/zfs_wait] +tests = ['zfs_wait_deleteq'] +tags = ['functional', 'cli_root', 'zfs_wait'] + +[tests/functional/cli_root/zpool] +tests = ['zpool_001_neg', 'zpool_002_pos', 'zpool_003_pos', 'zpool_colors'] +tags = ['functional', 'cli_root', 'zpool'] + +[tests/functional/cli_root/zpool_attach] +tests = ['zpool_attach_001_neg', 'attach-o_ashift'] +tags = ['functional', 'cli_root', 'zpool_attach'] + +[tests/functional/cli_root/zpool_clear] +tests = ['zpool_clear_001_pos', 'zpool_clear_002_neg', 'zpool_clear_003_neg', + 'zpool_clear_readonly'] +tags = ['functional', 'cli_root', 'zpool_clear'] + +[tests/functional/cli_root/zpool_create] +tests = ['zpool_create_001_pos', 'zpool_create_002_pos', + 'zpool_create_003_pos', 'zpool_create_004_pos', 'zpool_create_005_pos', + 'zpool_create_006_pos', 'zpool_create_007_neg', 'zpool_create_008_pos', + 'zpool_create_009_neg', 'zpool_create_010_neg', 'zpool_create_011_neg', + 'zpool_create_012_neg', 'zpool_create_014_neg', 'zpool_create_015_neg', + 'zpool_create_017_neg', 'zpool_create_018_pos', 'zpool_create_019_pos', + 'zpool_create_020_pos', 'zpool_create_021_pos', 'zpool_create_022_pos', + 'zpool_create_023_neg', +# 'zpool_create_024_pos', + 'zpool_create_encrypted', 'zpool_create_crypt_combos', + 'zpool_create_features_001_pos', 'zpool_create_features_002_pos', + 'zpool_create_features_003_pos', 'zpool_create_features_004_neg', + 'zpool_create_features_005_pos', + 'create-o_ashift', 'zpool_create_tempname'] +tags = ['functional', 'cli_root', 'zpool_create'] + +[tests/functional/cli_root/zpool_destroy] +tests = ['zpool_destroy_001_pos', 'zpool_destroy_002_pos', + 'zpool_destroy_003_neg'] +pre = +post = +tags = ['functional', 'cli_root', 'zpool_destroy'] + +[tests/functional/cli_root/zpool_detach] +tests = ['zpool_detach_001_neg'] +tags = ['functional', 'cli_root', 'zpool_detach'] + +[tests/functional/cli_root/zpool_events] +tests = ['zpool_events_clear', 'zpool_events_cliargs', 'zpool_events_follow', + 'zpool_events_poolname', 'zpool_events_errors'] +tags = ['functional', 'cli_root', 'zpool_events'] + +[tests/functional/cli_root/zpool_export] +tests = ['zpool_export_001_pos', 'zpool_export_002_pos', + 'zpool_export_003_neg', 'zpool_export_004_pos'] +tags = ['functional', 'cli_root', 'zpool_export'] + +[tests/functional/cli_root/zpool_get] +tests = ['zpool_get_001_pos', 'zpool_get_002_pos', 'zpool_get_003_pos', + 'zpool_get_004_neg', 'zpool_get_005_pos'] +tags = ['functional', 'cli_root', 'zpool_get'] + +[tests/functional/cli_root/zpool_history] +tests = ['zpool_history_001_neg', 'zpool_history_002_pos'] +tags = ['functional', 'cli_root', 'zpool_history'] + +[tests/functional/cli_root/zpool_import] +tests = ['zpool_import_001_pos', 'zpool_import_002_pos', + 'zpool_import_003_pos', 'zpool_import_004_pos', 'zpool_import_005_pos', + 'zpool_import_006_pos', 'zpool_import_007_pos', 'zpool_import_008_pos', + 'zpool_import_009_neg', 'zpool_import_010_pos', 'zpool_import_011_neg', + 'zpool_import_012_pos', 'zpool_import_013_neg', 'zpool_import_014_pos', + 'zpool_import_015_pos', + 'zpool_import_features_001_pos', 'zpool_import_features_002_neg', + 'zpool_import_features_003_pos', 'zpool_import_missing_001_pos', + 'zpool_import_missing_002_pos', 'zpool_import_missing_003_pos', + 'zpool_import_rename_001_pos', 'zpool_import_all_001_pos', + 'zpool_import_encrypted', 'zpool_import_encrypted_load', + 'zpool_import_errata3', 'zpool_import_errata4', + 'import_cachefile_device_added', + 'import_cachefile_device_removed', + 'import_cachefile_device_replaced', + 'import_cachefile_mirror_attached', + 'import_cachefile_mirror_detached', + 'import_cachefile_shared_device', + 'import_devices_missing', + 'import_paths_changed', +# 'import_rewind_config_changed', + 'import_rewind_device_replaced'] +tags = ['functional', 'cli_root', 'zpool_import'] + +[tests/functional/cli_root/zpool_labelclear] +tests = ['zpool_labelclear_active', 'zpool_labelclear_exported', + 'zpool_labelclear_removed', 'zpool_labelclear_valid'] +pre = +post = +tags = ['functional', 'cli_root', 'zpool_labelclear'] + +[tests/functional/cli_root/zpool_initialize] +tests = ['zpool_initialize_attach_detach_add_remove', + 'zpool_initialize_import_export', + 'zpool_initialize_offline_export_import_online', + 'zpool_initialize_online_offline', + 'zpool_initialize_split', + 'zpool_initialize_start_and_cancel_neg', + 'zpool_initialize_start_and_cancel_pos', + 'zpool_initialize_suspend_resume', + 'zpool_initialize_unsupported_vdevs', + 'zpool_initialize_verify_checksums', + 'zpool_initialize_verify_initialized'] +pre = +tags = ['functional', 'cli_root', 'zpool_initialize'] + +[tests/functional/cli_root/zpool_offline] +tests = ['zpool_offline_001_pos', 'zpool_offline_002_neg', + 'zpool_offline_003_pos'] +tags = ['functional', 'cli_root', 'zpool_offline'] + +[tests/functional/cli_root/zpool_online] +tests = ['zpool_online_001_pos', 'zpool_online_002_neg'] +tags = ['functional', 'cli_root', 'zpool_online'] + +[tests/functional/cli_root/zpool_remove] +tests = ['zpool_remove_001_neg', 'zpool_remove_002_pos', + 'zpool_remove_003_pos'] +tags = ['functional', 'cli_root', 'zpool_remove'] + +[tests/functional/cli_root/zpool_replace] +tests = ['zpool_replace_001_neg', 'replace-o_ashift', 'replace_prop_ashift'] +tags = ['functional', 'cli_root', 'zpool_replace'] + +[tests/functional/cli_root/zpool_resilver] +tests = ['zpool_resilver_bad_args', 'zpool_resilver_restart'] +tags = ['functional', 'cli_root', 'zpool_resilver'] + +[tests/functional/cli_root/zpool_scrub] +tests = ['zpool_scrub_001_neg', 'zpool_scrub_002_pos', 'zpool_scrub_003_pos', + 'zpool_scrub_004_pos', 'zpool_scrub_005_pos', + 'zpool_scrub_encrypted_unloaded', 'zpool_scrub_print_repairing', + 'zpool_scrub_offline_device', 'zpool_scrub_multiple_copies'] +tags = ['functional', 'cli_root', 'zpool_scrub'] + +[tests/functional/cli_root/zpool_set] +tests = ['zpool_set_001_pos', 'zpool_set_002_neg', 'zpool_set_003_neg', + 'zpool_set_ashift', 'zpool_set_features'] +tags = ['functional', 'cli_root', 'zpool_set'] + +[tests/functional/cli_root/zpool_split] +tests = ['zpool_split_cliargs', 'zpool_split_devices', + 'zpool_split_encryption', 'zpool_split_props', 'zpool_split_vdevs', + 'zpool_split_resilver', 'zpool_split_indirect'] +tags = ['functional', 'cli_root', 'zpool_split'] + +[tests/functional/cli_root/zpool_status] +tests = ['zpool_status_001_pos', 'zpool_status_002_pos'] +tags = ['functional', 'cli_root', 'zpool_status'] + +[tests/functional/cli_root/zpool_sync] +tests = ['zpool_sync_001_pos', 'zpool_sync_002_neg'] +tags = ['functional', 'cli_root', 'zpool_sync'] + +[tests/functional/cli_root/zpool_trim] +tests = ['zpool_trim_attach_detach_add_remove', + 'zpool_trim_import_export', 'zpool_trim_multiple', 'zpool_trim_neg', + 'zpool_trim_offline_export_import_online', 'zpool_trim_online_offline', + 'zpool_trim_partial', 'zpool_trim_rate', 'zpool_trim_rate_neg', + 'zpool_trim_secure', 'zpool_trim_split', 'zpool_trim_start_and_cancel_neg', + 'zpool_trim_start_and_cancel_pos', 'zpool_trim_suspend_resume', + 'zpool_trim_unsupported_vdevs', 'zpool_trim_verify_checksums', + 'zpool_trim_verify_trimmed'] +tags = ['functional', 'zpool_trim'] + +[tests/functional/cli_root/zpool_upgrade] +tests = ['zpool_upgrade_001_pos', 'zpool_upgrade_002_pos', + 'zpool_upgrade_003_pos', 'zpool_upgrade_004_pos', + 'zpool_upgrade_005_neg', 'zpool_upgrade_006_neg', +# macOS +# 'zpool_upgrade_007_pos', + 'zpool_upgrade_008_pos', + 'zpool_upgrade_009_neg'] +tags = ['functional', 'cli_root', 'zpool_upgrade'] + +[tests/functional/cli_root/zpool_wait/scan] +tests = ['zpool_wait_replace_cancel', 'zpool_wait_resilver', 'zpool_wait_scrub_cancel', + 'zpool_wait_replace', 'zpool_wait_scrub_basic', 'zpool_wait_scrub_flag'] +tags = ['functional', 'cli_root', 'zpool_wait'] + +[tests/functional/cli_user/zfs_list] +tests = ['zfs_list_001_pos', 'zfs_list_002_pos', 'zfs_list_003_pos', + 'zfs_list_004_neg', 'zfs_list_007_pos', 'zfs_list_008_neg'] +user = +tags = ['functional', 'cli_user', 'zfs_list'] + +[tests/functional/cli_user/zpool_iostat] +tests = ['zpool_iostat_001_neg', 'zpool_iostat_002_pos', + 'zpool_iostat_003_neg', 'zpool_iostat_004_pos', + 'zpool_iostat_005_pos', 'zpool_iostat_-c_disable', + 'zpool_iostat_-c_homedir', 'zpool_iostat_-c_searchpath'] +user = +tags = ['functional', 'cli_user', 'zpool_iostat'] + +[tests/functional/cli_user/zpool_list] +tests = ['zpool_list_001_pos', 'zpool_list_002_neg'] +user = +tags = ['functional', 'cli_user', 'zpool_list'] + +[tests/functional/cli_user/zpool_status] +tests = ['zpool_status_003_pos', 'zpool_status_-c_disable', + 'zpool_status_-c_homedir', 'zpool_status_-c_searchpath'] +user = +tags = ['functional', 'cli_user', 'zpool_status'] + +[tests/functional/compression] +tests = ['compress_001_pos', 'compress_002_pos', 'compress_003_pos'] +tags = ['functional', 'compression'] + +[tests/functional/cp_files] +tests = ['cp_files_001_pos'] +tags = ['functional', 'cp_files'] + +[tests/functional/ctime] +tests = ['ctime_001_pos' ] +tags = ['functional', 'ctime'] + +[tests/functional/delegate] +tests = ['zfs_allow_001_pos', 'zfs_allow_002_pos', 'zfs_allow_003_pos', + 'zfs_allow_004_pos', 'zfs_allow_005_pos', 'zfs_allow_006_pos', + 'zfs_allow_007_pos', 'zfs_allow_008_pos', 'zfs_allow_009_neg', + 'zfs_allow_010_pos', 'zfs_allow_011_neg', 'zfs_allow_012_neg', + 'zfs_unallow_001_pos', 'zfs_unallow_002_pos', 'zfs_unallow_003_pos', + 'zfs_unallow_004_pos', 'zfs_unallow_005_pos', 'zfs_unallow_006_pos', + 'zfs_unallow_007_neg', 'zfs_unallow_008_neg'] +tags = ['functional', 'delegate'] + +[tests/functional/exec] +tests = ['exec_001_pos', 'exec_002_neg'] +tags = ['functional', 'exec'] + +[tests/functional/features/async_destroy] +tests = ['async_destroy_001_pos'] +tags = ['functional', 'features', 'async_destroy'] + +[tests/functional/features/large_dnode] +tests = ['large_dnode_001_pos', 'large_dnode_003_pos', 'large_dnode_004_neg', + 'large_dnode_005_pos', 'large_dnode_007_neg', 'large_dnode_009_pos'] +tags = ['functional', 'features', 'large_dnode'] + +[tests/functional/grow] +pre = +post = +tests = ['grow_pool_001_pos', 'grow_replicas_001_pos'] +tags = ['functional', 'grow'] + +[tests/functional/history] +tests = ['history_001_pos', 'history_002_pos', 'history_003_pos', + 'history_004_pos', 'history_005_neg', 'history_006_neg', + 'history_007_pos', 'history_008_pos', 'history_009_pos', + 'history_010_pos'] +tags = ['functional', 'history'] + +[tests/functional/hkdf] +tests = ['run_hkdf_test'] +tags = ['functional', 'hkdf'] + +[tests/functional/inheritance] +tests = ['inherit_001_pos'] +pre = +tags = ['functional', 'inheritance'] + +[tests/functional/io] +tests = ['sync', 'psync', 'posixaio', 'mmap'] +tags = ['functional', 'io'] + +[tests/functional/inuse] +tests = ['inuse_004_pos', 'inuse_005_pos', 'inuse_008_pos', 'inuse_009_pos'] +post = +tags = ['functional', 'inuse'] + +[tests/functional/large_files] +tests = ['large_files_001_pos', 'large_files_002_pos'] +tags = ['functional', 'large_files'] + +[tests/functional/largest_pool] +tests = ['largest_pool_001_pos'] +pre = +post = +tags = ['functional', 'largest_pool'] + +[tests/functional/limits] +tests = ['filesystem_count', 'filesystem_limit', 'snapshot_count', + 'snapshot_limit'] +tags = ['functional', 'limits'] + +[tests/functional/link_count] +tests = ['link_count_001', 'link_count_root_inode'] +tags = ['functional', 'link_count'] + +[tests/functional/migration] +tests = ['migration_001_pos', 'migration_002_pos', 'migration_003_pos', + 'migration_004_pos', 'migration_005_pos', 'migration_006_pos', + 'migration_007_pos', 'migration_008_pos', 'migration_009_pos', + 'migration_010_pos', 'migration_011_pos', 'migration_012_pos'] +tags = ['functional', 'migration'] + +[tests/functional/mmap] +tests = ['mmap_write_001_pos', 'mmap_read_001_pos'] +tags = ['functional', 'mmap'] + +[tests/functional/mount] +tests = ['umount_001', 'umountall_001'] +tags = ['functional', 'mount'] + +[tests/functional/mv_files] +tests = ['mv_files_001_pos', 'mv_files_002_pos', 'random_creation'] +tags = ['functional', 'mv_files'] + +[tests/functional/nestedfs] +tests = ['nestedfs_001_pos'] +tags = ['functional', 'nestedfs'] + +[tests/functional/no_space] +tests = ['enospc_001_pos', 'enospc_002_pos', 'enospc_003_pos', + 'enospc_df'] +tags = ['functional', 'no_space'] + +[tests/functional/nopwrite] +tests = ['nopwrite_copies', 'nopwrite_mtime', 'nopwrite_negative', + 'nopwrite_promoted_clone', 'nopwrite_recsize', 'nopwrite_sync', + 'nopwrite_varying_compression', 'nopwrite_volume'] +tags = ['functional', 'nopwrite'] + +[tests/functional/online_offline] +tests = ['online_offline_001_pos', 'online_offline_002_neg', + 'online_offline_003_neg'] +tags = ['functional', 'online_offline'] + +[tests/functional/persist_l2arc] +tests = ['persist_l2arc_001_pos', 'persist_l2arc_002_pos', + 'persist_l2arc_003_neg', 'persist_l2arc_004_pos', 'persist_l2arc_005_pos', + 'persist_l2arc_006_pos', 'persist_l2arc_007_pos', 'persist_l2arc_008_pos'] +tags = ['functional', 'persist_l2arc'] + +[tests/functional/pool_names] +tests = ['pool_names_001_pos', # (run as root) [08:37] [PASS] + 'pool_names_002_neg'] +pre = +post = +tags = ['functional', 'pool_names'] + +[tests/functional/poolversion] +tests = ['poolversion_001_pos', 'poolversion_002_pos'] +tags = ['functional', 'poolversion'] + +[tests/functional/pyzfs] +tests = ['pyzfs_unittest'] +pre = +post = +tags = ['functional', 'pyzfs'] + +[tests/functional/quota] +tests = ['quota_001_pos', 'quota_002_pos', 'quota_003_pos', + 'quota_004_pos', 'quota_005_pos', 'quota_006_neg'] +tags = ['functional', 'quota'] + +[tests/functional/raidz] +tests = ['raidz_001_neg', 'raidz_002_pos'] +tags = ['functional', 'raidz'] + +[tests/functional/redundancy] +tests = ['redundancy_001_pos', 'redundancy_002_pos', 'redundancy_003_pos', + 'redundancy_004_neg'] +tags = ['functional', 'redundancy'] + +[tests/functional/refquota] +tests = ['refquota_001_pos', 'refquota_002_pos', 'refquota_003_pos', + 'refquota_004_pos', 'refquota_005_pos', 'refquota_006_neg', + 'refquota_007_neg', 'refquota_008_neg'] +tags = ['functional', 'refquota'] + +[tests/functional/refreserv] +tests = ['refreserv_001_pos', 'refreserv_002_pos', 'refreserv_003_pos', + 'refreserv_004_pos', 'refreserv_005_pos', 'refreserv_multi_raidz', + 'refreserv_raidz'] +tags = ['functional', 'refreserv'] + +[tests/functional/rename_dirs] +tests = ['rename_dirs_001_pos'] +tags = ['functional', 'rename_dirs'] + +[tests/functional/replacement] +tests = ['replacement_001_pos', 'replacement_002_pos', 'replacement_003_pos'] +tags = ['functional', 'replacement'] + +[tests/functional/resilver] +tests = ['resilver_restart_001', 'resilver_restart_002'] +tags = ['functional', 'resilver'] + +[tests/functional/rootpool] +tests = ['rootpool_002_neg', 'rootpool_003_neg', 'rootpool_007_pos'] +tags = ['functional', 'rootpool'] + +[tests/functional/rsend] +tests = ['recv_dedup', 'recv_dedup_encrypted_zvol', 'rsend_001_pos', + 'rsend_002_pos', 'rsend_003_pos', 'rsend_004_pos', 'rsend_005_pos', + 'rsend_006_pos', 'rsend_007_pos', 'rsend_008_pos', 'rsend_009_pos', + 'rsend_010_pos', 'rsend_011_pos', 'rsend_012_pos', 'rsend_013_pos', + 'rsend_014_pos', 'rsend_016_neg', 'rsend_019_pos', 'rsend_020_pos', + 'rsend_021_pos', 'rsend_022_pos', 'rsend_024_pos', + 'send-c_verify_ratio', 'send-c_verify_contents', 'send-c_props', + 'send-c_incremental', 'send-c_volume', 'send-c_zstreamdump', + 'send-c_lz4_disabled', 'send-c_recv_lz4_disabled', + 'send-c_mixed_compression', 'send-c_stream_size_estimate', + 'send-c_embedded_blocks', 'send-c_resume', 'send-cpL_varied_recsize', + 'send-c_recv_dedup', 'send-L_toggle', 'send_encrypted_hierarchy', + 'send_encrypted_props', 'send_encrypted_truncated_files', + 'send_freeobjects', 'send_realloc_files', + 'send_realloc_encrypted_files', 'send_spill_block', 'send_holds', + 'send_hole_birth', 'send_mixed_raw', 'send-wR_encrypted_zvol', + 'send_partial_dataset'] +tags = ['functional', 'rsend'] + +[tests/functional/scrub_mirror] +tests = ['scrub_mirror_001_pos', 'scrub_mirror_002_pos', + 'scrub_mirror_003_pos', 'scrub_mirror_004_pos'] +tags = ['functional', 'scrub_mirror'] + +[tests/functional/snapshot] +tests = ['clone_001_pos', 'rollback_001_pos', 'rollback_002_pos', + 'rollback_003_pos', 'snapshot_001_pos', 'snapshot_002_pos', + 'snapshot_003_pos', 'snapshot_004_pos', 'snapshot_005_pos', + 'snapshot_006_pos', 'snapshot_007_pos', 'snapshot_008_pos', + 'snapshot_009_pos', 'snapshot_010_pos', 'snapshot_011_pos', + 'snapshot_012_pos', 'snapshot_013_pos', 'snapshot_014_pos', + 'snapshot_017_pos'] +tags = ['functional', 'snapshot'] + +[tests/functional/snapused] +tests = ['snapused_001_pos', 'snapused_002_pos', 'snapused_003_pos', + 'snapused_004_pos', 'snapused_005_pos'] +tags = ['functional', 'snapused'] + +[tests/functional/sparse] +tests = ['sparse_001_pos'] +tags = ['functional', 'sparse'] + +[tests/functional/suid] +tests = ['suid_write_to_suid', 'suid_write_to_sgid', 'suid_write_to_suid_sgid', + 'suid_write_to_none'] +tags = ['functional', 'suid'] + +[tests/functional/threadsappend] +tests = ['threadsappend_001_pos'] +tags = ['functional', 'threadsappend'] + +[tests/functional/trim] +tests = ['autotrim_integrity', 'autotrim_config', 'autotrim_trim_integrity', + 'trim_integrity', 'trim_config', 'trim_l2arc'] +tags = ['functional', 'trim'] + +[tests/functional/truncate] +tests = ['truncate_001_pos', 'truncate_002_pos', 'truncate_timestamps'] +tags = ['functional', 'truncate'] + +[tests/functional/upgrade] +tests = ['upgrade_userobj_001_pos', 'upgrade_readonly_pool'] +tags = ['functional', 'upgrade'] + +[tests/functional/userquota] +tests = [ + 'userquota_001_pos', 'userquota_002_pos', 'userquota_003_pos', + 'userquota_004_pos', 'userquota_005_neg', 'userquota_006_pos', + 'userquota_007_pos', 'userquota_008_pos', 'userquota_009_pos', + 'userquota_010_pos', 'userquota_011_pos', 'userquota_012_neg', + 'userspace_001_pos', 'userspace_002_pos'] +tags = ['functional', 'userquota'] + +[tests/functional/vdev_zaps] +tests = ['vdev_zaps_001_pos', 'vdev_zaps_002_pos', 'vdev_zaps_003_pos', + 'vdev_zaps_004_pos', 'vdev_zaps_005_pos', 'vdev_zaps_006_pos', + 'vdev_zaps_007_pos'] +tags = ['functional', 'vdev_zaps'] + +[tests/functional/write_dirs] +tests = ['write_dirs_001_pos', 'write_dirs_002_pos'] +tags = ['functional', 'write_dirs'] + +[tests/functional/xattr] +tests = ['xattr_001_pos', 'xattr_002_neg', 'xattr_003_neg', 'xattr_004_pos', + 'xattr_005_pos', 'xattr_006_pos', 'xattr_007_neg', + 'xattr_011_pos', 'xattr_012_pos', 'xattr_013_pos'] +tags = ['functional', 'xattr'] + +[tests/functional/zvol/zvol_ENOSPC] +tests = ['zvol_ENOSPC_001_pos'] +tags = ['functional', 'zvol', 'zvol_ENOSPC'] + +[tests/functional/zvol/zvol_cli] +tests = ['zvol_cli_001_pos', 'zvol_cli_002_pos', 'zvol_cli_003_neg'] +tags = ['functional', 'zvol', 'zvol_cli'] + +[tests/functional/zvol/zvol_misc] +tests = ['zvol_misc_002_pos', 'zvol_misc_hierarchy', 'zvol_misc_rename_inuse', + 'zvol_misc_snapdev', 'zvol_misc_volmode', 'zvol_misc_zil'] +tags = ['functional', 'zvol', 'zvol_misc'] + +[tests/functional/zvol/zvol_swap] +tests = ['zvol_swap_001_pos', 'zvol_swap_002_pos', 'zvol_swap_004_pos'] +tags = ['functional', 'zvol', 'zvol_swap'] + +[tests/functional/libzfs] +tests = ['many_fds', 'libzfs_input'] +tags = ['functional', 'libzfs'] + +[tests/functional/log_spacemap] +tests = ['log_spacemap_import_logs'] +pre = +post = +tags = ['functional', 'log_spacemap'] diff --git a/tests/runfiles/macOS-CI.run b/tests/runfiles/macOS-CI.run new file mode 100644 index 000000000000..7d2fa7fe62e3 --- /dev/null +++ b/tests/runfiles/macOS-CI.run @@ -0,0 +1,350 @@ +# +# 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. +# + +[DEFAULT] +pre = setup +quiet = False +pre_user = root +user = root +timeout = 600 +post_user = root +post = cleanup +failsafe_user = root +failsafe = callbacks/zfs_failsafe +outputdir = /var/tmp/test_results +tags = ['functional'] + +[tests/functional/checksum] +tests = ['run_sha2_test', 'run_skein_test', 'filetest_001_pos'] +tags = ['functional', 'checksum'] + +[tests/functional/clean_mirror] +tests = [ 'clean_mirror_001_pos', 'clean_mirror_002_pos', + 'clean_mirror_003_pos', 'clean_mirror_004_pos'] +tags = ['functional', 'clean_mirror'] + +[tests/functional/cli_root/zfs_bookmark] +tests = ['zfs_bookmark_cliargs'] +tags = ['functional', 'cli_root', 'zfs_bookmark'] + +[tests/functional/cli_root/zfs_change-key] +tests = ['zfs_change-key', 'zfs_change-key_child', 'zfs_change-key_format', + 'zfs_change-key_inherit', 'zfs_change-key_load', 'zfs_change-key_location', + 'zfs_change-key_pbkdf2iters', 'zfs_change-key_clones'] +tags = ['functional', 'cli_root', 'zfs_change-key'] + +[tests/functional/cli_root/zfs_create] +tests = ['zfs_create_001_pos', +# 'zfs_create_002_pos', [02:08] + 'zfs_create_003_pos', + 'zfs_create_004_pos', 'zfs_create_005_pos', 'zfs_create_006_pos', + 'zfs_create_007_pos', 'zfs_create_008_neg', 'zfs_create_009_neg', + 'zfs_create_010_neg', 'zfs_create_011_pos', 'zfs_create_012_pos', +# 'zfs_create_013_pos', [02:10] + 'zfs_create_014_pos', 'zfs_create_encrypted', + 'zfs_create_crypt_combos', 'zfs_create_dryrun', 'zfs_create_verbose'] +tags = ['functional', 'cli_root', 'zfs_create'] + +[tests/functional/cli_root/zfs_diff] +tests = ['zfs_diff_changes', 'zfs_diff_cliargs', 'zfs_diff_timestamp', + 'zfs_diff_types', 'zfs_diff_encrypted'] +tags = ['functional', 'cli_root', 'zfs_diff'] + +[tests/functional/cli_root/zfs_inherit] +tests = ['zfs_inherit_001_neg', 'zfs_inherit_002_neg', 'zfs_inherit_003_pos', + 'zfs_inherit_mountpoint'] +tags = ['functional', 'cli_root', 'zfs_inherit'] + +[tests/functional/cli_root/zfs_load-key] +tests = ['zfs_load-key', 'zfs_load-key_all', 'zfs_load-key_file', + 'zfs_load-key_location', 'zfs_load-key_noop', 'zfs_load-key_recursive'] +tags = ['functional', 'cli_root', 'zfs_load-key'] + +[tests/functional/cli_root/zfs_mount] +tests = ['zfs_mount_001_pos', 'zfs_mount_002_pos', 'zfs_mount_003_pos', + 'zfs_mount_004_pos', 'zfs_mount_005_pos', 'zfs_mount_007_pos', + 'zfs_mount_009_neg', 'zfs_mount_010_neg', 'zfs_mount_011_neg', + 'zfs_mount_012_pos', 'zfs_mount_all_001_pos', 'zfs_mount_encrypted', + 'zfs_mount_remount', 'zfs_mount_all_fail', 'zfs_mount_all_mountpoints', + 'zfs_mount_test_race'] +tags = ['functional', 'cli_root', 'zfs_mount'] + +[tests/functional/cli_root/zfs_property] +tests = ['zfs_written_property_001_pos'] +tags = ['functional', 'cli_root', 'zfs_property'] + +[tests/functional/cli_root/zfs_rollback] +tests = ['zfs_rollback_001_pos', 'zfs_rollback_002_pos', + 'zfs_rollback_003_neg', 'zfs_rollback_004_neg'] +tags = ['functional', 'cli_root', 'zfs_rollback'] + +[tests/functional/cli_root/zfs_send] +tests = ['zfs_send_001_pos', 'zfs_send_002_pos', 'zfs_send_003_pos', + 'zfs_send_004_neg', 'zfs_send_005_pos', 'zfs_send_006_pos', + 'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw', + 'zfs_send_sparse', 'zfs_send-b'] +tags = ['functional', 'cli_root', 'zfs_send'] + +[tests/functional/cli_root/zfs_unload-key] +tests = ['zfs_unload-key', 'zfs_unload-key_all', 'zfs_unload-key_recursive'] +tags = ['functional', 'cli_root', 'zfs_unload-key'] + +[tests/functional/cli_root/zpool_attach] +tests = ['zpool_attach_001_neg', 'attach-o_ashift'] +tags = ['functional', 'cli_root', 'zpool_attach'] + +[tests/functional/cli_root/zpool_clear] +tests = ['zpool_clear_001_pos', 'zpool_clear_002_neg', 'zpool_clear_003_neg', + 'zpool_clear_readonly'] +tags = ['functional', 'cli_root', 'zpool_clear'] + +[tests/functional/cli_root/zpool_destroy] +tests = ['zpool_destroy_001_pos', 'zpool_destroy_002_pos', + 'zpool_destroy_003_neg'] +pre = +post = +tags = ['functional', 'cli_root', 'zpool_destroy'] + +[tests/functional/cli_root/zpool_detach] +tests = ['zpool_detach_001_neg'] +tags = ['functional', 'cli_root', 'zpool_detach'] + +[tests/functional/cli_root/zpool_export] +tests = ['zpool_export_001_pos', 'zpool_export_002_pos', + 'zpool_export_003_neg', 'zpool_export_004_pos'] +tags = ['functional', 'cli_root', 'zpool_export'] + +[tests/functional/cli_root/zpool_offline] +tests = ['zpool_offline_001_pos', 'zpool_offline_002_neg', + 'zpool_offline_003_pos'] +tags = ['functional', 'cli_root', 'zpool_offline'] + +[tests/functional/cli_root/zpool_online] +tests = ['zpool_online_001_pos', 'zpool_online_002_neg'] +tags = ['functional', 'cli_root', 'zpool_online'] + +[tests/functional/cli_root/zpool_remove] +tests = ['zpool_remove_001_neg', 'zpool_remove_002_pos', + 'zpool_remove_003_pos'] +tags = ['functional', 'cli_root', 'zpool_remove'] + +[tests/functional/cli_root/zpool_replace] +tests = ['zpool_replace_001_neg', 'replace-o_ashift', 'replace_prop_ashift'] +tags = ['functional', 'cli_root', 'zpool_replace'] + +[tests/functional/cli_root/zpool_resilver] +tests = ['zpool_resilver_bad_args', 'zpool_resilver_restart'] +tags = ['functional', 'cli_root', 'zpool_resilver'] + +[tests/functional/cli_root/zpool_scrub] +tests = ['zpool_scrub_001_neg', 'zpool_scrub_002_pos', 'zpool_scrub_003_pos', + 'zpool_scrub_004_pos', 'zpool_scrub_005_pos', + 'zpool_scrub_encrypted_unloaded', 'zpool_scrub_print_repairing', + 'zpool_scrub_offline_device', 'zpool_scrub_multiple_copies'] +tags = ['functional', 'cli_root', 'zpool_scrub'] + +[tests/functional/cli_root/zpool_split] +tests = ['zpool_split_cliargs', 'zpool_split_devices', + 'zpool_split_encryption', 'zpool_split_props', 'zpool_split_vdevs', + 'zpool_split_resilver', 'zpool_split_indirect'] +tags = ['functional', 'cli_root', 'zpool_split'] + +[tests/functional/cli_root/zpool_status] +tests = ['zpool_status_001_pos', 'zpool_status_002_pos'] +tags = ['functional', 'cli_root', 'zpool_status'] + +[tests/functional/cli_root/zpool_sync] +tests = ['zpool_sync_001_pos', 'zpool_sync_002_neg'] +tags = ['functional', 'cli_root', 'zpool_sync'] + +[tests/functional/compression] +tests = ['compress_001_pos', 'compress_002_pos', 'compress_003_pos'] +tags = ['functional', 'compression'] + +[tests/functional/cp_files] +tests = ['cp_files_001_pos'] +tags = ['functional', 'cp_files'] + +[tests/functional/features/async_destroy] +tests = ['async_destroy_001_pos'] +tags = ['functional', 'features', 'async_destroy'] + +[tests/functional/features/large_dnode] +tests = ['large_dnode_001_pos', 'large_dnode_003_pos', 'large_dnode_004_neg', + 'large_dnode_005_pos', 'large_dnode_007_neg', 'large_dnode_009_pos'] +tags = ['functional', 'features', 'large_dnode'] + +[tests/functional/inuse] +tests = ['inuse_004_pos', 'inuse_005_pos', 'inuse_008_pos', 'inuse_009_pos'] +post = +tags = ['functional', 'inuse'] + +[tests/functional/large_files] +tests = ['large_files_001_pos', 'large_files_002_pos'] +tags = ['functional', 'large_files'] + +[tests/functional/largest_pool] +tests = ['largest_pool_001_pos'] +pre = +post = +tags = ['functional', 'largest_pool'] + +[tests/functional/mount] +tests = ['umount_001', 'umountall_001'] +tags = ['functional', 'mount'] + +[tests/functional/nestedfs] +tests = ['nestedfs_001_pos'] +tags = ['functional', 'nestedfs'] + +[tests/functional/no_space] +tests = ['enospc_001_pos', 'enospc_002_pos', 'enospc_003_pos', + 'enospc_df'] +tags = ['functional', 'no_space'] + +[tests/functional/nopwrite] +tests = ['nopwrite_copies', 'nopwrite_mtime', 'nopwrite_negative', + 'nopwrite_promoted_clone', 'nopwrite_recsize', 'nopwrite_sync', + 'nopwrite_varying_compression', 'nopwrite_volume'] +tags = ['functional', 'nopwrite'] + +[tests/functional/online_offline] +tests = ['online_offline_001_pos', 'online_offline_002_neg', + 'online_offline_003_neg'] +tags = ['functional', 'online_offline'] + +[tests/functional/poolversion] +tests = ['poolversion_001_pos', 'poolversion_002_pos'] +tags = ['functional', 'poolversion'] + +[tests/functional/quota] +tests = ['quota_001_pos', 'quota_002_pos', 'quota_003_pos', + 'quota_004_pos', 'quota_005_pos', 'quota_006_neg'] +tags = ['functional', 'quota'] + +[tests/functional/refquota] +tests = ['refquota_001_pos', 'refquota_002_pos', 'refquota_003_pos', + 'refquota_004_pos', 'refquota_005_pos', 'refquota_006_neg', + 'refquota_007_neg', 'refquota_008_neg'] +tags = ['functional', 'refquota'] + +[tests/functional/refreserv] +tests = ['refreserv_001_pos', 'refreserv_002_pos', 'refreserv_003_pos', + 'refreserv_004_pos', 'refreserv_005_pos', 'refreserv_multi_raidz', + 'refreserv_raidz'] +tags = ['functional', 'refreserv'] + +[tests/functional/removal] +pre = +tests = ['removal_all_vdev', 'removal_cancel', 'removal_check_space', + 'removal_condense_export', 'removal_multiple_indirection', + 'removal_nopwrite', 'removal_remap_deadlists', + 'removal_resume_export', 'removal_sanity', 'removal_with_add', + 'removal_with_create_fs', 'removal_with_dedup', + 'removal_with_errors', 'removal_with_export', + 'removal_with_ganging', 'removal_with_faulted', + 'removal_with_remove', 'removal_with_scrub', 'removal_with_send', + 'removal_with_send_recv', 'removal_with_snapshot', + 'removal_with_write', 'removal_with_zdb', 'remove_expanded', + 'remove_mirror', 'remove_mirror_sanity', 'remove_raidz', + 'remove_indirect'] +tags = ['functional', 'removal'] + +[tests/functional/resilver] +tests = ['resilver_restart_001', 'resilver_restart_002'] +tags = ['functional', 'resilver'] + +[tests/functional/rsend] +tests = ['recv_dedup', 'recv_dedup_encrypted_zvol', 'rsend_001_pos', + 'rsend_002_pos', 'rsend_003_pos', 'rsend_004_pos', 'rsend_005_pos', + 'rsend_006_pos', 'rsend_007_pos', 'rsend_008_pos', 'rsend_009_pos', + 'rsend_010_pos', 'rsend_011_pos', 'rsend_012_pos', 'rsend_013_pos', + 'rsend_014_pos', 'rsend_016_neg', 'rsend_019_pos', 'rsend_020_pos', + 'rsend_021_pos', 'rsend_022_pos', 'rsend_024_pos', + 'send-c_verify_ratio', 'send-c_verify_contents', 'send-c_props', + 'send-c_incremental', 'send-c_volume', 'send-c_zstreamdump', + 'send-c_lz4_disabled', 'send-c_recv_lz4_disabled', + 'send-c_mixed_compression', 'send-c_stream_size_estimate', + 'send-c_embedded_blocks', 'send-c_resume', 'send-cpL_varied_recsize', + 'send-c_recv_dedup', 'send-L_toggle', 'send_encrypted_hierarchy', + 'send_encrypted_props', 'send_encrypted_truncated_files', + 'send_freeobjects', 'send_realloc_files', + 'send_realloc_encrypted_files', 'send_spill_block', 'send_holds', + 'send_hole_birth', 'send_mixed_raw', 'send-wR_encrypted_zvol', + 'send_partial_dataset'] +tags = ['functional', 'rsend'] + +[tests/functional/scrub_mirror] +tests = ['scrub_mirror_001_pos', 'scrub_mirror_002_pos', + 'scrub_mirror_003_pos', 'scrub_mirror_004_pos'] +tags = ['functional', 'scrub_mirror'] + +[tests/functional/slog] +tests = ['slog_001_pos', 'slog_002_pos', 'slog_003_pos', 'slog_004_pos', + 'slog_005_pos', 'slog_006_pos', 'slog_007_pos', 'slog_008_neg', + 'slog_009_neg', 'slog_010_neg', 'slog_011_neg', 'slog_012_neg', + 'slog_013_pos', 'slog_014_pos', 'slog_015_neg', 'slog_replay_fs_001', + 'slog_replay_fs_002', 'slog_replay_volume'] +tags = ['functional', 'slog'] + +[tests/functional/snapshot] +tests = ['clone_001_pos', 'rollback_001_pos', 'rollback_002_pos', + 'rollback_003_pos', 'snapshot_001_pos', 'snapshot_002_pos', + 'snapshot_003_pos', 'snapshot_004_pos', 'snapshot_005_pos', + 'snapshot_006_pos', 'snapshot_007_pos', 'snapshot_008_pos', + 'snapshot_009_pos', 'snapshot_010_pos', 'snapshot_011_pos', + 'snapshot_012_pos', 'snapshot_013_pos', 'snapshot_014_pos', + 'snapshot_017_pos'] +tags = ['functional', 'snapshot'] + +[tests/functional/threadsappend] +tests = ['threadsappend_001_pos'] +tags = ['functional', 'threadsappend'] + +[tests/functional/upgrade] +tests = ['upgrade_userobj_001_pos', 'upgrade_readonly_pool'] +tags = ['functional', 'upgrade'] + +[tests/functional/userquota] +tests = [ + 'userquota_001_pos', 'userquota_002_pos', 'userquota_003_pos', + 'userquota_004_pos', 'userquota_005_neg', 'userquota_006_pos', + 'userquota_007_pos', 'userquota_008_pos', 'userquota_009_pos', + 'userquota_010_pos', 'userquota_011_pos', 'userquota_012_neg', + 'userspace_001_pos', 'userspace_002_pos'] +tags = ['functional', 'userquota'] + +[tests/functional/vdev_zaps] +tests = ['vdev_zaps_001_pos', 'vdev_zaps_002_pos', 'vdev_zaps_003_pos', + 'vdev_zaps_004_pos', 'vdev_zaps_005_pos', 'vdev_zaps_006_pos', + 'vdev_zaps_007_pos'] +tags = ['functional', 'vdev_zaps'] + +[tests/functional/xattr] +tests = ['xattr_001_pos', 'xattr_002_neg', 'xattr_003_neg', 'xattr_004_pos', + 'xattr_005_pos', 'xattr_006_pos', 'xattr_007_neg', + 'xattr_011_pos', 'xattr_012_pos', 'xattr_013_pos'] +tags = ['functional', 'xattr'] + +[tests/functional/zvol/zvol_cli] +tests = ['zvol_cli_001_pos', 'zvol_cli_002_pos', 'zvol_cli_003_neg'] +tags = ['functional', 'zvol', 'zvol_cli'] + +[tests/functional/zvol/zvol_misc] +tests = ['zvol_misc_002_pos', 'zvol_misc_hierarchy', 'zvol_misc_rename_inuse', + 'zvol_misc_snapdev', 'zvol_misc_volmode', 'zvol_misc_zil'] +tags = ['functional', 'zvol', 'zvol_misc'] + +[tests/functional/log_spacemap] +tests = ['log_spacemap_import_logs'] +pre = +post = +tags = ['functional', 'log_spacemap'] diff --git a/tests/zfs-tests/cmd/Makefile.am b/tests/zfs-tests/cmd/Makefile.am index 9bdb3c209756..dd08b8d553ba 100644 --- a/tests/zfs-tests/cmd/Makefile.am +++ b/tests/zfs-tests/cmd/Makefile.am @@ -87,7 +87,9 @@ scripts_zfs_tests_bin_PROGRAMS += %D%/send_doall libnvpair.la scripts_zfs_tests_bin_PROGRAMS += %D%/stride_dd +if !BUILD_MACOS %C%_stride_dd_LDADD = -lrt +endif scripts_zfs_tests_bin_PROGRAMS += %D%/threadsappend %C%_threadsappend_LDADD = -lpthread @@ -134,3 +136,7 @@ scripts_zfs_tests_bin_PROGRAMS += %D%/randfree_file scripts_zfs_tests_bin_PROGRAMS += %D%/file_fadvise %C%_file_fadvise_SOURCES = %D%/file/file_fadvise.c endif + +if BUILD_MACOS +include $(srcdir)/%D%/librt/Makefile.am +endif diff --git a/tests/zfs-tests/cmd/checksum/blake3_test.c b/tests/zfs-tests/cmd/checksum/blake3_test.c index aebe0363cc6e..eadcb8a87446 100644 --- a/tests/zfs-tests/cmd/checksum/blake3_test.c +++ b/tests/zfs-tests/cmd/checksum/blake3_test.c @@ -452,11 +452,13 @@ static blake3_test_t TestArray[] = { } }; +#ifndef dprintf #ifdef BLAKE3_DEBUG #define dprintf printf #else #define dprintf(...) #endif +#endif static char fmt_tohex(char c); static size_t fmt_hexdump(char *dest, const char *src, size_t len); diff --git a/tests/zfs-tests/cmd/dosmode_readonly_write.c b/tests/zfs-tests/cmd/dosmode_readonly_write.c index b45602d80651..0f79aede3d9a 100644 --- a/tests/zfs-tests/cmd/dosmode_readonly_write.c +++ b/tests/zfs-tests/cmd/dosmode_readonly_write.c @@ -41,6 +41,12 @@ #include #endif +#ifdef __APPLE__ +#ifndef UF_READONLY +#define UF_READONLY UF_IMMUTABLE +#endif +#endif + int main(int argc, const char *argv[]) { @@ -56,6 +62,7 @@ main(int argc, const char *argv[]) fd = open(path, O_CREAT|O_RDWR, 0777); if (fd == -1) err(EXIT_FAILURE, "%s: open failed", path); + #ifdef __linux__ uint64_t dosflags = ZFS_READONLY; if (ioctl(fd, ZFS_IOC_SETDOSFLAGS, &dosflags) == -1) diff --git a/tests/zfs-tests/cmd/librt/Makefile.am b/tests/zfs-tests/cmd/librt/Makefile.am new file mode 100644 index 000000000000..9a6dcfd1cabb --- /dev/null +++ b/tests/zfs-tests/cmd/librt/Makefile.am @@ -0,0 +1,12 @@ + +lib_LTLIBRARIES += librt.la + +nodist_librt_la_SOURCES = \ + %D%/mach_gettime.c + +# We don't want .dylib, but .so.1 as it is hardcoded into test-runner.py +# -module -version-info produces librt.1.so (not .so.1 as we need) +librt_la_LDFLAGS=-module -avoid-version -shrext .so.1 + +#install-exec-local: librt_la +# ln -s diff --git a/tests/zfs-tests/cmd/librt/mach_gettime.c b/tests/zfs-tests/cmd/librt/mach_gettime.c new file mode 100644 index 000000000000..7f3fc0ba3e16 --- /dev/null +++ b/tests/zfs-tests/cmd/librt/mach_gettime.c @@ -0,0 +1,28 @@ +/* + * This relies on lib/libspl/include/os/macos/sys/time.h + * being included. + * Old macOS did not have clock_gettime, and current + * has it in libc. Linux assumes that we are to use + * librt for it, so we create this dummy library. + * The linker will sort out connecting it for us. + */ +#include +#include +#include +#include +#include +#include + +#if !defined(MAC_OS_X_VERSION_10_12) || \ + (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12) +extern int +clock_gettime(clock_id_t clock_id, struct timespec *tp); +#endif + +extern void gettime_dummy(void); + +void +gettime_dummy(void) +{ + clock_gettime(0, NULL); +} diff --git a/tests/zfs-tests/cmd/mmap_seek.c b/tests/zfs-tests/cmd/mmap_seek.c index 7be92d109565..77808a0abfad 100644 --- a/tests/zfs-tests/cmd/mmap_seek.c +++ b/tests/zfs-tests/cmd/mmap_seek.c @@ -38,23 +38,27 @@ static void seek_data(int fd, off_t offset, off_t expected) { +#if defined(SEEK_HOLE) && defined(SEEK_DATA) off_t data_offset = lseek(fd, offset, SEEK_DATA); if (data_offset != expected) { fprintf(stderr, "lseek(fd, %d, SEEK_DATA) = %d (expected %d)\n", (int)offset, (int)data_offset, (int)expected); exit(2); } +#endif } static void seek_hole(int fd, off_t offset, off_t expected) { +#if defined(SEEK_HOLE) && defined(SEEK_DATA) off_t hole_offset = lseek(fd, offset, SEEK_HOLE); if (hole_offset != expected) { fprintf(stderr, "lseek(fd, %d, SEEK_HOLE) = %d (expected %d)\n", (int)offset, (int)hole_offset, (int)expected); exit(2); } +#endif } int diff --git a/tests/zfs-tests/include/blkdev.shlib b/tests/zfs-tests/include/blkdev.shlib index 6b83b10d604d..93fa9dc7fdd8 100644 --- a/tests/zfs-tests/include/blkdev.shlib +++ b/tests/zfs-tests/include/blkdev.shlib @@ -84,6 +84,11 @@ function block_device_wait sysctl kern.geom.conftxt >/dev/null return fi + elif is_macos; then + if [[ ${#@} -eq 0 ]]; then + sleep 3 + return + fi fi # Poll for the given paths to appear, but give up eventually. typeset -i i @@ -123,6 +128,10 @@ function is_physical_device #device -e '^nda[0-9]+$' \ -e '^nvd[0-9]+$' \ -e '^vtbd[0-9]+$' + elif is_macos; then + is_disk_device "$DEV_DSKDIR/$device" && \ + echo $device | egrep -q \ + -e '^disk[0-9]+$' else echo $device | grep -qE "^c[0-F]+([td][0-F]+)+$" fi @@ -486,7 +495,7 @@ function get_pool_devices #testpool #devdir typeset out="" case "$UNAME" in - Linux|FreeBSD) + Linux|FreeBSD|Darwin) zpool status -P $testpool | awk -v d="$devdir" '$1 ~ d {sub(d "/", ""); printf("%s ", $1)}' ;; esac diff --git a/tests/zfs-tests/include/default.cfg.in b/tests/zfs-tests/include/default.cfg.in index 3aed8ebea03a..d1c9a7c0c2da 100644 --- a/tests/zfs-tests/include/default.cfg.in +++ b/tests/zfs-tests/include/default.cfg.in @@ -209,6 +209,22 @@ FreeBSD) NEWFS_DEFAULT_FS="ufs" SLICE_PREFIX="p" ;; +Darwin) + unpack_opts="xv" + pack_opts="cf" + verbose="v" + unpack_preserve="xpf" + pack_preserve="cpf" + + ZVOL_DEVDIR="/dev/zvol" + ZVOL_RDEVDIR="/dev/zvol" + DEV_DSKDIR="/dev" + DEV_RDSKDIR="/dev" + DEV_MPATHDIR="/dev/multipath" + + NEWFS_DEFAULT_FS="hfs" + SLICE_PREFIX="s" + ;; *) export AUTO_SNAP=$(svcs -a | \ awk '/auto-snapshot/ && /online/ { print $3 }') @@ -232,6 +248,22 @@ FreeBSD) NEWFS_DEFAULT_FS="ufs" SLICE_PREFIX="s" ;; +darwin) + unpack_opts="xv" + pack_opts="cf" + verbose="v" + unpack_preserve="xpf" + pack_preserve="cpf" + + ZVOL_DEVDIR="/var/run/zfs/" + ZVOL_RDEVDIR="/var/run/zfs/" + DEV_DSKDIR="/dev" + DEV_RDSKDIR="/dev" + DEV_MPATHDIR="/dev/multipath" + + NEWFS_DEFAULT_FS="hfs" + SLICE_PREFIX="s" + ;; esac export unpack_opts pack_opts verbose unpack_preserve pack_preserve \ ZVOL_DEVDIR ZVOL_RDEVDIR DEV_DSKDIR DEV_RDSKDIR DEV_MPATHDIR \ diff --git a/tests/zfs-tests/include/libtest.shlib b/tests/zfs-tests/include/libtest.shlib index 844caa17d8ed..34e2c6823dd0 100644 --- a/tests/zfs-tests/include/libtest.shlib +++ b/tests/zfs-tests/include/libtest.shlib @@ -103,6 +103,30 @@ function is_freebsd [ "$UNAME" = "FreeBSD" ] } +# Determine if this is a macOS test system +# +# Return 0 if platform macOS, 1 if otherwise + +function is_macos +{ + [ "$UNAME" = "Darwin" ] +} + +# Determine if this is a DilOS test system +# +# Return 0 if platform DilOS, 1 if otherwise + +function is_dilos +{ + typeset ID="" + [[ -f /etc/os-release ]] && . /etc/os-release + if [[ $ID == "dilos" ]]; then + return 0 + else + return 1 + fi +} + # Determine if this is a 32-bit system # # Return 0 if platform is 32-bit, 1 if otherwise @@ -162,6 +186,14 @@ function ismounted ext*) df -t $fstype $1 > /dev/null 2>&1 ;; + hfs) + out=$(df -T $fstype $1 | sed -e /^Filesystem/d 2>/dev/null) + ret=$? + (($ret != 0)) && return $ret + dir=$(echo $out | awk '{print $9}') + name=$(echo $out | awk '{print $1}') + [[ "$1" == "$dir" || "/private${1}" == "$dir" || "$1" == "$name" ]] && return 0 + ;; zvol) if [[ -L "$ZVOL_DEVDIR/$1" ]]; then link=$(readlink -f $ZVOL_DEVDIR/$1) @@ -751,6 +783,8 @@ function zero_partitions # if is_freebsd; then gpart destroy -F $diskname + elif is_macos; then + set_partition 0 "" 0mb $diskname elif is_linux; then DSK=$DEV_DSKDIR/$diskname DSK=$(echo $DSK | sed -e "s|//|/|g") @@ -861,6 +895,41 @@ function set_partition block_device_wait $disk ;; + + Darwin) + if [[ -z $size || -z $disk ]]; then + log_fail "The size or disk name is unspecified." + fi + log_note diskutil info $disk | grep "Block Size:" + typeset -i sector_size=$(diskutil info $disk | grep "Block Size:" | awk '{print $4}') + + # To create slice "0" erase this gpt, recreate, and add EFI partition. + if [[ $slicenum == 0 ]]; then + log_must gpt destroy $disk + log_must gpt create $disk + fi + + typeset size_mb=${size%%[mMgG]} + + size_mb=${size_mb%%[mMgG][bB]} + if [[ ${size:1:1} == 'g' ]]; then + ((size_mb = size_mb * 1024)) + fi + + typeset bytes + bytes=size_mb * 1024 + + # Dont create zero sized partitions + [[ $bytes == 0 ]] && return 0 + + # Convert to a sector count + typeset sectors=$(( $bytes / $sector_size )) + + # Add partition as "ZFS" - hopefully prevent DiskArbitrator popup. + log_must gpt add -s $sectors -t "6A898CC3-1DD2-11B2-99A6-080020736631" $disk + typeset ret_val=$? + ;; + *) if [[ -z $slicenum || -z $size || -z $disk ]]; then log_fail "The slice, size or disk name is unspecified." @@ -953,6 +1022,10 @@ function get_endslice # endcyl=$(gpart show $disk | \ awk -v slice=$slice '$3 == slice { print $1 + $2 }') ;; + Darwin) + # OSX we just append partitions, so return rubbish + endcyl=0; + ;; *) disk=${disk#/dev/dsk/} disk=${disk#/dev/rdsk/} @@ -990,6 +1063,15 @@ function partition_disk # typeset total_slices=$3 typeset cyl + if is_macos; then + #OSX we ignore the start cylinder and just let gpt add a new partition + while ((i < $total_slices)); do + set_partition $i "" $slice_size $disk_name + ((i = i+1)) + done + return + fi + zero_partitions $disk_name while ((i < $total_slices)); do if ! is_linux; then @@ -1154,6 +1236,24 @@ function is_shared_linux ! exportfs -s | awk -v fs="${fs//\\/\\\\}" '/^\// && $1 == fs {exit 1}' } +function is_shared_macos +{ + typeset fs=$1 + + for mtpt in `$CAT /etc/exports | $SED 's/\"//g' | $AWK '{ print $1 }'` ; do + if [[ $mtpt == $fs ]] ; then + log_note "filesystem ==>${fs}<== is shared" + return 0 + fi + done + + typeset stat=$(/sbin/nfsd status) + if ! [[ $stat =~ "enabled" ]]; then + log_note "Current nfs/server status: $stat" + fi + return 1 +} + # # Given a mountpoint, or a dataset name, determine if it is shared via NFS. # @@ -1181,6 +1281,7 @@ function is_shared case "$UNAME" in FreeBSD) is_shared_freebsd "$fs" ;; Linux) is_shared_linux "$fs" ;; + Darwin) is_shared_macos "$fs" ;; *) is_shared_illumos "$fs" ;; esac } @@ -1351,6 +1452,12 @@ function unshare_nfs #fs log_must mv /etc/zfs/exports.$$ /etc/zfs/exports log_must kill -s HUP "$mountd" ;; + Darwin) + /sbin/nfsd update + if [[ $? != 0 ]]; then + /sbin/nfsd start + fi + ;; *) log_must unshare -F nfs $fs ;; @@ -1404,6 +1511,11 @@ function setup_nfs_server return fi + if is_macos; then + /sbin/nfsd start + return + fi + if is_linux; then # # Re-synchronize /var/lib/nfs/etab with /etc/exports and @@ -1465,7 +1577,7 @@ function setup_nfs_server # function is_global_zone { - if is_linux || is_freebsd; then + if is_macos || is_linux || is_freebsd; then return 0 else typeset cur_zone=$(zonename 2>/dev/null) @@ -2072,7 +2184,7 @@ function cleanup_devices #vdevs function find_disks { # Trust provided list, no attempt is made to locate unused devices. - if is_linux || is_freebsd; then + if is_macos || is_linux || is_freebsd; then echo "$@" return fi @@ -2235,6 +2347,60 @@ function del_group_freebsd # return 0 } +function add_user_macos # +{ + typeset group=$1 + typeset user=$2 + typeset basedir=$3 + + # Find out the next available user ID + typeset -i maxid=$(dscl . -list /Users UniqueID | awk '{print $2}' | sort -ug | tail -1) + typeset -i userid=$((maxid+1)) + + # Create the user account by running dscl. + log_must dscl . -create /Users/$user + log_must dscl . -create /Users/$user UserShell /bin/bash + log_must dscl . -create /Users/$user RealName "$user" + log_must dscl . -create /Users/$user UniqueID "$userid" + log_must dscl . -create /Users/$user PrimaryGroupID 20 + log_must dscl . -create /Users/$user NFSHomeDirectory $basedir/$user + # log_must dscl . -passwd /Users/$user $password + + log_must dseditgroup -o edit -t user -a $user $group + + # Create the home directory + log_must createhomedir -l -u $user 2>&1 | grep -v "shell-init" + + return 0 +} + +function del_user_macos # +{ + typeset user=$1 + + if $ID $user > /dev/null 2>&1; then + log_must dscl . -delete /Users/$user + fi + + return 0 +} + +function add_group_macos # +{ + typeset group=$1 + + /usr/sbin/dseditgroup -o create $group + return 0 +} + +function del_group_macos # +{ + typeset group=$1 + + /usr/sbin/dseditgroup -o delete $group + return 0 +} + function add_user_illumos # { typeset group=$1 @@ -2368,6 +2534,9 @@ function add_user # Linux) add_user_linux "$group" "$user" "$basedir" ;; + Darwin) + add_user_macos "$group" "$user" "$basedir" + ;; *) add_user_illumos "$group" "$user" "$basedir" ;; @@ -2398,6 +2567,9 @@ function del_user # Linux) del_user_linux "$user" ;; + Darwin) + del_user_macos "$user" + ;; *) del_user_illumos "$user" ;; @@ -2428,6 +2600,9 @@ function add_group # Linux) add_group_linux "$group" ;; + Darwin) + add_group_macos "$group" + ;; *) add_group_illumos "$group" ;; @@ -2456,6 +2631,9 @@ function del_group # Linux) del_group_linux "$group" ;; + Darwin) + del_group_macos "$group" + ;; *) del_group_illumos "$group" ;; @@ -2800,6 +2978,8 @@ function get_num_cpus grep -c '^processor' /proc/cpuinfo elif is_freebsd; then sysctl -n kern.smp.cpus + elif is_macos; then + sysctl -n hw.ncpu else psrinfo | wc -l fi @@ -2813,7 +2993,9 @@ function is_mp function get_cpu_freq { - if is_linux; then + if is_macos; then + sysctl -n hw.cpufrequency + elif is_linux; then lscpu | awk '/CPU MHz/ { print $3 }' elif is_freebsd; then sysctl -n hw.clockrate @@ -2942,7 +3124,7 @@ function get_objnum typeset objnum [[ -e $pathname ]] || log_fail "No such file or directory: $pathname" - if is_freebsd; then + if is_freebsd || is_macos; then objnum=$(stat -f "%i" $pathname) else objnum=$(stat -c %i $pathname) @@ -3054,7 +3236,7 @@ function zed_rc_restore # $@ Optional list of zedlets to run under zed. function zed_setup { - if ! is_linux; then + if ! is_linux && ! is_macos; then log_unsupported "No zed on $UNAME" fi @@ -3101,7 +3283,7 @@ function zed_setup # $@ Optional list of zedlets to remove from our test zed.d directory. function zed_cleanup { - if ! is_linux; then + if ! is_linux && ! is_macos; then return fi @@ -3131,7 +3313,7 @@ function zed_check # function zed_start { - if ! is_linux; then + if ! is_linux && ! is_macos; then return fi @@ -3166,7 +3348,7 @@ function zed_start # function zed_stop { - if ! is_linux; then + if ! is_linux && ! is_macos; then return "" fi @@ -3228,10 +3410,16 @@ function is_swap_inuse FreeBSD) swapctl -l | grep -wq $device ;; + Darwin) + # No swap devices on macOS + true + ;; *) swap -l | grep -wq $device ;; esac + + return $? } # @@ -3249,6 +3437,8 @@ function swap_setup FreeBSD) log_must swapctl -a $swapdev ;; + Darwin) + ;; *) log_must swap -a $swapdev ;; @@ -3269,6 +3459,8 @@ function swap_cleanup log_must swapoff $swapdev elif is_freebsd; then log_must swapoff $swapdev + elif is_macos; then + return 0 else log_must swap -d $swapdev fi @@ -3328,6 +3520,10 @@ function set_tunable_impl FreeBSD) sysctl vfs.zfs.$tunable=$value ;; + Darwin) + sysctl kstat.zfs.darwin.tunable.$tunable=$value + return "$?" + ;; SunOS) echo "${tunable}/${mdb_cmd}0t${value}" | mdb -kw ;; @@ -3378,6 +3574,9 @@ function get_tunable_impl FreeBSD) sysctl -n vfs.zfs.$tunable ;; + Darwin) + sysctl -n kstat.zfs.darwin.tunable.$tunable + ;; SunOS) [[ "$module" -eq "zfs" ]] || return 1 ;; @@ -3404,6 +3603,9 @@ function md5digest FreeBSD) md5 -q $file ;; + Darwin) + md5 -q $file + ;; *) typeset sum _ read -r sum _ < <(md5sum -b $file) @@ -3424,6 +3626,9 @@ function sha256digest FreeBSD) sha256 -q $file ;; + Darwin) + shasum -a 256 -p $file + ;; *) typeset sum _ read -r sum _ < <(sha256sum -b $file) @@ -3438,6 +3643,9 @@ function new_fs # FreeBSD) newfs "$@" ;; + Darwin) + newfs_hfs "$@" + ;; *) echo y | newfs -v "$@" ;; @@ -3452,6 +3660,9 @@ function stat_size # FreeBSD) stat -f %z "$path" ;; + Darwin) + stat -f %z "$path" + ;; *) stat -c %s "$path" ;; @@ -3522,7 +3733,7 @@ function stat_generation # # function faketty { - if is_freebsd; then + if is_freebsd || is_macos; then script -q /dev/null env "$@" else script --return --quiet -c "$*" /dev/null @@ -3553,6 +3764,10 @@ function get_xattr # name path FreeBSD) getextattr -qq user "${name}" "${path}" ;; + Darwin) + xattr -px "${name}" "${path}" > /dev/null && + xattr -px "${name}" "${path}" | xxd -r -p + ;; *) attr -qg "${name}" "${path}" ;; @@ -3569,6 +3784,9 @@ function set_xattr # name value path FreeBSD) setextattr user "${name}" "${value}" "${path}" ;; + Darwin) + xattr -wx "${name}" "`echo ${value} | xxd -p -`" "${path}" + ;; *) attr -qs "${name}" -V "${value}" "${path}" ;; @@ -3584,6 +3802,9 @@ function set_xattr_stdin # name value FreeBSD) setextattr -i user "${name}" "${path}" ;; + Darwin) + xattr -wx "${name}" "`xxd -p -`" "${path}" + ;; *) attr -qs "${name}" "${path}" ;; @@ -3599,6 +3820,9 @@ function rm_xattr # name path FreeBSD) rmextattr -q user "${name}" "${path}" ;; + Darwin) + xattr -d "${name}" "${path}" + ;; *) attr -qr "${name}" "${path}" ;; @@ -3613,6 +3837,9 @@ function ls_xattr # path FreeBSD) lsextattr -qq user "${path}" ;; + Darwin) + xattr "${path}" + ;; *) attr -ql "${path}" ;; @@ -3645,6 +3872,9 @@ function get_arcstat # stat FreeBSD) kstat arcstats.$stat ;; + Darwin) + sysctl -n kstat.zfs.misc.arcstats.$stat + ;; Linux) kstat arcstats | awk "/$stat/"' { print $3 }' ;; @@ -3848,6 +4078,10 @@ function push_coredump_pattern # dir sysctl -n kern.corefile sysctl kern.corefile="$1/core.%N" >/dev/null ;; + Darwin) + sysctl -n kern.corefile + sysctl kern.corefile="$1/core.%N" >/dev/null + ;; *) # Nothing to output – set only for this shell coreadm -p "$1/core.%f" @@ -3871,5 +4105,42 @@ function pop_coredump_pattern FreeBSD) sysctl kern.corefile="$(<"$1")" >/dev/null ;; + Darwin) + sysctl kern.corefile="$(<"$1")" >/dev/null + ;; esac } + +function disable_spotlight # pool +{ + if is_macos; then + typeset pool=$1 + typeset mntpnt=$(get_prop mountpoint $pool) + mdutil -i off $mntpnt + mdutil -E $mntpnt + fi +} + +function enable_spotlight # pool +{ + if is_macos; then + typeset pool=$1 + typeset mntpnt=$(get_prop mountpoint $pool) + mdutil -i on $mntpnt + mdutil -E $mntpnt + fi +} + +function disable_mds +{ + if is_macos; then + launchctl unload -w /System/Library/LaunchDaemons/com.apple.metadata.mds.plist + fi +} + +function enable_mds +{ + if is_macos; then + launchctl load -w /System/Library/LaunchDaemons/com.apple.metadata.mds.plist + fi +} diff --git a/tests/zfs-tests/include/tunables.cfg b/tests/zfs-tests/include/tunables.cfg index 80e7bcb3bd09..522f9615afb0 100644 --- a/tests/zfs-tests/include/tunables.cfg +++ b/tests/zfs-tests/include/tunables.cfg @@ -14,88 +14,88 @@ UNAME=$(uname) -# NAME FreeBSD tunable Linux tunable +# NAME FreeBSD tunable Linux tunable macOS tunable cat <<%%%% | -ADMIN_SNAPSHOT UNSUPPORTED zfs_admin_snapshot -ALLOW_REDACTED_DATASET_MOUNT allow_redacted_dataset_mount zfs_allow_redacted_dataset_mount -ARC_MAX arc.max zfs_arc_max -ARC_MIN arc.min zfs_arc_min -ASYNC_BLOCK_MAX_BLOCKS async_block_max_blocks zfs_async_block_max_blocks -CHECKSUM_EVENTS_PER_SECOND checksum_events_per_second zfs_checksum_events_per_second -COMMIT_TIMEOUT_PCT commit_timeout_pct zfs_commit_timeout_pct -COMPRESSED_ARC_ENABLED compressed_arc_enabled zfs_compressed_arc_enabled -CONDENSE_INDIRECT_COMMIT_ENTRY_DELAY_MS condense.indirect_commit_entry_delay_ms zfs_condense_indirect_commit_entry_delay_ms -CONDENSE_INDIRECT_OBSOLETE_PCT condense.indirect_obsolete_pct zfs_condense_indirect_obsolete_pct -CONDENSE_MIN_MAPPING_BYTES condense.min_mapping_bytes zfs_condense_min_mapping_bytes -DBUF_CACHE_SHIFT dbuf.cache_shift dbuf_cache_shift -DEADMAN_CHECKTIME_MS deadman.checktime_ms zfs_deadman_checktime_ms -DEADMAN_FAILMODE deadman.failmode zfs_deadman_failmode -DEADMAN_SYNCTIME_MS deadman.synctime_ms zfs_deadman_synctime_ms -DEADMAN_ZIOTIME_MS deadman.ziotime_ms zfs_deadman_ziotime_ms -DISABLE_IVSET_GUID_CHECK disable_ivset_guid_check zfs_disable_ivset_guid_check -DMU_OFFSET_NEXT_SYNC dmu_offset_next_sync zfs_dmu_offset_next_sync -INITIALIZE_CHUNK_SIZE initialize_chunk_size zfs_initialize_chunk_size -INITIALIZE_VALUE initialize_value zfs_initialize_value -KEEP_LOG_SPACEMAPS_AT_EXPORT keep_log_spacemaps_at_export zfs_keep_log_spacemaps_at_export -LUA_MAX_MEMLIMIT lua.max_memlimit zfs_lua_max_memlimit -L2ARC_MFUONLY l2arc.mfuonly l2arc_mfuonly -L2ARC_NOPREFETCH l2arc.noprefetch l2arc_noprefetch -L2ARC_REBUILD_BLOCKS_MIN_L2SIZE l2arc.rebuild_blocks_min_l2size l2arc_rebuild_blocks_min_l2size -L2ARC_REBUILD_ENABLED l2arc.rebuild_enabled l2arc_rebuild_enabled -L2ARC_TRIM_AHEAD l2arc.trim_ahead l2arc_trim_ahead -L2ARC_WRITE_BOOST l2arc.write_boost l2arc_write_boost -L2ARC_WRITE_MAX l2arc.write_max l2arc_write_max -LIVELIST_CONDENSE_NEW_ALLOC livelist.condense.new_alloc zfs_livelist_condense_new_alloc -LIVELIST_CONDENSE_SYNC_CANCEL livelist.condense.sync_cancel zfs_livelist_condense_sync_cancel -LIVELIST_CONDENSE_SYNC_PAUSE livelist.condense.sync_pause zfs_livelist_condense_sync_pause -LIVELIST_CONDENSE_ZTHR_CANCEL livelist.condense.zthr_cancel zfs_livelist_condense_zthr_cancel -LIVELIST_CONDENSE_ZTHR_PAUSE livelist.condense.zthr_pause zfs_livelist_condense_zthr_pause -LIVELIST_MAX_ENTRIES livelist.max_entries zfs_livelist_max_entries -LIVELIST_MIN_PERCENT_SHARED livelist.min_percent_shared zfs_livelist_min_percent_shared -MAX_DATASET_NESTING max_dataset_nesting zfs_max_dataset_nesting -MAX_MISSING_TVDS max_missing_tvds zfs_max_missing_tvds -METASLAB_DEBUG_LOAD metaslab.debug_load metaslab_debug_load -METASLAB_FORCE_GANGING metaslab.force_ganging metaslab_force_ganging -MULTIHOST_FAIL_INTERVALS multihost.fail_intervals zfs_multihost_fail_intervals -MULTIHOST_HISTORY multihost.history zfs_multihost_history -MULTIHOST_IMPORT_INTERVALS multihost.import_intervals zfs_multihost_import_intervals -MULTIHOST_INTERVAL multihost.interval zfs_multihost_interval -OVERRIDE_ESTIMATE_RECORDSIZE send.override_estimate_recordsize zfs_override_estimate_recordsize -PREFETCH_DISABLE prefetch.disable zfs_prefetch_disable -REBUILD_SCRUB_ENABLED rebuild_scrub_enabled zfs_rebuild_scrub_enabled -REMOVAL_SUSPEND_PROGRESS removal_suspend_progress zfs_removal_suspend_progress -REMOVE_MAX_SEGMENT remove_max_segment zfs_remove_max_segment -RESILVER_MIN_TIME_MS resilver_min_time_ms zfs_resilver_min_time_ms -SCAN_LEGACY scan_legacy zfs_scan_legacy -SCAN_SUSPEND_PROGRESS scan_suspend_progress zfs_scan_suspend_progress -SCAN_VDEV_LIMIT scan_vdev_limit zfs_scan_vdev_limit -SEND_HOLES_WITHOUT_BIRTH_TIME send_holes_without_birth_time send_holes_without_birth_time -SLOW_IO_EVENTS_PER_SECOND slow_io_events_per_second zfs_slow_io_events_per_second -SPA_ASIZE_INFLATION spa.asize_inflation spa_asize_inflation -SPA_DISCARD_MEMORY_LIMIT spa.discard_memory_limit zfs_spa_discard_memory_limit -SPA_LOAD_VERIFY_DATA spa.load_verify_data spa_load_verify_data -SPA_LOAD_VERIFY_METADATA spa.load_verify_metadata spa_load_verify_metadata -TRIM_EXTENT_BYTES_MIN trim.extent_bytes_min zfs_trim_extent_bytes_min -TRIM_METASLAB_SKIP trim.metaslab_skip zfs_trim_metaslab_skip -TRIM_TXG_BATCH trim.txg_batch zfs_trim_txg_batch -TXG_HISTORY txg.history zfs_txg_history -TXG_TIMEOUT txg.timeout zfs_txg_timeout -UNLINK_SUSPEND_PROGRESS UNSUPPORTED zfs_unlink_suspend_progress -VDEV_FILE_LOGICAL_ASHIFT vdev.file.logical_ashift vdev_file_logical_ashift -VDEV_FILE_PHYSICAL_ASHIFT vdev.file.physical_ashift vdev_file_physical_ashift -VDEV_MAX_AUTO_ASHIFT vdev.max_auto_ashift zfs_vdev_max_auto_ashift -VDEV_MIN_MS_COUNT vdev.min_ms_count zfs_vdev_min_ms_count -VDEV_VALIDATE_SKIP vdev.validate_skip vdev_validate_skip -VOL_INHIBIT_DEV UNSUPPORTED zvol_inhibit_dev -VOL_MODE vol.mode zvol_volmode -VOL_RECURSIVE vol.recursive UNSUPPORTED -VOL_USE_BLK_MQ UNSUPPORTED zvol_use_blk_mq -XATTR_COMPAT xattr_compat zfs_xattr_compat -ZEVENT_LEN_MAX zevent.len_max zfs_zevent_len_max -ZEVENT_RETAIN_MAX zevent.retain_max zfs_zevent_retain_max -ZIO_SLOW_IO_MS zio.slow_io_ms zio_slow_io_ms -ZIL_SAXATTR zil_saxattr zfs_zil_saxattr +ADMIN_SNAPSHOT UNSUPPORTED zfs_admin_snapshot zfs_admin_snapshot +ALLOW_REDACTED_DATASET_MOUNT allow_redacted_dataset_mount zfs_allow_redacted_dataset_mount zfs.allow_redacted_dataset_mount +ARC_MAX arc.max zfs_arc_max zfs_arc.max +ARC_MIN arc.min zfs_arc_min zfs_arc.min +ASYNC_BLOCK_MAX_BLOCKS async_block_max_blocks zfs_async_block_max_blocks zfs.async_block_max_blocks +CHECKSUM_EVENTS_PER_SECOND checksum_events_per_second zfs_checksum_events_per_second zfs.checksum_events_per_second +COMMIT_TIMEOUT_PCT commit_timeout_pct zfs_commit_timeout_pct zfs.commit_timeout_pct +COMPRESSED_ARC_ENABLED compressed_arc_enabled zfs_compressed_arc_enabled zfs.compressed_arc_enabled +CONDENSE_INDIRECT_COMMIT_ENTRY_DELAY_MS condense.indirect_commit_entry_delay_ms zfs_condense_indirect_commit_entry_delay_ms zfs_condense.indirect_commit_entry_delay_ms +CONDENSE_INDIRECT_OBSOLETE_PCT condense.indirect_obsolete_pct zfs_condense_indirect_obsolete_pct zfs_condense.indirect_obsolete_pct +CONDENSE_MIN_MAPPING_BYTES condense.min_mapping_bytes zfs_condense_min_mapping_bytes zfs_condense.min_mapping_bytes +DBUF_CACHE_SHIFT dbuf.cache_shift dbuf_cache_shift zfs_dbuf.cache_shift +DEADMAN_CHECKTIME_MS deadman.checktime_ms zfs_deadman_checktime_ms zfs_deadman.checktime_ms +DEADMAN_FAILMODE deadman.failmode zfs_deadman_failmode zfs_deadman.failmode +DEADMAN_SYNCTIME_MS deadman.synctime_ms zfs_deadman_synctime_ms zfs_deadman.synctime_ms +DEADMAN_ZIOTIME_MS deadman.ziotime_ms zfs_deadman_ziotime_ms zfs_deadman.ziotime_ms +DISABLE_IVSET_GUID_CHECK disable_ivset_guid_check zfs_disable_ivset_guid_check zfs.disable_ivset_guid_check +DMU_OFFSET_NEXT_SYNC dmu_offset_next_sync zfs_dmu_offset_next_sync zfs.dmu_offset_next_sync +INITIALIZE_CHUNK_SIZE initialize_chunk_size zfs_initialize_chunk_size zfs.initialize_chunk_size +INITIALIZE_VALUE initialize_value zfs_initialize_value zfs.initialize_value +KEEP_LOG_SPACEMAPS_AT_EXPORT keep_log_spacemaps_at_export zfs_keep_log_spacemaps_at_export zfs.keep_log_spacemaps_at_export +LUA_MAX_MEMLIMIT lua.max_memlimit zfs_lua_max_memlimit zfs_lua.max_memlimit +L2ARC_MFUONLY l2arc.mfuonly l2arc_mfuonly zfs_l2arc.mfuonly +L2ARC_NOPREFETCH l2arc.noprefetch l2arc_noprefetch zfs_l2arc.noprefetch +L2ARC_REBUILD_BLOCKS_MIN_L2SIZE l2arc.rebuild_blocks_min_l2size l2arc_rebuild_blocks_min_l2size zfs_l2arc.rebuild_blocks_min_l2size +L2ARC_REBUILD_ENABLED l2arc.rebuild_enabled l2arc_rebuild_enabled zfs_l2arc.rebuild_enabled +L2ARC_TRIM_AHEAD l2arc.trim_ahead l2arc_trim_ahead zfs_l2arc.trim_ahead +L2ARC_WRITE_BOOST l2arc.write_boost l2arc_write_boost zfs_l2arc.write_boost +L2ARC_WRITE_MAX l2arc.write_max l2arc_write_max zfs_l2arc.write_max +LIVELIST_CONDENSE_NEW_ALLOC livelist.condense.new_alloc zfs_livelist_condense_new_alloc zfs_livelist.condense.new_alloc +LIVELIST_CONDENSE_SYNC_CANCEL livelist.condense.sync_cancel zfs_livelist_condense_sync_cancel zfs_livelist.condense.sync_cancel +LIVELIST_CONDENSE_SYNC_PAUSE livelist.condense.sync_pause zfs_livelist_condense_sync_pause zfs.livelist.condense_sync_pause +LIVELIST_CONDENSE_ZTHR_CANCEL livelist.condense.zthr_cancel zfs_livelist_condense_zthr_cancel zfs.livelist.condense_zthr_cancel +LIVELIST_CONDENSE_ZTHR_PAUSE livelist.condense.zthr_pause zfs_livelist_condense_zthr_pause zfs.livelist.condense_zthr_pause +LIVELIST_MAX_ENTRIES livelist.max_entries zfs_livelist_max_entries zfs.livelist.max_entries +LIVELIST_MIN_PERCENT_SHARED livelist.min_percent_shared zfs_livelist_min_percent_shared zfs.livelist.min_percent_shared +MAX_DATASET_NESTING max_dataset_nesting zfs_max_dataset_nesting zfs.max_dataset_nesting +MAX_MISSING_TVDS max_missing_tvds zfs_max_missing_tvds zfs.max_missing_tvds +METASLAB_DEBUG_LOAD metaslab.debug_load metaslab_debug_load zfs_metaslab.debug_load +METASLAB_FORCE_GANGING metaslab.force_ganging metaslab_force_ganging zfs_metaslab.force_ganging +MULTIHOST_FAIL_INTERVALS multihost.fail_intervals zfs_multihost_fail_intervals zfs_multihost.fail_intervals +MULTIHOST_HISTORY multihost.history zfs_multihost_history zfs_multihost.history +MULTIHOST_IMPORT_INTERVALS multihost.import_intervals zfs_multihost_import_intervals zfs_multihost.import_intervals +MULTIHOST_INTERVAL multihost.interval zfs_multihost_interval zfs_multihost.interval +OVERRIDE_ESTIMATE_RECORDSIZE send.override_estimate_recordsize zfs_override_estimate_recordsize zfs_send.override_estimate_recordsize +PREFETCH_DISABLE prefetch.disable zfs_prefetch_disable zfs_prefetch.disable +REBUILD_SCRUB_ENABLED rebuild_scrub_enabled zfs_rebuild_scrub_enabled zfs.rebuild_scrub_enabled +REMOVAL_SUSPEND_PROGRESS removal_suspend_progress zfs_removal_suspend_progress zfs_vdev.removal_suspend_progress +REMOVE_MAX_SEGMENT remove_max_segment zfs_remove_max_segment zfs_vdev.remove_max_segment +RESILVER_MIN_TIME_MS resilver_min_time_ms zfs_resilver_min_time_ms zfs.resilver_min_time_ms +SCAN_LEGACY scan_legacy zfs_scan_legacy zfs.scan_legacy +SCAN_SUSPEND_PROGRESS scan_suspend_progress zfs_scan_suspend_progress zfs.scan_suspend_progress +SCAN_VDEV_LIMIT scan_vdev_limit zfs_scan_vdev_limit zfs.scan_vdev_limit +SEND_HOLES_WITHOUT_BIRTH_TIME send_holes_without_birth_time send_holes_without_birth_time zfs.send_holes_without_birth_time +SLOW_IO_EVENTS_PER_SECOND slow_io_events_per_second zfs_slow_io_events_per_second zfs.slow_io_events_per_second +SPA_ASIZE_INFLATION spa.asize_inflation spa_asize_inflation zfs_spa.asize_inflation +SPA_DISCARD_MEMORY_LIMIT spa.discard_memory_limit zfs_spa_discard_memory_limit zfs_spa.discard_memory_limit +SPA_LOAD_VERIFY_DATA spa.load_verify_data spa_load_verify_data zfs_spa.load_verify_data +SPA_LOAD_VERIFY_METADATA spa.load_verify_metadata spa_load_verify_metadata zfs_spa.load_verify_metadata +TRIM_EXTENT_BYTES_MIN trim.extent_bytes_min zfs_trim_extent_bytes_min zfs_trim.extent_bytes_min +TRIM_METASLAB_SKIP trim.metaslab_skip zfs_trim_metaslab_skip zfs_trim.metaslab_skip +TRIM_TXG_BATCH trim.txg_batch zfs_trim_txg_batch zfs_trim.txg_batch +TXG_HISTORY txg.history zfs_txg_history zfs_txg.history +TXG_TIMEOUT txg.timeout zfs_txg_timeout zfs_txg.timeout +UNLINK_SUSPEND_PROGRESS UNSUPPORTED zfs_unlink_suspend_progress UNSUPPORTED +VDEV_FILE_LOGICAL_ASHIFT vdev.file.logical_ashift vdev_file_logical_ashift UNSUPPORTED +VDEV_FILE_PHYSICAL_ASHIFT vdev.file.physical_ashift vdev_file_physical_ashift UNSUPPORTED +VDEV_MAX_AUTO_ASHIFT vdev.max_auto_ashift zfs_vdev_max_auto_ashift zfs_vdev.max_auto_ashift +VDEV_MIN_MS_COUNT vdev.min_ms_count zfs_vdev_min_ms_count zfs_vdev.min_ms_count +VDEV_VALIDATE_SKIP vdev.validate_skip vdev_validate_skip zfs_vdev.validate_skip +VOL_INHIBIT_DEV UNSUPPORTED zvol_inhibit_dev UNSUPPORTED +VOL_MODE vol.mode zvol_volmode UNSUPPORTED +VOL_RECURSIVE vol.recursive UNSUPPORTED UNSUPPORTED +VOL_USE_BLK_MQ UNSUPPORTED zvol_use_blk_mq UNSUPPORTED +XATTR_COMPAT xattr_compat zfs_xattr_compat UNSUPPORTED +ZEVENT_LEN_MAX zevent.len_max zfs_zevent_len_max zfs_zevent.len_max +ZEVENT_RETAIN_MAX zevent.retain_max zfs_zevent_retain_max zfs_zevent.retain_max +ZIO_SLOW_IO_MS zio.slow_io_ms zio_slow_io_ms zfs_zio.slow_io_ms +ZIL_SAXATTR zil_saxattr zfs_zil_saxattr zfs.zil_saxattr %%%% -while read name FreeBSD Linux; do +while read name FreeBSD Linux Darwin; do eval "export ${name}=\$${UNAME}" done diff --git a/tests/zfs-tests/tests/functional/hkdf/hkdf_test.c b/tests/zfs-tests/tests/functional/hkdf/hkdf_test.c index 24aeb0b224a7..a8ca346b9087 100644 --- a/tests/zfs-tests/tests/functional/hkdf/hkdf_test.c +++ b/tests/zfs-tests/tests/functional/hkdf/hkdf_test.c @@ -22,6 +22,7 @@ #include #include #include +#include /* * Byte arrays are given as char pointers so that they diff --git a/tests/zfs-tests/tests/perf/perf.shlib b/tests/zfs-tests/tests/perf/perf.shlib index 5555e910d722..eeb718f80a2b 100644 --- a/tests/zfs-tests/tests/perf/perf.shlib +++ b/tests/zfs-tests/tests/perf/perf.shlib @@ -387,6 +387,9 @@ function get_min_arc_size FreeBSD) sysctl -n kstat.zfs.misc.arcstats.c_min ;; + Darwin) + sysctl -n kstat.zfs.misc.arcstats.c_min + ;; *) dtrace -qn 'BEGIN { printf("%u\n", `arc_stats.arcstat_c_min.value.ui64); @@ -405,6 +408,9 @@ function get_max_arc_size FreeBSD) sysctl -n kstat.zfs.misc.arcstats.c_max ;; + Darwin) + sysctl -n kstat.zfs.misc.arcstats.c_max + ;; *) dtrace -qn 'BEGIN { printf("%u\n", `arc_stats.arcstat_c_max.value.ui64); @@ -423,6 +429,9 @@ function get_arc_target FreeBSD) sysctl -n kstat.zfs.misc.arcstats.c ;; + Darwin) + sysctl -n kstat.zfs.misc.arcstats.c + ;; *) dtrace -qn 'BEGIN { printf("%u\n", `arc_stats.arcstat_c.value.ui64); From 5b2f117f5f1a5a179f75c5cbb807d04f6d34ab26 Mon Sep 17 00:00:00 2001 From: Jorgen Lundman Date: Mon, 23 Oct 2023 09:06:45 +0900 Subject: [PATCH 4/9] 4: config/* scripts/* contrib/* part4: config/, scripts/, contrib/* Signed-off-by: Jorgen Lundman --- config/Rules.am | 5 +- config/always-compiler-options.m4 | 20 + config/always-system.m4 | 7 + config/kernel-macos-headers.m4 | 103 +++ config/kernel.m4 | 29 +- config/macos.m4 | 11 + config/user-libfetch.m4 | 10 +- config/user.m4 | 7 +- config/zfs-build.m4 | 6 +- contrib/macOS/VolumeIcon.icns | Bin 0 -> 1753264 bytes contrib/macOS/pkg-scripts/postinstall | 51 ++ .../pkg-scripts/postinstall_actions/kextspost | 44 + .../postinstall_actions/loadplists | 29 + .../pkg-scripts/postinstall_actions/startzed | 20 + contrib/macOS/pkg-scripts/preinstall | 51 ++ .../pkg-scripts/preinstall_actions/0uninstall | 236 +++++ .../preinstall_actions/lookforzpool | 34 + .../pkg-scripts/preinstall_actions/unloadzfs | 42 + contrib/macOS/product-scripts/poolcheck.sh | 11 + contrib/macOS/product-scripts/zevocheck.sh | 18 + .../resources/English.lproj/Conclusion.rtf | 70 ++ .../macOS/resources/English.lproj/License.txt | 828 ++++++++++++++++++ .../English.lproj/Localizable.strings | Bin 0 -> 1044 bytes .../macOS/resources/English.lproj/ReadMe.rtf | 54 ++ .../macOS/resources/English.lproj/Welcome.rtf | 48 + contrib/macOS/resources/background.png | Bin 0 -> 20428 bytes contrib/macOS/resources/javascript.js | 200 +++++ scripts/zfs-tests.sh | 75 +- 28 files changed, 1995 insertions(+), 14 deletions(-) create mode 100644 config/kernel-macos-headers.m4 create mode 100644 config/macos.m4 create mode 100644 contrib/macOS/VolumeIcon.icns create mode 100755 contrib/macOS/pkg-scripts/postinstall create mode 100755 contrib/macOS/pkg-scripts/postinstall_actions/kextspost create mode 100755 contrib/macOS/pkg-scripts/postinstall_actions/loadplists create mode 100755 contrib/macOS/pkg-scripts/postinstall_actions/startzed create mode 100755 contrib/macOS/pkg-scripts/preinstall create mode 100755 contrib/macOS/pkg-scripts/preinstall_actions/0uninstall create mode 100755 contrib/macOS/pkg-scripts/preinstall_actions/lookforzpool create mode 100755 contrib/macOS/pkg-scripts/preinstall_actions/unloadzfs create mode 100755 contrib/macOS/product-scripts/poolcheck.sh create mode 100755 contrib/macOS/product-scripts/zevocheck.sh create mode 100644 contrib/macOS/resources/English.lproj/Conclusion.rtf create mode 100644 contrib/macOS/resources/English.lproj/License.txt create mode 100644 contrib/macOS/resources/English.lproj/Localizable.strings create mode 100644 contrib/macOS/resources/English.lproj/ReadMe.rtf create mode 100644 contrib/macOS/resources/English.lproj/Welcome.rtf create mode 100644 contrib/macOS/resources/background.png create mode 100644 contrib/macOS/resources/javascript.js diff --git a/config/Rules.am b/config/Rules.am index 7c266964f3f3..65c5b7370543 100644 --- a/config/Rules.am +++ b/config/Rules.am @@ -27,6 +27,10 @@ AM_CFLAGS += -fPIC -Werror -Wno-unknown-pragmas -Wno-enum-conversion AM_CFLAGS += -include $(top_srcdir)/include/os/freebsd/spl/sys/ccompile.h AM_CFLAGS += -I/usr/include -I/usr/local/include endif +if BUILD_MACOS +AM_CFLAGS += \ + -I$(top_srcdir)/lib/libspl/include/os/macos +endif AM_CPPFLAGS += -D_GNU_SOURCE AM_CPPFLAGS += -D_REENTRANT @@ -75,7 +79,6 @@ AM_LDFLAGS += -Wl,-x -Wl,--fatal-warnings -Wl,--warn-shared-textrel AM_LDFLAGS += -lm endif - # If a target includes kernel code, generate warnings for large stack frames KERNEL_CFLAGS = $(FRAME_LARGER_THAN) diff --git a/config/always-compiler-options.m4 b/config/always-compiler-options.m4 index 6383b12506ee..d2689ee56ae8 100644 --- a/config/always-compiler-options.m4 +++ b/config/always-compiler-options.m4 @@ -355,3 +355,23 @@ AC_DEFUN([ZFS_AC_CONFIG_ALWAYS_KERNEL_CC_NO_IPA_SRA], [ CFLAGS="$saved_flags" AC_SUBST([KERNEL_NO_IPA_SRA]) ]) + +dnl # Check if cc supports -finline-hint-functions +dnl # +AC_DEFUN([ZFS_AC_CONFIG_ALWAYS_INLINE_HINT_FUNCTIONS], [ + AC_MSG_CHECKING([whether $CC supports -finline-hint-functions]) + + saved_flags="$CFLAGS" + CFLAGS="$CFLAGS -Werror -finline-hint-functions" + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [])], [ + INLINE_HINT_FUNCTIONS=-finline-hint-functions + AC_MSG_RESULT([yes]) + ], [ + INLINE_HINT_FUNCTIONS= + AC_MSG_RESULT([no]) + ]) + + CFLAGS="$saved_flags" + AC_SUBST([INLINE_HINT_FUNCTIONS]) +]) diff --git a/config/always-system.m4 b/config/always-system.m4 index 3a3d4212f8b0..4bda7831e23d 100644 --- a/config/always-system.m4 +++ b/config/always-system.m4 @@ -16,6 +16,12 @@ AC_DEFUN([ZFS_AC_CONFIG_ALWAYS_SYSTEM], [ ac_system="FreeBSD" ac_system_l="freebsd" ;; + *darwin*) + AC_DEFINE([SYSTEM_MACOS], [1], + [True if ZFS is to be compiled for a macOS system]) + ac_system="macOS" + ac_system_l="macos" + ;; *) ac_system="unknown" ac_system_l="unknown" @@ -27,4 +33,5 @@ AC_DEFUN([ZFS_AC_CONFIG_ALWAYS_SYSTEM], [ AM_CONDITIONAL([BUILD_LINUX], [test "x$ac_system" = "xLinux"]) AM_CONDITIONAL([BUILD_FREEBSD], [test "x$ac_system" = "xFreeBSD"]) + AM_CONDITIONAL([BUILD_MACOS], [test "x$ac_system" = "xmacOS"]) ]) diff --git a/config/kernel-macos-headers.m4 b/config/kernel-macos-headers.m4 new file mode 100644 index 000000000000..0adbb9ba7133 --- /dev/null +++ b/config/kernel-macos-headers.m4 @@ -0,0 +1,103 @@ +dnl # +dnl # macOS - attempt to find kernel headers. This is expected to +dnl # only run on mac platforms (using xcrun command) to iterate +dnl # through versions of xcode, and xnu kernel source locations +dnl # +AC_DEFUN([ZFS_AC_KERNEL_SRC_MACOS_HEADERS], [ + AM_COND_IF([BUILD_MACOS], [ + AC_MSG_CHECKING([macOS kernel source directory]) + AS_IF([test -z "$kernelsrc"], [ + system_major_version=`sw_vers -productVersion | $AWK -F '.' '{ print $[]1 "." $[]2 }'` + AS_IF([test -d "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX${system_major_version}.sdk/System/Library/Frameworks/Kernel.framework/Headers"], [ + kernelsrc="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX${system_major_version}.sdk/System/Library/Frameworks/Kernel.framework"]) + ]) + AS_IF([test -z "$kernelsrc"], [ + AS_IF([test -d "/System/Library/Frameworks/Kernel.framework/Headers"], [ + kernelsrc="/System/Library/Frameworks/Kernel.framework"]) + ]) + AS_IF([test -z "$kernelsrc"], [ + tmpdir=`xcrun --show-sdk-path` + AS_IF([test -d "$tmpdir/System/Library/Frameworks/Kernel.framework/Headers"], [ + kernelsrc="$tmpdir/System/Library/Frameworks/Kernel.framework"]) + ]) + AS_IF([test -z "$kernelsrc"], [ + AS_IF([test -d "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/System/Library/Frameworks/Kernel.framework"], [ + kernelsrc="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/System/Library/Frameworks/Kernel.framework"]) + ]) + AS_IF([test -z "$kernelsrc"], [ + AS_IF([test -d "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/Kernel.framework"], [ + kernelsrc="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/Kernel.framework"]) + ]) + AS_IF([test -z "$kernelsrc"], [ + AS_IF([test -d "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/Kernel.framework"], [ + kernelsrc="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/Kernel.framework"]) + ]) + + AC_MSG_RESULT([$kernelsrc]) + + AC_MSG_CHECKING([macOS kernel build directory]) + AS_IF([test -z "$kernelbuild"], [ + kernelbuild=${kernelsrc} + ]) + ]) + AC_MSG_RESULT([$kernelbuild]) + + AC_ARG_WITH([kernel-modprefix], + AS_HELP_STRING([--with-kernel-modprefix=PATH], + [Path to kernel module prefix]), + [KERNEL_MODPREFIX="$withval"]) + AC_MSG_CHECKING([macOS kernel module prefix]) + AS_IF([test -z "$KERNEL_MODPREFIX"], [ + KERNEL_MODPREFIX="/Library/Extensions" + ]) + AC_MSG_RESULT([$KERNEL_MODPREFIX]) + AC_DEFINE_UNQUOTED([KERNEL_MODPREFIX], + ["$KERNEL_MODPREFIX"], + [Path where the kernel module is installed.] + ) + + AC_MSG_CHECKING([macOS kernel source version]) + utsrelease1=$kernelbuild/Headers/libkern/version.h + AS_IF([test -r $utsrelease1 && fgrep -q OSRELEASE $utsrelease1], [ + kernverfile=libkern/version.h ]) + + AS_IF([test "$kernverfile"], [ + kernsrcver=`(echo "#include <$kernverfile>"; + echo "kernsrcver=OSRELEASE") | + cpp -I$kernelbuild/Headers | + grep "^kernsrcver=" | cut -d \" -f 2` + + AS_IF([test -z "$kernsrcver"], [ + AC_MSG_RESULT([Not found]) + AC_MSG_ERROR([*** Cannot determine kernel version.]) + ]) + AC_MSG_RESULT([$kernsrcver]) + ]) + + AC_MSG_CHECKING([mach_kernel]) + AS_IF([test -z "$machkernel"], [ + AS_IF([test -e "/System/Library/Kernels/kernel"], [ + machkernel="/System/Library/Kernels/kernel" ] ) + AS_IF([test -e "/mach_kernel"], [ + machkernel="/mach_kernel" ] ) + AS_IF([test ! -f "$machkernel"], [ + AC_MSG_ERROR([ + *** mach_kernel file not found. For 10.9 and prior, this should be + *** '/mach_kernel' and for 10.10 and following, this should be + *** '/System/Library/Kernels/kernel']) + ]) + ]) + AC_MSG_RESULT($machkernel) + + +dnl More Generic names: + MACH_KERNEL=${machkernel} + KERNEL_HEADERS=${kernelsrc} + KERNEL_VERSION=${kernsrcver} + AC_SUBST(KERNEL_HEADERS) + AC_SUBST(KERNEL_MODPREFIX) + AC_SUBST(KERNEL_VERSION) + AC_SUBST(MACH_KERNEL) + ]) +]) + diff --git a/config/kernel.m4 b/config/kernel.m4 index 056517a841f2..d35d5f0916a6 100644 --- a/config/kernel.m4 +++ b/config/kernel.m4 @@ -28,6 +28,10 @@ AC_DEFUN([ZFS_AC_CONFIG_KERNEL], [ AC_SUBST(KERNEL_MAKE) ]) + AM_COND_IF([BUILD_MACOS], [ + ZFS_AC_KERNEL_SRC_MACOS_HEADERS + ZFS_AC_CONFIG_ALWAYS_CC_FRAME_LARGER_THAN + ]) ]) dnl # @@ -374,6 +378,7 @@ dnl # and `/usr/src/linux-*` with the highest version number according dnl # to `sort -V` is assumed to be both source and build directory. dnl # AC_DEFUN([ZFS_AC_KERNEL], [ + AC_ARG_WITH([linux], AS_HELP_STRING([--with-linux=PATH], [Path to kernel source]), @@ -384,6 +389,15 @@ AC_DEFUN([ZFS_AC_KERNEL], [ [Path to kernel build objects]), [kernelbuild="$withval"]) + AC_ARG_WITH([macos], + AS_HELP_STRING([--with-macos=PATH], + [Path to macOS source]), + [kernelsrc="$withval/sys"]) + AC_ARG_WITH(macos-obj, + AS_HELP_STRING([--with-macos-obj=PATH], + [Path to macOS build objects]), + [kernelbuild="$withval/$kernelsrc"]) + AC_MSG_CHECKING([kernel source and build directories]) AS_IF([test -n "$kernelsrc" && test -z "$kernelbuild"], [ kernelbuild="$kernelsrc" @@ -447,7 +461,8 @@ AC_DEFUN([ZFS_AC_KERNEL], [ *** Please make sure the kernel devel package for your distribution *** is installed and then try again. If that fails, you can specify the *** location of the kernel source and build with the '--with-linux=PATH' and - *** '--with-linux-obj=PATH' options respectively.]) + *** '--with-linux-obj=PATH' options respectively. + *** If you are configuring for macOS, use '--with-macos=PATH'.]) ]) AC_MSG_CHECKING([kernel source version]) @@ -598,9 +613,9 @@ dnl # ZFS_LINUX_CONFTEST_H dnl # AC_DEFUN([ZFS_LINUX_CONFTEST_H], [ test -d build/$2 || mkdir -p build/$2 -cat - <<_ACEOF >build/$2/$2.h +cat - <<_ACEOF1 >build/$2/$2.h $1 -_ACEOF +_ACEOF1 ]) dnl # @@ -608,9 +623,9 @@ dnl # ZFS_LINUX_CONFTEST_C dnl # AC_DEFUN([ZFS_LINUX_CONFTEST_C], [ test -d build/$2 || mkdir -p build/$2 -cat confdefs.h - <<_ACEOF >build/$2/$2.c +cat confdefs.h - <<_ACEOF2 >build/$2/$2.c $1 -_ACEOF +_ACEOF2 ]) dnl # @@ -627,12 +642,12 @@ AC_DEFUN([ZFS_LINUX_CONFTEST_MAKEFILE], [ file=build/$1/Makefile dnl # Example command line to manually build source. - cat - <<_ACEOF >$file + cat - <<_ACEOF3 >$file # Example command line to manually build source # make modules -C $LINUX_OBJ $ARCH_UM M=$PWD/build/$1 ccflags-y := -Werror $FRAME_LARGER_THAN -_ACEOF +_ACEOF3 dnl # Additional custom CFLAGS as requested. m4_ifval($3, [echo "ccflags-y += $3" >>$file], []) diff --git a/config/macos.m4 b/config/macos.m4 new file mode 100644 index 000000000000..30e8ebf2ec46 --- /dev/null +++ b/config/macos.m4 @@ -0,0 +1,11 @@ + + +AC_DEFUN([ZFS_AC_MACOS_IMPURE_ENABLE], [ + AC_ARG_ENABLE(macos_impure, + AS_HELP_STRING([--enable-macos-impure], + [Use XNU Private.exports [[default: no]]]), + [CPPFLAGS="$CPPFLAGS -DMACOS_IMPURE"], + []) +]) + + diff --git a/config/user-libfetch.m4 b/config/user-libfetch.m4 index d961c6ca77a1..0b727e43dd66 100644 --- a/config/user-libfetch.m4 +++ b/config/user-libfetch.m4 @@ -45,8 +45,14 @@ AC_DEFUN([ZFS_AC_CONFIG_USER_LIBFETCH], [ LIBFETCH_IS_LIBCURL=1 if test "$(curl-config --built-shared)" = "yes"; then LIBFETCH_DYNAMIC=1 - LIBFETCH_SONAME="libcurl.so.4" - LIBFETCH_LIBS="-ldl" + dnl # why are we hardcoding libnames? + AM_COND_IF([BUILD_MACOS], [ + LIBFETCH_SONAME="libcurl.4.dylib" + LIBFETCH_LIBS="" + ], [ + LIBFETCH_SONAME="libcurl.so.4" + LIBFETCH_LIBS="-ldl" + ]) AC_MSG_RESULT([libcurl]) else LIBFETCH_LIBS="$(curl-config --libs)" diff --git a/config/user.m4 b/config/user.m4 index 6ec27a5b2cf5..6a97a21bbecf 100644 --- a/config/user.m4 +++ b/config/user.m4 @@ -24,8 +24,11 @@ AC_DEFUN([ZFS_AC_CONFIG_USER], [ ZFS_AC_CONFIG_USER_LIBATOMIC ZFS_AC_CONFIG_USER_LIBFETCH ZFS_AC_CONFIG_USER_AIO_H - ZFS_AC_CONFIG_USER_CLOCK_GETTIME - ZFS_AC_CONFIG_USER_PAM + AM_COND_IF([BUILD_MACOS], [ + ZFS_AC_MACOS_IMPURE_ENABLE], [ + ZFS_AC_CONFIG_USER_CLOCK_GETTIME + ZFS_AC_CONFIG_USER_PAM + ]) ZFS_AC_CONFIG_USER_RUNSTATEDIR ZFS_AC_CONFIG_USER_MAKEDEV_IN_SYSMACROS ZFS_AC_CONFIG_USER_MAKEDEV_IN_MKDEV diff --git a/config/zfs-build.m4 b/config/zfs-build.m4 index 5f36569fe25b..155c7dbf6bd0 100644 --- a/config/zfs-build.m4 +++ b/config/zfs-build.m4 @@ -538,6 +538,8 @@ AC_DEFUN([ZFS_AC_DEFAULT_PACKAGE], [ VENDOR=freebsd ; elif test -f /etc/openEuler-release ; then VENDOR=openeuler ; + elif test -f /usr/bin/sw_vers ; then + VENDOR=apple ; else VENDOR= ; fi], @@ -563,6 +565,7 @@ AC_DEFUN([ZFS_AC_DEFAULT_PACKAGE], [ debian) DEFAULT_PACKAGE=deb ;; freebsd) DEFAULT_PACKAGE=pkg ;; openeuler) DEFAULT_PACKAGE=rpm ;; + apple) DEFAULT_PACKAGE=pkg ;; *) DEFAULT_PACKAGE=rpm ;; esac AC_MSG_RESULT([$DEFAULT_PACKAGE]) @@ -606,6 +609,7 @@ AC_DEFUN([ZFS_AC_DEFAULT_PACKAGE], [ ubuntu) initconfdir=/etc/default ;; debian) initconfdir=/etc/default ;; freebsd) initconfdir=$sysconfdir/rc.conf.d;; + apple) initconfdir=${prefix}/etc/launchd/launchd.d/ ;; *) initconfdir=/etc/default ;; esac AC_MSG_RESULT([$initconfdir]) @@ -639,7 +643,7 @@ dnl # Default ZFS package configuration dnl # AC_DEFUN([ZFS_AC_PACKAGE], [ ZFS_AC_DEFAULT_PACKAGE - AS_IF([test x$VENDOR != xfreebsd], [ + AS_IF([test x$VENDOR != xfreebsd -a x$VENDOR != xapple], [ ZFS_AC_RPM ZFS_AC_DPKG ZFS_AC_ALIEN diff --git a/contrib/macOS/VolumeIcon.icns b/contrib/macOS/VolumeIcon.icns new file mode 100644 index 0000000000000000000000000000000000000000..ec23398ef3f02681203e5c44d6fd392b30da8f70 GIT binary patch literal 1753264 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%0D0Rk98ESF>9rIQ|9;PKTKe!9N&4VYw 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 z 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 zem⪚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

?>djV@@646tqdgRqjWhU=O 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Π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%0AwuN+LZTX{s+XP?naa_XgAwe@Tje*dGF#n~ai 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

    VSWuZD7t{ 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

    (c27!hhk4S-)la?P35~lCALj~%Fmzc+X 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ⅇ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�G!k}A0sH*Z#sY8 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

    ^}_!Bf5 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∨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� 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

    ^f5Wf20;qGeT;E0s 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≥L&=|P)u@;RmhBk9XFDLElKl)Ej6 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�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 B99aMW literal 0 HcmV?d00001 diff --git a/contrib/macOS/pkg-scripts/postinstall b/contrib/macOS/pkg-scripts/postinstall new file mode 100755 index 000000000000..647dae71b7c1 --- /dev/null +++ b/contrib/macOS/pkg-scripts/postinstall @@ -0,0 +1,51 @@ +#!/usr/bin/perl + +$0 =~ m/.*\/(.*)$/; +my $stage = $1; +my $action = $ARGV[3]; +my $wd = `pwd`; +chomp($wd); +my $actionsDir = $wd . "/" . 'postinstall' . "_actions/"; +my $upgradeDir = $wd . "/" . 'postupgrade' . "_actions/"; +my $upgrade = 0; +my $debug = 1; + +if ( $0 =~ /postupgrade/ || $0 =~ /preupgrade/ || $action =~ /upgrade/) { + system("logger -p install.info 'Upgrade scripts will be run.'") if ( $debug ); + $upgrade = 1; +} + +if ( $stage =~ /pre/ ) +{ + $actionsDir = $wd . "/" . 'preinstall' . "_actions/"; + $upgradeDir = $wd . "/" . 'preupgrade' . "_actions/"; +} + +if ( $upgrade == 1 && -e $upgradeDir ) { + system("logger -p install.info 'Running Upgrade Scripts . . .'") if ( $debug ); + $count = 0; + foreach my $action (`/bin/ls \"$upgradeDir\"`) { + chomp($action); + system("logger -p install.info 'Begin script: $action'") if ( $debug ); + system($upgradeDir . $action, $ARGV[0], $ARGV[1], $ARGV[2], $ARGV[3]) == 0 or die; + system("logger -p install.info 'End script: $action'") if ( $debug ); + $count++; + } + system("logger -p install.info '$count Upgrade Scripts run.'") if ( $debug ); +} + +if ( -e $actionsDir ) { + system("logger -p install.info 'Running Install Scripts . . .'") if ( $debug ); + $count = 0; + foreach my $action (`/bin/ls \"$actionsDir\"`) { + chomp($action); + system("logger -p install.info 'Begin script: $action'") if ( $debug ); + system($actionsDir . $action, $ARGV[0], $ARGV[1], $ARGV[2], $ARGV[3],$upgrade) == 0 or die; + system("logger -p install.info 'End script: $action'") if ( $debug ); + $count++; + } + system("logger -p install.info '$count Install Scripts run.'") if ( $debug ); +} + +exit(0); + diff --git a/contrib/macOS/pkg-scripts/postinstall_actions/kextspost b/contrib/macOS/pkg-scripts/postinstall_actions/kextspost new file mode 100755 index 000000000000..20c633252be7 --- /dev/null +++ b/contrib/macOS/pkg-scripts/postinstall_actions/kextspost @@ -0,0 +1,44 @@ +#!/bin/sh +date '+%s' >> /tmp/o3x.log +echo $0 >> /tmp/o3x.log + +if [ "$(uname -r | awk -F '.' '{print $1;}')" -lt 13 ] ; then + kernelextensionsdir=$3System/Library/Extensions +else + kernelextensionsdir=$3Library/Extensions + if [ ! -d $3Library/Extensions/zfs.kext/ ] ; then + kernelextensionsdir=$3System/Library/Extensions + fi +fi + +if [ $(/usr/sbin/kextstat -b org.openzfsonosx.zfs | wc -l) -gt 1 ] ; then + printf "\nUnloading zfs.kext...\n" + /sbin/kextunload -b org.openzfsonosx.zfs +fi +if [ $(/usr/sbin/kextstat -b net.lundman.zfs | wc -l) -gt 1 ] ; then + printf "\nUnloading zfs.kext...\n" + /sbin/kextunload -b net.lundman.zfs +fi +if [ $(/usr/sbin/kextstat -b net.lundman.spl | wc -l) -gt 1 ] ; then + printf "\nUnloading spl.kext...\n" + /sbin/kextunload -b net.lundman.spl +fi + +printf "\nTouching %s\n" "${kernelextensionsdir}" +/usr/bin/touch "${kernelextensionsdir}" + +# HUP kextd +/usr/bin/killall -HUP kextd + +# Load kext +printf "\nLoading spl.kext...\n" +/usr/bin/kextutil "${kernelextensionsdir}/zfs.kext" 2>&1 | grep -q "rejected due to system policy" + +if [[ $? -eq 0 ]]; then + open /System/Library/PreferencePanes/Security.prefPane +else + printf "\nLoading zfs.kext...\n" + /sbin/kextload "${kernelextensionsdir}/zfs.kext" +fi + +exit 0 diff --git a/contrib/macOS/pkg-scripts/postinstall_actions/loadplists b/contrib/macOS/pkg-scripts/postinstall_actions/loadplists new file mode 100755 index 000000000000..85cd15f19080 --- /dev/null +++ b/contrib/macOS/pkg-scripts/postinstall_actions/loadplists @@ -0,0 +1,29 @@ +#!/bin/bash +date '+%s' >> /tmp/o3x.log +echo $0 >> /tmp/o3x.log + +launchctl list | grep org.openzfsonosx.zconfigd 1>/dev/null +[ $? -eq 0 ] && /bin/launchctl remove org.openzfsonosx.zconfigd + +/bin/launchctl load -w /Library/LaunchDaemons/org.openzfsonosx.zconfigd.plist + +launchctl list | grep org.openzfsonosx.zed.service 1>/dev/null +[ $? -eq 0 ] && /bin/launchctl remove org.openzfsonosx.zed.service + +launchctl list | grep org.openzfsonosx.zed 1>/dev/null +[ $? -eq 0 ] && /bin/launchctl remove org.openzfsonosx.zed + +/bin/launchctl load -w /Library/LaunchDaemons/org.openzfsonosx.zed.plist + +launchctl list | grep org.openzfsonosx.InvariantDisks 1>/dev/null +[ $? -eq 0 ] && /bin/launchctl remove org.openzfsonosx.InvariantDisks + +/bin/launchctl load -w /Library/LaunchDaemons/org.openzfsonosx.InvariantDisks.plist + +launchctl list | grep org.openzfsonosx.zpool-autoimport 1>/dev/null +[ $? -eq 0 ] && /bin/launchctl remove org.openzfsonosx.zpool-autoimport + +launchctl list | grep org.openzfsonosx.zpool-import-all 1>/dev/null +[ $? -eq 0 ] && /bin/launchctl remove org.openzfsonosx.zpool-import-all + +/bin/launchctl load -w /Library/LaunchDaemons/org.openzfsonosx.zpool-import-all.plist diff --git a/contrib/macOS/pkg-scripts/postinstall_actions/startzed b/contrib/macOS/pkg-scripts/postinstall_actions/startzed new file mode 100755 index 000000000000..a17445241389 --- /dev/null +++ b/contrib/macOS/pkg-scripts/postinstall_actions/startzed @@ -0,0 +1,20 @@ +#!/bin/bash + +set +e +pgrep zed 1>/dev/null +pgrepret=$? +set -e + +if [ $pgrepret -ne 0 ] ; then + echo "zed not started" + if [ -f /usr/sbin/zed ] ; then + echo "Starting zed ..." + /usr/sbin/zed + else + echo "zed binary /usr/sbin/zed not found" + fi +else + echo "zed already running" +fi + +exit 0 diff --git a/contrib/macOS/pkg-scripts/preinstall b/contrib/macOS/pkg-scripts/preinstall new file mode 100755 index 000000000000..647dae71b7c1 --- /dev/null +++ b/contrib/macOS/pkg-scripts/preinstall @@ -0,0 +1,51 @@ +#!/usr/bin/perl + +$0 =~ m/.*\/(.*)$/; +my $stage = $1; +my $action = $ARGV[3]; +my $wd = `pwd`; +chomp($wd); +my $actionsDir = $wd . "/" . 'postinstall' . "_actions/"; +my $upgradeDir = $wd . "/" . 'postupgrade' . "_actions/"; +my $upgrade = 0; +my $debug = 1; + +if ( $0 =~ /postupgrade/ || $0 =~ /preupgrade/ || $action =~ /upgrade/) { + system("logger -p install.info 'Upgrade scripts will be run.'") if ( $debug ); + $upgrade = 1; +} + +if ( $stage =~ /pre/ ) +{ + $actionsDir = $wd . "/" . 'preinstall' . "_actions/"; + $upgradeDir = $wd . "/" . 'preupgrade' . "_actions/"; +} + +if ( $upgrade == 1 && -e $upgradeDir ) { + system("logger -p install.info 'Running Upgrade Scripts . . .'") if ( $debug ); + $count = 0; + foreach my $action (`/bin/ls \"$upgradeDir\"`) { + chomp($action); + system("logger -p install.info 'Begin script: $action'") if ( $debug ); + system($upgradeDir . $action, $ARGV[0], $ARGV[1], $ARGV[2], $ARGV[3]) == 0 or die; + system("logger -p install.info 'End script: $action'") if ( $debug ); + $count++; + } + system("logger -p install.info '$count Upgrade Scripts run.'") if ( $debug ); +} + +if ( -e $actionsDir ) { + system("logger -p install.info 'Running Install Scripts . . .'") if ( $debug ); + $count = 0; + foreach my $action (`/bin/ls \"$actionsDir\"`) { + chomp($action); + system("logger -p install.info 'Begin script: $action'") if ( $debug ); + system($actionsDir . $action, $ARGV[0], $ARGV[1], $ARGV[2], $ARGV[3],$upgrade) == 0 or die; + system("logger -p install.info 'End script: $action'") if ( $debug ); + $count++; + } + system("logger -p install.info '$count Install Scripts run.'") if ( $debug ); +} + +exit(0); + diff --git a/contrib/macOS/pkg-scripts/preinstall_actions/0uninstall b/contrib/macOS/pkg-scripts/preinstall_actions/0uninstall new file mode 100755 index 000000000000..4019ab91e0e9 --- /dev/null +++ b/contrib/macOS/pkg-scripts/preinstall_actions/0uninstall @@ -0,0 +1,236 @@ +#!/bin/bash +set +e +date '+%s' >> /tmp/o3x.log +echo $0 >> /tmp/o3x.log +pkgutil --pkgs | egrep 'net.lundman.openzfs|org.openzfsonosx.zfs' |\ +while read apkg +do + for i in {1..2} + do + pkgutil --lsbom "$apkg" | sed -E 's/^\.//' |\ + while read e + do + if [ -f "$e" ] ; then + rm "$e" + elif [ -d "$e" ] ; then + rmdir -p "$e" + elif [ -L "$e" ] ; then + rm "$e" + fi + done + done + pkgutil --forget "$apkg" +done + +rm -rfv /Library/OpenZFSonOSX +rm -rfv /Library/Extensions/spl.kext +rm -rfv /System/Library/Extensions/spl.kext +rm -rfv /usr/src/spl-* +rm -rfv /Library/Extensions/zfs.kext +rm -rfv /System/Library/Extensions/zfs.kext +rm -rfv /usr/src/zfs-* +rm -rfv /usr/lib/modules-load.d +rm -rfv /usr/lib/systemd +rm -rfv /usr/local/etc/init.d +rm -rfv /etc/init.d +rm -rfv /usr/local/etc/zfs +rm -rfv /usr/local/include/libspl +rm -rfv /usr/local/include/libzfs +rm -rfv /usr/include/libspl +rm -rfv /usr/include/libzfs +rm -rfv /usr/local/lib/dracut +rm -rfv /usr/lib/dracut +rm -rfv /usr/local/lib/udev +rm -rfv /usr/lib/udev +rm -rfv /usr/local/share/zfs +rm -rfv /usr/share/zfs +rm -rfv /usr/local/libexec/zfs +rm -rfv /usr/libexec/zfs + +rm -rfv /usr/local/zfs + +rm -fv /usr/local/share/man/man1/zhack.1 +rm -fv /usr/local/share/man/man1/zpios.1 +rm -fv /usr/local/share/man/man1/ztest.1 +rm -fv /usr/local/share/man/man5/zfs-events.5 +rm -fv /usr/local/share/man/man5/vdev_id.conf.5 +rm -fv /usr/local/share/man/man5/zfs-module-parameters.5 +rm -fv /usr/local/share/man/man5/zpool-features.5 +rm -fv /usr/local/share/man/man8/fsck.zfs.8 +rm -fv /usr/local/share/man/man8/mount.zfs.8 +rm -fv /usr/local/share/man/man8/vdev_id.8 +rm -fv /usr/local/share/man/man8/zdb.8 +rm -fv /usr/local/share/man/man8/zed.8 +rm -fv /usr/local/share/man/man8/zfs.8 +rm -fv /usr/local/share/man/man8/zinject.8 +rm -fv /usr/local/share/man/man8/zpool.8 +rm -fv /usr/local/share/man/man8/zstreamdump.8 +rm -fv /usr/share/man/man1/zhack.1 +rm -fv /usr/share/man/man1/zpios.1 +rm -fv /usr/share/man/man1/ztest.1 +rm -fv /usr/share/man/man5/vdev_id.conf.5 +rm -fv /usr/share/man/man5/zfs-events.5 +rm -fv /usr/share/man/man5/zfs-module-parameters.5 +rm -fv /usr/share/man/man5/zpool-features.5 +rm -fv /usr/share/man/man8/fsck.zfs.8 +rm -fv /usr/share/man/man8/mount.zfs.8 +rm -fv /usr/share/man/man8/vdev_id.8 +rm -fv /usr/share/man/man8/zdb.8 +rm -fv /usr/share/man/man8/zed.8 +rm -fv /usr/share/man/man8/zfs.8 +rm -fv /usr/share/man/man8/zinject.8 +rm -fv /usr/share/man/man8/zpool.8 +rm -fv /usr/share/man/man8/zstreamdump.8 + +rm -fv /usr/local/lib/libdiskmgt.1.dylib +rm -fv /usr/local/lib/libdiskmgt.a +rm -fv /usr/local/lib/libdiskmgt.dylib +rm -fv /usr/local/lib/libdiskmgt.la +rm -fv /usr/local/lib/libnvpair.1.dylib +rm -fv /usr/local/lib/libnvpair.a +rm -fv /usr/local/lib/libnvpair.dylib +rm -fv /usr/local/lib/libnvpair.la +rm -fv /usr/local/lib/libuutil.1.dylib +rm -fv /usr/local/lib/libuutil.a +rm -fv /usr/local/lib/libuutil.dylib +rm -fv /usr/local/lib/libuutil.la +rm -fv /usr/local/lib/libzfs.2.dylib +rm -fv /usr/local/lib/libzfs.a +rm -fv /usr/local/lib/libzfs.dylib +rm -fv /usr/local/lib/libzfs.la +rm -fv /usr/local/lib/libzfs_core.1.dylib +rm -fv /usr/local/lib/libzfs_core.a +rm -fv /usr/local/lib/libzfs_core.dylib +rm -fv /usr/local/lib/libzfs_core.la +rm -fv /usr/local/lib/libzpool.1.dylib +rm -fv /usr/local/lib/libzpool.a +rm -fv /usr/local/lib/libzpool.dylib +rm -fv /usr/local/lib/libzpool.la +rm -fv /usr/lib/libdiskmgt.1.dylib +rm -fv /usr/lib/libdiskmgt.a +rm -fv /usr/lib/libdiskmgt.dylib +rm -fv /usr/lib/libdiskmgt.la +rm -fv /usr/lib/libnvpair.1.dylib +rm -fv /usr/lib/libnvpair.a +rm -fv /usr/lib/libnvpair.dylib +rm -fv /usr/lib/libnvpair.la +rm -fv /usr/lib/libuutil.1.dylib +rm -fv /usr/lib/libuutil.a +rm -fv /usr/lib/libuutil.dylib +rm -fv /usr/lib/libuutil.la +rm -fv /usr/lib/libzfs.2.dylib +rm -fv /usr/lib/libzfs.a +rm -fv /usr/lib/libzfs.dylib +rm -fv /usr/lib/libzfs.la +rm -fv /usr/lib/libzfs_core.1.dylib +rm -fv /usr/lib/libzfs_core.a +rm -fv /usr/lib/libzfs_core.dylib +rm -fv /usr/lib/libzfs_core.la +rm -fv /usr/lib/libzpool.1.dylib +rm -fv /usr/lib/libzpool.a +rm -fv /usr/lib/libzpool.dylib +rm -fv /usr/lib/libzpool.la + +rm -fv /usr/local/bin/arcstat.pl +rm -fv /usr/local/bin/InvariantDisks +rm -fv /usr/local/bin/zconfigd +rm -fv /usr/local/bin/zdb +rm -fv /usr/local/bin/zdb_static +rm -fv /usr/local/bin/zed +rm -fv /usr/local/bin/zfs +rm -fv /usr/local/bin/zhack +rm -fv /usr/local/bin/zinject +rm -fv /usr/local/bin/zpios +rm -fv /usr/local/bin/zpool +rm -fv /usr/local/bin/zstreamdump +rm -fv /usr/local/bin/zsysctl +rm -fv /usr/local/bin/ztest +rm -fv /usr/local/bin/ztest_static +rm -fv /usr/local/sbin/InvariantDisks +rm -fv /usr/local/sbin/zdb +rm -fv /usr/local/sbin/zdb_static +rm -fv /usr/local/sbin/zed +rm -fv /usr/local/sbin/zfs +rm -fv /usr/local/sbin/zhack +rm -fv /usr/local/sbin/zinject +rm -fv /usr/local/sbin/zpios +rm -fv /usr/local/sbin/zpool +rm -fv /usr/local/sbin/zstreamdump +rm -fv /usr/local/sbin/ztest +rm -fv /usr/local/sbin/ztest_static +rm -fv /usr/bin/arcstat.pl +rm -fv /usr/sbin/InvariantDisks +rm -fv /usr/sbin/zconfigd +rm -fv /usr/sbin/zdb +rm -fv /usr/sbin/zdb_static +rm -fv /usr/sbin/zed +rm -fv /usr/sbin/zfs +rm -fv /usr/sbin/zhack +rm -fv /usr/sbin/zinject +rm -fv /usr/sbin/zpios +rm -fv /usr/sbin/zpool +rm -fv /usr/sbin/zstreamdump +rm -fv /usr/sbin/zsysctl +rm -fv /usr/sbin/ztest +rm -fv /usr/sbin/ztest_static + +rm -fv /sbin/mount.zfs +rm -fv /sbin/mount_zfs +rm -fv /sbin/umount_zfs +rm -fv /usr/local/bin/mount.zfs +rm -fv /usr/local/bin/mount_zfs +rm -fv /usr/local/bin/umount_zfs +rm -fv /usr/local/sbin/mount.zfs +rm -fv /usr/local/sbin/mount_zfs +rm -fv /usr/local/sbin/umount_zfs + +rm -fv /etc/zfs/zpool.cache +rm -fv /etc/zfs/zpool.cache.tmp + +rm -fv /etc/zfs/vdev_id.conf.alias.example +rm -fv /etc/zfs/vdev_id.conf.multipath.example +rm -fv /etc/zfs/vdev_id.conf.sas_direct.example +rm -fv /etc/zfs/vdev_id.conf.sas_switch.example +rm -fv /etc/zfs/zsysctl.conf.example + +rm -fv /etc/zfs/zed.d/checksum-email.sh +rm -fv /etc/zfs/zed.d/data-email.sh +rm -fv /etc/zfs/zed.d/generic-email.sh +rm -fv /etc/zfs/zed.d/io-email.sh +rm -fv /etc/zfs/zed.d/resilver.finish-email.sh +rm -fv /etc/zfs/zed.d/scrub.finish-email.sh + +rm -fv /etc/zfs/zed.d/all-debug.sh +rm -fv /etc/zfs/zed.d/all-syslog.sh +rm -fv /etc/zfs/zed.d/checksum-notify.sh +rm -fv /etc/zfs/zed.d/checksum-spare.sh +rm -fv /etc/zfs/zed.d/config.remove.sh +rm -fv /etc/zfs/zed.d/config.sync.sh +rm -fv /etc/zfs/zed.d/config.rename.sh +rm -fv /etc/zfs/zed.d/data-notify.sh +rm -fv /etc/zfs/zed.d/generic-notify.sh +rm -fv /etc/zfs/zed.d/io-notify.sh +rm -fv /etc/zfs/zed.d/io-spare.sh +rm -fv /etc/zfs/zed.d/resilver.finish-notify.sh +rm -fv /etc/zfs/zed.d/scrub.finish-notify.sh +rm -fv /etc/zfs/zed.d/snapshot.mount.sh +rm -fv /etc/zfs/zed.d/zed.rc +rm -fv /etc/zfs/zed.d/zpool.destroy.sh +rm -fv /etc/zfs/zed.d/zpool.import.sh +rm -fv /etc/zfs/zed.d/zvol.create.sh +rm -fv /etc/zfs/zed.d/zvol.remove.sh + +rm -fv /Library/LaunchDaemons/org.openzfsonosx.InvariantDisks.plist +rm -fv /Library/LaunchDaemons/org.openzfsonosx.zconfigd.plist +rm -fv /Library/LaunchDaemons/org.openzfsonosx.zed.plist +rm -fv /Library/LaunchDaemons/org.openzfsonosx.zed.service.plist +rm -fv /Library/LaunchDaemons/org.openzfsonosx.zpool-autoimport.plist +rm -fv /Library/LaunchDaemons/org.openzfsonosx.zpool-import-all.plist + +rm -rfv /Library/Filesystems/zfs.fs +rm -rfv /System/Library/Filesystems/zfs.fs + +[ -d /etc/zfs/zed.d ] && [ $(ls -A /etc/zfs/zed.d | wc -l) -eq 0 ] && rmdir /etc/zfs/zed.d +[ -d /etc/zfs ] && [ $(ls -A /etc/zfs | wc -l) -eq 0 ] && rmdir /etc/zfs + +exit 0 diff --git a/contrib/macOS/pkg-scripts/preinstall_actions/lookforzpool b/contrib/macOS/pkg-scripts/preinstall_actions/lookforzpool new file mode 100755 index 000000000000..3b2ed7f01c58 --- /dev/null +++ b/contrib/macOS/pkg-scripts/preinstall_actions/lookforzpool @@ -0,0 +1,34 @@ +#!/bin/bash +date '+%s' >> /tmp/o3x.log +echo $0 >> /tmp/o3x.log +set -e + +TMPF=`mktemp /private/tmp/zfsinstaller-lookforzpool.XXXXXX` + +echo "Checking for /dev/zfs" +if [ -c /dev/zfs ] ; then +echo "Found /dev/zfs" +for zpoolcommand in /usr/local/zfs/bin/zpool /Library/OpenZFSonOSX/ZFSUserland/usr/local/sbin/zpool /Library/OpenZFSonOSX/ZFSCommandLineTools/usr/sbin/zpool /usr/sbin/zpool /usr/local/sbin/zpool /sbin /usr/local/bin/zpool /usr/bin/zpool /bin/zpool zpool ; do + printf "Checking for zpool command at %s\n" "$zpoolcommand" + if [ -f "$zpoolcommand" -o -L "$zpoolcommand" ] ; then + echo "Found zpool command $zpoolcommand" + set +e + echo "Checking output of zpool status" + 2>"$TMPF" 1>"$TMPF" sudo "$zpoolcommand" status + err=$? + set -e + count=$(cat "$TMPF" | wc -l | tr -d ' ') + if [ "$count" -gt 1 ] ; then + cat "$TMPF" + if [ $err -eq 0 ] ; then + printf "First export all pools\n" + fi + exit 1 + fi + if [ "$err" -ne 0 -a "$err" -ne 1 ] ; then + echo "$err" + exit "$err" + fi + fi +done +fi diff --git a/contrib/macOS/pkg-scripts/preinstall_actions/unloadzfs b/contrib/macOS/pkg-scripts/preinstall_actions/unloadzfs new file mode 100755 index 000000000000..baca13e37d99 --- /dev/null +++ b/contrib/macOS/pkg-scripts/preinstall_actions/unloadzfs @@ -0,0 +1,42 @@ +#!/bin/bash +date '+%s' >> /tmp/o3x.log +echo $0 >> /tmp/o3x.log +set -e + +TMPF=`mktemp /private/tmp/zfsinstaller-unloadzfs.XXXXXX` + +if [ $(/usr/sbin/kextstat -b net.lundman.zfs | wc -l) -gt 1 ] ; then + echo "Unloading zfs.kext" + /sbin/kextunload -b net.lundman.zfs + if [ $(/usr/sbin/kextstat -b net.lundman.zfs | wc -l) -gt 1 ] ; then + echo "zfs.kext could not be unloaded" + exit 2 + fi +fi + +if [ $(/usr/sbin/kextstat -b org.openzfsonosx.zfs | wc -l) -gt 1 ] ; then + echo "Unloading zfs.kext" + /sbin/kextunload -b org.openzfsonosx.zfs + if [ $(/usr/sbin/kextstat -b org.openzfsonosx.zfs | wc -l) -gt 1 ] ; then + echo "zfs.kext could not be unloaded" + exit 2 + fi +fi + +if [ -c /dev/zfs ] ; then + echo "/dev/zfs still exists. zfs kernel extension likely still loaded. Exiting." + exit 3 +fi + +if [ $(/usr/sbin/kextstat -b net.lundman.spl | wc -l) -gt 1 ] ; then + /sbin/kextunload -b net.lundman.spl + if [ $(/usr/sbin/kextstat -b net.lundman.spl | wc -l) -gt 1 ] ; then + echo "spl.kext could not be unloaded" + exit 4 + fi +fi + +set +e +killall zed + +exit 0 diff --git a/contrib/macOS/product-scripts/poolcheck.sh b/contrib/macOS/product-scripts/poolcheck.sh new file mode 100755 index 000000000000..c7a989fbe87d --- /dev/null +++ b/contrib/macOS/product-scripts/poolcheck.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +#exit code 1 means no zfs file systems mounted + +echo "Mounted ZFS file system(s) check" +myvar="$(2>/dev/null /usr/bin/lsvfs zfs | /usr/bin/tail -1 | /usr/bin/awk '{print $2}')" +[ ! -z "${myvar##*[!0-9]*}" ] || exit 1 +[ $myvar -ne 0 ] && exit 0 +sysctl -n kstat.zfs.darwin.ldi.handle_count || exit 1 +if [ x"$(sysctl -n kstat.zfs.darwin.ldi.handle_count)" != x"0" ]; then exit 0; fi +exit 1 diff --git a/contrib/macOS/product-scripts/zevocheck.sh b/contrib/macOS/product-scripts/zevocheck.sh new file mode 100755 index 000000000000..be541693774d --- /dev/null +++ b/contrib/macOS/product-scripts/zevocheck.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +#exit code 1 means ZEVO is neither installed nor just uninstalled without reboot + +echo "ZEVO files check" +ls /System/Library/Extensions/ZFSDriver.kext/ &>/dev/null && exit 0 +ls /System/Library/Extensions/ZFSFilesystem.kext/ &>/dev/null && exit 0 +ls /Library/LaunchDaemons/com.getgreenbytes* &>/dev/null && exit 0 + +echo "ZEVO launchctl check" +/bin/launchctl list | grep greenbytes &>/dev/null +[ $? -eq 0 ] && exit 0 + +echo "ZEVO kextstat check" +/usr/sbin/kextstat | grep greenbytes &>/dev/null +[ $? -eq 0 ] && exit 0 + +exit 1 diff --git a/contrib/macOS/resources/English.lproj/Conclusion.rtf b/contrib/macOS/resources/English.lproj/Conclusion.rtf new file mode 100644 index 000000000000..97d834fd7120 --- /dev/null +++ b/contrib/macOS/resources/English.lproj/Conclusion.rtf @@ -0,0 +1,70 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400 +{\fonttbl\f0\fswiss\fcharset0 ArialMT;} +{\colortbl;\red255\green255\blue255;} +{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid1\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1} +{\list\listtemplateid2\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid101\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid2} +{\list\listtemplateid3\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid201\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid3}} +{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}{\listoverride\listid3\listoverridecount0\ls3}} +\margl1440\margr1440\vieww18180\viewh10020\viewkind0 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural + +\f0\fs24 \cf0 \CocoaLigature0 \ +J\'f6rgen Lundman is the principal developer of the OpenZFS on OS X port. He works full time as a Unix Engineer at GMO Internet, Tokyo, Japan. This port would not have been possible without the help of many others who have contributed their time, effort, and insight.\ +\ +\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural +\ls1\ilvl0\cf0 \CocoaLigature1 {\listtext \'95 }\CocoaLigature0 J\'f6rgen Lundman \ +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural +\cf0 \ +First and foremost the hard working ZFS developers at Sun/Oracle. They are responsible for the bulk of the code in this project and without their efforts there never would have been a ZFS filesystem.\ +\ +\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural +\ls2\ilvl0\cf0 \CocoaLigature1 {\listtext \'95 }\CocoaLigature0 The ZFS Development Team at Sun/Oracle\ +\pard\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural +\cf0 \ +The OpenZFS project community brings together developers from the illumos, FreeBSD, Linux, and OS X platforms, and a wide range of companies that build products on top of OpenZFS.\ +\ +\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural +\cf0 \CocoaLigature1 \'95 \CocoaLigature0 OpenZFS http://open-zfs.org/\ +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural +\cf0 \ +The OpenZFS on OS X port was made easier by the great work of ZFS on Linux, and more information on that project can be found below.\ +\ +\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural +\ls3\ilvl0\cf0 \CocoaLigature1 {\listtext \'95 }\CocoaLigature0 ZFS on Linux by Lawrence Livermore National Laboratory\ +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural +\cf0 \ +Large parts come from the original work in MacZFS, and the excellent work of those early developers.\ +\ +\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural +\cf0 \CocoaLigature1 \'95 \CocoaLigature0 MacZFS http://maczfs.org/\ +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural +\cf0 \ +VFS layer and glue comes from the FreeBSD project, who has done a excellent job connecting the BSD and SYSV kernels together.\ +\ +\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural +\cf0 \CocoaLigature1 \'95 \CocoaLigature0 FreeBSD http://www.freebsd.org/\ +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural +\cf0 \ +Additional assistance has been given by people online, reluctant and sometimes\ +willingly,\ +\ +\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural +\cf0 \CocoaLigature1 \'95 \CocoaLigature0 Richard Yao\ +\CocoaLigature1 \'95 \CocoaLigature0 Bj\'f6rn Kahl\ +\CocoaLigature1 \'95 \CocoaLigature0 Will Andrews\ +\CocoaLigature1 \'95 \CocoaLigature0 Evan Lojewski\ +\CocoaLigature1 \'95 \CocoaLigature0 Daniel Bethe\ +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural +\cf0 \ +The flexible and welcoming attitude toward Open Source by GMO Internet, Tokyo, Japan has been an integral part of this project.\ +\ +\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural +\cf0 \CocoaLigature1 \'95 \CocoaLigature0 GMO Internet, Inc. http://www.gmo.jp/\ +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural +\cf0 \ +The book [OS X and iOS Kernel Programming] has been useful when working out the internals of the newer IOKit section of the Apple OS X kernel. Available on Amazon.\ +\ +\pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural +\cf0 \CocoaLigature1 \'95 \CocoaLigature0 Ole Henry Halvorsen (Author) http://osxkernel.com/\ +\CocoaLigature1 \'95 \CocoaLigature0 Douglas Clarke (Author)\ +} \ No newline at end of file diff --git a/contrib/macOS/resources/English.lproj/License.txt b/contrib/macOS/resources/English.lproj/License.txt new file mode 100644 index 000000000000..b45fa15769e5 --- /dev/null +++ b/contrib/macOS/resources/English.lproj/License.txt @@ -0,0 +1,828 @@ + +OpenZFS on OS X consists of software that is covered under the +Common Development and Distribution License 1.0 and the Apple +Public Source License 2.0. + +Each file at https://github.com/openzfsonosx records which license it is +released under. + +Much of OpenZFS on OS X is a port of ZFS on Linux. + +-------------------------------------------------------------------- +ZFS on Linux COPYRIGHT + +The majority of the code in the ZFS on Linux port comes from +OpenSolaris which has been released under the terms of the CDDL +open source license. This includes the core ZFS code, libavl, +libnvpair, libefi, libunicode, and libutil. The original OpenSolaris +source can be downloaded from: + +http://dlc.sun.com/osol/on/downloads/b121/on-src.tar.bz2 + +Files which do not originate from OpenSolaris are noted in the file +header and attributed properly. These exceptions include, but are not +limited to, the vdev_disk.c and zvol.c implementation which are +licensed under the CDDL. + +The zpios test code is originally derived from the Lustre pios test +code which is licensed under the GPLv2. As such the heavily modified +zpios kernel test code also remains licensed under the GPLv2. + +The latest stable and development versions of this port can be +downloaded from the official ZFS on Linux site located at: + +http://zfsonlinux.org/ + +This ZFS on Linux port was produced at the Lawrence Livermore National +Laboratory (LLNL) under Contract No. DE-AC52-07NA27344 (Contract 44) +between the U.S. Department of Energy (DOE) and Lawrence Livermore +National Security, LLC (LLNS) for the operation of LLNL. It has been +approved for release under LLNL-CODE-403049. + +Unless otherwise noted, all files in this distribution are released +under the Common Development and Distribution License (CDDL). +Exceptions are noted within the associated source files. + +-------------------------------------------------------------------- +ZFS on Linux Disclaimer + +This work was produced at the Lawrence Livermore National Laboratory +(LLNL) under Contract No. DE-AC52-07NA27344 (Contract 44) between +the U.S. Department of Energy (DOE) and Lawrence Livermore National +Security, LLC (LLNS) for the operation of LLNL. + +This work was prepared as an account of work sponsored by an agency of +the United States Government. Neither the United States Government nor +Lawrence Livermore National Security, LLC nor any of their employees, +makes any warranty, express or implied, or assumes any liability or +responsibility for the accuracy, completeness, or usefulness of any +information, apparatus, product, or process disclosed, or represents +that its use would not infringe privately-owned rights. + +Reference herein to any specific commercial products, process, or +services by trade name, trademark, manufacturer or otherwise does +not necessarily constitute or imply its endorsement, recommendation, +or favoring by the United States Government or Lawrence Livermore +National Security, LLC. The views and opinions of authors expressed +herein do not necessarily state or reflect those of the United States +Government or Lawrence Livermore National Security, LLC, and shall +not be used for advertising or product endorsement purposes. + +The precise terms and conditions for copying, distribution, and +modification are specified in the file OPENSOLARIS.LICENSE.txt + +-------------------------------------------------------------------- +Contents of OPENSOLARIS.LICENSE: + +Unless otherwise noted, all files in this distribution are released +under the Common Development and Distribution License (CDDL). +Exceptions are noted within the associated source files. + +-------------------------------------------------------------------- + +COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0 + +1. Definitions. + + 1.1. "Contributor" means each individual or entity that creates + or contributes to the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Software, prior Modifications used by a Contributor (if any), + and the Modifications made by that particular Contributor. + + 1.3. "Covered Software" means (a) the Original Software, or (b) + Modifications, or (c) the combination of files containing + Original Software with files containing Modifications, in + each case including portions thereof. + + 1.4. "Executable" means the Covered Software in any form other + than Source Code. + + 1.5. "Initial Developer" means the individual or entity that first + makes Original Software available under this License. + + 1.6. "Larger Work" means a work which combines Covered Software or + portions thereof with code not governed by the terms of this + License. + + 1.7. "License" means this document. + + 1.8. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed + herein. + + 1.9. "Modifications" means the Source Code and Executable form of + any of the following: + + A. Any file that results from an addition to, deletion from or + modification of the contents of a file containing Original + Software or previous Modifications; + + B. Any new file that contains any part of the Original + Software or previous Modifications; or + + C. Any new file that is contributed or otherwise made + available under the terms of this License. + + 1.10. "Original Software" means the Source Code and Executable + form of computer software code that is originally released + under this License. + + 1.11. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, + process, and apparatus claims, in any patent Licensable by + grantor. + + 1.12. "Source Code" means (a) the common form of computer software + code in which modifications are made and (b) associated + documentation included in or with such code. + + 1.13. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms + of, this License. For legal entities, "You" includes any + entity which controls, is controlled by, or is under common + control with You. For purposes of this definition, + "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty + percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants. + + 2.1. The Initial Developer Grant. + + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, the Initial + Developer hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer, to use, + reproduce, modify, display, perform, sublicense and + distribute the Original Software (or portions thereof), + with or without Modifications, and/or as part of a Larger + Work; and + + (b) under Patent Claims infringed by the making, using or + selling of Original Software, to make, have made, use, + practice, sell, and offer for sale, and/or otherwise + dispose of the Original Software (or portions thereof). + + (c) The licenses granted in Sections 2.1(a) and (b) are + effective on the date Initial Developer first distributes + or otherwise makes the Original Software available to a + third party under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: (1) for code that You delete from the Original + Software, or (2) for infringements caused by: (i) the + modification of the Original Software, or (ii) the + combination of the Original Software with other software + or devices. + + 2.2. Contributor Grant. + + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, each + Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor to use, reproduce, + modify, display, perform, sublicense and distribute the + Modifications created by such Contributor (or portions + thereof), either on an unmodified basis, with other + Modifications, as Covered Software and/or as part of a + Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either + alone and/or in combination with its Contributor Version + (or portions of such combination), to make, use, sell, + offer for sale, have made, and/or otherwise dispose of: + (1) Modifications made by that Contributor (or portions + thereof); and (2) the combination of Modifications made by + that Contributor with its Contributor Version (or portions + of such combination). + + (c) The licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first distributes or + otherwise makes the Modifications available to a third + party. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: (1) for any code that Contributor has deleted + from the Contributor Version; (2) for infringements caused + by: (i) third party modifications of Contributor Version, + or (ii) the combination of Modifications made by that + Contributor with other software (except as part of the + Contributor Version) or other devices; or (3) under Patent + Claims infringed by Covered Software in the absence of + Modifications made by that Contributor. + +3. Distribution Obligations. + + 3.1. Availability of Source Code. + + Any Covered Software that You distribute or otherwise make + available in Executable form must also be made available in Source + Code form and that Source Code form must be distributed only under + the terms of this License. You must include a copy of this + License with every copy of the Source Code form of the Covered + Software You distribute or otherwise make available. You must + inform recipients of any such Covered Software in Executable form + as to how they can obtain such Covered Software in Source Code + form in a reasonable manner on or through a medium customarily + used for software exchange. + + 3.2. Modifications. + + The Modifications that You create or to which You contribute are + governed by the terms of this License. You represent that You + believe Your Modifications are Your original creation(s) and/or + You have sufficient rights to grant the rights conveyed by this + License. + + 3.3. Required Notices. + + You must include a notice in each of Your Modifications that + identifies You as the Contributor of the Modification. You may + not remove or alter any copyright, patent or trademark notices + contained within the Covered Software, or any notices of licensing + or any descriptive text giving attribution to any Contributor or + the Initial Developer. + + 3.4. Application of Additional Terms. + + You may not offer or impose any terms on any Covered Software in + Source Code form that alters or restricts the applicable version + of this License or the recipients' rights hereunder. You may + choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of + Covered Software. However, you may do so only on Your own behalf, + and not on behalf of the Initial Developer or any Contributor. + You must make it absolutely clear that any such warranty, support, + indemnity or liability obligation is offered by You alone, and You + hereby agree to indemnify the Initial Developer and every + Contributor for any liability incurred by the Initial Developer or + such Contributor as a result of warranty, support, indemnity or + liability terms You offer. + + 3.5. Distribution of Executable Versions. + + You may distribute the Executable form of the Covered Software + under the terms of this License or under the terms of a license of + Your choice, which may contain terms different from this License, + provided that You are in compliance with the terms of this License + and that the license for the Executable form does not attempt to + limit or alter the recipient's rights in the Source Code form from + the rights set forth in this License. If You distribute the + Covered Software in Executable form under a different license, You + must make it absolutely clear that any terms which differ from + this License are offered by You alone, not by the Initial + Developer or Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred + by the Initial Developer or such Contributor as a result of any + such terms You offer. + + 3.6. Larger Works. + + You may create a Larger Work by combining Covered Software with + other code not governed by the terms of this License and + distribute the Larger Work as a single product. In such a case, + You must make sure the requirements of this License are fulfilled + for the Covered Software. + +4. Versions of the License. + + 4.1. New Versions. + + Sun Microsystems, Inc. is the initial license steward and may + publish revised and/or new versions of this License from time to + time. Each version will be given a distinguishing version number. + Except as provided in Section 4.3, no one other than the license + steward has the right to modify this License. + + 4.2. Effect of New Versions. + + You may always continue to use, distribute or otherwise make the + Covered Software available under the terms of the version of the + License under which You originally received the Covered Software. + If the Initial Developer includes a notice in the Original + Software prohibiting it from being distributed or otherwise made + available under any subsequent version of the License, You must + distribute and make the Covered Software available under the terms + of the version of the License under which You originally received + the Covered Software. Otherwise, You may also choose to use, + distribute or otherwise make the Covered Software available under + the terms of any subsequent version of the License published by + the license steward. + + 4.3. Modified Versions. + + When You are an Initial Developer and You want to create a new + license for Your Original Software, You may create and use a + modified version of this License if You: (a) rename the license + and remove any references to the name of the license steward + (except to note that the license differs from this License); and + (b) otherwise make it clear that the license contains terms which + differ from this License. + +5. DISCLAIMER OF WARRANTY. + + COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" + BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, + INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED + SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR + PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND + PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY + COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE + INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY + NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF + WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS + DISCLAIMER. + +6. TERMINATION. + + 6.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to + cure such breach within 30 days of becoming aware of the breach. + Provisions which, by their nature, must remain in effect beyond + the termination of this License shall survive. + + 6.2. If You assert a patent infringement claim (excluding + declaratory judgment actions) against Initial Developer or a + Contributor (the Initial Developer or Contributor against whom You + assert such claim is referred to as "Participant") alleging that + the Participant Software (meaning the Contributor Version where + the Participant is a Contributor or the Original Software where + the Participant is the Initial Developer) directly or indirectly + infringes any patent, then any and all rights granted directly or + indirectly to You by such Participant, the Initial Developer (if + the Initial Developer is not the Participant) and all Contributors + under Sections 2.1 and/or 2.2 of this License shall, upon 60 days + notice from Participant terminate prospectively and automatically + at the expiration of such 60 day notice period, unless if within + such 60 day period You withdraw Your claim with respect to the + Participant Software against such Participant either unilaterally + or pursuant to a written agreement with Participant. + + 6.3. In the event of termination under Sections 6.1 or 6.2 above, + all end user licenses that have been validly granted by You or any + distributor hereunder prior to termination (excluding licenses + granted to You by any distributor) shall survive termination. + +7. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE + INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF + COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE + LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT + LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK + STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL + INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT + APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO + NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR + CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT + APPLY TO YOU. + +8. U.S. GOVERNMENT END USERS. + + The Covered Software is a "commercial item," as that term is + defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial + computer software" (as that term is defined at 48 + C.F.R. 252.227-7014(a)(1)) and "commercial computer software + documentation" as such terms are used in 48 C.F.R. 12.212 + (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 + C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all + U.S. Government End Users acquire Covered Software with only those + rights set forth herein. This U.S. Government Rights clause is in + lieu of, and supersedes, any other FAR, DFAR, or other clause or + provision that addresses Government rights in computer software + under this License. + +9. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed + by the law of the jurisdiction specified in a notice contained + within the Original Software (except to the extent applicable law, + if any, provides otherwise), excluding such jurisdiction's + conflict-of-law provisions. Any litigation relating to this + License shall be subject to the jurisdiction of the courts located + in the jurisdiction and venue specified in a notice contained + within the Original Software, with the losing party responsible + for costs, including, without limitation, court costs and + reasonable attorneys' fees and expenses. The application of the + United Nations Convention on Contracts for the International Sale + of Goods is expressly excluded. Any law or regulation which + provides that the language of a contract shall be construed + against the drafter shall not apply to this License. You agree + that You alone are responsible for compliance with the United + States export administration regulations (and the export control + laws and regulation of any other countries) when You use, + distribute or otherwise make available any Covered Software. + +10. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or + indirectly, out of its utilization of rights under this License + and You agree to work with Initial Developer and Contributors to + distribute such responsibility on an equitable basis. Nothing + herein is intended or shall be deemed to constitute any admission + of liability. + +-------------------------------------------------------------------- + +NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND +DISTRIBUTION LICENSE (CDDL) + +For Covered Software in this distribution, this License shall +be governed by the laws of the State of California (excluding +conflict-of-law provisions). + +Any litigation relating to this License shall be subject to the +jurisdiction of the Federal Courts of the Northern District of +California and the state courts of the State of California, with +venue lying in Santa Clara County, California. + +-------------------------------------------------------------------- +APPLE PUBLIC SOURCE LICENSE +Version 2.0 - August 6, 2003 + +Please read this License carefully before downloading this software. +By downloading or using this software, you are agreeing to be bound by +the terms of this License. If you do not or cannot agree to the terms +of this License, please do not download or use the software. + +1. General; Definitions. This License applies to any program or other +work which Apple Computer, Inc. ("Apple") makes publicly available and +which contains a notice placed by Apple identifying such program or +work as "Original Code" and stating that it is subject to the terms of +this Apple Public Source License version 2.0 ("License"). As used in +this License: + +1.1 "Applicable Patent Rights" mean: (a) in the case where Apple is +the grantor of rights, (i) claims of patents that are now or hereafter +acquired, owned by or assigned to Apple and (ii) that cover subject +matter contained in the Original Code, but only to the extent +necessary to use, reproduce and/or distribute the Original Code +without infringement; and (b) in the case where You are the grantor of +rights, (i) claims of patents that are now or hereafter acquired, +owned by or assigned to You and (ii) that cover subject matter in Your +Modifications, taken alone or in combination with Original Code. + +1.2 "Contributor" means any person or entity that creates or +contributes to the creation of Modifications. + +1.3 "Covered Code" means the Original Code, Modifications, the +combination of Original Code and any Modifications, and/or any +respective portions thereof. + +1.4 "Externally Deploy" means: (a) to sublicense, distribute or +otherwise make Covered Code available, directly or indirectly, to +anyone other than You; and/or (b) to use Covered Code, alone or as +part of a Larger Work, in any way to provide a service, including but +not limited to delivery of content, through electronic communication +with a client other than You. + +1.5 "Larger Work" means a work which combines Covered Code or portions +thereof with code not governed by the terms of this License. + +1.6 "Modifications" mean any addition to, deletion from, and/or change +to, the substance and/or structure of the Original Code, any previous +Modifications, the combination of Original Code and any previous +Modifications, and/or any respective portions thereof. When code is +released as a series of files, a Modification is: (a) any addition to +or deletion from the contents of a file containing Covered Code; +and/or (b) any new file or other representation of computer program +statements that contains any part of Covered Code. + +1.7 "Original Code" means (a) the Source Code of a program or other +work as originally made available by Apple under this License, +including the Source Code of any updates or upgrades to such programs +or works made available by Apple under this License, and that has been +expressly identified by Apple as such in the header file(s) of such +work; and (b) the object code compiled from such Source Code and +originally made available by Apple under this License. + +1.8 "Source Code" means the human readable form of a program or other +work that is suitable for making modifications to it, including all +modules it contains, plus any associated interface definition files, +scripts used to control compilation and installation of an executable +(object code). + +1.9 "You" or "Your" means an individual or a legal entity exercising +rights under this License. For legal entities, "You" or "Your" +includes any entity which controls, is controlled by, or is under +common control with, You, where "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of fifty percent +(50%) or more of the outstanding shares or beneficial ownership of +such entity. + +2. Permitted Uses; Conditions & Restrictions. Subject to the terms +and conditions of this License, Apple hereby grants You, effective on +the date You accept this License and download the Original Code, a +world-wide, royalty-free, non-exclusive license, to the extent of +Apple's Applicable Patent Rights and copyrights covering the Original +Code, to do the following: + +2.1 Unmodified Code. You may use, reproduce, display, perform, +internally distribute within Your organization, and Externally Deploy +verbatim, unmodified copies of the Original Code, for commercial or +non-commercial purposes, provided that in each instance: + +(a) You must retain and reproduce in all copies of Original Code the +copyright and other proprietary notices and disclaimers of Apple as +they appear in the Original Code, and keep intact all notices in the +Original Code that refer to this License; and + +(b) You must include a copy of this License with every copy of Source +Code of Covered Code and documentation You distribute or Externally +Deploy, and You may not offer or impose any terms on such Source Code +that alter or restrict this License or the recipients' rights +hereunder, except as permitted under Section 6. + +2.2 Modified Code. You may modify Covered Code and use, reproduce, +display, perform, internally distribute within Your organization, and +Externally Deploy Your Modifications and Covered Code, for commercial +or non-commercial purposes, provided that in each instance You also +meet all of these conditions: + +(a) You must satisfy all the conditions of Section 2.1 with respect to +the Source Code of the Covered Code; + +(b) You must duplicate, to the extent it does not already exist, the +notice in Exhibit A in each file of the Source Code of all Your +Modifications, and cause the modified files to carry prominent notices +stating that You changed the files and the date of any change; and + +(c) If You Externally Deploy Your Modifications, You must make +Source Code of all Your Externally Deployed Modifications either +available to those to whom You have Externally Deployed Your +Modifications, or publicly available. Source Code of Your Externally +Deployed Modifications must be released under the terms set forth in +this License, including the license grants set forth in Section 3 +below, for as long as you Externally Deploy the Covered Code or twelve +(12) months from the date of initial External Deployment, whichever is +longer. You should preferably distribute the Source Code of Your +Externally Deployed Modifications electronically (e.g. download from a +web site). + +2.3 Distribution of Executable Versions. In addition, if You +Externally Deploy Covered Code (Original Code and/or Modifications) in +object code, executable form only, You must include a prominent +notice, in the code itself as well as in related documentation, +stating that Source Code of the Covered Code is available under the +terms of this License with information on how and where to obtain such +Source Code. + +2.4 Third Party Rights. You expressly acknowledge and agree that +although Apple and each Contributor grants the licenses to their +respective portions of the Covered Code set forth herein, no +assurances are provided by Apple or any Contributor that the Covered +Code does not infringe the patent or other intellectual property +rights of any other entity. Apple and each Contributor disclaim any +liability to You for claims brought by any other entity based on +infringement of intellectual property rights or otherwise. As a +condition to exercising the rights and licenses granted hereunder, You +hereby assume sole responsibility to secure any other intellectual +property rights needed, if any. For example, if a third party patent +license is required to allow You to distribute the Covered Code, it is +Your responsibility to acquire that license before distributing the +Covered Code. + +3. Your Grants. In consideration of, and as a condition to, the +licenses granted to You under this License, You hereby grant to any +person or entity receiving or distributing Covered Code under this +License a non-exclusive, royalty-free, perpetual, irrevocable license, +under Your Applicable Patent Rights and other intellectual property +rights (other than patent) owned or controlled by You, to use, +reproduce, display, perform, modify, sublicense, distribute and +Externally Deploy Your Modifications of the same scope and extent as +Apple's licenses under Sections 2.1 and 2.2 above. + +4. Larger Works. You may create a Larger Work by combining Covered +Code with other code not governed by the terms of this License and +distribute the Larger Work as a single product. In each such instance, +You must make sure the requirements of this License are fulfilled for +the Covered Code or any portion thereof. + +5. Limitations on Patent License. Except as expressly stated in +Section 2, no other patent rights, express or implied, are granted by +Apple herein. Modifications and/or Larger Works may require additional +patent licenses from Apple which Apple may grant in its sole +discretion. + +6. Additional Terms. You may choose to offer, and to charge a fee for, +warranty, support, indemnity or liability obligations and/or other +rights consistent with the scope of the license granted herein +("Additional Terms") to one or more recipients of Covered Code. +However, You may do so only on Your own behalf and as Your sole +responsibility, and not on behalf of Apple or any Contributor. You +must obtain the recipient's agreement that any such Additional Terms +are offered by You alone, and You hereby agree to indemnify, defend +and hold Apple and every Contributor harmless for any liability +incurred by or claims asserted against Apple or such Contributor by +reason of any such Additional Terms. + +7. Versions of the License. Apple may publish revised and/or new +versions of this License from time to time. Each version will be given +a distinguishing version number. Once Original Code has been published +under a particular version of this License, You may continue to use it +under the terms of that version. You may also choose to use such +Original Code under the terms of any subsequent version of this +License published by Apple. No one other than Apple has the right to +modify the terms applicable to Covered Code created under this +License. + +8. NO WARRANTY OR SUPPORT. The Covered Code may contain in whole or in +part pre-release, untested, or not fully tested works. The Covered +Code may contain errors that could cause failures or loss of data, and +may be incomplete or contain inaccuracies. You expressly acknowledge +and agree that use of the Covered Code, or any portion thereof, is at +Your sole and entire risk. THE COVERED CODE IS PROVIDED "AS IS" AND +WITHOUT WARRANTY, UPGRADES OR SUPPORT OF ANY KIND AND APPLE AND +APPLE'S LICENSOR(S) (COLLECTIVELY REFERRED TO AS "APPLE" FOR THE +PURPOSES OF SECTIONS 8 AND 9) AND ALL CONTRIBUTORS EXPRESSLY DISCLAIM +ALL WARRANTIES AND/OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF +MERCHANTABILITY, OF SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR +PURPOSE, OF ACCURACY, OF QUIET ENJOYMENT, AND NONINFRINGEMENT OF THIRD +PARTY RIGHTS. APPLE AND EACH CONTRIBUTOR DOES NOT WARRANT AGAINST +INTERFERENCE WITH YOUR ENJOYMENT OF THE COVERED CODE, THAT THE +FUNCTIONS CONTAINED IN THE COVERED CODE WILL MEET YOUR REQUIREMENTS, +THAT THE OPERATION OF THE COVERED CODE WILL BE UNINTERRUPTED OR +ERROR-FREE, OR THAT DEFECTS IN THE COVERED CODE WILL BE CORRECTED. NO +ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE, AN APPLE +AUTHORIZED REPRESENTATIVE OR ANY CONTRIBUTOR SHALL CREATE A WARRANTY. +You acknowledge that the Covered Code is not intended for use in the +operation of nuclear facilities, aircraft navigation, communication +systems, or air traffic control machines in which case the failure of +the Covered Code could lead to death, personal injury, or severe +physical or environmental damage. + +9. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO +EVENT SHALL APPLE OR ANY CONTRIBUTOR BE LIABLE FOR ANY INCIDENTAL, +SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING +TO THIS LICENSE OR YOUR USE OR INABILITY TO USE THE COVERED CODE, OR +ANY PORTION THEREOF, WHETHER UNDER A THEORY OF CONTRACT, WARRANTY, +TORT (INCLUDING NEGLIGENCE), PRODUCTS LIABILITY OR OTHERWISE, EVEN IF +APPLE OR SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES AND NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY +REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY OF +INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY +TO YOU. In no event shall Apple's total liability to You for all +damages (other than as may be required by applicable law) under this +License exceed the amount of fifty dollars ($50.00). + +10. Trademarks. This License does not grant any rights to use the +trademarks or trade names "Apple", "Apple Computer", "Mac", "Mac OS", +"QuickTime", "QuickTime Streaming Server" or any other trademarks, +service marks, logos or trade names belonging to Apple (collectively +"Apple Marks") or to any trademark, service mark, logo or trade name +belonging to any Contributor. You agree not to use any Apple Marks in +or as part of the name of products derived from the Original Code or +to endorse or promote products derived from the Original Code other +than as expressly permitted by and in strict compliance at all times +with Apple's third party trademark usage guidelines which are posted +at http://www.apple.com/legal/guidelinesfor3rdparties.html. + +11. Ownership. Subject to the licenses granted under this License, +each Contributor retains all rights, title and interest in and to any +Modifications made by such Contributor. Apple retains all rights, +title and interest in and to the Original Code and any Modifications +made by or on behalf of Apple ("Apple Modifications"), and such Apple +Modifications will not be automatically subject to this License. Apple +may, at its sole discretion, choose to license such Apple +Modifications under this License, or on different terms from those +contained in this License or may choose not to license them at all. + +12. Termination. + +12.1 Termination. This License and the rights granted hereunder will +terminate: + +(a) automatically without notice from Apple if You fail to comply with +any term(s) of this License and fail to cure such breach within 30 +days of becoming aware of such breach; + +(b) immediately in the event of the circumstances described in Section +13.5(b); or + +(c) automatically without notice from Apple if You, at any time during +the term of this License, commence an action for patent infringement +against Apple; provided that Apple did not first commence +an action for patent infringement against You in that instance. + +12.2 Effect of Termination. Upon termination, You agree to immediately +stop any further use, reproduction, modification, sublicensing and +distribution of the Covered Code. All sublicenses to the Covered Code +which have been properly granted prior to termination shall survive +any termination of this License. Provisions which, by their nature, +should remain in effect beyond the termination of this License shall +survive, including but not limited to Sections 3, 5, 8, 9, 10, 11, +12.2 and 13. No party will be liable to any other for compensation, +indemnity or damages of any sort solely as a result of terminating +this License in accordance with its terms, and termination of this +License will be without prejudice to any other right or remedy of +any party. + +13. Miscellaneous. + +13.1 Government End Users. The Covered Code is a "commercial item" as +defined in FAR 2.101. Government software and technical data rights in +the Covered Code include only those rights customarily provided to the +public as defined in this License. This customary commercial license +in technical data and software is provided in accordance with FAR +12.211 (Technical Data) and 12.212 (Computer Software) and, for +Department of Defense purchases, DFAR 252.227-7015 (Technical Data -- +Commercial Items) and 227.7202-3 (Rights in Commercial Computer +Software or Computer Software Documentation). Accordingly, all U.S. +Government End Users acquire Covered Code with only those rights set +forth herein. + +13.2 Relationship of Parties. This License will not be construed as +creating an agency, partnership, joint venture or any other form of +legal association between or among You, Apple or any Contributor, and +You will not represent to the contrary, whether expressly, by +implication, appearance or otherwise. + +13.3 Independent Development. Nothing in this License will impair +Apple's right to acquire, license, develop, have others develop for +it, market and/or distribute technology or products that perform the +same or similar functions as, or otherwise compete with, +Modifications, Larger Works, technology or products that You may +develop, produce, market or distribute. + +13.4 Waiver; Construction. Failure by Apple or any Contributor to +enforce any provision of this License will not be deemed a waiver of +future enforcement of that or any other provision. Any law or +regulation which provides that the language of a contract shall be +construed against the drafter will not apply to this License. + +13.5 Severability. (a) If for any reason a court of competent +jurisdiction finds any provision of this License, or portion thereof, +to be unenforceable, that provision of the License will be enforced to +the maximum extent permissible so as to effect the economic benefits +and intent of the parties, and the remainder of this License will +continue in full force and effect. (b) Notwithstanding the foregoing, +if applicable law prohibits or restricts You from fully and/or +specifically complying with Sections 2 and/or 3 or prevents the +enforceability of either of those Sections, this License will +immediately terminate and You must immediately discontinue any use of +the Covered Code and destroy all copies of it that are in your +possession or control. + +13.6 Dispute Resolution. Any litigation or other dispute resolution +between You and Apple relating to this License shall take place in the +Northern District of California, and You and Apple hereby consent to +the personal jurisdiction of, and venue in, the state and federal +courts within that District with respect to this License. The +application of the United Nations Convention on Contracts for the +International Sale of Goods is expressly excluded. + +13.7 Entire Agreement; Governing Law. This License constitutes the +entire agreement between the parties with respect to the subject +matter hereof. This License shall be governed by the laws of the +United States and the State of California, except that body of +California law concerning conflicts of law. + +Where You are located in the province of Quebec, Canada, the following +clause applies: The parties hereby confirm that they have requested +that this License and all related documents be drafted in English. Les +parties ont exige que le present contrat et tous les documents +connexes soient rediges en anglais. + +EXHIBIT A. + +"Portions Copyright (c) 1999-2003 Apple Computer, Inc. All Rights +Reserved. + +This file contains Original Code and/or Modifications of Original Code +as defined in and that are subject to the Apple Public Source License +Version 2.0 (the 'License'). You may not use this file except in +compliance with the License. Please obtain a copy of the License at +http://www.opensource.apple.com/apsl/ and read it before using this +file. + +The Original Code and all software distributed under the License are +distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +Please see the License for the specific language governing rights and +limitations under the License." diff --git a/contrib/macOS/resources/English.lproj/Localizable.strings b/contrib/macOS/resources/English.lproj/Localizable.strings new file mode 100644 index 0000000000000000000000000000000000000000..9b778679528d01507b9befadd8422271176076c0 GIT binary patch literal 1044 zcmcJOUr)kN48_m0pJL(Jkm#%N0l_~+1QayN3jrO1(T&JZ@x!aXb{QMQ7>O~>?rraF zZ%^B^&ySKWbf|M(s_xnuR#RQ|b)*Vs4eT10op+Fmj?wzuG_kta4WxorSBta0sye}= zs#Ek`l@w^7uSAAO6TX=e4fKcv>fw1sT);`BJN9RV8Y)H-V)nF!{1#))F;=Ogu66;@ z=2+XH3&AvF4K)7|`5Z5+>mJWUV~@z|ntDYePOY*?w|+nMdP8k5W1_X%%h+{|SQqqS z&i=q^z55PN=)KFb+N(pn&38Y?jWatigGKL=N4MZ`Mvd+D` z9#i3LV=RATViD5~cx?Jl%jt(JD6>Clf=7luovK+WdSgQfm#i}5vvD$ZCYoo;-F%8) Du-2v7 literal 0 HcmV?d00001 diff --git a/contrib/macOS/resources/English.lproj/ReadMe.rtf b/contrib/macOS/resources/English.lproj/ReadMe.rtf new file mode 100644 index 000000000000..c0c327ae7aed --- /dev/null +++ b/contrib/macOS/resources/English.lproj/ReadMe.rtf @@ -0,0 +1,54 @@ +{\rtf1\ansi\ansicpg1252\cocoartf2512 +\cocoascreenfonts1\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 LucidaGrande-Bold;\f2\fnil\fcharset0 LucidaGrande; +\f3\fswiss\fcharset0 Helvetica-Bold;} +{\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} +{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid1\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{hyphen\}}{\leveltext\leveltemplateid2\'01\uc0\u8259 ;}{\levelnumbers;}\fi-360\li1440\lin1440 }{\listname ;}\listid1}} +{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}} +\margl1440\margr1440\vieww17820\viewh11820\viewkind0 +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 + +\f0\fs24 \cf0 \ +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\li360\slleading40\pardirnatural\partightenfactor0 + +\f1\b \cf0 WARNING: +\f2\b0 ZFS is not in and of itself a backup. You should have INDEPENDENT backups of all of your data, whether it is on ZFS or not. You only have a backup of a pool if every disk in the pool can simultaneously end up in a swimming pool, and yet you still have your data. +\f0 \ +\ +\pard\tx220\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\li720\fi-720\slleading40\pardirnatural\partightenfactor0 +\ls1\ilvl0\cf0 {\listtext \uc0\u8226 } +\f3\b Forum +\f0\b0 and +\f3\b wiki:\ +\pard\tx940\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\li1440\fi-1440\slleading40\pardirnatural\partightenfactor0 +\ls1\ilvl1 +\f0\b0 \cf0 {\field{\*\fldinst{HYPERLINK "https://openzfsonosx.org"}}{\fldrslt https://openzfsonosx.org}} ({\field{\*\fldinst{HYPERLINK "http://o3x.org"}}{\fldrslt http://o3x.org}})\ +\pard\tx220\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\li720\fi-720\slleading40\pardirnatural\partightenfactor0 +\ls1\ilvl0\cf0 {\listtext \uc0\u8226 }If you want to help with development:\ +\pard\tx940\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\li1440\fi-1440\slleading40\pardirnatural\partightenfactor0 +\ls1\ilvl1\cf0 {\field{\*\fldinst{HYPERLINK "https://github.com/openzfsonosx"}}{\fldrslt https://github.com/openzfsonosx}}\ +\pard\tx220\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\li720\fi-720\slleading40\pardirnatural\partightenfactor0 +\ls1\ilvl0\cf0 {\listtext \uc0\u8226 }Bug reports and enhancement suggestions:\ +\pard\tx940\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\li1440\fi-1440\slleading40\pardirnatural\partightenfactor0 +\ls1\ilvl1\cf0 {\field{\*\fldinst{HYPERLINK "https://github.com/openzfsonosx/openzfs/issues"}}{\fldrslt https://github.com/openzfsonosx/openzfs/issues}}\ +\ +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\li360\slleading40\pardirnatural\partightenfactor0 + +\f1\b \cf0 About OpenZFS on OS X +\f0\b0 \ +\ +OpenZFS on OS X brings OpenZFS to Mac OS X. It is a free software project released under the CDDL and the Apple Public Source License.\ +\ + +\f1\b Backups +\f0\b0 \ +\ +Please note that all software is known to contain bugs. While care has been taken to ensure that under normal operations things don't go wrong, there's no guarantee of that fact. Using kernel extensions introduces a degree of instability into a system that userland processes don't encounter; the software has been known to cause kernel panics in the past. In addition, any file system has the possibility of causing damage to files. While ZFS creates checksums of all blocks (and so can detect failure earlier than in other systems), there is no guarantee that your data will be accessible in the event of problems. You should therefore take full responsibility for backups of data in the event that a restoration is needed. Any single filing system, including ZFS, is not a substitute for backups.\ +\ + +\f1\b Disclaimer +\f0\b0 \ +\ +The Original Code and all software distributed under the License are distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.\ +\ +Please see the License for the specific language governing rights and limitations under the License.} \ No newline at end of file diff --git a/contrib/macOS/resources/English.lproj/Welcome.rtf b/contrib/macOS/resources/English.lproj/Welcome.rtf new file mode 100644 index 000000000000..a67536e470cc --- /dev/null +++ b/contrib/macOS/resources/English.lproj/Welcome.rtf @@ -0,0 +1,48 @@ +{\rtf1\ansi\ansicpg1252\cocoartf2512 +\cocoascreenfonts1\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 LucidaGrande;\f1\fnil\fcharset0 LucidaGrande-Bold;\f2\fnil\fcharset0 AppleSymbols; +\f3\fswiss\fcharset0 Helvetica;\f4\fswiss\fcharset0 ArialMT;\f5\fswiss\fcharset0 Arial-BoldMT; +\f6\fswiss\fcharset0 Arial-ItalicMT;} +{\colortbl;\red255\green255\blue255;\red255\green128\blue0;\red52\green52\blue52;\red93\green93\blue93; +\red76\green76\blue76;} +{\*\expandedcolortbl;;\csgenericrgb\c100000\c50196\c0;\csgenericrgb\c20392\c20392\c20392;\csgenericrgb\c36471\c36471\c36471; +\csgenericrgb\c29804\c29804\c29804;} +\paperw11900\paperh16840\margl1440\margr1440\vieww17860\viewh11000\viewkind0 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\li360\pardirnatural\partightenfactor0 + +\f0\fs24 \cf0 \ +You will be guided through the steps necessary to install +\f1\b OpenZFS on OS X +\f0\b0 on your system.\ +\ +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\li360\pardirnatural\partightenfactor0 + +\f2\fs36 \cf2 {{}{\*\glid2449 ⚠}\uc0\u9888 } +\f1\b\fs24 \cf3 IMPORTANT +\f0\b0 \cf0 \ +- You must "sudo zpool export" any imported pools before\ + running this installer.\ +- You must uninstall any other ZFS implementation before\ + installing OpenZFS on OS X.\ +- If this installer fails, please check install.log in Console.app\ + (or /var/log/install.log) for errors. +\f3 \ +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardeftab720\li360\sl420\partightenfactor0 + +\f4\fs18 \cf4 \'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\'97\ + +\f5\b\fs24 \cf5 System Requirements: +\f4\b0\fs26 \ + +\f6\i\fs22 Mac OS X operating system: +\f4\i0 \ + +\f5\b BigSur +\f4\b0 (v11) down to +\f5\b Yosemite +\f4\b0 (v10.10) +\f6\i \ +2 GB of memory +\f0\i0 \uc0\u8232 +\f6\i 25 MB of available disk space +\f0\i0 \uc0\u8232 +\f6\i Additional internal/external hard disks (reformatting required to add ZFS)} \ No newline at end of file diff --git a/contrib/macOS/resources/background.png b/contrib/macOS/resources/background.png new file mode 100644 index 0000000000000000000000000000000000000000..f8aa84d282cc3d2f3be53b1bea4946a7ff918b12 GIT binary patch literal 20428 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 Hu0mjfd~aUc literal 0 HcmV?d00001 diff --git a/contrib/macOS/resources/javascript.js b/contrib/macOS/resources/javascript.js new file mode 100644 index 000000000000..01d45f30b349 --- /dev/null +++ b/contrib/macOS/resources/javascript.js @@ -0,0 +1,200 @@ +const __IC_FLAT_DISTRIBUTION__=false; +const IC_OS_DISTRIBUTION_TYPE_ANY=0; +const IC_OS_DISTRIBUTION_TYPE_CLIENT=1; +const IC_DISK_TYPE_DESTINATION=0; +const IC_OS_DISTRIBUTION_TYPE_SERVER=2; +const IC_DISK_TYPE_STARTUP_DISK=1; + +const IC_COMPARATOR_IS_EQUAL=0; +const IC_COMPARATOR_IS_GREATER=1; +const IC_COMPARATOR_IS_NOT_EQUAL=2; +const IC_COMPARATOR_IS_LESS=-1; + +function IC_CheckOS(inDiskType,inMustBeInstalled,inMinimumVersion,inMaximumVersion,inDistributionType) +{ + var tOSVersion=undefined; + + /* Check Version Constraints */ + + if (inDiskType==IC_DISK_TYPE_DESTINATION) + { + if (my.target.systemVersion!=undefined) + { + tOSVersion=my.target.systemVersion.ProductVersion; + } + + /* Check if no OS is installed on the potential target */ + + if (tOSVersion==undefined) + { + return (inMustBeInstalled==false); + } + + if (inMustBeInstalled==false) + { + return false; + } + } + else + { + tOSVersion=system.version.ProductVersion; + } + + if (system.compareVersions(tOSVersion,inMinimumVersion)==-1) + return false; + + if (inMaximumVersion!=undefined && + system.compareVersions(tOSVersion,inMaximumVersion)==1) + return false; + + /* Check Distribution Type */ + + if (inDistributionType!=IC_OS_DISTRIBUTION_TYPE_ANY) + { + var tIsServer; + + if (system.compareVersions(tOSVersion,'10.8.0')==-1) + { + if (inDiskType==IC_DISK_TYPE_DESTINATION) + { + tIsServer=system.files.fileExistsAtPath(my.target.mountpoint+'/System/Library/CoreServices/ServerVersion.plist'); + } + else + { + tIsServer=system.files.fileExistsAtPath('/System/Library/CoreServices/ServerVersion.plist'); + } + } + else + { + if (inDiskType==IC_DISK_TYPE_DESTINATION) + { + tIsServer=system.files.fileExistsAtPath(my.target.mountpoint+'/Applications/Server.app'); + } + else + { + tIsServer=system.files.fileExistsAtPath('/Applications/Server.app'); + } + } + if (inDistributionType==IC_OS_DISTRIBUTION_TYPE_CLIENT && tIsServer==true) + { + return false; + } + + if (inDistributionType==IC_OS_DISTRIBUTION_TYPE_SERVER && tIsServer==false) + { + return false; + } + } + + return true; +} + +function IC_CheckScriptReturnValue(inScriptPath,inArguments,inComparator,inReturnValue) +{ + var tReturnValue; + + if (inScriptPath.charAt(0)=='/') + { + /* Check Absolute Path Existence */ + + if (system.files.fileExistsAtPath(inScriptPath)==false) + { + return false; + } + } + else + { + if (__IC_FLAT_DISTRIBUTION__==true && system.compareVersions(system.version.ProductVersion, '10.6.0')<0) + { + system.log("[WARNING] Embedded scripts are not supported in Flat distribution format on Mac OS X 10.5"); + + return true; + } + } + if (inArguments.length>0) + { + var tMethodCall; + var tStringArguments=[]; + + for(var i=0;i<inArguments.length;i++) + { + tStringArguments[i]='inArguments['+i+']'; + } + + tMethodCall='system.run(inScriptPath,'+tStringArguments.join(',')+');'; + + tReturnValue=eval(tMethodCall); + } + else + { + tReturnValue=system.run(inScriptPath); + } + + if (tReturnValue==undefined) + { + return false; + } + + if (inComparator==IC_COMPARATOR_IS_EQUAL) + { + return (tReturnValue==inReturnValue); + } + else if (inComparator==IC_COMPARATOR_IS_GREATER) + { + return (tReturnValue>inReturnValue); + } + else if (inComparator==IC_COMPARATOR_IS_LESS) + { + return (tReturnValue<inReturnValue); + } + else if (inComparator==IC_COMPARATOR_IS_NOT_EQUAL) + { + return (tReturnValue!=inReturnValue); + } + + return false; +} + +function installation_check() +{ + var tResult; + + tResult=IC_CheckOS(IC_DISK_TYPE_STARTUP_DISK,true,'10.5',undefined,IC_OS_DISTRIBUTION_TYPE_ANY); + + if (tResult==false) + { + my.result.title = system.localizedStandardStringWithFormat('InstallationCheckError', system.localizedString('DISTRIBUTION_TITLE')); + my.result.message = ' '; + my.result.type = 'Fatal'; + } + + if (tResult==true) + { + var tScriptArguments1=new Array(); + + tResult=IC_CheckScriptReturnValue('poolcheck.sh',tScriptArguments1,IC_COMPARATOR_IS_EQUAL,1); + + if (tResult==false) + { + my.result.title = system.localizedString('REQUIREMENT_FAILED_MESSAGE_INSTALLATION_CHECK_1'); + my.result.message = system.localizedString('REQUIREMENT_FAILED_DESCRIPTION_INSTALLATION_CHECK_1'); + my.result.type = 'Fatal'; + } + + if (tResult==true) + { + var tScriptArguments2=new Array(); + + tResult=IC_CheckScriptReturnValue('zevocheck.sh',tScriptArguments2,IC_COMPARATOR_IS_EQUAL,1); + + if (tResult==false) + { + my.result.title = system.localizedString('REQUIREMENT_FAILED_MESSAGE_INSTALLATION_CHECK_2'); + my.result.message = system.localizedString('REQUIREMENT_FAILED_DESCRIPTION_INSTALLATION_CHECK_2'); + my.result.type = 'Fatal'; + } + } + } + + return tResult; +} diff --git a/scripts/zfs-tests.sh b/scripts/zfs-tests.sh index 179e24d7a0ef..6d99c8e73d4d 100755 --- a/scripts/zfs-tests.sh +++ b/scripts/zfs-tests.sh @@ -57,6 +57,10 @@ if [ "$UNAME" = "FreeBSD" ] ; then TESTFAIL_CALLBACKS=${TESTFAIL_CALLBACKS:-"$ZFS_DMESG"} LOSETUP=/sbin/mdconfig DMSETUP=/sbin/gpart +elif [ "$UNAME" = "Darwin" ] ; then + TESTFAIL_CALLBACKS=${TESTFAIL_CALLBACKS:-"$ZFS_DMESG"} + LOSETUP=/usr/bin/hdiutil + DMSETUP=/sbin/something2 else ZFS_MMP="$STF_SUITE/callbacks/zfs_mmp.ksh" TESTFAIL_CALLBACKS=${TESTFAIL_CALLBACKS:-"$ZFS_DBGMSG:$ZFS_DMESG:$ZFS_MMP"} @@ -91,6 +95,16 @@ cleanup_freebsd_loopback() { done } +cleanup_macos_loopback() { + for TEST_LOOPBACK in ${LOOPBACKS}; do + sudo "$ZPOOL" export -a + if [ -b "${TEST_LOOPBACK}" ]; then + sudo "${LOSETUP}" detach "${TEST_LOOPBACK}" || + echo "Failed to destroy: ${TEST_LOOPBACK}" + fi + done +} + cleanup_linux_loopback() { for TEST_LOOPBACK in ${LOOPBACKS}; do LOOP_DEV="${TEST_LOOPBACK##*/}" @@ -123,6 +137,8 @@ cleanup() { if [ "$LOOPBACK" = "yes" ]; then if [ "$UNAME" = "FreeBSD" ] ; then cleanup_freebsd_loopback + elif [ "$UNAME" = "Darwin" ] ; then + cleanup_macos_loopback else cleanup_linux_loopback fi @@ -147,6 +163,8 @@ cleanup_all() { TEST_POOLS=$(ASAN_OPTIONS=detect_leaks=false "$ZPOOL" list -Ho name | grep testpool) if [ "$UNAME" = "FreeBSD" ] ; then TEST_LOOPBACKS=$(sudo "${LOSETUP}" -l) + elif [ "$UNAME" = "Darwin" ] ; then + TEST_LOOPBACKS=$(sudo "${LOSETUP}" info|grep /dev/disk) else TEST_LOOPBACKS=$("${LOSETUP}" -a | awk -F: '/file-vdev/ {print $1}') fi @@ -171,6 +189,8 @@ cleanup_all() { for TEST_LOOPBACK in $TEST_LOOPBACKS; do if [ "$UNAME" = "FreeBSD" ] ; then sudo "${LOSETUP}" -d -u "${TEST_LOOPBACK}" + elif [ "$UNAME" = "Darwin" ] ; then + sudo "${LOSETUP}" detach "${TEST_LOOPBACK}" else sudo "${LOSETUP}" -d "${TEST_LOOPBACK}" fi @@ -282,6 +302,8 @@ constrain_path() { SYSTEM_FILES="$SYSTEM_FILES_COMMON" if [ "$UNAME" = "FreeBSD" ] ; then SYSTEM_FILES="$SYSTEM_FILES $SYSTEM_FILES_FREEBSD" + elif [ "$UNAME" = "Darwin" ] ; then + SYSTEM_FILES="$SYSTEM_FILES $SYSTEM_FILES_MACOS" else SYSTEM_FILES="$SYSTEM_FILES $SYSTEM_FILES_LINUX" fi @@ -295,6 +317,20 @@ constrain_path() { ln -fs "$STF_PATH/gunzip" "$STF_PATH/uncompress" elif [ "$UNAME" = "FreeBSD" ] ; then ln -fs /usr/local/bin/ksh93 "$STF_PATH/ksh" + elif [ "$UNAME" = "Darwin" ] ; then + [ -f "/usr/local/bin/gdd" ] && ln -fs /usr/local/bin/gdd "$STF_PATH/dd" + [ -f "/usr/local/bin/gsed" ] && ln -fs /usr/local/bin/gsed "$STF_PATH/gsed" + ln -fs /bin/ksh "$STF_PATH/ksh" + ln -fs /sbin/fsck_hfs "$STF_PATH/fsck" + ln -fs /sbin/newfs_hfs "$STF_PATH/newfs_hfs" + ln -fs /sbin/mount_hfs "$STF_PATH/mount" + ln -fs /usr/local/bin/gtruncate "$STF_PATH/truncate" + ln -fs /usr/sbin/sysctl "$STF_PATH/sysctl" + ln -fs /usr/bin/dscl "$STF_PATH/dscl" + ln -fs /usr/bin/xxd "$STF_PATH/xxd" + ln -fs /usr/sbin/dseditgroup "$STF_PATH/dseditgroup" + ln -fs /usr/bin/xattr "$STF_PATH/xattr" + ln -fs /usr/sbin/createhomedir "$STF_PATH/createhomedir" fi } @@ -521,6 +557,17 @@ fi [ -e "$STF_SUITE/include/default.cfg" ] || fail \ "Missing $STF_SUITE/include/default.cfg file." +if [ "$UNAME" = "Darwin" ]; then + DYLD_LIBRARY_PATH=$STF_SUITE/cmd/librt/.libs:$DYLD_LIBRARY_PATH + export DYLD_LIBRARY_PATH + # Tell ZFS to not to use /Volumes + __ZFS_MAIN_MOUNTPOINT_DIR=/ + export __ZFS_MAIN_MOUNTPOINT_DIR + # Catalina and up has root as read/only. + # BigSur gets even harder. + sudo /sbin/mount -uw / + export SHELL=ksh +fi # # Verify the ZFS module stack is loaded. # @@ -579,8 +626,15 @@ if [ -z "${DISKS}" ]; then # for TEST_FILE in ${FILES}; do [ -f "$TEST_FILE" ] && fail "Failed file exists: ${TEST_FILE}" - truncate -s "${FILESIZE}" "${TEST_FILE}" || + + if [ "$UNAME" = "Darwin" ] ; then + mkfile -n "${FILESIZE}" "${TEST_FILE}" || fail "Failed creating: ${TEST_FILE} ($?)" + else + truncate -s "${FILESIZE}" "${TEST_FILE}" || + fail "Failed creating: ${TEST_FILE} ($?)" + fi + done # @@ -597,6 +651,21 @@ if [ -z "${DISKS}" ]; then fi DISKS="$DISKS $MDDEVICE" LOOPBACKS="$LOOPBACKS $MDDEVICE" + elif [ "$UNAME" = "Darwin" ] ; then + MDDEVICE=$(sudo "${LOSETUP}" attach -imagekey diskimage-class=CRawDiskImage -nomount "${TEST_FILE}") + if [ -z "$MDDEVICE" ] ; then + fail "Failed: ${TEST_FILE} -> loopback" + fi + LOOPBACKS="${LOOPBACKS}${MDDEVICE} " + BASEMDDEVICE=$(basename "$MDDEVICE") + if [ -z "$DISKS" ]; then + DISKS="$BASEMDDEVICE" + else + DISKS="$DISKS $BASEMDDEVICE" + fi + # If we use attached disk, remove the file-vdev + # from list. + DISKS=${DISKS:-"$TEST_FILE"} else TEST_LOOPBACK=$(sudo "${LOSETUP}" --show -f "${TEST_FILE}") || fail "Failed: ${TEST_FILE} -> ${TEST_LOOPBACK}" @@ -621,6 +690,8 @@ if [ "$TAGS" != "perf" ]; then [ "$NUM_DISKS" -lt 3 ] && fail "Not enough disks ($NUM_DISKS/3 minimum)" fi +echo "Finished with DISKS $DISKS" + # # Disable SELinux until the ZFS Test Suite has been updated accordingly. # @@ -667,6 +738,8 @@ export TESTFAIL_CALLBACKS mktemp_file() { if [ "$UNAME" = "FreeBSD" ]; then mktemp -u "${FILEDIR}/$1.XXXXXX" + elif [ "$UNAME" = "Darwin" ]; then + mktemp -u "${FILEDIR}/$1.XXXXXX" else mktemp -ut "$1.XXXXXX" -p "$FILEDIR" fi From bb8e0a5e66bf19a92ad8c17fa56ff11a226b3a1a Mon Sep 17 00:00:00 2001 From: Jorgen Lundman Date: Mon, 23 Oct 2023 09:07:40 +0900 Subject: [PATCH 5/9] 5: include/* lib/* and top build Bonus part5, the non-macos changes to include lib and the top level Makefiles Signed-off-by: Jorgen Lundman --- Makefile.am | 9 ++- configure.ac | 5 +- etc/Makefile.am | 6 ++ include/libzfs.h | 17 ++++++ include/libzutil.h | 4 ++ include/os/freebsd/spl/sys/simd_aarch64.h | 9 +++ include/os/linux/kernel/linux/simd_aarch64.h | 10 ++++ include/os/linux/zfs/sys/zfs_context_os.h | 4 ++ include/sys/abd.h | 9 ++- include/sys/abd_impl.h | 3 + include/sys/asm_linkage.h | 4 ++ include/sys/crypto/icp.h | 3 + include/sys/fs/zfs.h | 21 ++++++- include/sys/mntent.h | 7 +++ include/sys/spa.h | 2 + include/sys/spa_impl.h | 3 + include/sys/sysevent/dev.h | 2 +- include/sys/vdev_raidz.h | 1 + include/sys/xvattr.h | 12 +++- include/sys/zfs_bootenv.h | 1 + include/sys/zfs_debug.h | 21 ++++++- include/sys/zfs_file.h | 2 + include/sys/zfs_ioctl_impl.h | 3 + include/sys/zfs_sa.h | 10 ++++ include/sys/zfs_znode.h | 3 + include/sys/zio.h | 4 ++ include/sys/zio_crypt.h | 5 ++ include/zfs_fletcher.h | 1 + lib/Makefile.am | 14 +++-- lib/libefi/Makefile.am | 13 +++- lib/libicp/Makefile.am | 3 +- lib/libnvpair/Makefile.am | 23 ++++++++ lib/libshare/Makefile.am | 6 ++ lib/libspl/Makefile.am | 7 +++ lib/libspl/atomic.c | 8 +-- lib/libspl/include/Makefile.am | 41 +++++++++++++ lib/libspl/include/sys/asm_linkage.h | 5 +- lib/libspl/include/sys/dkio.h | 2 + lib/libspl/include/sys/isa_defs.h | 3 + lib/libspl/include/sys/simd.h | 32 ++++++++++ lib/libspl/include/sys/uio.h | 6 +- lib/libuutil/Makefile.am | 2 + lib/libzfs/Makefile.am | 17 ++++++ lib/libzfs/libzfs.abi | 7 ++- lib/libzfs/libzfs_crypto.c | 2 + lib/libzfs/libzfs_dataset.c | 62 ++++++++++++++++++++ lib/libzfs/libzfs_diff.c | 5 ++ lib/libzfs/libzfs_iter.c | 16 ++--- lib/libzfs/libzfs_sendrecv.c | 27 +++++++++ lib/libzfs_core/Makefile.am | 9 +++ lib/libzfs_core/libzfs_core.c | 3 + lib/libzfsbootenv/Makefile.am | 2 + lib/libzpool/Makefile.am | 2 + lib/libzutil/Makefile.am | 11 ++++ lib/libzutil/zutil_device_path.c | 40 ++++++++++++- lib/libzutil/zutil_import.c | 17 ++++++ lib/libzutil/zutil_pool.c | 1 + 57 files changed, 532 insertions(+), 35 deletions(-) diff --git a/Makefile.am b/Makefile.am index 11e45dae8255..5c6f339d8464 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,6 +18,12 @@ if BUILD_LINUX include $(srcdir)/%D%/rpm/Makefile.am endif +if BUILD_MACOS +bin_PROGRAMS= +noinst_PROGRAMS= +include $(srcdir)/%D%/module/os/macos/Makefile.am +endif + if CONFIG_USER include $(srcdir)/%D%/cmd/Makefile.am include $(srcdir)/%D%/contrib/Makefile.am @@ -194,11 +200,12 @@ cscopelist: PHONY += tags tags: ctags etags -PHONY += pkg pkg-dkms pkg-kmod pkg-utils +PHONY += pkg pkg-dkms pkg-kmod pkg-utils pkg-macos pkg: @DEFAULT_PACKAGE@ pkg-dkms: @DEFAULT_PACKAGE@-dkms pkg-kmod: @DEFAULT_PACKAGE@-kmod pkg-utils: @DEFAULT_PACKAGE@-utils +pkg-macos: @DEFAULT_PACKAGE@-macos include config/rpm.am include config/deb.am diff --git a/configure.ac b/configure.ac index 4c75616e4299..403c2762a675 100644 --- a/configure.ac +++ b/configure.ac @@ -44,7 +44,7 @@ AM_INIT_AUTOMAKE([subdir-objects foreign]) # Remove default macros from config.h: # PACKAGE, PACKAGE_{BUGREPORT,NAME,STRING,TARNAME,VERSION}, STDC_HEADERS, VERSION AC_CONFIG_HEADERS([zfs_config.h], [ - sed -nri~ -e '/^$/be' -e 'N;N;/#define (PACKAGE|VERSION|STDC_HEADERS)/d' -e ':e' -e 'p' zfs_config.h && rm zfs_config.h~ || exit]) + $SED -nri~ -e '/^$/be' -e 'N;N;/#define (PACKAGE|VERSION|STDC_HEADERS)/d' -e ':e' -e 'p' zfs_config.h && rm zfs_config.h~ || exit]) LT_INIT AC_PROG_INSTALL @@ -55,6 +55,9 @@ AM_PROG_AS AM_PROG_CC_C_O AX_CODE_COVERAGE _AM_PROG_TAR(pax) +AC_PROG_CXX(clang++) +AC_PROG_OBJC(clang) +AC_PROG_OBJCXX(clang++) ZFS_AC_LICENSE ZFS_AC_CONFIG diff --git a/etc/Makefile.am b/etc/Makefile.am index 7187762d3802..3121ebf2680b 100644 --- a/etc/Makefile.am +++ b/etc/Makefile.am @@ -97,3 +97,9 @@ dist_modulesload_DATA = \ %D%/modules-load.d/zfs.conf endif endif + +if BUILD_MACOS +include $(srcdir)/%D%/launchd/Makefile.am +include $(srcdir)/%D%/paths.d/Makefile.am +include $(srcdir)/%D%/launchd.d/Makefile.am +endif diff --git a/include/libzfs.h b/include/libzfs.h index 4adfa38e87be..ca74841029eb 100644 --- a/include/libzfs.h +++ b/include/libzfs.h @@ -1003,6 +1003,7 @@ _LIBZFS_H int zpool_enable_datasets(zpool_handle_t *, const char *, int); _LIBZFS_H int zpool_disable_datasets(zpool_handle_t *, boolean_t); _LIBZFS_H void zpool_disable_datasets_os(zpool_handle_t *, boolean_t); _LIBZFS_H void zpool_disable_volume_os(const char *); +_LIBZFS_H void zfs_rollback_os(struct zfs_handle *); /* * Parse a features file for -o compatibility @@ -1039,9 +1040,25 @@ _LIBZFS_H int zpool_nextboot(libzfs_handle_t *, uint64_t, uint64_t, * Add or delete the given filesystem to/from the given user namespace. */ _LIBZFS_H int zfs_userns(zfs_handle_t *zhp, const char *nspath, int attach); +#endif +#ifdef __APPLE__ +_LIBZFS_H int zfs_snapshot_mount(zfs_handle_t *, const char *, int); +_LIBZFS_H int zfs_snapshot_unmount(zfs_handle_t *, int); +/* We moved these from libspl to libzfs to be able to do more */ +_LIBZFS_H int getmntent(FILE *, struct mnttab *); +_LIBZFS_H char *hasmntopt(struct mnttab *, const char *); +_LIBZFS_H int getextmntent(const char *, struct extmnttab *, + struct stat64 *); +_LIBZFS_H int do_mount(zfs_handle_t *, const char *, const char *, int); #endif +/* + * Manual mounting of snapshots. + */ +extern int zfs_snapshot_mount(zfs_handle_t *, const char *, int); +extern int zfs_snapshot_unmount(zfs_handle_t *, int); + #ifdef __cplusplus } #endif diff --git a/include/libzutil.h b/include/libzutil.h index 053b1ed4b52a..4d7973f413a2 100644 --- a/include/libzutil.h +++ b/include/libzutil.h @@ -109,7 +109,11 @@ _LIBZUTIL_H void update_vdev_config_dev_strs(nvlist_t *); * Default device paths */ #define DISK_ROOT "/dev" +#ifdef __APPLE__ +#define UDISK_ROOT "/private/var/run/disk" +#else #define UDISK_ROOT "/dev/disk" +#endif #define ZVOL_ROOT "/dev/zvol" _LIBZUTIL_H int zfs_append_partition(char *path, size_t max_len); diff --git a/include/os/freebsd/spl/sys/simd_aarch64.h b/include/os/freebsd/spl/sys/simd_aarch64.h index 234f401db791..2af01a939afb 100644 --- a/include/os/freebsd/spl/sys/simd_aarch64.h +++ b/include/os/freebsd/spl/sys/simd_aarch64.h @@ -91,4 +91,13 @@ zfs_sha512_available(void) return (elf_hwcap & HWCAP_SHA512); } +/* + * Check if AESV8 is available + */ +static inline boolean_t +zfs_aesv8_available(void) +{ + return (elf_hwcap & HWCAP_AES); +} + #endif /* _FREEBSD_SIMD_AARCH64_H */ diff --git a/include/os/linux/kernel/linux/simd_aarch64.h b/include/os/linux/kernel/linux/simd_aarch64.h index 16276b08c759..e48c9b3be932 100644 --- a/include/os/linux/kernel/linux/simd_aarch64.h +++ b/include/os/linux/kernel/linux/simd_aarch64.h @@ -113,4 +113,14 @@ zfs_sha512_available(void) return (ftr & 0x2); } +/* + * Check if AESV8 is available + */ +static inline boolean_t +zfs_aesv8_available(void) +{ + unsigned long ftr = ((get_ftr(ID_AA64ISAR0_EL1)) >> 4) & 0x3; + return (ftr); +} + #endif /* _LINUX_SIMD_AARCH64_H */ diff --git a/include/os/linux/zfs/sys/zfs_context_os.h b/include/os/linux/zfs/sys/zfs_context_os.h index 04a5f0c0d239..ca424785b975 100644 --- a/include/os/linux/zfs/sys/zfs_context_os.h +++ b/include/os/linux/zfs/sys/zfs_context_os.h @@ -37,4 +37,8 @@ #undef longjmp #endif +#ifndef MODULE_PARAM_MAX +#define MODULE_PARAM_MAX 1024 +#endif + #endif diff --git a/include/sys/abd.h b/include/sys/abd.h index 750f9986c1da..08c1c9ce5760 100644 --- a/include/sys/abd.h +++ b/include/sys/abd.h @@ -60,7 +60,8 @@ typedef struct abd { union { struct abd_scatter { uint_t abd_offset; -#if defined(__FreeBSD__) && defined(_KERNEL) +#if defined(_KERNEL) && (defined(__FreeBSD__) || defined(__APPLE__)) + uint_t abd_chunk_size; void *abd_chunks[1]; /* actually variable-length */ #else uint_t abd_nents; @@ -129,6 +130,7 @@ void abd_copy_off(abd_t *, abd_t *, size_t, size_t, size_t); void abd_copy_from_buf_off(abd_t *, const void *, size_t, size_t); void abd_copy_to_buf_off(void *, abd_t *, size_t, size_t); int abd_cmp(abd_t *, abd_t *); +int abd_cmp_size(abd_t *, abd_t *, size_t); int abd_cmp_buf_off(abd_t *, const void *, size_t, size_t); void abd_zero_off(abd_t *, size_t, size_t); void abd_verify(abd_t *); @@ -176,6 +178,11 @@ abd_zero(abd_t *abd, size_t size) abd_zero_off(abd, 0, size); } +#ifdef __APPLE__ +void abd_return_buf_off(abd_t *, void *, size_t, size_t, size_t); +void abd_return_buf_copy_off(abd_t *, void *, size_t, size_t, size_t); +#endif + /* * ABD type check functions */ diff --git a/include/sys/abd_impl.h b/include/sys/abd_impl.h index 40546d4af137..6d7b4ccc3a59 100644 --- a/include/sys/abd_impl.h +++ b/include/sys/abd_impl.h @@ -95,6 +95,9 @@ void abd_iter_unmap(struct abd_iter *); #if defined(__FreeBSD__) #define abd_enter_critical(flags) critical_enter() #define abd_exit_critical(flags) critical_exit() +#elif defined(__APPLE__) +#define abd_enter_critical(flags) (flags) = ml_set_interrupts_enabled(FALSE) +#define abd_exit_critical(flags) ml_set_interrupts_enabled((flags)) #else #define abd_enter_critical(flags) local_irq_save(flags) #define abd_exit_critical(flags) local_irq_restore(flags) diff --git a/include/sys/asm_linkage.h b/include/sys/asm_linkage.h index 749157d4c3db..95958ad918ae 100644 --- a/include/sys/asm_linkage.h +++ b/include/sys/asm_linkage.h @@ -33,6 +33,10 @@ #include /* XX64 x86/sys/asm_linkage.h */ +#elif defined(__aarch64__) + +#include + #endif #if defined(_KERNEL) && defined(HAVE_KERNEL_OBJTOOL) diff --git a/include/sys/crypto/icp.h b/include/sys/crypto/icp.h index 8c3f19886fd8..fca3f8194546 100644 --- a/include/sys/crypto/icp.h +++ b/include/sys/crypto/icp.h @@ -39,6 +39,9 @@ int icp_init(void); void icp_fini(void); int aes_impl_set(const char *); +int aes_impl_get(char *, size_t); int gcm_impl_set(const char *); +int gcm_impl_get(char *, size_t); + #endif /* _SYS_CRYPTO_ALGS_H */ diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index 9d7eca9784b6..7b58a746814c 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -192,6 +192,11 @@ typedef enum { ZFS_PROP_REDACT_SNAPS, ZFS_PROP_SNAPSHOTS_CHANGED, ZFS_PROP_PREFETCH, + ZFS_PROP_BROWSE, /* macOS: nobrowse/browse */ + ZFS_PROP_IGNOREOWNER, /* macOS: ignoreowner mount */ + ZFS_PROP_LASTUNMOUNT, /* macOS: Spotlight required */ + ZFS_PROP_MIMIC, /* macOS: mimic=hfs|apfs */ + ZFS_PROP_DEVDISK, /* macOS: create IOkit virtual disk */ ZFS_NUM_PROPS } zfs_prop_t; @@ -550,6 +555,18 @@ typedef enum { ZFS_PREFETCH_ALL = 2 } zfs_prefetch_type_t; +typedef enum zfs_mimic { + ZFS_MIMIC_OFF = 0, + ZFS_MIMIC_HFS, + ZFS_MIMIC_APFS +} zfs_mimic_t; + +typedef enum zfs_devdisk { + ZFS_DEVDISK_POOLONLY = 0, + ZFS_DEVDISK_OFF, + ZFS_DEVDISK_ON +} zfs_devdisk_t; + #define DEFAULT_PBKDF2_ITERATIONS 350000 #define MIN_PBKDF2_ITERATIONS 100000 @@ -1386,7 +1403,7 @@ typedef enum zfs_ioc { /* * Core features - 88/128 numbers reserved. */ -#ifdef __FreeBSD__ +#if defined(__FreeBSD__) || defined(__APPLE__) ZFS_IOC_FIRST = 0, #else ZFS_IOC_FIRST = ('Z' << 8), @@ -1495,6 +1512,8 @@ typedef enum zfs_ioc { ZFS_IOC_USERNS_DETACH = ZFS_IOC_UNJAIL, /* 0x86 (Linux) */ ZFS_IOC_SET_BOOTENV, /* 0x87 */ ZFS_IOC_GET_BOOTENV, /* 0x88 */ + ZFS_IOC_PROXY_DATASET, /* 0x89 (macOS) */ + ZFS_IOC_PROXY_REMOVE, /* 0x8a (macOS) */ ZFS_IOC_LAST } zfs_ioc_t; diff --git a/include/sys/mntent.h b/include/sys/mntent.h index 5bb7e080cda8..4449ea1206a8 100644 --- a/include/sys/mntent.h +++ b/include/sys/mntent.h @@ -79,6 +79,13 @@ #elif defined(__FreeBSD__) #define MNTOPT_SETUID "setuid" /* Set uid allowed */ #define MNTOPT_NOSETUID "nosetuid" /* Set uid not allowed */ +#elif defined(__APPLE__) +#define MNTOPT_SETUID "setuid" /* Set uid allowed */ +#define MNTOPT_NOSETUID "nosetuid" /* Set uid not allowed */ +#define MNTOPT_BROWSE "browse" /* browsable autofs mount */ +#define MNTOPT_NOBROWSE "nobrowse" /* non-browsable autofs mount */ +#define MNTOPT_OWNERS "owners" /* use ownership */ +#define MNTOPT_NOOWNERS "noowners" /* ignore ownership */ #else #error "unknown OS" #endif diff --git a/include/sys/spa.h b/include/sys/spa.h index 88ef510b744b..d8ec4f496227 100644 --- a/include/sys/spa.h +++ b/include/sys/spa.h @@ -1144,6 +1144,8 @@ extern boolean_t zfs_ereport_is_valid(const char *clazz, spa_t *spa, vdev_t *vd, zio_t *zio); extern void zfs_ereport_taskq_fini(void); extern void zfs_ereport_clear(spa_t *spa, vdev_t *vd); +extern void zfs_ereport_zvol_post(const char *subclass, const char *name, + const char *bsd, const char *rbsd); extern nvlist_t *zfs_event_create(spa_t *spa, vdev_t *vd, const char *type, const char *name, nvlist_t *aux); extern void zfs_post_remove(spa_t *spa, vdev_t *vd); diff --git a/include/sys/spa_impl.h b/include/sys/spa_impl.h index 094258d47a48..194a2e0743a3 100644 --- a/include/sys/spa_impl.h +++ b/include/sys/spa_impl.h @@ -441,6 +441,9 @@ struct spa { boolean_t spa_waiters_cancel; /* waiters should return */ char *spa_compatibility; /* compatibility file(s) */ +#ifdef __APPLE__ + spa_iokit_t *spa_iokit_proxy; /* IOKit pool proxy */ +#endif /* * spa_refcount & spa_config_lock must be the last elements diff --git a/include/sys/sysevent/dev.h b/include/sys/sysevent/dev.h index 0783d0073162..d0eb96eb6b17 100644 --- a/include/sys/sysevent/dev.h +++ b/include/sys/sysevent/dev.h @@ -239,7 +239,7 @@ extern "C" { #define DEV_INSTANCE "instance" #define DEV_PROP_PREFIX "prop-" -#ifdef __linux__ +#if defined(__linux__) || defined(__APPLE__) #define DEV_IDENTIFIER "devid" #define DEV_PATH "path" #define DEV_IS_PART "is_slice" diff --git a/include/sys/vdev_raidz.h b/include/sys/vdev_raidz.h index e34b6e4b158e..f8fc4bacbdc0 100644 --- a/include/sys/vdev_raidz.h +++ b/include/sys/vdev_raidz.h @@ -64,6 +64,7 @@ int vdev_raidz_math_generate(struct raidz_map *, struct raidz_row *); int vdev_raidz_math_reconstruct(struct raidz_map *, struct raidz_row *, const int *, const int *, const int); int vdev_raidz_impl_set(const char *); +int vdev_raidz_impl_get(char *buffer, size_t max); typedef struct vdev_raidz { int vd_logical_width; diff --git a/include/sys/xvattr.h b/include/sys/xvattr.h index a7994db894b9..53d02d3b584d 100644 --- a/include/sys/xvattr.h +++ b/include/sys/xvattr.h @@ -67,6 +67,9 @@ typedef struct xoptattr { uint8_t xoa_sparse; uint8_t xoa_projinherit; uint64_t xoa_projid; + uint8_t xoa_tracked; /* macOS */; + uint8_t xoa_sappendonly; /* macOS */; + uint8_t xoa_simmutable; /* macOS */; } xoptattr_t; /* @@ -174,12 +177,16 @@ typedef struct xvattr { #define XAT0_SPARSE 0x00010000 /* sparse */ #define XAT0_PROJINHERIT 0x00020000 /* Create with parent projid */ #define XAT0_PROJID 0x00040000 /* Project ID */ +#define XAT0_TRACKED 0x00080000 /* macOS UF_TRACKED */ +#define XAT0_SAPPENDONLY 0x00100000 /* macOS SF_APPENDONLY */ +#define XAT0_SIMMUTABLE 0x00200000 /* macOS SF_IMMUTABLE */ #define XAT0_ALL_ATTRS (XAT0_CREATETIME|XAT0_ARCHIVE|XAT0_SYSTEM| \ XAT0_READONLY|XAT0_HIDDEN|XAT0_NOUNLINK|XAT0_IMMUTABLE|XAT0_APPENDONLY| \ XAT0_NODUMP|XAT0_OPAQUE|XAT0_AV_QUARANTINED| XAT0_AV_MODIFIED| \ XAT0_AV_SCANSTAMP|XAT0_REPARSE|XATO_GEN|XAT0_OFFLINE|XAT0_SPARSE| \ - XAT0_PROJINHERIT | XAT0_PROJID) + XAT0_PROJINHERIT | XAT0_PROJID|XAT0_TRACKED|XAT0_SAPPENDONLY| \ + XAT0_SIMMUTABLE) /* Support for XAT_* optional attributes */ #define XVA_MASK 0xffffffff /* Used to mask off 32 bits */ @@ -218,6 +225,9 @@ typedef struct xvattr { #define XAT_SPARSE ((XAT0_INDEX << XVA_SHFT) | XAT0_SPARSE) #define XAT_PROJINHERIT ((XAT0_INDEX << XVA_SHFT) | XAT0_PROJINHERIT) #define XAT_PROJID ((XAT0_INDEX << XVA_SHFT) | XAT0_PROJID) +#define XAT_TRACKED ((XAT0_INDEX << XVA_SHFT) | XAT0_TRACKED) +#define XAT_SAPPENDONLY ((XAT0_INDEX << XVA_SHFT) | XAT0_SAPPENDONLY) +#define XAT_SIMMUTABLE ((XAT0_INDEX << XVA_SHFT) | XAT0_SIMMUTABLE) /* * The returned attribute map array (xva_rtnattrmap[]) is located past the diff --git a/include/sys/zfs_bootenv.h b/include/sys/zfs_bootenv.h index 7af0a57dd008..5823da980b48 100644 --- a/include/sys/zfs_bootenv.h +++ b/include/sys/zfs_bootenv.h @@ -30,6 +30,7 @@ extern "C" { #define BE_FREEBSD_VENDOR "freebsd" #define BE_GRUB_VENDOR "grub" #define BE_LINUX_VENDOR "linux" +#define BE_MACOS_VENDOR "macos" #include diff --git a/include/sys/zfs_debug.h b/include/sys/zfs_debug.h index a1dfef1d89ff..b4fad7ffa6e2 100644 --- a/include/sys/zfs_debug.h +++ b/include/sys/zfs_debug.h @@ -83,12 +83,27 @@ extern void __dprintf(boolean_t dprint, const char *file, const char *func, if (zfs_dbgmsg_enable) \ __dprintf(B_FALSE, __FILE__, __func__, __LINE__, __VA_ARGS__) -#ifdef ZFS_DEBUG +#ifdef __APPLE__ /* * To enable this: * - * $ echo 1 >/sys/module/zfs/parameters/zfs_flags + * $ sysctl kstat.zfs.darwin.tunable.zfs_flags=1 */ +#ifdef _KERNEL +#undef dprintf +#define dprintf(...) \ + if (zfs_flags & ZFS_DEBUG_DPRINTF) \ + __dprintf(B_TRUE, __FILE__, __func__, __LINE__, __VA_ARGS__) +#endif + +#else /* !APPLE */ + +#ifdef ZFS_DEBUG + /* + * To enable this: + * + * $ echo 1 >/sys/module/zfs/parameters/zfs_flags + */ #define dprintf(...) \ if (zfs_flags & ZFS_DEBUG_DPRINTF) \ __dprintf(B_TRUE, __FILE__, __func__, __LINE__, __VA_ARGS__) @@ -96,6 +111,8 @@ extern void __dprintf(boolean_t dprint, const char *file, const char *func, #define dprintf(...) ((void)0) #endif /* ZFS_DEBUG */ +#endif /* !APPLE */ + extern void zfs_panic_recover(const char *fmt, ...); extern void zfs_dbgmsg_init(void); diff --git a/include/sys/zfs_file.h b/include/sys/zfs_file.h index e944165adc40..c11ba376ded4 100644 --- a/include/sys/zfs_file.h +++ b/include/sys/zfs_file.h @@ -31,6 +31,8 @@ typedef struct zfs_file { } zfs_file_t; #elif defined(__linux__) || defined(__FreeBSD__) typedef struct file zfs_file_t; +#elif defined(__APPLE__) +typedef struct spl_fileproc zfs_file_t; #else #error "unknown OS" #endif diff --git a/include/sys/zfs_ioctl_impl.h b/include/sys/zfs_ioctl_impl.h index cb852c5577fd..df5fe41bae43 100644 --- a/include/sys/zfs_ioctl_impl.h +++ b/include/sys/zfs_ioctl_impl.h @@ -75,6 +75,9 @@ int zfs_secpolicy_config(zfs_cmd_t *, nvlist_t *, cred_t *); void zfs_ioctl_register_dataset_nolog(zfs_ioc_t, zfs_ioc_legacy_func_t *, zfs_secpolicy_func_t *, zfs_ioc_poolcheck_t); +void zfs_ioctl_register_pool(zfs_ioc_t, zfs_ioc_legacy_func_t *, + zfs_secpolicy_func_t *, boolean_t, zfs_ioc_poolcheck_t); + void zfs_ioctl_register(const char *, zfs_ioc_t, zfs_ioc_func_t *, zfs_secpolicy_func_t *, zfs_ioc_namecheck_t, zfs_ioc_poolcheck_t, diff --git a/include/sys/zfs_sa.h b/include/sys/zfs_sa.h index 1b4b8abf0244..502d3e8f35a2 100644 --- a/include/sys/zfs_sa.h +++ b/include/sys/zfs_sa.h @@ -75,6 +75,16 @@ typedef enum zpl_attr { ZPL_DACL_ACES, ZPL_DXATTR, ZPL_PROJID, + + /* + * Apple defines a ADDEDTIME, which is the time the entry was placed + * in the containing directory. Ie, CRTIME and updated when moved + * into a different directory. This can be retrieved with getxattr + * "FinderInfo" or the getattrlist() syscall. + */ + ZPL_ADDTIME, + ZPL_DOCUMENTID, + ZPL_END } zpl_attr_t; diff --git a/include/sys/zfs_znode.h b/include/sys/zfs_znode.h index 2f266f53247e..4798e4cdd459 100644 --- a/include/sys/zfs_znode.h +++ b/include/sys/zfs_znode.h @@ -112,6 +112,9 @@ extern "C" { #define SA_ZPL_PAD(z) z->z_attr_table[ZPL_PAD] #define SA_ZPL_PROJID(z) z->z_attr_table[ZPL_PROJID] +#define SA_ZPL_ADDTIME(z) z->z_attr_table[ZPL_ADDTIME] +#define SA_ZPL_DOCUMENTID(z) z->z_attr_table[ZPL_DOCUMENTID] + /* * Is ID ephemeral? */ diff --git a/include/sys/zio.h b/include/sys/zio.h index e1f4d5c04499..a4c93697a981 100644 --- a/include/sys/zio.h +++ b/include/sys/zio.h @@ -520,6 +520,10 @@ struct zio { kcondvar_t io_cv; int io_allocator; +#ifdef ZIO_OS_FIELDS + ZIO_OS_FIELDS +#endif + /* FMA state */ zio_cksum_report_t *io_cksum_report; uint64_t io_ena; diff --git a/include/sys/zio_crypt.h b/include/sys/zio_crypt.h index 6a3efabb0405..75b2dd962160 100644 --- a/include/sys/zio_crypt.h +++ b/include/sys/zio_crypt.h @@ -148,6 +148,11 @@ int zio_crypt_do_hmac(zio_crypt_key_t *key, uint8_t *data, uint_t datalen, uint8_t *digestbuf, uint_t digestlen); int zio_crypt_do_objset_hmacs(zio_crypt_key_t *key, void *data, uint_t datalen, boolean_t byteswap, uint8_t *portable_mac, uint8_t *local_mac); +#ifdef __APPLE__ +int zio_crypt_do_objset_hmacs_errata1(zio_crypt_key_t *key, void *data, + uint_t datalen, boolean_t should_bswap, uint8_t *portable_mac, + uint8_t *local_mac); +#endif int zio_do_crypt_data(boolean_t encrypt, zio_crypt_key_t *key, dmu_object_type_t ot, boolean_t byteswap, uint8_t *salt, uint8_t *iv, uint8_t *mac, uint_t datalen, uint8_t *plainbuf, uint8_t *cipherbuf, diff --git a/include/zfs_fletcher.h b/include/zfs_fletcher.h index ca1a092928d6..b61f428ef335 100644 --- a/include/zfs_fletcher.h +++ b/include/zfs_fletcher.h @@ -132,6 +132,7 @@ typedef struct fletcher_4_func { _ZFS_FLETCHER_H const fletcher_4_ops_t fletcher_4_superscalar_ops; _ZFS_FLETCHER_H const fletcher_4_ops_t fletcher_4_superscalar4_ops; +_ZFS_FLETCHER_H int fletcher_4_get(char *, size_t); #if defined(HAVE_SSE2) _ZFS_FLETCHER_H const fletcher_4_ops_t fletcher_4_sse2_ops; diff --git a/lib/Makefile.am b/lib/Makefile.am index 499ebdaeba9b..91ebc4b440d9 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -54,6 +54,16 @@ noinst_LTLIBRARIES = lib_LTLIBRARIES = pkgconfig_DATA = + +if BUILD_MACOS +include $(srcdir)/%D%/os/macos/libdiskmgt/Makefile.am +include $(srcdir)/%D%/libefi/Makefile.am +endif + +if BUILD_LINUX +include $(srcdir)/%D%/libefi/Makefile.am +endif + include $(srcdir)/%D%/libavl/Makefile.am include $(srcdir)/%D%/libicp/Makefile.am include $(srcdir)/%D%/libnvpair/Makefile.am @@ -68,10 +78,6 @@ include $(srcdir)/%D%/libzfsbootenv/Makefile.am include $(srcdir)/%D%/libzpool/Makefile.am include $(srcdir)/%D%/libzstd/Makefile.am include $(srcdir)/%D%/libzutil/Makefile.am -if BUILD_LINUX -include $(srcdir)/%D%/libefi/Makefile.am -endif - PHONY += lib lib: $(noinst_LTLIBRARIES) $(lib_LTLIBRARIES) diff --git a/lib/libefi/Makefile.am b/lib/libefi/Makefile.am index 5c3e57346c86..f8abc88701be 100644 --- a/lib/libefi/Makefile.am +++ b/lib/libefi/Makefile.am @@ -5,7 +5,18 @@ libefi_la_CFLAGS += -fvisibility=hidden noinst_LTLIBRARIES += libefi.la CPPCHECKTARGETS += libefi.la -libefi_la_SOURCES = \ +libefi_la_SOURCES = +libefi_la_LDFLAGS = + +if BUILD_MACOS +libefi_la_SOURCES += \ + %D%/rdwr_efi_macos.c +libefi_la_LDFLAGS += -framework DiskArbitration -framework CoreServices +endif + +if BUILD_LINUX +libefi_la_SOURCES += \ %D%/rdwr_efi.c +endif libefi_la_LIBADD = $(LIBUUID_LIBS) $(ZLIB_LIBS) diff --git a/lib/libicp/Makefile.am b/lib/libicp/Makefile.am index 4ba55b2158bc..257f71e3fbcd 100644 --- a/lib/libicp/Makefile.am +++ b/lib/libicp/Makefile.am @@ -9,6 +9,7 @@ nodist_libicp_la_SOURCES = \ module/icp/api/kcf_cipher.c \ module/icp/api/kcf_mac.c \ module/icp/algs/aes/aes_impl_aesni.c \ + module/icp/algs/aes/aes_impl_aesv8.c \ module/icp/algs/aes/aes_impl_generic.c \ module/icp/algs/aes/aes_impl_x86-64.c \ module/icp/algs/aes/aes_impl.c \ @@ -44,6 +45,7 @@ nodist_libicp_la_SOURCES = \ if TARGET_CPU_AARCH64 nodist_libicp_la_SOURCES += \ + module/icp/asm-aarch64/aes/aesv8-armx.S \ module/icp/asm-aarch64/blake3/b3_aarch64_sse2.S \ module/icp/asm-aarch64/blake3/b3_aarch64_sse41.S \ module/icp/asm-aarch64/sha2/sha256-armv8.S \ @@ -81,4 +83,3 @@ nodist_libicp_la_SOURCES += \ module/icp/asm-x86_64/blake3/blake3_sse2.S \ module/icp/asm-x86_64/blake3/blake3_sse41.S endif - diff --git a/lib/libnvpair/Makefile.am b/lib/libnvpair/Makefile.am index 87b8d32aa175..1c6ea889f18d 100644 --- a/lib/libnvpair/Makefile.am +++ b/lib/libnvpair/Makefile.am @@ -6,6 +6,27 @@ libnvpair_la_CFLAGS += -fvisibility=hidden %D%/libnvpair_la-libnvpair_json.$(OBJEXT) : CFLAGS += -Wno-type-limits %D%/libnvpair_la-libnvpair_json.l$(OBJEXT): CFLAGS += -Wno-type-limits + +if BUILD_MACOS +# See comment in macOS rpc/xdr.h, starting at: +# "A xdrproc_t exists for each data type which is to be encoded or decoded." +module/nvpair/nvpair.$(OBJEXT): CFLAGS += -Wno-incompatible-pointer-types +../../module/nvpair/nvpair.$(OBJEXT): CFLAGS += -Wwgat -Wno-incompatible-pointer-types +%D%/libnvpair_la-nvpair.$(OBJEXT) : CFLAGS += -Wno-type-limits +nvpair.$(OBJEXT) : CFLAGS += -Wno-type-limits2 +libnvpair_la-nvpair.$(OBJEXT) : CFLAGS += -Wno-type-limits3 +%D%/../../module/nvpair/libnvpair_la-nvpair.$(OBJEXT) : CFLAGS += -Wno-type-limits4 +endif +nvpair.lo : CFLAGS += -Wno-type-limits2 +libnvpair_la-nvpair.lo : CFLAGS += -Wno-type-limits2 +%D%/libnvpair_la-nvpair.lo : CFLAGS += -Wno-type-limits2 +../../module/nvpair/nvpair.lo : CFLAGS += -Wno-type-limits2 +module/nvpair/nvpair.lo : CFLAGS += -Wno-type-limits2 +module/nvpair/libnvpair_la-nvpair.lo : CFLAGS += -Wno-type-limits2 + + + + lib_LTLIBRARIES += libnvpair.la CPPCHECKTARGETS += libnvpair.la @@ -26,9 +47,11 @@ libnvpair_la_LIBADD += $(LIBTIRPC_LIBS) $(LTLIBINTL) libnvpair_la_LDFLAGS = +if !BUILD_MACOS if !ASAN_ENABLED libnvpair_la_LDFLAGS += -Wl,-z,defs endif +endif libnvpair_la_LDFLAGS += -version-info 3:0:0 diff --git a/lib/libshare/Makefile.am b/lib/libshare/Makefile.am index 48d8cb832428..c61b6a44e6b2 100644 --- a/lib/libshare/Makefile.am +++ b/lib/libshare/Makefile.am @@ -25,3 +25,9 @@ libshare_la_SOURCES += \ %D%/os/freebsd/nfs.c \ %D%/os/freebsd/smb.c endif + +if BUILD_MACOS +libshare_la_SOURCES += \ + %D%/os/macos/nfs.c \ + %D%/os/macos/smb.c +endif diff --git a/lib/libspl/Makefile.am b/lib/libspl/Makefile.am index 822bef7e7a8d..9ab692418e2c 100644 --- a/lib/libspl/Makefile.am +++ b/lib/libspl/Makefile.am @@ -39,6 +39,13 @@ libspl_la_SOURCES += \ %D%/os/freebsd/zone.c endif +if BUILD_MACOS +libspl_la_SOURCES += \ + %D%/os/macos/getexecname.c \ + %D%/os/macos/gethostid.c \ + %D%/os/macos/zone.c +endif + libspl_la_LIBADD = \ libspl_assert.la diff --git a/lib/libspl/atomic.c b/lib/libspl/atomic.c index 8cc350710ba0..103949ecdffa 100644 --- a/lib/libspl/atomic.c +++ b/lib/libspl/atomic.c @@ -343,13 +343,13 @@ atomic_swap_ptr(volatile void *target, void *bits) uint64_t atomic_load_64(volatile uint64_t *target) { - return (__atomic_load_n(target, __ATOMIC_RELAXED)); + return (__atomic_load_n(target, __ATOMIC_ACQUIRE)); } void atomic_store_64(volatile uint64_t *target, uint64_t bits) { - return (__atomic_store_n(target, bits, __ATOMIC_RELAXED)); + return (__atomic_store_n(target, bits, __ATOMIC_RELEASE)); } #endif @@ -390,11 +390,11 @@ membar_sync(void) void membar_producer(void) { - __atomic_thread_fence(__ATOMIC_RELEASE); + __atomic_thread_fence(__ATOMIC_SEQ_CST); } void membar_consumer(void) { - __atomic_thread_fence(__ATOMIC_ACQUIRE); + __atomic_thread_fence(__ATOMIC_SEQ_CST); } diff --git a/lib/libspl/include/Makefile.am b/lib/libspl/include/Makefile.am index 2c1d21edf19d..872b105328ce 100644 --- a/lib/libspl/include/Makefile.am +++ b/lib/libspl/include/Makefile.am @@ -101,3 +101,44 @@ libspl_sys_dktpdir = $(libspl_sysdir)/dktp libspl_sys_dktp_HEADERS = \ %D%/sys/dktp/fdisk.h +if BUILD_MACOS + +# macOS does not promote header location (legacy) +libsplmacosdir = $(includedir)/libspl/os/macos +libsplmacos_HEADERS = \ + %D%/os/macos/dirent.h \ + %D%/os/macos/libdiskmgt.h \ + %D%/os/macos/mntent.h \ + %D%/os/macos/poll.h \ + %D%/os/macos/pthread.h \ + %D%/os/macos/stdio.h \ + %D%/os/macos/stdlib.h \ + %D%/os/macos/string.h \ + %D%/os/macos/synch.h \ + %D%/os/macos/time.h \ + %D%/os/macos/unistd.h + +libsplmacos_machdir = $(libsplmacosdir)/mach +libsplmacos_mach_HEADERS = \ + %D%/os/macos/mach/boolean.h \ + %D%/os/macos/mach/task.h + +libsplmacos_sysdir = $(libsplmacosdir)/sys +libsplmacos_sys_HEADERS = \ + %D%/os/macos/sys/byteorder.h \ + %D%/os/macos/sys/errno.h \ + %D%/os/macos/sys/fcntl.h \ + %D%/os/macos/sys/file.h \ + %D%/os/macos/sys/kernel_types.h \ + %D%/os/macos/sys/misc.h \ + %D%/os/macos/sys/mnttab.h \ + %D%/os/macos/sys/mount.h \ + %D%/os/macos/sys/param.h \ + %D%/os/macos/sys/stat.h \ + %D%/os/macos/sys/sysmacros.h \ + %D%/os/macos/sys/time.h \ + %D%/os/macos/sys/uio.h \ + %D%/os/macos/sys/vfs.h \ + %D%/os/macos/sys/xattr.h \ + %D%/os/macos/sys/zfs_context_os.h +endif diff --git a/lib/libspl/include/sys/asm_linkage.h b/lib/libspl/include/sys/asm_linkage.h index 84aa0854a9ff..ba08164c954a 100644 --- a/lib/libspl/include/sys/asm_linkage.h +++ b/lib/libspl/include/sys/asm_linkage.h @@ -29,8 +29,11 @@ #if defined(__i386) || defined(__amd64) +#if defined(__APPLE__) +#include /* XX64 x86/sys/asm_linkage.h */ +#else #include /* XX64 x86/sys/asm_linkage.h */ - +#endif #endif #if defined(_KERNEL) && defined(HAVE_KERNEL_OBJTOOL) diff --git a/lib/libspl/include/sys/dkio.h b/lib/libspl/include/sys/dkio.h index 9517b580bdf5..72e9eeeb71f8 100644 --- a/lib/libspl/include/sys/dkio.h +++ b/lib/libspl/include/sys/dkio.h @@ -161,7 +161,9 @@ struct dk_geom { */ #define DKIOCGGEOM (DKIOC|1) /* Get geometry */ #define DKIOCINFO (DKIOC|3) /* Get info */ +#ifndef DKIOCEJECT #define DKIOCEJECT (DKIOC|6) /* Generic 'eject' */ +#endif #define DKIOCGVTOC (DKIOC|11) /* Get VTOC */ #define DKIOCSVTOC (DKIOC|12) /* Set VTOC & Write to Disk */ diff --git a/lib/libspl/include/sys/isa_defs.h b/lib/libspl/include/sys/isa_defs.h index 302f31e989cb..4b3fbe56318e 100644 --- a/lib/libspl/include/sys/isa_defs.h +++ b/lib/libspl/include/sys/isa_defs.h @@ -128,13 +128,16 @@ extern "C" { /* arm arch specific defines */ #elif defined(__arm) || defined(__arm__) +/* We can NOT define __arm / __arm__ on macOS, it is only for 32bit */ #if !defined(__arm) #define __arm #endif +#ifndef __APPLE__ #if !defined(__arm__) #define __arm__ #endif +#endif #if !defined(_ILP32) #define _ILP32 diff --git a/lib/libspl/include/sys/simd.h b/lib/libspl/include/sys/simd.h index 41f9df506468..3b14d554c9c4 100644 --- a/lib/libspl/include/sys/simd.h +++ b/lib/libspl/include/sys/simd.h @@ -49,6 +49,27 @@ static inline unsigned long getauxval(unsigned long key) #define AT_HWCAP 16 #define AT_HWCAP2 26 extern unsigned long getauxval(unsigned long type); +#elif defined(__APPLE__) +#include +#define AT_HWCAP 0 +static inline unsigned long getauxval(unsigned long key) +{ + (void) key; + /* HWCAP_ are all defined halfway down this file */ + unsigned long val = 1 /* HWCAP_FP */; + int intval; + size_t intvallen = sizeof (intval); + int err; + err = sysctlbyname("hw.optional.arm.FEAT_SHA256", + &intval, &intvallen, NULL, 0); + if (err == 0 && intval != 0) + val |= 0x00000040; /* SHA256 */ + err = sysctlbyname("hw.optional.arm.FEAT_SHA512", + &intval, &intvallen, NULL, 0); + if (err == 0 && intval != 0) + val |= 0x00200000; /* SHA512 */ + return (val); +} #endif /* __linux__ */ #endif /* arm || aarch64 || powerpc */ @@ -516,6 +537,7 @@ zfs_sha256_available(void) #define kfpu_end() do {} while (0) #define HWCAP_FP 0x00000001 +#define HWCAP_AES 0x00000008 #define HWCAP_SHA2 0x00000040 #define HWCAP_SHA512 0x00200000 @@ -549,6 +571,16 @@ zfs_sha512_available(void) return (hwcap & HWCAP_SHA512); } +/* + * Check if AESV8 is available + */ +static inline boolean_t +zfs_aesv8_available(void) +{ + unsigned long hwcap = getauxval(AT_HWCAP); + return (hwcap & HWCAP_AES); +} + #elif defined(__powerpc__) #define kfpu_allowed() 0 diff --git a/lib/libspl/include/sys/uio.h b/lib/libspl/include/sys/uio.h index e9e21819d4f8..94088a5cf4fb 100644 --- a/lib/libspl/include/sys/uio.h +++ b/lib/libspl/include/sys/uio.h @@ -43,14 +43,10 @@ #include #include_next -#ifdef __APPLE__ -#include -#endif - #include typedef struct iovec iovec_t; -#if defined(__linux__) || defined(__APPLE__) +#if defined(__linux__) typedef enum zfs_uio_rw { UIO_READ = 0, UIO_WRITE = 1, diff --git a/lib/libuutil/Makefile.am b/lib/libuutil/Makefile.am index b973ce3cca4c..18ecc74144c1 100644 --- a/lib/libuutil/Makefile.am +++ b/lib/libuutil/Makefile.am @@ -19,9 +19,11 @@ libuutil_la_LIBADD += $(LTLIBINTL) libuutil_la_LDFLAGS = -pthread +if !BUILD_MACOS if !ASAN_ENABLED libuutil_la_LDFLAGS += -Wl,-z,defs endif +endif libuutil_la_LDFLAGS += -version-info 3:0:0 diff --git a/lib/libzfs/Makefile.am b/lib/libzfs/Makefile.am index 5e74d908de3d..697da3a30c87 100644 --- a/lib/libzfs/Makefile.am +++ b/lib/libzfs/Makefile.am @@ -33,6 +33,15 @@ dist_libzfs_la_SOURCES += \ %D%/os/linux/libzfs_util_os.c endif +if BUILD_MACOS +dist_libzfs_la_SOURCES += \ + %D%/os/macos/libzfs_dataset_os.c \ + %D%/os/macos/libzfs_getmntany.c \ + %D%/os/macos/libzfs_mount_os.c \ + %D%/os/macos/libzfs_pool_os.c \ + %D%/os/macos/libzfs_util_os.c +endif + nodist_libzfs_la_SOURCES = \ module/zcommon/cityhash.c \ module/zcommon/zfeature_common.c \ @@ -61,14 +70,22 @@ libzfs_la_LIBADD += -lrt -lm $(LIBCRYPTO_LIBS) $(ZLIB_LIBS) $(LIBFETCH_LIBS) $(L libzfs_la_LDFLAGS = -pthread +if !BUILD_MACOS if !ASAN_ENABLED libzfs_la_LDFLAGS += -Wl,-z,defs endif +endif if BUILD_FREEBSD libzfs_la_LIBADD += -lutil -lgeom endif +if BUILD_MACOS +libzfs_la_LIBADD += \ + libdiskmgt.la +libzfs_la_LDFLAGS += -lobjc -framework IOKit -framework Foundation -lssl +endif + libzfs_la_LDFLAGS += -version-info 5:0:1 pkgconfig_DATA += %D%/libzfs.pc diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi index 083c27be7d31..2410b7034bbe 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -1868,7 +1868,12 @@ - + + + + + + diff --git a/lib/libzfs/libzfs_crypto.c b/lib/libzfs/libzfs_crypto.c index 8f2a50d55e87..8dd842dd6c27 100644 --- a/lib/libzfs/libzfs_crypto.c +++ b/lib/libzfs/libzfs_crypto.c @@ -611,7 +611,9 @@ get_key_material_https(libzfs_handle_t *hdl, const char *uri, (void) unlink(path); free(path); +#ifdef O_TMPFILE kfdok: +#endif if ((key = fdopen(kfd, "r+")) == NULL) { ret = errno; (void) close(kfd); diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c index 727efc5a91ad..9a00bf9ec2f7 100644 --- a/lib/libzfs/libzfs_dataset.c +++ b/lib/libzfs/libzfs_dataset.c @@ -33,6 +33,7 @@ * Copyright (c) 2019 Datto Inc. * Copyright (c) 2019, loli10K * Copyright (c) 2021 Matt Fiddaman + * Copyright (c) 2020, Jorgen Lundman */ #include @@ -2219,6 +2220,18 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src, mntopt_off = MNTOPT_NONBMAND; break; +#ifdef __APPLE__ /* So they don't need to have MNTOPT_BROWSE */ + case ZFS_PROP_BROWSE: + mntopt_on = MNTOPT_BROWSE; + mntopt_off = MNTOPT_NOBROWSE; + break; + + case ZFS_PROP_IGNOREOWNER: + mntopt_on = MNTOPT_NOOWNERS; + mntopt_off = MNTOPT_OWNERS; + break; +#endif + default: break; } @@ -2255,6 +2268,10 @@ get_numeric_property(zfs_handle_t *zhp, zfs_prop_t prop, zprop_source_t *src, case ZFS_PROP_SETUID: #ifndef __FreeBSD__ case ZFS_PROP_XATTR: +#endif +#ifdef __APPLE__ + case ZFS_PROP_BROWSE: + case ZFS_PROP_IGNOREOWNER: #endif case ZFS_PROP_NBMAND: *val = getprop_uint64(zhp, prop, source); @@ -2765,6 +2782,33 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen, relpath[0] != '\0')) str++; +#ifdef __APPLE__ + /* + * On OSX by default we mount pools under /Volumes + * unless the dataset property mountpoint specifies + * otherwise. + * In addition to this, there is an undocumented + * environment variable __ZFS_MAIN_MOUNTPOINT_DIR, + * used mainly by the testing environment, as it + * expects "/" by default. + */ + const char *default_mountpoint; + default_mountpoint = + getenv("__ZFS_MAIN_MOUNTPOINT_DIR"); + if (!default_mountpoint) + default_mountpoint = "/Volumes/"; + + if (relpath[0] == '\0') + (void) snprintf(propbuf, proplen, "%s%s", + root, str); + else + (void) snprintf(propbuf, proplen, "%s%s%s%s", + root, str, source == NULL || + source[0] == '\0' ? default_mountpoint : + "/", relpath); + +#else + if (relpath[0] == '\0') (void) snprintf(propbuf, proplen, "%s%s", root, str); @@ -2772,6 +2816,8 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen, (void) snprintf(propbuf, proplen, "%s%s%s%s", root, str, relpath[0] == '@' ? "" : "/", relpath); +#endif /* APPLE */ + } else { /* 'legacy' or 'none' */ (void) strlcpy(propbuf, str, proplen); @@ -3911,7 +3957,23 @@ zfs_destroy(zfs_handle_t *zhp, boolean_t defer) error = lzc_destroy_snaps(nv, defer, NULL); fnvlist_free(nv); } else { + +#ifdef __APPLE__ + /* DiskArbitrationd gets in the way a lot */ + int retry = 0; + do { + if ((retry++) != 1) { + sleep(1); + } +#endif + error = lzc_destroy(zhp->zfs_name); + +#ifdef __APPLE__ + } while ((error == EBUSY) && (retry <= 5)); +#endif + + } if (error != 0 && error != ENOENT) { diff --git a/lib/libzfs/libzfs_diff.c b/lib/libzfs/libzfs_diff.c index da2b26ef99ce..4b743bc593aa 100644 --- a/lib/libzfs/libzfs_diff.c +++ b/lib/libzfs/libzfs_diff.c @@ -761,7 +761,12 @@ zfs_show_diffs(zfs_handle_t *zhp, int outfd, const char *fromsnap, return (-1); } +#if defined(__APPLE__) + /* Can't do IO on pipes, open fds mkfifo */ + if (libzfs_macos_pipefd(&pipefd[0], &pipefd[1])) { +#else if (pipe2(pipefd, O_CLOEXEC)) { +#endif zfs_error_aux(zhp->zfs_hdl, "%s", strerror(errno)); teardown_differ_info(&di); return (zfs_error(zhp->zfs_hdl, EZFS_PIPEFAILED, errbuf)); diff --git a/lib/libzfs/libzfs_iter.c b/lib/libzfs/libzfs_iter.c index 452d8fd6ab71..e1404475a3c1 100644 --- a/lib/libzfs/libzfs_iter.c +++ b/lib/libzfs/libzfs_iter.c @@ -634,17 +634,19 @@ zfs_iter_mounted(zfs_handle_t *zhp, zfs_iter_f func, void *data) continue; if ((mtab_zhp = zfs_open(zhp->zfs_hdl, entry.mnt_special, - ZFS_TYPE_FILESYSTEM)) == NULL) + ZFS_TYPE_FILESYSTEM|ZFS_TYPE_SNAPSHOT)) == 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; + if (mtab_zhp->zfs_type != ZFS_TYPE_SNAPSHOT) { + 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); } diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c index e9bc78aa8d39..ba2becec0186 100644 --- a/lib/libzfs/libzfs_sendrecv.c +++ b/lib/libzfs/libzfs_sendrecv.c @@ -1252,6 +1252,12 @@ dump_snapshot(zfs_handle_t *zhp, void *arg) } if (!sdd->dryrun) { + +#if defined(__APPLE__) + /* Can't do IO on pipes, possibly wrap fd in domain socket */ + libzfs_macos_wrapfd(&sdd->outfd, B_TRUE); +#endif + /* * If progress reporting is requested, spawn a new thread to * poll ZFS_IOC_SEND_PROGRESS at a regular interval. @@ -1961,6 +1967,11 @@ zfs_send_resume_impl_cb_impl(libzfs_handle_t *hdl, sendflags_t *flags, SEND_PROGRESS_THREAD_PARENT_BLOCK(&oldmask); } +#if defined(__APPLE__) + /* Can't do IO on pipes, possibly wrap fd in domain socket */ + libzfs_macos_wrapfd(&outfd, B_TRUE); +#endif + error = lzc_send_resume_redacted(zhp->zfs_name, fromname, outfd, lzc_flags, resumeobj, resumeoff, redact_book); if (redact_book != NULL) @@ -2747,6 +2758,11 @@ zfs_send_one_cb_impl(zfs_handle_t *zhp, const char *from, int fd, if (flags->dryrun) return (0); +#if defined(__APPLE__) + /* Can't do IO on pipes, possibly wrap fd in domain socket */ + libzfs_macos_wrapfd(&fd, B_TRUE); +#endif + /* * If progress reporting is requested, spawn a new thread to poll * ZFS_IOC_SEND_PROGRESS at a regular interval. @@ -2830,6 +2846,7 @@ zfs_send_one_cb_impl(zfs_handle_t *zhp, const char *from, int fd, return (zfs_standard_error(hdl, errno, errbuf)); } } + return (err != 0); } @@ -5019,6 +5036,11 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap, goto out; } +#if defined(__APPLE__) + /* Can't do IO on pipes, possibly wrap fd in domain socket */ + libzfs_macos_wrapfd(&infd, B_FALSE); +#endif + if (flags->heal) { err = ioctl_err = lzc_receive_with_heal(destsnap, rcvprops, oxprops, wkeydata, wkeylen, origin, flags->force, @@ -5448,6 +5470,11 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, return (zfs_error(hdl, EZFS_NOENT, errbuf)); } +#if defined(__APPLE__) + /* Can't do IO on pipes, possibly wrap fd in domain socket */ + libzfs_macos_wrapfd(&infd, B_FALSE); +#endif + /* read in the BEGIN record */ if (0 != (err = recv_read(hdl, infd, &drr, sizeof (drr), B_FALSE, &zcksum))) diff --git a/lib/libzfs_core/Makefile.am b/lib/libzfs_core/Makefile.am index d1c6fb86d186..e392e34f767d 100644 --- a/lib/libzfs_core/Makefile.am +++ b/lib/libzfs_core/Makefile.am @@ -23,6 +23,13 @@ nodist_libzfs_core_la_SOURCES = \ module/os/freebsd/zfs/zfs_ioctl_compat.c endif +if BUILD_MACOS +libzfs_core_la_CPPFLAGS += -I$(top_srcdir)/include/os/macos/zfs + +libzfs_core_la_SOURCES += \ + %D%/os/macos/libzfs_core_ioctl.c +endif + libzfs_core_la_LIBADD = \ libnvpair.la \ libspl.la @@ -31,9 +38,11 @@ libzfs_core_la_LIBADD += $(LTLIBINTL) libzfs_core_la_LDFLAGS = -pthread +if !BUILD_MACOS if !ASAN_ENABLED libzfs_core_la_LDFLAGS += -Wl,-z,defs endif +endif if BUILD_FREEBSD libzfs_core_la_LIBADD += -lutil -lgeom diff --git a/lib/libzfs_core/libzfs_core.c b/lib/libzfs_core/libzfs_core.c index 01d803e21db0..1c4f3898a043 100644 --- a/lib/libzfs_core/libzfs_core.c +++ b/lib/libzfs_core/libzfs_core.c @@ -94,6 +94,9 @@ #if __FreeBSD__ #define BIG_PIPE_SIZE (64 * 1024) /* From sys/pipe.h */ #endif +#if __APPLE__ +#define BIG_PIPE_SIZE (64 * 1024) +#endif static int g_fd = -1; static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; diff --git a/lib/libzfsbootenv/Makefile.am b/lib/libzfsbootenv/Makefile.am index 118f154821fc..8913f07983aa 100644 --- a/lib/libzfsbootenv/Makefile.am +++ b/lib/libzfsbootenv/Makefile.am @@ -18,9 +18,11 @@ libzfsbootenv_la_LIBADD = \ libzfsbootenv_la_LDFLAGS = +if !BUILD_MACOS if !ASAN_ENABLED libzfsbootenv_la_LDFLAGS += -Wl,-z,defs endif +endif libzfsbootenv_la_LDFLAGS += -version-info 1:0:0 diff --git a/lib/libzpool/Makefile.am b/lib/libzpool/Makefile.am index 58d7f07527aa..4ef6d4b87bbb 100644 --- a/lib/libzpool/Makefile.am +++ b/lib/libzpool/Makefile.am @@ -197,9 +197,11 @@ libzpool_la_LIBADD += $(LIBCLOCK_GETTIME) $(ZLIB_LIBS) -ldl -lm libzpool_la_LDFLAGS = -pthread +if !BUILD_MACOS if !ASAN_ENABLED libzpool_la_LDFLAGS += -Wl,-z,defs endif +endif if BUILD_FREEBSD libzpool_la_LIBADD += -lgeom diff --git a/lib/libzutil/Makefile.am b/lib/libzutil/Makefile.am index 519906235f7f..cbd0363279a7 100644 --- a/lib/libzutil/Makefile.am +++ b/lib/libzutil/Makefile.am @@ -28,6 +28,12 @@ libzutil_la_SOURCES += \ %D%/os/freebsd/zutil_import_os.c endif +if BUILD_MACOS +libzutil_la_SOURCES += \ + %D%/os/macos/zutil_device_path_os.c \ + %D%/os/macos/zutil_import_os.c +endif + libzutil_la_LIBADD = \ libavl.la \ libtpool.la \ @@ -40,4 +46,9 @@ libzutil_la_LIBADD += \ -lrt endif +if BUILD_MACOS +libzutil_la_LIBADD += \ + libefi.la +endif + libzutil_la_LIBADD += -lm $(LIBBLKID_LIBS) $(LIBUDEV_LIBS) diff --git a/lib/libzutil/zutil_device_path.c b/lib/libzutil/zutil_device_path.c index 0425018e1022..4447f4af86c7 100644 --- a/lib/libzutil/zutil_device_path.c +++ b/lib/libzutil/zutil_device_path.c @@ -24,6 +24,7 @@ */ #include +#include #include #include #include @@ -158,6 +159,8 @@ zfs_strcmp_pathname(const char *name, const char *cmp, int wholedisk) char path_name[MAXPATHLEN]; char cmp_name[MAXPATHLEN]; char *dir, *tmp = NULL; + char *d, *b; + const char *dpath, *bname; /* Strip redundant slashes if they exist due to ZPOOL_IMPORT_PATH */ cmp_name[0] = '\0'; @@ -182,8 +185,39 @@ zfs_strcmp_pathname(const char *name, const char *cmp, int wholedisk) return (ENOMEM); } - if ((path_len != cmp_len) || strcmp(path_name, cmp_name)) - return (ENOENT); + if ((path_len == cmp_len) && strcmp(path_name, cmp_name) == 0) + return (0); + else { + int idx; + d = strdup(path_name); + b = strdup(path_name); + idx = zfs_dirnamelen(d); + if (idx != -1) + d[idx] = 0; + dpath = d; + bname = zfs_basename(b); + if (realpath(dpath, path_name) == NULL) { + (void) fprintf(stderr, "cannot resolve path '%s'\n", + dpath); + free(d); + free(b); + return (ENOENT); + } + + if (strcmp(dpath, path_name) == 0) { + free(d); + free(b); + return (ENOENT); // We already tried this path + } + + strlcat(path_name, "/", sizeof (path_name)); + path_len = strlcat(path_name, bname, sizeof (path_name)); + free(d); + free(b); + + if ((path_len == cmp_len) && strcmp(path_name, cmp_name) == 0) + return (0); + } - return (0); + return (ENOENT); } diff --git a/lib/libzutil/zutil_import.c b/lib/libzutil/zutil_import.c index 19d8a4742813..15a1c9a4c178 100644 --- a/lib/libzutil/zutil_import.c +++ b/lib/libzutil/zutil_import.c @@ -910,6 +910,12 @@ get_configs(libpc_handle_t *hdl, pool_list_t *pl, boolean_t active_ok, return (NULL); } +#ifdef __APPLE__ + +/* We have our own zpool_read_label() / label_offset() */ + +#else + /* * Return the offset of the given label. */ @@ -921,6 +927,9 @@ label_offset(uint64_t size, int l) 0 : size - VDEV_LABELS * sizeof (vdev_label_t))); } +#ifdef __APPLE__ +/* We have our own */ +#else /* * The same description applies as to zpool_read_label below, * except here we do it without aio, presumably because an aio call @@ -997,6 +1006,7 @@ zpool_read_label_slow(int fd, nvlist_t **config, int *num_labels) return (0); } +#endif /* APPLE */ /* * Given a file descriptor, read the label information and return an nvlist @@ -1133,6 +1143,7 @@ zpool_read_label(int fd, nvlist_t **config, int *num_labels) return (0); #endif } +#endif /* APPLE */ /* * Sorted by full path and then vdev guid to allow for multiple entries with @@ -1239,6 +1250,12 @@ zpool_find_import_scan_add_slice(libpc_handle_t *hdl, pthread_mutex_t *lock, slice->rn_lock = lock; slice->rn_avl = cache; slice->rn_hdl = hdl; +#ifdef __APPLE__ + /* Prefer diskX over rdiskX: involve os/ somehow? */ + if (name[0] == 'r') + slice->rn_order = order + IMPORT_ORDER_DEFAULT; + else +#endif slice->rn_order = order + IMPORT_ORDER_SCAN_OFFSET; slice->rn_labelpaths = B_FALSE; diff --git a/lib/libzutil/zutil_pool.c b/lib/libzutil/zutil_pool.c index 288a0033cd13..c3383442961c 100644 --- a/lib/libzutil/zutil_pool.c +++ b/lib/libzutil/zutil_pool.c @@ -28,6 +28,7 @@ #include #include #include +#include #include From 57a596a5bd528079915f442925ea50360989ddb0 Mon Sep 17 00:00:00 2001 From: Jorgen Lundman Date: Mon, 23 Oct 2023 09:13:43 +0900 Subject: [PATCH 6/9] 6: etc/* Signed-off-by: Jorgen Lundman --- etc/launchd.d/.gitignore | 1 + etc/launchd.d/Makefile.am | 19 + etc/launchd.d/zpool-import-all.sh.in | 60 ++ etc/launchd/Makefile.am | 4 + etc/launchd/daemons/.gitignore | 5 + etc/launchd/daemons/Makefile.am | 33 + .../org.openzfsonosx.InvariantDisks.plist.in | 16 + .../org.openzfsonosx.zconfigd.plist.in | 20 + .../daemons/org.openzfsonosx.zed.plist.in | 21 + ...org.openzfsonosx.zpool-import-all.plist.in | 18 + .../org.openzfsonosx.zpool-import.plist.in | 24 + etc/paths.d/.gitignore | 1 + etc/paths.d/Makefile.am | 20 + etc/paths.d/zfs-path.in | 2 + lib/libspl/asm-x86_64/atomic.S | 693 ++++++++++++++++++ 15 files changed, 937 insertions(+) create mode 100644 etc/launchd.d/.gitignore create mode 100644 etc/launchd.d/Makefile.am create mode 100755 etc/launchd.d/zpool-import-all.sh.in create mode 100644 etc/launchd/Makefile.am create mode 100644 etc/launchd/daemons/.gitignore create mode 100644 etc/launchd/daemons/Makefile.am create mode 100644 etc/launchd/daemons/org.openzfsonosx.InvariantDisks.plist.in create mode 100644 etc/launchd/daemons/org.openzfsonosx.zconfigd.plist.in create mode 100644 etc/launchd/daemons/org.openzfsonosx.zed.plist.in create mode 100644 etc/launchd/daemons/org.openzfsonosx.zpool-import-all.plist.in create mode 100644 etc/launchd/daemons/org.openzfsonosx.zpool-import.plist.in create mode 100644 etc/paths.d/.gitignore create mode 100644 etc/paths.d/Makefile.am create mode 100644 etc/paths.d/zfs-path.in create mode 100644 lib/libspl/asm-x86_64/atomic.S diff --git a/etc/launchd.d/.gitignore b/etc/launchd.d/.gitignore new file mode 100644 index 000000000000..f11cc9cf5e4f --- /dev/null +++ b/etc/launchd.d/.gitignore @@ -0,0 +1 @@ +zpool-import-all.sh diff --git a/etc/launchd.d/Makefile.am b/etc/launchd.d/Makefile.am new file mode 100644 index 000000000000..064602465dc9 --- /dev/null +++ b/etc/launchd.d/Makefile.am @@ -0,0 +1,19 @@ + +noinst_launch_d_DATA = \ + %D%/zpool-import-all.sh.in +noinst_launch_ddir = \ + $(launchdscriptdir)/ + +do_subst = -$(SED) -e 's,@bindir\@,$(bindir),g' \ + -e 's,@runstatedir\@,$(runstatedir),g' \ + -e 's,@sbindir\@,$(sbindir),g' \ + -e 's,@sysconfdir\@,$(sysconfdir),g' \ + -e 's,@launchdscriptdir\@,$(launchdscriptdir),g' + +CLEANFILES += %D%/zpool-import-all.sh + +INSTALL_DATA_HOOKS += install-zpool-import + +install-zpool-import: %D%/zpool-import-all.sh.in + $(do_subst) < %D%/zpool-import-all.sh.in > $(DESTDIR)/$(launchdscriptdir)/zpool-import-all.sh + chmod +x $(DESTDIR)/$(launchdscriptdir)/zpool-import-all.sh diff --git a/etc/launchd.d/zpool-import-all.sh.in b/etc/launchd.d/zpool-import-all.sh.in new file mode 100755 index 000000000000..7df5eba0ca64 --- /dev/null +++ b/etc/launchd.d/zpool-import-all.sh.in @@ -0,0 +1,60 @@ +#!/bin/bash + +echo "+zpool-import-all.sh" +date +export ZPOOL_IMPORT_ALL_COOKIE=/var/run/org.openzfsonosx.zpool-import-all.didRun +export INVARIANT_DISKS_IDLE_FILE=/var/run/disk/invariant.idle +export TIMEOUT_SECONDS=60 +export MAXIMUM_SLEEP_ITERATIONS=$((${TIMEOUT_SECONDS} * 10)) + +/usr/bin/time /usr/sbin/system_profiler SPParallelATADataType SPCardReaderDataType SPFibreChannelDataType SPFireWireDataType SPHardwareRAIDDataType SPNetworkDataType SPPCIDataType SPParallelSCSIDataType SPSASDataType SPSerialATADataType SPStorageDataType SPThunderboltDataType SPUSBDataType SPNetworkVolumeDataType 1>/dev/null 2>&1 + +/bin/sync + +echo "Waiting up to ${TIMEOUT_SECONDS} seconds for the InvariantDisks idle file ${INVARIANT_DISKS_IDLE_FILE} to exist" + +i=0 +while [ "${i}" -lt "${MAXIMUM_SLEEP_ITERATIONS}" -a ! -e "${INVARIANT_DISKS_IDLE_FILE}" ] +do + i=$((i+1)) + sleep .1 +done + +if [ -e "${INVARIANT_DISKS_IDLE_FILE}" ] +then + echo "Found ${INVARIANT_DISKS_IDLE_FILE} after ${i} iterations of sleeping 0.1 seconds" +else + echo "File ${INVARIANT_DISKS_IDLE_FILE} not found within ${TIMEOUT_SECONDS} seconds" +fi +date + +if [ -f "@sysconfdir@/zfs/zsysctl.conf" ]; then + @sbindir@/zsysctl -f @sysconfdir@/zfs/zsysctl.conf +fi + +# check to see if we have been instructed not to try to import at all +if [ -f /etc/zfs/noautoimport ]; +then + echo "/etc/zfs/noautoimport exits, exiting"; + exit 0; +fi + +date +echo "-zpool-import-all.sh" + +sleep 10 +echo "Loading and starting org.openzfsonosx.zpool-import" +date + +/bin/launchctl load /Library/LaunchDaemons/org.openzfsonosx.zpool-import.plist +/bin/launchctl kickstart -kp system/org.openzfsonosx.zpool-import + +echo Status: $? + +echo "Touching the file ${ZPOOL_IMPORT_ALL_COOKIE}" +touch "${ZPOOL_IMPORT_ALL_COOKIE}" + +date +echo "-zpool-import-all.sh" + +exit 0 diff --git a/etc/launchd/Makefile.am b/etc/launchd/Makefile.am new file mode 100644 index 000000000000..70047aa02e36 --- /dev/null +++ b/etc/launchd/Makefile.am @@ -0,0 +1,4 @@ +launchddaemondir = /Library/LaunchDaemons +launchdscriptdir = ${libexecdir}/zfs/launchd.d + +include $(srcdir)/%D%/daemons/Makefile.am diff --git a/etc/launchd/daemons/.gitignore b/etc/launchd/daemons/.gitignore new file mode 100644 index 000000000000..8b43b0f2a1fd --- /dev/null +++ b/etc/launchd/daemons/.gitignore @@ -0,0 +1,5 @@ +org.openzfsonosx.zconfigd.plist +org.openzfsonosx.zed.plist +org.openzfsonosx.zpool-import.plist +org.openzfsonosx.zpool-import-all.plist +org.openzfsonosx.InvariantDisks.plist diff --git a/etc/launchd/daemons/Makefile.am b/etc/launchd/daemons/Makefile.am new file mode 100644 index 000000000000..5f16a4c1fce5 --- /dev/null +++ b/etc/launchd/daemons/Makefile.am @@ -0,0 +1,33 @@ + +CLEANFILES += $(launchddaemon_DATA) + +launchddaemon_DATA = \ + $(top_srcdir)/etc/launchd/daemons/org.openzfsonosx.zconfigd.plist \ + $(top_srcdir)/etc/launchd/daemons/org.openzfsonosx.zed.plist \ + $(top_srcdir)/etc/launchd/daemons/org.openzfsonosx.zpool-import.plist \ + $(top_srcdir)/etc/launchd/daemons/org.openzfsonosx.zpool-import-all.plist \ + $(top_srcdir)/etc/launchd/daemons/org.openzfsonosx.InvariantDisks.plist + +dist_launchddaemon_DATA = \ + %D%/org.openzfsonosx.zconfigd.plist.in \ + %D%/org.openzfsonosx.zed.plist.in \ + %D%/org.openzfsonosx.zpool-import.plist.in \ + %D%/org.openzfsonosx.zpool-import-all.plist.in \ + %D%/org.openzfsonosx.InvariantDisks.plist.in + +%D%/org.openzfsonosx.zconfigd.plist: %D%/org.openzfsonosx.zconfigd.plist.in +%D%/org.openzfsonosx.zed.plist: %D%/org.openzfsonosx.zed.plist.in +%D%/org.openzfsonosx.zpool-import.plist: %D%/org.openzfsonosx.zpool-import.plist.in +%D%/org.openzfsonosx.zpool-import-all.plist: %D%/org.openzfsonosx.zpool-import-all.plist.in +%D%/org.openzfsonosx.InvariantDisks.plist: %D%/org.openzfsonosx.InvariantDisks.plist.in + +$(launchddaemon_DATA): + -$(SED) -e 's,@bindir\@,$(bindir),g' \ + -e 's,@runstatedir\@,$(runstatedir),g' \ + -e 's,@sbindir\@,$(sbindir),g' \ + -e 's,@sysconfdir\@,$(sysconfdir),g' \ + -e 's,@launchddaemondir\@,$(launchddaemondir),g' \ + -e 's,@launchdscriptdir\@,$(launchdscriptdir),g' \ + '$@.in' >'$@' + + diff --git a/etc/launchd/daemons/org.openzfsonosx.InvariantDisks.plist.in b/etc/launchd/daemons/org.openzfsonosx.InvariantDisks.plist.in new file mode 100644 index 000000000000..4e49548aedb7 --- /dev/null +++ b/etc/launchd/daemons/org.openzfsonosx.InvariantDisks.plist.in @@ -0,0 +1,16 @@ + + + + + Label + org.openzfsonosx.InvariantDisks + ProgramArguments + + @sbindir@/InvariantDisks + + RunAtLoad + + KeepAlive + + + diff --git a/etc/launchd/daemons/org.openzfsonosx.zconfigd.plist.in b/etc/launchd/daemons/org.openzfsonosx.zconfigd.plist.in new file mode 100644 index 000000000000..51154e84661a --- /dev/null +++ b/etc/launchd/daemons/org.openzfsonosx.zconfigd.plist.in @@ -0,0 +1,20 @@ + + + + + Label + org.openzfsonosx.zconfigd + ProgramArguments + + @sbindir@/zconfigd + + RunAtLoad + + KeepAlive + + StandardErrorPath + /private/var/log/org.openzfsonosx.zconfigd.err + StandardOutPath + /private/var/log/org.openzfsonosx.zconfigd.log + + diff --git a/etc/launchd/daemons/org.openzfsonosx.zed.plist.in b/etc/launchd/daemons/org.openzfsonosx.zed.plist.in new file mode 100644 index 000000000000..1f62d4e2a9d2 --- /dev/null +++ b/etc/launchd/daemons/org.openzfsonosx.zed.plist.in @@ -0,0 +1,21 @@ + + + + + Label + org.openzfsonosx.zed + ProgramArguments + + @sbindir@/zed + -vfF + + RunAtLoad + + KeepAlive + + StandardErrorPath + /private/var/log/org.openzfsonosx.zed.err + StandardOutPath + /private/var/log/org.openzfsonosx.zed.log + + diff --git a/etc/launchd/daemons/org.openzfsonosx.zpool-import-all.plist.in b/etc/launchd/daemons/org.openzfsonosx.zpool-import-all.plist.in new file mode 100644 index 000000000000..3687eb418e33 --- /dev/null +++ b/etc/launchd/daemons/org.openzfsonosx.zpool-import-all.plist.in @@ -0,0 +1,18 @@ + + + + + Label + org.openzfsonosx.zpool-import-all + ProgramArguments + + @launchdscriptdir@/zpool-import-all.sh + + RunAtLoad + + StandardErrorPath + /private/var/log/org.openzfsonosx.zpool-import-all.err + StandardOutPath + /private/var/log/org.openzfsonosx.zpool-import-all.log + + diff --git a/etc/launchd/daemons/org.openzfsonosx.zpool-import.plist.in b/etc/launchd/daemons/org.openzfsonosx.zpool-import.plist.in new file mode 100644 index 000000000000..ee60285ba19e --- /dev/null +++ b/etc/launchd/daemons/org.openzfsonosx.zpool-import.plist.in @@ -0,0 +1,24 @@ + + + + + Label + org.openzfsonosx.zpool-import + ProgramArguments + + @sbindir@/zpool + import + -a + -d + /var/run/disk/by-id + + RunAtLoad + + LaunchOnlyOnce + + StandardErrorPath + /private/var/log/org.openzfsonosx.zpool-import-all.log + StandardOutPath + /private/var/log/org.openzfsonosx.zpool-import-all.log + + diff --git a/etc/paths.d/.gitignore b/etc/paths.d/.gitignore new file mode 100644 index 000000000000..f916e9fbee04 --- /dev/null +++ b/etc/paths.d/.gitignore @@ -0,0 +1 @@ +zfs-path diff --git a/etc/paths.d/Makefile.am b/etc/paths.d/Makefile.am new file mode 100644 index 000000000000..8b7f5998b926 --- /dev/null +++ b/etc/paths.d/Makefile.am @@ -0,0 +1,20 @@ +pathsddir = $(sysconfdir)/paths.d +pathsd_DATA = %D%/zfs-path + +paths_ddir = $(sysconfdir)/paths.d/ +dist_paths_d_DATA = \ + %D%/zfs-path + +zfs-path: %D%/zfs-path.in + +CLEANFILES += $(pathsd_DATA) +CLEANFILES += %D%/zfs-path +INSTALL_DATA_HOOKS += pathsd-data-hook +pathsd-data-hook: %D%/zfs-path.in + -$(SED) -e 's,@bindir\@,$(bindir),g' \ + -e 's,@runstatedir\@,$(runstatedir),g' \ + -e 's,@sbindir\@,$(sbindir),g' \ + -e 's,@sysconfdir\@,$(sysconfdir),g' \ + -e 's,@launchddaemondir\@,$(launchddaemondir),g' \ + -e 's,@launchdscriptdir\@,$(launchdscriptdir),g' \ + %D%/zfs-path.in > $(DESTDIR)/$(sysconfdir)/paths.d/zfs-path diff --git a/etc/paths.d/zfs-path.in b/etc/paths.d/zfs-path.in new file mode 100644 index 000000000000..5532f40754b3 --- /dev/null +++ b/etc/paths.d/zfs-path.in @@ -0,0 +1,2 @@ +@bindir@ +@sbindir@ diff --git a/lib/libspl/asm-x86_64/atomic.S b/lib/libspl/asm-x86_64/atomic.S new file mode 100644 index 000000000000..bb7dff12cc8e --- /dev/null +++ b/lib/libspl/asm-x86_64/atomic.S @@ -0,0 +1,693 @@ +/* + * 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 +#elif __FreeBSD__ +#include +#define SET_SIZE(x) +#elif __APPLE__ +#include +#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 From 136118adeb81f774b06f15f513d6582266678ed1 Mon Sep 17 00:00:00 2001 From: Andrew Innes Date: Tue, 14 Nov 2023 08:21:09 +0800 Subject: [PATCH 7/9] MacOS: Build with n+1 cpus (#22) Signed-off-by: Andrew Innes --- .github/workflows/macos-build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/macos-build.yaml b/.github/workflows/macos-build.yaml index de387c9adbfd..2818467e2338 100644 --- a/.github/workflows/macos-build.yaml +++ b/.github/workflows/macos-build.yaml @@ -31,7 +31,7 @@ jobs: ./configure CPPFLAGS="-I/usr/local/opt/gettext/include -I/usr/local/opt/openssl/include" LDFLAGS="-L/usr/local/opt/gettext/lib/ -L/usr/local/opt/openssl/lib" - name: build run: | - make -j 2 + make -j $(($(sysctl -n hw.ncpu)+1)) #- name: install # run: | # sudo make install DESTDIR=/// From ab6764d808f193c7f89a9ba3d44cb67cf94f8a66 Mon Sep 17 00:00:00 2001 From: Yurii Kolesnykov Date: Thu, 11 Jan 2024 22:05:07 +0000 Subject: [PATCH 8/9] macOS: use portable paths for brewed deps (#23) Signed-off-by: Yurii Kolesnykov --- .github/workflows/macos-build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/macos-build.yaml b/.github/workflows/macos-build.yaml index 2818467e2338..a02d698aa5df 100644 --- a/.github/workflows/macos-build.yaml +++ b/.github/workflows/macos-build.yaml @@ -28,7 +28,7 @@ jobs: - name: configure run: | #https://stackoverflow.com/a/62591864 - ./configure CPPFLAGS="-I/usr/local/opt/gettext/include -I/usr/local/opt/openssl/include" LDFLAGS="-L/usr/local/opt/gettext/lib/ -L/usr/local/opt/openssl/lib" + ./configure CPPFLAGS="-I${HOMEBREW_PREFIX}/opt/gettext/include -I${HOMEBREW_PREFIX}/opt/openssl/include" LDFLAGS="-L${HOMEBREW_PREFIX}/opt/gettext/lib/ -L${HOMEBREW_PREFIX}/opt/openssl/lib" - name: build run: | make -j $(($(sysctl -n hw.ncpu)+1)) From d9e3c90be4d4add059e8e7bd6b5854a2da8d69ae Mon Sep 17 00:00:00 2001 From: Andrew Innes Date: Fri, 12 Jan 2024 06:05:35 +0800 Subject: [PATCH 9/9] Search for C++ and Obj C compilers (#21) Signed-off-by: Andrew Innes --- configure.ac | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index 403c2762a675..7fae32671198 100644 --- a/configure.ac +++ b/configure.ac @@ -55,9 +55,9 @@ AM_PROG_AS AM_PROG_CC_C_O AX_CODE_COVERAGE _AM_PROG_TAR(pax) -AC_PROG_CXX(clang++) -AC_PROG_OBJC(clang) -AC_PROG_OBJCXX(clang++) +AC_PROG_CXX +AC_PROG_OBJC +AC_PROG_OBJCXX ZFS_AC_LICENSE ZFS_AC_CONFIG

    OeC{chd1v2XLu(Y99x=14p}Nf^wMa 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�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