Skip to content

Commit 27327f1

Browse files
Respect TMP environment variables for tmp::file on POSIX (#230)
Unfortunately std::tmpfile doesn't respect TMPDIR, TMP, and other environment variables for base tmp directories Implementation mirrors glibc's tmpfile but uses std::filesystem::temp_directory_path to get correct base path --------- Co-authored-by: bugdea1er <bugdealer@icloud.com>
1 parent 905c5f1 commit 27327f1

3 files changed

Lines changed: 83 additions & 2 deletions

File tree

src/create_file.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,73 @@
88
#include <cerrno>
99
#include <cstdio>
1010
#include <filesystem>
11+
#include <string>
1112
#include <system_error>
1213

14+
#if __has_include(<unistd.h>)
15+
#include <sys/fcntl.h>
16+
#include <sys/stat.h>
17+
#include <unistd.h>
18+
#endif
19+
1320
namespace tmp::detail {
1421

1522
namespace fs = std::filesystem;
1623

24+
namespace {
25+
26+
#if __has_include(<unistd.h>)
27+
/// Creates and opens a binary temporary file as if by POSIX `mkstemp`
28+
/// @returns A file descriptor associated with the temporary file
29+
/// @throws fs::filesystem_error if cannot create a temporary file
30+
int create_file_descriptor() {
31+
fs::path temp_dir = fs::temp_directory_path();
32+
int descriptor = int{};
33+
34+
#ifdef __linux__
35+
descriptor = open(temp_dir.c_str(), O_RDWR | O_TMPFILE, S_IRUSR | S_IWUSR);
36+
if (descriptor >= 0) {
37+
return descriptor;
38+
}
39+
#endif
40+
41+
std::string path = temp_dir / "XXXXXX";
42+
43+
descriptor = mkstemp(path.data());
44+
if (descriptor == -1) {
45+
std::error_code ec = std::error_code(errno, std::system_category());
46+
throw fs::filesystem_error("Cannot create a temporary file", ec);
47+
}
48+
49+
unlink(path.data());
50+
51+
return descriptor;
52+
}
53+
#endif
54+
} // namespace
55+
1756
/// Creates and opens a binary temporary file as if by POSIX `tmpfile`
1857
/// @returns A pointer to the file stream associated with the temporary file
1958
/// @throws fs::filesystem_error if cannot create a temporary file
2059
std::FILE* create_file() {
60+
#if __has_include(<unistd.h>)
61+
int descriptor = create_file_descriptor();
62+
63+
// TODO: let `filebuf` use the file descriptor without `FILE` wrapping
64+
std::FILE* file = fdopen(descriptor, "wb+");
65+
if (file == nullptr) {
66+
std::error_code ec = std::error_code(errno, std::system_category());
67+
close(descriptor);
68+
69+
throw fs::filesystem_error("Cannot create a temporary file", ec);
70+
}
71+
#else
2172
std::FILE* file = std::tmpfile();
2273
if (file == nullptr) {
2374
std::error_code ec = std::error_code(errno, std::generic_category());
2475
throw fs::filesystem_error("Cannot create a temporary file", ec);
2576
}
77+
#endif
2678

2779
return file;
2880
}

tests/directory.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
#include <filesystem>
99
#include <fstream>
10-
#include <ios>
1110
#include <stdexcept>
1211
#include <string>
1312
#include <string_view>

tests/file.cpp

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include <istream>
1212
#include <iterator>
1313
#include <ostream>
14-
#include <system_error>
1514
#include <type_traits>
1615
#include <utility>
1716

@@ -20,11 +19,14 @@
2019
#include <Windows.h>
2120
#else
2221
#include <fcntl.h>
22+
#include <sys/stat.h>
2323
#endif
2424

2525
namespace tmp {
2626
namespace {
2727

28+
namespace fs = std::filesystem;
29+
2830
/// Test fixture for `basic_file` tests
2931
template<class charT> class file : public testing::Test {
3032
public:
@@ -85,6 +87,34 @@ TYPED_TEST(file, create) {
8587
basic_file<TypeParam> tmpfile = basic_file<TypeParam>();
8688
EXPECT_TRUE(TestFixture::is_open(tmpfile.rdbuf()));
8789
EXPECT_TRUE(TestFixture::is_open(tmpfile.native_handle()));
90+
91+
fs::path temp_dir = fs::temp_directory_path();
92+
auto handle = tmpfile.native_handle();
93+
94+
#ifdef _WIN32
95+
BY_HANDLE_FILE_INFORMATION file_info;
96+
ASSERT_TRUE(GetFileInformationByHandle(handle, &file_info));
97+
98+
HANDLE dir_handle =
99+
CreateFileW(temp_dir.c_str(), FILE_READ_ATTRIBUTES,
100+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
101+
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
102+
ASSERT_NE(dir_handle, INVALID_HANDLE_VALUE);
103+
104+
BY_HANDLE_FILE_INFORMATION dir_info;
105+
ASSERT_TRUE(GetFileInformationByHandle(dir_handle, &dir_info));
106+
CloseHandle(dir_handle);
107+
108+
EXPECT_EQ(file_info.dwVolumeSerialNumber, dir_info.dwVolumeSerialNumber);
109+
#else
110+
struct stat file_stat {};
111+
ASSERT_EQ(fstat(handle, &file_stat), 0);
112+
113+
struct stat dir_stat {};
114+
ASSERT_EQ(stat(temp_dir.c_str(), &dir_stat), 0);
115+
116+
EXPECT_EQ(file_stat.st_dev, dir_stat.st_dev);
117+
#endif
88118
}
89119

90120
/// Tests multiple file creation

0 commit comments

Comments
 (0)