blob: e910367c57b0172e6eabebec3db4bc9eea5839f2 [file] [edit]
//===----------------------------------------------------------------------===//
//
// 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));
}