//===-- flang/unittests/Runtime/AccessTest.cpp ----------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // TODO: ACCESS is not yet implemented on Windows #ifndef _WIN32 #include "CrashHandlerFixture.h" #include "gtest/gtest.h" #include "flang/Runtime/extensions.h" #include "llvm/ADT/Twine.h" #include #include #include #include namespace { struct AccessTests : public CrashHandlerFixture {}; struct AccessType { bool read{false}; bool write{false}; bool execute{false}; bool exists{false}; }; } // namespace static bool userSkipsPermissionChecks() { // The tests in this file assume normal permission checks apply to the user // running the tests. This isn't true when the test is run by root. return geteuid() == 0; } static std::string addPIDSuffix(const char *name) { std::stringstream ss; ss << name; ss << '.'; ss << getpid(); return ss.str(); } static bool exists(const std::string &path) { return access(path.c_str(), F_OK) == 0; } // Implementation of std::filesystem::temp_directory_path adapted from libcxx // See llvm-project/libcxx/src/filesystem/operations.cpp // Using std::filesystem is inconvenient because the required flags are not // consistent accross compilers and CMake doesn't have built in support to // determine the correct flags. static const char *temp_directory_path() { // TODO: Windows const char *env_paths[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR"}; const char *ret = nullptr; for (auto &ep : env_paths) { if ((ret = getenv(ep))) { break; } } if (ret == nullptr) { #if defined(__ANDROID__) ret = "/data/local/tmp"; #else ret = "/tmp"; #endif } assert(exists(ret)); return ret; } static std::string createTemporaryFile( const char *name, const AccessType &accessType) { std::string path = (llvm::Twine{temp_directory_path()} + "/" + addPIDSuffix(name)).str(); // O_CREAT | O_EXCL enforces that this file is newly created by this call. // This feels risky. If we don't have permission to create files in the // temporary directory or if the files already exist, the test will fail. // But we can't use std::tmpfile() because we need a path to the file and // to control the filesystem permissions mode_t mode{0}; if (accessType.read) { mode |= S_IRUSR; } if (accessType.write) { mode |= S_IWUSR; } if (accessType.execute) { mode |= S_IXUSR; } int file = open(path.c_str(), O_CREAT | O_EXCL, mode); if (file == -1) { return {}; } close(file); return path; } static std::int64_t callAccess( const std::string &path, const AccessType &accessType) { const char *cpath{path.c_str()}; std::int64_t pathlen = std::strlen(cpath); std::string mode; if (accessType.read) { mode += 'r'; } if (accessType.write) { mode += 'w'; } if (accessType.execute) { mode += 'x'; } if (accessType.exists) { mode += ' '; } const char *cmode = mode.c_str(); std::int64_t modelen = std::strlen(cmode); return FORTRAN_PROCEDURE_NAME(access)(cpath, pathlen, cmode, modelen); } TEST(AccessTests, TestExists) { AccessType accessType; accessType.exists = true; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); ASSERT_EQ(res, 0); } TEST(AccessTests, TestNotExists) { std::string nonExistant{addPIDSuffix(__func__)}; ASSERT_FALSE(exists(nonExistant)); AccessType accessType; accessType.exists = true; std::int64_t res = callAccess(nonExistant, accessType); ASSERT_NE(res, 0); } TEST(AccessTests, TestRead) { AccessType accessType; accessType.read = true; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_EQ(res, 0); } TEST(AccessTests, TestNotRead) { AccessType accessType; accessType.read = false; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); accessType.read = true; std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_NE(res, 0); } TEST(AccessTests, TestWrite) { AccessType accessType; accessType.write = true; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_EQ(res, 0); } TEST(AccessTests, TestNotWrite) { AccessType accessType; accessType.write = false; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); accessType.write = true; std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_NE(res, 0); } TEST(AccessTests, TestReadWrite) { AccessType accessType; accessType.read = true; accessType.write = true; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_EQ(res, 0); } TEST(AccessTests, TestNotReadWrite0) { AccessType accessType; accessType.read = false; accessType.write = false; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); accessType.read = true; accessType.write = true; std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_NE(res, 0); } TEST(AccessTests, TestNotReadWrite1) { AccessType accessType; accessType.read = true; accessType.write = false; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); accessType.read = true; accessType.write = true; std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_NE(res, 0); } TEST(AccessTests, TestNotReadWrite2) { AccessType accessType; accessType.read = false; accessType.write = true; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); accessType.read = true; accessType.write = true; std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_NE(res, 0); } TEST(AccessTests, TestExecute) { AccessType accessType; accessType.execute = true; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_EQ(res, 0); } TEST(AccessTests, TestNotExecute) { AccessType accessType; accessType.execute = false; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); accessType.execute = true; std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_NE(res, 0); } TEST(AccessTests, TestRWX) { AccessType accessType; accessType.read = true; accessType.write = true; accessType.execute = true; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_EQ(res, 0); } TEST(AccessTests, TestNotRWX0) { AccessType accessType; accessType.read = false; accessType.write = false; accessType.execute = false; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); accessType.read = true; accessType.write = true; accessType.execute = true; std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_NE(res, 0); } TEST(AccessTests, TestNotRWX1) { AccessType accessType; accessType.read = true; accessType.write = false; accessType.execute = false; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); accessType.read = true; accessType.write = true; accessType.execute = true; std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_NE(res, 0); } TEST(AccessTests, TestNotRWX2) { AccessType accessType; accessType.read = true; accessType.write = true; accessType.execute = false; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); accessType.read = true; accessType.write = true; accessType.execute = true; std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_NE(res, 0); } TEST(AccessTests, TestNotRWX3) { AccessType accessType; accessType.read = true; accessType.write = false; accessType.execute = true; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); accessType.read = true; accessType.write = true; accessType.execute = true; std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_NE(res, 0); } TEST(AccessTests, TestNotRWX4) { AccessType accessType; accessType.read = false; accessType.write = true; accessType.execute = true; std::string path = createTemporaryFile(__func__, accessType); ASSERT_FALSE(path.empty()); accessType.read = true; accessType.write = true; accessType.execute = true; std::int64_t res = callAccess(path, accessType); ASSERT_EQ(unlink(path.c_str()), 0); if (userSkipsPermissionChecks()) { return; } ASSERT_NE(res, 0); } #endif // !_WIN32