1 | //===-- unittests/Runtime/AccessTest.cpp ------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | // TODO: ACCESS is not yet implemented on Windows |
10 | #ifndef _WIN32 |
11 | |
12 | #include "CrashHandlerFixture.h" |
13 | #include "gtest/gtest.h" |
14 | #include "flang/Runtime/extensions.h" |
15 | #include "llvm/ADT/Twine.h" |
16 | |
17 | #include <fcntl.h> |
18 | #include <sys/stat.h> |
19 | #include <sys/types.h> |
20 | #include <unistd.h> |
21 | |
22 | namespace { |
23 | |
24 | struct AccessTests : public CrashHandlerFixture {}; |
25 | |
26 | struct AccessType { |
27 | bool read{false}; |
28 | bool write{false}; |
29 | bool execute{false}; |
30 | bool exists{false}; |
31 | }; |
32 | |
33 | } // namespace |
34 | |
35 | static bool userSkipsPermissionChecks() { |
36 | // The tests in this file assume normal permission checks apply to the user |
37 | // running the tests. This isn't true when the test is run by root. |
38 | return geteuid() == 0; |
39 | } |
40 | |
41 | static std::string addPIDSuffix(const char *name) { |
42 | std::stringstream ss; |
43 | ss << name; |
44 | ss << '.'; |
45 | |
46 | ss << getpid(); |
47 | |
48 | return ss.str(); |
49 | } |
50 | |
51 | static bool exists(const std::string &path) { |
52 | return access(name: path.c_str(), F_OK) == 0; |
53 | } |
54 | |
55 | // Implementation of std::filesystem::temp_directory_path adapted from libcxx |
56 | // See llvm-project/libcxx/src/filesystem/operations.cpp |
57 | // Using std::filesystem is inconvenient because the required flags are not |
58 | // consistent accross compilers and CMake doesn't have built in support to |
59 | // determine the correct flags. |
60 | static const char *temp_directory_path() { |
61 | // TODO: Windows |
62 | const char *env_paths[] = {"TMPDIR" , "TMP" , "TEMP" , "TEMPDIR" }; |
63 | const char *ret = nullptr; |
64 | |
65 | for (auto &ep : env_paths) { |
66 | if ((ret = getenv(name: ep))) { |
67 | break; |
68 | } |
69 | } |
70 | |
71 | if (ret == nullptr) { |
72 | #if defined(__ANDROID__) |
73 | ret = "/data/local/tmp" ; |
74 | #else |
75 | ret = "/tmp" ; |
76 | #endif |
77 | } |
78 | |
79 | assert(exists(ret)); |
80 | return ret; |
81 | } |
82 | |
83 | static std::string createTemporaryFile( |
84 | const char *name, const AccessType &accessType) { |
85 | std::string path = |
86 | (llvm::Twine{temp_directory_path()} + "/" + addPIDSuffix(name)).str(); |
87 | |
88 | // O_CREAT | O_EXCL enforces that this file is newly created by this call. |
89 | // This feels risky. If we don't have permission to create files in the |
90 | // temporary directory or if the files already exist, the test will fail. |
91 | // But we can't use std::tmpfile() because we need a path to the file and |
92 | // to control the filesystem permissions |
93 | mode_t mode{0}; |
94 | if (accessType.read) { |
95 | mode |= S_IRUSR; |
96 | } |
97 | if (accessType.write) { |
98 | mode |= S_IWUSR; |
99 | } |
100 | if (accessType.execute) { |
101 | mode |= S_IXUSR; |
102 | } |
103 | |
104 | int file = open(file: path.c_str(), O_CREAT | O_EXCL, mode); |
105 | if (file == -1) { |
106 | return {}; |
107 | } |
108 | |
109 | close(fd: file); |
110 | |
111 | return path; |
112 | } |
113 | |
114 | static std::int64_t callAccess( |
115 | const std::string &path, const AccessType &accessType) { |
116 | const char *cpath{path.c_str()}; |
117 | std::int64_t pathlen = std::strlen(s: cpath); |
118 | |
119 | std::string mode; |
120 | if (accessType.read) { |
121 | mode += 'r'; |
122 | } |
123 | if (accessType.write) { |
124 | mode += 'w'; |
125 | } |
126 | if (accessType.execute) { |
127 | mode += 'x'; |
128 | } |
129 | if (accessType.exists) { |
130 | mode += ' '; |
131 | } |
132 | |
133 | const char *cmode = mode.c_str(); |
134 | std::int64_t modelen = std::strlen(s: cmode); |
135 | |
136 | return FORTRAN_PROCEDURE_NAME(access)(cpath, pathlen, cmode, modelen); |
137 | } |
138 | |
139 | TEST(AccessTests, TestExists) { |
140 | AccessType accessType; |
141 | accessType.exists = true; |
142 | |
143 | std::string path = createTemporaryFile(name: __func__, accessType); |
144 | ASSERT_FALSE(path.empty()); |
145 | |
146 | std::int64_t res = callAccess(path, accessType); |
147 | |
148 | ASSERT_EQ(unlink(path.c_str()), 0); |
149 | |
150 | ASSERT_EQ(res, 0); |
151 | } |
152 | |
153 | TEST(AccessTests, TestNotExists) { |
154 | std::string nonExistant{addPIDSuffix(name: __func__)}; |
155 | ASSERT_FALSE(exists(nonExistant)); |
156 | |
157 | AccessType accessType; |
158 | accessType.exists = true; |
159 | std::int64_t res = callAccess(path: nonExistant, accessType); |
160 | |
161 | ASSERT_NE(res, 0); |
162 | } |
163 | |
164 | TEST(AccessTests, TestRead) { |
165 | AccessType accessType; |
166 | accessType.read = true; |
167 | |
168 | std::string path = createTemporaryFile(name: __func__, accessType); |
169 | ASSERT_FALSE(path.empty()); |
170 | |
171 | std::int64_t res = callAccess(path, accessType); |
172 | |
173 | ASSERT_EQ(unlink(path.c_str()), 0); |
174 | |
175 | if (userSkipsPermissionChecks()) { |
176 | return; |
177 | } |
178 | |
179 | ASSERT_EQ(res, 0); |
180 | } |
181 | |
182 | TEST(AccessTests, TestNotRead) { |
183 | AccessType accessType; |
184 | accessType.read = false; |
185 | |
186 | std::string path = createTemporaryFile(name: __func__, accessType); |
187 | ASSERT_FALSE(path.empty()); |
188 | |
189 | accessType.read = true; |
190 | std::int64_t res = callAccess(path, accessType); |
191 | |
192 | ASSERT_EQ(unlink(path.c_str()), 0); |
193 | |
194 | if (userSkipsPermissionChecks()) { |
195 | return; |
196 | } |
197 | |
198 | ASSERT_NE(res, 0); |
199 | } |
200 | |
201 | TEST(AccessTests, TestWrite) { |
202 | AccessType accessType; |
203 | accessType.write = true; |
204 | |
205 | std::string path = createTemporaryFile(name: __func__, accessType); |
206 | ASSERT_FALSE(path.empty()); |
207 | |
208 | std::int64_t res = callAccess(path, accessType); |
209 | |
210 | ASSERT_EQ(unlink(path.c_str()), 0); |
211 | |
212 | if (userSkipsPermissionChecks()) { |
213 | return; |
214 | } |
215 | |
216 | ASSERT_EQ(res, 0); |
217 | } |
218 | |
219 | TEST(AccessTests, TestNotWrite) { |
220 | AccessType accessType; |
221 | accessType.write = false; |
222 | |
223 | std::string path = createTemporaryFile(name: __func__, accessType); |
224 | ASSERT_FALSE(path.empty()); |
225 | |
226 | accessType.write = true; |
227 | std::int64_t res = callAccess(path, accessType); |
228 | |
229 | ASSERT_EQ(unlink(path.c_str()), 0); |
230 | |
231 | if (userSkipsPermissionChecks()) { |
232 | return; |
233 | } |
234 | |
235 | ASSERT_NE(res, 0); |
236 | } |
237 | |
238 | TEST(AccessTests, TestReadWrite) { |
239 | AccessType accessType; |
240 | accessType.read = true; |
241 | accessType.write = true; |
242 | |
243 | std::string path = createTemporaryFile(name: __func__, accessType); |
244 | ASSERT_FALSE(path.empty()); |
245 | |
246 | std::int64_t res = callAccess(path, accessType); |
247 | |
248 | ASSERT_EQ(unlink(path.c_str()), 0); |
249 | |
250 | if (userSkipsPermissionChecks()) { |
251 | return; |
252 | } |
253 | |
254 | ASSERT_EQ(res, 0); |
255 | } |
256 | |
257 | TEST(AccessTests, TestNotReadWrite0) { |
258 | AccessType accessType; |
259 | accessType.read = false; |
260 | accessType.write = false; |
261 | |
262 | std::string path = createTemporaryFile(name: __func__, accessType); |
263 | ASSERT_FALSE(path.empty()); |
264 | |
265 | accessType.read = true; |
266 | accessType.write = true; |
267 | std::int64_t res = callAccess(path, accessType); |
268 | |
269 | ASSERT_EQ(unlink(path.c_str()), 0); |
270 | |
271 | if (userSkipsPermissionChecks()) { |
272 | return; |
273 | } |
274 | |
275 | ASSERT_NE(res, 0); |
276 | } |
277 | |
278 | TEST(AccessTests, TestNotReadWrite1) { |
279 | AccessType accessType; |
280 | accessType.read = true; |
281 | accessType.write = false; |
282 | |
283 | std::string path = createTemporaryFile(name: __func__, accessType); |
284 | ASSERT_FALSE(path.empty()); |
285 | |
286 | accessType.read = true; |
287 | accessType.write = true; |
288 | std::int64_t res = callAccess(path, accessType); |
289 | |
290 | ASSERT_EQ(unlink(path.c_str()), 0); |
291 | |
292 | if (userSkipsPermissionChecks()) { |
293 | return; |
294 | } |
295 | |
296 | ASSERT_NE(res, 0); |
297 | } |
298 | |
299 | TEST(AccessTests, TestNotReadWrite2) { |
300 | AccessType accessType; |
301 | accessType.read = false; |
302 | accessType.write = true; |
303 | |
304 | std::string path = createTemporaryFile(name: __func__, accessType); |
305 | ASSERT_FALSE(path.empty()); |
306 | |
307 | accessType.read = true; |
308 | accessType.write = true; |
309 | std::int64_t res = callAccess(path, accessType); |
310 | |
311 | ASSERT_EQ(unlink(path.c_str()), 0); |
312 | |
313 | if (userSkipsPermissionChecks()) { |
314 | return; |
315 | } |
316 | |
317 | ASSERT_NE(res, 0); |
318 | } |
319 | |
320 | TEST(AccessTests, TestExecute) { |
321 | AccessType accessType; |
322 | accessType.execute = true; |
323 | |
324 | std::string path = createTemporaryFile(name: __func__, accessType); |
325 | ASSERT_FALSE(path.empty()); |
326 | |
327 | std::int64_t res = callAccess(path, accessType); |
328 | |
329 | ASSERT_EQ(unlink(path.c_str()), 0); |
330 | |
331 | if (userSkipsPermissionChecks()) { |
332 | return; |
333 | } |
334 | |
335 | ASSERT_EQ(res, 0); |
336 | } |
337 | |
338 | TEST(AccessTests, TestNotExecute) { |
339 | AccessType accessType; |
340 | accessType.execute = false; |
341 | |
342 | std::string path = createTemporaryFile(name: __func__, accessType); |
343 | ASSERT_FALSE(path.empty()); |
344 | |
345 | accessType.execute = true; |
346 | std::int64_t res = callAccess(path, accessType); |
347 | |
348 | ASSERT_EQ(unlink(path.c_str()), 0); |
349 | |
350 | if (userSkipsPermissionChecks()) { |
351 | return; |
352 | } |
353 | |
354 | ASSERT_NE(res, 0); |
355 | } |
356 | |
357 | TEST(AccessTests, TestRWX) { |
358 | AccessType accessType; |
359 | accessType.read = true; |
360 | accessType.write = true; |
361 | accessType.execute = true; |
362 | |
363 | std::string path = createTemporaryFile(name: __func__, accessType); |
364 | ASSERT_FALSE(path.empty()); |
365 | |
366 | std::int64_t res = callAccess(path, accessType); |
367 | |
368 | ASSERT_EQ(unlink(path.c_str()), 0); |
369 | |
370 | if (userSkipsPermissionChecks()) { |
371 | return; |
372 | } |
373 | |
374 | ASSERT_EQ(res, 0); |
375 | } |
376 | |
377 | TEST(AccessTests, TestNotRWX0) { |
378 | AccessType accessType; |
379 | accessType.read = false; |
380 | accessType.write = false; |
381 | accessType.execute = false; |
382 | |
383 | std::string path = createTemporaryFile(name: __func__, accessType); |
384 | ASSERT_FALSE(path.empty()); |
385 | |
386 | accessType.read = true; |
387 | accessType.write = true; |
388 | accessType.execute = true; |
389 | std::int64_t res = callAccess(path, accessType); |
390 | |
391 | ASSERT_EQ(unlink(path.c_str()), 0); |
392 | |
393 | if (userSkipsPermissionChecks()) { |
394 | return; |
395 | } |
396 | |
397 | ASSERT_NE(res, 0); |
398 | } |
399 | |
400 | TEST(AccessTests, TestNotRWX1) { |
401 | AccessType accessType; |
402 | accessType.read = true; |
403 | accessType.write = false; |
404 | accessType.execute = false; |
405 | |
406 | std::string path = createTemporaryFile(name: __func__, accessType); |
407 | ASSERT_FALSE(path.empty()); |
408 | |
409 | accessType.read = true; |
410 | accessType.write = true; |
411 | accessType.execute = true; |
412 | std::int64_t res = callAccess(path, accessType); |
413 | |
414 | ASSERT_EQ(unlink(path.c_str()), 0); |
415 | |
416 | if (userSkipsPermissionChecks()) { |
417 | return; |
418 | } |
419 | |
420 | ASSERT_NE(res, 0); |
421 | } |
422 | |
423 | TEST(AccessTests, TestNotRWX2) { |
424 | AccessType accessType; |
425 | accessType.read = true; |
426 | accessType.write = true; |
427 | accessType.execute = false; |
428 | |
429 | std::string path = createTemporaryFile(name: __func__, accessType); |
430 | ASSERT_FALSE(path.empty()); |
431 | |
432 | accessType.read = true; |
433 | accessType.write = true; |
434 | accessType.execute = true; |
435 | std::int64_t res = callAccess(path, accessType); |
436 | |
437 | ASSERT_EQ(unlink(path.c_str()), 0); |
438 | |
439 | if (userSkipsPermissionChecks()) { |
440 | return; |
441 | } |
442 | |
443 | ASSERT_NE(res, 0); |
444 | } |
445 | |
446 | TEST(AccessTests, TestNotRWX3) { |
447 | AccessType accessType; |
448 | accessType.read = true; |
449 | accessType.write = false; |
450 | accessType.execute = true; |
451 | |
452 | std::string path = createTemporaryFile(name: __func__, accessType); |
453 | ASSERT_FALSE(path.empty()); |
454 | |
455 | accessType.read = true; |
456 | accessType.write = true; |
457 | accessType.execute = true; |
458 | std::int64_t res = callAccess(path, accessType); |
459 | |
460 | ASSERT_EQ(unlink(path.c_str()), 0); |
461 | |
462 | if (userSkipsPermissionChecks()) { |
463 | return; |
464 | } |
465 | |
466 | ASSERT_NE(res, 0); |
467 | } |
468 | |
469 | TEST(AccessTests, TestNotRWX4) { |
470 | AccessType accessType; |
471 | accessType.read = false; |
472 | accessType.write = true; |
473 | accessType.execute = true; |
474 | |
475 | std::string path = createTemporaryFile(name: __func__, accessType); |
476 | ASSERT_FALSE(path.empty()); |
477 | |
478 | accessType.read = true; |
479 | accessType.write = true; |
480 | accessType.execute = true; |
481 | std::int64_t res = callAccess(path, accessType); |
482 | |
483 | ASSERT_EQ(unlink(path.c_str()), 0); |
484 | |
485 | if (userSkipsPermissionChecks()) { |
486 | return; |
487 | } |
488 | |
489 | ASSERT_NE(res, 0); |
490 | } |
491 | |
492 | #endif // !_WIN32 |
493 | |