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 "src/errno/libc_errno.h" |
22 | #include <stdio.h> |
23 | #include <stdlib.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(ptr: ss->buf, size: (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(ptr: 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: &read_ss, .write: &write_ss, |
89 | .seek: &seek_ss, .close: &close_ss}; |
90 | |
91 | TEST(LlvmLibcFOpenCookie, ReadOnlyCookieTest) { |
92 | constexpr char CONTENT[] = "Hello,readonly!" ; |
93 | auto *ss = reinterpret_cast<StringStream *>(malloc(size: sizeof(StringStream))); |
94 | ss->buf = reinterpret_cast<char *>(malloc(size: 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(cookie: ss, mode: "r" , desc: 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(stream: f); |
121 | ASSERT_EQ(LIBC_NAMESPACE::ferror(f), 0); |
122 | |
123 | ASSERT_EQ(0, LIBC_NAMESPACE::fclose(f)); |
124 | free(ptr: ss); |
125 | } |
126 | |
127 | TEST(LlvmLibcFOpenCookie, WriteOnlyCookieTest) { |
128 | size_t INIT_BUFSIZE = 32; |
129 | auto *ss = reinterpret_cast<StringStream *>(malloc(size: sizeof(StringStream))); |
130 | ss->buf = reinterpret_cast<char *>(malloc(size: INIT_BUFSIZE)); |
131 | ss->bufsize = INIT_BUFSIZE; |
132 | ss->offset = 0; |
133 | ss->endpos = 0; |
134 | |
135 | ::FILE *f = LIBC_NAMESPACE::fopencookie(cookie: ss, mode: "w" , desc: 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(stream: f); |
155 | ASSERT_EQ(LIBC_NAMESPACE::ferror(f), 0); |
156 | |
157 | ASSERT_EQ(0, LIBC_NAMESPACE::fclose(f)); |
158 | free(ptr: 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(size: sizeof(StringStream))); |
165 | ss->buf = reinterpret_cast<char *>(malloc(size: 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(cookie: ss, mode: "a" , desc: 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(stream: 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(ptr: ss); |
193 | } |
194 | |
195 | TEST(LlvmLibcFOpenCookie, ReadUpdateCookieTest) { |
196 | const char INITIAL_CONTENT[] = "1234567890987654321" ; |
197 | auto *ss = reinterpret_cast<StringStream *>(malloc(size: sizeof(StringStream))); |
198 | ss->buf = reinterpret_cast<char *>(malloc(size: 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(cookie: ss, mode: "r+" , desc: 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(ptr: ss); |
224 | } |
225 | |
226 | TEST(LlvmLibcFOpenCookie, WriteUpdateCookieTest) { |
227 | constexpr char WRITE_DATA[] = "hello, file" ; |
228 | auto *ss = reinterpret_cast<StringStream *>(malloc(size: sizeof(StringStream))); |
229 | ss->buf = reinterpret_cast<char *>(malloc(size: sizeof(WRITE_DATA))); |
230 | ss->bufsize = sizeof(WRITE_DATA); |
231 | ss->offset = 0; |
232 | ss->endpos = 0; |
233 | |
234 | ::FILE *f = LIBC_NAMESPACE::fopencookie(cookie: ss, mode: "w+" , desc: 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(ptr: ss); |
249 | } |
250 | |