aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Kyuafile52
-rw-r--r--tests/Makefile20
-rw-r--r--tests/Makefile.depend11
-rw-r--r--tests/README64
-rw-r--r--tests/etc/Makefile12
-rw-r--r--tests/etc/Makefile.depend11
-rw-r--r--tests/etc/rc.d/Makefile7
-rw-r--r--tests/etc/rc.d/Makefile.depend11
-rwxr-xr-xtests/etc/rc.d/routing_test.sh77
-rw-r--r--tests/freebsd_test_suite/macros.h71
-rw-r--r--tests/sys/Makefile25
-rw-r--r--tests/sys/Makefile.depend11
-rw-r--r--tests/sys/acl/00.sh88
-rw-r--r--tests/sys/acl/01.sh87
-rw-r--r--tests/sys/acl/02.sh93
-rw-r--r--tests/sys/acl/03.sh117
-rw-r--r--tests/sys/acl/04.sh74
-rw-r--r--tests/sys/acl/Makefile37
-rw-r--r--tests/sys/acl/Makefile.depend11
-rwxr-xr-xtests/sys/acl/aclfuzzer.sh225
-rwxr-xr-xtests/sys/acl/mktrivial.sh53
-rw-r--r--tests/sys/acl/run329
-rw-r--r--tests/sys/acl/tools-crossfs.test323
-rw-r--r--tests/sys/acl/tools-nfs4-psarc.test562
-rw-r--r--tests/sys/acl/tools-nfs4-trivial.test82
-rw-r--r--tests/sys/acl/tools-nfs4.test828
-rw-r--r--tests/sys/acl/tools-posix.test453
-rw-r--r--tests/sys/aio/Makefile16
-rw-r--r--tests/sys/aio/Makefile.depend20
-rw-r--r--tests/sys/aio/aio_kqueue_test.c220
-rw-r--r--tests/sys/aio/aio_test.c1025
-rw-r--r--tests/sys/aio/lio_kqueue_test.c238
-rw-r--r--tests/sys/aio/local.h74
-rw-r--r--tests/sys/fifo/Makefile13
-rw-r--r--tests/sys/fifo/Makefile.depend18
-rw-r--r--tests/sys/fifo/fifo_create.c282
-rw-r--r--tests/sys/fifo/fifo_io.c1399
-rw-r--r--tests/sys/fifo/fifo_misc.c336
-rw-r--r--tests/sys/fifo/fifo_open.c476
-rw-r--r--tests/sys/file/Makefile22
-rw-r--r--tests/sys/file/Makefile.depend20
-rw-r--r--tests/sys/file/closefrom_test.c275
-rw-r--r--tests/sys/file/dup_test.c386
-rw-r--r--tests/sys/file/fcntlflags_test.c110
-rw-r--r--tests/sys/file/flock_helper.c1598
-rwxr-xr-xtests/sys/file/flock_test.sh53
-rw-r--r--tests/sys/file/ftruncate_test.c177
-rw-r--r--tests/sys/file/newfileops_on_fork_test.c121
-rw-r--r--tests/sys/fs/Makefile23
-rw-r--r--tests/sys/fs/tmpfs/Makefile56
-rw-r--r--tests/sys/geom/Makefile7
-rw-r--r--tests/sys/geom/Makefile.depend11
-rw-r--r--tests/sys/geom/class/Makefile21
-rw-r--r--tests/sys/geom/class/Makefile.depend11
-rw-r--r--tests/sys/geom/class/concat/1_test.sh23
-rw-r--r--tests/sys/geom/class/concat/2_test.sh30
-rw-r--r--tests/sys/geom/class/concat/Makefile16
-rw-r--r--tests/sys/geom/class/concat/Makefile.depend11
-rw-r--r--tests/sys/geom/class/concat/conf.sh15
-rw-r--r--tests/sys/geom/class/eli/Makefile43
-rw-r--r--tests/sys/geom/class/eli/Makefile.depend11
-rw-r--r--tests/sys/geom/class/eli/attach_d_test.sh38
-rwxr-xr-xtests/sys/geom/class/eli/conf.sh69
-rw-r--r--tests/sys/geom/class/eli/configure_b_B_test.sh129
-rw-r--r--tests/sys/geom/class/eli/delkey_test.sh140
-rw-r--r--tests/sys/geom/class/eli/detach_l_test.sh44
-rw-r--r--tests/sys/geom/class/eli/init_B_test.sh104
-rw-r--r--tests/sys/geom/class/eli/init_J_test.sh126
-rw-r--r--tests/sys/geom/class/eli/init_a_test.sh50
-rwxr-xr-xtests/sys/geom/class/eli/init_alias_test.sh64
-rw-r--r--tests/sys/geom/class/eli/init_i_P_test.sh22
-rw-r--r--tests/sys/geom/class/eli/init_test.sh55
-rw-r--r--tests/sys/geom/class/eli/integrity_copy_test.sh88
-rw-r--r--tests/sys/geom/class/eli/integrity_data_test.sh45
-rw-r--r--tests/sys/geom/class/eli/integrity_hmac_test.sh46
-rw-r--r--tests/sys/geom/class/eli/kill_test.sh97
-rw-r--r--tests/sys/geom/class/eli/nokey_test.sh65
-rw-r--r--tests/sys/geom/class/eli/onetime_a_test.sh45
-rw-r--r--tests/sys/geom/class/eli/onetime_d_test.sh34
-rw-r--r--tests/sys/geom/class/eli/onetime_test.sh50
-rw-r--r--tests/sys/geom/class/eli/readonly_test.sh94
-rw-r--r--tests/sys/geom/class/eli/resize_test.sh148
-rw-r--r--tests/sys/geom/class/eli/setkey_test.sh156
-rw-r--r--tests/sys/geom/class/gate/1_test.sh63
-rw-r--r--tests/sys/geom/class/gate/2_test.sh48
-rw-r--r--tests/sys/geom/class/gate/3_test.sh49
-rw-r--r--tests/sys/geom/class/gate/Makefile17
-rw-r--r--tests/sys/geom/class/gate/Makefile.depend11
-rwxr-xr-xtests/sys/geom/class/gate/conf.sh8
-rw-r--r--tests/sys/geom/class/geom_subr.sh58
-rw-r--r--tests/sys/geom/class/mirror/1_test.sh23
-rw-r--r--tests/sys/geom/class/mirror/2_test.sh52
-rw-r--r--tests/sys/geom/class/mirror/3_test.sh64
-rw-r--r--tests/sys/geom/class/mirror/4_test.sh66
-rw-r--r--tests/sys/geom/class/mirror/5_test.sh64
-rw-r--r--tests/sys/geom/class/mirror/6_test.sh45
-rw-r--r--tests/sys/geom/class/mirror/7_test.sh64
-rw-r--r--tests/sys/geom/class/mirror/Makefile21
-rw-r--r--tests/sys/geom/class/mirror/Makefile.depend11
-rw-r--r--tests/sys/geom/class/mirror/conf.sh15
-rw-r--r--tests/sys/geom/class/nop/Makefile9
-rw-r--r--tests/sys/geom/class/nop/Makefile.depend11
-rwxr-xr-xtests/sys/geom/class/nop/nop_test.sh166
-rw-r--r--tests/sys/geom/class/raid3/10_test.sh32
-rw-r--r--tests/sys/geom/class/raid3/11_test.sh32
-rw-r--r--tests/sys/geom/class/raid3/12_test.sh38
-rw-r--r--tests/sys/geom/class/raid3/1_test.sh28
-rw-r--r--tests/sys/geom/class/raid3/2_test.sh32
-rw-r--r--tests/sys/geom/class/raid3/3_test.sh36
-rw-r--r--tests/sys/geom/class/raid3/4_test.sh36
-rw-r--r--tests/sys/geom/class/raid3/5_test.sh36
-rw-r--r--tests/sys/geom/class/raid3/6_test.sh40
-rw-r--r--tests/sys/geom/class/raid3/7_test.sh43
-rw-r--r--tests/sys/geom/class/raid3/8_test.sh39
-rw-r--r--tests/sys/geom/class/raid3/9_test.sh42
-rw-r--r--tests/sys/geom/class/raid3/Makefile26
-rw-r--r--tests/sys/geom/class/raid3/Makefile.depend11
-rw-r--r--tests/sys/geom/class/raid3/conf.sh15
-rw-r--r--tests/sys/geom/class/shsec/1_test.sh28
-rw-r--r--tests/sys/geom/class/shsec/2_test.sh52
-rw-r--r--tests/sys/geom/class/shsec/Makefile16
-rw-r--r--tests/sys/geom/class/shsec/Makefile.depend11
-rw-r--r--tests/sys/geom/class/shsec/conf.sh15
-rw-r--r--tests/sys/geom/class/stripe/1_test.sh23
-rw-r--r--tests/sys/geom/class/stripe/2_test.sh30
-rw-r--r--tests/sys/geom/class/stripe/Makefile16
-rw-r--r--tests/sys/geom/class/stripe/Makefile.depend11
-rw-r--r--tests/sys/geom/class/stripe/conf.sh15
-rw-r--r--tests/sys/geom/class/uzip/1_endian_big.img.uzip.uue87
-rw-r--r--tests/sys/geom/class/uzip/1_endian_little.img.uzip.uue110
-rw-r--r--tests/sys/geom/class/uzip/1_test.sh34
-rw-r--r--tests/sys/geom/class/uzip/Makefile42
-rw-r--r--tests/sys/geom/class/uzip/Makefile.depend11
-rwxr-xr-xtests/sys/geom/class/uzip/conf.sh20
-rw-r--r--tests/sys/geom/class/uzip/etalon/etalon.txt43
-rw-r--r--tests/sys/kern/Makefile46
-rw-r--r--tests/sys/kern/Makefile.depend22
-rw-r--r--tests/sys/kern/acct/Makefile19
-rw-r--r--tests/sys/kern/acct/Makefile.depend20
-rw-r--r--tests/sys/kern/acct/acct_test.c240
-rw-r--r--tests/sys/kern/coredump_phnum_helper.c61
-rwxr-xr-xtests/sys/kern/coredump_phnum_test.sh92
-rw-r--r--tests/sys/kern/execve/Makefile39
-rw-r--r--tests/sys/kern/execve/Makefile.depend17
-rw-r--r--tests/sys/kern/execve/bad_interp_len4
-rw-r--r--tests/sys/kern/execve/dev_null_script4
-rw-r--r--tests/sys/kern/execve/execve_helper.c54
-rw-r--r--tests/sys/kern/execve/execve_test.sh115
-rw-r--r--tests/sys/kern/execve/good_aout.c45
-rw-r--r--tests/sys/kern/execve/good_script4
-rw-r--r--tests/sys/kern/execve/non_exist_shell4
-rw-r--r--tests/sys/kern/execve/script_arg4
-rw-r--r--tests/sys/kern/execve/script_arg_nospace4
-rw-r--r--tests/sys/kern/kern_copyin.c96
-rw-r--r--tests/sys/kern/kern_descrip_test.c191
-rw-r--r--tests/sys/kern/pipe/Makefile16
-rw-r--r--tests/sys/kern/pipe/Makefile.depend18
-rw-r--r--tests/sys/kern/pipe/big_pipe_test.c88
-rw-r--r--tests/sys/kern/pipe/pipe_fstat_bug_test.c156
-rw-r--r--tests/sys/kern/pipe/pipe_ino_test.c67
-rw-r--r--tests/sys/kern/pipe/pipe_overcommit1_test.c51
-rw-r--r--tests/sys/kern/pipe/pipe_overcommit2_test.c83
-rw-r--r--tests/sys/kern/pipe/pipe_reverse2_test.c67
-rw-r--r--tests/sys/kern/pipe/pipe_reverse_test.c149
-rw-r--r--tests/sys/kern/pipe/pipe_wraparound_test.c140
-rw-r--r--tests/sys/kern/ptrace_test.c1705
-rw-r--r--tests/sys/kern/reaper.c758
-rw-r--r--tests/sys/kern/unix_passfd_test.c396
-rw-r--r--tests/sys/kern/unix_seqpacket_test.c1191
-rw-r--r--tests/sys/kern/waitpid_nohang.c70
-rw-r--r--tests/sys/kqueue/Makefile20
-rw-r--r--tests/sys/kqueue/libkqueue/Makefile21
-rw-r--r--tests/sys/kqueue/libkqueue/Makefile.depend18
-rw-r--r--tests/sys/kqueue/libkqueue/common.h79
-rw-r--r--tests/sys/kqueue/libkqueue/config.h13
-rwxr-xr-xtests/sys/kqueue/libkqueue/kqueue_test.sh17
-rw-r--r--tests/sys/kqueue/libkqueue/main.c329
-rw-r--r--tests/sys/kqueue/libkqueue/proc.c423
-rw-r--r--tests/sys/kqueue/libkqueue/read.c326
-rw-r--r--tests/sys/kqueue/libkqueue/signal.c199
-rw-r--r--tests/sys/kqueue/libkqueue/timer.c178
-rw-r--r--tests/sys/kqueue/libkqueue/user.c129
-rw-r--r--tests/sys/kqueue/libkqueue/vnode.c266
-rw-r--r--tests/sys/mac/Makefile8
-rw-r--r--tests/sys/mac/Makefile.depend11
-rw-r--r--tests/sys/mac/bsdextended/Makefile13
-rw-r--r--tests/sys/mac/bsdextended/Makefile.depend19
-rw-r--r--tests/sys/mac/bsdextended/matches_test.sh365
-rw-r--r--tests/sys/mac/bsdextended/ugidfw_test.c252
-rw-r--r--tests/sys/mac/portacl/LICENSE27
-rw-r--r--tests/sys/mac/portacl/Makefile17
-rw-r--r--tests/sys/mac/portacl/Makefile.depend11
-rwxr-xr-xtests/sys/mac/portacl/misc.sh106
-rwxr-xr-xtests/sys/mac/portacl/nobody_test.sh67
-rwxr-xr-xtests/sys/mac/portacl/root_test.sh51
-rw-r--r--tests/sys/mqueue/Makefile21
-rw-r--r--tests/sys/mqueue/Makefile.depend21
-rw-r--r--tests/sys/mqueue/mqtest1.c57
-rw-r--r--tests/sys/mqueue/mqtest2.c101
-rw-r--r--tests/sys/mqueue/mqtest3.c118
-rw-r--r--tests/sys/mqueue/mqtest4.c120
-rw-r--r--tests/sys/mqueue/mqtest5.c128
-rwxr-xr-xtests/sys/mqueue/mqueue_test.sh81
-rw-r--r--tests/sys/netinet/Makefile14
-rw-r--r--tests/sys/netinet/Makefile.depend19
-rwxr-xr-xtests/sys/netinet/fibs_test.sh488
-rw-r--r--tests/sys/netinet/tcp_user_cookie.c111
-rw-r--r--tests/sys/netinet/udp_dontroute.c111
-rw-r--r--tests/sys/opencrypto/Makefile15
-rw-r--r--tests/sys/opencrypto/Makefile.depend11
-rw-r--r--tests/sys/opencrypto/cryptodev.py561
-rw-r--r--tests/sys/opencrypto/cryptodevh.py250
-rw-r--r--tests/sys/opencrypto/cryptotest.py265
-rw-r--r--tests/sys/opencrypto/dpkt.py160
-rwxr-xr-xtests/sys/opencrypto/runtests.sh60
-rw-r--r--tests/sys/pjdfstest/Makefile10
-rw-r--r--tests/sys/pjdfstest/pjdfstest/Makefile20
-rw-r--r--tests/sys/pjdfstest/pjdfstest/Makefile.depend18
-rw-r--r--tests/sys/pjdfstest/tests/Makefile39
-rw-r--r--tests/sys/pjdfstest/tests/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/chflags/Makefile18
-rw-r--r--tests/sys/pjdfstest/tests/chflags/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/chmod/Makefile17
-rw-r--r--tests/sys/pjdfstest/tests/chmod/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/chown/Makefile15
-rw-r--r--tests/sys/pjdfstest/tests/chown/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/conf15
-rw-r--r--tests/sys/pjdfstest/tests/ftruncate/Makefile19
-rw-r--r--tests/sys/pjdfstest/tests/ftruncate/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/granular/Makefile10
-rw-r--r--tests/sys/pjdfstest/tests/granular/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/link/Makefile22
-rw-r--r--tests/sys/pjdfstest/tests/link/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/mkdir/Makefile17
-rw-r--r--tests/sys/pjdfstest/tests/mkdir/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/mkfifo/Makefile17
-rw-r--r--tests/sys/pjdfstest/tests/mkfifo/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/mknod/Makefile16
-rw-r--r--tests/sys/pjdfstest/tests/mknod/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/open/Makefile29
-rw-r--r--tests/sys/pjdfstest/tests/open/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/pjdfstest.test.mk14
-rw-r--r--tests/sys/pjdfstest/tests/rename/Makefile26
-rw-r--r--tests/sys/pjdfstest/tests/rename/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/rmdir/Makefile20
-rw-r--r--tests/sys/pjdfstest/tests/rmdir/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/symlink/Makefile17
-rw-r--r--tests/sys/pjdfstest/tests/symlink/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/truncate/Makefile19
-rw-r--r--tests/sys/pjdfstest/tests/truncate/Makefile.depend11
-rw-r--r--tests/sys/pjdfstest/tests/unlink/Makefile18
-rw-r--r--tests/sys/pjdfstest/tests/unlink/Makefile.depend11
-rw-r--r--tests/sys/posixshm/Makefile9
-rw-r--r--tests/sys/posixshm/Makefile.depend19
-rw-r--r--tests/sys/posixshm/posixshm_test.c635
-rw-r--r--tests/sys/sys/Makefile9
-rw-r--r--tests/sys/sys/Makefile.depend19
-rw-r--r--tests/sys/sys/bitstring_test.c433
-rw-r--r--tests/sys/vfs/Makefile14
-rw-r--r--tests/sys/vfs/Makefile.depend11
-rw-r--r--tests/sys/vfs/lookup_cap_dotdot.c313
-rwxr-xr-xtests/sys/vfs/trailing_slash.sh42
-rw-r--r--tests/sys/vm/Makefile9
-rw-r--r--tests/sys/vm/Makefile.depend19
-rw-r--r--tests/sys/vm/mmap_test.c273
265 files changed, 28605 insertions, 0 deletions
diff --git a/tests/Kyuafile b/tests/Kyuafile
new file mode 100644
index 000000000000..10cf039ae041
--- /dev/null
+++ b/tests/Kyuafile
@@ -0,0 +1,52 @@
+-- $FreeBSD$
+--
+-- Copyright 2011 Google 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:
+--
+-- * 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 Google Inc. 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
+-- OWNER 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.
+
+-- Automatically recurses into any subdirectory that holds a Kyuafile.
+-- As such, this Kyuafile is suitable for installation into the root of
+-- the tests hierarchy as well as into any other subdirectory that needs
+-- "auto-discovery" of tests.
+--
+-- This file is based on the Kyuafile.top sample file distributed in the
+-- kyua-cli package.
+
+syntax(2)
+
+local directory = fs.dirname(current_kyuafile())
+for file in fs.files(directory) do
+ if file == "." or file == ".." then
+ -- Skip these special entries.
+ else
+ local kyuafile_relative = fs.join(file, "Kyuafile")
+ local kyuafile_absolute = fs.join(directory, kyuafile_relative)
+ if fs.exists(kyuafile_absolute) then
+ include(kyuafile_relative)
+ end
+ end
+end
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644
index 000000000000..0aed9cfb1b83
--- /dev/null
+++ b/tests/Makefile
@@ -0,0 +1,20 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}
+
+${PACKAGE}FILES+= README
+
+KYUAFILE= yes
+
+SUBDIR+= etc
+SUBDIR+= sys
+
+SUBDIR_PARALLEL=
+
+afterinstall: install-tests-local
+install-tests-local: .PHONY
+ ${INSTALL_SYMLINK} ../local/tests ${DESTDIR}${TESTSDIR}/local
+
+.include <bsd.test.mk>
diff --git a/tests/Makefile.depend b/tests/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/README b/tests/README
new file mode 100644
index 000000000000..2102dd1f9b18
--- /dev/null
+++ b/tests/README
@@ -0,0 +1,64 @@
+src/tests: The FreeBSD test suite
+=================================
+
+To run the FreeBSD test suite:
+(1) Make sure that kyua is installed:
+ pkg install kyua
+(2) To run the tests:
+ kyua test -k /usr/tests/Kyuafile
+(3) To see the test results:
+ kyua report
+
+For further information on using the test suite, read tests(7):
+ man tests
+
+Description of FreeBSD test suite
+=================================
+The build of the test suite is organized in the following manner:
+
+* The build of all test artifacts is protected by the MK_TESTS knob.
+ The user can disable these with the WITHOUT_TESTS setting in
+ src.conf(5).
+
+* The goal for /usr/tests/ (the installed test programs) is to follow
+ the same hierarchy as /usr/src/ wherever possible, which in turn drives
+ several of the design decisions described below. This simplifies the
+ discoverability of tests. We want a mapping such as:
+
+ /usr/src/bin/cp/ -> /usr/tests/bin/cp/
+ /usr/src/lib/libc/ -> /usr/tests/lib/libc/
+ /usr/src/usr.bin/cut/ -> /usr/tests/usr.bin/cut/
+ ... and many more ...
+
+* Test programs for specific utilities and libraries are located next
+ to the source code of such programs. For example, the tests for the
+ src/lib/libcrypt/ library live in src/lib/libcrypt/tests/. The tests/
+ subdirectory is optional and should, in general, be avoided.
+
+* The src/tests/ hierarchy (this directory) provides generic test
+ infrastructure and glue code to join all test programs together into
+ a single test suite definition.
+
+* The src/tests/ hierarchy also includes cross-functional test programs:
+ i.e. test programs that cover more than a single utility or library
+ and thus don't fit anywhere else in the tree. Consider this to follow
+ the same rationale as src/share/man/: this directory contains generic
+ manual pages while the manual pages that are specific to individual
+ tools or libraries live next to the source code.
+
+In order to keep the src/tests/ hierarchy decoupled from the actual test
+programs being installed --which is a worthy goal because it simplifies
+the addition of new test programs and simplifies the maintenance of the
+tree-- the top-level Kyuafile does not know which subdirectories may
+exist upfront. Instead, such Kyuafile automatically detects, at
+run-time, which */Kyuafile files exist and uses those directly.
+
+Similarly, every directory in src/ that wants to install a Kyuafile to
+just recurse into other subdirectories reuses this Kyuafile with
+auto-discovery features. As an example, take a look at src/lib/tests/
+whose sole purpose is to install a Kyuafile into /usr/tests/lib/.
+The goal in this specific case is for /usr/tests/lib/ to be generated
+entirely from src/lib/.
+
+--
+$FreeBSD$
diff --git a/tests/etc/Makefile b/tests/etc/Makefile
new file mode 100644
index 000000000000..1e49666767c0
--- /dev/null
+++ b/tests/etc/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+.include <bsd.own.mk>
+
+TESTSDIR= ${TESTSBASE}/etc
+
+.PATH: ${SRCTOP}/tests
+KYUAFILE= yes
+
+SUBDIR+= rc.d
+
+.include <bsd.test.mk>
diff --git a/tests/etc/Makefile.depend b/tests/etc/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/etc/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/etc/rc.d/Makefile b/tests/etc/rc.d/Makefile
new file mode 100644
index 000000000000..368e8f4fc875
--- /dev/null
+++ b/tests/etc/rc.d/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/etc/rc.d
+
+ATF_TESTS_SH+= routing_test
+
+.include <bsd.test.mk>
diff --git a/tests/etc/rc.d/Makefile.depend b/tests/etc/rc.d/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/etc/rc.d/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/etc/rc.d/routing_test.sh b/tests/etc/rc.d/routing_test.sh
new file mode 100755
index 000000000000..f1510cbf1730
--- /dev/null
+++ b/tests/etc/rc.d/routing_test.sh
@@ -0,0 +1,77 @@
+#
+# Copyright (c) 2014 Spectra Logic Corporation
+# 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,
+# without modification.
+# 2. Redistributions in binary form must reproduce at minimum a disclaimer
+# substantially similar to the "NO WARRANTY" disclaimer below
+# ("Disclaimer") and any redistribution must be conditioned upon
+# including a substantially similar Disclaimer requirement for further
+# binary redistribution.
+#
+# NO WARRANTY
+# 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 MERCHANTIBILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+#
+# Authors: Alan Somers (Spectra Logic Corporation)
+#
+# $FreeBSD$
+
+atf_test_case static_ipv4_loopback_route_for_each_fib cleanup
+static_ipv4_loopback_route_for_each_fib_head()
+{
+ atf_set "descr" "Every FIB should have a static IPv4 loopback route"
+}
+static_ipv4_loopback_route_for_each_fib_body()
+{
+ local nfibs fib
+ nfibs=`sysctl -n net.fibs`
+
+ # Check for an IPv4 loopback route
+ for fib in `seq 0 $((${nfibs} - 1))`; do
+ atf_check -o match:"interface: lo0" -s exit:0 \
+ setfib -F ${fib} route -4 get 127.0.0.1
+ done
+}
+
+atf_test_case static_ipv6_loopback_route_for_each_fib cleanup
+static_ipv6_loopback_route_for_each_fib_head()
+{
+ atf_set "descr" "Every FIB should have a static IPv6 loopback route"
+}
+static_ipv6_loopback_route_for_each_fib_body()
+{
+ local nfibs fib
+ nfibs=`sysctl -n net.fibs`
+
+ if [ "`sysctl -in kern.features.inet6`" != "1" ]; then
+ atf_skip "This test requires IPv6 support"
+ fi
+
+ # Check for an IPv6 loopback route
+ for fib in `seq 0 $((${nfibs} - 1))`; do
+ atf_check -o match:"interface: lo0" -s exit:0 \
+ setfib -F ${fib} route -6 get ::1
+ done
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case static_ipv4_loopback_route_for_each_fib
+ atf_add_test_case static_ipv6_loopback_route_for_each_fib
+}
+
diff --git a/tests/freebsd_test_suite/macros.h b/tests/freebsd_test_suite/macros.h
new file mode 100644
index 000000000000..98da0ededbc3
--- /dev/null
+++ b/tests/freebsd_test_suite/macros.h
@@ -0,0 +1,71 @@
+/*-
+ * Copyright (c) 2015 EMC / Isilon Storage Division
+ * 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 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 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 _FREEBSD_TEST_MACROS_H_
+#define _FREEBSD_TEST_MACROS_H_
+
+#include <sys/param.h>
+#include <sys/module.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#define ATF_REQUIRE_FEATURE(_feature_name) do { \
+ if (feature_present(_feature_name) == 0) { \
+ atf_tc_skip("kernel feature (%s) not present", \
+ _feature_name); \
+ } \
+} while(0)
+
+#define ATF_REQUIRE_KERNEL_MODULE(_mod_name) do { \
+ if (modfind(_mod_name) == -1) { \
+ atf_tc_skip("module %s could not be resolved: %s", \
+ _mod_name, strerror(errno)); \
+ } \
+} while(0)
+
+#define PLAIN_REQUIRE_FEATURE(_feature_name, _exit_code) do { \
+ if (feature_present(_feature_name) == 0) { \
+ printf("kernel feature (%s) not present\n", \
+ _feature_name); \
+ _exit(_exit_code); \
+ } \
+} while(0)
+
+#define PLAIN_REQUIRE_KERNEL_MODULE(_mod_name, _exit_code) do { \
+ if (modfind(_mod_name) == -1) { \
+ printf("module %s could not be resolved: %s\n", \
+ _mod_name, strerror(errno)); \
+ _exit(_exit_code); \
+ } \
+} while(0)
+
+#endif
diff --git a/tests/sys/Makefile b/tests/sys/Makefile
new file mode 100644
index 000000000000..1bcd76ec2649
--- /dev/null
+++ b/tests/sys/Makefile
@@ -0,0 +1,25 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys
+
+TESTS_SUBDIRS+= acl
+TESTS_SUBDIRS+= aio
+TESTS_SUBDIRS+= fifo
+TESTS_SUBDIRS+= file
+TESTS_SUBDIRS+= fs
+TESTS_SUBDIRS+= geom
+TESTS_SUBDIRS+= kern
+TESTS_SUBDIRS+= kqueue
+TESTS_SUBDIRS+= mac
+TESTS_SUBDIRS+= mqueue
+TESTS_SUBDIRS+= netinet
+TESTS_SUBDIRS+= opencrypto
+TESTS_SUBDIRS+= posixshm
+TESTS_SUBDIRS+= sys
+TESTS_SUBDIRS+= vfs
+TESTS_SUBDIRS+= vm
+
+# Items not integrated into kyua runs by default
+SUBDIR+= pjdfstest
+
+.include <bsd.test.mk>
diff --git a/tests/sys/Makefile.depend b/tests/sys/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/acl/00.sh b/tests/sys/acl/00.sh
new file mode 100644
index 000000000000..98b5e6fdf509
--- /dev/null
+++ b/tests/sys/acl/00.sh
@@ -0,0 +1,88 @@
+#!/bin/sh
+#
+# Copyright (c) 2008, 2009 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE 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 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$
+#
+
+# This is a wrapper script to run tools-posix.test on UFS filesystem.
+#
+# If any of the tests fails, here is how to debug it: go to
+# the directory with problematic filesystem mounted on it,
+# and do /path/to/test run /path/to/test tools-posix.test, e.g.
+#
+# /usr/src/tools/regression/acltools/run /usr/src/tools/regression/acltools/tools-posix.test
+#
+# Output should be obvious.
+
+if [ $(sysctl -n kern.features.ufs_acl 2>/dev/null || echo 0) -eq 0 ]; then
+ echo "1..0 # SKIP system does not have UFS ACL support"
+ exit 0
+fi
+if [ $(id -u) -ne 0 ]; then
+ echo "1..0 # SKIP you must be root"
+ exit 0
+fi
+
+echo "1..4"
+
+TESTDIR=$(dirname $(realpath $0))
+
+# Set up the test filesystem.
+MD=`mdconfig -at swap -s 10m`
+MNT=`mktemp -dt acltools`
+newfs /dev/$MD > /dev/null
+trap "cd /; umount -f $MNT; rmdir $MNT; mdconfig -d -u $MD" EXIT
+mount -o acls /dev/$MD $MNT
+if [ $? -ne 0 ]; then
+ echo "not ok 1 - mount failed."
+ echo 'Bail out!'
+ exit 1
+fi
+
+echo "ok 1"
+
+cd $MNT
+
+# First, check whether we can crash the kernel by creating too many
+# entries. For some reason this won't work in the test file.
+touch xxx
+i=0;
+while :; do i=$(($i+1)); setfacl -m u:$i:rwx xxx 2> /dev/null; if [ $? -ne 0 ]; then break; fi; done
+chmod 600 xxx
+rm xxx
+echo "ok 2"
+
+perl $TESTDIR/run $TESTDIR/tools-posix.test >&2
+
+if [ $? -eq 0 ]; then
+ echo "ok 3"
+else
+ echo "not ok 3"
+fi
+
+cd /
+
+echo "ok 4"
diff --git a/tests/sys/acl/01.sh b/tests/sys/acl/01.sh
new file mode 100644
index 000000000000..07449362b7cb
--- /dev/null
+++ b/tests/sys/acl/01.sh
@@ -0,0 +1,87 @@
+#!/bin/sh
+#
+# Copyright (c) 2008, 2009 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE 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 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$
+#
+
+# This is a wrapper script to run tools-nfs4.test on ZFS filesystem.
+#
+# WARNING: It uses hardcoded ZFS pool name "acltools"
+#
+# If any of the tests fails, here is how to debug it: go to
+# the directory with problematic filesystem mounted on it,
+# and do /path/to/test run /path/to/test tools-nfs4.test, e.g.
+#
+# /usr/src/tools/regression/acltools/run /usr/src/tools/regression/acltools/tools-nfs4.test
+#
+# Output should be obvious.
+
+if ! sysctl vfs.zfs.version.spa >/dev/null 2>&1; then
+ echo "1..0 # SKIP system doesn't have ZFS loaded"
+ exit 0
+fi
+if [ $(id -u) -ne 0 ]; then
+ echo "1..0 # SKIP you must be root"
+ exit 0
+fi
+
+echo "1..4"
+
+TESTDIR=$(dirname $(realpath $0))
+
+# Set up the test filesystem.
+MD=`mdconfig -at swap -s 64m`
+MNT=`mktemp -dt acltools`
+trap "cd /; zpool destroy -f acltools; rmdir $MNT; mdconfig -d -u $MD" EXIT
+zpool create -m $MNT acltools /dev/$MD
+if [ $? -ne 0 ]; then
+ echo "not ok 1 - 'zpool create' failed."
+ echo 'Bail out!'
+ exit 1
+fi
+
+echo "ok 1"
+
+cd $MNT
+
+# First, check whether we can crash the kernel by creating too many
+# entries. For some reason this won't work in the test file.
+touch xxx
+setfacl -x2 xxx
+while :; do setfacl -a0 u:42:rwx:allow xxx 2> /dev/null; if [ $? -ne 0 ]; then break; fi; done
+chmod 600 xxx
+rm xxx
+echo "ok 2"
+
+perl $TESTDIR/run $TESTDIR/tools-nfs4-psarc.test >&2
+
+if [ $? -eq 0 ]; then
+ echo "ok 3"
+else
+ echo "not ok 3 # TODO: fails due to ACL changes in ZFS; bug 212323"
+fi
+
+echo "ok 4"
diff --git a/tests/sys/acl/02.sh b/tests/sys/acl/02.sh
new file mode 100644
index 000000000000..a989ee39702c
--- /dev/null
+++ b/tests/sys/acl/02.sh
@@ -0,0 +1,93 @@
+#!/bin/sh
+#
+# Copyright (c) 2008, 2009 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE 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 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$
+#
+
+# This is a wrapper script to run tools-nfs4.test on UFS filesystem.
+#
+# If any of the tests fails, here is how to debug it: go to
+# the directory with problematic filesystem mounted on it,
+# and do /path/to/test run /path/to/test tools-nfs4.test, e.g.
+#
+# /usr/src/tools/regression/acltools/run /usr/src/tools/regression/acltools/tools-nfs4.test
+#
+# Output should be obvious.
+
+if [ $(sysctl -n kern.features.ufs_acl 2>/dev/null || echo 0) -eq 0 ]; then
+ echo "1..0 # SKIP system does not have UFS ACL support"
+ exit 0
+fi
+if [ $(id -u) -ne 0 ]; then
+ echo "1..0 # SKIP you must be root"
+ exit 0
+fi
+
+echo "1..4"
+
+TESTDIR=$(dirname $(realpath $0))
+
+# Set up the test filesystem.
+MD=`mdconfig -at swap -s 10m`
+MNT=`mktemp -dt acltools`
+newfs /dev/$MD > /dev/null
+trap "cd /; umount -f $MNT; rmdir $MNT; mdconfig -d -u $MD" EXIT
+mount -o nfsv4acls /dev/$MD $MNT
+if [ $? -ne 0 ]; then
+ echo "not ok 1 - mount failed."
+ echo 'Bail out!'
+ exit 1
+fi
+
+echo "ok 1"
+
+cd $MNT
+
+# First, check whether we can crash the kernel by creating too many
+# entries. For some reason this won't work in the test file.
+touch xxx
+setfacl -x2 xxx
+while :; do setfacl -a0 u:42:rwx:allow xxx 2> /dev/null; if [ $? -ne 0 ]; then break; fi; done
+chmod 600 xxx
+rm xxx
+echo "ok 2"
+
+if [ `sysctl -n vfs.acl_nfs4_old_semantics` = 0 ]; then
+ perl $TESTDIR/run $TESTDIR/tools-nfs4-psarc.test >&2
+else
+ perl $TESTDIR/run $TESTDIR/tools-nfs4.test >&2
+fi
+
+if [ $? -eq 0 ]; then
+ echo "ok 3"
+else
+ echo "not ok 3"
+fi
+
+cd /
+
+echo "ok 4"
+
diff --git a/tests/sys/acl/03.sh b/tests/sys/acl/03.sh
new file mode 100644
index 000000000000..e6379a6d4497
--- /dev/null
+++ b/tests/sys/acl/03.sh
@@ -0,0 +1,117 @@
+#!/bin/sh
+#
+# Copyright (c) 2008, 2009 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE 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 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$
+#
+
+# This is a wrapper script to run tools-crossfs.test between UFS without
+# ACLs, UFS with POSIX.1e ACLs, and ZFS with NFSv4 ACLs.
+#
+# WARNING: It uses hardcoded ZFS pool name "acltools"
+#
+# Output should be obvious.
+
+if ! sysctl vfs.zfs.version.spa >/dev/null 2>&1; then
+ echo "1..0 # SKIP system doesn't have ZFS loaded"
+ exit 0
+fi
+if [ $(id -u) -ne 0 ]; then
+ echo "1..0 # SKIP you must be root"
+ exit 0
+fi
+
+echo "1..5"
+
+TESTDIR=$(dirname $(realpath $0))
+MNTROOT=`mktemp -dt acltools`
+
+# Set up the test filesystems.
+MD1=`mdconfig -at swap -s 64m`
+MNT1=$MNTROOT/nfs4
+mkdir $MNT1
+zpool create -m $MNT1 acltools /dev/$MD1
+if [ $? -ne 0 ]; then
+ echo "not ok 1 - 'zpool create' failed."
+ echo 'Bail out!'
+ exit 1
+fi
+
+echo "ok 1"
+
+MD2=`mdconfig -at swap -s 10m`
+MNT2=$MNTROOT/posix
+mkdir $MNT2
+newfs /dev/$MD2 > /dev/null
+mount -o acls /dev/$MD2 $MNT2
+if [ $? -ne 0 ]; then
+ echo "not ok 2 - mount failed."
+ echo 'Bail out!'
+ exit 1
+fi
+
+echo "ok 2"
+
+MD3=`mdconfig -at swap -s 10m`
+MNT3=$MNTROOT/none
+mkdir $MNT3
+newfs /dev/$MD3 > /dev/null
+mount /dev/$MD3 $MNT3
+if [ $? -ne 0 ]; then
+ echo "not ok 3 - mount failed."
+ echo 'Bail out!'
+ exit 1
+fi
+
+echo "ok 3"
+
+cd $MNTROOT
+
+perl $TESTDIR/run $TESTDIR/tools-crossfs.test >&2
+
+if [ $? -eq 0 ]; then
+ echo "ok 4"
+else
+ echo "not ok 4"
+fi
+
+cd /
+
+umount -f $MNT3
+rmdir $MNT3
+mdconfig -du $MD3
+
+umount -f $MNT2
+rmdir $MNT2
+mdconfig -du $MD2
+
+zpool destroy -f acltools
+rmdir $MNT1
+mdconfig -du $MD1
+
+rmdir $MNTROOT
+
+echo "ok 5"
+
diff --git a/tests/sys/acl/04.sh b/tests/sys/acl/04.sh
new file mode 100644
index 000000000000..917f5acb54a5
--- /dev/null
+++ b/tests/sys/acl/04.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE 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 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$
+#
+
+# This is a wrapper script to run tools-nfs4-trivial.test on ZFS filesystem.
+#
+# WARNING: It uses hardcoded ZFS pool name "acltools"
+
+if ! sysctl vfs.zfs.version.spa >/dev/null 2>&1; then
+ echo "1..0 # SKIP system doesn't have ZFS loaded"
+ exit 0
+fi
+if [ $(id -u) -ne 0 ]; then
+ echo "1..0 # SKIP you must be root"
+ exit 0
+fi
+
+echo "1..3"
+
+TESTDIR=$(dirname $(realpath $0))
+
+# Set up the test filesystem.
+MD=`mdconfig -at swap -s 64m`
+MNT=`mktemp -dt acltools`
+zpool create -m $MNT acltools /dev/$MD
+if [ $? -ne 0 ]; then
+ echo "not ok 1 - 'zpool create' failed."
+ echo 'Bail out!'
+ exit 1
+fi
+
+echo "ok 1"
+
+cd $MNT
+
+perl $TESTDIR/run $TESTDIR/tools-nfs4-trivial.test >&2
+
+if [ $? -eq 0 ]; then
+ echo "ok 2"
+else
+ echo "not ok 2"
+fi
+
+cd /
+zpool destroy -f acltools
+rmdir $MNT
+mdconfig -du $MD
+
+echo "ok 3"
diff --git a/tests/sys/acl/Makefile b/tests/sys/acl/Makefile
new file mode 100644
index 000000000000..bd548f06951e
--- /dev/null
+++ b/tests/sys/acl/Makefile
@@ -0,0 +1,37 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/acl
+
+BINDIR= ${TESTSDIR}
+
+${PACKAGE}FILES+= tools-crossfs.test
+${PACKAGE}FILES+= tools-nfs4.test
+${PACKAGE}FILES+= tools-nfs4-psarc.test
+${PACKAGE}FILES+= tools-nfs4-trivial.test
+${PACKAGE}FILES+= tools-posix.test
+
+SCRIPTS+= run
+
+TAP_TESTS_SH+= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+
+.for t in ${TAP_TESTS_SH}
+TEST_METADATA.$t+= required_user="root"
+.endfor
+
+_ACL_PROGS= getfacl setfacl
+
+.for t in 01 03 04
+TEST_METADATA.$t+= required_programs="perl zpool ${_ACL_PROGS}"
+.endfor
+
+.for t in 00 02
+TEST_METADATA.$t+= required_programs="perl ${_ACL_PROGS}"
+.endfor
+
+.include <bsd.test.mk>
diff --git a/tests/sys/acl/Makefile.depend b/tests/sys/acl/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/acl/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/acl/aclfuzzer.sh b/tests/sys/acl/aclfuzzer.sh
new file mode 100755
index 000000000000..dff07d8218d7
--- /dev/null
+++ b/tests/sys/acl/aclfuzzer.sh
@@ -0,0 +1,225 @@
+#!/bin/sh
+#
+# Copyright (c) 2008, 2009 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE 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 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$
+#
+
+# This is an NFSv4 ACL fuzzer. It expects to be run by non-root in a scratch
+# directory on a filesystem with NFSv4 ACLs support. Output it generates
+# is expected to be fed to /usr/src/tools/regression/acltools/run script.
+
+NUMBER_OF_COMMANDS=300
+
+run_command()
+{
+ echo "\$ $1"
+ eval $1 2>&1 | sed 's/^/> /'
+}
+
+rnd_from_0_to()
+{
+ max=`expr $1 + 1`
+ rnd=`jot -r 1`
+ rnd=`expr $rnd % $max`
+
+ echo $rnd
+}
+
+rnd_path()
+{
+ rnd=`rnd_from_0_to 3`
+ case $rnd in
+ 0) echo "$TMP/aaa" ;;
+ 1) echo "$TMP/bbb" ;;
+ 2) echo "$TMP/aaa/ccc" ;;
+ 3) echo "$TMP/bbb/ddd" ;;
+ esac
+}
+
+f_prepend_random_acl_on()
+{
+ rnd=`rnd_from_0_to 4`
+ case $rnd in
+ 0) u="owner@" ;;
+ 1) u="group@" ;;
+ 2) u="everyone@" ;;
+ 3) u="u:1138" ;;
+ 4) u="g:1138" ;;
+ esac
+
+ p=""
+ while :; do
+ rnd=`rnd_from_0_to 30`
+ if [ -n "$p" -a $rnd -ge 14 ]; then
+ break;
+ fi
+
+ case $rnd in
+ 0) p="${p}r" ;;
+ 1) p="${p}w" ;;
+ 2) p="${p}x" ;;
+ 3) p="${p}p" ;;
+ 4) p="${p}d" ;;
+ 5) p="${p}D" ;;
+ 6) p="${p}a" ;;
+ 7) p="${p}A" ;;
+ 8) p="${p}R" ;;
+ 9) p="${p}W" ;;
+ 10) p="${p}R" ;;
+ 11) p="${p}c" ;;
+ 12) p="${p}C" ;;
+ 13) p="${p}o" ;;
+ 14) p="${p}s" ;;
+ esac
+ done
+
+ f=""
+ while :; do
+ rnd=`rnd_from_0_to 10`
+ if [ $rnd -ge 6 ]; then
+ break;
+ fi
+
+ case $rnd in
+ 0) f="${f}f" ;;
+ 1) f="${f}d" ;;
+ 2) f="${f}n" ;;
+ 3) f="${f}i" ;;
+ esac
+ done
+
+ rnd=`rnd_from_0_to 1`
+ case $rnd in
+ 0) x="allow" ;;
+ 1) x="deny" ;;
+ esac
+
+ acl="$u:$p:$f:$x"
+
+ file=`rnd_path`
+ run_command "setfacl -a0 $acl $file"
+}
+
+f_getfacl()
+{
+ file=`rnd_path`
+ run_command "getfacl -qn $file"
+}
+
+f_ls_mode()
+{
+ file=`rnd_path`
+ run_command "ls -al $file | sed -n '2p' | cut -d' ' -f1"
+}
+
+f_chmod()
+{
+ b1=`rnd_from_0_to 7`
+ b2=`rnd_from_0_to 7`
+ b3=`rnd_from_0_to 7`
+ b4=`rnd_from_0_to 7`
+ file=`rnd_path`
+
+ run_command "chmod $b1$b2$b3$b4 $file $2"
+}
+
+f_touch()
+{
+ file=`rnd_path`
+ run_command "touch $file"
+}
+
+f_rm()
+{
+ file=`rnd_path`
+ run_command "rm -f $file"
+}
+
+f_mkdir()
+{
+ file=`rnd_path`
+ run_command "mkdir $file"
+}
+
+f_rmdir()
+{
+ file=`rnd_path`
+ run_command "rmdir $file"
+}
+
+f_mv()
+{
+ from=`rnd_path`
+ to=`rnd_path`
+ run_command "mv -f $from $to"
+}
+
+# XXX: To be implemented: chown(8), setting times with touch(1).
+
+switch_to_random_user()
+{
+ # XXX: To be implemented.
+}
+
+execute_random_command()
+{
+ rnd=`rnd_from_0_to 20`
+
+ case $rnd in
+ 0|10|11|12|13|15) cmd=f_prepend_random_acl_on ;;
+ 1) cmd=f_getfacl ;;
+ 2) cmd=f_ls_mode ;;
+ 3) cmd=f_chmod ;;
+ 4|18|19) cmd=f_touch ;;
+ 5) cmd=f_rm ;;
+ 6|16|17) cmd=f_mkdir ;;
+ 7) cmd=f_rmdir ;;
+ 8) cmd=f_mv ;;
+ esac
+
+ $cmd "XXX"
+}
+
+echo "# Fuzzing; will stop after $NUMBER_OF_COMMANDS commands."
+TMP="aclfuzzer_`dd if=/dev/random bs=1k count=1 2>/dev/null | openssl md5`"
+
+run_command "whoami"
+umask 022
+run_command "umask 022"
+run_command "mkdir $TMP"
+
+i=0;
+while [ "$i" -lt "$NUMBER_OF_COMMANDS" ]; do
+ switch_to_random_user
+ execute_random_command
+ i=`expr $i + 1`
+done
+
+run_command "find $TMP -exec setfacl -a0 everyone@:rxd:allow {} \;"
+run_command "rm -rfv $TMP"
+
+echo "# Fuzzed, thank you."
+
diff --git a/tests/sys/acl/mktrivial.sh b/tests/sys/acl/mktrivial.sh
new file mode 100755
index 000000000000..99e3614cdc62
--- /dev/null
+++ b/tests/sys/acl/mktrivial.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE 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 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$
+#
+
+# This shell script generates an input file for the "run" script, used
+# to verify generation of trivial ACLs.
+
+echo "$ touch f"
+touch f
+
+for s in `jot 7 0 7`; do
+ for u in `jot 7 0 7`; do
+ for g in `jot 7 0 7`; do
+ for o in `jot 7 0 7`; do
+ echo "$ chmod 0$s$u$g$o f"
+ chmod "0$s$u$g$o" f
+ echo "$ ls -l f | cut -d' ' -f1"
+ ls -l f | cut -d' ' -f1 | sed 's/^/> /'
+ echo "$ getfacl -q f"
+ getfacl -q f | sed 's/^/> /'
+ done
+ done
+ done
+done
+
+echo "$ rm f"
+rm f
+
diff --git a/tests/sys/acl/run b/tests/sys/acl/run
new file mode 100644
index 000000000000..383f47e62397
--- /dev/null
+++ b/tests/sys/acl/run
@@ -0,0 +1,329 @@
+#!/usr/bin/perl -w -U
+
+# Copyright (c) 2007, 2008 Andreas Gruenbacher.
+# 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,
+# without modification, immediately at the beginning of the file.
+# 2. The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# Alternatively, this software may be distributed under the terms of the
+# GNU Public License ("GPL").
+#
+# 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 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$
+#
+
+#
+# Possible improvements:
+#
+# - distinguish stdout and stderr output
+# - add environment variable like assignments
+# - run up to a specific line
+# - resume at a specific line
+#
+
+use strict;
+use FileHandle;
+use Getopt::Std;
+use POSIX qw(isatty setuid getcwd);
+use vars qw($opt_l $opt_v);
+
+no warnings qw(taint);
+
+$opt_l = ~0; # a really huge number
+getopts('l:v');
+
+my ($OK, $FAILED) = ("ok", "failed");
+if (isatty(fileno(STDOUT))) {
+ $OK = "\033[32m" . $OK . "\033[m";
+ $FAILED = "\033[31m\033[1m" . $FAILED . "\033[m";
+}
+
+sub exec_test($$);
+sub process_test($$$$);
+
+my ($prog, $in, $out) = ([], [], []);
+my $prog_line = 0;
+my ($tests, $failed) = (0,0);
+my $lineno;
+my $width = ($ENV{COLUMNS} || 80) >> 1;
+
+for (;;) {
+ my $line = <>; $lineno++;
+ if (defined $line) {
+ # Substitute %VAR and %{VAR} with environment variables.
+ $line =~ s[%(\w+)][$ENV{$1}]eg;
+ $line =~ s[%{(\w+)}][$ENV{$1}]eg;
+ }
+ if (defined $line) {
+ if ($line =~ s/^\s*< ?//) {
+ push @$in, $line;
+ } elsif ($line =~ s/^\s*> ?//) {
+ push @$out, $line;
+ } else {
+ process_test($prog, $prog_line, $in, $out);
+ last if $prog_line >= $opt_l;
+
+ $prog = [];
+ $prog_line = 0;
+ }
+ if ($line =~ s/^\s*\$ ?//) {
+ $prog = [ map { s/\\(.)/$1/g; $_ } split /(?<!\\)\s+/, $line ];
+ $prog_line = $lineno;
+ $in = [];
+ $out = [];
+ }
+ } else {
+ process_test($prog, $prog_line, $in, $out);
+ last;
+ }
+}
+
+my $status = sprintf("%d commands (%d passed, %d failed)",
+ $tests, $tests-$failed, $failed);
+if (isatty(fileno(STDOUT))) {
+ if ($failed) {
+ $status = "\033[31m\033[1m" . $status . "\033[m";
+ } else {
+ $status = "\033[32m" . $status . "\033[m";
+ }
+}
+print $status, "\n";
+exit $failed ? 1 : 0;
+
+
+sub process_test($$$$) {
+ my ($prog, $prog_line, $in, $out) = @_;
+
+ return unless @$prog;
+
+ my $p = [ @$prog ];
+ print "[$prog_line] \$ ", join(' ',
+ map { s/\s/\\$&/g; $_ } @$p), " -- ";
+ my $result = exec_test($prog, $in);
+ my @good = ();
+ my $nmax = (@$out > @$result) ? @$out : @$result;
+ for (my $n=0; $n < $nmax; $n++) {
+ my $use_re;
+ if (defined $out->[$n] && $out->[$n] =~ /^~ /) {
+ $use_re = 1;
+ $out->[$n] =~ s/^~ //g;
+ }
+
+ if (!defined($out->[$n]) || !defined($result->[$n]) ||
+ (!$use_re && $result->[$n] ne $out->[$n]) ||
+ ( $use_re && $result->[$n] !~ /^$out->[$n]/)) {
+ push @good, ($use_re ? '!~' : '!=');
+ }
+ else {
+ push @good, ($use_re ? '=~' : '==');
+ }
+ }
+ my $good = !(grep /!/, @good);
+ $tests++;
+ $failed++ unless $good;
+ print $good ? $OK : $FAILED, "\n";
+ if (!$good || $opt_v) {
+ for (my $n=0; $n < $nmax; $n++) {
+ my $l = defined($out->[$n]) ? $out->[$n] : "~";
+ chomp $l;
+ my $r = defined($result->[$n]) ? $result->[$n] : "~";
+ chomp $r;
+ print sprintf("%-" . ($width-3) . "s %s %s\n",
+ $r, $good[$n], $l);
+ }
+ }
+}
+
+
+sub su($) {
+ my ($user) = @_;
+
+ $user ||= "root";
+
+ my ($login, $pass, $uid, $gid) = getpwnam($user)
+ or return [ "su: user $user does not exist\n" ];
+ my @groups = ();
+ my $fh = new FileHandle("/etc/group")
+ or return [ "opening /etc/group: $!\n" ];
+ while (<$fh>) {
+ chomp;
+ my ($group, $passwd, $gid, $users) = split /:/;
+ foreach my $u (split /,/, $users) {
+ push @groups, $gid
+ if ($user eq $u);
+ }
+ }
+ $fh->close;
+
+ my $groups = join(" ", ($gid, $gid, @groups));
+ #print STDERR "[[$groups]]\n";
+ $! = 0; # reset errno
+ $> = 0;
+ $( = $gid;
+ $) = $groups;
+ if ($!) {
+ return [ "su: $!\n" ];
+ }
+ if ($uid != 0) {
+ $> = $uid;
+ #$< = $uid;
+ if ($!) {
+ return [ "su: $prog->[1]: $!\n" ];
+ }
+ }
+ #print STDERR "[($>,$<)($(,$))]";
+ return [];
+}
+
+
+sub sg($) {
+ my ($group) = @_;
+
+ my $gid = getgrnam($group)
+ or return [ "sg: group $group does not exist\n" ];
+ my %groups = map { $_ eq $gid ? () : ($_ => 1) } (split /\s/, $));
+
+ #print STDERR "<<", join("/", keys %groups), ">>\n";
+ my $groups = join(" ", ($gid, $gid, keys %groups));
+ #print STDERR "[[$groups]]\n";
+ $! = 0; # reset errno
+ if ($> != 0) {
+ my $uid = $>;
+ $> = 0;
+ $( = $gid;
+ $) = $groups;
+ $> = $uid;
+ } else {
+ $( = $gid;
+ $) = $groups;
+ }
+ if ($!) {
+ return [ "sg: $!\n" ];
+ }
+ print STDERR "[($>,$<)($(,$))]";
+ return [];
+}
+
+
+sub exec_test($$) {
+ my ($prog, $in) = @_;
+ local (*IN, *IN_DUP, *IN2, *OUT_DUP, *OUT, *OUT2);
+ my $needs_shell = (join('', @$prog) =~ /[][|<>"'`\$\*\?]/);
+
+ if ($prog->[0] eq "umask") {
+ umask oct $prog->[1];
+ return [];
+ } elsif ($prog->[0] eq "cd") {
+ if (!chdir $prog->[1]) {
+ return [ "chdir: $prog->[1]: $!\n" ];
+ }
+ $ENV{PWD} = getcwd;
+ return [];
+ } elsif ($prog->[0] eq "su") {
+ return su($prog->[1]);
+ } elsif ($prog->[0] eq "sg") {
+ return sg($prog->[1]);
+ } elsif ($prog->[0] eq "export") {
+ my ($name, $value) = split /=/, $prog->[1];
+ # FIXME: need to evaluate $value, so that things like this will work:
+ # export dir=$PWD/dir
+ $ENV{$name} = $value;
+ return [];
+ } elsif ($prog->[0] eq "unset") {
+ delete $ENV{$prog->[1]};
+ return [];
+ }
+
+ pipe *IN2, *OUT
+ or die "Can't create pipe for reading: $!";
+ open *IN_DUP, "<&STDIN"
+ or *IN_DUP = undef;
+ open *STDIN, "<&IN2"
+ or die "Can't duplicate pipe for reading: $!";
+ close *IN2;
+
+ open *OUT_DUP, ">&STDOUT"
+ or die "Can't duplicate STDOUT: $!";
+ pipe *IN, *OUT2
+ or die "Can't create pipe for writing: $!";
+ open *STDOUT, ">&OUT2"
+ or die "Can't duplicate pipe for writing: $!";
+ close *OUT2;
+
+ *STDOUT->autoflush();
+ *OUT->autoflush();
+
+ $SIG{CHLD} = 'IGNORE';
+
+ if (fork()) {
+ # Server
+ if (*IN_DUP) {
+ open *STDIN, "<&IN_DUP"
+ or die "Can't duplicate STDIN: $!";
+ close *IN_DUP
+ or die "Can't close STDIN duplicate: $!";
+ }
+ open *STDOUT, ">&OUT_DUP"
+ or die "Can't duplicate STDOUT: $!";
+ close *OUT_DUP
+ or die "Can't close STDOUT duplicate: $!";
+
+ foreach my $line (@$in) {
+ #print "> $line";
+ print OUT $line;
+ }
+ close *OUT
+ or die "Can't close pipe for writing: $!";
+
+ my $result = [];
+ while (<IN>) {
+ #print "< $_";
+ if ($needs_shell) {
+ s#^/bin/sh: line \d+: ##;
+ }
+ push @$result, $_;
+ }
+ return $result;
+ } else {
+ # Client
+ $< = $>;
+ close IN
+ or die "Can't close read end for input pipe: $!";
+ close OUT
+ or die "Can't close write end for output pipe: $!";
+ close OUT_DUP
+ or die "Can't close STDOUT duplicate: $!";
+ local *ERR_DUP;
+ open ERR_DUP, ">&STDERR"
+ or die "Can't duplicate STDERR: $!";
+ open STDERR, ">&STDOUT"
+ or die "Can't join STDOUT and STDERR: $!";
+
+ if ($needs_shell) {
+ exec ('/bin/sh', '-c', join(" ", @$prog));
+ } else {
+ exec @$prog;
+ }
+ print STDERR $prog->[0], ": $!\n";
+ exit;
+ }
+}
+
diff --git a/tests/sys/acl/tools-crossfs.test b/tests/sys/acl/tools-crossfs.test
new file mode 100644
index 000000000000..1f596376331c
--- /dev/null
+++ b/tests/sys/acl/tools-crossfs.test
@@ -0,0 +1,323 @@
+# Copyright (c) 2008, 2009 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE 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 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$
+#
+
+# This is a tools-level test intended to verify that cp(1) and mv(1)
+# do the right thing with respect to ACLs. Run it as root using
+# ACL-enabled kernel:
+#
+# /usr/src/tools/regression/acltools/run /usr/src/tools/regression/acltools/tools-nfs4.test
+#
+# You need to have three subdirectories, named nfs4, posix and none,
+# with filesystems with NFSv4 ACLs, POSIX.1e ACLs and no ACLs enabled,
+# respectively, mounted on them, in your current directory.
+#
+# WARNING: Creates files in unsafe way.
+
+$ whoami
+> root
+$ umask 022
+
+$ touch nfs4/xxx
+$ getfacl -nq nfs4/xxx
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ touch posix/xxx
+$ getfacl -nq posix/xxx
+> user::rw-
+> group::r--
+> other::r--
+
+# mv with POSIX.1e ACLs.
+$ rm -f posix/xxx
+$ rm -f posix/yyy
+$ touch posix/xxx
+$ chmod 456 posix/xxx
+$ ls -l posix/xxx | cut -d' ' -f1
+> -r--r-xrw-
+$ setfacl -m u:42:x,g:43:w posix/xxx
+$ mv posix/xxx posix/yyy
+$ getfacl -nq posix/yyy
+> user::r--
+> user:42:--x
+> group::r-x
+> group:43:-w-
+> mask::rwx
+> other::rw-
+$ ls -l posix/yyy | cut -d' ' -f1
+> -r--rwxrw-+
+
+# mv from POSIX.1e to none.
+$ rm -f posix/xxx
+$ rm -f none/xxx
+$ touch posix/xxx
+$ chmod 345 posix/xxx
+$ setfacl -m u:42:x,g:43:w posix/xxx
+$ ls -l posix/xxx | cut -d' ' -f1
+> --wxrwxr-x+
+$ mv posix/xxx none/xxx
+> mv: failed to set acl entries for none/xxx: Operation not supported
+$ ls -l none/xxx | cut -d' ' -f1
+> --wxrwxr-x
+
+# mv from POSIX.1e to NFSv4.
+$ rm -f posix/xxx
+$ rm -f nfs4/xxx
+$ touch posix/xxx
+$ chmod 456 posix/xxx
+$ setfacl -m u:42:x,g:43:w posix/xxx
+$ ls -l posix/xxx | cut -d' ' -f1
+> -r--rwxrw-+
+$ mv posix/yyy nfs4/xxx
+> mv: failed to set acl entries for nfs4/xxx: Invalid argument
+$ getfacl -nq nfs4/xxx
+> owner@:-wxp----------:-------:deny
+> owner@:r-----aARWcCos:-------:allow
+> group@:rwxp--a-R-c--s:-------:allow
+> everyone@:rw-p--a-R-c--s:-------:allow
+$ ls -l nfs4/xxx | cut -d' ' -f1
+> -r--rwxrw-
+
+# mv with NFSv4 ACLs.
+$ rm -f nfs4/xxx
+$ rm -f nfs4/yyy
+$ touch nfs4/xxx
+$ setfacl -a0 u:42:x:allow,g:43:w:allow nfs4/xxx
+$ mv nfs4/xxx nfs4/yyy
+$ getfacl -nq nfs4/yyy
+> user:42:--x-----------:-------:allow
+> group:43:-w------------:-------:allow
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> everyone@:r-----a-R-c--s:-------:allow
+$ ls -l nfs4/yyy | cut -d' ' -f1
+> -rw-r--r--+
+
+# mv from NFSv4 to POSIX.1e without any ACLs.
+$ rm -f nfs4/xxx
+$ rm -f posix/xxx
+$ touch nfs4/xxx
+$ chmod 456 nfs4/xxx
+$ ls -l nfs4/xxx | cut -d' ' -f1
+> -r--r-xrw-
+$ mv nfs4/xxx posix/xxx
+$ ls -l posix/xxx | cut -d' ' -f1
+> -r--r-xrw-
+
+# mv from NFSv4 to none.
+$ rm -f nfs4/xxx
+$ rm -f none/xxx
+$ touch nfs4/xxx
+$ chmod 345 nfs4/xxx
+$ ls -l nfs4/xxx | cut -d' ' -f1
+> --wxr--r-x
+$ setfacl -a0 u:42:x:allow,g:43:w:allow nfs4/xxx
+$ ls -l nfs4/xxx | cut -d' ' -f1
+> --wxr--r-x+
+$ mv nfs4/xxx none/xxx
+> mv: failed to set acl entries for none/xxx: Operation not supported
+$ ls -l none/xxx | cut -d' ' -f1
+> --wxr--r-x
+
+# mv from NFSv4 to POSIX.1e.
+$ rm -f nfs4/xxx
+$ rm -f posix/xxx
+$ touch nfs4/xxx
+$ chmod 345 nfs4/xxx
+$ ls -l nfs4/xxx | cut -d' ' -f1
+> --wxr--r-x
+$ setfacl -a0 u:42:x:allow,g:43:w:allow nfs4/xxx
+$ ls -l nfs4/xxx | cut -d' ' -f1
+> --wxr--r-x+
+$ mv nfs4/xxx posix/xxx
+> mv: failed to set acl entries for posix/xxx: Invalid argument
+$ ls -l posix/xxx | cut -d' ' -f1
+> --wxr--r-x
+
+# cp with POSIX.1e ACLs.
+$ rm -f posix/xxx
+$ rm -f posix/yyy
+$ touch posix/xxx
+$ setfacl -m u:42:x,g:43:w posix/xxx
+$ ls -l posix/xxx | cut -d' ' -f1
+> -rw-rwxr--+
+$ cp posix/xxx posix/yyy
+$ ls -l posix/yyy | cut -d' ' -f1
+> -rw-r-xr--
+
+# cp -p with POSIX.1e ACLs.
+$ rm -f posix/xxx
+$ rm -f posix/yyy
+$ touch posix/xxx
+$ setfacl -m u:42:x,g:43:w posix/xxx
+$ getfacl -nq posix/xxx
+> user::rw-
+> user:42:--x
+> group::r--
+> group:43:-w-
+> mask::rwx
+> other::r--
+$ ls -l posix/xxx | cut -d' ' -f1
+> -rw-rwxr--+
+$ cp -p posix/xxx posix/yyy
+$ getfacl -nq posix/yyy
+> user::rw-
+> user:42:--x
+> group::r--
+> group:43:-w-
+> mask::rwx
+> other::r--
+$ ls -l posix/yyy | cut -d' ' -f1
+> -rw-rwxr--+
+
+# cp from POSIX.1e to none.
+$ rm -f posix/xxx
+$ rm -f none/xxx
+$ touch posix/xxx
+$ setfacl -m u:42:x,g:43:w posix/xxx
+$ ls -l posix/xxx | cut -d' ' -f1
+> -rw-rwxr--+
+$ cp posix/xxx none/xxx
+$ ls -l none/xxx | cut -d' ' -f1
+> -rw-r-xr--
+
+# cp -p from POSIX.1e to none.
+$ rm -f posix/xxx
+$ rm -f none/xxx
+$ touch posix/xxx
+$ setfacl -m u:42:x,g:43:w posix/xxx
+$ ls -l posix/xxx | cut -d' ' -f1
+> -rw-rwxr--+
+$ cp -p posix/xxx none/xxx
+> cp: failed to set acl entries for none/xxx: Operation not supported
+$ ls -l none/xxx | cut -d' ' -f1
+> -rw-rwxr--
+
+# cp from POSIX.1e to NFSv4.
+$ rm -f posix/xxx
+$ rm -f nfs4/xxx
+$ touch posix/xxx
+$ setfacl -m u:42:x,g:43:w posix/xxx
+$ ls -l posix/xxx | cut -d' ' -f1
+> -rw-rwxr--+
+$ cp posix/xxx nfs4/xxx
+$ ls -l nfs4/xxx | cut -d' ' -f1
+> -rw-r-xr--
+
+# cp -p from POSIX.1e to NFSv4.
+$ rm -f posix/xxx
+$ rm -f nfs4/xxx
+$ touch posix/xxx
+$ setfacl -m u:42:x,g:43:w posix/xxx
+$ ls -l posix/xxx | cut -d' ' -f1
+> -rw-rwxr--+
+$ cp -p posix/xxx nfs4/xxx
+> cp: failed to set acl entries for nfs4/xxx: Invalid argument
+$ ls -l nfs4/xxx | cut -d' ' -f1
+> -rw-rwxr--
+
+# cp with NFSv4 ACLs.
+$ rm -f nfs4/xxx
+$ rm -f nfs4/yyy
+$ touch nfs4/xxx
+$ chmod 543 nfs4/xxx
+$ setfacl -a0 u:42:x:allow,g:43:w:allow nfs4/xxx
+$ ls -l nfs4/xxx | cut -d' ' -f1
+> -r-xr---wx+
+$ cp nfs4/xxx nfs4/yyy
+$ ls -l nfs4/yyy | cut -d' ' -f1
+> -r-xr----x
+
+# cp -p with NFSv4 ACLs.
+$ rm -f nfs4/xxx
+$ rm -f nfs4/yyy
+$ touch nfs4/xxx
+$ chmod 543 nfs4/xxx
+$ setfacl -a0 u:42:x:allow,g:43:w:allow nfs4/xxx
+$ cp -p nfs4/xxx nfs4/yyy
+$ getfacl -nq nfs4/yyy
+> user:42:--x-----------:-------:allow
+> group:43:-w------------:-------:allow
+> owner@:--x-----------:-------:allow
+> owner@:-w-p----------:-------:deny
+> group@:-wxp----------:-------:deny
+> owner@:r-x---aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> everyone@:-wxp--a-R-c--s:-------:allow
+$ ls -l nfs4/yyy | cut -d' ' -f1
+> -r-xr---wx+
+
+# cp from NFSv4 to none.
+$ rm -f nfs4/xxx
+$ rm -f none/xxx
+$ touch nfs4/xxx
+$ chmod 543 nfs4/xxx
+$ setfacl -a0 u:42:x:allow,g:43:w:allow nfs4/xxx
+$ ls -l nfs4/xxx | cut -d' ' -f1
+> -r-xr---wx+
+$ cp nfs4/xxx none/xxx
+$ ls -l none/xxx | cut -d' ' -f1
+> -r-xr----x
+
+# cp -p from NFSv4 to none.
+$ rm -f nfs4/xxx
+$ rm -f none/xxx
+$ touch nfs4/xxx
+$ chmod 543 nfs4/xxx
+$ setfacl -a0 u:42:x:allow,g:43:w:allow nfs4/xxx
+$ ls -l nfs4/xxx | cut -d' ' -f1
+> -r-xr---wx+
+$ cp -p nfs4/xxx none/xxx
+> cp: failed to set acl entries for none/xxx: Operation not supported
+$ ls -l none/xxx | cut -d' ' -f1
+> -r-xr---wx
+
+# cp from NFSv4 to POSIX.1e.
+$ rm -f nfs4/xxx
+$ rm -f posix/xxx
+$ touch nfs4/xxx
+$ chmod 543 nfs4/xxx
+$ setfacl -a0 u:42:x:allow,g:43:w:allow nfs4/xxx
+$ ls -l nfs4/xxx | cut -d' ' -f1
+> -r-xr---wx+
+$ cp nfs4/xxx posix/xxx
+$ ls -l posix/xxx | cut -d' ' -f1
+> -r-xr----x
+
+# cp -p from NFSv4 to POSIX.1e.
+$ rm -f nfs4/xxx
+$ rm -f posix/xxx
+$ touch nfs4/xxx
+$ chmod 543 nfs4/xxx
+$ setfacl -a0 u:42:x:allow,g:43:w:allow nfs4/xxx
+$ ls -l nfs4/xxx | cut -d' ' -f1
+> -r-xr---wx+
+$ cp -p nfs4/xxx posix/xxx
+> cp: failed to set acl entries for posix/xxx: Invalid argument
+$ ls -l posix/xxx | cut -d' ' -f1
+> -r-xr---wx
diff --git a/tests/sys/acl/tools-nfs4-psarc.test b/tests/sys/acl/tools-nfs4-psarc.test
new file mode 100644
index 000000000000..9ab0b51eedf5
--- /dev/null
+++ b/tests/sys/acl/tools-nfs4-psarc.test
@@ -0,0 +1,562 @@
+# Copyright (c) 2008, 2009 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE 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 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$
+#
+
+# This is a tools-level test for NFSv4 ACL functionality with PSARC/2010/029
+# semantics. Run it as root using ACL-enabled kernel:
+#
+# /usr/src/tools/regression/acltools/run /usr/src/tools/regression/acltools/tools-nfs4-psarc.test
+#
+# WARNING: Creates files in unsafe way.
+
+$ whoami
+> root
+$ umask 022
+
+# Smoke test for getfacl(1).
+$ touch xxx
+$ getfacl xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ getfacl -q xxx
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Check verbose mode formatting.
+$ getfacl -v xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:read_data/write_data/append_data/read_attributes/write_attributes/read_xattr/write_xattr/read_acl/write_acl/write_owner/synchronize::allow
+> group@:read_data/read_attributes/read_xattr/read_acl/synchronize::allow
+> everyone@:read_data/read_attributes/read_xattr/read_acl/synchronize::allow
+
+# Test setfacl -a.
+$ setfacl -a2 u:0:write_acl:allow,g:1:read_acl:deny xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> user:0:-----------C--:-------:allow
+> group:1:----------c---:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Test user and group name resolving.
+$ rm xxx
+$ touch xxx
+$ setfacl -a2 u:root:write_acl:allow,g:daemon:read_acl:deny xxx
+$ getfacl xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> user:root:-----------C--:-------:allow
+> group:daemon:----------c---:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Check whether ls correctly marks files with "+".
+$ ls -l xxx | cut -d' ' -f1
+> -rw-r--r--+
+
+# Test removing entries by number.
+$ setfacl -x 1 xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:rw-p--aARWcCos:-------:allow
+> user:0:-----------C--:-------:allow
+> group:1:----------c---:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Test setfacl -m.
+$ setfacl -a0 everyone@:rwx:deny xxx
+$ setfacl -a0 everyone@:rwx:deny xxx
+$ setfacl -a0 everyone@:rwx:deny xxx
+$ setfacl -m everyone@::deny xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> everyone@:--------------:-------:deny
+> everyone@:--------------:-------:deny
+> everyone@:--------------:-------:deny
+> owner@:rw-p--aARWcCos:-------:allow
+> user:0:-----------C--:-------:allow
+> group:1:----------c---:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Test getfacl -i.
+$ getfacl -i xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> everyone@:--------------:-------:deny
+> everyone@:--------------:-------:deny
+> everyone@:--------------:-------:deny
+> owner@:rw-p--aARWcCos:-------:allow
+> user:root:-----------C--:-------:allow:0
+> group:daemon:----------c---:-------:deny:1
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Make sure cp without any flags does not copy copy the ACL.
+$ cp xxx yyy
+$ ls -l yyy | cut -d' ' -f1
+> -rw-r--r--
+
+# Make sure it does with the "-p" flag.
+$ rm yyy
+$ cp -p xxx yyy
+$ getfacl -n yyy
+> # file: yyy
+> # owner: root
+> # group: wheel
+> everyone@:--------------:-------:deny
+> everyone@:--------------:-------:deny
+> everyone@:--------------:-------:deny
+> owner@:rw-p--aARWcCos:-------:allow
+> user:0:-----------C--:-------:allow
+> group:1:----------c---:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ rm yyy
+
+# Test removing entries by... by example?
+$ setfacl -x everyone@::deny xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:rw-p--aARWcCos:-------:allow
+> user:0:-----------C--:-------:allow
+> group:1:----------c---:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Test setfacl -b.
+$ setfacl -b xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ ls -l xxx | cut -d' ' -f1
+> -rw-r--r--
+
+# Check setfacl(1) and getfacl(1) with multiple files.
+$ touch xxx yyy zzz
+
+$ ls -l xxx yyy zzz | cut -d' ' -f1
+> -rw-r--r--
+> -rw-r--r--
+> -rw-r--r--
+
+$ setfacl -m u:42:x:allow,g:43:w:allow nnn xxx yyy zzz
+> setfacl: nnn: stat() failed: No such file or directory
+
+$ ls -l nnn xxx yyy zzz | cut -d' ' -f1
+> ls: nnn: No such file or directory
+> -rw-r--r--+
+> -rw-r--r--+
+> -rw-r--r--+
+
+$ getfacl -nq nnn xxx yyy zzz
+> getfacl: nnn: stat() failed: No such file or directory
+> user:42:--x-----------:-------:allow
+> group:43:-w------------:-------:allow
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> everyone@:r-----a-R-c--s:-------:allow
+>
+> user:42:--x-----------:-------:allow
+> group:43:-w------------:-------:allow
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> everyone@:r-----a-R-c--s:-------:allow
+>
+> user:42:--x-----------:-------:allow
+> group:43:-w------------:-------:allow
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ setfacl -b nnn xxx yyy zzz
+> setfacl: nnn: stat() failed: No such file or directory
+
+$ ls -l nnn xxx yyy zzz | cut -d' ' -f1
+> ls: nnn: No such file or directory
+> -rw-r--r--
+> -rw-r--r--
+> -rw-r--r--
+
+$ rm xxx yyy zzz
+
+# Test applying mode to an ACL.
+$ touch xxx
+$ setfacl -a0 user:42:r:allow,user:43:w:deny,user:43:w:allow,user:44:x:allow -x everyone@::allow xxx
+$ chmod 600 xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:------a-R-c--s:-------:allow
+> everyone@:------a-R-c--s:-------:allow
+
+$ ls -l xxx | cut -d' ' -f1
+> -rw-------
+
+$ rm xxx
+$ touch xxx
+$ chown 42 xxx
+$ setfacl -a0 user:42:r:allow,user:43:w:deny,user:43:w:allow,user:44:x:allow xxx
+$ chmod 600 xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: 42
+> # group: wheel
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:------a-R-c--s:-------:allow
+> everyone@:------a-R-c--s:-------:allow
+$ ls -l xxx | cut -d' ' -f1
+> -rw-------
+
+$ rm xxx
+$ touch xxx
+$ chown 43 xxx
+$ setfacl -a0 user:42:r:allow,user:43:w:deny,user:43:w:allow,user:44:x:allow xxx
+$ chmod 124 xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: 43
+> # group: wheel
+> owner@:rw-p----------:-------:deny
+> group@:r-------------:-------:deny
+> owner@:--x---aARWcCos:-------:allow
+> group@:-w-p--a-R-c--s:-------:allow
+> everyone@:r-----a-R-c--s:-------:allow
+$ ls -l xxx | cut -d' ' -f1
+> ---x-w-r--
+
+$ rm xxx
+$ touch xxx
+$ chown 43 xxx
+$ setfacl -a0 user:42:r:allow,user:43:w:deny,user:43:w:allow,user:44:x:allow xxx
+$ chmod 412 xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: 43
+> # group: wheel
+> owner@:-wxp----------:-------:deny
+> group@:-w-p----------:-------:deny
+> owner@:r-----aARWcCos:-------:allow
+> group@:--x---a-R-c--s:-------:allow
+> everyone@:-w-p--a-R-c--s:-------:allow
+$ ls -l xxx | cut -d' ' -f1
+> -r----x-w-
+
+$ mkdir ddd
+$ setfacl -a0 group:44:rwapd:allow ddd
+$ setfacl -a0 group:43:write_data/delete_child:d:deny,group@:ad:allow ddd
+$ setfacl -a0 user:42:rx:fi:allow,group:42:write_data/delete_child:d:allow ddd
+$ setfacl -m everyone@:-w-p--a-R-c--s:fi:allow ddd
+$ getfacl -n ddd
+> # file: ddd
+> # owner: root
+> # group: wheel
+> user:42:r-x-----------:f-i----:allow
+> group:42:-w--D---------:-d-----:allow
+> group:43:-w--D---------:-d-----:deny
+> group@:-----da-------:-------:allow
+> group:44:rw-p-da-------:-------:allow
+> owner@:rwxp--aARWcCos:-------:allow
+> group@:r-x---a-R-c--s:-------:allow
+> everyone@:-w-p--a-R-c--s:f-i----:allow
+
+$ chmod 777 ddd
+$ getfacl -n ddd
+> # file: ddd
+> # owner: root
+> # group: wheel
+> owner@:rwxp--aARWcCos:-------:allow
+> group@:rwxp--a-R-c--s:-------:allow
+> everyone@:rwxp--a-R-c--s:-------:allow
+
+# Test applying ACL to mode.
+$ rmdir ddd
+$ mkdir ddd
+$ setfacl -a0 u:42:rwx:fi:allow ddd
+$ ls -ld ddd | cut -d' ' -f1
+> drwxr-xr-x+
+
+$ rmdir ddd
+$ mkdir ddd
+$ chmod 0 ddd
+$ setfacl -a0 owner@:r:allow,group@:w:deny,group@:wx:allow ddd
+$ ls -ld ddd | cut -d' ' -f1
+> dr----x---+
+
+$ rmdir ddd
+$ mkdir ddd
+$ chmod 0 ddd
+$ setfacl -a0 owner@:r:allow,group@:w:fi:deny,group@:wx:allow ddd
+$ ls -ld ddd | cut -d' ' -f1
+> dr---wx---+
+
+$ rmdir ddd
+$ mkdir ddd
+$ chmod 0 ddd
+$ setfacl -a0 owner@:r:allow,group:43:w:deny,group:43:wx:allow ddd
+$ ls -ld ddd | cut -d' ' -f1
+> dr--------+
+
+$ rmdir ddd
+$ mkdir ddd
+$ chmod 0 ddd
+$ setfacl -a0 owner@:r:allow,user:43:w:deny,user:43:wx:allow ddd
+$ ls -ld ddd | cut -d' ' -f1
+> dr--------+
+
+# Test inheritance.
+$ rmdir ddd
+$ mkdir ddd
+$ setfacl -a0 group:43:write_data/write_acl:fin:deny,u:43:rwxp:allow ddd
+$ setfacl -a0 user:42:rx:fi:allow,group:42:write_data/delete_child:dn:deny ddd
+$ setfacl -a0 user:42:write_acl/write_owner:fi:allow ddd
+$ setfacl -a0 group:41:read_data/read_attributes:dni:allow ddd
+$ setfacl -a0 user:41:write_data/write_attributes:fn:allow ddd
+$ getfacl -qn ddd
+> user:41:-w-----A------:f--n---:allow
+> group:41:r-----a-------:-din---:allow
+> user:42:-----------Co-:f-i----:allow
+> user:42:r-x-----------:f-i----:allow
+> group:42:-w--D---------:-d-n---:deny
+> group:43:-w---------C--:f-in---:deny
+> user:43:rwxp----------:-------:allow
+> owner@:rwxp--aARWcCos:-------:allow
+> group@:r-x---a-R-c--s:-------:allow
+> everyone@:r-x---a-R-c--s:-------:allow
+
+$ cd ddd
+$ touch xxx
+$ getfacl -qn xxx
+> user:41:--------------:------I:allow
+> user:42:--------------:------I:allow
+> user:42:r-------------:------I:allow
+> group:43:-w---------C--:------I:deny
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ rm xxx
+$ umask 077
+$ touch xxx
+$ getfacl -qn xxx
+> user:41:--------------:------I:allow
+> user:42:--------------:------I:allow
+> user:42:--------------:------I:allow
+> group:43:-w---------C--:------I:deny
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:------a-R-c--s:-------:allow
+> everyone@:------a-R-c--s:-------:allow
+
+$ rm xxx
+$ umask 770
+$ touch xxx
+$ getfacl -qn xxx
+> owner@:rw-p----------:-------:deny
+> group@:rw-p----------:-------:deny
+> user:41:--------------:------I:allow
+> user:42:--------------:------I:allow
+> user:42:--------------:------I:allow
+> group:43:-w---------C--:------I:deny
+> owner@:------aARWcCos:-------:allow
+> group@:------a-R-c--s:-------:allow
+> everyone@:rw-p--a-R-c--s:-------:allow
+
+$ rm xxx
+$ umask 707
+$ touch xxx
+$ getfacl -qn xxx
+> owner@:rw-p----------:-------:deny
+> user:41:-w------------:------I:allow
+> user:42:--------------:------I:allow
+> user:42:r-------------:------I:allow
+> group:43:-w---------C--:------I:deny
+> owner@:------aARWcCos:-------:allow
+> group@:rw-p--a-R-c--s:-------:allow
+> everyone@:------a-R-c--s:-------:allow
+
+$ umask 077
+$ mkdir yyy
+$ getfacl -qn yyy
+> group:41:------a-------:------I:allow
+> user:42:-----------Co-:f-i---I:allow
+> user:42:r-x-----------:f-i---I:allow
+> group:42:-w--D---------:------I:deny
+> owner@:rwxp--aARWcCos:-------:allow
+> group@:------a-R-c--s:-------:allow
+> everyone@:------a-R-c--s:-------:allow
+
+$ rmdir yyy
+$ umask 770
+$ mkdir yyy
+$ getfacl -qn yyy
+> owner@:rwxp----------:-------:deny
+> group@:rwxp----------:-------:deny
+> group:41:------a-------:------I:allow
+> user:42:-----------Co-:f-i---I:allow
+> user:42:r-x-----------:f-i---I:allow
+> group:42:-w--D---------:------I:deny
+> owner@:------aARWcCos:-------:allow
+> group@:------a-R-c--s:-------:allow
+> everyone@:rwxp--a-R-c--s:-------:allow
+
+$ rmdir yyy
+$ umask 707
+$ mkdir yyy
+$ getfacl -qn yyy
+> owner@:rwxp----------:-------:deny
+> group:41:r-----a-------:------I:allow
+> user:42:-----------Co-:f-i---I:allow
+> user:42:r-x-----------:f-i---I:allow
+> group:42:-w--D---------:------I:deny
+> owner@:------aARWcCos:-------:allow
+> group@:rwxp--a-R-c--s:-------:allow
+> everyone@:------a-R-c--s:-------:allow
+
+# There is some complication regarding how write_acl and write_owner flags
+# get inherited. Make sure we got it right.
+$ setfacl -b .
+$ setfacl -a0 u:42:Co:f:allow .
+$ setfacl -a0 u:43:Co:d:allow .
+$ setfacl -a0 u:44:Co:fd:allow .
+$ setfacl -a0 u:45:Co:fi:allow .
+$ setfacl -a0 u:46:Co:di:allow .
+$ setfacl -a0 u:47:Co:fdi:allow .
+$ setfacl -a0 u:48:Co:fn:allow .
+$ setfacl -a0 u:49:Co:dn:allow .
+$ setfacl -a0 u:50:Co:fdn:allow .
+$ setfacl -a0 u:51:Co:fni:allow .
+$ setfacl -a0 u:52:Co:dni:allow .
+$ setfacl -a0 u:53:Co:fdni:allow .
+$ umask 022
+$ rm xxx
+$ touch xxx
+$ getfacl -nq xxx
+> user:53:--------------:------I:allow
+> user:51:--------------:------I:allow
+> user:50:--------------:------I:allow
+> user:48:--------------:------I:allow
+> user:47:--------------:------I:allow
+> user:45:--------------:------I:allow
+> user:44:--------------:------I:allow
+> user:42:--------------:------I:allow
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ rmdir yyy
+$ mkdir yyy
+$ getfacl -nq yyy
+> user:53:--------------:------I:allow
+> user:52:--------------:------I:allow
+> user:50:--------------:------I:allow
+> user:49:--------------:------I:allow
+> user:47:--------------:fd----I:allow
+> user:46:--------------:-d----I:allow
+> user:45:-----------Co-:f-i---I:allow
+> user:44:--------------:fd----I:allow
+> user:43:--------------:-d----I:allow
+> user:42:-----------Co-:f-i---I:allow
+> owner@:rwxp--aARWcCos:-------:allow
+> group@:r-x---a-R-c--s:-------:allow
+> everyone@:r-x---a-R-c--s:-------:allow
+
+$ setfacl -b .
+$ setfacl -a0 u:42:Co:f:deny .
+$ setfacl -a0 u:43:Co:d:deny .
+$ setfacl -a0 u:44:Co:fd:deny .
+$ setfacl -a0 u:45:Co:fi:deny .
+$ setfacl -a0 u:46:Co:di:deny .
+$ setfacl -a0 u:47:Co:fdi:deny .
+$ setfacl -a0 u:48:Co:fn:deny .
+$ setfacl -a0 u:49:Co:dn:deny .
+$ setfacl -a0 u:50:Co:fdn:deny .
+$ setfacl -a0 u:51:Co:fni:deny .
+$ setfacl -a0 u:52:Co:dni:deny .
+$ setfacl -a0 u:53:Co:fdni:deny .
+$ umask 022
+$ rm xxx
+$ touch xxx
+$ getfacl -nq xxx
+> user:53:-----------Co-:------I:deny
+> user:51:-----------Co-:------I:deny
+> user:50:-----------Co-:------I:deny
+> user:48:-----------Co-:------I:deny
+> user:47:-----------Co-:------I:deny
+> user:45:-----------Co-:------I:deny
+> user:44:-----------Co-:------I:deny
+> user:42:-----------Co-:------I:deny
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ rmdir yyy
+$ mkdir yyy
+$ getfacl -nq yyy
+> user:53:-----------Co-:------I:deny
+> user:52:-----------Co-:------I:deny
+> user:50:-----------Co-:------I:deny
+> user:49:-----------Co-:------I:deny
+> user:47:-----------Co-:fd----I:deny
+> user:46:-----------Co-:-d----I:deny
+> user:45:-----------Co-:f-i---I:deny
+> user:44:-----------Co-:fd----I:deny
+> user:43:-----------Co-:-d----I:deny
+> user:42:-----------Co-:f-i---I:deny
+> owner@:rwxp--aARWcCos:-------:allow
+> group@:r-x---a-R-c--s:-------:allow
+> everyone@:r-x---a-R-c--s:-------:allow
+
+$ rmdir yyy
+$ rm xxx
+$ cd ..
+$ rmdir ddd
+
+$ rm xxx
+
diff --git a/tests/sys/acl/tools-nfs4-trivial.test b/tests/sys/acl/tools-nfs4-trivial.test
new file mode 100644
index 000000000000..366b099d86b1
--- /dev/null
+++ b/tests/sys/acl/tools-nfs4-trivial.test
@@ -0,0 +1,82 @@
+# Copyright (c) 2011 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE 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 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$
+#
+
+# This is a tools-level test for acl_is_trivial_np(3). Run it as root on ZFS.
+# Note that this does not work on UFS with NFSv4 ACLs enabled - UFS recognizes
+# both kind of trivial ACLs and replaces it by the default one.
+#
+# WARNING: Creates files in unsafe way.
+
+$ whoami
+> root
+$ umask 022
+
+# Check whether ls(1) correctly recognizes PSARC/2010/029-style trivial ACLs.
+$ touch xxx
+
+$ ls -l xxx | cut -d' ' -f1
+> -rw-r--r--
+
+$ getfacl -q xxx
+> owner@:rw-p--aARWcCos:-------:allow
+> group@:r-----a-R-c--s:-------:allow
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Check whether ls(1) correctly recognizes draft-style trivial ACLs.
+$ rm xxx
+$ touch xxx
+$ setfacl -a0 owner@:x:deny,owner@:rwpAWCo:allow,group@:wxp:deny,group@:r:allow,everyone@:wxpAWCo:deny,everyone@:raRcs:allow xxx
+$ setfacl -x5 xxx
+$ setfacl -x5 xxx
+$ setfacl -x5 xxx
+
+$ ls -l xxx | cut -d' ' -f1
+> -rw-r--r--
+
+$ getfacl -q xxx
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> group@:-wxp----------:-------:deny
+> group@:r-------------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Make sure ls(1) actually can recognize something as non-trivial.
+$ setfacl -x0 xxx
+
+$ ls -l xxx | cut -d' ' -f1
+> -rw-r--r--+
+
+$ getfacl -q xxx
+> owner@:rw-p---A-W-Co-:-------:allow
+> group@:-wxp----------:-------:deny
+> group@:r-------------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ rm xxx
+
diff --git a/tests/sys/acl/tools-nfs4.test b/tests/sys/acl/tools-nfs4.test
new file mode 100644
index 000000000000..e2ddc2bb201e
--- /dev/null
+++ b/tests/sys/acl/tools-nfs4.test
@@ -0,0 +1,828 @@
+# Copyright (c) 2008, 2009 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE 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 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$
+#
+
+# This is a tools-level test for NFSv4 ACL functionality. Run it as root
+# using ACL-enabled kernel:
+#
+# /usr/src/tools/regression/acltools/run /usr/src/tools/regression/acltools/tools-nfs4.test
+#
+# WARNING: Creates files in unsafe way.
+
+$ whoami
+> root
+$ umask 022
+
+# Smoke test for getfacl(1).
+$ touch xxx
+$ getfacl xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> group@:-wxp----------:-------:deny
+> group@:r-------------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ getfacl -q xxx
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> group@:-wxp----------:-------:deny
+> group@:r-------------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Check verbose mode formatting.
+$ getfacl -v xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:execute::deny
+> owner@:read_data/write_data/append_data/write_attributes/write_xattr/write_acl/write_owner::allow
+> group@:write_data/execute/append_data::deny
+> group@:read_data::allow
+> everyone@:write_data/execute/append_data/write_attributes/write_xattr/write_acl/write_owner::deny
+> everyone@:read_data/read_attributes/read_xattr/read_acl/synchronize::allow
+
+# Test setfacl -a.
+$ setfacl -a2 u:0:write_acl:allow,g:1:read_acl:deny xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> user:0:-----------C--:-------:allow
+> group:1:----------c---:-------:deny
+> group@:-wxp----------:-------:deny
+> group@:r-------------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Test user and group name resolving.
+$ rm xxx
+$ touch xxx
+$ setfacl -a2 u:root:write_acl:allow,g:daemon:read_acl:deny xxx
+$ getfacl xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> user:root:-----------C--:-------:allow
+> group:daemon:----------c---:-------:deny
+> group@:-wxp----------:-------:deny
+> group@:r-------------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Check whether ls correctly marks files with "+".
+$ ls -l xxx | cut -d' ' -f1
+> -rw-r--r--+
+
+# Test removing entries by number.
+$ setfacl -x 4 xxx
+$ setfacl -x 4 xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> user:0:-----------C--:-------:allow
+> group:1:----------c---:-------:deny
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Test setfacl -m.
+$ setfacl -a0 everyone@:rwx:deny xxx
+$ setfacl -a0 everyone@:rwx:deny xxx
+$ setfacl -a0 everyone@:rwx:deny xxx
+$ setfacl -m everyone@::deny xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> everyone@:--------------:-------:deny
+> everyone@:--------------:-------:deny
+> everyone@:--------------:-------:deny
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> user:0:-----------C--:-------:allow
+> group:1:----------c---:-------:deny
+> everyone@:--------------:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Test getfacl -i.
+$ getfacl -i xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> everyone@:--------------:-------:deny
+> everyone@:--------------:-------:deny
+> everyone@:--------------:-------:deny
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> user:root:-----------C--:-------:allow:0
+> group:daemon:----------c---:-------:deny:1
+> everyone@:--------------:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Make sure cp without any flags does not copy copy the ACL.
+$ cp xxx yyy
+$ ls -l yyy | cut -d' ' -f1
+> -rw-r--r--
+
+# Make sure it does with the "-p" flag.
+$ rm yyy
+$ cp -p xxx yyy
+$ getfacl -n yyy
+> # file: yyy
+> # owner: root
+> # group: wheel
+> everyone@:--------------:-------:deny
+> everyone@:--------------:-------:deny
+> everyone@:--------------:-------:deny
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> user:0:-----------C--:-------:allow
+> group:1:----------c---:-------:deny
+> everyone@:--------------:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ rm yyy
+
+# Test removing entries by... by example?
+$ setfacl -x everyone@::deny xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> user:0:-----------C--:-------:allow
+> group:1:----------c---:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+# Test setfacl -b.
+$ setfacl -b xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> group@:-wxp----------:-------:deny
+> group@:r-------------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ ls -l xxx | cut -d' ' -f1
+> -rw-r--r--
+
+# Check setfacl(1) and getfacl(1) with multiple files.
+$ touch xxx yyy zzz
+
+$ ls -l xxx yyy zzz | cut -d' ' -f1
+> -rw-r--r--
+> -rw-r--r--
+> -rw-r--r--
+
+$ setfacl -m u:42:x:allow,g:43:w:allow nnn xxx yyy zzz
+> setfacl: nnn: stat() failed: No such file or directory
+
+$ ls -l nnn xxx yyy zzz | cut -d' ' -f1
+> ls: nnn: No such file or directory
+> -rw-r--r--+
+> -rw-r--r--+
+> -rw-r--r--+
+
+$ getfacl -nq nnn xxx yyy zzz
+> getfacl: nnn: stat() failed: No such file or directory
+> user:42:--x-----------:-------:allow
+> group:43:-w------------:-------:allow
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> group@:-wxp----------:-------:deny
+> group@:r-------------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+>
+> user:42:--x-----------:-------:allow
+> group:43:-w------------:-------:allow
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> group@:-wxp----------:-------:deny
+> group@:r-------------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+>
+> user:42:--x-----------:-------:allow
+> group:43:-w------------:-------:allow
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> group@:-wxp----------:-------:deny
+> group@:r-------------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ setfacl -b nnn xxx yyy zzz
+> setfacl: nnn: stat() failed: No such file or directory
+
+$ ls -l nnn xxx yyy zzz | cut -d' ' -f1
+> ls: nnn: No such file or directory
+> -rw-r--r--
+> -rw-r--r--
+> -rw-r--r--
+
+$ rm xxx yyy zzz
+
+# Test applying mode to an ACL.
+$ touch xxx
+$ setfacl -a0 user:42:r:allow,user:43:w:deny,user:43:w:allow,user:44:x:allow -x everyone@::allow xxx
+$ chmod 600 xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> user:42:r-------------:-------:deny
+> user:42:r-------------:-------:allow
+> user:43:-w------------:-------:deny
+> user:43:-w------------:-------:allow
+> user:44:--x-----------:-------:deny
+> user:44:--x-----------:-------:allow
+> owner@:--------------:-------:deny
+> owner@:-------A-W-Co-:-------:allow
+> group@:--------------:-------:deny
+> group@:--------------:-------:allow
+> everyone@:-------A-W-Co-:-------:deny
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> group@:rwxp----------:-------:deny
+> group@:--------------:-------:allow
+> everyone@:rwxp---A-W-Co-:-------:deny
+> everyone@:------a-R-c--s:-------:allow
+$ ls -l xxx | cut -d' ' -f1
+> -rw-------+
+
+$ rm xxx
+$ touch xxx
+$ chown 42 xxx
+$ setfacl -a0 user:42:r:allow,user:43:w:deny,user:43:w:allow,user:44:x:allow xxx
+$ chmod 600 xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: 42
+> # group: wheel
+> user:42:--------------:-------:deny
+> user:42:r-------------:-------:allow
+> user:43:-w------------:-------:deny
+> user:43:-w------------:-------:allow
+> user:44:--x-----------:-------:deny
+> user:44:--x-----------:-------:allow
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> group@:rwxp----------:-------:deny
+> group@:--------------:-------:allow
+> everyone@:rwxp---A-W-Co-:-------:deny
+> everyone@:------a-R-c--s:-------:allow
+$ ls -l xxx | cut -d' ' -f1
+> -rw-------+
+
+$ rm xxx
+$ touch xxx
+$ chown 43 xxx
+$ setfacl -a0 user:42:r:allow,user:43:w:deny,user:43:w:allow,user:44:x:allow xxx
+$ chmod 124 xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: 43
+> # group: wheel
+> user:42:r-------------:-------:deny
+> user:42:r-------------:-------:allow
+> user:43:-w------------:-------:deny
+> user:43:-w------------:-------:allow
+> user:44:--x-----------:-------:deny
+> user:44:--x-----------:-------:allow
+> owner@:rw-p----------:-------:deny
+> owner@:--x----A-W-Co-:-------:allow
+> group@:r-x-----------:-------:deny
+> group@:-w-p----------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+$ ls -l xxx | cut -d' ' -f1
+> ---x-w-r--+
+
+$ rm xxx
+$ touch xxx
+$ chown 43 xxx
+$ setfacl -a0 user:42:r:allow,user:43:w:deny,user:43:w:allow,user:44:x:allow xxx
+$ chmod 412 xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: 43
+> # group: wheel
+> user:42:r-------------:-------:deny
+> user:42:r-------------:-------:allow
+> user:43:-w------------:-------:deny
+> user:43:-w------------:-------:allow
+> user:44:--------------:-------:deny
+> user:44:--x-----------:-------:allow
+> owner@:-wxp----------:-------:deny
+> owner@:r------A-W-Co-:-------:allow
+> group@:rw-p----------:-------:deny
+> group@:--x-----------:-------:allow
+> everyone@:r-x----A-W-Co-:-------:deny
+> everyone@:-w-p--a-R-c--s:-------:allow
+$ ls -l xxx | cut -d' ' -f1
+> -r----x-w-+
+
+$ mkdir ddd
+$ setfacl -a0 group:44:rwapd:allow ddd
+$ setfacl -a0 group:43:write_data/delete_child:d:deny,group@:ad:allow ddd
+$ setfacl -a0 user:42:rx:fi:allow,group:42:write_data/delete_child:d:allow ddd
+$ setfacl -m everyone@:-w-p--a-R-c--s:fi:allow ddd
+$ getfacl -n ddd
+> # file: ddd
+> # owner: root
+> # group: wheel
+> user:42:r-x-----------:f-i----:allow
+> group:42:-w--D---------:-d-----:allow
+> group:43:-w--D---------:-d-----:deny
+> group@:-----da-------:-------:allow
+> group:44:rw-p-da-------:-------:allow
+> owner@:--------------:-------:deny
+> owner@:rwxp---A-W-Co-:-------:allow
+> group@:-w-p----------:-------:deny
+> group@:r-x-----------:-------:allow
+> everyone@:-w-p---A-W-Co-:-------:deny
+> everyone@:-w-p--a-R-c--s:f-i----:allow
+$ chmod 777 ddd
+$ getfacl -n ddd
+> # file: ddd
+> # owner: root
+> # group: wheel
+> user:42:r-x-----------:f-i----:allow
+> group:42:-w--D---------:-di----:allow
+> group:42:--------------:-------:deny
+> group:42:-w--D---------:-------:allow
+> group:43:-w--D---------:-di----:deny
+> group:43:-w--D---------:-------:deny
+> group@:-----da-------:-------:allow
+> group:44:--------------:-------:deny
+> group:44:rw-p-da-------:-------:allow
+> owner@:--------------:-------:deny
+> owner@:-------A-W-Co-:-------:allow
+> group@:--------------:-------:deny
+> group@:--------------:-------:allow
+> everyone@:-------A-W-Co-:-------:deny
+> everyone@:-w-p--a-R-c--s:f-i----:allow
+> owner@:--------------:-------:deny
+> owner@:rwxp---A-W-Co-:-------:allow
+> group@:--------------:-------:deny
+> group@:rwxp----------:-------:allow
+> everyone@:-------A-W-Co-:-------:deny
+> everyone@:rwxp--a-R-c--s:-------:allow
+
+$ rmdir ddd
+$ mkdir ddd
+$ setfacl -a0 group:44:rwapd:allow ddd
+$ setfacl -a0 group:43:write_data/delete_child:d:deny,group@:ad:allow ddd
+$ setfacl -a0 user:42:rx:fi:allow,group:42:write_data/delete_child:d:allow ddd
+$ setfacl -m everyone@:-w-p--a-R-c--s:fi:allow ddd
+$ chmod 124 ddd
+$ getfacl -n ddd
+> # file: ddd
+> # owner: root
+> # group: wheel
+> user:42:r-x-----------:f-i----:allow
+> group:42:-w--D---------:-di----:allow
+> group:42:--------------:-------:deny
+> group:42:----D---------:-------:allow
+> group:43:-w--D---------:-di----:deny
+> group:43:-w--D---------:-------:deny
+> group@:-----da-------:-------:allow
+> group:44:r-------------:-------:deny
+> group:44:r----da-------:-------:allow
+> owner@:--------------:-------:deny
+> owner@:-------A-W-Co-:-------:allow
+> group@:--------------:-------:deny
+> group@:--------------:-------:allow
+> everyone@:-------A-W-Co-:-------:deny
+> everyone@:-w-p--a-R-c--s:f-i----:allow
+> owner@:rw-p----------:-------:deny
+> owner@:--x----A-W-Co-:-------:allow
+> group@:r-x-----------:-------:deny
+> group@:-w-p----------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ rmdir ddd
+$ mkdir ddd
+$ setfacl -a0 group:44:rwapd:allow ddd
+$ setfacl -a0 group:43:write_data/delete_child:d:deny,group@:ad:allow ddd
+$ setfacl -a0 user:42:rx:allow,user:42:rx:fi:allow,group:42:write_data/delete_child:d:allow ddd
+$ setfacl -m everyone@:-w-p--a-R-c--s:fi:allow ddd
+$ chmod 412 ddd
+$ getfacl -n ddd
+> # file: ddd
+> # owner: root
+> # group: wheel
+> user:42:r-------------:-------:deny
+> user:42:r-x-----------:-------:allow
+> user:42:r-x-----------:f-i----:allow
+> group:42:-w--D---------:-di----:allow
+> group:42:-w------------:-------:deny
+> group:42:-w--D---------:-------:allow
+> group:43:-w--D---------:-di----:deny
+> group:43:-w--D---------:-------:deny
+> group@:-----da-------:-------:allow
+> group:44:rw-p----------:-------:deny
+> group:44:rw-p-da-------:-------:allow
+> owner@:--------------:-------:deny
+> owner@:-------A-W-Co-:-------:allow
+> group@:--------------:-------:deny
+> group@:--------------:-------:allow
+> everyone@:-------A-W-Co-:-------:deny
+> everyone@:-w-p--a-R-c--s:f-i----:allow
+> owner@:-wxp----------:-------:deny
+> owner@:r------A-W-Co-:-------:allow
+> group@:rw-p----------:-------:deny
+> group@:--x-----------:-------:allow
+> everyone@:r-x----A-W-Co-:-------:deny
+> everyone@:-w-p--a-R-c--s:-------:allow
+
+$ rmdir ddd
+$ mkdir ddd
+$ setfacl -a0 group:44:rwapd:allow ddd
+$ setfacl -a0 group:43:write_data/delete_child:d:deny,group@:ad:allow ddd
+$ setfacl -a0 user:42:rx:allow,user:42:rx:fi:allow,group:42:write_data/delete_child:d:allow ddd
+$ setfacl -m everyone@:-w-p--a-R-c--s:fi:allow ddd
+$ chown 42 ddd
+$ chmod 412 ddd
+$ getfacl -n ddd
+> # file: ddd
+> # owner: 42
+> # group: wheel
+> user:42:--x-----------:-------:deny
+> user:42:r-x-----------:-------:allow
+> user:42:r-x-----------:f-i----:allow
+> group:42:-w--D---------:-di----:allow
+> group:42:-w------------:-------:deny
+> group:42:-w--D---------:-------:allow
+> group:43:-w--D---------:-di----:deny
+> group:43:-w--D---------:-------:deny
+> group@:-----da-------:-------:allow
+> group:44:rw-p----------:-------:deny
+> group:44:rw-p-da-------:-------:allow
+> owner@:--------------:-------:deny
+> owner@:-------A-W-Co-:-------:allow
+> group@:--------------:-------:deny
+> group@:--------------:-------:allow
+> everyone@:-------A-W-Co-:-------:deny
+> everyone@:-w-p--a-R-c--s:f-i----:allow
+> owner@:-wxp----------:-------:deny
+> owner@:r------A-W-Co-:-------:allow
+> group@:rw-p----------:-------:deny
+> group@:--x-----------:-------:allow
+> everyone@:r-x----A-W-Co-:-------:deny
+> everyone@:-w-p--a-R-c--s:-------:allow
+
+# Test applying ACL to mode.
+$ rmdir ddd
+$ mkdir ddd
+$ setfacl -a0 u:42:rwx:fi:allow ddd
+$ ls -ld ddd | cut -d' ' -f1
+> drwxr-xr-x+
+
+$ rmdir ddd
+$ mkdir ddd
+$ chmod 0 ddd
+$ setfacl -a0 owner@:r:allow,group@:w:deny,group@:wx:allow ddd
+$ ls -ld ddd | cut -d' ' -f1
+> dr----x---+
+
+$ rmdir ddd
+$ mkdir ddd
+$ chmod 0 ddd
+$ setfacl -a0 owner@:r:allow,group@:w:fi:deny,group@:wx:allow ddd
+$ ls -ld ddd | cut -d' ' -f1
+> dr---wx---+
+
+$ rmdir ddd
+$ mkdir ddd
+$ chmod 0 ddd
+$ setfacl -a0 owner@:r:allow,group:43:w:deny,group:43:wx:allow ddd
+$ ls -ld ddd | cut -d' ' -f1
+> dr--------+
+
+$ rmdir ddd
+$ mkdir ddd
+$ chmod 0 ddd
+$ setfacl -a0 owner@:r:allow,user:43:w:deny,user:43:wx:allow ddd
+$ ls -ld ddd | cut -d' ' -f1
+> dr--------+
+
+# Test inheritance.
+$ rmdir ddd
+$ mkdir ddd
+$ setfacl -a0 group:43:write_data/write_acl:fin:deny,u:43:rwxp:allow ddd
+$ setfacl -a0 user:42:rx:fi:allow,group:42:write_data/delete_child:dn:deny ddd
+$ setfacl -a0 user:42:write_acl/write_owner:fi:allow ddd
+$ setfacl -a0 group:41:read_data/read_attributes:dni:allow ddd
+$ setfacl -a0 user:41:write_data/write_attributes:fn:allow ddd
+$ getfacl -qn ddd
+> user:41:-w-----A------:f--n---:allow
+> group:41:r-----a-------:-din---:allow
+> user:42:-----------Co-:f-i----:allow
+> user:42:r-x-----------:f-i----:allow
+> group:42:-w--D---------:-d-n---:deny
+> group:43:-w---------C--:f-in---:deny
+> user:43:rwxp----------:-------:allow
+> owner@:--------------:-------:deny
+> owner@:rwxp---A-W-Co-:-------:allow
+> group@:-w-p----------:-------:deny
+> group@:r-x-----------:-------:allow
+> everyone@:-w-p---A-W-Co-:-------:deny
+> everyone@:r-x---a-R-c--s:-------:allow
+
+$ cd ddd
+$ touch xxx
+$ getfacl -qn xxx
+> user:41:-w------------:-------:deny
+> user:41:-w-----A------:-------:allow
+> user:42:--------------:-------:deny
+> user:42:--------------:-------:allow
+> user:42:--x-----------:-------:deny
+> user:42:r-x-----------:-------:allow
+> group:43:-w---------C--:-------:deny
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> group@:-wxp----------:-------:deny
+> group@:r-------------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ rm xxx
+$ umask 077
+$ touch xxx
+$ getfacl -qn xxx
+> user:41:-w------------:-------:deny
+> user:41:-w-----A------:-------:allow
+> user:42:--------------:-------:deny
+> user:42:--------------:-------:allow
+> user:42:r-x-----------:-------:deny
+> user:42:r-x-----------:-------:allow
+> group:43:-w---------C--:-------:deny
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> group@:rwxp----------:-------:deny
+> group@:--------------:-------:allow
+> everyone@:rwxp---A-W-Co-:-------:deny
+> everyone@:------a-R-c--s:-------:allow
+
+$ rm xxx
+$ umask 770
+$ touch xxx
+$ getfacl -qn xxx
+> user:41:-w------------:-------:deny
+> user:41:-w-----A------:-------:allow
+> user:42:--------------:-------:deny
+> user:42:--------------:-------:allow
+> user:42:r-x-----------:-------:deny
+> user:42:r-x-----------:-------:allow
+> group:43:-w---------C--:-------:deny
+> owner@:rwxp----------:-------:deny
+> owner@:-------A-W-Co-:-------:allow
+> group@:rwxp----------:-------:deny
+> group@:--------------:-------:allow
+> everyone@:--x----A-W-Co-:-------:deny
+> everyone@:rw-p--a-R-c--s:-------:allow
+
+$ rm xxx
+$ umask 707
+$ touch xxx
+$ getfacl -qn xxx
+> user:41:--------------:-------:deny
+> user:41:-w-----A------:-------:allow
+> user:42:--------------:-------:deny
+> user:42:--------------:-------:allow
+> user:42:--x-----------:-------:deny
+> user:42:r-x-----------:-------:allow
+> group:43:-w---------C--:-------:deny
+> owner@:rwxp----------:-------:deny
+> owner@:-------A-W-Co-:-------:allow
+> group@:--x-----------:-------:deny
+> group@:rw-p----------:-------:allow
+> everyone@:rwxp---A-W-Co-:-------:deny
+> everyone@:------a-R-c--s:-------:allow
+
+$ umask 077
+$ mkdir yyy
+$ getfacl -qn yyy
+> group:41:r-------------:-------:deny
+> group:41:r-----a-------:-------:allow
+> user:42:-----------Co-:f-i----:allow
+> user:42:r-x-----------:f-i----:allow
+> group:42:-w--D---------:-------:deny
+> owner@:--------------:-------:deny
+> owner@:rwxp---A-W-Co-:-------:allow
+> group@:rwxp----------:-------:deny
+> group@:--------------:-------:allow
+> everyone@:rwxp---A-W-Co-:-------:deny
+> everyone@:------a-R-c--s:-------:allow
+
+$ rmdir yyy
+$ umask 770
+$ mkdir yyy
+$ getfacl -qn yyy
+> group:41:r-------------:-------:deny
+> group:41:r-----a-------:-------:allow
+> user:42:-----------Co-:f-i----:allow
+> user:42:r-x-----------:f-i----:allow
+> group:42:-w--D---------:-------:deny
+> owner@:rwxp----------:-------:deny
+> owner@:-------A-W-Co-:-------:allow
+> group@:rwxp----------:-------:deny
+> group@:--------------:-------:allow
+> everyone@:-------A-W-Co-:-------:deny
+> everyone@:rwxp--a-R-c--s:-------:allow
+
+$ rmdir yyy
+$ umask 707
+$ mkdir yyy
+$ getfacl -qn yyy
+> group:41:--------------:-------:deny
+> group:41:------a-------:-------:allow
+> user:42:-----------Co-:f-i----:allow
+> user:42:r-x-----------:f-i----:allow
+> group:42:-w--D---------:-------:deny
+> owner@:rwxp----------:-------:deny
+> owner@:-------A-W-Co-:-------:allow
+> group@:--------------:-------:deny
+> group@:rwxp----------:-------:allow
+> everyone@:rwxp---A-W-Co-:-------:deny
+> everyone@:------a-R-c--s:-------:allow
+
+# There is some complication regarding how write_acl and write_owner flags
+# get inherited. Make sure we got it right.
+$ setfacl -b .
+$ setfacl -a0 u:42:Co:f:allow .
+$ setfacl -a0 u:43:Co:d:allow .
+$ setfacl -a0 u:44:Co:fd:allow .
+$ setfacl -a0 u:45:Co:fi:allow .
+$ setfacl -a0 u:46:Co:di:allow .
+$ setfacl -a0 u:47:Co:fdi:allow .
+$ setfacl -a0 u:48:Co:fn:allow .
+$ setfacl -a0 u:49:Co:dn:allow .
+$ setfacl -a0 u:50:Co:fdn:allow .
+$ setfacl -a0 u:51:Co:fni:allow .
+$ setfacl -a0 u:52:Co:dni:allow .
+$ setfacl -a0 u:53:Co:fdni:allow .
+$ umask 022
+$ rm xxx
+$ touch xxx
+$ getfacl -nq xxx
+> user:53:--------------:-------:deny
+> user:53:--------------:-------:allow
+> user:51:--------------:-------:deny
+> user:51:--------------:-------:allow
+> user:50:--------------:-------:deny
+> user:50:--------------:-------:allow
+> user:48:--------------:-------:deny
+> user:48:--------------:-------:allow
+> user:47:--------------:-------:deny
+> user:47:--------------:-------:allow
+> user:45:--------------:-------:deny
+> user:45:--------------:-------:allow
+> user:44:--------------:-------:deny
+> user:44:--------------:-------:allow
+> user:42:--------------:-------:deny
+> user:42:--------------:-------:allow
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> group@:-wxp----------:-------:deny
+> group@:r-------------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ rmdir yyy
+$ mkdir yyy
+$ getfacl -nq yyy
+> user:53:--------------:-------:deny
+> user:53:--------------:-------:allow
+> user:52:--------------:-------:deny
+> user:52:--------------:-------:allow
+> user:50:--------------:-------:deny
+> user:50:--------------:-------:allow
+> user:49:--------------:-------:deny
+> user:49:--------------:-------:allow
+> user:47:-----------Co-:fdi----:allow
+> user:47:--------------:-------:deny
+> user:47:--------------:-------:allow
+> user:46:-----------Co-:-di----:allow
+> user:46:--------------:-------:deny
+> user:46:--------------:-------:allow
+> user:45:-----------Co-:f-i----:allow
+> user:44:-----------Co-:fdi----:allow
+> user:44:--------------:-------:deny
+> user:44:--------------:-------:allow
+> user:43:-----------Co-:-di----:allow
+> user:43:--------------:-------:deny
+> user:43:--------------:-------:allow
+> user:42:-----------Co-:f-i----:allow
+> owner@:--------------:-------:deny
+> owner@:rwxp---A-W-Co-:-------:allow
+> group@:-w-p----------:-------:deny
+> group@:r-x-----------:-------:allow
+> everyone@:-w-p---A-W-Co-:-------:deny
+> everyone@:r-x---a-R-c--s:-------:allow
+
+$ setfacl -b .
+$ setfacl -a0 u:42:Co:f:deny .
+$ setfacl -a0 u:43:Co:d:deny .
+$ setfacl -a0 u:44:Co:fd:deny .
+$ setfacl -a0 u:45:Co:fi:deny .
+$ setfacl -a0 u:46:Co:di:deny .
+$ setfacl -a0 u:47:Co:fdi:deny .
+$ setfacl -a0 u:48:Co:fn:deny .
+$ setfacl -a0 u:49:Co:dn:deny .
+$ setfacl -a0 u:50:Co:fdn:deny .
+$ setfacl -a0 u:51:Co:fni:deny .
+$ setfacl -a0 u:52:Co:dni:deny .
+$ setfacl -a0 u:53:Co:fdni:deny .
+$ umask 022
+$ rm xxx
+$ touch xxx
+$ getfacl -nq xxx
+> user:53:-----------Co-:-------:deny
+> user:51:-----------Co-:-------:deny
+> user:50:-----------Co-:-------:deny
+> user:48:-----------Co-:-------:deny
+> user:47:-----------Co-:-------:deny
+> user:45:-----------Co-:-------:deny
+> user:44:-----------Co-:-------:deny
+> user:42:-----------Co-:-------:deny
+> owner@:--x-----------:-------:deny
+> owner@:rw-p---A-W-Co-:-------:allow
+> group@:-wxp----------:-------:deny
+> group@:r-------------:-------:allow
+> everyone@:-wxp---A-W-Co-:-------:deny
+> everyone@:r-----a-R-c--s:-------:allow
+
+$ rmdir yyy
+$ mkdir yyy
+$ getfacl -nq yyy
+> user:53:-----------Co-:-------:deny
+> user:52:-----------Co-:-------:deny
+> user:50:-----------Co-:-------:deny
+> user:49:-----------Co-:-------:deny
+> user:47:-----------Co-:fdi----:deny
+> user:47:-----------Co-:-------:deny
+> user:46:-----------Co-:-di----:deny
+> user:46:-----------Co-:-------:deny
+> user:45:-----------Co-:f-i----:deny
+> user:44:-----------Co-:fdi----:deny
+> user:44:-----------Co-:-------:deny
+> user:43:-----------Co-:-di----:deny
+> user:43:-----------Co-:-------:deny
+> user:42:-----------Co-:f-i----:deny
+> owner@:--------------:-------:deny
+> owner@:rwxp---A-W-Co-:-------:allow
+> group@:-w-p----------:-------:deny
+> group@:r-x-----------:-------:allow
+> everyone@:-w-p---A-W-Co-:-------:deny
+> everyone@:r-x---a-R-c--s:-------:allow
+
+$ rmdir yyy
+$ rm xxx
+$ cd ..
+$ rmdir ddd
+
+$ rm xxx
+
diff --git a/tests/sys/acl/tools-posix.test b/tests/sys/acl/tools-posix.test
new file mode 100644
index 000000000000..4741db383fd6
--- /dev/null
+++ b/tests/sys/acl/tools-posix.test
@@ -0,0 +1,453 @@
+# Copyright (c) 2008, 2009 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE 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 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$
+#
+
+# This is a tools-level test for POSIX.1e ACL functionality. Run it as root
+# using ACL-enabled kernel:
+#
+# /usr/src/tools/regression/acltools/run /usr/src/tools/regression/acltools/tools-posix.test
+#
+# WARNING: Creates files in unsafe way.
+
+$ whoami
+> root
+$ umask 022
+
+# Smoke test for getfacl(1).
+$ touch xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> user::rw-
+> group::r--
+> other::r--
+
+$ getfacl -q xxx
+> user::rw-
+> group::r--
+> other::r--
+
+$ setfacl -m u:42:r,g:43:w xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> user::rw-
+> user:42:r--
+> group::r--
+> group:43:-w-
+> mask::rw-
+> other::r--
+
+# Check whether ls correctly marks files with "+".
+$ ls -l xxx | cut -d' ' -f1
+> -rw-rw-r--+
+
+# Same as above, but for symlinks.
+$ ln -s xxx lll
+$ getfacl -h lll
+> # file: lll
+> # owner: root
+> # group: wheel
+> user::rwx
+> group::r-x
+> other::r-x
+
+$ getfacl -qh lll
+> user::rwx
+> group::r-x
+> other::r-x
+
+$ getfacl -q lll
+> user::rw-
+> user:42:r--
+> group::r--
+> group:43:-w-
+> mask::rw-
+> other::r--
+
+$ setfacl -hm u:44:x,g:45:w lll
+$ getfacl -h lll
+> # file: lll
+> # owner: root
+> # group: wheel
+> user::rwx
+> user:44:--x
+> group::r-x
+> group:45:-w-
+> mask::rwx
+> other::r-x
+
+$ ls -l lll | cut -d' ' -f1
+> lrwxrwxr-x+
+
+# Check whether the original file is left untouched.
+$ ls -l xxx | cut -d' ' -f1
+> -rw-rw-r--+
+
+$ rm lll
+
+# Test removing entries.
+$ setfacl -x user:42: xxx
+$ getfacl xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> user::rw-
+> group::r--
+> group:43:-w-
+> mask::rw-
+> other::r--
+
+$ setfacl -m u:42:r xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> user::rw-
+> user:42:r--
+> group::r--
+> group:43:-w-
+> mask::rw-
+> other::r--
+
+# Test removing entries by number.
+$ setfacl -x 1 xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> user::rw-
+> group::r--
+> group:43:-w-
+> mask::rw-
+> other::r--
+
+$ setfacl -m g:43:r xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> user::rw-
+> group::r--
+> group:43:r--
+> mask::r--
+> other::r--
+
+# Make sure cp without any flags does not copy the ACL.
+$ cp xxx yyy
+$ ls -l yyy | cut -d' ' -f1
+> -rw-r--r--
+
+# Make sure it does with the "-p" flag.
+$ rm yyy
+$ cp -p xxx yyy
+$ getfacl -n yyy
+> # file: yyy
+> # owner: root
+> # group: wheel
+> user::rw-
+> group::r--
+> group:43:r--
+> mask::r--
+> other::r--
+
+$ rm yyy
+
+# Test removing entries by... by example?
+$ setfacl -m u:42:r,g:43:w xxx
+$ setfacl -x u:42: xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> user::rw-
+> group::r--
+> group:43:-w-
+> mask::rw-
+> other::r--
+
+# Test setfacl -b.
+$ setfacl -b xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> user::rw-
+> group::r--
+> mask::r--
+> other::r--
+
+$ ls -l xxx | cut -d' ' -f1
+> -rw-r--r--+
+
+$ setfacl -nb xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> user::rw-
+> group::r--
+> other::r--
+
+$ ls -l xxx | cut -d' ' -f1
+> -rw-r--r--
+
+# Check setfacl(1) and getfacl(1) with multiple files.
+$ touch xxx yyy zzz
+
+$ ls -l xxx yyy zzz | cut -d' ' -f1
+> -rw-r--r--
+> -rw-r--r--
+> -rw-r--r--
+
+$ setfacl -m u:42:x,g:43:w nnn xxx yyy zzz
+> setfacl: nnn: stat() failed: No such file or directory
+
+$ ls -l nnn xxx yyy zzz | cut -d' ' -f1
+> ls: nnn: No such file or directory
+> -rw-rwxr--+
+> -rw-rwxr--+
+> -rw-rwxr--+
+
+$ getfacl -nq nnn xxx yyy zzz
+> getfacl: nnn: stat() failed: No such file or directory
+> user::rw-
+> user:42:--x
+> group::r--
+> group:43:-w-
+> mask::rwx
+> other::r--
+>
+> user::rw-
+> user:42:--x
+> group::r--
+> group:43:-w-
+> mask::rwx
+> other::r--
+>
+> user::rw-
+> user:42:--x
+> group::r--
+> group:43:-w-
+> mask::rwx
+> other::r--
+
+$ setfacl -b nnn xxx yyy zzz
+> setfacl: nnn: stat() failed: No such file or directory
+
+$ ls -l nnn xxx yyy zzz | cut -d' ' -f1
+> ls: nnn: No such file or directory
+> -rw-r--r--+
+> -rw-r--r--+
+> -rw-r--r--+
+
+$ setfacl -bn nnn xxx yyy zzz
+> setfacl: nnn: stat() failed: No such file or directory
+
+$ ls -l nnn xxx yyy zzz | cut -d' ' -f1
+> ls: nnn: No such file or directory
+> -rw-r--r--
+> -rw-r--r--
+> -rw-r--r--
+
+$ rm xxx yyy zzz
+
+# Check whether chmod actually does what it should do.
+$ touch xxx
+$ setfacl -m u:42:rwx,g:43:rwx xxx
+$ chmod 600 xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> user::rw-
+> user:42:rwx # effective: ---
+> group::r-- # effective: ---
+> group:43:rwx # effective: ---
+> mask::---
+> other::---
+
+$ chmod 060 xxx
+$ getfacl -n xxx
+> # file: xxx
+> # owner: root
+> # group: wheel
+> user::---
+> user:42:rwx # effective: rw-
+> group::r--
+> group:43:rwx # effective: rw-
+> mask::rw-
+> other::---
+
+# Test default ACLs.
+$ umask 022
+$ mkdir ddd
+$ getfacl -qn ddd
+> user::rwx
+> group::r-x
+> other::r-x
+
+$ ls -l | grep ddd | cut -d' ' -f1
+> drwxr-xr-x
+
+$ getfacl -dq ddd
+$ setfacl -dm u::rwx,g::rx,o::rx,mask::rwx ddd
+$ getfacl -dqn ddd
+> user::rwx
+> group::r-x
+> mask::rwx
+> other::r-x
+
+# No change - ls(1) output doesn't take into account default ACLs.
+$ ls -l | grep ddd | cut -d' ' -f1
+> drwxr-xr-x
+
+$ setfacl -dm g:42:rwx,u:42:r ddd
+$ setfacl -dm g::w ddd
+$ getfacl -dqn ddd
+> user::rwx
+> user:42:r--
+> group::-w-
+> group:42:rwx
+> mask::rwx
+> other::r-x
+
+$ setfacl -dx group:42: ddd
+$ getfacl -dqn ddd
+> user::rwx
+> user:42:r--
+> group::-w-
+> mask::rw-
+> other::r-x
+
+$ ls -l | grep ddd | cut -d' ' -f1
+> drwxr-xr-x
+
+$ rmdir ddd
+$ rm xxx
+
+# Test inheritance.
+$ mkdir ddd
+
+$ touch ddd/xxx
+$ getfacl -q ddd/xxx
+> user::rw-
+> group::r--
+> other::r--
+
+$ mkdir ddd/ddd
+$ getfacl -q ddd/ddd
+> user::rwx
+> group::r-x
+> other::r-x
+
+$ rmdir ddd/ddd
+$ rm ddd/xxx
+
+$ setfacl -dm u::rwx,g::rx,o::rx,mask::rwx ddd
+$ setfacl -dm g:42:rwx,u:43:r ddd
+$ getfacl -dq ddd
+> user::rwx
+> user:43:r--
+> group::r-x
+> group:42:rwx
+> mask::rwx
+> other::r-x
+
+$ touch ddd/xxx
+$ getfacl -q ddd/xxx
+> user::rw-
+> user:43:r--
+> group::r-x # effective: r--
+> group:42:rwx # effective: r--
+> mask::r--
+> other::r--
+
+$ mkdir ddd/ddd
+$ getfacl -q ddd/ddd
+> user::rwx
+> user:43:r--
+> group::r-x
+> group:42:rwx # effective: r-x
+> mask::r-x
+> other::r-x
+
+$ rmdir ddd/ddd
+$ rm ddd/xxx
+$ rmdir ddd
+
+# Test if we deal properly with fifos.
+$ mkfifo fff
+$ ls -l fff | cut -d' ' -f1
+> prw-r--r--
+
+$ setfacl -m u:42:r,g:43:w fff
+$ getfacl fff
+> # file: fff
+> # owner: root
+> # group: wheel
+> user::rw-
+> user:42:r--
+> group::r--
+> group:43:-w-
+> mask::rw-
+> other::r--
+
+$ ls -l fff | cut -d' ' -f1
+> prw-rw-r--+
+
+$ setfacl -bn fff
+$ getfacl fff
+> # file: fff
+> # owner: root
+> # group: wheel
+> user::rw-
+> group::r--
+> other::r--
+
+$ ls -l fff | cut -d' ' -f1
+> prw-r--r--
+
+$ rm fff
+
+# Test if we deal properly with device files.
+$ mknod bbb b 1 1
+$ setfacl -m u:42:r,g:43:w bbb
+> setfacl: bbb: acl_get_file() failed: Operation not supported
+$ ls -l bbb | cut -d' ' -f1
+> brw-r--r--
+
+$ rm bbb
+
+$ mknod ccc c 1 1
+$ setfacl -m u:42:r,g:43:w ccc
+> setfacl: ccc: acl_get_file() failed: Operation not supported
+$ ls -l ccc | cut -d' ' -f1
+> crw-r--r--
+
+$ rm ccc
diff --git a/tests/sys/aio/Makefile b/tests/sys/aio/Makefile
new file mode 100644
index 000000000000..98b436068a9a
--- /dev/null
+++ b/tests/sys/aio/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/aio
+
+ATF_TESTS_C+= aio_test
+
+PLAIN_TESTS_C+= aio_kqueue_test
+PLAIN_TESTS_C+= lio_kqueue_test
+
+LIBADD.aio_test+= util
+
+CFLAGS+= -I${.CURDIR:H:H}
+
+WARNS?= 6
+
+.include <bsd.test.mk>
diff --git a/tests/sys/aio/Makefile.depend b/tests/sys/aio/Makefile.depend
new file mode 100644
index 000000000000..5915a38b10f4
--- /dev/null
+++ b/tests/sys/aio/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/atf/libatf-c \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/aio/aio_kqueue_test.c b/tests/sys/aio/aio_kqueue_test.c
new file mode 100644
index 000000000000..08d57f870104
--- /dev/null
+++ b/tests/sys/aio/aio_kqueue_test.c
@@ -0,0 +1,220 @@
+/*-
+ * Copyright (C) 2005 IronPort Systems, 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 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 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$
+ */
+
+/*
+ * Prerequisities:
+ * - AIO support must be compiled into the kernel (see sys/<arch>/NOTES for
+ * more details).
+ *
+ * Note: it is a good idea to run this against a physical drive to
+ * exercise the physio fast path (ie. aio_kqueue /dev/<something safe>)
+ */
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <aio.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "freebsd_test_suite/macros.h"
+#include "local.h"
+
+#define PATH_TEMPLATE "aio.XXXXXXXXXX"
+
+#define MAX_IOCBS 128
+#define MAX_RUNS 300
+/* #define DEBUG */
+
+int
+main (int argc, char *argv[])
+{
+ struct aiocb *iocb[MAX_IOCBS], *kq_iocb;
+ char *file, pathname[sizeof(PATH_TEMPLATE)+1];
+ struct kevent ke, kq_returned;
+ struct timespec ts;
+ char buffer[32768];
+#ifdef DEBUG
+ int cancel, error;
+#endif
+ int failed = 0, fd, kq, pending, result, run;
+ int tmp_file = 0;
+ unsigned i, j;
+
+ PLAIN_REQUIRE_KERNEL_MODULE("aio", 0);
+ PLAIN_REQUIRE_UNSAFE_AIO(0);
+
+ kq = kqueue();
+ if (kq < 0) {
+ perror("No kqeueue\n");
+ exit(1);
+ }
+
+ if (argc == 1) {
+ strcpy(pathname, PATH_TEMPLATE);
+ fd = mkstemp(pathname);
+ file = pathname;
+ tmp_file = 1;
+ } else {
+ file = argv[1];
+ fd = open(file, O_RDWR|O_CREAT, 0666);
+ }
+ if (fd == -1)
+ err(1, "Can't open %s\n", file);
+
+ for (run = 0; run < MAX_RUNS; run++){
+#ifdef DEBUG
+ printf("Run %d\n", run);
+#endif
+ for (i = 0; i < nitems(iocb); i++) {
+ iocb[i] = (struct aiocb *)calloc(1,
+ sizeof(struct aiocb));
+ if (iocb[i] == NULL)
+ err(1, "calloc");
+ }
+
+ pending = 0;
+ for (i = 0; i < nitems(iocb); i++) {
+ pending++;
+ iocb[i]->aio_nbytes = sizeof(buffer);
+ iocb[i]->aio_buf = buffer;
+ iocb[i]->aio_fildes = fd;
+ iocb[i]->aio_offset = iocb[i]->aio_nbytes * i * run;
+
+ iocb[i]->aio_sigevent.sigev_notify_kqueue = kq;
+ iocb[i]->aio_sigevent.sigev_value.sival_ptr = iocb[i];
+ iocb[i]->aio_sigevent.sigev_notify = SIGEV_KEVENT;
+
+ result = aio_write(iocb[i]);
+ if (result != 0) {
+ perror("aio_write");
+ printf("Result %d iteration %d\n", result, i);
+ exit(1);
+ }
+#ifdef DEBUG
+ printf("WRITE %d is at %p\n", i, iocb[i]);
+#endif
+ result = rand();
+ if (result < RAND_MAX/32) {
+ if (result > RAND_MAX/64) {
+ result = aio_cancel(fd, iocb[i]);
+#ifdef DEBUG
+ printf("Cancel %d %p result %d\n", i, iocb[i], result);
+#endif
+ if (result == AIO_CANCELED) {
+ aio_return(iocb[i]);
+ iocb[i] = NULL;
+ pending--;
+ }
+ }
+ }
+ }
+#ifdef DEBUG
+ cancel = nitems(iocb) - pending;
+#endif
+
+ i = 0;
+ while (pending) {
+
+ for (;;) {
+
+ bzero(&ke, sizeof(ke));
+ bzero(&kq_returned, sizeof(ke));
+ ts.tv_sec = 0;
+ ts.tv_nsec = 1;
+ result = kevent(kq, NULL, 0,
+ &kq_returned, 1, &ts);
+#ifdef DEBUG
+ error = errno;
+#endif
+ if (result < 0)
+ perror("kevent error: ");
+ kq_iocb = kq_returned.udata;
+#ifdef DEBUG
+ printf("kevent %d %d errno %d return.ident %p "
+ "return.data %p return.udata %p %p\n",
+ i, result, error,
+ kq_returned.ident, kq_returned.data,
+ kq_returned.udata,
+ kq_iocb);
+#endif
+
+ if (kq_iocb)
+ break;
+#ifdef DEBUG
+ printf("Try again left %d out of %d %d\n",
+ pending, nitems(iocb), cancel);
+#endif
+ }
+
+ for (j = 0; j < nitems(iocb) && iocb[j] != kq_iocb;
+ j++) ;
+#ifdef DEBUG
+ printf("kq_iocb %p\n", kq_iocb);
+
+ printf("Error Result for %d is %d pending %d\n",
+ j, result, pending);
+#endif
+ result = aio_return(kq_iocb);
+#ifdef DEBUG
+ printf("Return Result for %d is %d\n\n", j, result);
+#endif
+ if (result != sizeof(buffer)) {
+ printf("FAIL: run %d, operation %d, result %d "
+ " (errno=%d) should be %zu\n", run, pending,
+ result, errno, sizeof(buffer));
+ failed++;
+ } else
+ printf("PASS: run %d, left %d\n", run,
+ pending - 1);
+
+ free(kq_iocb);
+ iocb[j] = NULL;
+ pending--;
+ i++;
+ }
+
+ for (i = 0; i < nitems(iocb); i++)
+ free(iocb[i]);
+
+ }
+
+ if (tmp_file)
+ unlink(pathname);
+
+ if (failed != 0)
+ printf("FAIL: %d tests failed\n", failed);
+ else
+ printf("PASS: All tests passed\n");
+
+ exit (failed == 0 ? 0 : 1);
+}
diff --git a/tests/sys/aio/aio_test.c b/tests/sys/aio/aio_test.c
new file mode 100644
index 000000000000..33ccd7d3ab7b
--- /dev/null
+++ b/tests/sys/aio/aio_test.c
@@ -0,0 +1,1025 @@
+/*-
+ * Copyright (c) 2004 Robert N. M. Watson
+ * 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 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 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$
+ */
+
+/*
+ * Regression test to do some very basic AIO exercising on several types of
+ * file descriptors. Currently, the tests consist of initializing a fixed
+ * size buffer with pseudo-random data, writing it to one fd using AIO, then
+ * reading it from a second descriptor using AIO. For some targets, the same
+ * fd is used for write and read (i.e., file, md device), but for others the
+ * operation is performed on a peer (pty, socket, fifo, etc). A timeout is
+ * initiated to detect undo blocking. This test does not attempt to exercise
+ * error cases or more subtle asynchronous behavior, just make sure that the
+ * basic operations work on some basic object types.
+ */
+
+#include <sys/param.h>
+#include <sys/module.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/mdioctl.h>
+
+#include <aio.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#include "freebsd_test_suite/macros.h"
+#include "local.h"
+
+#define PATH_TEMPLATE "aio.XXXXXXXXXX"
+
+/*
+ * GLOBAL_MAX sets the largest usable buffer size to be read and written, as
+ * it sizes ac_buffer in the aio_context structure. It is also the default
+ * size for file I/O. For other types, we use smaller blocks or we risk
+ * blocking (and we run in a single process/thread so that would be bad).
+ */
+#define GLOBAL_MAX 16384
+
+#define BUFFER_MAX GLOBAL_MAX
+struct aio_context {
+ int ac_read_fd, ac_write_fd;
+ long ac_seed;
+ char ac_buffer[GLOBAL_MAX];
+ int ac_buflen;
+ int ac_seconds;
+ void (*ac_cleanup)(void *arg);
+ void *ac_cleanup_arg;
+};
+
+static int aio_timedout;
+
+/*
+ * Each test run specifies a timeout in seconds. Use the somewhat obsoleted
+ * signal(3) and alarm(3) APIs to set this up.
+ */
+static void
+aio_timeout_signal(int sig __unused)
+{
+
+ aio_timedout = 1;
+}
+
+static void
+aio_timeout_start(int seconds)
+{
+
+ aio_timedout = 0;
+ ATF_REQUIRE_MSG(signal(SIGALRM, aio_timeout_signal) != SIG_ERR,
+ "failed to set SIGALRM handler: %s", strerror(errno));
+ alarm(seconds);
+}
+
+static void
+aio_timeout_stop(void)
+{
+
+ ATF_REQUIRE_MSG(signal(SIGALRM, NULL) != SIG_ERR,
+ "failed to reset SIGALRM handler to default: %s", strerror(errno));
+ alarm(0);
+}
+
+/*
+ * Fill a buffer given a seed that can be fed into srandom() to initialize
+ * the PRNG in a repeatable manner.
+ */
+static void
+aio_fill_buffer(char *buffer, int len, long seed)
+{
+ char ch;
+ int i;
+
+ srandom(seed);
+ for (i = 0; i < len; i++) {
+ ch = random() & 0xff;
+ buffer[i] = ch;
+ }
+}
+
+/*
+ * Test that a buffer matches a given seed. See aio_fill_buffer(). Return
+ * (1) on a match, (0) on a mismatch.
+ */
+static int
+aio_test_buffer(char *buffer, int len, long seed)
+{
+ char ch;
+ int i;
+
+ srandom(seed);
+ for (i = 0; i < len; i++) {
+ ch = random() & 0xff;
+ if (buffer[i] != ch)
+ return (0);
+ }
+ return (1);
+}
+
+/*
+ * Initialize a testing context given the file descriptors provided by the
+ * test setup.
+ */
+static void
+aio_context_init(struct aio_context *ac, int read_fd,
+ int write_fd, int buflen, int seconds, void (*cleanup)(void *),
+ void *cleanup_arg)
+{
+
+ ATF_REQUIRE_MSG(buflen <= BUFFER_MAX,
+ "aio_context_init: buffer too large (%d > %d)",
+ buflen, BUFFER_MAX);
+ bzero(ac, sizeof(*ac));
+ ac->ac_read_fd = read_fd;
+ ac->ac_write_fd = write_fd;
+ ac->ac_buflen = buflen;
+ srandomdev();
+ ac->ac_seed = random();
+ aio_fill_buffer(ac->ac_buffer, buflen, ac->ac_seed);
+ ATF_REQUIRE_MSG(aio_test_buffer(ac->ac_buffer, buflen,
+ ac->ac_seed) != 0, "aio_test_buffer: internal error");
+ ac->ac_seconds = seconds;
+ ac->ac_cleanup = cleanup;
+ ac->ac_cleanup_arg = cleanup_arg;
+}
+
+/*
+ * Each tester can register a callback to clean up in the event the test
+ * fails. Preserve the value of errno so that subsequent calls to errx()
+ * work properly.
+ */
+static void
+aio_cleanup(struct aio_context *ac)
+{
+ int error;
+
+ if (ac->ac_cleanup == NULL)
+ return;
+ error = errno;
+ (ac->ac_cleanup)(ac->ac_cleanup_arg);
+ errno = error;
+}
+
+/*
+ * Perform a simple write test of our initialized data buffer to the provided
+ * file descriptor.
+ */
+static void
+aio_write_test(struct aio_context *ac)
+{
+ struct aiocb aio, *aiop;
+ ssize_t len;
+
+ ATF_REQUIRE_KERNEL_MODULE("aio");
+
+ bzero(&aio, sizeof(aio));
+ aio.aio_buf = ac->ac_buffer;
+ aio.aio_nbytes = ac->ac_buflen;
+ aio.aio_fildes = ac->ac_write_fd;
+ aio.aio_offset = 0;
+
+ aio_timeout_start(ac->ac_seconds);
+
+ if (aio_write(&aio) < 0) {
+ if (errno == EINTR) {
+ if (aio_timedout) {
+ aio_cleanup(ac);
+ atf_tc_fail("aio_write timed out");
+ }
+ }
+ aio_cleanup(ac);
+ atf_tc_fail("aio_write failed: %s", strerror(errno));
+ }
+
+ len = aio_waitcomplete(&aiop, NULL);
+ if (len < 0) {
+ if (errno == EINTR) {
+ if (aio_timedout) {
+ aio_cleanup(ac);
+ atf_tc_fail("aio_waitcomplete timed out");
+ }
+ }
+ aio_cleanup(ac);
+ atf_tc_fail("aio_waitcomplete failed: %s", strerror(errno));
+ }
+
+ aio_timeout_stop();
+
+ if (len != ac->ac_buflen) {
+ aio_cleanup(ac);
+ atf_tc_fail("aio_waitcomplete short write (%jd)",
+ (intmax_t)len);
+ }
+}
+
+/*
+ * Perform a simple read test of our initialized data buffer from the
+ * provided file descriptor.
+ */
+static void
+aio_read_test(struct aio_context *ac)
+{
+ struct aiocb aio, *aiop;
+ ssize_t len;
+
+ ATF_REQUIRE_KERNEL_MODULE("aio");
+
+ bzero(ac->ac_buffer, ac->ac_buflen);
+ bzero(&aio, sizeof(aio));
+ aio.aio_buf = ac->ac_buffer;
+ aio.aio_nbytes = ac->ac_buflen;
+ aio.aio_fildes = ac->ac_read_fd;
+ aio.aio_offset = 0;
+
+ aio_timeout_start(ac->ac_seconds);
+
+ if (aio_read(&aio) < 0) {
+ if (errno == EINTR) {
+ if (aio_timedout) {
+ aio_cleanup(ac);
+ atf_tc_fail("aio_write timed out");
+ }
+ }
+ aio_cleanup(ac);
+ atf_tc_fail("aio_read failed: %s", strerror(errno));
+ }
+
+ len = aio_waitcomplete(&aiop, NULL);
+ if (len < 0) {
+ if (errno == EINTR) {
+ if (aio_timedout) {
+ aio_cleanup(ac);
+ atf_tc_fail("aio_waitcomplete timed out");
+ }
+ }
+ aio_cleanup(ac);
+ atf_tc_fail("aio_waitcomplete failed: %s", strerror(errno));
+ }
+
+ aio_timeout_stop();
+
+ if (len != ac->ac_buflen) {
+ aio_cleanup(ac);
+ atf_tc_fail("aio_waitcomplete short read (%jd)",
+ (intmax_t)len);
+ }
+
+ if (aio_test_buffer(ac->ac_buffer, ac->ac_buflen, ac->ac_seed) == 0) {
+ aio_cleanup(ac);
+ atf_tc_fail("buffer mismatched");
+ }
+}
+
+/*
+ * Series of type-specific tests for AIO. For now, we just make sure we can
+ * issue a write and then a read to each type. We assume that once a write
+ * is issued, a read can follow.
+ */
+
+/*
+ * Test with a classic file. Assumes we can create a moderate size temporary
+ * file.
+ */
+struct aio_file_arg {
+ int afa_fd;
+ char *afa_pathname;
+};
+
+static void
+aio_file_cleanup(void *arg)
+{
+ struct aio_file_arg *afa;
+
+ afa = arg;
+ close(afa->afa_fd);
+ unlink(afa->afa_pathname);
+}
+
+#define FILE_LEN GLOBAL_MAX
+#define FILE_TIMEOUT 30
+ATF_TC_WITHOUT_HEAD(aio_file_test);
+ATF_TC_BODY(aio_file_test, tc)
+{
+ char pathname[PATH_MAX];
+ struct aio_file_arg arg;
+ struct aio_context ac;
+ int fd;
+
+ ATF_REQUIRE_KERNEL_MODULE("aio");
+ ATF_REQUIRE_UNSAFE_AIO();
+
+ strcpy(pathname, PATH_TEMPLATE);
+ fd = mkstemp(pathname);
+ ATF_REQUIRE_MSG(fd != -1, "mkstemp failed: %s", strerror(errno));
+
+ arg.afa_fd = fd;
+ arg.afa_pathname = pathname;
+
+ aio_context_init(&ac, fd, fd, FILE_LEN,
+ FILE_TIMEOUT, aio_file_cleanup, &arg);
+ aio_write_test(&ac);
+ aio_read_test(&ac);
+
+ aio_file_cleanup(&arg);
+}
+
+struct aio_fifo_arg {
+ int afa_read_fd;
+ int afa_write_fd;
+ char *afa_pathname;
+};
+
+static void
+aio_fifo_cleanup(void *arg)
+{
+ struct aio_fifo_arg *afa;
+
+ afa = arg;
+ if (afa->afa_read_fd != -1)
+ close(afa->afa_read_fd);
+ if (afa->afa_write_fd != -1)
+ close(afa->afa_write_fd);
+ unlink(afa->afa_pathname);
+}
+
+#define FIFO_LEN 256
+#define FIFO_TIMEOUT 30
+ATF_TC_WITHOUT_HEAD(aio_fifo_test);
+ATF_TC_BODY(aio_fifo_test, tc)
+{
+ int error, read_fd = -1, write_fd = -1;
+ struct aio_fifo_arg arg;
+ char pathname[PATH_MAX];
+ struct aio_context ac;
+
+ ATF_REQUIRE_KERNEL_MODULE("aio");
+ ATF_REQUIRE_UNSAFE_AIO();
+
+ /*
+ * In theory, mkstemp() can return a name that is then collided with.
+ * Because this is a regression test, we treat that as a test failure
+ * rather than retrying.
+ */
+ strcpy(pathname, PATH_TEMPLATE);
+ ATF_REQUIRE_MSG(mkstemp(pathname) != -1,
+ "mkstemp failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(unlink(pathname) == 0,
+ "unlink failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(mkfifo(pathname, 0600) != -1,
+ "mkfifo failed: %s", strerror(errno));
+ arg.afa_pathname = pathname;
+ arg.afa_read_fd = -1;
+ arg.afa_write_fd = -1;
+
+ read_fd = open(pathname, O_RDONLY | O_NONBLOCK);
+ if (read_fd == -1) {
+ error = errno;
+ aio_fifo_cleanup(&arg);
+ errno = error;
+ atf_tc_fail("read_fd open failed: %s",
+ strerror(errno));
+ }
+ arg.afa_read_fd = read_fd;
+
+ write_fd = open(pathname, O_WRONLY);
+ if (write_fd == -1) {
+ error = errno;
+ aio_fifo_cleanup(&arg);
+ errno = error;
+ atf_tc_fail("write_fd open failed: %s",
+ strerror(errno));
+ }
+ arg.afa_write_fd = write_fd;
+
+ aio_context_init(&ac, read_fd, write_fd, FIFO_LEN,
+ FIFO_TIMEOUT, aio_fifo_cleanup, &arg);
+ aio_write_test(&ac);
+ aio_read_test(&ac);
+
+ aio_fifo_cleanup(&arg);
+}
+
+struct aio_unix_socketpair_arg {
+ int asa_sockets[2];
+};
+
+static void
+aio_unix_socketpair_cleanup(void *arg)
+{
+ struct aio_unix_socketpair_arg *asa;
+
+ asa = arg;
+ close(asa->asa_sockets[0]);
+ close(asa->asa_sockets[1]);
+}
+
+#define UNIX_SOCKETPAIR_LEN 256
+#define UNIX_SOCKETPAIR_TIMEOUT 30
+ATF_TC_WITHOUT_HEAD(aio_unix_socketpair_test);
+ATF_TC_BODY(aio_unix_socketpair_test, tc)
+{
+ struct aio_unix_socketpair_arg arg;
+ struct aio_context ac;
+ struct rusage ru_before, ru_after;
+ int sockets[2];
+
+ ATF_REQUIRE_KERNEL_MODULE("aio");
+
+ ATF_REQUIRE_MSG(socketpair(PF_UNIX, SOCK_STREAM, 0, sockets) != -1,
+ "socketpair failed: %s", strerror(errno));
+
+ arg.asa_sockets[0] = sockets[0];
+ arg.asa_sockets[1] = sockets[1];
+ aio_context_init(&ac, sockets[0],
+ sockets[1], UNIX_SOCKETPAIR_LEN, UNIX_SOCKETPAIR_TIMEOUT,
+ aio_unix_socketpair_cleanup, &arg);
+ ATF_REQUIRE_MSG(getrusage(RUSAGE_SELF, &ru_before) != -1,
+ "getrusage failed: %s", strerror(errno));
+ aio_write_test(&ac);
+ ATF_REQUIRE_MSG(getrusage(RUSAGE_SELF, &ru_after) != -1,
+ "getrusage failed: %s", strerror(errno));
+ ATF_REQUIRE(ru_after.ru_msgsnd == ru_before.ru_msgsnd + 1);
+ ru_before = ru_after;
+ aio_read_test(&ac);
+ ATF_REQUIRE_MSG(getrusage(RUSAGE_SELF, &ru_after) != -1,
+ "getrusage failed: %s", strerror(errno));
+ ATF_REQUIRE(ru_after.ru_msgrcv == ru_before.ru_msgrcv + 1);
+
+ aio_unix_socketpair_cleanup(&arg);
+}
+
+struct aio_pty_arg {
+ int apa_read_fd;
+ int apa_write_fd;
+};
+
+static void
+aio_pty_cleanup(void *arg)
+{
+ struct aio_pty_arg *apa;
+
+ apa = arg;
+ close(apa->apa_read_fd);
+ close(apa->apa_write_fd);
+};
+
+#define PTY_LEN 256
+#define PTY_TIMEOUT 30
+ATF_TC_WITHOUT_HEAD(aio_pty_test);
+ATF_TC_BODY(aio_pty_test, tc)
+{
+ struct aio_pty_arg arg;
+ struct aio_context ac;
+ int read_fd, write_fd;
+ struct termios ts;
+ int error;
+
+ ATF_REQUIRE_KERNEL_MODULE("aio");
+ ATF_REQUIRE_UNSAFE_AIO();
+
+ ATF_REQUIRE_MSG(openpty(&read_fd, &write_fd, NULL, NULL, NULL) == 0,
+ "openpty failed: %s", strerror(errno));
+
+ arg.apa_read_fd = read_fd;
+ arg.apa_write_fd = write_fd;
+
+ if (tcgetattr(write_fd, &ts) < 0) {
+ error = errno;
+ aio_pty_cleanup(&arg);
+ errno = error;
+ atf_tc_fail("tcgetattr failed: %s", strerror(errno));
+ }
+ cfmakeraw(&ts);
+ if (tcsetattr(write_fd, TCSANOW, &ts) < 0) {
+ error = errno;
+ aio_pty_cleanup(&arg);
+ errno = error;
+ atf_tc_fail("tcsetattr failed: %s", strerror(errno));
+ }
+ aio_context_init(&ac, read_fd, write_fd, PTY_LEN,
+ PTY_TIMEOUT, aio_pty_cleanup, &arg);
+
+ aio_write_test(&ac);
+ aio_read_test(&ac);
+
+ aio_pty_cleanup(&arg);
+}
+
+static void
+aio_pipe_cleanup(void *arg)
+{
+ int *pipes = arg;
+
+ close(pipes[0]);
+ close(pipes[1]);
+}
+
+#define PIPE_LEN 256
+#define PIPE_TIMEOUT 30
+ATF_TC_WITHOUT_HEAD(aio_pipe_test);
+ATF_TC_BODY(aio_pipe_test, tc)
+{
+ struct aio_context ac;
+ int pipes[2];
+
+ ATF_REQUIRE_KERNEL_MODULE("aio");
+ ATF_REQUIRE_UNSAFE_AIO();
+
+ ATF_REQUIRE_MSG(pipe(pipes) != -1,
+ "pipe failed: %s", strerror(errno));
+
+ aio_context_init(&ac, pipes[0], pipes[1], PIPE_LEN,
+ PIPE_TIMEOUT, aio_pipe_cleanup, pipes);
+ aio_write_test(&ac);
+ aio_read_test(&ac);
+
+ aio_pipe_cleanup(pipes);
+}
+
+struct aio_md_arg {
+ int ama_mdctl_fd;
+ int ama_unit;
+ int ama_fd;
+};
+
+static void
+aio_md_cleanup(void *arg)
+{
+ struct aio_md_arg *ama;
+ struct md_ioctl mdio;
+ int error;
+
+ ama = arg;
+
+ if (ama->ama_fd != -1)
+ close(ama->ama_fd);
+
+ if (ama->ama_unit != -1) {
+ bzero(&mdio, sizeof(mdio));
+ mdio.md_version = MDIOVERSION;
+ mdio.md_unit = ama->ama_unit;
+ if (ioctl(ama->ama_mdctl_fd, MDIOCDETACH, &mdio) == -1) {
+ error = errno;
+ close(ama->ama_mdctl_fd);
+ errno = error;
+ atf_tc_fail("ioctl MDIOCDETACH failed: %s",
+ strerror(errno));
+ }
+ }
+
+ close(ama->ama_mdctl_fd);
+}
+
+#define MD_LEN GLOBAL_MAX
+#define MD_TIMEOUT 30
+ATF_TC(aio_md_test);
+ATF_TC_HEAD(aio_md_test, tc)
+{
+
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(aio_md_test, tc)
+{
+ int error, fd, mdctl_fd, unit;
+ char pathname[PATH_MAX];
+ struct aio_md_arg arg;
+ struct aio_context ac;
+ struct md_ioctl mdio;
+
+ ATF_REQUIRE_KERNEL_MODULE("aio");
+
+ mdctl_fd = open("/dev/" MDCTL_NAME, O_RDWR, 0);
+ ATF_REQUIRE_MSG(mdctl_fd != -1,
+ "opening /dev/%s failed: %s", MDCTL_NAME, strerror(errno));
+
+ bzero(&mdio, sizeof(mdio));
+ mdio.md_version = MDIOVERSION;
+ mdio.md_type = MD_MALLOC;
+ mdio.md_options = MD_AUTOUNIT | MD_COMPRESS;
+ mdio.md_mediasize = GLOBAL_MAX;
+ mdio.md_sectorsize = 512;
+
+ arg.ama_mdctl_fd = mdctl_fd;
+ arg.ama_unit = -1;
+ arg.ama_fd = -1;
+ if (ioctl(mdctl_fd, MDIOCATTACH, &mdio) < 0) {
+ error = errno;
+ aio_md_cleanup(&arg);
+ errno = error;
+ atf_tc_fail("ioctl MDIOCATTACH failed: %s", strerror(errno));
+ }
+
+ arg.ama_unit = unit = mdio.md_unit;
+ snprintf(pathname, PATH_MAX, "/dev/md%d", unit);
+ fd = open(pathname, O_RDWR);
+ ATF_REQUIRE_MSG(fd != -1,
+ "opening %s failed: %s", pathname, strerror(errno));
+ arg.ama_fd = fd;
+
+ aio_context_init(&ac, fd, fd, MD_LEN, MD_TIMEOUT,
+ aio_md_cleanup, &arg);
+ aio_write_test(&ac);
+ aio_read_test(&ac);
+
+ aio_md_cleanup(&arg);
+}
+
+ATF_TC_WITHOUT_HEAD(aio_large_read_test);
+ATF_TC_BODY(aio_large_read_test, tc)
+{
+ char pathname[PATH_MAX];
+ struct aiocb cb, *cbp;
+ ssize_t nread;
+ size_t len;
+ int fd;
+#ifdef __LP64__
+ int clamped;
+#endif
+
+ ATF_REQUIRE_KERNEL_MODULE("aio");
+ ATF_REQUIRE_UNSAFE_AIO();
+
+#ifdef __LP64__
+ len = sizeof(clamped);
+ if (sysctlbyname("debug.iosize_max_clamp", &clamped, &len, NULL, 0) ==
+ -1)
+ atf_libc_error(errno, "Failed to read debug.iosize_max_clamp");
+#endif
+
+ /* Determine the maximum supported read(2) size. */
+ len = SSIZE_MAX;
+#ifdef __LP64__
+ if (clamped)
+ len = INT_MAX;
+#endif
+
+ strcpy(pathname, PATH_TEMPLATE);
+ fd = mkstemp(pathname);
+ ATF_REQUIRE_MSG(fd != -1, "mkstemp failed: %s", strerror(errno));
+
+ unlink(pathname);
+
+ memset(&cb, 0, sizeof(cb));
+ cb.aio_nbytes = len;
+ cb.aio_fildes = fd;
+ cb.aio_buf = NULL;
+ if (aio_read(&cb) == -1)
+ atf_tc_fail("aio_read() of maximum read size failed: %s",
+ strerror(errno));
+
+ nread = aio_waitcomplete(&cbp, NULL);
+ if (nread == -1)
+ atf_tc_fail("aio_waitcomplete() failed: %s", strerror(errno));
+ if (nread != 0)
+ atf_tc_fail("aio_read() from empty file returned data: %zd",
+ nread);
+
+ memset(&cb, 0, sizeof(cb));
+ cb.aio_nbytes = len + 1;
+ cb.aio_fildes = fd;
+ cb.aio_buf = NULL;
+ if (aio_read(&cb) == -1) {
+ if (errno == EINVAL)
+ goto finished;
+ atf_tc_fail("aio_read() of too large read size failed: %s",
+ strerror(errno));
+ }
+
+ nread = aio_waitcomplete(&cbp, NULL);
+ if (nread == -1) {
+ if (errno == EINVAL)
+ goto finished;
+ atf_tc_fail("aio_waitcomplete() failed: %s", strerror(errno));
+ }
+ atf_tc_fail("aio_read() of too large read size returned: %zd", nread);
+
+finished:
+ close(fd);
+}
+
+/*
+ * This tests for a bug where arriving socket data can wakeup multiple
+ * AIO read requests resulting in an uncancellable request.
+ */
+ATF_TC_WITHOUT_HEAD(aio_socket_two_reads);
+ATF_TC_BODY(aio_socket_two_reads, tc)
+{
+ struct ioreq {
+ struct aiocb iocb;
+ char buffer[1024];
+ } ioreq[2];
+ struct aiocb *iocb;
+ unsigned i;
+ int s[2];
+ char c;
+
+ ATF_REQUIRE_KERNEL_MODULE("aio");
+#if __FreeBSD_version < 1100101
+ aft_tc_skip("kernel version %d is too old (%d required)",
+ __FreeBSD_version, 1100101);
+#endif
+
+ ATF_REQUIRE(socketpair(PF_UNIX, SOCK_STREAM, 0, s) != -1);
+
+ /* Queue two read requests. */
+ memset(&ioreq, 0, sizeof(ioreq));
+ for (i = 0; i < nitems(ioreq); i++) {
+ ioreq[i].iocb.aio_nbytes = sizeof(ioreq[i].buffer);
+ ioreq[i].iocb.aio_fildes = s[0];
+ ioreq[i].iocb.aio_buf = ioreq[i].buffer;
+ ATF_REQUIRE(aio_read(&ioreq[i].iocb) == 0);
+ }
+
+ /* Send a single byte. This should complete one request. */
+ c = 0xc3;
+ ATF_REQUIRE(write(s[1], &c, sizeof(c)) == 1);
+
+ ATF_REQUIRE(aio_waitcomplete(&iocb, NULL) == 1);
+
+ /* Determine which request completed and verify the data was read. */
+ if (iocb == &ioreq[0].iocb)
+ i = 0;
+ else
+ i = 1;
+ ATF_REQUIRE(ioreq[i].buffer[0] == c);
+
+ i ^= 1;
+
+ /*
+ * Try to cancel the other request. On broken systems this
+ * will fail and the process will hang on exit.
+ */
+ ATF_REQUIRE(aio_error(&ioreq[i].iocb) == EINPROGRESS);
+ ATF_REQUIRE(aio_cancel(s[0], &ioreq[i].iocb) == AIO_CANCELED);
+
+ close(s[1]);
+ close(s[0]);
+}
+
+/*
+ * This test ensures that aio_write() on a blocking socket of a "large"
+ * buffer does not return a short completion.
+ */
+ATF_TC_WITHOUT_HEAD(aio_socket_blocking_short_write);
+ATF_TC_BODY(aio_socket_blocking_short_write, tc)
+{
+ struct aiocb iocb, *iocbp;
+ char *buffer[2];
+ ssize_t done;
+ int buffer_size, sb_size;
+ socklen_t len;
+ int s[2];
+
+ ATF_REQUIRE_KERNEL_MODULE("aio");
+
+ ATF_REQUIRE(socketpair(PF_UNIX, SOCK_STREAM, 0, s) != -1);
+
+ len = sizeof(sb_size);
+ ATF_REQUIRE(getsockopt(s[0], SOL_SOCKET, SO_RCVBUF, &sb_size, &len) !=
+ -1);
+ ATF_REQUIRE(len == sizeof(sb_size));
+ buffer_size = sb_size;
+
+ ATF_REQUIRE(getsockopt(s[1], SOL_SOCKET, SO_SNDBUF, &sb_size, &len) !=
+ -1);
+ ATF_REQUIRE(len == sizeof(sb_size));
+ if (sb_size > buffer_size)
+ buffer_size = sb_size;
+
+ /*
+ * Use twice the size of the MAX(receive buffer, send buffer)
+ * to ensure that the write is split up into multiple writes
+ * internally.
+ */
+ buffer_size *= 2;
+
+ buffer[0] = malloc(buffer_size);
+ ATF_REQUIRE(buffer[0] != NULL);
+ buffer[1] = malloc(buffer_size);
+ ATF_REQUIRE(buffer[1] != NULL);
+
+ srandomdev();
+ aio_fill_buffer(buffer[1], buffer_size, random());
+
+ memset(&iocb, 0, sizeof(iocb));
+ iocb.aio_fildes = s[1];
+ iocb.aio_buf = buffer[1];
+ iocb.aio_nbytes = buffer_size;
+ ATF_REQUIRE(aio_write(&iocb) == 0);
+
+ done = recv(s[0], buffer[0], buffer_size, MSG_WAITALL);
+ ATF_REQUIRE(done == buffer_size);
+
+ done = aio_waitcomplete(&iocbp, NULL);
+ ATF_REQUIRE(iocbp == &iocb);
+ ATF_REQUIRE(done == buffer_size);
+
+ ATF_REQUIRE(memcmp(buffer[0], buffer[1], buffer_size) == 0);
+
+ close(s[1]);
+ close(s[0]);
+}
+
+/*
+ * This test verifies that cancelling a partially completed socket write
+ * returns a short write rather than ECANCELED.
+ */
+ATF_TC_WITHOUT_HEAD(aio_socket_short_write_cancel);
+ATF_TC_BODY(aio_socket_short_write_cancel, tc)
+{
+ struct aiocb iocb, *iocbp;
+ char *buffer[2];
+ ssize_t done;
+ int buffer_size, sb_size;
+ socklen_t len;
+ int s[2];
+
+ ATF_REQUIRE_KERNEL_MODULE("aio");
+
+ ATF_REQUIRE(socketpair(PF_UNIX, SOCK_STREAM, 0, s) != -1);
+
+ len = sizeof(sb_size);
+ ATF_REQUIRE(getsockopt(s[0], SOL_SOCKET, SO_RCVBUF, &sb_size, &len) !=
+ -1);
+ ATF_REQUIRE(len == sizeof(sb_size));
+ buffer_size = sb_size;
+
+ ATF_REQUIRE(getsockopt(s[1], SOL_SOCKET, SO_SNDBUF, &sb_size, &len) !=
+ -1);
+ ATF_REQUIRE(len == sizeof(sb_size));
+ if (sb_size > buffer_size)
+ buffer_size = sb_size;
+
+ /*
+ * Use three times the size of the MAX(receive buffer, send
+ * buffer) for the write to ensure that the write is split up
+ * into multiple writes internally. The recv() ensures that
+ * the write has partially completed, but a remaining size of
+ * two buffers should ensure that the write has not completed
+ * fully when it is cancelled.
+ */
+ buffer[0] = malloc(buffer_size);
+ ATF_REQUIRE(buffer[0] != NULL);
+ buffer[1] = malloc(buffer_size * 3);
+ ATF_REQUIRE(buffer[1] != NULL);
+
+ srandomdev();
+ aio_fill_buffer(buffer[1], buffer_size * 3, random());
+
+ memset(&iocb, 0, sizeof(iocb));
+ iocb.aio_fildes = s[1];
+ iocb.aio_buf = buffer[1];
+ iocb.aio_nbytes = buffer_size * 3;
+ ATF_REQUIRE(aio_write(&iocb) == 0);
+
+ done = recv(s[0], buffer[0], buffer_size, MSG_WAITALL);
+ ATF_REQUIRE(done == buffer_size);
+
+ ATF_REQUIRE(aio_error(&iocb) == EINPROGRESS);
+ ATF_REQUIRE(aio_cancel(s[1], &iocb) == AIO_NOTCANCELED);
+
+ done = aio_waitcomplete(&iocbp, NULL);
+ ATF_REQUIRE(iocbp == &iocb);
+ ATF_REQUIRE(done >= buffer_size && done <= buffer_size * 2);
+
+ ATF_REQUIRE(memcmp(buffer[0], buffer[1], buffer_size) == 0);
+
+ close(s[1]);
+ close(s[0]);
+}
+
+/*
+ * This test just performs a basic test of aio_fsync().
+ */
+ATF_TC_WITHOUT_HEAD(aio_fsync_test);
+ATF_TC_BODY(aio_fsync_test, tc)
+{
+ struct aiocb synccb, *iocbp;
+ struct {
+ struct aiocb iocb;
+ bool done;
+ char *buffer;
+ } buffers[16];
+ struct stat sb;
+ char pathname[PATH_MAX];
+ ssize_t rval;
+ unsigned i;
+ int fd;
+
+ ATF_REQUIRE_KERNEL_MODULE("aio");
+ ATF_REQUIRE_UNSAFE_AIO();
+
+ strcpy(pathname, PATH_TEMPLATE);
+ fd = mkstemp(pathname);
+ ATF_REQUIRE_MSG(fd != -1, "mkstemp failed: %s", strerror(errno));
+ unlink(pathname);
+
+ ATF_REQUIRE(fstat(fd, &sb) == 0);
+ ATF_REQUIRE(sb.st_blksize != 0);
+ ATF_REQUIRE(ftruncate(fd, sb.st_blksize * nitems(buffers)) == 0);
+
+ /*
+ * Queue several asynchronous write requests. Hopefully this
+ * forces the aio_fsync() request to be deferred. There is no
+ * reliable way to guarantee that however.
+ */
+ srandomdev();
+ for (i = 0; i < nitems(buffers); i++) {
+ buffers[i].done = false;
+ memset(&buffers[i].iocb, 0, sizeof(buffers[i].iocb));
+ buffers[i].buffer = malloc(sb.st_blksize);
+ aio_fill_buffer(buffers[i].buffer, sb.st_blksize, random());
+ buffers[i].iocb.aio_fildes = fd;
+ buffers[i].iocb.aio_buf = buffers[i].buffer;
+ buffers[i].iocb.aio_nbytes = sb.st_blksize;
+ buffers[i].iocb.aio_offset = sb.st_blksize * i;
+ ATF_REQUIRE(aio_write(&buffers[i].iocb) == 0);
+ }
+
+ /* Queue the aio_fsync request. */
+ memset(&synccb, 0, sizeof(synccb));
+ synccb.aio_fildes = fd;
+ ATF_REQUIRE(aio_fsync(O_SYNC, &synccb) == 0);
+
+ /* Wait for requests to complete. */
+ for (;;) {
+ next:
+ rval = aio_waitcomplete(&iocbp, NULL);
+ ATF_REQUIRE(iocbp != NULL);
+ if (iocbp == &synccb) {
+ ATF_REQUIRE(rval == 0);
+ break;
+ }
+
+ for (i = 0; i < nitems(buffers); i++) {
+ if (iocbp == &buffers[i].iocb) {
+ ATF_REQUIRE(buffers[i].done == false);
+ ATF_REQUIRE(rval == sb.st_blksize);
+ buffers[i].done = true;
+ goto next;
+ }
+ }
+
+ ATF_REQUIRE_MSG(false, "unmatched AIO request");
+ }
+
+ for (i = 0; i < nitems(buffers); i++)
+ ATF_REQUIRE_MSG(buffers[i].done,
+ "AIO request %u did not complete", i);
+
+ close(fd);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, aio_file_test);
+ ATF_TP_ADD_TC(tp, aio_fifo_test);
+ ATF_TP_ADD_TC(tp, aio_unix_socketpair_test);
+ ATF_TP_ADD_TC(tp, aio_pty_test);
+ ATF_TP_ADD_TC(tp, aio_pipe_test);
+ ATF_TP_ADD_TC(tp, aio_md_test);
+ ATF_TP_ADD_TC(tp, aio_large_read_test);
+ ATF_TP_ADD_TC(tp, aio_socket_two_reads);
+ ATF_TP_ADD_TC(tp, aio_socket_blocking_short_write);
+ ATF_TP_ADD_TC(tp, aio_socket_short_write_cancel);
+ ATF_TP_ADD_TC(tp, aio_fsync_test);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/aio/lio_kqueue_test.c b/tests/sys/aio/lio_kqueue_test.c
new file mode 100644
index 000000000000..5178a1d563e4
--- /dev/null
+++ b/tests/sys/aio/lio_kqueue_test.c
@@ -0,0 +1,238 @@
+/*-
+ * Copyright (C) 2005 IronPort Systems, 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 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 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$
+ */
+
+/*
+ * Note: it is a good idea to run this against a physical drive to
+ * exercise the physio fast path (ie. lio_kqueue /dev/<something safe>)
+ * This will ensure op's counting is correct. It is currently broken.
+ *
+ * Also note that LIO & kqueue is not implemented in FreeBSD yet, LIO
+ * is also broken with respect to op's and some paths.
+ *
+ * A patch to make this work is at:
+ * http://www.ambrisko.com/doug/listio_kqueue/listio_kqueue.patch
+ */
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <aio.h>
+#include <fcntl.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "freebsd_test_suite/macros.h"
+#include "local.h"
+
+#define PATH_TEMPLATE "aio.XXXXXXXXXX"
+
+#define LIO_MAX 5
+#define MAX_IOCBS LIO_MAX * 16
+#define MAX_RUNS 300
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ struct aiocb *iocb[MAX_IOCBS];
+ struct aiocb **lio[LIO_MAX], **kq_lio;
+ int i, result, run, error, j, k;
+ char buffer[32768];
+ int kq;
+ struct kevent ke, kq_returned;
+ struct timespec ts;
+ struct sigevent sig;
+ time_t time1, time2;
+ char *file, pathname[sizeof(PATH_TEMPLATE)];
+ int tmp_file = 0, failed = 0;
+
+ PLAIN_REQUIRE_KERNEL_MODULE("aio", 0);
+ PLAIN_REQUIRE_UNSAFE_AIO(0);
+
+ kq = kqueue();
+ if (kq < 0)
+ err(1, "kqeueue(2) failed");
+
+ if (argc == 1) {
+ strcpy(pathname, PATH_TEMPLATE);
+ fd = mkstemp(pathname);
+ file = pathname;
+ tmp_file = 1;
+ } else {
+ file = argv[1];
+ fd = open(file, O_RDWR|O_CREAT, 0666);
+ }
+ if (fd < 0)
+ err(1, "can't open %s", argv[1]);
+
+#ifdef DEBUG
+ printf("Hello kq %d fd %d\n", kq, fd);
+#endif
+
+ for (run = 0; run < MAX_RUNS; run++) {
+#ifdef DEBUG
+ printf("Run %d\n", run);
+#endif
+ for (j = 0; j < LIO_MAX; j++) {
+ lio[j] =
+ malloc(sizeof(struct aiocb *) * MAX_IOCBS/LIO_MAX);
+ for (i = 0; i < MAX_IOCBS / LIO_MAX; i++) {
+ k = (MAX_IOCBS / LIO_MAX * j) + i;
+ lio[j][i] = iocb[k] =
+ calloc(1, sizeof(struct aiocb));
+ iocb[k]->aio_nbytes = sizeof(buffer);
+ iocb[k]->aio_buf = buffer;
+ iocb[k]->aio_fildes = fd;
+ iocb[k]->aio_offset
+ = iocb[k]->aio_nbytes * k * (run + 1);
+
+#ifdef DEBUG
+ printf("hello iocb[k] %d\n",
+ iocb[k]->aio_offset);
+#endif
+ iocb[k]->aio_lio_opcode = LIO_WRITE;
+ }
+ sig.sigev_notify_kqueue = kq;
+ sig.sigev_value.sival_ptr = lio[j];
+ sig.sigev_notify = SIGEV_KEVENT;
+ time(&time1);
+ result = lio_listio(LIO_NOWAIT, lio[j],
+ MAX_IOCBS / LIO_MAX, &sig);
+ error = errno;
+ time(&time2);
+#ifdef DEBUG
+ printf("Time %d %d %d result -> %d\n",
+ time1, time2, time2-time1, result);
+#endif
+ if (result != 0) {
+ errno = error;
+ err(1, "FAIL: Result %d iteration %d\n",
+ result, j);
+ }
+#ifdef DEBUG
+ printf("write %d is at %p\n", j, lio[j]);
+#endif
+ }
+
+ for (i = 0; i < LIO_MAX; i++) {
+ for (j = LIO_MAX - 1; j >=0; j--) {
+ if (lio[j])
+ break;
+ }
+
+ for (;;) {
+ bzero(&ke, sizeof(ke));
+ bzero(&kq_returned, sizeof(ke));
+ ts.tv_sec = 0;
+ ts.tv_nsec = 1;
+#ifdef DEBUG
+ printf("FOO lio %d -> %p\n", j, lio[j]);
+#endif
+ EV_SET(&ke, (uintptr_t)lio[j],
+ EVFILT_LIO, EV_ONESHOT, 0, 0, iocb[j]);
+ result = kevent(kq, NULL, 0,
+ &kq_returned, 1, &ts);
+ error = errno;
+ if (result < 0) {
+ perror("kevent error: ");
+ }
+ kq_lio = kq_returned.udata;
+#ifdef DEBUG
+ printf("kevent %d %d errno %d return.ident %p "
+ "return.data %p return.udata %p %p\n",
+ i, result, error,
+ kq_returned.ident, kq_returned.data,
+ kq_returned.udata,
+ lio[j]);
+#endif
+
+ if (kq_lio)
+ break;
+#ifdef DEBUG
+ printf("Try again\n");
+#endif
+ }
+
+#ifdef DEBUG
+ printf("lio %p\n", lio);
+#endif
+
+ for (j = 0; j < LIO_MAX; j++) {
+ if (lio[j] == kq_lio)
+ break;
+ }
+ if (j == LIO_MAX)
+ errx(1, "FAIL: ");
+
+#ifdef DEBUG
+ printf("Error Result for %d is %d\n", j, result);
+#endif
+ if (result < 0) {
+ printf("FAIL: run %d, operation %d result %d \n", run, LIO_MAX - i -1, result);
+ failed++;
+ } else
+ printf("PASS: run %d, operation %d result %d \n", run, LIO_MAX - i -1, result);
+ for (k = 0; k < MAX_IOCBS / LIO_MAX; k++) {
+ result = aio_return(kq_lio[k]);
+#ifdef DEBUG
+ printf("Return Resulto for %d %d is %d\n", j, k, result);
+#endif
+ if (result != sizeof(buffer)) {
+ printf("FAIL: run %d, operation %d sub-opt %d result %d (errno=%d) should be %zu\n",
+ run, LIO_MAX - i -1, k, result, errno, sizeof(buffer));
+ } else {
+ printf("PASS: run %d, operation %d sub-opt %d result %d\n",
+ run, LIO_MAX - i -1, k, result);
+ }
+ }
+#ifdef DEBUG
+ printf("\n");
+#endif
+
+ for (k = 0; k < MAX_IOCBS / LIO_MAX; k++)
+ free(lio[j][k]);
+ free(lio[j]);
+ lio[j] = NULL;
+ }
+ }
+#ifdef DEBUG
+ printf("Done\n");
+#endif
+
+ if (tmp_file)
+ unlink(pathname);
+
+ if (failed)
+ errx(1, "FAIL: %d testcases failed", failed);
+ else
+ errx(0, "PASS: All\n");
+
+}
diff --git a/tests/sys/aio/local.h b/tests/sys/aio/local.h
new file mode 100644
index 000000000000..308b70010cf4
--- /dev/null
+++ b/tests/sys/aio/local.h
@@ -0,0 +1,74 @@
+/*-
+ * Copyright (c) 2016 Chelsio Communications, Inc.
+ * All rights reserved.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ *
+ * 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 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 _AIO_TEST_LOCAL_H_
+#define _AIO_TEST_LOCAL_H_
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#define ATF_REQUIRE_UNSAFE_AIO() do { \
+ size_t _len; \
+ int _unsafe; \
+ \
+ _len = sizeof(_unsafe); \
+ if (sysctlbyname("vfs.aio.enable_unsafe", &_unsafe, &_len, NULL,\
+ 0) < 0) { \
+ if (errno != ENOENT) \
+ atf_libc_error(errno, \
+ "Failed to read vfs.aio.enable_unsafe"); \
+ } else if (_unsafe == 0) \
+ atf_tc_skip("Unsafe AIO is disabled"); \
+} while (0)
+
+#define PLAIN_REQUIRE_UNSAFE_AIO(_exit_code) do { \
+ size_t _len; \
+ int _unsafe; \
+ \
+ _len = sizeof(_unsafe); \
+ if (sysctlbyname("vfs.aio.enable_unsafe", &_unsafe, &_len, NULL,\
+ 0) < 0) { \
+ if (errno != ENOENT) { \
+ printf("Failed to read vfs.aio.enable_unsafe: %s\n",\
+ strerror(errno)); \
+ _exit(1); \
+ } \
+ } else if (_unsafe == 0) { \
+ printf("Unsafe AIO is disabled"); \
+ _exit(_exit_code); \
+ } \
+} while (0)
+
+#endif /* !_AIO_TEST_LOCAL_H_ */
diff --git a/tests/sys/fifo/Makefile b/tests/sys/fifo/Makefile
new file mode 100644
index 000000000000..602bf1ed2940
--- /dev/null
+++ b/tests/sys/fifo/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/fifo
+
+PLAIN_TESTS_C+= fifo_create
+PLAIN_TESTS_C+= fifo_io
+PLAIN_TESTS_C+= fifo_misc
+PLAIN_TESTS_C+= fifo_open
+
+TEST_METADATA.fifo_create+= required_user="root"
+TEST_METADATA.fifo_open+= required_user="root"
+
+.include <bsd.test.mk>
diff --git a/tests/sys/fifo/Makefile.depend b/tests/sys/fifo/Makefile.depend
new file mode 100644
index 000000000000..3646e2e2b1af
--- /dev/null
+++ b/tests/sys/fifo/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/fifo/fifo_create.c b/tests/sys/fifo/fifo_create.c
new file mode 100644
index 000000000000..2eb01e577c53
--- /dev/null
+++ b/tests/sys/fifo/fifo_create.c
@@ -0,0 +1,282 @@
+/*-
+ * Copyright (c) 2005-2008 Robert N. M. Watson
+ * 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 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 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$
+ */
+
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Simple regression test for the creation and destruction of POSIX fifos in
+ * the file system name space. Using a specially created directory, create
+ * a fifo in it and check that the following properties are present, as
+ * specified in IEEE Std 1003.1, 2004 Edition:
+ *
+ * - When mkfifo() or mknod(S_IFIFO) is called, on success, a fifo is
+ * created.
+ *
+ * - On an error, no fifo is created. (XXX: Not tested)
+ *
+ * - The mode bits on the fifo are a product of combining the umask and
+ * requested mode.
+ *
+ * - The fifo's owner will be the processes effective user ID. (XXX: Not
+ * tested)
+ *
+ * - The fifo's group will be the parent directory's group or the effective
+ * group ID of the process. For historical reasons, BSD prefers the group
+ * ID of the process, so we will generate an error if it's not that. (XXX:
+ * Not tested)
+ *
+ * - The st_atime, st_ctime, st_mtime of the fifo will be set appropriately,
+ * and st_ctime and st_mtime on the directory will be updated. (XXX: We
+ * test they are updated, not correct)
+ *
+ * - EEXIST is returned if the named file already exists.
+ *
+ * In addition, we check that we can unlink the fifo, and that if we do, it
+ * disappears.
+ *
+ * This test must run as root in order to usefully frob the process
+ * credential to test permission parts.
+ */
+
+/*
+ * All activity occurs within a temporary directory created early in the
+ * test.
+ */
+static char temp_dir[PATH_MAX];
+
+static void __unused
+atexit_temp_dir(void)
+{
+
+ rmdir(temp_dir);
+}
+
+/*
+ * Basic creation tests: verify that mkfifo(2) (or mknod(2)) creates a fifo,
+ * that the time stamps on the directory are updated, that if we try twice we
+ * get EEXIST, and that we can unlink it.
+ */
+static void
+fifo_create_test(int use_mkfifo)
+{
+ struct stat old_dirsb, dirsb, fifosb;
+ const char *testname;
+ char path[] = "testfifo";
+ int error;
+
+ if (use_mkfifo)
+ testname = "mkfifo";
+ else
+ testname = "mknod";
+
+ /*
+ * Sleep to make sure that the time stamp on the directory will be
+ * updated.
+ */
+ if (stat(".", &old_dirsb) < 0)
+ err(-1, "basic_create_test: %s: stat: %s", testname,
+ temp_dir);
+
+ sleep(2);
+
+ if (use_mkfifo) {
+ if (mkfifo(path, 0600) < 0)
+ err(-1, "basic_create_test: %s: %s", testname, path);
+ } else {
+ if (mknod(path, S_IFIFO | 0600, 0) < 0)
+ err(-1, "basic_create_test: %s: %s", testname, path);
+ }
+
+ if (stat(path, &fifosb) < 0) {
+ error = errno;
+ (void)unlink(path);
+ errno = error;
+ err(-1, "basic_create_test: %s: stat: %s", testname, path);
+ }
+
+ if (!(S_ISFIFO(fifosb.st_mode))) {
+ (void)unlink(path);
+ errx(-1, "basic_create_test: %s produced non-fifo",
+ testname);
+ }
+
+ if (use_mkfifo) {
+ if (mkfifo(path, 0600) == 0)
+ errx(-1, "basic_create_test: dup %s succeeded",
+ testname);
+ } else {
+ if (mknod(path, S_IFIFO | 0600, 0) == 0)
+ errx(-1, "basic_create_test: dup %s succeeded",
+ testname);
+ }
+
+ if (errno != EEXIST)
+ err(-1, "basic_create_test: dup %s unexpected error",
+ testname);
+
+ if (stat(".", &dirsb) < 0) {
+ error = errno;
+ (void)unlink(path);
+ errno = error;
+ err(-1, "basic_create_test: %s: stat: %s", testname,
+ temp_dir);
+ }
+
+ if (old_dirsb.st_ctime == dirsb.st_ctime) {
+ (void)unlink(path);
+ errx(-1, "basic_create_test: %s: old_dirsb.st_ctime == "
+ "dirsb.st_ctime", testname);
+ }
+
+ if (old_dirsb.st_mtime == dirsb.st_mtime) {
+ (void)unlink(path);
+ errx(-1, "basic_create_test: %s: old_dirsb.st_mtime == "
+ "dirsb.st_mtime", testname);
+ }
+
+ if (unlink(path) < 0)
+ err(-1, "basic_create_test: %s: unlink: %s", testname, path);
+
+ if (stat(path, &fifosb) == 0)
+ errx(-1, "basic_create_test: %s: unlink failed to unlink",
+ testname);
+ if (errno != ENOENT)
+ err(-1, "basic_create_test: %s: unlink unexpected error",
+ testname);
+}
+
+/*
+ * Having determined that basic create/remove/etc functionality is present
+ * for fifos, now make sure that the umask, requested permissions, and
+ * resulting mode are handled properly.
+ */
+static const struct permission_test {
+ mode_t pt_umask;
+ mode_t pt_reqmode;
+ mode_t pt_mode;
+} permission_test[] = {
+ {0000, 0, S_IFIFO},
+ {0000, S_IRWXU, S_IFIFO | S_IRWXU},
+ {0000, S_IRWXU | S_IRWXG | S_IRWXO, S_IFIFO | S_IRWXU | S_IRWXG |
+ S_IRWXO },
+ {0077, S_IRWXU, S_IFIFO | S_IRWXU},
+ {0077, S_IRWXU | S_IRWXG | S_IRWXO, S_IFIFO | S_IRWXU},
+};
+static const int permission_test_count = sizeof(permission_test) /
+ sizeof(struct permission_test);
+
+static void
+fifo_permission_test(int use_mkfifo)
+{
+ const struct permission_test *ptp;
+ mode_t __unused old_umask;
+ char path[] = "testfifo";
+ const char *testname;
+ struct stat sb;
+ int error, i;
+
+ if (use_mkfifo)
+ testname = "mkfifo";
+ else
+ testname = "mknod";
+
+ old_umask = umask(0022);
+ for (i = 0; i < permission_test_count; i++) {
+ ptp = &permission_test[i];
+
+ umask(ptp->pt_umask);
+ if (use_mkfifo) {
+ if (mkfifo(path, ptp->pt_reqmode) < 0)
+ err(-1, "fifo_permission_test: %s: %08o "
+ "%08o %08o\n", testname, ptp->pt_umask,
+ ptp->pt_reqmode, ptp->pt_mode);
+ } else {
+ if (mknod(path, S_IFIFO | ptp->pt_reqmode, 0) < 0)
+ err(-1, "fifo_permission_test: %s: %08o "
+ "%08o %08o\n", testname, ptp->pt_umask,
+ ptp->pt_reqmode, ptp->pt_mode);
+ }
+
+ if (stat(path, &sb) < 0) {
+ error = errno;
+ (void)unlink(path);
+ errno = error;
+ err(-1, "fifo_permission_test: %s: %s", testname,
+ path);
+ }
+
+ if (sb.st_mode != ptp->pt_mode) {
+ (void)unlink(path);
+ errx(-1, "fifo_permission_test: %s: %08o %08o %08o "
+ "got %08o", testname, ptp->pt_umask,
+ ptp->pt_reqmode, ptp->pt_mode, sb.st_mode);
+ }
+
+ if (unlink(path) < 0)
+ err(-1, "fifo_permission_test: %s: unlink: %s",
+ testname, path);
+ }
+ umask(old_umask);
+}
+
+int
+main(void)
+{
+ int i;
+
+ if (geteuid() != 0)
+ errx(-1, "must be run as root");
+
+ strcpy(temp_dir, "fifo_create.XXXXXXXXXXX");
+ if (mkdtemp(temp_dir) == NULL)
+ err(-1, "mkdtemp");
+ atexit(atexit_temp_dir);
+
+ if (chdir(temp_dir) < 0)
+ err(-1, "chdir");
+
+ /*
+ * Run each test twice, once with mknod(2) and a second time with
+ * mkfifo(2). Historically, BSD has not allowed mknod(2) to be used
+ * to create fifos, but the Single UNIX Specification requires it.
+ */
+ for (i = 0; i < 2; i++) {
+ fifo_create_test(i);
+ fifo_permission_test(i);
+ }
+
+ return (0);
+}
diff --git a/tests/sys/fifo/fifo_io.c b/tests/sys/fifo/fifo_io.c
new file mode 100644
index 000000000000..93d4be7e2f17
--- /dev/null
+++ b/tests/sys/fifo/fifo_io.c
@@ -0,0 +1,1399 @@
+/*-
+ * Copyright (c) 2005 Robert N. M. Watson
+ * 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 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 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$
+ */
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Regression test to exercise POSIX fifo I/O.
+ *
+ * We test a number of aspect of behavior, including:
+ *
+ * - If there's no data to read, then for blocking fifos, we block, and for
+ * non-blocking, we return EAGAIN.
+ *
+ * - If we write ten bytes, ten bytes can be read, and they're the same
+ * bytes, in the same order.
+ *
+ * - If we write two batches of five bytes, we can read the same ten bytes in
+ * one read of ten bytes.
+ *
+ * - If we write ten bytes, we can read the same ten bytes in two reads of
+ * five bytes each.
+ *
+ * - If we over-fill a buffer (by writing 512k, which we take to be a large
+ * number above default buffer sizes), we block if there is no reader.
+ *
+ * - That once 512k (ish) is read from the other end, the blocked writer
+ * wakes up.
+ *
+ * - When a fifo is empty, poll, select, kqueue, and fionread report it is
+ * writable but not readable.
+ *
+ * - When a fifo has data in it, poll, select, and kqueue report that it is
+ * writable.
+ *
+ * - XXX: blocked reader semantics?
+ *
+ * - XXX: event behavior on remote close?
+ *
+ * Although behavior of O_RDWR isn't defined for fifos by POSIX, we expect
+ * "reasonable" behavior, and run some additional tests relating to event
+ * management on O_RDWR fifo descriptors.
+ */
+
+#define KQUEUE_MAX_EVENT 8
+
+/*
+ * All activity occurs within a temporary directory created early in the
+ * test.
+ */
+static char temp_dir[PATH_MAX];
+
+static void __unused
+atexit_temp_dir(void)
+{
+
+ rmdir(temp_dir);
+}
+
+static void
+makefifo(const char *fifoname, const char *testname)
+{
+
+ if (mkfifo(fifoname, 0700) < 0)
+ err(-1, "%s: makefifo: mkfifo: %s", testname, fifoname);
+}
+
+static void
+cleanfifo2(const char *fifoname, int fd1, int fd2)
+{
+
+ if (fd1 != -1)
+ close(fd1);
+ if (fd2 != -1)
+ close(fd2);
+ (void)unlink(fifoname);
+}
+
+static void
+cleanfifo3(const char *fifoname, int fd1, int fd2, int fd3)
+{
+
+ if (fd3 != -1)
+ close(fd3);
+ cleanfifo2(fifoname, fd1, fd2);
+}
+
+/*
+ * Open two different file descriptors for a fifo: one read, one write. Do
+ * so using non-blocking opens in order to avoid deadlocking the process.
+ */
+static int
+openfifo(const char *fifoname, int *reader_fdp, int *writer_fdp)
+{
+ int error, fd1, fd2;
+
+ fd1 = open(fifoname, O_RDONLY | O_NONBLOCK);
+ if (fd1 < 0)
+ return (-1);
+ fd2 = open(fifoname, O_WRONLY | O_NONBLOCK);
+ if (fd2 < 0) {
+ error = errno;
+ close(fd1);
+ errno = error;
+ return (-1);
+ }
+ *reader_fdp = fd1;
+ *writer_fdp = fd2;
+
+ return (0);
+}
+
+/*
+ * Open one file descriptor for the fifo, supporting both read and write.
+ */
+static int
+openfifo_rw(const char *fifoname, int *fdp)
+{
+ int fd;
+
+ fd = open(fifoname, O_RDWR);
+ if (fd < 0)
+ return (-1);
+ *fdp = fd;
+
+ return (0);
+}
+
+static int
+set_nonblocking(int fd, const char *testname)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0) {
+ warn("%s: fcntl(fd, F_GETFL)", testname);
+ return(-1);
+ }
+
+ flags |= O_NONBLOCK;
+
+ if (fcntl(fd, F_SETFL, flags) < 0) {
+ warn("%s: fcntl(fd, 0x%x)", testname, flags);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+set_blocking(int fd, const char *testname)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0) {
+ warn("%s: fcntl(fd, F_GETFL)", testname);
+ return(-1);
+ }
+
+ flags &= ~O_NONBLOCK;
+
+ if (fcntl(fd, F_SETFL, flags) < 0) {
+ warn("%s: fcntl(fd, 0x%x)", testname, flags);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Drain a file descriptor (fifo) of any readable data. Note: resets the
+ * blocking state.
+ */
+static int
+drain_fd(int fd, const char *testname)
+{
+ ssize_t len;
+ u_char ch;
+
+ if (set_nonblocking(fd, testname) < 0)
+ return (-1);
+
+ while ((len = read(fd, &ch, sizeof(ch))) > 0);
+ if (len < 0) {
+ switch (errno) {
+ case EAGAIN:
+ return (0);
+ default:
+ warn("%s: drain_fd: read", testname);
+ return (-1);
+ }
+ }
+ warn("%s: drain_fd: read: returned 0 bytes", testname);
+ return (-1);
+}
+
+/*
+ * Simple I/O test: write ten integers, and make sure we get back the same
+ * integers in the same order. This assumes a minimum fifo buffer > 10
+ * bytes in order to not block and deadlock.
+ */
+static void
+test_simpleio(void)
+{
+ int i, reader_fd, writer_fd;
+ u_char buffer[10];
+ ssize_t len;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd)
+ < 0) {
+ warn("test_simpleio: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ for (i = 0; i < 10; i++)
+ buffer[i] = i;
+
+ len = write(writer_fd, (char *)buffer, sizeof(buffer));
+ if (len < 0) {
+ warn("test_simpleio: write");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != sizeof(buffer)) {
+ warnx("test_simplio: tried %zu but wrote %zd", sizeof(buffer),
+ len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ len = read(reader_fd, (char *)buffer, sizeof(buffer));
+ if (len < 0) {
+ warn("test_simpleio: read");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != sizeof(buffer)) {
+ warnx("test_simpleio: tried %zu but read %zd", sizeof(buffer),
+ len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ for (i = 0; i < 10; i++) {
+ if (buffer[i] == i)
+ continue;
+ warnx("test_simpleio: write byte %d as 0x%02x, but read "
+ "0x%02x", i, i, buffer[i]);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+}
+
+static volatile int alarm_fired;
+/*
+ * Non-destructive SIGALRM handler.
+ */
+static void
+sigalarm(int signum __unused)
+{
+
+ alarm_fired = 1;
+}
+
+/*
+ * Wrapper function for write, which uses a timer to interrupt any blocking.
+ * Because we can't reliably detect EINTR for blocking I/O, we also track
+ * whether or not our timeout fired.
+ */
+static int __unused
+timed_write(int fd, void *data, size_t len, ssize_t *written_lenp,
+ int timeout, int *timedoutp, const char *testname)
+{
+ struct sigaction act, oact;
+ ssize_t written_len;
+ int error;
+
+ alarm_fired = 0;
+ bzero(&act, sizeof(oact));
+ act.sa_handler = sigalarm;
+ if (sigaction(SIGALRM, &act, &oact) < 0) {
+ warn("%s: timed_write: sigaction", testname);
+ return (-1);
+ }
+ alarm(timeout);
+ written_len = write(fd, data, len);
+ error = errno;
+ alarm(0);
+ if (sigaction(SIGALRM, &oact, NULL) < 0) {
+ warn("%s: timed_write: sigaction", testname);
+ return (-1);
+ }
+ if (alarm_fired)
+ *timedoutp = 1;
+ else
+ *timedoutp = 0;
+
+ errno = error;
+ if (written_len < 0)
+ return (-1);
+ *written_lenp = written_len;
+ return (0);
+}
+
+/*
+ * Wrapper function for read, which uses a timer to interrupt any blocking.
+ * Because we can't reliably detect EINTR for blocking I/O, we also track
+ * whether or not our timeout fired.
+ */
+static int
+timed_read(int fd, void *data, size_t len, ssize_t *read_lenp,
+ int timeout, int *timedoutp, const char *testname)
+{
+ struct sigaction act, oact;
+ ssize_t read_len;
+ int error;
+
+ alarm_fired = 0;
+ bzero(&act, sizeof(oact));
+ act.sa_handler = sigalarm;
+ if (sigaction(SIGALRM, &act, &oact) < 0) {
+ warn("%s: timed_write: sigaction", testname);
+ return (-1);
+ }
+ alarm(timeout);
+ read_len = read(fd, data, len);
+ error = errno;
+ alarm(0);
+ if (sigaction(SIGALRM, &oact, NULL) < 0) {
+ warn("%s: timed_write: sigaction", testname);
+ return (-1);
+ }
+ if (alarm_fired)
+ *timedoutp = 1;
+ else
+ *timedoutp = 0;
+
+ errno = error;
+ if (read_len < 0)
+ return (-1);
+ *read_lenp = read_len;
+ return (0);
+}
+
+/*
+ * This test operates on blocking and non-blocking fifo file descriptors, in
+ * order to determine whether they block at good moments or not. By good we
+ * mean: don't block for non-blocking sockets, and do block for blocking
+ * ones, assuming there isn't I/O buffer to satisfy the request.
+ *
+ * We use a timeout of 5 seconds, concluding that in 5 seconds either all I/O
+ * that can take place will, and that if we reach the end of the timeout,
+ * then blocking has occurred.
+ *
+ * We assume that the buffer size on a fifo is <512K, and as such, that
+ * writing that much data without an active reader will result in blocking.
+ */
+static void
+test_blocking_read_empty(void)
+{
+ int reader_fd, ret, timedout, writer_fd;
+ ssize_t len;
+ u_char ch;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd)
+ < 0) {
+ warn("test_blocking_read_empty: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ /*
+ * Read one byte from an empty blocking fifo, block as there is no
+ * data.
+ */
+ if (set_blocking(reader_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ ret = timed_read(reader_fd, &ch, sizeof(ch), &len, 5, &timedout,
+ __func__);
+ if (ret != -1) {
+ warnx("test_blocking_read_empty: timed_read: returned "
+ "success");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (errno != EINTR) {
+ warn("test_blocking_read_empty: timed_read");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ /*
+ * Read one byte from an empty non-blocking fifo, return EAGAIN as
+ * there is no data.
+ */
+ if (set_nonblocking(reader_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ ret = timed_read(reader_fd, &ch, sizeof(ch), &len, 5, &timedout,
+ __func__);
+ if (ret != -1) {
+ warnx("test_blocking_read_empty: timed_read: returned "
+ "success");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (errno != EAGAIN) {
+ warn("test_blocking_read_empty: timed_read");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+}
+
+/*
+ * Write one byte to an empty fifo, then try to read one byte and make sure
+ * we don't block in either the write or the read. This tests both for
+ * improper blocking in the send and receive code.
+ */
+static void
+test_blocking_one_byte(void)
+{
+ int reader_fd, ret, timedout, writer_fd;
+ ssize_t len;
+ u_char ch;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_blocking: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ if (set_blocking(writer_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (set_blocking(reader_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ ch = 0xfe;
+ ret = timed_write(writer_fd, &ch, sizeof(ch), &len, 5, &timedout,
+ __func__);
+ if (ret < 0) {
+ warn("test_blocking_one_byte: timed_write");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != sizeof(ch)) {
+ warnx("test_blocking_one_byte: timed_write: tried to write "
+ "%zu, wrote %zd", sizeof(ch), len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ ch = 0xab;
+ ret = timed_read(reader_fd, &ch, sizeof(ch), &len, 5, &timedout,
+ __func__);
+ if (ret < 0) {
+ warn("test_blocking_one_byte: timed_read");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != sizeof(ch)) {
+ warnx("test_blocking_one_byte: timed_read: wanted %zu, "
+ "read %zd", sizeof(ch), len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (ch != 0xfe) {
+ warnx("test_blocking_one_byte: timed_read: expected to read "
+ "0x%02x, read 0x%02x", 0xfe, ch);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+}
+
+/*
+ * Write one byte to an empty fifo, then try to read one byte and make sure
+ * we don't get back EAGAIN.
+ */
+static void
+test_nonblocking_one_byte(void)
+{
+ int reader_fd, ret, timedout, writer_fd;
+ ssize_t len;
+ u_char ch;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_nonblocking: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ if (set_nonblocking(reader_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ ch = 0xfe;
+ ret = timed_write(writer_fd, &ch, sizeof(ch), &len, 5, &timedout,
+ __func__);
+ if (ret < 0) {
+ warn("test_nonblocking_one_byte: timed_write");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != sizeof(ch)) {
+ warnx("test_nonblocking_one_byte: timed_write: tried to write "
+ "%zu, wrote %zd", sizeof(ch), len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ ch = 0xab;
+ ret = timed_read(reader_fd, &ch, sizeof(ch), &len, 5, &timedout,
+ __func__);
+ if (ret < 0) {
+ warn("test_nonblocking_one_byte: timed_read");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != sizeof(ch)) {
+ warnx("test_nonblocking_one_byte: timed_read: wanted %zu, read "
+ "%zd", sizeof(ch), len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (ch != 0xfe) {
+ warnx("test_nonblocking_one_byte: timed_read: expected to read "
+ "0x%02x, read 0x%02x", 0xfe, ch);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+}
+
+/*
+ * First of two test cases involving a 512K buffer: write the buffer into a
+ * blocking file descriptor. We'd like to know it blocks, but the closest we
+ * can get is to see if SIGALRM fired during the I/O resulting in a partial
+ * write.
+ */
+static void
+test_blocking_partial_write(void)
+{
+ int reader_fd, ret, timedout, writer_fd;
+ u_char *buffer;
+ ssize_t len;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_blocking_partial_write: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ if (set_blocking(writer_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ buffer = malloc(512*1024);
+ if (buffer == NULL) {
+ warn("test_blocking_partial_write: malloc");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ bzero(buffer, 512*1024);
+
+ ret = timed_write(writer_fd, buffer, 512*1024, &len, 5, &timedout,
+ __func__);
+ if (ret < 0) {
+ warn("test_blocking_partial_write: timed_write");
+ free(buffer);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (!timedout) {
+ warnx("test_blocking_partial_write: timed_write: blocking "
+ "socket didn't time out");
+ free(buffer);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ free(buffer);
+
+ if (drain_fd(reader_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+}
+
+/*
+ * Write a 512K buffer to an empty fifo using a non-blocking file descriptor,
+ * and make sure it doesn't block.
+ */
+static void
+test_nonblocking_partial_write(void)
+{
+ int reader_fd, ret, timedout, writer_fd;
+ u_char *buffer;
+ ssize_t len;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_blocking_partial_write: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ if (set_nonblocking(writer_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ buffer = malloc(512*1024);
+ if (buffer == NULL) {
+ warn("test_blocking_partial_write: malloc");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ bzero(buffer, 512*1024);
+
+ ret = timed_write(writer_fd, buffer, 512*1024, &len, 5, &timedout,
+ __func__);
+ if (ret < 0) {
+ warn("test_blocking_partial_write: timed_write");
+ free(buffer);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (timedout) {
+ warnx("test_blocking_partial_write: timed_write: "
+ "non-blocking socket timed out");
+ free(buffer);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (len == 0 || len >= 512*1024) {
+ warnx("test_blocking_partial_write: timed_write: requested "
+ "%d, sent %zd", 512*1024, len);
+ free(buffer);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ free(buffer);
+
+ if (drain_fd(reader_fd, __func__) < 0) {
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+}
+
+/*
+ * test_coalesce_big_read() verifies that data mingles in the fifo across
+ * message boundaries by performing two small writes, then a bigger read
+ * that should return data from both writes.
+ */
+static void
+test_coalesce_big_read(void)
+{
+ int i, reader_fd, writer_fd;
+ u_char buffer[10];
+ ssize_t len;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_coalesce_big_read: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ /* Write five, write five, read ten. */
+ for (i = 0; i < 10; i++)
+ buffer[i] = i;
+
+ len = write(writer_fd, buffer, 5);
+ if (len < 0) {
+ warn("test_coalesce_big_read: write 5");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != 5) {
+ warnx("test_coalesce_big_read: write 5 wrote %zd", len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ len = write(writer_fd, buffer + 5, 5);
+ if (len < 0) {
+ warn("test_coalesce_big_read: write 5");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != 5) {
+ warnx("test_coalesce_big_read: write 5 wrote %zd", len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ len = read(reader_fd, buffer, 10);
+ if (len < 0) {
+ warn("test_coalesce_big_read: read 10");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != 10) {
+ warnx("test_coalesce_big_read: read 10 read %zd", len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ for (i = 0; i < 10; i++) {
+ if (buffer[i] == i)
+ continue;
+ warnx("test_coalesce_big_read: expected to read 0x%02x, "
+ "read 0x%02x", i, buffer[i]);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", -1, -1);
+}
+
+/*
+ * test_coalesce_big_write() verifies that data mingles in the fifo across
+ * message boundaries by performing one big write, then two smaller reads
+ * that should return sequential elements of data from the write.
+ */
+static void
+test_coalesce_big_write(void)
+{
+ int i, reader_fd, writer_fd;
+ u_char buffer[10];
+ ssize_t len;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_coalesce_big_write: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ /* Write ten, read five, read five. */
+ for (i = 0; i < 10; i++)
+ buffer[i] = i;
+
+ len = write(writer_fd, buffer, 10);
+ if (len < 0) {
+ warn("test_coalesce_big_write: write 10");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != 10) {
+ warnx("test_coalesce_big_write: write 10 wrote %zd", len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ len = read(reader_fd, buffer, 5);
+ if (len < 0) {
+ warn("test_coalesce_big_write: read 5");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != 5) {
+ warnx("test_coalesce_big_write: read 5 read %zd", len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ len = read(reader_fd, buffer + 5, 5);
+ if (len < 0) {
+ warn("test_coalesce_big_write: read 5");
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (len != 5) {
+ warnx("test_coalesce_big_write: read 5 read %zd", len);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ for (i = 0; i < 10; i++) {
+ if (buffer[i] == i)
+ continue;
+ warnx("test_coalesce_big_write: expected to read 0x%02x, "
+ "read 0x%02x", i, buffer[i]);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", -1, -1);
+}
+
+static int
+poll_status(int fd, int *readable, int *writable, int *exception,
+ const char *testname)
+{
+ struct pollfd fds[1];
+
+ fds[0].fd = fd;
+ fds[0].events = POLLIN | POLLOUT | POLLERR;
+ fds[0].revents = 0;
+
+ if (poll(fds, 1, 0) < 0) {
+ warn("%s: poll", testname);
+ return (-1);
+ }
+ *readable = (fds[0].revents & POLLIN) ? 1 : 0;
+ *writable = (fds[0].revents & POLLOUT) ? 1 : 0;
+ *exception = (fds[0].revents & POLLERR) ? 1 : 0;
+ return (0);
+}
+
+static int
+select_status(int fd, int *readable, int *writable, int *exception,
+ const char *testname)
+{
+ struct fd_set readfds, writefds, exceptfds;
+ struct timeval timeout;
+
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_ZERO(&exceptfds);
+ FD_SET(fd, &readfds);
+ FD_SET(fd, &writefds);
+ FD_SET(fd, &exceptfds);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ if (select(fd+1, &readfds, &writefds, &exceptfds, &timeout) < 0) {
+ warn("%s: select", testname);
+ return (-1);
+ }
+ *readable = FD_ISSET(fd, &readfds) ? 1 : 0;
+ *writable = FD_ISSET(fd, &writefds) ? 1 : 0;
+ *exception = FD_ISSET(fd, &exceptfds) ? 1 : 0;
+ return (0);
+}
+
+/*
+ * Given an existing kqueue, set up read and write event filters for the
+ * passed file descriptor. Typically called once for the read endpoint, and
+ * once for the write endpoint.
+ */
+static int
+kqueue_setup(int kqueue_fd, int fd, const char *testname)
+{
+ struct kevent kevent_changelist[2];
+ struct kevent kevent_eventlist[KQUEUE_MAX_EVENT], *kp;
+ struct timespec timeout;
+ int i, ret;
+
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 0;
+
+ bzero(&kevent_changelist, sizeof(kevent_changelist));
+ EV_SET(&kevent_changelist[0], fd, EVFILT_READ, EV_ADD, 0, 0, 0);
+ EV_SET(&kevent_changelist[1], fd, EVFILT_WRITE, EV_ADD, 0, 0, 0);
+
+ bzero(&kevent_eventlist, sizeof(kevent_eventlist));
+ ret = kevent(kqueue_fd, kevent_changelist, 2, kevent_eventlist,
+ KQUEUE_MAX_EVENT, &timeout);
+ if (ret < 0) {
+ warn("%s:%s: kevent initial register", testname, __func__);
+ return (-1);
+ }
+
+ /*
+ * Verify that the events registered alright.
+ */
+ for (i = 0; i < ret; i++) {
+ kp = &kevent_eventlist[i];
+ if (kp->flags != EV_ERROR)
+ continue;
+ errno = kp->data;
+ warn("%s:%s: kevent register index %d", testname, __func__,
+ i);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+kqueue_status(int kqueue_fd, int fd, int *readable, int *writable,
+ int *exception, const char *testname)
+{
+ struct kevent kevent_eventlist[KQUEUE_MAX_EVENT], *kp;
+ struct timespec timeout;
+ int i, ret;
+
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 0;
+
+ ret = kevent(kqueue_fd, NULL, 0, kevent_eventlist, KQUEUE_MAX_EVENT,
+ &timeout);
+ if (ret < 0) {
+ warn("%s: %s: kevent", testname, __func__);
+ return (-1);
+ }
+
+ *readable = *writable = *exception = 0;
+ for (i = 0; i < ret; i++) {
+ kp = &kevent_eventlist[i];
+ if (kp->ident != (u_int)fd)
+ continue;
+ if (kp->filter == EVFILT_READ)
+ *readable = 1;
+ if (kp->filter == EVFILT_WRITE)
+ *writable = 1;
+ }
+
+ return (0);
+}
+
+static int
+fionread_status(int fd, int *readable, const char *testname)
+{
+ int i;
+
+ if (ioctl(fd, FIONREAD, &i) < 0) {
+ warn("%s: ioctl(FIONREAD)", testname);
+ return (-1);
+ }
+
+ if (i > 0)
+ *readable = 1;
+ else
+ *readable = 0;
+ return (0);
+}
+
+#define READABLE 1
+#define WRITABLE 1
+#define EXCEPTION 1
+
+#define NOT_READABLE 0
+#define NOT_WRITABLE 0
+#define NOT_EXCEPTION 0
+
+static int
+assert_status(int fd, int kqueue_fd, int assert_readable,
+ int assert_writable, int assert_exception, const char *testname,
+ const char *conditionname, const char *fdname)
+{
+ int readable, writable, exception;
+
+ if (poll_status(fd, &readable, &writable, &exception, testname) < 0)
+ return (-1);
+
+ if (readable != assert_readable || writable != assert_writable ||
+ exception != assert_exception) {
+ warnx("%s: %s polls r:%d, w:%d, e:%d on %s", testname,
+ fdname, readable, writable, exception, conditionname);
+ return (-1);
+ }
+
+ if (select_status(fd, &readable, &writable, &exception, testname) < 0)
+ return (-1);
+
+ if (readable != assert_readable || writable != assert_writable ||
+ exception != assert_exception) {
+ warnx("%s: %s selects r:%d, w:%d, e:%d on %s", testname,
+ fdname, readable, writable, exception, conditionname);
+ return (-1);
+ }
+
+ if (kqueue_status(kqueue_fd, fd, &readable, &writable, &exception,
+ testname) < 0)
+ return (-1);
+
+ if (readable != assert_readable || writable != assert_writable ||
+ exception != assert_exception) {
+ warnx("%s: %s kevent r:%d, w:%d, e:%d on %s", testname,
+ fdname, readable, writable, exception, conditionname);
+ return (-1);
+ }
+
+ if (fionread_status(fd, &readable, __func__) < 0)
+ return (-1);
+
+ if (readable != assert_readable) {
+ warnx("%s: %s fionread r:%d on %s", testname, fdname,
+ readable, conditionname);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * test_events() uses poll(), select(), and kevent() to query the status of
+ * fifo file descriptors and determine whether they match expected state
+ * based on earlier semantic tests: specifically, whether or not poll/select/
+ * kevent will correctly inform on readable/writable state following I/O.
+ *
+ * It would be nice to also test status changes as a result of closing of one
+ * or another fifo endpoint.
+ */
+static void
+test_events_outofbox(void)
+{
+ int kqueue_fd, reader_fd, writer_fd;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_events_outofbox: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ kqueue_fd = kqueue();
+ if (kqueue_fd < 0) {
+ warn("%s: kqueue", __func__);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (kqueue_setup(kqueue_fd, reader_fd, __func__) < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (kqueue_setup(kqueue_fd, writer_fd, __func__) < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * Make sure that fresh, out-of-the-box fifo file descriptors have
+ * good initial states. The reader_fd should have no active state,
+ * since it will not be readable (no data in pipe), writable (it's
+ * a read-only descriptor), and there's no reason for error yet.
+ */
+ if (assert_status(reader_fd, kqueue_fd, NOT_READABLE, NOT_WRITABLE,
+ NOT_EXCEPTION, __func__, "create", "reader_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * Make sure that fresh, out-of-the-box fifo file descriptors have
+ * good initial states. The writer_fd should be ready to write.
+ */
+ if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, WRITABLE,
+ NOT_EXCEPTION, __func__, "create", "writer_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+}
+
+static void
+test_events_write_read_byte(void)
+{
+ int kqueue_fd, reader_fd, writer_fd;
+ ssize_t len;
+ u_char ch;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_events_write_read_byte: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ kqueue_fd = kqueue();
+ if (kqueue_fd < 0) {
+ warn("%s: kqueue", __func__);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (kqueue_setup(kqueue_fd, reader_fd, __func__) < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (kqueue_setup(kqueue_fd, writer_fd, __func__) < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * Write a byte to the fifo, and make sure that the read end becomes
+ * readable, and that the write end remains writable (small write).
+ */
+ ch = 0x00;
+ len = write(writer_fd, &ch, sizeof(ch));
+ if (len < 0) {
+ warn("%s: write", __func__);
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (assert_status(reader_fd, kqueue_fd, READABLE, NOT_WRITABLE,
+ NOT_EXCEPTION, __func__, "write", "reader_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * the writer_fd should remain writable.
+ */
+ if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, WRITABLE,
+ NOT_EXCEPTION, __func__, "write", "writer_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * Read the byte from the reader_fd, and now confirm that that fifo
+ * becomes unreadable.
+ */
+ len = read(reader_fd, &ch, sizeof(ch));
+ if (len < 0) {
+ warn("%s: read", __func__);
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (assert_status(reader_fd, kqueue_fd, NOT_READABLE, NOT_WRITABLE,
+ NOT_EXCEPTION, __func__, "write+read", "reader_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * The writer_fd should remain writable.
+ */
+ if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, WRITABLE,
+ NOT_EXCEPTION, __func__, "write+read", "writer_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+}
+
+/*
+ * Write a 512k buffer to the fifo in non-blocking mode, and make sure that
+ * the write end becomes un-writable as a result of a partial write that
+ * fills the fifo buffer.
+ */
+static void
+test_events_partial_write(void)
+{
+ int kqueue_fd, reader_fd, writer_fd;
+ u_char *buffer;
+ ssize_t len;
+
+ makefifo("testfifo", __func__);
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("test_events_partial_write: openfifo: testfifo");
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ kqueue_fd = kqueue();
+ if (kqueue_fd < 0) {
+ warn("%s: kqueue", __func__);
+ cleanfifo2("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (kqueue_setup(kqueue_fd, reader_fd, __func__) < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (kqueue_setup(kqueue_fd, writer_fd, __func__) < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (set_nonblocking(writer_fd, "test_events") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ buffer = malloc(512*1024);
+ if (buffer == NULL) {
+ warn("test_events_partial_write: malloc");
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+ bzero(buffer, 512*1024);
+
+ len = write(writer_fd, buffer, 512*1024);
+ if (len < 0) {
+ warn("test_events_partial_write: write");
+ free(buffer);
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ free(buffer);
+
+ if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, NOT_WRITABLE,
+ NOT_EXCEPTION, __func__, "big write", "writer_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (drain_fd(reader_fd, "test_events") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * Test that the writer_fd has been restored to writable state after
+ * draining.
+ */
+ if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, WRITABLE,
+ NOT_EXCEPTION, __func__, "big write + drain", "writer_fd") < 0) {
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+ exit(-1);
+ }
+
+ cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd);
+}
+
+/*
+ * We don't comprehensively test O_RDWR file descriptors, but do run a couple
+ * of event tests to make sure that the fifo implementation doesn't mixed up
+ * status checks. In particular, at least one past FreeBSD bug exists in
+ * which the FIONREAD test was performed on the wrong socket implementing the
+ * fifo, resulting in the fifo never returning readable.
+ */
+static void
+test_events_rdwr(void)
+{
+ int fd, kqueue_fd;
+ ssize_t len;
+ char ch;
+
+ makefifo("testfifo", __func__);
+ if (openfifo_rw("testfifo", &fd) < 0) {
+ warn("%s: openfifo_rw: testfifo", __func__);
+ cleanfifo2("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ kqueue_fd = kqueue();
+ if (kqueue_fd < 0) {
+ warn("%s: kqueue", __func__);
+ cleanfifo2("testifo", fd, -1);
+ exit(-1);
+ }
+
+ if (kqueue_setup(kqueue_fd, fd, __func__) < 0) {
+ cleanfifo2("testfifo", fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * On first creation, the O_RDWR descriptor should be writable but
+ * not readable.
+ */
+ if (assert_status(fd, kqueue_fd, NOT_READABLE, WRITABLE,
+ NOT_EXCEPTION, __func__, "create", "fd") < 0) {
+ cleanfifo2("testfifo", fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * Write a byte, which should cause the file descriptor to become
+ * readable and writable.
+ */
+ ch = 0x00;
+ len = write(fd, &ch, sizeof(ch));
+ if (len < 0) {
+ warn("%s: write", __func__);
+ cleanfifo2("testfifo", fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (assert_status(fd, kqueue_fd, READABLE, WRITABLE, NOT_EXCEPTION,
+ __func__, "write", "fd") < 0) {
+ cleanfifo2("testfifo", fd, kqueue_fd);
+ exit(-1);
+ }
+
+ /*
+ * Read a byte, which should cause the file descriptor to return to
+ * simply being writable.
+ */
+ len = read(fd, &ch, sizeof(ch));
+ if (len < 0) {
+ warn("%s: read", __func__);
+ cleanfifo2("testfifo", fd, kqueue_fd);
+ exit(-1);
+ }
+
+ if (assert_status(fd, kqueue_fd, NOT_READABLE, WRITABLE,
+ NOT_EXCEPTION, __func__, "write+read", "fd") < 0) {
+ cleanfifo2("testfifo", fd, kqueue_fd);
+ exit(-1);
+ }
+
+ cleanfifo2("testfifo", fd, kqueue_fd);
+}
+
+int
+main(void)
+{
+
+ strcpy(temp_dir, "fifo_io.XXXXXXXXXXX");
+ if (mkdtemp(temp_dir) == NULL)
+ err(-1, "mkdtemp");
+ atexit(atexit_temp_dir);
+
+ if (chdir(temp_dir) < 0)
+ err(-1, "chdir %s", temp_dir);
+
+ test_simpleio();
+ test_blocking_read_empty();
+ test_blocking_one_byte();
+ test_nonblocking_one_byte();
+ test_blocking_partial_write();
+ test_nonblocking_partial_write();
+ test_coalesce_big_read();
+ test_coalesce_big_write();
+ test_events_outofbox();
+ test_events_write_read_byte();
+ test_events_partial_write();
+ test_events_rdwr();
+
+ return (0);
+}
diff --git a/tests/sys/fifo/fifo_misc.c b/tests/sys/fifo/fifo_misc.c
new file mode 100644
index 000000000000..2c834b127e63
--- /dev/null
+++ b/tests/sys/fifo/fifo_misc.c
@@ -0,0 +1,336 @@
+/*-
+ * Copyright (c) 2005 Robert N. M. Watson
+ * Copyright (c) 2012 Jilles Tjoelker
+ * 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 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 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$
+ */
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/filio.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Regression test for piddling details of fifos.
+ */
+
+/*
+ * All activity occurs within a temporary directory created early in the
+ * test.
+ */
+static char temp_dir[PATH_MAX];
+
+static void __unused
+atexit_temp_dir(void)
+{
+
+ rmdir(temp_dir);
+}
+
+static void
+makefifo(const char *fifoname, const char *testname)
+{
+
+ if (mkfifo(fifoname, 0700) < 0)
+ err(-1, "%s: makefifo: mkfifo: %s", testname, fifoname);
+}
+
+static void
+cleanfifo(const char *fifoname, int fd1, int fd2)
+{
+
+ if (fd1 != -1)
+ close(fd1);
+ if (fd2 != -1)
+ close(fd2);
+ (void)unlink(fifoname);
+}
+
+static int
+openfifo(const char *fifoname, int *reader_fdp, int *writer_fdp)
+{
+ int error, fd1, fd2;
+
+ fd1 = open(fifoname, O_RDONLY | O_NONBLOCK);
+ if (fd1 < 0)
+ return (-1);
+ fd2 = open(fifoname, O_WRONLY | O_NONBLOCK);
+ if (fd2 < 0) {
+ error = errno;
+ close(fd1);
+ errno = error;
+ return (-1);
+ }
+ *reader_fdp = fd1;
+ *writer_fdp = fd2;
+
+ return (0);
+}
+
+/*
+ * POSIX does not allow lseek(2) on fifos, so we expect ESPIPE as a result.
+ */
+static void
+test_lseek(void)
+{
+ int reader_fd, writer_fd;
+
+ makefifo("testfifo", __func__);
+
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("%s: openfifo", __func__);
+ cleanfifo("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ if (lseek(reader_fd, 1, SEEK_CUR) >= 0) {
+ warnx("%s: lseek succeeded instead of returning ESPIPE",
+ __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (errno != ESPIPE) {
+ warn("%s: lseek returned instead of ESPIPE", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo("testfifo", reader_fd, writer_fd);
+}
+
+/*
+ * truncate(2) on FIFO should silently return success.
+ */
+static void
+test_truncate(void)
+{
+
+ makefifo("testfifo", __func__);
+
+ if (truncate("testfifo", 1024) != 0) {
+ warn("%s: truncate", __func__);
+ cleanfifo("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ cleanfifo("testfifo", -1, -1);
+}
+
+static int
+test_ioctl_setclearflag(int fd, unsigned long flag, const char *testname,
+ const char *fdname, const char *flagname)
+{
+ int i;
+
+ i = 1;
+ if (ioctl(fd, flag, &i) < 0) {
+ warn("%s:%s: ioctl(%s, %s, 1)", testname, __func__, fdname,
+ flagname);
+ cleanfifo("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ i = 0;
+ if (ioctl(fd, flag, &i) < 0) {
+ warn("%s:%s: ioctl(%s, %s, 0)", testname, __func__, fdname,
+ flagname);
+ cleanfifo("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Test that various ioctls can be issued against the file descriptor. We
+ * don't currently test the semantics of these changes here.
+ */
+static void
+test_ioctl(void)
+{
+ int reader_fd, writer_fd;
+
+ makefifo("testfifo", __func__);
+
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("%s: openfifo", __func__);
+ cleanfifo("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ /*
+ * Set and remove the non-blocking I/O flag.
+ */
+ if (test_ioctl_setclearflag(reader_fd, FIONBIO, __func__,
+ "reader_fd", "FIONBIO") < 0) {
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (test_ioctl_setclearflag(writer_fd, FIONBIO, __func__,
+ "writer_fd", "FIONBIO") < 0) {
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ /*
+ * Set and remove the async I/O flag.
+ */
+ if (test_ioctl_setclearflag(reader_fd, FIOASYNC, __func__,
+ "reader_fd", "FIOASYNC") < 0) {
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (test_ioctl_setclearflag(writer_fd, FIOASYNC, __func__,
+ "writer_fd", "FIONASYNC") < 0) {
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo("testfifo", reader_fd, writer_fd);
+}
+
+/*
+ * fchmod(2)/fchown(2) on FIFO should work.
+ */
+static void
+test_chmodchown(void)
+{
+ struct stat sb;
+ int reader_fd, writer_fd;
+ uid_t u;
+ gid_t g;
+
+ makefifo("testfifo", __func__);
+
+ if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) {
+ warn("%s: openfifo", __func__);
+ cleanfifo("testfifo", -1, -1);
+ exit(-1);
+ }
+
+ if (fchmod(reader_fd, 0666) != 0) {
+ warn("%s: fchmod", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (stat("testfifo", &sb) != 0) {
+ warn("%s: stat", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if ((sb.st_mode & 0777) != 0666) {
+ warnx("%s: stat chmod result", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (fstat(writer_fd, &sb) != 0) {
+ warn("%s: fstat", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if ((sb.st_mode & 0777) != 0666) {
+ warnx("%s: fstat chmod result", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (fchown(reader_fd, -1, -1) != 0) {
+ warn("%s: fchown 1", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ u = geteuid();
+ if (u == 0)
+ u = 1;
+ g = getegid();
+ if (fchown(reader_fd, u, g) != 0) {
+ warn("%s: fchown 2", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+ if (stat("testfifo", &sb) != 0) {
+ warn("%s: stat", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (sb.st_uid != u || sb.st_gid != g) {
+ warnx("%s: stat chown result", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (fstat(writer_fd, &sb) != 0) {
+ warn("%s: fstat", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ if (sb.st_uid != u || sb.st_gid != g) {
+ warnx("%s: fstat chown result", __func__);
+ cleanfifo("testfifo", reader_fd, writer_fd);
+ exit(-1);
+ }
+
+ cleanfifo("testfifo", -1, -1);
+}
+
+int
+main(void)
+{
+
+ strcpy(temp_dir, "fifo_misc.XXXXXXXXXXX");
+ if (mkdtemp(temp_dir) == NULL)
+ err(-1, "mkdtemp");
+ atexit(atexit_temp_dir);
+
+ if (chdir(temp_dir) < 0)
+ err(-1, "chdir %s", temp_dir);
+
+ test_lseek();
+ test_truncate();
+ test_ioctl();
+ test_chmodchown();
+
+ return (0);
+}
diff --git a/tests/sys/fifo/fifo_open.c b/tests/sys/fifo/fifo_open.c
new file mode 100644
index 000000000000..892f481b170b
--- /dev/null
+++ b/tests/sys/fifo/fifo_open.c
@@ -0,0 +1,476 @@
+/*-
+ * Copyright (c) 2005 Robert N. M. Watson
+ * 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 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 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$
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Regression test to exercise various POSIX-defined parts of fifo behavior
+ * described for open(2):
+ *
+ * O_NONBLOCK
+ * When opening a FIFO with O_RDONLY or O_WRONLY set:
+ *
+ * - If O_NONBLOCK is set, an open() for reading-only shall return without
+ * delay. An open() for writing-only shall return an error if no process
+ * currently has the file open for reading.
+ *
+ * - If O_NONBLOCK is clear, an open() for reading-only shall block the
+ * calling thread until a thread opens the file for writing. An open()
+ * for writing-only shall block the calling thread until a thread opens
+ * the file for reading.
+ *
+ * When opening a block special or character special file that supports
+ * non-blocking opens:
+ *
+ * - If O_NONBLOCK is set, the open() function shall return without blocking
+ * for the device to be ready or available. Subsequent behavior of the
+ * device is device-specific.
+ *
+ * - If O_NONBLOCK is clear, the open() function shall block the calling
+ * thread until the device is ready or available before returning.
+ *
+ * Special errors:
+ *
+ * [ENXIO]
+ * O_NONBLOCK is set, the named file is a FIFO, O_WRONLY is set, and no
+ * process has the file open for reading.
+ */
+
+/*
+ * In order to test blocking/non-blocking behavior, test processes must
+ * potentially block themselves until released. As bugs in blocking result
+ * in processes that won't un-block, we must sacrifice a process to the task,
+ * watching and potentially killing it after a time-out. The main test
+ * process is never used to open or act directly on a fifo (other than to
+ * create or unlink it) in order to avoid the main test process being
+ * blocked.
+ */
+
+/*
+ * All activity occurs within a temporary directory created early in the
+ * test.
+ */
+static char temp_dir[PATH_MAX];
+
+static void __unused
+atexit_temp_dir(void)
+{
+
+ rmdir(temp_dir);
+}
+
+/*
+ * Run a function in a particular test process.
+ */
+static int
+run_in_process(int (*func)(void), pid_t *pidp, const char *errstr)
+{
+ pid_t pid;
+
+ pid = fork();
+ if (pid < 0) {
+ warn("%s: run_in_process: fork", errstr);
+ return (-1);
+ }
+
+ if (pid == 0)
+ exit(func());
+
+ if (pidp != NULL)
+ *pidp = pid;
+
+ return (0);
+}
+
+/*
+ * Wait for a process on a timeout, and if the timeout expires, kill the
+ * process. Test each second rather than waiting the full timeout at once to
+ * minimize the amount of time spent hanging around unnecessarily.
+ */
+static int
+wait_and_timeout(pid_t pid, int timeout, int *status, const char *errstr)
+{
+ pid_t wpid;
+ int i;
+
+ /*
+ * Count up to the timeout, but do a non-hanging waitpid() after each
+ * second so we can avoid waiting a lot of extra time.
+ */
+ for (i = 0; i < timeout; i++) {
+ wpid = waitpid(pid, status, WNOHANG);
+ if (wpid < 0) {
+ warn("%s: wait_and_timeout: waitpid %d", errstr, pid);
+ return (-1);
+ }
+
+ if (wpid == pid)
+ return (0);
+
+ sleep(1);
+ }
+
+ wpid = waitpid(pid, status, WNOHANG);
+ if (wpid < 0) {
+ warn("%s: wait_and_timeout: waitpid %d", errstr, pid);
+ return (-1);
+ }
+
+ if (wpid == pid)
+ return (0);
+
+ if (kill(pid, SIGTERM) < 0) {
+ warn("%s: wait_and_timeout: kill %d", errstr, pid);
+ return (-1);
+ }
+
+ wpid = waitpid(pid, status, 0);
+ if (wpid < 0) {
+ warn("%s: wait_and_timeout: waitpid %d", errstr, pid);
+ return (-1);
+ }
+
+ if (wpid != pid) {
+ warn("%s: waitpid: returned %d not %d", errstr, wpid, pid);
+ return (-1);
+ }
+
+ warnx("%s: process blocked", errstr);
+ return (-1);
+}
+
+static int
+non_blocking_open_reader(void)
+{
+ int fd;
+
+ fd = open("testfifo", O_RDONLY | O_NONBLOCK);
+ if (fd < 0)
+ return (errno);
+ close(fd);
+
+ return (0);
+}
+
+static int
+non_blocking_open_writer(void)
+{
+ int fd;
+
+ fd = open("testfifo", O_WRONLY | O_NONBLOCK);
+ if (fd < 0)
+ return (errno);
+ close(fd);
+
+ return (0);
+}
+
+static int
+blocking_open_reader(void)
+{
+ int fd;
+
+ fd = open("testfifo", O_RDONLY);
+ if (fd < 0)
+ return (errno);
+ close(fd);
+
+ return (0);
+}
+
+static int
+blocking_open_writer(void)
+{
+ int fd;
+
+ fd = open("testfifo", O_WRONLY);
+ if (fd < 0)
+ return (errno);
+ close(fd);
+
+ return (0);
+}
+
+static void
+test_blocking_reader(void)
+{
+ pid_t reader_pid, writer_pid, wpid;
+ int error, status;
+
+ if (mkfifo("testfifo", 0600) < 0)
+ err(-1, "test_blocking_reader: mkfifo: testfifo");
+
+ /*
+ * Block a process in opening the fifo.
+ */
+ if (run_in_process(blocking_open_reader, &reader_pid,
+ "test_blocking_reader: blocking_open_reader") < 0) {
+ (void)unlink("testfifo");
+ exit(-1);
+ }
+
+ /*
+ * Test that it blocked.
+ */
+ sleep(5);
+ wpid = waitpid(reader_pid, &status, WNOHANG);
+ if (wpid < 0) {
+ error = errno;
+ (void)unlink("testfifo");
+ errno = error;
+ err(-1, "test_blocking_reader: waitpid %d", reader_pid);
+ }
+
+ if (wpid != 0 && wpid != reader_pid) {
+ (void)unlink("testfifo");
+ errx(-1, "test_blocking_reader: waitpid %d returned %d",
+ reader_pid, wpid);
+ }
+
+ if (wpid == reader_pid) {
+ (void)unlink("testfifo");
+ errx(-1, "test_blocking_reader: blocking child didn't "
+ "block");
+ }
+
+ /*
+ * Unblock the blocking reader.
+ */
+ if (run_in_process(blocking_open_writer, &writer_pid,
+ "test_blocking_reader: blocking_open_writer") < 0) {
+ (void)unlink("testfifo");
+ (void)kill(reader_pid, SIGTERM);
+ (void)waitpid(reader_pid, &status, 0);
+ exit(-1);
+ }
+
+ /*
+ * Make sure both processes exited quickly (<1 second) to make sure
+ * they didn't block, and GC.
+ */
+ if (wait_and_timeout(reader_pid, 1, &status,
+ "test_blocking_reader: blocking_open_reader") < 0) {
+ (void)unlink("testinfo");
+ (void)kill(reader_pid, SIGTERM);
+ (void)kill(writer_pid, SIGTERM);
+ exit(-1);
+ }
+
+ if (wait_and_timeout(writer_pid, 1, &status,
+ "test_blocking_reader: blocking_open_writer") < 0) {
+ (void)unlink("testinfo");
+ (void)kill(writer_pid, SIGTERM);
+ exit(-1);
+ }
+
+ if (unlink("testfifo") < 0)
+ err(-1, "test_blocking_reader: unlink: testfifo");
+}
+static void
+test_blocking_writer(void)
+{
+ pid_t reader_pid, writer_pid, wpid;
+ int error, status;
+
+ if (mkfifo("testfifo", 0600) < 0)
+ err(-1, "test_blocking_writer: mkfifo: testfifo");
+
+ /*
+ * Block a process in opening the fifo.
+ */
+ if (run_in_process(blocking_open_writer, &writer_pid,
+ "test_blocking_writer: blocking_open_writer") < 0) {
+ (void)unlink("testfifo");
+ exit(-1);
+ }
+
+ /*
+ * Test that it blocked.
+ */
+ sleep(5);
+ wpid = waitpid(writer_pid, &status, WNOHANG);
+ if (wpid < 0) {
+ error = errno;
+ (void)unlink("testfifo");
+ errno = error;
+ err(-1, "test_blocking_writer: waitpid %d", writer_pid);
+ }
+
+ if (wpid != 0 && wpid != writer_pid) {
+ (void)unlink("testfifo");
+ errx(-1, "test_blocking_writer: waitpid %d returned %d",
+ writer_pid, wpid);
+ }
+
+ if (wpid == writer_pid) {
+ (void)unlink("testfifo");
+ errx(-1, "test_blocking_writer: blocking child didn't "
+ "block");
+ }
+
+ /*
+ * Unblock the blocking writer.
+ */
+ if (run_in_process(blocking_open_reader, &reader_pid,
+ "test_blocking_writer: blocking_open_reader") < 0) {
+ (void)unlink("testfifo");
+ (void)kill(writer_pid, SIGTERM);
+ (void)waitpid(writer_pid, &status, 0);
+ exit(-1);
+ }
+
+ /*
+ * Make sure both processes exited quickly (<1 second) to make sure
+ * they didn't block, and GC.
+ */
+ if (wait_and_timeout(writer_pid, 1, &status,
+ "test_blocking_writer: blocking_open_writer") < 0) {
+ (void)unlink("testinfo");
+ (void)kill(writer_pid, SIGTERM);
+ (void)kill(reader_pid, SIGTERM);
+ (void)waitpid(writer_pid, &status, 0);
+ (void)waitpid(reader_pid, &status, 0);
+ exit(-1);
+ }
+
+ if (wait_and_timeout(reader_pid, 1, &status,
+ "test_blocking_writer: blocking_open_reader") < 0) {
+ (void)unlink("testinfo");
+ (void)kill(reader_pid, SIGTERM);
+ (void)waitpid(reader_pid, &status, 0);
+ exit(-1);
+ }
+
+ if (unlink("testfifo") < 0)
+ err(-1, "test_blocking_writer: unlink: testfifo");
+}
+
+static void
+test_non_blocking_reader(void)
+{
+ int status;
+ pid_t pid;
+
+ if (mkfifo("testfifo", 0600) < 0)
+ err(-1, "test_non_blocking_reader: mkfifo: testfifo");
+
+ if (run_in_process(non_blocking_open_reader, &pid,
+ "test_non_blocking_reader: non_blocking_open_reader") < 0) {
+ (void)unlink("testfifo");
+ exit(-1);
+ }
+
+ status = -1;
+ if (wait_and_timeout(pid, 5, &status,
+ "test_non_blocking_reader: non_blocking_open_reader") < 0) {
+ (void)unlink("testfifo");
+ exit(-1);
+ }
+
+ if (WEXITSTATUS(status) != 0) {
+ (void)unlink("testfifo");
+ errno = WEXITSTATUS(status);
+ err(-1, "test_non_blocking_reader: "
+ "non_blocking_open_reader: open: testfifo");
+ }
+
+ if (unlink("testfifo") < 0)
+ err(-1, "test_non_blocking_reader: unlink: testfifo");
+}
+
+static void
+test_non_blocking_writer(void)
+{
+ int status;
+ pid_t pid;
+
+ if (mkfifo("testfifo", 0600) < 0)
+ err(-1, "test_non_blocking_writer: mkfifo: testfifo");
+
+ if (run_in_process(non_blocking_open_writer, &pid,
+ "test_non_blocking_writer: non_blocking_open_writer") < 0) {
+ (void)unlink("testfifo");
+ exit(-1);
+ }
+
+ status = -1;
+ if (wait_and_timeout(pid, 5, &status,
+ "test_non_blocking_writer: non_blocking_open_writer") < 0) {
+ (void)unlink("testfifo");
+ exit(-1);
+ }
+
+ if (WEXITSTATUS(status) != ENXIO) {
+ (void)unlink("testfifo");
+
+ errno = WEXITSTATUS(status);
+ if (errno == 0)
+ errx(-1, "test_non_blocking_writer: "
+ "non_blocking_open_writer: open succeeded");
+ err(-1, "test_non_blocking_writer: "
+ "non_blocking_open_writer: open: testfifo");
+ }
+
+ if (unlink("testfifo") < 0)
+ err(-1, "test_non_blocking_writer: unlink: testfifo");
+}
+
+int
+main(void)
+{
+
+ if (geteuid() != 0)
+ errx(-1, "must be run as root");
+
+ strcpy(temp_dir, "fifo_open.XXXXXXXXXXX");
+ if (mkdtemp(temp_dir) == NULL)
+ err(-1, "mkdtemp");
+ if (chdir(temp_dir) < 0)
+ err(-1, "chdir: %s", temp_dir);
+ atexit(atexit_temp_dir);
+
+ test_non_blocking_reader();
+ test_non_blocking_writer();
+
+ test_blocking_reader();
+ test_blocking_writer();
+
+ return (0);
+}
diff --git a/tests/sys/file/Makefile b/tests/sys/file/Makefile
new file mode 100644
index 000000000000..ed2d2c4fbe2e
--- /dev/null
+++ b/tests/sys/file/Makefile
@@ -0,0 +1,22 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/file
+
+BINDIR= ${TESTSDIR}
+
+TAP_TESTS_C+= closefrom_test
+TAP_TESTS_C+= dup_test
+TAP_TESTS_C+= fcntlflags_test
+TAP_TESTS_SH+= flock_test
+PLAIN_TESTS_C+= ftruncate_test
+PLAIN_TESTS_C+= newfileops_on_fork_test
+
+PROGS+= flock_helper
+
+LIBADD.closefrom_test= util
+
+LIBADD.flock_helper= pthread
+
+LIBADD.newfileops_on_fork_test= pthread
+
+.include <bsd.test.mk>
diff --git a/tests/sys/file/Makefile.depend b/tests/sys/file/Makefile.depend
new file mode 100644
index 000000000000..1c2cb193ad93
--- /dev/null
+++ b/tests/sys/file/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libthr \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/file/closefrom_test.c b/tests/sys/file/closefrom_test.c
new file mode 100644
index 000000000000..78cfeecae026
--- /dev/null
+++ b/tests/sys/file/closefrom_test.c
@@ -0,0 +1,275 @@
+/*-
+ * Copyright (c) 2009 Hudson River Trading LLC
+ * Written by: John H. Baldwin <jhb@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Regression tests for the closefrom(2) system call.
+ */
+
+#include <sys/param.h>
+#include <sys/mman.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libutil.h>
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct shared_info {
+ int failed;
+ char tag[64];
+ char message[0];
+};
+
+static int test = 1;
+
+static void
+ok(const char *descr)
+{
+
+ printf("ok %d - %s\n", test, descr);
+ test++;
+}
+
+static void
+fail(const char *descr, const char *fmt, ...)
+{
+ va_list ap;
+
+ printf("not ok %d - %s", test, descr);
+ test++;
+ if (fmt) {
+ va_start(ap, fmt);
+ printf(" # ");
+ vprintf(fmt, ap);
+ va_end(ap);
+ }
+ printf("\n");
+ exit(1);
+}
+
+#define fail_err(descr) fail((descr), "%s", strerror(errno))
+
+static void
+cok(struct shared_info *info, const char *descr)
+{
+
+ info->failed = 0;
+ strlcpy(info->tag, descr, sizeof(info->tag));
+ exit(0);
+}
+
+static void
+cfail(struct shared_info *info, const char *descr, const char *fmt, ...)
+{
+ va_list ap;
+
+ info->failed = 1;
+ strlcpy(info->tag, descr, sizeof(info->tag));
+ if (fmt) {
+ va_start(ap, fmt);
+ vsprintf(info->message, fmt, ap);
+ va_end(ap);
+ }
+ exit(0);
+}
+
+#define cfail_err(info, descr) cfail((info), (descr), "%s", strerror(errno))
+
+/*
+ * Use kinfo_getfile() to fetch the list of file descriptors and figure out
+ * the highest open file descriptor.
+ */
+static int
+highest_fd(void)
+{
+ struct kinfo_file *kif;
+ int cnt, i, highest;
+
+ kif = kinfo_getfile(getpid(), &cnt);
+ if (kif == NULL)
+ fail_err("kinfo_getfile");
+ highest = INT_MIN;
+ for (i = 0; i < cnt; i++)
+ if (kif[i].kf_fd > highest)
+ highest = kif[i].kf_fd;
+ free(kif);
+ return (highest);
+}
+
+static int
+devnull(void)
+{
+ int fd;
+
+ fd = open(_PATH_DEVNULL, O_RDONLY);
+ if (fd < 0)
+ fail_err("open(\" "_PATH_DEVNULL" \")");
+ return (fd);
+}
+
+int
+main(void)
+{
+ struct shared_info *info;
+ pid_t pid;
+ int fd, i, start;
+
+ printf("1..15\n");
+
+ /* We better start up with fd's 0, 1, and 2 open. */
+ start = devnull();
+ if (start == -1)
+ fail("open", "bad descriptor %d", start);
+ ok("open");
+
+ /* Make sure highest_fd() works. */
+ fd = highest_fd();
+ if (start != fd)
+ fail("highest_fd", "bad descriptor %d != %d", start, fd);
+ ok("highest_fd");
+
+ /* Try to use closefrom() for just closing fd 3. */
+ closefrom(start + 1);
+ fd = highest_fd();
+ if (fd != start)
+ fail("closefrom", "highest fd %d", fd);
+ ok("closefrom");
+
+ /* Eat up 16 descriptors. */
+ for (i = 0; i < 16; i++)
+ (void)devnull();
+ fd = highest_fd();
+ if (fd != start + 16)
+ fail("open 16", "highest fd %d", fd);
+ ok("open 16");
+
+ /* Close half of them. */
+ closefrom(11);
+ fd = highest_fd();
+ if (fd != 10)
+ fail("closefrom", "highest fd %d", fd);
+ ok("closefrom");
+
+ /* Explicitly close descriptors 6 and 8 to create holes. */
+ if (close(6) < 0 || close(8) < 0)
+ fail_err("close2 ");
+ ok("close 2");
+
+ /* Verify that close on 6 and 8 fails with EBADF. */
+ if (close(6) == 0)
+ fail("close(6)", "did not fail");
+ if (errno != EBADF)
+ fail_err("close(6)");
+ ok("close(6)");
+ if (close(8) == 0)
+ fail("close(8)", "did not fail");
+ if (errno != EBADF)
+ fail_err("close(8)");
+ ok("close(8)");
+
+ /* Close from 4 on. */
+ closefrom(4);
+ fd = highest_fd();
+ if (fd != 3)
+ fail("closefrom", "highest fd %d", fd);
+ ok("closefrom");
+
+ /* Allocate a small SHM region for IPC with our child. */
+ info = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_ANON |
+ MAP_SHARED, -1, 0);
+ if (info == MAP_FAILED)
+ fail_err("mmap");
+ ok("mmap");
+
+ /* Fork a child process to test closefrom(0). */
+ pid = fork();
+ if (pid < 0)
+ fail_err("fork");
+ if (pid == 0) {
+ /* Child. */
+ closefrom(0);
+ fd = highest_fd();
+ if (fd >= 0)
+ cfail(info, "closefrom(0)", "highest fd %d", fd);
+ cok(info, "closefrom(0)");
+ }
+ if (wait(NULL) < 0)
+ fail_err("wait");
+ if (info->failed)
+ fail(info->tag, "%s", info->message);
+ ok(info->tag);
+
+ /* Fork a child process to test closefrom(-1). */
+ pid = fork();
+ if (pid < 0)
+ fail_err("fork");
+ if (pid == 0) {
+ /* Child. */
+ closefrom(-1);
+ fd = highest_fd();
+ if (fd >= 0)
+ cfail(info, "closefrom(-1)", "highest fd %d", fd);
+ cok(info, "closefrom(-1)");
+ }
+ if (wait(NULL) < 0)
+ fail_err("wait");
+ if (info->failed)
+ fail(info->tag, "%s", info->message);
+ ok(info->tag);
+
+ /* Dup stdout to 6. */
+ if (dup2(1, 6) < 0)
+ fail_err("dup2");
+ fd = highest_fd();
+ if (fd != 6)
+ fail("dup2", "highest fd %d", fd);
+ ok("dup2");
+
+ /* Do a closefrom() starting in a hole. */
+ closefrom(4);
+ fd = highest_fd();
+ if (fd != 3)
+ fail("closefrom", "highest fd %d", fd);
+ ok("closefrom");
+
+ /* Do a closefrom() beyond our highest open fd. */
+ closefrom(32);
+ fd = highest_fd();
+ if (fd != 3)
+ fail("closefrom", "highest fd %d", fd);
+ ok("closefrom");
+
+ return (0);
+}
diff --git a/tests/sys/file/dup_test.c b/tests/sys/file/dup_test.c
new file mode 100644
index 000000000000..817381889625
--- /dev/null
+++ b/tests/sys/file/dup_test.c
@@ -0,0 +1,386 @@
+/*
+ * $OpenBSD: dup2test.c,v 1.3 2003/07/31 21:48:08 deraadt Exp $
+ * $OpenBSD: dup2_self.c,v 1.3 2003/07/31 21:48:08 deraadt Exp $
+ * $OpenBSD: fcntl_dup.c,v 1.2 2003/07/31 21:48:08 deraadt Exp $
+ *
+ * Written by Artur Grabowski <art@openbsd.org> 2002 Public Domain.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * Test #1: check if dup(2) works.
+ * Test #2: check if dup2(2) works.
+ * Test #3: check if dup2(2) returned a fd we asked for.
+ * Test #4: check if dup2(2) cleared close-on-exec flag for duped fd.
+ * Test #5: check if dup2(2) allows to dup fd to itself.
+ * Test #6: check if dup2(2) returned a fd we asked for.
+ * Test #7: check if dup2(2) did not clear close-on-exec flag for duped fd.
+ * Test #8: check if fcntl(F_DUPFD) works.
+ * Test #9: check if fcntl(F_DUPFD) cleared close-on-exec flag for duped fd.
+ * Test #10: check if dup2() to a fd > current maximum number of open files
+ * limit work.
+ * Test #11: check if fcntl(F_DUP2FD) works.
+ * Test #12: check if fcntl(F_DUP2FD) returned a fd we asked for.
+ * Test #13: check if fcntl(F_DUP2FD) cleared close-on-exec flag for duped fd.
+ * Test #14: check if fcntl(F_DUP2FD) allows to dup fd to itself.
+ * Test #15: check if fcntl(F_DUP2FD) returned a fd we asked for.
+ * Test #16: check if fcntl(F_DUP2FD) did not clear close-on-exec flag for
+ * duped fd.
+ * Test #17: check if fcntl(F_DUP2FD) to a fd > current maximum number of open
+ * files limit work.
+ * Test #18: check if fcntl(F_DUPFD_CLOEXEC) works.
+ * Test #19: check if fcntl(F_DUPFD_CLOEXEC) set close-on-exec flag for duped
+ * fd.
+ * Test #20: check if fcntl(F_DUP2FD_CLOEXEC) works.
+ * Test #21: check if fcntl(F_DUP2FD_CLOEXEC) returned a fd we asked for.
+ * Test #22: check if fcntl(F_DUP2FD_CLOEXEC) set close-on-exec flag for duped
+ * fd.
+ * Test #23: check if fcntl(F_DUP2FD_CLOEXEC) to a fd > current maximum number
+ * of open files limit work.
+ * Test #24: check if dup3(O_CLOEXEC) works.
+ * Test #25: check if dup3(O_CLOEXEC) returned a fd we asked for.
+ * Test #26: check if dup3(O_CLOEXEC) set close-on-exec flag for duped fd.
+ * Test #27: check if dup3(0) works.
+ * Test #28: check if dup3(0) returned a fd we asked for.
+ * Test #29: check if dup3(0) cleared close-on-exec flag for duped fd.
+ * Test #30: check if dup3(O_CLOEXEC) fails if oldfd == newfd.
+ * Test #31: check if dup3(0) fails if oldfd == newfd.
+ * Test #32: check if dup3(O_CLOEXEC) to a fd > current maximum number of
+ * open files limit work.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static int getafile(void);
+
+static int
+getafile(void)
+{
+ int fd;
+
+ char temp[] = "/tmp/dup2XXXXXXXXX";
+ if ((fd = mkstemp(temp)) < 0)
+ err(1, "mkstemp");
+ remove(temp);
+ if (ftruncate(fd, 1024) != 0)
+ err(1, "ftruncate");
+ return (fd);
+}
+
+int
+main(int __unused argc, char __unused *argv[])
+{
+ struct rlimit rlp;
+ int orgfd, fd1, fd2, test = 0;
+
+ orgfd = getafile();
+
+ printf("1..32\n");
+
+ /* If dup(2) ever work? */
+ if ((fd1 = dup(orgfd)) < 0)
+ err(1, "dup");
+ printf("ok %d - dup(2) works\n", ++test);
+
+ /* Set close-on-exec */
+ if (fcntl(fd1, F_SETFD, 1) != 0)
+ err(1, "fcntl(F_SETFD)");
+
+ /* If dup2(2) ever work? */
+ if ((fd2 = dup2(fd1, fd1 + 1)) < 0)
+ err(1, "dup2");
+ printf("ok %d - dup2(2) works\n", ++test);
+
+ /* Do we get the right fd? */
+ ++test;
+ if (fd2 != fd1 + 1)
+ printf("no ok %d - dup2(2) didn't give us the right fd\n",
+ test);
+ else
+ printf("ok %d - dup2(2) returned a correct fd\n", test);
+
+ /* Was close-on-exec cleared? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) != 0)
+ printf("not ok %d - dup2(2) didn't clear close-on-exec\n",
+ test);
+ else
+ printf("ok %d - dup2(2) cleared close-on-exec\n", test);
+
+ /*
+ * Dup to itself.
+ *
+ * We're testing a small tweak in dup2 semantics.
+ * Normally dup and dup2 will clear the close-on-exec
+ * flag on the new fd (which appears to be an implementation
+ * mistake from start and not some planned behavior).
+ * In today's implementations of dup and dup2 we have to make
+ * an effort to really clear that flag. But all tested
+ * implementations of dup2 have another tweak. If we
+ * dup2(old, new) when old == new, the syscall short-circuits
+ * and returns early (because there is no need to do all the
+ * work (and there is a risk for serious mistakes)).
+ * So although the docs say that dup2 should "take 'old',
+ * close 'new' perform a dup(2) of 'old' into 'new'"
+ * the docs are not really followed because close-on-exec
+ * is not cleared on 'new'.
+ *
+ * Since everyone has this bug, we pretend that this is
+ * the way it is supposed to be and test here that it really
+ * works that way.
+ *
+ * This is a fine example on where two separate implementation
+ * fuckups take out each other and make the end-result the way
+ * it was meant to be.
+ */
+ if ((fd2 = dup2(fd1, fd1)) < 0)
+ err(1, "dup2");
+ printf("ok %d - dup2(2) to itself works\n", ++test);
+
+ /* Do we get the right fd? */
+ ++test;
+ if (fd2 != fd1)
+ printf("not ok %d - dup2(2) didn't give us the right fd\n",
+ test);
+ else
+ printf("ok %d - dup2(2) to itself returned a correct fd\n",
+ test);
+
+ /* Was close-on-exec cleared? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) == 0)
+ printf("not ok %d - dup2(2) cleared close-on-exec\n", test);
+ else
+ printf("ok %d - dup2(2) didn't clear close-on-exec\n", test);
+
+ /* Does fcntl(F_DUPFD) work? */
+ if ((fd2 = fcntl(fd1, F_DUPFD, 10)) < 0)
+ err(1, "fcntl(F_DUPFD)");
+ if (fd2 < 10)
+ printf("not ok %d - fcntl(F_DUPFD) returned wrong fd %d\n",
+ ++test, fd2);
+ else
+ printf("ok %d - fcntl(F_DUPFD) works\n", ++test);
+
+ /* Was close-on-exec cleared? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) != 0)
+ printf(
+ "not ok %d - fcntl(F_DUPFD) didn't clear close-on-exec\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUPFD) cleared close-on-exec\n", test);
+
+ ++test;
+ if (getrlimit(RLIMIT_NOFILE, &rlp) < 0)
+ err(1, "getrlimit");
+ if ((fd2 = dup2(fd1, rlp.rlim_cur + 1)) >= 0)
+ printf("not ok %d - dup2(2) bypassed NOFILE limit\n", test);
+ else
+ printf("ok %d - dup2(2) didn't bypass NOFILE limit\n", test);
+
+ /* If fcntl(F_DUP2FD) ever work? */
+ if ((fd2 = fcntl(fd1, F_DUP2FD, fd1 + 1)) < 0)
+ err(1, "fcntl(F_DUP2FD)");
+ printf("ok %d - fcntl(F_DUP2FD) works\n", ++test);
+
+ /* Do we get the right fd? */
+ ++test;
+ if (fd2 != fd1 + 1)
+ printf(
+ "no ok %d - fcntl(F_DUP2FD) didn't give us the right fd\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUP2FD) returned a correct fd\n",
+ test);
+
+ /* Was close-on-exec cleared? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) != 0)
+ printf(
+ "not ok %d - fcntl(F_DUP2FD) didn't clear close-on-exec\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUP2FD) cleared close-on-exec\n",
+ test);
+
+ /* Dup to itself */
+ if ((fd2 = fcntl(fd1, F_DUP2FD, fd1)) < 0)
+ err(1, "fcntl(F_DUP2FD)");
+ printf("ok %d - fcntl(F_DUP2FD) to itself works\n", ++test);
+
+ /* Do we get the right fd? */
+ ++test;
+ if (fd2 != fd1)
+ printf(
+ "not ok %d - fcntl(F_DUP2FD) didn't give us the right fd\n",
+ test);
+ else
+ printf(
+ "ok %d - fcntl(F_DUP2FD) to itself returned a correct fd\n",
+ test);
+
+ /* Was close-on-exec cleared? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) == 0)
+ printf("not ok %d - fcntl(F_DUP2FD) cleared close-on-exec\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUP2FD) didn't clear close-on-exec\n",
+ test);
+
+ ++test;
+ if (getrlimit(RLIMIT_NOFILE, &rlp) < 0)
+ err(1, "getrlimit");
+ if ((fd2 = fcntl(fd1, F_DUP2FD, rlp.rlim_cur + 1)) >= 0)
+ printf("not ok %d - fcntl(F_DUP2FD) bypassed NOFILE limit\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUP2FD) didn't bypass NOFILE limit\n",
+ test);
+
+ /* Does fcntl(F_DUPFD_CLOEXEC) work? */
+ if ((fd2 = fcntl(fd1, F_DUPFD_CLOEXEC, 10)) < 0)
+ err(1, "fcntl(F_DUPFD_CLOEXEC)");
+ if (fd2 < 10)
+ printf("not ok %d - fcntl(F_DUPFD_CLOEXEC) returned wrong fd %d\n",
+ ++test, fd2);
+ else
+ printf("ok %d - fcntl(F_DUPFD_CLOEXEC) works\n", ++test);
+
+ /* Was close-on-exec cleared? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) != 1)
+ printf(
+ "not ok %d - fcntl(F_DUPFD_CLOEXEC) didn't set close-on-exec\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUPFD_CLOEXEC) set close-on-exec\n",
+ test);
+
+ /* If fcntl(F_DUP2FD_CLOEXEC) ever work? */
+ if ((fd2 = fcntl(fd1, F_DUP2FD_CLOEXEC, fd1 + 1)) < 0)
+ err(1, "fcntl(F_DUP2FD_CLOEXEC)");
+ printf("ok %d - fcntl(F_DUP2FD_CLOEXEC) works\n", ++test);
+
+ /* Do we get the right fd? */
+ ++test;
+ if (fd2 != fd1 + 1)
+ printf(
+ "no ok %d - fcntl(F_DUP2FD_CLOEXEC) didn't give us the right fd\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUP2FD_CLOEXEC) returned a correct fd\n",
+ test);
+
+ /* Was close-on-exec set? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) != FD_CLOEXEC)
+ printf(
+ "not ok %d - fcntl(F_DUP2FD_CLOEXEC) didn't set close-on-exec\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUP2FD_CLOEXEC) set close-on-exec\n",
+ test);
+
+ /*
+ * It is unclear what F_DUP2FD_CLOEXEC should do when duplicating a
+ * file descriptor onto itself.
+ */
+
+ ++test;
+ if (getrlimit(RLIMIT_NOFILE, &rlp) < 0)
+ err(1, "getrlimit");
+ if ((fd2 = fcntl(fd1, F_DUP2FD_CLOEXEC, rlp.rlim_cur + 1)) >= 0)
+ printf("not ok %d - fcntl(F_DUP2FD_CLOEXEC) bypassed NOFILE limit\n",
+ test);
+ else
+ printf("ok %d - fcntl(F_DUP2FD_CLOEXEC) didn't bypass NOFILE limit\n",
+ test);
+
+ /* Does dup3(O_CLOEXEC) ever work? */
+ if ((fd2 = dup3(fd1, fd1 + 1, O_CLOEXEC)) < 0)
+ err(1, "dup3(O_CLOEXEC)");
+ printf("ok %d - dup3(O_CLOEXEC) works\n", ++test);
+
+ /* Do we get the right fd? */
+ ++test;
+ if (fd2 != fd1 + 1)
+ printf(
+ "no ok %d - dup3(O_CLOEXEC) didn't give us the right fd\n",
+ test);
+ else
+ printf("ok %d - dup3(O_CLOEXEC) returned a correct fd\n",
+ test);
+
+ /* Was close-on-exec set? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) != FD_CLOEXEC)
+ printf(
+ "not ok %d - dup3(O_CLOEXEC) didn't set close-on-exec\n",
+ test);
+ else
+ printf("ok %d - dup3(O_CLOEXEC) set close-on-exec\n",
+ test);
+
+ /* Does dup3(0) ever work? */
+ if ((fd2 = dup3(fd1, fd1 + 1, 0)) < 0)
+ err(1, "dup3(0)");
+ printf("ok %d - dup3(0) works\n", ++test);
+
+ /* Do we get the right fd? */
+ ++test;
+ if (fd2 != fd1 + 1)
+ printf(
+ "no ok %d - dup3(0) didn't give us the right fd\n",
+ test);
+ else
+ printf("ok %d - dup3(0) returned a correct fd\n",
+ test);
+
+ /* Was close-on-exec cleared? */
+ ++test;
+ if (fcntl(fd2, F_GETFD) != 0)
+ printf(
+ "not ok %d - dup3(0) didn't clear close-on-exec\n",
+ test);
+ else
+ printf("ok %d - dup3(0) cleared close-on-exec\n",
+ test);
+
+ /* dup3() does not allow duplicating to the same fd */
+ ++test;
+ if (dup3(fd1, fd1, O_CLOEXEC) != -1)
+ printf(
+ "not ok %d - dup3(fd1, fd1, O_CLOEXEC) succeeded\n", test);
+ else
+ printf("ok %d - dup3(fd1, fd1, O_CLOEXEC) failed\n", test);
+
+ ++test;
+ if (dup3(fd1, fd1, 0) != -1)
+ printf(
+ "not ok %d - dup3(fd1, fd1, 0) succeeded\n", test);
+ else
+ printf("ok %d - dup3(fd1, fd1, 0) failed\n", test);
+
+ ++test;
+ if (getrlimit(RLIMIT_NOFILE, &rlp) < 0)
+ err(1, "getrlimit");
+ if ((fd2 = dup3(fd1, rlp.rlim_cur + 1, O_CLOEXEC)) >= 0)
+ printf("not ok %d - dup3(O_CLOEXEC) bypassed NOFILE limit\n",
+ test);
+ else
+ printf("ok %d - dup3(O_CLOEXEC) didn't bypass NOFILE limit\n",
+ test);
+
+ return (0);
+}
diff --git a/tests/sys/file/fcntlflags_test.c b/tests/sys/file/fcntlflags_test.c
new file mode 100644
index 000000000000..bcb3b54d6048
--- /dev/null
+++ b/tests/sys/file/fcntlflags_test.c
@@ -0,0 +1,110 @@
+/*-
+ * Copyright (c) 2013 Jilles Tjoelker
+ * 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 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 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$
+ */
+
+#include <sys/cdefs.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+/*
+ * O_ACCMODE is currently defined incorrectly. This is what it should be.
+ * Various code depends on the incorrect value.
+ */
+#define CORRECT_O_ACCMODE (O_ACCMODE | O_EXEC)
+
+static int testnum;
+
+static void
+subtests(const char *path, int omode, const char *omodetext)
+{
+ int fd, flags1, flags2, flags3;
+
+ fd = open(path, omode);
+ if (fd == -1)
+ printf("not ok %d - open(\"%s\", %s) failed\n",
+ testnum++, path, omodetext);
+ else
+ printf("ok %d - open(\"%s\", %s) succeeded\n",
+ testnum++, path, omodetext);
+ flags1 = fcntl(fd, F_GETFL);
+ if (flags1 == -1)
+ printf("not ok %d - fcntl(F_GETFL) failed\n", testnum++);
+ else if ((flags1 & CORRECT_O_ACCMODE) == omode)
+ printf("ok %d - fcntl(F_GETFL) gave correct result\n",
+ testnum++);
+ else
+ printf("not ok %d - fcntl(F_GETFL) gave incorrect result "
+ "(%#x & %#x != %#x)\n",
+ testnum++, flags1, CORRECT_O_ACCMODE, omode);
+ if (fcntl(fd, F_SETFL, flags1) == -1)
+ printf("not ok %d - fcntl(F_SETFL) same flags failed\n",
+ testnum++);
+ else
+ printf("ok %d - fcntl(F_SETFL) same flags succeeded\n",
+ testnum++);
+ flags2 = fcntl(fd, F_GETFL);
+ if (flags2 == -1)
+ printf("not ok %d - fcntl(F_GETFL) failed\n", testnum++);
+ else if (flags2 == flags1)
+ printf("ok %d - fcntl(F_GETFL) gave same result\n",
+ testnum++);
+ else
+ printf("not ok %d - fcntl(F_SETFL) caused fcntl(F_GETFL) to "
+ "change from %#x to %#x\n",
+ testnum++, flags1, flags2);
+ if (fcntl(fd, F_SETFL, flags2 | O_NONBLOCK) == -1)
+ printf("not ok %d - fcntl(F_SETFL) O_NONBLOCK failed\n",
+ testnum++);
+ else
+ printf("ok %d - fcntl(F_SETFL) O_NONBLOCK succeeded\n",
+ testnum++);
+ flags3 = fcntl(fd, F_GETFL);
+ if (flags3 == -1)
+ printf("not ok %d - fcntl(F_GETFL) failed\n", testnum++);
+ else if (flags3 == (flags2 | O_NONBLOCK))
+ printf("ok %d - fcntl(F_GETFL) gave expected result\n",
+ testnum++);
+ else
+ printf("not ok %d - fcntl(F_SETFL) gave unexpected result "
+ "(%#x != %#x)\n",
+ testnum++, flags3, flags2 | O_NONBLOCK);
+ (void)close(fd);
+}
+
+int
+main(int argc __unused, char **argv __unused)
+{
+ printf("1..24\n");
+ testnum = 1;
+ subtests("/dev/null", O_RDONLY, "O_RDONLY");
+ subtests("/dev/null", O_WRONLY, "O_WRONLY");
+ subtests("/dev/null", O_RDWR, "O_RDWR");
+ subtests("/bin/sh", O_EXEC, "O_EXEC");
+ return (0);
+}
diff --git a/tests/sys/file/flock_helper.c b/tests/sys/file/flock_helper.c
new file mode 100644
index 000000000000..0fca15e48ef2
--- /dev/null
+++ b/tests/sys/file/flock_helper.c
@@ -0,0 +1,1598 @@
+/*-
+ * Copyright (c) 2008 Isilon Inc http://www.isilon.com/
+ * Authors: Doug Rabson <dfr@rabson.org>
+ * Developed with Red Inc: Alfred Perlstein <alfred@freebsd.org>
+ *
+ * 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 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$
+ */
+
+#include <sys/param.h>
+#include <sys/file.h>
+#include <sys/time.h>
+#ifdef __FreeBSD__
+#include <sys/mount.h>
+#endif
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef __FreeBSD__
+#if __FreeBSD_version >= 800028
+#define HAVE_SYSID
+#endif
+#include <sys/cdefs.h>
+#else
+#ifndef nitems
+#define nitems(x) (sizeof((x)) / sizeof((x)[0]))
+#endif
+
+#ifndef __unused
+#ifdef __GNUC__
+#define __unused __attribute__((__unused__))
+#else
+#define __unused
+#endif
+#endif
+#endif
+
+static int verbose = 0;
+
+static int
+make_file(const char *pathname, off_t sz)
+{
+ struct stat st;
+ const char *template = "/flocktempXXXXXX";
+ size_t len;
+ char *filename;
+ int fd;
+
+ if (stat(pathname, &st) == 0) {
+ if (S_ISREG(st.st_mode)) {
+ fd = open(pathname, O_RDWR);
+ if (fd < 0)
+ err(1, "open(%s)", pathname);
+ if (ftruncate(fd, sz) < 0)
+ err(1, "ftruncate");
+ return (fd);
+ }
+ }
+
+ len = strlen(pathname) + strlen(template) + 1;
+ filename = malloc(len);
+ strcpy(filename, pathname);
+ strcat(filename, template);
+ fd = mkstemp(filename);
+ if (fd < 0)
+ err(1, "mkstemp");
+ if (ftruncate(fd, sz) < 0)
+ err(1, "ftruncate");
+ if (unlink(filename) < 0)
+ err(1, "unlink");
+ free(filename);
+
+ return (fd);
+}
+
+static void
+ignore_alarm(int __unused sig)
+{
+}
+
+static int
+safe_waitpid(pid_t pid)
+{
+ int save_errno;
+ int status;
+
+ save_errno = errno;
+ errno = 0;
+ while (waitpid(pid, &status, 0) != pid) {
+ if (errno == EINTR)
+ continue;
+ err(1, "waitpid");
+ }
+ errno = save_errno;
+
+ return (status);
+}
+
+#define FAIL(test) \
+ do { \
+ if (test) { \
+ printf("FAIL (%s)\n", #test); \
+ return -1; \
+ } \
+ } while (0)
+
+#define SUCCEED \
+ do { printf("SUCCEED\n"); return 0; } while (0)
+
+/*
+ * Test 1 - F_GETLK on unlocked region
+ *
+ * If no lock is found that would prevent this lock from being
+ * created, the structure is left unchanged by this function call
+ * except for the lock type which is set to F_UNLCK.
+ */
+static int
+test1(int fd, __unused int argc, const __unused char **argv)
+{
+ struct flock fl1, fl2;
+
+ memset(&fl1, 1, sizeof(fl1));
+ fl1.l_type = F_WRLCK;
+ fl1.l_whence = SEEK_SET;
+ fl2 = fl1;
+
+ if (fcntl(fd, F_GETLK, &fl1) < 0)
+ err(1, "F_GETLK");
+
+ printf("1 - F_GETLK on unlocked region: ");
+ FAIL(fl1.l_start != fl2.l_start);
+ FAIL(fl1.l_len != fl2.l_len);
+ FAIL(fl1.l_pid != fl2.l_pid);
+ FAIL(fl1.l_type != F_UNLCK);
+ FAIL(fl1.l_whence != fl2.l_whence);
+#ifdef HAVE_SYSID
+ FAIL(fl1.l_sysid != fl2.l_sysid);
+#endif
+
+ SUCCEED;
+}
+
+/*
+ * Test 2 - F_SETLK on locked region
+ *
+ * If a shared or exclusive lock cannot be set, fcntl returns
+ * immediately with EACCES or EAGAIN.
+ */
+static int
+test2(int fd, __unused int argc, const __unused char **argv)
+{
+ /*
+ * We create a child process to hold the lock which we will
+ * test. We use a pipe to communicate with the child.
+ */
+ int pid;
+ int pfd[2];
+ struct flock fl;
+ char ch;
+ int res;
+
+ if (pipe(pfd) < 0)
+ err(1, "pipe");
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+
+ if (pid == 0) {
+ /*
+ * We are the child. We set a write lock and then
+ * write one byte back to the parent to tell it. The
+ * parent will kill us when its done.
+ */
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_SETLK (child)");
+ if (write(pfd[1], "a", 1) < 0)
+ err(1, "writing to pipe (child)");
+ pause();
+ exit(0);
+ }
+
+ /*
+ * Wait until the child has set its lock and then perform the
+ * test.
+ */
+ if (read(pfd[0], &ch, 1) != 1)
+ err(1, "reading from pipe (child)");
+
+ /*
+ * fcntl should return -1 with errno set to either EACCES or
+ * EAGAIN.
+ */
+ printf("2 - F_SETLK on locked region: ");
+ res = fcntl(fd, F_SETLK, &fl);
+ kill(pid, SIGTERM);
+ safe_waitpid(pid);
+ close(pfd[0]);
+ close(pfd[1]);
+ FAIL(res == 0);
+ FAIL(errno != EACCES && errno != EAGAIN);
+
+ SUCCEED;
+}
+
+/*
+ * Test 3 - F_SETLKW on locked region
+ *
+ * If a shared or exclusive lock is blocked by other locks, the
+ * process waits until the request can be satisfied.
+ *
+ * XXX this test hangs on FreeBSD NFS filesystems due to limitations
+ * in FreeBSD's client (and server) lockd implementation.
+ */
+static int
+test3(int fd, __unused int argc, const __unused char **argv)
+{
+ /*
+ * We create a child process to hold the lock which we will
+ * test. We use a pipe to communicate with the child.
+ */
+ int pid;
+ int pfd[2];
+ struct flock fl;
+ char ch;
+ int res;
+
+ if (pipe(pfd) < 0)
+ err(1, "pipe");
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+
+ if (pid == 0) {
+ /*
+ * We are the child. We set a write lock and then
+ * write one byte back to the parent to tell it. The
+ * parent will kill us when its done.
+ */
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_SETLK (child)");
+ if (write(pfd[1], "a", 1) < 0)
+ err(1, "writing to pipe (child)");
+ pause();
+ exit(0);
+ }
+
+ /*
+ * Wait until the child has set its lock and then perform the
+ * test.
+ */
+ if (read(pfd[0], &ch, 1) != 1)
+ err(1, "reading from pipe (child)");
+
+ /*
+ * fcntl should wait until the alarm and then return -1 with
+ * errno set to EINTR.
+ */
+ printf("3 - F_SETLKW on locked region: ");
+
+ alarm(1);
+
+ res = fcntl(fd, F_SETLKW, &fl);
+ kill(pid, SIGTERM);
+ safe_waitpid(pid);
+ close(pfd[0]);
+ close(pfd[1]);
+ FAIL(res == 0);
+ FAIL(errno != EINTR);
+
+ SUCCEED;
+}
+
+/*
+ * Test 4 - F_GETLK on locked region
+ *
+ * Get the first lock that blocks the lock.
+ */
+static int
+test4(int fd, __unused int argc, const __unused char **argv)
+{
+ /*
+ * We create a child process to hold the lock which we will
+ * test. We use a pipe to communicate with the child.
+ */
+ int pid;
+ int pfd[2];
+ struct flock fl;
+ char ch;
+
+ if (pipe(pfd) < 0)
+ err(1, "pipe");
+
+ fl.l_start = 0;
+ fl.l_len = 99;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+
+ if (pid == 0) {
+ /*
+ * We are the child. We set a write lock and then
+ * write one byte back to the parent to tell it. The
+ * parent will kill us when its done.
+ */
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_SETLK (child)");
+ if (write(pfd[1], "a", 1) < 0)
+ err(1, "writing to pipe (child)");
+ pause();
+ exit(0);
+ }
+
+ /*
+ * Wait until the child has set its lock and then perform the
+ * test.
+ */
+ if (read(pfd[0], &ch, 1) != 1)
+ err(1, "reading from pipe (child)");
+
+ /*
+ * fcntl should return a lock structure reflecting the lock we
+ * made in the child process.
+ */
+ if (fcntl(fd, F_GETLK, &fl) < 0)
+ err(1, "F_GETLK");
+
+ printf("4 - F_GETLK on locked region: ");
+ FAIL(fl.l_start != 0);
+ FAIL(fl.l_len != 99);
+ FAIL(fl.l_type != F_WRLCK);
+ FAIL(fl.l_pid != pid);
+#ifdef HAVE_SYSID
+ FAIL(fl.l_sysid != 0);
+#endif
+
+ kill(pid, SIGTERM);
+ safe_waitpid(pid);
+ close(pfd[0]);
+ close(pfd[1]);
+
+ SUCCEED;
+}
+
+/*
+ * Test 5 - F_SETLKW simple deadlock
+ *
+ * If a blocking shared lock request would cause a deadlock (i.e. the
+ * lock request is blocked by a process which is itself blocked on a
+ * lock currently owned by the process making the new request),
+ * EDEADLK is returned.
+ */
+static int
+test5(int fd, __unused int argc, const __unused char **argv)
+{
+ /*
+ * We create a child process to hold the lock which we will
+ * test. Because our test relies on the child process being
+ * blocked on the parent's lock, we can't easily use a pipe to
+ * synchronize so we just sleep in the parent to given the
+ * child a chance to setup.
+ *
+ * To create the deadlock condition, we arrange for the parent
+ * to lock the first byte of the file and the child to lock
+ * the second byte. After locking the second byte, the child
+ * will attempt to lock the first byte of the file, and
+ * block. The parent will then attempt to lock the second byte
+ * (owned by the child) which should cause deadlock.
+ */
+ int pid;
+ struct flock fl;
+ int res;
+
+ /*
+ * Lock the first byte in the parent.
+ */
+ fl.l_start = 0;
+ fl.l_len = 1;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_SETLK 1 (parent)");
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+
+ if (pid == 0) {
+ /*
+ * Lock the second byte in the child and then block on
+ * the parent's lock.
+ */
+ fl.l_start = 1;
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_SETLK (child)");
+ fl.l_start = 0;
+ if (fcntl(fd, F_SETLKW, &fl) < 0)
+ err(1, "F_SETLKW (child)");
+ exit(0);
+ }
+
+ /*
+ * Wait until the child has set its lock and then perform the
+ * test.
+ */
+ sleep(1);
+
+ /*
+ * fcntl should immediately return -1 with errno set to
+ * EDEADLK. If the alarm fires, we failed to detect the
+ * deadlock.
+ */
+ alarm(1);
+ printf("5 - F_SETLKW simple deadlock: ");
+
+ fl.l_start = 1;
+ res = fcntl(fd, F_SETLKW, &fl);
+ kill(pid, SIGTERM);
+ safe_waitpid(pid);
+
+ FAIL(res == 0);
+ FAIL(errno != EDEADLK);
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_UNLCK;
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_UNLCK");
+
+ /*
+ * Cancel the alarm to avoid confusing later tests.
+ */
+ alarm(0);
+
+ SUCCEED;
+}
+
+/*
+ * Test 6 - F_SETLKW complex deadlock.
+ *
+ * This test involves three process, P, C1 and C2. We set things up so
+ * that P locks byte zero, C1 locks byte 1 and C2 locks byte 2. We
+ * also block C2 by attempting to lock byte zero. Lastly, P attempts
+ * to lock a range including byte 1 and 2. This represents a deadlock
+ * (due to C2's blocking attempt to lock byte zero).
+ */
+static int
+test6(int fd, __unused int argc, const __unused char **argv)
+{
+ /*
+ * Because our test relies on the child process being blocked
+ * on the parent's lock, we can't easily use a pipe to
+ * synchronize so we just sleep in the parent to given the
+ * children a chance to setup.
+ */
+ int pid1, pid2;
+ struct flock fl;
+ int res;
+
+ /*
+ * Lock the first byte in the parent.
+ */
+ fl.l_start = 0;
+ fl.l_len = 1;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_SETLK 1 (parent)");
+
+ pid1 = fork();
+ if (pid1 < 0)
+ err(1, "fork");
+
+ if (pid1 == 0) {
+ /*
+ * C1
+ * Lock the second byte in the child and then sleep
+ */
+ fl.l_start = 1;
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_SETLK (child1)");
+ pause();
+ exit(0);
+ }
+
+ pid2 = fork();
+ if (pid2 < 0)
+ err(1, "fork");
+
+ if (pid2 == 0) {
+ /*
+ * C2
+ * Lock the third byte in the child and then block on
+ * the parent's lock.
+ */
+ fl.l_start = 2;
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_SETLK (child2)");
+ fl.l_start = 0;
+ if (fcntl(fd, F_SETLKW, &fl) < 0)
+ err(1, "F_SETLKW (child2)");
+ exit(0);
+ }
+
+ /*
+ * Wait until the children have set their locks and then
+ * perform the test.
+ */
+ sleep(1);
+
+ /*
+ * fcntl should immediately return -1 with errno set to
+ * EDEADLK. If the alarm fires, we failed to detect the
+ * deadlock.
+ */
+ alarm(1);
+ printf("6 - F_SETLKW complex deadlock: ");
+
+ fl.l_start = 1;
+ fl.l_len = 2;
+ res = fcntl(fd, F_SETLKW, &fl);
+ kill(pid1, SIGTERM);
+ safe_waitpid(pid1);
+ kill(pid2, SIGTERM);
+ safe_waitpid(pid2);
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_UNLCK;
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_UNLCK");
+
+ FAIL(res == 0);
+ FAIL(errno != EDEADLK);
+
+ /*
+ * Cancel the alarm to avoid confusing later tests.
+ */
+ alarm(0);
+
+ SUCCEED;
+}
+
+/*
+ * Test 7 - F_SETLK shared lock on exclusive locked region
+ *
+ * If a shared or exclusive lock cannot be set, fcntl returns
+ * immediately with EACCES or EAGAIN.
+ */
+static int
+test7(int fd, __unused int argc, const __unused char **argv)
+{
+ /*
+ * We create a child process to hold the lock which we will
+ * test. We use a pipe to communicate with the child.
+ */
+ int pid;
+ int pfd[2];
+ struct flock fl;
+ char ch;
+ int res;
+
+ if (pipe(pfd) < 0)
+ err(1, "pipe");
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+
+ if (pid == 0) {
+ /*
+ * We are the child. We set a write lock and then
+ * write one byte back to the parent to tell it. The
+ * parent will kill us when its done.
+ */
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_SETLK (child)");
+ if (write(pfd[1], "a", 1) < 0)
+ err(1, "writing to pipe (child)");
+ pause();
+ exit(0);
+ }
+
+ /*
+ * Wait until the child has set its lock and then perform the
+ * test.
+ */
+ if (read(pfd[0], &ch, 1) != 1)
+ err(1, "reading from pipe (child)");
+
+ /*
+ * fcntl should wait until the alarm and then return -1 with
+ * errno set to EINTR.
+ */
+ printf("7 - F_SETLK shared lock on exclusive locked region: ");
+
+ fl.l_type = F_RDLCK;
+ res = fcntl(fd, F_SETLK, &fl);
+ kill(pid, SIGTERM);
+ safe_waitpid(pid);
+ close(pfd[0]);
+ close(pfd[1]);
+
+ FAIL(res == 0);
+ FAIL(errno != EACCES && errno != EAGAIN);
+
+ SUCCEED;
+}
+
+/*
+ * Test 8 - F_SETLK shared lock on share locked region
+ *
+ * When a shared lock is set on a segment of a file, other processes
+ * shall be able to set shared locks on that segment or a portion of
+ * it.
+ */
+static int
+test8(int fd, __unused int argc, const __unused char **argv)
+{
+ /*
+ * We create a child process to hold the lock which we will
+ * test. We use a pipe to communicate with the child.
+ */
+ int pid;
+ int pfd[2];
+ struct flock fl;
+ char ch;
+ int res;
+
+ if (pipe(pfd) < 0)
+ err(1, "pipe");
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_RDLCK;
+ fl.l_whence = SEEK_SET;
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+
+ if (pid == 0) {
+ /*
+ * We are the child. We set a write lock and then
+ * write one byte back to the parent to tell it. The
+ * parent will kill us when its done.
+ */
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_SETLK (child)");
+ if (write(pfd[1], "a", 1) < 0)
+ err(1, "writing to pipe (child)");
+ pause();
+ exit(0);
+ }
+
+ /*
+ * Wait until the child has set its lock and then perform the
+ * test.
+ */
+ if (read(pfd[0], &ch, 1) != 1)
+ err(1, "reading from pipe (child)");
+
+ /*
+ * fcntl should wait until the alarm and then return -1 with
+ * errno set to EINTR.
+ */
+ printf("8 - F_SETLK shared lock on share locked region: ");
+
+ fl.l_type = F_RDLCK;
+ res = fcntl(fd, F_SETLK, &fl);
+
+ kill(pid, SIGTERM);
+ safe_waitpid(pid);
+ close(pfd[0]);
+ close(pfd[1]);
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_UNLCK;
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_UNLCK");
+
+ FAIL(res != 0);
+
+ SUCCEED;
+}
+
+/*
+ * Test 9 - F_SETLK exclusive lock on share locked region
+ *
+ * If a shared or exclusive lock cannot be set, fcntl returns
+ * immediately with EACCES or EAGAIN.
+ */
+static int
+test9(int fd, __unused int argc, const __unused char **argv)
+{
+ /*
+ * We create a child process to hold the lock which we will
+ * test. We use a pipe to communicate with the child.
+ */
+ int pid;
+ int pfd[2];
+ struct flock fl;
+ char ch;
+ int res;
+
+ if (pipe(pfd) < 0)
+ err(1, "pipe");
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_RDLCK;
+ fl.l_whence = SEEK_SET;
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+
+ if (pid == 0) {
+ /*
+ * We are the child. We set a write lock and then
+ * write one byte back to the parent to tell it. The
+ * parent will kill us when its done.
+ */
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_SETLK (child)");
+ if (write(pfd[1], "a", 1) < 0)
+ err(1, "writing to pipe (child)");
+ pause();
+ exit(0);
+ }
+
+ /*
+ * Wait until the child has set its lock and then perform the
+ * test.
+ */
+ if (read(pfd[0], &ch, 1) != 1)
+ err(1, "reading from pipe (child)");
+
+ /*
+ * fcntl should wait until the alarm and then return -1 with
+ * errno set to EINTR.
+ */
+ printf("9 - F_SETLK exclusive lock on share locked region: ");
+
+ fl.l_type = F_WRLCK;
+ res = fcntl(fd, F_SETLK, &fl);
+ kill(pid, SIGTERM);
+ safe_waitpid(pid);
+ close(pfd[0]);
+ close(pfd[1]);
+
+ FAIL(res == 0);
+ FAIL(errno != EACCES && errno != EAGAIN);
+
+ SUCCEED;
+}
+
+/*
+ * Test 10 - trying to set bogus pid or sysid values
+ *
+ * The l_pid and l_sysid fields are only used with F_GETLK to return
+ * the process ID of the process holding a blocking lock and the
+ * system ID of the system that owns that process
+ */
+static int
+test10(int fd, __unused int argc, const __unused char **argv)
+{
+ /*
+ * We create a child process to hold the lock which we will
+ * test. We use a pipe to communicate with the child.
+ */
+ int pid;
+ int pfd[2];
+ struct flock fl;
+ char ch;
+
+ if (pipe(pfd) < 0)
+ err(1, "pipe");
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_pid = 9999;
+#ifdef HAVE_SYSID
+ fl.l_sysid = 9999;
+#endif
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+
+ if (pid == 0) {
+ /*
+ * We are the child. We set a write lock and then
+ * write one byte back to the parent to tell it. The
+ * parent will kill us when its done.
+ */
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_SETLK (child)");
+ if (write(pfd[1], "a", 1) < 0)
+ err(1, "writing to pipe (child)");
+ pause();
+ exit(0);
+ }
+
+ /*
+ * Wait until the child has set its lock and then perform the
+ * test.
+ */
+ if (read(pfd[0], &ch, 1) != 1)
+ err(1, "reading from pipe (child)");
+
+ printf("10 - trying to set bogus pid or sysid values: ");
+
+ if (fcntl(fd, F_GETLK, &fl) < 0)
+ err(1, "F_GETLK");
+
+ kill(pid, SIGTERM);
+ safe_waitpid(pid);
+ close(pfd[0]);
+ close(pfd[1]);
+
+ FAIL(fl.l_pid != pid);
+#ifdef HAVE_SYSID
+ FAIL(fl.l_sysid != 0);
+#endif
+
+ SUCCEED;
+}
+
+/*
+ * Test 11 - remote locks
+ *
+ * XXX temporary interface which will be removed when the kernel lockd
+ * is added.
+ */
+static int
+test11(int fd, __unused int argc, const __unused char **argv)
+{
+#ifdef F_SETLK_REMOTE
+ struct flock fl;
+ int res;
+
+ if (geteuid() != 0)
+ return 0;
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_pid = 9999;
+ fl.l_sysid = 1001;
+
+ printf("11 - remote locks: ");
+
+ res = fcntl(fd, F_SETLK_REMOTE, &fl);
+ FAIL(res != 0);
+
+ fl.l_sysid = 1002;
+ res = fcntl(fd, F_SETLK_REMOTE, &fl);
+ FAIL(res == 0);
+ FAIL(errno != EACCES && errno != EAGAIN);
+
+ res = fcntl(fd, F_GETLK, &fl);
+ FAIL(res != 0);
+ FAIL(fl.l_pid != 9999);
+ FAIL(fl.l_sysid != 1001);
+
+ fl.l_type = F_UNLCK;
+ fl.l_sysid = 1001;
+ fl.l_start = 0;
+ fl.l_len = 0;
+ res = fcntl(fd, F_SETLK_REMOTE, &fl);
+ FAIL(res != 0);
+
+ fl.l_pid = 1234;
+ fl.l_sysid = 1001;
+ fl.l_start = 0;
+ fl.l_len = 1;
+ fl.l_whence = SEEK_SET;
+ fl.l_type = F_RDLCK;
+ res = fcntl(fd, F_SETLK_REMOTE, &fl);
+ FAIL(res != 0);
+
+ fl.l_sysid = 1002;
+ res = fcntl(fd, F_SETLK_REMOTE, &fl);
+ FAIL(res != 0);
+
+ fl.l_type = F_UNLCKSYS;
+ fl.l_sysid = 1001;
+ res = fcntl(fd, F_SETLK_REMOTE, &fl);
+ FAIL(res != 0);
+
+ fl.l_type = F_WRLCK;
+ res = fcntl(fd, F_GETLK, &fl);
+ FAIL(res != 0);
+ FAIL(fl.l_pid != 1234);
+ FAIL(fl.l_sysid != 1002);
+
+ fl.l_type = F_UNLCKSYS;
+ fl.l_sysid = 1002;
+ res = fcntl(fd, F_SETLK_REMOTE, &fl);
+ FAIL(res != 0);
+
+ SUCCEED;
+#else
+ return 0;
+#endif
+}
+
+/*
+ * Test 12 - F_SETLKW on locked region which is then unlocked
+ *
+ * If a shared or exclusive lock is blocked by other locks, the
+ * process waits until the request can be satisfied.
+ */
+static int
+test12(int fd, __unused int argc, const __unused char **argv)
+{
+ /*
+ * We create a child process to hold the lock which we will
+ * test. We use a pipe to communicate with the child.
+ */
+ int pid;
+ int pfd[2];
+ struct flock fl;
+ char ch;
+ int res;
+
+ if (pipe(pfd) < 0)
+ err(1, "pipe");
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+
+ if (pid == 0) {
+ /*
+ * We are the child. We set a write lock and then
+ * write one byte back to the parent to tell it. The
+ * parent will kill us when its done.
+ */
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_SETLK (child)");
+ if (write(pfd[1], "a", 1) < 0)
+ err(1, "writing to pipe (child)");
+
+ sleep(1);
+ exit(0);
+ }
+
+ /*
+ * Wait until the child has set its lock and then perform the
+ * test.
+ */
+ if (read(pfd[0], &ch, 1) != 1)
+ err(1, "reading from pipe (child)");
+
+ /*
+ * fcntl should wait until the alarm and then return -1 with
+ * errno set to EINTR.
+ */
+ printf("12 - F_SETLKW on locked region which is then unlocked: ");
+
+ //alarm(1);
+
+ res = fcntl(fd, F_SETLKW, &fl);
+ kill(pid, SIGTERM);
+ safe_waitpid(pid);
+ close(pfd[0]);
+ close(pfd[1]);
+ FAIL(res != 0);
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_UNLCK;
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_UNLCK");
+
+ SUCCEED;
+}
+
+/*
+ * Test 13 - F_SETLKW on locked region, race with owner
+ *
+ * If a shared or exclusive lock is blocked by other locks, the
+ * process waits until the request can be satisfied.
+ */
+static int
+test13(int fd, __unused int argc, const __unused char **argv)
+{
+ /*
+ * We create a child process to hold the lock which we will
+ * test. We use a pipe to communicate with the child.
+ */
+ int i;
+ int pid;
+ int pfd[2];
+ struct flock fl;
+ char ch;
+ int res;
+ struct itimerval itv;
+
+ printf("13 - F_SETLKW on locked region, race with owner: ");
+ fflush(stdout);
+
+ for (i = 0; i < 100; i++) {
+ if (pipe(pfd) < 0)
+ err(1, "pipe");
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+
+ if (pid == 0) {
+ /*
+ * We are the child. We set a write lock and then
+ * write one byte back to the parent to tell it. The
+ * parent will kill us when its done.
+ */
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_SETLK (child)");
+ if (write(pfd[1], "a", 1) < 0)
+ err(1, "writing to pipe (child)");
+
+ usleep(1);
+ exit(0);
+ }
+
+ /*
+ * Wait until the child has set its lock and then perform the
+ * test.
+ */
+ while (read(pfd[0], &ch, 1) != 1) {
+ if (errno == EINTR)
+ continue;
+ err(1, "reading from pipe (child)");
+ }
+
+ /*
+ * fcntl should wait until the alarm and then return -1 with
+ * errno set to EINTR.
+ */
+ itv.it_interval.tv_sec = 0;
+ itv.it_interval.tv_usec = 0;
+ itv.it_value.tv_sec = 0;
+ itv.it_value.tv_usec = 2;
+ setitimer(ITIMER_REAL, &itv, NULL);
+
+ res = fcntl(fd, F_SETLKW, &fl);
+ kill(pid, SIGTERM);
+ safe_waitpid(pid);
+ close(pfd[0]);
+ close(pfd[1]);
+ FAIL(!(res == 0 || (res == -1 && errno == EINTR)));
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_UNLCK;
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "F_UNLCK");
+ }
+ SUCCEED;
+}
+
+/*
+ * Test 14 - soak test
+ */
+static int
+test14(int fd, int argc, const char **argv)
+{
+#define CHILD_COUNT 20
+ /*
+ * We create a set of child processes and let each one run
+ * through a random sequence of locks and unlocks.
+ */
+ int i, j, id, id_base;
+ int pids[CHILD_COUNT], pid;
+ char buf[128];
+ char tbuf[128];
+ int map[128];
+ char outbuf[512];
+ struct flock fl;
+ struct itimerval itv;
+ int status;
+
+ id_base = 0;
+ if (argc >= 2)
+ id_base = strtol(argv[1], NULL, 0);
+
+ printf("14 - soak test: ");
+ fflush(stdout);
+
+ for (i = 0; i < 128; i++)
+ map[i] = F_UNLCK;
+
+ for (i = 0; i < CHILD_COUNT; i++) {
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+ if (pid) {
+ /*
+ * Parent - record the pid and continue.
+ */
+ pids[i] = pid;
+ continue;
+ }
+
+ /*
+ * Child - do some work and exit.
+ */
+ id = id_base + i;
+ srandom(getpid());
+
+ for (j = 0; j < 50; j++) {
+ int start, end, len;
+ int set, wrlock;
+
+ do {
+ start = random() & 127;
+ end = random() & 127;
+ } while (end <= start);
+
+ set = random() & 1;
+ wrlock = random() & 1;
+
+ len = end - start;
+ fl.l_start = start;
+ fl.l_len = len;
+ fl.l_whence = SEEK_SET;
+ if (set)
+ fl.l_type = wrlock ? F_WRLCK : F_RDLCK;
+ else
+ fl.l_type = F_UNLCK;
+
+ itv.it_interval.tv_sec = 0;
+ itv.it_interval.tv_usec = 0;
+ itv.it_value.tv_sec = 0;
+ itv.it_value.tv_usec = 3000;
+ setitimer(ITIMER_REAL, &itv, NULL);
+
+ if (fcntl(fd, F_SETLKW, &fl) < 0) {
+ if (errno == EDEADLK || errno == EINTR) {
+ if (verbose) {
+ snprintf(outbuf, sizeof(outbuf),
+ "%d[%d]: %s [%d .. %d] %s\n",
+ id, j,
+ set ? (wrlock ? "write lock"
+ : "read lock")
+ : "unlock", start, end,
+ errno == EDEADLK
+ ? "deadlock"
+ : "interrupted");
+ write(1, outbuf,
+ strlen(outbuf));
+ }
+ continue;
+ } else {
+ perror("fcntl");
+ }
+ }
+
+ itv.it_interval.tv_sec = 0;
+ itv.it_interval.tv_usec = 0;
+ itv.it_value.tv_sec = 0;
+ itv.it_value.tv_usec = 0;
+ setitimer(ITIMER_REAL, &itv, NULL);
+
+ if (verbose) {
+ snprintf(outbuf, sizeof(outbuf),
+ "%d[%d]: %s [%d .. %d] succeeded\n",
+ id, j,
+ set ? (wrlock ? "write lock" : "read lock")
+ : "unlock", start, end);
+ write(1, outbuf, strlen(outbuf));
+ }
+
+ if (set) {
+ if (wrlock) {
+ /*
+ * We got a write lock - write
+ * our ID to each byte that we
+ * managed to claim.
+ */
+ for (i = start; i < end; i++)
+ map[i] = F_WRLCK;
+ memset(&buf[start], id, len);
+ if (pwrite(fd, &buf[start], len,
+ start) != len) {
+ printf("%d: short write\n", id);
+ exit(1);
+ }
+ } else {
+ /*
+ * We got a read lock - read
+ * the bytes which we claimed
+ * so that we can check that
+ * they don't change
+ * unexpectedly.
+ */
+ for (i = start; i < end; i++)
+ map[i] = F_RDLCK;
+ if (pread(fd, &buf[start], len,
+ start) != len) {
+ printf("%d: short read\n", id);
+ exit(1);
+ }
+ }
+ } else {
+ for (i = start; i < end; i++)
+ map[i] = F_UNLCK;
+ }
+
+ usleep(1000);
+
+ /*
+ * Read back the whole region so that we can
+ * check that all the bytes we have some kind
+ * of claim to have the correct value.
+ */
+ if (pread(fd, tbuf, sizeof(tbuf), 0) != sizeof(tbuf)) {
+ printf("%d: short read\n", id);
+ exit(1);
+ }
+
+ for (i = 0; i < 128; i++) {
+ if (map[i] != F_UNLCK && buf[i] != tbuf[i]) {
+ snprintf(outbuf, sizeof(outbuf),
+ "%d: byte %d expected %d, "
+ "got %d\n", id, i, buf[i], tbuf[i]);
+ write(1, outbuf, strlen(outbuf));
+ exit(1);
+ }
+ }
+ }
+ if (verbose)
+ printf("%d[%d]: done\n", id, j);
+
+ exit(0);
+ }
+
+ status = 0;
+ for (i = 0; i < CHILD_COUNT; i++) {
+ status += safe_waitpid(pids[i]);
+ }
+ if (status)
+ FAIL(status != 0);
+
+ SUCCEED;
+}
+
+/*
+ * Test 15 - flock(2) semantcs
+ *
+ * When a lock holder has a shared lock and attempts to upgrade that
+ * shared lock to exclusive, it must drop the shared lock before
+ * blocking on the exclusive lock.
+ *
+ * To test this, we first arrange for two shared locks on the file,
+ * and then attempt to upgrade one of them to exclusive. This should
+ * drop one of the shared locks and block. We interrupt the blocking
+ * lock request and examine the lock state of the file after dropping
+ * the other shared lock - there should be no active locks at this
+ * point.
+ */
+static int
+test15(int fd, __unused int argc, const __unused char **argv)
+{
+#ifdef LOCK_EX
+ /*
+ * We create a child process to hold the lock which we will
+ * test. We use a pipe to communicate with the child.
+ *
+ * Since we only have one file descriptors and lock ownership
+ * for flock(2) goes with the file descriptor, we use fcntl to
+ * set the child's shared lock.
+ */
+ int pid;
+ int pfd[2];
+ struct flock fl;
+ char ch;
+ int res;
+
+ if (pipe(pfd) < 0)
+ err(1, "pipe");
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+
+ if (pid == 0) {
+ /*
+ * We are the child. We set a shared lock and then
+ * write one byte back to the parent to tell it. The
+ * parent will kill us when its done.
+ */
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_RDLCK;
+ fl.l_whence = SEEK_SET;
+ if (fcntl(fd, F_SETLK, &fl) < 0)
+ err(1, "fcntl(F_SETLK) (child)");
+ if (write(pfd[1], "a", 1) < 0)
+ err(1, "writing to pipe (child)");
+ pause();
+ exit(0);
+ }
+
+ /*
+ * Wait until the child has set its lock and then perform the
+ * test.
+ */
+ if (read(pfd[0], &ch, 1) != 1)
+ err(1, "reading from pipe (child)");
+
+ (void)dup(fd);
+ if (flock(fd, LOCK_SH) < 0)
+ err(1, "flock shared");
+
+ /*
+ * flock should wait until the alarm and then return -1 with
+ * errno set to EINTR.
+ */
+ printf("15 - flock(2) semantics: ");
+
+ alarm(1);
+ flock(fd, LOCK_EX);
+
+ /*
+ * Kill the child to force it to drop its locks.
+ */
+ kill(pid, SIGTERM);
+ safe_waitpid(pid);
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+ res = fcntl(fd, F_GETLK, &fl);
+
+ close(pfd[0]);
+ close(pfd[1]);
+ FAIL(res != 0);
+ FAIL(fl.l_type != F_UNLCK);
+
+ SUCCEED;
+#else
+ return 0;
+#endif
+}
+
+struct test_ctx {
+ struct flock tc_fl;
+ int tc_fd;
+};
+
+static void *
+test16_func(void *tc_in)
+{
+ uintptr_t error;
+ struct test_ctx *tc = tc_in;
+
+ error = fcntl(tc->tc_fd, F_SETLKW, &tc->tc_fl);
+
+ pthread_exit((void *)error);
+}
+
+#define THREADS 10
+
+/*
+ * Test 16 - F_SETLKW from two threads
+ *
+ * If two threads within a process are blocked on a lock and the lock
+ * is granted, make sure things are sane.
+ */
+static int
+test16(int fd, __unused int argc, const __unused char **argv)
+{
+ /*
+ * We create a child process to hold the lock which we will
+ * test. We use a pipe to communicate with the child.
+ */
+ int pid;
+ int pfd[2];
+ struct test_ctx tc = { .tc_fd = fd };
+ char ch;
+ int i;
+ int error;
+ pthread_t thr[THREADS];
+
+ if (pipe(pfd) < 0)
+ err(1, "pipe");
+
+ tc.tc_fl.l_start = 0;
+ tc.tc_fl.l_len = 0;
+ tc.tc_fl.l_type = F_WRLCK;
+ tc.tc_fl.l_whence = SEEK_SET;
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+
+ if (pid == 0) {
+ /*
+ * We are the child. We set a write lock and then
+ * write one byte back to the parent to tell it. The
+ * parent will kill us when its done.
+ */
+ if (fcntl(fd, F_SETLK, &tc.tc_fl) < 0)
+ err(1, "F_SETLK (child)");
+ if (write(pfd[1], "a", 1) < 0)
+ err(1, "writing to pipe (child)");
+ pause();
+ exit(0);
+ }
+
+ /*
+ * Wait until the child has set its lock and then perform the
+ * test.
+ */
+ if (read(pfd[0], &ch, 1) != 1)
+ err(1, "reading from pipe (child)");
+
+ /*
+ * fcntl should wait until the alarm and then return -1 with
+ * errno set to EINTR.
+ */
+ printf("16 - F_SETLKW on locked region by two threads: ");
+
+ for (i = 0; i < THREADS; i++) {
+ error = pthread_create(&thr[i], NULL, test16_func, &tc);
+ if (error)
+ err(1, "pthread_create");
+ }
+
+ /*
+ * Sleep, then kill the child. This makes me a little sad, but it's
+ * tricky to tell whether the threads are all really blocked by this
+ * point.
+ */
+ sleep(1);
+ kill(pid, SIGTERM);
+ safe_waitpid(pid);
+ close(pfd[0]);
+ close(pfd[1]);
+
+ for (i = 0; i < THREADS; i++) {
+ void *res;
+ error = pthread_join(thr[i], &res);
+ if (error)
+ err(1, "pthread_join");
+ FAIL((uintptr_t)res != 0);
+ }
+
+ SUCCEED;
+}
+
+struct test {
+ int (*testfn)(int, int, const char **); /* function to perform the test */
+ int num; /* test number */
+ int intr; /* non-zero if the test interrupts a lock */
+};
+
+static struct test tests[] = {
+ { test1, 1, 0 },
+ { test2, 2, 0 },
+ { test3, 3, 1 },
+ { test4, 4, 0 },
+ { test5, 5, 1 },
+ { test6, 6, 1 },
+ { test7, 7, 0 },
+ { test8, 8, 0 },
+ { test9, 9, 0 },
+ { test10, 10, 0 },
+ { test11, 11, 1 },
+ { test12, 12, 0 },
+ { test13, 13, 1 },
+ { test14, 14, 0 },
+ { test15, 15, 1 },
+ { test16, 16, 1 },
+};
+
+int
+main(int argc, const char *argv[])
+{
+ int testnum;
+ int fd;
+ int nointr;
+ unsigned i;
+ struct sigaction sa;
+ int test_argc;
+ const char **test_argv;
+
+ if (argc < 2) {
+ errx(1, "usage: flock <directory> [test number] ...");
+ }
+
+ fd = make_file(argv[1], 1024);
+ if (argc >= 3) {
+ testnum = strtol(argv[2], NULL, 0);
+ test_argc = argc - 2;
+ test_argv = argv + 2;
+ } else {
+ testnum = 0;
+ test_argc = 0;
+ test_argv = NULL;
+ }
+
+ sa.sa_handler = ignore_alarm;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(SIGALRM, &sa, 0);
+
+ nointr = 0;
+#if defined(__FreeBSD__) && __FreeBSD_version < 800040
+ {
+ /*
+ * FreeBSD with userland NLM can't interrupt a blocked
+ * lock request on an NFS mounted filesystem.
+ */
+ struct statfs st;
+ fstatfs(fd, &st);
+ nointr = !strcmp(st.f_fstypename, "nfs");
+ }
+#endif
+
+ for (i = 0; i < nitems(tests); i++) {
+ if (tests[i].intr && nointr)
+ continue;
+ if (!testnum || tests[i].num == testnum)
+ tests[i].testfn(fd, test_argc, test_argv);
+ }
+
+ return 0;
+}
diff --git a/tests/sys/file/flock_test.sh b/tests/sys/file/flock_test.sh
new file mode 100755
index 000000000000..ead4ff1a9806
--- /dev/null
+++ b/tests/sys/file/flock_test.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+#
+# Copyright 2014 EMC Corp.
+# 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 Google Inc. 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
+# OWNER 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$
+
+# Testcase # 11 is racy; uses an undocumented kernel interface for testing
+# locking
+last_testcase=16
+
+echo "1..$last_testcase"
+
+for n in `seq 1 $last_testcase`; do
+ todomsg=""
+
+ if [ $n -eq 11 ]; then
+ todomsg=" # TODO: racy testcase"
+ fi
+
+ output=$($(dirname $0)/flock_helper . $n)
+ if echo "$output" | grep -q SUCCEED; then
+ echo "ok $n$todomsg"
+ else
+ echo "not ok $n$todomsg"
+ echo "$output" >&2
+ fi
+done
diff --git a/tests/sys/file/ftruncate_test.c b/tests/sys/file/ftruncate_test.c
new file mode 100644
index 000000000000..7eaba14e2bfa
--- /dev/null
+++ b/tests/sys/file/ftruncate_test.c
@@ -0,0 +1,177 @@
+/*-
+ * Copyright (c) 2006 Robert N. M. Watson
+ * 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 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 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$
+ */
+
+/*
+ * Very simple regression test.
+ *
+ * Future tests that might be of interest:
+ *
+ * - Make sure we get EISDIR on a directory.
+ */
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <unistd.h>
+
+/*
+ * Select various potentially interesting lengths at and around power of 2
+ * edges.
+ */
+static off_t lengths[] = {0, 1, 2, 3, 4, 127, 128, 129, 511, 512, 513, 1023,
+ 1024, 1025, 2047, 2048, 2049, 4095, 4096, 4097, 8191, 8192, 8193, 16383,
+ 16384, 16385};
+static int lengths_count = sizeof(lengths) / sizeof(off_t);
+
+int
+main(int argc, char *argv[])
+{
+ int error, fd, fds[2], i, read_only_fd;
+ char path[PATH_MAX];
+ struct stat sb;
+ ssize_t size;
+ off_t len;
+ char ch;
+
+ /*
+ * Tests using a writable temporary file: grow and then shrink a file
+ * using ftruncate and various lengths. Make sure that a negative
+ * file length is rejected. Make sure that when we grow the file,
+ * bytes now in the range of the file size return 0.
+ *
+ * Save a read-only reference to the file to use later for read-only
+ * descriptor tests.
+ */
+ snprintf(path, PATH_MAX, "/tmp/ftruncate.XXXXXXXXXXXXX");
+ fd = mkstemp(path);
+ if (fd < 0)
+ err(-1, "mkstemp");
+ read_only_fd = open(path, O_RDONLY);
+ if (read_only_fd < 0) {
+ error = errno;
+ (void)unlink(path);
+ errno = error;
+ err(-1, "open(%s, O_RDONLY)", path);
+ }
+ (void)unlink(path);
+
+ if (ftruncate(fd, -1) == 0)
+ errx(-1, "ftruncate(fd, -1) succeeded");
+ if (errno != EINVAL)
+ err(-1, "ftruncate(fd, -1) returned wrong error");
+
+ for (i = 0; i < lengths_count; i++) {
+ len = lengths[i];
+ if (ftruncate(fd, len) < 0)
+ err(-1, "ftruncate(%jd) up", (intmax_t)len);
+ if (fstat(fd, &sb) < 0)
+ err(-1, "stat");
+ if (sb.st_size != len)
+ errx(-1, "fstat with len=%jd returned len %jd up",
+ (intmax_t)len, (intmax_t)sb.st_size);
+ if (len != 0) {
+ size = pread(fd, &ch, sizeof(ch), len - 1);
+ if (size < 0)
+ err(-1, "pread on len %jd up", (intmax_t)len);
+ if (size != sizeof(ch))
+ errx(-1, "pread len %jd size %jd up",
+ (intmax_t)len, (intmax_t)size);
+ if (ch != 0)
+ errx(-1,
+ "pread length %jd size %jd ch %d up",
+ (intmax_t)len, (intmax_t)size, ch);
+ }
+ }
+
+ for (i = lengths_count - 1; i >= 0; i--) {
+ len = lengths[i];
+ if (ftruncate(fd, len) < 0)
+ err(-1, "ftruncate(%jd) down", (intmax_t)len);
+ if (fstat(fd, &sb) < 0)
+ err(-1, "stat");
+ if (sb.st_size != len)
+ errx(-1, "fstat(%jd) returned %jd down", (intmax_t)len,
+ sb.st_size);
+ }
+ close(fd);
+
+ /*
+ * Make sure that a read-only descriptor can't be truncated.
+ */
+ if (ftruncate(read_only_fd, 0) == 0)
+ errx(-1, "ftruncate(read_only_fd) succeeded");
+ if (errno != EINVAL)
+ err(-1, "ftruncate(read_only_fd) returned wrong error");
+ close(read_only_fd);
+
+ /*
+ * Make sure that ftruncate on sockets doesn't work.
+ */
+ fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ err(-1, "socket(PF_UNIX, SOCK_STREAM, 0)");
+ if (ftruncate(fd, 0) == 0)
+ errx(-1, "ftruncate(socket) succeeded");
+ if (errno != EINVAL)
+ err(-1, "ftruncate(socket) returned wrong error");
+ close(fd);
+
+ /*
+ * Make sure that ftruncate on pipes doesn't work.
+ */
+ if (pipe(fds) < 0)
+ err(-1, "pipe");
+ if (ftruncate(fds[0], 0) == 0)
+ errx(-1, "ftruncate(pipe) succeeded");
+ if (errno != EINVAL)
+ err(-1, "ftruncate(pipe) returned wrong error");
+ close(fds[0]);
+ close(fds[1]);
+
+ /*
+ * Make sure that ftruncate on kqueues doesn't work.
+ */
+ fd = kqueue();
+ if (fd < 0)
+ err(-1, "kqueue");
+ if (ftruncate(fds[0], 0) == 0)
+ errx(-1, "ftruncate(kqueue) succeeded");
+ if (errno != EINVAL)
+ err(-1, "ftruncate(kqueue) returned wrong error");
+ close(fd);
+
+ return (0);
+}
diff --git a/tests/sys/file/newfileops_on_fork_test.c b/tests/sys/file/newfileops_on_fork_test.c
new file mode 100644
index 000000000000..85eea9dac96f
--- /dev/null
+++ b/tests/sys/file/newfileops_on_fork_test.c
@@ -0,0 +1,121 @@
+/*-
+ * Copyright (c) 2009 Robert N. M. Watson
+ * All rights reserved.
+ *
+ * This software was developed at the University of Cambridge Computer
+ * Laboratory with support from a grant from Google, 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 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$
+ */
+
+/*
+ * When a multi-threaded application calls fork(2) from one thread while
+ * another thread is blocked in accept(2), we prefer that the file descriptor
+ * to be returned by accept(2) not appear in the child process. Test this by
+ * creating a thread blocked in accept(2), then forking a child and seeing if
+ * the fd it would have returned is defined in the child or not.
+ */
+
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define PORT 9000
+
+static int listen_fd;
+
+static void *
+do_accept(__unused void *arg)
+{
+ int accept_fd;
+
+ accept_fd = accept(listen_fd, NULL, NULL);
+ if (accept_fd < 0)
+ err(-1, "accept");
+
+ return (NULL);
+}
+
+static void
+do_fork(void)
+{
+ int pid;
+
+ pid = fork();
+ if (pid < 0)
+ err(-1, "fork");
+ if (pid > 0) {
+ waitpid(pid, NULL, 0);
+ exit(0);
+ }
+
+ /*
+ * We will call ftruncate(2) on the next available file descriptor,
+ * listen_fd+1, and get back EBADF if it's not a valid descriptor,
+ * and EINVAL if it is. This (currently) works fine in practice.
+ */
+ if (ftruncate(listen_fd + 1, 0 < 0)) {
+ if (errno == EBADF)
+ exit(0);
+ else if (errno == EINVAL)
+ errx(-1, "file descriptor still open in child");
+ else
+ err(-1, "unexpected error");
+ } else
+ errx(-1, "ftruncate succeeded");
+}
+
+int
+main(__unused int argc, __unused char *argv[])
+{
+ struct sockaddr_in sin;
+ pthread_t accept_thread;
+
+ listen_fd = socket(PF_INET, SOCK_STREAM, 0);
+ if (listen_fd < 0)
+ err(-1, "socket");
+ bzero(&sin, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_len = sizeof(sin);
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sin.sin_port = htons(PORT);
+ if (bind(listen_fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ err(-1, "bind");
+ if (listen(listen_fd, -1) <0)
+ err(-1, "listen");
+ if (pthread_create(&accept_thread, NULL, do_accept, NULL) != 0)
+ err(-1, "pthread_create");
+ sleep(1); /* Easier than using a CV. */
+ do_fork();
+ exit(0);
+}
diff --git a/tests/sys/fs/Makefile b/tests/sys/fs/Makefile
new file mode 100644
index 000000000000..c82ee143a785
--- /dev/null
+++ b/tests/sys/fs/Makefile
@@ -0,0 +1,23 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/fs
+
+TESTSRC= ${SRCTOP}/contrib/netbsd-tests/fs
+
+#TESTS_SUBDIRS+= nullfs # XXX: needs rump
+TESTS_SUBDIRS+= tmpfs
+
+${PACKAGE}FILES+= h_funcs.subr
+${PACKAGE}FILESDIR= ${TESTSDIR}
+
+CLEANFILES+= h_funcs.subr
+CLEANFILES+= h_funcs.subr.tmp
+
+h_funcs.subr: ${TESTSRC}/h_funcs.subr
+ cat ${.ALLSRC} | \
+ sed -e '/atf_require_prog mount_$${name}/d' >>${.TARGET}.tmp
+ mv ${.TARGET}.tmp ${.TARGET}
+
+.include <bsd.test.mk>
diff --git a/tests/sys/fs/tmpfs/Makefile b/tests/sys/fs/tmpfs/Makefile
new file mode 100644
index 000000000000..63facd1d02fb
--- /dev/null
+++ b/tests/sys/fs/tmpfs/Makefile
@@ -0,0 +1,56 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+FILESYSTEM?= ${.CURDIR:T}
+
+TESTSDIR= ${TESTSBASE}/sys/fs/${FILESYSTEM}
+
+TESTSRC= ${SRCTOP}/contrib/netbsd-tests/fs/${FILESYSTEM}
+
+# XXX: uses /dev/MAKEDEV to create pseudo /dev/{null,zero} character devices
+#NETBSD_ATF_TESTS_SH+= devices_test
+NETBSD_ATF_TESTS_SH+= create_test
+NETBSD_ATF_TESTS_SH+= read_write_test
+NETBSD_ATF_TESTS_SH+= dots_test
+NETBSD_ATF_TESTS_SH+= exec_test
+NETBSD_ATF_TESTS_SH+= link_test
+NETBSD_ATF_TESTS_SH+= mkdir_test
+NETBSD_ATF_TESTS_SH+= mknod_test
+NETBSD_ATF_TESTS_SH+= mount_test
+# XXX: need to replace `mknod ... p` with something more sensible
+#NETBSD_ATF_TESTS_SH+= pipes_test
+NETBSD_ATF_TESTS_SH+= trail_slash_test
+NETBSD_ATF_TESTS_SH+= readdir_test
+NETBSD_ATF_TESTS_SH+= remove_test
+NETBSD_ATF_TESTS_SH+= rename_test
+NETBSD_ATF_TESTS_SH+= rmdir_test
+NETBSD_ATF_TESTS_SH+= setattr_test
+NETBSD_ATF_TESTS_SH+= sizes_test
+NETBSD_ATF_TESTS_SH+= sockets_test
+NETBSD_ATF_TESTS_SH+= statvfs_test
+NETBSD_ATF_TESTS_SH+= symlink_test
+NETBSD_ATF_TESTS_SH+= times_test
+NETBSD_ATF_TESTS_SH+= truncate_test
+NETBSD_ATF_TESTS_SH+= vnd_test
+NETBSD_ATF_TESTS_SH+= vnode_leak_test
+
+${PACKAGE}FILES+= h_funcs.subr
+${PACKAGE}FILESDIR= ${TESTSDIR}
+
+PROGS+= h_tools
+BINDIR.h_tools= ${TESTSDIR}
+
+ATF_TESTS_SH_SED_mount_test= \
+ -e 's,-o -g,-o gid=,g' \
+ -e 's,-o -m,-o mode=,g' \
+ -e 's,-o -s,-o size=,g' \
+ -e 's,-o -u,-o uid=,g' \
+ -e 's,mount_${FILESYSTEM},mount -t ${FILESYSTEM},g'
+ATF_TESTS_SH_SED_sizes_test= -e 's,-o -s,-o size=,g'
+ATF_TESTS_SH_SED_statvfs_test= -e 's,-o -s,-o size=,g'
+ATF_TESTS_SH_SED_vnode_leak_test= -e 's,-o -s,-o size=,g'
+
+.include <netbsd-tests.test.mk>
+
+.include <bsd.test.mk>
diff --git a/tests/sys/geom/Makefile b/tests/sys/geom/Makefile
new file mode 100644
index 000000000000..bf8604dcabc3
--- /dev/null
+++ b/tests/sys/geom/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/geom
+
+TESTS_SUBDIRS+= class
+
+.include <bsd.test.mk>
diff --git a/tests/sys/geom/Makefile.depend b/tests/sys/geom/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/geom/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/geom/class/Makefile b/tests/sys/geom/class/Makefile
new file mode 100644
index 000000000000..b633fd081ee1
--- /dev/null
+++ b/tests/sys/geom/class/Makefile
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/geom/class
+
+TESTS_SUBDIRS+= concat
+TESTS_SUBDIRS+= eli
+TESTS_SUBDIRS+= gate
+# XXX: might not work due to geom(4) changes; more investigation's needed
+#TESTS_SUBDIRS+= gpt
+TESTS_SUBDIRS+= mirror
+TESTS_SUBDIRS+= nop
+TESTS_SUBDIRS+= raid3
+TESTS_SUBDIRS+= shsec
+TESTS_SUBDIRS+= stripe
+TESTS_SUBDIRS+= uzip
+
+${PACKAGE}FILES+= geom_subr.sh
+
+.include <bsd.test.mk>
diff --git a/tests/sys/geom/class/Makefile.depend b/tests/sys/geom/class/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/geom/class/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/geom/class/concat/1_test.sh b/tests/sys/geom/class/concat/1_test.sh
new file mode 100644
index 000000000000..ef80a61bc18e
--- /dev/null
+++ b/tests/sys/geom/class/concat/1_test.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo '1..1'
+
+us0=$(attach_md -t malloc -s 1M) || exit 1
+us1=$(attach_md -t malloc -s 2M) || exit 1
+us2=$(attach_md -t malloc -s 3M) || exit 1
+
+gconcat create $name /dev/$us0 /dev/$us1 /dev/$us2 || exit 1
+devwait
+
+# Size of created device should be 1MB + 2MB + 3MB.
+
+size=`diskinfo /dev/concat/${name} | awk '{print $3}'`
+
+if [ $size -eq 6291456 ]; then
+ echo "ok - Size is 6291456"
+else
+ echo "not ok - Size is 6291456"
+fi
diff --git a/tests/sys/geom/class/concat/2_test.sh b/tests/sys/geom/class/concat/2_test.sh
new file mode 100644
index 000000000000..95636be860eb
--- /dev/null
+++ b/tests/sys/geom/class/concat/2_test.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo '1..1'
+
+tsize=6
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+us0=$(attach_md -t malloc -s 1M) || exit 1
+us1=$(attach_md -t malloc -s 2M) || exit 1
+us2=$(attach_md -t malloc -s 3M) || exit 1
+
+dd if=/dev/random of=${src} bs=1m count=$tsize >/dev/null 2>&1
+
+gconcat create $name /dev/$us0 /dev/$us1 /dev/$us2 || exit 1
+devwait
+
+dd if=${src} of=/dev/concat/${name} bs=1m count=$tsize >/dev/null 2>&1
+dd if=/dev/concat/${name} of=${dst} bs=1m count=$tsize >/dev/null 2>&1
+
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok - md5 checksum comparison"
+else
+ echo "ok - md5 checksum comparison"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/concat/Makefile b/tests/sys/geom/class/concat/Makefile
new file mode 100644
index 000000000000..b0849962aa01
--- /dev/null
+++ b/tests/sys/geom/class/concat/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/geom/class/${.CURDIR:T}
+
+TAP_TESTS_SH+= 1_test
+TAP_TESTS_SH+= 2_test
+
+${PACKAGE}FILES+= conf.sh
+
+.for t in ${TAP_TESTS_SH}
+TEST_METADATA.$t+= required_user="root"
+.endfor
+
+.include <bsd.test.mk>
diff --git a/tests/sys/geom/class/concat/Makefile.depend b/tests/sys/geom/class/concat/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/geom/class/concat/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/geom/class/concat/conf.sh b/tests/sys/geom/class/concat/conf.sh
new file mode 100644
index 000000000000..374ed12aecc2
--- /dev/null
+++ b/tests/sys/geom/class/concat/conf.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+# $FreeBSD$
+
+name="$(mktemp -u concat.XXXXXX)"
+class="concat"
+base=`basename $0`
+
+gconcat_test_cleanup()
+{
+ [ -c /dev/$class/$name ] && gconcat destroy $name
+ geom_test_cleanup
+}
+trap gconcat_test_cleanup ABRT EXIT INT TERM
+
+. `dirname $0`/../geom_subr.sh
diff --git a/tests/sys/geom/class/eli/Makefile b/tests/sys/geom/class/eli/Makefile
new file mode 100644
index 000000000000..f108f877aabd
--- /dev/null
+++ b/tests/sys/geom/class/eli/Makefile
@@ -0,0 +1,43 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/geom/class/${.CURDIR:T}
+
+TAP_TESTS_SH+= attach_d_test
+TAP_TESTS_SH+= configure_b_B_test
+TAP_TESTS_SH+= delkey_test
+TAP_TESTS_SH+= detach_l_test
+TAP_TESTS_SH+= init_B_test
+TAP_TESTS_SH+= init_J_test
+TAP_TESTS_SH+= init_a_test
+TAP_TESTS_SH+= init_alias_test
+TAP_TESTS_SH+= init_i_P_test
+TAP_TESTS_SH+= init_test
+TAP_TESTS_SH+= integrity_copy_test
+TAP_TESTS_SH+= integrity_data_test
+TAP_TESTS_SH+= integrity_hmac_test
+TAP_TESTS_SH+= kill_test
+TAP_TESTS_SH+= nokey_test
+TAP_TESTS_SH+= onetime_a_test
+TAP_TESTS_SH+= onetime_d_test
+TAP_TESTS_SH+= onetime_test
+TAP_TESTS_SH+= readonly_test
+TAP_TESTS_SH+= resize_test
+TAP_TESTS_SH+= setkey_test
+
+TEST_METADATA.init_a_test+= timeout="3600"
+TEST_METADATA.init_test+= timeout="600"
+TEST_METADATA.integrity_copy_test+= timeout="3600"
+TEST_METADATA.integrity_data_test+= timeout="1800"
+TEST_METADATA.integrity_hmac_test+= timeout="1800"
+TEST_METADATA.onetime_a_test+= timeout="1800"
+TEST_METADATA.onetime_test+= timeout="1800"
+
+${PACKAGE}FILES+= conf.sh
+
+.for t in ${TAP_TESTS_SH}
+TEST_METADATA.$t+= required_user="root"
+.endfor
+
+.include <bsd.test.mk>
diff --git a/tests/sys/geom/class/eli/Makefile.depend b/tests/sys/geom/class/eli/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/geom/class/eli/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/geom/class/eli/attach_d_test.sh b/tests/sys/geom/class/eli/attach_d_test.sh
new file mode 100644
index 000000000000..5d700b3270c7
--- /dev/null
+++ b/tests/sys/geom/class/eli/attach_d_test.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+keyfile=`mktemp $base.XXXXXX` || exit 1
+mdconfig -a -t malloc -s `expr $sectors + 1` -u $no || exit 1
+
+echo "1..3"
+
+dd if=/dev/random of=${keyfile} bs=512 count=16 >/dev/null 2>&1
+
+geli init -B none -P -K $keyfile md${no}
+geli attach -d -p -k $keyfile md${no}
+if [ -c /dev/md${no}.eli ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
+# Be sure it doesn't detach on read.
+dd if=/dev/md${no}.eli of=/dev/null 2>/dev/null
+sleep 1
+if [ -c /dev/md${no}.eli ]; then
+ echo "ok 2"
+else
+ echo "not ok 2"
+fi
+true > /dev/md${no}.eli
+sleep 1
+if [ ! -c /dev/md${no}.eli ]; then
+ echo "ok 3"
+else
+ echo "not ok 3"
+fi
+
+rm -f $keyfile
diff --git a/tests/sys/geom/class/eli/conf.sh b/tests/sys/geom/class/eli/conf.sh
new file mode 100755
index 000000000000..5ac291b2e15d
--- /dev/null
+++ b/tests/sys/geom/class/eli/conf.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+# $FreeBSD$
+
+class="eli"
+base=`basename $0`
+
+# We need to use linear probing in order to detect the first available md(4)
+# device instead of using mdconfig -a -t, because geli(8) attachs md(4) devices
+no=0
+while [ -c /dev/md$no ]; do
+ : $(( no += 1 ))
+done
+
+# Execute `func` for each combination of cipher, sectorsize, and hmac algo
+# `func` usage should be:
+# func <cipher> <aalgo> <secsize>
+for_each_geli_config() {
+ func=$1
+
+ for cipher in aes-xts:128 aes-xts:256 \
+ aes-cbc:128 aes-cbc:192 aes-cbc:256 \
+ 3des-cbc:192 \
+ blowfish-cbc:128 blowfish-cbc:160 blowfish-cbc:192 \
+ blowfish-cbc:224 blowfish-cbc:256 blowfish-cbc:288 \
+ blowfish-cbc:320 blowfish-cbc:352 blowfish-cbc:384 \
+ blowfish-cbc:416 blowfish-cbc:448 \
+ camellia-cbc:128 camellia-cbc:192 camellia-cbc:256; do
+ ealgo=${cipher%%:*}
+ keylen=${cipher##*:}
+ for aalgo in hmac/md5 hmac/sha1 hmac/ripemd160 hmac/sha256 \
+ hmac/sha384 hmac/sha512; do
+ for secsize in 512 1024 2048 4096 8192; do
+ ${func} $cipher $aalgo $secsize
+ done
+ done
+ done
+}
+
+# Execute `func` for each combination of cipher, and sectorsize, with no hmac
+# `func` usage should be:
+# func <cipher> <secsize>
+for_each_geli_config_nointegrity() {
+ func=$1
+
+ for cipher in aes-xts:128 aes-xts:256 \
+ aes-cbc:128 aes-cbc:192 aes-cbc:256 \
+ 3des-cbc:192 \
+ blowfish-cbc:128 blowfish-cbc:160 blowfish-cbc:192 \
+ blowfish-cbc:224 blowfish-cbc:256 blowfish-cbc:288 \
+ blowfish-cbc:320 blowfish-cbc:352 blowfish-cbc:384 \
+ blowfish-cbc:416 blowfish-cbc:448 \
+ camellia-cbc:128 camellia-cbc:192 camellia-cbc:256; do
+ ealgo=${cipher%%:*}
+ keylen=${cipher##*:}
+ for secsize in 512 1024 2048 4096 8192; do
+ ${func} $cipher $aalgo $secsize
+ done
+ done
+}
+
+
+geli_test_cleanup()
+{
+ [ -c /dev/md${no}.eli ] && geli detach md${no}.eli
+ mdconfig -d -u $no
+}
+trap geli_test_cleanup ABRT EXIT INT TERM
+
+. `dirname $0`/../geom_subr.sh
diff --git a/tests/sys/geom/class/eli/configure_b_B_test.sh b/tests/sys/geom/class/eli/configure_b_B_test.sh
new file mode 100644
index 000000000000..b6cdf4fe1d9a
--- /dev/null
+++ b/tests/sys/geom/class/eli/configure_b_B_test.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+mdconfig -a -t malloc -s `expr $sectors + 1` -u $no || exit 1
+
+echo "1..17"
+
+geli init -B none -P -K /dev/null md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
+
+geli dump md${no} | egrep 'flags: 0x0$' >/dev/null
+if [ $? -eq 0 ]; then
+ echo "ok 2"
+else
+ echo "not ok 2"
+fi
+
+geli init -B none -b -P -K /dev/null md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 3"
+else
+ echo "not ok 3"
+fi
+
+geli dump md${no} | egrep 'flags: 0x2$' >/dev/null
+if [ $? -eq 0 ]; then
+ echo "ok 4"
+else
+ echo "not ok 4"
+fi
+
+geli configure -B md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 5"
+else
+ echo "not ok 5"
+fi
+
+geli dump md${no} | egrep 'flags: 0x0$' >/dev/null
+if [ $? -eq 0 ]; then
+ echo "ok 6"
+else
+ echo "not ok 6"
+fi
+
+geli configure -b md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 7"
+else
+ echo "not ok 7"
+fi
+
+geli dump md${no} | egrep 'flags: 0x2$' >/dev/null
+if [ $? -eq 0 ]; then
+ echo "ok 8"
+else
+ echo "not ok 8"
+fi
+
+geli attach -p -k /dev/null md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 9"
+else
+ echo "not ok 9"
+fi
+
+geli list md${no}.eli | egrep '^Flags: .*BOOT' >/dev/null
+if [ $? -eq 0 ]; then
+ echo "ok 10"
+else
+ echo "not ok 10"
+fi
+
+geli configure -B md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 11"
+else
+ echo "not ok 11"
+fi
+
+geli list md${no}.eli | egrep '^Flags: .*BOOT' >/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 12"
+else
+ echo "not ok 12"
+fi
+
+geli dump md${no} | egrep 'flags: 0x0$' >/dev/null
+if [ $? -eq 0 ]; then
+ echo "ok 13"
+else
+ echo "not ok 13"
+fi
+
+geli configure -b md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 14"
+else
+ echo "not ok 14"
+fi
+
+geli list md${no}.eli | egrep '^Flags: .*BOOT' >/dev/null
+if [ $? -eq 0 ]; then
+ echo "ok 15"
+else
+ echo "not ok 15"
+fi
+
+geli dump md${no} | egrep 'flags: 0x2$' >/dev/null
+if [ $? -eq 0 ]; then
+ echo "ok 16"
+else
+ echo "not ok 16"
+fi
+
+geli detach md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 17"
+else
+ echo "not ok 17"
+fi
diff --git a/tests/sys/geom/class/eli/delkey_test.sh b/tests/sys/geom/class/eli/delkey_test.sh
new file mode 100644
index 000000000000..67b253efd0d8
--- /dev/null
+++ b/tests/sys/geom/class/eli/delkey_test.sh
@@ -0,0 +1,140 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+keyfile1=`mktemp $base.XXXXXX` || exit 1
+keyfile2=`mktemp $base.XXXXXX` || exit 1
+keyfile3=`mktemp $base.XXXXXX` || exit 1
+keyfile4=`mktemp $base.XXXXXX` || exit 1
+mdconfig -a -t malloc -s `expr $sectors + 1` -u $no || exit 1
+
+echo "1..14"
+
+dd if=/dev/random of=${keyfile1} bs=512 count=16 >/dev/null 2>&1
+dd if=/dev/random of=${keyfile2} bs=512 count=16 >/dev/null 2>&1
+dd if=/dev/random of=${keyfile3} bs=512 count=16 >/dev/null 2>&1
+dd if=/dev/random of=${keyfile4} bs=512 count=16 >/dev/null 2>&1
+
+geli init -B none -P -K $keyfile1 md${no}
+geli attach -p -k $keyfile1 md${no}
+geli setkey -n 1 -P -K $keyfile2 md${no}
+
+# Remove key 0 for attached provider.
+geli delkey -n 0 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
+geli detach md${no}
+
+# We cannot use keyfile1 anymore.
+geli attach -p -k $keyfile1 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 2"
+else
+ echo "not ok 2"
+fi
+
+# Attach with key 1.
+geli attach -p -k $keyfile2 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 3"
+else
+ echo "not ok 3"
+fi
+
+# We cannot remove last key without -f option (for attached provider).
+geli delkey -n 1 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 4"
+else
+ echo "not ok 4"
+fi
+
+# Remove last key for attached provider.
+geli delkey -f -n 1 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 5"
+else
+ echo "not ok 5"
+fi
+
+# If there are no valid keys, but provider is attached, we can save situation.
+geli setkey -n 0 -P -K $keyfile3 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 6"
+else
+ echo "not ok 6"
+fi
+geli detach md${no}
+
+# We cannot use keyfile2 anymore.
+geli attach -p -k $keyfile2 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 7"
+else
+ echo "not ok 7"
+fi
+
+# Attach with key 0.
+geli attach -p -k $keyfile3 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 8"
+else
+ echo "not ok 8"
+fi
+
+# Setup key 1.
+geli setkey -n 1 -P -K $keyfile4 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 9"
+else
+ echo "not ok 9"
+fi
+geli detach md${no}
+
+# Remove key 1 for detached provider.
+geli delkey -n 1 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 10"
+else
+ echo "not ok 10"
+fi
+
+# We cannot use keyfile4 anymore.
+geli attach -p -k $keyfile4 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 11"
+else
+ echo "not ok 11"
+fi
+
+# We cannot remove last key without -f option (for detached provider).
+geli delkey -n 0 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 12"
+else
+ echo "not ok 12"
+fi
+
+# Remove last key for detached provider.
+geli delkey -f -n 0 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 13"
+else
+ echo "not ok 13"
+fi
+
+# We cannot use keyfile3 anymore.
+geli attach -p -k $keyfile3 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 14"
+else
+ echo "not ok 14"
+fi
+
+rm -f $keyfile1 $keyfile2 $keyfile3 $keyfile4
diff --git a/tests/sys/geom/class/eli/detach_l_test.sh b/tests/sys/geom/class/eli/detach_l_test.sh
new file mode 100644
index 000000000000..605ae94e6bf0
--- /dev/null
+++ b/tests/sys/geom/class/eli/detach_l_test.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+keyfile=`mktemp $base.XXXXXX` || exit 1
+mdconfig -a -t malloc -s `expr $sectors + 1` -u $no || exit 1
+
+echo "1..4"
+
+dd if=/dev/random of=${keyfile} bs=512 count=16 >/dev/null 2>&1
+
+geli init -B none -P -K $keyfile md${no}
+geli attach -p -k $keyfile md${no}
+if [ -c /dev/md${no}.eli ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
+# Be sure it doesn't detach before 'detach -l'.
+dd if=/dev/md${no}.eli of=/dev/null 2>/dev/null
+sleep 1
+if [ -c /dev/md${no}.eli ]; then
+ echo "ok 2"
+else
+ echo "not ok 2"
+fi
+geli detach -l md${no}
+if [ -c /dev/md${no}.eli ]; then
+ echo "ok 3"
+else
+ echo "not ok 3"
+fi
+dd if=/dev/md${no}.eli of=/dev/null 2>/dev/null
+sleep 1
+if [ ! -c /dev/md${no}.eli ]; then
+ echo "ok 4"
+else
+ echo "not ok 4"
+fi
+
+rm -f $keyfile
diff --git a/tests/sys/geom/class/eli/init_B_test.sh b/tests/sys/geom/class/eli/init_B_test.sh
new file mode 100644
index 000000000000..3ba743cfbc64
--- /dev/null
+++ b/tests/sys/geom/class/eli/init_B_test.sh
@@ -0,0 +1,104 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+keyfile=`mktemp $base.XXXXXX` || exit 1
+backupfile=`mktemp $base.XXXXXX` || exit 1
+
+echo "1..13"
+
+dd if=/dev/random of=${keyfile} bs=512 count=16 >/dev/null 2>&1
+
+mdconfig -a -t malloc -s $sectors -u $no || exit 1
+
+# -B none
+rm -f /var/backups/md${no}.eli
+geli init -B none -P -K $keyfile md${no} 2>/dev/null
+if [ ! -f /var/backups/md${no}.eli ]; then
+ echo "ok 1 - -B none"
+else
+ echo "not ok 1 - -B none"
+fi
+
+# no -B
+rm -f /var/backups/md${no}.eli
+geli init -P -K $keyfile md${no} >/dev/null 2>&1
+if [ -f /var/backups/md${no}.eli ]; then
+ echo "ok 2 - no -B"
+else
+ echo "not ok 2 - no -B"
+fi
+geli clear md${no}
+geli attach -p -k $keyfile md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 3 - no -B"
+else
+ echo "not ok 3 - no -B"
+fi
+if [ ! -c /dev/md${no}.eli ]; then
+ echo "ok 4 - no -B"
+else
+ echo "not ok 4 - no -B"
+fi
+geli restore /var/backups/md${no}.eli md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 5 - no -B"
+else
+ echo "not ok 5 - no -B"
+fi
+geli attach -p -k $keyfile md${no} 2>/dev/null
+if [ $? -eq 0 ]; then
+ echo "ok 6 - no -B"
+else
+ echo "not ok 6 - no -B"
+fi
+if [ -c /dev/md${no}.eli ]; then
+ echo "ok 7 - no -B"
+else
+ echo "not ok 7 - no -B"
+fi
+geli detach md${no}
+rm -f /var/backups/md${no}.eli
+
+# -B file
+rm -f $backupfile
+geli init -B $backupfile -P -K $keyfile md${no} >/dev/null 2>&1
+if [ -f $backupfile ]; then
+ echo "ok 8 - -B file"
+else
+ echo "not ok 8 - -B file"
+fi
+geli clear md${no}
+geli attach -p -k $keyfile md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 9 - -B file"
+else
+ echo "not ok 9 - -B file"
+fi
+if [ ! -c /dev/md${no}.eli ]; then
+ echo "ok 10 - -B file"
+else
+ echo "not ok 10 - -B file"
+fi
+geli restore $backupfile md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 11 - -B file"
+else
+ echo "not ok 11 - -B file"
+fi
+geli attach -p -k $keyfile md${no} 2>/dev/null
+if [ $? -eq 0 ]; then
+ echo "ok 12 - -B file"
+else
+ echo "not ok 12 - -B file"
+fi
+if [ -c /dev/md${no}.eli ]; then
+ echo "ok 13 - -B file"
+else
+ echo "not ok 13 - -B file"
+fi
+
+rm -f $backupfile $keyfile
diff --git a/tests/sys/geom/class/eli/init_J_test.sh b/tests/sys/geom/class/eli/init_J_test.sh
new file mode 100644
index 000000000000..266a3d537e03
--- /dev/null
+++ b/tests/sys/geom/class/eli/init_J_test.sh
@@ -0,0 +1,126 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+keyfile0=`mktemp $base.XXXXXX` || exit 1
+keyfile1=`mktemp $base.XXXXXX` || exit 1
+passfile0=`mktemp $base.XXXXXX` || exit 1
+passfile1=`mktemp $base.XXXXXX` || exit 1
+mdconfig -a -t malloc -s `expr $sectors + 1` -u $no || exit 1
+
+echo "1..150"
+
+dd if=/dev/random of=${keyfile0} bs=512 count=16 >/dev/null 2>&1
+dd if=/dev/random of=${keyfile1} bs=512 count=16 >/dev/null 2>&1
+dd if=/dev/random bs=512 count=16 2>/dev/null | sha1 > ${passfile0}
+dd if=/dev/random bs=512 count=16 2>/dev/null | sha1 > ${passfile1}
+
+i=1
+for iter in -1 0 64; do
+ geli init -i ${iter} -B none -J ${passfile0} -P md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli init -i ${iter} -B none -J ${passfile0} -P -K ${keyfile0} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli init -i ${iter} -B none -J ${passfile0} -K ${keyfile0} md${no} 2>/dev/null || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile0} -p md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -j ${passfile0} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -j ${keyfile0} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${passfile0} -p md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -j ${keyfile0} -k ${passfile0} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -j ${keyfile0} -k ${keyfile0} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -j ${passfile0} -k ${passfile0} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -j ${passfile0} -k ${keyfile0} md${no} 2>/dev/null || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli detach md${no} || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ cat ${keyfile0} | geli attach -j ${passfile0} -k - md${no} 2>/dev/null || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli detach md${no} || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ cat ${passfile0} | geli attach -j - -k ${keyfile0} md${no} 2>/dev/null || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli detach md${no} || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+
+ geli init -i ${iter} -B none -J ${passfile0} -J ${passfile1} -P md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli init -i ${iter} -B none -J ${passfile0} -J ${passfile1} -P -K ${keyfile0} -K ${keyfile1} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli init -i ${iter} -B none -J ${passfile0} -J ${passfile1} -K ${keyfile0} -K ${keyfile1} md${no} 2>/dev/null || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile0} -p md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile1} -p md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -j ${passfile0} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -j ${passfile1} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile0} -k ${keyfile1} -p md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -j ${passfile0} -j ${passfile1} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile0} -j ${passfile0} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile0} -j ${passfile1} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile1} -j ${passfile0} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile1} -j ${passfile1} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile0} -j ${passfile0} -j ${passfile1} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile1} -j ${passfile0} -j ${passfile1} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile0} -k ${keyfile1} -j ${passfile0} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile0} -k ${keyfile1} -j ${passfile1} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile1} -k ${keyfile0} -j ${passfile0} -j ${passfile1} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile0} -k ${keyfile1} -j ${passfile1} -j ${passfile0} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -k ${keyfile1} -k ${keyfile0} -j ${passfile1} -j ${passfile0} md${no} 2>/dev/null && echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli attach -j ${passfile0} -j ${passfile1} -k ${keyfile0} -k ${keyfile1} md${no} 2>/dev/null || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli detach md${no} || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ cat ${passfile0} | geli attach -j - -j ${passfile1} -k ${keyfile0} -k ${keyfile1} md${no} 2>/dev/null || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli detach md${no} || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ cat ${passfile1} | geli attach -j ${passfile0} -j - -k ${keyfile0} -k ${keyfile1} md${no} 2>/dev/null || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli detach md${no} || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ cat ${keyfile0} | geli attach -j ${passfile0} -j ${passfile1} -k - -k ${keyfile1} md${no} 2>/dev/null || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli detach md${no} || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ cat ${keyfile1} | geli attach -j ${passfile0} -j ${passfile1} -k ${keyfile0} -k - md${no} 2>/dev/null || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli detach md${no} || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ cat ${keyfile0} ${keyfile1} | geli attach -j ${passfile0} -j ${passfile1} -k - md${no} 2>/dev/null || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli detach md${no} || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ cat ${passfile0} ${passfile1} | awk '{printf "%s", $0}' | geli attach -j - -k ${keyfile0} -k ${keyfile1} md${no} 2>/dev/null || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+ geli detach md${no} || echo -n "not "
+ echo "ok ${i}"; i=$((i+1))
+done
+
+rm -f ${keyfile0} ${keyfile1} ${passfile0} ${passfile1}
diff --git a/tests/sys/geom/class/eli/init_a_test.sh b/tests/sys/geom/class/eli/init_a_test.sh
new file mode 100644
index 000000000000..9b5b251c930b
--- /dev/null
+++ b/tests/sys/geom/class/eli/init_a_test.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+keyfile=`mktemp $base.XXXXXX` || exit 1
+rnd=`mktemp $base.XXXXXX` || exit 1
+
+do_test() {
+ cipher=$1
+ aalgo=$2
+ secsize=$3
+ ealgo=${cipher%%:*}
+ keylen=${cipher##*:}
+
+ mdconfig -a -t malloc -s `expr $secsize \* $sectors + 512`b -u $no || exit 1
+ geli init -B none -a $aalgo -e $ealgo -l $keylen -P -K $keyfile -s $secsize md${no} 2>/dev/null
+ geli attach -p -k $keyfile md${no}
+
+ secs=`diskinfo /dev/md${no}.eli | awk '{print $4}'`
+
+ dd if=${rnd} of=/dev/md${no}.eli bs=${secsize} count=${secs} 2>/dev/null
+
+ md_rnd=`dd if=${rnd} bs=${secsize} count=${secs} 2>/dev/null | md5`
+ md_ddev=`dd if=/dev/md${no}.eli bs=${secsize} count=${secs} 2>/dev/null | md5`
+
+ if [ ${md_rnd} = ${md_ddev} ]; then
+ echo "ok $i - aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ else
+ echo "not ok $i - aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ fi
+ i=$((i+1))
+
+ geli detach md${no}
+ mdconfig -d -u $no
+}
+
+echo "1..600"
+
+i=1
+
+dd if=/dev/random of=${keyfile} bs=512 count=16 >/dev/null 2>&1
+dd if=/dev/random of=${rnd} bs=8192 count=${sectors} >/dev/null 2>&1
+
+for_each_geli_config do_test
+
+rm -f $rnd
+rm -f $keyfile
diff --git a/tests/sys/geom/class/eli/init_alias_test.sh b/tests/sys/geom/class/eli/init_alias_test.sh
new file mode 100755
index 000000000000..0422bee07065
--- /dev/null
+++ b/tests/sys/geom/class/eli/init_alias_test.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+# $FreeBSD$
+
+# Test "geli init"'s various cipher aliases
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+keyfile=`mktemp $base.XXXXXX` || exit 1
+rnd=`mktemp $base.XXXXXX` || exit 1
+
+do_test() {
+ ealgo=$1
+ keylen=$2
+ expected_ealgo=$3
+ expected_keylen=$4
+
+ geli init -B none -e $ealgo -l $keylen -P -K $keyfile md${no} 2>/dev/null
+ geli attach -p -k $keyfile md${no}
+ real_ealgo=`geli list md${no}.eli | awk '/EncryptionAlgorithm/ {print $2}'`
+ real_keylen=`geli list md${no}.eli | awk '/KeyLength/ {print $2}'`
+
+ if [ ${real_ealgo} = ${expected_ealgo} ]; then
+ echo "ok $i - ${ealgo} aliased to ${real_ealgo}"
+ else
+ echo "not ok $i - expected ${expected_ealgo} but got ${real_ealgo}"
+ fi
+ i=$((i+1))
+
+ if [ ${real_keylen} = ${expected_keylen} ]; then
+ echo "ok $i - keylen=${keylen} for ealgo=${ealgo} aliases to ${real_keylen}"
+ else
+ echo "not ok $i - expected ${expected_keylen} but got ${real_keylen}"
+ fi
+ i=$((i+1))
+
+ geli detach md${no}
+}
+
+echo "1..38"
+i=1
+mdconfig -a -t malloc -s 1024k -u $no || exit 1
+dd if=/dev/random of=${keyfile} bs=512 count=16 >/dev/null 2>&1
+
+for spec in aes:0:AES-XTS:128 aes:128:AES-XTS:128 aes:256:AES-XTS:256 \
+ 3des:0:3DES-CBC:192 3des:192:3DES-CBC:192 \
+ blowfish:0:Blowfish-CBC:128 blowfish:128:Blowfish-CBC:128 \
+ blowfish:160:Blowfish-CBC:160 blowfish:192:Blowfish-CBC:192 \
+ blowfish:224:Blowfish-CBC:224 blowfish:256:Blowfish-CBC:256 \
+ blowfish:288:Blowfish-CBC:288 blowfish:352:Blowfish-CBC:352 \
+ blowfish:384:Blowfish-CBC:384 blowfish:416:Blowfish-CBC:416 \
+ blowfish:448:Blowfish-CBC:448 \
+ camellia:0:CAMELLIA-CBC:128 camellia:128:CAMELLIA-CBC:128 \
+ camellia:256:CAMELLIA-CBC:256 ; do
+
+ ealgo=`echo $spec | cut -d : -f 1`
+ keylen=`echo $spec | cut -d : -f 2`
+ expected_ealgo=`echo $spec | cut -d : -f 3`
+ expected_keylen=`echo $spec | cut -d : -f 4`
+
+ do_test $ealgo $keylen $expected_ealgo $expected_keylen
+done
+
+rm -f $keyfile
diff --git a/tests/sys/geom/class/eli/init_i_P_test.sh b/tests/sys/geom/class/eli/init_i_P_test.sh
new file mode 100644
index 000000000000..1c59a97d391c
--- /dev/null
+++ b/tests/sys/geom/class/eli/init_i_P_test.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+keyfile=`mktemp $base.XXXXXX` || exit 1
+mdconfig -a -t malloc -s `expr $sectors + 1` -u $no || exit 1
+
+echo "1..1"
+
+dd if=/dev/random of=${keyfile} bs=512 count=16 >/dev/null 2>&1
+
+geli init -B none -i 64 -P -K ${keyfile} md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
+
+rm -f $keyfile
diff --git a/tests/sys/geom/class/eli/init_test.sh b/tests/sys/geom/class/eli/init_test.sh
new file mode 100644
index 000000000000..31fca551436f
--- /dev/null
+++ b/tests/sys/geom/class/eli/init_test.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=32
+keyfile=`mktemp $base.XXXXXX` || exit 1
+rnd=`mktemp $base.XXXXXX` || exit 1
+
+echo "1..200"
+
+do_test() {
+ cipher=$1
+ secsize=$2
+ ealgo=${cipher%%:*}
+ keylen=${cipher##*:}
+
+ mdconfig -a -t malloc -s `expr $secsize \* $sectors + 512`b -u $no || exit 1
+
+ geli init -B none -e $ealgo -l $keylen -P -K $keyfile -s $secsize md${no} 2>/dev/null
+ geli attach -p -k $keyfile md${no}
+
+ secs=`diskinfo /dev/md${no}.eli | awk '{print $4}'`
+
+ dd if=/dev/random of=${rnd} bs=${secsize} count=${secs} >/dev/null 2>&1
+ dd if=${rnd} of=/dev/md${no}.eli bs=${secsize} count=${secs} 2>/dev/null
+
+ md_rnd=`dd if=${rnd} bs=${secsize} count=${secs} 2>/dev/null | md5`
+ md_ddev=`dd if=/dev/md${no}.eli bs=${secsize} count=${secs} 2>/dev/null | md5`
+ md_edev=`dd if=/dev/md${no} bs=${secsize} count=${secs} 2>/dev/null | md5`
+
+ if [ ${md_rnd} = ${md_ddev} ]; then
+ echo "ok $i - ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ else
+ echo "not ok $i - ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ fi
+ i=$((i+1))
+ if [ ${md_rnd} != ${md_edev} ]; then
+ echo "ok $i - ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ else
+ echo "not ok $i - ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ fi
+ i=$((i+1))
+
+ geli detach md${no}
+ mdconfig -d -u $no
+}
+
+i=1
+dd if=/dev/random of=${keyfile} bs=512 count=16 >/dev/null 2>&1
+for_each_geli_config_nointegrity do_test
+
+rm -f $rnd
+rm -f $keyfile
diff --git a/tests/sys/geom/class/eli/integrity_copy_test.sh b/tests/sys/geom/class/eli/integrity_copy_test.sh
new file mode 100644
index 000000000000..ae345d74cae3
--- /dev/null
+++ b/tests/sys/geom/class/eli/integrity_copy_test.sh
@@ -0,0 +1,88 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+keyfile=`mktemp $base.XXXXXX` || exit 1
+sector=`mktemp $base.XXXXXX` || exit 1
+
+echo "1..2400"
+
+do_test() {
+ cipher=$1
+ aalgo=$2
+ secsize=$3
+ ealgo=${cipher%%:*}
+ keylen=${cipher##*:}
+
+ mdconfig -a -t malloc -s `expr $secsize \* 2 + 512`b -u $no || exit 1
+ geli init -B none -a $aalgo -e $ealgo -l $keylen -P -K $keyfile -s $secsize md${no} 2>/dev/null
+ geli attach -p -k $keyfile md${no}
+
+ dd if=/dev/random of=/dev/md${no}.eli bs=${secsize} count=1 >/dev/null 2>&1
+
+ dd if=/dev/md${no}.eli bs=${secsize} count=1 >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ echo "ok $i - small 1 aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ else
+ echo "not ok $i - small 1 aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ fi
+ i=$((i+1))
+
+ geli detach md${no}
+ # Copy first small sector to the second small sector.
+ # This should be detected as corruption.
+ dd if=/dev/md${no} of=${sector} bs=512 count=1 >/dev/null 2>&1
+ dd if=${sector} of=/dev/md${no} bs=512 count=1 seek=1 >/dev/null 2>&1
+ geli attach -p -k $keyfile md${no}
+
+ dd if=/dev/md${no}.eli of=/dev/null bs=${secsize} count=1 >/dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ echo "ok $i - small 2 aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ else
+ echo "not ok $i - small 2 aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ fi
+ i=$((i+1))
+
+ ms=`diskinfo /dev/md${no} | awk '{print $3 - 512}'`
+ ns=`diskinfo /dev/md${no}.eli | awk '{print $4}'`
+ usecsize=`echo "($ms / $ns) - (($ms / $ns) % 512)" | bc`
+
+ # Fix the corruption
+ dd if=/dev/random of=/dev/md${no}.eli bs=${secsize} count=2 >/dev/null 2>&1
+
+ dd if=/dev/md${no}.eli bs=${secsize} count=2 >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ echo "ok $i - big 1 aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ else
+ echo "not ok $i - big 1 aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ fi
+ i=$((i+1))
+
+ geli detach md${no}
+ # Copy first big sector to the second big sector.
+ # This should be detected as corruption.
+ dd if=/dev/md${no} of=${sector} bs=${usecsize} count=1 >/dev/null 2>&1
+ dd if=${sector} of=/dev/md${no} bs=${usecsize} count=1 seek=1 >/dev/null 2>&1
+ geli attach -p -k $keyfile md${no}
+
+ dd if=/dev/md${no}.eli of=/dev/null bs=${secsize} count=2 >/dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ echo "ok $i - big 2 aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ else
+ echo "not ok $i - big 2 aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ fi
+ i=$((i+1))
+
+ geli detach md${no}
+ mdconfig -d -u $no
+}
+
+
+i=1
+dd if=/dev/random of=${keyfile} bs=512 count=16 >/dev/null 2>&1
+
+for_each_geli_config do_test
+
+rm -f $keyfile $sector
diff --git a/tests/sys/geom/class/eli/integrity_data_test.sh b/tests/sys/geom/class/eli/integrity_data_test.sh
new file mode 100644
index 000000000000..73b950a58a5e
--- /dev/null
+++ b/tests/sys/geom/class/eli/integrity_data_test.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+keyfile=`mktemp $base.XXXXXX` || exit 1
+sector=`mktemp $base.XXXXXX` || exit 1
+
+echo "1..600"
+
+do_test() {
+ cipher=$1
+ aalgo=$2
+ secsize=$3
+ ealgo=${cipher%%:*}
+ keylen=${cipher##*:}
+
+ mdconfig -a -t malloc -s `expr $secsize \* 2 + 512`b -u $no || exit 1
+ geli init -B none -a $aalgo -e $ealgo -l $keylen -P -K $keyfile -s $secsize md${no} 2>/dev/null
+
+ # Corrupt 8 bytes of data.
+ dd if=/dev/md${no} of=${sector} bs=512 count=1 >/dev/null 2>&1
+ dd if=/dev/random of=${sector} bs=1 count=8 seek=64 conv=notrunc >/dev/null 2>&1
+ dd if=${sector} of=/dev/md${no} bs=512 count=1 >/dev/null 2>&1
+ geli attach -p -k $keyfile md${no}
+
+ dd if=/dev/md${no}.eli of=/dev/null bs=${secsize} count=1 >/dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ echo "ok $i - aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ else
+ echo "not ok $i - aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ fi
+ i=$((i+1))
+
+ geli detach md${no}
+ mdconfig -d -u $no
+}
+
+i=1
+dd if=/dev/random of=${keyfile} bs=512 count=16 >/dev/null 2>&1
+
+for_each_geli_config do_test
+
+rm -f $keyfile $sector
diff --git a/tests/sys/geom/class/eli/integrity_hmac_test.sh b/tests/sys/geom/class/eli/integrity_hmac_test.sh
new file mode 100644
index 000000000000..6e1dfa585108
--- /dev/null
+++ b/tests/sys/geom/class/eli/integrity_hmac_test.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+keyfile=`mktemp $base.XXXXXX` || exit 1
+sector=`mktemp $base.XXXXXX` || exit 1
+
+echo "1..600"
+
+do_test() {
+ cipher=$1
+ aalgo=$2
+ secsize=$3
+ ealgo=${cipher%%:*}
+ keylen=${cipher##*:}
+
+ mdconfig -a -t malloc -s `expr $secsize \* 2 + 512`b -u $no || exit 2
+ geli init -B none -a $aalgo -e $ealgo -l $keylen -P -K $keyfile -s $secsize md${no} 2>/dev/null
+
+ # Corrupt 8 bytes of HMAC.
+ dd if=/dev/md${no} of=${sector} bs=512 count=1 >/dev/null 2>&1
+ dd if=/dev/random of=${sector} bs=1 count=16 conv=notrunc >/dev/null 2>&1
+ dd if=${sector} of=/dev/md${no} bs=512 count=1 >/dev/null 2>&1
+ geli attach -p -k $keyfile md${no}
+
+ dd if=/dev/md${no}.eli bs=${secsize} count=1 >/dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ echo "ok $i - aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ else
+ echo "not ok $i - aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ fi
+ i=$((i+1))
+
+ geli detach md${no}
+ mdconfig -d -u $no
+}
+
+
+i=1
+dd if=/dev/random of=${keyfile} bs=512 count=16 >/dev/null 2>&1
+
+for_each_geli_config do_test
+
+rm -f $keyfile $sector
diff --git a/tests/sys/geom/class/eli/kill_test.sh b/tests/sys/geom/class/eli/kill_test.sh
new file mode 100644
index 000000000000..ccced9f47397
--- /dev/null
+++ b/tests/sys/geom/class/eli/kill_test.sh
@@ -0,0 +1,97 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+keyfile1=`mktemp $base.XXXXXX` || exit 1
+keyfile2=`mktemp $base.XXXXXX` || exit 1
+mdconfig -a -t malloc -s `expr $sectors + 1` -u $no || exit 1
+
+echo "1..9"
+
+dd if=/dev/random of=${keyfile1} bs=512 count=16 >/dev/null 2>&1
+dd if=/dev/random of=${keyfile2} bs=512 count=16 >/dev/null 2>&1
+
+geli init -B none -P -K $keyfile1 md${no}
+geli attach -p -k $keyfile1 md${no}
+geli setkey -n 1 -P -K $keyfile2 md${no}
+
+# Kill attached provider.
+geli kill md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
+sleep 1
+# Provider should be automatically detached.
+if [ ! -c /dev/md{$no}.eli ]; then
+ echo "ok 2"
+else
+ echo "not ok 2"
+fi
+
+# We cannot use keyfile1 anymore.
+geli attach -p -k $keyfile1 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 3"
+else
+ echo "not ok 3"
+fi
+
+# We cannot use keyfile2 anymore.
+geli attach -p -k $keyfile2 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 4"
+else
+ echo "not ok 4"
+fi
+
+geli init -B none -P -K $keyfile1 md${no}
+geli setkey -n 1 -p -k $keyfile1 -P -K $keyfile2 md${no}
+
+# Should be possible to attach with keyfile1.
+geli attach -p -k $keyfile1 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 5"
+else
+ echo "not ok 5"
+fi
+geli detach md${no}
+
+# Should be possible to attach with keyfile2.
+geli attach -p -k $keyfile2 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 6"
+else
+ echo "not ok 6"
+fi
+geli detach md${no}
+
+# Kill detached provider.
+geli kill md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 7"
+else
+ echo "not ok 7"
+fi
+
+# We cannot use keyfile1 anymore.
+geli attach -p -k $keyfile1 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 8"
+else
+ echo "not ok 8"
+fi
+
+# We cannot use keyfile2 anymore.
+geli attach -p -k $keyfile2 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 9"
+else
+ echo "not ok 9"
+fi
+
+rm -f $keyfile1 $keyfile2
diff --git a/tests/sys/geom/class/eli/nokey_test.sh b/tests/sys/geom/class/eli/nokey_test.sh
new file mode 100644
index 000000000000..f32e1a4f1eba
--- /dev/null
+++ b/tests/sys/geom/class/eli/nokey_test.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+keyfile=`mktemp $base.XXXXXX` || exit 1
+mdconfig -a -t malloc -s `expr $sectors + 1` -u $no || exit 1
+
+echo "1..8"
+
+geli init -B none -P md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
+
+dd if=/dev/random of=${keyfile} bs=512 count=16 >/dev/null 2>&1
+
+geli init -B none -P -K ${keyfile} md${no} 2>/dev/null
+if [ $? -eq 0 ]; then
+ echo "ok 2"
+else
+ echo "not ok 2"
+fi
+geli attach -p md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 3"
+else
+ echo "not ok 3"
+fi
+geli attach -p -k ${keyfile} md${no} 2>/dev/null
+if [ $? -eq 0 ]; then
+ echo "ok 4"
+else
+ echo "not ok 4"
+fi
+geli setkey -n 0 -P md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 5"
+else
+ echo "not ok 5"
+fi
+geli detach md${no} 2>/dev/null
+if [ $? -eq 0 ]; then
+ echo "ok 6"
+else
+ echo "not ok 6"
+fi
+geli setkey -n 0 -p -P -K ${keyfile} md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 7"
+else
+ echo "not ok 7"
+fi
+geli setkey -n 0 -p -k ${keyfile} -P md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 8"
+else
+ echo "not ok 8"
+fi
+
+rm -f $keyfile
diff --git a/tests/sys/geom/class/eli/onetime_a_test.sh b/tests/sys/geom/class/eli/onetime_a_test.sh
new file mode 100644
index 000000000000..0cccf3031355
--- /dev/null
+++ b/tests/sys/geom/class/eli/onetime_a_test.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=8
+rnd=`mktemp $base.XXXXXX` || exit 1
+
+echo "1..600"
+
+do_test() {
+ cipher=$1
+ aalgo=$2
+ secsize=$3
+ ealgo=${cipher%%:*}
+ keylen=${cipher##*:}
+
+ mdconfig -a -t malloc -s `expr $secsize \* $sectors + 512`b -u $no || exit 1
+ geli onetime -a $aalgo -e $ealgo -l $keylen -s $secsize md${no} 2>/dev/null
+
+ secs=`diskinfo /dev/md${no}.eli | awk '{print $4}'`
+
+ dd if=${rnd} of=/dev/md${no}.eli bs=${secsize} count=${secs} 2>/dev/null
+
+ md_rnd=`dd if=${rnd} bs=${secsize} count=${secs} 2>/dev/null | md5`
+ md_ddev=`dd if=/dev/md${no}.eli bs=${secsize} count=${secs} 2>/dev/null | md5`
+
+ if [ ${md_rnd} = ${md_ddev} ]; then
+ echo "ok $i - aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ else
+ echo "not ok $i - aalgo=${aalgo} ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ fi
+ i=$((i+1))
+
+ geli detach md${no}
+ mdconfig -d -u $no
+}
+
+i=1
+dd if=/dev/random of=${rnd} bs=1024 count=1024 >/dev/null 2>&1
+
+for_each_geli_config do_test
+
+rm -f $rnd
diff --git a/tests/sys/geom/class/eli/onetime_d_test.sh b/tests/sys/geom/class/eli/onetime_d_test.sh
new file mode 100644
index 000000000000..51a6abb8c97a
--- /dev/null
+++ b/tests/sys/geom/class/eli/onetime_d_test.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+mdconfig -a -t malloc -s $sectors -u $no || exit 1
+
+echo "1..3"
+
+geli onetime -d md${no}
+if [ -c /dev/md${no}.eli ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
+# Be sure it doesn't detach on read.
+dd if=/dev/md${no}.eli of=/dev/null 2>/dev/null
+sleep 1
+if [ -c /dev/md${no}.eli ]; then
+ echo "ok 2"
+else
+ echo "not ok 2"
+fi
+true > /dev/md${no}.eli
+sleep 1
+if [ ! -c /dev/md${no}.eli ]; then
+ echo "ok 3"
+else
+ echo "not ok 3"
+fi
+
+mdconfig -d -u $no
diff --git a/tests/sys/geom/class/eli/onetime_test.sh b/tests/sys/geom/class/eli/onetime_test.sh
new file mode 100644
index 000000000000..3cade152829a
--- /dev/null
+++ b/tests/sys/geom/class/eli/onetime_test.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+
+echo "1..200"
+
+do_test() {
+ cipher=$1
+ secsize=$2
+ ealgo=${cipher%%:*}
+ keylen=${cipher##*:}
+
+ rnd=`mktemp $base.XXXXXX` || exit 1
+ mdconfig -a -t malloc -s `expr $secsize \* $sectors`b -u $no || exit 1
+
+ geli onetime -e $ealgo -l $keylen -s $secsize md${no} 2>/dev/null
+
+ secs=`diskinfo /dev/md${no}.eli | awk '{print $4}'`
+
+ dd if=/dev/random of=${rnd} bs=${secsize} count=${secs} >/dev/null 2>&1
+ dd if=${rnd} of=/dev/md${no}.eli bs=${secsize} count=${secs} 2>/dev/null
+
+ md_rnd=`dd if=${rnd} bs=${secsize} count=${secs} 2>/dev/null | md5`
+ md_ddev=`dd if=/dev/md${no}.eli bs=${secsize} count=${secs} 2>/dev/null | md5`
+ md_edev=`dd if=/dev/md${no} bs=${secsize} count=${secs} 2>/dev/null | md5`
+
+ if [ ${md_rnd} = ${md_ddev} ]; then
+ echo "ok $i - ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ else
+ echo "not ok $i - ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ fi
+ i=$((i+1))
+ if [ ${md_rnd} != ${md_edev} ]; then
+ echo "ok $i - ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ else
+ echo "not ok $i - ealgo=${ealgo} keylen=${keylen} sec=${secsize}"
+ fi
+ i=$((i+1))
+
+ geli detach md${no}
+ rm -f $rnd
+ mdconfig -d -u $no
+}
+
+i=1
+for_each_geli_config_nointegrity do_test
diff --git a/tests/sys/geom/class/eli/readonly_test.sh b/tests/sys/geom/class/eli/readonly_test.sh
new file mode 100644
index 000000000000..721ad62f3b5f
--- /dev/null
+++ b/tests/sys/geom/class/eli/readonly_test.sh
@@ -0,0 +1,94 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+keyfile=`mktemp $base.XXXXXX` || exit 1
+mdconfig -a -t malloc -s `expr $sectors + 1` -u $no || exit 1
+
+echo "1..11"
+
+dd if=/dev/random of=${keyfile} bs=512 count=16 >/dev/null 2>&1
+
+geli init -B none -P -K $keyfile md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
+
+geli attach -r -p -k $keyfile md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 2"
+else
+ echo "not ok 2"
+fi
+
+sh -c "true >/dev/md${no}.eli" 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 3"
+else
+ echo "not ok 3"
+fi
+
+geli kill md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 4"
+else
+ echo "not ok 4"
+fi
+
+# kill should detach provider...
+if [ ! -c /dev/md{$no}.eli ]; then
+ echo "ok 5"
+else
+ echo "not ok 5"
+fi
+
+# ...but not destroy the metadata.
+geli attach -r -p -k $keyfile md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 6"
+else
+ echo "not ok 6"
+fi
+
+geli setkey -n 1 -P -K /dev/null md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 7"
+else
+ echo "not ok 7"
+fi
+
+geli delkey -n 0 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 8"
+else
+ echo "not ok 8"
+fi
+
+geli delkey -f -n 0 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 9"
+else
+ echo "not ok 9"
+fi
+
+geli list md${no}.eli | egrep '^Flags: .*READ-ONLY' >/dev/null
+if [ $? -eq 0 ]; then
+ echo "ok 10"
+else
+ echo "not ok 10"
+fi
+
+geli detach md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 11"
+else
+ echo "not ok 11"
+fi
+
+mdconfig -d -u $no
+rm -f $keyfile
diff --git a/tests/sys/geom/class/eli/resize_test.sh b/tests/sys/geom/class/eli/resize_test.sh
new file mode 100644
index 000000000000..7a7cf635837b
--- /dev/null
+++ b/tests/sys/geom/class/eli/resize_test.sh
@@ -0,0 +1,148 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+echo 1..27
+
+BLK=512
+BLKS_PER_MB=2048
+
+md=$(mdconfig -s40m) || exit 1
+unit=${md#md}
+i=1
+
+setsize() {
+ partszMB=$1 unitszMB=$2
+
+ {
+ echo a: $(($partszMB * $BLKS_PER_MB)) 0 4.2BSD 1024 8192
+ echo c: $(($unitszMB * $BLKS_PER_MB)) 0 unused 0 0
+ } | bsdlabel -R $md /dev/stdin
+}
+
+# Initialise
+
+setsize 10 40 || echo -n "not "
+echo ok $i - "Sized ${md}a to 10m"
+i=$((i + 1))
+
+echo secret >tmp.key
+geli init -Bnone -PKtmp.key ${md}a || echo -n "not "
+echo ok $i - "Initialised geli on ${md}a"
+i=$((i + 1))
+geli attach -pk tmp.key ${md}a || echo -n "not "
+echo ok $i - "Attached ${md}a as ${md}a.eli"
+i=$((i + 1))
+
+newfs -U ${md}a.eli >/dev/null || echo -n "not "
+echo ok $i - "Initialised the filesystem on ${md}a.eli"
+i=$((i + 1))
+out=$(fsck -tufs -y ${md}a.eli)
+echo "$out" | fgrep -q MODIFIED && echo -n "not "
+echo ok $i - "fsck says ${md}a.eli is clean," $(echo $(echo "$out" | wc -l)) \
+ "lines of output"
+i=$((i + 1))
+
+
+# Doing a backup, resize & restore must be forced (with -f) as geli
+# verifies that the provider size in the metadata matches the consumer.
+
+geli backup ${md}a tmp.meta || echo -n "not "
+echo ok $i - "Backed up ${md}a metadata"
+i=$((i + 1))
+
+geli detach ${md}a.eli || echo -n "not "
+echo ok $i - "Detached ${md}a.eli"
+i=$((i + 1))
+
+setsize 20 40 || echo -n "not "
+echo ok $i - "Sized ${md}a to 20m"
+i=$((i + 1))
+geli attach -pktmp.key ${md}a && echo -n "not "
+echo ok $i - "Attaching ${md}a fails after resizing the consumer"
+i=$((i + 1))
+
+geli restore tmp.meta ${md}a && echo -n "not "
+echo ok $i - "Restoring metadata on ${md}a.eli fails without -f"
+i=$((i + 1))
+geli restore -f tmp.meta ${md}a || echo -n "not "
+echo ok $i - "Restoring metadata on ${md}a.eli can be forced"
+i=$((i + 1))
+
+geli attach -pktmp.key ${md}a || echo -n "not "
+echo ok $i - "Attaching ${md}a is now possible"
+i=$((i + 1))
+
+growfs -y ${md}a.eli >/dev/null || echo -n "not "
+echo ok $i - "Extended the filesystem on ${md}a.eli"
+i=$((i + 1))
+
+out=$(fsck -tufs -y ${md}a.eli)
+echo "$out" | fgrep -q MODIFIED && echo -n "not "
+echo ok $i - "fsck says ${md}a.eli is clean," $(echo $(echo "$out" | wc -l)) \
+ "lines of output"
+i=$((i + 1))
+
+
+# Now do the resize properly
+
+geli detach ${md}a.eli || echo -n "not "
+echo ok $i - "Detached ${md}a.eli"
+i=$((i + 1))
+
+setsize 30 40 || echo -n "not "
+echo ok $i - "Sized ${md}a to 30m"
+i=$((i + 1))
+
+geli resize -s20m ${md}a || echo -n "not "
+echo ok $i - "Resizing works ok"
+i=$((i + 1))
+geli resize -s20m ${md}a && echo -n "not "
+echo ok $i - "Resizing doesn't work a 2nd time (no old metadata)"
+i=$((i + 1))
+
+geli attach -pktmp.key ${md}a || echo -n "not "
+echo ok $i - "Attaching ${md}a works ok"
+i=$((i + 1))
+
+growfs -y ${md}a.eli >/dev/null || echo -n "not "
+echo ok $i - "Extended the filesystem on ${md}a.eli"
+i=$((i + 1))
+
+out=$(fsck -tufs -y ${md}a.eli)
+echo "$out" | fgrep -q MODIFIED && echo -n "not "
+echo ok $i - "fsck says ${md}a.eli is clean," $(echo $(echo "$out" | wc -l)) \
+ "lines of output"
+i=$((i + 1))
+
+geli detach ${md}a.eli
+gpart destroy -F $md >/dev/null
+
+
+# Verify that the man page example works, changing ada0 to $md,
+# 1g to 20m, 2g to 30m and keyfile to tmp.key, and adding -B none
+# to geli init.
+
+gpart create -s GPT $md || echo -n "not "
+echo ok $i - "Installed a GPT on ${md}"
+i=$((i + 1))
+gpart add -s 20m -t freebsd-ufs -i 1 $md || echo -n "not "
+echo ok $i - "Added a 20m partition in slot 1"
+i=$((i + 1))
+geli init -B none -K tmp.key -P ${md}p1 || echo -n "not "
+echo ok $i - "Initialised geli on ${md}p1"
+i=$((i + 1))
+gpart resize -s 30m -i 1 $md || echo -n "not "
+echo ok $i - "Resized partition ${md}p1 to 30m"
+i=$((i + 1))
+geli resize -s 20m ${md}p1 || echo -n "not "
+echo ok $i - "Resized geli on ${md}p1 to 30m"
+i=$((i + 1))
+geli attach -k tmp.key -p ${md}p1 || echo -n "not "
+echo ok $i - "Attached ${md}p1.eli"
+i=$((i + 1))
+
+geli detach ${md}p1.eli
+
+rm tmp.*
diff --git a/tests/sys/geom/class/eli/setkey_test.sh b/tests/sys/geom/class/eli/setkey_test.sh
new file mode 100644
index 000000000000..458100c7da86
--- /dev/null
+++ b/tests/sys/geom/class/eli/setkey_test.sh
@@ -0,0 +1,156 @@
+#!/bin/sh
+# $FreeBSD$
+
+. $(dirname $0)/conf.sh
+
+base=`basename $0`
+sectors=100
+rnd=`mktemp $base.XXXXXX` || exit 1
+keyfile1=`mktemp $base.XXXXXX` || exit 1
+keyfile2=`mktemp $base.XXXXXX` || exit 1
+keyfile3=`mktemp $base.XXXXXX` || exit 1
+keyfile4=`mktemp $base.XXXXXX` || exit 1
+keyfile5=`mktemp $base.XXXXXX` || exit 1
+mdconfig -a -t malloc -s `expr $sectors + 1` -u $no || exit 1
+
+echo "1..16"
+
+dd if=/dev/random of=${rnd} bs=512 count=${sectors} >/dev/null 2>&1
+hash1=`dd if=${rnd} bs=512 count=${sectors} 2>/dev/null | md5`
+dd if=/dev/random of=${keyfile1} bs=512 count=16 >/dev/null 2>&1
+dd if=/dev/random of=${keyfile2} bs=512 count=16 >/dev/null 2>&1
+dd if=/dev/random of=${keyfile3} bs=512 count=16 >/dev/null 2>&1
+dd if=/dev/random of=${keyfile4} bs=512 count=16 >/dev/null 2>&1
+dd if=/dev/random of=${keyfile5} bs=512 count=16 >/dev/null 2>&1
+
+geli init -B none -P -K $keyfile1 md${no}
+geli attach -p -k $keyfile1 md${no}
+
+dd if=${rnd} of=/dev/md${no}.eli bs=512 count=${sectors} 2>/dev/null
+rm -f $rnd
+hash2=`dd if=/dev/md${no}.eli bs=512 count=${sectors} 2>/dev/null | md5`
+
+# Change current key (0) for attached provider.
+geli setkey -P -K $keyfile2 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
+geli detach md${no}
+
+# We cannot use keyfile1 anymore.
+geli attach -p -k $keyfile1 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 2"
+else
+ echo "not ok 2"
+fi
+
+# Attach with new key.
+geli attach -p -k $keyfile2 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 3"
+else
+ echo "not ok 3"
+fi
+hash3=`dd if=/dev/md${no}.eli bs=512 count=${sectors} 2>/dev/null | md5`
+
+# Change key 1 for attached provider.
+geli setkey -n 1 -P -K $keyfile3 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 4"
+else
+ echo "not ok 4"
+fi
+geli detach md${no}
+
+# Attach with key 1.
+geli attach -p -k $keyfile3 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 5"
+else
+ echo "not ok 5"
+fi
+hash4=`dd if=/dev/md${no}.eli bs=512 count=${sectors} 2>/dev/null | md5`
+geli detach md${no}
+
+# Change current (1) key for detached provider.
+geli setkey -p -k $keyfile3 -P -K $keyfile4 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 6"
+else
+ echo "not ok 6"
+fi
+
+# We cannot use keyfile3 anymore.
+geli attach -p -k $keyfile3 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 7"
+else
+ echo "not ok 7"
+fi
+
+# Attach with key 1.
+geli attach -p -k $keyfile4 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 8"
+else
+ echo "not ok 8"
+fi
+hash5=`dd if=/dev/md${no}.eli bs=512 count=${sectors} 2>/dev/null | md5`
+geli detach md${no}
+
+# Change key 0 for detached provider.
+geli setkey -n 0 -p -k $keyfile4 -P -K $keyfile5 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 9"
+else
+ echo "not ok 9"
+fi
+
+# We cannot use keyfile2 anymore.
+geli attach -p -k $keyfile2 md${no} 2>/dev/null
+if [ $? -ne 0 ]; then
+ echo "ok 10"
+else
+ echo "not ok 10"
+fi
+
+# Attach with key 0.
+geli attach -p -k $keyfile5 md${no}
+if [ $? -eq 0 ]; then
+ echo "ok 11"
+else
+ echo "not ok 11"
+fi
+hash6=`dd if=/dev/md${no}.eli bs=512 count=${sectors} 2>/dev/null | md5`
+geli detach md${no}
+
+if [ ${hash1} = ${hash2} ]; then
+ echo "ok 12"
+else
+ echo "not ok 12"
+fi
+if [ ${hash1} = ${hash3} ]; then
+ echo "ok 13"
+else
+ echo "not ok 13"
+fi
+if [ ${hash1} = ${hash4} ]; then
+ echo "ok 14"
+else
+ echo "not ok 14"
+fi
+if [ ${hash1} = ${hash5} ]; then
+ echo "ok 15"
+else
+ echo "not ok 15"
+fi
+if [ ${hash1} = ${hash6} ]; then
+ echo "ok 16"
+else
+ echo "not ok 16"
+fi
+
+rm -f $keyfile1 $keyfile2 $keyfile3 $keyfile4 $keyfile5
diff --git a/tests/sys/geom/class/gate/1_test.sh b/tests/sys/geom/class/gate/1_test.sh
new file mode 100644
index 000000000000..ba573bb6e122
--- /dev/null
+++ b/tests/sys/geom/class/gate/1_test.sh
@@ -0,0 +1,63 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo '1..2'
+
+base=`basename $0`
+us=0
+while [ -c /dev/ggate${us} ]; do
+ : $(( us += 1 ))
+done
+pidfile=ggated.$$.pid
+conf=`mktemp $base.XXXXXX` || exit 1
+port=33080
+
+work=$(attach_md -t malloc -s 1M)
+src=$(attach_md -t malloc -s 1M)
+
+test_cleanup()
+{
+ ggatec destroy -f -u $us
+ pkill -F $pidfile
+ geom_test_cleanup
+}
+trap test_cleanup ABRT EXIT INT TERM
+
+dd if=/dev/random of=/dev/$work bs=1m count=1 conv=sync
+dd if=/dev/random of=/dev/$src bs=1m count=1 conv=sync
+src_checksum=$(md5 -q /dev/$src)
+
+echo "127.0.0.1 RW /dev/$work" > $conf
+
+if ! ggated -p $port -F $pidfile $conf; then
+ echo 'ggated failed to start'
+ echo 'Bail out!'
+ exit 1
+fi
+sleep 1
+if ! ggatec create -p $port -u $us 127.0.0.1 /dev/$work; then
+ echo 'ggatec create failed'
+ echo 'Bail out!'
+ exit 1
+fi
+sleep 1
+
+dd if=/dev/${src} of=/dev/ggate${us} bs=1m count=1
+sleep 1
+
+work_checksum=$(md5 -q /dev/$work)
+if [ "$work_checksum" != "$src_checksum" ]; then
+ echo "not ok 1 - md5 checksums didn't match ($work_checksum != $src_checksum)"
+ echo "not ok 2 # SKIP"
+else
+ echo 'ok 1 - md5 checksum'
+
+ ggate_checksum=$(md5 -q /dev/ggate${us})
+ if [ "$ggate_checksum" != "$src_checksum" ]; then
+ echo "not ok 2 - md5 checksums didn't match ($ggate_checksum != $src_checksum)"
+ else
+ echo 'ok 2 - md5 checksum'
+ fi
+fi
diff --git a/tests/sys/geom/class/gate/2_test.sh b/tests/sys/geom/class/gate/2_test.sh
new file mode 100644
index 000000000000..be89accfcb9c
--- /dev/null
+++ b/tests/sys/geom/class/gate/2_test.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+base=`basename $0`
+us=46
+work=`mktemp -u $base.XXXXXX` || exit 1
+src=`mktemp -u $base.XXXXXX` || exit 1
+
+test_cleanup()
+{
+ ggatel destroy -f -u $us
+ rm -f $work $src
+
+ geom_test_cleanup
+}
+trap test_cleanup ABRT EXIT INT TERM
+
+dd if=/dev/random of=$work bs=1m count=1 conv=sync
+dd if=/dev/random of=$src bs=1m count=1 conv=sync
+
+if ! ggatel create -u $us $work; then
+ echo 'ggatel create failed'
+ echo 'Bail out!'
+ exit 1
+fi
+
+dd if=${src} of=/dev/ggate${us} bs=1m count=1
+sleep 1
+
+echo '1..2'
+
+src_checksum=$(md5 -q $src)
+work_checksum=$(md5 -q $work)
+if [ "$work_checksum" != "$src_checksum" ]; then
+ echo "not ok 1 - md5 checksums didn't match ($work_checksum != $src_checksum) # TODO: bug 204616"
+ echo 'not ok 2 # SKIP'
+else
+ echo 'ok 1 - md5 checksum'
+
+ ggate_checksum=$(md5 -q /dev/ggate${us})
+ if [ "$ggate_checksum" != "$src_checksum" ]; then
+ echo "not ok 2 - md5 checksums didn't match ($ggate_checksum != $src_checksum)"
+ else
+ echo 'ok 2 - md5 checksum'
+ fi
+fi
diff --git a/tests/sys/geom/class/gate/3_test.sh b/tests/sys/geom/class/gate/3_test.sh
new file mode 100644
index 000000000000..3511df761ca6
--- /dev/null
+++ b/tests/sys/geom/class/gate/3_test.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+base=`basename $0`
+us=47
+
+test_cleanup()
+{
+ ggatel destroy -f -u $us
+
+ geom_test_cleanup
+}
+trap test_cleanup ABRT EXIT INT TERM
+
+work=$(attach_md -t malloc -s 1M)
+src=$(attach_md -t malloc -s 1M)
+
+dd if=/dev/random of=/dev/$work bs=1m count=1 conv=sync
+dd if=/dev/random of=/dev/$src bs=1m count=1 conv=sync
+src_checksum=$(md5 -q /dev/$src)
+
+if ! ggatel create -u $us /dev/$work; then
+ echo 'ggatel create failed'
+ echo 'Bail out!'
+ exit 1
+fi
+
+sleep 1
+dd if=/dev/${src} of=/dev/ggate${us} bs=1m count=1 conv=sync
+sleep 1
+
+echo '1..2'
+
+work_checksum=$(md5 -q /dev/$work)
+if [ "$work_checksum" != "$src_checksum" ]; then
+ echo "not ok 1 - md5 checksums didn't match ($work_checksum != $src_checksum)"
+ echo 'not ok 2 # SKIP'
+else
+ echo 'ok 1 - md5 checksum'
+
+ ggate_checksum=$(md5 -q /dev/ggate${us})
+ if [ "$ggate_checksum" != "$src_checksum" ]; then
+ echo "not ok 2 - md5 checksums didn't match ($ggate_checksum != $src_checksum)"
+ else
+ echo 'ok 2 - md5 checksum'
+ fi
+fi
diff --git a/tests/sys/geom/class/gate/Makefile b/tests/sys/geom/class/gate/Makefile
new file mode 100644
index 000000000000..c034bf037efb
--- /dev/null
+++ b/tests/sys/geom/class/gate/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/geom/class/${.CURDIR:T}
+
+TAP_TESTS_SH+= 1_test
+TAP_TESTS_SH+= 2_test
+TAP_TESTS_SH+= 3_test
+
+${PACKAGE}FILES+= conf.sh
+
+.for t in ${TAP_TESTS_SH}
+TEST_METADATA.$t+= required_user="root"
+.endfor
+
+.include <bsd.test.mk>
diff --git a/tests/sys/geom/class/gate/Makefile.depend b/tests/sys/geom/class/gate/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/geom/class/gate/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/geom/class/gate/conf.sh b/tests/sys/geom/class/gate/conf.sh
new file mode 100755
index 000000000000..7e22ce46af66
--- /dev/null
+++ b/tests/sys/geom/class/gate/conf.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+# $FreeBSD$
+
+name="$(mktemp -u gate.XXXXXX)"
+class="gate"
+base=`basename $0`
+
+. `dirname $0`/../geom_subr.sh
diff --git a/tests/sys/geom/class/geom_subr.sh b/tests/sys/geom/class/geom_subr.sh
new file mode 100644
index 000000000000..b03ee434c4e8
--- /dev/null
+++ b/tests/sys/geom/class/geom_subr.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+# $FreeBSD$
+
+devwait()
+{
+ while :; do
+ if [ -c /dev/${class}/${name} ]; then
+ return
+ fi
+ sleep 0.2
+ done
+}
+
+attach_md()
+{
+ local test_md
+
+ test_md=$(mdconfig -a "$@") || exit
+ echo $test_md >> $TEST_MDS_FILE || exit
+ echo $test_md
+}
+
+geom_test_cleanup()
+{
+ local test_md
+
+ if [ -f "$TEST_MDS_FILE" ]; then
+ while read test_md; do
+ # The "#" tells the TAP parser this is a comment
+ echo "# Removing test memory disk: $test_md"
+ mdconfig -d -u $test_md
+ done < $TEST_MDS_FILE
+ fi
+ rm -f "$TEST_MDS_FILE"
+}
+
+if [ $(id -u) -ne 0 ]; then
+ echo '1..0 # SKIP tests must be run as root'
+ exit 0
+fi
+# If the geom class isn't already loaded, try loading it.
+if ! kldstat -q -m g_${class}; then
+ if ! geom ${class} load; then
+ echo "1..0 # SKIP could not load module for geom class=${class}"
+ exit 0
+ fi
+fi
+
+# Need to keep track of the test md devices to avoid the scenario where a test
+# failing will cause the other tests to bomb out, or a test failing will leave
+# a large number of md(4) devices lingering around
+: ${TMPDIR=/tmp}
+export TMPDIR
+if ! TEST_MDS_FILE=$(mktemp ${TMPDIR}/test_mds.XXXXXX); then
+ echo 'Failed to create temporary file for tracking the test md(4) devices'
+ echo 'Bail out!'
+ exit 1
+fi
diff --git a/tests/sys/geom/class/mirror/1_test.sh b/tests/sys/geom/class/mirror/1_test.sh
new file mode 100644
index 000000000000..af82a14730c2
--- /dev/null
+++ b/tests/sys/geom/class/mirror/1_test.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..1"
+
+us0=$(attach_md -t malloc -s 1M) || exit 1
+us1=$(attach_md -t malloc -s 2M) || exit 1
+us2=$(attach_md -t malloc -s 3M) || exit 1
+
+gmirror label $name /dev/$us0 /dev/$us1 /dev/$us2 || exit 1
+devwait
+
+# Size of created device should be 1MB - 512b.
+
+size=`diskinfo /dev/mirror/${name} | awk '{print $3}'`
+
+if [ $size -eq 1048064 ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
diff --git a/tests/sys/geom/class/mirror/2_test.sh b/tests/sys/geom/class/mirror/2_test.sh
new file mode 100644
index 000000000000..5605c44eb464
--- /dev/null
+++ b/tests/sys/geom/class/mirror/2_test.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..4"
+
+balance="round-robin"
+ddbs=2048
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+us0=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+us1=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+us2=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+
+gmirror label -b $balance $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+dd if=${src} of=/dev/mirror/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+dd if=/dev/${us0} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 2"
+else
+ echo "ok 2"
+fi
+dd if=/dev/${us1} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 3"
+else
+ echo "ok 3"
+fi
+
+dd if=/dev/${us2} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 4"
+else
+ echo "ok 4"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/mirror/3_test.sh b/tests/sys/geom/class/mirror/3_test.sh
new file mode 100644
index 000000000000..b7f85255e076
--- /dev/null
+++ b/tests/sys/geom/class/mirror/3_test.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..5"
+
+balance="round-robin"
+ddbs=2048
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+us0=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+us1=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+us2=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+
+gmirror label -b $balance $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+dd if=${src} of=/dev/mirror/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+gmirror remove $name ${us0}
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 2"
+else
+ echo "ok 2"
+fi
+
+gmirror remove $name ${us1}
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 3"
+else
+ echo "ok 3"
+fi
+
+gmirror remove $name ${us2}
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 4"
+else
+ echo "ok 4"
+fi
+
+# mirror/${name} should be removed.
+if [ -c /dev/${name} ]; then
+ echo "not ok 5"
+else
+ echo "ok 5"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/mirror/4_test.sh b/tests/sys/geom/class/mirror/4_test.sh
new file mode 100644
index 000000000000..6efcc021ef65
--- /dev/null
+++ b/tests/sys/geom/class/mirror/4_test.sh
@@ -0,0 +1,66 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..5"
+
+balance="load"
+ddbs=2048
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+us0=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+us1=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+us2=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+
+gmirror label -b $balance $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+dd if=${src} of=/dev/mirror/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+gmirror remove $name ${us0}
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 2"
+else
+ echo "ok 2"
+fi
+
+gmirror remove $name ${us1}
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 3"
+else
+ echo "ok 3"
+fi
+
+gmirror remove $name ${us2}
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 4"
+else
+ echo "ok 4"
+fi
+
+gmirror destroy $name
+
+# mirror/${name} should be removed.
+if [ -c /dev/${name} ]; then
+ echo "not ok 5"
+else
+ echo "ok 5"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/mirror/5_test.sh b/tests/sys/geom/class/mirror/5_test.sh
new file mode 100644
index 000000000000..3a176b55531f
--- /dev/null
+++ b/tests/sys/geom/class/mirror/5_test.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..5"
+
+balance="split"
+ddbs=8192
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+us0=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+us1=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+us2=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+
+gmirror label -b $balance -s `expr $ddbs / 2` $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+dd if=${src} of=/dev/mirror/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+gmirror remove $name ${us0}
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 2"
+else
+ echo "ok 2"
+fi
+
+gmirror remove $name ${us1}
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 3"
+else
+ echo "ok 3"
+fi
+
+gmirror remove $name ${us2}
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 4"
+else
+ echo "ok 4"
+fi
+
+# mirror/${name} should be removed.
+if [ -c /dev/${name} ]; then
+ echo "not ok 5"
+else
+ echo "ok 5"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/mirror/6_test.sh b/tests/sys/geom/class/mirror/6_test.sh
new file mode 100644
index 000000000000..68036e9c8c6a
--- /dev/null
+++ b/tests/sys/geom/class/mirror/6_test.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..2"
+
+balance="split"
+ddbs=8192
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+us0=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+us1=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+us2=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+
+gmirror label -b $balance -s `expr $ddbs / 2` $name /dev/${us0} /dev/${us1} || exit 1
+devwait
+
+dd if=${src} of=/dev/mirror/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+dd if=/dev/zero of=/dev/${us2} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+# Connect disk to the mirror.
+gmirror insert ${name} ${us2}
+# Wait for synchronization.
+sleep 1
+dd if=/dev/${us2} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 2"
+else
+ echo "ok 2"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/mirror/7_test.sh b/tests/sys/geom/class/mirror/7_test.sh
new file mode 100644
index 000000000000..f5bf71ab7a3b
--- /dev/null
+++ b/tests/sys/geom/class/mirror/7_test.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..5"
+
+balance="prefer"
+ddbs=2048
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+us0=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+us1=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+us2=$(attach_md -t malloc -s `expr $nblocks1 + 1`) || exit 1
+
+gmirror label -b $balance $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+dd if=${src} of=/dev/mirror/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+gmirror remove $name ${us0}
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 2"
+else
+ echo "ok 2"
+fi
+
+gmirror remove $name ${us1}
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 3"
+else
+ echo "ok 3"
+fi
+
+gmirror remove $name ${us2}
+dd if=/dev/mirror/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 4"
+else
+ echo "ok 4"
+fi
+
+# mirror/${name} should be removed.
+if [ -c /dev/${name} ]; then
+ echo "not ok 5"
+else
+ echo "ok 5"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/mirror/Makefile b/tests/sys/geom/class/mirror/Makefile
new file mode 100644
index 000000000000..2665631d606b
--- /dev/null
+++ b/tests/sys/geom/class/mirror/Makefile
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/geom/class/${.CURDIR:T}
+
+TAP_TESTS_SH+= 1_test
+TAP_TESTS_SH+= 2_test
+TAP_TESTS_SH+= 3_test
+TAP_TESTS_SH+= 4_test
+TAP_TESTS_SH+= 5_test
+TAP_TESTS_SH+= 6_test
+TAP_TESTS_SH+= 7_test
+
+${PACKAGE}FILES+= conf.sh
+
+.for t in ${TAP_TESTS_SH}
+TEST_METADATA.$t+= required_user="root"
+.endfor
+
+.include <bsd.test.mk>
diff --git a/tests/sys/geom/class/mirror/Makefile.depend b/tests/sys/geom/class/mirror/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/geom/class/mirror/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/geom/class/mirror/conf.sh b/tests/sys/geom/class/mirror/conf.sh
new file mode 100644
index 000000000000..d8595f199bf3
--- /dev/null
+++ b/tests/sys/geom/class/mirror/conf.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+# $FreeBSD$
+
+name="$(mktemp -u mirror.XXXXXX)"
+class="mirror"
+base=`basename $0`
+
+gmirror_test_cleanup()
+{
+ [ -c /dev/$class/$name ] && gmirror destroy $name
+ geom_test_cleanup
+}
+trap gmirror_test_cleanup ABRT EXIT INT TERM
+
+. `dirname $0`/../geom_subr.sh
diff --git a/tests/sys/geom/class/nop/Makefile b/tests/sys/geom/class/nop/Makefile
new file mode 100644
index 000000000000..e2d639f79cdf
--- /dev/null
+++ b/tests/sys/geom/class/nop/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/geom/class/${.CURDIR:T}
+
+ATF_TESTS_SH+= nop_test
+
+.include <bsd.test.mk>
diff --git a/tests/sys/geom/class/nop/Makefile.depend b/tests/sys/geom/class/nop/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/geom/class/nop/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/geom/class/nop/nop_test.sh b/tests/sys/geom/class/nop/nop_test.sh
new file mode 100755
index 000000000000..35cc191de170
--- /dev/null
+++ b/tests/sys/geom/class/nop/nop_test.sh
@@ -0,0 +1,166 @@
+# Copyright (c) 2016 Alan Somers
+# 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 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 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$
+
+MD_DEVS="md.devs"
+PLAINFILES=plainfiles
+
+atf_test_case diskinfo cleanup
+diskinfo_head()
+{
+ atf_set "descr" "gnop should preserve diskinfo's basic properties"
+ atf_set "require.user" "root"
+ atf_set "timeout" 15
+}
+diskinfo_body()
+{
+ us=$(alloc_md)
+ atf_check gnop create /dev/${us}
+ md_secsize=$(diskinfo ${us} | cut -wf 2)
+ md_mediasize=$(diskinfo ${us} | cut -wf 3)
+ md_stripesize=$(diskinfo ${us} | cut -wf 5)
+ nop_secsize=$(diskinfo ${us}.nop | cut -wf 2)
+ nop_mediasize=$(diskinfo ${us}.nop | cut -wf 3)
+ nop_stripesize=$(diskinfo ${us}.nop | cut -wf 5)
+ atf_check_equal "$md_secsize" "$nop_secsize"
+ atf_check_equal "$md_mediasize" "$nop_mediasize"
+ atf_check_equal "$md_stripesize" "$nop_stripesize"
+}
+diskinfo_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case io cleanup
+io_head()
+{
+ atf_set "descr" "I/O works on gnop devices"
+ atf_set "require.user" "root"
+ atf_set "timeout" 15
+}
+io_body()
+{
+ us=$(alloc_md)
+ atf_check gnop create /dev/${us}
+
+ echo src >> $PLAINFILES
+ echo dst >> $PLAINFILES
+ dd if=/dev/random of=src bs=1m count=1 >/dev/null 2>&1
+ dd if=src of=/dev/${us}.nop bs=1m count=1 > /dev/null 2>&1
+ dd if=/dev/${us}.nop of=dst bs=1m count=1 > /dev/null 2>&1
+
+ atf_check_equal `md5 -q src` `md5 -q dst`
+}
+io_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case size cleanup
+size_head()
+{
+ atf_set "descr" "Test gnop's -s option"
+ atf_set "require.user" "root"
+ atf_set "timeout" 15
+}
+size_body()
+{
+ us=$(alloc_md)
+ for mediasize in 65536 524288 1048576; do
+ atf_check gnop create -s ${mediasize} /dev/${us}
+ gnop_mediasize=`diskinfo /dev/${us}.nop | cut -wf 3`
+ atf_check_equal "${mediasize}" "${gnop_mediasize}"
+ atf_check gnop destroy /dev/${us}.nop
+ done
+ # We shouldn't be able to extend the provider's size
+ atf_check -s not-exit:0 -e ignore gnop create -s 2097152 /dev/${us}
+}
+size_cleanup()
+{
+ common_cleanup
+}
+
+atf_test_case stripesize cleanup
+stripesize_head()
+{
+ atf_set "descr" "Test gnop's -p and -P options"
+ atf_set "require.user" "root"
+ atf_set "timeout" 15
+}
+stripesize_body()
+{
+ us=$(alloc_md)
+ for ss in 512 1024 2048 4096 8192; do
+ for sofs in `seq 0 512 ${ss}`; do
+ [ "$sofs" -eq "$ss" ] && continue
+ atf_check gnop create -p ${ss} -P ${sofs} /dev/${us}
+ gnop_ss=`diskinfo /dev/${us}.nop | cut -wf 5`
+ gnop_sofs=`diskinfo /dev/${us}.nop | cut -wf 6`
+ atf_check_equal "${ss}" "${gnop_ss}"
+ atf_check_equal "${sofs}" "${gnop_sofs}"
+ atf_check gnop destroy /dev/${us}.nop
+ done
+ done
+}
+stripesize_cleanup()
+{
+ common_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case io
+ atf_add_test_case diskinfo
+ atf_add_test_case stripesize
+ atf_add_test_case size
+}
+
+alloc_md()
+{
+ local md
+
+ md=$(mdconfig -a -t swap -s 1M) || atf_fail "mdconfig -a failed"
+ echo ${md} >> $MD_DEVS
+ echo ${md}
+}
+
+common_cleanup()
+{
+ if [ -f "$MD_DEVS" ]; then
+ while read test_md; do
+ gnop destroy -f ${test_md}.nop 2>/dev/null
+ mdconfig -d -u $test_md 2>/dev/null
+ done < $MD_DEVS
+ rm $MD_DEVS
+ fi
+
+ if [ -f "$PLAINFILES" ]; then
+ while read f; do
+ rm -f ${f}
+ done < ${PLAINFILES}
+ rm ${PLAINFILES}
+ fi
+ true
+}
diff --git a/tests/sys/geom/class/raid3/10_test.sh b/tests/sys/geom/class/raid3/10_test.sh
new file mode 100644
index 000000000000..edd827d03ddf
--- /dev/null
+++ b/tests/sys/geom/class/raid3/10_test.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..1"
+
+ddbs=2048
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+us0=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us1=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us2=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+graid3 label -r $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+dd if=${src} of=/dev/raid3/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+dd if=/dev/raid3/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/raid3/11_test.sh b/tests/sys/geom/class/raid3/11_test.sh
new file mode 100644
index 000000000000..0407261ca7df
--- /dev/null
+++ b/tests/sys/geom/class/raid3/11_test.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..1"
+
+ddbs=2048
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+us0=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us1=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us2=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+graid3 label -w $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+dd if=${src} of=/dev/raid3/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+dd if=/dev/raid3/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/raid3/12_test.sh b/tests/sys/geom/class/raid3/12_test.sh
new file mode 100644
index 000000000000..10533c6710d1
--- /dev/null
+++ b/tests/sys/geom/class/raid3/12_test.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..1"
+
+nblocks1=9
+nblocks2=`expr $nblocks1 - 1`
+nblocks3=`expr $nblocks2 / 2`
+
+us0=$(attach_md -t malloc -s $nblocks1) || exit 1
+us1=$(attach_md -t malloc -s $nblocks1) || exit 1
+us2=$(attach_md -t malloc -s $nblocks1) || exit 1
+
+dd if=/dev/random of=/dev/${us0} count=$nblocks1 >/dev/null 2>&1
+dd if=/dev/random of=/dev/${us1} count=$nblocks1 >/dev/null 2>&1
+dd if=/dev/random of=/dev/${us2} count=$nblocks1 >/dev/null 2>&1
+
+graid3 label -w $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+# Wait for synchronization.
+sleep 2
+graid3 stop $name
+# Break one component.
+dd if=/dev/random of=/dev/${us1} count=$nblocks2 >/dev/null 2>&1
+# Provoke retaste of the rest components.
+true > /dev/${us0}
+true > /dev/${us2}
+sleep 1
+
+dd if=/dev/raid3/${name} of=/dev/null bs=1k count=$nblocks3 >/dev/null 2>&1
+ec=$?
+if [ $ec -eq 0 ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
diff --git a/tests/sys/geom/class/raid3/1_test.sh b/tests/sys/geom/class/raid3/1_test.sh
new file mode 100644
index 000000000000..4c0b4a230c69
--- /dev/null
+++ b/tests/sys/geom/class/raid3/1_test.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..2"
+
+us0=$(attach_md -t malloc -s 1M) || exit 1
+us1=$(attach_md -t malloc -s 2M) || exit 1
+us2=$(attach_md -t malloc -s 3M) || exit 1
+
+graid3 label $name /dev/${us0} /dev/${us1} /dev/${us2} 2>/dev/null || exit 1
+devwait
+
+# Size of created device should be 2MB - 1024B.
+
+mediasize=`diskinfo /dev/raid3/${name} | awk '{print $3}'`
+if [ $mediasize -eq 2096128 ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
+sectorsize=`diskinfo /dev/raid3/${name} | awk '{print $2}'`
+if [ $sectorsize -eq 1024 ]; then
+ echo "ok 2"
+else
+ echo "not ok 2"
+fi
diff --git a/tests/sys/geom/class/raid3/2_test.sh b/tests/sys/geom/class/raid3/2_test.sh
new file mode 100644
index 000000000000..22ebd38f6987
--- /dev/null
+++ b/tests/sys/geom/class/raid3/2_test.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..1"
+
+ddbs=2048
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+us0=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us1=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us2=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+graid3 label $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+dd if=${src} of=/dev/raid3/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+dd if=/dev/raid3/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/raid3/3_test.sh b/tests/sys/geom/class/raid3/3_test.sh
new file mode 100644
index 000000000000..f068b064242b
--- /dev/null
+++ b/tests/sys/geom/class/raid3/3_test.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..1"
+
+ddbs=2048
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+us0=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us1=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us2=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+graid3 label $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+dd if=${src} of=/dev/raid3/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+#
+# Reading without one DATA component (so with parity).
+#
+graid3 remove -n 1 $name
+dd if=/dev/raid3/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/raid3/4_test.sh b/tests/sys/geom/class/raid3/4_test.sh
new file mode 100644
index 000000000000..810f13db4066
--- /dev/null
+++ b/tests/sys/geom/class/raid3/4_test.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..1"
+
+ddbs=2048
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+us0=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us1=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us2=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+graid3 label $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+#
+# Writing without one DATA component.
+#
+graid3 remove -n 1 $name
+dd if=${src} of=/dev/raid3/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+dd if=/dev/raid3/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/raid3/5_test.sh b/tests/sys/geom/class/raid3/5_test.sh
new file mode 100644
index 000000000000..7bc8d42eb185
--- /dev/null
+++ b/tests/sys/geom/class/raid3/5_test.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..1"
+
+ddbs=2048
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+us0=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us1=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us2=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+graid3 label $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+#
+# Writing without PARITY component.
+#
+graid3 remove -n 2 $name
+dd if=${src} of=/dev/raid3/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+dd if=/dev/raid3/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/raid3/6_test.sh b/tests/sys/geom/class/raid3/6_test.sh
new file mode 100644
index 000000000000..20bf1922bca6
--- /dev/null
+++ b/tests/sys/geom/class/raid3/6_test.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..1"
+
+ddbs=2048
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+us0=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us1=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us2=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+graid3 label $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+dd if=${src} of=/dev/raid3/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+#
+# Rebuild of DATA component.
+#
+graid3 remove -n 1 $name
+dd if=/dev/zero of=/dev/${us1} bs=512 count=`expr $nblocks1 + 1` >/dev/null 2>&1
+graid3 insert -n 1 $name md${us1}
+sleep 1
+
+dd if=/dev/raid3/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/raid3/7_test.sh b/tests/sys/geom/class/raid3/7_test.sh
new file mode 100644
index 000000000000..23666f8f4c59
--- /dev/null
+++ b/tests/sys/geom/class/raid3/7_test.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..1"
+
+ddbs=2048
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+us0=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us1=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us2=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+graid3 label $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+dd if=${src} of=/dev/raid3/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+#
+# Rebuild of PARITY component.
+#
+graid3 remove -n 2 $name
+dd if=/dev/zero of=/dev/${us2} bs=512 count=`expr $nblocks1 + 1` >/dev/null 2>&1
+graid3 insert -n 2 $name md${us2}
+sleep 1
+# Remove DATA component, so PARITY component can be used while reading.
+graid3 remove -n 1 $name
+dd if=/dev/zero of=/dev/${us1} bs=512 count=`expr $nblocks1 + 1` >/dev/null 2>&1
+
+dd if=/dev/raid3/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/raid3/8_test.sh b/tests/sys/geom/class/raid3/8_test.sh
new file mode 100644
index 000000000000..2eb9b1a7dc92
--- /dev/null
+++ b/tests/sys/geom/class/raid3/8_test.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..1"
+
+ddbs=2048
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+us0=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us1=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us2=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+graid3 label $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+#
+# Writing without DATA component and rebuild of DATA component.
+#
+graid3 remove -n 1 $name
+dd if=/dev/zero of=/dev/${us1} bs=512 count=`expr $nblocks1 + 1` >/dev/null 2>&1
+dd if=${src} of=/dev/raid3/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+graid3 insert -n 1 $name md${us1}
+sleep 1
+
+dd if=/dev/raid3/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/raid3/9_test.sh b/tests/sys/geom/class/raid3/9_test.sh
new file mode 100644
index 000000000000..0ef010a00a78
--- /dev/null
+++ b/tests/sys/geom/class/raid3/9_test.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..1"
+
+ddbs=2048
+nblocks1=1024
+nblocks2=`expr $nblocks1 / \( $ddbs / 512 \)`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+us0=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us1=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+us2=$(attach_md -t malloc -s $(expr $nblocks1 + 1)) || exit 1
+
+dd if=/dev/random of=${src} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+
+graid3 label $name /dev/${us0} /dev/${us1} /dev/${us2} || exit 1
+devwait
+
+#
+# Writing without PARITY component and rebuild of PARITY component.
+#
+graid3 remove -n 2 $name
+dd if=/dev/zero of=/dev/${us2} bs=512 count=`expr $nblocks1 + 1` >/dev/null 2>&1
+dd if=${src} of=/dev/raid3/${name} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+graid3 insert -n 2 $name md${us2}
+sleep 1
+# Remove DATA component, so PARITY component can be used while reading.
+graid3 remove -n 1 $name
+dd if=/dev/zero of=/dev/${us1} bs=512 count=`expr $nblocks1 + 1` >/dev/null 2>&1
+
+dd if=/dev/raid3/${name} of=${dst} bs=$ddbs count=$nblocks2 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/raid3/Makefile b/tests/sys/geom/class/raid3/Makefile
new file mode 100644
index 000000000000..a53a937cee69
--- /dev/null
+++ b/tests/sys/geom/class/raid3/Makefile
@@ -0,0 +1,26 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/geom/class/${.CURDIR:T}
+
+TAP_TESTS_SH+= 1_test
+TAP_TESTS_SH+= 2_test
+TAP_TESTS_SH+= 3_test
+TAP_TESTS_SH+= 4_test
+TAP_TESTS_SH+= 5_test
+TAP_TESTS_SH+= 6_test
+TAP_TESTS_SH+= 7_test
+TAP_TESTS_SH+= 8_test
+TAP_TESTS_SH+= 9_test
+TAP_TESTS_SH+= 10_test
+TAP_TESTS_SH+= 11_test
+TAP_TESTS_SH+= 12_test
+
+${PACKAGE}FILES+= conf.sh
+
+.for t in ${TAP_TESTS_SH}
+TEST_METADATA.$t+= required_user="root"
+.endfor
+
+.include <bsd.test.mk>
diff --git a/tests/sys/geom/class/raid3/Makefile.depend b/tests/sys/geom/class/raid3/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/geom/class/raid3/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/geom/class/raid3/conf.sh b/tests/sys/geom/class/raid3/conf.sh
new file mode 100644
index 000000000000..f1b270aacd4d
--- /dev/null
+++ b/tests/sys/geom/class/raid3/conf.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+# $FreeBSD$
+
+name="$(mktemp -u graid3.XXXXXX)"
+class="raid3"
+base=`basename $0`
+
+graid3_test_cleanup()
+{
+ [ -c /dev/$class/$name ] && graid3 stop $name
+ geom_test_cleanup
+}
+trap graid3_test_cleanup ABRT EXIT INT TERM
+
+. `dirname $0`/../geom_subr.sh
diff --git a/tests/sys/geom/class/shsec/1_test.sh b/tests/sys/geom/class/shsec/1_test.sh
new file mode 100644
index 000000000000..ab0bb37ea445
--- /dev/null
+++ b/tests/sys/geom/class/shsec/1_test.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..2"
+
+us0=$(attach_md -t malloc -s 1M) || exit 1
+us1=$(attach_md -t malloc -s 2M) || exit 1
+us2=$(attach_md -t malloc -s 3M) || exit 1
+
+gshsec label $name /dev/${us0} /dev/${us1} /dev/${us2} 2>/dev/null || exit 1
+devwait
+
+# Size of created device should be 1MB - 512B.
+
+mediasize=`diskinfo /dev/shsec/${name} | awk '{print $3}'`
+if [ $mediasize -eq 1048064 ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
+sectorsize=`diskinfo /dev/shsec/${name} | awk '{print $2}'`
+if [ $sectorsize -eq 512 ]; then
+ echo "ok 2"
+else
+ echo "not ok 2"
+fi
diff --git a/tests/sys/geom/class/shsec/2_test.sh b/tests/sys/geom/class/shsec/2_test.sh
new file mode 100644
index 000000000000..9dfe36ace48f
--- /dev/null
+++ b/tests/sys/geom/class/shsec/2_test.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..4"
+
+nblocks1=1024
+nblocks2=`expr $nblocks1 + 1`
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+dd if=/dev/random of=${src} count=$nblocks1 >/dev/null 2>&1
+
+us0=$(attach_md -t malloc -s $nblocks2) || exit 1
+us1=$(attach_md -t malloc -s $nblocks2) || exit 1
+us2=$(attach_md -t malloc -s $nblocks2) || exit 1
+
+gshsec label $name /dev/$us0 /dev/$us1 /dev/$us2 || exit 1
+devwait
+
+dd if=${src} of=/dev/shsec/${name} count=$nblocks1 >/dev/null 2>&1
+
+dd if=/dev/shsec/${name} of=${dst} count=$nblocks1 >/dev/null 2>&1
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+dd if=/dev/${us0} of=${dst} count=$nblocks1 >/dev/null 2>&1
+if [ `md5 -q ${src}` = `md5 -q ${dst}` ]; then
+ echo "not ok 2"
+else
+ echo "ok 2"
+fi
+
+dd if=/dev/${us1} of=${dst} count=$nblocks1 >/dev/null 2>&1
+if [ `md5 -q ${src}` = `md5 -q ${dst}` ]; then
+ echo "not ok 3"
+else
+ echo "ok 3"
+fi
+
+dd if=/dev/${us2} of=${dst} count=$nblocks1 >/dev/null 2>&1
+if [ `md5 -q ${src}` = `md5 -q ${dst}` ]; then
+ echo "not ok 4"
+else
+ echo "ok 4"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/shsec/Makefile b/tests/sys/geom/class/shsec/Makefile
new file mode 100644
index 000000000000..b0849962aa01
--- /dev/null
+++ b/tests/sys/geom/class/shsec/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/geom/class/${.CURDIR:T}
+
+TAP_TESTS_SH+= 1_test
+TAP_TESTS_SH+= 2_test
+
+${PACKAGE}FILES+= conf.sh
+
+.for t in ${TAP_TESTS_SH}
+TEST_METADATA.$t+= required_user="root"
+.endfor
+
+.include <bsd.test.mk>
diff --git a/tests/sys/geom/class/shsec/Makefile.depend b/tests/sys/geom/class/shsec/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/geom/class/shsec/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/geom/class/shsec/conf.sh b/tests/sys/geom/class/shsec/conf.sh
new file mode 100644
index 000000000000..7800eb7764ab
--- /dev/null
+++ b/tests/sys/geom/class/shsec/conf.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+# $FreeBSD$
+
+name="$(mktemp -u shsec.XXXXXX)"
+class="shsec"
+base=`basename $0`
+
+shsec_test_cleanup()
+{
+ [ -c /dev/$class/$name ] && gshsec stop $name
+ geom_test_cleanup
+}
+trap shsec_test_cleanup ABRT EXIT INT TERM
+
+. `dirname $0`/../geom_subr.sh
diff --git a/tests/sys/geom/class/stripe/1_test.sh b/tests/sys/geom/class/stripe/1_test.sh
new file mode 100644
index 000000000000..7923763a4c29
--- /dev/null
+++ b/tests/sys/geom/class/stripe/1_test.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..1"
+
+us0=$(attach_md -t malloc -s 1M) || exit 1
+us1=$(attach_md -t malloc -s 2M) || exit 1
+us2=$(attach_md -t malloc -s 3M) || exit 1
+
+gstripe create -s 16384 $name /dev/$us0 /dev/$us1 /dev/$us2 || exit 1
+devwait
+
+# Size of created device should be 1MB * 3.
+
+size=`diskinfo /dev/stripe/${name} | awk '{print $3}'`
+
+if [ $size -eq 3145728 ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
diff --git a/tests/sys/geom/class/stripe/2_test.sh b/tests/sys/geom/class/stripe/2_test.sh
new file mode 100644
index 000000000000..f6e11f5a5821
--- /dev/null
+++ b/tests/sys/geom/class/stripe/2_test.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+# $FreeBSD$
+
+. `dirname $0`/conf.sh
+
+echo "1..1"
+
+tsize=3
+src=`mktemp $base.XXXXXX` || exit 1
+dst=`mktemp $base.XXXXXX` || exit 1
+
+dd if=/dev/random of=${src} bs=1m count=$tsize >/dev/null 2>&1
+
+us0=$(attach_md -t malloc -s 1M) || exit 1
+us1=$(attach_md -t malloc -s 2M) || exit 1
+us2=$(attach_md -t malloc -s 3M) || exit 1
+
+gstripe create -s 8192 $name /dev/$us0 /dev/$us1 /dev/$us2 || exit 1
+devwait
+
+dd if=${src} of=/dev/stripe/${name} bs=1m count=$tsize >/dev/null 2>&1
+dd if=/dev/stripe/${name} of=${dst} bs=1m count=$tsize >/dev/null 2>&1
+
+if [ `md5 -q ${src}` != `md5 -q ${dst}` ]; then
+ echo "not ok 1"
+else
+ echo "ok 1"
+fi
+
+rm -f ${src} ${dst}
diff --git a/tests/sys/geom/class/stripe/Makefile b/tests/sys/geom/class/stripe/Makefile
new file mode 100644
index 000000000000..b0849962aa01
--- /dev/null
+++ b/tests/sys/geom/class/stripe/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/geom/class/${.CURDIR:T}
+
+TAP_TESTS_SH+= 1_test
+TAP_TESTS_SH+= 2_test
+
+${PACKAGE}FILES+= conf.sh
+
+.for t in ${TAP_TESTS_SH}
+TEST_METADATA.$t+= required_user="root"
+.endfor
+
+.include <bsd.test.mk>
diff --git a/tests/sys/geom/class/stripe/Makefile.depend b/tests/sys/geom/class/stripe/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/geom/class/stripe/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/geom/class/stripe/conf.sh b/tests/sys/geom/class/stripe/conf.sh
new file mode 100644
index 000000000000..fd0f41c6c5e6
--- /dev/null
+++ b/tests/sys/geom/class/stripe/conf.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+# $FreeBSD$
+
+name="$(mktemp -u stripe.XXXXXX)"
+class="stripe"
+base=`basename $0`
+
+gstripe_test_cleanup()
+{
+ [ -c /dev/$class/$name ] && gstripe destroy $name
+ geom_test_cleanup
+}
+trap gstripe_test_cleanup ABRT EXIT INT TERM
+
+. `dirname $0`/../geom_subr.sh
diff --git a/tests/sys/geom/class/uzip/1_endian_big.img.uzip.uue b/tests/sys/geom/class/uzip/1_endian_big.img.uzip.uue
new file mode 100644
index 000000000000..b4903e487dca
--- /dev/null
+++ b/tests/sys/geom/class/uzip/1_endian_big.img.uzip.uue
@@ -0,0 +1,87 @@
+#
+# $FreeBSD$
+#
+
+begin 644 1_endian_big.img.uzip
+M(R$O8FEN+W-H"B-6,BXP($9O<FUA=`HH:VQD<W1A="`M<6T@9U]U>FEP?'QK
+M;&1L;V%D(&=E;VU?=7II<"D^)BTF)FUO=6YT7V-D.38V,"`O9&5V+V!M9&-O
+M;F9I9R`M868@)#!@+G5Z:7`@)#$*97AI="`D/PH``````````````$``````
+M0`````````*0`````````I`````````"D`````````*0`````````I``````
+M```#;P````````-O````````!#$````````$,0````````2H````````!*@`
+M```````&T@````````B4````````",4````````(Q0````````NZ````````
+M"[H````````,@0````````R!````````#($````````,@0````````R!````
+M````#($````````,@0````````R!````````#($````````,@0````````R!
+M````````#($````````,@0````````R!````````#($````````,@0``````
+M``R!````````#($````````,@0````````R!````````#($````````,@0``
+M``````R!````````#($````````,@0````````R!````````#($````````,
+M@0````````R!````````#($````````,@0````````R!````````#($`````
+M```,@0````````R!````````#($````````,@0````````R!````````#($`
+M```````,@0````````R!````````#($````````,@0````````R!````````
+M#($````````,@0````````R!````````#*UXVNW4/0Z",!3`\3:B,FA@A,W1
+MN,C@`3R$":?0R<'!Q.H)3+R0DZOQ(L3%N1:H:``3'=#E_TM>WX.TI7P4(0J!
+MB8&)H8E(U)-"*)/\M'9M9+16)JYIZ9GHF7!LOU8>_LLT6:WJ+A`GVU/G,IH\
+M^_EOEI*.EW4K5./Y8BF`!AW3YK!W\JP>Q]5/M+Q]<NY?%BTK56#SVN;^)]/$
+MR:Y;FO!L<]3L#4R+QOXK;C]Z<N9?)E;VS2FWK75^>F.SI^L&A3,9LE,`````
+M``````````````````````#PC3L712#(>-KMU#T*PD`0AN$)_FTA)*7I+,4J
+MA0?P$()7L=+U<K;B1<0;K+/)$,6LH(4_Q?O`EUG"[F9#F(BT)IJI9J:I)"T3
+M\5J*.':66@A><XG#7#/6]&U>KTEQMTT]]JD'K,_[P_`T7]SF%4^.$M=GJ1-Z
+M`?Z"[[9/P_WD.-UV*:UNK8Y>V2;VZ,.&1ZO59U]@V5Z^3/]ELK$OY]T@A.;V
+MSFH>4HO*55;2`P```````````````````````````'C'%6?Y%ZQXVNW.L0W"
+M,!"&40<J"FHH&8-%$,M0P#89@4$8@@(Q`2+\A8-DA?04[TF?BSM9NE)BM3@<
+MGY>A?'7CLTVGM*Z+?5J6>=WXN>K3-3W2+=W3+IUKC?:&UN;G]/T:9DSNFLX`
+M``````````````````#@;WP`XC\HZGC:Y=MY*`11'`?PL>[<-AN28\D5Y:I=
+M(6W:+<+F_H-"<H3P%]:1W$H16C8*191(2,Z4L$V6M"3''XZ4*]:5<K6TN<HB
+M?Q#ZOC_F-[TW]7Z?WFN:]V:&(+ZWZ$:Z#!'`92-<'/V?\F4=$Q3YVI<J-=DA
+M0E(H?(KW;Z*0%DB>RLZ?8TV;&?F%;IF_Q*5\TJ;\4<,[[E=_91Y!XUZWRJ*L
+M<$K35O_P<,OY"]L)!?G+E`S?C/_B1Y%*ICI1/<]L'Z-X7X$MCOE"'NZ_Y%?\
+MZ0[<!.H-R/<_YQC6'K*_0^3!1_9G'52K(ON+1)-^R/Z<[DX2V2_5IA0C^]EV
+MY?7(_@D?EC6R/U2P$H?LKZ`1Y\C^S,L;Z.<_[Y)-?V2_GHBM@^R7:#CV(?N-
+M2^T'D/U)$RIKR/Z6!:UM9+\:(^@&>OY[GW0C^\?6)YG(_K:2/%-DO\V4S2"R
+MO\/\7!_9?U1&LT3V6VEVG2#[!;OQ5&1_(N<R'MF?.G=D@NS/M]CA(?OGZX^A
+MW__QI4O0^Q^5XV8^R'Z'GNA>9#^;EXS,)Z+NI'7(_HMID039K[RW,(_L]Y+<
+M'R+[&Q.VKI#]Z3-NT.L?NI1>"SW_N7[5R'[.,C\$V<\=K[%#]AM'%A@@^V-3
+M3M.1_;D<6SKT^F\V"WK_V\+1-1S9+^S7O$7VVY.COLC^B#D&]/^/`=D;P\C^
+MX++F*F1_4\8Z]/<O&0.N!<A^HS"K$60_TXC'^$_Y/@#S,;!8>-KEVETK@V$8
+M!_!E8<W*2T@M,_,R)[1ERBQ'FB*)4BQ9B2'9DLCCT&J1#9,'*TN+;3BP1M,.
+M:&Q6#C2)PF;$D)48FBDFOH03_:]/</_JOE[NKIM"^=OXR5PJH`!'.MLL0?:'
+M.>8@LG]"&FQ"]K.>-")D_X+/58OL%Z<.C2#[OYY)`MF_&]+F(ON9L3W0]]^H
+M?HE']MN,!CJRGU]^487L3UP[IB'[3Z/9M\A^1_*;'=E?[XUV(OMK.`-"9'^:
+M2O&*[%^G;EY"Y_^5&GK^HW]86Y#]#7WN'62_/Y!Q@NR_OF'HD/WV$I*![-_8
+M]Y<A^U7R/>C^_][H\2#[+<QM%[*?%Z>"[O_DMP5Z_U7ML[F1_33=L`#9S\YI
+M2T+V?W8'>,C^>\%D,[*_B`P_(/MMH?PPLM\M<\XC^\<.%,A\"FM+/([L=R0P
+MO<C^+.E<'K)?:^J"WO^:.(04V:\L5$#GOY`0=2#[E[E6/;)_<=!?@>S7!#@6
+M9+_><`C]_F-4RJ'K'Z&_@_[_W-M^#KW_JSO3SB+[4\3T?F1_ZVK,"K(_(E$Z
+MH><_JBL$7?]DI5/(_J/':>C^/Q/A%B/[V:I1_G\Z[R\+%8'F>-KMPS$-`"`0
+M`+%#`#MB$<^K8&J35JLZ\\X=````````````````````\-T#NY8`HWC:[91+
+M;]0P$,=WRR(J"PFNE3C,TDJ`M%T!%Q`<H-OR$%2J!"M5W'`V3NS6CBO;(<V%
+MQY?CRI5/P5<H8R>E%2"$Q/,P/VGC9.P9C_\SWL%@L#0X/QI.!XFEP:71TK3[
+M.#.X.#H[]14_Z.9&PX_+3`2N;34-AV%`$,0/>?O5]_#X9?F?I#/\QK+2CZ_[
+M\=S/A-G]].[]5P$_]./U/WN`^U\>?YD1_@[[RKU=/GMTU)G?]..%H^\YK<R'
+M*W0'"((@"((@B/^)5;8*:P^=$+/G6VMLE;$G&[/9@V>[.YM/7S"V+1KE89,[
+M9[5F5PMG#<RELW4I(4@!V];NJZI<?Z2Y]\"K''8E#["AU4+`0UNC82Z%$Q.X
+M<?O6S6N,O9PWW$/FE-:JG"2/&,=K%60+P;X2GFVI',K6B31;*I-I`:I*ZQJ>
+MB3ML0VLPRO@6&HR=)C+K;!F])VRCCVFL$>!XD!YL'4J'KE/&+L]$PWNG)SS+
+MA&OL8G\"I@5OJS'#=&&/-QX7X$$R%3#WN':AOQ@7/"SDF)T.5&=[=8:K7=Z=
+MR<NZ2J$*5QME:P\S-`OGJ^1[F;''Z(GB@41]7UEWP#7XQKH\GE3BVCMLVU8E
+M!&6Z+0RO#N.!"BL@"A9+$&!]G3VWX(0/(H_FK$V+Y[4)M8&`9>WT\+A7#KR1
+MZEC+Y(]ZX"RF[*.Q+@KEY?%<VB6Z3=)!3FO58+%`M`*%+:#0W.`NF_@$C%\4
+M6L6\3S5)J'4IL%8I5LPFJQW6-$_;1CF-&#.V4T6E&SN&D[>NE%TD?O*>$NI%
+MRS3/<6-1!?"56NP+MXXB+_;'46$MBA"WR`7O"Y-25\'CZ7@>ER3/DNO:',B8
+M>(:^L4V2+I+[)`?V)^\[\$2'>VPS%B38V#O<&9^:*!/<1!4SVX[9#M:?9WNQ
+M_CEOQWB1M+96=B]HN!(S6$CK0I0C5AZ[8<^VT]]R4>[^RD6A?T:"(`B"(`B"
+M(`B"(`B"(`B"(`B"(`B"(`B"(`B"(`B"^/_X#/X6=T5XVNW7+0H"01B`X=EQ
+M004Q>@3!,B`8A$V#;#$+1@4/(=C4H,VD[@FT>`9ABPB;-GD$,6@0L_B#)L$@
+MIOW>)\Q/?&>FC%(JI7*N8]23=BZN-J\-DL\>[_?_0;\7F<?0//4WW^8?5)-Z
+MGJ&)/<GOR?'CEN3^2I"=2^XOM^U><O\B\J:2^[N'25IR_R`*ZY+[>ZOE5G+_
+M-:^'DOO]TF@FN7]=LT7)_8U@UY'</RZH,[]*``````````````````````#P
+M3S?_-B4S>-KMT#$!```,`B"C&]T*>_9!!!(```````````````````!X405W
+M`]:``($`````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+=````````````````````````````````````````
+`
+end
diff --git a/tests/sys/geom/class/uzip/1_endian_little.img.uzip.uue b/tests/sys/geom/class/uzip/1_endian_little.img.uzip.uue
new file mode 100644
index 000000000000..044dd3107929
--- /dev/null
+++ b/tests/sys/geom/class/uzip/1_endian_little.img.uzip.uue
@@ -0,0 +1,110 @@
+#
+# $FreeBSD$
+#
+
+begin 644 1_endian_little.img.uzip
+M(R$O8FEN+W-H"B-6,BXP($9O<FUA=`HH:VQD<W1A="`M<6T@9U]U>FEP?'QK
+M;&1L;V%D(&=E;VU?=7II<"D^)BTF)FUO=6YT7V-D.38V,"`O9&5V+V!M9&-O
+M;F9I9R`M868@)#!@+G5Z:7`@)#$*97AI="`D/PH``````````````$``````
+M0`````````*0`````````W<````````$G@````````>&````````!ZT`````
+M```'U`````````?[````````""(````````(20````````AP````````")<`
+M```````(O@````````CE````````"0P````````),P````````E:````````
+M"8$````````)J`````````G/````````"?8````````*'0````````I$````
+M````"FL````````*D@````````JY````````"N`````````+!P````````LN
+M````````"U4````````+?`````````NC````````"\H````````+\0``````
+M``P8````````##\````````,9@````````R-````````#+0````````,VP``
+M``````T"````````#2D````````-4`````````UW````````#9X````````-
+MQ0````````WL````````#A,````````..@````````YA````````#H@`````
+M```.KP````````[6````````#OT````````/)`````````]+````````#W(`
+M```````/F0````````_`````````#^<````````0#@```````!`U````````
+M$%P````````0@P```````!"J````````$-9XVNW4L0K",!"`X8LM$@1!!T$G
+M.[JZ.W3Q#1Q\`P??H(LZ^T@^B*L/(L1+>L6L#E:0_X-KKTU"C[8Y$0``````
+M````````\"\F&G.-2F-E]X*ZK8=[*47NA8@3FZ#77MJ(-O'P"$&>(8PU'75C
+M>M!E[3J?/4SS6D^#[-I9&I]WV%Z/J8;2HEMO\V+4-A[SQFI(4YV<^9KHS?3B
+MTJ\]LW.NRO+RQW5:+=T^:_*]^T%YJ1]D"V)?D*QG?%5M^[YGWMY3[%?AY)S7
+M(L+2>F3QGN>R]QOM%H[]`0``````````@%Z]`'B8&[EXVNW9,4H#013&\?>2
+M1=:`J(6@E2G3"I86VW@#A=Q`P1ND,:ESD13!TB9-;F'K$3R`,+ZW^U8'42$$
+M-\W_!]_N;':&?0R9:4:D<60YM0PMH_@MF=7%WE@*D9>^B$ITL.=2FK@KO[RF
+M).\I'5AST+ZSBPUKQK6=HUW9K9<]:S3]>W?7\X>ZAB+2CH]^GBK>>WL2-=1=
+M5:8"=.5XIO5?^R3NN6'6+G9<9]32KK-)OG8W**_>#[(!OB](MF?\JRK6?<?*
+MF"??K]*C:FE%I//8(_M?_32;7W=SIJP/````````````=.ZVM_]YKJ=^PO;3
+MN;K\?L;W_91K85E:GBS/EGOK<&D96Z9;57J8/[REG?F[2I^/]48C````````
+M``````#8S@?5^JSR>-KMFL]/%#$4QQ<EAC1ZY_@62!2R;((7#9J871`-DI`(
+M"7JSL].9%MHI:3L,X\W_Q:OZ!_@/>3'Q3_"UL[`<](07X7V2V7;:OA_]OLY<
+M9GN]V\W/T9VN,VV^;]Q[%]M?W[X<76U7KQ-DX=)];^U!=UWP^=-<:A?GN_NO
+MBW^.OW;-^/-_B4\0!$$0!$$0!$$0!$$0!$$0!$$0!$'<#.+7YX]X/>_-OA$3
+M!'$[B/]+N=^;GQO.^G>&>',7^S_F%I@(7-MJ&,X#:440-XUEM@PK.TZ(\<'V
+M"EMF;'<T'K]\>[2_]>8]8WNB41ZVN'-6:_:H<-;`H72V+B4$*6#/VA-5E>NO
+M-/<>>)7#D>0!1EI-!.S8&@<.I7!B`!M/GSQ>9>S#8<,]9$YIK<I!LHA^O%9!
+MMA#LF?!L6^50MDZDV5*93`M055K7\$QLLI'68)3Q+33H.TUDUMDR6@_8:.K3
+M6"/`\2`]V#J4#DV'C"V-1<.G1KL\RX1K[.1D`*8%;ZL^PW3AF#<>%^!&,A4P
+M][AVHB\')SQ,9)]==51GQW6&JUW>[<G+NDJN"E<;96L/8QP6SE?)=HFQUVB)
+MXH%$?<^L.^4:?&-='G<J<>TFV[-5"4&9+H3AU7G<4&$%1,%B"0*LK[,#"T[X
+M(/(XG+5I\6%M0FT@8%D[/3S&RH$W4EUHF>Q1#YS%E'T<K(M">7DQEZ)$LT':
+MR%6M&BP6B%:@L`44FAN,LH6_@/Z+0JN8]Y5#$FI="JQ5\A6SR6J'-<U3V"BG
+M$7W&]JNH=&/[,.MUI>P\\5D_)305+=,\Q\"B"N`K-3D1;AU%GISTH\):%"&&
+MR`6?%B:EKH+'W?$\+DF6)=>U.94Q\0QMXS%)NDCNDQQX/OGT!,YT>,&V8D&"
+MC6>'.^/3(<H$-U'%S+9]MH_UY]EQK'_.VSX^2%I;*[L.#CR,&4RD=2'*$2N/
+MI^'8ML-_\J`\N\Z#0F]&@B`(@B`(@B`(@B`(@OC_^0WE#GM@>-KMP3$!````
+MPJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!````PJ#U3VT,
+M'Z````````````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````
+M``````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````````````
+M``````"`MP%````!>-KMP3$!````PJ#U3VT,'Z````````````````````"`
+MMP%````!>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%````!
+M>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!
+M````PJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!````PJ#U
+M3VT,'Z````````````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``
+M``````````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````````
+M``````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````````````````
+M``"`MP%````!>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%`
+M```!>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%````!>-KM
+MP3$!````PJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!````
+MPJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!````PJ#U3VT,
+M'Z````````````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````
+M``````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````````````
+M``````"`MP%````!>-KMP3$!````PJ#U3VT,'Z````````````````````"`
+MMP%````!>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%````!
+M>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!
+M````PJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!````PJ#U
+M3VT,'Z````````````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``
+M``````````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````````
+M``````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````````````````
+M``"`MP%````!>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%`
+M```!>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%````!>-KM
+MP3$!````PJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!````
+MPJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!````PJ#U3VT,
+M'Z````````````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````
+M``````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````````````
+M``````"`MP%````!>-KMP3$!````PJ#U3VT,'Z````````````````````"`
+MMP%````!>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%````!
+M>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!
+M````PJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!````PJ#U
+M3VT,'Z````````````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``
+M``````````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````````
+M``````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````````````````
+M``"`MP%````!>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%`
+M```!>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%````!>-KM
+MP3$!````PJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!````
+MPJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!````PJ#U3VT,
+M'Z````````````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````
+M``````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````````````
+M``````"`MP%````!>-KMP3$!````PJ#U3VT,'Z````````````````````"`
+MMP%````!>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%````!
+M>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!
+M````PJ#U3VT,'Z````````````````````"`MP%````!>-KMP3$!````PJ#U
+M3VT,'Z````````````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``
+M``````````````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````````
+M``````````"`MP%````!>-KMP3$!````PJ#U3VT,'Z``````````````````
+M``"`MP%````!>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%`
+M```!>-KMP3$!````PJ#U3VT,'Z````````````````````"`MP%````!>-KM
+MP3$!````PJ#U3VT,'Z````````````````````"`MP%````!>-KMT#$!```,
+M`B"C&]T*>_9!!!(```````````````````!X405W`]:``($`````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+M````````````````````````````````````````````````````````````
+2````````````````````````
+`
+end
diff --git a/tests/sys/geom/class/uzip/1_test.sh b/tests/sys/geom/class/uzip/1_test.sh
new file mode 100644
index 000000000000..c73f20e18764
--- /dev/null
+++ b/tests/sys/geom/class/uzip/1_test.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# $FreeBSD$
+
+testsdir=$(dirname $0)
+. $testsdir/conf.sh
+
+# Check host endianness
+ret=$(echo I | tr -d "[:space:]" | od -to2 | head -n1 | awk '{print $2}' | cut -c6)
+if [ "$ret" = "1" ]; then
+ # Little endian
+ UUE=$testsdir/1_endian_little.img.uzip.uue
+elif [ "$ret" = "0" ]; then
+ # Big endian
+ UUE=$testsdir/1_endian_big.img.uzip.uue
+else
+ echo "Couldn't detect host endianness"
+ exit 2
+fi
+
+echo "1..1"
+
+uudecode $UUE
+us0=$(attach_md -f $(basename $UUE .uue)) || exit 1
+sleep 1
+
+mount -o ro /dev/${us0}.uzip "${mntpoint}" || exit 1
+
+#cat "${mntpoint}/etalon.txt"
+diff -I '\$FreeBSD.*\$' -u $testsdir/etalon/etalon.txt "${mntpoint}/etalon.txt"
+if [ $? -eq 0 ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
diff --git a/tests/sys/geom/class/uzip/Makefile b/tests/sys/geom/class/uzip/Makefile
new file mode 100644
index 000000000000..88f9e47ccced
--- /dev/null
+++ b/tests/sys/geom/class/uzip/Makefile
@@ -0,0 +1,42 @@
+#
+# $FreeBSD$
+#
+# Regression test for geom_uzip.
+#
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/geom/class/${.CURDIR:T}
+
+IMAGE= 1_endian_unknown_autogenerated.img
+ZIMAGE= ${IMAGE}.uzip
+UZIMAGE= ${ZIMAGE}.uue
+
+CLEANFILES+= ${IMAGE} ${UZIMAGE} ${ZIMAGE}
+
+${IMAGE}:
+ makefs -s 1048576 ${.TARGET} ${.CURDIR}/etalon
+
+${ZIMAGE}: ${IMAGE}
+ mkuzip -o ${.TARGET} ${.ALLSRC}
+
+${UZIMAGE}: ${IMAGE} ${ZIMAGE}
+ printf "#\n# $$" >${.TARGET}
+ printf "FreeBSD$$\n#\n\n" >> ${.TARGET}
+ uuencode ${ZIMAGE} ${ZIMAGE} >>${.TARGET}
+
+${PACKAGE}FILES+= conf.sh 1_endian_big.img.uzip.uue \
+ 1_endian_little.img.uzip.uue
+
+FILESGROUPS+= etalon
+etalon+= etalon/etalon.txt
+etalonDIR= ${TESTSDIR}/etalon
+etalonPACKAGE= ${PACKAGE}
+
+TAP_TESTS_SH+= 1_test
+
+.for t in ${TAP_TESTS_SH}
+TEST_METADATA.$t+= required_user="root"
+.endfor
+
+.include <bsd.test.mk>
diff --git a/tests/sys/geom/class/uzip/Makefile.depend b/tests/sys/geom/class/uzip/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/geom/class/uzip/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/geom/class/uzip/conf.sh b/tests/sys/geom/class/uzip/conf.sh
new file mode 100755
index 000000000000..9a22841fdbe9
--- /dev/null
+++ b/tests/sys/geom/class/uzip/conf.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+# $FreeBSD$
+
+class="uzip"
+base=`basename $0`
+
+uzip_test_cleanup()
+{
+ if [ -n "$mntpoint" ]; then
+ umount $mntpoint
+ rmdir $mntpoint
+ fi
+ geom_test_cleanup
+}
+trap uzip_test_cleanup ABRT EXIT INT TERM
+
+. `dirname $0`/../geom_subr.sh
+
+# NOTE: make sure $TMPDIR has been set by geom_subr.sh if unset [by kyua, etc]
+mntpoint=$(mktemp -d tmp.XXXXXX) || exit
diff --git a/tests/sys/geom/class/uzip/etalon/etalon.txt b/tests/sys/geom/class/uzip/etalon/etalon.txt
new file mode 100644
index 000000000000..cb8acf31f650
--- /dev/null
+++ b/tests/sys/geom/class/uzip/etalon/etalon.txt
@@ -0,0 +1,43 @@
+#
+# $FreeBSD$
+#
+
+JABBERWOCKY
+
+Lewis Carroll
+(from Through the Looking-Glass and What Alice Found There, 1872)
+
+`Twas brillig, and the slithy toves
+Did gyre and gimble in the wabe:
+All mimsy were the borogoves,
+And the mome raths outgrabe.
+
+"Beware the Jabberwock, my son!
+The jaws that bite, the claws that catch!
+Beware the Jubjub bird, and shun
+The frumious Bandersnatch!"
+
+He took his vorpal sword in hand:
+Long time the manxome foe he sought --
+So rested he by the Tumtum tree,
+And stood awhile in thought.
+
+And, as in uffish thought he stood,
+The Jabberwock, with eyes of flame,
+Came whiffling through the tulgey wood,
+And burbled as it came!
+
+One, two! One, two! And through and through
+The vorpal blade went snicker-snack!
+He left it dead, and with its head
+He went galumphing back.
+
+"And, has thou slain the Jabberwock?
+Come to my arms, my beamish boy!
+O frabjous day! Callooh! Callay!'
+He chortled in his joy.
+
+`Twas brillig, and the slithy toves
+Did gyre and gimble in the wabe;
+All mimsy were the borogoves,
+And the mome raths outgrabe.
diff --git a/tests/sys/kern/Makefile b/tests/sys/kern/Makefile
new file mode 100644
index 000000000000..61b315f18d12
--- /dev/null
+++ b/tests/sys/kern/Makefile
@@ -0,0 +1,46 @@
+# $FreeBSD$
+
+TESTSRC= ${SRCTOP}/contrib/netbsd-tests/kernel
+.PATH: ${SRCTOP}/sys/kern
+
+TESTSDIR= ${TESTSBASE}/sys/kern
+
+ATF_TESTS_C+= kern_copyin
+ATF_TESTS_C+= kern_descrip_test
+ATF_TESTS_C+= ptrace_test
+ATF_TESTS_C+= reaper
+PLAIN_TESTS_C+= subr_unit_test
+ATF_TESTS_C+= unix_seqpacket_test
+ATF_TESTS_C+= unix_passfd_test
+TEST_METADATA.unix_seqpacket_test+= timeout="15"
+ATF_TESTS_C+= waitpid_nohang
+
+ATF_TESTS_SH+= coredump_phnum_test
+
+BINDIR= ${TESTSDIR}
+PROGS+= coredump_phnum_helper
+
+LIBADD.ptrace_test+= pthread
+LIBADD.unix_seqpacket_test+= pthread
+
+NETBSD_ATF_TESTS_C+= lockf_test
+NETBSD_ATF_TESTS_C+= mqueue_test
+
+CFLAGS.mqueue_test+= -I${SRCTOP}/tests
+LIBADD.mqueue_test+= rt
+
+# subr_unit.c contains functions whose prototypes lie in headers that cannot be
+# included in userland. But as far as subr_unit_test goes, they're effectively
+# static. So it's ok to disable -Wmissing-prototypes for this program.
+CFLAGS.subr_unit.c+= -Wno-missing-prototypes
+SRCS.subr_unit_test+= subr_unit.c
+
+WARNS?= 5
+
+TESTS_SUBDIRS+= acct
+TESTS_SUBDIRS+= execve
+TESTS_SUBDIRS+= pipe
+
+.include <netbsd-tests.test.mk>
+
+.include <bsd.test.mk>
diff --git a/tests/sys/kern/Makefile.depend b/tests/sys/kern/Makefile.depend
new file mode 100644
index 000000000000..e2d637cb0fc5
--- /dev/null
+++ b/tests/sys/kern/Makefile.depend
@@ -0,0 +1,22 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/atf/libatf-c \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libnetbsd \
+ lib/librt \
+ lib/libthr \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/kern/acct/Makefile b/tests/sys/kern/acct/Makefile
new file mode 100644
index 000000000000..ca3df4c6c703
--- /dev/null
+++ b/tests/sys/kern/acct/Makefile
@@ -0,0 +1,19 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/kern/acct
+
+ATF_TESTS_C= acct_test
+
+CFLAGS+= -I${.OBJDIR}
+
+CLEANFILES+= convert.c convert.c.tmp
+
+DPSRCS.acct_test= convert.c
+acct_test.o: convert.c
+
+convert.c: ${SRCTOP}/sys/kern/kern_acct.c
+ sed -n -e 's/log(/syslog(/g' \
+ -e '/FLOAT_CONVERSION_START/,/FLOAT_CONVERSION_END/p' ${.ALLSRC} >${.TARGET}.tmp
+ mv ${.TARGET}.tmp ${.TARGET}
+
+.include <bsd.test.mk>
diff --git a/tests/sys/kern/acct/Makefile.depend b/tests/sys/kern/acct/Makefile.depend
new file mode 100644
index 000000000000..1e94e70f7739
--- /dev/null
+++ b/tests/sys/kern/acct/Makefile.depend
@@ -0,0 +1,20 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/atf/libatf-c \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/msun \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/kern/acct/acct_test.c b/tests/sys/kern/acct/acct_test.c
new file mode 100644
index 000000000000..c2e9673df8f2
--- /dev/null
+++ b/tests/sys/kern/acct/acct_test.c
@@ -0,0 +1,240 @@
+/*-
+ * Copyright (c) 2011 Giorgos Keramidas. All rights reserved.
+ * Copyright (c) 2007 Diomidis Spinellis. 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 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 AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <syslog.h>
+#include <time.h>
+
+#include <atf-c.h>
+
+#define KASSERT(val, msg) assert(val)
+
+typedef u_int32_t comp_t;
+
+#define AHZ 1000000
+
+#include "convert.c"
+
+union cf {
+ comp_t c;
+ float f;
+};
+
+static void
+check_result(const char *name, float expected, union cf v)
+{
+ double eps;
+
+ eps = fabs(expected - v.f) / expected;
+ ATF_CHECK(eps <= FLT_EPSILON);
+ if (eps > FLT_EPSILON) {
+ printf("Error in %s\n", name);
+ printf("Got 0x%08x %12g\n", v.c, v.f);
+ v.f = expected;
+ printf("Expected 0x%08x %12g (%.15lg)\n", v.c, v.f, expected);
+ printf("Epsilon=%lg, rather than %g\n", eps, FLT_EPSILON);
+ }
+}
+
+/*
+ * Test case for encoding {0 sec, 0 usec} within a reasonable epsilon.
+ */
+
+ATF_TC_WITHOUT_HEAD(encode_tv_zero);
+ATF_TC_BODY(encode_tv_zero, tc)
+{
+ union cf v;
+ struct timeval tv;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ v.c = encode_timeval(tv);
+ ATF_CHECK(fabs(v.f - 0.0) < FLT_EPSILON);
+}
+
+/*
+ * Test case for encoding a random long number.
+ */
+
+ATF_TC_WITHOUT_HEAD(encode_long);
+ATF_TC_BODY(encode_long, tc)
+{
+ union cf v;
+ long l;
+
+ l = random();
+ v.c = encode_long(l);
+ check_result(atf_tc_get_ident(tc), l, v);
+}
+
+/*
+ * Test case for encoding a small number of seconds {1 sec, 0 usec}.
+ */
+
+ATF_TC_WITHOUT_HEAD(encode_tv_only_sec);
+ATF_TC_BODY(encode_tv_only_sec, tc)
+{
+ union cf v;
+ struct timeval tv;
+
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ v.c = encode_timeval(tv);
+ check_result(atf_tc_get_ident(tc),
+ (float)tv.tv_sec * AHZ + tv.tv_usec, v);
+}
+
+/*
+ * Test case for encoding a small number of usec {0 sec, 1 usec}.
+ */
+
+ATF_TC_WITHOUT_HEAD(encode_tv_only_usec);
+ATF_TC_BODY(encode_tv_only_usec, tc)
+{
+ union cf v;
+ struct timeval tv;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 1;
+ v.c = encode_timeval(tv);
+ check_result(atf_tc_get_ident(tc),
+ (float)tv.tv_sec * AHZ + tv.tv_usec, v);
+}
+
+/*
+ * Test case for encoding a large number of usec {1 sec, 999.999 usec}.
+ */
+
+ATF_TC_WITHOUT_HEAD(encode_tv_many_usec);
+ATF_TC_BODY(encode_tv_many_usec, tc)
+{
+ union cf v;
+ struct timeval tv;
+
+ tv.tv_sec = 1;
+ tv.tv_usec = 999999L;
+ v.c = encode_timeval(tv);
+ check_result(atf_tc_get_ident(tc),
+ (float)tv.tv_sec * AHZ + tv.tv_usec, v);
+}
+
+/*
+ * Test case for encoding a huge number of usec {1 sec, 1.000.000 usec} that
+ * overflows the usec counter and should show up as an increase in timeval's
+ * seconds instead.
+ */
+
+ATF_TC_WITHOUT_HEAD(encode_tv_usec_overflow);
+ATF_TC_BODY(encode_tv_usec_overflow, tc)
+{
+ union cf v;
+ struct timeval tv;
+
+ tv.tv_sec = 1;
+ tv.tv_usec = 1000000L;
+ v.c = encode_timeval(tv);
+ check_result(atf_tc_get_ident(tc),
+ (float)tv.tv_sec * AHZ + tv.tv_usec, v);
+}
+
+/*
+ * Test case for encoding a very large number of seconds, one that is very
+ * near to the limit of 32-bit signed values. With a usec value of 999.999
+ * microseconds this should result in the largest value we can represent with
+ * a timeval struct.
+ */
+
+ATF_TC_WITHOUT_HEAD(encode_tv_upper_limit);
+ATF_TC_BODY(encode_tv_upper_limit, tc)
+{
+ union cf v;
+ struct timeval tv;
+
+ tv.tv_sec = 2147483647L;
+ tv.tv_usec = 999999L;
+ v.c = encode_timeval(tv);
+ check_result(atf_tc_get_ident(tc),
+ (float)tv.tv_sec * AHZ + tv.tv_usec, v);
+}
+
+/*
+ * Test case for encoding a million random timeval objects, and checking that
+ * the conversion does not diverge too much from the expected values.
+ */
+
+ATF_TC_WITHOUT_HEAD(encode_tv_random_million);
+ATF_TC_BODY(encode_tv_random_million, tc)
+{
+ union cf v;
+ struct timeval tv;
+ long k;
+
+#ifdef __LP64__
+ atf_tc_expect_fail("the testcase violates FLT_EPSILON on 64-bit "
+ "platforms, e.g. amd64");
+#endif
+
+ ATF_REQUIRE_MSG(unsetenv("TZ") == 0, "unsetting TZ failed; errno=%d", errno);
+
+ for (k = 1; k < 1000000L; k++) {
+ tv.tv_sec = random();
+ tv.tv_usec = (random() % 1000000L);
+ v.c = encode_timeval(tv);
+ check_result(atf_tc_get_ident(tc),
+ (float)tv.tv_sec * AHZ + tv.tv_usec, v);
+ }
+}
+
+/* ---------------------------------------------------------------------
+ * Main.
+ * --------------------------------------------------------------------- */
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, encode_long);
+ ATF_TP_ADD_TC(tp, encode_tv_zero);
+ ATF_TP_ADD_TC(tp, encode_tv_only_sec);
+ ATF_TP_ADD_TC(tp, encode_tv_only_usec);
+ ATF_TP_ADD_TC(tp, encode_tv_many_usec);
+ ATF_TP_ADD_TC(tp, encode_tv_usec_overflow);
+ ATF_TP_ADD_TC(tp, encode_tv_upper_limit);
+ ATF_TP_ADD_TC(tp, encode_tv_random_million);
+
+ return atf_no_error();
+}
diff --git a/tests/sys/kern/coredump_phnum_helper.c b/tests/sys/kern/coredump_phnum_helper.c
new file mode 100644
index 000000000000..da023e691a24
--- /dev/null
+++ b/tests/sys/kern/coredump_phnum_helper.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2016, Conrad Meyer
+ * 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 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/mman.h>
+
+#include <err.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/*
+ * This program is intended to create a bunch of segment mappings, then dump
+ * core.
+ */
+int
+main(int argc __unused, char **argv __unused)
+{
+ void *v;
+ unsigned i;
+
+ for (i = 0; i < UINT16_MAX + 1000; i++) {
+ /*
+ * Alternate protections; otherwise the kernel will just extend
+ * the adjacent same-protection previous mapping.
+ */
+ v = mmap(NULL, PAGE_SIZE,
+ (((i % 2) == 0) ? PROT_READ : 0) | PROT_WRITE,
+ MAP_ANON | MAP_PRIVATE, -1, 0);
+ if (v == MAP_FAILED)
+ err(1, "mmap");
+ }
+
+ /* Dump core. */
+ abort();
+}
diff --git a/tests/sys/kern/coredump_phnum_test.sh b/tests/sys/kern/coredump_phnum_test.sh
new file mode 100755
index 000000000000..cb38ff0ca929
--- /dev/null
+++ b/tests/sys/kern/coredump_phnum_test.sh
@@ -0,0 +1,92 @@
+#
+# Copyright (c) 2016 Dell EMC Isilon
+# 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,
+# without modification.
+# 2. Redistributions in binary form must reproduce at minimum a disclaimer
+# substantially similar to the "NO WARRANTY" disclaimer below
+# ("Disclaimer") and any redistribution must be conditioned upon
+# including a substantially similar Disclaimer requirement for further
+# binary redistribution.
+#
+# NO WARRANTY
+# 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 MERCHANTIBILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+#
+# $FreeBSD$
+
+atf_test_case coredump_phnum cleanup
+coredump_phnum_head()
+{
+ atf_set "descr" "More than 65534 segments"
+ atf_set "require.config" "allow_sysctl_side_effects"
+ atf_set "require.progs" "readelf procstat"
+ atf_set "require.user" "root"
+}
+coredump_phnum_body()
+{
+ atf_expect_fail "elftoolchain (base) readelf doesn't handle extended program header numbers; bug # 215019"
+
+ # Set up core dumping
+ cat > coredump_phnum_restore_state.sh <<-EOF
+ #!/bin/sh
+ ulimit -c '$(ulimit -c)'
+ sysctl kern.coredump=$(sysctl -n kern.coredump)
+ sysctl kern.corefile='$(sysctl -n kern.corefile)'
+EOF
+ chmod +x coredump_phnum_restore_state.sh
+
+ ulimit -c unlimited
+ sysctl kern.coredump=1
+ sysctl kern.corefile="$(pwd)/coredump_phnum_helper.core"
+
+ atf_check -s signal:sigabrt "$(atf_get_srcdir)/coredump_phnum_helper"
+
+ # Check that core looks good
+ if [ ! -f coredump_phnum_helper.core ]; then
+ atf_fail "Helper program did not dump core"
+ fi
+
+ # These magic numbers don't have any real significance. They are just
+ # the result of running the helper program and dumping core. The only
+ # important bit is that they're larger than 65535 (UINT16_MAX).
+ readelf -h coredump_phnum_helper.core | \
+ atf_check -o "match:65535 \(66[0-9]{3}\)" \
+ grep "Number of program headers:"
+ readelf -l coredump_phnum_helper.core | \
+ atf_check -o "match:There are 66[0-9]{3} program headers" \
+ grep -1 "program headers"
+ readelf -S coredump_phnum_helper.core | \
+ atf_check -o "match: 0000000000000001 .* 66[0-9]{3} " \
+ grep -A1 "^ \[ 0\] "
+
+ procstat -v coredump_phnum_helper.core | \
+ atf_check -o "match:66[0-9]{3}" wc -l
+}
+coredump_phnum_cleanup()
+{
+ rm -f coredump_phnum_helper.core
+ if [ -x coredump_phnum_restore_state.sh ]; then
+ ./coredump_phnum_restore_state.sh
+ fi
+ rm -f coredump_phnum_restore_state.sh
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case coredump_phnum
+}
diff --git a/tests/sys/kern/execve/Makefile b/tests/sys/kern/execve/Makefile
new file mode 100644
index 000000000000..82c5d4b85b10
--- /dev/null
+++ b/tests/sys/kern/execve/Makefile
@@ -0,0 +1,39 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/kern/execve
+
+BINDIR= ${TESTSDIR}
+
+MAN=
+
+ATF_TESTS_SH+= execve_test
+
+PROGS+= good_aout
+PROGS+= execve_helper
+
+LDFLAGS.goodaout+= -static
+
+CLEANFILES+= empty
+CLEANFILES+= sparse_aout
+CLEANFILES+= trunc_aout
+
+SCRIPTS+= bad_interp_len
+SCRIPTS+= dev_null_script
+SCRIPTS+= empty
+SCRIPTS+= good_script
+SCRIPTS+= non_exist_shell
+SCRIPTS+= script_arg
+SCRIPTS+= script_arg_nospace
+SCRIPTS+= sparse_aout
+SCRIPTS+= trunc_aout
+
+empty:
+ @touch $@
+
+sparse_aout:
+ @truncate -s 20480 $@
+
+trunc_aout:
+ @truncate -s 16 $@
+
+.include <bsd.test.mk>
diff --git a/tests/sys/kern/execve/Makefile.depend b/tests/sys/kern/execve/Makefile.depend
new file mode 100644
index 000000000000..9cb890b58360
--- /dev/null
+++ b/tests/sys/kern/execve/Makefile.depend
@@ -0,0 +1,17 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/kern/execve/bad_interp_len b/tests/sys/kern/execve/bad_interp_len
new file mode 100644
index 000000000000..96c049fdc839
--- /dev/null
+++ b/tests/sys/kern/execve/bad_interp_len
@@ -0,0 +1,4 @@
+#! 456789012345678 0123456789012345 789012345678 012345678901234 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890
+# $FreeBSD$
+
+echo succeeded
diff --git a/tests/sys/kern/execve/dev_null_script b/tests/sys/kern/execve/dev_null_script
new file mode 100644
index 000000000000..73b10205cdbb
--- /dev/null
+++ b/tests/sys/kern/execve/dev_null_script
@@ -0,0 +1,4 @@
+#! /dev/null
+# $FreeBSD$
+
+echo succeeded
diff --git a/tests/sys/kern/execve/execve_helper.c b/tests/sys/kern/execve/execve_helper.c
new file mode 100644
index 000000000000..164a8f3fc61e
--- /dev/null
+++ b/tests/sys/kern/execve/execve_helper.c
@@ -0,0 +1,54 @@
+/* $NetBSD: doexec.c,v 1.8 2003/07/26 19:38:48 salo Exp $ */
+
+/*
+ * Copyright (c) 1993 Christopher G. Demetriou
+ * 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 for the
+ * NetBSD Project. See http://www.NetBSD.org/ for
+ * information about NetBSD.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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$
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int
+main(int argc, char **argv)
+{
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s <progname>\n", argv[0]);
+ exit(2);
+ }
+
+ execve(argv[1], &argv[1], NULL);
+ err(1, "");
+}
diff --git a/tests/sys/kern/execve/execve_test.sh b/tests/sys/kern/execve/execve_test.sh
new file mode 100644
index 000000000000..ef803a18d442
--- /dev/null
+++ b/tests/sys/kern/execve/execve_test.sh
@@ -0,0 +1,115 @@
+
+bad_interp_len_head()
+{
+ atf_set "descr" "Bad interpreter length"
+}
+bad_interp_len_body()
+{
+ atf_check -s exit:1 -e 'match:No such file or directory' -o empty \
+ -x "cd $(atf_get_srcdir) && ./execve_helper bad_interp_len"
+}
+
+empty_head()
+{
+ atf_set "descr" "Empty file"
+}
+empty_body()
+{
+ atf_check -s exit:1 -e 'match:Exec format error' -o empty \
+ -x "cd $(atf_get_srcdir) && ./execve_helper empty"
+}
+
+good_aout_head()
+{
+ atf_set "descr" "Good a.out"
+}
+good_aout_body()
+{
+ atf_check -s exit:0 -e empty -o 'match:succeeded' \
+ -x "cd $(atf_get_srcdir) && ./execve_helper ./good_aout"
+}
+
+good_script_head()
+{
+ atf_set "descr" "Good script"
+}
+good_script_body()
+{
+ atf_check -s exit:0 -e empty -o 'match:succeeded' \
+ -x "cd $(atf_get_srcdir) && ./execve_helper good_script"
+}
+
+non_exist_head()
+{
+ atf_set "descr" "Non-existent file"
+}
+non_exist_body()
+{
+ atf_check -s exit:1 -e 'match:No such file or directory' -o empty \
+ -x "cd $(atf_get_srcdir) && ./execve_helper non_exist"
+}
+
+non_exist_shell_head()
+{
+ atf_set "descr" "Non-existent shell"
+}
+non_exist_shell_body()
+{
+ atf_check -s exit:1 -e 'match:No such file or directory' -o empty \
+ -x "cd $(atf_get_srcdir) && ./execve_helper non_exist_shell"
+}
+
+script_arg_head()
+{
+ atf_set "descr" "-x in the shebang"
+}
+script_arg_body()
+{
+ atf_check -s exit:0 -e 'match:\+ echo succeeded' -o 'match:succeeded' \
+ -x "cd $(atf_get_srcdir) && ./execve_helper script_arg"
+}
+
+script_arg_nospace_head()
+{
+ atf_set "descr" '-x in the shebang; no space between #! and /bin/sh'
+}
+script_arg_nospace_body()
+{
+ atf_check -s exit:0 -e 'match:\+ echo succeeded' -o 'match:succeeded' \
+ -x "cd $(atf_get_srcdir) && ./execve_helper script_arg_nospace"
+}
+
+sparse_aout_head()
+{
+ atf_set "descr" 'Sparse file'
+}
+sparse_aout_body()
+{
+ atf_check -s exit:1 -e 'match:Exec format error' -o empty \
+ -x "cd $(atf_get_srcdir) && ./execve_helper sparse_aout"
+}
+
+trunc_aout_head()
+{
+ atf_set "descr" 'Truncated file'
+}
+trunc_aout_body()
+{
+ atf_check -s exit:1 -e 'match:Exec format error' -o empty \
+ -x "cd $(atf_get_srcdir) && ./execve_helper trunc_aout"
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case bad_interp_len
+ atf_add_test_case empty
+ atf_add_test_case good_aout
+ atf_add_test_case good_script
+ atf_add_test_case non_exist
+ atf_add_test_case non_exist_shell
+ atf_add_test_case script_arg
+ atf_add_test_case script_arg_nospace
+ atf_add_test_case sparse_aout
+ atf_add_test_case trunc_aout
+
+}
diff --git a/tests/sys/kern/execve/good_aout.c b/tests/sys/kern/execve/good_aout.c
new file mode 100644
index 000000000000..39e986770c5d
--- /dev/null
+++ b/tests/sys/kern/execve/good_aout.c
@@ -0,0 +1,45 @@
+/* $NetBSD: goodaout.c,v 1.8 2003/07/26 19:38:49 salo Exp $ */
+
+/*-
+ * Copyright (c) 1993 Christopher G. Demetriou
+ * 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 for the
+ * NetBSD Project. See http://www.NetBSD.org/ for
+ * information about NetBSD.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main(void)
+{
+ printf("succeeded\n");
+ exit(0);
+}
diff --git a/tests/sys/kern/execve/good_script b/tests/sys/kern/execve/good_script
new file mode 100644
index 000000000000..11c76898ca2e
--- /dev/null
+++ b/tests/sys/kern/execve/good_script
@@ -0,0 +1,4 @@
+#!/bin/sh
+# $FreeBSD$
+
+echo succeeded
diff --git a/tests/sys/kern/execve/non_exist_shell b/tests/sys/kern/execve/non_exist_shell
new file mode 100644
index 000000000000..f9ee705bd9e9
--- /dev/null
+++ b/tests/sys/kern/execve/non_exist_shell
@@ -0,0 +1,4 @@
+#! /foo/bar/baz
+# $FreeBSD$
+
+echo foo
diff --git a/tests/sys/kern/execve/script_arg b/tests/sys/kern/execve/script_arg
new file mode 100644
index 000000000000..2700f1c3a38f
--- /dev/null
+++ b/tests/sys/kern/execve/script_arg
@@ -0,0 +1,4 @@
+#! /bin/sh -x
+# $FreeBSD$
+
+echo succeeded
diff --git a/tests/sys/kern/execve/script_arg_nospace b/tests/sys/kern/execve/script_arg_nospace
new file mode 100644
index 000000000000..6731ad50f4ac
--- /dev/null
+++ b/tests/sys/kern/execve/script_arg_nospace
@@ -0,0 +1,4 @@
+#!/bin/sh -x
+# $FreeBSD$
+
+echo succeeded
diff --git a/tests/sys/kern/kern_copyin.c b/tests/sys/kern/kern_copyin.c
new file mode 100644
index 000000000000..a8cf0422eec5
--- /dev/null
+++ b/tests/sys/kern/kern_copyin.c
@@ -0,0 +1,96 @@
+/*-
+ * Copyright (c) 2015 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Konstantin Belousov <kib@FreeBSD.org>
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * 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 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <atf-c.h>
+#include <vm/vm.h>
+#include <vm/pmap.h>
+#include <machine/vmparam.h>
+
+static int scratch_file;
+
+static int
+copyin_checker(uintptr_t uaddr, size_t len)
+{
+ ssize_t ret;
+
+ ret = write(scratch_file, (const void *)uaddr, len);
+ return (ret == -1 ? errno : 0);
+}
+
+#define FMAX ULONG_MAX
+
+ATF_TC_WITHOUT_HEAD(kern_copyin);
+ATF_TC_BODY(kern_copyin, tc)
+{
+ char template[] = "copyin.XXXXXX";
+
+#ifdef __mips__
+ /*
+ * MIPS has different VM layout: the UVA map on mips ends the
+ * highest mapped entry at the VM_MAXUSER_ADDRESS - PAGE_SIZE,
+ * while all other arches map either stack or shared page up
+ * to the VM_MAXUSER_ADDRESS.
+ */
+ atf_tc_skip("Platform is not supported.");
+#endif
+
+ scratch_file = mkstemp(template);
+ ATF_REQUIRE(scratch_file != -1);
+ unlink(template);
+
+ ATF_CHECK(copyin_checker(0, 0) == 0);
+ ATF_CHECK(copyin_checker(VM_MAXUSER_ADDRESS - 10, 9) == 0);
+ ATF_CHECK(copyin_checker(VM_MAXUSER_ADDRESS - 10, 10) == 0);
+ ATF_CHECK(copyin_checker(VM_MAXUSER_ADDRESS - 10, 11) == EFAULT);
+ ATF_CHECK(copyin_checker(VM_MAXUSER_ADDRESS - 1, 1) == 0);
+ ATF_CHECK(copyin_checker(VM_MAXUSER_ADDRESS, 0) == 0);
+ ATF_CHECK(copyin_checker(VM_MAXUSER_ADDRESS, 1) == EFAULT);
+ ATF_CHECK(copyin_checker(VM_MAXUSER_ADDRESS, 2) == EFAULT);
+ ATF_CHECK(copyin_checker(VM_MAXUSER_ADDRESS + 1, 0) == 0);
+ ATF_CHECK(copyin_checker(VM_MAXUSER_ADDRESS + 1, 2) == EFAULT);
+ ATF_CHECK(copyin_checker(FMAX - 10, 9) == EFAULT);
+ ATF_CHECK(copyin_checker(FMAX - 10, 10) == EFAULT);
+ ATF_CHECK(copyin_checker(FMAX - 10, 11) == EFAULT);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, kern_copyin);
+ return (atf_no_error());
+}
diff --git a/tests/sys/kern/kern_descrip_test.c b/tests/sys/kern/kern_descrip_test.c
new file mode 100644
index 000000000000..07240d75b900
--- /dev/null
+++ b/tests/sys/kern/kern_descrip_test.c
@@ -0,0 +1,191 @@
+/*-
+ * Copyright (c) 2014 EMC Corp.
+ * 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 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 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <sys/limits.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <unistd.h>
+#include <atf-c.h>
+
+static volatile sig_atomic_t done;
+
+#define AFILE "afile"
+#define EXPANDBY 1000
+#define PARALLEL 4
+#define RENDEZVOUS "rendezvous"
+#define VALUE "value"
+
+ATF_TC_WITHOUT_HEAD(dup2__simple);
+ATF_TC_BODY(dup2__simple, tc)
+{
+ int fd1, fd2;
+ struct stat sb1, sb2;
+
+ ATF_REQUIRE((fd1 = open(AFILE, O_CREAT, 0644)) != -1);
+ fd2 = 27;
+ ATF_REQUIRE(dup2(fd1, fd2) != -1);
+ ATF_REQUIRE(fstat(fd1, &sb1) != -1);
+ ATF_REQUIRE(fstat(fd2, &sb2) != -1);
+ ATF_REQUIRE(bcmp(&sb1, &sb2, sizeof(sb1)) == 0);
+}
+
+ATF_TC(dup2__ebadf_when_2nd_arg_out_of_range);
+ATF_TC_HEAD(dup2__ebadf_when_2nd_arg_out_of_range, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Regression test for r234131");
+}
+
+ATF_TC_BODY(dup2__ebadf_when_2nd_arg_out_of_range, tc)
+{
+ int fd1, fd2, ret;
+
+ ATF_REQUIRE((fd1 = open(AFILE, O_CREAT, 0644)) != -1);
+ fd2 = INT_MAX;
+ ret = dup2(fd1, fd2);
+ ATF_CHECK_EQ(-1, ret);
+ ATF_CHECK_EQ(EBADF, errno);
+}
+
+static void
+handler(int s __unused)
+{
+ done++;
+}
+
+static void
+openfiles2(size_t n)
+{
+ size_t i;
+ int r;
+
+ errno = 0;
+ for (i = 0; i < n; i++)
+ ATF_REQUIRE((r = open(AFILE, O_RDONLY)) != -1);
+ kill(getppid(), SIGUSR1);
+
+ for (;;) {
+ if (access(RENDEZVOUS, R_OK) != 0)
+ break;
+ usleep(1000);
+ }
+ _exit(0);
+}
+
+static void
+openfiles(size_t n)
+{
+ int i, fd;
+
+ signal(SIGUSR1, handler);
+ ATF_REQUIRE((fd = open(AFILE, O_CREAT, 0644)) != -1);
+ close(fd);
+ ATF_REQUIRE((fd = open(RENDEZVOUS, O_CREAT, 0644)) != -1);
+ close(fd);
+ done = 0;
+ for (i = 0; i < PARALLEL; i++)
+ if (fork() == 0)
+ openfiles2(n / PARALLEL);
+ while (done != PARALLEL)
+ usleep(1000);
+ unlink(RENDEZVOUS);
+ usleep(40000);
+}
+
+ATF_TC_WITH_CLEANUP(kern_maxfiles__increase);
+ATF_TC_HEAD(kern_maxfiles__increase, tc)
+{
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "require.config", "allow_sysctl_side_effects");
+ atf_tc_set_md_var(tc, "descr",
+ "Check kern.maxfiles expansion");
+}
+
+ATF_TC_BODY(kern_maxfiles__increase, tc)
+{
+ size_t oldlen;
+ int maxfiles, oldmaxfiles, current;
+ char buf[80];
+
+ oldlen = sizeof(maxfiles);
+ if (sysctlbyname("kern.maxfiles", &maxfiles, &oldlen, NULL, 0) == -1)
+ atf_tc_fail("getsysctlbyname(%s): %s", "kern.maxfiles",
+ strerror(errno));
+ if (sysctlbyname("kern.openfiles", &current, &oldlen, NULL, 0) == -1)
+ atf_tc_fail("getsysctlbyname(%s): %s", "kern.openfiles",
+ strerror(errno));
+
+ oldmaxfiles = maxfiles;
+
+ /* Store old kern.maxfiles in a symlink for cleanup */
+ snprintf(buf, sizeof(buf), "%d", oldmaxfiles);
+ if (symlink(buf, VALUE) == 1)
+ atf_tc_fail("symlink(%s, %s): %s", buf, VALUE,
+ strerror(errno));
+
+ maxfiles += EXPANDBY;
+ if (sysctlbyname("kern.maxfiles", NULL, 0, &maxfiles, oldlen) == -1)
+ atf_tc_fail("getsysctlbyname(%s): %s", "kern.maxfiles",
+ strerror(errno));
+
+ openfiles(oldmaxfiles - current + 1);
+ (void)unlink(VALUE);
+}
+
+ATF_TC_CLEANUP(kern_maxfiles__increase, tc)
+{
+ size_t oldlen;
+ int n, oldmaxfiles;
+ char buf[80];
+
+ if ((n = readlink(VALUE, buf, sizeof(buf))) > 0) {
+ buf[MIN((size_t)n, sizeof(buf) - 1)] = '\0';
+ if (sscanf(buf, "%d", &oldmaxfiles) == 1) {
+ oldlen = sizeof(oldmaxfiles);
+ (void) sysctlbyname("kern.maxfiles", NULL, 0,
+ &oldmaxfiles, oldlen);
+ }
+ }
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, dup2__simple);
+ ATF_TP_ADD_TC(tp, dup2__ebadf_when_2nd_arg_out_of_range);
+ ATF_TP_ADD_TC(tp, kern_maxfiles__increase);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/kern/pipe/Makefile b/tests/sys/kern/pipe/Makefile
new file mode 100644
index 000000000000..bcd2d070cc8b
--- /dev/null
+++ b/tests/sys/kern/pipe/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/kern/pipe
+
+PLAIN_TESTS_C+= big_pipe_test
+PLAIN_TESTS_C+= pipe_fstat_bug_test
+PLAIN_TESTS_C+= pipe_ino_test
+PLAIN_TESTS_C+= pipe_overcommit1_test
+PLAIN_TESTS_C+= pipe_overcommit2_test
+PLAIN_TESTS_C+= pipe_reverse2_test
+PLAIN_TESTS_C+= pipe_reverse_test
+PLAIN_TESTS_C+= pipe_wraparound_test
+
+WARNS?= 6
+
+.include <bsd.test.mk>
diff --git a/tests/sys/kern/pipe/Makefile.depend b/tests/sys/kern/pipe/Makefile.depend
new file mode 100644
index 000000000000..3646e2e2b1af
--- /dev/null
+++ b/tests/sys/kern/pipe/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/kern/pipe/big_pipe_test.c b/tests/sys/kern/pipe/big_pipe_test.c
new file mode 100644
index 000000000000..fa5687e83fec
--- /dev/null
+++ b/tests/sys/kern/pipe/big_pipe_test.c
@@ -0,0 +1,88 @@
+#include <sys/select.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define BIG_PIPE_SIZE 64*1024 /* From sys/pipe.h */
+
+/*
+ * Test for the non-blocking big pipe bug (write(2) returning
+ * EAGAIN while select(2) returns the descriptor as ready for write).
+ *
+ * $FreeBSD$
+ */
+
+static void
+write_frame(int fd, char *buf, unsigned long buflen)
+{
+ fd_set wfd;
+ int i;
+
+ while (buflen) {
+ FD_ZERO(&wfd);
+ FD_SET(fd, &wfd);
+ i = select(fd+1, NULL, &wfd, NULL, NULL);
+ if (i < 0)
+ err(1, "select failed");
+ if (i != 1) {
+ errx(1, "select returned unexpected value %d\n", i);
+ exit(1);
+ }
+ i = write(fd, buf, buflen);
+ if (i < 0) {
+ if (errno != EAGAIN)
+ warn("write failed");
+ exit(1);
+ }
+ buf += i;
+ buflen -= i;
+ }
+}
+
+int
+main(void)
+{
+ /* any value over PIPE_SIZE should do */
+ char buf[BIG_PIPE_SIZE];
+ int i, flags, fd[2];
+
+ if (pipe(fd) < 0)
+ errx(1, "pipe failed");
+
+ flags = fcntl(fd[1], F_GETFL);
+ if (flags == -1 || fcntl(fd[1], F_SETFL, flags|O_NONBLOCK) == -1) {
+ printf("fcntl failed: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ switch (fork()) {
+ case -1:
+ err(1, "fork failed: %s\n", strerror(errno));
+ break;
+ case 0:
+ close(fd[1]);
+ for (;;) {
+ /* Any small size should do */
+ i = read(fd[0], buf, 256);
+ if (i == 0)
+ break;
+ if (i < 0)
+ err(1, "read");
+ }
+ exit(0);
+ default:
+ break;
+ }
+
+ close(fd[0]);
+ memset(buf, 0, sizeof buf);
+ for (i = 0; i < 1000; i++)
+ write_frame(fd[1], buf, sizeof buf);
+
+ printf("ok\n");
+ exit(0);
+}
diff --git a/tests/sys/kern/pipe/pipe_fstat_bug_test.c b/tests/sys/kern/pipe/pipe_fstat_bug_test.c
new file mode 100644
index 000000000000..89060a2f0495
--- /dev/null
+++ b/tests/sys/kern/pipe/pipe_fstat_bug_test.c
@@ -0,0 +1,156 @@
+/*
+Copyright (C) 2004 Michael J. Silbersack. 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 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 AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+*/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/event.h>
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/*
+ * $FreeBSD$
+ * The goal of this program is to see if fstat reports the correct
+ * data count for a pipe. Prior to revision 1.172 of sys_pipe.c,
+ * 0 would be returned once the pipe entered direct write mode.
+ *
+ * Linux (2.6) always returns zero, so it's not a valuable platform
+ * for comparison.
+ */
+
+int
+main(void)
+{
+ char buffer[32768], buffer2[32768], go[] = "go", go2[] = "go2";
+ int desc[2], ipc_coord[2];
+ struct kevent event, ke;
+ ssize_t error;
+ int successes = 0;
+ struct stat status;
+ pid_t new_pid;
+ int kq;
+
+ error = pipe(desc);
+ if (error == -1)
+ err(1, "Couldn't allocate data pipe");
+
+ error = pipe(ipc_coord);
+ if (error == -1)
+ err(1, "Couldn't allocate IPC coordination pipe");
+
+ new_pid = fork();
+ assert(new_pid != -1);
+
+ close(new_pid == 0 ? desc[0] : desc[1]);
+
+#define SYNC_R(i, _buf) do { \
+ int _error = errno; \
+ warnx("%d: waiting for synchronization", __LINE__); \
+ if (read(ipc_coord[i], &_buf, sizeof(_buf)) != sizeof(_buf)) \
+ err(1, "failed to synchronize (%s)", (i == 0 ? "parent" : "child")); \
+ errno = _error; \
+ } while(0)
+
+#define SYNC_W(i, _buf) do { \
+ int _error = errno; \
+ warnx("%d: sending synchronization", __LINE__); \
+ if (write(ipc_coord[i], &_buf, sizeof(_buf)) != sizeof(_buf)) \
+ err(1, "failed to synchronize (%s)", (i == 0 ? "child" : "parent")); \
+ errno = _error; \
+ } while(0)
+
+#define WRITE(s) do { \
+ ssize_t _size; \
+ if ((_size = write(desc[1], &buffer, s)) != s) \
+ warn("short write; wrote %zd, expected %d", _size, s); \
+ } while(0)
+
+ if (new_pid == 0) {
+
+ SYNC_R(0, go);
+ WRITE(145);
+ SYNC_W(0, go2);
+
+ SYNC_R(0, go);
+ WRITE(2048);
+ SYNC_W(0, go2);
+
+ SYNC_R(0, go);
+ WRITE(4096);
+ SYNC_W(0, go2);
+
+ SYNC_R(0, go);
+ WRITE(8191);
+ SYNC_W(0, go2);
+
+ SYNC_R(0, go);
+ SYNC_W(0, go2); /* XXX: why is this required? */
+ WRITE(8192);
+ SYNC_W(0, go2);
+
+ close(ipc_coord[0]);
+ close(ipc_coord[1]);
+
+ _exit(0);
+ }
+
+ kq = kqueue();
+ if (kq == -1)
+ _exit(1);
+
+ EV_SET(&ke, desc[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
+
+ /* Attach event to the kqueue. */
+ if (kevent(kq, &ke, 1, NULL, 0, NULL) != 0)
+ _exit(2);
+
+ while (successes < 5) {
+ SYNC_W(1, go);
+ SYNC_R(1, go2);
+
+ /* Ensure data is available to read */
+ if (kevent(kq, NULL, 0, &event, 1, NULL) != 1)
+ _exit(3);
+
+ fstat(desc[0], &status);
+ error = read(desc[0], &buffer2, sizeof(buffer2));
+
+ if (status.st_size != error)
+ err(1, "FAILURE: stat size %jd read size %zd",
+ (intmax_t)status.st_size, error);
+ if (error > 0) {
+ printf("SUCCESS at stat size %jd read size %zd\n",
+ (intmax_t)status.st_size, error);
+ successes++;
+ }
+ }
+
+ exit(0);
+}
diff --git a/tests/sys/kern/pipe/pipe_ino_test.c b/tests/sys/kern/pipe/pipe_ino_test.c
new file mode 100644
index 000000000000..c31e4d5cfa53
--- /dev/null
+++ b/tests/sys/kern/pipe/pipe_ino_test.c
@@ -0,0 +1,67 @@
+/*-
+ * Copyright (c) 2011 Giovanni Trematerra <giovanni.trematerra@gmail.com>
+ * 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 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 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$
+ * Test conformance to stat(2) SUSv4 description:
+ * "For all other file types defined in this volume of POSIX.1-2008, the
+ * structure members st_mode, st_ino, st_dev, st_uid, st_gid, st_atim,
+ * st_ctim, and st_mtim shall have meaningful values ...".
+ * Check that st_dev and st_ino are meaningful.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <stdio.h>
+#include <unistd.h>
+
+int
+main(void)
+{
+ int pipefd[2];
+ struct stat st1, st2;
+
+ if (pipe(pipefd) == -1)
+ err(1, "FAIL: pipe");
+
+ if (fstat(pipefd[0], &st1) == -1)
+ err(1, "FAIL: fstat st1");
+ if (fstat(pipefd[1], &st2) == -1)
+ err(1, "FAIL: fstat st2");
+ if (st1.st_dev != st2.st_dev || st1.st_dev == 0 || st2.st_dev == 0)
+ errx(1, "FAIL: wrong dev number %ju %ju",
+ (uintmax_t)st1.st_dev, (uintmax_t)st2.st_dev);
+ if (st1.st_ino == st2.st_ino)
+ errx(1, "FAIL: inode numbers are equal: %ju",
+ (uintmax_t)st1.st_ino);
+
+ close(pipefd[0]);
+ close(pipefd[1]);
+ printf("PASS\n");
+
+ return (0);
+}
diff --git a/tests/sys/kern/pipe/pipe_overcommit1_test.c b/tests/sys/kern/pipe/pipe_overcommit1_test.c
new file mode 100644
index 000000000000..4e40be7c699e
--- /dev/null
+++ b/tests/sys/kern/pipe/pipe_overcommit1_test.c
@@ -0,0 +1,51 @@
+/*-
+ * Copyright (C) 2005 Michael J. Silbersack <silby@freebsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice(s), this list of conditions and the following disclaimer as
+ * the first lines of this file unmodified other than the possible
+ * addition of one or more copyright notices.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice(s), 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 COPYRIGHT HOLDER(S) ``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(S) 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 <sys/param.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/*
+ * $FreeBSD$
+ * This program just allocates as many pipes as it can to ensure
+ * that using up all pipe memory doesn't cause a panic.
+ */
+
+int
+main(void)
+{
+ int pipes[10000];
+ unsigned int i;
+
+ for (i = 0; i < nitems(pipes); i++)
+ (void)pipe(&pipes[i]);
+ printf("PASS\n");
+
+ exit(0);
+}
diff --git a/tests/sys/kern/pipe/pipe_overcommit2_test.c b/tests/sys/kern/pipe/pipe_overcommit2_test.c
new file mode 100644
index 000000000000..ee1e714d0d8a
--- /dev/null
+++ b/tests/sys/kern/pipe/pipe_overcommit2_test.c
@@ -0,0 +1,83 @@
+/*-
+ * Copyright (C) 2005 Michael J. Silbersack <silby@freebsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice(s), this list of conditions and the following disclaimer as
+ * the first lines of this file unmodified other than the possible
+ * addition of one or more copyright notices.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice(s), 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 COPYRIGHT HOLDER(S) ``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(S) 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 <sys/param.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/*
+ * $FreeBSD$
+ * This program tests how sys_pipe.c handles the case where there
+ * is ample memory to allocate a pipe, but the file descriptor
+ * limit for that user has been exceeded.
+ */
+
+int
+main(void)
+{
+ char template[] = "pipe.XXXXXXXXXX";
+ int lastfd, pipes[10000], returnval;
+ unsigned int i;
+
+ lastfd = -1;
+
+ if (mkstemp(template) == -1)
+ err(1, "mkstemp failed");
+
+ for (i = 0; i < nitems(pipes); i++) {
+ returnval = open(template, O_RDONLY);
+ if (returnval == -1 && (errno == ENFILE || errno == EMFILE))
+ break; /* All descriptors exhausted. */
+ else
+ lastfd = returnval;
+ }
+
+ /* First falloc failure case in sys_pipe.c:pipe() */
+ for (i = 0; i < 1000; i++) {
+ returnval = pipe(&pipes[i]);
+ }
+
+ /*
+ * Free just one FD so that the second falloc failure
+ * case will occur.
+ */
+ close(lastfd);
+
+ for (i = 0; i < 1000; i++) {
+ returnval = pipe(&pipes[i]);
+ }
+ printf("PASS\n");
+
+ unlink(template);
+
+ exit(0);
+}
diff --git a/tests/sys/kern/pipe/pipe_reverse2_test.c b/tests/sys/kern/pipe/pipe_reverse2_test.c
new file mode 100644
index 000000000000..4666d955b069
--- /dev/null
+++ b/tests/sys/kern/pipe/pipe_reverse2_test.c
@@ -0,0 +1,67 @@
+/*-
+ * Copyright (c) 2010 Jilles Tjoelker
+ * 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 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 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$
+ */
+
+#include <sys/select.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <unistd.h>
+
+/*
+ * Check that pipes can be selected for writing in the reverse direction.
+ */
+int
+main(void)
+{
+ int pip[2];
+ fd_set set;
+ int n;
+
+ if (pipe(pip) == -1)
+ err(1, "FAIL: pipe");
+
+ FD_ZERO(&set);
+ FD_SET(pip[0], &set);
+ n = select(pip[1] + 1, NULL, &set, NULL, &(struct timeval){ 0, 0 });
+ if (n != 1)
+ errx(1, "FAIL: select initial reverse direction");
+
+ n = write(pip[0], "x", 1);
+ if (n != 1)
+ err(1, "FAIL: write reverse direction");
+
+ FD_ZERO(&set);
+ FD_SET(pip[0], &set);
+ n = select(pip[1] + 1, NULL, &set, NULL, &(struct timeval){ 0, 0 });
+ if (n != 1)
+ errx(1, "FAIL: select reverse direction after write");
+
+ printf("PASS\n");
+
+ return (0);
+}
diff --git a/tests/sys/kern/pipe/pipe_reverse_test.c b/tests/sys/kern/pipe/pipe_reverse_test.c
new file mode 100644
index 000000000000..fd4518d76fbb
--- /dev/null
+++ b/tests/sys/kern/pipe/pipe_reverse_test.c
@@ -0,0 +1,149 @@
+/*
+Copyright (C) 2004 Michael J. Silbersack. 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 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 AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+*/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * $FreeBSD$
+ * This program simply tests writing through the reverse direction of
+ * a pipe. Nothing too fancy, it's only needed because most pipe-using
+ * programs never touch the reverse direction (it doesn't exist on
+ * Linux.)
+ */
+
+int
+main(void)
+{
+ char buffer[65535], buffer2[65535], go[] = "go", go2[] = "go2";
+ int desc[2], ipc_coord[2];
+ size_t i;
+ ssize_t total;
+ int buggy, error;
+ pid_t new_pid;
+
+ buggy = 0;
+ total = 0;
+
+ error = pipe(desc);
+ if (error == -1)
+ err(1, "Couldn't allocate data pipe");
+
+ error = pipe(ipc_coord);
+ if (error == -1)
+ err(1, "Couldn't allocate IPC coordination pipe");
+
+ buffer[0] = 'A';
+
+ for (i = 1; i < (int)sizeof(buffer); i++) {
+ buffer[i] = buffer[i - 1] + 1;
+ if (buffer[i] > 'Z')
+ buffer[i] = 'A';
+ }
+
+ new_pid = fork();
+ assert(new_pid != -1);
+
+#define SYNC_R(i, _buf) do { \
+ int _error = errno; \
+ warnx("%d: waiting for synchronization", __LINE__); \
+ if (read(ipc_coord[i], &_buf, sizeof(_buf)) != sizeof(_buf)) \
+ err(1, "failed to synchronize (%s)", (i == 0 ? "parent" : "child")); \
+ errno = _error; \
+ } while(0)
+
+#define SYNC_W(i, _buf) do { \
+ int _error = errno; \
+ warnx("%d: sending synchronization", __LINE__); \
+ if (write(ipc_coord[i], &_buf, sizeof(_buf)) != sizeof(_buf)) \
+ err(1, "failed to synchronize (%s)", (i == 0 ? "child" : "parent")); \
+ errno = _error; \
+ } while(0)
+
+#define WRITE(s) do { \
+ ssize_t _size; \
+ if ((_size = write(desc[1], &buffer[total], s)) != s) \
+ warn("short write; wrote %zd, expected %d", _size, s); \
+ total += _size; \
+ } while(0)
+
+ if (new_pid == 0) {
+ SYNC_R(0, go);
+ for (i = 0; i < 8; i++)
+ WRITE(4096);
+
+ SYNC_W(0, go2);
+ SYNC_R(0, go);
+
+ for (i = 0; i < 2; i++)
+ WRITE(4096);
+
+ SYNC_W(0, go2);
+
+ _exit(0);
+ }
+
+ SYNC_W(1, go);
+ SYNC_R(1, go2);
+
+ error = read(desc[0], &buffer2, 8 * 4096);
+ total += error;
+ printf("Read %d bytes\n", error);
+
+ SYNC_W(1, go);
+ SYNC_R(1, go2);
+
+ error = read(desc[0], &buffer2[total], 2 * 4096);
+ total += error;
+ printf("Read %d bytes, done\n", error);
+
+ if (memcmp(buffer, buffer2, total) != 0) {
+ for (i = 0; i < (size_t)total; i++) {
+ if (buffer[i] != buffer2[i]) {
+ buggy = 1;
+ printf("Location %zu input: %hhx "
+ "output: %hhx\n",
+ i, buffer[i], buffer2[i]);
+ }
+ }
+ }
+
+ waitpid(new_pid, NULL, 0);
+
+ if ((buggy == 1) || (total != 10 * 4096))
+ errx(1, "FAILED");
+ else
+ printf("SUCCESS\n");
+
+ exit(0);
+}
diff --git a/tests/sys/kern/pipe/pipe_wraparound_test.c b/tests/sys/kern/pipe/pipe_wraparound_test.c
new file mode 100644
index 000000000000..1ee02035aa70
--- /dev/null
+++ b/tests/sys/kern/pipe/pipe_wraparound_test.c
@@ -0,0 +1,140 @@
+/*
+Copyright (C) 2004 Michael J. Silbersack. 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 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 AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+*/
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * $FreeBSD$
+ * This program tests to make sure that wraparound writes and reads
+ * are working, assuming that 16K socket buffers are used. In order
+ * to really stress the pipe code with this test, kernel modifications
+ * nay be necessary.
+ */
+
+int main (void)
+{
+ char buffer[32768], buffer2[32768], go[] = "go", go2[] = "go2";
+ int desc[2], ipc_coord[2];
+ ssize_t error, total;
+ int buggy, i;
+ pid_t new_pid;
+
+ buggy = 0;
+ total = 0;
+
+ error = pipe(desc);
+ if (error == -1)
+ err(1, "Couldn't allocate data pipe");
+
+ error = pipe(ipc_coord);
+ if (error == -1)
+ err(1, "Couldn't allocate IPC coordination pipe");
+
+ buffer[0] = 'A';
+
+ for (i = 1; i < (int)sizeof(buffer); i++) {
+ buffer[i] = buffer[i - 1] + 1;
+ if (buffer[i] > 'Z')
+ buffer[i] = 'A';
+ }
+
+ new_pid = fork();
+ assert(new_pid != -1);
+
+#define SYNC_R(i, _buf) do { \
+ int _error = errno; \
+ warnx("%d: waiting for synchronization", __LINE__); \
+ if (read(ipc_coord[i], &_buf, sizeof(_buf)) != sizeof(_buf)) \
+ err(1, "failed to synchronize (%s)", (i == 0 ? "parent" : "child")); \
+ errno = _error; \
+ } while(0)
+
+#define SYNC_W(i, _buf) do { \
+ int _error = errno; \
+ warnx("%d: sending synchronization", __LINE__); \
+ if (write(ipc_coord[i], &_buf, sizeof(_buf)) != sizeof(_buf)) \
+ err(1, "failed to synchronize (%s)", (i == 0 ? "child" : "parent")); \
+ errno = _error; \
+ } while(0)
+
+#define WRITE(s) do { \
+ ssize_t _size; \
+ if ((_size = write(desc[1], &buffer[total], s)) != s) \
+ warn("short write; wrote %zd, expected %d", _size, s); \
+ total += _size; \
+ } while(0)
+
+ if (new_pid == 0) {
+ WRITE(4096);
+ WRITE(4096);
+ WRITE(4000);
+ SYNC_W(0, go2);
+
+ SYNC_R(0, go);
+ WRITE(3000);
+ WRITE(3000);
+ SYNC_W(0, go2);
+
+ _exit(0);
+ }
+
+ SYNC_R(1, go2);
+ error = read(desc[0], &buffer2, 8192);
+ total += error;
+ printf("Read %zd bytes\n", error);
+ SYNC_W(1, go);
+ SYNC_R(1, go2);
+ error = read(desc[0], &buffer2[total], 16384);
+ total += error;
+ printf("Read %zd bytes, done\n", error);
+
+ if (memcmp(buffer, buffer2, total) != 0) {
+ for (i = 0; i < total; i++) {
+ if (buffer[i] != buffer2[i]) {
+ buggy = 1;
+ printf("Location %d input: %hhx output: %hhx\n",
+ i, buffer[i], buffer2[i]);
+ }
+ }
+ }
+
+ waitpid(new_pid, NULL, 0);
+
+ if (buggy)
+ errx(1, "FAILURE");
+
+ printf("SUCCESS\n");
+
+ exit(0);
+}
diff --git a/tests/sys/kern/ptrace_test.c b/tests/sys/kern/ptrace_test.c
new file mode 100644
index 000000000000..dede0f84f4aa
--- /dev/null
+++ b/tests/sys/kern/ptrace_test.c
@@ -0,0 +1,1705 @@
+/*-
+ * Copyright (c) 2015 John Baldwin <jhb@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE 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 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/ptrace.h>
+#include <sys/syscall.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <atf-c.h>
+
+/*
+ * A variant of ATF_REQUIRE that is suitable for use in child
+ * processes. This only works if the parent process is tripped up by
+ * the early exit and fails some requirement itself.
+ */
+#define CHILD_REQUIRE(exp) do { \
+ if (!(exp)) \
+ child_fail_require(__FILE__, __LINE__, \
+ #exp " not met"); \
+ } while (0)
+
+static __dead2 void
+child_fail_require(const char *file, int line, const char *str)
+{
+ char buf[128];
+
+ snprintf(buf, sizeof(buf), "%s:%d: %s\n", file, line, str);
+ write(2, buf, strlen(buf));
+ _exit(32);
+}
+
+static void
+trace_me(void)
+{
+
+ /* Attach the parent process as a tracer of this process. */
+ CHILD_REQUIRE(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1);
+
+ /* Trigger a stop. */
+ raise(SIGSTOP);
+}
+
+static void
+attach_child(pid_t pid)
+{
+ pid_t wpid;
+ int status;
+
+ ATF_REQUIRE(ptrace(PT_ATTACH, pid, NULL, 0) == 0);
+
+ wpid = waitpid(pid, &status, 0);
+ ATF_REQUIRE(wpid == pid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+}
+
+static void
+wait_for_zombie(pid_t pid)
+{
+
+ /*
+ * Wait for a process to exit. This is kind of gross, but
+ * there is not a better way.
+ */
+ for (;;) {
+ struct kinfo_proc kp;
+ size_t len;
+ int mib[4];
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = pid;
+ len = sizeof(kp);
+ if (sysctl(mib, nitems(mib), &kp, &len, NULL, 0) == -1) {
+ /* The KERN_PROC_PID sysctl fails for zombies. */
+ ATF_REQUIRE(errno == ESRCH);
+ break;
+ }
+ usleep(5000);
+ }
+}
+
+/*
+ * Verify that a parent debugger process "sees" the exit of a debugged
+ * process exactly once when attached via PT_TRACE_ME.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__parent_wait_after_trace_me);
+ATF_TC_BODY(ptrace__parent_wait_after_trace_me, tc)
+{
+ pid_t child, wpid;
+ int status;
+
+ ATF_REQUIRE((child = fork()) != -1);
+ if (child == 0) {
+ /* Child process. */
+ trace_me();
+
+ _exit(1);
+ }
+
+ /* Parent process. */
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(child, &status, 0);
+ ATF_REQUIRE(wpid == child);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1);
+
+ /* The second wait() should report the exit status. */
+ wpid = waitpid(child, &status, 0);
+ ATF_REQUIRE(wpid == child);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+ /* The child should no longer exist. */
+ wpid = waitpid(child, &status, 0);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that a parent debugger process "sees" the exit of a debugged
+ * process exactly once when attached via PT_ATTACH.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__parent_wait_after_attach);
+ATF_TC_BODY(ptrace__parent_wait_after_attach, tc)
+{
+ pid_t child, wpid;
+ int cpipe[2], status;
+ char c;
+
+ ATF_REQUIRE(pipe(cpipe) == 0);
+ ATF_REQUIRE((child = fork()) != -1);
+ if (child == 0) {
+ /* Child process. */
+ close(cpipe[0]);
+
+ /* Wait for the parent to attach. */
+ CHILD_REQUIRE(read(cpipe[1], &c, sizeof(c)) == 0);
+
+ _exit(1);
+ }
+ close(cpipe[1]);
+
+ /* Parent process. */
+
+ /* Attach to the child process. */
+ attach_child(child);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1);
+
+ /* Signal the child to exit. */
+ close(cpipe[0]);
+
+ /* The second wait() should report the exit status. */
+ wpid = waitpid(child, &status, 0);
+ ATF_REQUIRE(wpid == child);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+ /* The child should no longer exist. */
+ wpid = waitpid(child, &status, 0);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that a parent process "sees" the exit of a debugged process only
+ * after the debugger has seen it.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__parent_sees_exit_after_child_debugger);
+ATF_TC_BODY(ptrace__parent_sees_exit_after_child_debugger, tc)
+{
+ pid_t child, debugger, wpid;
+ int cpipe[2], dpipe[2], status;
+ char c;
+
+ ATF_REQUIRE(pipe(cpipe) == 0);
+ ATF_REQUIRE((child = fork()) != -1);
+
+ if (child == 0) {
+ /* Child process. */
+ close(cpipe[0]);
+
+ /* Wait for parent to be ready. */
+ CHILD_REQUIRE(read(cpipe[1], &c, sizeof(c)) == sizeof(c));
+
+ _exit(1);
+ }
+ close(cpipe[1]);
+
+ ATF_REQUIRE(pipe(dpipe) == 0);
+ ATF_REQUIRE((debugger = fork()) != -1);
+
+ if (debugger == 0) {
+ /* Debugger process. */
+ close(dpipe[0]);
+
+ CHILD_REQUIRE(ptrace(PT_ATTACH, child, NULL, 0) != -1);
+
+ wpid = waitpid(child, &status, 0);
+ CHILD_REQUIRE(wpid == child);
+ CHILD_REQUIRE(WIFSTOPPED(status));
+ CHILD_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ CHILD_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1);
+
+ /* Signal parent that debugger is attached. */
+ CHILD_REQUIRE(write(dpipe[1], &c, sizeof(c)) == sizeof(c));
+
+ /* Wait for parent's failed wait. */
+ CHILD_REQUIRE(read(dpipe[1], &c, sizeof(c)) == 0);
+
+ wpid = waitpid(child, &status, 0);
+ CHILD_REQUIRE(wpid == child);
+ CHILD_REQUIRE(WIFEXITED(status));
+ CHILD_REQUIRE(WEXITSTATUS(status) == 1);
+
+ _exit(0);
+ }
+ close(dpipe[1]);
+
+ /* Parent process. */
+
+ /* Wait for the debugger to attach to the child. */
+ ATF_REQUIRE(read(dpipe[0], &c, sizeof(c)) == sizeof(c));
+
+ /* Release the child. */
+ ATF_REQUIRE(write(cpipe[0], &c, sizeof(c)) == sizeof(c));
+ ATF_REQUIRE(read(cpipe[0], &c, sizeof(c)) == 0);
+ close(cpipe[0]);
+
+ wait_for_zombie(child);
+
+ /*
+ * This wait should return a pid of 0 to indicate no status to
+ * report. The parent should see the child as non-exited
+ * until the debugger sees the exit.
+ */
+ wpid = waitpid(child, &status, WNOHANG);
+ ATF_REQUIRE(wpid == 0);
+
+ /* Signal the debugger to wait for the child. */
+ close(dpipe[0]);
+
+ /* Wait for the debugger. */
+ wpid = waitpid(debugger, &status, 0);
+ ATF_REQUIRE(wpid == debugger);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 0);
+
+ /* The child process should now be ready. */
+ wpid = waitpid(child, &status, WNOHANG);
+ ATF_REQUIRE(wpid == child);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+}
+
+/*
+ * Verify that a parent process "sees" the exit of a debugged process
+ * only after a non-direct-child debugger has seen it. In particular,
+ * various wait() calls in the parent must avoid failing with ESRCH by
+ * checking the parent's orphan list for the debugee.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__parent_sees_exit_after_unrelated_debugger);
+ATF_TC_BODY(ptrace__parent_sees_exit_after_unrelated_debugger, tc)
+{
+ pid_t child, debugger, fpid, wpid;
+ int cpipe[2], dpipe[2], status;
+ char c;
+
+ ATF_REQUIRE(pipe(cpipe) == 0);
+ ATF_REQUIRE((child = fork()) != -1);
+
+ if (child == 0) {
+ /* Child process. */
+ close(cpipe[0]);
+
+ /* Wait for parent to be ready. */
+ CHILD_REQUIRE(read(cpipe[1], &c, sizeof(c)) == sizeof(c));
+
+ _exit(1);
+ }
+ close(cpipe[1]);
+
+ ATF_REQUIRE(pipe(dpipe) == 0);
+ ATF_REQUIRE((debugger = fork()) != -1);
+
+ if (debugger == 0) {
+ /* Debugger parent. */
+
+ /*
+ * Fork again and drop the debugger parent so that the
+ * debugger is not a child of the main parent.
+ */
+ CHILD_REQUIRE((fpid = fork()) != -1);
+ if (fpid != 0)
+ _exit(2);
+
+ /* Debugger process. */
+ close(dpipe[0]);
+
+ CHILD_REQUIRE(ptrace(PT_ATTACH, child, NULL, 0) != -1);
+
+ wpid = waitpid(child, &status, 0);
+ CHILD_REQUIRE(wpid == child);
+ CHILD_REQUIRE(WIFSTOPPED(status));
+ CHILD_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ CHILD_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1);
+
+ /* Signal parent that debugger is attached. */
+ CHILD_REQUIRE(write(dpipe[1], &c, sizeof(c)) == sizeof(c));
+
+ /* Wait for parent's failed wait. */
+ CHILD_REQUIRE(read(dpipe[1], &c, sizeof(c)) == sizeof(c));
+
+ wpid = waitpid(child, &status, 0);
+ CHILD_REQUIRE(wpid == child);
+ CHILD_REQUIRE(WIFEXITED(status));
+ CHILD_REQUIRE(WEXITSTATUS(status) == 1);
+
+ _exit(0);
+ }
+ close(dpipe[1]);
+
+ /* Parent process. */
+
+ /* Wait for the debugger parent process to exit. */
+ wpid = waitpid(debugger, &status, 0);
+ ATF_REQUIRE(wpid == debugger);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 2);
+
+ /* A WNOHANG wait here should see the non-exited child. */
+ wpid = waitpid(child, &status, WNOHANG);
+ ATF_REQUIRE(wpid == 0);
+
+ /* Wait for the debugger to attach to the child. */
+ ATF_REQUIRE(read(dpipe[0], &c, sizeof(c)) == sizeof(c));
+
+ /* Release the child. */
+ ATF_REQUIRE(write(cpipe[0], &c, sizeof(c)) == sizeof(c));
+ ATF_REQUIRE(read(cpipe[0], &c, sizeof(c)) == 0);
+ close(cpipe[0]);
+
+ wait_for_zombie(child);
+
+ /*
+ * This wait should return a pid of 0 to indicate no status to
+ * report. The parent should see the child as non-exited
+ * until the debugger sees the exit.
+ */
+ wpid = waitpid(child, &status, WNOHANG);
+ ATF_REQUIRE(wpid == 0);
+
+ /* Signal the debugger to wait for the child. */
+ ATF_REQUIRE(write(dpipe[0], &c, sizeof(c)) == sizeof(c));
+
+ /* Wait for the debugger. */
+ ATF_REQUIRE(read(dpipe[0], &c, sizeof(c)) == 0);
+ close(dpipe[0]);
+
+ /* The child process should now be ready. */
+ wpid = waitpid(child, &status, WNOHANG);
+ ATF_REQUIRE(wpid == child);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+}
+
+/*
+ * The parent process should always act the same regardless of how the
+ * debugger is attached to it.
+ */
+static __dead2 void
+follow_fork_parent(bool use_vfork)
+{
+ pid_t fpid, wpid;
+ int status;
+
+ if (use_vfork)
+ CHILD_REQUIRE((fpid = vfork()) != -1);
+ else
+ CHILD_REQUIRE((fpid = fork()) != -1);
+
+ if (fpid == 0)
+ /* Child */
+ _exit(2);
+
+ wpid = waitpid(fpid, &status, 0);
+ CHILD_REQUIRE(wpid == fpid);
+ CHILD_REQUIRE(WIFEXITED(status));
+ CHILD_REQUIRE(WEXITSTATUS(status) == 2);
+
+ _exit(1);
+}
+
+/*
+ * Helper routine for follow fork tests. This waits for two stops
+ * that report both "sides" of a fork. It returns the pid of the new
+ * child process.
+ */
+static pid_t
+handle_fork_events(pid_t parent, struct ptrace_lwpinfo *ppl)
+{
+ struct ptrace_lwpinfo pl;
+ bool fork_reported[2];
+ pid_t child, wpid;
+ int i, status;
+
+ fork_reported[0] = false;
+ fork_reported[1] = false;
+ child = -1;
+
+ /*
+ * Each process should report a fork event. The parent should
+ * report a PL_FLAG_FORKED event, and the child should report
+ * a PL_FLAG_CHILD event.
+ */
+ for (i = 0; i < 2; i++) {
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid > 0);
+ ATF_REQUIRE(WIFSTOPPED(status));
+
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl,
+ sizeof(pl)) != -1);
+ ATF_REQUIRE((pl.pl_flags & (PL_FLAG_FORKED | PL_FLAG_CHILD)) !=
+ 0);
+ ATF_REQUIRE((pl.pl_flags & (PL_FLAG_FORKED | PL_FLAG_CHILD)) !=
+ (PL_FLAG_FORKED | PL_FLAG_CHILD));
+ if (pl.pl_flags & PL_FLAG_CHILD) {
+ ATF_REQUIRE(wpid != parent);
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+ ATF_REQUIRE(!fork_reported[1]);
+ if (child == -1)
+ child = wpid;
+ else
+ ATF_REQUIRE(child == wpid);
+ if (ppl != NULL)
+ ppl[1] = pl;
+ fork_reported[1] = true;
+ } else {
+ ATF_REQUIRE(wpid == parent);
+ ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+ ATF_REQUIRE(!fork_reported[0]);
+ if (child == -1)
+ child = pl.pl_child_pid;
+ else
+ ATF_REQUIRE(child == pl.pl_child_pid);
+ if (ppl != NULL)
+ ppl[0] = pl;
+ fork_reported[0] = true;
+ }
+ }
+
+ return (child);
+}
+
+/*
+ * Verify that a new child process is stopped after a followed fork and
+ * that the traced parent sees the exit of the child after the debugger
+ * when both processes remain attached to the debugger.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_both_attached);
+ATF_TC_BODY(ptrace__follow_fork_both_attached, tc)
+{
+ pid_t children[2], fpid, wpid;
+ int status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ follow_fork_parent(false);
+ }
+
+ /* Parent process. */
+ children[0] = fpid;
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(children[0], &status, 0);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+ children[1] = handle_fork_events(children[0], NULL);
+ ATF_REQUIRE(children[1] > 0);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1);
+
+ /*
+ * The child can't exit until the grandchild reports status, so the
+ * grandchild should report its exit first to the debugger.
+ */
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[1]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 2);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that a new child process is stopped after a followed fork
+ * and that the traced parent sees the exit of the child when the new
+ * child process is detached after it reports its fork.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_child_detached);
+ATF_TC_BODY(ptrace__follow_fork_child_detached, tc)
+{
+ pid_t children[2], fpid, wpid;
+ int status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ follow_fork_parent(false);
+ }
+
+ /* Parent process. */
+ children[0] = fpid;
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(children[0], &status, 0);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+ children[1] = handle_fork_events(children[0], NULL);
+ ATF_REQUIRE(children[1] > 0);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+ ATF_REQUIRE(ptrace(PT_DETACH, children[1], (caddr_t)1, 0) != -1);
+
+ /*
+ * Should not see any status from the grandchild now, only the
+ * child.
+ */
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that a new child process is stopped after a followed fork
+ * and that the traced parent sees the exit of the child when the
+ * traced parent is detached after the fork.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_parent_detached);
+ATF_TC_BODY(ptrace__follow_fork_parent_detached, tc)
+{
+ pid_t children[2], fpid, wpid;
+ int status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ follow_fork_parent(false);
+ }
+
+ /* Parent process. */
+ children[0] = fpid;
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(children[0], &status, 0);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+ children[1] = handle_fork_events(children[0], NULL);
+ ATF_REQUIRE(children[1] > 0);
+
+ ATF_REQUIRE(ptrace(PT_DETACH, children[0], (caddr_t)1, 0) != -1);
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1);
+
+ /*
+ * The child can't exit until the grandchild reports status, so the
+ * grandchild should report its exit first to the debugger.
+ *
+ * Even though the child process is detached, it is still a
+ * child of the debugger, so it will still report it's exit
+ * after the grandchild.
+ */
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[1]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 2);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+static void
+attach_fork_parent(int cpipe[2])
+{
+ pid_t fpid;
+
+ close(cpipe[0]);
+
+ /* Double-fork to disassociate from the debugger. */
+ CHILD_REQUIRE((fpid = fork()) != -1);
+ if (fpid != 0)
+ _exit(3);
+
+ /* Send the pid of the disassociated child to the debugger. */
+ fpid = getpid();
+ CHILD_REQUIRE(write(cpipe[1], &fpid, sizeof(fpid)) == sizeof(fpid));
+
+ /* Wait for the debugger to attach. */
+ CHILD_REQUIRE(read(cpipe[1], &fpid, sizeof(fpid)) == 0);
+}
+
+/*
+ * Verify that a new child process is stopped after a followed fork and
+ * that the traced parent sees the exit of the child after the debugger
+ * when both processes remain attached to the debugger. In this test
+ * the parent that forks is not a direct child of the debugger.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_both_attached_unrelated_debugger);
+ATF_TC_BODY(ptrace__follow_fork_both_attached_unrelated_debugger, tc)
+{
+ pid_t children[2], fpid, wpid;
+ int cpipe[2], status;
+
+ ATF_REQUIRE(pipe(cpipe) == 0);
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ attach_fork_parent(cpipe);
+ follow_fork_parent(false);
+ }
+
+ /* Parent process. */
+ close(cpipe[1]);
+
+ /* Wait for the direct child to exit. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 3);
+
+ /* Read the pid of the fork parent. */
+ ATF_REQUIRE(read(cpipe[0], &children[0], sizeof(children[0])) ==
+ sizeof(children[0]));
+
+ /* Attach to the fork parent. */
+ attach_child(children[0]);
+
+ ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1);
+
+ /* Continue the fork parent ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+ /* Signal the fork parent to continue. */
+ close(cpipe[0]);
+
+ children[1] = handle_fork_events(children[0], NULL);
+ ATF_REQUIRE(children[1] > 0);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1);
+
+ /*
+ * The fork parent can't exit until the child reports status,
+ * so the child should report its exit first to the debugger.
+ */
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[1]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 2);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that a new child process is stopped after a followed fork
+ * and that the traced parent sees the exit of the child when the new
+ * child process is detached after it reports its fork. In this test
+ * the parent that forks is not a direct child of the debugger.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_child_detached_unrelated_debugger);
+ATF_TC_BODY(ptrace__follow_fork_child_detached_unrelated_debugger, tc)
+{
+ pid_t children[2], fpid, wpid;
+ int cpipe[2], status;
+
+ ATF_REQUIRE(pipe(cpipe) == 0);
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ attach_fork_parent(cpipe);
+ follow_fork_parent(false);
+ }
+
+ /* Parent process. */
+ close(cpipe[1]);
+
+ /* Wait for the direct child to exit. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 3);
+
+ /* Read the pid of the fork parent. */
+ ATF_REQUIRE(read(cpipe[0], &children[0], sizeof(children[0])) ==
+ sizeof(children[0]));
+
+ /* Attach to the fork parent. */
+ attach_child(children[0]);
+
+ ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1);
+
+ /* Continue the fork parent ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+ /* Signal the fork parent to continue. */
+ close(cpipe[0]);
+
+ children[1] = handle_fork_events(children[0], NULL);
+ ATF_REQUIRE(children[1] > 0);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+ ATF_REQUIRE(ptrace(PT_DETACH, children[1], (caddr_t)1, 0) != -1);
+
+ /*
+ * Should not see any status from the child now, only the fork
+ * parent.
+ */
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that a new child process is stopped after a followed fork
+ * and that the traced parent sees the exit of the child when the
+ * traced parent is detached after the fork. In this test the parent
+ * that forks is not a direct child of the debugger.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_parent_detached_unrelated_debugger);
+ATF_TC_BODY(ptrace__follow_fork_parent_detached_unrelated_debugger, tc)
+{
+ pid_t children[2], fpid, wpid;
+ int cpipe[2], status;
+
+ ATF_REQUIRE(pipe(cpipe) == 0);
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ attach_fork_parent(cpipe);
+ follow_fork_parent(false);
+ }
+
+ /* Parent process. */
+ close(cpipe[1]);
+
+ /* Wait for the direct child to exit. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 3);
+
+ /* Read the pid of the fork parent. */
+ ATF_REQUIRE(read(cpipe[0], &children[0], sizeof(children[0])) ==
+ sizeof(children[0]));
+
+ /* Attach to the fork parent. */
+ attach_child(children[0]);
+
+ ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1);
+
+ /* Continue the fork parent ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+ /* Signal the fork parent to continue. */
+ close(cpipe[0]);
+
+ children[1] = handle_fork_events(children[0], NULL);
+ ATF_REQUIRE(children[1] > 0);
+
+ ATF_REQUIRE(ptrace(PT_DETACH, children[0], (caddr_t)1, 0) != -1);
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1);
+
+ /*
+ * Should not see any status from the fork parent now, only
+ * the child.
+ */
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[1]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 2);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that a child process does not see an unrelated debugger as its
+ * parent but sees its original parent process.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__getppid);
+ATF_TC_BODY(ptrace__getppid, tc)
+{
+ pid_t child, debugger, ppid, wpid;
+ int cpipe[2], dpipe[2], status;
+ char c;
+
+ ATF_REQUIRE(pipe(cpipe) == 0);
+ ATF_REQUIRE((child = fork()) != -1);
+
+ if (child == 0) {
+ /* Child process. */
+ close(cpipe[0]);
+
+ /* Wait for parent to be ready. */
+ CHILD_REQUIRE(read(cpipe[1], &c, sizeof(c)) == sizeof(c));
+
+ /* Report the parent PID to the parent. */
+ ppid = getppid();
+ CHILD_REQUIRE(write(cpipe[1], &ppid, sizeof(ppid)) ==
+ sizeof(ppid));
+
+ _exit(1);
+ }
+ close(cpipe[1]);
+
+ ATF_REQUIRE(pipe(dpipe) == 0);
+ ATF_REQUIRE((debugger = fork()) != -1);
+
+ if (debugger == 0) {
+ /* Debugger process. */
+ close(dpipe[0]);
+
+ CHILD_REQUIRE(ptrace(PT_ATTACH, child, NULL, 0) != -1);
+
+ wpid = waitpid(child, &status, 0);
+ CHILD_REQUIRE(wpid == child);
+ CHILD_REQUIRE(WIFSTOPPED(status));
+ CHILD_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ CHILD_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1);
+
+ /* Signal parent that debugger is attached. */
+ CHILD_REQUIRE(write(dpipe[1], &c, sizeof(c)) == sizeof(c));
+
+ /* Wait for traced child to exit. */
+ wpid = waitpid(child, &status, 0);
+ CHILD_REQUIRE(wpid == child);
+ CHILD_REQUIRE(WIFEXITED(status));
+ CHILD_REQUIRE(WEXITSTATUS(status) == 1);
+
+ _exit(0);
+ }
+ close(dpipe[1]);
+
+ /* Parent process. */
+
+ /* Wait for the debugger to attach to the child. */
+ ATF_REQUIRE(read(dpipe[0], &c, sizeof(c)) == sizeof(c));
+
+ /* Release the child. */
+ ATF_REQUIRE(write(cpipe[0], &c, sizeof(c)) == sizeof(c));
+
+ /* Read the parent PID from the child. */
+ ATF_REQUIRE(read(cpipe[0], &ppid, sizeof(ppid)) == sizeof(ppid));
+ close(cpipe[0]);
+
+ ATF_REQUIRE(ppid == getpid());
+
+ /* Wait for the debugger. */
+ wpid = waitpid(debugger, &status, 0);
+ ATF_REQUIRE(wpid == debugger);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 0);
+
+ /* The child process should now be ready. */
+ wpid = waitpid(child, &status, WNOHANG);
+ ATF_REQUIRE(wpid == child);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+}
+
+/*
+ * Verify that pl_syscall_code in struct ptrace_lwpinfo for a new
+ * child process created via fork() reports the correct value.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__new_child_pl_syscall_code_fork);
+ATF_TC_BODY(ptrace__new_child_pl_syscall_code_fork, tc)
+{
+ struct ptrace_lwpinfo pl[2];
+ pid_t children[2], fpid, wpid;
+ int status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ follow_fork_parent(false);
+ }
+
+ /* Parent process. */
+ children[0] = fpid;
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(children[0], &status, 0);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+ /* Wait for both halves of the fork event to get reported. */
+ children[1] = handle_fork_events(children[0], pl);
+ ATF_REQUIRE(children[1] > 0);
+
+ ATF_REQUIRE((pl[0].pl_flags & PL_FLAG_SCX) != 0);
+ ATF_REQUIRE((pl[1].pl_flags & PL_FLAG_SCX) != 0);
+ ATF_REQUIRE(pl[0].pl_syscall_code == SYS_fork);
+ ATF_REQUIRE(pl[0].pl_syscall_code == pl[1].pl_syscall_code);
+ ATF_REQUIRE(pl[0].pl_syscall_narg == pl[1].pl_syscall_narg);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1);
+
+ /*
+ * The child can't exit until the grandchild reports status, so the
+ * grandchild should report its exit first to the debugger.
+ */
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[1]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 2);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that pl_syscall_code in struct ptrace_lwpinfo for a new
+ * child process created via vfork() reports the correct value.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__new_child_pl_syscall_code_vfork);
+ATF_TC_BODY(ptrace__new_child_pl_syscall_code_vfork, tc)
+{
+ struct ptrace_lwpinfo pl[2];
+ pid_t children[2], fpid, wpid;
+ int status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ follow_fork_parent(true);
+ }
+
+ /* Parent process. */
+ children[0] = fpid;
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(children[0], &status, 0);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+ /* Wait for both halves of the fork event to get reported. */
+ children[1] = handle_fork_events(children[0], pl);
+ ATF_REQUIRE(children[1] > 0);
+
+ ATF_REQUIRE((pl[0].pl_flags & PL_FLAG_SCX) != 0);
+ ATF_REQUIRE((pl[1].pl_flags & PL_FLAG_SCX) != 0);
+ ATF_REQUIRE(pl[0].pl_syscall_code == SYS_vfork);
+ ATF_REQUIRE(pl[0].pl_syscall_code == pl[1].pl_syscall_code);
+ ATF_REQUIRE(pl[0].pl_syscall_narg == pl[1].pl_syscall_narg);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1);
+
+ /*
+ * The child can't exit until the grandchild reports status, so the
+ * grandchild should report its exit first to the debugger.
+ */
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[1]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 2);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+static void *
+simple_thread(void *arg __unused)
+{
+
+ pthread_exit(NULL);
+}
+
+static __dead2 void
+simple_thread_main(void)
+{
+ pthread_t thread;
+
+ CHILD_REQUIRE(pthread_create(&thread, NULL, simple_thread, NULL) == 0);
+ CHILD_REQUIRE(pthread_join(thread, NULL) == 0);
+ exit(1);
+}
+
+/*
+ * Verify that pl_syscall_code in struct ptrace_lwpinfo for a new
+ * thread reports the correct value.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__new_child_pl_syscall_code_thread);
+ATF_TC_BODY(ptrace__new_child_pl_syscall_code_thread, tc)
+{
+ struct ptrace_lwpinfo pl;
+ pid_t fpid, wpid;
+ lwpid_t mainlwp;
+ int status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ simple_thread_main();
+ }
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl,
+ sizeof(pl)) != -1);
+ mainlwp = pl.pl_lwpid;
+
+ /*
+ * Continue the child ignoring the SIGSTOP and tracing all
+ * system call exits.
+ */
+ ATF_REQUIRE(ptrace(PT_TO_SCX, fpid, (caddr_t)1, 0) != -1);
+
+ /*
+ * Wait for the new thread to arrive. pthread_create() might
+ * invoke any number of system calls. For now we just wait
+ * for the new thread to arrive and make sure it reports a
+ * valid system call code. If ptrace grows thread event
+ * reporting then this test can be made more precise.
+ */
+ for (;;) {
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl,
+ sizeof(pl)) != -1);
+ ATF_REQUIRE((pl.pl_flags & PL_FLAG_SCX) != 0);
+ ATF_REQUIRE(pl.pl_syscall_code != 0);
+ if (pl.pl_lwpid != mainlwp)
+ /* New thread seen. */
+ break;
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+ }
+
+ /* Wait for the child to exit. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+ for (;;) {
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ if (WIFEXITED(status))
+ break;
+
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+ }
+
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that the expected LWP events are reported for a child thread.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__lwp_events);
+ATF_TC_BODY(ptrace__lwp_events, tc)
+{
+ struct ptrace_lwpinfo pl;
+ pid_t fpid, wpid;
+ lwpid_t lwps[2];
+ int status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ simple_thread_main();
+ }
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl,
+ sizeof(pl)) != -1);
+ lwps[0] = pl.pl_lwpid;
+
+ ATF_REQUIRE(ptrace(PT_LWP_EVENTS, wpid, NULL, 1) == 0);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* The first event should be for the child thread's birth. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+ ATF_REQUIRE((pl.pl_flags & (PL_FLAG_BORN | PL_FLAG_SCX)) ==
+ (PL_FLAG_BORN | PL_FLAG_SCX));
+ ATF_REQUIRE(pl.pl_lwpid != lwps[0]);
+ lwps[1] = pl.pl_lwpid;
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* The next event should be for the child thread's death. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+ ATF_REQUIRE((pl.pl_flags & (PL_FLAG_EXITED | PL_FLAG_SCE)) ==
+ (PL_FLAG_EXITED | PL_FLAG_SCE));
+ ATF_REQUIRE(pl.pl_lwpid == lwps[1]);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* The last event should be for the child process's exit. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+static void *
+exec_thread(void *arg __unused)
+{
+
+ execl("/usr/bin/true", "true", NULL);
+ exit(127);
+}
+
+static __dead2 void
+exec_thread_main(void)
+{
+ pthread_t thread;
+
+ CHILD_REQUIRE(pthread_create(&thread, NULL, exec_thread, NULL) == 0);
+ for (;;)
+ sleep(60);
+ exit(1);
+}
+
+/*
+ * Verify that the expected LWP events are reported for a multithreaded
+ * process that calls execve(2).
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__lwp_events_exec);
+ATF_TC_BODY(ptrace__lwp_events_exec, tc)
+{
+ struct ptrace_lwpinfo pl;
+ pid_t fpid, wpid;
+ lwpid_t lwps[2];
+ int status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ exec_thread_main();
+ }
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl,
+ sizeof(pl)) != -1);
+ lwps[0] = pl.pl_lwpid;
+
+ ATF_REQUIRE(ptrace(PT_LWP_EVENTS, wpid, NULL, 1) == 0);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* The first event should be for the child thread's birth. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+ ATF_REQUIRE((pl.pl_flags & (PL_FLAG_BORN | PL_FLAG_SCX)) ==
+ (PL_FLAG_BORN | PL_FLAG_SCX));
+ ATF_REQUIRE(pl.pl_lwpid != lwps[0]);
+ lwps[1] = pl.pl_lwpid;
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /*
+ * The next event should be for the main thread's death due to
+ * single threading from execve().
+ */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+ ATF_REQUIRE((pl.pl_flags & (PL_FLAG_EXITED | PL_FLAG_SCE)) ==
+ (PL_FLAG_EXITED));
+ ATF_REQUIRE(pl.pl_lwpid == lwps[0]);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* The next event should be for the child process's exec. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+ ATF_REQUIRE((pl.pl_flags & (PL_FLAG_EXEC | PL_FLAG_SCX)) ==
+ (PL_FLAG_EXEC | PL_FLAG_SCX));
+ ATF_REQUIRE(pl.pl_lwpid == lwps[1]);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* The last event should be for the child process's exit. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 0);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+static void
+handler(int sig __unused)
+{
+}
+
+static void
+signal_main(void)
+{
+
+ signal(SIGINFO, handler);
+ raise(SIGINFO);
+ exit(0);
+}
+
+/*
+ * Verify that the expected ptrace event is reported for a signal.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__siginfo);
+ATF_TC_BODY(ptrace__siginfo, tc)
+{
+ struct ptrace_lwpinfo pl;
+ pid_t fpid, wpid;
+ int status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ signal_main();
+ }
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* The next event should be for the SIGINFO. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGINFO);
+
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+ ATF_REQUIRE(pl.pl_event == PL_EVENT_SIGNAL);
+ ATF_REQUIRE(pl.pl_flags & PL_FLAG_SI);
+ ATF_REQUIRE(pl.pl_siginfo.si_code == SI_LWP);
+ ATF_REQUIRE(pl.pl_siginfo.si_pid == wpid);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* The last event should be for the child process's exit. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 0);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that the expected ptrace events are reported for PTRACE_EXEC.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__ptrace_exec_disable);
+ATF_TC_BODY(ptrace__ptrace_exec_disable, tc)
+{
+ pid_t fpid, wpid;
+ int events, status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ exec_thread(NULL);
+ }
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ events = 0;
+ ATF_REQUIRE(ptrace(PT_SET_EVENT_MASK, fpid, (caddr_t)&events,
+ sizeof(events)) == 0);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* Should get one event at exit. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 0);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+ATF_TC_WITHOUT_HEAD(ptrace__ptrace_exec_enable);
+ATF_TC_BODY(ptrace__ptrace_exec_enable, tc)
+{
+ struct ptrace_lwpinfo pl;
+ pid_t fpid, wpid;
+ int events, status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ exec_thread(NULL);
+ }
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ events = PTRACE_EXEC;
+ ATF_REQUIRE(ptrace(PT_SET_EVENT_MASK, fpid, (caddr_t)&events,
+ sizeof(events)) == 0);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* The next event should be for the child process's exec. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+ ATF_REQUIRE((pl.pl_flags & (PL_FLAG_EXEC | PL_FLAG_SCX)) ==
+ (PL_FLAG_EXEC | PL_FLAG_SCX));
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* The last event should be for the child process's exit. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 0);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+ATF_TC_WITHOUT_HEAD(ptrace__event_mask);
+ATF_TC_BODY(ptrace__event_mask, tc)
+{
+ pid_t fpid, wpid;
+ int events, status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ exit(0);
+ }
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ /* PT_FOLLOW_FORK should toggle the state of PTRACE_FORK. */
+ ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, fpid, NULL, 1) != -1);
+ ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, fpid, (caddr_t)&events,
+ sizeof(events)) == 0);
+ ATF_REQUIRE(events & PTRACE_FORK);
+ ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, fpid, NULL, 0) != -1);
+ ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, fpid, (caddr_t)&events,
+ sizeof(events)) == 0);
+ ATF_REQUIRE(!(events & PTRACE_FORK));
+
+ /* PT_LWP_EVENTS should toggle the state of PTRACE_LWP. */
+ ATF_REQUIRE(ptrace(PT_LWP_EVENTS, fpid, NULL, 1) != -1);
+ ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, fpid, (caddr_t)&events,
+ sizeof(events)) == 0);
+ ATF_REQUIRE(events & PTRACE_LWP);
+ ATF_REQUIRE(ptrace(PT_LWP_EVENTS, fpid, NULL, 0) != -1);
+ ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, fpid, (caddr_t)&events,
+ sizeof(events)) == 0);
+ ATF_REQUIRE(!(events & PTRACE_LWP));
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) == 0);
+
+ /* Should get one event at exit. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 0);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+/*
+ * Verify that the expected ptrace events are reported for PTRACE_VFORK.
+ */
+ATF_TC_WITHOUT_HEAD(ptrace__ptrace_vfork);
+ATF_TC_BODY(ptrace__ptrace_vfork, tc)
+{
+ struct ptrace_lwpinfo pl;
+ pid_t fpid, wpid;
+ int events, status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ follow_fork_parent(true);
+ }
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(fpid, &status, 0);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, fpid, (caddr_t)&events,
+ sizeof(events)) == 0);
+ events |= PTRACE_VFORK;
+ ATF_REQUIRE(ptrace(PT_SET_EVENT_MASK, fpid, (caddr_t)&events,
+ sizeof(events)) == 0);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) != -1);
+
+ /* The next event should report the end of the vfork. */
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1);
+ ATF_REQUIRE((pl.pl_flags & PL_FLAG_VFORK_DONE) != 0);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) != -1);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == fpid);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+ATF_TC_WITHOUT_HEAD(ptrace__ptrace_vfork_follow);
+ATF_TC_BODY(ptrace__ptrace_vfork_follow, tc)
+{
+ struct ptrace_lwpinfo pl[2];
+ pid_t children[2], fpid, wpid;
+ int events, status;
+
+ ATF_REQUIRE((fpid = fork()) != -1);
+ if (fpid == 0) {
+ trace_me();
+ follow_fork_parent(true);
+ }
+
+ /* Parent process. */
+ children[0] = fpid;
+
+ /* The first wait() should report the stop from SIGSTOP. */
+ wpid = waitpid(children[0], &status, 0);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP);
+
+ ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, children[0], (caddr_t)&events,
+ sizeof(events)) == 0);
+ events |= PTRACE_FORK | PTRACE_VFORK;
+ ATF_REQUIRE(ptrace(PT_SET_EVENT_MASK, children[0], (caddr_t)&events,
+ sizeof(events)) == 0);
+
+ /* Continue the child ignoring the SIGSTOP. */
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+ /* Wait for both halves of the fork event to get reported. */
+ children[1] = handle_fork_events(children[0], pl);
+ ATF_REQUIRE(children[1] > 0);
+
+ ATF_REQUIRE((pl[0].pl_flags & PL_FLAG_VFORKED) != 0);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1);
+
+ /*
+ * The child can't exit until the grandchild reports status, so the
+ * grandchild should report its exit first to the debugger.
+ */
+ wpid = waitpid(children[1], &status, 0);
+ ATF_REQUIRE(wpid == children[1]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 2);
+
+ /*
+ * The child should report it's vfork() completion before it
+ * exits.
+ */
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFSTOPPED(status));
+ ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP);
+ ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl[0], sizeof(pl[0])) !=
+ -1);
+ ATF_REQUIRE((pl[0].pl_flags & PL_FLAG_VFORK_DONE) != 0);
+
+ ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == children[0]);
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE(WEXITSTATUS(status) == 1);
+
+ wpid = wait(&status);
+ ATF_REQUIRE(wpid == -1);
+ ATF_REQUIRE(errno == ECHILD);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, ptrace__parent_wait_after_trace_me);
+ ATF_TP_ADD_TC(tp, ptrace__parent_wait_after_attach);
+ ATF_TP_ADD_TC(tp, ptrace__parent_sees_exit_after_child_debugger);
+ ATF_TP_ADD_TC(tp, ptrace__parent_sees_exit_after_unrelated_debugger);
+ ATF_TP_ADD_TC(tp, ptrace__follow_fork_both_attached);
+ ATF_TP_ADD_TC(tp, ptrace__follow_fork_child_detached);
+ ATF_TP_ADD_TC(tp, ptrace__follow_fork_parent_detached);
+ ATF_TP_ADD_TC(tp, ptrace__follow_fork_both_attached_unrelated_debugger);
+ ATF_TP_ADD_TC(tp,
+ ptrace__follow_fork_child_detached_unrelated_debugger);
+ ATF_TP_ADD_TC(tp,
+ ptrace__follow_fork_parent_detached_unrelated_debugger);
+ ATF_TP_ADD_TC(tp, ptrace__getppid);
+ ATF_TP_ADD_TC(tp, ptrace__new_child_pl_syscall_code_fork);
+ ATF_TP_ADD_TC(tp, ptrace__new_child_pl_syscall_code_vfork);
+ ATF_TP_ADD_TC(tp, ptrace__new_child_pl_syscall_code_thread);
+ ATF_TP_ADD_TC(tp, ptrace__lwp_events);
+ ATF_TP_ADD_TC(tp, ptrace__lwp_events_exec);
+ ATF_TP_ADD_TC(tp, ptrace__siginfo);
+ ATF_TP_ADD_TC(tp, ptrace__ptrace_exec_disable);
+ ATF_TP_ADD_TC(tp, ptrace__ptrace_exec_enable);
+ ATF_TP_ADD_TC(tp, ptrace__event_mask);
+ ATF_TP_ADD_TC(tp, ptrace__ptrace_vfork);
+ ATF_TP_ADD_TC(tp, ptrace__ptrace_vfork_follow);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/kern/reaper.c b/tests/sys/kern/reaper.c
new file mode 100644
index 000000000000..603171334435
--- /dev/null
+++ b/tests/sys/kern/reaper.c
@@ -0,0 +1,758 @@
+/*-
+ * Copyright (c) 2016 Jilles Tjoelker
+ * 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 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 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/procctl.h>
+#include <sys/wait.h>
+
+#include <atf-c.h>
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+
+static void
+dummy_sighandler(int sig __unused, siginfo_t *info __unused, void *ctx __unused)
+{
+}
+
+ATF_TC_WITHOUT_HEAD(reaper_wait_child_first);
+ATF_TC_BODY(reaper_wait_child_first, tc)
+{
+ pid_t parent, child, grandchild, pid;
+ int status, r;
+ int pip[2];
+
+ /* Be paranoid. */
+ pid = waitpid(-1, NULL, WNOHANG);
+ ATF_REQUIRE(pid == -1 && errno == ECHILD);
+
+ parent = getpid();
+ r = procctl(P_PID, parent, PROC_REAP_ACQUIRE, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ r = pipe(pip);
+ ATF_REQUIRE_EQ(0, r);
+
+ child = fork();
+ ATF_REQUIRE(child != -1);
+ if (child == 0) {
+ if (close(pip[1]) != 0)
+ _exit(100);
+ grandchild = fork();
+ if (grandchild == -1)
+ _exit(101);
+ else if (grandchild == 0) {
+ if (read(pip[0], &(uint8_t){ 0 }, 1) != 0)
+ _exit(102);
+ if (getppid() != parent)
+ _exit(103);
+ _exit(2);
+ } else
+ _exit(3);
+ }
+
+ pid = waitpid(child, &status, 0);
+ ATF_REQUIRE_EQ(child, pid);
+ r = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+ ATF_CHECK_EQ(3, r);
+
+ r = close(pip[1]);
+ ATF_REQUIRE_EQ(0, r);
+
+ pid = waitpid(-1, &status, 0);
+ ATF_REQUIRE(pid > 0 && pid != child);
+ r = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+ ATF_CHECK_EQ(2, r);
+
+ r = close(pip[0]);
+ ATF_REQUIRE_EQ(0, r);
+}
+
+ATF_TC_WITHOUT_HEAD(reaper_wait_grandchild_first);
+ATF_TC_BODY(reaper_wait_grandchild_first, tc)
+{
+ pid_t parent, child, grandchild, pid;
+ int status, r;
+
+ /* Be paranoid. */
+ pid = waitpid(-1, NULL, WNOHANG);
+ ATF_REQUIRE(pid == -1 && errno == ECHILD);
+
+ parent = getpid();
+ r = procctl(P_PID, parent, PROC_REAP_ACQUIRE, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ child = fork();
+ ATF_REQUIRE(child != -1);
+ if (child == 0) {
+ grandchild = fork();
+ if (grandchild == -1)
+ _exit(101);
+ else if (grandchild == 0)
+ _exit(2);
+ else {
+ if (waitid(P_PID, grandchild, NULL,
+ WNOWAIT | WEXITED) != 0)
+ _exit(102);
+ _exit(3);
+ }
+ }
+
+ pid = waitpid(child, &status, 0);
+ ATF_REQUIRE_EQ(child, pid);
+ r = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+ ATF_CHECK_EQ(3, r);
+
+ pid = waitpid(-1, &status, 0);
+ ATF_REQUIRE(pid > 0 && pid != child);
+ r = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+ ATF_CHECK_EQ(2, r);
+}
+
+ATF_TC(reaper_sigchld_child_first);
+ATF_TC_HEAD(reaper_sigchld_child_first, tc)
+{
+ atf_tc_set_md_var(tc, "timeout", "2");
+}
+ATF_TC_BODY(reaper_sigchld_child_first, tc)
+{
+ struct sigaction act;
+ sigset_t mask;
+ siginfo_t info;
+ pid_t parent, child, grandchild, pid;
+ int r;
+ int pip[2];
+
+ /* Be paranoid. */
+ pid = waitpid(-1, NULL, WNOHANG);
+ ATF_REQUIRE(pid == -1 && errno == ECHILD);
+
+ act.sa_sigaction = dummy_sighandler;
+ act.sa_flags = SA_SIGINFO | SA_RESTART;
+ r = sigemptyset(&act.sa_mask);
+ ATF_REQUIRE_EQ(0, r);
+ r = sigaction(SIGCHLD, &act, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ r = sigemptyset(&mask);
+ ATF_REQUIRE_EQ(0, r);
+ r = sigaddset(&mask, SIGCHLD);
+ ATF_REQUIRE_EQ(0, r);
+ r = sigprocmask(SIG_BLOCK, &mask, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ parent = getpid();
+ r = procctl(P_PID, parent, PROC_REAP_ACQUIRE, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ r = pipe(pip);
+ ATF_REQUIRE_EQ(0, r);
+
+ child = fork();
+ ATF_REQUIRE(child != -1);
+ if (child == 0) {
+ if (close(pip[1]) != 0)
+ _exit(100);
+ grandchild = fork();
+ if (grandchild == -1)
+ _exit(101);
+ else if (grandchild == 0) {
+ if (read(pip[0], &(uint8_t){ 0 }, 1) != 0)
+ _exit(102);
+ if (getppid() != parent)
+ _exit(103);
+ _exit(2);
+ } else
+ _exit(3);
+ }
+
+ r = sigwaitinfo(&mask, &info);
+ ATF_REQUIRE_EQ(SIGCHLD, r);
+ ATF_CHECK_EQ(SIGCHLD, info.si_signo);
+ ATF_CHECK_EQ(CLD_EXITED, info.si_code);
+ ATF_CHECK_EQ(3, info.si_status);
+ ATF_CHECK_EQ(child, info.si_pid);
+
+ pid = waitpid(child, NULL, 0);
+ ATF_REQUIRE_EQ(child, pid);
+
+ r = close(pip[1]);
+ ATF_REQUIRE_EQ(0, r);
+
+ r = sigwaitinfo(&mask, &info);
+ ATF_REQUIRE_EQ(SIGCHLD, r);
+ ATF_CHECK_EQ(SIGCHLD, info.si_signo);
+ ATF_CHECK_EQ(CLD_EXITED, info.si_code);
+ ATF_CHECK_EQ(2, info.si_status);
+ grandchild = info.si_pid;
+ ATF_REQUIRE(grandchild > 0);
+ ATF_REQUIRE(grandchild != parent);
+ ATF_REQUIRE(grandchild != child);
+
+ pid = waitpid(-1, NULL, 0);
+ ATF_REQUIRE_EQ(grandchild, pid);
+
+ r = close(pip[0]);
+ ATF_REQUIRE_EQ(0, r);
+}
+
+ATF_TC(reaper_sigchld_grandchild_first);
+ATF_TC_HEAD(reaper_sigchld_grandchild_first, tc)
+{
+ atf_tc_set_md_var(tc, "timeout", "2");
+}
+ATF_TC_BODY(reaper_sigchld_grandchild_first, tc)
+{
+ struct sigaction act;
+ sigset_t mask;
+ siginfo_t info;
+ pid_t parent, child, grandchild, pid;
+ int r;
+
+ /* Be paranoid. */
+ pid = waitpid(-1, NULL, WNOHANG);
+ ATF_REQUIRE(pid == -1 && errno == ECHILD);
+
+ act.sa_sigaction = dummy_sighandler;
+ act.sa_flags = SA_SIGINFO | SA_RESTART;
+ r = sigemptyset(&act.sa_mask);
+ ATF_REQUIRE_EQ(0, r);
+ r = sigaction(SIGCHLD, &act, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ r = sigemptyset(&mask);
+ ATF_REQUIRE_EQ(0, r);
+ r = sigaddset(&mask, SIGCHLD);
+ ATF_REQUIRE_EQ(0, r);
+ r = sigprocmask(SIG_BLOCK, &mask, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ parent = getpid();
+ r = procctl(P_PID, parent, PROC_REAP_ACQUIRE, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ child = fork();
+ ATF_REQUIRE(child != -1);
+ if (child == 0) {
+ grandchild = fork();
+ if (grandchild == -1)
+ _exit(101);
+ else if (grandchild == 0)
+ _exit(2);
+ else {
+ if (waitid(P_PID, grandchild, NULL,
+ WNOWAIT | WEXITED) != 0)
+ _exit(102);
+ _exit(3);
+ }
+ }
+
+ pid = waitpid(child, NULL, 0);
+ ATF_REQUIRE_EQ(child, pid);
+
+ r = sigwaitinfo(&mask, &info);
+ ATF_REQUIRE_EQ(SIGCHLD, r);
+ ATF_CHECK_EQ(SIGCHLD, info.si_signo);
+ ATF_CHECK_EQ(CLD_EXITED, info.si_code);
+ ATF_CHECK_EQ(2, info.si_status);
+ grandchild = info.si_pid;
+ ATF_REQUIRE(grandchild > 0);
+ ATF_REQUIRE(grandchild != parent);
+ ATF_REQUIRE(grandchild != child);
+
+ pid = waitpid(-1, NULL, 0);
+ ATF_REQUIRE_EQ(grandchild, pid);
+}
+
+ATF_TC_WITHOUT_HEAD(reaper_status);
+ATF_TC_BODY(reaper_status, tc)
+{
+ struct procctl_reaper_status st;
+ ssize_t sr;
+ pid_t parent, child, pid;
+ int r, status;
+ int pip[2];
+
+ parent = getpid();
+ r = procctl(P_PID, parent, PROC_REAP_STATUS, &st);
+ ATF_REQUIRE_EQ(0, r);
+ ATF_CHECK_EQ(0, st.rs_flags & REAPER_STATUS_OWNED);
+ ATF_CHECK(st.rs_children > 0);
+ ATF_CHECK(st.rs_descendants > 0);
+ ATF_CHECK(st.rs_descendants >= st.rs_children);
+ ATF_CHECK(st.rs_reaper != parent);
+ ATF_CHECK(st.rs_reaper > 0);
+
+ r = procctl(P_PID, parent, PROC_REAP_ACQUIRE, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ r = procctl(P_PID, parent, PROC_REAP_STATUS, &st);
+ ATF_REQUIRE_EQ(0, r);
+ ATF_CHECK_EQ(REAPER_STATUS_OWNED,
+ st.rs_flags & (REAPER_STATUS_OWNED | REAPER_STATUS_REALINIT));
+ ATF_CHECK_EQ(0, st.rs_children);
+ ATF_CHECK_EQ(0, st.rs_descendants);
+ ATF_CHECK(st.rs_reaper == parent);
+ ATF_CHECK_EQ(-1, st.rs_pid);
+
+ r = pipe(pip);
+ ATF_REQUIRE_EQ(0, r);
+ child = fork();
+ ATF_REQUIRE(child != -1);
+ if (child == 0) {
+ if (close(pip[0]) != 0)
+ _exit(100);
+ if (procctl(P_PID, parent, PROC_REAP_STATUS, &st) != 0)
+ _exit(101);
+ if (write(pip[1], &st, sizeof(st)) != (ssize_t)sizeof(st))
+ _exit(102);
+ if (procctl(P_PID, getpid(), PROC_REAP_STATUS, &st) != 0)
+ _exit(103);
+ if (write(pip[1], &st, sizeof(st)) != (ssize_t)sizeof(st))
+ _exit(104);
+ _exit(0);
+ }
+ r = close(pip[1]);
+ ATF_REQUIRE_EQ(0, r);
+
+ sr = read(pip[0], &st, sizeof(st));
+ ATF_REQUIRE_EQ((ssize_t)sizeof(st), sr);
+ ATF_CHECK_EQ(REAPER_STATUS_OWNED,
+ st.rs_flags & (REAPER_STATUS_OWNED | REAPER_STATUS_REALINIT));
+ ATF_CHECK_EQ(1, st.rs_children);
+ ATF_CHECK_EQ(1, st.rs_descendants);
+ ATF_CHECK(st.rs_reaper == parent);
+ ATF_CHECK_EQ(child, st.rs_pid);
+ sr = read(pip[0], &st, sizeof(st));
+ ATF_REQUIRE_EQ((ssize_t)sizeof(st), sr);
+ ATF_CHECK_EQ(0,
+ st.rs_flags & (REAPER_STATUS_OWNED | REAPER_STATUS_REALINIT));
+ ATF_CHECK_EQ(1, st.rs_children);
+ ATF_CHECK_EQ(1, st.rs_descendants);
+ ATF_CHECK(st.rs_reaper == parent);
+ ATF_CHECK_EQ(child, st.rs_pid);
+
+ r = close(pip[0]);
+ ATF_REQUIRE_EQ(0, r);
+ pid = waitpid(child, &status, 0);
+ ATF_REQUIRE_EQ(child, pid);
+ ATF_CHECK_EQ(0, status);
+
+ r = procctl(P_PID, parent, PROC_REAP_STATUS, &st);
+ ATF_REQUIRE_EQ(0, r);
+ ATF_CHECK_EQ(REAPER_STATUS_OWNED,
+ st.rs_flags & (REAPER_STATUS_OWNED | REAPER_STATUS_REALINIT));
+ ATF_CHECK_EQ(0, st.rs_children);
+ ATF_CHECK_EQ(0, st.rs_descendants);
+ ATF_CHECK(st.rs_reaper == parent);
+ ATF_CHECK_EQ(-1, st.rs_pid);
+}
+
+ATF_TC_WITHOUT_HEAD(reaper_getpids);
+ATF_TC_BODY(reaper_getpids, tc)
+{
+ struct procctl_reaper_pidinfo info[10];
+ ssize_t sr;
+ pid_t parent, child, grandchild, pid;
+ int r, status, childidx;
+ int pipa[2], pipb[2];
+
+ parent = getpid();
+ r = procctl(P_PID, parent, PROC_REAP_ACQUIRE, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ memset(info, '\0', sizeof(info));
+ r = procctl(P_PID, parent, PROC_REAP_GETPIDS,
+ &(struct procctl_reaper_pids){
+ .rp_count = sizeof(info) / sizeof(info[0]),
+ .rp_pids = info
+ });
+ ATF_CHECK_EQ(0, r);
+ ATF_CHECK_EQ(0, info[0].pi_flags & REAPER_PIDINFO_VALID);
+
+ r = pipe(pipa);
+ ATF_REQUIRE_EQ(0, r);
+ r = pipe(pipb);
+ ATF_REQUIRE_EQ(0, r);
+ child = fork();
+ ATF_REQUIRE(child != -1);
+ if (child == 0) {
+ if (close(pipa[1]) != 0)
+ _exit(100);
+ if (close(pipb[0]) != 0)
+ _exit(100);
+ if (read(pipa[0], &(uint8_t){ 0 }, 1) != 1)
+ _exit(101);
+ grandchild = fork();
+ if (grandchild == -1)
+ _exit(102);
+ if (grandchild == 0) {
+ if (write(pipb[1], &(uint8_t){ 0 }, 1) != 1)
+ _exit(103);
+ if (read(pipa[0], &(uint8_t){ 0 }, 1) != 1)
+ _exit(104);
+ _exit(0);
+ }
+ for (;;)
+ pause();
+ }
+ r = close(pipa[0]);
+ ATF_REQUIRE_EQ(0, r);
+ r = close(pipb[1]);
+ ATF_REQUIRE_EQ(0, r);
+
+ memset(info, '\0', sizeof(info));
+ r = procctl(P_PID, parent, PROC_REAP_GETPIDS,
+ &(struct procctl_reaper_pids){
+ .rp_count = sizeof(info) / sizeof(info[0]),
+ .rp_pids = info
+ });
+ ATF_CHECK_EQ(0, r);
+ ATF_CHECK_EQ(REAPER_PIDINFO_VALID | REAPER_PIDINFO_CHILD,
+ info[0].pi_flags & (REAPER_PIDINFO_VALID | REAPER_PIDINFO_CHILD));
+ ATF_CHECK_EQ(child, info[0].pi_pid);
+ ATF_CHECK_EQ(child, info[0].pi_subtree);
+ ATF_CHECK_EQ(0, info[1].pi_flags & REAPER_PIDINFO_VALID);
+
+ sr = write(pipa[1], &(uint8_t){ 0 }, 1);
+ ATF_REQUIRE_EQ(1, sr);
+ sr = read(pipb[0], &(uint8_t){ 0 }, 1);
+ ATF_REQUIRE_EQ(1, sr);
+
+ memset(info, '\0', sizeof(info));
+ r = procctl(P_PID, parent, PROC_REAP_GETPIDS,
+ &(struct procctl_reaper_pids){
+ .rp_count = sizeof(info) / sizeof(info[0]),
+ .rp_pids = info
+ });
+ ATF_CHECK_EQ(0, r);
+ ATF_CHECK_EQ(REAPER_PIDINFO_VALID,
+ info[0].pi_flags & REAPER_PIDINFO_VALID);
+ ATF_CHECK_EQ(REAPER_PIDINFO_VALID,
+ info[1].pi_flags & REAPER_PIDINFO_VALID);
+ ATF_CHECK_EQ(0, info[2].pi_flags & REAPER_PIDINFO_VALID);
+ ATF_CHECK_EQ(child, info[0].pi_subtree);
+ ATF_CHECK_EQ(child, info[1].pi_subtree);
+ childidx = info[1].pi_pid == child ? 1 : 0;
+ ATF_CHECK_EQ(REAPER_PIDINFO_CHILD,
+ info[childidx].pi_flags & REAPER_PIDINFO_CHILD);
+ ATF_CHECK_EQ(0, info[childidx ^ 1].pi_flags & REAPER_PIDINFO_CHILD);
+ ATF_CHECK(info[childidx].pi_pid == child);
+ grandchild = info[childidx ^ 1].pi_pid;
+ ATF_CHECK(grandchild > 0);
+ ATF_CHECK(grandchild != child);
+ ATF_CHECK(grandchild != parent);
+
+ r = kill(child, SIGTERM);
+ ATF_REQUIRE_EQ(0, r);
+
+ pid = waitpid(child, &status, 0);
+ ATF_REQUIRE_EQ(child, pid);
+ ATF_CHECK(WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM);
+
+ memset(info, '\0', sizeof(info));
+ r = procctl(P_PID, parent, PROC_REAP_GETPIDS,
+ &(struct procctl_reaper_pids){
+ .rp_count = sizeof(info) / sizeof(info[0]),
+ .rp_pids = info
+ });
+ ATF_CHECK_EQ(0, r);
+ ATF_CHECK_EQ(REAPER_PIDINFO_VALID,
+ info[0].pi_flags & REAPER_PIDINFO_VALID);
+ ATF_CHECK_EQ(0, info[1].pi_flags & REAPER_PIDINFO_VALID);
+ ATF_CHECK_EQ(child, info[0].pi_subtree);
+ ATF_CHECK_EQ(REAPER_PIDINFO_CHILD,
+ info[0].pi_flags & REAPER_PIDINFO_CHILD);
+ ATF_CHECK_EQ(grandchild, info[0].pi_pid);
+
+ sr = write(pipa[1], &(uint8_t){ 0 }, 1);
+ ATF_REQUIRE_EQ(1, sr);
+
+ memset(info, '\0', sizeof(info));
+ r = procctl(P_PID, parent, PROC_REAP_GETPIDS,
+ &(struct procctl_reaper_pids){
+ .rp_count = sizeof(info) / sizeof(info[0]),
+ .rp_pids = info
+ });
+ ATF_CHECK_EQ(0, r);
+ ATF_CHECK_EQ(REAPER_PIDINFO_VALID,
+ info[0].pi_flags & REAPER_PIDINFO_VALID);
+ ATF_CHECK_EQ(0, info[1].pi_flags & REAPER_PIDINFO_VALID);
+ ATF_CHECK_EQ(child, info[0].pi_subtree);
+ ATF_CHECK_EQ(REAPER_PIDINFO_CHILD,
+ info[0].pi_flags & REAPER_PIDINFO_CHILD);
+ ATF_CHECK_EQ(grandchild, info[0].pi_pid);
+
+ pid = waitpid(grandchild, &status, 0);
+ ATF_REQUIRE_EQ(grandchild, pid);
+ ATF_CHECK_EQ(0, status);
+
+ memset(info, '\0', sizeof(info));
+ r = procctl(P_PID, parent, PROC_REAP_GETPIDS,
+ &(struct procctl_reaper_pids){
+ .rp_count = sizeof(info) / sizeof(info[0]),
+ .rp_pids = info
+ });
+ ATF_CHECK_EQ(0, r);
+ ATF_CHECK_EQ(0, info[0].pi_flags & REAPER_PIDINFO_VALID);
+
+ r = close(pipa[1]);
+ ATF_REQUIRE_EQ(0, r);
+ r = close(pipb[0]);
+ ATF_REQUIRE_EQ(0, r);
+}
+
+ATF_TC_WITHOUT_HEAD(reaper_kill_badsig);
+ATF_TC_BODY(reaper_kill_badsig, tc)
+{
+ struct procctl_reaper_kill params;
+ pid_t parent;
+ int r;
+
+ parent = getpid();
+ r = procctl(P_PID, parent, PROC_REAP_ACQUIRE, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ params.rk_sig = -1;
+ params.rk_flags = 0;
+ r = procctl(P_PID, parent, PROC_REAP_KILL, &params);
+ ATF_CHECK(r == -1 && errno == EINVAL);
+}
+
+ATF_TC_WITHOUT_HEAD(reaper_kill_sigzero);
+ATF_TC_BODY(reaper_kill_sigzero, tc)
+{
+ struct procctl_reaper_kill params;
+ pid_t parent;
+ int r;
+
+ parent = getpid();
+ r = procctl(P_PID, parent, PROC_REAP_ACQUIRE, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ params.rk_sig = 0;
+ params.rk_flags = 0;
+ r = procctl(P_PID, parent, PROC_REAP_KILL, &params);
+ ATF_CHECK(r == -1 && errno == EINVAL);
+}
+
+ATF_TC_WITHOUT_HEAD(reaper_kill_empty);
+ATF_TC_BODY(reaper_kill_empty, tc)
+{
+ struct procctl_reaper_kill params;
+ pid_t parent;
+ int r;
+
+ parent = getpid();
+ r = procctl(P_PID, parent, PROC_REAP_ACQUIRE, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ params.rk_sig = SIGTERM;
+ params.rk_flags = 0;
+ params.rk_killed = 77;
+ r = procctl(P_PID, parent, PROC_REAP_KILL, &params);
+ ATF_CHECK(r == -1 && errno == ESRCH);
+ ATF_CHECK_EQ(0, params.rk_killed);
+}
+
+ATF_TC_WITHOUT_HEAD(reaper_kill_normal);
+ATF_TC_BODY(reaper_kill_normal, tc)
+{
+ struct procctl_reaper_kill params;
+ ssize_t sr;
+ pid_t parent, child, grandchild, pid;
+ int r, status;
+ int pip[2];
+
+ parent = getpid();
+ r = procctl(P_PID, parent, PROC_REAP_ACQUIRE, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ r = pipe(pip);
+ ATF_REQUIRE_EQ(0, r);
+ child = fork();
+ ATF_REQUIRE(child != -1);
+ if (child == 0) {
+ if (close(pip[0]) != 0)
+ _exit(100);
+ grandchild = fork();
+ if (grandchild == -1)
+ _exit(101);
+ if (grandchild == 0) {
+ if (write(pip[1], &(uint8_t){ 0 }, 1) != 1)
+ _exit(102);
+ for (;;)
+ pause();
+ }
+ for (;;)
+ pause();
+ }
+ r = close(pip[1]);
+ ATF_REQUIRE_EQ(0, r);
+
+ sr = read(pip[0], &(uint8_t){ 0 }, 1);
+ ATF_REQUIRE_EQ(1, sr);
+
+ params.rk_sig = SIGTERM;
+ params.rk_flags = 0;
+ params.rk_killed = 77;
+ r = procctl(P_PID, parent, PROC_REAP_KILL, &params);
+ ATF_CHECK_EQ(0, r);
+ ATF_CHECK_EQ(2, params.rk_killed);
+
+ pid = waitpid(child, &status, 0);
+ ATF_REQUIRE_EQ(child, pid);
+ ATF_CHECK(WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM);
+
+ pid = waitpid(-1, &status, 0);
+ ATF_REQUIRE(pid > 0);
+ ATF_CHECK(pid != parent);
+ ATF_CHECK(pid != child);
+ ATF_CHECK(WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM);
+
+ r = close(pip[0]);
+ ATF_REQUIRE_EQ(0, r);
+}
+
+ATF_TC_WITHOUT_HEAD(reaper_kill_subtree);
+ATF_TC_BODY(reaper_kill_subtree, tc)
+{
+ struct procctl_reaper_kill params;
+ ssize_t sr;
+ pid_t parent, child1, child2, grandchild1, grandchild2, pid;
+ int r, status;
+ int pip[2];
+
+ parent = getpid();
+ r = procctl(P_PID, parent, PROC_REAP_ACQUIRE, NULL);
+ ATF_REQUIRE_EQ(0, r);
+
+ r = pipe(pip);
+ ATF_REQUIRE_EQ(0, r);
+ child1 = fork();
+ ATF_REQUIRE(child1 != -1);
+ if (child1 == 0) {
+ if (close(pip[0]) != 0)
+ _exit(100);
+ grandchild1 = fork();
+ if (grandchild1 == -1)
+ _exit(101);
+ if (grandchild1 == 0) {
+ if (write(pip[1], &(uint8_t){ 0 }, 1) != 1)
+ _exit(102);
+ for (;;)
+ pause();
+ }
+ for (;;)
+ pause();
+ }
+ child2 = fork();
+ ATF_REQUIRE(child2 != -1);
+ if (child2 == 0) {
+ if (close(pip[0]) != 0)
+ _exit(100);
+ grandchild2 = fork();
+ if (grandchild2 == -1)
+ _exit(101);
+ if (grandchild2 == 0) {
+ if (write(pip[1], &(uint8_t){ 0 }, 1) != 1)
+ _exit(102);
+ for (;;)
+ pause();
+ }
+ for (;;)
+ pause();
+ }
+ r = close(pip[1]);
+ ATF_REQUIRE_EQ(0, r);
+
+ sr = read(pip[0], &(uint8_t){ 0 }, 1);
+ ATF_REQUIRE_EQ(1, sr);
+ sr = read(pip[0], &(uint8_t){ 0 }, 1);
+ ATF_REQUIRE_EQ(1, sr);
+
+ params.rk_sig = SIGUSR1;
+ params.rk_flags = REAPER_KILL_SUBTREE;
+ params.rk_subtree = child1;
+ params.rk_killed = 77;
+ r = procctl(P_PID, parent, PROC_REAP_KILL, &params);
+ ATF_REQUIRE_EQ(0, r);
+ ATF_REQUIRE_EQ(2, params.rk_killed);
+ ATF_CHECK_EQ(-1, params.rk_fpid);
+
+ pid = waitpid(child1, &status, 0);
+ ATF_REQUIRE_EQ(child1, pid);
+ ATF_CHECK(WIFSIGNALED(status) && WTERMSIG(status) == SIGUSR1);
+
+ pid = waitpid(-1, &status, 0);
+ ATF_REQUIRE(pid > 0);
+ ATF_CHECK(pid != parent);
+ ATF_CHECK(pid != child1);
+ ATF_CHECK(pid != child2);
+ ATF_CHECK(WIFSIGNALED(status) && WTERMSIG(status) == SIGUSR1);
+
+ params.rk_sig = SIGUSR2;
+ params.rk_flags = REAPER_KILL_SUBTREE;
+ params.rk_subtree = child2;
+ params.rk_killed = 77;
+ r = procctl(P_PID, parent, PROC_REAP_KILL, &params);
+ ATF_REQUIRE_EQ(0, r);
+ ATF_REQUIRE_EQ(2, params.rk_killed);
+ ATF_CHECK_EQ(-1, params.rk_fpid);
+
+ pid = waitpid(child2, &status, 0);
+ ATF_REQUIRE_EQ(child2, pid);
+ ATF_CHECK(WIFSIGNALED(status) && WTERMSIG(status) == SIGUSR2);
+
+ pid = waitpid(-1, &status, 0);
+ ATF_REQUIRE(pid > 0);
+ ATF_CHECK(pid != parent);
+ ATF_CHECK(pid != child1);
+ ATF_CHECK(pid != child2);
+ ATF_CHECK(WIFSIGNALED(status) && WTERMSIG(status) == SIGUSR2);
+
+ r = close(pip[0]);
+ ATF_REQUIRE_EQ(0, r);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, reaper_wait_child_first);
+ ATF_TP_ADD_TC(tp, reaper_wait_grandchild_first);
+ ATF_TP_ADD_TC(tp, reaper_sigchld_child_first);
+ ATF_TP_ADD_TC(tp, reaper_sigchld_grandchild_first);
+ ATF_TP_ADD_TC(tp, reaper_status);
+ ATF_TP_ADD_TC(tp, reaper_getpids);
+ ATF_TP_ADD_TC(tp, reaper_kill_badsig);
+ ATF_TP_ADD_TC(tp, reaper_kill_sigzero);
+ ATF_TP_ADD_TC(tp, reaper_kill_empty);
+ ATF_TP_ADD_TC(tp, reaper_kill_normal);
+ ATF_TP_ADD_TC(tp, reaper_kill_subtree);
+ return (atf_no_error());
+}
diff --git a/tests/sys/kern/unix_passfd_test.c b/tests/sys/kern/unix_passfd_test.c
new file mode 100644
index 000000000000..12568389bc3d
--- /dev/null
+++ b/tests/sys/kern/unix_passfd_test.c
@@ -0,0 +1,396 @@
+/*-
+ * Copyright (c) 2005 Robert N. M. Watson
+ * Copyright (c) 2015 Mark Johnston
+ * 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 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 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$
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+/*
+ * UNIX domain sockets allow file descriptors to be passed via "ancillary
+ * data", or control messages. This regression test is intended to exercise
+ * this facility, both performing some basic tests that it operates, and also
+ * causing some kernel edge cases to execute, such as garbage collection when
+ * there are cyclic file descriptor references. Right now we test only with
+ * stream sockets, but ideally we'd also test with datagram sockets.
+ */
+
+static void
+domainsocketpair(int *fdp)
+{
+
+ ATF_REQUIRE_MSG(socketpair(PF_UNIX, SOCK_STREAM, 0, fdp) != -1,
+ "socketpair(PF_UNIX, SOCK_STREAM) failed: %s", strerror(errno));
+}
+
+static void
+closesocketpair(int *fdp)
+{
+
+ close(fdp[0]);
+ close(fdp[1]);
+}
+
+static void
+devnull(int *fdp)
+{
+ int fd;
+
+ fd = open("/dev/null", O_RDONLY);
+ ATF_REQUIRE_MSG(fd != -1, "open failed: %s", strerror(errno));
+ *fdp = fd;
+}
+
+static void
+tempfile(int *fdp)
+{
+ char path[PATH_MAX];
+ int fd;
+
+ snprintf(path, PATH_MAX, "%s/unix_passfd.XXXXXXXXXXXXXXX",
+ getenv("TMPDIR") == NULL ? "/tmp" : getenv("TMPDIR"));
+ fd = mkstemp(path);
+ ATF_REQUIRE_MSG(fd != -1, "mkstemp(%s) failed", path);
+ (void)unlink(path);
+ *fdp = fd;
+}
+
+static void
+dofstat(int fd, struct stat *sb)
+{
+
+ ATF_REQUIRE_MSG(fstat(fd, sb) == 0,
+ "fstat failed: %s", strerror(errno));
+}
+
+static void
+samefile(struct stat *sb1, struct stat *sb2)
+{
+
+ ATF_REQUIRE_MSG(sb1->st_dev == sb2->st_dev, "different device");
+ ATF_REQUIRE_MSG(sb1->st_ino == sb2->st_ino, "different inode");
+}
+
+static void
+sendfd_payload(int sockfd, int send_fd, void *payload, size_t paylen)
+{
+ struct iovec iovec;
+ char message[CMSG_SPACE(sizeof(int))];
+ struct cmsghdr *cmsghdr;
+ struct msghdr msghdr;
+ ssize_t len;
+
+ bzero(&msghdr, sizeof(msghdr));
+ bzero(&message, sizeof(message));
+
+ msghdr.msg_control = message;
+ msghdr.msg_controllen = sizeof(message);
+
+ iovec.iov_base = payload;
+ iovec.iov_len = paylen;
+
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+
+ cmsghdr = (struct cmsghdr *)(void*)message;
+ cmsghdr->cmsg_len = CMSG_LEN(sizeof(int));
+ cmsghdr->cmsg_level = SOL_SOCKET;
+ cmsghdr->cmsg_type = SCM_RIGHTS;
+ memcpy(CMSG_DATA(cmsghdr), &send_fd, sizeof(int));
+
+ len = sendmsg(sockfd, &msghdr, 0);
+ ATF_REQUIRE_MSG(len != -1, "sendmsg failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG((size_t)len == paylen,
+ "sendmsg: %zd messages sent; expected: %zu; %s", len, paylen,
+ strerror(errno));
+}
+
+static void
+sendfd(int sockfd, int send_fd)
+{
+ char ch = 0;
+
+ return (sendfd_payload(sockfd, send_fd, &ch, sizeof(ch)));
+}
+
+static void
+recvfd_payload(int sockfd, int *recv_fd, void *buf, size_t buflen)
+{
+ struct cmsghdr *cmsghdr;
+ char message[CMSG_SPACE(SOCKCREDSIZE(CMGROUP_MAX)) + sizeof(int)];
+ struct msghdr msghdr;
+ struct iovec iovec;
+ ssize_t len;
+
+ bzero(&msghdr, sizeof(msghdr));
+
+ msghdr.msg_control = message;
+ msghdr.msg_controllen = sizeof(message);
+
+ iovec.iov_base = buf;
+ iovec.iov_len = buflen;
+
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+
+ len = recvmsg(sockfd, &msghdr, 0);
+ ATF_REQUIRE_MSG(len != -1, "recvmsg failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG((size_t)len == buflen,
+ "recvmsg: %zd bytes received; expected %zd", len, buflen);
+
+ cmsghdr = CMSG_FIRSTHDR(&msghdr);
+ ATF_REQUIRE_MSG(cmsghdr != NULL,
+ "recvmsg: did not receive control message");
+ *recv_fd = -1;
+ for (; cmsghdr != NULL; cmsghdr = CMSG_NXTHDR(&msghdr, cmsghdr)) {
+ if (cmsghdr->cmsg_level == SOL_SOCKET &&
+ cmsghdr->cmsg_type == SCM_RIGHTS &&
+ cmsghdr->cmsg_len == CMSG_LEN(sizeof(int))) {
+ memcpy(recv_fd, CMSG_DATA(cmsghdr), sizeof(int));
+ ATF_REQUIRE(*recv_fd != -1);
+ }
+ }
+ ATF_REQUIRE_MSG(*recv_fd != -1,
+ "recvmsg: did not receive single-fd message");
+}
+
+static void
+recvfd(int sockfd, int *recv_fd)
+{
+ char ch = 0;
+
+ return (recvfd_payload(sockfd, recv_fd, &ch, sizeof(ch)));
+}
+
+/*
+ * Put a temporary file into a UNIX domain socket, then take it out and make
+ * sure it's the same file. First time around, don't close the reference
+ * after sending.
+ */
+ATF_TC_WITHOUT_HEAD(simple_send_fd);
+ATF_TC_BODY(simple_send_fd, tc)
+{
+ struct stat getfd_stat, putfd_stat;
+ int fd[2], getfd, putfd;
+
+ domainsocketpair(fd);
+ tempfile(&putfd);
+ dofstat(putfd, &putfd_stat);
+ sendfd(fd[0], putfd);
+ recvfd(fd[1], &getfd);
+ dofstat(getfd, &getfd_stat);
+ samefile(&putfd_stat, &getfd_stat);
+ close(putfd);
+ close(getfd);
+ closesocketpair(fd);
+}
+
+/*
+ * Same as simple_send_fd, only close the file reference after sending, so that
+ * the only reference is the descriptor in the UNIX domain socket buffer.
+ */
+ATF_TC_WITHOUT_HEAD(send_and_close);
+ATF_TC_BODY(send_and_close, tc)
+{
+ struct stat getfd_stat, putfd_stat;
+ int fd[2], getfd, putfd;
+
+ domainsocketpair(fd);
+ tempfile(&putfd);
+ dofstat(putfd, &putfd_stat);
+ sendfd(fd[0], putfd);
+ close(putfd);
+ recvfd(fd[1], &getfd);
+ dofstat(getfd, &getfd_stat);
+ samefile(&putfd_stat, &getfd_stat);
+ close(getfd);
+ closesocketpair(fd);
+}
+
+/*
+ * Put a temporary file into a UNIX domain socket, then close both endpoints
+ * causing garbage collection to kick off.
+ */
+ATF_TC_WITHOUT_HEAD(send_and_cancel);
+ATF_TC_BODY(send_and_cancel, tc)
+{
+ int fd[2], putfd;
+
+ domainsocketpair(fd);
+ tempfile(&putfd);
+ sendfd(fd[0], putfd);
+ close(putfd);
+ closesocketpair(fd);
+}
+
+/*
+ * Send two files. Then receive them. Make sure they are returned in the
+ * right order, and both get there.
+ */
+ATF_TC_WITHOUT_HEAD(two_files);
+ATF_TC_BODY(two_files, tc)
+{
+ struct stat getfd_1_stat, getfd_2_stat, putfd_1_stat, putfd_2_stat;
+ int fd[2], getfd_1, getfd_2, putfd_1, putfd_2;
+
+ domainsocketpair(fd);
+ tempfile(&putfd_1);
+ tempfile(&putfd_2);
+ dofstat(putfd_1, &putfd_1_stat);
+ dofstat(putfd_2, &putfd_2_stat);
+ sendfd(fd[0], putfd_1);
+ sendfd(fd[0], putfd_2);
+ close(putfd_1);
+ close(putfd_2);
+ recvfd(fd[1], &getfd_1);
+ recvfd(fd[1], &getfd_2);
+ dofstat(getfd_1, &getfd_1_stat);
+ dofstat(getfd_2, &getfd_2_stat);
+ samefile(&putfd_1_stat, &getfd_1_stat);
+ samefile(&putfd_2_stat, &getfd_2_stat);
+ close(getfd_1);
+ close(getfd_2);
+ closesocketpair(fd);
+}
+
+/*
+ * Big bundling test. Send an endpoint of the UNIX domain socket over itself,
+ * closing the door behind it.
+ */
+ATF_TC_WITHOUT_HEAD(bundle);
+ATF_TC_BODY(bundle, tc)
+{
+ int fd[2], getfd;
+
+ domainsocketpair(fd);
+
+ sendfd(fd[0], fd[0]);
+ close(fd[0]);
+ recvfd(fd[1], &getfd);
+ close(getfd);
+ close(fd[1]);
+}
+
+/*
+ * Big bundling test part two: Send an endpoint of the UNIX domain socket over
+ * itself, close the door behind it, and never remove it from the other end.
+ */
+ATF_TC_WITHOUT_HEAD(bundle_cancel);
+ATF_TC_BODY(bundle_cancel, tc)
+{
+ int fd[2];
+
+ domainsocketpair(fd);
+ sendfd(fd[0], fd[0]);
+ sendfd(fd[1], fd[0]);
+ closesocketpair(fd);
+}
+
+/*
+ * Test for PR 151758: Send an character device over the UNIX domain socket
+ * and then close both sockets to orphan the device.
+ */
+ATF_TC_WITHOUT_HEAD(devfs_orphan);
+ATF_TC_BODY(devfs_orphan, tc)
+{
+ int fd[2], putfd;
+
+ domainsocketpair(fd);
+ devnull(&putfd);
+ sendfd(fd[0], putfd);
+ close(putfd);
+ closesocketpair(fd);
+}
+
+#define LOCAL_SENDSPACE_SYSCTL "net.local.stream.sendspace"
+
+/*
+ * Test for PR 181741. Receiver sets LOCAL_CREDS, and kernel prepends a
+ * control message to the data. Sender sends large payload.
+ * Payload + SCM_RIGHTS + LOCAL_CREDS hit socket buffer limit, and receiver
+ * receives truncated data.
+ */
+ATF_TC_WITHOUT_HEAD(rights_creds_payload);
+ATF_TC_BODY(rights_creds_payload, tc)
+{
+ const int on = 1;
+ u_long sendspace;
+ size_t len;
+ void *buf;
+ int fd[2], getfd, putfd, rc;
+
+ atf_tc_expect_fail("PR 181741: Packet loss when 'control' messages "
+ "are present with large data");
+
+ len = sizeof(sendspace);
+ rc = sysctlbyname(LOCAL_SENDSPACE_SYSCTL, &sendspace,
+ &len, NULL, 0);
+ ATF_REQUIRE_MSG(rc != -1,
+ "sysctl %s failed: %s", LOCAL_SENDSPACE_SYSCTL, strerror(errno));
+
+ buf = calloc(1, sendspace);
+ ATF_REQUIRE(buf != NULL);
+
+ domainsocketpair(fd);
+ rc = setsockopt(fd[1], 0, LOCAL_CREDS, &on, sizeof(on));
+ ATF_REQUIRE_MSG(rc != -1, "setsockopt(LOCAL_CREDS) failed: %s",
+ strerror(errno));
+ tempfile(&putfd);
+ sendfd_payload(fd[0], putfd, buf, sendspace);
+ recvfd_payload(fd[1], &getfd, buf, sendspace);
+ close(putfd);
+ close(getfd);
+ closesocketpair(fd);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, simple_send_fd);
+ ATF_TP_ADD_TC(tp, send_and_close);
+ ATF_TP_ADD_TC(tp, send_and_cancel);
+ ATF_TP_ADD_TC(tp, two_files);
+ ATF_TP_ADD_TC(tp, bundle);
+ ATF_TP_ADD_TC(tp, bundle_cancel);
+ ATF_TP_ADD_TC(tp, devfs_orphan);
+ ATF_TP_ADD_TC(tp, rights_creds_payload);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/kern/unix_seqpacket_test.c b/tests/sys/kern/unix_seqpacket_test.c
new file mode 100644
index 000000000000..7305ab35f282
--- /dev/null
+++ b/tests/sys/kern/unix_seqpacket_test.c
@@ -0,0 +1,1191 @@
+/*-
+ * Copyright (c) 2014 Spectra Logic Corporation. 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 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 AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdio.h>
+
+#include <atf-c.h>
+
+/*
+ * Helper functions
+ */
+
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+
+static void
+do_socketpair(int *sv)
+{
+ int s;
+
+ s = socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sv);
+ ATF_REQUIRE_EQ(0, s);
+ ATF_REQUIRE(sv[0] >= 0);
+ ATF_REQUIRE(sv[1] >= 0);
+ ATF_REQUIRE(sv[0] != sv[1]);
+}
+
+static void
+do_socketpair_nonblocking(int *sv)
+{
+ int s;
+
+ s = socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sv);
+ ATF_REQUIRE_EQ(0, s);
+ ATF_REQUIRE(sv[0] >= 0);
+ ATF_REQUIRE(sv[1] >= 0);
+ ATF_REQUIRE(sv[0] != sv[1]);
+ ATF_REQUIRE(-1 != fcntl(sv[0], F_SETFL, O_NONBLOCK));
+ ATF_REQUIRE(-1 != fcntl(sv[1], F_SETFL, O_NONBLOCK));
+}
+
+/*
+ * Returns a pair of sockets made the hard way: bind, listen, connect & accept
+ * @return const char* The path to the socket
+ */
+static const char*
+mk_pair_of_sockets(int *sv)
+{
+ struct sockaddr_un sun;
+ /* ATF's isolation mechanisms will guarantee uniqueness of this file */
+ const char *path = "sock";
+ int s, err, s2, s1;
+
+ s = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s >= 0);
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_LOCAL;
+ sun.sun_len = sizeof(sun);
+ strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
+ err = bind(s, (struct sockaddr *)&sun, sizeof(sun));
+ err = listen(s, -1);
+ ATF_CHECK_EQ(0, err);
+
+ /* Create the other socket */
+ s2 = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s2 >= 0);
+ err = connect(s2, (struct sockaddr*)&sun, sizeof(sun));
+ if (err != 0) {
+ perror("connect");
+ atf_tc_fail("connect(2) failed");
+ }
+
+ /* Accept it */
+ s1 = accept(s, NULL, NULL);
+ if (s1 == -1) {
+ perror("accept");
+ atf_tc_fail("accept(2) failed");
+ }
+
+ sv[0] = s1;
+ sv[1] = s2;
+
+ close(s);
+
+ return (path);
+}
+
+static volatile sig_atomic_t got_sigpipe = 0;
+static void
+shutdown_send_sigpipe_handler(int __unused x)
+{
+ got_sigpipe = 1;
+}
+
+/*
+ * Parameterized test function bodies
+ */
+static void
+test_eagain(size_t sndbufsize, size_t rcvbufsize)
+{
+ int i;
+ int sv[2];
+ const size_t totalsize = (sndbufsize + rcvbufsize) * 2;
+ const size_t pktsize = MIN(sndbufsize, rcvbufsize) / 4;
+ const int numpkts = totalsize / pktsize;
+ char sndbuf[pktsize];
+ ssize_t ssize;
+
+ /* setup the socket pair */
+ do_socketpair_nonblocking(sv);
+ /* Setup the buffers */
+ ATF_REQUIRE_EQ(0, setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbufsize,
+ sizeof(sndbufsize)));
+ ATF_REQUIRE_EQ(0, setsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &rcvbufsize,
+ sizeof(rcvbufsize)));
+
+ bzero(sndbuf, pktsize);
+ /* Send data until we get EAGAIN */
+ for(i=0; i < numpkts; i++) {
+ ssize = send(sv[0], sndbuf, pktsize, MSG_EOR);
+ if (ssize == -1) {
+ if (errno == EAGAIN) {
+ close(sv[0]);
+ close(sv[1]);
+ atf_tc_pass();
+ }
+ else {
+ perror("send");
+ atf_tc_fail("send returned < 0 but not EAGAIN");
+ }
+ }
+ }
+ atf_tc_fail("Never got EAGAIN");
+}
+
+static void
+test_sendrecv_symmetric_buffers(size_t bufsize, int blocking) {
+ int s;
+ int sv[2];
+ const ssize_t pktsize = bufsize / 2;
+ char sndbuf[pktsize];
+ char recv_buf[pktsize];
+ ssize_t ssize, rsize;
+
+ /* setup the socket pair */
+ if (blocking)
+ do_socketpair(sv);
+ else
+ do_socketpair_nonblocking(sv);
+
+ /* Setup the buffers */
+ s = setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));
+ ATF_REQUIRE_EQ(0, s);
+ s = setsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));
+ ATF_REQUIRE_EQ(0, s);
+
+ /* Fill the send buffer */
+ bzero(sndbuf, pktsize);
+
+ /* send and receive the packet */
+ ssize = send(sv[0], sndbuf, pktsize, MSG_EOR);
+ if (ssize < 0) {
+ perror("send");
+ atf_tc_fail("send returned < 0");
+ }
+ ATF_CHECK_EQ_MSG(pktsize, ssize, "expected %zd=send(...) but got %zd",
+ pktsize, ssize);
+
+ rsize = recv(sv[1], recv_buf, pktsize, MSG_WAITALL);
+ if (rsize < 0) {
+ perror("recv");
+ atf_tc_fail("recv returned < 0");
+ }
+ ATF_CHECK_EQ_MSG(pktsize, rsize, "expected %zd=send(...) but got %zd",
+ pktsize, rsize);
+ close(sv[0]);
+ close(sv[1]);
+}
+
+static void
+test_pipe_simulator(size_t sndbufsize, size_t rcvbufsize)
+{
+ int num_sent, num_received;
+ int sv[2];
+ const ssize_t pktsize = MIN(sndbufsize, rcvbufsize) / 4;
+ int numpkts;
+ char sndbuf[pktsize];
+ char rcvbuf[pktsize];
+ char comparebuf[pktsize];
+ ssize_t ssize, rsize;
+ bool currently_sending = true;
+
+ /* setup the socket pair */
+ do_socketpair_nonblocking(sv);
+ /* Setup the buffers */
+ ATF_REQUIRE_EQ(0, setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbufsize,
+ sizeof(sndbufsize)));
+ ATF_REQUIRE_EQ(0, setsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &rcvbufsize,
+ sizeof(rcvbufsize)));
+
+ /* Send a total amount of data comfortably greater than the buffers */
+ numpkts = MAX(sndbufsize, rcvbufsize) * 8 / pktsize;
+ for (num_sent=0, num_received=0;
+ num_sent < numpkts || num_received < numpkts; ) {
+ if (currently_sending && num_sent < numpkts) {
+ /* The simulated sending process */
+ /* fill the buffer */
+ memset(sndbuf, num_sent, pktsize);
+ ssize = send(sv[0], sndbuf, pktsize, MSG_EOR);
+ if (ssize < 0) {
+ /*
+ * XXX: This is bug-compatible with the kernel.
+ * The kernel returns EMSGSIZE when it should
+ * return EAGAIN
+ */
+ if (errno == EAGAIN || errno == EMSGSIZE)
+ currently_sending = false;
+ else {
+ perror("send");
+ atf_tc_fail("send failed");
+ }
+ } else {
+ ATF_CHECK_EQ_MSG(pktsize, ssize,
+ "expected %zd=send(...) but got %zd",
+ pktsize, ssize);
+ num_sent++;
+ }
+ } else {
+ /* The simulated receiving process */
+ rsize = recv(sv[1], rcvbuf, pktsize, MSG_WAITALL);
+ if (rsize < 0) {
+ if (errno == EAGAIN) {
+ currently_sending = true;
+ ATF_REQUIRE_MSG(num_sent < numpkts,
+ "Packets were lost!");
+ }
+ else {
+ perror("recv");
+ atf_tc_fail("recv failed");
+ }
+ } else {
+ ATF_CHECK_EQ_MSG(pktsize, rsize,
+ "expected %zd=recv(...) but got %zd",
+ pktsize, rsize);
+ memset(comparebuf, num_received, pktsize);
+ ATF_CHECK_EQ_MSG(0, memcmp(comparebuf, rcvbuf,
+ pktsize),
+ "Received data miscompare");
+ num_received++;
+ }
+ }
+ }
+ close(sv[0]);
+ close(sv[1]);
+}
+
+typedef struct {
+ ssize_t pktsize;
+ int numpkts;
+ int so;
+} test_pipe_thread_data_t;
+
+static void*
+test_pipe_writer(void* args)
+{
+ test_pipe_thread_data_t* td = args;
+ char sndbuf[td->pktsize];
+ ssize_t ssize;
+ int i;
+
+ for(i=0; i < td->numpkts; i++) {
+ memset(sndbuf, i, td->pktsize);
+ ssize = send(td->so, sndbuf, td->pktsize, MSG_EOR);
+ if (ssize < 0) {
+ perror("send");
+ atf_tc_fail("send returned < 0");
+ }
+ ATF_CHECK_EQ_MSG(td->pktsize, ssize,
+ "expected %zd=send(...) but got %zd",
+ td->pktsize, ssize);
+ }
+ return (0);
+}
+
+static void*
+test_pipe_reader(void* args)
+{
+ test_pipe_thread_data_t* td = args;
+ char rcvbuf[td->pktsize];
+ char comparebuf[td->pktsize];
+ ssize_t rsize;
+ int i, d;
+
+ for(i=0; i < td->numpkts; i++) {
+ memset(comparebuf, i, td->pktsize);
+ rsize = recv(td->so, rcvbuf, td->pktsize, MSG_WAITALL);
+ if (rsize < 0) {
+ perror("recv");
+ atf_tc_fail("recv returned < 0");
+ }
+ ATF_CHECK_EQ_MSG(td->pktsize, rsize,
+ "expected %zd=send(...) but got %zd",
+ td->pktsize, rsize);
+ d = memcmp(comparebuf, rcvbuf, td->pktsize);
+ ATF_CHECK_EQ_MSG(0, d,
+ "Received data miscompare on packet %d", i);
+ }
+ return (0);
+}
+
+
+static void
+test_pipe(size_t sndbufsize, size_t rcvbufsize)
+{
+ test_pipe_thread_data_t writer_data, reader_data;
+ pthread_t writer, reader;
+ int sv[2];
+ const size_t pktsize = MIN(sndbufsize, rcvbufsize) / 4;
+ int numpkts;
+
+ /* setup the socket pair */
+ do_socketpair(sv);
+ /* Setup the buffers */
+ ATF_REQUIRE_EQ(0, setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbufsize,
+ sizeof(sndbufsize)));
+ ATF_REQUIRE_EQ(0, setsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &rcvbufsize,
+ sizeof(rcvbufsize)));
+
+ /* Send a total amount of data comfortably greater than the buffers */
+ numpkts = MAX(sndbufsize, rcvbufsize) * 8 / pktsize;
+
+ /* Start the child threads */
+ writer_data.pktsize = pktsize;
+ writer_data.numpkts = numpkts;
+ writer_data.so = sv[0];
+ reader_data.pktsize = pktsize;
+ reader_data.numpkts = numpkts;
+ reader_data.so = sv[1];
+ ATF_REQUIRE_EQ(0, pthread_create(&writer, NULL, test_pipe_writer,
+ (void*)&writer_data));
+ /*
+ * Give the writer time to start writing, and hopefully block, before
+ * starting the reader. This increases the likelihood of the test case
+ * failing due to PR kern/185812
+ */
+ usleep(1000);
+ ATF_REQUIRE_EQ(0, pthread_create(&reader, NULL, test_pipe_reader,
+ (void*)&reader_data));
+
+ /* Join the children */
+ ATF_REQUIRE_EQ(0, pthread_join(writer, NULL));
+ ATF_REQUIRE_EQ(0, pthread_join(reader, NULL));
+ close(sv[0]);
+ close(sv[1]);
+}
+
+
+/*
+ * Test Cases
+ */
+
+/* Create a SEQPACKET socket */
+ATF_TC_WITHOUT_HEAD(create_socket);
+ATF_TC_BODY(create_socket, tc)
+{
+ int s;
+
+ s = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s >= 0);
+ close(s);
+}
+
+/* Create SEQPACKET sockets using socketpair(2) */
+ATF_TC_WITHOUT_HEAD(create_socketpair);
+ATF_TC_BODY(create_socketpair, tc)
+{
+ int sv[2];
+ int s;
+
+ s = socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sv);
+ ATF_CHECK_EQ(0, s);
+ ATF_CHECK(sv[0] >= 0);
+ ATF_CHECK(sv[1] >= 0);
+ ATF_CHECK(sv[0] != sv[1]);
+ close(sv[0]);
+ close(sv[1]);
+}
+
+/* Call listen(2) without first calling bind(2). It should fail */
+ATF_TC_WITHOUT_HEAD(listen_unbound);
+ATF_TC_BODY(listen_unbound, tc)
+{
+ int s, r;
+
+ s = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s > 0);
+ r = listen(s, -1);
+ /* expect listen to fail since we haven't called bind(2) */
+ ATF_CHECK(r != 0);
+ close(s);
+}
+
+/* Bind the socket to a file */
+ATF_TC_WITHOUT_HEAD(bind);
+ATF_TC_BODY(bind, tc)
+{
+ struct sockaddr_un sun;
+ /* ATF's isolation mechanisms will guarantee uniqueness of this file */
+ const char *path = "sock";
+ int s, r;
+
+ s = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s >= 0);
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_LOCAL;
+ sun.sun_len = sizeof(sun);
+ strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
+ r = bind(s, (struct sockaddr *)&sun, sizeof(sun));
+ ATF_CHECK_EQ(0, r);
+ close(s);
+}
+
+/* listen(2) a socket that is already bound(2) should succeed */
+ATF_TC_WITHOUT_HEAD(listen_bound);
+ATF_TC_BODY(listen_bound, tc)
+{
+ struct sockaddr_un sun;
+ /* ATF's isolation mechanisms will guarantee uniqueness of this file */
+ const char *path = "sock";
+ int s, r, l;
+
+ s = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s >= 0);
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_LOCAL;
+ sun.sun_len = sizeof(sun);
+ strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
+ r = bind(s, (struct sockaddr *)&sun, sizeof(sun));
+ l = listen(s, -1);
+ ATF_CHECK_EQ(0, r);
+ ATF_CHECK_EQ(0, l);
+ close(s);
+}
+
+/* connect(2) can make a connection */
+ATF_TC_WITHOUT_HEAD(connect);
+ATF_TC_BODY(connect, tc)
+{
+ struct sockaddr_un sun;
+ /* ATF's isolation mechanisms will guarantee uniqueness of this file */
+ const char *path = "sock";
+ int s, r, err, l, s2;
+
+ s = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s >= 0);
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_LOCAL;
+ sun.sun_len = sizeof(sun);
+ strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
+ r = bind(s, (struct sockaddr *)&sun, sizeof(sun));
+ l = listen(s, -1);
+ ATF_CHECK_EQ(0, r);
+ ATF_CHECK_EQ(0, l);
+
+ /* Create the other socket */
+ s2 = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s2 >= 0);
+ err = connect(s2, (struct sockaddr*)&sun, sizeof(sun));
+ if (err != 0) {
+ perror("connect");
+ atf_tc_fail("connect(2) failed");
+ }
+ close(s);
+ close(s2);
+}
+
+/* accept(2) can receive a connection */
+ATF_TC_WITHOUT_HEAD(accept);
+ATF_TC_BODY(accept, tc)
+{
+ int sv[2];
+
+ mk_pair_of_sockets(sv);
+ close(sv[0]);
+ close(sv[1]);
+}
+
+
+/* Set O_NONBLOCK on the socket */
+ATF_TC_WITHOUT_HEAD(fcntl_nonblock);
+ATF_TC_BODY(fcntl_nonblock, tc)
+{
+ int s;
+
+ s = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s >= 0);
+ if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) {
+ perror("fcntl");
+ atf_tc_fail("fcntl failed");
+ }
+ close(s);
+}
+
+/* Resize the send and receive buffers */
+ATF_TC_WITHOUT_HEAD(resize_buffers);
+ATF_TC_BODY(resize_buffers, tc)
+{
+ int s;
+ int sndbuf = 12345;
+ int rcvbuf = 23456;
+ int xs, xr;
+ socklen_t sl = sizeof(xs);
+
+ s = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s >= 0);
+
+ printf(" Socket Buffer Sizes\n");
+ printf(" | SNDBUF | RCVBUF |\n");
+ ATF_CHECK_EQ(0, getsockopt(s, SOL_SOCKET, SO_SNDBUF, &xs, &sl));
+ ATF_CHECK_EQ(0, getsockopt(s, SOL_SOCKET, SO_RCVBUF, &xr, &sl));
+ printf("Default | %7d | %7d |\n", xs, xr);
+
+ if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) != 0){
+ perror("setsockopt");
+ atf_tc_fail("setsockopt(SO_SNDBUF) failed");
+ }
+ ATF_CHECK_EQ(0, getsockopt(s, SOL_SOCKET, SO_SNDBUF, &xs, &sl));
+ ATF_CHECK_EQ(0, getsockopt(s, SOL_SOCKET, SO_RCVBUF, &xr, &sl));
+ printf("After changing SNDBUF | %7d | %7d |\n", xs, xr);
+
+ if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) != 0){
+ perror("setsockopt");
+ atf_tc_fail("setsockopt(SO_RCVBUF) failed");
+ }
+ ATF_CHECK_EQ(0, getsockopt(s, SOL_SOCKET, SO_SNDBUF, &xs, &sl));
+ ATF_CHECK_EQ(0, getsockopt(s, SOL_SOCKET, SO_RCVBUF, &xr, &sl));
+ printf("After changing RCVBUF | %7d | %7d |\n", xs, xr);
+ close(s);
+}
+
+/*
+ * Resize the send and receive buffers of a connected socketpair
+ * Print some useful debugging info too
+ */
+ATF_TC_WITHOUT_HEAD(resize_connected_buffers);
+ATF_TC_BODY(resize_connected_buffers, tc)
+{
+ int sv[2];
+ int sndbuf = 12345;
+ int rcvbuf = 23456;
+ int err;
+ int ls, lr, rs, rr;
+ socklen_t sl = sizeof(ls);
+
+ /* setup the socket pair */
+ do_socketpair(sv);
+
+ printf(" Socket Buffer Sizes\n");
+ printf(" | Left Socket | Right Socket |\n");
+ printf(" | SNDBUF | RCVBUF | SNDBUF | RCVBUF |\n");
+ ATF_CHECK_EQ(0, getsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &ls, &sl));
+ ATF_CHECK_EQ(0, getsockopt(sv[0], SOL_SOCKET, SO_RCVBUF, &lr, &sl));
+ ATF_CHECK_EQ(0, getsockopt(sv[1], SOL_SOCKET, SO_SNDBUF, &rs, &sl));
+ ATF_CHECK_EQ(0, getsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &rr, &sl));
+ printf("Default | %7d | %7d | %7d | %7d |\n",
+ ls, lr, rs, rr);
+
+ /* Update one side's send buffer */
+ err = setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
+ if (err != 0){
+ perror("setsockopt");
+ atf_tc_fail("setsockopt(SO_SNDBUF) failed");
+ }
+
+ ATF_CHECK_EQ(0, getsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &ls, &sl));
+ ATF_CHECK_EQ(0, getsockopt(sv[0], SOL_SOCKET, SO_RCVBUF, &lr, &sl));
+ ATF_CHECK_EQ(0, getsockopt(sv[1], SOL_SOCKET, SO_SNDBUF, &rs, &sl));
+ ATF_CHECK_EQ(0, getsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &rr, &sl));
+ printf("After changing Left's SNDBUF | %7d | %7d | %7d | %7d |\n",
+ ls, lr, rs, rr);
+
+ /* Update the same side's receive buffer */
+ err = setsockopt(sv[0], SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
+ if (err != 0){
+ perror("setsockopt");
+ atf_tc_fail("setsockopt(SO_RCVBUF) failed");
+ }
+
+ ATF_CHECK_EQ(0, getsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &ls, &sl));
+ ATF_CHECK_EQ(0, getsockopt(sv[0], SOL_SOCKET, SO_RCVBUF, &lr, &sl));
+ ATF_CHECK_EQ(0, getsockopt(sv[1], SOL_SOCKET, SO_SNDBUF, &rs, &sl));
+ ATF_CHECK_EQ(0, getsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &rr, &sl));
+ printf("After changing Left's RCVBUF | %7d | %7d | %7d | %7d |\n",
+ ls, lr, rs, rr);
+ close(sv[0]);
+ close(sv[1]);
+}
+
+
+/* send(2) and recv(2) a single short record */
+ATF_TC_WITHOUT_HEAD(send_recv);
+ATF_TC_BODY(send_recv, tc)
+{
+ int sv[2];
+ const int bufsize = 64;
+ const char *data = "data";
+ char recv_buf[bufsize];
+ ssize_t datalen;
+ ssize_t ssize, rsize;
+
+ /* setup the socket pair */
+ do_socketpair(sv);
+
+ /* send and receive a small packet */
+ datalen = strlen(data) + 1; /* +1 for the null */
+ ssize = send(sv[0], data, datalen, MSG_EOR);
+ if (ssize < 0) {
+ perror("send");
+ atf_tc_fail("send returned < 0");
+ }
+ ATF_CHECK_EQ_MSG(datalen, ssize, "expected %zd=send(...) but got %zd",
+ datalen, ssize);
+
+ rsize = recv(sv[1], recv_buf, bufsize, MSG_WAITALL);
+ ATF_CHECK_EQ(datalen, rsize);
+ close(sv[0]);
+ close(sv[1]);
+}
+
+/* sendto(2) and recvfrom(2) a single short record
+ * According to The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004
+ * Edition, sendto(2) is exactly the same as send(2) on a connection-mode socket
+ *
+ * According to the same spec, not all protocols are required to provide the
+ * source addres in recvfrom(2).
+ */
+ATF_TC_WITHOUT_HEAD(sendto_recvfrom);
+ATF_TC_BODY(sendto_recvfrom, tc)
+{
+#ifdef TEST_SEQ_PACKET_SOURCE_ADDRESS
+ const char* path;
+#endif
+ struct sockaddr_storage from;
+ int sv[2];
+ const int bufsize = 64;
+ const char *data = "data";
+ char recv_buf[bufsize];
+ ssize_t datalen;
+ ssize_t ssize, rsize;
+ socklen_t fromlen;
+
+ /* setup the socket pair */
+#ifdef TEST_SEQ_PACKET_SOURCE_ADDRESS
+ path =
+#endif
+ mk_pair_of_sockets(sv);
+
+ /* send and receive a small packet */
+ datalen = strlen(data) + 1; /* +1 for the null */
+ ssize = sendto(sv[0], data, datalen, MSG_EOR, NULL, 0);
+ if (ssize < 0) {
+ perror("send");
+ atf_tc_fail("send returned < 0");
+ }
+ ATF_CHECK_EQ_MSG(datalen, ssize, "expected %zd=send(...) but got %zd",
+ datalen, ssize);
+
+ fromlen = sizeof(from);
+ rsize = recvfrom(sv[1], recv_buf, bufsize, MSG_WAITALL,
+ (struct sockaddr*)&from, &fromlen);
+ if (ssize < 0) {
+ perror("recvfrom");
+ atf_tc_fail("recvfrom returned < 0");
+ }
+ ATF_CHECK_EQ(datalen, rsize);
+
+#ifdef TEST_SEQ_PACKET_SOURCE_ADDRESS
+ /*
+ * FreeBSD does not currently provide the source address for SEQ_PACKET
+ * AF_UNIX sockets, and POSIX does not require it, so these two checks
+ * are disabled. If FreeBSD gains that feature in the future, then
+ * these checks may be reenabled
+ */
+ ATF_CHECK_EQ(PF_LOCAL, from.ss_family);
+ ATF_CHECK_STREQ(path, ((struct sockaddr_un*)&from)->sun_path);
+#endif
+ close(sv[0]);
+ close(sv[1]);
+}
+
+/*
+ * send(2) and recv(2) a single short record with sockets created the
+ * traditional way, involving bind, listen, connect, and accept
+ */
+ATF_TC_WITHOUT_HEAD(send_recv_with_connect);
+ATF_TC_BODY(send_recv_with_connect, tc)
+{
+ int sv[2];
+ const int bufsize = 64;
+ const char *data = "data";
+ char recv_buf[bufsize];
+ ssize_t datalen;
+ ssize_t ssize, rsize;
+
+ mk_pair_of_sockets(sv);
+
+ /* send and receive a small packet */
+ datalen = strlen(data) + 1; /* +1 for the null */
+ ssize = send(sv[0], data, datalen, MSG_EOR);
+ if (ssize < 0) {
+ perror("send");
+ atf_tc_fail("send returned < 0");
+ }
+ ATF_CHECK_EQ_MSG(datalen, ssize, "expected %zd=send(...) but got %zd",
+ datalen, ssize);
+
+ rsize = recv(sv[1], recv_buf, bufsize, MSG_WAITALL);
+ ATF_CHECK_EQ(datalen, rsize);
+ close(sv[0]);
+ close(sv[1]);
+}
+
+/* send(2) should fail on a shutdown socket */
+ATF_TC_WITHOUT_HEAD(shutdown_send);
+ATF_TC_BODY(shutdown_send, tc)
+{
+ struct sockaddr_un sun;
+ /* ATF's isolation mechanisms will guarantee uniqueness of this file */
+ const char *path = "sock";
+ const char *data = "data";
+ ssize_t ssize;
+ int s, err, s2;
+
+ s = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s >= 0);
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_LOCAL;
+ sun.sun_len = sizeof(sun);
+ strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
+ err = bind(s, (struct sockaddr *)&sun, sizeof(sun));
+ err = listen(s, -1);
+ ATF_CHECK_EQ(0, err);
+
+ /* Create the other socket */
+ s2 = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s2 >= 0);
+ err = connect(s2, (struct sockaddr*)&sun, sizeof(sun));
+ if (err != 0) {
+ perror("connect");
+ atf_tc_fail("connect(2) failed");
+ }
+
+ ATF_CHECK_EQ(0, shutdown(s2, SHUT_RDWR));
+ /* USE MSG_NOSIGNAL so we don't get SIGPIPE */
+ ssize = send(s2, data, sizeof(data), MSG_EOR | MSG_NOSIGNAL);
+ ATF_CHECK_EQ(EPIPE, errno);
+ ATF_CHECK_EQ(-1, ssize);
+ close(s);
+ close(s2);
+}
+
+/* send(2) should cause SIGPIPE on a shutdown socket */
+ATF_TC_WITHOUT_HEAD(shutdown_send_sigpipe);
+ATF_TC_BODY(shutdown_send_sigpipe, tc)
+{
+ struct sockaddr_un sun;
+ /* ATF's isolation mechanisms will guarantee uniqueness of this file */
+ const char *path = "sock";
+ const char *data = "data";
+ int s, err, s2;
+
+ s = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s >= 0);
+
+ bzero(&sun, sizeof(sun));
+ sun.sun_family = AF_LOCAL;
+ sun.sun_len = sizeof(sun);
+ strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
+ err = bind(s, (struct sockaddr *)&sun, sizeof(sun));
+ err = listen(s, -1);
+ ATF_CHECK_EQ(0, err);
+
+ /* Create the other socket */
+ s2 = socket(PF_LOCAL, SOCK_SEQPACKET, 0);
+ ATF_REQUIRE(s2 >= 0);
+ err = connect(s2, (struct sockaddr*)&sun, sizeof(sun));
+ if (err != 0) {
+ perror("connect");
+ atf_tc_fail("connect(2) failed");
+ }
+
+ ATF_CHECK_EQ(0, shutdown(s2, SHUT_RDWR));
+ ATF_REQUIRE(SIG_ERR != signal(SIGPIPE, shutdown_send_sigpipe_handler));
+ (void)send(s2, data, sizeof(data), MSG_EOR);
+ ATF_CHECK_EQ(1, got_sigpipe);
+ close(s);
+ close(s2);
+}
+
+/* nonblocking send(2) and recv(2) a single short record */
+ATF_TC_WITHOUT_HEAD(send_recv_nonblocking);
+ATF_TC_BODY(send_recv_nonblocking, tc)
+{
+ int sv[2];
+ const int bufsize = 64;
+ const char *data = "data";
+ char recv_buf[bufsize];
+ ssize_t datalen;
+ ssize_t ssize, rsize;
+
+ /* setup the socket pair */
+ do_socketpair_nonblocking(sv);
+
+ /* Verify that there is nothing to receive */
+ rsize = recv(sv[1], recv_buf, bufsize, MSG_WAITALL);
+ ATF_CHECK_EQ(EAGAIN, errno);
+ ATF_CHECK_EQ(-1, rsize);
+
+ /* send and receive a small packet */
+ datalen = strlen(data) + 1; /* +1 for the null */
+ ssize = send(sv[0], data, datalen, MSG_EOR);
+ if (ssize < 0) {
+ perror("send");
+ atf_tc_fail("send returned < 0");
+ }
+ ATF_CHECK_EQ_MSG(datalen, ssize, "expected %zd=send(...) but got %zd",
+ datalen, ssize);
+
+ rsize = recv(sv[1], recv_buf, bufsize, MSG_WAITALL);
+ ATF_CHECK_EQ(datalen, rsize);
+ close(sv[0]);
+ close(sv[1]);
+}
+
+/*
+ * We should get EMSGSIZE if we try to send a message larger than the socket
+ * buffer, with blocking sockets
+ */
+ATF_TC_WITHOUT_HEAD(emsgsize);
+ATF_TC_BODY(emsgsize, tc)
+{
+ int sv[2];
+ const size_t sndbufsize = 8192;
+ const size_t rcvbufsize = 8192;
+ const size_t pktsize = (sndbufsize + rcvbufsize) * 2;
+ char sndbuf[pktsize];
+ ssize_t ssize;
+
+ /* setup the socket pair */
+ do_socketpair(sv);
+ /* Setup the buffers */
+ ATF_REQUIRE_EQ(0, setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbufsize,
+ sizeof(sndbufsize)));
+ ATF_REQUIRE_EQ(0, setsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &rcvbufsize,
+ sizeof(rcvbufsize)));
+
+ ssize = send(sv[0], sndbuf, pktsize, MSG_EOR);
+ ATF_CHECK_EQ(EMSGSIZE, errno);
+ ATF_CHECK_EQ(-1, ssize);
+ close(sv[0]);
+ close(sv[1]);
+}
+
+/*
+ * We should get EMSGSIZE if we try to send a message larger than the socket
+ * buffer, with nonblocking sockets
+ */
+ATF_TC_WITHOUT_HEAD(emsgsize_nonblocking);
+ATF_TC_BODY(emsgsize_nonblocking, tc)
+{
+ int sv[2];
+ const size_t sndbufsize = 8192;
+ const size_t rcvbufsize = 8192;
+ const size_t pktsize = (sndbufsize + rcvbufsize) * 2;
+ char sndbuf[pktsize];
+ ssize_t ssize;
+
+ /* setup the socket pair */
+ do_socketpair_nonblocking(sv);
+ /* Setup the buffers */
+ ATF_REQUIRE_EQ(0, setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbufsize,
+ sizeof(sndbufsize)));
+ ATF_REQUIRE_EQ(0, setsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &rcvbufsize,
+ sizeof(rcvbufsize)));
+
+ ssize = send(sv[0], sndbuf, pktsize, MSG_EOR);
+ ATF_CHECK_EQ(EMSGSIZE, errno);
+ ATF_CHECK_EQ(-1, ssize);
+ close(sv[0]);
+ close(sv[1]);
+}
+
+
+/*
+ * We should get EAGAIN if we try to send a message larger than the socket
+ * buffer, with nonblocking sockets. Test with several different sockbuf sizes
+ */
+ATF_TC_WITHOUT_HEAD(eagain_8k_8k);
+ATF_TC_BODY(eagain_8k_8k, tc)
+{
+ test_eagain(8192, 8192);
+}
+ATF_TC_WITHOUT_HEAD(eagain_8k_128k);
+ATF_TC_BODY(eagain_8k_128k, tc)
+{
+ test_eagain(8192, 131072);
+}
+ATF_TC_WITHOUT_HEAD(eagain_128k_8k);
+ATF_TC_BODY(eagain_128k_8k, tc)
+{
+ test_eagain(131072, 8192);
+}
+ATF_TC_WITHOUT_HEAD(eagain_128k_128k);
+ATF_TC_BODY(eagain_128k_128k, tc)
+{
+ test_eagain(131072, 131072);
+}
+
+
+/*
+ * nonblocking send(2) and recv(2) of several records, which should collectively
+ * fill up the send buffer but not the receive buffer
+ */
+ATF_TC_WITHOUT_HEAD(rcvbuf_oversized);
+ATF_TC_BODY(rcvbuf_oversized, tc)
+{
+ int i;
+ int sv[2];
+ const ssize_t pktsize = 1024;
+ const size_t sndbufsize = 8192;
+ const size_t rcvbufsize = 131072;
+ const size_t geometric_mean_bufsize = 32768;
+ const int numpkts = geometric_mean_bufsize / pktsize;
+ char sndbuf[pktsize];
+ char recv_buf[pktsize];
+ ssize_t ssize, rsize;
+
+ /* setup the socket pair */
+ do_socketpair_nonblocking(sv);
+ ATF_REQUIRE_EQ(0, setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbufsize,
+ sizeof(sndbufsize)));
+ ATF_REQUIRE_EQ(0, setsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &rcvbufsize,
+ sizeof(rcvbufsize)));
+
+ /*
+ * Send and receive packets that are collectively greater than the send
+ * buffer, but less than the receive buffer
+ */
+ for (i=0; i < numpkts; i++) {
+ /* Fill the buffer */
+ memset(sndbuf, i, pktsize);
+
+ /* send the packet */
+ ssize = send(sv[0], sndbuf, pktsize, MSG_EOR);
+ if (ssize < 0) {
+ perror("send");
+ atf_tc_fail("send returned < 0");
+ }
+ ATF_CHECK_EQ_MSG(pktsize, ssize,
+ "expected %zd=send(...) but got %zd", pktsize, ssize);
+
+ /* Receive it */
+
+ rsize = recv(sv[1], recv_buf, pktsize, MSG_WAITALL);
+ if (rsize < 0) {
+ perror("recv");
+ atf_tc_fail("recv returned < 0");
+ }
+ ATF_CHECK_EQ_MSG(pktsize, rsize,
+ "expected %zd=send(...) but got %zd", pktsize, rsize);
+
+ /* Verify the contents */
+ ATF_CHECK_EQ_MSG(0, memcmp(sndbuf, recv_buf, pktsize),
+ "Received data miscompare");
+ }
+
+ /* Trying to receive again should return EAGAIN */
+ rsize = recv(sv[1], recv_buf, pktsize, MSG_WAITALL);
+ ATF_CHECK_EQ(EAGAIN, errno);
+ ATF_CHECK_EQ(-1, rsize);
+ close(sv[0]);
+ close(sv[1]);
+}
+
+/*
+ * Simulate the behavior of a blocking pipe. The sender will send until his
+ * buffer fills up, then we'll simulate a scheduler switch that will allow the
+ * receiver to read until his buffer empties. Repeat the process until the
+ * transfer is complete.
+ * Repeat the test with multiple send and receive buffer sizes
+ */
+ATF_TC_WITHOUT_HEAD(pipe_simulator_8k_8k);
+ATF_TC_BODY(pipe_simulator_8k_8k, tc)
+{
+ test_pipe_simulator(8192, 8192);
+}
+
+ATF_TC_WITHOUT_HEAD(pipe_simulator_8k_128k);
+ATF_TC_BODY(pipe_simulator_8k_128k, tc)
+{
+ test_pipe_simulator(8192, 131072);
+}
+
+ATF_TC_WITHOUT_HEAD(pipe_simulator_128k_8k);
+ATF_TC_BODY(pipe_simulator_128k_8k, tc)
+{
+ test_pipe_simulator(131072, 8192);
+}
+
+ATF_TC_WITHOUT_HEAD(pipe_simulator_128k_128k);
+ATF_TC_BODY(pipe_simulator_128k_128k, tc)
+{
+ test_pipe_simulator(131072, 131072);
+}
+
+/*
+ * Test blocking I/O by passing data between two threads. The total amount of
+ * data will be >> buffer size to force blocking. Repeat the test with multiple
+ * send and receive buffer sizes
+ */
+ATF_TC_WITHOUT_HEAD(pipe_8k_8k);
+ATF_TC_BODY(pipe_8k_8k, tc)
+{
+ test_pipe(8192, 8192);
+}
+
+ATF_TC_WITHOUT_HEAD(pipe_8k_128k);
+ATF_TC_BODY(pipe_8k_128k, tc)
+{
+ test_pipe(8192, 131072);
+}
+
+ATF_TC_WITHOUT_HEAD(pipe_128k_8k);
+ATF_TC_BODY(pipe_128k_8k, tc)
+{
+ test_pipe(131072, 8192);
+}
+
+ATF_TC_WITHOUT_HEAD(pipe_128k_128k);
+ATF_TC_BODY(pipe_128k_128k, tc)
+{
+ test_pipe(131072, 131072);
+}
+
+
+/*
+ * Test single-packet I/O with and without blocking, with symmetric buffers of
+ * various sizes
+ */
+ATF_TC_WITHOUT_HEAD(sendrecv_8k);
+ATF_TC_BODY(sendrecv_8k, tc)
+{
+ test_sendrecv_symmetric_buffers(8 * 1024, true);
+}
+ATF_TC_WITHOUT_HEAD(sendrecv_16k);
+ATF_TC_BODY(sendrecv_16k, tc)
+{
+ test_sendrecv_symmetric_buffers(16 * 1024, true);
+}
+ATF_TC_WITHOUT_HEAD(sendrecv_32k);
+ATF_TC_BODY(sendrecv_32k, tc)
+{
+ test_sendrecv_symmetric_buffers(32 * 1024, true);
+}
+ATF_TC_WITHOUT_HEAD(sendrecv_64k);
+ATF_TC_BODY(sendrecv_64k, tc)
+{
+ test_sendrecv_symmetric_buffers(64 * 1024, true);
+}
+ATF_TC_WITHOUT_HEAD(sendrecv_128k);
+ATF_TC_BODY(sendrecv_128k, tc)
+{
+ test_sendrecv_symmetric_buffers(128 * 1024, true);
+}
+ATF_TC_WITHOUT_HEAD(sendrecv_8k_nonblocking);
+ATF_TC_BODY(sendrecv_8k_nonblocking, tc)
+{
+ test_sendrecv_symmetric_buffers(8 * 1024, false);
+}
+ATF_TC_WITHOUT_HEAD(sendrecv_16k_nonblocking);
+ATF_TC_BODY(sendrecv_16k_nonblocking, tc)
+{
+ test_sendrecv_symmetric_buffers(16 * 1024, false);
+}
+ATF_TC_WITHOUT_HEAD(sendrecv_32k_nonblocking);
+ATF_TC_BODY(sendrecv_32k_nonblocking, tc)
+{
+ test_sendrecv_symmetric_buffers(32 * 1024, false);
+}
+ATF_TC_WITHOUT_HEAD(sendrecv_64k_nonblocking);
+ATF_TC_BODY(sendrecv_64k_nonblocking, tc)
+{
+ test_sendrecv_symmetric_buffers(64 * 1024, false);
+}
+ATF_TC_WITHOUT_HEAD(sendrecv_128k_nonblocking);
+ATF_TC_BODY(sendrecv_128k_nonblocking, tc)
+{
+ test_sendrecv_symmetric_buffers(128 * 1024, false);
+}
+
+
+/*
+ * Main.
+ */
+
+ATF_TP_ADD_TCS(tp)
+{
+ /* Basic creation and connection tests */
+ ATF_TP_ADD_TC(tp, create_socket);
+ ATF_TP_ADD_TC(tp, create_socketpair);
+ ATF_TP_ADD_TC(tp, listen_unbound);
+ ATF_TP_ADD_TC(tp, bind);
+ ATF_TP_ADD_TC(tp, listen_bound);
+ ATF_TP_ADD_TC(tp, connect);
+ ATF_TP_ADD_TC(tp, accept);
+ ATF_TP_ADD_TC(tp, fcntl_nonblock);
+ ATF_TP_ADD_TC(tp, resize_buffers);
+ ATF_TP_ADD_TC(tp, resize_connected_buffers);
+
+ /* Unthreaded I/O tests */
+ ATF_TP_ADD_TC(tp, send_recv);
+ ATF_TP_ADD_TC(tp, send_recv_nonblocking);
+ ATF_TP_ADD_TC(tp, send_recv_with_connect);
+ ATF_TP_ADD_TC(tp, sendto_recvfrom);
+ ATF_TP_ADD_TC(tp, shutdown_send);
+ ATF_TP_ADD_TC(tp, shutdown_send_sigpipe);
+ ATF_TP_ADD_TC(tp, emsgsize);
+ ATF_TP_ADD_TC(tp, emsgsize_nonblocking);
+ ATF_TP_ADD_TC(tp, eagain_8k_8k);
+ ATF_TP_ADD_TC(tp, eagain_8k_128k);
+ ATF_TP_ADD_TC(tp, eagain_128k_8k);
+ ATF_TP_ADD_TC(tp, eagain_128k_128k);
+ ATF_TP_ADD_TC(tp, sendrecv_8k);
+ ATF_TP_ADD_TC(tp, sendrecv_16k);
+ ATF_TP_ADD_TC(tp, sendrecv_32k);
+ ATF_TP_ADD_TC(tp, sendrecv_64k);
+ ATF_TP_ADD_TC(tp, sendrecv_128k);
+ ATF_TP_ADD_TC(tp, sendrecv_8k_nonblocking);
+ ATF_TP_ADD_TC(tp, sendrecv_16k_nonblocking);
+ ATF_TP_ADD_TC(tp, sendrecv_32k_nonblocking);
+ ATF_TP_ADD_TC(tp, sendrecv_64k_nonblocking);
+ ATF_TP_ADD_TC(tp, sendrecv_128k_nonblocking);
+ ATF_TP_ADD_TC(tp, rcvbuf_oversized);
+ ATF_TP_ADD_TC(tp, pipe_simulator_8k_8k);
+ ATF_TP_ADD_TC(tp, pipe_simulator_8k_128k);
+ ATF_TP_ADD_TC(tp, pipe_simulator_128k_8k);
+ ATF_TP_ADD_TC(tp, pipe_simulator_128k_128k);
+
+ /* Threaded I/O tests with blocking sockets */
+ ATF_TP_ADD_TC(tp, pipe_8k_8k);
+ ATF_TP_ADD_TC(tp, pipe_8k_128k);
+ ATF_TP_ADD_TC(tp, pipe_128k_8k);
+ ATF_TP_ADD_TC(tp, pipe_128k_128k);
+
+ return atf_no_error();
+}
diff --git a/tests/sys/kern/waitpid_nohang.c b/tests/sys/kern/waitpid_nohang.c
new file mode 100644
index 000000000000..116ccf335d38
--- /dev/null
+++ b/tests/sys/kern/waitpid_nohang.c
@@ -0,0 +1,70 @@
+/*-
+ * Copyright (c) 2016 Jilles Tjoelker
+ * 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 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 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/wait.h>
+
+#include <atf-c.h>
+#include <signal.h>
+#include <unistd.h>
+
+ATF_TC_WITHOUT_HEAD(waitpid_nohang);
+ATF_TC_BODY(waitpid_nohang, tc)
+{
+ pid_t child, pid;
+ int status, r;
+
+ child = fork();
+ ATF_REQUIRE(child != -1);
+ if (child == 0) {
+ sleep(10);
+ _exit(1);
+ }
+
+ status = 42;
+ pid = waitpid(child, &status, WNOHANG);
+ ATF_REQUIRE(pid == 0);
+ ATF_CHECK(status == 42);
+
+ r = kill(child, SIGTERM);
+ ATF_REQUIRE(r == 0);
+ r = waitid(P_PID, child, NULL, WEXITED | WNOWAIT);
+ ATF_REQUIRE(r == 0);
+
+ status = -1;
+ pid = waitpid(child, &status, WNOHANG);
+ ATF_REQUIRE(pid == child);
+ ATF_CHECK(WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, waitpid_nohang);
+ return (atf_no_error());
+}
diff --git a/tests/sys/kqueue/Makefile b/tests/sys/kqueue/Makefile
new file mode 100644
index 000000000000..40f892d36fbd
--- /dev/null
+++ b/tests/sys/kqueue/Makefile
@@ -0,0 +1,20 @@
+# $FreeBSD$
+
+TESTSRC= ${SRCTOP}/contrib/netbsd-tests/kernel/kqueue
+
+TESTSDIR= ${TESTSBASE}/sys/kqueue
+BINDIR= ${TESTSDIR}
+
+NETBSD_ATF_TESTS_C= proc1_test
+# XXX: fails `ke.fflags & NOTE_TRACKERR` invariant
+#NETBSD_ATF_TESTS_C+= proc2_test
+NETBSD_ATF_TESTS_C+= proc3_test
+NETBSD_ATF_TESTS_C+= sig_test
+NETBSD_ATF_TESTS_C+= vnode_test
+
+WARNS?= 3
+
+TESTS_SUBDIRS+= libkqueue
+
+.include <netbsd-tests.test.mk>
+.include <bsd.test.mk>
diff --git a/tests/sys/kqueue/libkqueue/Makefile b/tests/sys/kqueue/libkqueue/Makefile
new file mode 100644
index 000000000000..94d198bf9900
--- /dev/null
+++ b/tests/sys/kqueue/libkqueue/Makefile
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/kqueue/libkqueue
+BINDIR= ${TESTSDIR}
+
+# libkqueue and test suite by Mark Heily <mark@heily.com>
+TAP_TESTS_SH= kqueue_test
+
+PROGS= kqtest
+
+SRCS.kqtest= \
+ main.c \
+ read.c \
+ timer.c \
+ vnode.c \
+ proc.c \
+ signal.c \
+ user.c
+WARNS?= 2
+
+.include <bsd.test.mk>
diff --git a/tests/sys/kqueue/libkqueue/Makefile.depend b/tests/sys/kqueue/libkqueue/Makefile.depend
new file mode 100644
index 000000000000..3646e2e2b1af
--- /dev/null
+++ b/tests/sys/kqueue/libkqueue/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/kqueue/libkqueue/common.h b/tests/sys/kqueue/libkqueue/common.h
new file mode 100644
index 000000000000..89a402986021
--- /dev/null
+++ b/tests/sys/kqueue/libkqueue/common.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _COMMON_H
+#define _COMMON_H
+
+
+#if HAVE_ERR_H
+# include <err.h>
+#else
+# define err(rc,msg,...) do { perror(msg); exit(rc); } while (0)
+# define errx(rc,msg,...) do { puts(msg); exit(rc); } while (0)
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <sys/event.h>
+
+#include "config.h"
+
+extern char *cur_test_id;
+int vnode_fd;
+
+extern const char * kevent_to_str(struct kevent *);
+struct kevent * kevent_get(int);
+struct kevent * kevent_get_timeout(int, int);
+
+
+void kevent_cmp(struct kevent *, struct kevent *);
+
+void
+kevent_add(int kqfd, struct kevent *kev,
+ uintptr_t ident,
+ short filter,
+ u_short flags,
+ u_int fflags,
+ intptr_t data,
+ void *udata);
+
+/* DEPRECATED: */
+#define KEV_CMP(kev,_ident,_filter,_flags) do { \
+ if (kev.ident != (_ident) || \
+ kev.filter != (_filter) || \
+ kev.flags != (_flags)) \
+ err(1, "kevent mismatch: got [%d,%d,%d] but expecting [%d,%d,%d]", \
+ (int)_ident, (int)_filter, (int)_flags,\
+ (int)kev.ident, kev.filter, kev.flags);\
+} while (0);
+
+/* Checks if any events are pending, which is an error. */
+extern void test_no_kevents(void);
+
+extern void test_begin(const char *);
+extern void success(void);
+
+#endif /* _COMMON_H */
diff --git a/tests/sys/kqueue/libkqueue/config.h b/tests/sys/kqueue/libkqueue/config.h
new file mode 100644
index 000000000000..a204092a2ab2
--- /dev/null
+++ b/tests/sys/kqueue/libkqueue/config.h
@@ -0,0 +1,13 @@
+/* $FreeBSD$ */
+
+#define HAVE_ERR_H 1
+#define HAVE_SYS_EVENT_H 1
+#define HAVE_EV_DISPATCH 1
+#define HAVE_EV_RECEIPT 1
+#undef HAVE_NOTE_TRUNCATE
+#define HAVE_EVFILT_TIMER 1
+#define HAVE_EVFILT_USER 1
+#define PROGRAM "libkqueue-test"
+#define VERSION "0.1"
+#define TARGET "freebsd"
+#define CFLAGS "-g -O0 -Wall -Werror"
diff --git a/tests/sys/kqueue/libkqueue/kqueue_test.sh b/tests/sys/kqueue/libkqueue/kqueue_test.sh
new file mode 100755
index 000000000000..3185d826c7dd
--- /dev/null
+++ b/tests/sys/kqueue/libkqueue/kqueue_test.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+# $FreeBSD$
+
+i=1
+"$(dirname $0)/kqtest" | while read line; do
+ echo $line | grep -q passed
+ if [ $? -eq 0 ]; then
+ echo "ok - $i $line"
+ : $(( i += 1 ))
+ fi
+
+ echo $line | grep -q 'tests completed'
+ if [ $? -eq 0 ]; then
+ echo -n "1.."
+ echo $line | cut -d' ' -f3
+ fi
+done
diff --git a/tests/sys/kqueue/libkqueue/main.c b/tests/sys/kqueue/libkqueue/main.c
new file mode 100644
index 000000000000..553478a514f5
--- /dev/null
+++ b/tests/sys/kqueue/libkqueue/main.c
@@ -0,0 +1,329 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+
+#include "config.h"
+#include "common.h"
+
+int testnum = 1;
+char *cur_test_id = NULL;
+int kqfd;
+
+extern void test_evfilt_read();
+extern void test_evfilt_signal();
+extern void test_evfilt_vnode();
+extern void test_evfilt_timer();
+extern void test_evfilt_proc();
+#if HAVE_EVFILT_USER
+extern void test_evfilt_user();
+#endif
+
+/* Checks if any events are pending, which is an error. */
+void
+test_no_kevents(void)
+{
+ int nfds;
+ struct timespec timeo;
+ struct kevent kev;
+
+ puts("confirming that there are no events pending");
+ memset(&timeo, 0, sizeof(timeo));
+ nfds = kevent(kqfd, NULL, 0, &kev, 1, &timeo);
+ if (nfds != 0) {
+ puts("\nUnexpected event:");
+ puts(kevent_to_str(&kev));
+ errx(1, "%d event(s) pending, but none expected:", nfds);
+ }
+}
+
+/* Retrieve a single kevent */
+struct kevent *
+kevent_get(int kqfd)
+{
+ int nfds;
+ struct kevent *kev;
+
+ if ((kev = calloc(1, sizeof(*kev))) == NULL)
+ err(1, "out of memory");
+
+ nfds = kevent(kqfd, NULL, 0, kev, 1, NULL);
+ if (nfds < 1)
+ err(1, "kevent(2)");
+
+ return (kev);
+}
+
+/* Retrieve a single kevent, specifying a maximum time to wait for it. */
+struct kevent *
+kevent_get_timeout(int kqfd, int seconds)
+{
+ int nfds;
+ struct kevent *kev;
+ struct timespec timeout = {seconds, 0};
+
+ if ((kev = calloc(1, sizeof(*kev))) == NULL)
+ err(1, "out of memory");
+
+ nfds = kevent(kqfd, NULL, 0, kev, 1, &timeout);
+ if (nfds < 0) {
+ err(1, "kevent(2)");
+ } else if (nfds == 0) {
+ free(kev);
+ kev = NULL;
+ }
+
+ return (kev);
+}
+
+char *
+kevent_fflags_dump(struct kevent *kev)
+{
+ char *buf;
+
+#define KEVFFL_DUMP(attrib) \
+ if (kev->fflags & attrib) \
+ strncat(buf, #attrib" ", 64);
+
+ if ((buf = calloc(1, 1024)) == NULL)
+ abort();
+
+ /* Not every filter has meaningful fflags */
+ if (kev->filter == EVFILT_PROC) {
+ snprintf(buf, 1024, "fflags = %x (", kev->fflags);
+ KEVFFL_DUMP(NOTE_EXIT);
+ KEVFFL_DUMP(NOTE_FORK);
+ KEVFFL_DUMP(NOTE_EXEC);
+ KEVFFL_DUMP(NOTE_CHILD);
+ KEVFFL_DUMP(NOTE_TRACKERR);
+ KEVFFL_DUMP(NOTE_TRACK);
+ buf[strlen(buf) - 1] = ')';
+ } else if (kev->filter == EVFILT_PROCDESC) {
+ snprintf(buf, 1024, "fflags = %x (", kev->fflags);
+ KEVFFL_DUMP(NOTE_EXIT);
+ KEVFFL_DUMP(NOTE_FORK);
+ KEVFFL_DUMP(NOTE_EXEC);
+ buf[strlen(buf) - 1] = ')';
+ } else if (kev->filter == EVFILT_VNODE) {
+ snprintf(buf, 1024, "fflags = %x (", kev->fflags);
+ KEVFFL_DUMP(NOTE_DELETE);
+ KEVFFL_DUMP(NOTE_WRITE);
+ KEVFFL_DUMP(NOTE_EXTEND);
+#if HAVE_NOTE_TRUNCATE
+ KEVFFL_DUMP(NOTE_TRUNCATE);
+#endif
+ KEVFFL_DUMP(NOTE_ATTRIB);
+ KEVFFL_DUMP(NOTE_LINK);
+ KEVFFL_DUMP(NOTE_RENAME);
+#if HAVE_NOTE_REVOKE
+ KEVFFL_DUMP(NOTE_REVOKE);
+#endif
+ buf[strlen(buf) - 1] = ')';
+ } else {
+ snprintf(buf, 1024, "fflags = %x", kev->fflags);
+ }
+
+ return (buf);
+}
+
+char *
+kevent_flags_dump(struct kevent *kev)
+{
+ char *buf;
+
+#define KEVFL_DUMP(attrib) \
+ if (kev->flags & attrib) \
+ strncat(buf, #attrib" ", 64);
+
+ if ((buf = calloc(1, 1024)) == NULL)
+ abort();
+
+ snprintf(buf, 1024, "flags = %d (", kev->flags);
+ KEVFL_DUMP(EV_ADD);
+ KEVFL_DUMP(EV_ENABLE);
+ KEVFL_DUMP(EV_DISABLE);
+ KEVFL_DUMP(EV_DELETE);
+ KEVFL_DUMP(EV_ONESHOT);
+ KEVFL_DUMP(EV_CLEAR);
+ KEVFL_DUMP(EV_EOF);
+ KEVFL_DUMP(EV_ERROR);
+#if HAVE_EV_DISPATCH
+ KEVFL_DUMP(EV_DISPATCH);
+#endif
+#if HAVE_EV_RECEIPT
+ KEVFL_DUMP(EV_RECEIPT);
+#endif
+ buf[strlen(buf) - 1] = ')';
+
+ return (buf);
+}
+
+/* Copied from ../kevent.c kevent_dump() and improved */
+const char *
+kevent_to_str(struct kevent *kev)
+{
+ char buf[512];
+
+ snprintf(&buf[0], sizeof(buf),
+ "[ident=%d, filter=%d, %s, %s, data=%d, udata=%p]",
+ (u_int) kev->ident,
+ kev->filter,
+ kevent_flags_dump(kev),
+ kevent_fflags_dump(kev),
+ (int) kev->data,
+ kev->udata);
+
+ return (strdup(buf));
+}
+
+void
+kevent_add(int kqfd, struct kevent *kev,
+ uintptr_t ident,
+ short filter,
+ u_short flags,
+ u_int fflags,
+ intptr_t data,
+ void *udata)
+{
+ EV_SET(kev, ident, filter, flags, fflags, data, NULL);
+ if (kevent(kqfd, kev, 1, NULL, 0, NULL) < 0) {
+ printf("Unable to add the following kevent:\n%s\n",
+ kevent_to_str(kev));
+ err(1, "kevent(): %s", strerror(errno));
+ }
+}
+
+void
+kevent_cmp(struct kevent *k1, struct kevent *k2)
+{
+/* XXX-
+ Workaround for inconsistent implementation of kevent(2)
+ */
+#ifdef __FreeBSD__
+ if (k1->flags & EV_ADD)
+ k2->flags |= EV_ADD;
+#endif
+ if (memcmp(k1, k2, sizeof(*k1)) != 0) {
+ printf("kevent_cmp: mismatch:\n %s !=\n %s\n",
+ kevent_to_str(k1), kevent_to_str(k2));
+ abort();
+ }
+}
+
+void
+test_begin(const char *func)
+{
+ if (cur_test_id)
+ free(cur_test_id);
+ cur_test_id = strdup(func);
+ if (!cur_test_id)
+ err(1, "strdup failed");
+
+ printf("\n\nTest %d: %s\n", testnum++, func);
+}
+
+void
+success(void)
+{
+ printf("%-70s %s\n", cur_test_id, "passed");
+ free(cur_test_id);
+ cur_test_id = NULL;
+}
+
+void
+test_kqueue(void)
+{
+ test_begin("kqueue()");
+ if ((kqfd = kqueue()) < 0)
+ err(1, "kqueue()");
+ test_no_kevents();
+ success();
+}
+
+void
+test_kqueue_close(void)
+{
+ test_begin("close(kq)");
+ if (close(kqfd) < 0)
+ err(1, "close()");
+ success();
+}
+
+int
+main(int argc, char **argv)
+{
+ int test_proc = 1;
+ int test_socket = 1;
+ int test_signal = 1;
+ int test_vnode = 1;
+ int test_timer = 1;
+#ifdef __FreeBSD__
+ int test_user = 1;
+#else
+ /* XXX-FIXME temporary */
+ int test_user = 0;
+#endif
+
+ while (argc) {
+ if (strcmp(argv[0], "--no-proc") == 0)
+ test_proc = 0;
+ if (strcmp(argv[0], "--no-socket") == 0)
+ test_socket = 0;
+ if (strcmp(argv[0], "--no-timer") == 0)
+ test_timer = 0;
+ if (strcmp(argv[0], "--no-signal") == 0)
+ test_signal = 0;
+ if (strcmp(argv[0], "--no-vnode") == 0)
+ test_vnode = 0;
+ if (strcmp(argv[0], "--no-user") == 0)
+ test_user = 0;
+ argv++;
+ argc--;
+ }
+
+ /*
+ * Some tests fork. If output is fully buffered,
+ * the children inherit some buffered data and flush
+ * it when they exit, causing some data to be printed twice.
+ * Use line buffering to avoid this problem.
+ */
+ setlinebuf(stdout);
+ setlinebuf(stderr);
+
+ test_kqueue();
+ test_kqueue_close();
+
+ if (test_socket)
+ test_evfilt_read();
+ if (test_signal)
+ test_evfilt_signal();
+ if (test_vnode)
+ test_evfilt_vnode();
+#if HAVE_EVFILT_USER
+ if (test_user)
+ test_evfilt_user();
+#endif
+ if (test_timer)
+ test_evfilt_timer();
+ if (test_proc)
+ test_evfilt_proc();
+
+ printf("\n---\n"
+ "+OK All %d tests completed.\n", testnum - 1);
+ return (0);
+}
diff --git a/tests/sys/kqueue/libkqueue/proc.c b/tests/sys/kqueue/libkqueue/proc.c
new file mode 100644
index 000000000000..79b8d35f4f25
--- /dev/null
+++ b/tests/sys/kqueue/libkqueue/proc.c
@@ -0,0 +1,423 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/stat.h>
+
+#include <err.h>
+
+#include "config.h"
+#include "common.h"
+
+static int sigusr1_caught = 0;
+
+int kqfd;
+
+static void
+sig_handler(int signum)
+{
+ sigusr1_caught = 1;
+}
+
+static void
+add_and_delete(void)
+{
+ struct kevent kev;
+ pid_t pid;
+
+ /* Create a child that waits to be killed and then exits */
+ pid = fork();
+ if (pid == 0) {
+ struct stat s;
+ if (fstat(kqfd, &s) != -1)
+ errx(1, "kqueue inherited across fork! (%s() at %s:%d)",
+ __func__, __FILE__, __LINE__);
+
+ pause();
+ exit(2);
+ }
+ printf(" -- child created (pid %d)\n", (int) pid);
+
+ test_begin("kevent(EVFILT_PROC, EV_ADD)");
+
+ test_no_kevents();
+ kevent_add(kqfd, &kev, pid, EVFILT_PROC, EV_ADD, 0, 0, NULL);
+ test_no_kevents();
+
+ success();
+
+ test_begin("kevent(EVFILT_PROC, EV_DELETE)");
+
+ sleep(1);
+ test_no_kevents();
+ kevent_add(kqfd, &kev, pid, EVFILT_PROC, EV_DELETE, 0, 0, NULL);
+ if (kill(pid, SIGKILL) < 0)
+ err(1, "kill");
+ sleep(1);
+ test_no_kevents();
+
+ success();
+
+}
+
+static void
+proc_track(int sleep_time)
+{
+ char test_id[64];
+ struct kevent kev;
+ pid_t pid;
+ int pipe_fd[2];
+ ssize_t result;
+
+ snprintf(test_id, sizeof(test_id),
+ "kevent(EVFILT_PROC, NOTE_TRACK); sleep %d", sleep_time);
+ test_begin(test_id);
+ test_no_kevents();
+
+ if (pipe(pipe_fd)) {
+ err(1, "pipe (parent) failed! (%s() at %s:%d)",
+ __func__, __FILE__, __LINE__);
+ }
+
+ /* Create a child to track. */
+ pid = fork();
+ if (pid == 0) { /* Child */
+ pid_t grandchild = -1;
+
+ /*
+ * Give the parent a chance to start tracking us.
+ */
+ result = read(pipe_fd[1], test_id, 1);
+ if (result != 1) {
+ err(1, "read from pipe in child failed! (ret %zd) (%s() at %s:%d)",
+ result, __func__, __FILE__, __LINE__);
+ }
+
+ /*
+ * Spawn a grandchild that will immediately exit. If the kernel has bug
+ * 180385, the parent will see a kevent with both NOTE_CHILD and
+ * NOTE_EXIT. If that bug is fixed, it will see two separate kevents
+ * for those notes. Note that this triggers the conditions for
+ * detecting the bug quite reliably on a 1 CPU system (or if the test
+ * process is restricted to a single CPU), but may not trigger it on a
+ * multi-CPU system.
+ */
+ grandchild = fork();
+ if (grandchild == 0) { /* Grandchild */
+ if (sleep_time) sleep(sleep_time);
+ exit(1);
+ } else if (grandchild == -1) { /* Error */
+ err(1, "fork (grandchild) failed! (%s() at %s:%d)",
+ __func__, __FILE__, __LINE__);
+ } else { /* Child (Grandchild Parent) */
+ printf(" -- grandchild created (pid %d)\n", (int) grandchild);
+ }
+ if (sleep_time) sleep(sleep_time);
+ exit(0);
+ } else if (pid == -1) { /* Error */
+ err(1, "fork (child) failed! (%s() at %s:%d)",
+ __func__, __FILE__, __LINE__);
+ }
+
+ printf(" -- child created (pid %d)\n", (int) pid);
+
+ kevent_add(kqfd, &kev, pid, EVFILT_PROC, EV_ADD | EV_ENABLE,
+ NOTE_TRACK | NOTE_EXEC | NOTE_EXIT | NOTE_FORK,
+ 0, NULL);
+
+ printf(" -- tracking child (pid %d)\n", (int) pid);
+
+ /* Now that we're tracking the child, tell it to proceed. */
+ result = write(pipe_fd[0], test_id, 1);
+ if (result != 1) {
+ err(1, "write to pipe in parent failed! (ret %zd) (%s() at %s:%d)",
+ result, __func__, __FILE__, __LINE__);
+ }
+
+ /*
+ * Several events should be received:
+ * - NOTE_FORK (from child)
+ * - NOTE_CHILD (from grandchild)
+ * - NOTE_EXIT (from grandchild)
+ * - NOTE_EXIT (from child)
+ *
+ * The NOTE_FORK and NOTE_EXIT from the child could be combined into a
+ * single event, but the NOTE_CHILD and NOTE_EXIT from the grandchild must
+ * not be combined.
+ *
+ * The loop continues until no events are received within a 5 second
+ * period, at which point it is assumed that no more will be coming. The
+ * loop is deliberately designed to attempt to get events even after all
+ * the expected ones are received in case some spurious events are
+ * generated as well as the expected ones.
+ */
+ {
+ int child_exit = 0;
+ int child_fork = 0;
+ int gchild_exit = 0;
+ int gchild_note = 0;
+ pid_t gchild_pid = -1;
+ int done = 0;
+
+ while (!done)
+ {
+ int handled = 0;
+ struct kevent *kevp;
+
+ kevp = kevent_get_timeout(kqfd, 5);
+ if (kevp == NULL) {
+ done = 1;
+ } else {
+ printf(" -- Received kevent: %s\n", kevent_to_str(kevp));
+
+ if ((kevp->fflags & NOTE_CHILD) && (kevp->fflags & NOTE_EXIT)) {
+ errx(1, "NOTE_CHILD and NOTE_EXIT in same kevent: %s", kevent_to_str(kevp));
+ }
+
+ if (kevp->fflags & NOTE_CHILD) {
+ if (kevp->data == pid) {
+ if (!gchild_note) {
+ ++gchild_note;
+ gchild_pid = kevp->ident;
+ ++handled;
+ } else {
+ errx(1, "Spurious NOTE_CHILD: %s", kevent_to_str(kevp));
+ }
+ }
+ }
+
+ if (kevp->fflags & NOTE_EXIT) {
+ if ((kevp->ident == pid) && (!child_exit)) {
+ ++child_exit;
+ ++handled;
+ } else if ((kevp->ident == gchild_pid) && (!gchild_exit)) {
+ ++gchild_exit;
+ ++handled;
+ } else {
+ errx(1, "Spurious NOTE_EXIT: %s", kevent_to_str(kevp));
+ }
+ }
+
+ if (kevp->fflags & NOTE_FORK) {
+ if ((kevp->ident == pid) && (!child_fork)) {
+ ++child_fork;
+ ++handled;
+ } else {
+ errx(1, "Spurious NOTE_FORK: %s", kevent_to_str(kevp));
+ }
+ }
+
+ if (!handled) {
+ errx(1, "Spurious kevent: %s", kevent_to_str(kevp));
+ }
+
+ free(kevp);
+ }
+ }
+
+ /* Make sure all expected events were received. */
+ if (child_exit && child_fork && gchild_exit && gchild_note) {
+ printf(" -- Received all expected events.\n");
+ } else {
+ errx(1, "Did not receive all expected events.");
+ }
+ }
+
+ success();
+}
+
+#ifdef TODO
+static void
+event_trigger(void)
+{
+ struct kevent kev;
+ pid_t pid;
+
+ test_begin("kevent(EVFILT_PROC, wait)");
+
+ /* Create a child that waits to be killed and then exits */
+ pid = fork();
+ if (pid == 0) {
+ pause();
+ printf(" -- child caught signal, exiting\n");
+ exit(2);
+ }
+ printf(" -- child created (pid %d)\n", (int) pid);
+
+ test_no_kevents();
+ kevent_add(kqfd, &kev, pid, EVFILT_PROC, EV_ADD, 0, 0, NULL);
+
+ /* Cause the child to exit, then retrieve the event */
+ printf(" -- killing process %d\n", (int) pid);
+ if (kill(pid, SIGUSR1) < 0)
+ err(1, "kill");
+ kevent_cmp(&kev, kevent_get(kqfd));
+ test_no_kevents();
+
+ success();
+}
+
+void
+test_kevent_signal_disable(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_DISABLE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DISABLE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGKILL) < 0)
+ err(1, "kill");
+
+ test_no_kevents();
+
+ success();
+}
+
+void
+test_kevent_signal_enable(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_ENABLE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ENABLE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ kev.flags = EV_ADD | EV_CLEAR;
+#if LIBKQUEUE
+ kev.data = 1; /* WORKAROUND */
+#else
+ kev.data = 2; // one extra time from test_kevent_signal_disable()
+#endif
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Delete the watch */
+ kev.flags = EV_DELETE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_signal_del(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_DELETE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ /* Delete the kevent */
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DELETE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ test_no_kevents();
+ success();
+}
+
+void
+test_kevent_signal_oneshot(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_ONESHOT)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD | EV_ONESHOT, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ kev.flags |= EV_CLEAR;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Send another one and make sure we get no events */
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+ test_no_kevents();
+
+ success();
+}
+#endif
+
+void
+test_evfilt_proc()
+{
+ kqfd = kqueue();
+
+ signal(SIGUSR1, sig_handler);
+
+ add_and_delete();
+ proc_track(0); /* Run without sleeping before children exit. */
+ proc_track(1); /* Sleep a bit in the children before exiting. */
+
+#if TODO
+ event_trigger();
+#endif
+
+ signal(SIGUSR1, SIG_DFL);
+
+#if TODO
+ test_kevent_signal_add();
+ test_kevent_signal_del();
+ test_kevent_signal_get();
+ test_kevent_signal_disable();
+ test_kevent_signal_enable();
+ test_kevent_signal_oneshot();
+#endif
+ close(kqfd);
+}
diff --git a/tests/sys/kqueue/libkqueue/read.c b/tests/sys/kqueue/libkqueue/read.c
new file mode 100644
index 000000000000..28371444383f
--- /dev/null
+++ b/tests/sys/kqueue/libkqueue/read.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include "common.h"
+
+int kqfd;
+int sockfd[2];
+
+static void
+kevent_socket_drain(void)
+{
+ char buf[1];
+
+ /* Drain the read buffer, then make sure there are no more events. */
+ puts("draining the read buffer");
+ if (read(sockfd[0], &buf[0], 1) < 1)
+ err(1, "read(2)");
+}
+
+static void
+kevent_socket_fill(void)
+{
+ puts("filling the read buffer");
+ if (write(sockfd[1], ".", 1) < 1)
+ err(1, "write(2)");
+}
+
+
+void
+test_kevent_socket_add(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, EV_ADD)";
+ struct kevent kev;
+
+ test_begin(test_id);
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_socket_get(void)
+{
+ const char *test_id = "kevent(EVFILT_READ) wait";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ kevent_socket_fill();
+
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ kevent_socket_drain();
+ test_no_kevents();
+
+ kev.flags = EV_DELETE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_socket_clear(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, EV_CLEAR)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ kevent_socket_fill();
+ kevent_socket_fill();
+
+ kev.data = 2;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* We filled twice, but drain once. Edge-triggered would not generate
+ additional events.
+ */
+ kevent_socket_drain();
+ test_no_kevents();
+
+ kevent_socket_drain();
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_socket_disable_and_enable(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, EV_DISABLE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ /*
+ * Write to the socket before adding the event. This way we can verify that
+ * enabling a triggered kevent causes the event to be returned immediately.
+ */
+ kevent_socket_fill();
+
+ /* Add a disabled event. */
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD | EV_DISABLE, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ test_no_kevents();
+
+ /* Re-enable the knote, then see if an event is generated */
+ kev.flags = EV_ENABLE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ kev.flags = EV_ADD;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ kevent_socket_drain();
+
+ kev.flags = EV_DELETE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_socket_del(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, EV_DELETE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ kevent_socket_fill();
+ test_no_kevents();
+ kevent_socket_drain();
+
+ success();
+}
+
+void
+test_kevent_socket_oneshot(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, EV_ONESHOT)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ /* Re-add the watch and make sure no events are pending */
+ puts("-- re-adding knote");
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ test_no_kevents();
+
+ puts("-- getting one event");
+ kevent_socket_fill();
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ puts("-- checking knote disabled");
+ test_no_kevents();
+
+ /* Try to delete the knote, it should already be deleted */
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) == 0)
+ err(1, "%s", test_id);
+
+ kevent_socket_drain();
+
+ success();
+}
+
+
+#if HAVE_EV_DISPATCH
+void
+test_kevent_socket_dispatch(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, EV_DISPATCH)";
+
+ test_begin(test_id);
+
+ struct kevent kev;
+
+ /* Re-add the watch and make sure no events are pending */
+ puts("-- re-adding knote");
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD | EV_DISPATCH, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ test_no_kevents();
+
+ /* The event will occur only once, even though EV_CLEAR is not
+ specified. */
+ kevent_socket_fill();
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+ test_no_kevents();
+
+ /* Since the knote is disabled, the EV_DELETE operation succeeds. */
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ kevent_socket_drain();
+
+ success();
+}
+#endif /* HAVE_EV_DISPATCH */
+
+#if BROKEN
+void
+test_kevent_socket_lowat(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, NOTE_LOWAT)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ /* Re-add the watch and make sure no events are pending */
+ puts("-- re-adding knote, setting low watermark to 2 bytes");
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD | EV_ONESHOT, NOTE_LOWAT, 2, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ test_no_kevents();
+
+ puts("-- checking that one byte does not trigger an event..");
+ kevent_socket_fill();
+ test_no_kevents();
+
+ puts("-- checking that two bytes triggers an event..");
+ kevent_socket_fill();
+ if (kevent(kqfd, NULL, 0, &kev, 1, NULL) != 1)
+ err(1, "%s", test_id);
+ KEV_CMP(kev, sockfd[0], EVFILT_READ, 0);
+ test_no_kevents();
+
+ kevent_socket_drain();
+ kevent_socket_drain();
+
+ success();
+}
+#endif
+
+void
+test_kevent_socket_eof(void)
+{
+ const char *test_id = "kevent(EVFILT_READ, EV_EOF)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ /* Re-add the watch and make sure no events are pending */
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ test_no_kevents();
+
+ if (close(sockfd[1]) < 0)
+ err(1, "close(2)");
+
+ kev.flags |= EV_EOF;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Delete the watch */
+ EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_evfilt_read()
+{
+ /* Create a connected pair of full-duplex sockets for testing socket events */
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd) < 0)
+ abort();
+
+ kqfd = kqueue();
+ test_kevent_socket_add();
+ test_kevent_socket_del();
+ test_kevent_socket_get();
+ test_kevent_socket_disable_and_enable();
+ test_kevent_socket_oneshot();
+ test_kevent_socket_clear();
+#if HAVE_EV_DISPATCH
+ test_kevent_socket_dispatch();
+#endif
+ test_kevent_socket_eof();
+ close(kqfd);
+}
diff --git a/tests/sys/kqueue/libkqueue/signal.c b/tests/sys/kqueue/libkqueue/signal.c
new file mode 100644
index 000000000000..14e751db95c2
--- /dev/null
+++ b/tests/sys/kqueue/libkqueue/signal.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include "common.h"
+
+int kqfd;
+
+void
+test_kevent_signal_add(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_ADD)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_signal_get(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, wait)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ kev.flags |= EV_CLEAR;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ success();
+}
+
+void
+test_kevent_signal_disable(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_DISABLE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DISABLE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ test_no_kevents();
+
+ success();
+}
+
+void
+test_kevent_signal_enable(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_ENABLE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ENABLE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ kev.flags = EV_ADD | EV_CLEAR;
+#if LIBKQUEUE
+ kev.data = 1; /* WORKAROUND */
+#else
+ kev.data = 2; // one extra time from test_kevent_signal_disable()
+#endif
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Delete the watch */
+ kev.flags = EV_DELETE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_signal_del(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_DELETE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ /* Delete the kevent */
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DELETE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ test_no_kevents();
+ success();
+}
+
+void
+test_kevent_signal_oneshot(void)
+{
+ const char *test_id = "kevent(EVFILT_SIGNAL, EV_ONESHOT)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD | EV_ONESHOT, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Block SIGUSR1, then send it to ourselves */
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGUSR1);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
+ err(1, "sigprocmask");
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+
+ kev.flags |= EV_CLEAR;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Send another one and make sure we get no events */
+ if (kill(getpid(), SIGUSR1) < 0)
+ err(1, "kill");
+ test_no_kevents();
+
+ success();
+}
+
+void
+test_evfilt_signal()
+{
+ kqfd = kqueue();
+ test_kevent_signal_add();
+ test_kevent_signal_del();
+ test_kevent_signal_get();
+ test_kevent_signal_disable();
+ test_kevent_signal_enable();
+ test_kevent_signal_oneshot();
+ close(kqfd);
+}
diff --git a/tests/sys/kqueue/libkqueue/timer.c b/tests/sys/kqueue/libkqueue/timer.c
new file mode 100644
index 000000000000..766125d857e8
--- /dev/null
+++ b/tests/sys/kqueue/libkqueue/timer.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include "common.h"
+
+int kqfd;
+
+void
+test_kevent_timer_add(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER, EV_ADD)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, 1, EVFILT_TIMER, EV_ADD, 0, 1000, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_timer_del(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER, EV_DELETE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ test_no_kevents();
+
+ success();
+}
+
+void
+test_kevent_timer_get(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER, wait)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, 1, EVFILT_TIMER, EV_ADD, 0, 1000, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ kev.flags |= EV_CLEAR;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ EV_SET(&kev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+static void
+test_oneshot(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER, EV_ONESHOT)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 500,NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Retrieve the event */
+ kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Check if the event occurs again */
+ sleep(3);
+ test_no_kevents();
+
+
+ success();
+}
+
+static void
+test_periodic(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER, periodic)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, 1000,NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Retrieve the event */
+ kev.flags = EV_ADD | EV_CLEAR;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Check if the event occurs again */
+ sleep(1);
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ /* Delete the event */
+ kev.flags = EV_DELETE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+static void
+disable_and_enable(void)
+{
+ const char *test_id = "kevent(EVFILT_TIMER, EV_DISABLE and EV_ENABLE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ /* Add the watch and immediately disable it */
+ EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 2000,NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ kev.flags = EV_DISABLE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ test_no_kevents();
+
+ /* Re-enable and check again */
+ kev.flags = EV_ENABLE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT;
+ kev.data = 1;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ success();
+}
+
+void
+test_evfilt_timer()
+{
+ kqfd = kqueue();
+ test_kevent_timer_add();
+ test_kevent_timer_del();
+ test_kevent_timer_get();
+ test_oneshot();
+ test_periodic();
+ disable_and_enable();
+ close(kqfd);
+}
diff --git a/tests/sys/kqueue/libkqueue/user.c b/tests/sys/kqueue/libkqueue/user.c
new file mode 100644
index 000000000000..9ba25f9df4d3
--- /dev/null
+++ b/tests/sys/kqueue/libkqueue/user.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include "common.h"
+
+int kqfd;
+
+static void
+add_and_delete(void)
+{
+ const char *test_id = "kevent(EVFILT_USER, EV_ADD and EV_DELETE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ADD, 0, 0, NULL);
+ test_no_kevents();
+
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_DELETE, 0, 0, NULL);
+ test_no_kevents();
+
+ success();
+}
+
+static void
+event_wait(void)
+{
+ const char *test_id = "kevent(EVFILT_USER, wait)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ /* Add the event, and then trigger it */
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, NULL);
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL);
+
+ kev.fflags &= ~NOTE_FFCTRLMASK;
+ kev.fflags &= ~NOTE_TRIGGER;
+ kev.flags = EV_CLEAR;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ test_no_kevents();
+
+ success();
+}
+
+static void
+disable_and_enable(void)
+{
+ const char *test_id = "kevent(EVFILT_USER, EV_DISABLE and EV_ENABLE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ADD, 0, 0, NULL);
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_DISABLE, 0, 0, NULL);
+
+ /* Trigger the event, but since it is disabled, nothing will happen. */
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL);
+ test_no_kevents();
+
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ENABLE, 0, 0, NULL);
+ kevent_add(kqfd, &kev, 1, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL);
+
+ kev.flags = EV_CLEAR;
+ kev.fflags &= ~NOTE_FFCTRLMASK;
+ kev.fflags &= ~NOTE_TRIGGER;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ success();
+}
+
+static void
+oneshot(void)
+{
+ const char *test_id = "kevent(EVFILT_USER, EV_ONESHOT)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ kevent_add(kqfd, &kev, 2, EVFILT_USER, EV_ADD | EV_ONESHOT, 0, 0, NULL);
+
+ puts(" -- event 1");
+ kevent_add(kqfd, &kev, 2, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL);
+
+ kev.flags = EV_ONESHOT;
+ kev.fflags &= ~NOTE_FFCTRLMASK;
+ kev.fflags &= ~NOTE_TRIGGER;
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ test_no_kevents();
+
+ success();
+}
+
+void
+test_evfilt_user()
+{
+ kqfd = kqueue();
+
+ add_and_delete();
+ event_wait();
+ disable_and_enable();
+ oneshot();
+ /* TODO: try different fflags operations */
+
+ close(kqfd);
+}
diff --git a/tests/sys/kqueue/libkqueue/vnode.c b/tests/sys/kqueue/libkqueue/vnode.c
new file mode 100644
index 000000000000..55d5b3f5edc2
--- /dev/null
+++ b/tests/sys/kqueue/libkqueue/vnode.c
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2009 Mark Heily <mark@heily.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include "common.h"
+
+int kqfd;
+int vnode_fd;
+
+void
+test_kevent_vnode_add(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, EV_ADD)";
+ const char *testfile = "./kqueue-test.tmp";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ system("touch ./kqueue-test.tmp");
+ vnode_fd = open(testfile, O_RDONLY);
+ if (vnode_fd < 0)
+ err(1, "open of %s", testfile);
+ else
+ printf("vnode_fd = %d\n", vnode_fd);
+
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD,
+ NOTE_WRITE | NOTE_ATTRIB | NOTE_RENAME | NOTE_DELETE, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_vnode_note_delete(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, NOTE_DELETE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_DELETE, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ if (unlink("./kqueue-test.tmp") < 0)
+ err(1, "unlink");
+
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ success();
+}
+
+void
+test_kevent_vnode_note_write(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, NOTE_WRITE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_WRITE, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ if (system("echo hello >> ./kqueue-test.tmp") < 0)
+ err(1, "system");
+
+ /* BSD kqueue adds NOTE_EXTEND even though it was not requested */
+ /* BSD kqueue removes EV_ENABLE */
+ kev.flags &= ~EV_ENABLE; // XXX-FIXME compatibility issue
+ kev.fflags |= NOTE_EXTEND; // XXX-FIXME compatibility issue
+ kevent_cmp(&kev, kevent_get(kqfd));
+
+ success();
+}
+
+void
+test_kevent_vnode_note_attrib(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, NOTE_ATTRIB)";
+ struct kevent kev;
+ int nfds;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_ATTRIB, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ if (system("touch ./kqueue-test.tmp") < 0)
+ err(1, "system");
+
+ nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL);
+ if (nfds < 1)
+ err(1, "%s", test_id);
+ if (kev.ident != vnode_fd ||
+ kev.filter != EVFILT_VNODE ||
+ kev.fflags != NOTE_ATTRIB)
+ err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)",
+ test_id, (unsigned int)kev.ident, kev.filter, kev.flags);
+
+ success();
+}
+
+void
+test_kevent_vnode_note_rename(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, NOTE_RENAME)";
+ struct kevent kev;
+ int nfds;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_RENAME, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ if (system("mv ./kqueue-test.tmp ./kqueue-test2.tmp") < 0)
+ err(1, "system");
+
+ nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL);
+ if (nfds < 1)
+ err(1, "%s", test_id);
+ if (kev.ident != vnode_fd ||
+ kev.filter != EVFILT_VNODE ||
+ kev.fflags != NOTE_RENAME)
+ err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)",
+ test_id, (unsigned int)kev.ident, kev.filter, kev.flags);
+
+ if (system("mv ./kqueue-test2.tmp ./kqueue-test.tmp") < 0)
+ err(1, "system");
+
+ success();
+}
+
+void
+test_kevent_vnode_del(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, EV_DELETE)";
+ struct kevent kev;
+
+ test_begin(test_id);
+
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ success();
+}
+
+void
+test_kevent_vnode_disable_and_enable(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, EV_DISABLE and EV_ENABLE)";
+ struct kevent kev;
+ int nfds;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ /* Add the watch and immediately disable it */
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_ATTRIB, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ kev.flags = EV_DISABLE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ /* Confirm that the watch is disabled */
+ if (system("touch ./kqueue-test.tmp") < 0)
+ err(1, "system");
+ test_no_kevents();
+
+ /* Re-enable and check again */
+ kev.flags = EV_ENABLE;
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+ if (system("touch ./kqueue-test.tmp") < 0)
+ err(1, "system");
+ nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL);
+ if (nfds < 1)
+ err(1, "%s", test_id);
+ if (kev.ident != vnode_fd ||
+ kev.filter != EVFILT_VNODE ||
+ kev.fflags != NOTE_ATTRIB)
+ err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)",
+ test_id, (unsigned int)kev.ident, kev.filter, kev.flags);
+
+ success();
+}
+
+#if HAVE_EV_DISPATCH
+void
+test_kevent_vnode_dispatch(void)
+{
+ const char *test_id = "kevent(EVFILT_VNODE, EV_DISPATCH)";
+ struct kevent kev;
+ int nfds;
+
+ test_begin(test_id);
+
+ test_no_kevents();
+
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_DISPATCH, NOTE_ATTRIB, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "%s", test_id);
+
+ if (system("touch ./kqueue-test.tmp") < 0)
+ err(1, "system");
+
+ nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL);
+ if (nfds < 1)
+ err(1, "%s", test_id);
+ if (kev.ident != vnode_fd ||
+ kev.filter != EVFILT_VNODE ||
+ kev.fflags != NOTE_ATTRIB)
+ err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)",
+ test_id, (unsigned int)kev.ident, kev.filter, kev.flags);
+
+ /* Confirm that the watch is disabled automatically */
+ puts("-- checking that watch is disabled");
+ if (system("touch ./kqueue-test.tmp") < 0)
+ err(1, "system");
+ test_no_kevents();
+
+ /* Delete the watch */
+ EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_DELETE, NOTE_ATTRIB, 0, NULL);
+ if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0)
+ err(1, "remove watch failed: %s", test_id);
+
+ success();
+}
+#endif /* HAVE_EV_DISPATCH */
+
+void
+test_evfilt_vnode()
+{
+ kqfd = kqueue();
+ test_kevent_vnode_add();
+ test_kevent_vnode_del();
+ test_kevent_vnode_disable_and_enable();
+#if HAVE_EV_DISPATCH
+ test_kevent_vnode_dispatch();
+#endif
+ test_kevent_vnode_note_write();
+ test_kevent_vnode_note_attrib();
+ test_kevent_vnode_note_rename();
+ test_kevent_vnode_note_delete();
+ close(kqfd);
+}
diff --git a/tests/sys/mac/Makefile b/tests/sys/mac/Makefile
new file mode 100644
index 000000000000..ae2c4917b0d1
--- /dev/null
+++ b/tests/sys/mac/Makefile
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/mac
+
+TESTS_SUBDIRS+= bsdextended
+TESTS_SUBDIRS+= portacl
+
+.include <bsd.test.mk>
diff --git a/tests/sys/mac/Makefile.depend b/tests/sys/mac/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/mac/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/mac/bsdextended/Makefile b/tests/sys/mac/bsdextended/Makefile
new file mode 100644
index 000000000000..9d0b6f6907ca
--- /dev/null
+++ b/tests/sys/mac/bsdextended/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/mac/bsdextended
+
+TAP_TESTS_C+= ugidfw_test
+TAP_TESTS_SH+= matches_test
+
+LIBADD.ugidfw_test+= ugidfw
+
+TEST_METADATA.matches_test+= required_user="root"
+TEST_METADATA.ugidfw_test+= required_user="root"
+
+.include <bsd.test.mk>
diff --git a/tests/sys/mac/bsdextended/Makefile.depend b/tests/sys/mac/bsdextended/Makefile.depend
new file mode 100644
index 000000000000..24a1de909f18
--- /dev/null
+++ b/tests/sys/mac/bsdextended/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libugidfw \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/mac/bsdextended/matches_test.sh b/tests/sys/mac/bsdextended/matches_test.sh
new file mode 100644
index 000000000000..929bbca2fcca
--- /dev/null
+++ b/tests/sys/mac/bsdextended/matches_test.sh
@@ -0,0 +1,365 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+uidrange="60000:100000"
+gidrange="60000:100000"
+uidinrange="nobody"
+uidoutrange="daemon"
+gidinrange="nobody" # We expect $uidinrange in this group
+gidoutrange="daemon" # We expect $uidinrange in this group
+
+test_num=1
+pass()
+{
+ echo "ok $test_num # $@"
+ : $(( test_num += 1 ))
+}
+
+fail()
+{
+ echo "not ok $test_num # $@"
+ : $(( test_num += 1 ))
+}
+
+#
+# Setup
+#
+
+: ${TMPDIR=/tmp}
+if [ $(id -u) -ne 0 ]; then
+ echo "1..0 # SKIP test must be run as root"
+ exit 0
+fi
+if ! sysctl -N security.mac.bsdextended >/dev/null 2>&1; then
+ echo "1..0 # SKIP mac_bsdextended(4) support isn't available"
+ exit 0
+fi
+if [ "$TMPDIR" != "/tmp" ]; then
+ if ! chmod -Rf 0755 $TMPDIR; then
+ echo "1..0 # SKIP failed to chmod $TMPDIR"
+ exit 0
+ fi
+fi
+if ! playground=$(mktemp -d $TMPDIR/tmp.XXXXXXX); then
+ echo "1..0 # SKIP failed to create temporary directory"
+ exit 0
+fi
+trap "rmdir $playground" EXIT INT TERM
+if ! mdmfs -s 25m md $playground; then
+ echo "1..0 # SKIP failed to mount md device"
+ exit 0
+fi
+chmod a+rwx $playground
+md_device=$(mount -p | grep "$playground" | awk '{ gsub(/^\/dev\//, "", $1); print $1 }')
+trap "umount -f $playground; mdconfig -d -u $md_device; rmdir $playground" EXIT INT TERM
+if [ -z "$md_device" ]; then
+ mount -p | grep $playground
+ echo "1..0 # SKIP md device not properly attached to the system"
+fi
+
+ugidfw remove 1
+
+file1=$playground/test-$uidinrange
+file2=$playground/test-$uidoutrange
+cat > $playground/test-script.sh <<'EOF'
+#!/bin/sh
+: > $1
+EOF
+if [ $? -ne 0 ]; then
+ echo "1..0 # SKIP failed to create test script"
+ exit 0
+fi
+echo "1..30"
+
+command1="sh $playground/test-script.sh $file1"
+command2="sh $playground/test-script.sh $file2"
+
+desc="$uidinrange file"
+if su -m $uidinrange -c "$command1"; then
+ pass $desc
+else
+ fail $desc
+fi
+
+chown "$uidinrange":"$gidinrange" $file1
+chmod a+w $file1
+
+desc="$uidoutrange file"
+if $command2; then
+ pass $desc
+else
+ fail $desc
+fi
+
+chown "$uidoutrange":"$gidoutrange" $file2
+chmod a+w $file2
+
+#
+# No rules
+#
+desc="no rules $uidinrange"
+if su -fm $uidinrange -c "$command1"; then
+ pass $desc
+else
+ fail $desc
+fi
+
+desc="no rules $uidoutrange"
+if su -fm $uidoutrange -c "$command1"; then
+ pass $desc
+else
+ fail $desc
+fi
+
+#
+# Subject Match on uid
+#
+ugidfw set 1 subject uid $uidrange object mode rasx
+desc="subject uid in range"
+if su -fm $uidinrange -c "$command1"; then
+ fail $desc
+else
+ pass $desc
+fi
+
+desc="subject uid out range"
+if su -fm $uidoutrange -c "$command1"; then
+ pass $desc
+else
+ fail $desc
+fi
+
+#
+# Subject Match on gid
+#
+ugidfw set 1 subject gid $gidrange object mode rasx
+
+desc="subject gid in range"
+if su -fm $uidinrange -c "$command1"; then
+ fail $desc
+else
+ pass $desc
+fi
+
+desc="subject gid out range"
+if su -fm $uidoutrange -c "$command1"; then
+ pass $desc
+else
+ fail $desc
+fi
+
+if which jail >/dev/null; then
+ #
+ # Subject Match on jail
+ #
+ rm -f $playground/test-jail
+
+ desc="subject matching jailid"
+ jailid=`jail -i / localhost 127.0.0.1 /usr/sbin/daemon -f /bin/sh -c "(sleep 5; touch $playground/test-jail) &"`
+ ugidfw set 1 subject jailid $jailid object mode rasx
+ sleep 10
+
+ if [ -f $playground/test-jail ]; then
+ fail "TODO $desc: this testcase fails (see bug # 205481)"
+ else
+ pass $desc
+ fi
+
+ rm -f $playground/test-jail
+ desc="subject nonmatching jailid"
+ jailid=`jail -i / localhost 127.0.0.1 /usr/sbin/daemon -f /bin/sh -c "(sleep 5; touch $playground/test-jail) &"`
+ sleep 10
+ if [ -f $playground/test-jail ]; then
+ pass $desc
+ else
+ fail $desc
+ fi
+else
+ # XXX: kyua is too dumb to parse skip ranges, still..
+ pass "skip jail(8) not installed"
+ pass "skip jail(8) not installed"
+fi
+
+#
+# Object uid
+#
+ugidfw set 1 subject object uid $uidrange mode rasx
+
+desc="object uid in range"
+if su -fm $uidinrange -c "$command1"; then
+ fail $desc
+else
+ pass $desc
+fi
+
+desc="object uid out range"
+if su -fm $uidinrange -c "$command2"; then
+ pass $desc
+else
+ fail $desc
+fi
+ugidfw set 1 subject object uid $uidrange mode rasx
+
+desc="object uid in range (different subject)"
+if su -fm $uidoutrange -c "$command1"; then
+ fail $desc
+else
+ pass $desc
+fi
+
+desc="object uid out range (different subject)"
+if su -fm $uidoutrange -c "$command2"; then
+ pass $desc
+else
+ fail $desc
+fi
+
+#
+# Object gid
+#
+ugidfw set 1 subject object gid $uidrange mode rasx
+
+desc="object gid in range"
+if su -fm $uidinrange -c "$command1"; then
+ fail $desc
+else
+ pass $desc
+fi
+
+desc="object gid out range"
+if su -fm $uidinrange -c "$command2"; then
+ pass $desc
+else
+ fail $desc
+fi
+desc="object gid in range (different subject)"
+if su -fm $uidoutrange -c "$command1"; then
+ fail $desc
+else
+ pass $desc
+fi
+
+desc="object gid out range (different subject)"
+if su -fm $uidoutrange -c "$command2"; then
+ pass $desc
+else
+ fail $desc
+fi
+
+#
+# Object filesys
+#
+ugidfw set 1 subject uid $uidrange object filesys / mode rasx
+desc="object out of filesys"
+if su -fm $uidinrange -c "$command1"; then
+ pass $desc
+else
+ fail $desc
+fi
+
+ugidfw set 1 subject uid $uidrange object filesys $playground mode rasx
+desc="object in filesys"
+if su -fm $uidinrange -c "$command1"; then
+ fail $desc
+else
+ pass $desc
+fi
+
+#
+# Object suid
+#
+ugidfw set 1 subject uid $uidrange object suid mode rasx
+desc="object notsuid"
+if su -fm $uidinrange -c "$command1"; then
+ pass $desc
+else
+ fail $desc
+fi
+
+chmod u+s $file1
+desc="object suid"
+if su -fm $uidinrange -c "$command1"; then
+ fail $desc
+else
+ pass $desc
+fi
+chmod u-s $file1
+
+#
+# Object sgid
+#
+ugidfw set 1 subject uid $uidrange object sgid mode rasx
+desc="object notsgid"
+if su -fm $uidinrange -c "$command1"; then
+ pass $desc
+else
+ fail $desc
+fi
+
+chmod g+s $file1
+desc="object sgid"
+if su -fm $uidinrange -c "$command1"; then
+ fail $desc
+else
+ pass $desc
+fi
+chmod g-s $file1
+
+#
+# Object uid matches subject
+#
+ugidfw set 1 subject uid $uidrange object uid_of_subject mode rasx
+
+desc="object uid notmatches subject"
+if su -fm $uidinrange -c "$command2"; then
+ pass $desc
+else
+ fail $desc
+fi
+
+desc="object uid matches subject"
+if su -fm $uidinrange -c "$command1"; then
+ fail $desc
+else
+ pass $desc
+fi
+
+#
+# Object gid matches subject
+#
+ugidfw set 1 subject uid $uidrange object gid_of_subject mode rasx
+
+desc="object gid notmatches subject"
+if su -fm $uidinrange -c "$command2"; then
+ pass $desc
+else
+ fail $desc
+fi
+
+desc="object gid matches subject"
+if su -fm $uidinrange -c "$command1"; then
+ fail $desc
+else
+ pass $desc
+fi
+
+#
+# Object type
+#
+desc="object not type"
+ugidfw set 1 subject uid $uidrange object type dbclsp mode rasx
+if su -fm $uidinrange -c "$command1"; then
+ pass $desc
+else
+ fail $desc
+fi
+
+desc="object type"
+ugidfw set 1 subject uid $uidrange object type r mode rasx
+if su -fm $uidinrange -c "$command1"; then
+ fail $desc
+else
+ pass $desc
+fi
diff --git a/tests/sys/mac/bsdextended/ugidfw_test.c b/tests/sys/mac/bsdextended/ugidfw_test.c
new file mode 100644
index 000000000000..aab8553c89e2
--- /dev/null
+++ b/tests/sys/mac/bsdextended/ugidfw_test.c
@@ -0,0 +1,252 @@
+/*-
+ * Copyright (c) 2005 McAfee, 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 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 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$
+ */
+
+#include <sys/param.h>
+#include <sys/mac.h>
+#include <sys/mount.h>
+
+#include <security/mac_bsdextended/mac_bsdextended.h>
+
+#include <err.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ugidfw.h>
+#include <unistd.h>
+
+/*
+ * Starting point for a regression test for mac_bsdextended(4) and the
+ * supporting libugidfw(3).
+ */
+
+/*
+ * This section of the regression test passes some test cases through the
+ * rule<->string routines to confirm they work approximately as desired.
+ */
+
+/*
+ * List of users and groups we must check exists before we can begin, since
+ * they are used in the string test rules. We use users and groups that will
+ * always exist in a default install used for regression testing.
+ */
+static const char *test_users[] = {
+ "root",
+ "daemon",
+ "operator",
+ "bin",
+};
+
+static const char *test_groups[] = {
+ "wheel",
+ "daemon",
+ "operator",
+ "bin",
+};
+
+int test_num;
+
+/*
+ * List of test strings that must go in (and come out) of libugidfw intact.
+ */
+static const char *test_strings[] = {
+ /* Variations on subject and object uids. */
+ "subject uid root object uid root mode n",
+ "subject uid root object uid daemon mode n",
+ "subject uid daemon object uid root mode n",
+ "subject uid daemon object uid daemon mode n",
+ /* Variations on mode. */
+ "subject uid root object uid root mode a",
+ "subject uid root object uid root mode r",
+ "subject uid root object uid root mode s",
+ "subject uid root object uid root mode w",
+ "subject uid root object uid root mode x",
+ "subject uid root object uid root mode arswx",
+ /* Variations on subject and object gids. */
+ "subject gid wheel object gid wheel mode n",
+ "subject gid wheel object gid daemon mode n",
+ "subject gid daemon object gid wheel mode n",
+ "subject gid daemon object gid daemon mode n",
+ /* Subject uids and subject gids. */
+ "subject uid bin gid daemon object uid operator gid wheel mode n",
+ /* Not */
+ "subject not uid operator object uid bin mode n",
+ "subject uid bin object not uid operator mode n",
+ "subject not uid daemon object not uid operator mode n",
+ /* Ranges */
+ "subject uid root:operator object gid wheel:bin mode n",
+ /* Jail ID */
+ "subject jailid 1 object uid root mode n",
+ /* Filesys */
+ "subject uid root object filesys / mode n",
+ "subject uid root object filesys /dev mode n",
+ /* S/UGID */
+ "subject not uid root object sgid mode n",
+ "subject not uid root object sgid mode n",
+ /* Matching uid/gid */
+ "subject not uid root:operator object not uid_of_subject mode n",
+ "subject not gid wheel:bin object not gid_of_subject mode n",
+ /* Object types */
+ "subject uid root object type a mode a",
+ "subject uid root object type r mode a",
+ "subject uid root object type d mode a",
+ "subject uid root object type b mode a",
+ "subject uid root object type c mode a",
+ "subject uid root object type l mode a",
+ "subject uid root object type s mode a",
+ "subject uid root object type rbc mode a",
+ "subject uid root object type dls mode a",
+ /* Empty rules always match */
+ "subject object mode a",
+ /* Partial negations */
+ "subject ! uid root object mode n",
+ "subject ! gid wheel object mode n",
+ "subject ! jailid 2 object mode n",
+ "subject object ! uid root mode n",
+ "subject object ! gid wheel mode n",
+ "subject object ! filesys / mode n",
+ "subject object ! suid mode n",
+ "subject object ! sgid mode n",
+ "subject object ! uid_of_subject mode n",
+ "subject object ! gid_of_subject mode n",
+ "subject object ! type d mode n",
+ /* All out nonsense */
+ "subject uid root ! gid wheel:bin ! jailid 1 "
+ "object ! uid root:daemon gid daemon filesys / suid sgid uid_of_subject gid_of_subject ! type r "
+ "mode rsx",
+};
+
+static void
+test_libugidfw_strings(void)
+{
+ struct mac_bsdextended_rule rule;
+ char errorstr[256];
+ char rulestr[256];
+ int error, i;
+
+ for (i = 0; i < nitems(test_users); i++, test_num++) {
+ if (getpwnam(test_users[i]) == NULL)
+ printf("not ok %d # test_libugidfw_strings: getpwnam(%s) "
+ "failed: %s\n", test_num, test_users[i], strerror(errno));
+ else
+ printf("ok %d\n", test_num);
+ }
+
+ for (i = 0; i < nitems(test_groups); i++, test_num++) {
+ if (getgrnam(test_groups[i]) == NULL)
+ printf("not ok %d # test_libugidfw_strings: getgrnam(%s) "
+ "failed: %s\n", test_num, test_groups[i], strerror(errno));
+ else
+ printf("ok %d\n", test_num);
+ }
+
+ for (i = 0; i < nitems(test_strings); i++) {
+ error = bsde_parse_rule_string(test_strings[i], &rule,
+ sizeof(errorstr), errorstr);
+ if (error == -1)
+ printf("not ok %d # bsde_parse_rule_string: '%s' (%d) "
+ "failed: %s\n", test_num, test_strings[i], i, errorstr);
+ else
+ printf("ok %d\n", test_num);
+ test_num++;
+
+ error = bsde_rule_to_string(&rule, rulestr, sizeof(rulestr));
+ if (error < 0)
+ printf("not ok %d # bsde_rule_to_string: rule for '%s' "
+ "returned %d\n", test_num, test_strings[i], error);
+ else
+ printf("ok %d\n", test_num);
+ test_num++;
+
+ if (strcmp(test_strings[i], rulestr) != 0)
+ printf("not ok %d # test_libugidfw: '%s' in, '%s' "
+ "out\n", test_num, test_strings[i], rulestr);
+ else
+ printf("ok %d\n", test_num);
+ test_num++;
+ }
+}
+
+int
+main(void)
+{
+ char errorstr[256];
+ int count, slots;
+
+ test_num = 1;
+
+ /* Print an error if a non-root user attemps to run the tests. */
+ if (getuid() != 0) {
+ printf("1..0 # SKIP you must be root\n");
+ return (0);
+ }
+
+ switch (mac_is_present("bsdextended")) {
+ case -1:
+ printf("1..0 # SKIP mac_is_present failed: %s\n",
+ strerror(errno));
+ return (0);
+ case 1:
+ break;
+ case 0:
+ default:
+ printf("1..0 # SKIP mac_bsdextended not loaded\n");
+ return (0);
+ }
+
+ printf("1..%lu\n", nitems(test_users) + nitems(test_groups) +
+ 3 * nitems(test_strings) + 2);
+
+ test_libugidfw_strings();
+
+ /*
+ * Some simple up-front checks to see if we're able to query the
+ * policy for basic state. We want the rule count to be 0 before
+ * starting, but "slots" is a property of prior runs and so we ignore
+ * the return value.
+ */
+ count = bsde_get_rule_count(sizeof(errorstr), errorstr);
+ if (count == -1)
+ printf("not ok %d # bsde_get_rule_count: %s\n", test_num,
+ errorstr);
+ else
+ printf("ok %d\n", test_num);
+
+ test_num++;
+
+ slots = bsde_get_rule_slots(sizeof(errorstr), errorstr);
+ if (slots == -1)
+ printf("not ok %d # bsde_get_rule_slots: %s\n", test_num,
+ errorstr);
+ else
+ printf("ok %d\n", test_num);
+
+ return (0);
+}
diff --git a/tests/sys/mac/portacl/LICENSE b/tests/sys/mac/portacl/LICENSE
new file mode 100644
index 000000000000..c95c149304ce
--- /dev/null
+++ b/tests/sys/mac/portacl/LICENSE
@@ -0,0 +1,27 @@
+$FreeBSD$
+
+License for all mac_portacl regression tests:
+
+Copyright (c) 2009 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/tests/sys/mac/portacl/Makefile b/tests/sys/mac/portacl/Makefile
new file mode 100644
index 000000000000..6564ef93cdd1
--- /dev/null
+++ b/tests/sys/mac/portacl/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/mac/portacl
+
+${PACKAGE}FILES+= misc.sh
+
+TAP_TESTS_SH+= nobody_test
+TAP_TESTS_SH+= root_test
+
+.for t in ${TAP_TESTS_SH}
+TEST_METADATA.$t+= required_user="root"
+TEST_METADATA.$t+= timeout="450"
+.endfor
+
+.include <bsd.test.mk>
diff --git a/tests/sys/mac/portacl/Makefile.depend b/tests/sys/mac/portacl/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/mac/portacl/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/mac/portacl/misc.sh b/tests/sys/mac/portacl/misc.sh
new file mode 100755
index 000000000000..5a9e67bae74e
--- /dev/null
+++ b/tests/sys/mac/portacl/misc.sh
@@ -0,0 +1,106 @@
+#!/bin/sh
+# $FreeBSD$
+
+sysctl security.mac.portacl >/dev/null 2>&1
+if [ $? -ne 0 ]; then
+ echo "1..0 # SKIP MAC_PORTACL is unavailable."
+ exit 0
+fi
+if [ $(id -u) -ne 0 ]; then
+ echo "1..0 # SKIP testcases must be run as root"
+ exit 0
+fi
+
+ntest=1
+
+check_bind() {
+ local host idtype name proto port udpflag
+
+ host="127.0.0.1"
+
+ idtype=${1}
+ name=${2}
+ proto=${3}
+ port=${4}
+
+ [ "${proto}" = "udp" ] && udpflag="-u"
+
+ out=$(
+ case "${idtype}" in
+ uid|gid)
+ ( echo -n | su -m ${name} -c "nc ${udpflag} -l -w 10 $host $port" 2>&1 ) &
+ ;;
+ jail)
+ kill $$
+ ;;
+ *)
+ kill $$
+ esac
+ sleep 0.3
+ echo | nc ${udpflag} -w 10 $host $port >/dev/null 2>&1
+ wait
+ )
+ case "${out}" in
+ "nc: Permission denied"*|"nc: Operation not permitted"*)
+ echo fl
+ ;;
+ "")
+ echo ok
+ ;;
+ *)
+ echo ${out}
+ ;;
+ esac
+}
+
+bind_test() {
+ local expect_without_rule expect_with_rule idtype name proto port
+
+ expect_without_rule=${1}
+ expect_with_rule=${2}
+ idtype=${3}
+ name=${4}
+ proto=${5}
+ port=${6}
+
+ sysctl security.mac.portacl.rules= >/dev/null
+ out=$(check_bind ${idtype} ${name} ${proto} ${port})
+ if [ "${out}" = "${expect_without_rule}" ]; then
+ echo "ok ${ntest}"
+ elif [ "${out}" = "ok" -o "${out}" = "fl" ]; then
+ echo "not ok ${ntest} # '${out}' != '${expect_without_rule}'"
+ else
+ echo "not ok ${ntest} # unexpected output: '${out}'"
+ fi
+ : $(( ntest += 1 ))
+
+ if [ "${idtype}" = "uid" ]; then
+ idstr=$(id -u ${name})
+ elif [ "${idtype}" = "gid" ]; then
+ idstr=$(id -g ${name})
+ else
+ idstr=${name}
+ fi
+ sysctl security.mac.portacl.rules=${idtype}:${idstr}:${proto}:${port} >/dev/null
+ out=$(check_bind ${idtype} ${name} ${proto} ${port})
+ if [ "${out}" = "${expect_with_rule}" ]; then
+ echo "ok ${ntest}"
+ elif [ "${out}" = "ok" -o "${out}" = "fl" ]; then
+ echo "not ok ${ntest} # '${out}' != '${expect_with_rule}'"
+ else
+ echo "not ok ${ntest} # unexpected output: '${out}'"
+ fi
+ : $(( ntest += 1 ))
+
+ sysctl security.mac.portacl.rules= >/dev/null
+}
+
+reserved_high=$(sysctl -n net.inet.ip.portrange.reservedhigh)
+suser_exempt=$(sysctl -n security.mac.portacl.suser_exempt)
+port_high=$(sysctl -n security.mac.portacl.port_high)
+
+restore_settings() {
+ sysctl -n net.inet.ip.portrange.reservedhigh=${reserved_high} >/dev/null
+ sysctl -n security.mac.portacl.suser_exempt=${suser_exempt} >/dev/null
+ sysctl -n security.mac.portacl.port_high=${port_high} >/dev/null
+}
diff --git a/tests/sys/mac/portacl/nobody_test.sh b/tests/sys/mac/portacl/nobody_test.sh
new file mode 100755
index 000000000000..7d8dbd6b76b8
--- /dev/null
+++ b/tests/sys/mac/portacl/nobody_test.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+# $FreeBSD$
+
+dir=`dirname $0`
+. ${dir}/misc.sh
+
+echo "1..64"
+
+# security.mac.portacl.suser_exempt value doesn't affect unprivileged users
+# behaviour.
+# mac_portacl has no impact on ports <= net.inet.ip.portrange.reservedhigh.
+
+trap restore_settings EXIT INT TERM
+
+sysctl security.mac.portacl.suser_exempt=1 >/dev/null
+sysctl net.inet.ip.portrange.reservedhigh=78 >/dev/null
+
+bind_test fl fl uid nobody tcp 77
+bind_test ok ok uid nobody tcp 7777
+bind_test fl fl uid nobody udp 77
+bind_test ok ok uid nobody udp 7777
+
+bind_test fl fl gid nobody tcp 77
+bind_test ok ok gid nobody tcp 7777
+bind_test fl fl gid nobody udp 77
+bind_test ok ok gid nobody udp 7777
+
+sysctl security.mac.portacl.suser_exempt=0 >/dev/null
+
+bind_test fl fl uid nobody tcp 77
+bind_test ok ok uid nobody tcp 7777
+bind_test fl fl uid nobody udp 77
+bind_test ok ok uid nobody udp 7777
+
+bind_test fl fl gid nobody tcp 77
+bind_test ok ok gid nobody tcp 7777
+bind_test fl fl gid nobody udp 77
+bind_test ok ok gid nobody udp 7777
+
+# Verify if security.mac.portacl.port_high works.
+
+sysctl security.mac.portacl.port_high=7778 >/dev/null
+
+bind_test fl fl uid nobody tcp 77
+bind_test fl ok uid nobody tcp 7777
+bind_test fl fl uid nobody udp 77
+bind_test fl ok uid nobody udp 7777
+
+bind_test fl fl gid nobody tcp 77
+bind_test fl ok gid nobody tcp 7777
+bind_test fl fl gid nobody udp 77
+bind_test fl ok gid nobody udp 7777
+
+# Verify if mac_portacl rules work.
+
+sysctl net.inet.ip.portrange.reservedhigh=76 >/dev/null
+sysctl security.mac.portacl.port_high=7776 >/dev/null
+
+bind_test fl ok uid nobody tcp 77
+bind_test ok ok uid nobody tcp 7777
+bind_test fl ok uid nobody udp 77
+bind_test ok ok uid nobody udp 7777
+
+bind_test fl ok gid nobody tcp 77
+bind_test ok ok gid nobody tcp 7777
+bind_test fl ok gid nobody udp 77
+bind_test ok ok gid nobody udp 7777
diff --git a/tests/sys/mac/portacl/root_test.sh b/tests/sys/mac/portacl/root_test.sh
new file mode 100755
index 000000000000..9ed452fd197a
--- /dev/null
+++ b/tests/sys/mac/portacl/root_test.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+# $FreeBSD$
+
+dir=`dirname $0`
+. ${dir}/misc.sh
+
+echo "1..48"
+
+# Verify if security.mac.portacl.suser_exempt=1 really exempts super-user.
+
+trap restore_settings EXIT INT TERM
+
+sysctl security.mac.portacl.suser_exempt=1 >/dev/null
+
+bind_test ok ok uid root tcp 77
+bind_test ok ok uid root tcp 7777
+bind_test ok ok uid root udp 77
+bind_test ok ok uid root udp 7777
+
+bind_test ok ok gid root tcp 77
+bind_test ok ok gid root tcp 7777
+bind_test ok ok gid root udp 77
+bind_test ok ok gid root udp 7777
+
+# Verify if security.mac.portacl.suser_exempt=0 really doesn't exempt super-user.
+
+sysctl security.mac.portacl.suser_exempt=0 >/dev/null
+
+bind_test fl ok uid root tcp 77
+bind_test ok ok uid root tcp 7777
+bind_test fl ok uid root udp 77
+bind_test ok ok uid root udp 7777
+
+bind_test fl ok gid root tcp 77
+bind_test ok ok gid root tcp 7777
+bind_test fl ok gid root udp 77
+bind_test ok ok gid root udp 7777
+
+# Verify if security.mac.portacl.port_high works for super-user.
+
+sysctl security.mac.portacl.port_high=7778 >/dev/null
+
+bind_test fl ok uid root tcp 77
+bind_test fl ok uid root tcp 7777
+bind_test fl ok uid root udp 77
+bind_test fl ok uid root udp 7777
+
+bind_test fl ok gid root tcp 77
+bind_test fl ok gid root tcp 7777
+bind_test fl ok gid root udp 77
+bind_test fl ok gid root udp 7777
diff --git a/tests/sys/mqueue/Makefile b/tests/sys/mqueue/Makefile
new file mode 100644
index 000000000000..251c497e32e4
--- /dev/null
+++ b/tests/sys/mqueue/Makefile
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/mqueue
+
+ATF_TESTS_SH= mqueue_test
+
+BINDIR= ${TESTSDIR}
+
+CFLAGS+= -I${SRCTOP}/tests
+
+PROGS+= mqtest1
+PROGS+= mqtest2
+PROGS+= mqtest3
+PROGS+= mqtest4
+PROGS+= mqtest5
+
+LIBADD+= rt
+
+WARNS?= 6
+
+.include <bsd.test.mk>
diff --git a/tests/sys/mqueue/Makefile.depend b/tests/sys/mqueue/Makefile.depend
new file mode 100644
index 000000000000..24e69ea5bc4c
--- /dev/null
+++ b/tests/sys/mqueue/Makefile.depend
@@ -0,0 +1,21 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/atf/libatf-c \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/librt \
+ lib/libthr \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/mqueue/mqtest1.c b/tests/sys/mqueue/mqtest1.c
new file mode 100644
index 000000000000..3accb286ae98
--- /dev/null
+++ b/tests/sys/mqueue/mqtest1.c
@@ -0,0 +1,57 @@
+/* $FreeBSD$ */
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <mqueue.h>
+#include <signal.h>
+#include <stdio.h>
+
+#include "freebsd_test_suite/macros.h"
+
+#define MQNAME "/mytstqueue1"
+
+int
+main(void)
+{
+ struct mq_attr attr, attr2;
+ struct sigevent sigev;
+ mqd_t mq;
+ int status;
+
+ PLAIN_REQUIRE_KERNEL_MODULE("mqueuefs", 0);
+
+ attr.mq_maxmsg = 2;
+ attr.mq_msgsize = 100;
+ mq = mq_open(MQNAME, O_CREAT | O_RDWR | O_EXCL, 0666, &attr);
+ if (mq == (mqd_t)-1)
+ err(1, "mq_open");
+ status = mq_unlink(MQNAME);
+ if (status)
+ err(1, "mq_unlink");
+ status = mq_getattr(mq, &attr2);
+ if (status)
+ err(1, "mq_getattr");
+ if (attr.mq_maxmsg != attr2.mq_maxmsg)
+ err(1, "mq_maxmsg changed");
+ if (attr.mq_msgsize != attr2.mq_msgsize)
+ err(1, "mq_msgsize changed");
+
+ sigev.sigev_notify = SIGEV_SIGNAL;
+ sigev.sigev_signo = SIGRTMIN;
+ status = mq_notify(mq, &sigev);
+ if (status)
+ err(1, "mq_notify");
+ status = mq_notify(mq, &sigev);
+ if (status == 0)
+ err(1, "mq_notify 2");
+ else if (errno != EBUSY)
+ err(1, "mq_notify 3");
+ status = mq_notify(mq, NULL);
+ if (status)
+ err(1, "mq_notify 4");
+ status = mq_close(mq);
+ if (status)
+ err(1, "mq_close");
+ return (0);
+}
diff --git a/tests/sys/mqueue/mqtest2.c b/tests/sys/mqueue/mqtest2.c
new file mode 100644
index 000000000000..067e6190d22f
--- /dev/null
+++ b/tests/sys/mqueue/mqtest2.c
@@ -0,0 +1,101 @@
+/* $FreeBSD$ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <err.h>
+#include <fcntl.h>
+#include <mqueue.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "freebsd_test_suite/macros.h"
+
+#define MQNAME "/mytstqueue2"
+#define LOOPS 1000
+#define PRIO 10
+
+static void
+alarmhandler(int sig __unused)
+{
+ write(1, "timeout\n", 8);
+ _exit(1);
+}
+
+int
+main(void)
+{
+ struct mq_attr attr;
+ mqd_t mq;
+ int status;
+ pid_t pid;
+
+ PLAIN_REQUIRE_KERNEL_MODULE("mqueuefs", 0);
+
+ mq_unlink(MQNAME);
+
+ attr.mq_maxmsg = 5;
+ attr.mq_msgsize = 128;
+ mq = mq_open(MQNAME, O_CREAT | O_RDWR | O_EXCL, 0666, &attr);
+ if (mq == (mqd_t)-1)
+ err(1, "mq_open");
+ status = mq_getattr(mq, &attr);
+ if (status)
+ err(1, "mq_getattr");
+ pid = fork();
+ if (pid == 0) { /* child */
+ char *buf;
+ int j, i;
+ unsigned int prio;
+
+ mq_close(mq);
+
+ signal(SIGALRM, alarmhandler);
+
+ mq = mq_open(MQNAME, O_RDWR);
+ if (mq == (mqd_t)-1)
+ err(1, "child: mq_open");
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ alarm(3);
+ status = mq_receive(mq, buf, attr.mq_msgsize, &prio);
+ if (status == -1)
+ err(2, "child: mq_receive");
+ for (i = 0; i < attr.mq_msgsize; ++i)
+ if (buf[i] != i)
+ err(3, "child: message data corrupted");
+ if (prio != PRIO)
+ err(4, "child: priority is incorrect: %d",
+ prio);
+ }
+ alarm(0);
+ free(buf);
+ mq_close(mq);
+ return (0);
+ } else if (pid == -1) {
+ err(1, "fork()");
+ } else {
+ char *buf;
+ int i, j;
+
+ signal(SIGALRM, alarmhandler);
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ for (i = 0; i < attr.mq_msgsize; ++i)
+ buf[i] = i;
+ alarm(3);
+ status = mq_send(mq, buf, attr.mq_msgsize, PRIO);
+ if (status)
+ err(1, "mq_send");
+ }
+ alarm(3);
+ wait(&status);
+ alarm(0);
+ }
+ status = mq_close(mq);
+ if (status)
+ err(1, "mq_close");
+ mq_unlink(MQNAME);
+ return (0);
+}
diff --git a/tests/sys/mqueue/mqtest3.c b/tests/sys/mqueue/mqtest3.c
new file mode 100644
index 000000000000..3e20c4d458d1
--- /dev/null
+++ b/tests/sys/mqueue/mqtest3.c
@@ -0,0 +1,118 @@
+/* $FreeBSD$ */
+
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#include <err.h>
+#include <fcntl.h>
+#include <mqueue.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "freebsd_test_suite/macros.h"
+
+#define MQNAME "/mytstqueue3"
+#define LOOPS 1000
+#define PRIO 10
+
+static void
+sighandler(int sig __unused)
+{
+ write(1, "timeout\n", 8);
+ _exit(1);
+}
+
+int
+main(void)
+{
+ fd_set set;
+ struct mq_attr attr;
+ int status;
+ mqd_t mq;
+ pid_t pid;
+
+ PLAIN_REQUIRE_KERNEL_MODULE("mqueuefs", 0);
+
+ mq_unlink(MQNAME);
+
+ attr.mq_maxmsg = 5;
+ attr.mq_msgsize = 128;
+ mq = mq_open(MQNAME, O_CREAT | O_RDWR | O_EXCL, 0666, &attr);
+ if (mq == (mqd_t)-1)
+ err(1, "mq_open()");
+ status = mq_getattr(mq, &attr);
+ if (status)
+ err(1, "mq_getattr()");
+
+ pid = fork();
+ if (pid == 0) { /* child */
+ char *buf;
+ int j, i;
+ unsigned int prio;
+
+ mq_close(mq);
+
+ signal(SIGALRM, sighandler);
+
+ mq = mq_open(MQNAME, O_RDWR);
+ if (mq == (mqd_t)-1)
+ err(1, "child process: mq_open");
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ FD_ZERO(&set);
+ FD_SET(mq_getfd_np(mq), &set);
+ alarm(3);
+ status = select(mq_getfd_np(mq) + 1, &set, NULL,
+ NULL, NULL);
+ if (status != 1)
+ err(1, "child process: select()");
+ status = mq_receive(mq, buf, attr.mq_msgsize, &prio);
+ if (status == -1)
+ err(2, "child process: mq_receive");
+ for (i = 0; i < attr.mq_msgsize; ++i)
+ if (buf[i] != i)
+ err(3, "message data corrupted");
+ if (prio != PRIO)
+ err(4, "priority is incorrect: %d", prio);
+ }
+ alarm(0);
+ free(buf);
+ mq_close(mq);
+ return (0);
+ } else if (pid == -1) {
+ err(1, "fork()");
+ } else {
+ char *buf;
+ int i, j;
+
+ signal(SIGALRM, sighandler);
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ for (i = 0; i < attr.mq_msgsize; ++i) {
+ buf[i] = i;
+ }
+ alarm(3);
+ FD_ZERO(&set);
+ FD_SET(mq_getfd_np(mq), &set);
+ status = select(mq_getfd_np(mq) + 1, NULL, &set,
+ NULL, NULL);
+ if (status != 1)
+ err(1, "select()");
+ status = mq_send(mq, buf, attr.mq_msgsize, PRIO);
+ if (status) {
+ kill(pid, SIGKILL);
+ err(2, "mq_send()");
+ }
+ }
+ alarm(3);
+ wait(&status);
+ alarm(0);
+ }
+ status = mq_close(mq);
+ if (status)
+ err(1, "mq_close");
+ mq_unlink(MQNAME);
+ return (0);
+}
diff --git a/tests/sys/mqueue/mqtest4.c b/tests/sys/mqueue/mqtest4.c
new file mode 100644
index 000000000000..b0b395298ef8
--- /dev/null
+++ b/tests/sys/mqueue/mqtest4.c
@@ -0,0 +1,120 @@
+/* $FreeBSD$ */
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#include <err.h>
+#include <fcntl.h>
+#include <mqueue.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "freebsd_test_suite/macros.h"
+
+#define MQNAME "/mytstqueue4"
+#define LOOPS 1000
+#define PRIO 10
+
+static void
+sighandler(int sig __unused)
+{
+ write(1, "timeout\n", 8);
+ _exit(1);
+}
+
+int
+main(void)
+{
+ struct kevent kev;
+ struct mq_attr attr;
+ mqd_t mq;
+ int kq, status;
+ pid_t pid;
+
+ PLAIN_REQUIRE_KERNEL_MODULE("mqueuefs", 0);
+
+ mq_unlink(MQNAME);
+
+ attr.mq_maxmsg = 5;
+ attr.mq_msgsize = 128;
+ mq = mq_open(MQNAME, O_CREAT | O_RDWR | O_EXCL, 0666, &attr);
+ if (mq == (mqd_t) -1)
+ err(1, "mq_open()");
+ status = mq_getattr(mq, &attr);
+ if (status)
+ err(1, "mq_getattr()");
+ pid = fork();
+ if (pid == 0) { /* child */
+ char *buf;
+ int j, i;
+ unsigned int prio;
+
+ mq_close(mq);
+ kq = kqueue();
+ mq = mq_open(MQNAME, O_RDWR);
+ if (mq == (mqd_t)-1)
+ err(1, "child: mq_open");
+ EV_SET(&kev, mq_getfd_np(mq), EVFILT_READ, EV_ADD, 0, 0, 0);
+ status = kevent(kq, &kev, 1, NULL, 0, NULL);
+ if (status == -1)
+ err(1, "child: kevent");
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ alarm(3);
+ status = kevent(kq, NULL, 0, &kev, 1, NULL);
+ if (status != 1)
+ err(1, "child: kevent 2");
+ status = mq_receive(mq, buf, attr.mq_msgsize, &prio);
+ if (status == -1)
+ err(2, "child: mq_receive");
+ for (i = 0; i < attr.mq_msgsize; ++i)
+ if (buf[i] != i)
+ err(3, "child: message data corrupted");
+ if (prio != PRIO)
+ err(4, "child: priority is incorrect: %d",
+ prio);
+ }
+ alarm(0);
+ free(buf);
+ mq_close(mq);
+ return (0);
+ } else if (pid == -1) {
+ err(1, "fork()");
+ } else {
+ char *buf;
+ int i, j;
+
+ signal(SIGALRM, sighandler);
+ kq = kqueue();
+ EV_SET(&kev, mq_getfd_np(mq), EVFILT_WRITE, EV_ADD, 0, 0, 0);
+ status = kevent(kq, &kev, 1, NULL, 0, NULL);
+ if (status == -1)
+ err(1, "kevent");
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ for (i = 0; i < attr.mq_msgsize; ++i) {
+ buf[i] = i;
+ }
+ alarm(3);
+ status = kevent(kq, NULL, 0, &kev, 1, NULL);
+ if (status != 1)
+ err(1, "child: kevent 2");
+ status = mq_send(mq, buf, attr.mq_msgsize, PRIO);
+ if (status) {
+ err(2, "mq_send()");
+ }
+ }
+ free(buf);
+ alarm(3);
+ wait(&status);
+ alarm(0);
+ }
+ status = mq_close(mq);
+ if (status)
+ err(1, "mq_close");
+ mq_unlink(MQNAME);
+ return (0);
+}
diff --git a/tests/sys/mqueue/mqtest5.c b/tests/sys/mqueue/mqtest5.c
new file mode 100644
index 000000000000..0c8aa895ead2
--- /dev/null
+++ b/tests/sys/mqueue/mqtest5.c
@@ -0,0 +1,128 @@
+/* $FreeBSD$ */
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#include <err.h>
+#include <fcntl.h>
+#include <mqueue.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "freebsd_test_suite/macros.h"
+
+#define MQNAME "/mytstqueue5"
+#define LOOPS 1000
+#define PRIO 10
+
+static void
+sighandler(int sig __unused)
+{
+ write(1, "timeout\n", 8);
+ _exit(1);
+}
+
+int
+main(void)
+{
+ int status;
+ struct mq_attr attr;
+ struct sigaction sa;
+ sigset_t set;
+ siginfo_t info;
+ mqd_t mq;
+ pid_t pid;
+
+ PLAIN_REQUIRE_KERNEL_MODULE("mqueuefs", 0);
+
+ mq_unlink(MQNAME);
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGRTMIN);
+ sigprocmask(SIG_BLOCK, &set, NULL);
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_SIGINFO;
+ sa.sa_sigaction = (void *) SIG_DFL;
+ sigaction(SIGRTMIN, &sa, NULL);
+
+ attr.mq_maxmsg = 5;
+ attr.mq_msgsize = 128;
+ mq = mq_open(MQNAME, O_CREAT | O_RDWR | O_EXCL, 0666, &attr);
+ if (mq == (mqd_t)-1)
+ err(1, "mq_open()");
+ status = mq_getattr(mq, &attr);
+ if (status)
+ err(1, "mq_getattr()");
+ pid = fork();
+ if (pid == 0) { /* child */
+ int prio, j, i;
+ char *buf;
+ struct sigevent sigev;
+
+ signal(SIGALRM, sighandler);
+
+ sigev.sigev_notify = SIGEV_SIGNAL;
+ sigev.sigev_signo = SIGRTMIN;
+ sigev.sigev_value.sival_int = 2;
+
+ mq_close(mq);
+ mq = mq_open(MQNAME, O_RDWR | O_NONBLOCK);
+ if (mq == (mqd_t)-1)
+ err(1, "child: mq_open");
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ alarm(3);
+ status = mq_notify(mq, &sigev);
+ if (status)
+ err(1, "child: mq_notify");
+ status = sigwaitinfo(&set, &info);
+ if (status == -1)
+ err(1, "child: sigwaitinfo");
+ if (info.si_value.sival_int != 2)
+ err(1, "child: sival_int");
+ status = mq_receive(mq, buf, attr.mq_msgsize, &prio);
+ if (status == -1)
+ err(2, "child: mq_receive");
+ for (i = 0; i < attr.mq_msgsize; ++i)
+ if (buf[i] != i)
+ err(3, "child: message data corrupted");
+ if (prio != PRIO)
+ err(4, "child: priority is incorrect: %d",
+ prio);
+ }
+ alarm(0);
+ free(buf);
+ mq_close(mq);
+ return (0);
+ } else if (pid == -1) {
+ err(1, "fork()");
+ } else {
+ char *buf;
+ int i, j;
+
+ signal(SIGALRM, sighandler);
+ buf = malloc(attr.mq_msgsize);
+ for (j = 0; j < LOOPS; ++j) {
+ for (i = 0; i < attr.mq_msgsize; ++i) {
+ buf[i] = i;
+ }
+ alarm(3);
+ status = mq_send(mq, buf, attr.mq_msgsize, PRIO);
+ if (status) {
+ kill(pid, SIGKILL);
+ err(2, "mq_send()");
+ }
+ }
+ alarm(3);
+ wait(&status);
+ alarm(0);
+ }
+ status = mq_close(mq);
+ if (status)
+ err(1, "mq_close");
+ mq_unlink(MQNAME);
+ return (0);
+}
diff --git a/tests/sys/mqueue/mqueue_test.sh b/tests/sys/mqueue/mqueue_test.sh
new file mode 100755
index 000000000000..9a426eeda98c
--- /dev/null
+++ b/tests/sys/mqueue/mqueue_test.sh
@@ -0,0 +1,81 @@
+#
+# Copyright (c) 2015 EMC / Isilon Storage Division
+# 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 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 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$
+#
+
+mqtest1_head()
+{
+ :
+}
+mqtest1_body()
+{
+ atf_check -s exit:0 -x $(atf_get_srcdir)/mqtest1
+}
+
+mqtest2_head()
+{
+ :
+}
+mqtest2_body()
+{
+ atf_check -s exit:0 -x $(atf_get_srcdir)/mqtest2
+}
+
+mqtest3_head()
+{
+ :
+}
+mqtest3_body()
+{
+ atf_check -s exit:0 -x $(atf_get_srcdir)/mqtest3
+}
+
+mqtest4_head()
+{
+ :
+}
+mqtest4_body()
+{
+ atf_check -s exit:0 -x $(atf_get_srcdir)/mqtest4
+}
+
+mqtest5_head()
+{
+ :
+}
+mqtest5_body()
+{
+ atf_check -s exit:0 -x $(atf_get_srcdir)/mqtest5
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case mqtest1
+ atf_add_test_case mqtest2
+ #atf_add_test_case mqtest3
+ #atf_add_test_case mqtest4
+ atf_add_test_case mqtest5
+}
diff --git a/tests/sys/netinet/Makefile b/tests/sys/netinet/Makefile
new file mode 100644
index 000000000000..9e1150ebf61b
--- /dev/null
+++ b/tests/sys/netinet/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/netinet
+BINDIR= ${TESTSDIR}
+
+ATF_TESTS_SH= fibs_test
+
+PROGS= udp_dontroute tcp_user_cookie
+
+MAN=
+
+WARNS?= 6
+
+.include <bsd.test.mk>
diff --git a/tests/sys/netinet/Makefile.depend b/tests/sys/netinet/Makefile.depend
new file mode 100644
index 000000000000..54c1f6f1442e
--- /dev/null
+++ b/tests/sys/netinet/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/netinet/fibs_test.sh b/tests/sys/netinet/fibs_test.sh
new file mode 100755
index 000000000000..70b53e81e8da
--- /dev/null
+++ b/tests/sys/netinet/fibs_test.sh
@@ -0,0 +1,488 @@
+#
+# Copyright (c) 2014 Spectra Logic Corporation
+# 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,
+# without modification.
+# 2. Redistributions in binary form must reproduce at minimum a disclaimer
+# substantially similar to the "NO WARRANTY" disclaimer below
+# ("Disclaimer") and any redistribution must be conditioned upon
+# including a substantially similar Disclaimer requirement for further
+# binary redistribution.
+#
+# NO WARRANTY
+# 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 MERCHANTIBILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+#
+# Authors: Alan Somers (Spectra Logic Corporation)
+#
+# $FreeBSD$
+
+# All of the tests in this file requires the test-suite config variable "fibs"
+# to be defined to a space-delimited list of FIBs that may be used for testing.
+
+# arpresolve should check the interface fib for routes to a target when
+# creating an ARP table entry. This is a regression for kern/167947, where
+# arpresolve only checked the default route.
+#
+# Outline:
+# Create two tap(4) interfaces
+# Simulate a crossover cable between them by using net/socat
+# Use nping (from security/nmap) to send an ICMP echo request from one
+# interface to the other, spoofing the source IP. The source IP must be
+# spoofed, or else it will already have an entry in the arp table.
+# Check whether an arp entry exists for the spoofed IP
+atf_test_case arpresolve_checks_interface_fib cleanup
+arpresolve_checks_interface_fib_head()
+{
+ atf_set "descr" "arpresolve should check the interface fib, not the default fib, for routes"
+ atf_set "require.user" "root"
+ atf_set "require.config" "fibs"
+ atf_set "require.progs" "socat nping"
+}
+arpresolve_checks_interface_fib_body()
+{
+ # Configure the TAP interfaces to use a RFC5737 nonrouteable addresses
+ # and a non-default fib
+ ADDR0="192.0.2.2"
+ ADDR1="192.0.2.3"
+ SUBNET="192.0.2.0"
+ # Due to bug TBD (regressed by multiple_fibs_on_same_subnet) we need
+ # diffferent subnet masks, or FIB1 won't have a subnet route.
+ MASK0="24"
+ MASK1="25"
+ # Spoof a MAC that is reserved per RFC7042
+ SPOOF_ADDR="192.0.2.4"
+ SPOOF_MAC="00:00:5E:00:53:00"
+
+ # Check system configuration
+ if [ 0 != `sysctl -n net.add_addr_allfibs` ]; then
+ atf_skip "This test requires net.add_addr_allfibs=0"
+ fi
+ get_fibs 2
+
+ # Configure TAP interfaces
+ setup_tap "$FIB0" ${ADDR0} ${MASK0}
+ TAP0=$TAP
+ setup_tap "$FIB1" ${ADDR1} ${MASK1}
+ TAP1=$TAP
+
+ # Simulate a crossover cable
+ socat /dev/${TAP0} /dev/${TAP1} &
+ SOCAT_PID=$!
+ echo ${SOCAT_PID} >> "processes_to_kill"
+
+ # Send an ICMP echo request with a spoofed source IP
+ setfib 2 nping -c 1 -e ${TAP0} -S ${SPOOF_ADDR} \
+ --source-mac ${SPOOF_MAC} --icmp --icmp-type "echo-request" \
+ --icmp-code 0 --icmp-id 0xdead --icmp-seq 1 --data 0xbeef \
+ ${ADDR1}
+ # For informational and debugging purposes only, look for the
+ # characteristic error message
+ dmesg | grep "llinfo.*${SPOOF_ADDR}"
+ # Check that the ARP entry exists
+ atf_check -o match:"${SPOOF_ADDR}.*expires" setfib 3 arp ${SPOOF_ADDR}
+}
+arpresolve_checks_interface_fib_cleanup()
+{
+ if [ -f processes_to_kill ]; then
+ for pid in $(cat processes_to_kill); do
+ kill "${pid}"
+ done
+ rm -f processes_to_kill
+ fi
+ cleanup_tap
+}
+
+
+# Regression test for kern/187549
+atf_test_case loopback_and_network_routes_on_nondefault_fib cleanup
+loopback_and_network_routes_on_nondefault_fib_head()
+{
+ atf_set "descr" "When creating and deleting loopback routes, use the interface's fib"
+ atf_set "require.user" "root"
+ atf_set "require.config" "fibs"
+}
+
+loopback_and_network_routes_on_nondefault_fib_body()
+{
+ # Configure the TAP interface to use an RFC5737 nonrouteable address
+ # and a non-default fib
+ ADDR="192.0.2.2"
+ SUBNET="192.0.2.0"
+ MASK="24"
+
+ # Check system configuration
+ if [ 0 != `sysctl -n net.add_addr_allfibs` ]; then
+ atf_skip "This test requires net.add_addr_allfibs=0"
+ fi
+ get_fibs 1
+
+ # Configure a TAP interface
+ setup_tap ${FIB0} ${ADDR} ${MASK}
+
+ # Check whether the host route exists in only the correct FIB
+ setfib ${FIB0} netstat -rn -f inet | grep -q "^${ADDR}.*UHS.*lo0"
+ if [ 0 -ne $? ]; then
+ setfib ${FIB0} netstat -rn -f inet
+ atf_fail "Host route did not appear in the correct FIB"
+ fi
+ setfib 0 netstat -rn -f inet | grep -q "^${ADDR}.*UHS.*lo0"
+ if [ 0 -eq $? ]; then
+ setfib 0 netstat -rn -f inet
+ atf_fail "Host route appeared in the wrong FIB"
+ fi
+
+ # Check whether the network route exists in only the correct FIB
+ setfib ${FIB0} netstat -rn -f inet | \
+ grep -q "^${SUBNET}/${MASK}.*${TAPD}"
+ if [ 0 -ne $? ]; then
+ setfib ${FIB0} netstat -rn -f inet
+ atf_fail "Network route did not appear in the correct FIB"
+ fi
+ setfib 0 netstat -rn -f inet | \
+ grep -q "^${SUBNET}/${MASK}.*${TAPD}"
+ if [ 0 -eq $? ]; then
+ setfib ${FIB0} netstat -rn -f inet
+ atf_fail "Network route appeared in the wrong FIB"
+ fi
+}
+
+loopback_and_network_routes_on_nondefault_fib_cleanup()
+{
+ cleanup_tap
+}
+
+
+# Regression test for kern/187552
+atf_test_case default_route_with_multiple_fibs_on_same_subnet cleanup
+default_route_with_multiple_fibs_on_same_subnet_head()
+{
+ atf_set "descr" "Multiple interfaces on the same subnet but with different fibs can both have default routes"
+ atf_set "require.user" "root"
+ atf_set "require.config" "fibs"
+}
+
+default_route_with_multiple_fibs_on_same_subnet_body()
+{
+ # Configure the TAP interfaces to use a RFC5737 nonrouteable addresses
+ # and a non-default fib
+ ADDR0="192.0.2.2"
+ ADDR1="192.0.2.3"
+ GATEWAY="192.0.2.1"
+ SUBNET="192.0.2.0"
+ MASK="24"
+
+ # Check system configuration
+ if [ 0 != `sysctl -n net.add_addr_allfibs` ]; then
+ atf_skip "This test requires net.add_addr_allfibs=0"
+ fi
+ get_fibs 2
+
+ # Configure TAP interfaces
+ setup_tap "$FIB0" ${ADDR0} ${MASK}
+ TAP0=$TAP
+ setup_tap "$FIB1" ${ADDR1} ${MASK}
+ TAP1=$TAP
+
+ # Attempt to add default routes
+ setfib ${FIB0} route add default ${GATEWAY}
+ setfib ${FIB1} route add default ${GATEWAY}
+
+ # Verify that the default route exists for both fibs, with their
+ # respective interfaces.
+ atf_check -o match:"^default.*${TAP0}$" \
+ setfib ${FIB0} netstat -rn -f inet
+ atf_check -o match:"^default.*${TAP1}$" \
+ setfib ${FIB1} netstat -rn -f inet
+}
+
+default_route_with_multiple_fibs_on_same_subnet_cleanup()
+{
+ cleanup_tap
+}
+
+
+# Regression test for PR kern/189089
+# Create two tap interfaces and assign them both the same IP address but with
+# different netmasks, and both on the default FIB. Then remove one's IP
+# address. Hopefully the machine won't panic.
+atf_test_case same_ip_multiple_ifaces_fib0 cleanup
+same_ip_multiple_ifaces_fib0_head()
+{
+ atf_set "descr" "Can remove an IP alias from an interface when the same IP is also assigned to another interface."
+ atf_set "require.user" "root"
+ atf_set "require.config" "fibs"
+}
+same_ip_multiple_ifaces_fib0_body()
+{
+ ADDR="192.0.2.2"
+ MASK0="24"
+ MASK1="32"
+
+ # Unlike most of the tests in this file, this is applicable regardless
+ # of net.add_addr_allfibs
+
+ # Setup the interfaces, then remove one alias. It should not panic.
+ setup_tap 0 ${ADDR} ${MASK0}
+ TAP0=${TAP}
+ setup_tap 0 ${ADDR} ${MASK1}
+ TAP1=${TAP}
+ ifconfig ${TAP1} -alias ${ADDR}
+
+ # Do it again, in the opposite order. It should not panic.
+ setup_tap 0 ${ADDR} ${MASK0}
+ TAP0=${TAP}
+ setup_tap 0 ${ADDR} ${MASK1}
+ TAP1=${TAP}
+ ifconfig ${TAP0} -alias ${ADDR}
+}
+same_ip_multiple_ifaces_fib0_cleanup()
+{
+ cleanup_tap
+}
+
+# Regression test for PR kern/189088
+# Test that removing an IP address works even if the same IP is assigned to a
+# different interface, on a different FIB. Tests the same code that whose
+# panic was regressed by same_ip_multiple_ifaces_fib0.
+# Create two tap interfaces and assign them both the same IP address but with
+# different netmasks, and on different FIBs. Then remove one's IP
+# address. Hopefully the machine won't panic. Also, the IP's hostroute should
+# dissappear from the correct fib.
+atf_test_case same_ip_multiple_ifaces cleanup
+same_ip_multiple_ifaces_head()
+{
+ atf_set "descr" "Can remove an IP alias from an interface when the same IP is also assigned to another interface, on non-default FIBs."
+ atf_set "require.user" "root"
+ atf_set "require.config" "fibs"
+}
+same_ip_multiple_ifaces_body()
+{
+ atf_expect_fail "kern/189088 Assigning the same IP to multiple interfaces in different FIBs creates a host route for only one"
+ ADDR="192.0.2.2"
+ MASK0="24"
+ MASK1="32"
+
+ # Unlike most of the tests in this file, this is applicable regardless
+ # of net.add_addr_allfibs
+ get_fibs 2
+
+ # Setup the interfaces, then remove one alias. It should not panic.
+ setup_tap ${FIB0} ${ADDR} ${MASK0}
+ TAP0=${TAP}
+ setup_tap ${FIB1} ${ADDR} ${MASK1}
+ TAP1=${TAP}
+ ifconfig ${TAP1} -alias ${ADDR}
+ atf_check -o not-match:"^${ADDR}[[:space:]]" \
+ setfib ${FIB1} netstat -rn -f inet
+
+ # Do it again, in the opposite order. It should not panic.
+ setup_tap ${FIB0} ${ADDR} ${MASK0}
+ TAP0=${TAP}
+ setup_tap ${FIB1} ${ADDR} ${MASK1}
+ TAP1=${TAP}
+ ifconfig ${TAP0} -alias ${ADDR}
+ atf_check -o not-match:"^${ADDR}[[:space:]]" \
+ setfib ${FIB0} netstat -rn -f inet
+}
+same_ip_multiple_ifaces_cleanup()
+{
+ # Due to PR kern/189088, we must destroy the interfaces in LIFO order
+ # in order for the routes to be correctly cleaned up.
+ for TAPD in `tail -r "tap_devices_to_cleanup"`; do
+ ifconfig ${TAPD} destroy
+ done
+}
+
+# Regression test for kern/187550
+atf_test_case subnet_route_with_multiple_fibs_on_same_subnet cleanup
+subnet_route_with_multiple_fibs_on_same_subnet_head()
+{
+ atf_set "descr" "Multiple FIBs can have subnet routes for the same subnet"
+ atf_set "require.user" "root"
+ atf_set "require.config" "fibs"
+}
+
+subnet_route_with_multiple_fibs_on_same_subnet_body()
+{
+ # Configure the TAP interfaces to use a RFC5737 nonrouteable addresses
+ # and a non-default fib
+ ADDR0="192.0.2.2"
+ ADDR1="192.0.2.3"
+ SUBNET="192.0.2.0"
+ MASK="24"
+
+ # Check system configuration
+ if [ 0 != `sysctl -n net.add_addr_allfibs` ]; then
+ atf_skip "This test requires net.add_addr_allfibs=0"
+ fi
+ get_fibs 2
+
+ # Configure TAP interfaces
+ setup_tap "$FIB0" ${ADDR0} ${MASK}
+ setup_tap "$FIB1" ${ADDR1} ${MASK}
+
+ # Check that a subnet route exists on both fibs
+ atf_check -o ignore setfib "$FIB0" route get $ADDR1
+ atf_check -o ignore setfib "$FIB1" route get $ADDR0
+}
+
+subnet_route_with_multiple_fibs_on_same_subnet_cleanup()
+{
+ cleanup_tap
+}
+
+# Test that source address selection works correctly for UDP packets with
+# SO_DONTROUTE set that are sent on non-default FIBs.
+# This bug was discovered with "setfib 1 netperf -t UDP_STREAM -H some_host"
+# Regression test for kern/187553
+#
+# The root cause was that ifa_ifwithnet() did not have a fib argument. It
+# would return an address from an interface on any FIB that had a subnet route
+# for the destination. If more than one were available, it would choose the
+# most specific. This is most easily tested by creating a FIB without a
+# default route, then trying to send a UDP packet with SO_DONTROUTE set to an
+# address which is not routable on that FIB. Absent the fix for this bug,
+# in_pcbladdr would choose an interface on any FIB with a default route. With
+# the fix, you will get EUNREACH or ENETUNREACH.
+atf_test_case udp_dontroute cleanup
+udp_dontroute_head()
+{
+ atf_set "descr" "Source address selection for UDP packets with SO_DONTROUTE on non-default FIBs works"
+ atf_set "require.user" "root"
+ atf_set "require.config" "fibs"
+}
+
+udp_dontroute_body()
+{
+ # Configure the TAP interface to use an RFC5737 nonrouteable address
+ # and a non-default fib
+ ADDR0="192.0.2.2"
+ ADDR1="192.0.2.3"
+ SUBNET="192.0.2.0"
+ MASK="24"
+ # Use a different IP on the same subnet as the target
+ TARGET="192.0.2.100"
+ SRCDIR=`atf_get_srcdir`
+
+ # Check system configuration
+ if [ 0 != `sysctl -n net.add_addr_allfibs` ]; then
+ atf_skip "This test requires net.add_addr_allfibs=0"
+ fi
+ get_fibs 2
+
+ # Configure the TAP interfaces
+ setup_tap ${FIB0} ${ADDR0} ${MASK}
+ TARGET_TAP=${TAP}
+ setup_tap ${FIB1} ${ADDR1} ${MASK}
+
+ # Send a UDP packet with SO_DONTROUTE. In the failure case, it will
+ # return ENETUNREACH, or send the packet to the wrong tap
+ atf_check -o ignore setfib ${FIB0} \
+ ${SRCDIR}/udp_dontroute ${TARGET} /dev/${TARGET_TAP}
+ cleanup_tap
+
+ # Repeat, but this time target the other tap
+ setup_tap ${FIB0} ${ADDR0} ${MASK}
+ setup_tap ${FIB1} ${ADDR1} ${MASK}
+ TARGET_TAP=${TAP}
+
+ atf_check -o ignore setfib ${FIB1} \
+ ${SRCDIR}/udp_dontroute ${TARGET} /dev/${TARGET_TAP}
+}
+
+udp_dontroute_cleanup()
+{
+ cleanup_tap
+}
+
+
+atf_init_test_cases()
+{
+ atf_add_test_case arpresolve_checks_interface_fib
+ atf_add_test_case loopback_and_network_routes_on_nondefault_fib
+ atf_add_test_case default_route_with_multiple_fibs_on_same_subnet
+ atf_add_test_case same_ip_multiple_ifaces_fib0
+ atf_add_test_case same_ip_multiple_ifaces
+ atf_add_test_case subnet_route_with_multiple_fibs_on_same_subnet
+ atf_add_test_case udp_dontroute
+}
+
+# Looks up one or more fibs from the configuration data and validates them.
+# Returns the results in the env varilables FIB0, FIB1, etc.
+
+# parameter numfibs The number of fibs to lookup
+get_fibs()
+{
+ NUMFIBS=$1
+ net_fibs=`sysctl -n net.fibs`
+ i=0
+ while [ $i -lt "$NUMFIBS" ]; do
+ fib=`atf_config_get "fibs" | \
+ awk -v i=$(( i + 1 )) '{print $i}'`
+ echo "fib is ${fib}"
+ eval FIB${i}=${fib}
+ if [ "$fib" -ge "$net_fibs" ]; then
+ atf_skip "The ${i}th configured fib is ${fib}, which is not less than net.fibs, which is ${net_fibs}"
+ fi
+ i=$(( $i + 1 ))
+ done
+}
+
+# Creates a new tap(4) interface, registers it for cleanup, and returns the
+# name via the environment variable TAP
+get_tap()
+{
+ local TAPN=0
+ while ! ifconfig tap${TAPN} create > /dev/null 2>&1; do
+ if [ "$TAPN" -ge 8 ]; then
+ atf_skip "Could not create a tap(4) interface"
+ else
+ TAPN=$(($TAPN + 1))
+ fi
+ done
+ local TAPD=tap${TAPN}
+ # Record the TAP device so we can clean it up later
+ echo ${TAPD} >> "tap_devices_to_cleanup"
+ TAP=${TAPD}
+}
+
+# Create a tap(4) interface, configure it, and register it for cleanup.
+# parameters:
+# fib
+# IP address
+# Netmask in number of bits (eg 24 or 8)
+# Return: the tap interface name as the env variable TAP
+setup_tap()
+{
+ local FIB=$1
+ local ADDR=$2
+ local MASK=$3
+ get_tap
+ echo setfib ${FIB} ifconfig $TAP ${ADDR}/${MASK} fib $FIB
+ setfib ${FIB} ifconfig $TAP ${ADDR}/${MASK} fib $FIB
+}
+
+cleanup_tap()
+{
+ if [ -f tap_devices_to_cleanup ]; then
+ for tap_device in $(cat tap_devices_to_cleanup); do
+ ifconfig "${tap_device}" destroy
+ done
+ rm -f tap_devices_to_cleanup
+ fi
+}
diff --git a/tests/sys/netinet/tcp_user_cookie.c b/tests/sys/netinet/tcp_user_cookie.c
new file mode 100644
index 000000000000..162d965ba280
--- /dev/null
+++ b/tests/sys/netinet/tcp_user_cookie.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2016 Limelight Networks
+ * 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,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ *
+ * NO WARRANTY
+ * 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 MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+ *
+ * Authors: George Neville-Neil
+ *
+ * $FreeBSD$
+ */
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <err.h>
+#include <sysexits.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define buflen 80
+
+/*
+ * Setup a TCP server listening on a port for connections, all of
+ * which subseuqently have their user cookie set.
+ */
+int
+main(int argc, char **argv)
+{
+ struct sockaddr_in srv;
+ int sock, accepted, port, cookie;
+ int ret;
+ char recvbuf[buflen];
+
+ if (argc != 3) {
+ fprintf(stderr, "Usage: %s port cookie\n", argv[0]);
+ exit(2);
+ }
+
+ port = atoi(argv[1]);
+ cookie = atoi(argv[2]);
+
+ sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (sock < 0)
+ err(EXIT_FAILURE, "socket");
+
+ srv.sin_len = sizeof(srv);
+ srv.sin_family = AF_INET;
+ srv.sin_port = htons(port);
+ srv.sin_addr.s_addr = INADDR_ANY;
+
+ if (bind(sock, (struct sockaddr *)&srv, srv.sin_len) < 0)
+ err(EX_OSERR, "failed to bind to port %d", port);
+
+ if (listen(sock, 5) < 0)
+ err(EX_OSERR, "failed to listen on socket");
+
+ ret = setsockopt(sock, SOL_SOCKET, SO_USER_COOKIE, &cookie, sizeof(cookie));
+ if (ret < 0)
+ err(EX_OSERR, "setsockopt(SO_USER_COOKIE)");
+
+ while (1) {
+
+ accepted = accept(sock, NULL, 0);
+
+ if (accepted < 0)
+ err(EX_OSERR, "accept failed");
+
+ ret = setsockopt(accepted, SOL_SOCKET, SO_USER_COOKIE,
+ &cookie, sizeof(cookie));
+ if (ret < 0)
+ err(EX_OSERR, "setsockopt(SO_USER_COOKIE)");
+
+ ret = read(accepted, &recvbuf, buflen);
+
+ if (ret < 0)
+ warn("failed read");
+
+ close(accepted);
+ }
+
+ return (0);
+}
diff --git a/tests/sys/netinet/udp_dontroute.c b/tests/sys/netinet/udp_dontroute.c
new file mode 100644
index 000000000000..79421fd0b707
--- /dev/null
+++ b/tests/sys/netinet/udp_dontroute.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2014 Spectra Logic Corporation
+ * 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,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ *
+ * NO WARRANTY
+ * 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 MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+ *
+ * Authors: Alan Somers (Spectra Logic Corporation)
+ *
+ * $FreeBSD$
+ */
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Sends a single UDP packet to the provided address, with SO_DONTROUTE set
+ * I couldn't find a way to do this with builtin utilities like nc(1)
+ */
+int
+main(int argc, char **argv)
+{
+ struct sockaddr_in dst;
+ int s, t;
+ int opt;
+ int ret;
+ ssize_t len;
+ const char* sendbuf = "Hello, World!";
+ const size_t buflen = 80;
+ char recvbuf[buflen];
+
+ if (argc != 3) {
+ fprintf(stderr, "Usage: %s ip_address tapdev\n", argv[0]);
+ exit(2);
+ }
+
+ t = open(argv[2], O_RDWR | O_NONBLOCK);
+ if (t < 0)
+ err(EXIT_FAILURE, "open");
+
+ s = socket(PF_INET, SOCK_DGRAM, 0);
+ if (s < 0)
+ err(EXIT_FAILURE, "socket");
+ opt = 1;
+
+ ret = setsockopt(s, SOL_SOCKET, SO_DONTROUTE, &opt, sizeof(opt));
+ if (ret == -1)
+ err(EXIT_FAILURE, "setsockopt(SO_DONTROUTE)");
+
+ dst.sin_len = sizeof(dst);
+ dst.sin_family = AF_INET;
+ dst.sin_port = htons(46120);
+ dst.sin_addr.s_addr = inet_addr(argv[1]);
+ if (dst.sin_addr.s_addr == htonl(INADDR_NONE)) {
+ fprintf(stderr, "Invalid address: %s\n", argv[1]);
+ exit(2);
+ }
+ ret = sendto(s, sendbuf, strlen(sendbuf), 0, (struct sockaddr*)&dst,
+ dst.sin_len);
+ if (ret == -1)
+ err(EXIT_FAILURE, "sendto");
+
+ /* Verify that the packet went to the desired tap device */
+
+ len = read(t, recvbuf, buflen);
+ if (len == 0)
+ errx(EXIT_FAILURE, "read returned EOF");
+ else if (len < 0 && errno == EAGAIN)
+ errx(EXIT_FAILURE, "Did not receive any packets");
+ else if (len < 0)
+ err(EXIT_FAILURE, "read");
+
+ /*
+ * If read returned anything at all, consider it a success. The packet
+ * should be an Ethernet frame containing an ARP request for
+ * ip_address. We won't bother to decode it
+ */
+ return (0);
+}
diff --git a/tests/sys/opencrypto/Makefile b/tests/sys/opencrypto/Makefile
new file mode 100644
index 000000000000..17b9850384ac
--- /dev/null
+++ b/tests/sys/opencrypto/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/opencrypto
+BINDIR= ${TESTSDIR}
+
+PLAIN_TESTS_SH= runtests
+
+TEST_METADATA.foo+=required_programs="python"
+PYMODULES= cryptodev.py cryptodevh.py cryptotest.py dpkt.py
+
+${PACKAGE}FILES+= ${PYMODULES}
+
+.include <bsd.test.mk>
diff --git a/tests/sys/opencrypto/Makefile.depend b/tests/sys/opencrypto/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/opencrypto/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/opencrypto/cryptodev.py b/tests/sys/opencrypto/cryptodev.py
new file mode 100644
index 000000000000..d9f764adf7d1
--- /dev/null
+++ b/tests/sys/opencrypto/cryptodev.py
@@ -0,0 +1,561 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2014 The FreeBSD Foundation
+# Copyright 2014 John-Mark Gurney
+# All rights reserved.
+#
+# This software was developed by John-Mark Gurney under
+# the sponsorship from the FreeBSD Foundation.
+# 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 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$
+#
+
+import array
+import dpkt
+from fcntl import ioctl
+import os
+import signal
+from struct import pack as _pack
+
+from cryptodevh import *
+
+__all__ = [ 'Crypto', 'MismatchError', ]
+
+class FindOp(dpkt.Packet):
+ __byte_order__ = '@'
+ __hdr__ = ( ('crid', 'i', 0),
+ ('name', '32s', 0),
+ )
+
+class SessionOp(dpkt.Packet):
+ __byte_order__ = '@'
+ __hdr__ = ( ('cipher', 'I', 0),
+ ('mac', 'I', 0),
+ ('keylen', 'I', 0),
+ ('key', 'P', 0),
+ ('mackeylen', 'i', 0),
+ ('mackey', 'P', 0),
+ ('ses', 'I', 0),
+ )
+
+class SessionOp2(dpkt.Packet):
+ __byte_order__ = '@'
+ __hdr__ = ( ('cipher', 'I', 0),
+ ('mac', 'I', 0),
+ ('keylen', 'I', 0),
+ ('key', 'P', 0),
+ ('mackeylen', 'i', 0),
+ ('mackey', 'P', 0),
+ ('ses', 'I', 0),
+ ('crid', 'i', 0),
+ ('pad0', 'i', 0),
+ ('pad1', 'i', 0),
+ ('pad2', 'i', 0),
+ ('pad3', 'i', 0),
+ )
+
+class CryptOp(dpkt.Packet):
+ __byte_order__ = '@'
+ __hdr__ = ( ('ses', 'I', 0),
+ ('op', 'H', 0),
+ ('flags', 'H', 0),
+ ('len', 'I', 0),
+ ('src', 'P', 0),
+ ('dst', 'P', 0),
+ ('mac', 'P', 0),
+ ('iv', 'P', 0),
+ )
+
+class CryptAEAD(dpkt.Packet):
+ __byte_order__ = '@'
+ __hdr__ = (
+ ('ses', 'I', 0),
+ ('op', 'H', 0),
+ ('flags', 'H', 0),
+ ('len', 'I', 0),
+ ('aadlen', 'I', 0),
+ ('ivlen', 'I', 0),
+ ('src', 'P', 0),
+ ('dst', 'P', 0),
+ ('aad', 'P', 0),
+ ('tag', 'P', 0),
+ ('iv', 'P', 0),
+ )
+
+# h2py.py can't handle multiarg macros
+CRIOGET = 3221513060
+CIOCGSESSION = 3224396645
+CIOCGSESSION2 = 3225445226
+CIOCFSESSION = 2147771238
+CIOCCRYPT = 3224396647
+CIOCKEY = 3230688104
+CIOCASYMFEAT = 1074029417
+CIOCKEY2 = 3230688107
+CIOCFINDDEV = 3223610220
+CIOCCRYPTAEAD = 3225445229
+
+def _getdev():
+ fd = os.open('/dev/crypto', os.O_RDWR)
+ buf = array.array('I', [0])
+ ioctl(fd, CRIOGET, buf, 1)
+ os.close(fd)
+
+ return buf[0]
+
+_cryptodev = _getdev()
+
+def _findop(crid, name):
+ fop = FindOp()
+ fop.crid = crid
+ fop.name = name
+ s = array.array('B', fop.pack_hdr())
+ ioctl(_cryptodev, CIOCFINDDEV, s, 1)
+ fop.unpack(s)
+
+ try:
+ idx = fop.name.index('\x00')
+ name = fop.name[:idx]
+ except ValueError:
+ name = fop.name
+
+ return fop.crid, name
+
+class Crypto:
+ @staticmethod
+ def findcrid(name):
+ return _findop(-1, name)[0]
+
+ @staticmethod
+ def getcridname(crid):
+ return _findop(crid, '')[1]
+
+ def __init__(self, cipher=0, key=None, mac=0, mackey=None,
+ crid=CRYPTOCAP_F_SOFTWARE | CRYPTOCAP_F_HARDWARE):
+ self._ses = None
+ ses = SessionOp2()
+ ses.cipher = cipher
+ ses.mac = mac
+
+ if key is not None:
+ ses.keylen = len(key)
+ k = array.array('B', key)
+ ses.key = k.buffer_info()[0]
+ else:
+ self.key = None
+
+ if mackey is not None:
+ ses.mackeylen = len(mackey)
+ mk = array.array('B', mackey)
+ ses.mackey = mk.buffer_info()[0]
+ self._maclen = 16 # parameterize?
+ else:
+ self._maclen = None
+
+ if not cipher and not mac:
+ raise ValueError('one of cipher or mac MUST be specified.')
+ ses.crid = CRYPTOCAP_F_SOFTWARE | CRYPTOCAP_F_HARDWARE
+ #ses.crid = CRYPTOCAP_F_HARDWARE
+ #ses.crid = CRYPTOCAP_F_SOFTWARE
+ #ses.crid = 0
+ #print `ses`
+ s = array.array('B', ses.pack_hdr())
+ #print `s`
+ ioctl(_cryptodev, CIOCGSESSION2, s, 1)
+ ses.unpack(s)
+
+ self._ses = ses.ses
+
+ def __del__(self):
+ if self._ses is None:
+ return
+
+ try:
+ ioctl(_cryptodev, CIOCFSESSION, _pack('I', self._ses))
+ except TypeError:
+ pass
+ self._ses = None
+
+ def _doop(self, op, src, iv):
+ cop = CryptOp()
+ cop.ses = self._ses
+ cop.op = op
+ cop.flags = 0
+ cop.len = len(src)
+ s = array.array('B', src)
+ cop.src = cop.dst = s.buffer_info()[0]
+ if self._maclen is not None:
+ m = array.array('B', [0] * self._maclen)
+ cop.mac = m.buffer_info()[0]
+ ivbuf = array.array('B', iv)
+ cop.iv = ivbuf.buffer_info()[0]
+
+ #print 'cop:', `cop`
+ ioctl(_cryptodev, CIOCCRYPT, str(cop))
+
+ s = s.tostring()
+ if self._maclen is not None:
+ return s, m.tostring()
+
+ return s
+
+ def _doaead(self, op, src, aad, iv, tag=None):
+ caead = CryptAEAD()
+ caead.ses = self._ses
+ caead.op = op
+ caead.flags = CRD_F_IV_EXPLICIT
+ caead.flags = 0
+ caead.len = len(src)
+ s = array.array('B', src)
+ caead.src = caead.dst = s.buffer_info()[0]
+ caead.aadlen = len(aad)
+ saad = array.array('B', aad)
+ caead.aad = saad.buffer_info()[0]
+
+ if self._maclen is None:
+ raise ValueError('must have a tag length')
+
+ if tag is None:
+ tag = array.array('B', [0] * self._maclen)
+ else:
+ assert len(tag) == self._maclen, `len(tag), self._maclen`
+ tag = array.array('B', tag)
+
+ caead.tag = tag.buffer_info()[0]
+
+ ivbuf = array.array('B', iv)
+ caead.ivlen = len(iv)
+ caead.iv = ivbuf.buffer_info()[0]
+
+ ioctl(_cryptodev, CIOCCRYPTAEAD, str(caead))
+
+ s = s.tostring()
+
+ return s, tag.tostring()
+
+ def perftest(self, op, size, timeo=3):
+ import random
+ import time
+
+ inp = array.array('B', (random.randint(0, 255) for x in xrange(size)))
+ out = array.array('B', inp)
+
+ # prep ioctl
+ cop = CryptOp()
+ cop.ses = self._ses
+ cop.op = op
+ cop.flags = 0
+ cop.len = len(inp)
+ s = array.array('B', inp)
+ cop.src = s.buffer_info()[0]
+ cop.dst = out.buffer_info()[0]
+ if self._maclen is not None:
+ m = array.array('B', [0] * self._maclen)
+ cop.mac = m.buffer_info()[0]
+ ivbuf = array.array('B', (random.randint(0, 255) for x in xrange(16)))
+ cop.iv = ivbuf.buffer_info()[0]
+
+ exit = [ False ]
+ def alarmhandle(a, b, exit=exit):
+ exit[0] = True
+
+ oldalarm = signal.signal(signal.SIGALRM, alarmhandle)
+ signal.alarm(timeo)
+
+ start = time.time()
+ reps = 0
+ while not exit[0]:
+ ioctl(_cryptodev, CIOCCRYPT, str(cop))
+ reps += 1
+
+ end = time.time()
+
+ signal.signal(signal.SIGALRM, oldalarm)
+
+ print 'time:', end - start
+ print 'perf MB/sec:', (reps * size) / (end - start) / 1024 / 1024
+
+ def encrypt(self, data, iv, aad=None):
+ if aad is None:
+ return self._doop(COP_ENCRYPT, data, iv)
+ else:
+ return self._doaead(COP_ENCRYPT, data, aad,
+ iv)
+
+ def decrypt(self, data, iv, aad=None, tag=None):
+ if aad is None:
+ return self._doop(COP_DECRYPT, data, iv)
+ else:
+ return self._doaead(COP_DECRYPT, data, aad,
+ iv, tag=tag)
+
+class MismatchError(Exception):
+ pass
+
+class KATParser:
+ def __init__(self, fname, fields):
+ self.fp = open(fname)
+ self.fields = set(fields)
+ self._pending = None
+
+ def __iter__(self):
+ while True:
+ didread = False
+ if self._pending is not None:
+ i = self._pending
+ self._pending = None
+ else:
+ i = self.fp.readline()
+ didread = True
+
+ if didread and not i:
+ return
+
+ if (i and i[0] == '#') or not i.strip():
+ continue
+ if i[0] == '[':
+ yield i[1:].split(']', 1)[0], self.fielditer()
+ else:
+ raise ValueError('unknown line: %s' % `i`)
+
+ def eatblanks(self):
+ while True:
+ line = self.fp.readline()
+ if line == '':
+ break
+
+ line = line.strip()
+ if line:
+ break
+
+ return line
+
+ def fielditer(self):
+ while True:
+ values = {}
+
+ line = self.eatblanks()
+ if not line or line[0] == '[':
+ self._pending = line
+ return
+
+ while True:
+ try:
+ f, v = line.split(' =')
+ except:
+ if line == 'FAIL':
+ f, v = 'FAIL', ''
+ else:
+ print 'line:', `line`
+ raise
+ v = v.strip()
+
+ if f in values:
+ raise ValueError('already present: %s' % `f`)
+ values[f] = v
+ line = self.fp.readline().strip()
+ if not line:
+ break
+
+ # we should have everything
+ remain = self.fields.copy() - set(values.keys())
+ # XXX - special case GCM decrypt
+ if remain and not ('FAIL' in values and 'PT' in remain):
+ raise ValueError('not all fields found: %s' % `remain`)
+
+ yield values
+
+def _spdechex(s):
+ return ''.join(s.split()).decode('hex')
+
+if __name__ == '__main__':
+ if True:
+ try:
+ crid = Crypto.findcrid('aesni0')
+ print 'aesni:', crid
+ except IOError:
+ print 'aesni0 not found'
+
+ for i in xrange(10):
+ try:
+ name = Crypto.getcridname(i)
+ print '%2d: %s' % (i, `name`)
+ except IOError:
+ pass
+ elif False:
+ kp = KATParser('/usr/home/jmg/aesni.testing/format tweak value input - data unit seq no/XTSGenAES128.rsp', [ 'COUNT', 'DataUnitLen', 'Key', 'DataUnitSeqNumber', 'PT', 'CT' ])
+ for mode, ni in kp:
+ print `i`, `ni`
+ for j in ni:
+ print `j`
+ elif False:
+ key = _spdechex('c939cc13397c1d37de6ae0e1cb7c423c')
+ iv = _spdechex('00000000000000000000000000000001')
+ pt = _spdechex('ab3cabed693a32946055524052afe3c9cb49664f09fc8b7da824d924006b7496353b8c1657c5dec564d8f38d7432e1de35aae9d95590e66278d4acce883e51abaf94977fcd3679660109a92bf7b2973ccd547f065ec6cee4cb4a72a5e9f45e615d920d76cb34cba482467b3e21422a7242e7d931330c0fbf465c3a3a46fae943029fd899626dda542750a1eee253df323c6ef1573f1c8c156613e2ea0a6cdbf2ae9701020be2d6a83ecb7f3f9d8e')
+ #pt = _spdechex('00000000000000000000000000000000')
+ ct = _spdechex('f42c33853ecc5ce2949865fdb83de3bff1089e9360c94f830baebfaff72836ab5236f77212f1e7396c8c54ac73d81986375a6e9e299cfeca5ba051ed25e8d1affa5beaf6c1d2b45e90802408f2ced21663497e906de5f29341e5e52ddfea5363d628b3eb7806835e17bae051b3a6da3f8e2941fe44384eac17a9d298d2c331ca8320c775b5d53263a5e905059d891b21dede2d8110fd427c7bd5a9a274ddb47b1945ee79522203b6e297d0e399ef')
+
+ c = Crypto(CRYPTO_AES_ICM, key)
+ enc = c.encrypt(pt, iv)
+
+ print 'enc:', enc.encode('hex')
+ print ' ct:', ct.encode('hex')
+
+ assert ct == enc
+
+ dec = c.decrypt(ct, iv)
+
+ print 'dec:', dec.encode('hex')
+ print ' pt:', pt.encode('hex')
+
+ assert pt == dec
+ elif False:
+ key = _spdechex('c939cc13397c1d37de6ae0e1cb7c423c')
+ iv = _spdechex('00000000000000000000000000000001')
+ pt = _spdechex('ab3cabed693a32946055524052afe3c9cb49664f09fc8b7da824d924006b7496353b8c1657c5dec564d8f38d7432e1de35aae9d95590e66278d4acce883e51abaf94977fcd3679660109a92bf7b2973ccd547f065ec6cee4cb4a72a5e9f45e615d920d76cb34cba482467b3e21422a7242e7d931330c0fbf465c3a3a46fae943029fd899626dda542750a1eee253df323c6ef1573f1c8c156613e2ea0a6cdbf2ae9701020be2d6a83ecb7f3f9d8e0a3f')
+ #pt = _spdechex('00000000000000000000000000000000')
+ ct = _spdechex('f42c33853ecc5ce2949865fdb83de3bff1089e9360c94f830baebfaff72836ab5236f77212f1e7396c8c54ac73d81986375a6e9e299cfeca5ba051ed25e8d1affa5beaf6c1d2b45e90802408f2ced21663497e906de5f29341e5e52ddfea5363d628b3eb7806835e17bae051b3a6da3f8e2941fe44384eac17a9d298d2c331ca8320c775b5d53263a5e905059d891b21dede2d8110fd427c7bd5a9a274ddb47b1945ee79522203b6e297d0e399ef3768')
+
+ c = Crypto(CRYPTO_AES_ICM, key)
+ enc = c.encrypt(pt, iv)
+
+ print 'enc:', enc.encode('hex')
+ print ' ct:', ct.encode('hex')
+
+ assert ct == enc
+
+ dec = c.decrypt(ct, iv)
+
+ print 'dec:', dec.encode('hex')
+ print ' pt:', pt.encode('hex')
+
+ assert pt == dec
+ elif False:
+ key = _spdechex('c939cc13397c1d37de6ae0e1cb7c423c')
+ iv = _spdechex('6eba2716ec0bd6fa5cdef5e6d3a795bc')
+ pt = _spdechex('ab3cabed693a32946055524052afe3c9cb49664f09fc8b7da824d924006b7496353b8c1657c5dec564d8f38d7432e1de35aae9d95590e66278d4acce883e51abaf94977fcd3679660109a92bf7b2973ccd547f065ec6cee4cb4a72a5e9f45e615d920d76cb34cba482467b3e21422a7242e7d931330c0fbf465c3a3a46fae943029fd899626dda542750a1eee253df323c6ef1573f1c8c156613e2ea0a6cdbf2ae9701020be2d6a83ecb7f3f9d8e0a3f')
+ ct = _spdechex('f1f81f12e72e992dbdc304032705dc75dc3e4180eff8ee4819906af6aee876d5b00b7c36d282a445ce3620327be481e8e53a8e5a8e5ca9abfeb2281be88d12ffa8f46d958d8224738c1f7eea48bda03edbf9adeb900985f4fa25648b406d13a886c25e70cfdecdde0ad0f2991420eb48a61c64fd797237cf2798c2675b9bb744360b0a3f329ac53bbceb4e3e7456e6514f1a9d2f06c236c31d0f080b79c15dce1096357416602520daa098b17d1af427')
+ c = Crypto(CRYPTO_AES_CBC, key)
+
+ enc = c.encrypt(pt, iv)
+
+ print 'enc:', enc.encode('hex')
+ print ' ct:', ct.encode('hex')
+
+ assert ct == enc
+
+ dec = c.decrypt(ct, iv)
+
+ print 'dec:', dec.encode('hex')
+ print ' pt:', pt.encode('hex')
+
+ assert pt == dec
+ elif False:
+ key = _spdechex('c939cc13397c1d37de6ae0e1cb7c423c')
+ iv = _spdechex('b3d8cc017cbb89b39e0f67e2')
+ pt = _spdechex('c3b3c41f113a31b73d9a5cd4321030')
+ aad = _spdechex('24825602bd12a984e0092d3e448eda5f')
+ ct = _spdechex('93fe7d9e9bfd10348a5606e5cafa7354')
+ ct = _spdechex('93fe7d9e9bfd10348a5606e5cafa73')
+ tag = _spdechex('0032a1dc85f1c9786925a2e71d8272dd')
+ tag = _spdechex('8d11a0929cb3fbe1fef01a4a38d5f8ea')
+
+ c = Crypto(CRYPTO_AES_NIST_GCM_16, key,
+ mac=CRYPTO_AES_128_NIST_GMAC, mackey=key)
+
+ enc, enctag = c.encrypt(pt, iv, aad=aad)
+
+ print 'enc:', enc.encode('hex')
+ print ' ct:', ct.encode('hex')
+
+ assert enc == ct
+
+ print 'etg:', enctag.encode('hex')
+ print 'tag:', tag.encode('hex')
+ assert enctag == tag
+
+ # Make sure we get EBADMSG
+ #enctag = enctag[:-1] + 'a'
+ dec, dectag = c.decrypt(ct, iv, aad=aad, tag=enctag)
+
+ print 'dec:', dec.encode('hex')
+ print ' pt:', pt.encode('hex')
+
+ assert dec == pt
+
+ print 'dtg:', dectag.encode('hex')
+ print 'tag:', tag.encode('hex')
+
+ assert dectag == tag
+ elif False:
+ key = _spdechex('c939cc13397c1d37de6ae0e1cb7c423c')
+ iv = _spdechex('b3d8cc017cbb89b39e0f67e2')
+ key = key + iv[:4]
+ iv = iv[4:]
+ pt = _spdechex('c3b3c41f113a31b73d9a5cd432103069')
+ aad = _spdechex('24825602bd12a984e0092d3e448eda5f')
+ ct = _spdechex('93fe7d9e9bfd10348a5606e5cafa7354')
+ tag = _spdechex('0032a1dc85f1c9786925a2e71d8272dd')
+
+ c = Crypto(CRYPTO_AES_GCM_16, key, mac=CRYPTO_AES_128_GMAC, mackey=key)
+
+ enc, enctag = c.encrypt(pt, iv, aad=aad)
+
+ print 'enc:', enc.encode('hex')
+ print ' ct:', ct.encode('hex')
+
+ assert enc == ct
+
+ print 'etg:', enctag.encode('hex')
+ print 'tag:', tag.encode('hex')
+ assert enctag == tag
+ elif False:
+ for i in xrange(100000):
+ c = Crypto(CRYPTO_AES_XTS, '1bbfeadf539daedcae33ced497343f3ca1f2474ad932b903997d44707db41382'.decode('hex'))
+ data = '52a42bca4e9425a25bbc8c8bf6129dec'.decode('hex')
+ ct = '517e602becd066b65fa4f4f56ddfe240'.decode('hex')
+ iv = _pack('QQ', 71, 0)
+
+ enc = c.encrypt(data, iv)
+ assert enc == ct
+ elif True:
+ c = Crypto(CRYPTO_AES_XTS, '1bbfeadf539daedcae33ced497343f3ca1f2474ad932b903997d44707db41382'.decode('hex'))
+ data = '52a42bca4e9425a25bbc8c8bf6129dec'.decode('hex')
+ ct = '517e602becd066b65fa4f4f56ddfe240'.decode('hex')
+ iv = _pack('QQ', 71, 0)
+
+ enc = c.encrypt(data, iv)
+ assert enc == ct
+
+ dec = c.decrypt(enc, iv)
+ assert dec == data
+
+ #c.perftest(COP_ENCRYPT, 192*1024, reps=30000)
+
+ else:
+ key = '1bbfeadf539daedcae33ced497343f3ca1f2474ad932b903997d44707db41382'.decode('hex')
+ print 'XTS %d testing:' % (len(key) * 8)
+ c = Crypto(CRYPTO_AES_XTS, key)
+ for i in [ 8192, 192*1024]:
+ print 'block size: %d' % i
+ c.perftest(COP_ENCRYPT, i)
+ c.perftest(COP_DECRYPT, i)
diff --git a/tests/sys/opencrypto/cryptodevh.py b/tests/sys/opencrypto/cryptodevh.py
new file mode 100644
index 000000000000..ee024e9bd0f2
--- /dev/null
+++ b/tests/sys/opencrypto/cryptodevh.py
@@ -0,0 +1,250 @@
+# $FreeBSD$
+# Generated by h2py from stdin
+
+# Included from sys/ioccom.h
+IOCPARM_SHIFT = 13
+IOCPARM_MASK = ((1 << IOCPARM_SHIFT) - 1)
+def IOCPARM_LEN(x): return (((x) >> 16) & IOCPARM_MASK)
+
+def IOCBASECMD(x): return ((x) & ~(IOCPARM_MASK << 16))
+
+def IOCGROUP(x): return (((x) >> 8) & 0xff)
+
+IOCPARM_MAX = (1 << IOCPARM_SHIFT)
+IOC_VOID = 0x20000000
+IOC_OUT = 0x40000000
+IOC_IN = 0x80000000
+IOC_INOUT = (IOC_IN|IOC_OUT)
+IOC_DIRMASK = (IOC_VOID|IOC_OUT|IOC_IN)
+
+# Included from sys/cdefs.h
+def __has_feature(x): return 0
+
+def __has_include(x): return 0
+
+def __has_builtin(x): return 0
+
+__GNUCLIKE_ASM = 3
+__GNUCLIKE_ASM = 2
+__GNUCLIKE___TYPEOF = 1
+__GNUCLIKE___OFFSETOF = 1
+__GNUCLIKE___SECTION = 1
+__GNUCLIKE_CTOR_SECTION_HANDLING = 1
+__GNUCLIKE_BUILTIN_CONSTANT_P = 1
+__GNUCLIKE_BUILTIN_VARARGS = 1
+__GNUCLIKE_BUILTIN_STDARG = 1
+__GNUCLIKE_BUILTIN_VAALIST = 1
+__GNUC_VA_LIST_COMPATIBILITY = 1
+__GNUCLIKE_BUILTIN_NEXT_ARG = 1
+__GNUCLIKE_BUILTIN_MEMCPY = 1
+__CC_SUPPORTS_INLINE = 1
+__CC_SUPPORTS___INLINE = 1
+__CC_SUPPORTS___INLINE__ = 1
+__CC_SUPPORTS___FUNC__ = 1
+__CC_SUPPORTS_WARNING = 1
+__CC_SUPPORTS_VARADIC_XXX = 1
+__CC_SUPPORTS_DYNAMIC_ARRAY_INIT = 1
+def __P(protos): return protos
+
+def __STRING(x): return #x
+
+def __XSTRING(x): return __STRING(x)
+
+def __P(protos): return ()
+
+def __STRING(x): return "x"
+
+def __aligned(x): return __attribute__((__aligned__(x)))
+
+def __section(x): return __attribute__((__section__(x)))
+
+def __aligned(x): return __attribute__((__aligned__(x)))
+
+def __section(x): return __attribute__((__section__(x)))
+
+def _Alignas(x): return alignas(x)
+
+def _Alignas(x): return __aligned(x)
+
+def _Alignof(x): return alignof(x)
+
+def _Alignof(x): return __alignof(x)
+
+def __nonnull(x): return __attribute__((__nonnull__(x)))
+
+def __predict_true(exp): return __builtin_expect((exp), 1)
+
+def __predict_false(exp): return __builtin_expect((exp), 0)
+
+def __predict_true(exp): return (exp)
+
+def __predict_false(exp): return (exp)
+
+def __format_arg(fmtarg): return __attribute__((__format_arg__ (fmtarg)))
+
+def __GLOBL(sym): return __GLOBL1(sym)
+
+def __FBSDID(s): return __IDSTRING(__CONCAT(__rcsid_,__LINE__),s)
+
+def __RCSID(s): return __IDSTRING(__CONCAT(__rcsid_,__LINE__),s)
+
+def __RCSID_SOURCE(s): return __IDSTRING(__CONCAT(__rcsid_source_,__LINE__),s)
+
+def __SCCSID(s): return __IDSTRING(__CONCAT(__sccsid_,__LINE__),s)
+
+def __COPYRIGHT(s): return __IDSTRING(__CONCAT(__copyright_,__LINE__),s)
+
+_POSIX_C_SOURCE = 199009
+_POSIX_C_SOURCE = 199209
+__XSI_VISIBLE = 700
+_POSIX_C_SOURCE = 200809
+__XSI_VISIBLE = 600
+_POSIX_C_SOURCE = 200112
+__XSI_VISIBLE = 500
+_POSIX_C_SOURCE = 199506
+_POSIX_C_SOURCE = 198808
+__POSIX_VISIBLE = 200809
+__ISO_C_VISIBLE = 1999
+__POSIX_VISIBLE = 200112
+__ISO_C_VISIBLE = 1999
+__POSIX_VISIBLE = 199506
+__ISO_C_VISIBLE = 1990
+__POSIX_VISIBLE = 199309
+__ISO_C_VISIBLE = 1990
+__POSIX_VISIBLE = 199209
+__ISO_C_VISIBLE = 1990
+__POSIX_VISIBLE = 199009
+__ISO_C_VISIBLE = 1990
+__POSIX_VISIBLE = 198808
+__ISO_C_VISIBLE = 0
+__POSIX_VISIBLE = 0
+__XSI_VISIBLE = 0
+__BSD_VISIBLE = 0
+__ISO_C_VISIBLE = 1990
+__POSIX_VISIBLE = 0
+__XSI_VISIBLE = 0
+__BSD_VISIBLE = 0
+__ISO_C_VISIBLE = 1999
+__POSIX_VISIBLE = 0
+__XSI_VISIBLE = 0
+__BSD_VISIBLE = 0
+__ISO_C_VISIBLE = 2011
+__POSIX_VISIBLE = 200809
+__XSI_VISIBLE = 700
+__BSD_VISIBLE = 1
+__ISO_C_VISIBLE = 2011
+__NO_TLS = 1
+CRYPTO_DRIVERS_INITIAL = 4
+CRYPTO_SW_SESSIONS = 32
+NULL_HASH_LEN = 16
+MD5_HASH_LEN = 16
+SHA1_HASH_LEN = 20
+RIPEMD160_HASH_LEN = 20
+SHA2_256_HASH_LEN = 32
+SHA2_384_HASH_LEN = 48
+SHA2_512_HASH_LEN = 64
+MD5_KPDK_HASH_LEN = 16
+SHA1_KPDK_HASH_LEN = 20
+HASH_MAX_LEN = SHA2_512_HASH_LEN
+NULL_HMAC_BLOCK_LEN = 64
+MD5_HMAC_BLOCK_LEN = 64
+SHA1_HMAC_BLOCK_LEN = 64
+RIPEMD160_HMAC_BLOCK_LEN = 64
+SHA2_256_HMAC_BLOCK_LEN = 64
+SHA2_384_HMAC_BLOCK_LEN = 128
+SHA2_512_HMAC_BLOCK_LEN = 128
+HMAC_MAX_BLOCK_LEN = SHA2_512_HMAC_BLOCK_LEN
+HMAC_IPAD_VAL = 0x36
+HMAC_OPAD_VAL = 0x5C
+NULL_BLOCK_LEN = 4
+DES_BLOCK_LEN = 8
+DES3_BLOCK_LEN = 8
+BLOWFISH_BLOCK_LEN = 8
+SKIPJACK_BLOCK_LEN = 8
+CAST128_BLOCK_LEN = 8
+RIJNDAEL128_BLOCK_LEN = 16
+AES_BLOCK_LEN = RIJNDAEL128_BLOCK_LEN
+CAMELLIA_BLOCK_LEN = 16
+EALG_MAX_BLOCK_LEN = AES_BLOCK_LEN
+AALG_MAX_RESULT_LEN = 64
+CRYPTO_ALGORITHM_MIN = 1
+CRYPTO_DES_CBC = 1
+CRYPTO_3DES_CBC = 2
+CRYPTO_BLF_CBC = 3
+CRYPTO_CAST_CBC = 4
+CRYPTO_SKIPJACK_CBC = 5
+CRYPTO_MD5_HMAC = 6
+CRYPTO_SHA1_HMAC = 7
+CRYPTO_RIPEMD160_HMAC = 8
+CRYPTO_MD5_KPDK = 9
+CRYPTO_SHA1_KPDK = 10
+CRYPTO_RIJNDAEL128_CBC = 11
+CRYPTO_AES_CBC = 11
+CRYPTO_ARC4 = 12
+CRYPTO_MD5 = 13
+CRYPTO_SHA1 = 14
+CRYPTO_NULL_HMAC = 15
+CRYPTO_NULL_CBC = 16
+CRYPTO_DEFLATE_COMP = 17
+CRYPTO_SHA2_256_HMAC = 18
+CRYPTO_SHA2_384_HMAC = 19
+CRYPTO_SHA2_512_HMAC = 20
+CRYPTO_CAMELLIA_CBC = 21
+CRYPTO_AES_XTS = 22
+CRYPTO_AES_ICM = 23
+CRYPTO_AES_NIST_GMAC = 24
+CRYPTO_AES_NIST_GCM_16 = 25
+CRYPTO_AES_128_NIST_GMAC = 26
+CRYPTO_AES_192_NIST_GMAC = 27
+CRYPTO_AES_256_NIST_GMAC = 28
+CRYPTO_ALGORITHM_MAX = 28
+CRYPTO_ALG_FLAG_SUPPORTED = 0x01
+CRYPTO_ALG_FLAG_RNG_ENABLE = 0x02
+CRYPTO_ALG_FLAG_DSA_SHA = 0x04
+CRYPTO_FLAG_HARDWARE = 0x01000000
+CRYPTO_FLAG_SOFTWARE = 0x02000000
+COP_ENCRYPT = 1
+COP_DECRYPT = 2
+COP_F_BATCH = 0x0008
+CRK_MAXPARAM = 8
+CRK_ALGORITM_MIN = 0
+CRK_MOD_EXP = 0
+CRK_MOD_EXP_CRT = 1
+CRK_DSA_SIGN = 2
+CRK_DSA_VERIFY = 3
+CRK_DH_COMPUTE_KEY = 4
+CRK_ALGORITHM_MAX = 4
+CRF_MOD_EXP = (1 << CRK_MOD_EXP)
+CRF_MOD_EXP_CRT = (1 << CRK_MOD_EXP_CRT)
+CRF_DSA_SIGN = (1 << CRK_DSA_SIGN)
+CRF_DSA_VERIFY = (1 << CRK_DSA_VERIFY)
+CRF_DH_COMPUTE_KEY = (1 << CRK_DH_COMPUTE_KEY)
+CRD_F_ENCRYPT = 0x01
+CRD_F_IV_PRESENT = 0x02
+CRD_F_IV_EXPLICIT = 0x04
+CRD_F_DSA_SHA_NEEDED = 0x08
+CRD_F_COMP = 0x0f
+CRD_F_KEY_EXPLICIT = 0x10
+CRYPTO_F_IMBUF = 0x0001
+CRYPTO_F_IOV = 0x0002
+CRYPTO_F_BATCH = 0x0008
+CRYPTO_F_CBIMM = 0x0010
+CRYPTO_F_DONE = 0x0020
+CRYPTO_F_CBIFSYNC = 0x0040
+CRYPTO_BUF_CONTIG = 0x0
+CRYPTO_BUF_IOV = 0x1
+CRYPTO_BUF_MBUF = 0x2
+CRYPTO_OP_DECRYPT = 0x0
+CRYPTO_OP_ENCRYPT = 0x1
+CRYPTO_HINT_MORE = 0x1
+def CRYPTO_SESID2HID(_sid): return (((_sid) >> 32) & 0x00ffffff)
+
+def CRYPTO_SESID2CAPS(_sid): return (((_sid) >> 32) & 0xff000000)
+
+def CRYPTO_SESID2LID(_sid): return (((u_int32_t) (_sid)) & 0xffffffff)
+
+CRYPTOCAP_F_HARDWARE = CRYPTO_FLAG_HARDWARE
+CRYPTOCAP_F_SOFTWARE = CRYPTO_FLAG_SOFTWARE
+CRYPTOCAP_F_SYNC = 0x04000000
+CRYPTO_SYMQ = 0x1
+CRYPTO_ASYMQ = 0x2
diff --git a/tests/sys/opencrypto/cryptotest.py b/tests/sys/opencrypto/cryptotest.py
new file mode 100644
index 000000000000..e616bafa055d
--- /dev/null
+++ b/tests/sys/opencrypto/cryptotest.py
@@ -0,0 +1,265 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2014 The FreeBSD Foundation
+# All rights reserved.
+#
+# This software was developed by John-Mark Gurney under
+# the sponsorship from the FreeBSD Foundation.
+# 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 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$
+#
+
+import cryptodev
+import itertools
+import os
+import struct
+import unittest
+from cryptodev import *
+from glob import iglob
+
+katdir = '/usr/local/share/nist-kat'
+
+def katg(base, glob):
+ return iglob(os.path.join(katdir, base, glob))
+
+aesmodules = [ 'cryptosoft0', 'aesni0', ]
+desmodules = [ 'cryptosoft0', ]
+shamodules = [ 'cryptosoft0', ]
+
+def GenTestCase(cname):
+ try:
+ crid = cryptodev.Crypto.findcrid(cname)
+ except IOError:
+ return None
+
+ class GendCryptoTestCase(unittest.TestCase):
+ ###############
+ ##### AES #####
+ ###############
+ @unittest.skipIf(cname not in aesmodules, 'skipping AES on %s' % `cname`)
+ def test_xts(self):
+ for i in katg('XTSTestVectors/format tweak value input - data unit seq no', '*.rsp'):
+ self.runXTS(i, cryptodev.CRYPTO_AES_XTS)
+
+ def test_cbc(self):
+ for i in katg('KAT_AES', 'CBC[GKV]*.rsp'):
+ self.runCBC(i)
+
+ def test_gcm(self):
+ for i in katg('gcmtestvectors', 'gcmEncrypt*'):
+ self.runGCM(i, 'ENCRYPT')
+
+ for i in katg('gcmtestvectors', 'gcmDecrypt*'):
+ self.runGCM(i, 'DECRYPT')
+
+ _gmacsizes = { 32: cryptodev.CRYPTO_AES_256_NIST_GMAC,
+ 24: cryptodev.CRYPTO_AES_192_NIST_GMAC,
+ 16: cryptodev.CRYPTO_AES_128_NIST_GMAC,
+ }
+ def runGCM(self, fname, mode):
+ curfun = None
+ if mode == 'ENCRYPT':
+ swapptct = False
+ curfun = Crypto.encrypt
+ elif mode == 'DECRYPT':
+ swapptct = True
+ curfun = Crypto.decrypt
+ else:
+ raise RuntimeError('unknown mode: %s' % `mode`)
+
+ for bogusmode, lines in cryptodev.KATParser(fname,
+ [ 'Count', 'Key', 'IV', 'CT', 'AAD', 'Tag', 'PT', ]):
+ for data in lines:
+ curcnt = int(data['Count'])
+ cipherkey = data['Key'].decode('hex')
+ iv = data['IV'].decode('hex')
+ aad = data['AAD'].decode('hex')
+ tag = data['Tag'].decode('hex')
+ if 'FAIL' not in data:
+ pt = data['PT'].decode('hex')
+ ct = data['CT'].decode('hex')
+
+ if len(iv) != 12:
+ # XXX - isn't supported
+ continue
+
+ c = Crypto(cryptodev.CRYPTO_AES_NIST_GCM_16,
+ cipherkey,
+ mac=self._gmacsizes[len(cipherkey)],
+ mackey=cipherkey, crid=crid)
+
+ if mode == 'ENCRYPT':
+ rct, rtag = c.encrypt(pt, iv, aad)
+ rtag = rtag[:len(tag)]
+ data['rct'] = rct.encode('hex')
+ data['rtag'] = rtag.encode('hex')
+ self.assertEqual(rct, ct, `data`)
+ self.assertEqual(rtag, tag, `data`)
+ else:
+ if len(tag) != 16:
+ continue
+ args = (ct, iv, aad, tag)
+ if 'FAIL' in data:
+ self.assertRaises(IOError,
+ c.decrypt, *args)
+ else:
+ rpt, rtag = c.decrypt(*args)
+ data['rpt'] = rpt.encode('hex')
+ data['rtag'] = rtag.encode('hex')
+ self.assertEqual(rpt, pt,
+ `data`)
+
+ def runCBC(self, fname):
+ curfun = None
+ for mode, lines in cryptodev.KATParser(fname,
+ [ 'COUNT', 'KEY', 'IV', 'PLAINTEXT', 'CIPHERTEXT', ]):
+ if mode == 'ENCRYPT':
+ swapptct = False
+ curfun = Crypto.encrypt
+ elif mode == 'DECRYPT':
+ swapptct = True
+ curfun = Crypto.decrypt
+ else:
+ raise RuntimeError('unknown mode: %s' % `mode`)
+
+ for data in lines:
+ curcnt = int(data['COUNT'])
+ cipherkey = data['KEY'].decode('hex')
+ iv = data['IV'].decode('hex')
+ pt = data['PLAINTEXT'].decode('hex')
+ ct = data['CIPHERTEXT'].decode('hex')
+
+ if swapptct:
+ pt, ct = ct, pt
+ # run the fun
+ c = Crypto(cryptodev.CRYPTO_AES_CBC, cipherkey, crid=crid)
+ r = curfun(c, pt, iv)
+ self.assertEqual(r, ct)
+
+ def runXTS(self, fname, meth):
+ curfun = None
+ for mode, lines in cryptodev.KATParser(fname,
+ [ 'COUNT', 'DataUnitLen', 'Key', 'DataUnitSeqNumber', 'PT',
+ 'CT' ]):
+ if mode == 'ENCRYPT':
+ swapptct = False
+ curfun = Crypto.encrypt
+ elif mode == 'DECRYPT':
+ swapptct = True
+ curfun = Crypto.decrypt
+ else:
+ raise RuntimeError('unknown mode: %s' % `mode`)
+
+ for data in lines:
+ curcnt = int(data['COUNT'])
+ nbits = int(data['DataUnitLen'])
+ cipherkey = data['Key'].decode('hex')
+ iv = struct.pack('QQ', int(data['DataUnitSeqNumber']), 0)
+ pt = data['PT'].decode('hex')
+ ct = data['CT'].decode('hex')
+
+ if nbits % 128 != 0:
+ # XXX - mark as skipped
+ continue
+ if swapptct:
+ pt, ct = ct, pt
+ # run the fun
+ c = Crypto(meth, cipherkey, crid=crid)
+ r = curfun(c, pt, iv)
+ self.assertEqual(r, ct)
+
+ ###############
+ ##### DES #####
+ ###############
+ @unittest.skipIf(cname not in desmodules, 'skipping DES on %s' % `cname`)
+ def test_tdes(self):
+ for i in katg('KAT_TDES', 'TCBC[a-z]*.rsp'):
+ self.runTDES(i)
+
+ def runTDES(self, fname):
+ curfun = None
+ for mode, lines in cryptodev.KATParser(fname,
+ [ 'COUNT', 'KEYs', 'IV', 'PLAINTEXT', 'CIPHERTEXT', ]):
+ if mode == 'ENCRYPT':
+ swapptct = False
+ curfun = Crypto.encrypt
+ elif mode == 'DECRYPT':
+ swapptct = True
+ curfun = Crypto.decrypt
+ else:
+ raise RuntimeError('unknown mode: %s' % `mode`)
+
+ for data in lines:
+ curcnt = int(data['COUNT'])
+ key = data['KEYs'] * 3
+ cipherkey = key.decode('hex')
+ iv = data['IV'].decode('hex')
+ pt = data['PLAINTEXT'].decode('hex')
+ ct = data['CIPHERTEXT'].decode('hex')
+
+ if swapptct:
+ pt, ct = ct, pt
+ # run the fun
+ c = Crypto(cryptodev.CRYPTO_3DES_CBC, cipherkey, crid=crid)
+ r = curfun(c, pt, iv)
+ self.assertEqual(r, ct)
+
+ ###############
+ ##### SHA #####
+ ###############
+ @unittest.skipIf(cname not in shamodules, 'skipping SHA on %s' % `cname`)
+ def test_sha(self):
+ # SHA not available in software
+ pass
+ #for i in iglob('SHA1*'):
+ # self.runSHA(i)
+
+ def test_sha1hmac(self):
+ for i in katg('hmactestvectors', 'HMAC.rsp'):
+ self.runSHA1HMAC(i)
+
+ def runSHA1HMAC(self, fname):
+ for bogusmode, lines in cryptodev.KATParser(fname,
+ [ 'Count', 'Klen', 'Tlen', 'Key', 'Msg', 'Mac' ]):
+ for data in lines:
+ key = data['Key'].decode('hex')
+ msg = data['Msg'].decode('hex')
+ mac = data['Mac'].decode('hex')
+
+ if len(key) != 20:
+ # XXX - implementation bug
+ continue
+
+ c = Crypto(mac=cryptodev.CRYPTO_SHA1_HMAC,
+ mackey=key, crid=crid)
+
+ r = c.encrypt(msg)
+ self.assertEqual(r, mac, `data`)
+
+ return GendCryptoTestCase
+
+cryptosoft = GenTestCase('cryptosoft0')
+aesni = GenTestCase('aesni0')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/sys/opencrypto/dpkt.py b/tests/sys/opencrypto/dpkt.py
new file mode 100644
index 000000000000..b74aa35fbcd0
--- /dev/null
+++ b/tests/sys/opencrypto/dpkt.py
@@ -0,0 +1,160 @@
+# $FreeBSD$
+# $Id: dpkt.py 114 2005-09-11 15:15:12Z dugsong $
+
+"""fast, simple packet creation / parsing, with definitions for the
+basic TCP/IP protocols.
+"""
+
+__author__ = 'Dug Song <dugsong@monkey.org>'
+__copyright__ = 'Copyright (c) 2004 Dug Song'
+__license__ = 'BSD'
+__url__ = 'http://monkey.org/~dugsong/dpkt/'
+__version__ = '1.2'
+
+try:
+ from itertools import izip as _it_izip
+except ImportError:
+ _it_izip = zip
+
+from struct import calcsize as _st_calcsize, \
+ pack as _st_pack, unpack as _st_unpack, error as _st_error
+from re import compile as _re_compile
+
+intchr = _re_compile(r"(?P<int>[0-9]+)(?P<chr>.)")
+
+class MetaPacket(type):
+ def __new__(cls, clsname, clsbases, clsdict):
+ if '__hdr__' in clsdict:
+ st = clsdict['__hdr__']
+ clsdict['__hdr_fields__'] = [ x[0] for x in st ]
+ clsdict['__hdr_fmt__'] = clsdict.get('__byte_order__', '>') + \
+ ''.join([ x[1] for x in st ])
+ clsdict['__hdr_len__'] = _st_calcsize(clsdict['__hdr_fmt__'])
+ clsdict['__hdr_defaults__'] = \
+ dict(zip(clsdict['__hdr_fields__'], [ x[2] for x in st ]))
+ clsdict['__slots__'] = clsdict['__hdr_fields__']
+ return type.__new__(cls, clsname, clsbases, clsdict)
+
+class Packet(object):
+ """Packet class
+
+ __hdr__ should be defined as a list of (name, structfmt, default) tuples
+ __byte_order__ can be set to override the default ('>')
+ """
+ __metaclass__ = MetaPacket
+ data = ''
+
+ def __init__(self, *args, **kwargs):
+ """Packet constructor with ([buf], [field=val,...]) prototype.
+
+ Arguments:
+
+ buf -- packet buffer to unpack
+
+ Optional keyword arguments correspond to packet field names.
+ """
+ if args:
+ self.unpack(args[0])
+ else:
+ for k in self.__hdr_fields__:
+ setattr(self, k, self.__hdr_defaults__[k])
+ for k, v in kwargs.iteritems():
+ setattr(self, k, v)
+
+ def __len__(self):
+ return self.__hdr_len__ + len(self.data)
+
+ def __repr__(self):
+ l = [ '%s=%r' % (k, getattr(self, k))
+ for k in self.__hdr_defaults__
+ if getattr(self, k) != self.__hdr_defaults__[k] ]
+ if self.data:
+ l.append('data=%r' % self.data)
+ return '%s(%s)' % (self.__class__.__name__, ', '.join(l))
+
+ def __str__(self):
+ return self.pack_hdr() + str(self.data)
+
+ def pack_hdr(self):
+ """Return packed header string."""
+ try:
+ return _st_pack(self.__hdr_fmt__,
+ *[ getattr(self, k) for k in self.__hdr_fields__ ])
+ except _st_error:
+ vals = []
+ for k in self.__hdr_fields__:
+ v = getattr(self, k)
+ if isinstance(v, tuple):
+ vals.extend(v)
+ else:
+ vals.append(v)
+ return _st_pack(self.__hdr_fmt__, *vals)
+
+ def unpack(self, buf):
+ """Unpack packet header fields from buf, and set self.data."""
+
+ res = list(_st_unpack(self.__hdr_fmt__, buf[:self.__hdr_len__]))
+ for e, k in enumerate(self.__slots__):
+ sfmt = self.__hdr__[e][1]
+ mat = intchr.match(sfmt)
+ if mat and mat.group('chr') != 's':
+ cnt = int(mat.group('int'))
+ setattr(self, k, list(res[:cnt]))
+ del res[:cnt]
+ else:
+ if sfmt[-1] == 's':
+ i = res[0].find('\x00')
+ if i != -1:
+ res[0] = res[0][:i]
+ setattr(self, k, res[0])
+ del res[0]
+ assert len(res) == 0
+ self.data = buf[self.__hdr_len__:]
+
+# XXX - ''.join([(len(`chr(x)`)==3) and chr(x) or '.' for x in range(256)])
+__vis_filter = """................................ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[.]^_`abcdefghijklmnopqrstuvwxyz{|}~................................................................................................................................."""
+
+def hexdump(buf, length=16):
+ """Return a hexdump output string of the given buffer."""
+ n = 0
+ res = []
+ while buf:
+ line, buf = buf[:length], buf[length:]
+ hexa = ' '.join(['%02x' % ord(x) for x in line])
+ line = line.translate(__vis_filter)
+ res.append(' %04d: %-*s %s' % (n, length * 3, hexa, line))
+ n += length
+ return '\n'.join(res)
+
+def in_cksum_add(s, buf):
+ """in_cksum_add(cksum, buf) -> cksum
+
+ Return accumulated Internet checksum.
+ """
+ nleft = len(buf)
+ i = 0
+ while nleft > 1:
+ s += ord(buf[i]) * 256 + ord(buf[i+1])
+ i += 2
+ nleft -= 2
+ if nleft:
+ s += ord(buf[i]) * 256
+ return s
+
+def in_cksum_done(s):
+ """Fold and return Internet checksum."""
+ while (s >> 16):
+ s = (s >> 16) + (s & 0xffff)
+ return (~s & 0xffff)
+
+def in_cksum(buf):
+ """Return computed Internet checksum."""
+ return in_cksum_done(in_cksum_add(0, buf))
+
+try:
+ import psyco
+ psyco.bind(in_cksum)
+ psyco.bind(Packet)
+except ImportError:
+ pass
+
diff --git a/tests/sys/opencrypto/runtests.sh b/tests/sys/opencrypto/runtests.sh
new file mode 100755
index 000000000000..26c673a40b5f
--- /dev/null
+++ b/tests/sys/opencrypto/runtests.sh
@@ -0,0 +1,60 @@
+#!/bin/sh -
+#
+# Copyright (c) 2014 The FreeBSD Foundation
+# All rights reserved.
+#
+# This software was developed by John-Mark Gurney under
+# the sponsorship from the FreeBSD Foundation.
+# 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 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$
+#
+
+set -e
+
+if [ ! -d /usr/local/share/nist-kat ]; then
+ echo 'Skipping, nist-kat package not installed for test vectors.'
+ exit 0
+fi
+
+if kldload aesni 2>/dev/null; then
+ unloadaesni=1
+fi
+
+if kldload cryptodev 2>/dev/null; then
+ unloadcdev=1
+fi
+
+# Run software crypto test
+oldcdas=$(sysctl -e kern.cryptodevallowsoft)
+sysctl kern.cryptodevallowsoft=1
+
+python $(dirname $0)/cryptotest.py
+
+sysctl "$oldcdas"
+
+if [ x"$unloadcdev" = x"1" ]; then
+ kldunload cryptodev
+fi
+if [ x"$unloadaesni" = x"1" ]; then
+ kldunload aesni
+fi
diff --git a/tests/sys/pjdfstest/Makefile b/tests/sys/pjdfstest/Makefile
new file mode 100644
index 000000000000..77613029bd0a
--- /dev/null
+++ b/tests/sys/pjdfstest/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+SUBDIR+= pjdfstest
+SUBDIR+= tests
+
+SUBDIR_PARALLEL=
+
+.include <bsd.subdir.mk>
diff --git a/tests/sys/pjdfstest/pjdfstest/Makefile b/tests/sys/pjdfstest/pjdfstest/Makefile
new file mode 100644
index 000000000000..0d47366b6ee6
--- /dev/null
+++ b/tests/sys/pjdfstest/pjdfstest/Makefile
@@ -0,0 +1,20 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+.PATH: ${SRCTOP}/contrib/pjdfstest
+
+BINDIR= ${TESTSBASE}/sys/pjdfstest
+PROG= pjdfstest
+MAN=
+
+CFLAGS+= -D__OS_FreeBSD__ -DHAS_LCHMOD -DHAS_CHFLAGS -DHAS_FCHFLAGS
+CFLAGS+= -DHAS_CHFLAGSAT -DHAS_LCHFLAGS -DHAS_FREEBSD_ACL -DHAS_BINDAT
+CFLAGS+= -DHAS_CONNECTAT
+
+# XXX: the upstream sources need some massaging with how it assigns ngroups in
+# pjdfstest.c
+NO_WERROR=
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/tests/sys/pjdfstest/pjdfstest/Makefile.depend b/tests/sys/pjdfstest/pjdfstest/Makefile.depend
new file mode 100644
index 000000000000..3646e2e2b1af
--- /dev/null
+++ b/tests/sys/pjdfstest/pjdfstest/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/Makefile b/tests/sys/pjdfstest/tests/Makefile
new file mode 100644
index 000000000000..f3757f27d3ef
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/Makefile
@@ -0,0 +1,39 @@
+# $FreeBSD$
+
+PACKAGE= tests
+PJDFSTEST_SRCDIR= ${SRCTOP}/contrib/pjdfstest
+
+TESTSDIR= ${TESTSBASE}/sys/pjdfstest
+
+.PATH: ${.CURDIR}
+
+${PACKAGE}FILES+= conf
+${PACKAGE}FILES+= misc.sh
+CLEANFILES+= misc.sh misc.sh.tmp
+
+misc.sh: ${PJDFSTEST_SRCDIR}/tests/misc.sh
+ sed -e '\,maindir=,s,maindir=.*,maindir=`dirname $$0`/../,' < ${.ALLSRC} > ${.TARGET}.tmp
+ mv ${.TARGET}.tmp ${.TARGET}
+
+afterinstall: install-tests-symlink
+install-tests-symlink: .PHONY
+ rm -f ${DESTDIR}${TESTSDIR}/tests
+ ${INSTALL_SYMLINK} . ${DESTDIR}${TESTSDIR}/tests
+
+TESTS_SUBDIRS= chflags
+TESTS_SUBDIRS+= chmod
+TESTS_SUBDIRS+= chown
+TESTS_SUBDIRS+= ftruncate
+TESTS_SUBDIRS+= granular
+TESTS_SUBDIRS+= link
+TESTS_SUBDIRS+= mkdir
+TESTS_SUBDIRS+= mkfifo
+TESTS_SUBDIRS+= mknod
+TESTS_SUBDIRS+= open
+TESTS_SUBDIRS+= rename
+TESTS_SUBDIRS+= rmdir
+TESTS_SUBDIRS+= symlink
+TESTS_SUBDIRS+= truncate
+TESTS_SUBDIRS+= unlink
+
+.include <bsd.test.mk>
diff --git a/tests/sys/pjdfstest/tests/Makefile.depend b/tests/sys/pjdfstest/tests/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/chflags/Makefile b/tests/sys/pjdfstest/tests/chflags/Makefile
new file mode 100644
index 000000000000..3b6a5265fdd4
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/chflags/Makefile
@@ -0,0 +1,18 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+TAP_TESTS_SH+= 06
+TAP_TESTS_SH+= 07
+TAP_TESTS_SH+= 08
+TAP_TESTS_SH+= 09
+TAP_TESTS_SH+= 10
+TAP_TESTS_SH+= 11
+TAP_TESTS_SH+= 12
+TAP_TESTS_SH+= 13
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/chflags/Makefile.depend b/tests/sys/pjdfstest/tests/chflags/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/chflags/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/chmod/Makefile b/tests/sys/pjdfstest/tests/chmod/Makefile
new file mode 100644
index 000000000000..cc0a3bbf1af4
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/chmod/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+TAP_TESTS_SH+= 06
+TAP_TESTS_SH+= 07
+TAP_TESTS_SH+= 08
+TAP_TESTS_SH+= 09
+TAP_TESTS_SH+= 10
+TAP_TESTS_SH+= 11
+TAP_TESTS_SH+= 12
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/chmod/Makefile.depend b/tests/sys/pjdfstest/tests/chmod/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/chmod/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/chown/Makefile b/tests/sys/pjdfstest/tests/chown/Makefile
new file mode 100644
index 000000000000..665b6538d19e
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/chown/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+TAP_TESTS_SH+= 06
+TAP_TESTS_SH+= 07
+TAP_TESTS_SH+= 08
+TAP_TESTS_SH+= 09
+TAP_TESTS_SH+= 10
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/chown/Makefile.depend b/tests/sys/pjdfstest/tests/chown/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/chown/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/conf b/tests/sys/pjdfstest/tests/conf
new file mode 100644
index 000000000000..7a1168efea13
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/conf
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# $FreeBSD$
+
+: ${PJDFSTEST_TEST_PATH=.}
+if ! cd ${PJDFSTEST_TEST_PATH}/; then
+ echo 'Please set PJDFSTEST_TEST_PATH to a valid filesystem path'
+ echo 'Bail out!'
+ exit 1
+fi
+
+GREP=grep
+mountpoint=$(df . | tail -1 | awk '{print $6}')
+fs=$(mount -p | awk '$2 == "'$mountpoint'" { print toupper($3) }')
+os=FreeBSD
diff --git a/tests/sys/pjdfstest/tests/ftruncate/Makefile b/tests/sys/pjdfstest/tests/ftruncate/Makefile
new file mode 100644
index 000000000000..da2ce584d3af
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/ftruncate/Makefile
@@ -0,0 +1,19 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+TAP_TESTS_SH+= 06
+TAP_TESTS_SH+= 07
+TAP_TESTS_SH+= 08
+TAP_TESTS_SH+= 09
+TAP_TESTS_SH+= 10
+TAP_TESTS_SH+= 11
+TAP_TESTS_SH+= 12
+TAP_TESTS_SH+= 13
+TAP_TESTS_SH+= 14
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/ftruncate/Makefile.depend b/tests/sys/pjdfstest/tests/ftruncate/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/ftruncate/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/granular/Makefile b/tests/sys/pjdfstest/tests/granular/Makefile
new file mode 100644
index 000000000000..3f935e4e99ce
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/granular/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/granular/Makefile.depend b/tests/sys/pjdfstest/tests/granular/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/granular/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/link/Makefile b/tests/sys/pjdfstest/tests/link/Makefile
new file mode 100644
index 000000000000..14c7525689b0
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/link/Makefile
@@ -0,0 +1,22 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+TAP_TESTS_SH+= 06
+TAP_TESTS_SH+= 07
+TAP_TESTS_SH+= 08
+TAP_TESTS_SH+= 09
+TAP_TESTS_SH+= 10
+TAP_TESTS_SH+= 11
+TAP_TESTS_SH+= 12
+TAP_TESTS_SH+= 13
+TAP_TESTS_SH+= 14
+TAP_TESTS_SH+= 15
+TAP_TESTS_SH+= 16
+TAP_TESTS_SH+= 17
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/link/Makefile.depend b/tests/sys/pjdfstest/tests/link/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/link/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/mkdir/Makefile b/tests/sys/pjdfstest/tests/mkdir/Makefile
new file mode 100644
index 000000000000..cc0a3bbf1af4
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/mkdir/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+TAP_TESTS_SH+= 06
+TAP_TESTS_SH+= 07
+TAP_TESTS_SH+= 08
+TAP_TESTS_SH+= 09
+TAP_TESTS_SH+= 10
+TAP_TESTS_SH+= 11
+TAP_TESTS_SH+= 12
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/mkdir/Makefile.depend b/tests/sys/pjdfstest/tests/mkdir/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/mkdir/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/mkfifo/Makefile b/tests/sys/pjdfstest/tests/mkfifo/Makefile
new file mode 100644
index 000000000000..cc0a3bbf1af4
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/mkfifo/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+TAP_TESTS_SH+= 06
+TAP_TESTS_SH+= 07
+TAP_TESTS_SH+= 08
+TAP_TESTS_SH+= 09
+TAP_TESTS_SH+= 10
+TAP_TESTS_SH+= 11
+TAP_TESTS_SH+= 12
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/mkfifo/Makefile.depend b/tests/sys/pjdfstest/tests/mkfifo/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/mkfifo/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/mknod/Makefile b/tests/sys/pjdfstest/tests/mknod/Makefile
new file mode 100644
index 000000000000..1eaeaa09818e
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/mknod/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+TAP_TESTS_SH+= 06
+TAP_TESTS_SH+= 07
+TAP_TESTS_SH+= 08
+TAP_TESTS_SH+= 09
+TAP_TESTS_SH+= 10
+TAP_TESTS_SH+= 11
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/mknod/Makefile.depend b/tests/sys/pjdfstest/tests/mknod/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/mknod/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/open/Makefile b/tests/sys/pjdfstest/tests/open/Makefile
new file mode 100644
index 000000000000..bd2f7b5a985d
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/open/Makefile
@@ -0,0 +1,29 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+TAP_TESTS_SH+= 06
+TAP_TESTS_SH+= 07
+TAP_TESTS_SH+= 08
+TAP_TESTS_SH+= 09
+TAP_TESTS_SH+= 10
+TAP_TESTS_SH+= 11
+TAP_TESTS_SH+= 12
+TAP_TESTS_SH+= 13
+TAP_TESTS_SH+= 14
+TAP_TESTS_SH+= 15
+TAP_TESTS_SH+= 16
+TAP_TESTS_SH+= 17
+TAP_TESTS_SH+= 18
+TAP_TESTS_SH+= 19
+TAP_TESTS_SH+= 20
+TAP_TESTS_SH+= 21
+TAP_TESTS_SH+= 22
+TAP_TESTS_SH+= 23
+TAP_TESTS_SH+= 24
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/open/Makefile.depend b/tests/sys/pjdfstest/tests/open/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/open/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/pjdfstest.test.mk b/tests/sys/pjdfstest/tests/pjdfstest.test.mk
new file mode 100644
index 000000000000..3dc784f7eb63
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/pjdfstest.test.mk
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+PJDFSTEST_SRCDIR= ${.CURDIR:H:H:H:H:H}/contrib/pjdfstest
+
+.PATH: ${PJDFSTEST_SRCDIR}/tests/${.CURDIR:T}
+
+TESTSDIR?= ${TESTSBASE}/sys/pjdfstest/${.CURDIR:T}
+
+.for s in ${TAP_TESTS_SH}
+TAP_TESTS_SH_SRC_$s= $s.t
+TEST_METADATA.$s+= required_user="root"
+.endfor
+
+.include <bsd.test.mk>
diff --git a/tests/sys/pjdfstest/tests/rename/Makefile b/tests/sys/pjdfstest/tests/rename/Makefile
new file mode 100644
index 000000000000..7eada047ef15
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/rename/Makefile
@@ -0,0 +1,26 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+TAP_TESTS_SH+= 06
+TAP_TESTS_SH+= 07
+TAP_TESTS_SH+= 08
+TAP_TESTS_SH+= 09
+TAP_TESTS_SH+= 10
+TAP_TESTS_SH+= 11
+TAP_TESTS_SH+= 12
+TAP_TESTS_SH+= 13
+TAP_TESTS_SH+= 14
+TAP_TESTS_SH+= 15
+TAP_TESTS_SH+= 16
+TAP_TESTS_SH+= 17
+TAP_TESTS_SH+= 18
+TAP_TESTS_SH+= 19
+TAP_TESTS_SH+= 20
+TAP_TESTS_SH+= 21
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/rename/Makefile.depend b/tests/sys/pjdfstest/tests/rename/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/rename/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/rmdir/Makefile b/tests/sys/pjdfstest/tests/rmdir/Makefile
new file mode 100644
index 000000000000..301e4e5c33d1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/rmdir/Makefile
@@ -0,0 +1,20 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+TAP_TESTS_SH+= 06
+TAP_TESTS_SH+= 07
+TAP_TESTS_SH+= 08
+TAP_TESTS_SH+= 09
+TAP_TESTS_SH+= 10
+TAP_TESTS_SH+= 11
+TAP_TESTS_SH+= 12
+TAP_TESTS_SH+= 13
+TAP_TESTS_SH+= 14
+TAP_TESTS_SH+= 15
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/rmdir/Makefile.depend b/tests/sys/pjdfstest/tests/rmdir/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/rmdir/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/symlink/Makefile b/tests/sys/pjdfstest/tests/symlink/Makefile
new file mode 100644
index 000000000000..cc0a3bbf1af4
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/symlink/Makefile
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+TAP_TESTS_SH+= 06
+TAP_TESTS_SH+= 07
+TAP_TESTS_SH+= 08
+TAP_TESTS_SH+= 09
+TAP_TESTS_SH+= 10
+TAP_TESTS_SH+= 11
+TAP_TESTS_SH+= 12
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/symlink/Makefile.depend b/tests/sys/pjdfstest/tests/symlink/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/symlink/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/truncate/Makefile b/tests/sys/pjdfstest/tests/truncate/Makefile
new file mode 100644
index 000000000000..da2ce584d3af
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/truncate/Makefile
@@ -0,0 +1,19 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+TAP_TESTS_SH+= 06
+TAP_TESTS_SH+= 07
+TAP_TESTS_SH+= 08
+TAP_TESTS_SH+= 09
+TAP_TESTS_SH+= 10
+TAP_TESTS_SH+= 11
+TAP_TESTS_SH+= 12
+TAP_TESTS_SH+= 13
+TAP_TESTS_SH+= 14
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/truncate/Makefile.depend b/tests/sys/pjdfstest/tests/truncate/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/truncate/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/pjdfstest/tests/unlink/Makefile b/tests/sys/pjdfstest/tests/unlink/Makefile
new file mode 100644
index 000000000000..3b6a5265fdd4
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/unlink/Makefile
@@ -0,0 +1,18 @@
+# $FreeBSD$
+
+TAP_TESTS_SH= 00
+TAP_TESTS_SH+= 01
+TAP_TESTS_SH+= 02
+TAP_TESTS_SH+= 03
+TAP_TESTS_SH+= 04
+TAP_TESTS_SH+= 05
+TAP_TESTS_SH+= 06
+TAP_TESTS_SH+= 07
+TAP_TESTS_SH+= 08
+TAP_TESTS_SH+= 09
+TAP_TESTS_SH+= 10
+TAP_TESTS_SH+= 11
+TAP_TESTS_SH+= 12
+TAP_TESTS_SH+= 13
+
+.include "../pjdfstest.test.mk"
diff --git a/tests/sys/pjdfstest/tests/unlink/Makefile.depend b/tests/sys/pjdfstest/tests/unlink/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/pjdfstest/tests/unlink/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/posixshm/Makefile b/tests/sys/posixshm/Makefile
new file mode 100644
index 000000000000..6e9e345d5b16
--- /dev/null
+++ b/tests/sys/posixshm/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/posixshm
+
+ATF_TESTS_C+= posixshm_test
+
+.include <bsd.test.mk>
diff --git a/tests/sys/posixshm/Makefile.depend b/tests/sys/posixshm/Makefile.depend
new file mode 100644
index 000000000000..74074b9588ec
--- /dev/null
+++ b/tests/sys/posixshm/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/atf/libatf-c \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/posixshm/posixshm_test.c b/tests/sys/posixshm/posixshm_test.c
new file mode 100644
index 000000000000..05774df2b280
--- /dev/null
+++ b/tests/sys/posixshm/posixshm_test.c
@@ -0,0 +1,635 @@
+/*-
+ * Copyright (c) 2006 Robert N. M. Watson
+ * 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 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 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#define TEST_PATH_LEN 256
+static char test_path[TEST_PATH_LEN];
+
+static void
+gen_test_path(void)
+{
+
+ snprintf(test_path, sizeof(test_path), "%s/tmp.XXXXXX",
+ getenv("TMPDIR") == NULL ? "/tmp" : getenv("TMPDIR"));
+ test_path[sizeof(test_path) - 1] = '\0';
+ ATF_REQUIRE_MSG(mkstemp(test_path) != -1,
+ "mkstemp failed; errno=%d", errno);
+ ATF_REQUIRE_MSG(unlink(test_path) == 0,
+ "unlink failed; errno=%d", errno);
+}
+
+/*
+ * Attempt a shm_open() that should fail with an expected error of 'error'.
+ */
+static void
+shm_open_should_fail(const char *path, int flags, mode_t mode, int error)
+{
+ int fd;
+
+ fd = shm_open(path, flags, mode);
+ ATF_CHECK_MSG(fd == -1, "shm_open didn't fail");
+ ATF_CHECK_MSG(error == errno,
+ "shm_open didn't fail with expected errno; errno=%d; expected "
+ "errno=%d", errno, error);
+}
+
+/*
+ * Attempt a shm_unlink() that should fail with an expected error of 'error'.
+ */
+static void
+shm_unlink_should_fail(const char *path, int error)
+{
+
+ ATF_CHECK_MSG(shm_unlink(path) == -1, "shm_unlink didn't fail");
+ ATF_CHECK_MSG(error == errno,
+ "shm_unlink didn't fail with expected errno; errno=%d; expected "
+ "errno=%d", errno, error);
+}
+
+/*
+ * Open the test object and write '1' to the first byte. Returns valid fd
+ * on success and -1 on failure.
+ */
+static int
+scribble_object(void)
+{
+ char *page;
+ int fd, pagesize;
+
+ gen_test_path();
+
+ ATF_REQUIRE(0 < (pagesize = getpagesize()));
+
+ fd = shm_open(test_path, O_CREAT|O_EXCL|O_RDWR, 0777);
+ if (fd < 0 && errno == EEXIST) {
+ if (shm_unlink(test_path) < 0)
+ atf_tc_fail("shm_unlink");
+ fd = shm_open(test_path, O_CREAT | O_EXCL | O_RDWR, 0777);
+ }
+ if (fd < 0)
+ atf_tc_fail("shm_open failed; errno=%d", errno);
+ if (ftruncate(fd, pagesize) < 0)
+ atf_tc_fail("ftruncate failed; errno=%d", errno);
+
+ page = mmap(0, pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (page == MAP_FAILED)
+ atf_tc_fail("mmap failed; errno=%d", errno);
+
+ page[0] = '1';
+ ATF_REQUIRE_MSG(munmap(page, pagesize) == 0, "munmap failed; errno=%d",
+ errno);
+
+ return (fd);
+}
+
+ATF_TC_WITHOUT_HEAD(remap_object);
+ATF_TC_BODY(remap_object, tc)
+{
+ char *page;
+ int fd, pagesize;
+
+ ATF_REQUIRE(0 < (pagesize = getpagesize()));
+
+ fd = scribble_object();
+
+ page = mmap(0, pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (page == MAP_FAILED)
+ atf_tc_fail("mmap(2) failed; errno=%d", errno);
+
+ if (page[0] != '1')
+ atf_tc_fail("missing data ('%c' != '1')", page[0]);
+
+ close(fd);
+ ATF_REQUIRE_MSG(munmap(page, pagesize) == 0, "munmap failed; errno=%d",
+ errno);
+
+ ATF_REQUIRE_MSG(shm_unlink(test_path) != -1,
+ "shm_unlink failed; errno=%d", errno);
+}
+
+ATF_TC_WITHOUT_HEAD(reopen_object);
+ATF_TC_BODY(reopen_object, tc)
+{
+ char *page;
+ int fd, pagesize;
+
+ ATF_REQUIRE(0 < (pagesize = getpagesize()));
+
+ fd = scribble_object();
+ close(fd);
+
+ fd = shm_open(test_path, O_RDONLY, 0777);
+ if (fd < 0)
+ atf_tc_fail("shm_open(2) failed; errno=%d", errno);
+
+ page = mmap(0, pagesize, PROT_READ, MAP_SHARED, fd, 0);
+ if (page == MAP_FAILED)
+ atf_tc_fail("mmap(2) failed; errno=%d", errno);
+
+ if (page[0] != '1')
+ atf_tc_fail("missing data ('%c' != '1')", page[0]);
+
+ ATF_REQUIRE_MSG(munmap(page, pagesize) == 0, "munmap failed; errno=%d",
+ errno);
+ close(fd);
+ ATF_REQUIRE_MSG(shm_unlink(test_path) != -1,
+ "shm_unlink failed; errno=%d", errno);
+}
+
+ATF_TC_WITHOUT_HEAD(readonly_mmap_write);
+ATF_TC_BODY(readonly_mmap_write, tc)
+{
+ char *page;
+ int fd, pagesize;
+
+ ATF_REQUIRE(0 < (pagesize = getpagesize()));
+
+ gen_test_path();
+
+ fd = shm_open(test_path, O_RDONLY | O_CREAT, 0777);
+ ATF_REQUIRE_MSG(fd >= 0, "shm_open failed; errno=%d", errno);
+
+ /* PROT_WRITE should fail with EACCES. */
+ page = mmap(0, pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (page != MAP_FAILED)
+ atf_tc_fail("mmap(PROT_WRITE) succeeded unexpectedly");
+
+ if (errno != EACCES)
+ atf_tc_fail("mmap(PROT_WRITE) didn't fail with EACCES; "
+ "errno=%d", errno);
+
+ close(fd);
+ ATF_REQUIRE_MSG(shm_unlink(test_path) != -1,
+ "shm_unlink failed; errno=%d", errno);
+}
+
+ATF_TC_WITHOUT_HEAD(open_after_link);
+ATF_TC_BODY(open_after_link, tc)
+{
+ int fd;
+
+ gen_test_path();
+
+ fd = shm_open(test_path, O_RDONLY | O_CREAT, 0777);
+ ATF_REQUIRE_MSG(fd >= 0, "shm_open(1) failed; errno=%d", errno);
+ close(fd);
+
+ ATF_REQUIRE_MSG(shm_unlink(test_path) != -1, "shm_unlink failed: %d",
+ errno);
+
+ shm_open_should_fail(test_path, O_RDONLY, 0777, ENOENT);
+}
+
+ATF_TC_WITHOUT_HEAD(open_invalid_path);
+ATF_TC_BODY(open_invalid_path, tc)
+{
+
+ shm_open_should_fail("blah", O_RDONLY, 0777, EINVAL);
+}
+
+ATF_TC_WITHOUT_HEAD(open_write_only);
+ATF_TC_BODY(open_write_only, tc)
+{
+
+ gen_test_path();
+
+ shm_open_should_fail(test_path, O_WRONLY, 0777, EINVAL);
+}
+
+ATF_TC_WITHOUT_HEAD(open_extra_flags);
+ATF_TC_BODY(open_extra_flags, tc)
+{
+
+ gen_test_path();
+
+ shm_open_should_fail(test_path, O_RDONLY | O_DIRECT, 0777, EINVAL);
+}
+
+ATF_TC_WITHOUT_HEAD(open_anon);
+ATF_TC_BODY(open_anon, tc)
+{
+ int fd;
+
+ fd = shm_open(SHM_ANON, O_RDWR, 0777);
+ ATF_REQUIRE_MSG(fd >= 0, "shm_open failed; errno=%d", errno);
+ close(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(open_anon_readonly);
+ATF_TC_BODY(open_anon_readonly, tc)
+{
+
+ shm_open_should_fail(SHM_ANON, O_RDONLY, 0777, EINVAL);
+}
+
+ATF_TC_WITHOUT_HEAD(open_bad_path_pointer);
+ATF_TC_BODY(open_bad_path_pointer, tc)
+{
+
+ shm_open_should_fail((char *)1024, O_RDONLY, 0777, EFAULT);
+}
+
+ATF_TC_WITHOUT_HEAD(open_path_too_long);
+ATF_TC_BODY(open_path_too_long, tc)
+{
+ char *page;
+
+ page = malloc(MAXPATHLEN + 1);
+ memset(page, 'a', MAXPATHLEN);
+ page[MAXPATHLEN] = '\0';
+ shm_open_should_fail(page, O_RDONLY, 0777, ENAMETOOLONG);
+ free(page);
+}
+
+ATF_TC_WITHOUT_HEAD(open_nonexisting_object);
+ATF_TC_BODY(open_nonexisting_object, tc)
+{
+
+ shm_open_should_fail("/notreallythere", O_RDONLY, 0777, ENOENT);
+}
+
+ATF_TC_WITHOUT_HEAD(open_create_existing_object);
+ATF_TC_BODY(open_create_existing_object, tc)
+{
+ int fd;
+
+ gen_test_path();
+
+ fd = shm_open(test_path, O_RDONLY|O_CREAT, 0777);
+ ATF_REQUIRE_MSG(fd >= 0, "shm_open failed; errno=%d", errno);
+ close(fd);
+
+ shm_open_should_fail(test_path, O_RDONLY|O_CREAT|O_EXCL,
+ 0777, EEXIST);
+
+ ATF_REQUIRE_MSG(shm_unlink(test_path) != -1,
+ "shm_unlink failed; errno=%d", errno);
+}
+
+ATF_TC_WITHOUT_HEAD(trunc_resets_object);
+ATF_TC_BODY(trunc_resets_object, tc)
+{
+ struct stat sb;
+ int fd;
+
+ gen_test_path();
+
+ /* Create object and set size to 1024. */
+ fd = shm_open(test_path, O_RDWR | O_CREAT, 0777);
+ ATF_REQUIRE_MSG(fd >= 0, "shm_open(1) failed; errno=%d", errno);
+ ATF_REQUIRE_MSG(ftruncate(fd, 1024) != -1,
+ "ftruncate failed; errno=%d", errno);
+ ATF_REQUIRE_MSG(fstat(fd, &sb) != -1,
+ "fstat(1) failed; errno=%d", errno);
+ ATF_REQUIRE_MSG(sb.st_size == 1024, "size %d != 1024", (int)sb.st_size);
+ close(fd);
+
+ /* Open with O_TRUNC which should reset size to 0. */
+ fd = shm_open(test_path, O_RDWR | O_TRUNC, 0777);
+ ATF_REQUIRE_MSG(fd >= 0, "shm_open(2) failed; errno=%d", errno);
+ ATF_REQUIRE_MSG(fstat(fd, &sb) != -1,
+ "fstat(2) failed; errno=%d", errno);
+ ATF_REQUIRE_MSG(sb.st_size == 0,
+ "size was not 0 after truncation: %d", (int)sb.st_size);
+ close(fd);
+ ATF_REQUIRE_MSG(shm_unlink(test_path) != -1,
+ "shm_unlink failed; errno=%d", errno);
+}
+
+ATF_TC_WITHOUT_HEAD(unlink_bad_path_pointer);
+ATF_TC_BODY(unlink_bad_path_pointer, tc)
+{
+
+ shm_unlink_should_fail((char *)1024, EFAULT);
+}
+
+ATF_TC_WITHOUT_HEAD(unlink_path_too_long);
+ATF_TC_BODY(unlink_path_too_long, tc)
+{
+ char *page;
+
+ page = malloc(MAXPATHLEN + 1);
+ memset(page, 'a', MAXPATHLEN);
+ page[MAXPATHLEN] = '\0';
+ shm_unlink_should_fail(page, ENAMETOOLONG);
+ free(page);
+}
+
+ATF_TC_WITHOUT_HEAD(object_resize);
+ATF_TC_BODY(object_resize, tc)
+{
+ pid_t pid;
+ struct stat sb;
+ char *page;
+ int fd, pagesize, status;
+
+ ATF_REQUIRE(0 < (pagesize = getpagesize()));
+
+ /* Start off with a size of a single page. */
+ fd = shm_open(SHM_ANON, O_CREAT|O_RDWR, 0777);
+ if (fd < 0)
+ atf_tc_fail("shm_open failed; errno=%d", errno);
+
+ if (ftruncate(fd, pagesize) < 0)
+ atf_tc_fail("ftruncate(1) failed; errno=%d", errno);
+
+ if (fstat(fd, &sb) < 0)
+ atf_tc_fail("fstat(1) failed; errno=%d", errno);
+
+ if (sb.st_size != pagesize)
+ atf_tc_fail("first resize failed (%d != %d)",
+ (int)sb.st_size, pagesize);
+
+ /* Write a '1' to the first byte. */
+ page = mmap(0, pagesize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+ if (page == MAP_FAILED)
+ atf_tc_fail("mmap(1)");
+
+ page[0] = '1';
+
+ ATF_REQUIRE_MSG(munmap(page, pagesize) == 0, "munmap failed; errno=%d",
+ errno);
+
+ /* Grow the object to 2 pages. */
+ if (ftruncate(fd, pagesize * 2) < 0)
+ atf_tc_fail("ftruncate(2) failed; errno=%d", errno);
+
+ if (fstat(fd, &sb) < 0)
+ atf_tc_fail("fstat(2) failed; errno=%d", errno);
+
+ if (sb.st_size != pagesize * 2)
+ atf_tc_fail("second resize failed (%d != %d)",
+ (int)sb.st_size, pagesize * 2);
+
+ /* Check for '1' at the first byte. */
+ page = mmap(0, pagesize * 2, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+ if (page == MAP_FAILED)
+ atf_tc_fail("mmap(2) failed; errno=%d", errno);
+
+ if (page[0] != '1')
+ atf_tc_fail("'%c' != '1'", page[0]);
+
+ /* Write a '2' at the start of the second page. */
+ page[pagesize] = '2';
+
+ /* Shrink the object back to 1 page. */
+ if (ftruncate(fd, pagesize) < 0)
+ atf_tc_fail("ftruncate(3) failed; errno=%d", errno);
+
+ if (fstat(fd, &sb) < 0)
+ atf_tc_fail("fstat(3) failed; errno=%d", errno);
+
+ if (sb.st_size != pagesize)
+ atf_tc_fail("third resize failed (%d != %d)",
+ (int)sb.st_size, pagesize);
+
+ /*
+ * Fork a child process to make sure the second page is no
+ * longer valid.
+ */
+ pid = fork();
+ if (pid == -1)
+ atf_tc_fail("fork failed; errno=%d", errno);
+
+ if (pid == 0) {
+ struct rlimit lim;
+ char c;
+
+ /* Don't generate a core dump. */
+ ATF_REQUIRE(getrlimit(RLIMIT_CORE, &lim) == 0);
+ lim.rlim_cur = 0;
+ ATF_REQUIRE(setrlimit(RLIMIT_CORE, &lim) == 0);
+
+ /*
+ * The previous ftruncate(2) shrunk the backing object
+ * so that this address is no longer valid, so reading
+ * from it should trigger a SIGSEGV.
+ */
+ c = page[pagesize];
+ fprintf(stderr, "child: page 1: '%c'\n", c);
+ exit(0);
+ }
+
+ if (wait(&status) < 0)
+ atf_tc_fail("wait failed; errno=%d", errno);
+
+ if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGSEGV)
+ atf_tc_fail("child terminated with status %x", status);
+
+ /* Grow the object back to 2 pages. */
+ if (ftruncate(fd, pagesize * 2) < 0)
+ atf_tc_fail("ftruncate(2) failed; errno=%d", errno);
+
+ if (fstat(fd, &sb) < 0)
+ atf_tc_fail("fstat(2) failed; errno=%d", errno);
+
+ if (sb.st_size != pagesize * 2)
+ atf_tc_fail("fourth resize failed (%d != %d)",
+ (int)sb.st_size, pagesize);
+
+ /*
+ * Note that the mapping at 'page' for the second page is
+ * still valid, and now that the shm object has been grown
+ * back up to 2 pages, there is now memory backing this page
+ * so the read will work. However, the data should be zero
+ * rather than '2' as the old data was thrown away when the
+ * object was shrunk and the new pages when an object are
+ * grown are zero-filled.
+ */
+ if (page[pagesize] != 0)
+ atf_tc_fail("invalid data at %d: %x != 0",
+ pagesize, (int)page[pagesize]);
+
+ close(fd);
+}
+
+/* Signal handler which does nothing. */
+static void
+ignoreit(int sig __unused)
+{
+ ;
+}
+
+ATF_TC_WITHOUT_HEAD(shm_functionality_across_fork);
+ATF_TC_BODY(shm_functionality_across_fork, tc)
+{
+ char *cp, c;
+ int error, desc, rv;
+ long scval;
+ sigset_t ss;
+ struct sigaction sa;
+ void *region;
+ size_t i, psize;
+
+#ifndef _POSIX_SHARED_MEMORY_OBJECTS
+ printf("_POSIX_SHARED_MEMORY_OBJECTS is undefined\n");
+#else
+ printf("_POSIX_SHARED_MEMORY_OBJECTS is defined as %ld\n",
+ (long)_POSIX_SHARED_MEMORY_OBJECTS - 0);
+ if (_POSIX_SHARED_MEMORY_OBJECTS - 0 == -1)
+ printf("***Indicates this feature may be unsupported!\n");
+#endif
+ errno = 0;
+ scval = sysconf(_SC_SHARED_MEMORY_OBJECTS);
+ if (scval == -1 && errno != 0) {
+ atf_tc_fail("sysconf(_SC_SHARED_MEMORY_OBJECTS) failed; "
+ "errno=%d", errno);
+ } else {
+ printf("sysconf(_SC_SHARED_MEMORY_OBJECTS) returns %ld\n",
+ scval);
+ if (scval == -1)
+ printf("***Indicates this feature is unsupported!\n");
+ }
+
+ errno = 0;
+ scval = sysconf(_SC_PAGESIZE);
+ if (scval == -1 && errno != 0) {
+ atf_tc_fail("sysconf(_SC_PAGESIZE) failed; errno=%d", errno);
+ } else if (scval <= 0) {
+ fprintf(stderr, "bogus return from sysconf(_SC_PAGESIZE): %ld",
+ scval);
+ psize = 4096;
+ } else {
+ printf("sysconf(_SC_PAGESIZE) returns %ld\n", scval);
+ psize = scval;
+ }
+
+ gen_test_path();
+ desc = shm_open(test_path, O_EXCL | O_CREAT | O_RDWR, 0600);
+
+ ATF_REQUIRE_MSG(desc >= 0, "shm_open failed; errno=%d", errno);
+ ATF_REQUIRE_MSG(shm_unlink(test_path) == 0,
+ "shm_unlink failed; errno=%d", errno);
+ ATF_REQUIRE_MSG(ftruncate(desc, (off_t)psize) != -1,
+ "ftruncate failed; errno=%d", errno);
+
+ region = mmap(NULL, psize, PROT_READ | PROT_WRITE, MAP_SHARED, desc, 0);
+ ATF_REQUIRE_MSG(region != MAP_FAILED, "mmap failed; errno=%d", errno);
+ memset(region, '\377', psize);
+
+ sa.sa_flags = 0;
+ sa.sa_handler = ignoreit;
+ sigemptyset(&sa.sa_mask);
+ ATF_REQUIRE_MSG(sigaction(SIGUSR1, &sa, (struct sigaction *)0) == 0,
+ "sigaction failed; errno=%d", errno);
+
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGUSR1);
+ ATF_REQUIRE_MSG(sigprocmask(SIG_BLOCK, &ss, (sigset_t *)0) == 0,
+ "sigprocmask failed; errno=%d", errno);
+
+ rv = fork();
+ ATF_REQUIRE_MSG(rv != -1, "fork failed; errno=%d", errno);
+ if (rv == 0) {
+ sigemptyset(&ss);
+ sigsuspend(&ss);
+
+ for (cp = region; cp < (char *)region + psize; cp++) {
+ if (*cp != '\151')
+ _exit(1);
+ }
+ if (lseek(desc, 0, SEEK_SET) == -1)
+ _exit(1);
+ for (i = 0; i < psize; i++) {
+ error = read(desc, &c, 1);
+ if (c != '\151')
+ _exit(1);
+ }
+ _exit(0);
+ } else {
+ int status;
+
+ memset(region, '\151', psize - 2);
+ error = pwrite(desc, region, 2, psize - 2);
+ if (error != 2) {
+ if (error >= 0)
+ atf_tc_fail("short write; %d bytes written",
+ error);
+ else
+ atf_tc_fail("shmfd write");
+ }
+ kill(rv, SIGUSR1);
+ waitpid(rv, &status, 0);
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+ printf("Functionality test successful\n");
+ } else if (WIFEXITED(status)) {
+ atf_tc_fail("Child process exited with status %d",
+ WEXITSTATUS(status));
+ } else {
+ atf_tc_fail("Child process terminated with %s",
+ strsignal(WTERMSIG(status)));
+ }
+ }
+
+ ATF_REQUIRE_MSG(munmap(region, psize) == 0, "munmap failed; errno=%d",
+ errno);
+ shm_unlink(test_path);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, remap_object);
+ ATF_TP_ADD_TC(tp, reopen_object);
+ ATF_TP_ADD_TC(tp, readonly_mmap_write);
+ ATF_TP_ADD_TC(tp, open_after_link);
+ ATF_TP_ADD_TC(tp, open_invalid_path);
+ ATF_TP_ADD_TC(tp, open_write_only);
+ ATF_TP_ADD_TC(tp, open_extra_flags);
+ ATF_TP_ADD_TC(tp, open_anon);
+ ATF_TP_ADD_TC(tp, open_anon_readonly);
+ ATF_TP_ADD_TC(tp, open_bad_path_pointer);
+ ATF_TP_ADD_TC(tp, open_path_too_long);
+ ATF_TP_ADD_TC(tp, open_nonexisting_object);
+ ATF_TP_ADD_TC(tp, open_create_existing_object);
+ ATF_TP_ADD_TC(tp, shm_functionality_across_fork);
+ ATF_TP_ADD_TC(tp, trunc_resets_object);
+ ATF_TP_ADD_TC(tp, unlink_bad_path_pointer);
+ ATF_TP_ADD_TC(tp, unlink_path_too_long);
+ ATF_TP_ADD_TC(tp, object_resize);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/sys/Makefile b/tests/sys/sys/Makefile
new file mode 100644
index 000000000000..3adf6b83e4e0
--- /dev/null
+++ b/tests/sys/sys/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sys/sys
+
+ATF_TESTS_C= bitstring_test
+
+WARNS?= 5
+
+.include <bsd.test.mk>
diff --git a/tests/sys/sys/Makefile.depend b/tests/sys/sys/Makefile.depend
new file mode 100644
index 000000000000..74074b9588ec
--- /dev/null
+++ b/tests/sys/sys/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/atf/libatf-c \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/sys/bitstring_test.c b/tests/sys/sys/bitstring_test.c
new file mode 100644
index 000000000000..fbfe78275b23
--- /dev/null
+++ b/tests/sys/sys/bitstring_test.c
@@ -0,0 +1,433 @@
+/*-
+ * Copyright (c) 2014 Spectra Logic Corporation
+ * 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,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ *
+ * NO WARRANTY
+ * 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 MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+ *
+ * $FreeBSD$
+ */
+#include <sys/param.h>
+
+#include <bitstring.h>
+#include <stdio.h>
+
+#include <atf-c.h>
+
+typedef void (testfunc_t)(bitstr_t *bstr, int nbits, const char *memloc);
+
+static void
+bitstring_run_stack_test(testfunc_t *test, int nbits)
+{
+ bitstr_t bit_decl(bitstr, nbits);
+
+ test(bitstr, nbits, "stack");
+}
+
+static void
+bitstring_run_heap_test(testfunc_t *test, int nbits)
+{
+ bitstr_t *bitstr = bit_alloc(nbits);
+
+ test(bitstr, nbits, "heap");
+}
+
+static void
+bitstring_test_runner(testfunc_t *test)
+{
+ const int bitstr_sizes[] = {
+ 0,
+ 1,
+ _BITSTR_BITS - 1,
+ _BITSTR_BITS,
+ _BITSTR_BITS + 1,
+ 2 * _BITSTR_BITS - 1,
+ 2 * _BITSTR_BITS,
+ 1023,
+ 1024
+ };
+
+ for (unsigned long i = 0; i < nitems(bitstr_sizes); i++) {
+ bitstring_run_stack_test(test, bitstr_sizes[i]);
+ bitstring_run_heap_test(test, bitstr_sizes[i]);
+ }
+}
+
+#define BITSTRING_TC_DEFINE(name) \
+ATF_TC_WITHOUT_HEAD(name); \
+static testfunc_t name ## _test; \
+ \
+ATF_TC_BODY(name, tc) \
+{ \
+ bitstring_test_runner(name ## _test); \
+} \
+ \
+static void \
+name ## _test(bitstr_t *bitstr, int nbits, const char *memloc)
+
+#define BITSTRING_TC_ADD(tp, name) \
+do { \
+ ATF_TP_ADD_TC(tp, name); \
+} while (0)
+
+ATF_TC_WITHOUT_HEAD(bitstr_in_struct);
+ATF_TC_BODY(bitstr_in_struct, tc)
+{
+ struct bitstr_containing_struct {
+ bitstr_t bit_decl(bitstr, 8);
+ } test_struct;
+
+ bit_nclear(test_struct.bitstr, 0, 8);
+}
+
+ATF_TC_WITHOUT_HEAD(bitstr_size);
+ATF_TC_BODY(bitstr_size, tc)
+{
+ size_t sob = sizeof(bitstr_t);
+
+ ATF_CHECK_EQ(0, bitstr_size(0));
+ ATF_CHECK_EQ(sob, bitstr_size(1));
+ ATF_CHECK_EQ(sob, bitstr_size(sob * 8));
+ ATF_CHECK_EQ(2 * sob, bitstr_size(sob * 8 + 1));
+}
+
+BITSTRING_TC_DEFINE(bit_set)
+/* bitstr_t *bitstr, int nbits, const char *memloc */
+{
+ memset(bitstr, 0, bitstr_size(nbits));
+
+ for (int i = 0; i < nbits; i++) {
+ bit_set(bitstr, i);
+
+ for (int j = 0; j < nbits; j++) {
+ ATF_REQUIRE_MSG(bit_test(bitstr, j) == (j == i) ? 1 : 0,
+ "bit_set_%d_%s: Failed on bit %d",
+ nbits, memloc, i);
+ }
+
+ bit_clear(bitstr, i);
+ }
+}
+
+BITSTRING_TC_DEFINE(bit_clear)
+/* bitstr_t *bitstr, int nbits, const char *memloc */
+{
+ int i, j;
+
+ memset(bitstr, 0xFF, bitstr_size(nbits));
+ for (i = 0; i < nbits; i++) {
+ bit_clear(bitstr, i);
+
+ for (j = 0; j < nbits; j++) {
+ ATF_REQUIRE_MSG(bit_test(bitstr, j) == (j == i) ? 0 : 1,
+ "bit_clear_%d_%s: Failed on bit %d",
+ nbits, memloc, i);
+ }
+
+ bit_set(bitstr, i);
+ }
+}
+
+BITSTRING_TC_DEFINE(bit_ffs)
+/* bitstr_t *bitstr, int nbits, const char *memloc */
+{
+ int i;
+ int found_set_bit;
+
+ memset(bitstr, 0, bitstr_size(nbits));
+ bit_ffs(bitstr, nbits, &found_set_bit);
+ ATF_REQUIRE_MSG(found_set_bit == -1,
+ "bit_ffs_%d_%s: Failed all clear bits.", nbits, memloc);
+
+ for (i = 0; i < nbits; i++) {
+ memset(bitstr, 0xFF, bitstr_size(nbits));
+ if (i > 0)
+ bit_nclear(bitstr, 0, i - 1);
+
+ bit_ffs(bitstr, nbits, &found_set_bit);
+ ATF_REQUIRE_MSG(found_set_bit == i,
+ "bit_ffs_%d_%s: Failed on bit %d, Result %d",
+ nbits, memloc, i, found_set_bit);
+ }
+}
+
+BITSTRING_TC_DEFINE(bit_ffc)
+/* bitstr_t *bitstr, int nbits, const char *memloc */
+{
+ int i;
+ int found_clear_bit;
+
+ memset(bitstr, 0xFF, bitstr_size(nbits));
+ bit_ffc(bitstr, nbits, &found_clear_bit);
+ ATF_REQUIRE_MSG(found_clear_bit == -1,
+ "bit_ffc_%d_%s: Failed all set bits.", nbits, memloc);
+
+ for (i = 0; i < nbits; i++) {
+ memset(bitstr, 0, bitstr_size(nbits));
+ if (i > 0)
+ bit_nset(bitstr, 0, i - 1);
+
+ bit_ffc(bitstr, nbits, &found_clear_bit);
+ ATF_REQUIRE_MSG(found_clear_bit == i,
+ "bit_ffc_%d_%s: Failed on bit %d, Result %d",
+ nbits, memloc, i, found_clear_bit);
+ }
+}
+
+BITSTRING_TC_DEFINE(bit_ffs_at)
+/* bitstr_t *bitstr, int nbits, const char *memloc */
+{
+ int i;
+ int found_set_bit;
+
+ memset(bitstr, 0xFF, bitstr_size(nbits));
+ for (i = 0; i < nbits; i++) {
+ bit_ffs_at(bitstr, i, nbits, &found_set_bit);
+ ATF_REQUIRE_MSG(found_set_bit == i,
+ "bit_ffs_at_%d_%s: Failed on bit %d, Result %d",
+ nbits, memloc, i, found_set_bit);
+ }
+
+ memset(bitstr, 0, bitstr_size(nbits));
+ for (i = 0; i < nbits; i++) {
+ bit_ffs_at(bitstr, i, nbits, &found_set_bit);
+ ATF_REQUIRE_MSG(found_set_bit == -1,
+ "bit_ffs_at_%d_%s: Failed on bit %d, Result %d",
+ nbits, memloc, i, found_set_bit);
+ }
+
+ memset(bitstr, 0x55, bitstr_size(nbits));
+ for (i = 0; i < nbits; i++) {
+ bit_ffs_at(bitstr, i, nbits, &found_set_bit);
+ if (i == nbits - 1 && (nbits & 1) == 0) {
+ ATF_REQUIRE_MSG(found_set_bit == -1,
+ "bit_ffs_at_%d_%s: Failed on bit %d, Result %d",
+ nbits, memloc, i, found_set_bit);
+ } else {
+ ATF_REQUIRE_MSG(found_set_bit == i + (i & 1),
+ "bit_ffs_at_%d_%s: Failed on bit %d, Result %d",
+ nbits, memloc, i, found_set_bit);
+ }
+ }
+
+ memset(bitstr, 0xAA, bitstr_size(nbits));
+ for (i = 0; i < nbits; i++) {
+ bit_ffs_at(bitstr, i, nbits, &found_set_bit);
+ if (i == nbits - 1 && (nbits & 1) != 0) {
+ ATF_REQUIRE_MSG(found_set_bit == -1,
+ "bit_ffs_at_%d_%s: Failed on bit %d, Result %d",
+ nbits, memloc, i, found_set_bit);
+ } else {
+ ATF_REQUIRE_MSG(
+ found_set_bit == i + ((i & 1) ? 0 : 1),
+ "bit_ffs_at_%d_%s: Failed on bit %d, Result %d",
+ nbits, memloc, i, found_set_bit);
+ }
+ }
+}
+
+BITSTRING_TC_DEFINE(bit_ffc_at)
+/* bitstr_t *bitstr, int nbits, const char *memloc */
+{
+ int i, found_clear_bit;
+
+ memset(bitstr, 0, bitstr_size(nbits));
+ for (i = 0; i < nbits; i++) {
+ bit_ffc_at(bitstr, i, nbits, &found_clear_bit);
+ ATF_REQUIRE_MSG(found_clear_bit == i,
+ "bit_ffc_at_%d_%s: Failed on bit %d, Result %d",
+ nbits, memloc, i, found_clear_bit);
+ }
+
+ memset(bitstr, 0xFF, bitstr_size(nbits));
+ for (i = 0; i < nbits; i++) {
+ bit_ffc_at(bitstr, i, nbits, &found_clear_bit);
+ ATF_REQUIRE_MSG(found_clear_bit == -1,
+ "bit_ffc_at_%d_%s: Failed on bit %d, Result %d",
+ nbits, memloc, i, found_clear_bit);
+ }
+
+ memset(bitstr, 0x55, bitstr_size(nbits));
+ for (i = 0; i < nbits; i++) {
+ bit_ffc_at(bitstr, i, nbits, &found_clear_bit);
+ if (i == nbits - 1 && (nbits & 1) != 0) {
+ ATF_REQUIRE_MSG(found_clear_bit == -1,
+ "bit_ffc_at_%d_%s: Failed on bit %d, Result %d",
+ nbits, memloc, i, found_clear_bit);
+ } else {
+ ATF_REQUIRE_MSG(
+ found_clear_bit == i + ((i & 1) ? 0 : 1),
+ "bit_ffc_at_%d_%s: Failed on bit %d, Result %d",
+ nbits, memloc, i, found_clear_bit);
+ }
+ }
+
+ memset(bitstr, 0xAA, bitstr_size(nbits));
+ for (i = 0; i < nbits; i++) {
+ bit_ffc_at(bitstr, i, nbits, &found_clear_bit);
+ if (i == nbits - 1 && (nbits & 1) == 0) {
+ ATF_REQUIRE_MSG(found_clear_bit == -1,
+ "bit_ffc_at_%d_%s: Failed on bit %d, Result %d",
+ nbits, memloc, i, found_clear_bit);
+ } else {
+ ATF_REQUIRE_MSG(found_clear_bit == i + (i & 1),
+ "bit_ffc_at_%d_%s: Failed on bit %d, Result %d",
+ nbits, memloc, i, found_clear_bit);
+ }
+ }
+}
+
+BITSTRING_TC_DEFINE(bit_nclear)
+/* bitstr_t *bitstr, int nbits, const char *memloc */
+{
+ int i, j;
+ int found_set_bit;
+ int found_clear_bit;
+
+ for (i = 0; i < nbits; i++) {
+ for (j = i; j < nbits; j++) {
+ memset(bitstr, 0xFF, bitstr_size(nbits));
+ bit_nclear(bitstr, i, j);
+
+ bit_ffc(bitstr, nbits, &found_clear_bit);
+ ATF_REQUIRE_MSG(
+ found_clear_bit == i,
+ "bit_nclear_%d_%d_%d%s: Failed with result %d",
+ nbits, i, j, memloc, found_clear_bit);
+
+ bit_ffs_at(bitstr, i, nbits, &found_set_bit);
+ ATF_REQUIRE_MSG(
+ (j + 1 < nbits) ? found_set_bit == j + 1 : -1,
+ "bit_nset_%d_%d_%d%s: Failed with result %d",
+ nbits, i, j, memloc, found_set_bit);
+ }
+ }
+}
+
+BITSTRING_TC_DEFINE(bit_nset)
+/* bitstr_t *bitstr, int nbits, const char *memloc */
+{
+ int i, j;
+ int found_set_bit;
+ int found_clear_bit;
+
+ for (i = 0; i < nbits; i++) {
+ for (j = i; j < nbits; j++) {
+ memset(bitstr, 0, bitstr_size(nbits));
+ bit_nset(bitstr, i, j);
+
+ bit_ffs(bitstr, nbits, &found_set_bit);
+ ATF_REQUIRE_MSG(
+ found_set_bit == i,
+ "bit_nset_%d_%d_%d%s: Failed with result %d",
+ nbits, i, j, memloc, found_set_bit);
+
+ bit_ffc_at(bitstr, i, nbits, &found_clear_bit);
+ ATF_REQUIRE_MSG(
+ (j + 1 < nbits) ? found_clear_bit == j + 1 : -1,
+ "bit_nset_%d_%d_%d%s: Failed with result %d",
+ nbits, i, j, memloc, found_clear_bit);
+ }
+ }
+}
+
+BITSTRING_TC_DEFINE(bit_count)
+/* bitstr_t *bitstr, int nbits, const char *memloc */
+{
+ int result, s, e, expected;
+
+ /* Empty bitstr */
+ memset(bitstr, 0, bitstr_size(nbits));
+ bit_count(bitstr, 0, nbits, &result);
+ ATF_CHECK_MSG(0 == result,
+ "bit_count_%d_%s_%s: Failed with result %d",
+ nbits, "clear", memloc, result);
+
+ /* Full bitstr */
+ memset(bitstr, 0xFF, bitstr_size(nbits));
+ bit_count(bitstr, 0, nbits, &result);
+ ATF_CHECK_MSG(nbits == result,
+ "bit_count_%d_%s_%s: Failed with result %d",
+ nbits, "set", memloc, result);
+
+ /* Invalid _start value */
+ memset(bitstr, 0xFF, bitstr_size(nbits));
+ bit_count(bitstr, nbits, nbits, &result);
+ ATF_CHECK_MSG(0 == result,
+ "bit_count_%d_%s_%s: Failed with result %d",
+ nbits, "invalid_start", memloc, result);
+
+ /* Alternating bitstr, starts with 0 */
+ memset(bitstr, 0xAA, bitstr_size(nbits));
+ bit_count(bitstr, 0, nbits, &result);
+ ATF_CHECK_MSG(nbits / 2 == result,
+ "bit_count_%d_%s_%d_%s: Failed with result %d",
+ nbits, "alternating", 0, memloc, result);
+
+ /* Alternating bitstr, starts with 1 */
+ memset(bitstr, 0x55, bitstr_size(nbits));
+ bit_count(bitstr, 0, nbits, &result);
+ ATF_CHECK_MSG((nbits + 1) / 2 == result,
+ "bit_count_%d_%s_%d_%s: Failed with result %d",
+ nbits, "alternating", 1, memloc, result);
+
+ /* Varying start location */
+ memset(bitstr, 0xAA, bitstr_size(nbits));
+ for (s = 0; s < nbits; s++) {
+ expected = s % 2 == 0 ? (nbits - s) / 2 : (nbits - s + 1) / 2;
+ bit_count(bitstr, s, nbits, &result);
+ ATF_CHECK_MSG(expected == result,
+ "bit_count_%d_%s_%d_%s: Failed with result %d",
+ nbits, "vary_start", s, memloc, result);
+ }
+
+ /* Varying end location */
+ memset(bitstr, 0xAA, bitstr_size(nbits));
+ for (e = 0; e < nbits; e++) {
+ bit_count(bitstr, 0, e, &result);
+ ATF_CHECK_MSG(e / 2 == result,
+ "bit_count_%d_%s_%d_%s: Failed with result %d",
+ nbits, "vary_end", e, memloc, result);
+ }
+
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, bitstr_in_struct);
+ ATF_TP_ADD_TC(tp, bitstr_size);
+ BITSTRING_TC_ADD(tp, bit_set);
+ BITSTRING_TC_ADD(tp, bit_clear);
+ BITSTRING_TC_ADD(tp, bit_ffs);
+ BITSTRING_TC_ADD(tp, bit_ffc);
+ BITSTRING_TC_ADD(tp, bit_ffs_at);
+ BITSTRING_TC_ADD(tp, bit_ffc_at);
+ BITSTRING_TC_ADD(tp, bit_nclear);
+ BITSTRING_TC_ADD(tp, bit_nset);
+ BITSTRING_TC_ADD(tp, bit_count);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/vfs/Makefile b/tests/sys/vfs/Makefile
new file mode 100644
index 000000000000..43fde788f652
--- /dev/null
+++ b/tests/sys/vfs/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/vfs
+
+ATF_TESTS_C+= lookup_cap_dotdot
+CFLAGS.lookup_cap_dotdot.c+= -I${SRCTOP}/tests
+
+PLAIN_TESTS_SH+= trailing_slash
+
+WARNS?= 6
+
+.include <bsd.test.mk>
diff --git a/tests/sys/vfs/Makefile.depend b/tests/sys/vfs/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/tests/sys/vfs/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/vfs/lookup_cap_dotdot.c b/tests/sys/vfs/lookup_cap_dotdot.c
new file mode 100644
index 000000000000..e14b5887a75e
--- /dev/null
+++ b/tests/sys/vfs/lookup_cap_dotdot.c
@@ -0,0 +1,313 @@
+/*-
+ * Copyright (c) 2016 Ed Maste <emaste@FreeBSD.org>
+ * Copyright (c) 2016 Conrad Meyer <cem@FreeBSD.org>
+ *
+ * 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 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/capsicum.h>
+#include <sys/sysctl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <atf-c.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "freebsd_test_suite/macros.h"
+
+static char *abspath;
+static int dirfd = -1;
+
+typedef void (*child_test_fn_t)(void);
+
+static void
+touchat(int _dirfd, const char *name)
+{
+ int fd;
+
+ ATF_REQUIRE((fd = openat(_dirfd, name, O_CREAT | O_TRUNC | O_WRONLY,
+ 0777)) >= 0);
+ ATF_REQUIRE(close(fd) == 0);
+}
+
+static void
+prepare_dotdot_tests(void)
+{
+ char cwd[MAXPATHLEN];
+
+ ATF_REQUIRE(getcwd(cwd, sizeof(cwd)) != NULL);
+ asprintf(&abspath, "%s/testdir/d1/f1", cwd);
+
+ ATF_REQUIRE(mkdir("testdir", 0777) == 0);
+ ATF_REQUIRE((dirfd = open("testdir", O_RDONLY)) >= 0);
+
+ ATF_REQUIRE(mkdirat(dirfd, "d1", 0777) == 0);
+ ATF_REQUIRE(mkdirat(dirfd, "d1/d2", 0777) == 0);
+ ATF_REQUIRE(mkdirat(dirfd, "d1/d2/d3", 0777) == 0);
+ touchat(dirfd, "d1/f1");
+ touchat(dirfd, "d1/d2/f2");
+ touchat(dirfd, "d1/d2/d3/f3");
+ ATF_REQUIRE(symlinkat("d1/d2/d3", dirfd, "l3") == 0);
+ ATF_REQUIRE(symlinkat("../testdir/d1", dirfd, "lup") == 0);
+ ATF_REQUIRE(symlinkat("../..", dirfd, "d1/d2/d3/ld1") == 0);
+ ATF_REQUIRE(symlinkat("../../f1", dirfd, "d1/d2/d3/lf1") == 0);
+}
+
+static void
+check_capsicum(void)
+{
+
+ ATF_REQUIRE_FEATURE("security_capabilities");
+ ATF_REQUIRE_FEATURE("security_capability_mode");
+}
+
+static void
+run_capsicum_test(child_test_fn_t test_func)
+{
+ int child_exit_code, child_status;
+ pid_t child_pid;
+
+ check_capsicum();
+ prepare_dotdot_tests();
+
+ ATF_REQUIRE_MSG((child_pid = fork()) != -1,
+ "fork failed: %s", strerror(errno));
+
+ if (child_pid == 0) {
+ test_func();
+ _exit(0);
+ }
+
+ ATF_REQUIRE_MSG(waitpid(child_pid, &child_status, 0) != -1,
+ "waitpid failed: %s", strerror(errno));
+ if (WIFEXITED(child_status)) {
+ child_exit_code = WEXITSTATUS(child_status);
+ ATF_REQUIRE_MSG(child_exit_code == 0,
+ "child exited with non-zero exit code: %d",
+ child_exit_code);
+ } else if (WIFSIGNALED(child_status))
+ atf_tc_fail("child exited with signal: %d",
+ WTERMSIG(child_status));
+ else
+ atf_tc_fail("child exited with unexpected status: %d",
+ child_status);
+}
+
+/*
+ * Positive tests
+ */
+ATF_TC(openat__basic_positive);
+ATF_TC_HEAD(openat__basic_positive, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Basic positive openat testcases");
+}
+
+ATF_TC_BODY(openat__basic_positive, tc)
+{
+
+ prepare_dotdot_tests();
+
+ ATF_REQUIRE(openat(dirfd, "d1/d2/d3/f3", O_RDONLY) >= 0);
+ ATF_REQUIRE(openat(dirfd, "d1/d2/d3/../../f1", O_RDONLY) >= 0);
+ ATF_REQUIRE(openat(dirfd, "l3/f3", O_RDONLY) >= 0);
+ ATF_REQUIRE(openat(dirfd, "l3/../../f1", O_RDONLY) >= 0);
+ ATF_REQUIRE(openat(dirfd, "../testdir/d1/f1", O_RDONLY) >= 0);
+ ATF_REQUIRE(openat(dirfd, "lup/f1", O_RDONLY) >= 0);
+ ATF_REQUIRE(openat(dirfd, "l3/ld1", O_RDONLY) >= 0);
+ ATF_REQUIRE(openat(dirfd, "l3/lf1", O_RDONLY) >= 0);
+ ATF_REQUIRE(open(abspath, O_RDONLY) >= 0);
+ ATF_REQUIRE(openat(dirfd, abspath, O_RDONLY) >= 0);
+}
+
+ATF_TC(lookup_cap_dotdot__basic);
+ATF_TC_HEAD(lookup_cap_dotdot__basic, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Validate cap-mode (testdir)/d1/.. lookup");
+}
+
+static void
+lookup_cap_dotdot__basic_child(void)
+{
+ cap_rights_t rights;
+
+ cap_rights_init(&rights, CAP_LOOKUP, CAP_READ);
+
+ assert(cap_rights_limit(dirfd, &rights) >= 0);
+ assert(cap_enter() >= 0);
+ assert(openat(dirfd, "d1/..", O_RDONLY) >= 0);
+}
+
+ATF_TC_BODY(lookup_cap_dotdot__basic, tc)
+{
+
+ run_capsicum_test(lookup_cap_dotdot__basic_child);
+}
+
+ATF_TC(lookup_cap_dotdot__advanced);
+ATF_TC_HEAD(lookup_cap_dotdot__advanced, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Validate cap-mode (testdir)/d1/.. lookup");
+}
+
+static void
+lookup_cap_dotdot__advanced_child(void)
+{
+ cap_rights_t rights;
+
+ cap_rights_init(&rights, CAP_LOOKUP, CAP_READ);
+ assert(cap_rights_limit(dirfd, &rights) >= 0);
+
+ assert(cap_enter() >= 0);
+
+ assert(openat(dirfd, "d1/d2/d3/../../f1", O_RDONLY) >= 0);
+ assert(openat(dirfd, "l3/../../f1", O_RDONLY) >= 0);
+ assert(openat(dirfd, "l3/ld1", O_RDONLY) >= 0);
+ assert(openat(dirfd, "l3/lf1", O_RDONLY) >= 0);
+}
+
+ATF_TC_BODY(lookup_cap_dotdot__advanced, tc)
+{
+
+ run_capsicum_test(lookup_cap_dotdot__advanced_child);
+}
+
+/*
+ * Negative tests
+ */
+ATF_TC(openat__basic_negative);
+ATF_TC_HEAD(openat__basic_negative, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Basic negative openat testcases");
+}
+
+ATF_TC_BODY(openat__basic_negative, tc)
+{
+
+ prepare_dotdot_tests();
+
+ ATF_REQUIRE_ERRNO(ENOENT,
+ openat(dirfd, "does-not-exist", O_RDONLY) < 0);
+ ATF_REQUIRE_ERRNO(ENOENT,
+ openat(dirfd, "l3/does-not-exist", O_RDONLY) < 0);
+}
+
+ATF_TC(capmode__negative);
+ATF_TC_HEAD(capmode__negative, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Negative Capability mode testcases");
+}
+
+static void
+capmode__negative_child(void)
+{
+ int subdirfd;
+
+ assert(cap_enter() == 0);
+
+ /* open() not permitted in capability mode */
+ assert(open("testdir", O_RDONLY) < 0);
+ assert(errno == ECAPMODE);
+
+ /* AT_FDCWD not permitted in capability mode */
+ assert(openat(AT_FDCWD, "d1/f1", O_RDONLY) < 0);
+ assert(errno == ECAPMODE);
+
+ /* Relative path above dirfd not capable */
+ assert(openat(dirfd, "..", O_RDONLY) < 0);
+ assert(errno == ENOTCAPABLE);
+
+ assert((subdirfd = openat(dirfd, "l3", O_RDONLY)) >= 0);
+ assert(openat(subdirfd, "../../f1", O_RDONLY) < 0);
+ assert(errno == ENOTCAPABLE);
+ (void)close(subdirfd);
+
+ /* Absolute paths not capable */
+ assert(openat(dirfd, abspath, O_RDONLY) < 0);
+ assert(errno == ENOTCAPABLE);
+
+ /* Symlink above dirfd */
+ assert(openat(dirfd, "lup/f1", O_RDONLY) < 0);
+ assert(errno == ENOTCAPABLE);
+}
+
+ATF_TC_BODY(capmode__negative, tc)
+{
+
+ run_capsicum_test(capmode__negative_child);
+}
+
+ATF_TC(lookup_cap_dotdot__negative);
+ATF_TC_HEAD(lookup_cap_dotdot__negative, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Validate cap-mode (testdir)/.. lookup fails");
+}
+
+static void
+lookup_cap_dotdot__negative_child(void)
+{
+ cap_rights_t rights;
+
+ cap_rights_init(&rights, CAP_LOOKUP, CAP_READ);
+ assert(cap_rights_limit(dirfd, &rights) >= 0);
+
+ assert(cap_enter() >= 0);
+
+ assert(openat(dirfd, "..", O_RDONLY) < 0);
+ assert(errno == ENOTCAPABLE);
+
+ assert(openat(dirfd, "d1/../..", O_RDONLY) < 0);
+ assert(errno == ENOTCAPABLE);
+
+ assert(openat(dirfd, "../testdir/d1/f1", O_RDONLY) < 0);
+ assert(errno == ENOTCAPABLE);
+}
+
+ATF_TC_BODY(lookup_cap_dotdot__negative, tc)
+{
+
+ run_capsicum_test(lookup_cap_dotdot__negative_child);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, openat__basic_positive);
+ ATF_TP_ADD_TC(tp, openat__basic_negative);
+
+ ATF_TP_ADD_TC(tp, capmode__negative);
+
+ ATF_TP_ADD_TC(tp, lookup_cap_dotdot__basic);
+ ATF_TP_ADD_TC(tp, lookup_cap_dotdot__advanced);
+ ATF_TP_ADD_TC(tp, lookup_cap_dotdot__negative);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/vfs/trailing_slash.sh b/tests/sys/vfs/trailing_slash.sh
new file mode 100755
index 000000000000..b1b8523e93e2
--- /dev/null
+++ b/tests/sys/vfs/trailing_slash.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+# Tests vfs_lookup()'s handling of trailing slashes for symlinks that
+# point to files. See kern/21768 for details. Fixed in r193028.
+#
+
+testfile="/tmp/testfile-$$"
+testlink="/tmp/testlink-$$"
+
+tests="
+$testfile:$testlink:$testfile:0
+$testfile:$testlink:$testfile/:1
+$testfile:$testlink:$testlink:0
+$testfile:$testlink:$testlink/:1
+$testfile/:$testlink:$testlink:1
+$testfile/:$testlink:$testlink/:1
+"
+
+touch $testfile || exit 1
+trap "rm $testfile $testlink" EXIT
+
+set $tests
+echo "1..$#"
+n=1
+for testspec ; do
+ (
+ IFS=:
+ set $testspec
+ unset IFS
+ ln -fs "$1" "$2" || exit 1
+ cat "$3" >/dev/null 2>&1
+ ret=$?
+ if [ "$ret" -eq "$4" ] ; then
+ echo "ok $n"
+ else
+ echo "fail $n - expected $4, got $ret"
+ fi
+ )
+ n=$((n+1))
+done
diff --git a/tests/sys/vm/Makefile b/tests/sys/vm/Makefile
new file mode 100644
index 000000000000..a3c298b04b4d
--- /dev/null
+++ b/tests/sys/vm/Makefile
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/vm
+
+ATF_TESTS_C+= mmap_test
+
+.include <bsd.test.mk>
diff --git a/tests/sys/vm/Makefile.depend b/tests/sys/vm/Makefile.depend
new file mode 100644
index 000000000000..74074b9588ec
--- /dev/null
+++ b/tests/sys/vm/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/atf/libatf-c \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/tests/sys/vm/mmap_test.c b/tests/sys/vm/mmap_test.c
new file mode 100644
index 000000000000..8ed873896f03
--- /dev/null
+++ b/tests/sys/vm/mmap_test.c
@@ -0,0 +1,273 @@
+/*-
+ * Copyright (c) 2009 Simon L. Nielsen <simon@FreeBSD.org>,
+ * Bjoern A. Zeeb <bz@FreeBSD.org>
+ *
+ * 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 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$
+ */
+
+#include <sys/param.h>
+#include <sys/mman.h>
+#include <sys/sysctl.h>
+
+#include <atf-c.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static const struct {
+ void *addr;
+ int ok[2]; /* Depending on security.bsd.map_at_zero {0, !=0}. */
+} map_at_zero_tests[] = {
+ { (void *)0, { 0, 1 } }, /* Test sysctl. */
+ { (void *)1, { 0, 0 } },
+ { (void *)(PAGE_SIZE - 1), { 0, 0 } },
+ { (void *)PAGE_SIZE, { 1, 1 } },
+ { (void *)-1, { 0, 0 } },
+ { (void *)(-PAGE_SIZE), { 0, 0 } },
+ { (void *)(-1 - PAGE_SIZE), { 0, 0 } },
+ { (void *)(-1 - PAGE_SIZE - 1), { 0, 0 } },
+ { (void *)(0x1000 * PAGE_SIZE), { 1, 1 } },
+};
+
+#define MAP_AT_ZERO "security.bsd.map_at_zero"
+
+ATF_TC_WITHOUT_HEAD(mmap__map_at_zero);
+ATF_TC_BODY(mmap__map_at_zero, tc)
+{
+ void *p;
+ size_t len;
+ unsigned int i;
+ int map_at_zero;
+
+ len = sizeof(map_at_zero);
+ if (sysctlbyname(MAP_AT_ZERO, &map_at_zero, &len, NULL, 0) == -1) {
+ atf_tc_skip("sysctl for %s failed: %s\n", MAP_AT_ZERO,
+ strerror(errno));
+ return;
+ }
+
+ /* Normalize to 0 or 1 for array access. */
+ map_at_zero = !!map_at_zero;
+
+ for (i = 0; i < nitems(map_at_zero_tests); i++) {
+ p = mmap((void *)map_at_zero_tests[i].addr, PAGE_SIZE,
+ PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_FIXED,
+ -1, 0);
+ if (p == MAP_FAILED) {
+ ATF_CHECK_MSG(map_at_zero_tests[i].ok[map_at_zero] == 0,
+ "mmap(%p, ...) failed", map_at_zero_tests[i].addr);
+ } else {
+ ATF_CHECK_MSG(map_at_zero_tests[i].ok[map_at_zero] == 1,
+ "mmap(%p, ...) succeeded: p=%p\n",
+ map_at_zero_tests[i].addr, p);
+ }
+ }
+}
+
+static void
+checked_mmap(int prot, int flags, int fd, int error, const char *msg)
+{
+ void *p;
+ int pagesize;
+
+ ATF_REQUIRE((pagesize = getpagesize()) > 0);
+ p = mmap(NULL, pagesize, prot, flags, fd, 0);
+ if (p == MAP_FAILED) {
+ if (error == 0)
+ ATF_CHECK_MSG(0, "%s failed with errno %d", msg,
+ errno);
+ else
+ ATF_CHECK_EQ_MSG(error, errno,
+ "%s failed with wrong errno %d (expected %d)", msg,
+ errno, error);
+ } else {
+ ATF_CHECK_MSG(error == 0, "%s succeeded", msg);
+ munmap(p, pagesize);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(mmap__bad_arguments);
+ATF_TC_BODY(mmap__bad_arguments, tc)
+{
+ int devstatfd, pagesize, shmfd, zerofd;
+
+ ATF_REQUIRE((pagesize = getpagesize()) > 0);
+ ATF_REQUIRE((devstatfd = open("/dev/devstat", O_RDONLY)) >= 0);
+ ATF_REQUIRE((shmfd = shm_open(SHM_ANON, O_RDWR, 0644)) >= 0);
+ ATF_REQUIRE(ftruncate(shmfd, pagesize) == 0);
+ ATF_REQUIRE((zerofd = open("/dev/zero", O_RDONLY)) >= 0);
+
+ /* These should work. */
+ checked_mmap(PROT_READ | PROT_WRITE, MAP_ANON, -1, 0,
+ "simple MAP_ANON");
+ checked_mmap(PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0,
+ "simple shm fd shared");
+ checked_mmap(PROT_READ | PROT_WRITE, MAP_PRIVATE, shmfd, 0,
+ "simple shm fd private");
+ checked_mmap(PROT_READ, MAP_SHARED, zerofd, 0,
+ "simple /dev/zero shared");
+ checked_mmap(PROT_READ | PROT_WRITE, MAP_PRIVATE, zerofd, 0,
+ "simple /dev/zero private");
+ checked_mmap(PROT_READ, MAP_SHARED, devstatfd, 0,
+ "simple /dev/devstat shared");
+
+ /* Extra PROT flags. */
+ checked_mmap(PROT_READ | PROT_WRITE | 0x100000, MAP_ANON, -1, EINVAL,
+ "MAP_ANON with extra PROT flags");
+ checked_mmap(0xffff, MAP_SHARED, shmfd, EINVAL,
+ "shm fd with garbage PROT");
+
+ /* Undefined flag. */
+ checked_mmap(PROT_READ | PROT_WRITE, MAP_ANON | MAP_RESERVED0080, -1,
+ EINVAL, "Undefined flag");
+
+ /* Both MAP_SHARED and MAP_PRIVATE */
+ checked_mmap(PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE |
+ MAP_SHARED, -1, EINVAL, "MAP_ANON with both SHARED and PRIVATE");
+ checked_mmap(PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_SHARED, shmfd,
+ EINVAL, "shm fd with both SHARED and PRIVATE");
+
+ /* At least one of MAP_SHARED or MAP_PRIVATE without ANON */
+ checked_mmap(PROT_READ | PROT_WRITE, 0, shmfd, EINVAL,
+ "shm fd without sharing flag");
+
+ /* MAP_ANON with either sharing flag (impacts fork). */
+ checked_mmap(PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0,
+ "shared MAP_ANON");
+ checked_mmap(PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0,
+ "private MAP_ANON");
+
+ /* MAP_ANON should require an fd of -1. */
+ checked_mmap(PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, 0, EINVAL,
+ "MAP_ANON with fd != -1");
+
+ /* Writable MAP_SHARED should fail on read-only descriptors. */
+ checked_mmap(PROT_READ | PROT_WRITE, MAP_SHARED, zerofd, EACCES,
+ "MAP_SHARED of read-only /dev/zero");
+
+ /*
+ * Character devices other than /dev/zero do not support private
+ * mappings.
+ */
+ checked_mmap(PROT_READ, MAP_PRIVATE, devstatfd, EINVAL,
+ "MAP_PRIVATE of /dev/devstat");
+
+ close(devstatfd);
+ close(shmfd);
+ close(zerofd);
+}
+
+ATF_TC_WITHOUT_HEAD(mmap__dev_zero_private);
+ATF_TC_BODY(mmap__dev_zero_private, tc)
+{
+ char *p1, *p2, *p3;
+ size_t i;
+ int fd, pagesize;
+
+ ATF_REQUIRE((pagesize = getpagesize()) > 0);
+ ATF_REQUIRE((fd = open("/dev/zero", O_RDONLY)) >= 0);
+
+ p1 = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ ATF_REQUIRE(p1 != MAP_FAILED);
+
+ p2 = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ ATF_REQUIRE(p2 != MAP_FAILED);
+
+ for (i = 0; i < pagesize; i++)
+ ATF_REQUIRE_EQ_MSG(0, p1[i], "byte at p1[%zu] is %x", i, p1[i]);
+
+ ATF_REQUIRE(memcmp(p1, p2, pagesize) == 0);
+
+ p1[0] = 1;
+
+ ATF_REQUIRE(p2[0] == 0);
+
+ p2[0] = 2;
+
+ ATF_REQUIRE(p1[0] == 1);
+
+ p3 = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ ATF_REQUIRE(p3 != MAP_FAILED);
+
+ ATF_REQUIRE(p3[0] == 0);
+
+ munmap(p1, pagesize);
+ munmap(p2, pagesize);
+ munmap(p3, pagesize);
+ close(fd);
+}
+
+ATF_TC_WITHOUT_HEAD(mmap__dev_zero_shared);
+ATF_TC_BODY(mmap__dev_zero_shared, tc)
+{
+ char *p1, *p2, *p3;
+ size_t i;
+ int fd, pagesize;
+
+ ATF_REQUIRE((pagesize = getpagesize()) > 0);
+ ATF_REQUIRE((fd = open("/dev/zero", O_RDWR)) >= 0);
+
+ p1 = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ ATF_REQUIRE(p1 != MAP_FAILED);
+
+ p2 = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ ATF_REQUIRE(p2 != MAP_FAILED);
+
+ for (i = 0; i < pagesize; i++)
+ ATF_REQUIRE_EQ_MSG(0, p1[i], "byte at p1[%zu] is %x", i, p1[i]);
+
+ ATF_REQUIRE(memcmp(p1, p2, pagesize) == 0);
+
+ p1[0] = 1;
+
+ ATF_REQUIRE(p2[0] == 0);
+
+ p2[0] = 2;
+
+ ATF_REQUIRE(p1[0] == 1);
+
+ p3 = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
+ 0);
+ ATF_REQUIRE(p3 != MAP_FAILED);
+
+ ATF_REQUIRE(p3[0] == 0);
+
+ munmap(p1, pagesize);
+ munmap(p2, pagesize);
+ munmap(p3, pagesize);
+ close(fd);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, mmap__map_at_zero);
+ ATF_TP_ADD_TC(tp, mmap__bad_arguments);
+ ATF_TP_ADD_TC(tp, mmap__dev_zero_private);
+ ATF_TP_ADD_TC(tp, mmap__dev_zero_shared);
+
+ return (atf_no_error());
+}