diff options
Diffstat (limited to 'lib/libc/tests')
67 files changed, 4466 insertions, 91 deletions
diff --git a/lib/libc/tests/db/Makefile b/lib/libc/tests/db/Makefile index f1f33bd2bafc..771569183584 100644 --- a/lib/libc/tests/db/Makefile +++ b/lib/libc/tests/db/Makefile @@ -7,6 +7,10 @@ PROGS+= h_lfsr ${PACKAGE}FILES+= README +ATF_TESTS_C+= dbm_open_test +ATF_TESTS_C+= dbm_perm_test +ATF_TESTS_C+= dbm_nextkey_test + NETBSD_ATF_TESTS_C+= db_hash_seq_test NETBSD_ATF_TESTS_SH+= db_test ATF_TESTS_SH_SED_db_test= -e 's,/bin/csh,/bin/cat,g' diff --git a/lib/libc/tests/db/dbm_nextkey_test.c b/lib/libc/tests/db/dbm_nextkey_test.c new file mode 100644 index 000000000000..67b745efb196 --- /dev/null +++ b/lib/libc/tests/db/dbm_nextkey_test.c @@ -0,0 +1,53 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <fcntl.h> +#include <ndbm.h> +#include <stdio.h> + +#include <atf-c.h> + +static const char *path = "tmp"; +static const char *dbname = "tmp.db"; + +ATF_TC(dbm_nextkey_test); +ATF_TC_HEAD(dbm_nextkey_test, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Check that dbm_nextkey always returns NULL after reaching the end of the database"); +} + +ATF_TC_BODY(dbm_nextkey_test, tc) +{ + DBM *db; + datum key, data; + + data.dptr = "bar"; + data.dsize = strlen("bar"); + key.dptr = "foo"; + key.dsize = strlen("foo"); + + db = dbm_open(path, O_RDWR | O_CREAT, 0755); + ATF_CHECK(db != NULL); + ATF_REQUIRE(atf_utils_file_exists(dbname)); + ATF_REQUIRE(dbm_store(db, key, data, DBM_INSERT) != -1); + + key = dbm_firstkey(db); + ATF_REQUIRE(key.dptr != NULL); + key = dbm_nextkey(db); + ATF_REQUIRE(key.dptr == NULL); + key = dbm_nextkey(db); + ATF_REQUIRE(key.dptr == NULL); + + dbm_close(db); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, dbm_nextkey_test); + + return (atf_no_error()); +} diff --git a/lib/libc/tests/db/dbm_open_test.c b/lib/libc/tests/db/dbm_open_test.c new file mode 100644 index 000000000000..8a3e888bf72c --- /dev/null +++ b/lib/libc/tests/db/dbm_open_test.c @@ -0,0 +1,52 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <fcntl.h> +#include <ndbm.h> +#include <stdio.h> + +#include <atf-c.h> + +static const char *path = "tmp"; +static const char *dbname = "tmp.db"; + +ATF_TC(dbm_open_missing_test); +ATF_TC_HEAD(dbm_open_missing_test, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test dbm_open when creating a new database"); +} + +ATF_TC_BODY(dbm_open_missing_test, tc) +{ + + /* + * POSIX.1 specifies that a missing database file should + * always get created if O_CREAT is present, except when + * O_EXCL is specified as well. + */ + ATF_CHECK(dbm_open(path, O_RDONLY, 0755) == NULL); + ATF_REQUIRE(!atf_utils_file_exists(dbname)); + ATF_CHECK(dbm_open(path, O_RDONLY | O_CREAT, 0755) != NULL); + ATF_REQUIRE(atf_utils_file_exists(dbname)); + ATF_CHECK(dbm_open(path, O_RDONLY | O_CREAT | O_EXCL, 0755) == NULL); +} + +ATF_TC_WITHOUT_HEAD(dbm_open_wronly_test); +ATF_TC_BODY(dbm_open_wronly_test, tc) +{ + ATF_CHECK(dbm_open(path, O_WRONLY, 0755) == NULL); + ATF_REQUIRE(!atf_utils_file_exists(dbname)); + ATF_CHECK(dbm_open(path, O_WRONLY | O_CREAT, 0755) != NULL); + ATF_REQUIRE(atf_utils_file_exists(dbname)); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, dbm_open_missing_test); + ATF_TP_ADD_TC(tp, dbm_open_wronly_test); + return (atf_no_error()); +} diff --git a/lib/libc/tests/db/dbm_perm_test.c b/lib/libc/tests/db/dbm_perm_test.c new file mode 100644 index 000000000000..c07210292014 --- /dev/null +++ b/lib/libc/tests/db/dbm_perm_test.c @@ -0,0 +1,98 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <errno.h> +#include <fcntl.h> +#include <ndbm.h> +#include <stdio.h> + +#include <atf-c.h> + +static const char *path = "tmp"; +static const char *dbname = "tmp.db"; + +static void +create_db(void) +{ + DB *db; + datum data, key; + + data.dptr = "bar"; + data.dsize = strlen("bar"); + key.dptr = "foo"; + key.dsize = strlen("foo"); + + db = dbm_open(path, O_RDWR | O_CREAT, 0755); + ATF_CHECK(db != NULL); + ATF_REQUIRE(atf_utils_file_exists(dbname)); + ATF_REQUIRE(dbm_store(db, key, data, DBM_INSERT) != -1); + dbm_close(db); +} + +ATF_TC_WITHOUT_HEAD(dbm_rdonly_test); +ATF_TC_BODY(dbm_rdonly_test, tc) +{ + DB *db; + datum data, key; + + bzero(&data, sizeof(data)); + key.dptr = "foo"; + key.dsize = strlen("foo"); + create_db(); + + db = dbm_open(path, O_RDONLY, 0755); + data = dbm_fetch(db, key); + ATF_REQUIRE(data.dptr != NULL); + ATF_REQUIRE(strncmp((const char*)data.dptr, "bar", data.dsize) == 0); + ATF_REQUIRE(dbm_store(db, key, data, DBM_REPLACE) == -1); + ATF_REQUIRE(errno == EPERM); +} + +ATF_TC_WITHOUT_HEAD(dbm_wronly_test); +ATF_TC_BODY(dbm_wronly_test, tc) +{ + DB *db; + datum data, key; + + key.dptr = "foo"; + key.dsize = strlen("foo"); + data.dptr = "baz"; + data.dsize = strlen("baz"); + create_db(); + + db = dbm_open(path, O_WRONLY, 0755); + data = dbm_fetch(db, key); + ATF_REQUIRE(data.dptr == NULL); + ATF_REQUIRE(errno == EPERM); + ATF_REQUIRE(dbm_store(db, key, data, DBM_REPLACE) != -1); +} + +ATF_TC_WITHOUT_HEAD(dbm_rdwr_test); +ATF_TC_BODY(dbm_rdwr_test, tc) +{ + DB *db; + datum data, key; + + key.dptr = "foo"; + key.dsize = strlen("foo"); + create_db(); + + db = dbm_open(path, O_RDWR, 0755); + data = dbm_fetch(db, key); + ATF_REQUIRE(data.dptr != NULL); + data.dptr = "baz"; + data.dsize = strlen("baz"); + ATF_REQUIRE(dbm_store(db, key, data, DBM_REPLACE) != -1); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, dbm_rdonly_test); + ATF_TP_ADD_TC(tp, dbm_wronly_test); + ATF_TP_ADD_TC(tp, dbm_rdwr_test); + + return (atf_no_error()); +} diff --git a/lib/libc/tests/gen/Makefile b/lib/libc/tests/gen/Makefile index 87f2b8320d25..8c2151105209 100644 --- a/lib/libc/tests/gen/Makefile +++ b/lib/libc/tests/gen/Makefile @@ -7,14 +7,28 @@ ATF_TESTS_C+= fmtcheck2_test ATF_TESTS_C+= fmtmsg_test ATF_TESTS_C+= fnmatch2_test ATF_TESTS_C+= fpclassify2_test +.if ${COMPILER_FEATURES:Mblocks} +ATF_TESTS_C+= fts_blocks_test +.endif +ATF_TESTS_C+= fts_misc_test +ATF_TESTS_C+= fts_options_test ATF_TESTS_C+= ftw_test ATF_TESTS_C+= getentropy_test ATF_TESTS_C+= getmntinfo_test ATF_TESTS_C+= glob2_test +.if ${COMPILER_FEATURES:Mblocks} +ATF_TESTS_C+= glob_blocks_test +.endif ATF_TESTS_C+= makecontext_test +ATF_TESTS_C+= opendir_test ATF_TESTS_C+= popen_test ATF_TESTS_C+= posix_spawn_test ATF_TESTS_C+= realpath2_test +ATF_TESTS_C+= scandir_test +.if ${COMPILER_FEATURES:Mblocks} +ATF_TESTS_C+= scandir_blocks_test +.endif +ATF_TESTS_C+= sig2str_test ATF_TESTS_C+= sigsetops_test ATF_TESTS_C+= wordexp_test @@ -92,6 +106,20 @@ TEST_METADATA.setdomainname_test+= is_exclusive=true TESTS_SUBDIRS= execve TESTS_SUBDIRS+= posix_spawn +# Tests that require address sanitizer +.if ${COMPILER_FEATURES:Masan} +.for t in scandir_test realpath2_test +CFLAGS.${t}.c+= -fsanitize=address +LDFLAGS.${t}+= -fsanitize=address +.endfor +.endif + +# Tests that require blocks support +.for t in fts_blocks_test glob_blocks_test scandir_blocks_test +CFLAGS.${t}.c+= -fblocks +LIBADD.${t}+= BlocksRuntime +.endfor + # The old testcase name TEST_FNMATCH= test-fnmatch CLEANFILES+= ${GEN_SH_CASE_TESTCASES} diff --git a/lib/libc/tests/gen/fnmatch_test.c b/lib/libc/tests/gen/fnmatch_test.c index 6cdf5f2a05fa..0ff7400a4a4f 100644 --- a/lib/libc/tests/gen/fnmatch_test.c +++ b/lib/libc/tests/gen/fnmatch_test.c @@ -26,6 +26,7 @@ #include <sys/param.h> #include <errno.h> +#include <locale.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -176,10 +177,90 @@ ATF_TC_BODY(fnmatch_test, tc) } +ATF_TC(fnmatch_characterclass); +ATF_TC_HEAD(fnmatch_characterclass, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test fnmatch with character classes"); +} + +ATF_TC_BODY(fnmatch_characterclass, tc) +{ + ATF_CHECK(fnmatch("[[:alnum:]]", "a", 0) == 0); + ATF_CHECK(fnmatch("[[:cntrl:]]", "\a", 0) == 0); + ATF_CHECK(fnmatch("[[:lower:]]", "a", 0) == 0); + ATF_CHECK(fnmatch("[[:space:]]", " ", 0) == 0); + ATF_CHECK(fnmatch("[[:alpha:]]", "a", 0) == 0); + ATF_CHECK(fnmatch("[[:digit:]]", "0", 0) == 0); + ATF_CHECK(fnmatch("[[:print:]]", "a", 0) == 0); + ATF_CHECK(fnmatch("[[:upper:]]", "A", 0) == 0); + ATF_CHECK(fnmatch("[[:blank:]]", " ", 0) == 0); + ATF_CHECK(fnmatch("[[:graph:]]", "a", 0) == 0); + ATF_CHECK(fnmatch("[[:punct:]]", ".", 0) == 0); + ATF_CHECK(fnmatch("[[:xdigit:]]", "f", 0) == 0); + + /* + * POSIX.1, section 9.3.5. states that '[:' and ':]' + * should be interpreted as character classes symbol only + * when part of a bracket expression. + */ + ATF_CHECK(fnmatch("[:alnum:]", "a", 0) == 0); + ATF_CHECK(fnmatch("[:alnum:]", ":", 0) == 0); + ATF_CHECK(fnmatch("[:alnum:]", "1", 0) != 0); +} + +ATF_TC(fnmatch_collsym); +ATF_TC_HEAD(fnmatch_collsym, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test fnmatch with collating symbols"); +} + +ATF_TC_BODY(fnmatch_collsym, tc) +{ + setlocale(LC_ALL, "cs_CZ.UTF-8"); + ATF_CHECK(fnmatch("[ch]", "ch", 0) != 0); + ATF_CHECK(fnmatch("[[.ch.]]", "ch", 0) == 0); + ATF_CHECK(fnmatch("[[.ch.]]h", "chh", 0) == 0); + + /* + * POSIX.1, section 9.3.5. states that '[.' and '.]' + * should be interpreted as a collating symbol only + * when part of a bracket expression. + */ + ATF_CHECK(fnmatch("[.ch.]", "c", 0) == 0); + ATF_CHECK(fnmatch("[.ch.]", "h", 0) == 0); + ATF_CHECK(fnmatch("[.ch.]", ".", 0) == 0); +} + +ATF_TC(fnmatch_equivclass); +ATF_TC_HEAD(fnmatch_equivclass, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test fnmatch with equivalence classes"); +} + +ATF_TC_BODY(fnmatch_equivclass, tc) +{ + setlocale(LC_ALL, "en_US.UTF-8"); + ATF_CHECK(fnmatch("[[=a=]]b", "ab", 0) == 0); + ATF_CHECK(fnmatch("[[=a=]]b", "Ab", 0) == 0); + ATF_CHECK(fnmatch("[[=Ã =]]b", "ab", 0) == 0); + ATF_CHECK(fnmatch("[[=a=]]b", "Ã b", 0) == 0); + + /* + * POSIX.1, section 9.3.5. states that '[=' and '=]' + * should be interpreted as an equivalence class only + * when part of a bracket expression. + */ + ATF_CHECK(fnmatch("[=a=]b", "=b", 0) == 0); + ATF_CHECK(fnmatch("[=a=]b", "ab", 0) == 0); +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, fnmatch_test); + ATF_TP_ADD_TC(tp, fnmatch_collsym); + ATF_TP_ADD_TC(tp, fnmatch_characterclass); + ATF_TP_ADD_TC(tp, fnmatch_equivclass); return (atf_no_error()); } diff --git a/lib/libc/tests/gen/fts_blocks_test.c b/lib/libc/tests/gen/fts_blocks_test.c new file mode 100644 index 000000000000..f020dd8dea45 --- /dev/null +++ b/lib/libc/tests/gen/fts_blocks_test.c @@ -0,0 +1,68 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/stat.h> + +#include <fcntl.h> +#include <fts.h> + +#include <atf-c.h> + +/* + * Create two directories with three files each in lexicographical order, + * then call FTS with a sort block that sorts in reverse lexicographical + * order. This has the least chance of getting a false positive due to + * differing file system semantics. UFS will return the files in the + * order they were created while ZFS will sort them lexicographically; in + * both cases, the order we expect is the reverse. + */ +ATF_TC(fts_blocks_test); +ATF_TC_HEAD(fts_blocks_test, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test FTS with a block in lieu of a comparison function"); +} +ATF_TC_BODY(fts_blocks_test, tc) +{ + char *args[] = { + "bar", "foo", NULL + }; + char *paths[] = { + "foo", "z", "y", "x", "foo", + "bar", "c", "b", "a", "bar", + NULL + }; + char **expect = paths; + FTS *fts; + FTSENT *ftse; + + ATF_REQUIRE_EQ(0, mkdir("bar", 0755)); + ATF_REQUIRE_EQ(0, close(creat("bar/a", 0644))); + ATF_REQUIRE_EQ(0, close(creat("bar/b", 0644))); + ATF_REQUIRE_EQ(0, close(creat("bar/c", 0644))); + ATF_REQUIRE_EQ(0, mkdir("foo", 0755)); + ATF_REQUIRE_EQ(0, close(creat("foo/x", 0644))); + ATF_REQUIRE_EQ(0, close(creat("foo/y", 0644))); + ATF_REQUIRE_EQ(0, close(creat("foo/z", 0644))); + fts = fts_open_b(args, 0, + ^(const FTSENT * const *a, const FTSENT * const *b) { + return (strcmp((*b)->fts_name, (*a)->fts_name)); + }); + ATF_REQUIRE_MSG(fts != NULL, "fts_open_b(): %m"); + while ((ftse = fts_read(fts)) != NULL && *expect != NULL) { + ATF_CHECK_STREQ(*expect, ftse->fts_name); + expect++; + } + ATF_CHECK_EQ(NULL, ftse); + ATF_CHECK_EQ(NULL, *expect); + ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m"); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, fts_blocks_test); + return (atf_no_error()); +} diff --git a/lib/libc/tests/gen/fts_misc_test.c b/lib/libc/tests/gen/fts_misc_test.c new file mode 100644 index 000000000000..91640078f63c --- /dev/null +++ b/lib/libc/tests/gen/fts_misc_test.c @@ -0,0 +1,78 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/stat.h> + +#include <fcntl.h> +#include <fts.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <atf-c.h> + +#include "fts_test.h" + +ATF_TC(fts_unrdir); +ATF_TC_HEAD(fts_unrdir, tc) +{ + atf_tc_set_md_var(tc, "descr", "unreadable directories"); + atf_tc_set_md_var(tc, "require.user", "unprivileged"); +} +ATF_TC_BODY(fts_unrdir, tc) +{ + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + ATF_REQUIRE_EQ(0, mkdir("dir/unr", 0100)); + ATF_REQUIRE_EQ(0, mkdir("dir/unx", 0400)); + fts_test(tc, &(struct fts_testcase){ + (char *[]){ "dir", NULL }, + FTS_PHYSICAL, + (struct fts_expect[]){ + { FTS_D, "dir", "dir" }, + { FTS_D, "unr", "unr" }, + { FTS_DNR, "unr", "unr" }, + { FTS_D, "unx", "unx" }, + { FTS_DP, "unx", "unx" }, + { FTS_DP, "dir", "dir" }, + { 0 } + }, + }); +} + +ATF_TC(fts_unrdir_nochdir); +ATF_TC_HEAD(fts_unrdir_nochdir, tc) +{ + atf_tc_set_md_var(tc, "descr", "unreadable directories (nochdir)"); + atf_tc_set_md_var(tc, "require.user", "unprivileged"); +} +ATF_TC_BODY(fts_unrdir_nochdir, tc) +{ + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + ATF_REQUIRE_EQ(0, mkdir("dir/unr", 0100)); + ATF_REQUIRE_EQ(0, mkdir("dir/unx", 0400)); + fts_test(tc, &(struct fts_testcase){ + (char *[]){ "dir", NULL }, + FTS_PHYSICAL | FTS_NOCHDIR, + (struct fts_expect[]){ + { FTS_D, "dir", "dir" }, + { FTS_D, "unr", "dir/unr" }, + { FTS_DNR, "unr", "dir/unr" }, + { FTS_D, "unx", "dir/unx" }, + { FTS_DP, "unx", "dir/unx" }, + { FTS_DP, "dir", "dir" }, + { 0 } + }, + }); +} + +ATF_TP_ADD_TCS(tp) +{ + fts_check_debug(); + ATF_TP_ADD_TC(tp, fts_unrdir); + ATF_TP_ADD_TC(tp, fts_unrdir_nochdir); + return (atf_no_error()); +} diff --git a/lib/libc/tests/gen/fts_options_test.c b/lib/libc/tests/gen/fts_options_test.c new file mode 100644 index 000000000000..fc3015138a49 --- /dev/null +++ b/lib/libc/tests/gen/fts_options_test.c @@ -0,0 +1,394 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/stat.h> + +#include <fcntl.h> +#include <fts.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <atf-c.h> + +#include "fts_test.h" + +static char *all_paths[] = { + "dir", + "dirl", + "file", + "filel", + "dead", + "noent", + NULL +}; + +/* + * Prepare the files and directories we will be inspecting. + */ +static void +fts_options_prepare(const struct atf_tc *tc) +{ + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + ATF_REQUIRE_EQ(0, close(creat("file", 0644))); + ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644))); + ATF_REQUIRE_EQ(0, symlink("..", "dir/up")); + ATF_REQUIRE_EQ(0, symlink("dir", "dirl")); + ATF_REQUIRE_EQ(0, symlink("file", "filel")); + ATF_REQUIRE_EQ(0, symlink("noent", "dead")); +} + +ATF_TC(fts_options_logical); +ATF_TC_HEAD(fts_options_logical, tc) +{ + atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL"); +} +ATF_TC_BODY(fts_options_logical, tc) +{ + fts_options_prepare(tc); + fts_test(tc, &(struct fts_testcase){ + all_paths, + FTS_LOGICAL, + (struct fts_expect[]){ + { FTS_DL, "dead", "dead" }, + { FTS_D, "dir", "dir" }, + { FTS_F, "file", "dir/file" }, + { FTS_D, "up", "dir/up" }, + { FTS_DL, "dead", "dir/up/dead" }, + { FTS_DC, "dir", "dir/up/dir" }, + { FTS_DC, "dirl", "dir/up/dirl" }, + { FTS_F, "file", "dir/up/file" }, + { FTS_F, "filel", "dir/up/filel" }, + { FTS_DP, "up", "dir/up" }, + { FTS_DP, "dir", "dir" }, + { FTS_D, "dirl", "dirl" }, + { FTS_F, "file", "dirl/file" }, + { FTS_D, "up", "dirl/up" }, + { FTS_DL, "dead", "dirl/up/dead" }, + { FTS_DC, "dir", "dirl/up/dir" }, + { FTS_DC, "dirl", "dirl/up/dirl" }, + { FTS_F, "file", "dirl/up/file" }, + { FTS_F, "filel", "dirl/up/filel" }, + { FTS_DP, "up", "dirl/up" }, + { FTS_DP, "dirl", "dirl" }, + { FTS_F, "file", "file" }, + { FTS_F, "filel", "filel" }, + { FTS_NS, "noent", "noent" }, + { 0 } + }, + }); +} + +ATF_TC(fts_options_logical_nostat); +ATF_TC_HEAD(fts_options_logical_nostat, tc) +{ + atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL | FTS_NOSTAT"); +} +ATF_TC_BODY(fts_options_logical_nostat, tc) +{ + /* + * While FTS_LOGICAL is not documented as being incompatible with + * FTS_NOSTAT, and FTS does not clear FTS_NOSTAT if FTS_LOGICAL is + * set, FTS_LOGICAL effectively nullifies FTS_NOSTAT by overriding + * the follow check in fts_stat(). In theory, FTS could easily be + * changed to only stat links (to check what they point to) in the + * FTS_LOGICAL | FTS_NOSTAT case, which would produce a different + * result here, so keep the test around in case that ever happens. + */ + atf_tc_expect_fail("FTS_LOGICAL nullifies FTS_NOSTAT"); + fts_options_prepare(tc); + fts_test(tc, &(struct fts_testcase){ + all_paths, + FTS_LOGICAL | FTS_NOSTAT, + (struct fts_expect[]){ + { FTS_DL, "dead", "dead" }, + { FTS_D, "dir", "dir" }, + { FTS_NSOK, "file", "dir/file" }, + { FTS_D, "up", "dir/up" }, + { FTS_DL, "dead", "dir/up/dead" }, + { FTS_DC, "dir", "dir/up/dir" }, + { FTS_DC, "dirl", "dir/up/dirl" }, + { FTS_NSOK, "file", "dir/up/file" }, + { FTS_NSOK, "filel", "dir/up/filel" }, + { FTS_DP, "up", "dir/up" }, + { FTS_DP, "dir", "dir" }, + { FTS_D, "dirl", "dirl" }, + { FTS_NSOK, "file", "dirl/file" }, + { FTS_D, "up", "dirl/up" }, + { FTS_DL, "dead", "dirl/up/dead" }, + { FTS_DC, "dir", "dirl/up/dir" }, + { FTS_DC, "dirl", "dirl/up/dirl" }, + { FTS_NSOK, "file", "dirl/up/file" }, + { FTS_NSOK, "filel", "dirl/up/filel" }, + { FTS_DP, "up", "dirl/up" }, + { FTS_DP, "dirl", "dirl" }, + { FTS_F, "file", "file" }, + { FTS_F, "filel", "filel" }, + { FTS_NS, "noent", "noent" }, + { 0 } + }, + }); +} + +ATF_TC(fts_options_logical_seedot); +ATF_TC_HEAD(fts_options_logical_seedot, tc) +{ + atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL | FTS_SEEDOT"); +} +ATF_TC_BODY(fts_options_logical_seedot, tc) +{ + fts_options_prepare(tc); + fts_test(tc, &(struct fts_testcase){ + all_paths, + FTS_LOGICAL | FTS_SEEDOT, + (struct fts_expect[]){ + { FTS_DL, "dead", "dead" }, + { FTS_D, "dir", "dir" }, + { FTS_DOT, ".", "dir/." }, + { FTS_DOT, "..", "dir/.." }, + { FTS_F, "file", "dir/file" }, + { FTS_D, "up", "dir/up" }, + { FTS_DOT, ".", "dir/up/." }, + { FTS_DOT, "..", "dir/up/.." }, + { FTS_DL, "dead", "dir/up/dead" }, + { FTS_DC, "dir", "dir/up/dir" }, + { FTS_DC, "dirl", "dir/up/dirl" }, + { FTS_F, "file", "dir/up/file" }, + { FTS_F, "filel", "dir/up/filel" }, + { FTS_DP, "up", "dir/up" }, + { FTS_DP, "dir", "dir" }, + { FTS_D, "dirl", "dirl" }, + { FTS_DOT, ".", "dirl/." }, + { FTS_DOT, "..", "dirl/.." }, + { FTS_F, "file", "dirl/file" }, + { FTS_D, "up", "dirl/up" }, + { FTS_DOT, ".", "dirl/up/." }, + { FTS_DOT, "..", "dirl/up/.." }, + { FTS_DL, "dead", "dirl/up/dead" }, + { FTS_DC, "dir", "dirl/up/dir" }, + { FTS_DC, "dirl", "dirl/up/dirl" }, + { FTS_F, "file", "dirl/up/file" }, + { FTS_F, "filel", "dirl/up/filel" }, + { FTS_DP, "up", "dirl/up" }, + { FTS_DP, "dirl", "dirl" }, + { FTS_F, "file", "file" }, + { FTS_F, "filel", "filel" }, + { FTS_NS, "noent", "noent" }, + { 0 } + }, + }); +} + +ATF_TC(fts_options_physical); +ATF_TC_HEAD(fts_options_physical, tc) +{ + atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL"); +} +ATF_TC_BODY(fts_options_physical, tc) +{ + fts_options_prepare(tc); + fts_test(tc, &(struct fts_testcase){ + all_paths, + FTS_PHYSICAL, + (struct fts_expect[]){ + { FTS_SL, "dead", "dead" }, + { FTS_D, "dir", "dir" }, + { FTS_F, "file", "file" }, + { FTS_SL, "up", "up" }, + { FTS_DP, "dir", "dir" }, + { FTS_SL, "dirl", "dirl" }, + { FTS_F, "file", "file" }, + { FTS_SL, "filel", "filel" }, + { FTS_NS, "noent", "noent" }, + { 0 } + }, + }); +} + +ATF_TC(fts_options_physical_nochdir); +ATF_TC_HEAD(fts_options_physical_nochdir, tc) +{ + atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOCHDIR"); +} +ATF_TC_BODY(fts_options_physical_nochdir, tc) +{ + fts_options_prepare(tc); + fts_test(tc, &(struct fts_testcase){ + all_paths, + FTS_PHYSICAL | FTS_NOCHDIR, + (struct fts_expect[]){ + { FTS_SL, "dead", "dead" }, + { FTS_D, "dir", "dir" }, + { FTS_F, "file", "dir/file" }, + { FTS_SL, "up", "dir/up" }, + { FTS_DP, "dir", "dir" }, + { FTS_SL, "dirl", "dirl" }, + { FTS_F, "file", "file" }, + { FTS_SL, "filel", "filel" }, + { FTS_NS, "noent", "noent" }, + { 0 } + }, + }); +} + +ATF_TC(fts_options_physical_comfollow); +ATF_TC_HEAD(fts_options_physical_comfollow, tc) +{ + atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_COMFOLLOW"); +} +ATF_TC_BODY(fts_options_physical_comfollow, tc) +{ + fts_options_prepare(tc); + fts_test(tc, &(struct fts_testcase){ + all_paths, + FTS_PHYSICAL | FTS_COMFOLLOW, + (struct fts_expect[]){ + { FTS_DL, "dead", "dead" }, + { FTS_D, "dir", "dir" }, + { FTS_F, "file", "file" }, + { FTS_SL, "up", "up" }, + { FTS_DP, "dir", "dir" }, + { FTS_D, "dirl", "dirl" }, + { FTS_F, "file", "file" }, + { FTS_SL, "up", "up" }, + { FTS_DP, "dirl", "dirl" }, + { FTS_F, "file", "file" }, + { FTS_F, "filel", "filel" }, + { FTS_NS, "noent", "noent" }, + { 0 } + }, + }); +} + +ATF_TC(fts_options_physical_comfollowdir); +ATF_TC_HEAD(fts_options_physical_comfollowdir, tc) +{ + atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_COMFOLLOWDIR"); +} +ATF_TC_BODY(fts_options_physical_comfollowdir, tc) +{ + fts_options_prepare(tc); + fts_test(tc, &(struct fts_testcase){ + all_paths, + FTS_PHYSICAL | FTS_COMFOLLOWDIR, + (struct fts_expect[]){ + { FTS_DL, "dead", "dead" }, + { FTS_D, "dir", "dir" }, + { FTS_F, "file", "file" }, + { FTS_SL, "up", "up" }, + { FTS_DP, "dir", "dir" }, + { FTS_D, "dirl", "dirl" }, + { FTS_F, "file", "file" }, + { FTS_SL, "up", "up" }, + { FTS_DP, "dirl", "dirl" }, + { FTS_F, "file", "file" }, + { FTS_SL, "filel", "filel" }, + { FTS_NS, "noent", "noent" }, + { 0 } + }, + }); +} + +ATF_TC(fts_options_physical_nostat); +ATF_TC_HEAD(fts_options_physical_nostat, tc) +{ + atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOSTAT"); +} +ATF_TC_BODY(fts_options_physical_nostat, tc) +{ + fts_options_prepare(tc); + fts_test(tc, &(struct fts_testcase){ + all_paths, + FTS_PHYSICAL | FTS_NOSTAT, + (struct fts_expect[]){ + { FTS_SL, "dead", "dead" }, + { FTS_D, "dir", "dir" }, + { FTS_NSOK, "file", "file" }, + { FTS_NSOK, "up", "up" }, + { FTS_DP, "dir", "dir" }, + { FTS_SL, "dirl", "dirl" }, + { FTS_F, "file", "file" }, + { FTS_SL, "filel", "filel" }, + { FTS_NS, "noent", "noent" }, + { 0 } + }, + }); +} + +ATF_TC(fts_options_physical_nostat_type); +ATF_TC_HEAD(fts_options_physical_nostat_type, tc) +{ + atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOSTAT_TYPE"); +} +ATF_TC_BODY(fts_options_physical_nostat_type, tc) +{ + fts_options_prepare(tc); + fts_test(tc, &(struct fts_testcase){ + all_paths, + FTS_PHYSICAL | FTS_NOSTAT_TYPE, + (struct fts_expect[]){ + { FTS_SL, "dead", "dead" }, + { FTS_D, "dir", "dir" }, + { FTS_F, "file", "file" }, + { FTS_SL, "up", "up" }, + { FTS_DP, "dir", "dir" }, + { FTS_SL, "dirl", "dirl" }, + { FTS_F, "file", "file" }, + { FTS_SL, "filel", "filel" }, + { FTS_NS, "noent", "noent" }, + { 0 } + }, + }); +} + +ATF_TC(fts_options_physical_seedot); +ATF_TC_HEAD(fts_options_physical_seedot, tc) +{ + atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_SEEDOT"); +} +ATF_TC_BODY(fts_options_physical_seedot, tc) +{ + fts_options_prepare(tc); + fts_test(tc, &(struct fts_testcase){ + all_paths, + FTS_PHYSICAL | FTS_SEEDOT, + (struct fts_expect[]){ + { FTS_SL, "dead", "dead" }, + { FTS_D, "dir", "dir" }, + { FTS_DOT, ".", "." }, + { FTS_DOT, "..", ".." }, + { FTS_F, "file", "file" }, + { FTS_SL, "up", "up" }, + { FTS_DP, "dir", "dir" }, + { FTS_SL, "dirl", "dirl" }, + { FTS_F, "file", "file" }, + { FTS_SL, "filel", "filel" }, + { FTS_NS, "noent", "noent" }, + { 0 } + }, + }); +} + +/* + * TODO: Add tests for FTS_XDEV and FTS_WHITEOUT + */ + +ATF_TP_ADD_TCS(tp) +{ + fts_check_debug(); + ATF_TP_ADD_TC(tp, fts_options_logical); + ATF_TP_ADD_TC(tp, fts_options_logical_nostat); + ATF_TP_ADD_TC(tp, fts_options_logical_seedot); + ATF_TP_ADD_TC(tp, fts_options_physical); + ATF_TP_ADD_TC(tp, fts_options_physical_nochdir); + ATF_TP_ADD_TC(tp, fts_options_physical_comfollow); + ATF_TP_ADD_TC(tp, fts_options_physical_comfollowdir); + ATF_TP_ADD_TC(tp, fts_options_physical_nostat); + ATF_TP_ADD_TC(tp, fts_options_physical_nostat_type); + ATF_TP_ADD_TC(tp, fts_options_physical_seedot); + return (atf_no_error()); +} diff --git a/lib/libc/tests/gen/fts_test.h b/lib/libc/tests/gen/fts_test.h new file mode 100644 index 000000000000..b3f15050f265 --- /dev/null +++ b/lib/libc/tests/gen/fts_test.h @@ -0,0 +1,81 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef FTS_TEST_H_INCLUDED +#define FTS_TEST_H_INCLUDED + +struct fts_expect { + int fts_info; + const char *fts_name; + const char *fts_accpath; +}; + +struct fts_testcase { + char **paths; + int fts_options; + struct fts_expect *fts_expect; +}; + +/* shorter name for dead links */ +#define FTS_DL FTS_SLNONE + +/* are we being debugged? */ +static bool fts_test_debug; + +/* + * Set debug flag if appropriate. + */ +static void +fts_check_debug(void) +{ + fts_test_debug = !getenv("__RUNNING_INSIDE_ATF_RUN") && + isatty(STDERR_FILENO); +} + +/* + * Lexical order for reproducability. + */ +static int +fts_lexical_compar(const FTSENT * const *a, const FTSENT * const *b) +{ + return (strcmp((*a)->fts_name, (*b)->fts_name)); +} + +/* + * Run FTS with the specified paths and options and verify that it + * produces the expected result in the correct order. + */ +static void +fts_test(const struct atf_tc *tc, const struct fts_testcase *fts_tc) +{ + FTS *fts; + FTSENT *ftse; + const struct fts_expect *expect = fts_tc->fts_expect; + long level = 0; + + fts = fts_open(fts_tc->paths, fts_tc->fts_options, fts_lexical_compar); + ATF_REQUIRE_MSG(fts != NULL, "fts_open(): %m"); + while ((ftse = fts_read(fts)) != NULL && expect->fts_name != NULL) { + if (expect->fts_info == FTS_DP || expect->fts_info == FTS_DNR) + level--; + if (fts_test_debug) { + fprintf(stderr, "%2ld %2d %s\n", level, + ftse->fts_info, ftse->fts_name); + } + ATF_CHECK_STREQ(expect->fts_name, ftse->fts_name); + ATF_CHECK_STREQ(expect->fts_accpath, ftse->fts_accpath); + ATF_CHECK_INTEQ(expect->fts_info, ftse->fts_info); + ATF_CHECK_INTEQ(level, ftse->fts_level); + if (expect->fts_info == FTS_D) + level++; + expect++; + } + ATF_CHECK_EQ(NULL, ftse); + ATF_CHECK_EQ(NULL, expect->fts_name); + ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m"); +} + +#endif /* FTS_TEST_H_INCLUDED */ diff --git a/lib/libc/tests/gen/glob2_test.c b/lib/libc/tests/gen/glob2_test.c index 8f69c36fece7..ff1b36b830b8 100644 --- a/lib/libc/tests/gen/glob2_test.c +++ b/lib/libc/tests/gen/glob2_test.c @@ -25,9 +25,12 @@ */ #include <sys/param.h> +#include <sys/stat.h> + #include <errno.h> #include <fcntl.h> #include <glob.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -102,10 +105,80 @@ ATF_TC_BODY(glob_pathological_test, tc) } } +ATF_TC(glob_period); +ATF_TC_HEAD(glob_period, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test behaviour when matching files that start with a period" + "(documented in the glob(3) CAVEATS section)."); +} +ATF_TC_BODY(glob_period, tc) +{ + int i; + glob_t g; + + atf_utils_create_file(".test", ""); + glob(".", 0, NULL, &g); + ATF_REQUIRE_MSG(g.gl_matchc == 1, + "glob(3) shouldn't match files starting with a period when using '.'"); + for (i = 0; i < g.gl_matchc; i++) + printf("%s\n", g.gl_pathv[i]); + glob(".*", 0, NULL, &g); + ATF_REQUIRE_MSG(g.gl_matchc == 3 && strcmp(g.gl_pathv[2], ".test") == 0, + "glob(3) should match files starting with a period when using '.*'"); +} + +static bool glob_callback_invoked; + +static int +errfunc(const char *path, int err) +{ + ATF_CHECK_STREQ(path, "test/"); + ATF_CHECK(err == EACCES); + glob_callback_invoked = true; + /* Suppress EACCES errors. */ + return (0); +} + +ATF_TC(glob_callback); +ATF_TC_HEAD(glob_callback, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test ability of callback function to suppress errors"); + atf_tc_set_md_var(tc, "require.user", "unprivileged"); +} +ATF_TC_BODY(glob_callback, tc) +{ + glob_t g; + int rv; + + ATF_REQUIRE_EQ(0, mkdir("test", 0755)); + ATF_REQUIRE_EQ(0, symlink("foo", "test/foo")); + ATF_REQUIRE_EQ(0, chmod("test", 0)); + + glob_callback_invoked = false; + rv = glob("test/*", 0, errfunc, &g); + ATF_CHECK_MSG(glob_callback_invoked, + "glob(3) failed to invoke callback function"); + ATF_CHECK_EQ_MSG(GLOB_NOMATCH, rv, + "callback function failed to suppress EACCES"); + globfree(&g); + + /* GLOB_ERR should ignore the suppressed error. */ + glob_callback_invoked = false; + rv = glob("test/*", GLOB_ERR, errfunc, &g); + ATF_CHECK_MSG(glob_callback_invoked, + "glob(3) failed to invoke callback function"); + ATF_CHECK_EQ_MSG(GLOB_ABORTED, rv, + "GLOB_ERR didn't override callback function"); + globfree(&g); +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, glob_pathological_test); - + ATF_TP_ADD_TC(tp, glob_period); + ATF_TP_ADD_TC(tp, glob_callback); return (atf_no_error()); } diff --git a/lib/libc/tests/gen/glob_blocks_test.c b/lib/libc/tests/gen/glob_blocks_test.c new file mode 100644 index 000000000000..629b90bee762 --- /dev/null +++ b/lib/libc/tests/gen/glob_blocks_test.c @@ -0,0 +1,62 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/stat.h> + +#include <errno.h> +#include <glob.h> +#include <stdbool.h> + +#include <atf-c.h> + +ATF_TC(glob_b_callback); +ATF_TC_HEAD(glob_b_callback, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test ability of callback block to suppress errors"); + atf_tc_set_md_var(tc, "require.user", "unprivileged"); +} +ATF_TC_BODY(glob_b_callback, tc) +{ + static bool glob_callback_invoked; + static int (^errblk)(const char *, int) = + ^(const char *path, int err) { + ATF_CHECK_STREQ(path, "test/"); + ATF_CHECK(err == EACCES); + glob_callback_invoked = true; + /* Suppress EACCES errors. */ + return (0); + }; + glob_t g; + int rv; + + ATF_REQUIRE_EQ(0, mkdir("test", 0755)); + ATF_REQUIRE_EQ(0, symlink("foo", "test/foo")); + ATF_REQUIRE_EQ(0, chmod("test", 0)); + + glob_callback_invoked = false; + rv = glob_b("test/*", 0, errblk, &g); + ATF_CHECK_MSG(glob_callback_invoked, + "glob(3) failed to invoke callback block"); + ATF_CHECK_EQ_MSG(GLOB_NOMATCH, rv, + "callback function failed to suppress EACCES"); + globfree(&g); + + /* GLOB_ERR should ignore the suppressed error. */ + glob_callback_invoked = false; + rv = glob_b("test/*", GLOB_ERR, errblk, &g); + ATF_CHECK_MSG(glob_callback_invoked, + "glob(3) failed to invoke callback block"); + ATF_CHECK_EQ_MSG(GLOB_ABORTED, rv, + "GLOB_ERR didn't override callback block"); + globfree(&g); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, glob_b_callback); + return (atf_no_error()); +} diff --git a/lib/libc/tests/gen/opendir_test.c b/lib/libc/tests/gen/opendir_test.c new file mode 100644 index 000000000000..b7481255654f --- /dev/null +++ b/lib/libc/tests/gen/opendir_test.c @@ -0,0 +1,145 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/stat.h> + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> + +#include <atf-c.h> + +/* + * Create a directory with a single subdirectory. + */ +static void +opendir_prepare(const struct atf_tc *tc) +{ + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + ATF_REQUIRE_EQ(0, mkdir("dir/subdir", 0755)); +} + +/* + * Assuming dirp represents the directory created by opendir_prepare(), + * verify that readdir() returns what we expected to see there. + */ +static void +opendir_check(const struct atf_tc *tc, DIR *dirp) +{ + struct dirent *ent; + + ATF_REQUIRE((ent = readdir(dirp)) != NULL); + ATF_CHECK_EQ(1, ent->d_namlen); + ATF_CHECK_STREQ(".", ent->d_name); + ATF_CHECK_EQ(DT_DIR, ent->d_type); + ATF_REQUIRE((ent = readdir(dirp)) != NULL); + ATF_CHECK_EQ(2, ent->d_namlen); + ATF_CHECK_STREQ("..", ent->d_name); + ATF_CHECK_EQ(DT_DIR, ent->d_type); + ATF_REQUIRE((ent = readdir(dirp)) != NULL); + ATF_CHECK_EQ(sizeof("subdir") - 1, ent->d_namlen); + ATF_CHECK_STREQ("subdir", ent->d_name); + ATF_CHECK_EQ(DT_DIR, ent->d_type); + ATF_CHECK(readdir(dirp) == NULL); + ATF_CHECK(readdir(dirp) == NULL); +} + +ATF_TC(opendir_ok); +ATF_TC_HEAD(opendir_ok, tc) +{ + atf_tc_set_md_var(tc, "descr", "Open a directory."); +} +ATF_TC_BODY(opendir_ok, tc) +{ + DIR *dirp; + + opendir_prepare(tc); + ATF_REQUIRE((dirp = opendir("dir")) != NULL); + opendir_check(tc, dirp); + ATF_CHECK_EQ(0, closedir(dirp)); +} + +ATF_TC(opendir_fifo); +ATF_TC_HEAD(opendir_fifo, tc) +{ + atf_tc_set_md_var(tc, "descr", "Do not hang if given a named pipe."); +} +ATF_TC_BODY(opendir_fifo, tc) +{ + DIR *dirp; + int fd; + + ATF_REQUIRE((fd = mkfifo("fifo", 0644)) >= 0); + ATF_REQUIRE_EQ(0, close(fd)); + ATF_REQUIRE((dirp = opendir("fifo")) == NULL); + ATF_CHECK_EQ(ENOTDIR, errno); +} + +ATF_TC(fdopendir_ok); +ATF_TC_HEAD(fdopendir_ok, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Open a directory from a directory descriptor."); +} +ATF_TC_BODY(fdopendir_ok, tc) +{ + DIR *dirp; + int dd; + + opendir_prepare(tc); + ATF_REQUIRE((dd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0); + ATF_REQUIRE((dirp = fdopendir(dd)) != NULL); + opendir_check(tc, dirp); + ATF_CHECK_EQ(dd, fdclosedir(dirp)); + ATF_CHECK_EQ(0, close(dd)); +} + +ATF_TC(fdopendir_ebadf); +ATF_TC_HEAD(fdopendir_ebadf, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Open a directory from an invalid descriptor."); +} +ATF_TC_BODY(fdopendir_ebadf, tc) +{ + DIR *dirp; + int dd; + + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + ATF_REQUIRE((dd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0); + ATF_CHECK_EQ(0, close(dd)); + ATF_REQUIRE((dirp = fdopendir(dd)) == NULL); + ATF_CHECK_EQ(EBADF, errno); +} + +ATF_TC(fdopendir_enotdir); +ATF_TC_HEAD(fdopendir_enotdir, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Open a directory from a non-directory descriptor."); +} +ATF_TC_BODY(fdopendir_enotdir, tc) +{ + DIR *dirp; + int fd; + + ATF_REQUIRE((fd = open("file", O_CREAT | O_RDWR, 0644)) >= 0); + ATF_REQUIRE((dirp = fdopendir(fd)) == NULL); + ATF_CHECK_EQ(ENOTDIR, errno); + ATF_CHECK_EQ(0, close(fd)); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, opendir_ok); + ATF_TP_ADD_TC(tp, fdopendir_ok); + ATF_TP_ADD_TC(tp, fdopendir_ebadf); + ATF_TP_ADD_TC(tp, fdopendir_enotdir); + ATF_TP_ADD_TC(tp, opendir_fifo); + return (atf_no_error()); +} diff --git a/lib/libc/tests/gen/posix_spawn_test.c b/lib/libc/tests/gen/posix_spawn_test.c index 77c3b5a569d9..22133cf1d59a 100644 --- a/lib/libc/tests/gen/posix_spawn_test.c +++ b/lib/libc/tests/gen/posix_spawn_test.c @@ -29,8 +29,11 @@ * IEEE Std. 1003.1-2008. */ +#include <sys/param.h> +#include <sys/stat.h> #include <sys/wait.h> #include <errno.h> +#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -38,6 +41,10 @@ #include <atf-c.h> +static const char true_script[] = + "#!/usr/bin/env\n" + "/usr/bin/true\n"; + char *myenv[2] = { "answer=42", NULL }; ATF_TC_WITHOUT_HEAD(posix_spawn_simple_test); @@ -124,6 +131,48 @@ ATF_TC_BODY(posix_spawnp_enoexec_fallback_null_argv0, tc) ATF_REQUIRE(error == EINVAL); } +ATF_TC(posix_spawnp_eacces); +ATF_TC_HEAD(posix_spawnp_eacces, tc) +{ + atf_tc_set_md_var(tc, "descr", "Verify EACCES behavior in posix_spawnp"); + atf_tc_set_md_var(tc, "require.user", "unprivileged"); +} +ATF_TC_BODY(posix_spawnp_eacces, tc) +{ + const struct spawnp_eacces_tc { + const char *pathvar; + int error_expected; + } spawnp_eacces_tests[] = { + { ".", EACCES }, /* File exists, but not +x */ + { "unsearchable", ENOENT }, /* File exists, dir not +x */ + }; + char *myargs[2] = { "eacces", NULL }; + int error; + + error = mkdir("unsearchable", 0755); + ATF_REQUIRE(error == 0); + error = symlink("/usr/bin/true", "unsearchable/eacces"); + ATF_REQUIRE(error == 0); + + (void)chmod("unsearchable", 0444); + + /* this will create a non-executable file */ + atf_utils_create_file("eacces", true_script); + + for (size_t i = 0; i < nitems(spawnp_eacces_tests); i++) { + const struct spawnp_eacces_tc *tc = &spawnp_eacces_tests[i]; + pid_t pid; + + error = setenv("PATH", tc->pathvar, 1); + ATF_REQUIRE_EQ(0, error); + + error = posix_spawnp(&pid, myargs[0], NULL, NULL, myargs, + myenv); + ATF_CHECK_INTEQ_MSG(tc->error_expected, error, + "path '%s'", tc->pathvar); + } +} + ATF_TP_ADD_TCS(tp) { @@ -131,6 +180,7 @@ ATF_TP_ADD_TCS(tp) ATF_TP_ADD_TC(tp, posix_spawn_no_such_command_negative_test); ATF_TP_ADD_TC(tp, posix_spawnp_enoexec_fallback); ATF_TP_ADD_TC(tp, posix_spawnp_enoexec_fallback_null_argv0); + ATF_TP_ADD_TC(tp, posix_spawnp_eacces); return (atf_no_error()); } diff --git a/lib/libc/tests/gen/scandir_blocks_test.c b/lib/libc/tests/gen/scandir_blocks_test.c new file mode 100644 index 000000000000..b94270bc410e --- /dev/null +++ b/lib/libc/tests/gen/scandir_blocks_test.c @@ -0,0 +1,118 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/stat.h> + +#include <dirent.h> +#include <fcntl.h> +#include <stdlib.h> + +#include <atf-c.h> + +static void +scandir_blocks_prepare(const struct atf_tc *tc) +{ + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + ATF_REQUIRE_EQ(0, mkdir("dir/dir", 0755)); + ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644))); + ATF_REQUIRE_EQ(0, symlink("file", "dir/link")); + ATF_REQUIRE_EQ(0, mkdir("dir/skip", 0755)); +} + +static void +scandir_blocks_verify(const struct atf_tc *tc, int n, struct dirent **namelist) +{ + ATF_REQUIRE_EQ_MSG(5, n, "return value is %d", n); + ATF_CHECK_STREQ("link", namelist[0]->d_name); + ATF_CHECK_STREQ("file", namelist[1]->d_name); + ATF_CHECK_STREQ("dir", namelist[2]->d_name); + ATF_CHECK_STREQ("..", namelist[3]->d_name); + ATF_CHECK_STREQ(".", namelist[4]->d_name); +} + +ATF_TC(scandir_b_test); +ATF_TC_HEAD(scandir_b_test, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test scandir_b()"); +} +ATF_TC_BODY(scandir_b_test, tc) +{ + struct dirent **namelist = NULL; + int i, ret; + + scandir_blocks_prepare(tc); + ret = scandir_b("dir", &namelist, + ^(const struct dirent *ent) { + return (strcmp(ent->d_name, "skip") != 0); + }, + ^(const struct dirent **a, const struct dirent **b) { + return (strcmp((*b)->d_name, (*a)->d_name)); + }); + scandir_blocks_verify(tc, ret, namelist); + for (i = 0; i < ret; i++) + free(namelist[i]); + free(namelist); +} + +ATF_TC(fdscandir_b_test); +ATF_TC_HEAD(fdscandir_b_test, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test fdscandir_b()"); +} +ATF_TC_BODY(fdscandir_b_test, tc) +{ + struct dirent **namelist = NULL; + int fd, i, ret; + + scandir_blocks_prepare(tc); + ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0); + ret = fdscandir_b(fd, &namelist, + ^(const struct dirent *ent) { + return (strcmp(ent->d_name, "skip") != 0); + }, + ^(const struct dirent **a, const struct dirent **b) { + return (strcmp((*b)->d_name, (*a)->d_name)); + }); + scandir_blocks_verify(tc, ret, namelist); + for (i = 0; i < ret; i++) + free(namelist[i]); + free(namelist); + ATF_REQUIRE_EQ(0, close(fd)); +} + +ATF_TC(scandirat_b_test); +ATF_TC_HEAD(scandirat_b_test, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test scandirat_b()"); +} +ATF_TC_BODY(scandirat_b_test, tc) +{ + struct dirent **namelist = NULL; + int fd, i, ret; + + scandir_blocks_prepare(tc); + ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_SEARCH)) >= 0); + ret = scandirat_b(fd, ".", &namelist, + ^(const struct dirent *ent) { + return (strcmp(ent->d_name, "skip") != 0); + }, + ^(const struct dirent **a, const struct dirent **b) { + return (strcmp((*b)->d_name, (*a)->d_name)); + }); + scandir_blocks_verify(tc, ret, namelist); + for (i = 0; i < ret; i++) + free(namelist[i]); + free(namelist); + ATF_REQUIRE_EQ(0, close(fd)); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, scandir_b_test); + ATF_TP_ADD_TC(tp, fdscandir_b_test); + ATF_TP_ADD_TC(tp, scandirat_b_test); + return (atf_no_error()); +} diff --git a/lib/libc/tests/gen/scandir_test.c b/lib/libc/tests/gen/scandir_test.c new file mode 100644 index 000000000000..afd25bf7c0b2 --- /dev/null +++ b/lib/libc/tests/gen/scandir_test.c @@ -0,0 +1,195 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/stat.h> + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> + +#include <atf-c.h> + +static void +scandir_prepare(const struct atf_tc *tc) +{ + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + ATF_REQUIRE_EQ(0, mkdir("dir/dir", 0755)); + ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644))); + ATF_REQUIRE_EQ(0, symlink("file", "dir/link")); + ATF_REQUIRE_EQ(0, mkdir("dir/skip", 0755)); +} + +static void +scandir_verify(const struct atf_tc *tc, int n, struct dirent **namelist) +{ + ATF_REQUIRE_EQ_MSG(5, n, "return value is %d", n); + ATF_CHECK_STREQ("link", namelist[0]->d_name); + ATF_CHECK_STREQ("file", namelist[1]->d_name); + ATF_CHECK_STREQ("dir", namelist[2]->d_name); + ATF_CHECK_STREQ("..", namelist[3]->d_name); + ATF_CHECK_STREQ(".", namelist[4]->d_name); +} + +static int +scandir_select(const struct dirent *ent) +{ + return (strcmp(ent->d_name, "skip") != 0); +} + +static int +scandir_compare(const struct dirent **a, const struct dirent **b) +{ + return (strcmp((*b)->d_name, (*a)->d_name)); +} + +ATF_TC(scandir_test); +ATF_TC_HEAD(scandir_test, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test scandir()"); +} +ATF_TC_BODY(scandir_test, tc) +{ + struct dirent **namelist = NULL; + int i, ret; + + scandir_prepare(tc); + ret = scandir("dir", &namelist, scandir_select, scandir_compare); + scandir_verify(tc, ret, namelist); + for (i = 0; i < ret; i++) + free(namelist[i]); + free(namelist); +} + +ATF_TC(fdscandir_test); +ATF_TC_HEAD(fdscandir_test, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test fdscandir()"); +} +ATF_TC_BODY(fdscandir_test, tc) +{ + struct dirent **namelist = NULL; + int fd, i, ret; + + scandir_prepare(tc); + ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0); + ret = fdscandir(fd, &namelist, scandir_select, scandir_compare); + scandir_verify(tc, ret, namelist); + for (i = 0; i < ret; i++) + free(namelist[i]); + free(namelist); + ATF_REQUIRE_EQ(0, close(fd)); +} + +ATF_TC(scandirat_test); +ATF_TC_HEAD(scandirat_test, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test scandirat()"); +} +ATF_TC_BODY(scandirat_test, tc) +{ + struct dirent **namelist = NULL; + int fd, i, ret; + + scandir_prepare(tc); + ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_SEARCH)) >= 0); + ret = scandirat(fd, ".", &namelist, scandir_select, scandir_compare); + scandir_verify(tc, ret, namelist); + for (i = 0; i < ret; i++) + free(namelist[i]); + free(namelist); + ATF_REQUIRE_EQ(0, close(fd)); +} + +static int +scandir_none(const struct dirent *ent __unused) +{ + return (0); +} + +ATF_TC(scandir_none); +ATF_TC_HEAD(scandir_none, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test scandir() when no entries are selected"); +} +ATF_TC_BODY(scandir_none, tc) +{ + struct dirent **namelist = NULL; + + ATF_REQUIRE_EQ(0, scandir(".", &namelist, scandir_none, alphasort)); + ATF_REQUIRE(namelist); + free(namelist); +} + +/* + * Test that scandir() propagates errors from readdir(): we create a + * directory with enough entries that it can't be read in a single + * getdirentries() call, then abuse the selection callback to close the + * file descriptor scandir() is using after the first call, causing the + * next one to fail, and verify that readdir() returns an error instead of + * a partial result. We make two passes, one in which nothing was + * selected before the error occurred, and one in which everything was. + */ +static int scandir_error_count; +static int scandir_error_fd; +static int scandir_error_select_return; + +static int +scandir_error_select(const struct dirent *ent __unused) +{ + if (scandir_error_count++ == 0) + close(scandir_error_fd); + return (scandir_error_select_return); +} + +ATF_TC(scandir_error); +ATF_TC_HEAD(scandir_error, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test that scandir() propagates errors from readdir()"); +} +ATF_TC_BODY(scandir_error, tc) +{ + char path[16]; + struct dirent **namelist = NULL; + int fd, i; + + ATF_REQUIRE_EQ(0, mkdir("dir", 0755)); + for (i = 0; i < 1024; i++) { + snprintf(path, sizeof(path), "dir/%04x", i); + ATF_REQUIRE_EQ(0, symlink(path + 4, path)); + } + + /* first pass, select nothing */ + ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0); + scandir_error_count = 0; + scandir_error_fd = fd; + scandir_error_select_return = 0; + ATF_CHECK_ERRNO(EBADF, + fdscandir(fd, &namelist, scandir_error_select, NULL) < 0); + ATF_CHECK_EQ(NULL, namelist); + + /* second pass, select everything */ + ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0); + scandir_error_count = 0; + scandir_error_fd = fd; + scandir_error_select_return = 1; + ATF_CHECK_ERRNO(EBADF, + fdscandir(fd, &namelist, scandir_error_select, NULL) < 0); + ATF_CHECK_EQ(NULL, namelist); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, scandir_test); + ATF_TP_ADD_TC(tp, fdscandir_test); + ATF_TP_ADD_TC(tp, scandirat_test); + ATF_TP_ADD_TC(tp, scandir_none); + ATF_TP_ADD_TC(tp, scandir_error); + return (atf_no_error()); +} diff --git a/lib/libc/tests/gen/sig2str_test.c b/lib/libc/tests/gen/sig2str_test.c new file mode 100644 index 000000000000..00b6ebb2349a --- /dev/null +++ b/lib/libc/tests/gen/sig2str_test.c @@ -0,0 +1,213 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 Ricardo Branco <rbranco@suse.de> + * 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 <ctype.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> + +#include <atf-c.h> + +static void +test_roundtrip(int signum) +{ + char str[SIG2STR_MAX]; + int sig; + + ATF_REQUIRE_EQ_MSG(sig2str(signum, str), 0, + "sig2str(%d) failed", signum); + ATF_REQUIRE_EQ_MSG(str2sig(str, &sig), 0, + "str2sig(\"%s\") failed", str); + ATF_REQUIRE_INTEQ_MSG(sig, signum, + "Mismatch: roundtrip conversion gave %d instead of %d", + sig, signum); +} + +ATF_TC_WITHOUT_HEAD(sig2str_valid); +ATF_TC_BODY(sig2str_valid, tc) +{ + int sig; + + for (sig = 1; sig < sys_nsig; sig++) { + test_roundtrip(sig); + } +} + +ATF_TC_WITHOUT_HEAD(sig2str_invalid); +ATF_TC_BODY(sig2str_invalid, tc) +{ + char buf[SIG2STR_MAX]; + + ATF_CHECK(sig2str(0, buf) != 0); + ATF_CHECK(sig2str(-1, buf) != 0); + ATF_CHECK(sig2str(SIGRTMAX + 1, buf) != 0); +} + +ATF_TC_WITHOUT_HEAD(str2sig_rtmin_rtmax); +ATF_TC_BODY(str2sig_rtmin_rtmax, tc) +{ + int sig; + + ATF_CHECK_MSG(str2sig("RTMIN", &sig) == 0, + "str2sig(\"RTMIN\") failed"); + ATF_CHECK_INTEQ_MSG(sig, SIGRTMIN, + "RTMIN mapped to %d, expected %d", sig, SIGRTMIN); + + ATF_CHECK_MSG(str2sig("RTMAX", &sig) == 0, + "str2sig(\"RTMAX\") failed"); + ATF_CHECK_INTEQ_MSG(sig, SIGRTMAX, + "RTMAX mapped to %d, expected %d", sig, SIGRTMAX); + + ATF_CHECK_MSG(str2sig("RTMIN+1", &sig) == 0, + "str2sig(\"RTMIN+1\") failed"); + ATF_CHECK_INTEQ_MSG(sig, SIGRTMIN + 1, + "RTMIN+1 mapped to %d, expected %d", sig, SIGRTMIN + 1); + + ATF_CHECK_MSG(str2sig("RTMAX-1", &sig) == 0, + "str2sig(\"RTMAX-1\") failed"); + ATF_CHECK_INTEQ_MSG(sig, SIGRTMAX - 1, + "RTMAX-1 mapped to %d, expected %d", sig, SIGRTMAX - 1); +} + +ATF_TC_WITHOUT_HEAD(str2sig_invalid_rt); +ATF_TC_BODY(str2sig_invalid_rt, tc) +{ + int i, sig; + + const char *invalid[] = { + "RTMIN+0", + "RTMAX-0", + "RTMIN-777", + "RTMIN+777", + "RTMAX-777", + "RTMAX+777", + "RTMIN-", + "RTMAX-", + "RTMIN0", + "RTMAX1", + "RTMIN+abc", + "RTMIN-abc", + NULL + }; + + for (i = 0; invalid[i] != NULL; i++) { + ATF_CHECK_MSG(str2sig(invalid[i], &sig) != 0, + "str2sig(\"%s\") unexpectedly succeeded", invalid[i]); + } +} + +ATF_TC_WITHOUT_HEAD(str2sig_fullname); +ATF_TC_BODY(str2sig_fullname, tc) +{ + char fullname[SIG2STR_MAX + 3]; + int n, sig; + + for (sig = 1; sig < sys_nsig; sig++) { + snprintf(fullname, sizeof(fullname), "SIG%s", sys_signame[sig]); + + ATF_CHECK_MSG(str2sig(fullname, &n) == 0, + "str2sig(\"%s\") failed with errno %d (%s)", + fullname, errno, strerror(errno)); + + ATF_CHECK_INTEQ_MSG(n, sig, + "Mismatch: %s = %d, str2sig(\"%s\") = %d", + sys_signame[sig], sig, fullname, n); + } +} + +ATF_TC_WITHOUT_HEAD(str2sig_lowercase); +ATF_TC_BODY(str2sig_lowercase, tc) +{ + char fullname[SIG2STR_MAX + 3]; + int n, sig; + + for (sig = 1; sig < sys_nsig; sig++) { + snprintf(fullname, sizeof(fullname), "sig%s", sys_signame[sig]); + for (size_t i = 3; i < strlen(fullname); i++) + fullname[i] = toupper((unsigned char)fullname[i]); + + ATF_CHECK_MSG(str2sig(fullname, &n) == 0, + "str2sig(\"%s\") failed with errno %d (%s)", + fullname, errno, strerror(errno)); + + ATF_CHECK_INTEQ_MSG(n, sig, + "Mismatch: %s = %d, str2sig(\"%s\") = %d", + sys_signame[sig], sig, fullname, n); + } +} + +ATF_TC_WITHOUT_HEAD(str2sig_numeric); +ATF_TC_BODY(str2sig_numeric, tc) +{ + char buf[16]; + int n, sig; + + for (sig = NSIG; sig < SIGRTMIN; sig++) { + snprintf(buf, sizeof(buf), "%d", sig); + ATF_CHECK_MSG(str2sig(buf, &n) == 0, + "str2sig(\"%s\") failed", buf); + ATF_CHECK_INTEQ_MSG(n, sig, + "Mismatch: str2sig(\"%s\") = %d, expected %d", + buf, n, sig); + } +} + +ATF_TC_WITHOUT_HEAD(str2sig_invalid); +ATF_TC_BODY(str2sig_invalid, tc) +{ + const char *invalid[] = { + "SIGDOESNOTEXIST", + "DOESNOTEXIST", + "INTERRUPT", + "", + "SIG", + "123abc", + "sig1extra", + NULL + }; + int i, sig; + + for (i = 0; invalid[i] != NULL; i++) { + ATF_CHECK_MSG(str2sig(invalid[i], &sig) != 0, + "str2sig(\"%s\") unexpectedly succeeded", invalid[i]); + } +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, sig2str_valid); + ATF_TP_ADD_TC(tp, sig2str_invalid); + ATF_TP_ADD_TC(tp, str2sig_rtmin_rtmax); + ATF_TP_ADD_TC(tp, str2sig_invalid_rt); + ATF_TP_ADD_TC(tp, str2sig_fullname); + ATF_TP_ADD_TC(tp, str2sig_lowercase); + ATF_TP_ADD_TC(tp, str2sig_numeric); + ATF_TP_ADD_TC(tp, str2sig_invalid); + return (atf_no_error()); +} diff --git a/lib/libc/tests/gen/wordexp_test.c b/lib/libc/tests/gen/wordexp_test.c index 909097fbf51e..a8b9d5509633 100644 --- a/lib/libc/tests/gen/wordexp_test.c +++ b/lib/libc/tests/gen/wordexp_test.c @@ -292,6 +292,31 @@ ATF_TC_BODY(with_SIGCHILD_handler_test, tc) ATF_REQUIRE(r == 0); } +ATF_TC_WITHOUT_HEAD(with_SIGCHILD_ignore_test); +ATF_TC_BODY(with_SIGCHILD_ignore_test, tc) +{ + struct sigaction sa; + wordexp_t we; + int r; + + /* With SIGCHLD set to ignore so that the kernel auto-reaps zombies. */ + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + r = sigaction(SIGCHLD, &sa, NULL); + ATF_REQUIRE(r == 0); + r = wordexp("hello world", &we, 0); + ATF_REQUIRE(r == 0); + ATF_REQUIRE(we.we_wordc == 2); + ATF_REQUIRE(strcmp(we.we_wordv[0], "hello") == 0); + ATF_REQUIRE(strcmp(we.we_wordv[1], "world") == 0); + ATF_REQUIRE(we.we_wordv[2] == NULL); + wordfree(&we); + sa.sa_handler = SIG_DFL; + r = sigaction(SIGCHLD, &sa, NULL); + ATF_REQUIRE(r == 0); +} + ATF_TC_WITHOUT_HEAD(with_unused_non_default_IFS_test); ATF_TC_BODY(with_unused_non_default_IFS_test, tc) { @@ -350,6 +375,7 @@ ATF_TP_ADD_TCS(tp) ATF_TP_ADD_TC(tp, WRDE_BADCHAR_test); ATF_TP_ADD_TC(tp, WRDE_SYNTAX_test); ATF_TP_ADD_TC(tp, with_SIGCHILD_handler_test); + ATF_TP_ADD_TC(tp, with_SIGCHILD_ignore_test); ATF_TP_ADD_TC(tp, with_unused_non_default_IFS_test); ATF_TP_ADD_TC(tp, with_used_non_default_IFS_test); diff --git a/lib/libc/tests/locale/wctomb_test.c b/lib/libc/tests/locale/wctomb_test.c index 1e142ed74c48..ef2a6dcbe1e3 100644 --- a/lib/libc/tests/locale/wctomb_test.c +++ b/lib/libc/tests/locale/wctomb_test.c @@ -41,6 +41,18 @@ #include <atf-c.h> +ATF_TC_WITHOUT_HEAD(euccs1_test); +ATF_TC_BODY(euccs1_test, tc) +{ + wchar_t wc = 0x8e000000; + char buf[MB_LEN_MAX]; + + ATF_REQUIRE(strcmp(setlocale(LC_CTYPE, "zh_CN.eucCN"), + "zh_CN.eucCN") == 0); + + ATF_REQUIRE(wctomb(&buf[0], wc) == 4); +} + ATF_TC_WITHOUT_HEAD(wctomb_test); ATF_TC_BODY(wctomb_test, tc) { @@ -104,6 +116,7 @@ ATF_TC_BODY(wctomb_test, tc) ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, euccs1_test); ATF_TP_ADD_TC(tp, wctomb_test); return (atf_no_error()); diff --git a/lib/libc/tests/net/Makefile b/lib/libc/tests/net/Makefile index 016f0cd82ceb..24cff61e8d24 100644 --- a/lib/libc/tests/net/Makefile +++ b/lib/libc/tests/net/Makefile @@ -3,6 +3,9 @@ PACKAGE= tests ATF_TESTS_C+= ether_test ATF_TESTS_C+= eui64_aton_test ATF_TESTS_C+= eui64_ntoa_test +ATF_TESTS_CXX+= link_addr_test + +CXXSTD.link_addr_test= c++20 CFLAGS+= -I${.CURDIR} diff --git a/lib/libc/tests/net/getaddrinfo/Makefile b/lib/libc/tests/net/getaddrinfo/Makefile index 1c8d68d8cc25..1299e91615b7 100644 --- a/lib/libc/tests/net/getaddrinfo/Makefile +++ b/lib/libc/tests/net/getaddrinfo/Makefile @@ -1,12 +1,10 @@ PACKAGE= tests -TESTSRC= ${SRCTOP}/contrib/netbsd-tests/lib/libc/net/${.CURDIR:T} - .include <bsd.own.mk> BINDIR= ${TESTSDIR} -NETBSD_ATF_TESTS_SH= getaddrinfo_test +ATF_TESTS_SH= getaddrinfo_test ATF_TESTS_C= getaddrinfo PROGS= h_gai @@ -16,13 +14,33 @@ ${PACKAGE}DATA_FILESPACKAGE= tests ${PACKAGE}DATA_FILESDIR= ${TESTSDIR}/data -${PACKAGE}DATA_FILES+= basics_v4.exp basics_v4v6.exp -${PACKAGE}DATA_FILES+= no_host_v4.exp no_host_v4v6.exp -${PACKAGE}DATA_FILES+= no_serv_v4.exp no_serv_v4v6.exp -${PACKAGE}DATA_FILES+= sock_raw_v4.exp sock_raw_v4v6.exp -${PACKAGE}DATA_FILES+= spec_fam_v4.exp spec_fam_v4v6.exp -${PACKAGE}DATA_FILES+= scoped.exp -${PACKAGE}DATA_FILES+= unsup_fam.exp +${PACKAGE}DATA_FILES+= data/basics_v4.exp +${PACKAGE}DATA_FILES+= data/basics_v4_only.exp +${PACKAGE}DATA_FILES+= data/basics_v4v6.exp +${PACKAGE}DATA_FILES+= data/basics_v4v6_prefer_v4.exp +${PACKAGE}DATA_FILES+= data/no_host_v4.exp +${PACKAGE}DATA_FILES+= data/no_host_v4_only.exp +${PACKAGE}DATA_FILES+= data/no_host_v4v6.exp +${PACKAGE}DATA_FILES+= data/no_host_v4v6_prefer_v4.exp +${PACKAGE}DATA_FILES+= data/no_serv_v4.exp +${PACKAGE}DATA_FILES+= data/no_serv_v4_only.exp +${PACKAGE}DATA_FILES+= data/no_serv_v4v6.exp +${PACKAGE}DATA_FILES+= data/no_serv_v4v6_prefer_v4.exp +${PACKAGE}DATA_FILES+= data/scoped.exp +${PACKAGE}DATA_FILES+= data/scoped_v4_only.exp +${PACKAGE}DATA_FILES+= data/scoped_v4v6_prefer_v4.exp +${PACKAGE}DATA_FILES+= data/sock_raw_v4.exp +${PACKAGE}DATA_FILES+= data/sock_raw_v4_only.exp +${PACKAGE}DATA_FILES+= data/sock_raw_v4v6.exp +${PACKAGE}DATA_FILES+= data/sock_raw_v4v6_prefer_v4.exp +${PACKAGE}DATA_FILES+= data/spec_fam_v4.exp +${PACKAGE}DATA_FILES+= data/spec_fam_v4_only.exp +${PACKAGE}DATA_FILES+= data/spec_fam_v4v6.exp +${PACKAGE}DATA_FILES+= data/spec_fam_v4v6_prefer_v4.exp +${PACKAGE}DATA_FILES+= data/unsup_fam.exp +${PACKAGE}DATA_FILES+= data/unsup_fam_v4_only.exp +${PACKAGE}DATA_FILES+= data/unsup_fam_v4v6_prefer_v4.exp + .include "../../Makefile.netbsd-tests" diff --git a/lib/libc/tests/net/getaddrinfo/basics_v4.exp b/lib/libc/tests/net/getaddrinfo/data/basics_v4.exp index 4f1ee3517211..4f1ee3517211 100644 --- a/lib/libc/tests/net/getaddrinfo/basics_v4.exp +++ b/lib/libc/tests/net/getaddrinfo/data/basics_v4.exp diff --git a/lib/libc/tests/net/getaddrinfo/data/basics_v4_only.exp b/lib/libc/tests/net/getaddrinfo/data/basics_v4_only.exp new file mode 100644 index 000000000000..0a37d3212649 --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/basics_v4_only.exp @@ -0,0 +1,50 @@ +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv http +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv http +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http +ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv http +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http +ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http +ai4: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http +ai5: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http +ai6: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv tftp +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv tftp +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv tftp + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv tftp +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv tftp +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv tftp + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv tftp +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv tftp +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv tftp +ai3: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv tftp +ai4: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv tftp + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv echo +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv echo +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv echo +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv echo + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv echo +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv echo +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv echo +ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv echo + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv echo +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv echo +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv echo +ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv echo +ai4: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv echo +ai5: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv echo +ai6: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv echo + diff --git a/lib/libc/tests/net/getaddrinfo/basics_v4v6.exp b/lib/libc/tests/net/getaddrinfo/data/basics_v4v6.exp index b74758e93e2e..b74758e93e2e 100644 --- a/lib/libc/tests/net/getaddrinfo/basics_v4v6.exp +++ b/lib/libc/tests/net/getaddrinfo/data/basics_v4v6.exp diff --git a/lib/libc/tests/net/getaddrinfo/data/basics_v4v6_prefer_v4.exp b/lib/libc/tests/net/getaddrinfo/data/basics_v4v6_prefer_v4.exp new file mode 100644 index 000000000000..0a37d3212649 --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/basics_v4v6_prefer_v4.exp @@ -0,0 +1,50 @@ +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv http +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv http +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http +ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv http +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http +ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http +ai4: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http +ai5: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http +ai6: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv tftp +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv tftp +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv tftp + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv tftp +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv tftp +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv tftp + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv tftp +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv tftp +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv tftp +ai3: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv tftp +ai4: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv tftp + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv echo +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv echo +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv echo +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv echo + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv echo +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv echo +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv echo +ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv echo + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv echo +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv echo +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv echo +ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv echo +ai4: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv echo +ai5: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv echo +ai6: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv echo + diff --git a/lib/libc/tests/net/getaddrinfo/data/generate_testdata.sh b/lib/libc/tests/net/getaddrinfo/data/generate_testdata.sh new file mode 100755 index 000000000000..f0425a3b0283 --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/generate_testdata.sh @@ -0,0 +1,45 @@ +#service ip6addrctl prefer_ipv4 +TEST=./h_gai +family=v4_only + +( $TEST ::1 http + $TEST 127.0.0.1 http + $TEST localhost http + $TEST ::1 tftp + $TEST 127.0.0.1 tftp + $TEST localhost tftp + $TEST ::1 echo + $TEST 127.0.0.1 echo + $TEST localhost echo ) > basics_${family}.exp + +( $TEST -4 localhost http + $TEST -6 localhost http ) > spec_fam_${family}.exp + +( $TEST '' http + $TEST '' echo + $TEST '' tftp + $TEST '' 80 + $TEST -P '' http + $TEST -P '' echo + $TEST -P '' tftp + $TEST -P '' 80 + $TEST -S '' 80 + $TEST -D '' 80 ) > no_host_${family}.exp + +( $TEST ::1 '' + $TEST 127.0.0.1 '' + $TEST localhost '' + $TEST '' '' ) > no_serv_${family}.exp + +( $TEST -R -p 0 localhost '' + $TEST -R -p 59 localhost '' + $TEST -R -p 59 localhost 80 + $TEST -R -p 59 localhost www + $TEST -R -p 59 ::1 '' ) > sock_raw_${family}.exp + +( $TEST -f 99 localhost '' ) > unsup_fam_${family}.exp + +( $TEST fe80::1%lo0 http +# IF=`ifconfig -a | grep -v '^ ' | sed -e 's/:.*//' | head -1 | awk '{print $1}'` +# $TEST fe80::1%$IF http +) > scoped_${family}.exp diff --git a/lib/libc/tests/net/getaddrinfo/no_host_v4.exp b/lib/libc/tests/net/getaddrinfo/data/no_host_v4.exp index 7ed41cd9d88a..7ed41cd9d88a 100644 --- a/lib/libc/tests/net/getaddrinfo/no_host_v4.exp +++ b/lib/libc/tests/net/getaddrinfo/data/no_host_v4.exp diff --git a/lib/libc/tests/net/getaddrinfo/no_host_v4v6.exp b/lib/libc/tests/net/getaddrinfo/data/no_host_v4_only.exp index 596799305117..596799305117 100644 --- a/lib/libc/tests/net/getaddrinfo/no_host_v4v6.exp +++ b/lib/libc/tests/net/getaddrinfo/data/no_host_v4_only.exp diff --git a/lib/libc/tests/net/getaddrinfo/data/no_host_v4v6.exp b/lib/libc/tests/net/getaddrinfo/data/no_host_v4v6.exp new file mode 100644 index 000000000000..596799305117 --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/no_host_v4v6.exp @@ -0,0 +1,68 @@ +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv http +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http +ai4: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http +ai5: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http +ai6: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv echo +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv echo +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv echo +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv echo +ai4: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv echo +ai5: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv echo +ai6: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv echo + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv tftp +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv tftp +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv tftp +ai3: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv tftp +ai4: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv tftp + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv 80 +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http +ai4: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http +ai5: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http +ai6: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http + +arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv http +ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv http +ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv http +ai3: flags 0x3 family 28 socktype 5 protocol 132 addrlen 28 host :: serv http +ai4: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv http +ai5: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv http +ai6: flags 0x3 family 2 socktype 5 protocol 132 addrlen 16 host 0.0.0.0 serv http + +arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv echo +ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv echo +ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv echo +ai3: flags 0x3 family 28 socktype 5 protocol 132 addrlen 28 host :: serv echo +ai4: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv echo +ai5: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv echo +ai6: flags 0x3 family 2 socktype 5 protocol 132 addrlen 16 host 0.0.0.0 serv echo + +arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv tftp +ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv tftp +ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv tftp +ai3: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv tftp +ai4: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv tftp + +arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv 80 +ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv http +ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv http +ai3: flags 0x3 family 28 socktype 5 protocol 132 addrlen 28 host :: serv http +ai4: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv http +ai5: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv http +ai6: flags 0x3 family 2 socktype 5 protocol 132 addrlen 16 host 0.0.0.0 serv http + +arg: flags 0x2 family 0 socktype 1 protocol 0 addrlen 0 host (empty) serv 80 +ai1: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http + +arg: flags 0x2 family 0 socktype 2 protocol 0 addrlen 0 host (empty) serv 80 +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http +ai2: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http + diff --git a/lib/libc/tests/net/getaddrinfo/data/no_host_v4v6_prefer_v4.exp b/lib/libc/tests/net/getaddrinfo/data/no_host_v4v6_prefer_v4.exp new file mode 100644 index 000000000000..596799305117 --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/no_host_v4v6_prefer_v4.exp @@ -0,0 +1,68 @@ +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv http +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http +ai4: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http +ai5: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http +ai6: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv echo +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv echo +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv echo +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv echo +ai4: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv echo +ai5: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv echo +ai6: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv echo + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv tftp +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv tftp +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv tftp +ai3: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv tftp +ai4: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv tftp + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv 80 +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http +ai4: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http +ai5: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http +ai6: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http + +arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv http +ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv http +ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv http +ai3: flags 0x3 family 28 socktype 5 protocol 132 addrlen 28 host :: serv http +ai4: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv http +ai5: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv http +ai6: flags 0x3 family 2 socktype 5 protocol 132 addrlen 16 host 0.0.0.0 serv http + +arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv echo +ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv echo +ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv echo +ai3: flags 0x3 family 28 socktype 5 protocol 132 addrlen 28 host :: serv echo +ai4: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv echo +ai5: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv echo +ai6: flags 0x3 family 2 socktype 5 protocol 132 addrlen 16 host 0.0.0.0 serv echo + +arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv tftp +ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv tftp +ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv tftp +ai3: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv tftp +ai4: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv tftp + +arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv 80 +ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv http +ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv http +ai3: flags 0x3 family 28 socktype 5 protocol 132 addrlen 28 host :: serv http +ai4: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv http +ai5: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv http +ai6: flags 0x3 family 2 socktype 5 protocol 132 addrlen 16 host 0.0.0.0 serv http + +arg: flags 0x2 family 0 socktype 1 protocol 0 addrlen 0 host (empty) serv 80 +ai1: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http + +arg: flags 0x2 family 0 socktype 2 protocol 0 addrlen 0 host (empty) serv 80 +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http +ai2: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http + diff --git a/lib/libc/tests/net/getaddrinfo/no_serv_v4.exp b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4.exp index 667234d2161f..667234d2161f 100644 --- a/lib/libc/tests/net/getaddrinfo/no_serv_v4.exp +++ b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4.exp diff --git a/lib/libc/tests/net/getaddrinfo/data/no_serv_v4_only.exp b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4_only.exp new file mode 100644 index 000000000000..0d28490c8d81 --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4_only.exp @@ -0,0 +1,20 @@ +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv (empty) +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv 0 +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv 0 +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv 0 + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv (empty) +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv 0 +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv 0 +ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv 0 + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv (empty) +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv 0 +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv 0 +ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv 0 +ai4: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv 0 +ai5: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv 0 +ai6: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv 0 + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv (empty) +Name does not resolve diff --git a/lib/libc/tests/net/getaddrinfo/no_serv_v4v6.exp b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4v6.exp index 53502547c40e..53502547c40e 100644 --- a/lib/libc/tests/net/getaddrinfo/no_serv_v4v6.exp +++ b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4v6.exp diff --git a/lib/libc/tests/net/getaddrinfo/data/no_serv_v4v6_prefer_v4.exp b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4v6_prefer_v4.exp new file mode 100644 index 000000000000..0d28490c8d81 --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4v6_prefer_v4.exp @@ -0,0 +1,20 @@ +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv (empty) +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv 0 +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv 0 +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv 0 + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv (empty) +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv 0 +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv 0 +ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv 0 + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv (empty) +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv 0 +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv 0 +ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv 0 +ai4: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv 0 +ai5: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv 0 +ai6: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv 0 + +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv (empty) +Name does not resolve diff --git a/lib/libc/tests/net/getaddrinfo/scoped.exp b/lib/libc/tests/net/getaddrinfo/data/scoped.exp index f5ddb4bf6feb..f5ddb4bf6feb 100644 --- a/lib/libc/tests/net/getaddrinfo/scoped.exp +++ b/lib/libc/tests/net/getaddrinfo/data/scoped.exp diff --git a/lib/libc/tests/net/getaddrinfo/data/scoped_v4_only.exp b/lib/libc/tests/net/getaddrinfo/data/scoped_v4_only.exp new file mode 100644 index 000000000000..f5ddb4bf6feb --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/scoped_v4_only.exp @@ -0,0 +1,5 @@ +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host fe80::1%lo0 serv http +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host fe80::1%lo0 serv http +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host fe80::1%lo0 serv http +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host fe80::1%lo0 serv http + diff --git a/lib/libc/tests/net/getaddrinfo/data/scoped_v4v6_prefer_v4.exp b/lib/libc/tests/net/getaddrinfo/data/scoped_v4v6_prefer_v4.exp new file mode 100644 index 000000000000..f5ddb4bf6feb --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/scoped_v4v6_prefer_v4.exp @@ -0,0 +1,5 @@ +arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host fe80::1%lo0 serv http +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host fe80::1%lo0 serv http +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host fe80::1%lo0 serv http +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host fe80::1%lo0 serv http + diff --git a/lib/libc/tests/net/getaddrinfo/sock_raw_v4.exp b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4.exp index 021038b81dcc..021038b81dcc 100644 --- a/lib/libc/tests/net/getaddrinfo/sock_raw_v4.exp +++ b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4.exp diff --git a/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4_only.exp b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4_only.exp new file mode 100644 index 000000000000..932c1faab0d3 --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4_only.exp @@ -0,0 +1,15 @@ +arg: flags 0x2 family 0 socktype 3 protocol 0 addrlen 0 host localhost serv (empty) +ai1: flags 0x2 family 2 socktype 3 protocol 0 addrlen 16 host 127.0.0.1 serv 0 +ai2: flags 0x2 family 28 socktype 3 protocol 0 addrlen 28 host ::1 serv 0 + +arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host localhost serv (empty) +ai1: flags 0x2 family 2 socktype 3 protocol 59 addrlen 16 host 127.0.0.1 serv 0 +ai2: flags 0x2 family 28 socktype 3 protocol 59 addrlen 28 host ::1 serv 0 + +arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host localhost serv 80 +Service was not recognized for socket type +arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host localhost serv www +Service was not recognized for socket type +arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host ::1 serv (empty) +ai1: flags 0x2 family 28 socktype 3 protocol 59 addrlen 28 host ::1 serv 0 + diff --git a/lib/libc/tests/net/getaddrinfo/sock_raw_v4v6.exp b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4v6.exp index e494271a3d35..e494271a3d35 100644 --- a/lib/libc/tests/net/getaddrinfo/sock_raw_v4v6.exp +++ b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4v6.exp diff --git a/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4v6_prefer_v4.exp b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4v6_prefer_v4.exp new file mode 100644 index 000000000000..932c1faab0d3 --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4v6_prefer_v4.exp @@ -0,0 +1,15 @@ +arg: flags 0x2 family 0 socktype 3 protocol 0 addrlen 0 host localhost serv (empty) +ai1: flags 0x2 family 2 socktype 3 protocol 0 addrlen 16 host 127.0.0.1 serv 0 +ai2: flags 0x2 family 28 socktype 3 protocol 0 addrlen 28 host ::1 serv 0 + +arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host localhost serv (empty) +ai1: flags 0x2 family 2 socktype 3 protocol 59 addrlen 16 host 127.0.0.1 serv 0 +ai2: flags 0x2 family 28 socktype 3 protocol 59 addrlen 28 host ::1 serv 0 + +arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host localhost serv 80 +Service was not recognized for socket type +arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host localhost serv www +Service was not recognized for socket type +arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host ::1 serv (empty) +ai1: flags 0x2 family 28 socktype 3 protocol 59 addrlen 28 host ::1 serv 0 + diff --git a/lib/libc/tests/net/getaddrinfo/spec_fam_v4.exp b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4.exp index 924d2a16f47c..924d2a16f47c 100644 --- a/lib/libc/tests/net/getaddrinfo/spec_fam_v4.exp +++ b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4.exp diff --git a/lib/libc/tests/net/getaddrinfo/spec_fam_v4v6.exp b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4_only.exp index af3506938503..af3506938503 100644 --- a/lib/libc/tests/net/getaddrinfo/spec_fam_v4v6.exp +++ b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4_only.exp diff --git a/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4v6.exp b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4v6.exp new file mode 100644 index 000000000000..af3506938503 --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4v6.exp @@ -0,0 +1,10 @@ +arg: flags 0x2 family 2 socktype 0 protocol 0 addrlen 0 host localhost serv http +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http +ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http + +arg: flags 0x2 family 28 socktype 0 protocol 0 addrlen 0 host localhost serv http +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http + diff --git a/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4v6_prefer_v4.exp b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4v6_prefer_v4.exp new file mode 100644 index 000000000000..af3506938503 --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4v6_prefer_v4.exp @@ -0,0 +1,10 @@ +arg: flags 0x2 family 2 socktype 0 protocol 0 addrlen 0 host localhost serv http +ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http +ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http +ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http + +arg: flags 0x2 family 28 socktype 0 protocol 0 addrlen 0 host localhost serv http +ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http +ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http +ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http + diff --git a/lib/libc/tests/net/getaddrinfo/unsup_fam.exp b/lib/libc/tests/net/getaddrinfo/data/unsup_fam.exp index 69e6b48a854b..69e6b48a854b 100644 --- a/lib/libc/tests/net/getaddrinfo/unsup_fam.exp +++ b/lib/libc/tests/net/getaddrinfo/data/unsup_fam.exp diff --git a/lib/libc/tests/net/getaddrinfo/data/unsup_fam_v4_only.exp b/lib/libc/tests/net/getaddrinfo/data/unsup_fam_v4_only.exp new file mode 100644 index 000000000000..69e6b48a854b --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/unsup_fam_v4_only.exp @@ -0,0 +1,2 @@ +arg: flags 0x2 family 99 socktype 0 protocol 0 addrlen 0 host localhost serv (empty) +Address family not recognized diff --git a/lib/libc/tests/net/getaddrinfo/data/unsup_fam_v4v6_prefer_v4.exp b/lib/libc/tests/net/getaddrinfo/data/unsup_fam_v4v6_prefer_v4.exp new file mode 100644 index 000000000000..69e6b48a854b --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/data/unsup_fam_v4v6_prefer_v4.exp @@ -0,0 +1,2 @@ +arg: flags 0x2 family 99 socktype 0 protocol 0 addrlen 0 host localhost serv (empty) +Address family not recognized diff --git a/lib/libc/tests/net/getaddrinfo/getaddrinfo_test.sh b/lib/libc/tests/net/getaddrinfo/getaddrinfo_test.sh new file mode 100755 index 000000000000..dd17ab2e3f4a --- /dev/null +++ b/lib/libc/tests/net/getaddrinfo/getaddrinfo_test.sh @@ -0,0 +1,443 @@ +# $NetBSD: t_getaddrinfo.sh,v 1.2 2011/06/15 07:54:32 jmmv Exp $ + +# +# Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, and 2002 WIDE Project. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the project nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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. +# + +if [ "$(sysctl -i -n kern.features.vimage)" != 1 ]; then + atf_skip "This test requires VIMAGE" +fi + +vnet_mkjail() +{ + jailname=getaddrinfo_test_$1 + jail -c name=${jailname} persist vnet + ifconfig -j ${jailname} lo0 inet 127.0.0.1/8 + # For those machines not support IPv6 + ifconfig -j ${jailname} lo0 inet6 ::1/64 || true + service -j ${jailname} ip6addrctl $2 || true +} + +vnet_cleanup() +{ + jailname=getaddrinfo_test_$1 + jail -r ${jailname} +} + +check_output() +{ + if [ "$2" = "none" ]; then + if [ "$3" = "prefer_v6" ]; then + exp="${1}.exp" + else + exp="${1}_v4_only.exp" + fi + elif [ "$2" = "hosts" ]; then + lcl=$(cat /etc/hosts | sed -e 's/#.*$//' -e 's/[ ][ ]*/ /g' | awk '/ localhost($| )/ {printf "%s ", $1}') + if [ "${lcl%*::*}" = "${lcl}" ]; then + exp="${1}_v4_only.exp" + else + if [ "$3" = "prefer_v6" ]; then + exp="${1}_v4v6.exp" + else + exp="${1}_v4v6_prefer_v4.exp" + fi + fi + elif [ "$2" = "ifconfig" ]; then + lcl=$(ifconfig lo0 | grep inet6) + if [ -n "${lcl}" ]; then + if [ "$3" = "prefer_v6" ]; then + exp="${1}_v4v6.exp" + else + exp="${1}_v4v6_prefer_v4.exp" + fi + else + exp="${1}_v4_only.exp" + fi + else + atf_fail "Invalid family_match_type $2 requested." + fi + + cmp -s "$(atf_get_srcdir)/data/${exp}" out && return + diff -u "$(atf_get_srcdir)/data/${exp}" out || atf_fail "Actual output does not match expected output" +} + +atf_test_case basic_prefer_v4 cleanup +basic_prefer_v4_head() +{ + atf_set "descr" "Testing basic ones with prefer_v4" + atf_set "require.user" "root" +} +basic_prefer_v4_body() +{ + vnet_mkjail basic_prefer_v4 prefer_ipv4 + TEST="jexec getaddrinfo_test_basic_prefer_v4 $(atf_get_srcdir)/h_gai" + + ( $TEST ::1 http + $TEST 127.0.0.1 http + $TEST localhost http + $TEST ::1 tftp + $TEST 127.0.0.1 tftp + $TEST localhost tftp + $TEST ::1 echo + $TEST 127.0.0.1 echo + $TEST localhost echo ) > out 2>&1 + + check_output basics hosts prefer_v4 +} +basic_prefer_v4_cleanup() +{ + vnet_cleanup basic_prefer_v4 +} + +atf_test_case basic cleanup +basic_head() +{ + atf_set "descr" "Testing basic ones with prefer_v6" + atf_set "require.user" "root" +} +basic_body() +{ + vnet_mkjail basic prefer_ipv6 + TEST="jexec getaddrinfo_test_basic $(atf_get_srcdir)/h_gai" + + ( $TEST ::1 http + $TEST 127.0.0.1 http + $TEST localhost http + $TEST ::1 tftp + $TEST 127.0.0.1 tftp + $TEST localhost tftp + $TEST ::1 echo + $TEST 127.0.0.1 echo + $TEST localhost echo ) > out 2>&1 + + check_output basics ifconfig prefer_v6 +} +basic_cleanup() +{ + vnet_cleanup basic +} + +atf_test_case specific_prefer_v4 cleanup +specific_prefer_v4_head() +{ + atf_set "descr" "Testing specific address family with prefer_v4" + atf_set "require.user" "root" +} +specific_prefer_v4_body() +{ + vnet_mkjail specific_prefer_v4 prefer_ipv4 + TEST="jexec getaddrinfo_test_specific_prefer_v4 $(atf_get_srcdir)/h_gai" + + ( $TEST -4 localhost http + $TEST -6 localhost http ) > out 2>&1 + + check_output spec_fam hosts prefer_v4 +} +specific_prefer_v4_cleanup() +{ + vnet_cleanup specific_prefer_v4 +} + +atf_test_case specific cleanup +specific_head() +{ + atf_set "descr" "Testing specific address family with prefer_v6" + atf_set "require.user" "root" +} +specific_body() +{ + vnet_mkjail specific prefer_ipv6 + TEST="jexec getaddrinfo_test_specific $(atf_get_srcdir)/h_gai" + + ( $TEST -4 localhost http + $TEST -6 localhost http ) > out 2>&1 + + check_output spec_fam hosts prefer_v6 +} +specific_cleanup() +{ + vnet_cleanup specific +} + +atf_test_case empty_hostname_prefer_v4 cleanup +empty_hostname_prefer_v4_head() +{ + atf_set "descr" "Testing empty hostname with prefer_v4" + atf_set "require.user" "root" +} +empty_hostname_prefer_v4_body() +{ + vnet_mkjail empty_hostname_prefer_v4 prefer_ipv4 + TEST="jexec getaddrinfo_test_empty_hostname_prefer_v4 $(atf_get_srcdir)/h_gai" + + ( $TEST '' http + $TEST '' echo + $TEST '' tftp + $TEST '' 80 + $TEST -P '' http + $TEST -P '' echo + $TEST -P '' tftp + $TEST -P '' 80 + $TEST -S '' 80 + $TEST -D '' 80 ) > out 2>&1 + + check_output no_host ifconfig prefer_v4 +} +empty_hostname_prefer_v4_cleanup() +{ + vnet_cleanup empty_hostname_prefer_v4 +} + +atf_test_case empty_hostname cleanup +empty_hostname_head() +{ + atf_set "descr" "Testing empty hostname with prefer_v6" + atf_set "require.user" "root" +} +empty_hostname_body() +{ + vnet_mkjail empty_hostname prefer_ipv6 + TEST="jexec getaddrinfo_test_empty_hostname $(atf_get_srcdir)/h_gai" + + ( $TEST '' http + $TEST '' echo + $TEST '' tftp + $TEST '' 80 + $TEST -P '' http + $TEST -P '' echo + $TEST -P '' tftp + $TEST -P '' 80 + $TEST -S '' 80 + $TEST -D '' 80 ) > out 2>&1 + + check_output no_host ifconfig prefer_v6 +} +empty_hostname_cleanup() +{ + vnet_cleanup empty_hostname +} + +atf_test_case empty_servname_prefer_v4 cleanup +empty_servname_prefer_v4_head() +{ + atf_set "descr" "Testing empty service name with prefer_v4" + atf_set "require.user" "root" +} +empty_servname_prefer_v4_body() +{ + vnet_mkjail empty_servname_prefer_v4 prefer_ipv4 + TEST="jexec getaddrinfo_test_empty_servname_prefer_v4 $(atf_get_srcdir)/h_gai" + + ( $TEST ::1 '' + $TEST 127.0.0.1 '' + $TEST localhost '' + $TEST '' '' ) > out 2>&1 + + check_output no_serv hosts prefer_v4 +} +empty_servname_prefer_v4_cleanup() +{ + vnet_cleanup empty_servname_prefer_v4 +} + +atf_test_case empty_servname cleanup +empty_servname_head() +{ + atf_set "descr" "Testing empty service name with prefer_v6" + atf_set "require.user" "root" +} +empty_servname_body() +{ + vnet_mkjail empty_servname prefer_ipv6 + TEST="jexec getaddrinfo_test_empty_servname $(atf_get_srcdir)/h_gai" + + ( $TEST ::1 '' + $TEST 127.0.0.1 '' + $TEST localhost '' + $TEST '' '' ) > out 2>&1 + + check_output no_serv ifconfig prefer_v6 +} +empty_servname_cleanup() +{ + vnet_cleanup empty_servname +} + +atf_test_case sock_raw_prefer_v4 cleanup +sock_raw_prefer_v4_head() +{ + atf_set "descr" "Testing raw socket with prefer_v4" + atf_set "require.user" "root" +} +sock_raw_prefer_v4_body() +{ + vnet_mkjail sock_raw_prefer_v4 prefer_ipv4 + TEST="jexec getaddrinfo_test_sock_raw_prefer_v4 $(atf_get_srcdir)/h_gai" + + ( $TEST -R -p 0 localhost '' + $TEST -R -p 59 localhost '' + $TEST -R -p 59 localhost 80 + $TEST -R -p 59 localhost www + $TEST -R -p 59 ::1 '' ) > out 2>&1 + + check_output sock_raw hosts prefer_v4 +} +sock_raw_prefer_v4_cleanup() +{ + vnet_cleanup sock_raw_prefer_v4 +} + +atf_test_case sock_raw cleanup +sock_raw_head() +{ + atf_set "descr" "Testing raw socket with prefer_v6" + atf_set "require.user" "root" +} +sock_raw_body() +{ + vnet_mkjail sock_raw prefer_ipv6 + TEST="jexec getaddrinfo_test_sock_raw $(atf_get_srcdir)/h_gai" + + ( $TEST -R -p 0 localhost '' + $TEST -R -p 59 localhost '' + $TEST -R -p 59 localhost 80 + $TEST -R -p 59 localhost www + $TEST -R -p 59 ::1 '' ) > out 2>&1 + + check_output sock_raw ifconfig prefer_v6 +} +sock_raw_cleanup() +{ + vnet_cleanup sock_raw +} + +atf_test_case unsupported_family_prefer_v4 cleanup +unsupported_family_prefer_v4_head() +{ + atf_set "descr" "Testing unsupported family with prefer_v4" + atf_set "require.user" "root" +} +unsupported_family_prefer_v4_body() +{ + vnet_mkjail unsupported_family_prefer_v4 prefer_ipv4 + TEST="jexec getaddrinfo_test_unsupported_family_prefer_v4 $(atf_get_srcdir)/h_gai" + + ( $TEST -f 99 localhost '' ) > out 2>&1 + + check_output unsup_fam ifconfig prefer_v4 +} +unsupported_family_prefer_v4_cleanup() +{ + vnet_cleanup unsupported_family_prefer_v4 +} + +atf_test_case unsupported_family cleanup +unsupported_family_head() +{ + atf_set "descr" "Testing unsupported family with prefer_v6" + atf_set "require.user" "root" +} +unsupported_family_body() +{ + vnet_mkjail unsupported_family prefer_ipv6 + TEST="jexec getaddrinfo_test_unsupported_family $(atf_get_srcdir)/h_gai" + + ( $TEST -f 99 localhost '' ) > out 2>&1 + + check_output unsup_fam none prefer_v6 +} +unsupported_family_cleanup() +{ + vnet_cleanup unsupported_family +} + +atf_test_case scopeaddr_prefer_v4 cleanup +scopeaddr_prefer_v4_head() +{ + atf_set "descr" "Testing scoped address format with prefer_v4" + atf_set "require.user" "root" +} +scopeaddr_prefer_v4_body() +{ + vnet_mkjail scopeaddr_prefer_v4 prefer_ipv4 + TEST="jexec getaddrinfo_test_scopeaddr_prefer_v4 $(atf_get_srcdir)/h_gai" + + ( $TEST fe80::1%lo0 http +# IF=ifconfig -a | grep -v '^ ' | sed -e 's/:.*//' | head -1 | awk '{print $1}' +# $TEST fe80::1%$IF http + ) > out 2>&1 + + check_output scoped ifconfig prefer_v4 +} +scopeaddr_prefer_v4_cleanup() +{ + vnet_cleanup scopeaddr_prefer_v4 +} + +atf_test_case scopeaddr cleanup +scopeaddr_head() +{ + atf_set "descr" "Testing scoped address format with prefer_v6" + atf_set "require.user" "root" +} +scopeaddr_body() +{ + vnet_mkjail scopeaddr prefer_ipv6 + TEST="jexec getaddrinfo_test_scopeaddr $(atf_get_srcdir)/h_gai" + + ( $TEST fe80::1%lo0 http +# IF=ifconfig -a | grep -v '^ ' | sed -e 's/:.*//' | head -1 | awk '{print $1}' +# $TEST fe80::1%$IF http + ) > out 2>&1 + + check_output scoped none prefer_v6 +} +scopeaddr_cleanup() +{ + vnet_cleanup scopeaddr +} + +atf_init_test_cases() +{ + atf_add_test_case basic_prefer_v4 + atf_add_test_case specific_prefer_v4 + atf_add_test_case empty_hostname_prefer_v4 + atf_add_test_case empty_servname_prefer_v4 + atf_add_test_case sock_raw_prefer_v4 + atf_add_test_case unsupported_family_prefer_v4 + atf_add_test_case scopeaddr_prefer_v4 + + atf_add_test_case basic + atf_add_test_case specific + atf_add_test_case empty_hostname + atf_add_test_case empty_servname + atf_add_test_case sock_raw + atf_add_test_case unsupported_family + atf_add_test_case scopeaddr +} diff --git a/lib/libc/tests/net/link_addr_test.cc b/lib/libc/tests/net/link_addr_test.cc new file mode 100644 index 000000000000..b973b924dc13 --- /dev/null +++ b/lib/libc/tests/net/link_addr_test.cc @@ -0,0 +1,532 @@ +/* + * Copyright (c) 2025 Lexi Winter + * + * SPDX-License-Identifier: ISC + */ + +/* + * Tests for link_addr() and link_ntoa(). + * + * link_addr converts a string representing an (optionally null) interface name + * followed by an Ethernet address into a sockaddr_dl. The expected format is + * "[ifname]:lladdr". This means if ifname is not specified, the leading colon + * is still required. + * + * link_ntoa does the inverse of link_addr, returning a string representation + * of the address. + * + * Note that the output format of link_ntoa is not valid input for link_addr + * since the leading colon may be omitted. This is by design. + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <net/ethernet.h> +#include <net/if_dl.h> + +#include <format> +#include <iostream> +#include <ranges> +#include <span> +#include <utility> +#include <vector> + +#include <cstddef> +#include <cstdint> + +#include <atf-c++.hpp> + +using namespace std::literals; + +/* + * Define operator== and operator<< for ether_addr so we can use them in + * ATF_EXPECT_EQ expressions. + */ + +bool +operator==(ether_addr a, ether_addr b) +{ + return (std::ranges::equal(a.octet, b.octet)); +} + +std::ostream & +operator<<(std::ostream &s, ether_addr a) +{ + for (unsigned i = 0; i < ETHER_ADDR_LEN; ++i) { + if (i > 0) + s << ":"; + + s << std::format("{:02x}", static_cast<int>(a.octet[i])); + } + + return (s); +} + +/* + * Create a sockaddr_dl from a string using link_addr(), and ensure the + * returned struct looks valid. + */ +sockaddr_dl +make_linkaddr(const std::string &addr) +{ + auto sdl = sockaddr_dl{}; + int ret; + + sdl.sdl_len = sizeof(sdl); + ret = ::link_addr(addr.c_str(), &sdl); + + ATF_REQUIRE_EQ(0, ret); + ATF_REQUIRE_EQ(AF_LINK, static_cast<int>(sdl.sdl_family)); + ATF_REQUIRE_EQ(true, sdl.sdl_len > 0); + ATF_REQUIRE_EQ(true, sdl.sdl_nlen >= 0); + + return (sdl); +} + +/* + * Return the data stored in a sockaddr_dl as a span. + */ +std::span<const char> +data(const sockaddr_dl &sdl) +{ + // sdl_len is the entire structure, but we only want the length of the + // data area. + auto dlen = sdl.sdl_len - offsetof(sockaddr_dl, sdl_data); + return {&sdl.sdl_data[0], dlen}; +} + +/* + * Return the interface name stored in a sockaddr_dl as a string. + */ +std::string_view +ifname(const sockaddr_dl &sdl) +{ + auto name = data(sdl).subspan(0, sdl.sdl_nlen); + return {name.begin(), name.end()}; +} + +/* + * Return the Ethernet address stored in a sockaddr_dl as an ether_addr. + */ +ether_addr +addr(const sockaddr_dl &sdl) +{ + ether_addr ret{}; + ATF_REQUIRE_EQ(ETHER_ADDR_LEN, sdl.sdl_alen); + std::ranges::copy(data(sdl).subspan(sdl.sdl_nlen, ETHER_ADDR_LEN), + &ret.octet[0]); + return (ret); +} + +/* + * Return the link address stored in a sockaddr_dl as a span of octets. + */ +std::span<const std::uint8_t> +lladdr(const sockaddr_dl &sdl) +{ + auto data = reinterpret_cast<const uint8_t *>(LLADDR(&sdl)); + return {data, data + sdl.sdl_alen}; +} + + +/* + * Some sample addresses we use for testing. Include at least one address for + * each format we want to support. + */ + +struct test_address { + std::string input; /* value passed to link_addr */ + std::string ntoa; /* expected return from link_ntoa */ + ether_addr addr{}; /* expected return from link_addr */ +}; + +std::vector<test_address> test_addresses{ + // No delimiter + {"001122334455"s, "0.11.22.33.44.55", + ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}}, + + // Colon delimiter + {"00:11:22:33:44:55"s, "0.11.22.33.44.55", + ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}}, + + // Dash delimiter + {"00-11-22-33-44-55"s, "0.11.22.33.44.55", + ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}}, + + // Period delimiter (link_ntoa format) + {"00.11.22.33.44.55"s, "0.11.22.33.44.55", + ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}}, + + // Period delimiter (Cisco format) + {"0011.2233.4455"s, "0.11.22.33.44.55", + ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}}, + + // An addresses without leading zeroes. + {"0:1:02:30:4:55"s, "0.1.2.30.4.55", + ether_addr{0x00, 0x01, 0x02, 0x30, 0x04, 0x55}}, + + // An address with some uppercase letters. + {"AA:B:cC:Dd:e0:1f"s, "aa.b.cc.dd.e0.1f", + ether_addr{0xaa, 0x0b, 0xcc, 0xdd, 0xe0, 0x1f}}, + + // Addresses composed only of letters, to make sure they're not + // confused with an interface name. + + {"aabbccddeeff"s, "aa.bb.cc.dd.ee.ff", + ether_addr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}}, + + {"aa:bb:cc:dd:ee:ff"s, "aa.bb.cc.dd.ee.ff", + ether_addr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}}, + + // Address with a blank octet; this is an old form of Ethernet address. + {"00:11::33:44:55"s, "0.11.0.33.44.55", + ether_addr{0x00, 0x11, 0x00, 0x33, 0x44, 0x55}}, +}; + +/* + * Test without an interface name. + */ +ATF_TEST_CASE_WITHOUT_HEAD(basic) +ATF_TEST_CASE_BODY(basic) +{ + for (const auto &ta : test_addresses) { + // This does basic tests on the returned value. + auto sdl = make_linkaddr(":" + ta.input); + + // Check the ifname and address. + ATF_REQUIRE_EQ(""s, ifname(sdl)); + ATF_REQUIRE_EQ(ETHER_ADDR_LEN, static_cast<int>(sdl.sdl_alen)); + ATF_REQUIRE_EQ(ta.addr, addr(sdl)); + + // Check link_ntoa returns the expected value. + auto ntoa = std::string(::link_ntoa(&sdl)); + ATF_REQUIRE_EQ(ta.ntoa, ntoa); + } + +} + +/* + * Test with an interface name. + */ +ATF_TEST_CASE_WITHOUT_HEAD(ifname) +ATF_TEST_CASE_BODY(ifname) +{ + for (const auto &ta : test_addresses) { + auto sdl = make_linkaddr("ix0:" + ta.input); + + ATF_REQUIRE_EQ("ix0", ifname(sdl)); + ATF_REQUIRE_EQ(ETHER_ADDR_LEN, static_cast<int>(sdl.sdl_alen)); + ATF_REQUIRE_EQ(ta.addr, addr(sdl)); + + auto ntoa = std::string(::link_ntoa(&sdl)); + ATF_REQUIRE_EQ("ix0:" + ta.ntoa, ntoa); + } + +} + +/* + * Test with some invalid addresses. + */ +ATF_TEST_CASE_WITHOUT_HEAD(invalid) +ATF_TEST_CASE_BODY(invalid) +{ + auto const invalid_addresses = std::vector{ + // Invalid separator + ":1/2/3"s, + "ix0:1/2/3"s, + + // Multiple different separators + ":1.2-3"s, + "ix0:1.2-3"s, + + // An IP address + ":10.1.2.200/28"s, + "ix0:10.1.2.200/28"s, + + // Valid address followed by garbage + ":1.2.3xxx"s, + ":1.2.3.xxx"s, + "ix0:1.2.3xxx"s, + "ix0:1.2.3.xxx"s, + }; + + for (auto const &addr : invalid_addresses) { + int ret; + + auto sdl = sockaddr_dl{}; + sdl.sdl_len = sizeof(sdl); + + ret = link_addr(addr.c_str(), &sdl); + ATF_REQUIRE_EQ(-1, ret); + } +} + +/* + * Test some non-Ethernet addresses. + */ +ATF_TEST_CASE_WITHOUT_HEAD(nonether) +ATF_TEST_CASE_BODY(nonether) +{ + sockaddr_dl sdl; + + /* A short address */ + sdl = make_linkaddr(":1:23:cc"); + ATF_REQUIRE_EQ("", ifname(sdl)); + ATF_REQUIRE_EQ("1.23.cc"s, ::link_ntoa(&sdl)); + ATF_REQUIRE_EQ(3, sdl.sdl_alen); + ATF_REQUIRE_EQ(true, + std::ranges::equal(std::vector{0x01u, 0x23u, 0xccu}, lladdr(sdl))); + + /* A long address */ + sdl = make_linkaddr(":1:23:cc:a:b:c:d:e:f"); + ATF_REQUIRE_EQ("", ifname(sdl)); + ATF_REQUIRE_EQ("1.23.cc.a.b.c.d.e.f"s, ::link_ntoa(&sdl)); + ATF_REQUIRE_EQ(9, sdl.sdl_alen); + ATF_REQUIRE_EQ(true, std::ranges::equal( + std::vector{0x01u, 0x23u, 0xccu, 0xau, 0xbu, 0xcu, 0xdu, 0xeu, 0xfu}, + lladdr(sdl))); +} + +/* + * Test link_addr behaviour with undersized buffers. + */ +ATF_TEST_CASE_WITHOUT_HEAD(smallbuf) +ATF_TEST_CASE_BODY(smallbuf) +{ + static constexpr auto garbage = std::byte{0xcc}; + auto buf = std::vector<std::byte>(); + sockaddr_dl *sdl; + int ret; + + /* + * Make an sdl with an sdl_data member of the appropriate size, and + * place it in buf. Ensure it's followed by a trailing byte of garbage + * so we can test that link_addr doesn't write past the end. + */ + auto mksdl = [&buf](std::size_t datalen) -> sockaddr_dl * { + auto actual_size = datalen + offsetof(sockaddr_dl, sdl_data); + + buf.resize(actual_size + 1); + std::ranges::fill(buf, garbage); + + auto *sdl = new(reinterpret_cast<sockaddr_dl *>(&buf[0])) + sockaddr_dl; + sdl->sdl_len = actual_size; + return (sdl); + }; + + /* An sdl large enough to store the interface name */ + sdl = mksdl(3); + ret = link_addr("ix0:1.2.3", sdl); + ATF_REQUIRE(*rbegin(buf) == garbage); + ATF_REQUIRE_EQ(-1, ret); + ATF_REQUIRE_EQ(ENOSPC, errno); + ATF_REQUIRE_EQ(3, sdl->sdl_nlen); + ATF_REQUIRE_EQ("ix0", ifname(*sdl)); + ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen)); + + /* + * For these tests, test both with and without an interface name, since + * that will overflow the buffer in different places. + */ + + /* An empty sdl. Nothing may grow on this cursed ground. */ + + sdl = mksdl(0); + ret = link_addr("ix0:1.2.3", sdl); + ATF_REQUIRE(*rbegin(buf) == garbage); + ATF_REQUIRE_EQ(-1, ret); + ATF_REQUIRE_EQ(ENOSPC, errno); + ATF_REQUIRE_EQ(0, sdl->sdl_nlen); + ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen)); + + sdl = mksdl(0); + ret = link_addr(":1.2.3", sdl); + ATF_REQUIRE(*rbegin(buf) == garbage); + ATF_REQUIRE_EQ(-1, ret); + ATF_REQUIRE_EQ(ENOSPC, errno); + ATF_REQUIRE_EQ(0, sdl->sdl_nlen); + ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen)); + + /* + * An sdl large enough to store the interface name and two octets of the + * address. + */ + + sdl = mksdl(5); + ret = link_addr("ix0:1.2.3", sdl); + ATF_REQUIRE(*rbegin(buf) == garbage); + ATF_REQUIRE_EQ(-1, ret); + ATF_REQUIRE_EQ(ENOSPC, errno); + ATF_REQUIRE_EQ("ix0", ifname(*sdl)); + ATF_REQUIRE(std::ranges::equal( + std::vector{ 0x01, 0x02 }, lladdr(*sdl))); + + sdl = mksdl(2); + ret = link_addr(":1.2.3", sdl); + ATF_REQUIRE(*rbegin(buf) == garbage); + ATF_REQUIRE_EQ(-1, ret); + ATF_REQUIRE_EQ(ENOSPC, errno); + ATF_REQUIRE_EQ("", ifname(*sdl)); + ATF_REQUIRE(std::ranges::equal( + std::vector{ 0x01, 0x02 }, lladdr(*sdl))); + + /* + * An sdl large enough to store the entire address. + */ + + sdl = mksdl(6); + ret = link_addr("ix0:1.2.3", sdl); + ATF_REQUIRE(*rbegin(buf) == garbage); + ATF_REQUIRE_EQ(0, ret); + ATF_REQUIRE_EQ("ix0", ifname(*sdl)); + ATF_REQUIRE(std::ranges::equal( + std::vector{ 0x01, 0x02, 0x03 }, lladdr(*sdl))); + + sdl = mksdl(3); + ret = link_addr(":1.2.3", sdl); + ATF_REQUIRE(*rbegin(buf) == garbage); + ATF_REQUIRE_EQ(0, ret); + ATF_REQUIRE_EQ("", ifname(*sdl)); + ATF_REQUIRE(std::ranges::equal( + std::vector{ 0x01, 0x02, 0x03 }, lladdr(*sdl))); +} + +/* + * Test an extremely long address which would overflow link_ntoa's internal + * buffer. It should handle this by truncating the output. + * (Test for SA-16:37.libc / CVE-2016-6559.) + */ +ATF_TEST_CASE_WITHOUT_HEAD(overlong) +ATF_TEST_CASE_BODY(overlong) +{ + auto sdl = make_linkaddr( + ":01.02.03.04.05.06.07.08.09.0a.0b.0c.0d.0e.0f." + "11.12.13.14.15.16.17.18.19.1a.1b.1c.1d.1e.1f." + "22.22.23.24.25.26.27.28.29.2a.2b.2c.2d.2e.2f"); + + ATF_REQUIRE_EQ( + "1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.11.12.13.14.15.16.17.18.19.1a.1b."s, + ::link_ntoa(&sdl)); +} + +/* + * Test link_ntoa_r, the re-entrant version of link_ntoa(). + */ +ATF_TEST_CASE_WITHOUT_HEAD(link_ntoa_r) +ATF_TEST_CASE_BODY(link_ntoa_r) +{ + static constexpr char garbage = 0x41; + std::vector<char> buf; + sockaddr_dl sdl; + size_t len; + int ret; + + // Return the contents of buf as a string, using the NUL terminator to + // determine length. This is to ensure we're using the return value in + // the same way C code would, but we do a bit more verification to + // elicit a test failure rather than a SEGV if it's broken. + auto bufstr = [&buf]() -> std::string_view { + // Find the NUL. + auto end = std::ranges::find(buf, '\0'); + ATF_REQUIRE(end != buf.end()); + + // Intentionally chopping the NUL off. + return {begin(buf), end}; + }; + + // Resize the buffer and set the contents to a known garbage value, so + // we don't accidentally have a NUL in the right place when link_ntoa_r + // didn't put it there. + auto resetbuf = [&buf, &len](std::size_t size) { + len = size; + buf.resize(len); + std::ranges::fill(buf, garbage); + }; + + // Test a short address with a large buffer. + sdl = make_linkaddr("ix0:1.2.3"); + resetbuf(64); + ret = ::link_ntoa_r(&sdl, &buf[0], &len); + ATF_REQUIRE_EQ(0, ret); + ATF_REQUIRE_EQ(10, len); + ATF_REQUIRE_EQ("ix0:1.2.3"s, bufstr()); + + // Test a buffer which is exactly the right size. + sdl = make_linkaddr("ix0:1.2.3"); + resetbuf(10); + ret = ::link_ntoa_r(&sdl, &buf[0], &len); + ATF_REQUIRE_EQ(0, ret); + ATF_REQUIRE_EQ(10, len); + ATF_REQUIRE_EQ("ix0:1.2.3"sv, bufstr()); + + // Test various short buffers, using a table of buffer length and the + // output we expect. All of these should produce valid but truncated + // strings, with a trailing NUL and with buflen set correctly. + + auto buftests = std::vector<std::pair<std::size_t, std::string_view>>{ + {1u, ""sv}, + {2u, ""sv}, + {3u, ""sv}, + {4u, "ix0"sv}, + {5u, "ix0:"sv}, + {6u, "ix0:1"sv}, + {7u, "ix0:1."sv}, + {8u, "ix0:1.2"sv}, + {9u, "ix0:1.2."sv}, + }; + + for (auto const &[buflen, expected] : buftests) { + sdl = make_linkaddr("ix0:1.2.3"); + resetbuf(buflen); + ret = ::link_ntoa_r(&sdl, &buf[0], &len); + ATF_REQUIRE_EQ(-1, ret); + ATF_REQUIRE_EQ(10, len); + ATF_REQUIRE_EQ(expected, bufstr()); + } + + // Test a NULL buffer, which should just set buflen. + sdl = make_linkaddr("ix0:1.2.3"); + len = 0; + ret = ::link_ntoa_r(&sdl, NULL, &len); + ATF_REQUIRE_EQ(-1, ret); + ATF_REQUIRE_EQ(10, len); + + // A NULL buffer with a non-zero length should also be accepted. + sdl = make_linkaddr("ix0:1.2.3"); + len = 64; + ret = ::link_ntoa_r(&sdl, NULL, &len); + ATF_REQUIRE_EQ(-1, ret); + ATF_REQUIRE_EQ(10, len); + + // Test a non-NULL buffer, but with a length of zero. + sdl = make_linkaddr("ix0:1.2.3"); + resetbuf(1); + len = 0; + ret = ::link_ntoa_r(&sdl, &buf[0], &len); + ATF_REQUIRE_EQ(-1, ret); + ATF_REQUIRE_EQ(10, len); + // Check we really didn't write anything. + ATF_REQUIRE_EQ(garbage, buf[0]); + + // Test a buffer which would be truncated in the middle of a two-digit + // hex octet, which should not write the truncated octet at all. + sdl = make_linkaddr("ix0:1.22.3"); + resetbuf(8); + ret = ::link_ntoa_r(&sdl, &buf[0], &len); + ATF_REQUIRE_EQ(-1, ret); + ATF_REQUIRE_EQ(11, len); + ATF_REQUIRE_EQ("ix0:1."sv, bufstr()); +} + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, basic); + ATF_ADD_TEST_CASE(tcs, ifname); + ATF_ADD_TEST_CASE(tcs, smallbuf); + ATF_ADD_TEST_CASE(tcs, invalid); + ATF_ADD_TEST_CASE(tcs, nonether); + ATF_ADD_TEST_CASE(tcs, overlong); + ATF_ADD_TEST_CASE(tcs, link_ntoa_r); +} diff --git a/lib/libc/tests/secure/Makefile b/lib/libc/tests/secure/Makefile index beaa01457cfe..515f8f53a43e 100644 --- a/lib/libc/tests/secure/Makefile +++ b/lib/libc/tests/secure/Makefile @@ -10,6 +10,7 @@ FORTIFY_TCATS+= uio # non-sys/ headers FORTIFY_TCATS+= poll +FORTIFY_TCATS+= signal FORTIFY_TCATS+= stdlib FORTIFY_TCATS+= stdio FORTIFY_TCATS+= string diff --git a/lib/libc/tests/secure/fortify_signal_test.c b/lib/libc/tests/secure/fortify_signal_test.c new file mode 100644 index 000000000000..03cfb9a9a13a --- /dev/null +++ b/lib/libc/tests/secure/fortify_signal_test.c @@ -0,0 +1,316 @@ +/* @generated by `generate-fortify-tests.lua "signal"` */ + +#define _FORTIFY_SOURCE 2 +#define TMPFILE_SIZE (1024 * 32) + +#include <sys/param.h> +#include <sys/jail.h> +#include <sys/random.h> +#include <sys/resource.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/uio.h> +#include <sys/wait.h> +#include <dirent.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 <strings.h> +#include <sysexits.h> +#include <unistd.h> +#include <wchar.h> +#include <atf-c.h> + +static FILE * __unused +new_fp(size_t __len) +{ + static char fpbuf[LINE_MAX]; + FILE *fp; + + ATF_REQUIRE(__len <= sizeof(fpbuf)); + + memset(fpbuf, 'A', sizeof(fpbuf) - 1); + fpbuf[sizeof(fpbuf) - 1] = '\0'; + + fp = fmemopen(fpbuf, sizeof(fpbuf), "rb"); + ATF_REQUIRE(fp != NULL); + + return (fp); +} + +/* + * Create a new symlink to use for readlink(2) style tests, we'll just use a + * random target name to have something interesting to look at. + */ +static const char * __unused +new_symlink(size_t __len) +{ + static const char linkname[] = "link"; + char target[MAXNAMLEN]; + int error; + + ATF_REQUIRE(__len <= sizeof(target)); + + arc4random_buf(target, sizeof(target)); + + error = unlink(linkname); + ATF_REQUIRE(error == 0 || errno == ENOENT); + + error = symlink(target, linkname); + ATF_REQUIRE(error == 0); + + return (linkname); +} + +/* + * For our purposes, first descriptor will be the reader; we'll send both + * raw data and a control message over it so that the result can be used for + * any of our recv*() tests. + */ +static void __unused +new_socket(int sock[2]) +{ + unsigned char ctrl[CMSG_SPACE(sizeof(int))] = { 0 }; + static char sockbuf[256]; + ssize_t rv; + size_t total = 0; + struct msghdr hdr = { 0 }; + struct cmsghdr *cmsg; + int error, fd; + + error = socketpair(AF_UNIX, SOCK_STREAM, 0, sock); + ATF_REQUIRE(error == 0); + + while (total != sizeof(sockbuf)) { + rv = send(sock[1], &sockbuf[total], sizeof(sockbuf) - total, 0); + + ATF_REQUIRE_MSG(rv > 0, + "expected bytes sent, got %zd with %zu left (size %zu, total %zu)", + rv, sizeof(sockbuf) - total, sizeof(sockbuf), total); + ATF_REQUIRE_MSG(total + (size_t)rv <= sizeof(sockbuf), + "%zd exceeds total %zu", rv, sizeof(sockbuf)); + total += rv; + } + + hdr.msg_control = ctrl; + hdr.msg_controllen = sizeof(ctrl); + + cmsg = CMSG_FIRSTHDR(&hdr); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + fd = STDIN_FILENO; + memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd)); + + error = sendmsg(sock[1], &hdr, 0); + ATF_REQUIRE(error != -1); +} + +/* + * Constructs a tmpfile that we can use for testing read(2) and friends. + */ +static int __unused +new_tmpfile(void) +{ + char buf[1024]; + ssize_t rv; + size_t written; + int fd; + + fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644); + ATF_REQUIRE(fd >= 0); + + written = 0; + while (written < TMPFILE_SIZE) { + rv = write(fd, buf, sizeof(buf)); + ATF_REQUIRE(rv > 0); + + written += rv; + } + + ATF_REQUIRE_EQ(0, lseek(fd, 0, SEEK_SET)); + return (fd); +} + +static void +disable_coredumps(void) +{ + struct rlimit rl = { 0 }; + + if (setrlimit(RLIMIT_CORE, &rl) == -1) + _exit(EX_OSERR); +} + +/* + * Replaces stdin with a file that we can actually read from, for tests where + * we want a FILE * or fd that we can get data from. + */ +static void __unused +replace_stdin(void) +{ + int fd; + + fd = new_tmpfile(); + + (void)dup2(fd, STDIN_FILENO); + if (fd != STDIN_FILENO) + close(fd); +} + +ATF_TC(sig2str_before_end); +ATF_TC_HEAD(sig2str_before_end, tc) +{ +} +ATF_TC_BODY(sig2str_before_end, tc) +{ +#define BUF &__stack.__buf + struct { + uint8_t padding_l; + unsigned char __buf[SIG2STR_MAX + 1]; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(__stack.__buf); + const size_t __len = SIG2STR_MAX + 1; + const size_t __idx __unused = __len - 1; + + sig2str(1, __stack.__buf); +#undef BUF + +} + +ATF_TC(sig2str_end); +ATF_TC_HEAD(sig2str_end, tc) +{ +} +ATF_TC_BODY(sig2str_end, tc) +{ +#define BUF &__stack.__buf + struct { + uint8_t padding_l; + unsigned char __buf[SIG2STR_MAX]; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(__stack.__buf); + const size_t __len = SIG2STR_MAX; + const size_t __idx __unused = __len - 1; + + sig2str(1, __stack.__buf); +#undef BUF + +} + +ATF_TC(sig2str_heap_before_end); +ATF_TC_HEAD(sig2str_heap_before_end, tc) +{ +} +ATF_TC_BODY(sig2str_heap_before_end, tc) +{ +#define BUF __stack.__buf + struct { + uint8_t padding_l; + unsigned char * __buf; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(*__stack.__buf) * (SIG2STR_MAX + 1); + const size_t __len = SIG2STR_MAX + 1; + const size_t __idx __unused = __len - 1; + + __stack.__buf = malloc(__bufsz); + + sig2str(1, __stack.__buf); +#undef BUF + +} + +ATF_TC(sig2str_heap_end); +ATF_TC_HEAD(sig2str_heap_end, tc) +{ +} +ATF_TC_BODY(sig2str_heap_end, tc) +{ +#define BUF __stack.__buf + struct { + uint8_t padding_l; + unsigned char * __buf; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(*__stack.__buf) * (SIG2STR_MAX); + const size_t __len = SIG2STR_MAX; + const size_t __idx __unused = __len - 1; + + __stack.__buf = malloc(__bufsz); + + sig2str(1, __stack.__buf); +#undef BUF + +} + +ATF_TC(sig2str_heap_after_end); +ATF_TC_HEAD(sig2str_heap_after_end, tc) +{ +} +ATF_TC_BODY(sig2str_heap_after_end, tc) +{ +#define BUF __stack.__buf + struct { + uint8_t padding_l; + unsigned char * __buf; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(*__stack.__buf) * (SIG2STR_MAX - 1); + const size_t __len = SIG2STR_MAX - 1; + const size_t __idx __unused = __len - 1; + pid_t __child; + int __status; + + __child = fork(); + ATF_REQUIRE(__child >= 0); + if (__child > 0) + goto monitor; + + /* Child */ + disable_coredumps(); + __stack.__buf = malloc(__bufsz); + + sig2str(1, __stack.__buf); + _exit(EX_SOFTWARE); /* Should have aborted. */ + +monitor: + while (waitpid(__child, &__status, 0) != __child) { + ATF_REQUIRE_EQ(EINTR, errno); + } + + if (!WIFSIGNALED(__status)) { + switch (WEXITSTATUS(__status)) { + case EX_SOFTWARE: + atf_tc_fail("FORTIFY_SOURCE failed to abort"); + break; + case EX_OSERR: + atf_tc_fail("setrlimit(2) failed"); + break; + default: + atf_tc_fail("child exited with status %d", + WEXITSTATUS(__status)); + } + } else { + ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status)); + } +#undef BUF + +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, sig2str_before_end); + ATF_TP_ADD_TC(tp, sig2str_end); + ATF_TP_ADD_TC(tp, sig2str_heap_before_end); + ATF_TP_ADD_TC(tp, sig2str_heap_end); + ATF_TP_ADD_TC(tp, sig2str_heap_after_end); + return (atf_no_error()); +} diff --git a/lib/libc/tests/secure/fortify_stdlib_test.c b/lib/libc/tests/secure/fortify_stdlib_test.c index ae021e8418f7..d0b1af78da86 100644 --- a/lib/libc/tests/secure/fortify_stdlib_test.c +++ b/lib/libc/tests/secure/fortify_stdlib_test.c @@ -305,6 +305,148 @@ monitor: } +ATF_TC(getenv_r_before_end); +ATF_TC_HEAD(getenv_r_before_end, tc) +{ +} +ATF_TC_BODY(getenv_r_before_end, tc) +{ +#define BUF &__stack.__buf + struct { + uint8_t padding_l; + unsigned char __buf[42]; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(__stack.__buf); + const size_t __len = 42 - 1; + const size_t __idx __unused = __len - 1; + + getenv_r("PATH", __stack.__buf, __len); +#undef BUF + +} + +ATF_TC(getenv_r_end); +ATF_TC_HEAD(getenv_r_end, tc) +{ +} +ATF_TC_BODY(getenv_r_end, tc) +{ +#define BUF &__stack.__buf + struct { + uint8_t padding_l; + unsigned char __buf[42]; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(__stack.__buf); + const size_t __len = 42; + const size_t __idx __unused = __len - 1; + + getenv_r("PATH", __stack.__buf, __len); +#undef BUF + +} + +ATF_TC(getenv_r_heap_before_end); +ATF_TC_HEAD(getenv_r_heap_before_end, tc) +{ +} +ATF_TC_BODY(getenv_r_heap_before_end, tc) +{ +#define BUF __stack.__buf + struct { + uint8_t padding_l; + unsigned char * __buf; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42); + const size_t __len = 42 - 1; + const size_t __idx __unused = __len - 1; + + __stack.__buf = malloc(__bufsz); + + getenv_r("PATH", __stack.__buf, __len); +#undef BUF + +} + +ATF_TC(getenv_r_heap_end); +ATF_TC_HEAD(getenv_r_heap_end, tc) +{ +} +ATF_TC_BODY(getenv_r_heap_end, tc) +{ +#define BUF __stack.__buf + struct { + uint8_t padding_l; + unsigned char * __buf; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42); + const size_t __len = 42; + const size_t __idx __unused = __len - 1; + + __stack.__buf = malloc(__bufsz); + + getenv_r("PATH", __stack.__buf, __len); +#undef BUF + +} + +ATF_TC(getenv_r_heap_after_end); +ATF_TC_HEAD(getenv_r_heap_after_end, tc) +{ +} +ATF_TC_BODY(getenv_r_heap_after_end, tc) +{ +#define BUF __stack.__buf + struct { + uint8_t padding_l; + unsigned char * __buf; + uint8_t padding_r; + } __stack; + const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42); + const size_t __len = 42 + 1; + const size_t __idx __unused = __len - 1; + pid_t __child; + int __status; + + __child = fork(); + ATF_REQUIRE(__child >= 0); + if (__child > 0) + goto monitor; + + /* Child */ + disable_coredumps(); + __stack.__buf = malloc(__bufsz); + + getenv_r("PATH", __stack.__buf, __len); + _exit(EX_SOFTWARE); /* Should have aborted. */ + +monitor: + while (waitpid(__child, &__status, 0) != __child) { + ATF_REQUIRE_EQ(EINTR, errno); + } + + if (!WIFSIGNALED(__status)) { + switch (WEXITSTATUS(__status)) { + case EX_SOFTWARE: + atf_tc_fail("FORTIFY_SOURCE failed to abort"); + break; + case EX_OSERR: + atf_tc_fail("setrlimit(2) failed"); + break; + default: + atf_tc_fail("child exited with status %d", + WEXITSTATUS(__status)); + } + } else { + ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status)); + } +#undef BUF + +} + ATF_TC(realpath_before_end); ATF_TC_HEAD(realpath_before_end, tc) { @@ -454,6 +596,11 @@ ATF_TP_ADD_TCS(tp) ATF_TP_ADD_TC(tp, arc4random_buf_heap_before_end); ATF_TP_ADD_TC(tp, arc4random_buf_heap_end); ATF_TP_ADD_TC(tp, arc4random_buf_heap_after_end); + ATF_TP_ADD_TC(tp, getenv_r_before_end); + ATF_TP_ADD_TC(tp, getenv_r_end); + ATF_TP_ADD_TC(tp, getenv_r_heap_before_end); + ATF_TP_ADD_TC(tp, getenv_r_heap_end); + ATF_TP_ADD_TC(tp, getenv_r_heap_after_end); ATF_TP_ADD_TC(tp, realpath_before_end); ATF_TP_ADD_TC(tp, realpath_end); ATF_TP_ADD_TC(tp, realpath_heap_before_end); diff --git a/lib/libc/tests/secure/generate-fortify-tests.lua b/lib/libc/tests/secure/generate-fortify-tests.lua index 36ff01af7a17..c9cd9353a869 100755 --- a/lib/libc/tests/secure/generate-fortify-tests.lua +++ b/lib/libc/tests/secure/generate-fortify-tests.lua @@ -478,6 +478,18 @@ local all_tests = { init = poll_init, }, }, + signal = { + -- <signal.h> + { + func = "sig2str", + bufsize = "SIG2STR_MAX", + arguments = { + "1", + "__buf", + }, + exclude = excludes_stack_overflow, + }, + }, stdio = { -- <stdio.h> { @@ -585,6 +597,15 @@ local all_tests = { exclude = excludes_stack_overflow, }, { + func = "getenv_r", + arguments = { + "\"PATH\"", + "__buf", + "__len", + }, + exclude = excludes_stack_overflow, + }, + { func = "realpath", bufsize = "PATH_MAX", arguments = { diff --git a/lib/libc/tests/stdio/flushlbuf_test.c b/lib/libc/tests/stdio/flushlbuf_test.c index 11d9ea4ecc6c..3f8a6378f933 100644 --- a/lib/libc/tests/stdio/flushlbuf_test.c +++ b/lib/libc/tests/stdio/flushlbuf_test.c @@ -22,22 +22,23 @@ struct stream { unsigned int pos; }; -static int writefn(void *cookie, const char *buf, int len) +static int +writefn(void *cookie, const char *buf, int len) { struct stream *s = cookie; int written = 0; if (len <= 0) - return 0; + return (0); while (len > 0 && s->pos < s->len) { s->buf[s->pos++] = *buf++; written++; len--; } if (written > 0) - return written; + return (written); errno = EAGAIN; - return -1; + return (-1); } ATF_TC_WITHOUT_HEAD(flushlbuf_partial); @@ -147,7 +148,6 @@ ATF_TC_BODY(flushlbuf_full, tc) ATF_TP_ADD_TCS(tp) { - ATF_TP_ADD_TC(tp, flushlbuf_partial); ATF_TP_ADD_TC(tp, flushlbuf_full); diff --git a/lib/libc/tests/stdio/scanfloat_test.c b/lib/libc/tests/stdio/scanfloat_test.c index a93238504dd1..635d93d560b3 100644 --- a/lib/libc/tests/stdio/scanfloat_test.c +++ b/lib/libc/tests/stdio/scanfloat_test.c @@ -59,88 +59,88 @@ ATF_TC_BODY(normalized_numbers, tc) buf[0] = '\0'; ATF_REQUIRE(setlocale(LC_NUMERIC, "")); - sscanf("3.141592", "%e", &f); + ATF_REQUIRE_EQ(1, sscanf("3.141592", "%e", &f)); ATF_REQUIRE(eq(FLT, f, 3.141592)); - sscanf("3.141592653589793", "%lf", &d); + ATF_REQUIRE_EQ(1, sscanf("3.141592653589793", "%lf", &d)); ATF_REQUIRE(eq(DBL, d, 3.141592653589793)); - sscanf("1.234568e+06", "%E", &f); + ATF_REQUIRE_EQ(1, sscanf("1.234568e+06", "%E", &f)); ATF_REQUIRE(eq(FLT, f, 1.234568e+06)); - sscanf("-1.234568e6", "%lF", &d); + ATF_REQUIRE_EQ(1, sscanf("-1.234568e6", "%lF", &d)); ATF_REQUIRE(eq(DBL, d, -1.234568e6)); - sscanf("+1.234568e-52", "%LG", &ld); + ATF_REQUIRE_EQ(1, sscanf("+1.234568e-52", "%LG", &ld)); ATF_REQUIRE(eq(LDBL, ld, 1.234568e-52L)); - sscanf("0.1", "%la", &d); + ATF_REQUIRE_EQ(1, sscanf("0.1", "%la", &d)); ATF_REQUIRE(eq(DBL, d, 0.1)); - sscanf("00.2", "%lA", &d); + ATF_REQUIRE_EQ(1, sscanf("00.2", "%lA", &d)); ATF_REQUIRE(eq(DBL, d, 0.2)); - sscanf("123456", "%5le%s", &d, buf); + ATF_REQUIRE_EQ(2, sscanf("123456", "%5le%s", &d, buf)); ATF_REQUIRE(eq(DBL, d, 12345.)); ATF_REQUIRE(strcmp(buf, "6") == 0); - sscanf("1.0Q", "%*5le%s", buf); + ATF_REQUIRE_EQ(1, sscanf("1.0Q", "%*5le%s", buf)); ATF_REQUIRE(strcmp(buf, "Q") == 0); - sscanf("-1.23e", "%e%s", &f, buf); + ATF_REQUIRE_EQ(2, sscanf("-1.23e", "%e%s", &f, buf)); ATF_REQUIRE(eq(FLT, f, -1.23)); ATF_REQUIRE(strcmp(buf, "e") == 0); - sscanf("1.25e+", "%le%s", &d, buf); + ATF_REQUIRE_EQ(2, sscanf("1.25e+", "%le%s", &d, buf)); ATF_REQUIRE(eq(DBL, d, 1.25)); ATF_REQUIRE(strcmp(buf, "e+") == 0); - sscanf("1.23E4E5", "%le%s", &d, buf); + ATF_REQUIRE_EQ(2, sscanf("1.23E4E5", "%le%s", &d, buf)); ATF_REQUIRE(eq(DBL, d, 1.23e4)); ATF_REQUIRE(strcmp(buf, "E5") == 0); - sscanf("12e6", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("12e6", "%le", &d)); ATF_REQUIRE(eq(DBL, d, 12e6)); - sscanf("1.a", "%le%s", &d, buf); + ATF_REQUIRE_EQ(2, sscanf("1.a", "%le%s", &d, buf)); ATF_REQUIRE(eq(DBL, d, 1.0)); ATF_REQUIRE(strcmp(buf, "a") == 0); - sscanf(".0p4", "%le%s", &d, buf); + ATF_REQUIRE_EQ(2, sscanf(".0p4", "%le%s", &d, buf)); ATF_REQUIRE(eq(DBL, d, 0.0)); ATF_REQUIRE(strcmp(buf, "p4") == 0); d = 0.25; - ATF_REQUIRE(sscanf(".", "%le", &d) == 0); + ATF_REQUIRE_EQ(0, sscanf(".", "%le", &d)); ATF_REQUIRE(d == 0.25); - sscanf("0x08", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("0x08", "%le", &d)); ATF_REQUIRE(d == 0x8p0); - sscanf("0x90a.bcdefP+09a", "%le%s", &d, buf); + ATF_REQUIRE_EQ(2, sscanf("0x90a.bcdefP+09a", "%le%s", &d, buf)); ATF_REQUIRE(d == 0x90a.bcdefp+09); ATF_REQUIRE(strcmp(buf, "a") == 0); #if (LDBL_MANT_DIG > DBL_MANT_DIG) && !defined(__i386__) - sscanf("3.14159265358979323846", "%Lg", &ld); + ATF_REQUIRE_EQ(1, sscanf("3.14159265358979323846", "%Lg", &ld)); ATF_REQUIRE(eq(LDBL, ld, 3.14159265358979323846L)); - sscanf(" 0X.0123456789abcdefffp-3g", "%Le%s", &ld, buf); + ATF_REQUIRE_EQ(2, sscanf(" 0X.0123456789abcdefffp-3g", "%Le%s", &ld, buf)); ATF_REQUIRE(ld == 0x0.0123456789abcdefffp-3L); ATF_REQUIRE(strcmp(buf, "g") == 0); #endif - sscanf("0xg", "%le%s", &d, buf); + ATF_REQUIRE_EQ(2, sscanf("0xg", "%le%s", &d, buf)); ATF_REQUIRE(d == 0.0); ATF_REQUIRE(strcmp(buf, "xg") == 0); ATF_REQUIRE(setlocale(LC_NUMERIC, "ru_RU.ISO8859-5")); /* decimalpoint==, */ - sscanf("1.23", "%le%s", &d, buf); + ATF_REQUIRE_EQ(2, sscanf("1.23", "%le%s", &d, buf)); ATF_REQUIRE(d == 1.0); ATF_REQUIRE(strcmp(buf, ".23") == 0); - sscanf("1,23", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("1,23", "%le", &d)); ATF_REQUIRE(d == 1.23); ATF_REQUIRE(setlocale(LC_NUMERIC, "")); @@ -156,40 +156,40 @@ ATF_TC_BODY(infinities_and_nans, tc) ATF_REQUIRE(setlocale(LC_NUMERIC, "C")); - sscanf("-Inf", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("-Inf", "%le", &d)); ATF_REQUIRE(d < 0.0 && isinf(d)); - sscanf("iNfInItY and beyond", "%le%s", &d, buf); + ATF_REQUIRE_EQ(2, sscanf("iNfInItY and beyond", "%le%s", &d, buf)); ATF_REQUIRE(d > 0.0 && isinf(d)); ATF_REQUIRE(strcmp(buf, " and beyond")); - sscanf("NaN", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("NaN", "%le", &d)); ATF_REQUIRE(isnan(d)); - sscanf("NAN(123Y", "%le%s", &d, buf); + ATF_REQUIRE_EQ(2, sscanf("NAN(123Y", "%le%s", &d, buf)); ATF_REQUIRE(isnan(d)); ATF_REQUIRE(strcmp(buf, "(123Y") == 0); - sscanf("nan(f00f)plugh", "%le%s", &d, buf); + ATF_REQUIRE_EQ(2, sscanf("nan(f00f)plugh", "%le%s", &d, buf)); ATF_REQUIRE(isnan(d)); ATF_REQUIRE(strcmp(buf, "plugh") == 0); - sscanf("-nan", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("-nan", "%le", &d)); ATF_REQUIRE(isnan(d)); /* Only quiet NaNs should be returned. */ - sscanf("NaN", "%e", &f); - sscanf("nan", "%le", &d); - sscanf("nan", "%Le", &ld); - feclearexcept(FE_ALL_EXCEPT); + ATF_REQUIRE_EQ(1, sscanf("NaN", "%e", &f)); + ATF_REQUIRE_EQ(1, sscanf("nan", "%le", &d)); + ATF_REQUIRE_EQ(1, sscanf("nan", "%Le", &ld)); + ATF_REQUIRE_EQ(0, feclearexcept(FE_ALL_EXCEPT)); ATF_REQUIRE(f != f); ATF_REQUIRE(d != d); ATF_REQUIRE(ld != ld); ATF_REQUIRE(fetestexcept(FE_INVALID) == 0); - sscanf("nan(1234)", "%e", &f); - sscanf("nan(1234)", "%le", &d); - sscanf("nan(1234)", "%Le", &ld); - feclearexcept(FE_ALL_EXCEPT); + ATF_REQUIRE_EQ(1, sscanf("nan(1234)", "%e", &f)); + ATF_REQUIRE_EQ(1, sscanf("nan(1234)", "%le", &d)); + ATF_REQUIRE_EQ(1, sscanf("nan(1234)", "%Le", &ld)); + ATF_REQUIRE_EQ(0, feclearexcept(FE_ALL_EXCEPT)); ATF_REQUIRE(f != f); ATF_REQUIRE(d != d); ATF_REQUIRE(ld != ld); @@ -205,74 +205,74 @@ ATF_TC_BODY(rounding_tests, tc) ATF_REQUIRE(setlocale(LC_NUMERIC, "C")); - fesetround(FE_DOWNWARD); + ATF_REQUIRE_EQ(0, fesetround(FE_DOWNWARD)); - sscanf("1.999999999999999999999999999999999", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("1.999999999999999999999999999999999", "%le", &d)); ATF_REQUIRE(d < 2.0); - sscanf("0x1.ffffffffffffffp0", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("0x1.ffffffffffffffp0", "%le", &d)); ATF_REQUIRE(d < 2.0); - sscanf("1.999999999999999999999999999999999", "%Le", &ld); + ATF_REQUIRE_EQ(1, sscanf("1.999999999999999999999999999999999", "%Le", &ld)); ATF_REQUIRE(ld < 2.0); - sscanf("1.0571892669084007", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("1.0571892669084007", "%le", &d)); ATF_REQUIRE(d == 0x1.0ea3f4af0dc59p0); - sscanf("-1.0571892669084007", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("-1.0571892669084007", "%le", &d)); ATF_REQUIRE(d == -0x1.0ea3f4af0dc5ap0); - sscanf("1.0571892669084010", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("1.0571892669084010", "%le", &d)); ATF_REQUIRE(d == 0x1.0ea3f4af0dc5ap0); - sscanf("0x1.23p-5000", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("0x1.23p-5000", "%le", &d)); ATF_REQUIRE(d == 0.0); - sscanf("0x1.2345678p-1050", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("0x1.2345678p-1050", "%le", &d)); ATF_REQUIRE(d == 0x1.234567p-1050); - fesetround(FE_UPWARD); + ATF_REQUIRE_EQ(0, fesetround(FE_UPWARD)); - sscanf("1.0571892669084007", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("1.0571892669084007", "%le", &d)); ATF_REQUIRE(d == 0x1.0ea3f4af0dc5ap0); - sscanf("-1.0571892669084007", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("-1.0571892669084007", "%le", &d)); ATF_REQUIRE(d == -0x1.0ea3f4af0dc59p0); - sscanf("1.0571892669084010", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("1.0571892669084010", "%le", &d)); ATF_REQUIRE(d == 0x1.0ea3f4af0dc5bp0); - sscanf("0x1.23p-5000", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("0x1.23p-5000", "%le", &d)); ATF_REQUIRE(d == 0x1p-1074); - sscanf("0x1.2345678p-1050", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("0x1.2345678p-1050", "%le", &d)); ATF_REQUIRE(d == 0x1.234568p-1050); - fesetround(FE_TOWARDZERO); + ATF_REQUIRE_EQ(0, fesetround(FE_TOWARDZERO)); - sscanf("1.0571892669084007", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("1.0571892669084007", "%le", &d)); ATF_REQUIRE(d == 0x1.0ea3f4af0dc59p0); - sscanf("-1.0571892669084007", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("-1.0571892669084007", "%le", &d)); ATF_REQUIRE(d == -0x1.0ea3f4af0dc59p0); - sscanf("1.0571892669084010", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("1.0571892669084010", "%le", &d)); ATF_REQUIRE(d == 0x1.0ea3f4af0dc5ap0); - sscanf("0x1.23p-5000", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("0x1.23p-5000", "%le", &d)); ATF_REQUIRE(d == 0.0); - sscanf("0x1.2345678p-1050", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("0x1.2345678p-1050", "%le", &d)); ATF_REQUIRE(d == 0x1.234567p-1050); - fesetround(FE_TONEAREST); + ATF_REQUIRE_EQ(0, fesetround(FE_TONEAREST)); /* 1.0571892669084007 is slightly closer to 0x1.0ea3f4af0dc59p0 */ - sscanf("1.0571892669084007", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("1.0571892669084007", "%le", &d)); ATF_REQUIRE(d == 0x1.0ea3f4af0dc59p0); - sscanf("-1.0571892669084007", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("-1.0571892669084007", "%le", &d)); ATF_REQUIRE(d == -0x1.0ea3f4af0dc59p0); - sscanf("1.0571892669084010", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("1.0571892669084010", "%le", &d)); ATF_REQUIRE(d == 0x1.0ea3f4af0dc5bp0); /* strtod() should round small numbers to 0. */ - sscanf("0x1.23p-5000", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("0x1.23p-5000", "%le", &d)); ATF_REQUIRE(d == 0.0); /* Extra digits in a denormal shouldn't break anything. */ - sscanf("0x1.2345678p-1050", "%le", &d); + ATF_REQUIRE_EQ(1, sscanf("0x1.2345678p-1050", "%le", &d)); ATF_REQUIRE(d == 0x1.234568p-1050); } @@ -287,16 +287,16 @@ ATF_TC_BODY(strtod, tc) ATF_REQUIRE(strcmp("xy", endp) == 0); /* This used to cause an infinite loop and round the wrong way. */ - fesetround(FE_DOWNWARD); + ATF_REQUIRE_EQ(0, fesetround(FE_DOWNWARD)); ATF_REQUIRE(strtof("3.5e38", &endp) == FLT_MAX); ATF_REQUIRE(strtod("2e308", &endp) == DBL_MAX); - fesetround(FE_UPWARD); + ATF_REQUIRE_EQ(0, fesetround(FE_UPWARD)); ATF_REQUIRE(strtof("3.5e38", &endp) == INFINITY); ATF_REQUIRE(strtod("2e308", &endp) == INFINITY); - fesetround(FE_TOWARDZERO); + ATF_REQUIRE_EQ(0, fesetround(FE_TOWARDZERO)); ATF_REQUIRE(strtof("3.5e38", &endp) == FLT_MAX); ATF_REQUIRE(strtod("2e308", &endp) == DBL_MAX); - fesetround(FE_TONEAREST); + ATF_REQUIRE_EQ(0, fesetround(FE_TONEAREST)); ATF_REQUIRE(strtof("3.5e38", &endp) == INFINITY); ATF_REQUIRE(strtod("2e308", &endp) == INFINITY); } diff --git a/lib/libc/tests/stdio/sscanf_test.c b/lib/libc/tests/stdio/sscanf_test.c index e916873d38c3..e43292820eeb 100644 --- a/lib/libc/tests/stdio/sscanf_test.c +++ b/lib/libc/tests/stdio/sscanf_test.c @@ -68,6 +68,31 @@ static const struct sscanf_test_case { { "0e", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 14, 2 }, { 1, 0, 1 }, }, { "0f", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 15, 2 }, { 1, 0, 1 }, }, { "0x", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, }, + // all digits with two leading zeroes + { "000", { 1, 0, 3 }, { 1, 0, 3 }, { 1, 0, 3 }, { 1, 0, 3 }, { 1, 0, 3 }, }, + { "001", { 1, 1, 3 }, { 1, 1, 3 }, { 1, 1, 3 }, { 1, 1, 3 }, { 1, 1, 3 }, }, + { "002", { 1, 0, 2 }, { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 }, }, + { "003", { 1, 0, 2 }, { 1, 3, 3 }, { 1, 3, 3 }, { 1, 3, 3 }, { 1, 3, 3 }, }, + { "004", { 1, 0, 2 }, { 1, 4, 3 }, { 1, 4, 3 }, { 1, 4, 3 }, { 1, 4, 3 }, }, + { "005", { 1, 0, 2 }, { 1, 5, 3 }, { 1, 5, 3 }, { 1, 5, 3 }, { 1, 5, 3 }, }, + { "006", { 1, 0, 2 }, { 1, 6, 3 }, { 1, 6, 3 }, { 1, 6, 3 }, { 1, 6, 3 }, }, + { "007", { 1, 0, 2 }, { 1, 7, 3 }, { 1, 7, 3 }, { 1, 7, 3 }, { 1, 7, 3 }, }, + { "008", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 8, 3 }, { 1, 8, 3 }, { 1, 0, 2 }, }, + { "009", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 9, 3 }, { 1, 9, 3 }, { 1, 0, 2 }, }, + { "00A", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 10, 3 }, { 1, 0, 2 }, }, + { "00B", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 11, 3 }, { 1, 0, 2 }, }, + { "00C", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 12, 3 }, { 1, 0, 2 }, }, + { "00D", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 13, 3 }, { 1, 0, 2 }, }, + { "00E", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 14, 3 }, { 1, 0, 2 }, }, + { "00F", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 15, 3 }, { 1, 0, 2 }, }, + { "00X", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, }, + { "00a", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 10, 3 }, { 1, 0, 2 }, }, + { "00b", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 11, 3 }, { 1, 0, 2 }, }, + { "00c", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 12, 3 }, { 1, 0, 2 }, }, + { "00d", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 13, 3 }, { 1, 0, 2 }, }, + { "00e", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 14, 3 }, { 1, 0, 2 }, }, + { "00f", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 15, 3 }, { 1, 0, 2 }, }, + { "00x", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, }, // all digits with leading one { "10", { 1, 2, 2 }, { 1, 8, 2 }, { 1, 10, 2 }, { 1, 16, 2 }, { 1, 10, 2 }, }, { "11", { 1, 3, 2 }, { 1, 9, 2 }, { 1, 11, 2 }, { 1, 17, 2 }, { 1, 11, 2 }, }, diff --git a/lib/libc/tests/stdio/swscanf_test.c b/lib/libc/tests/stdio/swscanf_test.c index f7ad30b963a7..f0638081e16f 100644 --- a/lib/libc/tests/stdio/swscanf_test.c +++ b/lib/libc/tests/stdio/swscanf_test.c @@ -71,6 +71,31 @@ static const struct swscanf_test_case { { L"0e", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 14, 2 }, { 1, 0, 1 }, }, { L"0f", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 15, 2 }, { 1, 0, 1 }, }, { L"0x", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, }, + // all digits with two leading zeroes + { L"000", { 1, 0, 3 }, { 1, 0, 3 }, { 1, 0, 3 }, { 1, 0, 3 }, { 1, 0, 3 }, }, + { L"001", { 1, 1, 3 }, { 1, 1, 3 }, { 1, 1, 3 }, { 1, 1, 3 }, { 1, 1, 3 }, }, + { L"002", { 1, 0, 2 }, { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 }, }, + { L"003", { 1, 0, 2 }, { 1, 3, 3 }, { 1, 3, 3 }, { 1, 3, 3 }, { 1, 3, 3 }, }, + { L"004", { 1, 0, 2 }, { 1, 4, 3 }, { 1, 4, 3 }, { 1, 4, 3 }, { 1, 4, 3 }, }, + { L"005", { 1, 0, 2 }, { 1, 5, 3 }, { 1, 5, 3 }, { 1, 5, 3 }, { 1, 5, 3 }, }, + { L"006", { 1, 0, 2 }, { 1, 6, 3 }, { 1, 6, 3 }, { 1, 6, 3 }, { 1, 6, 3 }, }, + { L"007", { 1, 0, 2 }, { 1, 7, 3 }, { 1, 7, 3 }, { 1, 7, 3 }, { 1, 7, 3 }, }, + { L"008", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 8, 3 }, { 1, 8, 3 }, { 1, 0, 2 }, }, + { L"009", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 9, 3 }, { 1, 9, 3 }, { 1, 0, 2 }, }, + { L"00A", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 10, 3 }, { 1, 0, 2 }, }, + { L"00B", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 11, 3 }, { 1, 0, 2 }, }, + { L"00C", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 12, 3 }, { 1, 0, 2 }, }, + { L"00D", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 13, 3 }, { 1, 0, 2 }, }, + { L"00E", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 14, 3 }, { 1, 0, 2 }, }, + { L"00F", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 15, 3 }, { 1, 0, 2 }, }, + { L"00X", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, }, + { L"00a", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 10, 3 }, { 1, 0, 2 }, }, + { L"00b", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 11, 3 }, { 1, 0, 2 }, }, + { L"00c", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 12, 3 }, { 1, 0, 2 }, }, + { L"00d", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 13, 3 }, { 1, 0, 2 }, }, + { L"00e", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 14, 3 }, { 1, 0, 2 }, }, + { L"00f", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 15, 3 }, { 1, 0, 2 }, }, + { L"00x", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, }, // all digits with leading one { L"10", { 1, 2, 2 }, { 1, 8, 2 }, { 1, 10, 2 }, { 1, 16, 2 }, { 1, 10, 2 }, }, { L"11", { 1, 3, 2 }, { 1, 9, 2 }, { 1, 11, 2 }, { 1, 17, 2 }, { 1, 11, 2 }, }, diff --git a/lib/libc/tests/stdlib/Makefile b/lib/libc/tests/stdlib/Makefile index 6f57b8014a1e..50726a5d8af6 100644 --- a/lib/libc/tests/stdlib/Makefile +++ b/lib/libc/tests/stdlib/Makefile @@ -1,12 +1,14 @@ .include <src.opts.mk> ATF_TESTS_C+= clearenv_test +ATF_TESTS_C+= cxa_atexit_test ATF_TESTS_C+= dynthr_test +ATF_TESTS_C+= getenv_r_test ATF_TESTS_C+= heapsort_test ATF_TESTS_C+= libc_exit_test ATF_TESTS_C+= mergesort_test ATF_TESTS_C+= qsort_test -.if ${COMPILER_TYPE} == "clang" +.if ${COMPILER_FEATURES:Mblocks} ATF_TESTS_C+= qsort_b_test .endif ATF_TESTS_C+= qsort_r_compat_test @@ -58,11 +60,9 @@ PROGS+= h_getopt h_getopt_long CFLAGS+= -I${.CURDIR} -CXXSTD.cxa_thread_atexit_test= c++11 -CXXSTD.cxa_thread_atexit_nothr_test= c++11 LIBADD.cxa_thread_atexit_test+= pthread -# Tests that requires Blocks feature +# Tests that require blocks support .for t in qsort_b_test CFLAGS.${t}.c+= -fblocks LIBADD.${t}+= BlocksRuntime @@ -79,5 +79,6 @@ LIBADD.libc_exit_test+= pthread LIBADD.strtod_test+= m SUBDIR+= dynthr_mod +SUBDIR+= libatexit .include <bsd.test.mk> diff --git a/lib/libc/tests/stdlib/cxa_atexit_test.c b/lib/libc/tests/stdlib/cxa_atexit_test.c new file mode 100644 index 000000000000..7e2cafbce850 --- /dev/null +++ b/lib/libc/tests/stdlib/cxa_atexit_test.c @@ -0,0 +1,132 @@ +/*- + * Copyright (c) 2025 Kyle Evans <kevans@FreeBSD.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/wait.h> + +#include <dlfcn.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> + +#include <atf-c.h> + +#define ARBITRARY_EXIT_CODE 42 + +static char * +get_shlib(const char *srcdir) +{ + char *shlib; + + shlib = NULL; + if (asprintf(&shlib, "%s/libatexit.so", srcdir) < 0) + atf_tc_fail("failed to construct path to libatexit.so"); + return (shlib); +} + +static void +run_test(const atf_tc_t *tc, bool with_fatal_atexit, bool with_exit) +{ + pid_t p; + void (*set_fatal_atexit)(bool); + void (*set_exit_code)(int); + void *hdl; + char *shlib; + + shlib = get_shlib(atf_tc_get_config_var(tc, "srcdir")); + + hdl = dlopen(shlib, RTLD_LAZY); + ATF_REQUIRE_MSG(hdl != NULL, "dlopen: %s", dlerror()); + + free(shlib); + + if (with_fatal_atexit) { + set_fatal_atexit = dlsym(hdl, "set_fatal_atexit"); + ATF_REQUIRE_MSG(set_fatal_atexit != NULL, + "set_fatal_atexit: %s", dlerror()); + } + if (with_exit) { + set_exit_code = dlsym(hdl, "set_exit_code"); + ATF_REQUIRE_MSG(set_exit_code != NULL, "set_exit_code: %s", + dlerror()); + } + + p = atf_utils_fork(); + if (p == 0) { + /* + * Don't let the child clobber the results file; stderr/stdout + * have been replaced by atf_utils_fork() to capture it. We're + * intentionally using exit() instead of _exit() here to run + * __cxa_finalize at exit, otherwise we'd just leave it be. + */ + closefrom(3); + + if (with_fatal_atexit) + set_fatal_atexit(true); + if (with_exit) + set_exit_code(ARBITRARY_EXIT_CODE); + + dlclose(hdl); + + /* + * If the dtor was supposed to exit (most cases), then we should + * not have made it to this point. If it's not supposed to + * exit, then we just exit with success here because we might + * be expecting either a clean exit or a signal on our way out + * as the final __cxa_finalize tries to run a callback in the + * unloaded DSO. + */ + if (with_exit) + exit(1); + exit(0); + } + + dlclose(hdl); + atf_utils_wait(p, with_exit ? ARBITRARY_EXIT_CODE : 0, "", ""); +} + +ATF_TC_WITHOUT_HEAD(simple_cxa_atexit); +ATF_TC_BODY(simple_cxa_atexit, tc) +{ + /* + * This test exits in a global object's dtor so that we check for our + * dtor being run at dlclose() time. If it isn't, then the forked child + * will have a chance to exit(1) after dlclose() to raise a failure. + */ + run_test(tc, false, true); +} + +ATF_TC_WITHOUT_HEAD(late_cxa_atexit); +ATF_TC_BODY(late_cxa_atexit, tc) +{ + /* + * This test creates another global object during a __cxa_atexit handler + * invocation. It's been observed in the wild that we weren't executing + * it, then the DSO gets torn down and it was executed at application + * exit time instead. In the best case scenario we would crash if + * something else hadn't been mapped there. + */ + run_test(tc, true, false); +} + +ATF_TC_WITHOUT_HEAD(late_cxa_atexit_ran); +ATF_TC_BODY(late_cxa_atexit_ran, tc) +{ + /* + * This is a slight variation of the previous test where we trigger an + * exit() in our late-registered __cxa_atexit handler so that we can + * ensure it was ran *before* dlclose() finished and not through some + * weird chain of events afterwards. + */ + run_test(tc, true, true); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, simple_cxa_atexit); + ATF_TP_ADD_TC(tp, late_cxa_atexit); + ATF_TP_ADD_TC(tp, late_cxa_atexit_ran); + return (atf_no_error()); +} diff --git a/lib/libc/tests/stdlib/getenv_r_test.c b/lib/libc/tests/stdlib/getenv_r_test.c new file mode 100644 index 000000000000..8085b92b1064 --- /dev/null +++ b/lib/libc/tests/stdlib/getenv_r_test.c @@ -0,0 +1,69 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <atf-c.h> + +ATF_TC_WITHOUT_HEAD(getenv_r_ok); +ATF_TC_BODY(getenv_r_ok, tc) +{ + const char *ident = atf_tc_get_ident(tc); + char buf[256]; + + ATF_REQUIRE_EQ(0, setenv("ATF_TC_IDENT", ident, 1)); + ATF_REQUIRE_EQ(0, getenv_r("ATF_TC_IDENT", buf, sizeof(buf))); + ATF_REQUIRE_STREQ(ident, buf); +} + +ATF_TC_WITHOUT_HEAD(getenv_r_einval); +ATF_TC_BODY(getenv_r_einval, tc) +{ + char buf[256]; + + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r(NULL, buf, sizeof(buf))); + ATF_REQUIRE_EQ(EINVAL, errno); + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r("", buf, sizeof(buf))); + ATF_REQUIRE_EQ(EINVAL, errno); + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r("A=B", buf, sizeof(buf))); + ATF_REQUIRE_EQ(EINVAL, errno); +} + +ATF_TC_WITHOUT_HEAD(getenv_r_enoent); +ATF_TC_BODY(getenv_r_enoent, tc) +{ + char buf[256]; + + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r("no such variable", buf, sizeof(buf))); + ATF_REQUIRE_EQ(ENOENT, errno); +} + +ATF_TC_WITHOUT_HEAD(getenv_r_erange); +ATF_TC_BODY(getenv_r_erange, tc) +{ + const char *ident = atf_tc_get_ident(tc); + char buf[256]; + + ATF_REQUIRE_EQ(0, setenv("ATF_TC_IDENT", ident, 1)); + errno = 0; + ATF_REQUIRE_EQ(-1, getenv_r("ATF_TC_IDENT", buf, strlen(ident))); + ATF_REQUIRE_EQ(ERANGE, errno); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, getenv_r_ok); + ATF_TP_ADD_TC(tp, getenv_r_einval); + ATF_TP_ADD_TC(tp, getenv_r_enoent); + ATF_TP_ADD_TC(tp, getenv_r_erange); + return (atf_no_error()); +} diff --git a/lib/libc/tests/stdlib/libatexit/Makefile b/lib/libc/tests/stdlib/libatexit/Makefile new file mode 100644 index 000000000000..9ba04c77af62 --- /dev/null +++ b/lib/libc/tests/stdlib/libatexit/Makefile @@ -0,0 +1,11 @@ +SHLIB_CXX= libatexit +SHLIB_NAME= libatexit.so +SHLIB_MAJOR= 1 +SHLIBDIR= ${TESTSDIR} +PACKAGE= tests +SRCS= libatexit.cc + +TESTSDIR:= ${TESTSBASE}/${RELDIR:C/libc\/tests/libc/:H} + + +.include <bsd.lib.mk> diff --git a/lib/libc/tests/stdlib/libatexit/libatexit.cc b/lib/libc/tests/stdlib/libatexit/libatexit.cc new file mode 100644 index 000000000000..bb286c97e421 --- /dev/null +++ b/lib/libc/tests/stdlib/libatexit/libatexit.cc @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2025 Kyle Evans <kevans@FreeBSD.org> + * + * SPDX-License-Identifier: BSD-2-Clause + * + */ + +#include <unistd.h> + +static int exit_code = -1; +static bool fatal_atexit; + +extern "C" { + void set_fatal_atexit(bool); + void set_exit_code(int); +} + +void +set_fatal_atexit(bool fexit) +{ + fatal_atexit = fexit; +} + +void +set_exit_code(int code) +{ + exit_code = code; +} + +struct other_object { + ~other_object() { + + /* + * In previous versions of our __cxa_atexit handling, we would + * never actually execute this handler because it's added during + * ~object() below; __cxa_finalize would never revisit it. We + * will allow the caller to configure us to exit with a certain + * exit code so that it can run us twice: once to ensure we + * don't crash at the end, and again to make sure the handler + * actually ran. + */ + if (exit_code != -1) + _exit(exit_code); + } +}; + +void +create_staticobj() +{ + static other_object obj; +} + +struct object { + ~object() { + /* + * If we're doing the fatal_atexit behavior (i.e., create an + * object that will add its own dtor for __cxa_finalize), then + * we don't exit here. + */ + if (fatal_atexit) + create_staticobj(); + else if (exit_code != -1) + _exit(exit_code); + } +}; + +static object obj; diff --git a/lib/libc/tests/stdtime/Makefile b/lib/libc/tests/stdtime/Makefile index c7a7f5b9436f..adb883cc5b9a 100644 --- a/lib/libc/tests/stdtime/Makefile +++ b/lib/libc/tests/stdtime/Makefile @@ -1,6 +1,9 @@ -.include <bsd.own.mk> +.include <src.opts.mk> ATF_TESTS_C+= strptime_test +.if ${MK_DETECT_TZ_CHANGES} != "no" +ATF_TESTS_C+= detect_tz_changes_test +.endif TESTSDIR:= ${TESTSBASE}/${RELDIR:C/libc\/tests/libc/} diff --git a/lib/libc/tests/stdtime/detect_tz_changes_test.c b/lib/libc/tests/stdtime/detect_tz_changes_test.c new file mode 100644 index 000000000000..9722546747fd --- /dev/null +++ b/lib/libc/tests/stdtime/detect_tz_changes_test.c @@ -0,0 +1,281 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/stat.h> +#include <sys/wait.h> + +#include <dlfcn.h> +#include <fcntl.h> +#include <limits.h> +#include <poll.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +#include <atf-c.h> + +static const time_t then = 1751328000; /* 2025-07-01 00:00:00 UTC */ +static const char *tz_change_interval_sym = "__tz_change_interval"; +static int *tz_change_interval_p; +static const int tz_change_interval = 3; +static int tz_change_timeout = 90; + +static bool debugging; + +static void +debug(const char *fmt, ...) +{ + va_list ap; + + if (debugging) { + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + } +} + +static void +change_tz(const char *tzn) +{ + static const char *zfn = "/usr/share/zoneinfo"; + static const char *tfn = "root/etc/.localtime"; + static const char *dfn = "root/etc/localtime"; + ssize_t clen; + int zfd, sfd, dfd; + + ATF_REQUIRE((zfd = open(zfn, O_DIRECTORY | O_SEARCH)) >= 0); + ATF_REQUIRE((sfd = openat(zfd, tzn, O_RDONLY)) >= 0); + ATF_REQUIRE((dfd = open(tfn, O_CREAT | O_TRUNC | O_WRONLY)) >= 0); + do { + clen = copy_file_range(sfd, NULL, dfd, NULL, SSIZE_MAX, 0); + ATF_REQUIRE_MSG(clen != -1, "failed to copy %s/%s: %m", + zfn, tzn); + } while (clen > 0); + ATF_CHECK_EQ(0, close(dfd)); + ATF_CHECK_EQ(0, close(sfd)); + ATF_CHECK_EQ(0, close(zfd)); + ATF_REQUIRE_EQ(0, rename(tfn, dfn)); + debug("time zone %s installed", tzn); +} + +/* + * Test time zone change detection. + * + * The parent creates a chroot containing only /etc/localtime, initially + * set to UTC. It then forks a child which enters the chroot, repeatedly + * checks the current time zone, and prints it to stdout if it changes + * (including once on startup). Meanwhile, the parent waits for output + * from the child. Every time it receives a line of text from the child, + * it checks that it is as expected, then changes /etc/localtime within + * the chroot to the next case in the list. Once it reaches the end of + * the list, it closes a pipe to notify the child, which terminates. + * + * Note that ATF and / or Kyua may have set the timezone before the test + * case starts (even unintentionally). Therefore, we start the test only + * after we've received and discarded the first report from the child, + * which should come almost immediately on startup. + */ +ATF_TC(detect_tz_changes); +ATF_TC_HEAD(detect_tz_changes, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test timezone change detection"); + atf_tc_set_md_var(tc, "require.user", "root"); + atf_tc_set_md_var(tc, "timeout", "600"); +} +ATF_TC_BODY(detect_tz_changes, tc) +{ + static const struct tzcase { + const char *tzfn; + const char *expect; + } tzcases[] = { + /* + * A handful of time zones and the expected result of + * strftime("%z (%Z)", tm) when that time zone is active + * and tm represents a date in the summer of 2025. + */ + { "America/Vancouver", "-0700 (PDT)" }, + { "America/New_York", "-0400 (EDT)" }, + { "Europe/London", "+0100 (BST)" }, + { "Europe/Paris", "+0200 (CEST)" }, + { "Asia/Kolkata", "+0530 (IST)" }, + { "Asia/Tokyo", "+0900 (JST)" }, + { "Australia/Canberra", "+1000 (AEST)" }, + { "UTC", "+0000 (UTC)" }, + { 0 }, + }; + char obuf[1024] = ""; + char ebuf[1024] = ""; + struct pollfd fds[3]; + int opd[2], epd[2], spd[2]; + time_t changed, now; + const struct tzcase *tzcase = NULL; + struct tm *tm; + size_t olen = 0, elen = 0; + ssize_t rlen; + long curoff = LONG_MIN; + pid_t pid; + int nfds, status; + + /* speed up the test if possible */ + tz_change_interval_p = dlsym(RTLD_SELF, tz_change_interval_sym); + if (tz_change_interval_p != NULL && + *tz_change_interval_p > tz_change_interval) { + debug("reducing detection interval from %d to %d", + *tz_change_interval_p, tz_change_interval); + *tz_change_interval_p = tz_change_interval; + tz_change_timeout = tz_change_interval * 3; + } + /* prepare chroot */ + ATF_REQUIRE_EQ(0, mkdir("root", 0755)); + ATF_REQUIRE_EQ(0, mkdir("root/etc", 0755)); + change_tz("UTC"); + time(&changed); + /* output, error, sync pipes */ + if (pipe(opd) != 0 || pipe(epd) != 0 || pipe(spd) != 0) + atf_tc_fail("failed to pipe"); + /* fork child */ + if ((pid = fork()) < 0) + atf_tc_fail("failed to fork"); + if (pid == 0) { + /* child */ + dup2(opd[1], STDOUT_FILENO); + close(opd[0]); + close(opd[1]); + dup2(epd[1], STDERR_FILENO); + close(epd[0]); + close(epd[1]); + close(spd[0]); + unsetenv("TZ"); + ATF_REQUIRE_EQ(0, chroot("root")); + ATF_REQUIRE_EQ(0, chdir("/")); + fds[0].fd = spd[1]; + fds[0].events = POLLIN; + for (;;) { + ATF_REQUIRE(poll(fds, 1, 100) >= 0); + if (fds[0].revents & POLLHUP) { + /* parent closed sync pipe */ + _exit(0); + } + ATF_REQUIRE((tm = localtime(&then)) != NULL); + if (tm->tm_gmtoff == curoff) + continue; + olen = strftime(obuf, sizeof(obuf), "%z (%Z)", tm); + ATF_REQUIRE(olen > 0); + fprintf(stdout, "%s\n", obuf); + fflush(stdout); + curoff = tm->tm_gmtoff; + } + _exit(2); + } + /* parent */ + close(opd[1]); + close(epd[1]); + close(spd[1]); + /* receive output until child terminates */ + fds[0].fd = opd[0]; + fds[0].events = POLLIN; + fds[1].fd = epd[0]; + fds[1].events = POLLIN; + fds[2].fd = spd[0]; + fds[2].events = POLLIN; + nfds = 3; + for (;;) { + ATF_REQUIRE(poll(fds, 3, 1000) >= 0); + time(&now); + if (fds[0].revents & POLLIN && olen < sizeof(obuf)) { + rlen = read(opd[0], obuf + olen, sizeof(obuf) - olen); + ATF_REQUIRE(rlen >= 0); + olen += rlen; + } + if (olen > 0) { + ATF_REQUIRE_EQ('\n', obuf[olen - 1]); + obuf[--olen] = '\0'; + /* tzcase will be NULL at first */ + if (tzcase != NULL) { + debug("%s", obuf); + ATF_REQUIRE_STREQ(tzcase->expect, obuf); + debug("change to %s detected after %d s", + tzcase->tzfn, (int)(now - changed)); + if (tz_change_interval_p != NULL) { + ATF_CHECK((int)(now - changed) >= + *tz_change_interval_p - 1); + ATF_CHECK((int)(now - changed) <= + *tz_change_interval_p + 1); + } + } + olen = 0; + /* first / next test case */ + if (tzcase == NULL) + tzcase = tzcases; + else + tzcase++; + if (tzcase->tzfn == NULL) { + /* test is over */ + break; + } + change_tz(tzcase->tzfn); + changed = now; + } + if (fds[1].revents & POLLIN && elen < sizeof(ebuf)) { + rlen = read(epd[0], ebuf + elen, sizeof(ebuf) - elen); + ATF_REQUIRE(rlen >= 0); + elen += rlen; + } + if (elen > 0) { + ATF_REQUIRE_EQ(elen, fwrite(ebuf, 1, elen, stderr)); + elen = 0; + } + if (nfds > 2 && fds[2].revents & POLLHUP) { + /* child closed sync pipe */ + break; + } + /* + * The timeout for this test case is set to 10 minutes, + * because it can take that long to run with the default + * 61-second interval. However, each individual tzcase + * entry should not take much longer than the detection + * interval to test, so we can detect a problem long + * before Kyua terminates us. + */ + if ((now - changed) > tz_change_timeout) { + close(spd[0]); + if (tz_change_interval_p == NULL && + tzcase == tzcases) { + /* + * The most likely explanation in this + * case is that libc was built without + * time zone change detection. + */ + atf_tc_skip("time zone change detection " + "does not appear to be enabled"); + } + atf_tc_fail("timed out waiting for change to %s " + "to be detected", tzcase->tzfn); + } + } + close(opd[0]); + close(epd[0]); + close(spd[0]); /* this will wake up and terminate the child */ + if (olen > 0) + ATF_REQUIRE_EQ(olen, fwrite(obuf, 1, olen, stdout)); + if (elen > 0) + ATF_REQUIRE_EQ(elen, fwrite(ebuf, 1, elen, stderr)); + ATF_REQUIRE_EQ(pid, waitpid(pid, &status, 0)); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE_EQ(0, WEXITSTATUS(status)); +} + +ATF_TP_ADD_TCS(tp) +{ + debugging = !getenv("__RUNNING_INSIDE_ATF_RUN") && + isatty(STDERR_FILENO); + ATF_TP_ADD_TC(tp, detect_tz_changes); + return (atf_no_error()); +} diff --git a/lib/libc/tests/sys/Makefile b/lib/libc/tests/sys/Makefile index 89d341ff400a..88f8191a16eb 100644 --- a/lib/libc/tests/sys/Makefile +++ b/lib/libc/tests/sys/Makefile @@ -7,11 +7,11 @@ ATF_TESTS_C+= brk_test .endif ATF_TESTS_C+= cpuset_test ATF_TESTS_C+= errno_test +ATF_TESTS_C+= swapcontext_test ATF_TESTS_C+= queue_test ATF_TESTS_C+= sendfile_test -# TODO: clone, lwp_create, lwp_ctl, posix_fadvise, recvmmsg, -# swapcontext +# TODO: clone, lwp_create, lwp_ctl, posix_fadvise, recvmmsg NETBSD_ATF_TESTS_C+= access_test NETBSD_ATF_TESTS_C+= bind_test NETBSD_ATF_TESTS_C+= chroot_test diff --git a/lib/libc/tests/sys/swapcontext_test.c b/lib/libc/tests/sys/swapcontext_test.c new file mode 100644 index 000000000000..f341a746e515 --- /dev/null +++ b/lib/libc/tests/sys/swapcontext_test.c @@ -0,0 +1,63 @@ +/*- + * Copyright (c) 2025 Raptor Computing Systems, LLC + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <stdio.h> +#include <stdlib.h> +#include <ucontext.h> +#include <errno.h> + +#include <atf-c.h> + +#define STACK_SIZE (64ull << 10) + +static volatile int callback_reached = 0; + +static ucontext_t uctx_save, uctx_switch; + +static void swapcontext_callback() +{ + // Increment callback reached variable + // If this is called multiple times, we will fail the test + // If this is not called at all, we will fail the test + callback_reached++; +} + +ATF_TC(swapcontext_basic); +ATF_TC_HEAD(swapcontext_basic, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Verify basic functionality of swapcontext"); +} + +ATF_TC_BODY(swapcontext_basic, tc) +{ + char *stack; + int res; + + stack = malloc(STACK_SIZE); + ATF_REQUIRE_MSG(stack != NULL, "malloc failed: %s", strerror(errno)); + res = getcontext(&uctx_switch); + ATF_REQUIRE_MSG(res == 0, "getcontext failed: %s", strerror(errno)); + + uctx_switch.uc_stack.ss_sp = stack; + uctx_switch.uc_stack.ss_size = STACK_SIZE; + uctx_switch.uc_link = &uctx_save; + makecontext(&uctx_switch, swapcontext_callback, 0); + + res = swapcontext(&uctx_save, &uctx_switch); + + ATF_REQUIRE_MSG(res == 0, "swapcontext failed: %s", strerror(errno)); + ATF_REQUIRE_MSG(callback_reached == 1, + "callback failed, reached %d times", callback_reached); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, swapcontext_basic); + + return (atf_no_error()); +} + |