diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -146,7 +146,7 @@ } def StdIOAPI : PublicAPI<"stdio.h"> { - let Types = ["size_t", "FILE"]; + let Types = ["size_t", "FILE", "cookie_io_functions_t"]; } def StdlibAPI : PublicAPI<"stdlib.h"> { diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -255,6 +255,7 @@ libc.src.stdio.flockfile libc.src.stdio.fflush libc.src.stdio.fopen + libc.src.stdio.fopencookie libc.src.stdio.fread libc.src.stdio.fread_unlocked libc.src.stdio.fseek diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -125,6 +125,7 @@ DEPENDS .llvm_libc_common_h .llvm-libc-macros.file_seek_macros + .llvm-libc-types.cookie_io_functions_t .llvm-libc-types.FILE .llvm-libc-types.size_t ) diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt --- a/libc/include/llvm-libc-types/CMakeLists.txt +++ b/libc/include/llvm-libc-types/CMakeLists.txt @@ -5,6 +5,7 @@ add_header(__qsortcompare_t HDR __qsortcompare_t.h) add_header(__sighandler_t HDR __sighandler_t.h) add_header(cnd_t HDR cnd_t.h) +add_header(cookie_io_functions_t HDR cookie_io_functions_t.h DEPENDS .off64_t) add_header(double_t HDR double_t.h) add_header(div_t HDR div_t.h) add_header(ldiv_t HDR ldiv_t.h) @@ -17,6 +18,7 @@ add_header(mode_t HDR mode_t.h) add_header(mtx_t HDR mtx_t.h DEPENDS .__futex_word .__mutex_type) add_header(off_t HDR off_t.h) +add_header(off64_t HDR off64_t.h) add_header(once_flag HDR once_flag.h DEPENDS .__futex_word) add_header(pthread_attr_t HDR pthread_attr_t.h) add_header(pthread_mutexattr_t HDR pthread_mutexattr_t.h) diff --git a/libc/include/llvm-libc-types/cookie_io_functions_t.h b/libc/include/llvm-libc-types/cookie_io_functions_t.h new file mode 100644 --- /dev/null +++ b/libc/include/llvm-libc-types/cookie_io_functions_t.h @@ -0,0 +1,28 @@ +//===-- Definition of type cookie_io_functions_t --------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef __LLVM_LIBC_TYPES_COOKIE_IO_FUNCTIONS_T_H +#define __LLVM_LIBC_TYPES_COOKIE_IO_FUNCTIONS_T_H + +#include +#include +#include + +typedef ssize_t cookie_read_function_t(void *, char *, size_t); +typedef ssize_t cookie_write_function_t(void *, const char *, size_t); +typedef int cookie_seek_function_t(void *, off64_t *, int); +typedef int cookie_close_function_t(void *); + +typedef struct { + cookie_read_function_t *read; + cookie_write_function_t *write; + cookie_seek_function_t *seek; + cookie_close_function_t *close; +} cookie_io_functions_t; + +#endif // __LLVM_LIBC_TYPES_COOKIE_IO_FUNCTIONS_T_H diff --git a/libc/include/llvm-libc-types/off64_t.h b/libc/include/llvm-libc-types/off64_t.h new file mode 100644 --- /dev/null +++ b/libc/include/llvm-libc-types/off64_t.h @@ -0,0 +1,14 @@ +//===-- Definition of off64_t type ----------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef __LLVM_LIBC_TYPES_OFF64_T_H__ +#define __LLVM_LIBC_TYPES_OFF64_T_H__ + +typedef __INT64_TYPE__ off64_t; + +#endif // __LLVM_LIBC_TYPES_OFF64_T_H__ diff --git a/libc/spec/gnu_ext.td b/libc/spec/gnu_ext.td --- a/libc/spec/gnu_ext.td +++ b/libc/spec/gnu_ext.td @@ -1,4 +1,5 @@ def GnuExtensions : StandardSpec<"GNUExtensions"> { + NamedType CookieIOFunctionsT = NamedType<"cookie_io_functions_t">; HeaderSpec CType = HeaderSpec< "ctype.h", [], // Macros @@ -68,9 +69,14 @@ HeaderSpec StdIO = HeaderSpec< "stdio.h", [], // Macros - [], // Types + [CookieIOFunctionsT], // Types [], // Enumerations [ + FunctionSpec< + "fopencookie", + RetValSpec, + [ArgSpec, ArgSpec, ArgSpec] + >, FunctionSpec< "fread_unlocked", RetValSpec, @@ -88,7 +94,7 @@ ArgSpec] >, ] - >; + >; let Headers = [ CType, diff --git a/libc/spec/spec.td b/libc/spec/spec.td --- a/libc/spec/spec.td +++ b/libc/spec/spec.td @@ -110,7 +110,6 @@ //added because __assert_fail needs it. def UnsignedType : NamedType<"unsigned">; - class Macro { string Name = name; } diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt --- a/libc/src/stdio/CMakeLists.txt +++ b/libc/src/stdio/CMakeLists.txt @@ -119,3 +119,14 @@ libc.src.__support.File.file libc.src.__support.File.platform_file ) + +add_entrypoint_object( + fopencookie + SRCS + fopencookie.cpp + HDRS + fopencookie.h + DEPENDS + libc.include.stdio + libc.src.__support.File.file +) diff --git a/libc/src/stdio/fopencookie.h b/libc/src/stdio/fopencookie.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/fopencookie.h @@ -0,0 +1,21 @@ +//===-- Implementation header of fopencookie --------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_STDIO_FOPENCOOKIE_H +#define LLVM_LIBC_SRC_STDIO_FOPENCOOKIE_H + +#include + +namespace __llvm_libc { + +::FILE *fopencookie(void *cookie, const char *__restrict mode, + cookie_io_functions_t desc); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDIO_FOPENCOOKIE_H diff --git a/libc/src/stdio/fopencookie.cpp b/libc/src/stdio/fopencookie.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdio/fopencookie.cpp @@ -0,0 +1,83 @@ +//===-- Implementation of fopencookie -------------------------------------===// +// +// 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/stdio/fopencookie.h" +#include "src/__support/File/file.h" + +#include +#include +#include + +namespace __llvm_libc { + +namespace { + +class CookieFile : public __llvm_libc::File { +public: + void *cookie; + cookie_io_functions_t ops; +}; + +size_t write_func(File *f, const void *data, size_t size) { + auto cookie_file = reinterpret_cast(f); + if (cookie_file->ops.write == nullptr) + return 0; + return cookie_file->ops.write(cookie_file->cookie, + reinterpret_cast(data), size); +} + +size_t read_func(File *f, void *data, size_t size) { + auto cookie_file = reinterpret_cast(f); + if (cookie_file->ops.read == nullptr) + return 0; + return cookie_file->ops.read(cookie_file->cookie, + reinterpret_cast(data), size); +} + +int seek_func(File *f, long offset, int whence) { + auto cookie_file = reinterpret_cast(f); + if (cookie_file->ops.seek == nullptr) { + errno = EINVAL; + return -1; + } + off64_t offset64 = offset; + return cookie_file->ops.seek(cookie_file->cookie, &offset64, whence); +} + +int close_func(File *f) { + auto cookie_file = reinterpret_cast(f); + if (cookie_file->ops.close == nullptr) + return 0; + return cookie_file->ops.close(cookie_file->cookie); +} + +int flush_func(File *f) { return 0; } + +} // anonymous namespace + +LLVM_LIBC_FUNCTION(::FILE *, fopencookie, + (void *cookie, const char *mode, + cookie_io_functions_t ops)) { + auto modeflags = File::mode_flags(mode); + void *buffer = malloc(File::DEFAULT_BUFFER_SIZE); + auto *file = reinterpret_cast(malloc(sizeof(CookieFile))); + if (file == nullptr) + return nullptr; + + File::init(file, &write_func, &read_func, &seek_func, &close_func, + &flush_func, buffer, File::DEFAULT_BUFFER_SIZE, + 0, // Default buffering style + true, // Owned buffer + modeflags); + file->cookie = cookie; + file->ops = ops; + + return reinterpret_cast<::FILE *>(file); +} + +} // namespace __llvm_libc 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 @@ -30,6 +30,28 @@ libc.src.stdio.fwrite_unlocked ) +add_libc_unittest( + fopencookie_test + SUITE + libc_stdio_unittests + SRCS + fopencookie_test.cpp + DEPENDS + libc.include.errno + libc.include.stdio + libc.include.stdlib + libc.src.stdio.fclose + libc.src.stdio.fflush + libc.src.stdio.fopencookie + libc.src.stdio.fread + libc.src.stdio.fseek + libc.src.stdio.fwrite +) + +target_link_libraries( + libc.test.src.stdio.fopencookie_test PRIVATE LibcMemoryHelpers +) + add_subdirectory(printf_core) add_subdirectory(testdata) diff --git a/libc/test/src/stdio/fopencookie_test.cpp b/libc/test/src/stdio/fopencookie_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/stdio/fopencookie_test.cpp @@ -0,0 +1,228 @@ +//===-- Unittests for the fopencookie function ----------------------------===// +// +// 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/stdio/fclose.h" +#include "src/stdio/fflush.h" +#include "src/stdio/fopencookie.h" +#include "src/stdio/fread.h" +#include "src/stdio/fseek.h" +#include "src/stdio/fwrite.h" +#include "utils/UnitTest/MemoryMatcher.h" +#include "utils/UnitTest/Test.h" + +#include +#include +#include + +using MemoryView = __llvm_libc::memory::testing::MemoryView; + +struct StringStream { + char *buf; + size_t bufsize; // Size of buf + size_t endpos; // 1 more than current fill size + size_t offset; // Current read/write location +}; + +ssize_t write_ss(void *cookie, const char *buf, size_t size) { + auto *ss = reinterpret_cast(cookie); + if (ss->offset + size > ss->bufsize) + ss->buf = + reinterpret_cast(realloc(ss->buf, (ss->offset + size) * 2)); + for (size_t i = 0; i < size; ++i, ss->offset += 1) + ss->buf[ss->offset] = buf[i]; + if (ss->offset > ss->endpos) + ss->endpos = ss->offset; + return size; +} + +ssize_t read_ss(void *cookie, char *buf, size_t size) { + auto *ss = reinterpret_cast(cookie); + ssize_t copysize = size; + if (ss->offset + size > ss->endpos) { + // You cannot copy more than what you have available. + copysize = ss->endpos - ss->offset; + if (copysize < 0) + copysize = 0; // A seek could have moved offset past the endpos + } + for (size_t i = 0; i < size_t(copysize); ++i, ++ss->offset) + buf[i] = ss->buf[ss->offset]; + return copysize; +} + +int seek_ss(void *cookie, off64_t *offset, int whence) { + auto *ss = reinterpret_cast(cookie); + off64_t new_offset; + if (whence == SEEK_SET) { + new_offset = *offset; + } else if (whence == SEEK_CUR) { + new_offset = *offset + ss->offset; + } else if (whence == SEEK_END) { + new_offset = *offset + ss->endpos; + } else { + errno = EINVAL; + return -1; + } + if (new_offset < 0 || size_t(new_offset) > ss->bufsize) + return -1; + ss->offset = new_offset; + *offset = new_offset; + return 0; +} + +int close_ss(void *cookie) { + auto *ss = reinterpret_cast(cookie); + free(ss->buf); + ss->buf = nullptr; + ss->bufsize = ss->endpos = ss->offset = 0; + return 0; +} + +constexpr cookie_io_functions_t STRING_STREAM_FUNCS = {&read_ss, &write_ss, + &seek_ss, &close_ss}; + +TEST(LlvmLibcFOpenCookie, ReadOnlyCookieTest) { + constexpr char CONTENT[] = "Hello,readonly!"; + auto *ss = reinterpret_cast(malloc(sizeof(StringStream))); + ss->buf = reinterpret_cast(malloc(sizeof(CONTENT))); + ss->bufsize = sizeof(CONTENT); + ss->offset = 0; + ss->endpos = ss->bufsize; + for (size_t i = 0; i < sizeof(CONTENT); ++i) + ss->buf[i] = CONTENT[i]; + + ::FILE *f = __llvm_libc::fopencookie(ss, "r", STRING_STREAM_FUNCS); + ASSERT_TRUE(f != nullptr); + char read_data[sizeof(CONTENT)]; + ASSERT_EQ(sizeof(CONTENT), + __llvm_libc::fread(read_data, 1, sizeof(CONTENT), f)); + ASSERT_STREQ(read_data, CONTENT); + + ASSERT_EQ(0, __llvm_libc::fseek(f, 0, SEEK_SET)); + // Should be an error to write. + ASSERT_EQ(size_t(0), __llvm_libc::fwrite(CONTENT, 1, sizeof(CONTENT), f)); + ASSERT_EQ(errno, EBADF); + errno = 0; + + ASSERT_EQ(0, __llvm_libc::fclose(f)); + free(ss); +} + +TEST(LlvmLibcFOpenCookie, WriteOnlyCookieTest) { + size_t INIT_BUFSIZE = 32; + auto *ss = reinterpret_cast(malloc(sizeof(StringStream))); + ss->buf = reinterpret_cast(malloc(INIT_BUFSIZE)); + ss->bufsize = INIT_BUFSIZE; + ss->offset = 0; + ss->endpos = 0; + + ::FILE *f = __llvm_libc::fopencookie(ss, "w", STRING_STREAM_FUNCS); + ASSERT_TRUE(f != nullptr); + + constexpr char WRITE_DATA[] = "Hello,writeonly!"; + ASSERT_EQ(sizeof(WRITE_DATA), + __llvm_libc::fwrite(WRITE_DATA, 1, sizeof(WRITE_DATA), f)); + // Flushing will ensure the data to be written to the string stream. + ASSERT_EQ(0, __llvm_libc::fflush(f)); + ASSERT_STREQ(WRITE_DATA, ss->buf); + + ASSERT_EQ(0, __llvm_libc::fseek(f, 0, SEEK_SET)); + char read_data[sizeof(WRITE_DATA)]; + // Should be an error to read. + ASSERT_EQ(size_t(0), __llvm_libc::fread(read_data, 1, sizeof(WRITE_DATA), f)); + ASSERT_EQ(errno, EBADF); + errno = 0; + + ASSERT_EQ(0, __llvm_libc::fclose(f)); + free(ss); +} + +TEST(LlvmLibcFOpenCookie, AppendOnlyCookieTest) { + constexpr char INITIAL_CONTENT[] = "1234567890987654321"; + constexpr char WRITE_DATA[] = "append"; + auto *ss = reinterpret_cast(malloc(sizeof(StringStream))); + ss->buf = reinterpret_cast(malloc(sizeof(INITIAL_CONTENT))); + ss->bufsize = sizeof(INITIAL_CONTENT); + ss->offset = ss->bufsize; // We want to open the file in append mode. + ss->endpos = ss->bufsize; + for (size_t i = 0; i < sizeof(INITIAL_CONTENT); ++i) + ss->buf[i] = INITIAL_CONTENT[i]; + + ::FILE *f = __llvm_libc::fopencookie(ss, "a", STRING_STREAM_FUNCS); + ASSERT_TRUE(f != nullptr); + + constexpr size_t READ_SIZE = 5; + char read_data[READ_SIZE]; + // This is not a readable file. + ASSERT_EQ(__llvm_libc::fread(read_data, 1, READ_SIZE, f), size_t(0)); + EXPECT_NE(errno, 0); + errno = 0; + + ASSERT_EQ(__llvm_libc::fwrite(WRITE_DATA, 1, sizeof(WRITE_DATA), f), + sizeof(WRITE_DATA)); + EXPECT_EQ(__llvm_libc::fflush(f), 0); + EXPECT_EQ(ss->endpos, sizeof(WRITE_DATA) + sizeof(INITIAL_CONTENT)); + + ASSERT_EQ(__llvm_libc::fclose(f), 0); + free(ss); +} + +TEST(LlvmLibcFOpenCookie, ReadUpdateCookieTest) { + const char INITIAL_CONTENT[] = "1234567890987654321"; + auto *ss = reinterpret_cast(malloc(sizeof(StringStream))); + ss->buf = reinterpret_cast(malloc(sizeof(INITIAL_CONTENT))); + ss->bufsize = sizeof(INITIAL_CONTENT); + ss->offset = 0; + ss->endpos = ss->bufsize; + for (size_t i = 0; i < sizeof(INITIAL_CONTENT); ++i) + ss->buf[i] = INITIAL_CONTENT[i]; + + ::FILE *f = __llvm_libc::fopencookie(ss, "r+", STRING_STREAM_FUNCS); + ASSERT_TRUE(f != nullptr); + + constexpr size_t READ_SIZE = sizeof(INITIAL_CONTENT) / 2; + char read_data[READ_SIZE]; + ASSERT_EQ(READ_SIZE, __llvm_libc::fread(read_data, 1, READ_SIZE, f)); + + MemoryView src1(INITIAL_CONTENT, READ_SIZE), dst1(read_data, READ_SIZE); + EXPECT_MEM_EQ(src1, dst1); + + ASSERT_EQ(__llvm_libc::fseek(f, 0, SEEK_SET), 0); + constexpr char WRITE_DATA[] = "hello, file"; + ASSERT_EQ(sizeof(WRITE_DATA), + __llvm_libc::fwrite(WRITE_DATA, 1, sizeof(WRITE_DATA), f)); + ASSERT_EQ(__llvm_libc::fflush(f), 0); + EXPECT_STREQ(ss->buf, WRITE_DATA); + + ASSERT_EQ(__llvm_libc::fclose(f), 0); + free(ss); +} + +TEST(LlvmLibcFOpenCookie, WriteUpdateCookieTest) { + constexpr char WRITE_DATA[] = "hello, file"; + auto *ss = reinterpret_cast(malloc(sizeof(StringStream))); + ss->buf = reinterpret_cast(malloc(sizeof(WRITE_DATA))); + ss->bufsize = sizeof(WRITE_DATA); + ss->offset = 0; + ss->endpos = 0; + + ::FILE *f = __llvm_libc::fopencookie(ss, "w+", STRING_STREAM_FUNCS); + ASSERT_TRUE(f != nullptr); + + ASSERT_EQ(sizeof(WRITE_DATA), + __llvm_libc::fwrite(WRITE_DATA, 1, sizeof(WRITE_DATA), f)); + + ASSERT_EQ(__llvm_libc::fseek(f, 0, SEEK_SET), 0); + + char read_data[sizeof(WRITE_DATA)]; + ASSERT_EQ(__llvm_libc::fread(read_data, 1, sizeof(read_data), f), + sizeof(read_data)); + EXPECT_STREQ(read_data, WRITE_DATA); + + ASSERT_EQ(__llvm_libc::fclose(f), 0); + free(ss); +}