summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usr/src/cmd/bhyve/mevent.c327
-rw-r--r--usr/src/cmd/bhyve/test/tests/mevent/Makefile9
-rw-r--r--usr/src/cmd/bhyve/test/tests/mevent/mevent.c6
-rw-r--r--usr/src/cmd/bhyve/test/tests/mevent/testlib.h2
-rw-r--r--usr/src/cmd/bhyve/test/tests/mevent/vnode_file.c141
-rw-r--r--usr/src/cmd/bhyve/test/tests/mevent/vnode_zvol.c259
-rw-r--r--usr/src/pkg/manifests/system-bhyve-tests.p5m4
-rw-r--r--usr/src/test/bhyve-tests/runfiles/default.run7
8 files changed, 637 insertions, 118 deletions
diff --git a/usr/src/cmd/bhyve/mevent.c b/usr/src/cmd/bhyve/mevent.c
index 98a1d8ca7f..576ba3390e 100644
--- a/usr/src/cmd/bhyve/mevent.c
+++ b/usr/src/cmd/bhyve/mevent.c
@@ -30,7 +30,7 @@
/*
* Copyright 2018 Joyent, Inc.
- * Copyright 2021 OmniOS Community Edition (OmniOSce) Association.
+ * Copyright 2022 OmniOS Community Edition (OmniOSce) Association.
*/
/*
@@ -82,11 +82,15 @@ __FBSDID("$FreeBSD$");
#define EV_ADD EV_ENABLE
#define EV_DISABLE 0x02
#define EV_DELETE 0x04
+
+static int mevent_file_poll_interval_ms = 5000;
#endif
static pthread_t mevent_tid;
static pthread_once_t mevent_once = PTHREAD_ONCE_INIT;
+#ifdef __FreeBSD__
static int mevent_timid = 43;
+#endif
static int mevent_pipefd[2];
static int mfd;
static pthread_mutex_t mevent_lmutex = PTHREAD_MUTEX_INITIALIZER;
@@ -112,6 +116,12 @@ struct mevent {
boolean_t me_auto_requeue;
struct file_obj me_fobj;
char *me_fname;
+ struct {
+ int mp_fd;
+ off_t mp_size;
+ void (*mp_func)(int, enum ev_type, void *);
+ void *mp_param;
+ } me_poll;
#endif
LIST_ENTRY(mevent) me_list;
};
@@ -177,7 +187,7 @@ mevent_init(void)
cap_rights_init(&rights, CAP_KQUEUE);
if (caph_rights_limit(mfd, &rights) == -1)
errx(EX_OSERR, "Unable to apply rights for sandbox");
- #endif
+#endif
LIST_INIT(&change_head);
LIST_INIT(&global_head);
@@ -382,149 +392,233 @@ mevent_fdpath(int fd)
}
static void
-mevent_update_one(struct mevent *mevp)
+mevent_poll_file_attrib(int fd, enum ev_type type, void *param)
{
- int portfd = mevp->me_notify.portnfy_port;
+ struct mevent *mevp = param;
+ struct stat st;
- switch (mevp->me_type) {
- case EVF_READ:
- case EVF_WRITE:
- mevp->me_auto_requeue = B_FALSE;
+ if (fstat(mevp->me_poll.mp_fd, &st) != 0) {
+ (void) fprintf(stderr, "%s: fstat(%d) \"%s\" failed: %s\n",
+ __func__, fd, mevp->me_fname, strerror(errno));
+ return;
+ }
+
+ if (mevp->me_poll.mp_size != st.st_size ||
+ mevp->me_fobj.fo_ctime.tv_sec != st.st_ctim.tv_sec ||
+ mevp->me_fobj.fo_ctime.tv_nsec != st.st_ctim.tv_nsec) {
+ mevp->me_poll.mp_size = st.st_size;
+ mevp->me_fobj.fo_atime = st.st_atim;
+ mevp->me_fobj.fo_mtime = st.st_mtim;
+ mevp->me_fobj.fo_ctime = st.st_ctim;
+
+ (*mevp->me_poll.mp_func)(mevp->me_poll.mp_fd, EVF_VNODE,
+ mevp->me_poll.mp_param);
+ }
+}
- switch (mevp->me_state) {
- case EV_ENABLE:
- {
- int events;
+static void
+mevent_update_one_readwrite(struct mevent *mevp)
+{
+ int portfd = mevp->me_notify.portnfy_port;
- events = (mevp->me_type == EVF_READ) ? POLLIN : POLLOUT;
+ mevp->me_auto_requeue = B_FALSE;
- if (port_associate(portfd, PORT_SOURCE_FD, mevp->me_fd,
- events, mevp) != 0) {
- (void) fprintf(stderr,
- "port_associate fd %d %p failed: %s\n",
- mevp->me_fd, mevp, strerror(errno));
- }
- return;
+ switch (mevp->me_state) {
+ case EV_ENABLE:
+ {
+ const int events = (mevp->me_type == EVF_READ) ?
+ POLLIN : POLLOUT;
+
+ if (port_associate(portfd, PORT_SOURCE_FD, mevp->me_fd,
+ events, mevp) != 0) {
+ (void) fprintf(stderr,
+ "port_associate fd %d %p failed: %s\n",
+ mevp->me_fd, mevp, strerror(errno));
}
- case EV_DISABLE:
- case EV_DELETE:
- /*
- * A disable that comes in while an event is being
- * handled will result in an ENOENT.
- */
- if (port_dissociate(portfd, PORT_SOURCE_FD,
- mevp->me_fd) != 0 && errno != ENOENT) {
- (void) fprintf(stderr, "port_dissociate "
- "portfd %d fd %d mevp %p failed: %s\n",
- portfd, mevp->me_fd, mevp, strerror(errno));
- }
- return;
- default:
- goto abort;
+ return;
+ }
+ case EV_DISABLE:
+ case EV_DELETE:
+ /*
+ * A disable that comes in while an event is being
+ * handled will result in an ENOENT.
+ */
+ if (port_dissociate(portfd, PORT_SOURCE_FD,
+ mevp->me_fd) != 0 && errno != ENOENT) {
+ (void) fprintf(stderr, "port_dissociate "
+ "portfd %d fd %d mevp %p failed: %s\n",
+ portfd, mevp->me_fd, mevp, strerror(errno));
}
+ return;
+ default:
+ (void) fprintf(stderr, "%s: unhandled state %d\n", __func__,
+ mevp->me_state);
+ abort();
+ }
+}
- case EVF_TIMER:
- mevp->me_auto_requeue = B_TRUE;
+static void
+mevent_update_one_timer(struct mevent *mevp)
+{
+ mevp->me_auto_requeue = B_TRUE;
- switch (mevp->me_state) {
- case EV_ENABLE:
- {
- struct itimerspec it = { 0 };
+ switch (mevp->me_state) {
+ case EV_ENABLE:
+ {
+ struct itimerspec it = { 0 };
- mevp->me_sigev.sigev_notify = SIGEV_PORT;
- mevp->me_sigev.sigev_value.sival_ptr = &mevp->me_notify;
+ mevp->me_sigev.sigev_notify = SIGEV_PORT;
+ mevp->me_sigev.sigev_value.sival_ptr = &mevp->me_notify;
- if (timer_create(CLOCK_REALTIME, &mevp->me_sigev,
- &mevp->me_timid) != 0) {
- (void) fprintf(stderr,
- "timer_create failed: %s", strerror(errno));
- return;
- }
+ if (timer_create(CLOCK_REALTIME, &mevp->me_sigev,
+ &mevp->me_timid) != 0) {
+ (void) fprintf(stderr, "timer_create failed: %s",
+ strerror(errno));
+ return;
+ }
- /* The first timeout */
- it.it_value.tv_sec = mevp->me_msecs / MILLISEC;
- it.it_value.tv_nsec =
- MSEC2NSEC(mevp->me_msecs % MILLISEC);
- /* Repeat at the same interval */
- it.it_interval = it.it_value;
+ /* The first timeout */
+ it.it_value.tv_sec = mevp->me_msecs / MILLISEC;
+ it.it_value.tv_nsec =
+ MSEC2NSEC(mevp->me_msecs % MILLISEC);
+ /* Repeat at the same interval */
+ it.it_interval = it.it_value;
- if (timer_settime(mevp->me_timid, 0, &it, NULL) != 0) {
- (void) fprintf(stderr, "timer_settime failed: "
- "%s", strerror(errno));
- }
- return;
+ if (timer_settime(mevp->me_timid, 0, &it, NULL) != 0) {
+ (void) fprintf(stderr, "timer_settime failed: %s",
+ strerror(errno));
}
- case EV_DISABLE:
- case EV_DELETE:
- if (timer_delete(mevp->me_timid) != 0) {
- (void) fprintf(stderr, "timer_delete failed: "
- "%s", strerror(errno));
- }
- return;
- default:
- goto abort;
+ return;
+ }
+ case EV_DISABLE:
+ case EV_DELETE:
+ if (timer_delete(mevp->me_timid) != 0) {
+ (void) fprintf(stderr, "timer_delete failed: %s",
+ strerror(errno));
}
+ mevp->me_timid = -1;
+ return;
+ default:
+ (void) fprintf(stderr, "%s: unhandled state %d\n", __func__,
+ mevp->me_state);
+ abort();
+ }
+}
- case EVF_VNODE:
- mevp->me_auto_requeue = B_FALSE;
+static void
+mevent_update_one_vnode(struct mevent *mevp)
+{
+ int portfd = mevp->me_notify.portnfy_port;
- switch (mevp->me_state) {
- case EV_ENABLE:
- {
- int events = 0;
+ mevp->me_auto_requeue = B_FALSE;
- if ((mevp->me_fflags & EVFF_ATTRIB) != 0)
- events |= FILE_ATTRIB;
+ switch (mevp->me_state) {
+ case EV_ENABLE:
+ {
+ int events = 0;
- assert(events != 0);
+ if ((mevp->me_fflags & EVFF_ATTRIB) != 0)
+ events |= FILE_ATTRIB;
- if (mevp->me_fname == NULL) {
- mevp->me_fname = mevent_fdpath(mevp->me_fd);
- if (mevp->me_fname == NULL)
- return;
- }
+ assert(events != 0);
+
+ if (mevp->me_fname == NULL) {
+ mevp->me_fname = mevent_fdpath(mevp->me_fd);
+ if (mevp->me_fname == NULL)
+ return;
+ }
+
+ bzero(&mevp->me_fobj, sizeof (mevp->me_fobj));
+ mevp->me_fobj.fo_name = mevp->me_fname;
- bzero(&mevp->me_fobj, sizeof (mevp->me_fobj));
- mevp->me_fobj.fo_name = mevp->me_fname;
+ if (port_associate(portfd, PORT_SOURCE_FILE,
+ (uintptr_t)&mevp->me_fobj, events, mevp) != 0) {
+ /*
+ * If this file does not support event ports
+ * (e.g. ZVOLs do not yet have support)
+ * then convert this to a timer event and poll for
+ * file attribute changes.
+ */
+ struct stat st;
- if (port_associate(portfd, PORT_SOURCE_FILE,
- (uintptr_t)&mevp->me_fobj, events, mevp) != 0) {
+ if (errno != ENOTSUP) {
(void) fprintf(stderr,
- "port_associate fd %d (%s) %p failed: %s\n",
+ "port_associate fd %d (%s) %p failed: %s"
+ ", polling instead\n",
mevp->me_fd, mevp->me_fname, mevp,
strerror(errno));
}
- return;
- }
- case EV_DISABLE:
- case EV_DELETE:
- /*
- * A disable that comes in while an event is being
- * handled will result in an ENOENT.
- */
- if (port_dissociate(portfd, PORT_SOURCE_FILE,
- (uintptr_t)&mevp->me_fobj) != 0 &&
- errno != ENOENT) {
- (void) fprintf(stderr, "port_dissociate "
- "portfd %d fd %d mevp %p failed: %s\n",
- portfd, mevp->me_fd, mevp, strerror(errno));
+
+ if (fstat(mevp->me_fd, &st) != 0) {
+ (void) fprintf(stderr,
+ "fstat(%d) \"%s\" failed: %s\n",
+ mevp->me_fd, mevp->me_fname,
+ strerror(errno));
+ return;
}
- free(mevp->me_fname);
- mevp->me_fname = NULL;
- return;
- default:
- goto abort;
- }
+ mevp->me_fobj.fo_atime = st.st_atim;
+ mevp->me_fobj.fo_mtime = st.st_mtim;
+ mevp->me_fobj.fo_ctime = st.st_ctim;
+
+ mevp->me_poll.mp_fd = mevp->me_fd;
+ mevp->me_poll.mp_size = st.st_size;
+
+ mevp->me_poll.mp_func = mevp->me_func;
+ mevp->me_poll.mp_param = mevp->me_param;
+ mevp->me_func = mevent_poll_file_attrib;
+ mevp->me_param = mevp;
+
+ mevp->me_type = EVF_TIMER;
+ mevp->me_timid = -1;
+ mevp->me_msecs = mevent_file_poll_interval_ms;
+ mevent_update_one_timer(mevp);
+ }
+ return;
+ }
+ case EV_DISABLE:
+ case EV_DELETE:
+ /*
+ * A disable that comes in while an event is being
+ * handled will result in an ENOENT.
+ */
+ if (port_dissociate(portfd, PORT_SOURCE_FILE,
+ (uintptr_t)&mevp->me_fobj) != 0 &&
+ errno != ENOENT) {
+ (void) fprintf(stderr, "port_dissociate "
+ "portfd %d fd %d mevp %p failed: %s\n",
+ portfd, mevp->me_fd, mevp, strerror(errno));
+ }
+ free(mevp->me_fname);
+ mevp->me_fname = NULL;
+ return;
default:
- /* EVF_SIGNAL not yet implemented. */
- goto abort;
+ (void) fprintf(stderr, "%s: unhandled state %d\n", __func__,
+ mevp->me_state);
+ abort();
}
+}
-abort:
- (void) fprintf(stderr, "%s: unhandled type %d state %d\n", __func__,
- mevp->me_type, mevp->me_state);
- abort();
+static void
+mevent_update_one(struct mevent *mevp)
+{
+ switch (mevp->me_type) {
+ case EVF_READ:
+ case EVF_WRITE:
+ mevent_update_one_readwrite(mevp);
+ break;
+ case EVF_TIMER:
+ mevent_update_one_timer(mevp);
+ break;
+ case EVF_VNODE:
+ mevent_update_one_vnode(mevp);
+ break;
+ case EVF_SIGNAL: /* EVF_SIGNAL not yet implemented. */
+ default:
+ (void) fprintf(stderr, "%s: unhandled event type %d\n",
+ __func__, mevp->me_type);
+ abort();
+ }
}
static void
@@ -637,13 +731,16 @@ mevent_add_state(int tfd, enum ev_type type,
if (type == EVF_TIMER) {
mevp->me_msecs = tfd;
+#ifdef __FreeBSD__
mevp->me_timid = mevent_timid++;
+#else
+ mevp->me_timid = -1;
+#endif
} else
mevp->me_fd = tfd;
mevp->me_type = type;
mevp->me_func = func;
mevp->me_param = param;
-
mevp->me_state = state;
mevp->me_fflags = fflags;
diff --git a/usr/src/cmd/bhyve/test/tests/mevent/Makefile b/usr/src/cmd/bhyve/test/tests/mevent/Makefile
index 363deb02cc..9d93e17f5a 100644
--- a/usr/src/cmd/bhyve/test/tests/mevent/Makefile
+++ b/usr/src/cmd/bhyve/test/tests/mevent/Makefile
@@ -12,6 +12,7 @@
#
# Copyright 2018 Joyent, Inc.
# Copyright 2022 Oxide Computer Company
+# Copyright 2022 OmniOS Community Edition (OmniOSce) Association.
#
TESTSUBDIR = mevent
@@ -19,7 +20,9 @@ PROG = \
lists_delete \
read_disable \
read_pause \
- read_requeue
+ read_requeue \
+ vnode_file \
+ vnode_zvol
SUPOBJS = mevent.o testlib.o
@@ -34,8 +37,12 @@ install: $(TESTDIR) $(CMDS)
$(CMDS): $(PROG)
+vnode_zvol := LDLIBS += -lzfs -lnvpair
+
include ../../Makefile.targ
%: %.o $(SUPOBJS)
$(LINK.c) -o $@ $< $(SUPOBJS) $(LDLIBS)
$(POST_PROCESS)
+
+mevent.o: ../../../mevent.c
diff --git a/usr/src/cmd/bhyve/test/tests/mevent/mevent.c b/usr/src/cmd/bhyve/test/tests/mevent/mevent.c
index 971cf4aa77..51c94a4c09 100644
--- a/usr/src/cmd/bhyve/test/tests/mevent/mevent.c
+++ b/usr/src/cmd/bhyve/test/tests/mevent/mevent.c
@@ -55,3 +55,9 @@ test_mevent_count_lists(int *ret_global, int *ret_change, int *ret_del_pending)
*ret_change = change;
*ret_del_pending = del_pending;
}
+
+void
+set_mevent_file_poll_interval_ms(int ms)
+{
+ mevent_file_poll_interval_ms = ms;
+}
diff --git a/usr/src/cmd/bhyve/test/tests/mevent/testlib.h b/usr/src/cmd/bhyve/test/tests/mevent/testlib.h
index 7e5ca2e9c9..1639f29f87 100644
--- a/usr/src/cmd/bhyve/test/tests/mevent/testlib.h
+++ b/usr/src/cmd/bhyve/test/tests/mevent/testlib.h
@@ -71,6 +71,7 @@
#define ASSERT_INT_EQ(msg, got, exp) ASSERT_CMP(msg, got, ==, exp, "%d")
#define ASSERT_INT_NEQ(msg, got, exp) ASSERT_CMP(msg, got, !=, exp, "%d")
#define ASSERT_INT64_EQ(msg, got, exp) ASSERT_CMP(msg, got, ==, exp, "%ld")
+#define ASSERT_INT64_NEQ(msg, got, exp) ASSERT_CMP(msg, got, !=, exp, "%ld")
#define ASSERT_PTR_EQ(msg, got, exp) ASSERT_CMP(msg, got, ==, exp, "%p")
#define ASSERT_PTR_NEQ(msg, got, exp) ASSERT_CMP(msg, got, !=, exp, "%p")
@@ -89,5 +90,6 @@ extern boolean_t testlib_verbose;
extern void start_test(const char *, uint32_t);
extern void start_event_thread(void);
extern void test_mevent_count_lists(int *, int *, int *);
+extern void set_mevent_file_poll_interval_ms(int);
#endif /* _TESTLIB_H_ */
diff --git a/usr/src/cmd/bhyve/test/tests/mevent/vnode_file.c b/usr/src/cmd/bhyve/test/tests/mevent/vnode_file.c
new file mode 100644
index 0000000000..850ac1be27
--- /dev/null
+++ b/usr/src/cmd/bhyve/test/tests/mevent/vnode_file.c
@@ -0,0 +1,141 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2018 Joyent, Inc.
+ * Copyright 2022 OmniOS Community Edition (OmniOSce) Association.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "testlib.h"
+#include "mevent.h"
+
+static char *cookie = "Chocolate chip with fudge stripes";
+
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
+
+static void
+callback(int fd, enum ev_type ev, void *arg)
+{
+ static off_t size = 0;
+ struct stat st;
+
+ ASSERT_INT_EQ(("bad event"), ev, EVF_VNODE);
+ ASSERT_PTR_EQ(("bad cookie"), arg, cookie);
+
+ if (fstat(fd, &st) != 0)
+ FAIL_ERRNO("fstat failed");
+
+ ASSERT_INT64_NEQ(("File size has not changed"), size, st.st_size);
+ size = st.st_size;
+
+ pthread_mutex_lock(&mtx);
+ pthread_cond_signal(&cv);
+ VERBOSE(("wakeup"));
+ pthread_mutex_unlock(&mtx);
+}
+
+static void
+test_fd(int fd, char *tag)
+{
+ struct mevent *evp;
+ int err;
+
+ evp = mevent_add_flags(fd, EVF_VNODE, EVFF_ATTRIB, callback, cookie);
+ ASSERT_PTR_NEQ(("%s: mevent_add", tag), evp, NULL);
+
+ for (uint_t i = 0; cookie[i] != '\0'; i++) {
+ ssize_t written;
+
+ pthread_mutex_lock(&mtx);
+
+ if (i > 0) {
+ /*
+ * Check that no events are emitted for writes which do
+ * not alter the size.
+ */
+ if (lseek(fd, -1, SEEK_CUR) == -1)
+ FAIL_ERRNO("lseek");
+ if (write(fd, "X", 1) == -1)
+ FAIL_ERRNO("write");
+ }
+
+ written = write(fd, cookie + i, 1);
+ if (written < 0)
+ FAIL_ERRNO("bad write");
+ ASSERT_INT64_EQ(("write byte %d of cookie", i), written, 1);
+
+ /* Wait for the size change to be processed */
+ pthread_cond_wait(&cv, &mtx);
+ pthread_mutex_unlock(&mtx);
+ /*
+ * This is a bit unsatisfactory but we need to allow time
+ * for mevent to re-associate the port or the next write could
+ * be missed.
+ */
+ usleep(500);
+ }
+
+ err = mevent_disable(evp);
+ ASSERT_INT_EQ(("%s: mevent_disable: %s", tag, strerror(err)), err, 0);
+
+ (void) printf("PASS %s - %s\n", testlib_prog, tag);
+}
+
+int
+main(int argc, const char **argv)
+{
+ start_test(argv[0], 5);
+ start_event_thread();
+ int fd;
+
+ /* Test with a temporary file in /tmp */
+ char *template = strdup("/tmp/mevent.vnode.XXXXXX");
+ ASSERT_PTR_NEQ(("strdup"), template, NULL);
+ fd = mkstemp(template);
+ if (fd == -1)
+ FAIL_ERRNO("Couldn't create temporary file with mkstemp");
+
+ VERBOSE(("Opened temporary file at '%s'", template));
+
+ test_fd(fd, "temporary file");
+
+ /* Test with a file which is unlinked from the filesystem */
+ FILE *fp = tmpfile();
+ ASSERT_PTR_NEQ(("tmpfile"), fp, NULL);
+
+ fd = fileno(fp);
+ if (fd == -1)
+ FAIL_ERRNO("Couldn't get file descriptor for temporary file");
+
+ test_fd(fd, "anon file");
+
+ /*
+ * Defer to here to avoid generating a new event before the disable has
+ * been processed and the port deassociated.
+ */
+ unlink(template);
+ free(template);
+
+ PASS();
+}
diff --git a/usr/src/cmd/bhyve/test/tests/mevent/vnode_zvol.c b/usr/src/cmd/bhyve/test/tests/mevent/vnode_zvol.c
new file mode 100644
index 0000000000..8e99d7ba0d
--- /dev/null
+++ b/usr/src/cmd/bhyve/test/tests/mevent/vnode_zvol.c
@@ -0,0 +1,259 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2018 Joyent, Inc.
+ * Copyright 2022 OmniOS Community Edition (OmniOSce) Association.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <libzfs.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <zone.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "testlib.h"
+#include "mevent.h"
+
+#define MB (1024 * 1024)
+
+static char *cookie = "Chocolate chip with fudge stripes";
+
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
+
+static void
+callback(int fd, enum ev_type ev, void *arg)
+{
+ static off_t size = 0;
+ struct stat st;
+
+ ASSERT_INT_EQ(("bad event"), ev, EVF_VNODE);
+ ASSERT_PTR_EQ(("bad cookie"), arg, cookie);
+
+ if (fstat(fd, &st) != 0)
+ FAIL_ERRNO("fstat failed");
+
+ ASSERT_INT64_NEQ(("Size has not changed"), size, st.st_size);
+ size = st.st_size;
+
+ pthread_mutex_lock(&mtx);
+ pthread_cond_signal(&cv);
+ VERBOSE(("wakeup"));
+ pthread_mutex_unlock(&mtx);
+}
+
+static void
+destroy_zpool(libzfs_handle_t *zfshdl, zpool_handle_t *poolhdl,
+ zfs_handle_t *volhdl)
+{
+ if (volhdl != NULL) {
+ if (zfs_destroy(volhdl, B_FALSE) != 0) {
+ FAIL(("Failed to destroy ZVOL - %s",
+ libzfs_error_description(zfshdl)));
+ }
+ }
+
+ if (poolhdl != NULL) {
+ if (zpool_destroy(poolhdl, testlib_prog) != 0) {
+ FAIL(("Failed to destroy ZPOOL - %s",
+ libzfs_error_description(zfshdl)));
+ }
+ }
+}
+
+static void
+create_zpool(libzfs_handle_t *zfshdl, const char *pool, const char *file)
+{
+ nvlist_t *nvroot, *props;
+ nvlist_t *vdevs[1];
+
+ nvroot = fnvlist_alloc();
+ props = fnvlist_alloc();
+ vdevs[0] = fnvlist_alloc();
+
+ fnvlist_add_string(vdevs[0], ZPOOL_CONFIG_PATH, file);
+ fnvlist_add_string(vdevs[0], ZPOOL_CONFIG_TYPE, VDEV_TYPE_FILE);
+ fnvlist_add_uint64(vdevs[0], ZPOOL_CONFIG_IS_LOG, 0);
+
+ fnvlist_add_string(nvroot, ZPOOL_CONFIG_TYPE, VDEV_TYPE_ROOT);
+ fnvlist_add_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN, vdevs, 1);
+
+ fnvlist_add_string(props,
+ zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), ZFS_MOUNTPOINT_NONE);
+
+ if (zpool_create(zfshdl, pool, nvroot, NULL, props) != 0) {
+ FAIL(("Failed to create ZPOOL %s using %s - %s",
+ pool, file, libzfs_error_description(zfshdl)));
+ }
+
+ VERBOSE(("Created ZFS pool %s", pool));
+}
+
+static bool
+create_zvol(libzfs_handle_t *zfshdl, const char *vol)
+{
+ nvlist_t *volprops;
+ int err;
+
+ volprops = fnvlist_alloc();
+ fnvlist_add_uint64(volprops,
+ zfs_prop_to_name(ZFS_PROP_VOLSIZE), 1 * MB);
+
+ err = zfs_create(zfshdl, vol, ZFS_TYPE_VOLUME, volprops);
+ if (err != 0) {
+ (void) printf("Failed to create ZVOL %s - %s",
+ vol, libzfs_error_description(zfshdl));
+ return (false);
+ }
+
+ VERBOSE(("Created ZVOL %s", vol));
+ return (true);
+}
+
+int
+main(int argc, const char **argv)
+{
+ libzfs_handle_t *zfshdl;
+ char *template, *pool, *vol, *backend;
+ struct mevent *evp;
+ zpool_handle_t *poolhdl = NULL;
+ zfs_handle_t *volhdl = NULL;
+ int err, fd;
+
+ start_test(argv[0], 10);
+ set_mevent_file_poll_interval_ms(1000);
+
+ if (getzoneid() != GLOBAL_ZONEID)
+ FAIL(("Can only be run in the global zone"));
+
+ if ((zfshdl = libzfs_init()) == NULL)
+ FAIL_ERRNO("Could not open ZFS library");
+
+ template = strdup("/tmp/mevent.vnode.zvol.XXXXXX");
+ ASSERT_PTR_NEQ(("strdup"), template, NULL);
+ fd = mkstemp(template);
+ if (fd == -1)
+ FAIL_ERRNO("Couldn't create temporary file with mkstemp");
+ VERBOSE(("Opened temporary file at '%s'", template));
+
+ err = asprintf(&pool, "mevent_test_%d", getpid());
+ ASSERT_INT_NEQ(("asprintf pool"), err, -1);
+
+ err = asprintf(&vol, "%s/test_zvol_%d", pool, getpid());
+ ASSERT_INT_NEQ(("asprintf vol"), err, -1);
+
+ err = asprintf(&backend, "/dev/zvol/rdsk/%s", vol);
+ ASSERT_INT_NEQ(("asprintf backend"), err, -1);
+
+ err = ftruncate(fd, 64 * MB);
+ if (err != 0)
+ FAIL_ERRNO("ftruncate");
+ (void) close(fd);
+ fd = -1;
+
+ /*
+ * Create the pool as late as possible to reduce the risk of leaving
+ * a test pool hanging around.
+ */
+ create_zpool(zfshdl, pool, template);
+
+ if ((poolhdl = zpool_open(zfshdl, pool)) == NULL) {
+ (void) printf("Could not open ZPOOL - %s\n",
+ libzfs_error_description(zfshdl));
+ err = EXIT_FAIL;
+ goto out;
+ }
+
+ if (!create_zvol(zfshdl, vol)) {
+ err = EXIT_FAIL;
+ goto out;
+ }
+
+ if ((volhdl = zfs_open(zfshdl, vol, ZFS_TYPE_VOLUME)) == NULL) {
+ (void) printf("Could not open ZFS volume - %s\n",
+ libzfs_error_description(zfshdl));
+ err = EXIT_FAIL;
+ goto out;
+ }
+
+ if ((fd = open(backend, O_RDWR)) == -1) {
+ (void) printf("Failed to open '%s': %s\n",
+ backend, strerror(errno));
+ err = EXIT_FAIL;
+ goto out;
+ }
+ VERBOSE(("Opened backend %s", backend));
+
+ start_event_thread();
+
+ evp = mevent_add_flags(fd, EVF_VNODE, EVFF_ATTRIB, callback, cookie);
+ if (evp == NULL) {
+ (void) printf("mevent_add returned NULL\n");
+ err = EXIT_FAIL;
+ goto out;
+ }
+
+ for (uint_t i = 2; i < 4; i++) {
+ ssize_t written;
+ char buf[64];
+
+ /*
+ * Check that a write to the volume does not trigger an event.
+ */
+ if (lseek(fd, 0, SEEK_SET) == -1)
+ FAIL_ERRNO("lseek");
+ written = write(fd, cookie, strlen(cookie));
+ if (written < 0)
+ FAIL_ERRNO("bad write");
+ ASSERT_INT64_EQ(("write cookie", i), written, strlen(cookie));
+
+ (void) snprintf(buf, sizeof (buf), "%llu", i * MB);
+ VERBOSE(("Setting volsize to %s", buf));
+
+ if (zfs_prop_set(volhdl,
+ zfs_prop_to_name(ZFS_PROP_VOLSIZE), buf) != 0) {
+ (void) printf("Failed to increase ZFS volume size\n");
+ pthread_mutex_unlock(&mtx);
+ err = EXIT_FAIL;
+ goto out;
+ }
+
+ /* Wait for the size change to be processed */
+ pthread_mutex_lock(&mtx);
+ pthread_cond_wait(&cv, &mtx);
+ pthread_mutex_unlock(&mtx);
+ }
+
+ (void) mevent_disable(evp);
+
+ err = EXIT_PASS;
+
+out:
+
+ (void) close(fd);
+ destroy_zpool(zfshdl, poolhdl, volhdl);
+ (void) libzfs_fini(zfshdl);
+ (void) unlink(template);
+
+ if (err == EXIT_PASS)
+ PASS();
+
+ exit(err);
+}
diff --git a/usr/src/pkg/manifests/system-bhyve-tests.p5m b/usr/src/pkg/manifests/system-bhyve-tests.p5m
index 74881cf19c..5b4a7351c4 100644
--- a/usr/src/pkg/manifests/system-bhyve-tests.p5m
+++ b/usr/src/pkg/manifests/system-bhyve-tests.p5m
@@ -14,7 +14,7 @@
#
#
-# Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
+# Copyright 2022 OmniOS Community Edition (OmniOSce) Association.
# Copyright 2022 Oxide Computer Company
#
@@ -35,6 +35,8 @@ file path=opt/bhyve-tests/tests/mevent/lists_delete mode=0555
file path=opt/bhyve-tests/tests/mevent/read_disable mode=0555
file path=opt/bhyve-tests/tests/mevent/read_pause mode=0555
file path=opt/bhyve-tests/tests/mevent/read_requeue mode=0555
+file path=opt/bhyve-tests/tests/mevent/vnode_file mode=0555
+file path=opt/bhyve-tests/tests/mevent/vnode_zvol mode=0555
dir path=opt/bhyve-tests/tests/vmm
file path=opt/bhyve-tests/tests/vmm/mem_partial mode=0555
file path=opt/bhyve-tests/tests/vmm/mem_seg_map mode=0555
diff --git a/usr/src/test/bhyve-tests/runfiles/default.run b/usr/src/test/bhyve-tests/runfiles/default.run
index 51afd4581a..babfa0f7e9 100644
--- a/usr/src/test/bhyve-tests/runfiles/default.run
+++ b/usr/src/test/bhyve-tests/runfiles/default.run
@@ -24,4 +24,9 @@ tests = ['mem_partial', 'mem_seg_map']
# Tests of userspace mevent system, built from cmd/bhyve
[/opt/bhyve-tests/tests/mevent]
-tests = ['lists_delete', 'read_disable', 'read_pause', 'read_requeue']
+tests = ['lists_delete', 'read_disable', 'read_pause', 'read_requeue',
+ 'vnode_file']
+
+[/opt/bhyve-tests/tests/mevent/vnode_zvol]
+user = root
+