diff --git a/src/create_file.cpp b/src/create_file.cpp index 4dde405..88a0be9 100644 --- a/src/create_file.cpp +++ b/src/create_file.cpp @@ -8,21 +8,73 @@ #include #include #include +#include #include +#if __has_include() +#include +#include +#include +#endif + namespace tmp::detail { namespace fs = std::filesystem; +namespace { + +#if __has_include() +/// Creates and opens a binary temporary file as if by POSIX `mkstemp` +/// @returns A file descriptor associated with the temporary file +/// @throws fs::filesystem_error if cannot create a temporary file +int create_file_descriptor() { + fs::path temp_dir = fs::temp_directory_path(); + int descriptor = int{}; + +#ifdef __linux__ + descriptor = open(temp_dir.c_str(), O_RDWR | O_TMPFILE, S_IRUSR | S_IWUSR); + if (descriptor >= 0) { + return descriptor; + } +#endif + + std::string path = temp_dir / "XXXXXX"; + + descriptor = mkstemp(path.data()); + if (descriptor == -1) { + std::error_code ec = std::error_code(errno, std::system_category()); + throw fs::filesystem_error("Cannot create a temporary file", ec); + } + + unlink(path.data()); + + return descriptor; +} +#endif +} // namespace + /// Creates and opens a binary temporary file as if by POSIX `tmpfile` /// @returns A pointer to the file stream associated with the temporary file /// @throws fs::filesystem_error if cannot create a temporary file std::FILE* create_file() { +#if __has_include() + int descriptor = create_file_descriptor(); + + // TODO: let `filebuf` use the file descriptor without `FILE` wrapping + std::FILE* file = fdopen(descriptor, "wb+"); + if (file == nullptr) { + std::error_code ec = std::error_code(errno, std::system_category()); + close(descriptor); + + throw fs::filesystem_error("Cannot create a temporary file", ec); + } +#else std::FILE* file = std::tmpfile(); if (file == nullptr) { std::error_code ec = std::error_code(errno, std::generic_category()); throw fs::filesystem_error("Cannot create a temporary file", ec); } +#endif return file; } diff --git a/tests/directory.cpp b/tests/directory.cpp index 16a605a..7182a31 100644 --- a/tests/directory.cpp +++ b/tests/directory.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include #include diff --git a/tests/file.cpp b/tests/file.cpp index e27abfa..9dbe8b3 100644 --- a/tests/file.cpp +++ b/tests/file.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -20,11 +19,14 @@ #include #else #include +#include #endif namespace tmp { namespace { +namespace fs = std::filesystem; + /// Test fixture for `basic_file` tests template class file : public testing::Test { public: @@ -85,6 +87,34 @@ TYPED_TEST(file, create) { basic_file tmpfile = basic_file(); EXPECT_TRUE(TestFixture::is_open(tmpfile.rdbuf())); EXPECT_TRUE(TestFixture::is_open(tmpfile.native_handle())); + + fs::path temp_dir = fs::temp_directory_path(); + auto handle = tmpfile.native_handle(); + +#ifdef _WIN32 + BY_HANDLE_FILE_INFORMATION file_info; + ASSERT_TRUE(GetFileInformationByHandle(handle, &file_info)); + + HANDLE dir_handle = + CreateFileW(temp_dir.c_str(), FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + ASSERT_NE(dir_handle, INVALID_HANDLE_VALUE); + + BY_HANDLE_FILE_INFORMATION dir_info; + ASSERT_TRUE(GetFileInformationByHandle(dir_handle, &dir_info)); + CloseHandle(dir_handle); + + EXPECT_EQ(file_info.dwVolumeSerialNumber, dir_info.dwVolumeSerialNumber); +#else + struct stat file_stat {}; + ASSERT_EQ(fstat(handle, &file_stat), 0); + + struct stat dir_stat {}; + ASSERT_EQ(stat(temp_dir.c_str(), &dir_stat), 0); + + EXPECT_EQ(file_stat.st_dev, dir_stat.st_dev); +#endif } /// Tests multiple file creation