diff --git a/libc/config/gpu/entrypoints.txt b/libc/config/gpu/entrypoints.txt --- a/libc/config/gpu/entrypoints.txt +++ b/libc/config/gpu/entrypoints.txt @@ -90,6 +90,8 @@ # stdio.h entrypoints libc.src.stdio.puts libc.src.stdio.fputs + libc.src.stdio.fclose + libc.src.stdio.fopen libc.src.stdio.stdin libc.src.stdio.stdout libc.src.stdio.stderr diff --git a/libc/docs/gpu/support.rst b/libc/docs/gpu/support.rst --- a/libc/docs/gpu/support.rst +++ b/libc/docs/gpu/support.rst @@ -124,4 +124,6 @@ ============= ========= ============ puts |check| |check| fputs |check| |check| +fclose |check| |check| +fopen |check| |check| ============= ========= ============ diff --git a/libc/include/llvm-libc-types/rpc_opcodes_t.h b/libc/include/llvm-libc-types/rpc_opcodes_t.h --- a/libc/include/llvm-libc-types/rpc_opcodes_t.h +++ b/libc/include/llvm-libc-types/rpc_opcodes_t.h @@ -15,11 +15,15 @@ RPC_WRITE_TO_STDOUT = 2, RPC_WRITE_TO_STDERR = 3, RPC_WRITE_TO_STREAM = 4, - RPC_MALLOC = 5, - RPC_FREE = 6, - RPC_TEST_INCREMENT = 7, - RPC_TEST_INTERFACE = 8, - RPC_TEST_STREAM = 9, + RPC_OPEN_FILE = 5, + RPC_CLOSE_FILE = 6, + RPC_MALLOC = 7, + RPC_FREE = 8, + // TODO: Move these out of here and handle then with custom handlers in the + // loader. + RPC_TEST_INCREMENT = 1000, + RPC_TEST_INTERFACE = 1001, + RPC_TEST_STREAM = 1002, } rpc_opcode_t; #endif // __LLVM_LIBC_TYPES_RPC_OPCODE_H__ diff --git a/libc/src/__support/File/gpu/file.cpp b/libc/src/__support/File/gpu/file.cpp --- a/libc/src/__support/File/gpu/file.cpp +++ b/libc/src/__support/File/gpu/file.cpp @@ -8,8 +8,10 @@ #include "src/__support/File/file.h" +#include "llvm-libc-types/rpc_opcodes_t.h" #include "src/__support/RPC/rpc_client.h" #include "src/errno/libc_errno.h" // For error macros +#include "src/string/string_utils.h" #include @@ -18,6 +20,7 @@ namespace { FileIOResult write_func(File *, const void *, size_t); +int close_func(File *); } // namespace @@ -26,8 +29,8 @@ public: constexpr GPUFile(uintptr_t file, File::ModeFlags modeflags) - : File(&write_func, nullptr, nullptr, nullptr, nullptr, 0, _IONBF, false, - modeflags), + : File(&write_func, nullptr, nullptr, &close_func, nullptr, 0, _IONBF, + false, modeflags), file(file) {} uintptr_t get_file() const { return file; } @@ -85,8 +88,42 @@ return ret; } +int close_func(File *file) { + int ret = 0; + GPUFile *gpu_file = reinterpret_cast(file); + rpc::Client::Port port = rpc::client.open(); + port.send_and_recv( + [=](rpc::Buffer *buffer) { buffer->data[0] = gpu_file->get_file(); }, + [&](rpc::Buffer *buffer) { ret = buffer->data[0]; }); + port.close(); + + return ret; +} + } // namespace +void *ptr; + +ErrorOr openfile(const char *path, const char *mode) { + auto modeflags = File::mode_flags(mode); + if (modeflags == 0) + return Error(EINVAL); + + uintptr_t file; + rpc::Client::Port port = rpc::client.open(); + port.send_n(path, internal::string_length(path) + 1); + port.send_and_recv( + [=](rpc::Buffer *buffer) { + inline_memcpy(buffer->data, mode, internal::string_length(mode) + 1); + }, + [&](rpc::Buffer *buffer) { file = buffer->data[0]; }); + port.close(); + + static GPUFile gpu_file(0, 0); + gpu_file = GPUFile(file, modeflags); + return &gpu_file; +} + static GPUFile StdIn(0UL, File::ModeFlags(File::OpenMode::READ)); File *stdin = &StdIn; diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt --- a/libc/test/src/stdio/CMakeLists.txt +++ b/libc/test/src/stdio/CMakeLists.txt @@ -222,6 +222,18 @@ libc.src.stdio.fputs ) +add_libc_test( + fopen_test + SUITE + libc_stdio_unittests + SRCS + fopen_test.cpp + DEPENDS + libc.src.stdio.fputs + libc.src.stdio.fclose + libc.src.stdio.fopen +) + add_libc_unittest( putc_test SUITE @@ -329,6 +341,9 @@ libc.src.stdio.setvbuf ) +# Create an output directory for any temporary test files. +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/testdata) + if(LIBC_TARGET_ARCHITECTURE_IS_GPU) return() endif() diff --git a/libc/test/src/stdio/fopen_test.cpp b/libc/test/src/stdio/fopen_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/stdio/fopen_test.cpp @@ -0,0 +1,27 @@ +//===-- Unittests for fopen / fclose --------------------------------------===// +// +// 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 "src/stdio/fclose.h" +#include "src/stdio/fopen.h" +#include "src/stdio/fputs.h" + +#include "test/UnitTest/Test.h" + +TEST(LlvmLibcFOpenTest, PrintToFile) { + int result; + + FILE *file = __llvm_libc::fopen("./testdata/test_data.txt", "w"); + ASSERT_FALSE(file == nullptr); + + constexpr char another[] = "A simple string written to a file\n"; + result = __llvm_libc::fputs(another, file); + EXPECT_GE(result, 0); + + ASSERT_EQ(0, __llvm_libc::fclose(file)); +} diff --git a/libc/utils/gpu/server/rpc_server.cpp b/libc/utils/gpu/server/rpc_server.cpp --- a/libc/utils/gpu/server/rpc_server.cpp +++ b/libc/utils/gpu/server/rpc_server.cpp @@ -101,6 +101,24 @@ } break; } + case RPC_OPEN_FILE: { + uint64_t sizes[rpc::MAX_LANE_SIZE] = {0}; + void *paths[rpc::MAX_LANE_SIZE] = {nullptr}; + port->recv_n(paths, sizes, [&](uint64_t size) { return new char[size]; }); + port->recv_and_send([&](rpc::Buffer *buffer, uint32_t id) { + FILE *file = fopen(reinterpret_cast(paths[id]), + reinterpret_cast(buffer->data)); + buffer->data[0] = reinterpret_cast(file); + }); + break; + } + case RPC_CLOSE_FILE: { + port->recv_and_send([&](rpc::Buffer *buffer, uint32_t id) { + FILE *file = reinterpret_cast(buffer->data[0]); + buffer->data[0] = fclose(file); + }); + break; + } case RPC_EXIT: { // Send a response to the client to signal that we are ready to exit. port->recv_and_send([](rpc::Buffer *) {});