1 //===-- Unittests for the fopencookie function ----------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "src/stdio/fclose.h" 10 #include "src/stdio/fflush.h" 11 #include "src/stdio/fopencookie.h" 12 #include "src/stdio/fread.h" 13 #include "src/stdio/fseek.h" 14 #include "src/stdio/fwrite.h" 15 #include "utils/UnitTest/MemoryMatcher.h" 16 #include "utils/UnitTest/Test.h" 17 18 #include <errno.h> 19 #include <stdio.h> 20 #include <stdlib.h> 21 22 using MemoryView = __llvm_libc::memory::testing::MemoryView; 23 24 struct StringStream { 25 char *buf; 26 size_t bufsize; // Size of buf 27 size_t endpos; // 1 more than current fill size 28 size_t offset; // Current read/write location 29 }; 30 31 ssize_t write_ss(void *cookie, const char *buf, size_t size) { 32 auto *ss = reinterpret_cast<StringStream *>(cookie); 33 if (ss->offset + size > ss->bufsize) 34 ss->buf = 35 reinterpret_cast<char *>(realloc(ss->buf, (ss->offset + size) * 2)); 36 for (size_t i = 0; i < size; ++i, ss->offset += 1) 37 ss->buf[ss->offset] = buf[i]; 38 if (ss->offset > ss->endpos) 39 ss->endpos = ss->offset; 40 return size; 41 } 42 43 ssize_t read_ss(void *cookie, char *buf, size_t size) { 44 auto *ss = reinterpret_cast<StringStream *>(cookie); 45 ssize_t copysize = size; 46 if (ss->offset + size > ss->endpos) { 47 // You cannot copy more than what you have available. 48 copysize = ss->endpos - ss->offset; 49 if (copysize < 0) 50 copysize = 0; // A seek could have moved offset past the endpos 51 } 52 for (size_t i = 0; i < size_t(copysize); ++i, ++ss->offset) 53 buf[i] = ss->buf[ss->offset]; 54 return copysize; 55 } 56 57 int seek_ss(void *cookie, off64_t *offset, int whence) { 58 auto *ss = reinterpret_cast<StringStream *>(cookie); 59 off64_t new_offset; 60 if (whence == SEEK_SET) { 61 new_offset = *offset; 62 } else if (whence == SEEK_CUR) { 63 new_offset = *offset + ss->offset; 64 } else if (whence == SEEK_END) { 65 new_offset = *offset + ss->endpos; 66 } else { 67 errno = EINVAL; 68 return -1; 69 } 70 if (new_offset < 0 || size_t(new_offset) > ss->bufsize) 71 return -1; 72 ss->offset = new_offset; 73 *offset = new_offset; 74 return 0; 75 } 76 77 int close_ss(void *cookie) { 78 auto *ss = reinterpret_cast<StringStream *>(cookie); 79 free(ss->buf); 80 ss->buf = nullptr; 81 ss->bufsize = ss->endpos = ss->offset = 0; 82 return 0; 83 } 84 85 constexpr cookie_io_functions_t STRING_STREAM_FUNCS = {&read_ss, &write_ss, 86 &seek_ss, &close_ss}; 87 88 TEST(LlvmLibcFOpenCookie, ReadOnlyCookieTest) { 89 constexpr char CONTENT[] = "Hello,readonly!"; 90 auto *ss = reinterpret_cast<StringStream *>(malloc(sizeof(StringStream))); 91 ss->buf = reinterpret_cast<char *>(malloc(sizeof(CONTENT))); 92 ss->bufsize = sizeof(CONTENT); 93 ss->offset = 0; 94 ss->endpos = ss->bufsize; 95 for (size_t i = 0; i < sizeof(CONTENT); ++i) 96 ss->buf[i] = CONTENT[i]; 97 98 ::FILE *f = __llvm_libc::fopencookie(ss, "r", STRING_STREAM_FUNCS); 99 ASSERT_TRUE(f != nullptr); 100 char read_data[sizeof(CONTENT)]; 101 ASSERT_EQ(sizeof(CONTENT), 102 __llvm_libc::fread(read_data, 1, sizeof(CONTENT), f)); 103 ASSERT_STREQ(read_data, CONTENT); 104 105 ASSERT_EQ(0, __llvm_libc::fseek(f, 0, SEEK_SET)); 106 // Should be an error to write. 107 ASSERT_EQ(size_t(0), __llvm_libc::fwrite(CONTENT, 1, sizeof(CONTENT), f)); 108 ASSERT_EQ(errno, EBADF); 109 errno = 0; 110 111 ASSERT_EQ(0, __llvm_libc::fclose(f)); 112 free(ss); 113 } 114 115 TEST(LlvmLibcFOpenCookie, WriteOnlyCookieTest) { 116 size_t INIT_BUFSIZE = 32; 117 auto *ss = reinterpret_cast<StringStream *>(malloc(sizeof(StringStream))); 118 ss->buf = reinterpret_cast<char *>(malloc(INIT_BUFSIZE)); 119 ss->bufsize = INIT_BUFSIZE; 120 ss->offset = 0; 121 ss->endpos = 0; 122 123 ::FILE *f = __llvm_libc::fopencookie(ss, "w", STRING_STREAM_FUNCS); 124 ASSERT_TRUE(f != nullptr); 125 126 constexpr char WRITE_DATA[] = "Hello,writeonly!"; 127 ASSERT_EQ(sizeof(WRITE_DATA), 128 __llvm_libc::fwrite(WRITE_DATA, 1, sizeof(WRITE_DATA), f)); 129 // Flushing will ensure the data to be written to the string stream. 130 ASSERT_EQ(0, __llvm_libc::fflush(f)); 131 ASSERT_STREQ(WRITE_DATA, ss->buf); 132 133 ASSERT_EQ(0, __llvm_libc::fseek(f, 0, SEEK_SET)); 134 char read_data[sizeof(WRITE_DATA)]; 135 // Should be an error to read. 136 ASSERT_EQ(size_t(0), __llvm_libc::fread(read_data, 1, sizeof(WRITE_DATA), f)); 137 ASSERT_EQ(errno, EBADF); 138 errno = 0; 139 140 ASSERT_EQ(0, __llvm_libc::fclose(f)); 141 free(ss); 142 } 143 144 TEST(LlvmLibcFOpenCookie, AppendOnlyCookieTest) { 145 constexpr char INITIAL_CONTENT[] = "1234567890987654321"; 146 constexpr char WRITE_DATA[] = "append"; 147 auto *ss = reinterpret_cast<StringStream *>(malloc(sizeof(StringStream))); 148 ss->buf = reinterpret_cast<char *>(malloc(sizeof(INITIAL_CONTENT))); 149 ss->bufsize = sizeof(INITIAL_CONTENT); 150 ss->offset = ss->bufsize; // We want to open the file in append mode. 151 ss->endpos = ss->bufsize; 152 for (size_t i = 0; i < sizeof(INITIAL_CONTENT); ++i) 153 ss->buf[i] = INITIAL_CONTENT[i]; 154 155 ::FILE *f = __llvm_libc::fopencookie(ss, "a", STRING_STREAM_FUNCS); 156 ASSERT_TRUE(f != nullptr); 157 158 constexpr size_t READ_SIZE = 5; 159 char read_data[READ_SIZE]; 160 // This is not a readable file. 161 ASSERT_EQ(__llvm_libc::fread(read_data, 1, READ_SIZE, f), size_t(0)); 162 EXPECT_NE(errno, 0); 163 errno = 0; 164 165 ASSERT_EQ(__llvm_libc::fwrite(WRITE_DATA, 1, sizeof(WRITE_DATA), f), 166 sizeof(WRITE_DATA)); 167 EXPECT_EQ(__llvm_libc::fflush(f), 0); 168 EXPECT_EQ(ss->endpos, sizeof(WRITE_DATA) + sizeof(INITIAL_CONTENT)); 169 170 ASSERT_EQ(__llvm_libc::fclose(f), 0); 171 free(ss); 172 } 173 174 TEST(LlvmLibcFOpenCookie, ReadUpdateCookieTest) { 175 const char INITIAL_CONTENT[] = "1234567890987654321"; 176 auto *ss = reinterpret_cast<StringStream *>(malloc(sizeof(StringStream))); 177 ss->buf = reinterpret_cast<char *>(malloc(sizeof(INITIAL_CONTENT))); 178 ss->bufsize = sizeof(INITIAL_CONTENT); 179 ss->offset = 0; 180 ss->endpos = ss->bufsize; 181 for (size_t i = 0; i < sizeof(INITIAL_CONTENT); ++i) 182 ss->buf[i] = INITIAL_CONTENT[i]; 183 184 ::FILE *f = __llvm_libc::fopencookie(ss, "r+", STRING_STREAM_FUNCS); 185 ASSERT_TRUE(f != nullptr); 186 187 constexpr size_t READ_SIZE = sizeof(INITIAL_CONTENT) / 2; 188 char read_data[READ_SIZE]; 189 ASSERT_EQ(READ_SIZE, __llvm_libc::fread(read_data, 1, READ_SIZE, f)); 190 191 MemoryView src1(INITIAL_CONTENT, READ_SIZE), dst1(read_data, READ_SIZE); 192 EXPECT_MEM_EQ(src1, dst1); 193 194 ASSERT_EQ(__llvm_libc::fseek(f, 0, SEEK_SET), 0); 195 constexpr char WRITE_DATA[] = "hello, file"; 196 ASSERT_EQ(sizeof(WRITE_DATA), 197 __llvm_libc::fwrite(WRITE_DATA, 1, sizeof(WRITE_DATA), f)); 198 ASSERT_EQ(__llvm_libc::fflush(f), 0); 199 EXPECT_STREQ(ss->buf, WRITE_DATA); 200 201 ASSERT_EQ(__llvm_libc::fclose(f), 0); 202 free(ss); 203 } 204 205 TEST(LlvmLibcFOpenCookie, WriteUpdateCookieTest) { 206 constexpr char WRITE_DATA[] = "hello, file"; 207 auto *ss = reinterpret_cast<StringStream *>(malloc(sizeof(StringStream))); 208 ss->buf = reinterpret_cast<char *>(malloc(sizeof(WRITE_DATA))); 209 ss->bufsize = sizeof(WRITE_DATA); 210 ss->offset = 0; 211 ss->endpos = 0; 212 213 ::FILE *f = __llvm_libc::fopencookie(ss, "w+", STRING_STREAM_FUNCS); 214 ASSERT_TRUE(f != nullptr); 215 216 ASSERT_EQ(sizeof(WRITE_DATA), 217 __llvm_libc::fwrite(WRITE_DATA, 1, sizeof(WRITE_DATA), f)); 218 219 ASSERT_EQ(__llvm_libc::fseek(f, 0, SEEK_SET), 0); 220 221 char read_data[sizeof(WRITE_DATA)]; 222 ASSERT_EQ(__llvm_libc::fread(read_data, 1, sizeof(read_data), f), 223 sizeof(read_data)); 224 EXPECT_STREQ(read_data, WRITE_DATA); 225 226 ASSERT_EQ(__llvm_libc::fclose(f), 0); 227 free(ss); 228 } 229