aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/sys/kern/Makefile2
-rw-r--r--tests/sys/kern/copy_file_range.c231
-rw-r--r--tests/sys/net/Makefile1
-rwxr-xr-xtests/sys/net/if_bridge_test.sh54
4 files changed, 285 insertions, 3 deletions
diff --git a/tests/sys/kern/Makefile b/tests/sys/kern/Makefile
index 336e73f29835..9044b1e7e4f2 100644
--- a/tests/sys/kern/Makefile
+++ b/tests/sys/kern/Makefile
@@ -8,6 +8,7 @@ TESTSRC= ${SRCTOP}/contrib/netbsd-tests/kernel
TESTSDIR= ${TESTSBASE}/sys/kern
ATF_TESTS_C+= basic_signal
+ATF_TESTS_C+= copy_file_range
.if ${MACHINE_ARCH} != "i386" && ${MACHINE_ARCH} != "powerpc" && \
${MACHINE_ARCH} != "powerpcspe"
# No support for atomic_load_64 on i386 or (32-bit) powerpc
@@ -81,6 +82,7 @@ PROGS+= coredump_phnum_helper
PROGS+= pdeathsig_helper
PROGS+= sendfile_helper
+LIBADD.copy_file_range+= md
LIBADD.jail_lookup_root+= jail util
CFLAGS.sys_getrandom+= -I${SRCTOP}/sys/contrib/zstd/lib
LIBADD.sys_getrandom+= zstd
diff --git a/tests/sys/kern/copy_file_range.c b/tests/sys/kern/copy_file_range.c
new file mode 100644
index 000000000000..ca52eaf668e3
--- /dev/null
+++ b/tests/sys/kern/copy_file_range.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2025 Mark Johnston <markj@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+#include <sha256.h>
+
+/*
+ * Create a file with random data and size between 1B and 32MB. Return a file
+ * descriptor for the file.
+ */
+static int
+genfile(void)
+{
+ char buf[256], file[NAME_MAX];
+ size_t sz;
+ int fd;
+
+ sz = (random() % (32 * 1024 * 1024ul)) + 1;
+
+ snprintf(file, sizeof(file), "testfile.XXXXXX");
+ fd = mkstemp(file);
+ ATF_REQUIRE(fd != -1);
+
+ while (sz > 0) {
+ ssize_t n;
+ int error;
+
+ error = getentropy(buf, sizeof(buf));
+ ATF_REQUIRE(error == 0);
+ n = write(fd, buf, sizeof(buf) < sz ? sizeof(buf) : sz);
+ ATF_REQUIRE(n > 0);
+
+ sz -= n;
+ }
+
+ ATF_REQUIRE(lseek(fd, 0, SEEK_SET) == 0);
+ return (fd);
+}
+
+/*
+ * Return true if the file data in the two file descriptors is the same,
+ * false otherwise.
+ */
+static bool
+cmpfile(int fd1, int fd2)
+{
+ struct stat st1, st2;
+ void *addr1, *addr2;
+ size_t sz;
+ int res;
+
+ ATF_REQUIRE(fstat(fd1, &st1) == 0);
+ ATF_REQUIRE(fstat(fd2, &st2) == 0);
+ if (st1.st_size != st2.st_size)
+ return (false);
+
+ sz = st1.st_size;
+ addr1 = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd1, 0);
+ ATF_REQUIRE(addr1 != MAP_FAILED);
+ addr2 = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd2, 0);
+ ATF_REQUIRE(addr2 != MAP_FAILED);
+
+ res = memcmp(addr1, addr2, sz);
+
+ ATF_REQUIRE(munmap(addr1, sz) == 0);
+ ATF_REQUIRE(munmap(addr2, sz) == 0);
+
+ return (res == 0);
+}
+
+/*
+ * Exercise a few error paths in the copy_file_range() syscall.
+ */
+ATF_TC_WITHOUT_HEAD(copy_file_range_invalid);
+ATF_TC_BODY(copy_file_range_invalid, tc)
+{
+ off_t off1, off2;
+ int fd1, fd2;
+
+ fd1 = genfile();
+ fd2 = genfile();
+
+ /* Can't copy a file to itself without explicit offsets. */
+ ATF_REQUIRE_ERRNO(EINVAL,
+ copy_file_range(fd1, NULL, fd1, NULL, SSIZE_MAX, 0) == -1);
+
+ /* When copying a file to itself, ranges cannot overlap. */
+ off1 = off2 = 0;
+ ATF_REQUIRE_ERRNO(EINVAL,
+ copy_file_range(fd1, &off1, fd1, &off2, 1, 0) == -1);
+
+ /* Negative offsets are not allowed. */
+ off1 = -1;
+ off2 = 0;
+ ATF_REQUIRE_ERRNO(EINVAL,
+ copy_file_range(fd1, &off1, fd2, &off2, 42, 0) == -1);
+ ATF_REQUIRE_ERRNO(EINVAL,
+ copy_file_range(fd2, &off2, fd1, &off1, 42, 0) == -1);
+}
+
+/*
+ * Make sure that copy_file_range() updates the file offsets passed to it.
+ */
+ATF_TC_WITHOUT_HEAD(copy_file_range_offset);
+ATF_TC_BODY(copy_file_range_offset, tc)
+{
+ struct stat sb;
+ off_t off1, off2;
+ ssize_t n;
+ int fd1, fd2;
+
+ off1 = off2 = 0;
+
+ fd1 = genfile();
+ fd2 = open("copy", O_RDWR | O_CREAT, 0644);
+ ATF_REQUIRE(fd2 != -1);
+
+ ATF_REQUIRE(fstat(fd1, &sb) == 0);
+
+ ATF_REQUIRE(lseek(fd1, 0, SEEK_CUR) == 0);
+ ATF_REQUIRE(lseek(fd2, 0, SEEK_CUR) == 0);
+
+ do {
+ off_t ooff1, ooff2;
+
+ ooff1 = off1;
+ ooff2 = off2;
+ n = copy_file_range(fd1, &off1, fd2, &off2, sb.st_size, 0);
+ ATF_REQUIRE(n >= 0);
+ ATF_REQUIRE_EQ(off1, ooff1 + n);
+ ATF_REQUIRE_EQ(off2, ooff2 + n);
+ } while (n != 0);
+
+ /* Offsets should have been adjusted by copy_file_range(). */
+ ATF_REQUIRE_EQ(off1, sb.st_size);
+ ATF_REQUIRE_EQ(off2, sb.st_size);
+ /* Seek offsets should have been left alone. */
+ ATF_REQUIRE(lseek(fd1, 0, SEEK_CUR) == 0);
+ ATF_REQUIRE(lseek(fd2, 0, SEEK_CUR) == 0);
+ /* Make sure the file contents are the same. */
+ ATF_REQUIRE_MSG(cmpfile(fd1, fd2), "file contents differ");
+
+ ATF_REQUIRE(close(fd1) == 0);
+ ATF_REQUIRE(close(fd2) == 0);
+}
+
+/*
+ * Make sure that copying to a larger file doesn't cause it to be truncated.
+ */
+ATF_TC_WITHOUT_HEAD(copy_file_range_truncate);
+ATF_TC_BODY(copy_file_range_truncate, tc)
+{
+ struct stat sb, sb1, sb2;
+ char digest1[65], digest2[65];
+ off_t off;
+ ssize_t n;
+ int fd1, fd2;
+
+ fd1 = genfile();
+ fd2 = genfile();
+
+ ATF_REQUIRE(fstat(fd1, &sb1) == 0);
+ ATF_REQUIRE(fstat(fd2, &sb2) == 0);
+
+ /* fd1 refers to the smaller file. */
+ if (sb1.st_size > sb2.st_size) {
+ int tmp;
+
+ tmp = fd1;
+ fd1 = fd2;
+ fd2 = tmp;
+ ATF_REQUIRE(fstat(fd1, &sb1) == 0);
+ ATF_REQUIRE(fstat(fd2, &sb2) == 0);
+ }
+
+ /*
+ * Compute a hash of the bytes in the larger file which lie beyond the
+ * length of the smaller file.
+ */
+ SHA256_FdChunk(fd2, digest1, sb1.st_size, sb2.st_size - sb1.st_size);
+ ATF_REQUIRE(lseek(fd2, 0, SEEK_SET) == 0);
+
+ do {
+ n = copy_file_range(fd1, NULL, fd2, NULL, SSIZE_MAX, 0);
+ ATF_REQUIRE(n >= 0);
+ } while (n != 0);
+
+ /* Validate file offsets after the copy. */
+ off = lseek(fd1, 0, SEEK_CUR);
+ ATF_REQUIRE(off == sb1.st_size);
+ off = lseek(fd2, 0, SEEK_CUR);
+ ATF_REQUIRE(off == sb1.st_size);
+
+ /* The larger file's size should remain the same. */
+ ATF_REQUIRE(fstat(fd2, &sb) == 0);
+ ATF_REQUIRE(sb.st_size == sb2.st_size);
+
+ /* The bytes beyond the end of the copy should be unchanged. */
+ SHA256_FdChunk(fd2, digest2, sb1.st_size, sb2.st_size - sb1.st_size);
+ ATF_REQUIRE_MSG(strcmp(digest1, digest2) == 0,
+ "trailing file contents differ after copy_file_range()");
+
+ /*
+ * Verify that the copy actually replicated bytes from the smaller file.
+ */
+ ATF_REQUIRE(ftruncate(fd2, sb1.st_size) == 0);
+ ATF_REQUIRE(cmpfile(fd1, fd2));
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, copy_file_range_invalid);
+ ATF_TP_ADD_TC(tp, copy_file_range_offset);
+ ATF_TP_ADD_TC(tp, copy_file_range_truncate);
+
+ return (atf_no_error());
+}
diff --git a/tests/sys/net/Makefile b/tests/sys/net/Makefile
index bc8f9c5e9c80..65cc99a3e932 100644
--- a/tests/sys/net/Makefile
+++ b/tests/sys/net/Makefile
@@ -6,7 +6,6 @@ BINDIR= ${TESTSDIR}
ATF_TESTS_C+= if_epair
ATF_TESTS_SH+= if_epair_test
ATF_TESTS_SH+= if_bridge_test
-TEST_METADATA.if_bridge_test+= required_programs="python"
TEST_METADATA.if_bridge_test+= execenv="jail"
TEST_METADATA.if_bridge_test+= execenv_jail_params="vnet allow.raw_sockets"
ATF_TESTS_SH+= if_clone_test
diff --git a/tests/sys/net/if_bridge_test.sh b/tests/sys/net/if_bridge_test.sh
index 906f586d2483..0c19903714b1 100755
--- a/tests/sys/net/if_bridge_test.sh
+++ b/tests/sys/net/if_bridge_test.sh
@@ -899,7 +899,7 @@ member_ifaddrs_vlan_cleanup()
atf_test_case "vlan_pvid" "cleanup"
vlan_pvid_head()
{
- atf_set descr 'bridge with two ports with pvid set'
+ atf_set descr 'bridge with two ports with pvid and vlanfilter set'
atf_set require.user root
}
@@ -1327,6 +1327,56 @@ bridge_svi_in_bridge_cleanup()
vnet_cleanup
}
+atf_test_case "vlan_untagged" "cleanup"
+vlan_untagged_head()
+{
+ atf_set descr 'bridge with two ports with untagged set'
+ atf_set require.user root
+}
+
+vlan_untagged_body()
+{
+ vnet_init
+ vnet_init_bridge
+
+ epone=$(vnet_mkepair)
+ eptwo=$(vnet_mkepair)
+
+ vnet_mkjail one ${epone}b
+ vnet_mkjail two ${eptwo}b
+
+ jexec one ifconfig ${epone}b 192.0.2.1/24 up
+ jexec two ifconfig ${eptwo}b 192.0.2.2/24 up
+
+ bridge=$(vnet_mkbridge)
+
+ ifconfig ${bridge} up
+ ifconfig ${epone}a up
+ ifconfig ${eptwo}a up
+ ifconfig ${bridge} addm ${epone}a untagged 20
+ ifconfig ${bridge} addm ${eptwo}a untagged 30
+
+ # With two ports on different VLANs, traffic should not be passed.
+ atf_check -s exit:2 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:2 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+
+ # Move the second port to VLAN 20; now traffic should be passed.
+ atf_check -s exit:0 ifconfig ${bridge} ifuntagged ${eptwo}a 20
+ atf_check -s exit:0 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:0 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+
+ # Remove the first's port untagged config, now traffic should
+ # not pass again.
+ atf_check -s exit:0 ifconfig ${bridge} -ifuntagged ${epone}a
+ atf_check -s exit:2 -o ignore jexec one ping -c 3 -t 1 192.0.2.2
+ atf_check -s exit:2 -o ignore jexec two ping -c 3 -t 1 192.0.2.1
+}
+
+vlan_untagged_cleanup()
+{
+ vnet_cleanup
+}
+
atf_test_case "vlan_defuntagged" "cleanup"
vlan_defuntagged_head()
{
@@ -1340,7 +1390,6 @@ vlan_defuntagged_body()
vnet_init_bridge
bridge=$(vnet_mkbridge)
- atf_check -s exit:0 ifconfig ${bridge} vlanfilter
# Invalid VLAN IDs
atf_check -s exit:1 -ematch:"invalid vlan id: 0" \
@@ -1407,6 +1456,7 @@ atf_init_test_cases()
atf_add_test_case "vlan_ifconfig_iftagged"
atf_add_test_case "vlan_svi"
atf_add_test_case "vlan_qinq"
+ atf_add_test_case "vlan_untagged"
atf_add_test_case "vlan_defuntagged"
atf_add_test_case "bridge_svi_in_bridge"
}