* [PATCH 1/2] fuse: zero the partial EOF page when extending a file
2026-07-31 20:38 [PATCH 0/2] fuse: zero the partial EOF page when extending a file Jimmy Zuber
@ 2026-07-31 20:38 ` Jimmy Zuber
2026-07-31 20:38 ` [PATCH 2/2] selftests/fuse: test post-EOF page zeroing when a file is extended Jimmy Zuber
1 sibling, 0 replies; 3+ messages in thread
From: Jimmy Zuber @ 2026-07-31 20:38 UTC (permalink / raw)
To: Miklos Szeredi, Shuah Khan; +Cc: fuse-devel, linux-kernel, linux-kselftest
Extending a fuse file past a non-page-aligned EOF does not zero the tail of
the old last page. If that page is cached and was dirtied beyond the old
EOF -- e.g. an application mmap()ed the EOF page and stored into the region
past EOF, which is undefined until the file grows -- the now in-bounds tail
is exposed to subsequent reads as stale data instead of zeros, in violation
of POSIX file-extension semantics.
Other filesystems zero this via pagecache_isize_extended(), but that helper
is a no-op for fuse: it returns early when i_blocksize() >= PAGE_SIZE, and
a non-fuseblk fuse mount has s_blocksize == PAGE_SIZE (the server-supplied
st_blksize only sets fi->cached_i_blkbits, not i_blkbits). The NFS client
hit the same problem and open-codes the zeroing in
nfs_truncate_last_folio(); add the equivalent fuse_zero_partial_eof_folio()
and call it from the three paths that extend a file: a buffered write, a
size-extending setattr/truncate, and a size-extending fallocate
(fuse_write_update_attr(), fuse_do_setattr() and fuse_file_fallocate()).
writeback_cache connections are unaffected, as their writes go through
iomap_file_buffered_write(), which zeroes post-EOF folios. The bug is
observable on a non-writeback_cache server that returns FOPEN_KEEP_CACHE on
writable files (without FOPEN_DIRECT_IO), and is caught by the new
write_extend_eof fuse selftest.
Signed-off-by: Jimmy Zuber <jamz@amazon.com>
---
fs/fuse/dir.c | 3 +++
fs/fuse/file.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++
fs/fuse/fuse_i.h | 1 +
3 files changed, 60 insertions(+)
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 795e92037ce7..f6614ccef186 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -2282,6 +2282,9 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
*/
if ((is_truncate || !is_wb) &&
S_ISREG(inode->i_mode) && oldsize != outarg.attr.size) {
+ if (outarg.attr.size > oldsize)
+ fuse_zero_partial_eof_folio(inode, oldsize,
+ outarg.attr.size);
truncate_pagecache(inode, outarg.attr.size);
invalidate_inode_pages2(mapping);
}
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index cb8da4c06d17..a9063b4e9217 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -21,6 +21,8 @@
#include <linux/splice.h>
#include <linux/task_io_accounting_ops.h>
#include <linux/iomap.h>
+#include <linux/highmem.h>
+#include <linux/rmap.h>
static int fuse_send_open(struct fuse_mount *fm, u64 nodeid,
unsigned int open_flags, int opcode,
@@ -1200,20 +1202,64 @@ static ssize_t fuse_send_write(struct fuse_io_args *ia, loff_t pos,
return err ?: ia->write.out.size;
}
+/*
+ * An operation extended i_size past a non-folio-aligned old EOF at @from,
+ * turning [@from, @to) into a hole that must read back as zero. If the old
+ * last folio is cached and was dirtied beyond the old EOF (e.g. mmap stores
+ * into the post-EOF region, which are undefined until the file grows), zero
+ * that tail so it is not exposed as stale data (xfstests generic/363).
+ *
+ * pagecache_isize_extended() cannot be used: it bails out for
+ * i_blocksize() >= PAGE_SIZE, and a non-fuseblk mount has
+ * s_blocksize == PAGE_SIZE, so the zeroing has to be done here.
+ * Callers hold i_rwsem, serialising this against concurrent writes and
+ * truncates; it must not run under fi->lock, as it locks the folio.
+ */
+void fuse_zero_partial_eof_folio(struct inode *inode, loff_t from, loff_t to)
+{
+ struct folio *folio;
+ size_t offset, end;
+
+ if (from >= to)
+ return;
+
+ folio = filemap_lock_folio(inode->i_mapping, from >> PAGE_SHIFT);
+ if (IS_ERR(folio))
+ return;
+
+ if (folio_mkclean(folio))
+ folio_mark_dirty(folio);
+
+ if (folio_test_dirty(folio)) {
+ offset = offset_in_folio(folio, from);
+ end = min_t(loff_t, to - folio_pos(folio), folio_size(folio));
+ folio_zero_segment(folio, offset, end);
+ }
+
+ folio_unlock(folio);
+ folio_put(folio);
+}
+
bool fuse_write_update_attr(struct inode *inode, loff_t pos, ssize_t written)
{
struct fuse_conn *fc = get_fuse_conn(inode);
struct fuse_inode *fi = get_fuse_inode(inode);
bool ret = false;
+ loff_t old_size = 0;
spin_lock(&fi->lock);
fi->attr_version = atomic64_inc_return(&fc->attr_version);
if (written > 0 && pos > inode->i_size) {
+ old_size = inode->i_size;
i_size_write(inode, pos);
ret = true;
}
spin_unlock(&fi->lock);
+ /* [old_size, pos - written) is the hole this write opened past EOF. */
+ if (ret)
+ fuse_zero_partial_eof_folio(inode, old_size, pos - written);
+
fuse_invalidate_attr_mask(inode, FUSE_STATX_MODSIZE);
return ret;
@@ -2913,8 +2959,18 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset,
/* we could have extended the file */
if (!(mode & FALLOC_FL_KEEP_SIZE)) {
+ loff_t oldsize = i_size_read(inode);
+
if (fuse_write_update_attr(inode, offset + length, length))
file_update_time(file);
+ /*
+ * fuse_write_update_attr() already zeroes up to @offset when
+ * the write started past the old EOF; this additionally covers
+ * a fallocate whose range starts at or before it. fallocate
+ * writes no data, so the whole extension must read as zero; the
+ * overlap is a no-op.
+ */
+ fuse_zero_partial_eof_folio(inode, oldsize, offset + length);
}
if (mode & (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_ZERO_RANGE))
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 85f738c53122..ee3b91b56fef 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1183,6 +1183,7 @@ long fuse_ioctl_common(struct file *file, unsigned int cmd,
__poll_t fuse_file_poll(struct file *file, poll_table *wait);
bool fuse_write_update_attr(struct inode *inode, loff_t pos, ssize_t written);
+void fuse_zero_partial_eof_folio(struct inode *inode, loff_t from, loff_t to);
int fuse_flush_times(struct inode *inode, struct fuse_file *ff);
int fuse_write_inode(struct inode *inode, struct writeback_control *wbc);
--
2.50.1
^ permalink raw reply related [flat|nested] 3+ messages in thread* [PATCH 2/2] selftests/fuse: test post-EOF page zeroing when a file is extended
2026-07-31 20:38 [PATCH 0/2] fuse: zero the partial EOF page when extending a file Jimmy Zuber
2026-07-31 20:38 ` [PATCH 1/2] " Jimmy Zuber
@ 2026-07-31 20:38 ` Jimmy Zuber
1 sibling, 0 replies; 3+ messages in thread
From: Jimmy Zuber @ 2026-07-31 20:38 UTC (permalink / raw)
To: Miklos Szeredi, Shuah Khan; +Cc: fuse-devel, linux-kernel, linux-kselftest
Add a regression test for the bug where extending a file left the tail of
the old partial EOF page exposing stale mmap-dirtied data instead of zeros.
The test is a self-contained raw /dev/fuse server (no libfuse dependency)
that runs without writeback_cache and returns FOPEN_KEEP_CACHE, the
configuration in which the bug is visible. Its backing data is always zero
in the hole, so any non-zero byte a read sees is stale page-cache data.
All offsets are relative to the runtime page size. Four cases:
- write_extend: pollute the post-EOF tail, extend past it by writing into
a later page, and verify the tail reads back as zero;
- ftruncate_extend: same, but extend via ftruncate();
- fallocate_extend: same, but extend via fallocate() at the old EOF;
- extend_into_eof_page_preserves_data: an extending write landing inside
the old EOF page must not be clobbered by the zeroing.
Each case fails without the fix and passes with it.
Signed-off-by: Jimmy Zuber <jamz@amazon.com>
---
.../selftests/filesystems/fuse/.gitignore | 1 +
.../selftests/filesystems/fuse/Makefile | 3 +
.../filesystems/fuse/write_extend_eof_test.c | 368 ++++++++++++++++++
3 files changed, 372 insertions(+)
create mode 100644 tools/testing/selftests/filesystems/fuse/write_extend_eof_test.c
diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/testing/selftests/filesystems/fuse/.gitignore
index 3e72e742d08e..fb51603fe419 100644
--- a/tools/testing/selftests/filesystems/fuse/.gitignore
+++ b/tools/testing/selftests/filesystems/fuse/.gitignore
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
fuse_mnt
fusectl_test
+write_extend_eof_test
diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile
index 612aad69a93a..0c2c613af0ac 100644
--- a/tools/testing/selftests/filesystems/fuse/Makefile
+++ b/tools/testing/selftests/filesystems/fuse/Makefile
@@ -3,10 +3,13 @@
CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
TEST_GEN_PROGS := fusectl_test
+TEST_GEN_PROGS += write_extend_eof_test
TEST_GEN_FILES := fuse_mnt
include ../../lib.mk
+$(OUTPUT)/write_extend_eof_test: LDLIBS += -lpthread
+
VAR_CFLAGS := $(shell pkg-config fuse --cflags 2>/dev/null)
ifeq ($(VAR_CFLAGS),)
VAR_CFLAGS := -D_FILE_OFFSET_BITS=64 -I/usr/include/fuse
diff --git a/tools/testing/selftests/filesystems/fuse/write_extend_eof_test.c b/tools/testing/selftests/filesystems/fuse/write_extend_eof_test.c
new file mode 100644
index 000000000000..ca6ce6eca382
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/write_extend_eof_test.c
@@ -0,0 +1,368 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Regression test for the fuse write-extend partial-EOF-page zeroing bug.
+ *
+ * A buffered write that extends i_size past a non-page-aligned EOF must zero
+ * the tail of the old last page. If an application has mmap'd that page and
+ * stored into the post-EOF region (undefined until the file grows), the
+ * now-in-bounds tail must read back as zero, not as the stale stored bytes.
+ *
+ * The bug is exposed on a non-writeback_cache server that keeps the page cache
+ * across the write (FOPEN_KEEP_CACHE without FOPEN_DIRECT_IO). This test is a
+ * raw /dev/fuse server in that mode; the backing data is always zero in the
+ * hole, so any non-zero byte a read sees is stale page-cache data.
+ *
+ * Requires root to mount fuse.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/falloc.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <linux/fuse.h>
+
+#include "../../kselftest_harness.h"
+
+#define FUSE_ROOT_ID 1
+#define FILE_INO 2
+#define MAX_WRITE (128 * 1024)
+#define BACKING_SIZE (4 * 1024 * 1024)
+#define POLLUTE 0xee
+
+/* Server-side state, shared with the responder thread. */
+struct server {
+ int fd;
+ unsigned char backing[BACKING_SIZE]; /* authoritative bytes */
+ uint64_t size;
+};
+
+static void reply(int fd, uint64_t unique, int error, void *data, size_t len)
+{
+ struct fuse_out_header oh = {
+ .len = sizeof(oh) + (data ? len : 0),
+ .error = error,
+ .unique = unique,
+ };
+ struct iovec iov[2] = { { &oh, sizeof(oh) }, { data, len } };
+
+ /* Errors here are teardown races (device closed on unmount); ignore. */
+ if (writev(fd, iov, data ? 2 : 1) < 0)
+ return;
+}
+
+static void fill_attr(struct fuse_attr *a, uint64_t ino, uint32_t mode,
+ uint64_t size)
+{
+ memset(a, 0, sizeof(*a));
+ a->ino = ino;
+ a->mode = mode;
+ a->nlink = 1;
+ a->size = size;
+ a->blksize = sysconf(_SC_PAGESIZE);
+}
+
+static void *server_thread(void *arg)
+{
+ struct server *s = arg;
+ static char buf[MAX_WRITE + 4096];
+
+ for (;;) {
+ ssize_t n = read(s->fd, buf, sizeof(buf));
+ struct fuse_in_header *ih = (void *)buf;
+
+ if (n < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return NULL; /* device closed on unmount */
+ }
+ if (n < (ssize_t)sizeof(*ih))
+ continue;
+
+ switch (ih->opcode) {
+ case FUSE_INIT: {
+ struct fuse_init_in *in = (void *)(ih + 1);
+ struct fuse_init_out out = {0};
+
+ /* No FUSE_WRITEBACK_CACHE: the exposed configuration. */
+ out.major = FUSE_KERNEL_VERSION;
+ out.minor = FUSE_KERNEL_MINOR_VERSION;
+ out.max_readahead = in->max_readahead;
+ out.max_write = MAX_WRITE;
+ out.max_background = 16;
+ out.congestion_threshold = 12;
+ out.flags = FUSE_MAX_PAGES;
+ out.max_pages = MAX_WRITE / sysconf(_SC_PAGESIZE);
+ reply(s->fd, ih->unique, 0, &out, sizeof(out));
+ break;
+ }
+ case FUSE_GETATTR: {
+ struct fuse_attr_out out = {0};
+ int root = ih->nodeid == FUSE_ROOT_ID;
+
+ out.attr_valid = 3600;
+ fill_attr(&out.attr, ih->nodeid,
+ root ? (S_IFDIR | 0755) : (S_IFREG | 0644),
+ root ? 0 : s->size);
+ reply(s->fd, ih->unique, 0, &out, sizeof(out));
+ break;
+ }
+ case FUSE_LOOKUP: {
+ struct fuse_entry_out out = {0};
+
+ out.nodeid = FILE_INO;
+ out.attr_valid = 3600;
+ out.entry_valid = 3600;
+ fill_attr(&out.attr, FILE_INO, S_IFREG | 0644, s->size);
+ reply(s->fd, ih->unique, 0, &out, sizeof(out));
+ break;
+ }
+ case FUSE_OPEN:
+ case FUSE_OPENDIR: {
+ struct fuse_open_out out = {0};
+
+ /* Keep the cache across the write, but not direct I/O. */
+ out.open_flags = FOPEN_KEEP_CACHE;
+ reply(s->fd, ih->unique, 0, &out, sizeof(out));
+ break;
+ }
+ case FUSE_READ: {
+ struct fuse_read_in *in = (void *)(ih + 1);
+ uint64_t off = in->offset;
+ uint32_t size = in->size;
+
+ if (off >= BACKING_SIZE)
+ size = 0;
+ else if (off + size > BACKING_SIZE)
+ size = BACKING_SIZE - off;
+ reply(s->fd, ih->unique, 0, s->backing + off, size);
+ break;
+ }
+ case FUSE_WRITE: {
+ struct fuse_write_in *in = (void *)(ih + 1);
+ struct fuse_write_out out = {0};
+ uint64_t off = in->offset;
+ uint32_t size = in->size;
+
+ if (off < BACKING_SIZE) {
+ uint32_t c = size;
+
+ if (off + c > BACKING_SIZE)
+ c = BACKING_SIZE - off;
+ memcpy(s->backing + off, in + 1, c);
+ if (off + c > s->size)
+ s->size = off + c;
+ }
+ out.size = size;
+ reply(s->fd, ih->unique, 0, &out, sizeof(out));
+ break;
+ }
+ case FUSE_SETATTR: {
+ struct fuse_setattr_in *in = (void *)(ih + 1);
+ struct fuse_attr_out out = {0};
+
+ if ((in->valid & FATTR_SIZE) && in->size <= BACKING_SIZE) {
+ if (in->size > s->size)
+ memset(s->backing + s->size, 0,
+ in->size - s->size);
+ s->size = in->size;
+ }
+ out.attr_valid = 3600;
+ fill_attr(&out.attr, ih->nodeid, S_IFREG | 0644, s->size);
+ reply(s->fd, ih->unique, 0, &out, sizeof(out));
+ break;
+ }
+ case FUSE_FALLOCATE: {
+ struct fuse_fallocate_in *in = (void *)(ih + 1);
+ uint64_t end = in->offset + in->length;
+
+ /* Only plain (size-extending) fallocate is used here. */
+ if (!(in->mode & FALLOC_FL_KEEP_SIZE) &&
+ end <= BACKING_SIZE && end > s->size) {
+ memset(s->backing + s->size, 0, end - s->size);
+ s->size = end;
+ }
+ reply(s->fd, ih->unique, 0, NULL, 0);
+ break;
+ }
+ case FUSE_FLUSH:
+ case FUSE_RELEASE:
+ case FUSE_RELEASEDIR:
+ case FUSE_FSYNC:
+ case FUSE_ACCESS:
+ reply(s->fd, ih->unique, 0, NULL, 0);
+ break;
+ case FUSE_FORGET:
+ break;
+ default:
+ reply(s->fd, ih->unique, -EOPNOTSUPP, NULL, 0);
+ break;
+ }
+ }
+}
+
+FIXTURE(fuse)
+{
+ struct server *srv;
+ pthread_t thread;
+ char dir[64];
+ long page; /* runtime page size */
+ off_t eof; /* mid-page EOF, page-relative */
+ int fd; /* open test file */
+ char *map; /* mmap of the EOF page */
+ int mounted;
+};
+
+FIXTURE_SETUP(fuse)
+{
+ char opts[128];
+ pthread_t t;
+
+ if (geteuid() != 0)
+ SKIP(return, "need root to mount fuse");
+
+ self->page = sysconf(_SC_PAGESIZE);
+ self->fd = -1;
+ self->map = MAP_FAILED;
+
+ self->srv = mmap(NULL, sizeof(*self->srv), PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ ASSERT_NE(MAP_FAILED, self->srv);
+
+ self->srv->fd = open("/dev/fuse", O_RDWR);
+ ASSERT_GE(self->srv->fd, 0);
+
+ strcpy(self->dir, "/tmp/fuse_weof_XXXXXX");
+ ASSERT_NE(NULL, mkdtemp(self->dir));
+
+ snprintf(opts, sizeof(opts),
+ "fd=%d,rootmode=40000,user_id=0,group_id=0",
+ self->srv->fd);
+ ASSERT_EQ(0, mount("fuse", self->dir, "fuse", 0, opts));
+ self->mounted = 1;
+
+ ASSERT_EQ(0, pthread_create(&t, NULL, server_thread, self->srv));
+ self->thread = t;
+}
+
+FIXTURE_TEARDOWN(fuse)
+{
+ if (self->map != MAP_FAILED)
+ munmap(self->map, self->page);
+ if (self->fd >= 0)
+ close(self->fd);
+ if (self->mounted)
+ umount2(self->dir, MNT_DETACH);
+ if (self->srv && self->srv != MAP_FAILED) {
+ if (self->srv->fd > 0)
+ close(self->srv->fd);
+ munmap(self->srv, sizeof(*self->srv));
+ }
+ if (self->dir[0])
+ rmdir(self->dir);
+}
+
+/*
+ * Create the test file with a mid-page EOF and mmap-store POLLUTE into its
+ * post-EOF tail (a legal store, undefined until the file grows). Leaves the
+ * file open and the EOF page mapped in the fixture for the caller to extend.
+ */
+static void pollute_eof_tail(struct __test_metadata *_metadata,
+ FIXTURE_DATA(fuse) * self)
+{
+ off_t eof = 2 * self->page + self->page / 4;
+ char path[128];
+ char *buf;
+
+ snprintf(path, sizeof(path), "%s/file", self->dir);
+ self->fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644);
+ ASSERT_GE(self->fd, 0);
+ self->eof = eof;
+
+ buf = malloc(eof);
+ ASSERT_NE(NULL, buf);
+ memset(buf, 'A', eof);
+ ASSERT_EQ(eof, pwrite(self->fd, buf, eof, 0));
+ free(buf);
+
+ self->map = mmap(NULL, self->page, PROT_READ | PROT_WRITE, MAP_SHARED,
+ self->fd, eof & ~(self->page - 1));
+ ASSERT_NE(MAP_FAILED, self->map);
+ memset(self->map + (eof & (self->page - 1)), POLLUTE,
+ self->page - (eof & (self->page - 1)));
+}
+
+/* Assert the old post-EOF tail [eof, end of its page) now reads back as zero. */
+static void assert_tail_zeroed(struct __test_metadata *_metadata,
+ FIXTURE_DATA(fuse) * self)
+{
+ off_t base = self->eof & ~(self->page - 1);
+ char *tail = malloc(self->page);
+ int i;
+
+ ASSERT_NE(NULL, tail);
+ ASSERT_EQ(self->page, pread(self->fd, tail, self->page, base));
+ for (i = self->eof & (self->page - 1); i < self->page; i++)
+ ASSERT_EQ(0, tail[i]);
+ free(tail);
+}
+
+/* Basic: pollute the post-EOF tail, extend past it by a later write. */
+TEST_F(fuse, write_extend)
+{
+ pollute_eof_tail(_metadata, self);
+ ASSERT_EQ(4, pwrite(self->fd, "data", 4, 5 * self->page + self->page / 3));
+ assert_tail_zeroed(_metadata, self);
+}
+
+/* Extend via ftruncate() rather than a write. */
+TEST_F(fuse, ftruncate_extend)
+{
+ pollute_eof_tail(_metadata, self);
+ ASSERT_EQ(0, ftruncate(self->fd, 8 * self->page));
+ assert_tail_zeroed(_metadata, self);
+}
+
+/* Extend via fallocate() starting at the old EOF. */
+TEST_F(fuse, fallocate_extend)
+{
+ pollute_eof_tail(_metadata, self);
+ ASSERT_EQ(0, fallocate(self->fd, 0, self->eof, 4 * self->page));
+ assert_tail_zeroed(_metadata, self);
+}
+
+/* A write landing inside the old EOF page must not clobber its own data. */
+TEST_F(fuse, extend_into_eof_page_preserves_data)
+{
+ off_t base, wr;
+ char *buf, *rd;
+ int i;
+
+ pollute_eof_tail(_metadata, self);
+ base = self->eof & ~(self->page - 1);
+ wr = base + 3 * self->page / 4; /* starts in the EOF page */
+
+ buf = malloc(2 * self->page);
+ ASSERT_NE(NULL, buf);
+ memset(buf, 'B', 2 * self->page);
+ ASSERT_EQ(2 * self->page, pwrite(self->fd, buf, 2 * self->page, wr));
+ free(buf);
+
+ rd = malloc(self->page);
+ ASSERT_NE(NULL, rd);
+ ASSERT_EQ(self->page, pread(self->fd, rd, self->page, base));
+ /* [eof, wr) is hole -> zero; [wr, page) is written data -> 'B'. */
+ for (i = self->eof & (self->page - 1); i < wr - base; i++)
+ ASSERT_EQ(0, rd[i]);
+ for (i = wr - base; i < self->page; i++)
+ ASSERT_EQ('B', rd[i]);
+ free(rd);
+}
+
+TEST_HARNESS_MAIN
--
2.50.1
^ permalink raw reply related [flat|nested] 3+ messages in thread