]>
git.saurik.com Git - apple/libc.git/blob - tests/fflush.c
5 #include <darwintest.h>
6 #include <darwintest_utils.h>
8 static char tmpfile_template
[] = "/tmp/libc_test_fflushXXXXX";
10 static char wrbuf
[BUFSZ
] = "";
11 static const size_t filesz
= BUFSZ
* 120;
14 cleanup_tmp_file(void)
16 (void)unlink(tmpfile_template
);
20 assert_empty_tmp_file(void)
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
);
31 return tmpfile_template
;
35 assert_full_tmp_file(void)
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
);
44 * Write a pattern of bytes into the file -- the lowercase alphabet,
45 * separated by newlines.
47 for (size_t i
= 0; i
< BUFSZ
; i
++) {
48 wrbuf
[i
] = 'a' + (i
% 27);
53 for (size_t i
= 0; i
< filesz
; i
++) {
56 byteswr
= write(tmpfd
, wrbuf
, BUFSZ
);
57 } while (byteswr
== -1 && errno
== EAGAIN
);
59 T_QUIET
; T_ASSERT_POSIX_SUCCESS(byteswr
, "wrote %d bytes to tmp file",
61 T_QUIET
; T_ASSERT_EQ(byteswr
, (ssize_t
)BUFSZ
,
62 "wrote correct amount of bytes to tmp file");
69 return tmpfile_template
;
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.
76 T_DECL(fflush_input
, "fflush on a read-only FILE resets fd offset")
78 const char *tmpfile
= assert_full_tmp_file();
82 FILE *tmpf
= fopen(tmpfile
, "r");
83 T_QUIET
; T_WITH_ERRNO
;
84 T_ASSERT_NOTNULL(tmpf
, "opened tmp file for reading");
87 * Move some way into the file.
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];
94 off_t curoff
= lseek(fileno(tmpf
), 0, SEEK_CUR
);
95 T_ASSERT_GT(curoff
, (off_t
)0, "file offset should be non-zero");
100 * fflush(3) to reset the fd back to the FILE offset.
102 int ret
= fflush(tmpf
);
103 T_ASSERT_POSIX_SUCCESS(ret
, "fflush on read-only FILE");
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");
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
115 nread
= fread(&c
, sizeof(c
), 1, tmpf
);
117 T_ASSERT_EQ(nread
, (size_t)1, "read correct number of items from FILE");
120 * The pattern in the file is the alphabet -- and this doesn't land on
124 T_ASSERT_NE((flushoff
) % 27, (off_t
)0,
125 "previous offset shouldn't land on newline");
127 T_ASSERT_NE((flushoff
+ 1) % 27, (off_t
)0,
128 "current offset shouldn't land on newline");
130 T_ASSERT_EQ(c
, last_read_char
+ 1, "read correct byte after fflush");
133 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, "fflush on read-only FILE");
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");
140 * Use ungetc(3) to induce the optimized ungetc behavior in the FILE.
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");
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");
152 nread
= fread(&c
, sizeof(c
), 1, tmpf
);
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");
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.
167 * Only macOS contains a version of hdiutil that can create disk images.
170 #define DMGFILE "/tmp/test_fclose_enospc.dmg"
171 #define VOLNAME "test_fclose_enospc"
172 static const char *small_file
= "/Volumes/" VOLNAME
"/test.txt";
177 char *hdiutil_detach_argv
[] = {
178 "/usr/bin/hdiutil", "detach", "/Volumes/" VOLNAME
, NULL
,
180 pid_t hdiutil_detach
= -1;
181 int ret
= dt_launch_tool(&hdiutil_detach
, hdiutil_detach_argv
, false, NULL
,
185 (void)waitpid(hdiutil_detach
, &status
, 0);
187 (void)unlink(DMGFILE
);
190 T_DECL(fclose_enospc
, "ensure ENOSPC is preserved on fclose")
195 * Ensure a disk is available that will fill up and start returning ENOSPC.
197 * system(3) would be easier...
199 char *hdiutil_argv
[] = {
200 "/usr/bin/hdiutil", "create", "-size", "10m", "-type", "UDIF",
201 "-volname", VOLNAME
, "-nospotlight", "-fs", "HFS+", DMGFILE
, "-attach",
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
);
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");
213 T_ASSERT_TRUE(WIFEXITED(status
), "hdiutil should have exited");
215 T_ASSERT_EQ(WEXITSTATUS(status
), 0,
216 "hdiutil should have exited successfully");
219 * Open for updating, as previously only write-only files would be flushed
222 FILE *fp
= fopen(small_file
, "a+");
224 T_ASSERT_NOTNULL(fp
, "opened file at %s for append-updating", small_file
);
226 char *buf
= malloc(BUFSIZ
);
227 T_QUIET
; T_WITH_ERRNO
;
228 T_ASSERT_NOTNULL(buf
, "should allocate BUFSIZ bytes");
230 for (int i
= 0; i
< BUFSIZ
; i
++) {
231 buf
[i
] = (char)(i
% 256);
235 * Fill up the disk -- induce ENOSPC.
237 size_t wrsize
= BUFSIZ
;
238 for (int i
= 0; i
< 2; i
++) {
241 if (write(fileno(fp
), buf
, wrsize
) < 0) {
242 if (errno
== ENOSPC
) {
245 T_WITH_ERRNO
; T_ASSERT_FAIL("write(2) failed");
250 T_PASS("filled up the file until ENOSPC");
254 * Make sure the FILE is at the end, so any writes it does hit ENOSPC.
256 ret
= fseek(fp
, 0, SEEK_END
);
257 T_ASSERT_POSIX_SUCCESS(ret
, "fseek to the end of a complete file");
260 * Try to push a character into the file; since this is buffered, it should
263 ret
= fputc('a', fp
);
264 T_ASSERT_POSIX_SUCCESS(ret
,
265 "fputc to put an additional character in the FILE");
270 * fclose should catch the ENOSPC error when it flushes the file, before it
271 * closes the underlying descriptor.
276 T_ASSERT_FAIL("fclose should fail when the FILE is full");
278 if (errno
!= ENOSPC
) {
279 T_WITH_ERRNO
; T_ASSERT_FAIL("fclose should fail with ENOSPC");
282 T_PASS("fclose returned ENOSPC");
285 #endif // TARGET_OS_OSX
288 * Ensure no errors are returned when flushing a read-only, unseekable input
291 T_DECL(fflush_unseekable_input
,
292 "ensure sanity when an unseekable input stream is flushed")
297 * Use a pipe for the unseekable streams.
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");
310 * Fill the pipe with some text (but not too much that the write would
313 fprintf(out
, "this is a test and has some more text");
315 T_ASSERT_POSIX_SUCCESS(ret
, "flushed the output stream");
318 * Protect stdio from delving too deep into the pipe.
321 setbuffer(in
, inbuf
, sizeof(inbuf
));
324 * Just read a teensy bit to get the FILE offset different from the
325 * descriptor "offset."
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");
335 T_ASSERT_POSIX_SUCCESS(ret
,
336 "should successfully flush unseekable input stream after reading");
340 * Ensure that reading to the end of a file and then calling ftell() still
344 "ensure ftell does not reset feof when actually at end of file") {
346 FILE *fp
= fopen("/System/Library/CoreServices/SystemVersion.plist", "rb");
348 T_ASSERT_NOTNULL(fp
, "opened SystemVersion.plist");
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");
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");
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");
367 T_ASSERT_NOTNULL(fp
, "opened temporary file read/write");
369 T_ASSERT_EQ(fwrite("testing", 1, 7, fp
), 7UL, "write temp contents");
372 fp
= fopen(fname
, "r+");
374 T_ASSERT_NOTNULL(fp
, "opened temporary file read/write");
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");
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");
387 T_ASSERT_POSIX_SUCCESS(fclose(fp
), "close temp file");
389 fp
= fopen(fname
, "r");
391 T_ASSERT_NOTNULL(fp
, "opened temporary file read/write");
395 T_ASSERT_NOTNULL(fgets(buf
, sizeof(buf
), fp
), "read file data");
396 T_ASSERT_EQ_STR(buf
, "testing!", "read all the new data");
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");
405 T_ASSERT_NOTNULL(fp
, "opened temporary file read/write");
407 T_ASSERT_EQ(fwrite("testing", 1, 7, fp
), 7UL, "write temp contents");
410 fp
= fopen(fname
, "r+");
412 T_ASSERT_NOTNULL(fp
, "opened temporary file read/write");
414 T_ASSERT_POSIX_SUCCESS(fseek(fp
, -1, SEEK_END
), "seek to end - 1");
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");
424 T_ASSERT_POSIX_SUCCESS(fclose(fp
), "close temp file");
426 fp
= fopen(fname
, "r");
428 T_ASSERT_NOTNULL(fp
, "opened temporary file read/write");
432 T_ASSERT_NOTNULL(fgets(buf
, sizeof(buf
), fp
), "read file data");
433 T_ASSERT_EQ_STR(buf
, "testin!", "read all the new data");