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