]>
Commit | Line | Data |
---|---|---|
0ca7ba9a JW |
1 | // -*- C++ -*- |
2 | // Filesystem utils for the C++ library testsuite. | |
3 | // | |
a945c346 | 4 | // Copyright (C) 2014-2024 Free Software Foundation, Inc. |
0ca7ba9a JW |
5 | // |
6 | // This file is part of the GNU ISO C++ Library. This library is free | |
7 | // software; you can redistribute it and/or modify it under the | |
8 | // terms of the GNU General Public License as published by the | |
9 | // Free Software Foundation; either version 3, or (at your option) | |
10 | // any later version. | |
11 | // | |
12 | // This library is distributed in the hope that it will be useful, | |
13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | // GNU General Public License for more details. | |
16 | // | |
17 | // You should have received a copy of the GNU General Public License along | |
18 | // with this library; see the file COPYING3. If not see | |
19 | // <http://www.gnu.org/licenses/>. | |
20 | // | |
21 | ||
22 | #ifndef _TESTSUITE_FS_H | |
23 | #define _TESTSUITE_FS_H 1 | |
24 | ||
641cb5a6 JW |
25 | // Assume we want std::filesystem in C++17, unless USE_FILESYSTEM_TS defined: |
26 | #if __cplusplus >= 201703L && ! defined USE_FILESYSTEM_TS | |
27 | #include <filesystem> | |
28 | namespace test_fs = std::filesystem; | |
29 | #else | |
0ca7ba9a | 30 | #include <experimental/filesystem> |
641cb5a6 JW |
31 | namespace test_fs = std::experimental::filesystem; |
32 | #endif | |
f9b22a0c | 33 | #include <algorithm> |
fd5effb1 | 34 | #include <fstream> |
30a8f672 | 35 | #include <random> // std::random_device |
0ca7ba9a | 36 | #include <string> |
30a8f672 | 37 | #include <system_error> |
9caf7b27 | 38 | #include <cstdio> |
29b2fd37 | 39 | #include <unistd.h> // unlink, close, getpid, geteuid |
4179ec10 JW |
40 | |
41 | #if defined(_GNU_SOURCE) || _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200112L | |
42 | #include <stdlib.h> // mkstemp | |
4179ec10 | 43 | #endif |
0ca7ba9a | 44 | |
9f7f25bb AO |
45 | #ifndef _GLIBCXX_HAVE_SYMLINK |
46 | #define NO_SYMLINKS | |
47 | #endif | |
48 | ||
b931c687 AO |
49 | #if !defined (_GLIBCXX_HAVE_SYS_STATVFS_H) \ |
50 | && !defined (_GLIBCXX_FILESYSTEM_IS_WINDOWS) | |
51 | #define NO_SPACE | |
52 | #endif | |
53 | ||
f01cf5ea AO |
54 | #if !(_GLIBCXX_HAVE_SYS_STAT_H \ |
55 | && (_GLIBCXX_USE_UTIMENSAT || _GLIBCXX_USE_UTIME)) | |
56 | #define NO_LAST_WRITE_TIME 1 | |
57 | #endif | |
58 | ||
0ca7ba9a JW |
59 | namespace __gnu_test |
60 | { | |
61 | #define PATH_CHK(p1, p2, fn) \ | |
62 | if ( p1.fn() != p2.fn() ) \ | |
50e248f0 | 63 | throw test_fs::filesystem_error("comparing '" #fn "' failed", p1, p2, \ |
0ca7ba9a JW |
64 | std::make_error_code(std::errc::invalid_argument) ) |
65 | ||
66 | void | |
641cb5a6 JW |
67 | compare_paths(const test_fs::path& p1, |
68 | const test_fs::path& p2) | |
0ca7ba9a | 69 | { |
eeb517d3 | 70 | PATH_CHK( p1, p2, native ); |
0ca7ba9a JW |
71 | PATH_CHK( p1, p2, string ); |
72 | PATH_CHK( p1, p2, empty ); | |
73 | PATH_CHK( p1, p2, has_root_path ); | |
74 | PATH_CHK( p1, p2, has_root_name ); | |
75 | PATH_CHK( p1, p2, has_root_directory ); | |
76 | PATH_CHK( p1, p2, has_relative_path ); | |
77 | PATH_CHK( p1, p2, has_parent_path ); | |
78 | PATH_CHK( p1, p2, has_filename ); | |
79 | PATH_CHK( p1, p2, has_stem ); | |
80 | PATH_CHK( p1, p2, has_extension ); | |
81 | PATH_CHK( p1, p2, is_absolute ); | |
82 | PATH_CHK( p1, p2, is_relative ); | |
83 | auto d1 = std::distance(p1.begin(), p1.end()); | |
84 | auto d2 = std::distance(p2.begin(), p2.end()); | |
f9b22a0c | 85 | if (d1 != d2) |
641cb5a6 | 86 | throw test_fs::filesystem_error( |
f9b22a0c | 87 | "distance(begin1, end1) != distance(begin2, end2)", p1, p2, |
0ca7ba9a | 88 | std::make_error_code(std::errc::invalid_argument) ); |
d9b401df | 89 | if (!std::equal(p1.begin(), p1.end(), p2.begin())) |
f9b22a0c | 90 | throw test_fs::filesystem_error( |
d9b401df | 91 | "!equal(begin1, end1, begin2)", p1, p2, |
f9b22a0c JW |
92 | std::make_error_code(std::errc::invalid_argument) ); |
93 | ||
0ca7ba9a JW |
94 | } |
95 | ||
96 | const std::string test_paths[] = { | |
97 | "", "/", "//", "/.", "/./", "/a", "/a/", "/a//", "/a/b/c/d", "/a//b", | |
98 | "a", "a/b", "a/b/", "a/b/c", "a/b/c.d", "a/b/..", "a/b/c.", "a/b/.c" | |
99 | }; | |
100 | ||
9534a5e6 JW |
101 | test_fs::path |
102 | root_path() | |
103 | { | |
6a6f74be | 104 | #if defined(__MINGW32__) || defined(__MINGW64__) |
9534a5e6 JW |
105 | return L"c:/"; |
106 | #else | |
107 | return "/"; | |
108 | #endif | |
109 | } | |
110 | ||
9caf7b27 JW |
111 | // This is NOT supposed to be a secure way to get a unique name! |
112 | // We just need a path that doesn't exist for testing purposes. | |
641cb5a6 | 113 | test_fs::path |
7c4979b2 | 114 | nonexistent_path(std::string file = __builtin_FILE()) |
9caf7b27 | 115 | { |
7c4979b2 JW |
116 | // Include the caller's filename to help identify tests that fail to |
117 | // clean up the files they create. | |
118 | // Remove .cc extension: | |
119 | if (file.length() > 3 && file.compare(file.length() - 3, 3, ".cc") == 0) | |
120 | file.resize(file.length() - 3); | |
121 | // And directory: | |
122 | auto pos = file.find_last_of("/\\"); | |
123 | if (pos != file.npos) | |
124 | file.erase(0, pos+1); | |
125 | ||
30a8f672 JB |
126 | file.reserve(file.size() + 40); |
127 | file.insert(0, "filesystem-test."); | |
128 | ||
129 | // A counter, starting from a random value, to be included as part | |
130 | // of the filename being returned, and incremented each time | |
131 | // this function is used. It allows us to ensure that two calls | |
132 | // to this function can never return the same filename, something | |
133 | // testcases do when they need multiple non-existent filenames | |
134 | // for their purposes. | |
135 | static unsigned counter = std::random_device{}(); | |
136 | file += '.'; | |
137 | file += std::to_string(counter++); | |
138 | file += '.'; | |
139 | ||
641cb5a6 | 140 | test_fs::path p; |
9caf7b27 | 141 | #if defined(_GNU_SOURCE) || _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200112L |
30a8f672 JB |
142 | |
143 | // Use mkstemp to determine the name of a file which does not exist yet. | |
144 | // | |
145 | // Note that we have seen on some systems (such as RTEMS, for instance) | |
146 | // that mkstemp behaves very predictably, causing it to always try | |
147 | // the same sequence of file names. In other words, if we call mkstemp | |
148 | // with a pattern, delete the file it created (which is what we do, here), | |
149 | // and call mkstemp with the same pattern again, it returns the same | |
150 | // filename once more. While most implementations introduce a degree | |
151 | // of randomness, it is not mandated by the standard, and this is why | |
152 | // we also include a counter in the template passed to mkstemp. | |
153 | file += "XXXXXX"; | |
154 | int fd = ::mkstemp(&file[0]); | |
9caf7b27 | 155 | if (fd == -1) |
641cb5a6 | 156 | throw test_fs::filesystem_error("mkstemp failed", |
9caf7b27 | 157 | std::error_code(errno, std::generic_category())); |
30a8f672 | 158 | ::unlink(file.c_str()); |
9caf7b27 | 159 | ::close(fd); |
30a8f672 | 160 | p = std::move(file); |
9caf7b27 | 161 | #else |
7c4979b2 JW |
162 | if (file.length() > 64) |
163 | file.resize(64); | |
30a8f672 | 164 | // The combination of random counter and PID should be unique for a given |
bd2d0aab AO |
165 | // run of the testsuite. N.B. getpid() returns a pointer type on vxworks |
166 | // in kernel mode. | |
b44cba35 | 167 | file += std::to_string((unsigned long) ::getpid()); |
30a8f672 JB |
168 | p = std::move(file); |
169 | if (test_fs::exists(p)) | |
170 | throw test_fs::filesystem_error("Failed to generate unique pathname", p, | |
171 | std::make_error_code(std::errc::file_exists)); | |
9caf7b27 JW |
172 | #endif |
173 | return p; | |
174 | } | |
175 | ||
fd5effb1 JW |
176 | // RAII helper to remove a file on scope exit. |
177 | struct scoped_file | |
178 | { | |
641cb5a6 | 179 | using path_type = test_fs::path; |
fd5effb1 JW |
180 | |
181 | enum adopt_file_t { adopt_file }; | |
182 | ||
183 | explicit | |
184 | scoped_file(const path_type& p = nonexistent_path()) : path(p) | |
9534a5e6 | 185 | { std::ofstream{p.c_str()}; } |
fd5effb1 JW |
186 | |
187 | scoped_file(path_type p, adopt_file_t) : path(p) { } | |
188 | ||
ec04aad7 | 189 | ~scoped_file() { if (!path.empty()) remove(path); } |
fd5effb1 | 190 | |
c7dde4a9 JW |
191 | scoped_file(scoped_file&&) = default; |
192 | scoped_file& operator=(scoped_file&&) = default; | |
193 | ||
fd5effb1 JW |
194 | path_type path; |
195 | }; | |
196 | ||
bc97e736 | 197 | inline bool |
29b2fd37 JW |
198 | permissions_are_testable(bool print_msg = true) |
199 | { | |
200 | bool testable = false; | |
201 | #if !(defined __MINGW32__ || defined __MINGW64__) | |
202 | if (geteuid() != 0) | |
203 | testable = true; | |
204 | // XXX on Linux the CAP_DAC_OVERRIDE and CAP_DAC_READ_SEARCH capabilities | |
205 | // can give normal users extra permissions for files and directories. | |
206 | // We ignore that possibility here. | |
207 | #endif | |
208 | if (print_msg && !testable) | |
209 | std::puts("Skipping tests that depend on filesystem permissions"); | |
210 | return testable; | |
211 | } | |
212 | ||
0ca7ba9a JW |
213 | } // namespace __gnu_test |
214 | #endif |