xref: /aosp_15_r20/external/llvm-libc/test/src/stdio/fopencookie_test.cpp (revision 71db0c75aadcf003ffe3238005f61d7618a3fead)
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 
write_ss(void * cookie,const char * buf,size_t size)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 
read_ss(void * cookie,char * buf,size_t size)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 
seek_ss(void * cookie,off64_t * offset,int whence)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 
close_ss(void * cookie)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 
TEST(LlvmLibcFOpenCookie,ReadOnlyCookieTest)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 
TEST(LlvmLibcFOpenCookie,WriteOnlyCookieTest)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 
TEST(LlvmLibcFOpenCookie,AppendOnlyCookieTest)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 
TEST(LlvmLibcFOpenCookie,ReadUpdateCookieTest)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 
TEST(LlvmLibcFOpenCookie,WriteUpdateCookieTest)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