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