mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-19 05:06:45 +00:00
[flang][runtime] Add ACCESS library procedure (#88517)
Re-land https://github.com/llvm/llvm-project/pull/88395 Two build-bots were broken by the old version: - https://lab.llvm.org/buildbot/#/builders/285/builds/245 - https://lab.llvm.org/buildbot/#/builders/21/builds/96988 The problem in both cases was that the compiler did not support `std::filesystem` (which I use in the unit test). I have removed the dependency upon std::filesystem because there isn't an easy way to add the right linker options so that this is supported correctly in all build environments [1] [1] https://gitlab.kitware.com/cmake/cmake/-/issues/17834 --- This is a GNU extension: https://gcc.gnu.org/onlinedocs/gfortran/ACCESS.html Used in SALMON: https://salmon-tddft.jp/download.html Unfortunately the intrinsic takes a file path to operate on so there isn't an easy way to make the test robust. The unit test expects to be able to create, set read write and execute permissions, and delete files called std::filesystem::temp_directory_path() / <test_name>.<pid> The test will fail if a file already exists with that name. I have not implemented the intrinsic on Windows because this is wrapping a POSIX system call and Windows doesn't support all of the permission bits tested by the intrinsic. I don't have a Windows machine easily available to check if Gfortran implements this intrinsic on Windows.
This commit is contained in:
parent
70fe6ad535
commit
668a58b892
@ -657,6 +657,14 @@ CALL CO_REDUCE
|
||||
CALL CO_SUM
|
||||
```
|
||||
|
||||
### Inquiry Functions
|
||||
ACCESS (GNU extension) is not supported on Windows. Otherwise:
|
||||
```
|
||||
CHARACTER(LEN=*) :: path = 'path/to/file'
|
||||
IF (ACCESS(path, 'rwx')) &
|
||||
...
|
||||
```
|
||||
|
||||
## Non-standard intrinsics
|
||||
### PGI
|
||||
```
|
||||
|
@ -44,5 +44,12 @@ std::int64_t RTNAME(Signal)(std::int64_t number, void (*handler)(int));
|
||||
// GNU extension subroutine SLEEP(SECONDS)
|
||||
void RTNAME(Sleep)(std::int64_t seconds);
|
||||
|
||||
// GNU extension function ACCESS(NAME, MODE)
|
||||
// TODO: not supported on Windows
|
||||
#ifndef _WIN32
|
||||
std::int64_t FORTRAN_PROCEDURE_NAME(access)(const char *name,
|
||||
std::int64_t nameLength, const char *mode, std::int64_t modeLength);
|
||||
#endif
|
||||
|
||||
} // extern "C"
|
||||
#endif // FORTRAN_RUNTIME_EXTENSIONS_H_
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "flang/Runtime/entry-names.h"
|
||||
#include "flang/Runtime/io-api.h"
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <signal.h>
|
||||
#include <thread>
|
||||
@ -138,5 +139,77 @@ void RTNAME(Sleep)(std::int64_t seconds) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(seconds));
|
||||
}
|
||||
|
||||
// TODO: not supported on Windows
|
||||
#ifndef _WIN32
|
||||
std::int64_t FORTRAN_PROCEDURE_NAME(access)(const char *name,
|
||||
std::int64_t nameLength, const char *mode, std::int64_t modeLength) {
|
||||
std::int64_t ret{-1};
|
||||
if (nameLength <= 0 || modeLength <= 0 || !name || !mode) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ensure name is null terminated
|
||||
char *newName{nullptr};
|
||||
if (name[nameLength - 1] != '\0') {
|
||||
newName = static_cast<char *>(std::malloc(nameLength + 1));
|
||||
std::memcpy(newName, name, nameLength);
|
||||
newName[nameLength] = '\0';
|
||||
name = newName;
|
||||
}
|
||||
|
||||
// calculate mode
|
||||
bool read{false};
|
||||
bool write{false};
|
||||
bool execute{false};
|
||||
bool exists{false};
|
||||
int imode{0};
|
||||
|
||||
for (std::int64_t i = 0; i < modeLength; ++i) {
|
||||
switch (mode[i]) {
|
||||
case 'r':
|
||||
read = true;
|
||||
break;
|
||||
case 'w':
|
||||
write = true;
|
||||
break;
|
||||
case 'x':
|
||||
execute = true;
|
||||
break;
|
||||
case ' ':
|
||||
exists = true;
|
||||
break;
|
||||
default:
|
||||
// invalid mode
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
if (!read && !write && !execute && !exists) {
|
||||
// invalid mode
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!read && !write && !execute) {
|
||||
imode = F_OK;
|
||||
} else {
|
||||
if (read) {
|
||||
imode |= R_OK;
|
||||
}
|
||||
if (write) {
|
||||
imode |= W_OK;
|
||||
}
|
||||
if (execute) {
|
||||
imode |= X_OK;
|
||||
}
|
||||
}
|
||||
ret = access(name, imode);
|
||||
|
||||
cleanup:
|
||||
if (newName) {
|
||||
free(newName);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Fortran::runtime
|
||||
} // extern "C"
|
||||
|
422
flang/unittests/Runtime/AccessTest.cpp
Normal file
422
flang/unittests/Runtime/AccessTest.cpp
Normal file
@ -0,0 +1,422 @@
|
||||
//===-- 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 <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace {
|
||||
|
||||
struct AccessTests : public CrashHandlerFixture {};
|
||||
|
||||
struct AccessType {
|
||||
bool read{false};
|
||||
bool write{false};
|
||||
bool execute{false};
|
||||
bool exists{false};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
ASSERT_NE(res, 0);
|
||||
}
|
||||
|
||||
#endif // !_WIN32
|
@ -1,4 +1,5 @@
|
||||
add_flang_unittest(FlangRuntimeTests
|
||||
AccessTest.cpp
|
||||
Allocatable.cpp
|
||||
ArrayConstructor.cpp
|
||||
BufferTest.cpp
|
||||
|
Loading…
x
Reference in New Issue
Block a user