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 }