]> git.saurik.com Git - apple/libc.git/blob - tests/fflush.c
Libc-1353.60.8.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", "5m", "-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 5MB DMG");
207 int status = 0;
208 pid_t waited = waitpid(hdiutil_create, &status, 0);
209 T_QUIET; T_ASSERT_EQ(waited, hdiutil_create,
210 "should have waited for the process that was launched");
211 T_QUIET;
212 T_ASSERT_TRUE(WIFEXITED(status), "hdiutil should have exited");
213 T_QUIET;
214 T_ASSERT_EQ(WEXITSTATUS(status), 0,
215 "hdiutil should have exited successfully");
216
217 T_ATEND(cleanup_dmg);
218
219 /*
220 * Open for updating, as previously only write-only files would be flushed
221 * on fclose.
222 */
223 FILE *fp = fopen(small_file, "a+");
224 T_WITH_ERRNO;
225 T_ASSERT_NOTNULL(fp, "opened file at %s for append-updating", small_file);
226
227 char *buf = malloc(BUFSIZ);
228 T_QUIET; T_WITH_ERRNO;
229 T_ASSERT_NOTNULL(buf, "should allocate BUFSIZ bytes");
230
231 for (int i = 0; i < BUFSIZ; i++) {
232 buf[i] = (char)(i % 256);
233 }
234
235 /*
236 * Fill up the disk -- induce ENOSPC.
237 */
238 size_t wrsize = BUFSIZ;
239 for (int i = 0; i < 2; i++) {
240 for (;;) {
241 errno = 0;
242 if (write(fileno(fp), buf, wrsize) < 0) {
243 if (errno == ENOSPC) {
244 break;
245 }
246 T_WITH_ERRNO; T_ASSERT_FAIL("write(2) failed");
247 }
248 }
249 wrsize = 1;
250 }
251 T_PASS("filled up the file until ENOSPC");
252 free(buf);
253
254 /*
255 * Make sure the FILE is at the end, so any writes it does hit ENOSPC.
256 */
257 ret = fseek(fp, 0, SEEK_END);
258 T_ASSERT_POSIX_SUCCESS(ret, "fseek to the end of a complete file");
259
260 /*
261 * Try to push a character into the file; since this is buffered, it should
262 * succeed.
263 */
264 ret = fputc('a', fp);
265 T_ASSERT_POSIX_SUCCESS(ret,
266 "fputc to put an additional character in the FILE");
267
268 T_SETUPEND;
269
270 /*
271 * fclose should catch the ENOSPC error when it flushes the file, before it
272 * closes the underlying descriptor.
273 */
274 errno = 0;
275 ret = fclose(fp);
276 if (ret != EOF) {
277 T_ASSERT_FAIL("fclose should fail when the FILE is full");
278 }
279 if (errno != ENOSPC) {
280 T_WITH_ERRNO; T_ASSERT_FAIL("fclose should fail with ENOSPC");
281 }
282
283 T_PASS("fclose returned ENOSPC");
284 }
285
286 #endif // TARGET_OS_OSX
287
288 /*
289 * Ensure no errors are returned when flushing a read-only, unseekable input
290 * stream.
291 */
292 T_DECL(fflush_unseekable_input,
293 "ensure sanity when an unseekable input stream is flushed")
294 {
295 T_SETUPBEGIN;
296
297 /*
298 * Use a pipe for the unseekable streams.
299 */
300 int pipes[2];
301 int ret = pipe(pipes);
302 T_ASSERT_POSIX_SUCCESS(ret, "create a pipe");
303 FILE *in = fdopen(pipes[0], "r");
304 T_QUIET; T_WITH_ERRNO; T_ASSERT_NOTNULL(in,
305 "open input stream to read end of pipe");
306 FILE *out = fdopen(pipes[1], "w");
307 T_QUIET; T_WITH_ERRNO; T_ASSERT_NOTNULL(out,
308 "open output stream to write end of pipe");
309
310 /*
311 * Fill the pipe with some text (but not too much that the write would
312 * block!).
313 */
314 fprintf(out, "this is a test and has some more text");
315 ret = fflush(out);
316 T_ASSERT_POSIX_SUCCESS(ret, "flushed the output stream");
317
318 /*
319 * Protect stdio from delving too deep into the pipe.
320 */
321 char inbuf[8] = {};
322 setbuffer(in, inbuf, sizeof(inbuf));
323
324 /*
325 * Just read a teensy bit to get the FILE offset different from the
326 * descriptor "offset."
327 */
328 char rdbuf[2] = {};
329 size_t nitems = fread(rdbuf, sizeof(rdbuf), 1, in);
330 T_QUIET; T_ASSERT_GT(nitems, (size_t)0,
331 "read from the read end of the pipe");
332
333 T_SETUPEND;
334
335 ret = fflush(in);
336 T_ASSERT_POSIX_SUCCESS(ret,
337 "should successfully flush unseekable input stream after reading");
338 }
339
340 /*
341 * Ensure that reading to the end of a file and then calling ftell() still
342 * causes EOF.
343 */
344 T_DECL(ftell_feof,
345 "ensure ftell does not reset feof when actually at end of file") {
346 T_SETUPBEGIN;
347 FILE *fp = fopen("/System/Library/CoreServices/SystemVersion.plist", "rb");
348 T_WITH_ERRNO;
349 T_ASSERT_NOTNULL(fp, "opened SystemVersion.plist");
350 struct stat sb;
351 T_ASSERT_POSIX_SUCCESS(fstat(fileno(fp), &sb), "fstat SystemVersion.plist");
352 void *buf = malloc(sb.st_size * 2);
353 T_ASSERT_NOTNULL(buf, "allocating buffer for size of SystemVersion.plist");
354 T_SETUPEND;
355
356 T_ASSERT_POSIX_SUCCESS(fseek(fp, 0, SEEK_SET), "seek to beginning");
357 // fread can return short *or* zero, according to manpage
358 fread(buf, sb.st_size * 2, 1, fp);
359 T_ASSERT_EQ(ftell(fp), sb.st_size, "tfell() == file size");
360 T_ASSERT_TRUE(feof(fp), "feof() reports end-of-file");
361 free(buf);
362 }
363
364 T_DECL(putc_flush, "ensure putc flushes to file on close") {
365 const char *fname = assert_empty_tmp_file();
366 FILE *fp = fopen(fname, "w");
367 T_WITH_ERRNO;
368 T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
369 T_WITH_ERRNO;
370 T_ASSERT_EQ(fwrite("testing", 1, 7, fp), 7, "write temp contents");
371 (void)fclose(fp);
372
373 fp = fopen(fname, "r+");
374 T_WITH_ERRNO;
375 T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
376
377 T_ASSERT_POSIX_SUCCESS(fseek(fp, -1, SEEK_END), "seek to end - 1");
378 T_ASSERT_EQ(fgetc(fp), 'g', "fgetc should read 'g'");
379 T_ASSERT_EQ(fgetc(fp), EOF, "fgetc should read EOF");
380 T_ASSERT_EQ(ftell(fp), 7, "tfell should report position 7");
381
382 int ret = fputc('!', fp);
383 T_ASSERT_POSIX_SUCCESS(ret,
384 "fputc to put an additional character in the FILE");
385 T_ASSERT_EQ(ftell(fp), 8, "tfell should report position 8");
386
387 T_QUIET;
388 T_ASSERT_POSIX_SUCCESS(fclose(fp), "close temp file");
389
390 fp = fopen(fname, "r");
391 T_WITH_ERRNO;
392 T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
393
394 char buf[9];
395 T_WITH_ERRNO;
396 T_ASSERT_NOTNULL(fgets(buf, sizeof(buf), fp), "read file data");
397 T_ASSERT_EQ_STR(buf, "testing!", "read all the new data");
398
399 (void)fclose(fp);
400 }
401
402 T_DECL(putc_writedrop, "ensure writes are flushed with a pending read buffer") {
403 const char *fname = assert_empty_tmp_file();
404 FILE *fp = fopen(fname, "w");
405 T_WITH_ERRNO;
406 T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
407 T_WITH_ERRNO;
408 T_ASSERT_EQ(fwrite("testing", 1, 7, fp), 7, "write temp contents");
409 (void)fclose(fp);
410
411 fp = fopen(fname, "r+");
412 T_WITH_ERRNO;
413 T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
414
415 T_ASSERT_POSIX_SUCCESS(fseek(fp, -1, SEEK_END), "seek to end - 1");
416
417 int ret = fputc('!', fp);
418 T_ASSERT_POSIX_SUCCESS(ret,
419 "fputc to put an additional character in the FILE");
420 // flush the write buffer by reading a byte from the stream to put the
421 // FILE* into read mode
422 T_ASSERT_EQ(fgetc(fp), EOF, "fgetc should read EOF");
423
424 T_QUIET;
425 T_ASSERT_POSIX_SUCCESS(fclose(fp), "close temp file");
426
427 fp = fopen(fname, "r");
428 T_WITH_ERRNO;
429 T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
430
431 char buf[9];
432 T_WITH_ERRNO;
433 T_ASSERT_NOTNULL(fgets(buf, sizeof(buf), fp), "read file data");
434 T_ASSERT_EQ_STR(buf, "testin!", "read all the new data");
435
436 (void)fclose(fp);
437 }