]>
Commit | Line | Data |
---|---|---|
507116e3 A |
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[] = { | |
a9aaacca | 200 | "/usr/bin/hdiutil", "create", "-size", "10m", "-type", "UDIF", |
507116e3 A |
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); | |
a9aaacca A |
206 | T_ASSERT_POSIX_SUCCESS(ret, "created and attached 10MB DMG"); |
207 | T_ATEND(cleanup_dmg); | |
507116e3 A |
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 | ||
507116e3 A |
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"); | |
a9aaacca | 351 | void *buf = malloc((size_t)(sb.st_size * 2)); |
507116e3 A |
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 | |
a9aaacca A |
357 | fread(buf, (size_t)(sb.st_size * 2), 1, fp); |
358 | T_ASSERT_EQ(ftell(fp), (long)sb.st_size, "ftell() == file size"); | |
507116e3 A |
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; | |
a9aaacca | 369 | T_ASSERT_EQ(fwrite("testing", 1, 7, fp), 7UL, "write temp contents"); |
507116e3 A |
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"); | |
a9aaacca | 379 | T_ASSERT_EQ(ftell(fp), 7L, "ftell should report position 7"); |
507116e3 A |
380 | |
381 | int ret = fputc('!', fp); | |
382 | T_ASSERT_POSIX_SUCCESS(ret, | |
383 | "fputc to put an additional character in the FILE"); | |
a9aaacca | 384 | T_ASSERT_EQ(ftell(fp), 8L, "ftell should report position 8"); |
507116e3 A |
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; | |
a9aaacca | 407 | T_ASSERT_EQ(fwrite("testing", 1, 7, fp), 7UL, "write temp contents"); |
507116e3 A |
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 | } |