#include #include #include #include #include #include #include #include #include #include #include #include #include #if 1 #ifndef rounddown2 #define rounddown2(x, y) ((x)&(~((y)-1))) /* if y is powers of two */ #endif #define roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) /* if y is powers of two */ #define TEST_FIXTURE(fn) \ static void fn (void) struct suite { const char *name; void (*suite_setup)(void); void (*suite_teardown)(void); void (*test_setup)(void); void (*test_teardown)(void); int timeout; }; #define SUITE_DEFINE_FOR_FILE(n, ...) \ struct suite g_suite = { \ .name = # n, \ __VA_ARGS__ \ } struct test { const char *name; void (*func)(void); }; #include SET_DECLARE(my_test_set, struct test); #define TEST(fn) \ static void fn (void); \ struct test __test_ ## fn = { \ .name = # fn, \ .func = fn, \ }; \ DATA_SET(my_test_set, __test_ ## fn); \ static void fn (void) #include #define fail_if(cond, ...) do { \ if ((cond)) { \ warnx("Failed condition %s at %s:%d\n", \ # cond, __FILE__, __LINE__); \ warnx(__VA_ARGS__); \ return; \ } \ } while (0) #define fail_unless(cond, ...) fail_if(!(cond), __VA_ARGS__) #endif /* 1 */ TEST_FIXTURE(falloc_suite_setup) { srandom(time(NULL)); } TEST_FIXTURE(falloc_suite_teardown) { } #define TESTDIR "/tmp" static int test_fd = -1; static char test_fname[MAXPATHLEN]; TEST_FIXTURE(falloc_test_setup) { int fd; sprintf(test_fname, "%s/%s", TESTDIR, "temp.XXXXXX"); fd = mkstemp(test_fname); fail_if(fd < 0, "mkstemp(%s) gave %d", test_fname, errno); test_fd = fd; } TEST_FIXTURE(falloc_test_teardown) { if (test_fd >= 0) (void)close(test_fd); (void)unlink(test_fname); test_fd = -1; } SUITE_DEFINE_FOR_FILE(posix_fallocate, .suite_setup = falloc_suite_setup, .suite_teardown = falloc_suite_teardown, .test_setup = falloc_test_setup, .test_teardown = falloc_test_teardown, .timeout = 60, ); #define BUFELS (2 * PAGE_SIZE / sizeof(uint64_t)) static uint64_t buf[BUFELS]; #ifndef CTASSERT #define CTASSERT(cond) #endif CTASSERT(sizeof(buf) == 2 * PAGE_SIZE); static inline uint64_t data_at(off_t offset) { return ((offset & 0x8) == 0 ? offset : UINT64_MAX - offset); } static void write_known_data(int fd, off_t offset, off_t len) { off_t l; ssize_t rc; int i; while (len > 0) { for (i = 0; i < BUFELS; i++) buf[i] = data_at(offset + i * sizeof(uint64_t)); l = sizeof(buf); if (l > len) l = len; rc = pwrite(fd, buf, l, offset); fail_unless(rc == l, "wrote %zd not %lu, errno %d", rc, (u_long)l, errno); offset += l; len -= l; } } static void check_known_data(int fd, off_t offset, off_t len) { uint64_t expect; off_t off, l; ssize_t rc; int i; while (len > 0) { l = sizeof(buf); if (l > len) l = len; memset(buf, 0xAA, sizeof(buf)); rc = pread(fd, buf, l, offset); fail_unless(rc == l, "read %zd not %lu, errno %d", rc, (u_long)l, errno); for (i = 0; i < l / sizeof(buf[0]); i++) { off = offset + i * sizeof(uint64_t); expect = data_at(off); if (buf[i] == expect) continue; fail_if(1, "See %08jx not %08jx at %jx", buf[i], expect, off); } offset += l; len -= l; } } static void check_zero(int fd, off_t offset, off_t len) { off_t off, l; ssize_t rc; int i; while (len > 0) { l = sizeof(buf); if (l > len) l = len; memset(buf, 0xAA, sizeof(buf)); rc = pread(fd, buf, l, offset); fail_unless(rc == l, "read %zd not %lu, errno %d", rc, (u_long)l, errno); for (i = 0; i < l / sizeof(buf[0]); i++) { off = offset + i * sizeof(uint64_t); if (buf[i] == 0) continue; fail_if(1, "See non-zero %08jx at offset %jx", buf[i], off); } offset += l; len -= l; } } struct range { off_t off; off_t len; }; static int range_set_okay(const struct range *r) { const struct range *prev; for (prev = NULL; r->len > 0; prev = r, r++) { if (r->off < 0) return (0); if (prev == NULL) continue; if (prev->off + prev->len > r->off) return (0); } return (1); } static void range_print(struct sbuf *sb, const struct range *r) { if (r == NULL) sbuf_printf(sb, "(null)"); else sbuf_printf(sb, "[%jx, %jx)", r->off, r->off + r->len); } static void range_set_print(struct sbuf *sb, const struct range *r) { for (; r->len > 0; r++) { sbuf_printf(sb, " "); range_print(sb, r); } } static off_t range_bytes(const struct range *r, struct sbuf *sb) { off_t sum; off_t end, start; for (sum = 0; r->len > 0; r++) { start = rounddown2(r->off, DEV_BSIZE); end = roundup2(r->off + r->len, DEV_BSIZE); if (sb != NULL) { sbuf_printf(sb, "R [%jx, %jx) adding %jx bytes to sum %jx\n", start, end, end - start, sum); } sum += (end - start); } return (sum); } #define EXTEND_END(r, end, sb, did_extend) do { \ /* Skip wholly subsumed ranges. */ \ while (r->len > 0 && r->off + r->len <= end) { \ if (sb != NULL) { \ sbuf_printf(sb, "range "); \ range_print(sb, r); \ sbuf_printf(sb, \ " is completely before end %jx\n", end); \ } \ r++; \ } \ \ /* Extend end if needed. */ \ if (r->len > 0 && r->off < end) { \ end = rounddown2(r->off + r->len, DEV_BSIZE); \ if (sb != NULL) { \ sbuf_printf(sb, "range "); \ range_print(sb, r); \ sbuf_printf(sb, \ " extends end to %jx\n", end); \ } \ did_extend = 1; \ r++; \ } else \ did_extend = 0; \ } while (0) /* Sum the bytes (round to blocks) in the union of r1 and r2 */ static off_t range_bytes2(const struct range *r1, const struct range *r2, struct sbuf *sb) { int did_extend; off_t end, start, sum; if (sb != NULL) { sbuf_printf(sb, "R1 is "); range_set_print(sb, r1); sbuf_printf(sb, "\nR2 is "); range_set_print(sb, r2); sbuf_printf(sb, "\n"); } sum = 0; for (;;) { if (r1->len == 0 || r2->len == 0) break; if (r1->off < r2->off) { start = rounddown2(r1->off, DEV_BSIZE); end = roundup2(r1->off + r1->len, DEV_BSIZE); if (sb != NULL) { sbuf_printf(sb, "R1 at "); range_print(sb, r1); sbuf_printf(sb, " comes before R2 at "); range_print(sb, r2); sbuf_printf(sb, " and rounds to [%jx, %jx)\n", start, end); } r1++; for (;;) { EXTEND_END(r2, end, sb, did_extend); if (!did_extend) break; EXTEND_END(r1, end, sb, did_extend); if (!did_extend) break; } if (sb != NULL) { sbuf_printf(sb, "So, R1 [%jx, %jx) adding %jx bytes to sum %jx\n", start, end, end - start, sum); } sum += (end - start); } else { start = rounddown2(r2->off, DEV_BSIZE); end = roundup2(r2->off + r2->len, DEV_BSIZE); if (sb != NULL) { sbuf_printf(sb, "R2 at "); range_print(sb, r2); sbuf_printf(sb, " comes before R1 at "); range_print(sb, r1); sbuf_printf(sb, " and rounds to [%jx, %jx)\n", start, end); } r2++; for (;;) { EXTEND_END(r1, end, sb, did_extend); if (!did_extend) break; EXTEND_END(r2, end, sb, did_extend); if (!did_extend) break; } if (sb != NULL) { sbuf_printf(sb, "So, R2 [%jx, %jx) adding %jx bytes to sum %jx\n", start, end, end - start, sum); } sum += (end - start); } } return (sum + range_bytes(r1, sb) + range_bytes(r2, sb)); } static const struct range * range_last(const struct range *r) { if (r->len <= 0) return (NULL); for (; r->len > 0; r++) ; return (r - 1); } static void write_ranges(int fd, const struct range *r) { for (; r->len > 0; r++) write_known_data(fd, r->off, r->len); } static void alloc_ranges(int fd, const struct range *r) { int rc; for (; r->len > 0; r++) { rc = posix_fallocate(fd, r->off, r->len); fail_if(rc != 0, "rc %d errno %d", rc, errno); } } static void check_file(int fd, const struct range *w, const struct range *a) { struct stat st; const struct range *wl, *al; off_t val; int rc; rc = fstat(fd, &st); fail_unless(rc == 0, "rc %d errno %d", rc, errno); wl = range_last(w); al = range_last(a); val = 0; if (wl != NULL) val = wl->off + wl->len; if (al != NULL && al->off + al->len > val) val = al->off + al->len; if (st.st_size != val) { struct sbuf *sb; sb = sbuf_new_auto(); sbuf_printf(sb, "\nwrite range "); range_set_print(sb, w); sbuf_printf(sb, "\n and falloc range "); range_set_print(sb, a); sbuf_printf(sb, "\n have final ranges "); range_print(sb, wl); sbuf_printf(sb, " and "); range_print(sb, al); sbuf_printf(sb, "\n respectively; end is %jx, st_size is %jx\n", val, (uintmax_t)st.st_size); sbuf_finish(sb); printf("%s", sbuf_data(sb)); sbuf_delete(sb); fail_if(1, "file size %lx doesn't match", (u_long)st.st_size); } val = range_bytes2(w, a, NULL) / DEV_BSIZE; if (st.st_blocks < val) { struct sbuf *sb; sb = sbuf_new_auto(); (void)range_bytes2(w, a, sb); sbuf_finish(sb); printf("%s", sbuf_data(sb)); sbuf_delete(sb); fail_if(1, "st_blocks is %lx, less than calculated %lx\n", (u_long)st.st_blocks, (u_long)val); } /* XXX finish me -- check zero in holes? */ for (; w->len > 0; w++) check_known_data(fd, w->off, w->len); } #define NUM_RANGES 16 #define MAX_RANGE_LEN (1024 * 1024) static void range_random(struct range r[NUM_RANGES + 1]) { off_t off, len; int i, n, spread; n = random() % NUM_RANGES; off = 0; len = 0; spread = 1 + (random() % 16); for (i = 0; i < n; i++) { off += len; off += (random() % (spread * MAX_RANGE_LEN)); off = rounddown2(off, 8); len = 1 + (random() % MAX_RANGE_LEN); len = roundup2(len, 8); r[i].off = off; r[i].len = len; } r[i].off = 0; r[i].len = 0; fail_unless(range_set_okay(r), "range not okay?"); } static void do_file(int fd) { struct range wr[NUM_RANGES + 1], alloc[NUM_RANGES + 1]; int rc; rc = ftruncate(fd, 0); fail_unless(rc == 0, "rc %d errno %d", rc, errno); range_random(wr); range_random(alloc); write_ranges(fd, wr); alloc_ranges(fd, alloc); check_file(fd, wr, alloc); } /* Random ranges. */ TEST(random_ranges) { int i; for (i = 0; i < 100; i++) do_file(test_fd); } /* Bad file descriptor. */ TEST(err_EBADF_1) { char fname[] = TESTDIR "/temp.XXXXXX"; int fd, rc; fd = mkstemp(fname); fail_if(fd < 0, "mkstemp(%s) gave %d", fname, errno); rc = close(fd); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); rc = posix_fallocate(fd, 0, 1024); fail_unless(rc == -1 && errno == EBADF, "got rc %d errno %d", rc, errno); (void)unlink(fname); } /* No write permission. */ TEST(err_EBADF_2) { int rc; rc = close(test_fd); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); test_fd = open(test_fname, O_RDONLY); fail_unless(test_fd > 0, "got rc %d errno %d", rc, errno); rc = posix_fallocate(test_fd, 0, 1024); fail_unless(rc == -1 && errno == EBADF, "got rc %d errno %d", rc, errno); } /* Not a regular file. */ TEST(err_ENODEV_1) { int rc; rc = posix_fallocate(STDIN_FILENO, 0, 1024); fail_unless(rc == -1 && errno == ENODEV, "got rc %d errno %d", rc, errno); } /* Not a regular file. */ TEST(err_ENODEV_2) { int rc; rc = posix_fallocate(STDOUT_FILENO, 0, 1024); fail_unless(rc == -1 && errno == ENODEV, "got rc %d errno %d", rc, errno); } /* Try it on a device file. */ TEST(err_ENODEV_3) { int fd, rc; fd = open("/dev/null", O_RDWR); fail_unless(fd > 0, "open gave errno %d", errno); rc = posix_fallocate(fd, 0, 1024); fail_unless(rc == -1 && errno == ENODEV, "got rc %d errno %d", rc, errno); (void)close(fd); } /* Try it on a kqueue. */ TEST(err_ENODEV_4) { int fd, rc; fd = kqueue(); fail_unless(fd > 0, "kqueue gave errno %d", errno); rc = posix_fallocate(fd, 0, 1024); fail_unless(rc == -1 && errno == ENODEV, "got rc %d errno %d", rc, errno); (void)close(fd); } /* Bad offset. */ TEST(err_EINVAL_1) { char fname[] = TESTDIR "/temp.XXXXXX"; int fd, rc; fd = mkstemp(fname); fail_if(fd < 0, "mkstemp(%s) gave %d", fname, errno); rc = posix_fallocate(fd, -1, 1024); fail_unless(rc == -1 && errno == EINVAL, "got rc %d errno %d", rc, errno); (void)close(fd); (void)unlink(fname); } /* Bad offset. */ TEST(err_EINVAL_2) { int rc; rc = posix_fallocate(test_fd, INT64_MIN, 1024); fail_unless(rc == -1 && errno == EINVAL, "got rc %d errno %d", rc, errno); } /* Bad len. */ TEST(err_EINVAL_3) { int rc; rc = posix_fallocate(test_fd, 0, 0); fail_unless(rc == -1 && errno == EINVAL, "got rc %d errno %d", rc, errno); } /* Bad len. */ TEST(err_EINVAL_4) { int rc; rc = posix_fallocate(test_fd, 0, -1); fail_unless(rc == -1 && errno == EINVAL, "got rc %d errno %d", rc, errno); } /* Bad len. */ TEST(err_EINVAL_5) { int rc; rc = posix_fallocate(test_fd, 0, INT64_MIN); fail_unless(rc == -1 && errno == EINVAL, "got rc %d errno %d", rc, errno); } /* Try it on a pipe. */ TEST(err_ESPIPE_1) { int fd[2], rc; rc = pipe(fd); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); rc = posix_fallocate(fd[0], 0, 1024); fail_unless(rc == -1 && errno == ESPIPE, "got rc %d errno %d", rc, errno); (void)close(fd[0]); (void)close(fd[1]); } /* Try it on a pipe. */ TEST(err_ESPIPE_2) { int fd[2], rc; rc = pipe(fd); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); rc = posix_fallocate(fd[1], 0, 1024); fail_unless(rc == -1 && errno == ESPIPE, "got rc %d errno %d", rc, errno); (void)close(fd[0]); (void)close(fd[1]); } /* Try it on a fifo. */ TEST(err_ESPIPE_3) { char ftemplate[] = TESTDIR "/temp.XXXXXX"; char *fname; int fd, rc; fname = mktemp(ftemplate); fail_if(fname == NULL, "errno %d", errno); rc = mkfifo(fname, S_IRWXU); fail_unless(rc == 0, "mkfifo(%s) gave errno %d", fname, errno); fd = open(fname, O_RDWR); fail_if(fd < 0, "open fifo %s gave errno %d", fname, errno); rc = posix_fallocate(fd, 0, 1024); fail_unless(rc == -1 && errno == ESPIPE, "got rc %d errno %d", rc, errno); (void)close(fd); (void)unlink(fname); } #ifdef ISILON /* Test too large offset. */ TEST(err_EFBIG_1) { struct statfs sfs; off_t offset, len; int rc; rc = fstatfs(test_fd, &sfs); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); len = 1; offset = sfs.f_maxfilesize + 1; rc = posix_fallocate(test_fd, offset, len); fail_unless(rc == -1 && errno == EFBIG, "got rc %d errno %d", rc, errno); } /* Test too large len. */ TEST(err_EFBIG_2) { struct statfs sfs; off_t offset, len; int rc; rc = fstatfs(test_fd, &sfs); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); len = sfs.f_maxfilesize + 1; offset = 0; rc = posix_fallocate(test_fd, offset, len); fail_unless(rc == -1 && errno == EFBIG, "got rc %d errno %d", rc, errno); } /* Test too large offset + len. */ TEST(err_EFBIG_3) { struct statfs sfs; off_t offset, len; int rc; rc = fstatfs(test_fd, &sfs); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); len = sfs.f_maxfilesize / 2 + 1; offset = sfs.f_maxfilesize / 2 + 1; rc = posix_fallocate(test_fd, offset, len); fail_unless(rc == -1 && errno == EFBIG, "got rc %d errno %d", rc, errno); } /* Test too large offset + len. */ TEST(err_EFBIG_4) { struct statfs sfs; off_t offset, len; int rc; rc = fstatfs(test_fd, &sfs); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); len = sfs.f_maxfilesize; offset = 1; rc = posix_fallocate(test_fd, offset, len); fail_unless(rc == -1 && errno == EFBIG, "got rc %d errno %d", rc, errno); } /* Test too large offset + len. */ TEST(err_EFBIG_5) { struct statfs sfs; off_t offset, len; int rc; rc = fstatfs(test_fd, &sfs); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); len = 1; offset = sfs.f_maxfilesize; rc = posix_fallocate(test_fd, offset, len); fail_unless(rc == -1 && errno == EFBIG, "got rc %d errno %d", rc, errno); } #endif /* Check that, from an empty file length 0 we get allocated blocks. */ TEST(basic_success) { struct range wr[1]; struct range alloc[2]; bzero(wr, sizeof(wr)); bzero(alloc, sizeof(alloc)); alloc[0].off = 0; alloc[0].len = 1024; alloc_ranges(test_fd, alloc); check_file(test_fd, wr, alloc); } /* Check that, from an empty file length 0 we get allocated blocks. */ TEST(unaligned) { struct range wr[1]; struct range alloc[2]; bzero(wr, sizeof(wr)); bzero(alloc, sizeof(alloc)); alloc[0].off = 23; alloc[0].len = MAXPHYS; alloc_ranges(test_fd, alloc); check_file(test_fd, wr, alloc); } /* Check that, from an sparse file we get allocated blocks. */ TEST(extend) { struct range wr[1]; struct range alloc[2]; struct stat st; struct statfs sfs; off_t size; int rc; rc = fstatfs(test_fd, &sfs); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); size = 1024 * 1024; bzero(wr, sizeof(wr)); bzero(alloc, sizeof(alloc)); alloc[0].off = size; alloc[0].len = sfs.f_iosize; rc = ftruncate(test_fd, size); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); rc = fstat(test_fd, &st); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); fail_unless(st.st_size == size, "new file has size %lu not %lu?", (u_long)st.st_size, (u_long)size); alloc_ranges(test_fd, alloc); check_file(test_fd, wr, alloc); } /* Check that, from an sparse file we get allocated blocks. */ TEST(just_space) { struct stat st; off_t len, offset; u_long oblks; int rc; len = 1024 * 1024; offset = 0; rc = ftruncate(test_fd, len); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); check_zero(test_fd, offset, offset); rc = fstat(test_fd, &st); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); fail_unless(st.st_size == len, "new file has size %lu not %lu?", (u_long)st.st_size, (u_long)len); oblks = st.st_blocks; rc = posix_fallocate(test_fd, offset, len); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); rc = fstat(test_fd, &st); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); fail_unless(st.st_size == offset + len, "new file has size %lu?", (u_long)st.st_size); fail_unless(st.st_blocks > oblks, "file still has %lu blocks?", oblks); check_zero(test_fd, offset, len); } /* * Verify that existing data in the file isn't modified, and the size * doesn't change when allocating over existing data. */ TEST(no_change) { struct range wr[2]; struct range alloc[2]; bzero(wr, sizeof(wr)); wr[0].off = 0; wr[0].len = 1024 * 1024; write_ranges(test_fd, wr); bzero(alloc, sizeof(alloc)); alloc[0].off = 0; alloc[0].len = 1024 * 1024; alloc_ranges(test_fd, alloc); check_file(test_fd, wr, alloc); } /* * Verify that existing data in the file isn't modified, and the size * doesn't change when allocating over existing data. */ TEST(no_change2) { struct stat st; struct statfs sfs; off_t offset, size; ssize_t len; uint32_t *buf1, *buf2; u_long oblks; int i, rc; rc = fstatfs(test_fd, &sfs); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); size = 1024 * 1024; offset = 1024 * 1024 * 1024; buf1 = malloc(size); buf2 = malloc(size); fail_unless(buf1 != NULL && buf2 != NULL, "cannot malloc"); for (i = 0; i < size / sizeof(*buf1); i++) buf1[i] = random(); len = pwrite(test_fd, buf1, size, offset - size / 2); fail_unless(len == size, "write %zd, errno %d", len, errno); rc = fstat(test_fd, &st); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); fail_unless(st.st_size == offset + size / 2, "new file has size %lu not %lu?", (u_long)st.st_size, (u_long)offset + (u_long)size / 2); oblks = st.st_blocks; rc = posix_fallocate(test_fd, offset, size); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); rc = fstat(test_fd, &st); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); fail_unless(st.st_size == offset + size, "new file has size %lu not %lu?", (u_long)st.st_size, (u_long)offset + (u_long)size); fail_unless(st.st_blocks > oblks, "file still has %lu blocks?", oblks); len = pread(test_fd, buf2, size, offset - size / 2); fail_unless(len == size, "read %zd, errno %d", len, errno); rc = memcmp(buf1, buf2, size); fail_unless(rc == 0, "file changed after fallocate?"); check_zero(test_fd, offset + size / 2, size / 2); free(buf1); free(buf2); } #if 0 /* Test maxfilesize. */ TEST(maxfilesize) { struct stat st; struct statfs sfs; off_t offset, len; u_long oblks; int rc; rc = fstatfs(test_fd, &sfs); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); rc = fstat(test_fd, &st); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); fail_unless(st.st_size == 0, "new file has size %lu?", (u_long)st.st_size); oblks = st.st_blocks; len = 1; offset = sfs.f_maxfilesize - 1; rc = posix_fallocate(test_fd, offset, len); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); rc = fstat(test_fd, &st); fail_unless(rc == 0, "got rc %d errno %d", rc, errno); fail_unless(st.st_size == offset + len, "new file has size %lu?", (u_long)st.st_size); fail_unless(st.st_blocks > oblks, "file still has %lu blocks?", oblks); check_zero(test_fd, offset, len); } #endif /* falloc same range twice, see no change in block count */ /* quotas */ /* ENOSPC -- use memdisk ? */ /* maxfilesize */ /* snapshots */ /* EIO, EINTR? */ /* ftruncate after falloc */ /* creat/open O_TRUNC after falloc */ /* XXX system crashed while running? */ int main(void) { struct test **t; g_suite.suite_setup(); SET_FOREACH(t, my_test_set) { g_suite.test_setup(); printf("Running test named '%s'\n", (*t)->name); (*t)->func(); g_suite.test_teardown(); } g_suite.suite_teardown(); return (0); }