| //===----------------------------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| /// | |
| /// \file | |
| /// Tests for mkostemp | |
| /// See: https://pubs.opengroup.org/onlinepubs/9799919799/functions/mkdtemp.html | |
| /// | |
| //===----------------------------------------------------------------------===// | |
| #include "hdr/errno_macros.h" | |
| #include "hdr/fcntl_macros.h" | |
| #include "hdr/func/free.h" | |
| #include "hdr/signal_macros.h" | |
| #include "hdr/stdio_macros.h" | |
| #include "hdr/sys_stat_macros.h" | |
| #include "hdr/types/struct_stat.h" | |
| #include "hdr/unistd_macros.h" | |
| #include "src/__support/CPP/scope.h" | |
| #include "src/__support/CPP/string_view.h" | |
| #include "src/fcntl/fcntl.h" | |
| #include "src/stdlib/mkostemp.h" | |
| #include "src/string/strdup.h" | |
| #include "src/string/strlen.h" | |
| #include "src/sys/stat/stat.h" | |
| #include "src/unistd/access.h" | |
| #include "src/unistd/close.h" | |
| #include "src/unistd/lseek.h" | |
| #include "src/unistd/read.h" | |
| #include "src/unistd/unlink.h" | |
| #include "src/unistd/write.h" | |
| #include "test/UnitTest/ErrnoCheckingTest.h" | |
| #include "test/UnitTest/ErrnoSetterMatcher.h" | |
| #include "test/UnitTest/MemoryMatcher.h" | |
| #include "test/UnitTest/Test.h" | |
| using namespace LIBC_NAMESPACE::testing::ErrnoSetterMatcher; | |
| using LIBC_NAMESPACE::cpp::string_view; | |
| using LIBC_NAMESPACE::testing::MemoryView; | |
| using LlvmLibcMkostempTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest; | |
| TEST_F(LlvmLibcMkostempTest, ValidTemplateDefaultFlags) { | |
| char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX")); | |
| ASSERT_NE(tmpl, nullptr); | |
| auto cleanup_tmpl = LIBC_NAMESPACE::cpp::scope_exit([&] { | |
| LIBC_NAMESPACE::unlink(tmpl); | |
| ::free(tmpl); | |
| }); | |
| int fd = -1; | |
| ASSERT_THAT(fd = LIBC_NAMESPACE::mkostemp(tmpl, 0), | |
| returns(GE(0)).with_errno(EQ(0))); | |
| auto cleanup_fd = | |
| LIBC_NAMESPACE::cpp::scope_exit([&] { LIBC_NAMESPACE::close(fd); }); | |
| EXPECT_THAT(LIBC_NAMESPACE::access(tmpl, F_OK), Succeeds(0)); | |
| struct stat st; | |
| ASSERT_THAT(LIBC_NAMESPACE::stat(tmpl, &st), Succeeds(0)); | |
| EXPECT_EQ(st.st_mode & S_IFMT, static_cast<mode_t>(S_IFREG)); | |
| EXPECT_EQ(st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO), | |
| static_cast<mode_t>(S_IRUSR | S_IWUSR)); | |
| ASSERT_THAT(LIBC_NAMESPACE::write(fd, "llvm", 4), | |
| Succeeds(static_cast<ssize_t>(4))); | |
| } | |
| TEST_F(LlvmLibcMkostempTest, TemplateModifiedInPlace) { | |
| char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX")); | |
| ASSERT_NE(tmpl, nullptr); | |
| auto cleanup_tmpl = LIBC_NAMESPACE::cpp::scope_exit([&] { | |
| LIBC_NAMESPACE::unlink(tmpl); | |
| ::free(tmpl); | |
| }); | |
| char *orig = LIBC_NAMESPACE::strdup(tmpl); | |
| ASSERT_NE(orig, nullptr); | |
| auto cleanup_orig = LIBC_NAMESPACE::cpp::scope_exit([&] { ::free(orig); }); | |
| size_t len = LIBC_NAMESPACE::strlen(tmpl); | |
| int fd = -1; | |
| ASSERT_THAT(fd = LIBC_NAMESPACE::mkostemp(tmpl, 0), | |
| returns(GE(0)).with_errno(EQ(0))); | |
| auto cleanup_fd = | |
| LIBC_NAMESPACE::cpp::scope_exit([&] { LIBC_NAMESPACE::close(fd); }); | |
| EXPECT_EQ(string_view(tmpl, len - 6), string_view(orig, len - 6)); | |
| EXPECT_NE(string_view(tmpl, len).substr(len - 6), string_view("XXXXXX")); | |
| } | |
| TEST_F(LlvmLibcMkostempTest, AllCharactersInCharset) { | |
| char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX")); | |
| ASSERT_NE(tmpl, nullptr); | |
| auto cleanup_tmpl = LIBC_NAMESPACE::cpp::scope_exit([&] { | |
| LIBC_NAMESPACE::unlink(tmpl); | |
| ::free(tmpl); | |
| }); | |
| size_t len = LIBC_NAMESPACE::strlen(tmpl); | |
| int fd = -1; | |
| ASSERT_THAT(fd = LIBC_NAMESPACE::mkostemp(tmpl, 0), | |
| returns(GE(0)).with_errno(EQ(0))); | |
| auto cleanup_fd = | |
| LIBC_NAMESPACE::cpp::scope_exit([&] { LIBC_NAMESPACE::close(fd); }); | |
| // POSIX portable filename character set, sorted by ASCII value. | |
| // See | |
| // https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap03.html#tag_03_265 | |
| constexpr string_view CHARSET = "-._0123456789" | |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | |
| "abcdefghijklmnopqrstuvwxyz"; | |
| for (char c : string_view(tmpl, len).substr(len - 6)) | |
| EXPECT_NE(CHARSET.find_first_of(c), string_view::npos); | |
| } | |
| TEST_F(LlvmLibcMkostempTest, Uniqueness) { | |
| char *tmpl1 = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX")); | |
| ASSERT_NE(tmpl1, nullptr); | |
| auto cleanup_tmpl1 = LIBC_NAMESPACE::cpp::scope_exit([&] { | |
| LIBC_NAMESPACE::unlink(tmpl1); | |
| ::free(tmpl1); | |
| }); | |
| char *tmpl2 = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX")); | |
| ASSERT_NE(tmpl2, nullptr); | |
| auto cleanup_tmpl2 = LIBC_NAMESPACE::cpp::scope_exit([&] { | |
| LIBC_NAMESPACE::unlink(tmpl2); | |
| ::free(tmpl2); | |
| }); | |
| int fd1 = -1; | |
| ASSERT_THAT(fd1 = LIBC_NAMESPACE::mkostemp(tmpl1, 0), | |
| returns(GE(0)).with_errno(EQ(0))); | |
| auto cleanup_fd1 = | |
| LIBC_NAMESPACE::cpp::scope_exit([&] { LIBC_NAMESPACE::close(fd1); }); | |
| int fd2 = -1; | |
| ASSERT_THAT(fd2 = LIBC_NAMESPACE::mkostemp(tmpl2, 0), | |
| returns(GE(0)).with_errno(EQ(0))); | |
| auto cleanup_fd2 = | |
| LIBC_NAMESPACE::cpp::scope_exit([&] { LIBC_NAMESPACE::close(fd2); }); | |
| EXPECT_STRNE(tmpl1, tmpl2); | |
| } | |
| TEST_F(LlvmLibcMkostempTest, FlagCloexec) { | |
| char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX")); | |
| ASSERT_NE(tmpl, nullptr); | |
| auto cleanup_tmpl = LIBC_NAMESPACE::cpp::scope_exit([&] { | |
| LIBC_NAMESPACE::unlink(tmpl); | |
| ::free(tmpl); | |
| }); | |
| int fd = -1; | |
| ASSERT_THAT(fd = LIBC_NAMESPACE::mkostemp(tmpl, O_CLOEXEC), | |
| returns(GE(0)).with_errno(EQ(0))); | |
| auto cleanup_fd = | |
| LIBC_NAMESPACE::cpp::scope_exit([&] { LIBC_NAMESPACE::close(fd); }); | |
| int fd_flags = LIBC_NAMESPACE::fcntl(fd, F_GETFD); | |
| ASSERT_GE(fd_flags, 0); | |
| EXPECT_EQ(fd_flags & FD_CLOEXEC, FD_CLOEXEC); | |
| } | |
| TEST_F(LlvmLibcMkostempTest, FlagAppend) { | |
| char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX")); | |
| ASSERT_NE(tmpl, nullptr); | |
| auto cleanup_tmpl = LIBC_NAMESPACE::cpp::scope_exit([&] { | |
| LIBC_NAMESPACE::unlink(tmpl); | |
| ::free(tmpl); | |
| }); | |
| int fd = -1; | |
| ASSERT_THAT(fd = LIBC_NAMESPACE::mkostemp(tmpl, O_APPEND), | |
| returns(GE(0)).with_errno(EQ(0))); | |
| auto cleanup_fd = | |
| LIBC_NAMESPACE::cpp::scope_exit([&] { LIBC_NAMESPACE::close(fd); }); | |
| int fl_flags = LIBC_NAMESPACE::fcntl(fd, F_GETFL); | |
| ASSERT_GE(fl_flags, 0); | |
| EXPECT_EQ(fl_flags & O_APPEND, O_APPEND); | |
| ASSERT_THAT(LIBC_NAMESPACE::write(fd, "abc", 3), | |
| Succeeds(static_cast<ssize_t>(3))); | |
| ASSERT_THAT(LIBC_NAMESPACE::lseek(fd, 0, SEEK_SET), | |
| Succeeds(static_cast<off_t>(0))); | |
| // With O_APPEND, writes must always append to the end of the file. | |
| ASSERT_THAT(LIBC_NAMESPACE::write(fd, "def", 3), | |
| Succeeds(static_cast<ssize_t>(3))); | |
| ASSERT_THAT(LIBC_NAMESPACE::lseek(fd, 0, SEEK_SET), | |
| Succeeds(static_cast<off_t>(0))); | |
| char buf[6]; | |
| ASSERT_THAT(LIBC_NAMESPACE::read(fd, buf, 6), | |
| Succeeds(static_cast<ssize_t>(6))); | |
| EXPECT_MEM_EQ(MemoryView("abcdef", 6), MemoryView(buf, 6)); | |
| } | |
| TEST_F(LlvmLibcMkostempTest, FlagSyncVariants) { | |
| constexpr int SYNC_FLAGS[] = {O_SYNC, O_DSYNC}; | |
| for (int sync_flag : SYNC_FLAGS) { | |
| char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX")); | |
| ASSERT_NE(tmpl, nullptr); | |
| auto cleanup_tmpl = LIBC_NAMESPACE::cpp::scope_exit([&] { | |
| LIBC_NAMESPACE::unlink(tmpl); | |
| ::free(tmpl); | |
| }); | |
| int fd = -1; | |
| ASSERT_THAT(fd = LIBC_NAMESPACE::mkostemp(tmpl, sync_flag), | |
| returns(GE(0)).with_errno(EQ(0))); | |
| auto cleanup_fd = | |
| LIBC_NAMESPACE::cpp::scope_exit([&] { LIBC_NAMESPACE::close(fd); }); | |
| int fl = LIBC_NAMESPACE::fcntl(fd, F_GETFL); | |
| ASSERT_GE(fl, 0); | |
| EXPECT_EQ(fl & sync_flag, sync_flag); | |
| } | |
| } | |
| TEST_F(LlvmLibcMkostempTest, CombinedFlags) { | |
| char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX")); | |
| ASSERT_NE(tmpl, nullptr); | |
| auto cleanup_tmpl = LIBC_NAMESPACE::cpp::scope_exit([&] { | |
| LIBC_NAMESPACE::unlink(tmpl); | |
| ::free(tmpl); | |
| }); | |
| int fd = -1; | |
| ASSERT_THAT(fd = LIBC_NAMESPACE::mkostemp(tmpl, O_CLOEXEC | O_APPEND), | |
| returns(GE(0)).with_errno(EQ(0))); | |
| auto cleanup_fd = | |
| LIBC_NAMESPACE::cpp::scope_exit([&] { LIBC_NAMESPACE::close(fd); }); | |
| int fd_flags = LIBC_NAMESPACE::fcntl(fd, F_GETFD); | |
| ASSERT_GE(fd_flags, 0); | |
| EXPECT_EQ(fd_flags & FD_CLOEXEC, FD_CLOEXEC); | |
| int fl_flags = LIBC_NAMESPACE::fcntl(fd, F_GETFL); | |
| ASSERT_GE(fl_flags, 0); | |
| EXPECT_EQ(fl_flags & O_APPEND, O_APPEND); | |
| } | |
| TEST_F(LlvmLibcMkostempTest, SixXsNoPrefix) { | |
| char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("XXXXXX")); | |
| ASSERT_NE(tmpl, nullptr); | |
| auto cleanup_tmpl = LIBC_NAMESPACE::cpp::scope_exit([&] { | |
| LIBC_NAMESPACE::unlink(tmpl); | |
| ::free(tmpl); | |
| }); | |
| int fd = -1; | |
| ASSERT_THAT(fd = LIBC_NAMESPACE::mkostemp(tmpl, 0), | |
| returns(GE(0)).with_errno(EQ(0))); | |
| auto cleanup_fd = | |
| LIBC_NAMESPACE::cpp::scope_exit([&] { LIBC_NAMESPACE::close(fd); }); | |
| EXPECT_THAT(LIBC_NAMESPACE::access(tmpl, F_OK), Succeeds(0)); | |
| } | |
| TEST_F(LlvmLibcMkostempTest, MoreThanSixXs) { | |
| char *tmpl = | |
| LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXXXXXX")); | |
| ASSERT_NE(tmpl, nullptr); | |
| auto cleanup_tmpl = LIBC_NAMESPACE::cpp::scope_exit([&] { | |
| LIBC_NAMESPACE::unlink(tmpl); | |
| ::free(tmpl); | |
| }); | |
| char *orig = LIBC_NAMESPACE::strdup(tmpl); | |
| ASSERT_NE(orig, nullptr); | |
| auto cleanup_orig = LIBC_NAMESPACE::cpp::scope_exit([&] { ::free(orig); }); | |
| size_t len = LIBC_NAMESPACE::strlen(tmpl); | |
| int fd = -1; | |
| ASSERT_THAT(fd = LIBC_NAMESPACE::mkostemp(tmpl, 0), | |
| returns(GE(0)).with_errno(EQ(0))); | |
| auto cleanup_fd = | |
| LIBC_NAMESPACE::cpp::scope_exit([&] { LIBC_NAMESPACE::close(fd); }); | |
| EXPECT_EQ(string_view(tmpl, len - 10), string_view(orig, len - 10)); | |
| EXPECT_NE(string_view(tmpl, len).substr(len - 10), string_view("XXXXXXXXXX")); | |
| EXPECT_THAT(LIBC_NAMESPACE::access(tmpl, F_OK), Succeeds(0)); | |
| } | |
| #if defined(LIBC_ADD_NULL_CHECKS) | |
| TEST_F(LlvmLibcMkostempTest, NullPointer) { | |
| ASSERT_DEATH([] { LIBC_NAMESPACE::mkostemp(nullptr, 0); }, WITH_SIGNAL(-1)); | |
| } | |
| #endif | |
| TEST_F(LlvmLibcMkostempTest, InvalidFlags) { | |
| char tmpl[] = "tmp_XXXXXX"; | |
| EXPECT_THAT(LIBC_NAMESPACE::mkostemp(tmpl, O_CREAT), Fails(EINVAL)); | |
| EXPECT_THAT(LIBC_NAMESPACE::mkostemp(tmpl, O_EXCL), Fails(EINVAL)); | |
| EXPECT_THAT(LIBC_NAMESPACE::mkostemp(tmpl, O_RDWR), Fails(EINVAL)); | |
| EXPECT_THAT(LIBC_NAMESPACE::mkostemp(tmpl, O_WRONLY), Fails(EINVAL)); | |
| EXPECT_THAT(LIBC_NAMESPACE::mkostemp(tmpl, O_TRUNC), Fails(EINVAL)); | |
| EXPECT_THAT(LIBC_NAMESPACE::mkostemp(tmpl, -1), Fails(EINVAL)); | |
| EXPECT_STREQ(tmpl, "tmp_XXXXXX"); | |
| } | |
| TEST_F(LlvmLibcMkostempTest, TemplateTooShort) { | |
| char tmpl[] = "XXXXX"; | |
| EXPECT_THAT(LIBC_NAMESPACE::mkostemp(tmpl, 0), Fails(EINVAL)); | |
| } | |
| TEST_F(LlvmLibcMkostempTest, DoesNotEndInXs) { | |
| char tmpl[] = "tmp_XXXXXY"; | |
| EXPECT_THAT(LIBC_NAMESPACE::mkostemp(tmpl, 0), Fails(EINVAL)); | |
| } | |
| TEST_F(LlvmLibcMkostempTest, XsNotAtEnd) { | |
| char tmpl[] = "XXXXXXtmp"; | |
| EXPECT_THAT(LIBC_NAMESPACE::mkostemp(tmpl, 0), Fails(EINVAL)); | |
| } | |
| TEST_F(LlvmLibcMkostempTest, FiveXsAtEnd) { | |
| char tmpl[] = "tmp_XXXXX"; | |
| EXPECT_THAT(LIBC_NAMESPACE::mkostemp(tmpl, 0), Fails(EINVAL)); | |
| } | |
| TEST_F(LlvmLibcMkostempTest, EmptyString) { | |
| char tmpl[] = ""; | |
| EXPECT_THAT(LIBC_NAMESPACE::mkostemp(tmpl, 0), Fails(EINVAL)); | |
| } | |
| TEST_F(LlvmLibcMkostempTest, NonExistentParentDirectory) { | |
| char *tmpl = LIBC_NAMESPACE::strdup( | |
| libc_make_test_file_path("non_existent_dir/tmp_XXXXXX")); | |
| ASSERT_NE(tmpl, nullptr); | |
| auto cleanup = LIBC_NAMESPACE::cpp::scope_exit([&] { ::free(tmpl); }); | |
| EXPECT_THAT(LIBC_NAMESPACE::mkostemp(tmpl, 0), Fails(ENOENT)); | |
| } |