#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <darwintest.h>
#include <darwintest_utils.h>
static char tmpfile_template[] = "/tmp/libc_test_fflushXXXXX";
#define BUFSZ 128
static char wrbuf[BUFSZ] = "";
static const size_t filesz = BUFSZ * 120;
static void
cleanup_tmp_file(void)
{
(void)unlink(tmpfile_template);
}
static const char *
assert_empty_tmp_file(void)
{
T_SETUPBEGIN;
int tmpfd = mkstemp(tmpfile_template);
T_ASSERT_POSIX_SUCCESS(tmpfd, "created tmp file at %s", tmpfile_template);
T_ATEND(cleanup_tmp_file);
close(tmpfd);
T_SETUPEND;
return tmpfile_template;
}
static const char *
assert_full_tmp_file(void)
{
T_SETUPBEGIN;
int tmpfd = mkstemp(tmpfile_template);
T_ASSERT_POSIX_SUCCESS(tmpfd, "created tmp file at %s", tmpfile_template);
T_ATEND(cleanup_tmp_file);
for (size_t i = 0; i < BUFSZ; i++) {
wrbuf[i] = 'a' + (i % 27);
if (i % 27 == 26) {
wrbuf[i] = '\n';
}
}
for (size_t i = 0; i < filesz; i++) {
ssize_t byteswr = 0;
do {
byteswr = write(tmpfd, wrbuf, BUFSZ);
} while (byteswr == -1 && errno == EAGAIN);
T_QUIET; T_ASSERT_POSIX_SUCCESS(byteswr, "wrote %d bytes to tmp file",
BUFSZ);
T_QUIET; T_ASSERT_EQ(byteswr, (ssize_t)BUFSZ,
"wrote correct amount of bytes to tmp file");
}
close(tmpfd);
T_SETUPEND;
return tmpfile_template;
}
T_DECL(fflush_input, "fflush on a read-only FILE resets fd offset")
{
const char *tmpfile = assert_full_tmp_file();
T_SETUPBEGIN;
FILE *tmpf = fopen(tmpfile, "r");
T_QUIET; T_WITH_ERRNO;
T_ASSERT_NOTNULL(tmpf, "opened tmp file for reading");
char buf[100] = "";
size_t nread = fread(buf, sizeof(buf), 1, tmpf);
T_ASSERT_EQ(nread, (size_t)1, "read correct number of items from FILE");
char last_read_char = buf[sizeof(buf) - 1];
off_t curoff = lseek(fileno(tmpf), 0, SEEK_CUR);
T_ASSERT_GT(curoff, (off_t)0, "file offset should be non-zero");
T_SETUPEND;
int ret = fflush(tmpf);
T_ASSERT_POSIX_SUCCESS(ret, "fflush on read-only FILE");
off_t flushoff = lseek(fileno(tmpf), 0, SEEK_CUR);
T_ASSERT_EQ(flushoff, (off_t)sizeof(buf),
"offset of file should be bytes read on FILE after fflush");
char c = '\0';
nread = fread(&c, sizeof(c), 1, tmpf);
T_QUIET;
T_ASSERT_EQ(nread, (size_t)1, "read correct number of items from FILE");
T_QUIET;
T_ASSERT_NE((flushoff) % 27, (off_t)0,
"previous offset shouldn't land on newline");
T_QUIET;
T_ASSERT_NE((flushoff + 1) % 27, (off_t)0,
"current offset shouldn't land on newline");
T_ASSERT_EQ(c, last_read_char + 1, "read correct byte after fflush");
ret = fflush(tmpf);
T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "fflush on read-only FILE");
flushoff = lseek(fileno(tmpf), 0, SEEK_CUR);
T_ASSERT_EQ(flushoff, (off_t)(sizeof(buf) + sizeof(c)),
"offset of file should be incremented after subsequent read");
int ugret = ungetc(c, tmpf);
T_QUIET; T_ASSERT_NE(ugret, EOF, "ungetc after fflush");
T_QUIET; T_ASSERT_EQ((char)ugret, c, "ungetc un-got the correct char");
ret = fflush(tmpf);
T_ASSERT_POSIX_SUCCESS(ret, "fflush after ungetc");
flushoff = lseek(fileno(tmpf), 0, SEEK_CUR);
T_ASSERT_EQ(flushoff, (off_t)sizeof(buf),
"offset of file should be correct after ungetc and fflush");
nread = fread(&c, sizeof(c), 1, tmpf);
T_QUIET;
T_ASSERT_EQ(nread, (size_t)1, "read correct number of items from FILE");
T_ASSERT_EQ(c, last_read_char + 1,
"read correct byte after ungetc and fflush");
}
#if TARGET_OS_OSX
#define DMGFILE "/tmp/test_fclose_enospc.dmg"
#define VOLNAME "test_fclose_enospc"
static const char *small_file = "/Volumes/" VOLNAME "/test.txt";
static void
cleanup_dmg(void)
{
char *hdiutil_detach_argv[] = {
"/usr/bin/hdiutil", "detach", "/Volumes/" VOLNAME, NULL,
};
pid_t hdiutil_detach = -1;
int ret = dt_launch_tool(&hdiutil_detach, hdiutil_detach_argv, false, NULL,
NULL);
if (ret != -1) {
int status = 0;
(void)waitpid(hdiutil_detach, &status, 0);
}
(void)unlink(DMGFILE);
}
T_DECL(fclose_enospc, "ensure ENOSPC is preserved on fclose")
{
T_SETUPBEGIN;
char *hdiutil_argv[] = {
"/usr/bin/hdiutil", "create", "-size", "5m", "-type", "UDIF",
"-volname", VOLNAME, "-nospotlight", "-fs", "HFS+", DMGFILE, "-attach",
NULL,
};
pid_t hdiutil_create = -1;
int ret = dt_launch_tool(&hdiutil_create, hdiutil_argv, false, NULL, NULL);
T_ASSERT_POSIX_SUCCESS(ret, "created and attached 5MB DMG");
int status = 0;
pid_t waited = waitpid(hdiutil_create, &status, 0);
T_QUIET; T_ASSERT_EQ(waited, hdiutil_create,
"should have waited for the process that was launched");
T_QUIET;
T_ASSERT_TRUE(WIFEXITED(status), "hdiutil should have exited");
T_QUIET;
T_ASSERT_EQ(WEXITSTATUS(status), 0,
"hdiutil should have exited successfully");
T_ATEND(cleanup_dmg);
FILE *fp = fopen(small_file, "a+");
T_WITH_ERRNO;
T_ASSERT_NOTNULL(fp, "opened file at %s for append-updating", small_file);
char *buf = malloc(BUFSIZ);
T_QUIET; T_WITH_ERRNO;
T_ASSERT_NOTNULL(buf, "should allocate BUFSIZ bytes");
for (int i = 0; i < BUFSIZ; i++) {
buf[i] = (char)(i % 256);
}
size_t wrsize = BUFSIZ;
for (int i = 0; i < 2; i++) {
for (;;) {
errno = 0;
if (write(fileno(fp), buf, wrsize) < 0) {
if (errno == ENOSPC) {
break;
}
T_WITH_ERRNO; T_ASSERT_FAIL("write(2) failed");
}
}
wrsize = 1;
}
T_PASS("filled up the file until ENOSPC");
free(buf);
ret = fseek(fp, 0, SEEK_END);
T_ASSERT_POSIX_SUCCESS(ret, "fseek to the end of a complete file");
ret = fputc('a', fp);
T_ASSERT_POSIX_SUCCESS(ret,
"fputc to put an additional character in the FILE");
T_SETUPEND;
errno = 0;
ret = fclose(fp);
if (ret != EOF) {
T_ASSERT_FAIL("fclose should fail when the FILE is full");
}
if (errno != ENOSPC) {
T_WITH_ERRNO; T_ASSERT_FAIL("fclose should fail with ENOSPC");
}
T_PASS("fclose returned ENOSPC");
}
#endif // TARGET_OS_OSX
T_DECL(fflush_unseekable_input,
"ensure sanity when an unseekable input stream is flushed")
{
T_SETUPBEGIN;
int pipes[2];
int ret = pipe(pipes);
T_ASSERT_POSIX_SUCCESS(ret, "create a pipe");
FILE *in = fdopen(pipes[0], "r");
T_QUIET; T_WITH_ERRNO; T_ASSERT_NOTNULL(in,
"open input stream to read end of pipe");
FILE *out = fdopen(pipes[1], "w");
T_QUIET; T_WITH_ERRNO; T_ASSERT_NOTNULL(out,
"open output stream to write end of pipe");
fprintf(out, "this is a test and has some more text");
ret = fflush(out);
T_ASSERT_POSIX_SUCCESS(ret, "flushed the output stream");
char inbuf[8] = {};
setbuffer(in, inbuf, sizeof(inbuf));
char rdbuf[2] = {};
size_t nitems = fread(rdbuf, sizeof(rdbuf), 1, in);
T_QUIET; T_ASSERT_GT(nitems, (size_t)0,
"read from the read end of the pipe");
T_SETUPEND;
ret = fflush(in);
T_ASSERT_POSIX_SUCCESS(ret,
"should successfully flush unseekable input stream after reading");
}
T_DECL(ftell_feof,
"ensure ftell does not reset feof when actually at end of file") {
T_SETUPBEGIN;
FILE *fp = fopen("/System/Library/CoreServices/SystemVersion.plist", "rb");
T_WITH_ERRNO;
T_ASSERT_NOTNULL(fp, "opened SystemVersion.plist");
struct stat sb;
T_ASSERT_POSIX_SUCCESS(fstat(fileno(fp), &sb), "fstat SystemVersion.plist");
void *buf = malloc(sb.st_size * 2);
T_ASSERT_NOTNULL(buf, "allocating buffer for size of SystemVersion.plist");
T_SETUPEND;
T_ASSERT_POSIX_SUCCESS(fseek(fp, 0, SEEK_SET), "seek to beginning");
fread(buf, sb.st_size * 2, 1, fp);
T_ASSERT_EQ(ftell(fp), sb.st_size, "tfell() == file size");
T_ASSERT_TRUE(feof(fp), "feof() reports end-of-file");
free(buf);
}
T_DECL(putc_flush, "ensure putc flushes to file on close") {
const char *fname = assert_empty_tmp_file();
FILE *fp = fopen(fname, "w");
T_WITH_ERRNO;
T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
T_WITH_ERRNO;
T_ASSERT_EQ(fwrite("testing", 1, 7, fp), 7, "write temp contents");
(void)fclose(fp);
fp = fopen(fname, "r+");
T_WITH_ERRNO;
T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
T_ASSERT_POSIX_SUCCESS(fseek(fp, -1, SEEK_END), "seek to end - 1");
T_ASSERT_EQ(fgetc(fp), 'g', "fgetc should read 'g'");
T_ASSERT_EQ(fgetc(fp), EOF, "fgetc should read EOF");
T_ASSERT_EQ(ftell(fp), 7, "tfell should report position 7");
int ret = fputc('!', fp);
T_ASSERT_POSIX_SUCCESS(ret,
"fputc to put an additional character in the FILE");
T_ASSERT_EQ(ftell(fp), 8, "tfell should report position 8");
T_QUIET;
T_ASSERT_POSIX_SUCCESS(fclose(fp), "close temp file");
fp = fopen(fname, "r");
T_WITH_ERRNO;
T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
char buf[9];
T_WITH_ERRNO;
T_ASSERT_NOTNULL(fgets(buf, sizeof(buf), fp), "read file data");
T_ASSERT_EQ_STR(buf, "testing!", "read all the new data");
(void)fclose(fp);
}
T_DECL(putc_writedrop, "ensure writes are flushed with a pending read buffer") {
const char *fname = assert_empty_tmp_file();
FILE *fp = fopen(fname, "w");
T_WITH_ERRNO;
T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
T_WITH_ERRNO;
T_ASSERT_EQ(fwrite("testing", 1, 7, fp), 7, "write temp contents");
(void)fclose(fp);
fp = fopen(fname, "r+");
T_WITH_ERRNO;
T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
T_ASSERT_POSIX_SUCCESS(fseek(fp, -1, SEEK_END), "seek to end - 1");
int ret = fputc('!', fp);
T_ASSERT_POSIX_SUCCESS(ret,
"fputc to put an additional character in the FILE");
T_ASSERT_EQ(fgetc(fp), EOF, "fgetc should read EOF");
T_QUIET;
T_ASSERT_POSIX_SUCCESS(fclose(fp), "close temp file");
fp = fopen(fname, "r");
T_WITH_ERRNO;
T_ASSERT_NOTNULL(fp, "opened temporary file read/write");
char buf[9];
T_WITH_ERRNO;
T_ASSERT_NOTNULL(fgets(buf, sizeof(buf), fp), "read file data");
T_ASSERT_EQ_STR(buf, "testin!", "read all the new data");
(void)fclose(fp);
}