diff --git a/libc/src/__support/File/CMakeLists.txt b/libc/src/__support/File/CMakeLists.txt --- a/libc/src/__support/File/CMakeLists.txt +++ b/libc/src/__support/File/CMakeLists.txt @@ -15,3 +15,19 @@ libc.include.errno libc.src.errno.errno ) + +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}_file.cpp) + add_object_library( + platform_file + SRCS + ${LIBC_TARGET_OS}_file.cpp + DEPENDS + .file + libc.include.errno + libc.include.fcntl + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno + ) +endif() + diff --git a/libc/src/__support/File/linux_file.cpp b/libc/src/__support/File/linux_file.cpp new file mode 100644 --- /dev/null +++ b/libc/src/__support/File/linux_file.cpp @@ -0,0 +1,168 @@ +//===--- Linux specialization of the File data structure ------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "file.h" + +#include "src/__support/OSUtil/syscall.h" // For internal syscall function. + +#include +#include // For mode_t and other flags to the open syscall +#include // For malloc +#include // For syscall numbers + +namespace __llvm_libc { + +namespace { + +size_t write_func(File *, const void *, size_t); +size_t read_func(File *, void *, size_t); +int seek_func(File *, long, int); +int close_func(File *); +int flush_func(File *); + +} // anonymous namespace + +class LinuxFile : public File { + int fd; + +public: + constexpr LinuxFile(int file_descriptor, void *buffer, size_t buffer_size, + int buffer_mode, bool owned, File::ModeFlags modeflags) + : File(&write_func, &read_func, &seek_func, &close_func, flush_func, + buffer, buffer_size, buffer_mode, owned, modeflags), + fd(file_descriptor) {} + + static void init(LinuxFile *f, int file_descriptor, void *buffer, + size_t buffer_size, int buffer_mode, bool owned, + File::ModeFlags modeflags) { + File::init(f, &write_func, &read_func, &seek_func, &close_func, &flush_func, + buffer, buffer_size, buffer_mode, owned, modeflags); + f->fd = file_descriptor; + } + + int get_fd() const { return fd; } +}; + +namespace { + +size_t write_func(File *f, const void *data, size_t size) { + auto *lf = reinterpret_cast(f); + long ret = __llvm_libc::syscall(SYS_write, lf->get_fd(), data, size); + if (ret < 0) { + errno = -ret; + return 0; + } + return ret; +} + +size_t read_func(File *f, void *buf, size_t size) { + auto *lf = reinterpret_cast(f); + long ret = __llvm_libc::syscall(SYS_read, lf->get_fd(), buf, size); + if (ret < 0) { + errno = -ret; + return 0; + } + return ret; +} + +int seek_func(File *f, long offset, int whence) { + auto *lf = reinterpret_cast(f); +#ifdef SYS_lseek + long ret = __llvm_libc::syscall(SYS_lseek, lf->get_fd(), offset, whence); +#elif defined(SYS__llseek) + long result; + long ret = __llvm_libc::syscall(SYS__lseek, lf->get_fd(), offset >> 32, + offset, &result, whence); +#else +#error "lseek and _llseek syscalls not available to perform a seek operation." +#endif + + if (ret < 0) { + errno = -ret; + return -1; + } + return 0; +} + +int close_func(File *f) { + auto *lf = reinterpret_cast(f); + long ret = __llvm_libc::syscall(SYS_close, lf->get_fd()); + if (ret < 0) { + errno = -ret; + return -1; + } + return 0; +} + +int flush_func(File *f) { + auto *lf = reinterpret_cast(f); + long ret = __llvm_libc::syscall(SYS_fsync, lf->get_fd()); + if (ret < 0) { + errno = -ret; + return -1; + } + return 0; +} + +} // anonymous namespace + +File *openfile(const char *path, const char *mode) { + using ModeFlags = File::ModeFlags; + auto modeflags = File::mode_flags(mode); + if (modeflags == 0) { + errno = EINVAL; + return nullptr; + } + long open_flags = 0; + if (modeflags & ModeFlags(File::OpenMode::APPEND)) { + open_flags = O_CREAT | O_APPEND; + if (modeflags & ModeFlags(File::OpenMode::PLUS)) + open_flags |= O_RDWR; + else + open_flags |= O_WRONLY; + } else if (modeflags & ModeFlags(File::OpenMode::WRITE)) { + open_flags = O_CREAT | O_TRUNC; + if (modeflags & ModeFlags(File::OpenMode::PLUS)) + open_flags |= O_RDWR; + else + open_flags |= O_WRONLY; + } else { + if (modeflags & ModeFlags(File::OpenMode::PLUS)) + open_flags |= O_RDWR; + else + open_flags |= O_RDONLY; + } + + // File created will have 0666 permissions. + constexpr long OPEN_MODE = + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + +#ifdef SYS_open + int fd = __llvm_libc::syscall(SYS_open, path, open_flags, OPEN_MODE); +#elif defined(SYS_openat) + int fd = + __llvm_libc::syscall(SYS_openat, AT_FDCWD, path, open_flags, OPEN_MODE); +#else +#error "SYS_open and SYS_openat syscalls not available to perform a file open." +#endif + + if (fd < 0) { + errno = -fd; + return nullptr; + } + + void *buffer = malloc(File::DEFAULT_BUFFER_SIZE); + auto *file = reinterpret_cast(malloc(sizeof(LinuxFile))); + LinuxFile::init( + file, fd, buffer, File::DEFAULT_BUFFER_SIZE, + 0, // TODO: Set the correct buffer mode when buffer mode is available. + true, modeflags); + return file; +} + +} // namespace __llvm_libc diff --git a/libc/test/src/__support/File/CMakeLists.txt b/libc/test/src/__support/File/CMakeLists.txt --- a/libc/test/src/__support/File/CMakeLists.txt +++ b/libc/test/src/__support/File/CMakeLists.txt @@ -15,3 +15,19 @@ target_link_libraries( libc.test.src.__support.File.file_test PRIVATE LibcMemoryHelpers ) + +if (TARGET libc.src.__support.File.platform_file) + add_libc_unittest( + platform_file_test + SUITE + libc_support_unittests + SRCS + platform_file_test.cpp + DEPENDS + libc.src.__support.File.file + libc.src.__support.File.platform_file + libc.include.stdio + ) +endif() + +add_subdirectory(testdata) diff --git a/libc/test/src/__support/File/platform_file_test.cpp b/libc/test/src/__support/File/platform_file_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/__support/File/platform_file_test.cpp @@ -0,0 +1,171 @@ +//===-- Unittests for target platform file implementation -----------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "src/__support/File/file.h" +#include "utils/UnitTest/Test.h" + +#include // For SEEK_* macros + +using File = __llvm_libc::File; +constexpr char TEXT[] = "Hello, File"; +constexpr size_t TEXT_SIZE = sizeof(TEXT) - 1; // Ignore the null terminator + +TEST(LlvmLibcPlatformFileTest, CreateWriteCloseAndReadBack) { + constexpr char FILENAME[] = "testdata/create_write_close_and_readback.test"; + File *file = __llvm_libc::openfile(FILENAME, "w"); + ASSERT_FALSE(file == nullptr); + ASSERT_EQ(file->write(TEXT, TEXT_SIZE), TEXT_SIZE); + ASSERT_EQ(file->close(), 0); + + file = __llvm_libc::openfile(FILENAME, "r"); + ASSERT_FALSE(file == nullptr); + char data[sizeof(TEXT)]; + ASSERT_EQ(file->read(data, TEXT_SIZE), TEXT_SIZE); + data[TEXT_SIZE] = '\0'; + ASSERT_STREQ(data, TEXT); + + // Reading more data should trigger EOF. + ASSERT_EQ(file->read(data, TEXT_SIZE), size_t(0)); + ASSERT_TRUE(file->iseof()); + + ASSERT_EQ(file->close(), 0); +} + +TEST(LlvmLibcPlatformFileTest, CreateWriteSeekAndReadBack) { + constexpr char FILENAME[] = "testdata/create_write_seek_and_readback.test"; + File *file = __llvm_libc::openfile(FILENAME, "w+"); + ASSERT_FALSE(file == nullptr); + ASSERT_EQ(file->write(TEXT, TEXT_SIZE), TEXT_SIZE); + + ASSERT_EQ(file->seek(0, SEEK_SET), 0); + + char data[sizeof(TEXT)]; + ASSERT_EQ(file->read(data, TEXT_SIZE), TEXT_SIZE); + data[TEXT_SIZE] = '\0'; + ASSERT_STREQ(data, TEXT); + + // Reading more data should trigger EOF. + ASSERT_EQ(file->read(data, TEXT_SIZE), size_t(0)); + ASSERT_TRUE(file->iseof()); + + ASSERT_EQ(file->close(), 0); +} + +TEST(LlvmLibcPlatformFileTest, CreateAppendCloseAndReadBack) { + constexpr char FILENAME[] = "testdata/create_append_close_and_readback.test"; + File *file = __llvm_libc::openfile(FILENAME, "w"); + ASSERT_FALSE(file == nullptr); + ASSERT_EQ(file->write(TEXT, TEXT_SIZE), TEXT_SIZE); + ASSERT_EQ(file->close(), 0); + + file = __llvm_libc::openfile(FILENAME, "a"); + ASSERT_FALSE(file == nullptr); + constexpr char APPEND_TEXT[] = " Append Text"; + constexpr size_t APPEND_TEXT_SIZE = sizeof(APPEND_TEXT) - 1; + ASSERT_EQ(file->write(APPEND_TEXT, APPEND_TEXT_SIZE), APPEND_TEXT_SIZE); + ASSERT_EQ(file->close(), 0); + + file = __llvm_libc::openfile(FILENAME, "r"); + ASSERT_FALSE(file == nullptr); + constexpr size_t READ_SIZE = TEXT_SIZE + APPEND_TEXT_SIZE; + char data[READ_SIZE + 1]; + ASSERT_EQ(file->read(data, READ_SIZE), READ_SIZE); + data[READ_SIZE] = '\0'; + ASSERT_STREQ(data, "Hello, File Append Text"); + + // Reading more data should trigger EOF. + ASSERT_EQ(file->read(data, READ_SIZE), size_t(0)); + ASSERT_TRUE(file->iseof()); + + ASSERT_EQ(file->close(), 0); +} + +TEST(LlvmLibcPlatformFileTest, CreateAppendSeekAndReadBack) { + constexpr char FILENAME[] = "testdata/create_append_seek_and_readback.test"; + File *file = __llvm_libc::openfile(FILENAME, "w"); + ASSERT_FALSE(file == nullptr); + ASSERT_EQ(file->write(TEXT, TEXT_SIZE), TEXT_SIZE); + ASSERT_EQ(file->close(), 0); + + file = __llvm_libc::openfile(FILENAME, "a+"); + ASSERT_FALSE(file == nullptr); + constexpr char APPEND_TEXT[] = " Append Text"; + constexpr size_t APPEND_TEXT_SIZE = sizeof(APPEND_TEXT) - 1; + ASSERT_EQ(file->write(APPEND_TEXT, APPEND_TEXT_SIZE), APPEND_TEXT_SIZE); + + ASSERT_EQ(file->seek(-APPEND_TEXT_SIZE, SEEK_END), 0); + char data[APPEND_TEXT_SIZE + 1]; + ASSERT_EQ(file->read(data, APPEND_TEXT_SIZE), APPEND_TEXT_SIZE); + data[APPEND_TEXT_SIZE] = '\0'; + ASSERT_STREQ(data, APPEND_TEXT); + + // Reading more data should trigger EOF. + ASSERT_EQ(file->read(data, APPEND_TEXT_SIZE), size_t(0)); + ASSERT_TRUE(file->iseof()); + + ASSERT_EQ(file->close(), 0); +} + +TEST(LlvmLibcPlatformFileTest, LargeFile) { + constexpr size_t DATA_SIZE = File::DEFAULT_BUFFER_SIZE >> 2; + constexpr char BYTE = 123; + char write_data[DATA_SIZE]; + for (size_t i = 0; i < DATA_SIZE; ++i) + write_data[i] = BYTE; + + constexpr char FILENAME[] = "testdata/large_file.test"; + File *file = __llvm_libc::openfile(FILENAME, "w"); + ASSERT_FALSE(file == nullptr); + + constexpr int REPEAT = 5; + for (int i = 0; i < REPEAT; ++i) { + ASSERT_EQ(file->write(write_data, DATA_SIZE), DATA_SIZE); + } + ASSERT_EQ(file->close(), 0); + + file = __llvm_libc::openfile(FILENAME, "r"); + ASSERT_FALSE(file == nullptr); + constexpr size_t READ_SIZE = DATA_SIZE * REPEAT; + char data[READ_SIZE] = {0}; + ASSERT_EQ(file->read(data, READ_SIZE), READ_SIZE); + + for (size_t i = 0; i < READ_SIZE; ++i) + ASSERT_EQ(data[i], BYTE); + + // Reading more data should trigger EOF. + ASSERT_EQ(file->read(data, 1), size_t(0)); + ASSERT_TRUE(file->iseof()); + + ASSERT_EQ(file->close(), 0); +} + +TEST(LlvmLibcPlatformFileTest, IncorrectOperation) { + constexpr char FILENAME[] = "testdata/incorrect_operation.test"; + char data[1] = {123}; + + File *file = __llvm_libc::openfile(FILENAME, "w"); + ASSERT_FALSE(file == nullptr); + ASSERT_EQ(file->read(data, 1), size_t(0)); // Cannot read + ASSERT_FALSE(file->iseof()); + ASSERT_TRUE(file->error()); + ASSERT_EQ(file->close(), 0); + + file = __llvm_libc::openfile(FILENAME, "r"); + ASSERT_FALSE(file == nullptr); + ASSERT_EQ(file->write(data, 1), size_t(0)); // Cannot write + ASSERT_FALSE(file->iseof()); + ASSERT_TRUE(file->error()); + ASSERT_EQ(file->close(), 0); + + file = __llvm_libc::openfile(FILENAME, "a"); + ASSERT_FALSE(file == nullptr); + ASSERT_EQ(file->read(data, 1), size_t(0)); // Cannot read + ASSERT_FALSE(file->iseof()); + ASSERT_TRUE(file->error()); + ASSERT_EQ(file->close(), 0); +} diff --git a/libc/test/src/__support/File/testdata/CMakeLists.txt b/libc/test/src/__support/File/testdata/CMakeLists.txt new file mode 100644