--- /dev/null
+# $OpenBSD: Makefile.inc,v 1.1 2017/05/29 11:01:16 sf Exp $
+
+TESTS= create read mmap
+
+FILEOPS_MNT= /mnt/regress-fileops
+FILEOPS_PROG= ${.OBJDIR}/../fileops
+IMG= ${.OBJDIR}/diskimage
+CLEANFILES= ${IMG} stamp-*
+
+
+.poison !defined (MOUNT)
+.poison !defined (NEWFS)
+
+.PHONY: disk mount unconfig clean
+
+disk: unconfig
+ dd if=/dev/urandom of=${IMG} bs=1M count=32
+ vnconfig vnd0 ${IMG}
+ ${NEWFS} /dev/rvnd0c
+
+mount: disk
+ mkdir -p ${FILEOPS_MNT}
+ ${MOUNT} /dev/vnd0c ${FILEOPS_MNT}
+
+unconfig:
+ -umount -f /dev/vnd0c 2>/dev/null || true
+ -rmdir ${FILEOPS_MNT} 2>/dev/null || true
+ -vnconfig -u vnd0 2>/dev/null || true
+ -rm -f stamp-setup ${IMG}
+
+stamp-setup:
+ @echo '\n======== $@ ========'
+ ${.MAKE} -C ${.CURDIR} mount
+ date >$@
+
+${.OBJDIR}/../fileops:
+ ${.MAKE} -C ${.CURDIR}/.. fileops
+
+.for t in ${TESTS}
+REGRESS_TARGETS+= run-regress-${t}
+run-regress-${t}: stamp-setup ${.OBJDIR}/../fileops
+ @echo '\n======== $@ ========'
+ cd ${FILEOPS_MNT} && \
+ ${FILEOPS_PROG} ${t} ${FILEOPS_MNT}/file
+.endfor
+
+REGRESS_TARGETS+= run-regress-cleanup
+run-regress-cleanup:
+ @echo '\n======== $@ ========'
+ umount ${FILEOPS_MNT}
+ ${.MAKE} -C ${.CURDIR} unconfig
--- /dev/null
+/* $OpenBSD: fileops.c,v 1.1 2017/05/29 11:01:16 sf Exp $ */
+/*
+ * Copyright (c) 2017 Stefan Fritsch <sf@sfritsch.de>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <assert.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define BUFSIZE (16 * 1024)
+#define HOLESIZE (16 * BUFSIZE)
+
+#define myerr(rc, fmt, ...) \
+ do { err(rc, fmt "\nFAILED", ## __VA_ARGS__); } while (0)
+#define myerrx(rc, fmt, ...) \
+ do { errx(rc, fmt "\nFAILED", ## __VA_ARGS__); } while (0)
+
+static int debug = 0;
+static int fd = -1;
+static off_t curpos = 0;
+static char *fname;
+static char *gbuf;
+static char *mbuf;
+
+void
+gen_data(void *buf, size_t size, uint32_t seed)
+{
+ assert(size % 4 == 0);
+ if (debug)
+ printf("%s: size %zd seed %#08x\n", __func__, size, seed);
+ uint32_t *ibuf = buf;
+ for (size_t i = 0; i < size / 4; i++)
+ ibuf[i] = seed + i;
+}
+
+void
+check_data(const void *buf, size_t size, uint32_t seed)
+{
+ assert(size % 4 == 0);
+ const uint32_t *ibuf = buf;
+ for (size_t i = 0; i < size / 4; i++) {
+ if (ibuf[i] != seed + i) {
+ myerrx(3, "%s: pos %zd/%zd: expected %#08zx got %#08x\n", __func__, 4 * i, size, seed + i, ibuf[i]);
+ }
+ }
+}
+
+void
+check_zero(const void *buf, size_t size)
+{
+ assert(size % 4 == 0);
+ const uint32_t *ibuf = buf;
+ for (size_t i = 0; i < size / 4; i++) {
+ if (ibuf[i] != 0) {
+ myerrx(3, "%s: pos %zd/%zd: expected 0 got %#08x\n", __func__, 4 * i, size, ibuf[i]);
+ }
+ }
+}
+
+void
+check(const char *what, int64_t have, int64_t want)
+{
+ if (have != want) {
+ if (have == -1)
+ myerr(2, "%s returned %lld, expected %lld", what, have, want);
+ else
+ myerrx(2, "%s returned %lld, expected %lld", what, have, want);
+ }
+
+ if (debug)
+ printf("%s returned %lld\n", what, have);
+}
+
+void
+c_write(void *buf, size_t size)
+{
+ ssize_t ret = write(fd, buf, size);
+ check("write", ret, size);
+ curpos += ret;
+}
+
+void
+c_read(void *buf, size_t size)
+{
+ ssize_t ret = read(fd, buf, size);
+ check("read", ret, size);
+ curpos += ret;
+}
+
+void
+c_fsync(void)
+{
+ int ret = fsync(fd);
+ check("fsync", ret, 0);
+}
+
+void
+c_lseek(off_t offset, int whence)
+{
+ off_t ret = lseek(fd, offset, whence);
+ switch (whence) {
+ case SEEK_SET:
+ curpos = offset;
+ break;
+ case SEEK_CUR:
+ curpos += offset;
+ break;
+ default:
+ myerrx(1, "c_lseek not supported");
+ }
+ check("lseek", ret, curpos);
+ if (debug)
+ printf("curpos: %lld\n", (long long int)curpos);
+}
+
+void
+c_mmap(size_t size)
+{
+ mbuf = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, curpos);
+ if (mbuf == MAP_FAILED)
+ myerr(2, "mmap %zd pos %lld failed", size, (long long)curpos);
+ curpos += size;
+ if (debug)
+ printf("mmap: %p\n", mbuf);
+}
+
+void
+c_munmap(size_t size)
+{
+ int ret = munmap(mbuf, size);
+ if (ret != 0)
+ myerr(2, "munmap");
+}
+
+void
+c_open(int flags)
+{
+ fd = open(fname, flags, S_IRUSR|S_IWUSR);
+ if (fd == -1)
+ myerr(1, "open");
+}
+
+void
+check_read(size_t size, int hole)
+{
+ size_t pos = 0;
+ while (pos < size) {
+ size_t to_read = size - pos;
+ uint32_t seed = curpos;
+ if (to_read > BUFSIZE)
+ to_read = BUFSIZE;
+ c_read(gbuf, to_read);
+ if (hole)
+ check_zero(gbuf, to_read);
+ else
+ check_data(gbuf, to_read, seed);
+ pos += to_read;
+ }
+}
+
+/* XXX this assumes size is a multiple of the page size */
+void
+check_mmap(size_t size, int hole)
+{
+ size_t pos = 0;
+ while (pos < size) {
+ size_t to_read = size - pos;
+ uint32_t seed = curpos;
+ if (to_read > BUFSIZE)
+ to_read = BUFSIZE;
+ c_mmap(to_read);
+ if (hole)
+ check_zero(mbuf, to_read);
+ else
+ check_data(mbuf, to_read, seed);
+ c_munmap(to_read);
+ pos += to_read;
+ }
+}
+
+void
+do_create(void)
+{
+ unlink(fname);
+ c_open(O_EXCL|O_CREAT|O_RDWR);
+
+ gen_data(gbuf, BUFSIZE, curpos);
+ c_write(gbuf, BUFSIZE);
+
+ c_lseek(HOLESIZE, SEEK_CUR);
+
+ gen_data(gbuf, BUFSIZE, curpos);
+ c_write(gbuf, BUFSIZE);
+ c_fsync();
+}
+
+void
+do_read(void)
+{
+ c_open(O_RDWR);
+ check_read(BUFSIZE, 0);
+ check_read(HOLESIZE, 1);
+ check_read(BUFSIZE, 0);
+}
+
+void
+do_mmap(void)
+{
+ c_open(O_RDWR);
+ check_mmap(BUFSIZE, 0);
+ check_mmap(HOLESIZE, 1);
+ check_mmap(BUFSIZE, 0);
+}
+
+void
+usage(void)
+{
+ myerrx(1, "usage: fileops (create|read|mmap) filename");
+}
+
+int main(int argc, char **argv)
+{
+ if (argc != 3)
+ usage();
+
+ fname = argv[2];
+ gbuf = malloc(BUFSIZE);
+ if (gbuf == NULL)
+ myerrx(1, "malloc");
+
+ if (strcmp(argv[1], "create") == 0) {
+ do_create();
+ } else if (strcmp(argv[1], "read") == 0) {
+ do_read();
+ } else if (strcmp(argv[1], "mmap") == 0) {
+ do_mmap();
+ } else {
+ usage();
+ }
+
+ printf("ok\n");
+ return 0;
+}