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
22namespace {
23
24struct AccessTests : public CrashHandlerFixture {};
25
26struct AccessType {
27 bool read{false};
28 bool write{false};
29 bool execute{false};
30 bool exists{false};
31};
32
33} // namespace
34
35static 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
41static 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
51static 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.
60static 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
83static 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
114static 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
139TEST(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
153TEST(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
164TEST(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
182TEST(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
201TEST(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
219TEST(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
238TEST(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
257TEST(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
278TEST(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
299TEST(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
320TEST(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
338TEST(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
357TEST(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
377TEST(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
400TEST(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
423TEST(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
446TEST(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
469TEST(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

source code of flang-rt/unittests/Runtime/AccessTest.cpp