]> git.saurik.com Git - apple/libc.git/blob - tests/fflush.c
Libc-1439.40.11.tar.gz
[apple/libc.git] / tests / fflush.c
1 #include <errno.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <darwintest.h>
6 #include <darwintest_utils.h>
7
8 static char tmpfile_template[] = "/tmp/libc_test_fflushXXXXX";
9 #define BUFSZ 128
10 static char wrbuf[BUFSZ] = "";
11 static const size_t filesz = BUFSZ * 120;
12
13 static void
14 cleanup_tmp_file(void)
15 {
16 (void)unlink(tmpfile_template);
17 }
18
19 static const char *
20 assert_empty_tmp_file(void)
21 {
22 T_SETUPBEGIN;
23
24 int tmpfd = mkstemp(tmpfile_template);
25 T_ASSERT_POSIX_SUCCESS(tmpfd, "created tmp file at %s", tmpfile_template);
26 T_ATEND(cleanup_tmp_file);
27 close(tmpfd);
28
29 T_SETUPEND;
30
31 return tmpfile_template;
32 }
33
34 static const char *
35 assert_full_tmp_file(void)
36 {
37 T_SETUPBEGIN;
38
39 int tmpfd = mkstemp(tmpfile_template);
40 T_ASSERT_POSIX_SUCCESS(tmpfd, "created tmp file at %s", tmpfile_template);
41 T_ATEND(cleanup_tmp_file);
42
43 /*
44 * Write a pattern of bytes into the file -- the lowercase alphabet,
45 * separated by newlines.
46 */
47 for (size_t i = 0; i < BUFSZ; i++) {
48 wrbuf[i] = 'a' + (i % 27);
49 if (i % 27 == 26) {
50 wrbuf[i] = '\n';
51 }
52 }
53 for (size_t i = 0; i < filesz; i++) {
54 ssize_t byteswr = 0;
55 do {
56 byteswr = write(tmpfd, wrbuf, BUFSZ);
57 } while (byteswr == -1 && errno == EAGAIN);
58
59 T_QUIET; T_ASSERT_POSIX_SUCCESS(byteswr, "wrote %d bytes to tmp file",
60 BUFSZ);
61 T_QUIET; T_ASSERT_EQ(byteswr, (ssize_t)BUFSZ,
62 "wrote correct amount of bytes to tmp file");
63 }
64
65 close(tmpfd);
66
67 T_SETUPEND;
68
69 return tmpfile_template;
70 }
71
72 /*
73 * Ensure that fflush on an input stream conforms to the SUSv3 definition, which
74 * requires synchronizing the FILE position with the underlying file descriptor.
75 */
76 T_DECL(fflush_input, "fflush on a read-only FILE resets fd offset")
77 {
78 const char *tmpfile = assert_full_tmp_file();
79
80 T_SETUPBEGIN;
81
82 FILE *tmpf = fopen(tmpfile, "r");
83 T_QUIET; T_WITH_ERRNO;
84 T_ASSERT_NOTNULL(tmpf, "opened tmp file for reading");
85
86 /*
87 * Move some way into the file.
88 */
89 char buf[100] = "";
90 size_t nread = fread(buf, sizeof(buf), 1, tmpf);
91 T_ASSERT_EQ(nread, (size_t)1, "read correct number of items from FILE");
92 char last_read_char = buf[sizeof(buf) - 1];
93
94 off_t curoff = lseek(fileno(tmpf), 0, SEEK_CUR);
95 T_ASSERT_GT(curoff, (off_t)0, "file offset should be non-zero");
96
97 T_SETUPEND;
98
99 /*
100 * fflush(3) to reset the fd back to the FILE offset.
101 */
102 int ret = fflush(tmpf);
103 T_ASSERT_POSIX_SUCCESS(ret, "fflush on read-only FILE");
104
105 off_t flushoff = lseek(fileno(tmpf), 0, SEEK_CUR);
106 T_ASSERT_EQ(flushoff, (off_t)sizeof(buf),
107 "offset of file should be bytes read on FILE after fflush");
108
109 /*
110 * Make sure the FILE is reading the right thing -- the next character
111 * should be one letter after the last byte read, from the last call to
112 * fread(3).
113 */
114 char c = '\0';
115 nread = fread(&c, sizeof(c), 1, tmpf);
116 T_QUIET;
117 T_ASSERT_EQ(nread, (size_t)1, "read correct number of items from FILE");
118
119 /*
120 * The pattern in the file is the alphabet -- and this doesn't land on
121 * a newline.
122 */
123 T_QUIET;
124 T_ASSERT_NE((flushoff) % 27, (off_t)0,
125 "previous offset shouldn't land on newline");
126 T_QUIET;
127 T_ASSERT_NE((flushoff + 1) % 27, (off_t)0,
128 "current offset shouldn't land on newline");
129
130 T_ASSERT_EQ(c, last_read_char + 1, "read correct byte after fflush");
131
132 ret = fflush(tmpf);
133 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "fflush on read-only FILE");
134
135 flushoff = lseek(fileno(tmpf), 0, SEEK_CUR);
136 T_ASSERT_EQ(flushoff, (off_t)(sizeof(buf) + sizeof(c)),
137 "offset of file should be incremented after subsequent read");
138
139 /*
140 * Use ungetc(3) to induce the optimized ungetc behavior in the FILE.
141 */
142 int ugret = ungetc(c, tmpf);
143 T_QUIET; T_ASSERT_NE(ugret, EOF, "ungetc after fflush");
144 T_QUIET; T_ASSERT_EQ((char)ugret, c, "ungetc un-got the correct char");
145
146 ret = fflush(tmpf);
147 T_ASSERT_POSIX_SUCCESS(ret, "fflush after ungetc");
148 flushoff = lseek(fileno(tmpf), 0, SEEK_CUR);
149 T_ASSERT_EQ(flushoff, (off_t)sizeof(buf),
150 "offset of file should be correct after ungetc and fflush");
151
152 nread = fread(&c, sizeof(c), 1, tmpf);
153 T_QUIET;
154 T_ASSERT_EQ(nread, (size_t)1, "read correct number of items from FILE");
155 T_ASSERT_EQ(c, last_read_char + 1,
156 "read correct byte after ungetc and fflush");
157 }
158
159 /*
160 * Try to trick fclose into not reporting an ENOSPC error from the underlying
161 * descriptor in update mode. Previous versions of Libc only flushed the FILE
162 * if it was write-only.
163 */
164
165 #if TARGET_OS_OSX
166 /*
167 * Only macOS contains a version of hdiutil that can create disk images.
168 */
169
170 #define DMGFILE "/tmp/test_fclose_enospc.dmg"
171 #define VOLNAME "test_fclose_enospc"
172 static const char *small_file = "/Volumes/" VOLNAME "/test.txt";
173
174 static void
175 cleanup_dmg(void)
176 {
177 char *hdiutil_detach_argv[] = {
178 "/usr/bin/hdiutil", "detach", "/Volumes/" VOLNAME, NULL,
179 };
180 pid_t hdiutil_detach = -1;
181 int ret = dt_launch_tool(&hdiutil_detach, hdiutil_detach_argv, false, NULL,
182 NULL);
183 if (ret != -1) {
184 int status = 0;
185 (void)waitpid(hdiutil_detach, &status, 0);
186 }
187 (void)unlink(DMGFILE);
188 }
189
190 T_DECL(fclose_enospc, "ensure ENOSPC is preserved on fclose")
191 {
192 T_SETUPBEGIN;
193
194 /*
195 * Ensure a disk is available that will fill up and start returning ENOSPC.
196 *
197 * system(3) would be easier...
198 */
199 char *hdiutil_argv[] = {
200 "/usr/bin/hdiutil", "create", "-size", "10m", "-type", "UDIF",
201 "-volname", VOLNAME, "-nospotlight", "-fs", "HFS+", DMGFILE, "-attach",
202 NULL,
203 };
204 pid_t hdiutil_create = -1;
205 int ret = dt_launch_tool(&hdiutil_create, hdiutil_argv, false, NULL, NULL);
206 T_ASSERT_POSIX_SUCCESS(ret, "created and attached 10MB DMG");
207 T_ATEND(cleanup_dmg);
208 int status = 0;
209 pid_t waited = waitpid(hdiutil_create, &status, 0);
210 T_QUIET; T_ASSERT_EQ(waited, hdiutil_create,
211 "should have waited for the process that was launched");
212 T_QUIET;
213 T_ASSERT_TRUE(WIFEXITED(status), "hdiutil should have exited");
214 T_QUIET;
215 T_ASSERT_EQ(WEXITSTATUS(status), 0,
216 "hdiutil should have exited successfully");
217
218 /*
219 * Open for updating, as previously only write-only files would be flushed
220 * on fclose.
221 */
222 FILE *fp = fopen(small_file, "a+");
223 T_WITH_ERRNO;
224 T_ASSERT_NOTNULL(fp, "opened file at %s for append-updating", small_file);
225
226 char *buf = malloc(BUFSIZ);
227 T_QUIET; T_WITH_ERRNO;
228 T_ASSERT_NOTNULL(buf, "should allocate BUFSIZ bytes");
229
230 for (int i = 0; i < BUFSIZ; i++) {
231 buf[i] = (char)(i % 256);
232 }
233
234 /*
235 * Fill up the disk -- induce ENOSPC.
236 */
237 size_t wrsize = BUFSIZ;
238 for (int i = 0; i < 2; i++) {
239 for (;;) {
240 errno = 0;
241 if (write(fileno(fp), buf, wrsize) < 0) {
242 if (errno == ENOSPC) {
243 break;
244 }
245 T_WITH_ERRNO; T_ASSERT_FAIL("write(2) failed");
246 }
247 }
248 wrsize = 1;
249 }
250 T_PASS("filled up the file until ENOSPC");
251 free(buf);
252
253 /*
254 * Make sure the FILE is at the end, so any writes it does hit ENOSPC.
255 */
256 ret = fseek(fp, 0, SEEK_END);
257 T_ASSERT_POSIX_SUCCESS(ret, "fseek to the end of a complete file");
258
259 /*
260 * Try to push a character into the file; since this is buffered, it should
261 * succeed.
262 */
263 ret = fputc('a', fp);
264 T_ASSERT_POSIX_SUCCESS(ret,
265 "fputc to put an additional character in the FILE");
266
267 T_SETUPEND;
268
269 /*
270 * fclose should catch the ENOSPC error when it flushes the file, before it
271 * closes the underlying descriptor.
272 */
273 errno = 0;
274 ret = fclose(fp);
275 if (ret != EOF) {
276 T_ASSERT_FAIL("fclose should fail when the FILE is full");
277 }
278 if (errno != ENOSPC) {
279 T_WITH_ERRNO; T_ASSERT_FAIL("fclose should fail with ENOSPC");
280 }
281
282 T_PASS("fclose returned ENOSPC");
283 }
284
285 #endif // TARGET_OS_OSX
286
287 /*
288 * Ensure no errors are returned when flushing a read-only, unseekable input
289 * stream.
290 */
291 T_DECL(fflush_unseekable_input,
292 "ensure sanity when an unseekable input stream is flushed")
293 {
294 T_SETUPBEGIN;
295
296 /*
297 * Use a pipe for the unseekable streams.
298 */
299 int pipes[2];
300 int ret = pipe(pipes);
301 T_ASSERT_POSIX_SUCCESS(ret, "create a pipe");
302 FILE *in = fdopen(pipes[0], "r");
303 T_QUIET; T_WITH_ERRNO; T_ASSERT_NOTNULL(in,
304 "open input stream to read end of pipe");
305 FILE *out = fdopen(pipes[1], "w");
306 T_QUIET; T_WITH_ERRNO; T_ASSERT_NOTNULL(out,
307 "open output stream to write end of pipe");
308
309 /*
310 * Fill the pipe with some text (but not too much that the write would
311 * block!).
312 */
313 fprintf(out, "this is a test and has some more text");
314 ret = fflush(out);
315 T_ASSERT_POSIX_SUCCESS(ret, "flushed the output stream");
316
317 /*
318 * Protect stdio from delving too deep into the pipe.
319 */
320 char inbuf[8] = {};
321 setbuffer(in, inbuf, sizeof(inbuf));
322
323 /*
324 * Just read a teensy bit to get the FILE offset different from the
325 * descriptor "offset."
326 */
327 char rdbuf[2] = {};
328 size_t nitems = fread(rdbuf, sizeof(rdbuf), 1, in);
329 T_QUIET; T_ASSERT_GT(nitems, (size_t)0,
330 "read from the read end of the pipe");
331
332 T_SETUPEND;
333
334 ret = fflush(in);
335 T_ASSERT_POSIX_SUCCESS(ret,
336 "should successfully flush unseekable input stream after reading");
337 }
338
339 /*
340 * Ensure that reading to the end of a file and then calling ftell() still
341 * causes EOF.
342 */
343 T_DECL(ftell_feof,
344 "ensure ftell does not reset feof when actually at end of file") {
345 T_SETUPBEGIN;
346 FILE *fp = fopen("/System/Library/CoreServices/SystemVersion.plist", "rb");
347 T_WITH_ERRNO;
348 T_ASSERT_NOTNULL(fp, "opened SystemVersion.plist");
349 struct stat sb;
350 T_ASSERT_POSIX_SUCCESS(fstat(fileno(fp), &sb), "fstat SystemVersion.plist");
351 void *buf = malloc((size_t)(sb.st_size * 2));
352 T_ASSERT_NOTNULL(buf, "allocating buffer for size of SystemVersion.plist");
353 T_SETUPEND;
354
355 T_ASSERT_POSIX_SUCCESS(fseek(fp, 0, SEEK_SET), "seek to beginning");
356 // fread can return short *or* zero, according to manpage
357 fread(buf, (size_t)(sb.st_size * 2), 1, fp);
358 T_ASSERT_EQ(ftell(fp), (long)sb.st_size, "ftell() == file size");
359 T_ASSERT_TRUE(feof(fp), "feof() reports end-of-file");
360 free(buf);
361 }
362
363 T_DECL(putc_flush, "ensure putc flushes to file on close") {
364 const char *fname = assert_empty_tmp_file();
365 FILE *fp = fopen(fname, "w");
366 T_WITH_ERRNO;
367 T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
368 T_WITH_ERRNO;
369 T_ASSERT_EQ(fwrite("testing", 1, 7, fp), 7UL, "write temp contents");
370 (void)fclose(fp);
371
372 fp = fopen(fname, "r+");
373 T_WITH_ERRNO;
374 T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
375
376 T_ASSERT_POSIX_SUCCESS(fseek(fp, -1, SEEK_END), "seek to end - 1");
377 T_ASSERT_EQ(fgetc(fp), 'g', "fgetc should read 'g'");
378 T_ASSERT_EQ(fgetc(fp), EOF, "fgetc should read EOF");
379 T_ASSERT_EQ(ftell(fp), 7L, "ftell should report position 7");
380
381 int ret = fputc('!', fp);
382 T_ASSERT_POSIX_SUCCESS(ret,
383 "fputc to put an additional character in the FILE");
384 T_ASSERT_EQ(ftell(fp), 8L, "ftell should report position 8");
385
386 T_QUIET;
387 T_ASSERT_POSIX_SUCCESS(fclose(fp), "close temp file");
388
389 fp = fopen(fname, "r");
390 T_WITH_ERRNO;
391 T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
392
393 char buf[9];
394 T_WITH_ERRNO;
395 T_ASSERT_NOTNULL(fgets(buf, sizeof(buf), fp), "read file data");
396 T_ASSERT_EQ_STR(buf, "testing!", "read all the new data");
397
398 (void)fclose(fp);
399 }
400
401 T_DECL(putc_writedrop, "ensure writes are flushed with a pending read buffer") {
402 const char *fname = assert_empty_tmp_file();
403 FILE *fp = fopen(fname, "w");
404 T_WITH_ERRNO;
405 T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
406 T_WITH_ERRNO;
407 T_ASSERT_EQ(fwrite("testing", 1, 7, fp), 7UL, "write temp contents");
408 (void)fclose(fp);
409
410 fp = fopen(fname, "r+");
411 T_WITH_ERRNO;
412 T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
413
414 T_ASSERT_POSIX_SUCCESS(fseek(fp, -1, SEEK_END), "seek to end - 1");
415
416 int ret = fputc('!', fp);
417 T_ASSERT_POSIX_SUCCESS(ret,
418 "fputc to put an additional character in the FILE");
419 // flush the write buffer by reading a byte from the stream to put the
420 // FILE* into read mode
421 T_ASSERT_EQ(fgetc(fp), EOF, "fgetc should read EOF");
422
423 T_QUIET;
424 T_ASSERT_POSIX_SUCCESS(fclose(fp), "close temp file");
425
426 fp = fopen(fname, "r");
427 T_WITH_ERRNO;
428 T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
429
430 char buf[9];
431 T_WITH_ERRNO;
432 T_ASSERT_NOTNULL(fgets(buf, sizeof(buf), fp), "read file data");
433 T_ASSERT_EQ_STR(buf, "testin!", "read all the new data");
434
435 (void)fclose(fp);
436 }