# # 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. # SUBDIRS = mevent include ../Makefile.subdirs # # 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 Oxide Computer Company # Copyright 2022 OmniOS Community Edition (OmniOSce) Association. # TESTSUBDIR = mevent PROG = \ lists_delete \ mevent_test \ read_disable \ read_pause \ read_requeue \ vnode_file \ vnode_zvol SUPOBJS = mevent.o testlib.o include ../../Makefile.com CMDS = $(PROG:%=$(TESTDIR)/%) $(CMDS) : FILEMODE = 0555 all: $(PROG) install: $(TESTDIR) $(CMDS) $(CMDS): $(PROG) vnode_zvol : LDLIBS += -lzfs -lnvpair mevent_test : LDLIBS += -lsocket include ../../Makefile.targ # Hammerhead: Cancel GNU Make built-in compile-and-link rule (%: %.c) %: %.c %: %.o $(SUPOBJS) $(LINK.c) -o $@ $< $(SUPOBJS) $(LDLIBS) $(POST_PROCESS) mevent.o: ../../../common/mevent.c /* * 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. */ /* * Test: lists.delete * Assertion: mevent_delete() causes the total number of events to decrease * * Strategy: 1. Create a pipe. * 2. Call mevent_add() to be notified of writes to the pipe. The * callback will do nothing other than generate an error if it * is called. * 3. Create another pipe and add a read event watcher to it. The * callback will signal a cv when called. A write to the pipe * followed by a wait on the cv will ensure that async * operations in mevent.c are complete. See flush_and_wait(). * 4. Call flush_and_wait(), then get event count. * 5. Delete the event created in step 2. * 6. Call flush_and_wait(), then get event count. * 7. Verify result in step 6 is one less than result in step 4. */ #include #include #include #include #include #include #include #include #include #include #include "testlib.h" #include "mevent.h" static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cv = PTHREAD_COND_INITIALIZER; static int get_count(void) { int global = -1, change = -1, del_pending = -1; int total; test_mevent_count_lists(&global, &change, &del_pending); ASSERT_INT_NEQ(("count not set"), global, -1); ASSERT_INT_NEQ(("count not set"), change, -1); ASSERT_INT_NEQ(("count not set"), change, -1); ASSERT_INT_EQ(("pending delete not processed"), del_pending, 0); total = global + change + del_pending; VERBOSE(("count = %d (%d + %d + %d)", total, global, change, del_pending)); return (total); } static void not_called_cb(int fd, enum ev_type ev, void *arg) { FAIL(("this callback should never be called")); } static void flush_cb(int fd, enum ev_type ev, void *arg) { char buf[32]; /* Drain the pipe */ while (read(fd, buf, sizeof (buf)) > 0) ; pthread_mutex_lock(&mtx); pthread_cond_signal(&cv); pthread_mutex_unlock(&mtx); } void flush_and_wait(int fd) { uint8_t msg = 42; /* * Lock taken ahead of waking flush_cb so this thread doesn't race * with the event thread. */ pthread_mutex_lock(&mtx); if (write(fd, &msg, sizeof (msg)) != sizeof (msg)) { FAIL(("bad write")); } /* Wait for it to be read */ pthread_cond_wait(&cv, &mtx); pthread_mutex_unlock(&mtx); } int main(int argc, const char *argv[]) { int unused_pipe[2]; int flush_pipe[2]; struct mevent *unused_evp, *flush_evp; int count1, count2; start_test(argv[0], 5); start_event_thread(); /* * Create first pipe and related event */ if (pipe(unused_pipe) != 0) { FAIL_ERRNO("pipe"); } VERBOSE(("unused_pipe[] = { %d, %d }", unused_pipe[0], unused_pipe[1])); if (fcntl(unused_pipe[0], F_SETFL, O_NONBLOCK) != 0) { FAIL_ERRNO("set pipe nonblocking"); } unused_evp = mevent_add(unused_pipe[0], EVF_READ, not_called_cb, NULL); ASSERT_PTR_NEQ(("mevent_add"), unused_evp, NULL); /* * Create flush pipe and related event */ if (pipe(flush_pipe) != 0) { FAIL_ERRNO("pipe"); } VERBOSE(("flush_pipe[] = { %d, %d }", flush_pipe[0], flush_pipe[1])); if (fcntl(flush_pipe[0], F_SETFL, O_NONBLOCK) != 0) { FAIL_ERRNO("set pipe nonblocking"); } flush_evp = mevent_add(flush_pipe[0], EVF_READ, flush_cb, NULL); ASSERT_PTR_NEQ(("mevent_add"), flush_evp, NULL); /* Get count before delete. */ flush_and_wait(flush_pipe[1]); count1 = get_count(); /* * Delete the first event and flush a read after the delete is * complete. */ if (mevent_delete(unused_evp) != 0) { FAIL_ERRNO("mevent_delete"); } /* * Verify count decreased. */ flush_and_wait(flush_pipe[1]); count2 = get_count(); if (count1 - 1 != count2) { FAIL(("mevent_delete() did not decrease count by 1: " "was %d, now %d", count1, count2)); } PASS(); } /* * 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. */ #include "../../../common/mevent.c" #include "testlib.h" /* * Returns by reference the number of events on the global and change lists. * * Used by tests that wish to ensure that the event count changes as suggested * by mevent_add() and mevent_delete(). Note that a delete does not immediately * delete an event. Events that are pending delete are included in the change * list until the next pass through the change list to process pending changes. */ void test_mevent_count_lists(int *ret_global, int *ret_change, int *ret_del_pending) { struct mevent *mevp; int global = 0; int change = 0; int del_pending = 0; mevent_qlock(); LIST_FOREACH(mevp, &global_head, me_list) { global++; VERBOSE(("on global: type %d fd %d state %d", mevp->me_type, mevp->me_fd, mevp->me_state)); } LIST_FOREACH(mevp, &change_head, me_list) { change++; if (mevp->me_state == EV_DELETE) { del_pending++; } VERBOSE(("on change: type %d fd %d state %d", mevp->me_type, mevp->me_fd, mevp->me_state)); } mevent_qunlock(); *ret_global = global; *ret_change = change; *ret_del_pending = del_pending; } void set_mevent_file_poll_interval_ms(int ms) { mevent_file_poll_interval_ms = ms; } /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 NetApp, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Copyright 2018 Joyent, Inc. */ /* * Test program for the micro event library. Set up a simple TCP echo * service. * * cc mevent_test.c mevent.c -lpthread */ #include #include #ifdef __FreeBSD__ #include #endif #include #include #ifdef __FreeBSD__ #include #endif #include #include #include #include #include "mevent.h" #define TEST_PORT 4321 static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t accept_condvar = PTHREAD_COND_INITIALIZER; static struct mevent *tevp; #define MEVENT_ECHO /* Number of timer events to capture */ #define TEVSZ 4096 uint64_t tevbuf[TEVSZ]; static void timer_print(void) { uint64_t min, max, diff, sum; #ifdef __FreeBSD__ uint64_t tsc_freq; size_t len; #endif int j; min = UINT64_MAX; max = 0; sum = 0; #ifdef __FreeBSD__ len = sizeof(tsc_freq); sysctlbyname("machdep.tsc_freq", &tsc_freq, &len, NULL, 0); #endif for (j = 1; j < TEVSZ; j++) { #ifdef __FreeBSD__ /* Convert a tsc diff into microseconds */ diff = (tevbuf[j] - tevbuf[j-1]) * 1000000 / tsc_freq; #else diff = (tevbuf[j] - tevbuf[j-1]) / 1000; #endif sum += diff; if (min > diff) min = diff; if (max < diff) max = diff; } printf("timers done: usecs, min %ld, max %ld, mean %ld\n", min, max, sum/(TEVSZ - 1)); } static void timer_callback(int fd, enum ev_type type, void *param) { static int i; if (i >= TEVSZ) abort(); #ifdef __FreeBSD__ tevbuf[i++] = rdtsc(); #else tevbuf[i++] = gethrtime(); #endif if (i == TEVSZ) { mevent_delete(tevp); timer_print(); } } #ifdef MEVENT_ECHO struct esync { pthread_mutex_t e_mt; pthread_cond_t e_cond; }; static void echoer_callback(int fd, enum ev_type type, void *param) { struct esync *sync = param; pthread_mutex_lock(&sync->e_mt); pthread_cond_signal(&sync->e_cond); pthread_mutex_unlock(&sync->e_mt); } static void * echoer(void *param) { struct esync sync; struct mevent *mev; char buf[128]; int fd = (int)(uintptr_t) param; int len; pthread_mutex_init(&sync.e_mt, NULL); pthread_cond_init(&sync.e_cond, NULL); pthread_mutex_lock(&sync.e_mt); mev = mevent_add(fd, EVF_READ, echoer_callback, &sync); if (mev == NULL) { printf("Could not allocate echoer event\n"); exit(4); } while (!pthread_cond_wait(&sync.e_cond, &sync.e_mt)) { len = read(fd, buf, sizeof(buf)); if (len > 0) { write(fd, buf, len); write(0, buf, len); } else { break; } } mevent_delete_close(mev); pthread_mutex_unlock(&sync.e_mt); pthread_mutex_destroy(&sync.e_mt); pthread_cond_destroy(&sync.e_cond); return (NULL); } #else static void * echoer(void *param) { char buf[128]; int fd = (int)(uintptr_t) param; int len; while ((len = read(fd, buf, sizeof(buf))) > 0) { write(1, buf, len); } return (NULL); } #endif /* MEVENT_ECHO */ static void acceptor_callback(int fd, enum ev_type type, void *param) { pthread_mutex_lock(&accept_mutex); pthread_cond_signal(&accept_condvar); pthread_mutex_unlock(&accept_mutex); } static void * acceptor(void *param) { struct sockaddr_in sin; pthread_t tid; int news; int s; if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("cannot create socket"); exit(4); } #ifdef __FreeBSD__ sin.sin_len = sizeof(sin); #endif sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_ANY); sin.sin_port = htons(TEST_PORT); if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) { perror("cannot bind socket"); exit(4); } if (listen(s, 1) < 0) { perror("cannot listen socket"); exit(4); } (void) mevent_add(s, EVF_READ, acceptor_callback, NULL); pthread_mutex_lock(&accept_mutex); while (!pthread_cond_wait(&accept_condvar, &accept_mutex)) { news = accept(s, NULL, NULL); if (news < 0) { perror("accept error"); } else { static int first = 1; if (first) { /* * Start a timer */ first = 0; tevp = mevent_add(1, EVF_TIMER, timer_callback, NULL); } printf("incoming connection, spawning thread\n"); pthread_create(&tid, NULL, echoer, (void *)(uintptr_t)news); } } return (NULL); } int main() { pthread_t tid; pthread_create(&tid, NULL, acceptor, NULL); mevent_dispatch(); return (0); } /* * 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. */ /* * Test: read.cancel * Assertion: A read is not requeued if mevent_disable() is called while it is * being handled. * * Strategy: 1. Create a pipe * 2. Call mevent_add() to be notified of writes to the pipe. The * callback will signal a cv. * 3. Write to the pipe then wait for a wakeup. * 4. From the read event callback, disable the event then awaken * the main thread. * 5. In the main thread, add a timer event that will awaken the * main thread after a short delay. * 5. Write to the pipe and wait to be awoken. The wakeup should * come from the timer event, not the read event. */ #include #include #include #include #include #include #include #include #include #include #include "testlib.h" #include "mevent.h" typedef enum { CB_NONE, CB_READ, CB_TIMER, } lastwake_t; static lastwake_t lastwake = CB_NONE; static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cv = PTHREAD_COND_INITIALIZER; static struct mevent *read_event; static void munch(int fd, enum ev_type ev, void *arg) { ssize_t nbytes; char buf[32] = { 0 }; int err; if ((nbytes = read(fd, buf, sizeof (buf))) < 0) { FAIL_ERRNO("bad read"); } VERBOSE(("read %ld bytes '%s'", nbytes, buf)); err = mevent_disable(read_event); ASSERT_INT_EQ(("mevent_disable: ", strerror(err)), err, 0); pthread_mutex_lock(&mtx); ASSERT_INT_EQ(("wrong lastwake"), lastwake, CB_NONE); lastwake = CB_READ; pthread_cond_signal(&cv); VERBOSE(("wakeup")); pthread_mutex_unlock(&mtx); } static void tick(int ms, enum ev_type ev, void *arg) { pthread_mutex_lock(&mtx); ASSERT_INT_EQ(("wrong lastwake"), lastwake, CB_READ); lastwake = CB_TIMER; pthread_cond_signal(&cv); VERBOSE(("wakeup")); pthread_mutex_unlock(&mtx); } int main(int argc, const char *argv[]) { int pipefds[2]; struct mevent *timer; ssize_t written; char *msgs[] = { "first", "second" }; char *msg; start_test(argv[0], 5); start_event_thread(); if (pipe(pipefds) != 0) { FAIL_ERRNO("pipe"); } if (fcntl(pipefds[0], F_SETFL, O_NONBLOCK) != 0) { FAIL_ERRNO("set pipe nonblocking"); } /* * First write */ msg = msgs[0]; read_event = mevent_add(pipefds[0], EVF_READ, munch, msg); ASSERT_PTR_NEQ(("mevent_add pipefd"), read_event, NULL); pthread_mutex_lock(&mtx); written = write(pipefds[1], msg, strlen(msg)); if (written < 0) { FAIL_ERRNO("bad write"); } ASSERT_INT64_EQ(("write '%s' failed", msg), written, strlen(msg)); /* * Wait for it to be read */ pthread_cond_wait(&cv, &mtx); ASSERT_INT_EQ(("wrong lastwake"), lastwake, CB_READ); pthread_mutex_unlock(&mtx); /* * Add timer, second write. */ msg = msgs[1]; timer = mevent_add(50, EVF_TIMER, tick, msg); ASSERT_PTR_NEQ(("mevent_add timer"), timer, NULL); pthread_mutex_lock(&mtx); written = write(pipefds[1], msg, strlen(msg)); if (written < 0) { FAIL_ERRNO("bad write"); } ASSERT_INT64_EQ(("write '%s' failed", msg), written, strlen(msg)); /* * Wait for timer to expire */ pthread_cond_wait(&cv, &mtx); ASSERT_INT_EQ(("wrong lastwake"), lastwake, CB_TIMER); pthread_mutex_unlock(&mtx); PASS(); } /* * 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. */ /* * Test: read.pause * Assertion: mevent_disable() can be used to pause reads. * * Strategy: 1. Create a pipe * 2. Call mevent_add() to be notified of writes to the pipe. The * callback will signal a cv. * 3. In a loop, write to the pipe then wait on the cv. */ #include #include #include #include #include #include #include #include #include #include #include "testlib.h" #include "mevent.h" static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cv = PTHREAD_COND_INITIALIZER; static char cookie[] = "Chocolate chip with fudge stripes"; /* * After this many bytes are sent, writes will get batched up, progress will be * made on the write side via an interval timer */ const int pauseat = 8; static void munch(int fd, enum ev_type ev, void *arg) { static int i = 0; char buf[sizeof (cookie)] = { 0 }; ssize_t nbytes; ssize_t expected; ASSERT_INT_EQ(("bad event"), ev, EVF_READ); ASSERT_PTR_EQ(("bad cookie"), arg, cookie); /* * For the first while, expect data to come a byte at a time. After the * pause, we should get a burst with the rest of the data. */ if (i > pauseat) { expected = strlen(cookie) - pauseat - 1; } else { expected = 1; } if ((nbytes = read(fd, buf, sizeof (buf))) < 0) { FAIL_ERRNO("bad read"); } VERBOSE(("read %ld bytes '%s'", nbytes, buf)); ASSERT_INT64_EQ(("wanted a byte of cookie"), nbytes, expected); if (expected == 1) { ASSERT_CHAR_EQ(("bad byte %d of cookie", i), buf[0], cookie[i]); } else { ASSERT_STR_EQ(("bad last half of cookie"), buf, &cookie[i]); } pthread_mutex_lock(&mtx); pthread_cond_signal(&cv); VERBOSE(("wakeup")); pthread_mutex_unlock(&mtx); i++; } static void tick(int ms, enum ev_type ev, void *arg) { pthread_mutex_lock(&mtx); pthread_cond_signal(&cv); VERBOSE(("wakeup")); pthread_mutex_unlock(&mtx); } int main(int argc, const char *argv[]) { int pipefds[2]; struct mevent *evp, *timer; ssize_t written; start_test(argv[0], 5); start_event_thread(); if (pipe(pipefds) != 0) { FAIL_ERRNO("pipe"); } if (fcntl(pipefds[0], F_SETFL, O_NONBLOCK) != 0) { FAIL_ERRNO("set pipe nonblocking"); } evp = mevent_add(pipefds[0], EVF_READ, munch, cookie); ASSERT_PTR_NEQ(("mevent_add pipefd"), evp, NULL); for (int i = 0; cookie[i] != 0; i++) { pthread_mutex_lock(&mtx); written = write(pipefds[1], cookie + i, 1); if (written < 0) { FAIL_ERRNO("bad write"); } ASSERT_INT64_EQ(("write byte %d of cookie", i), written, 1); /* Wait for it to be read */ pthread_cond_wait(&cv, &mtx); pthread_mutex_unlock(&mtx); if (i == pauseat) { timer = mevent_add(10, EVF_TIMER, tick, &cookie[pauseat]); ASSERT_PTR_NEQ(("mevent_add timer"), timer, NULL); VERBOSE(("disable munch")); mevent_disable(evp); } } pthread_mutex_lock(&mtx); mevent_enable(evp); pthread_cond_wait(&cv, &mtx); pthread_mutex_unlock(&mtx); PASS(); } /* * 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. */ /* * Test: read.requeue * Assertion: A sequence of writes turns into a sequence of events. * * Strategy: 1. Create a pipe * 2. Call mevent_add() to be notified of writes to the pipe. The * callback will signal a cv. * 3. In a loop, write to the pipe then wait on the cv. */ #include #include #include #include #include #include #include #include #include #include #include "testlib.h" #include "mevent.h" static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cv = PTHREAD_COND_INITIALIZER; static char *cookie = "Chocolate chip with fudge stripes"; static void munch(int fd, enum ev_type ev, void *arg) { static int i = 0; char buf[8] = { 0 }; ssize_t nbytes; ASSERT_INT_EQ(("bad event"), ev, EVF_READ); ASSERT_PTR_EQ(("bad cookie"), arg, cookie); if ((nbytes = read(fd, buf, sizeof (buf))) < 0) { ASSERT_INT64_EQ(("bad read: %s", strerror(errno)), nbytes, 1); } VERBOSE(("read %ld bytes '%s'", nbytes, buf)); ASSERT_INT64_EQ(("wanted a byte of cookie"), nbytes, 1); ASSERT_CHAR_EQ(("bad byte %d of cookie", i), buf[0], cookie[i]); pthread_mutex_lock(&mtx); pthread_cond_signal(&cv); VERBOSE(("wakeup")); pthread_mutex_unlock(&mtx); i++; } int main(int argc, const char *argv[]) { int pipefds[2]; struct mevent *evp; start_test(argv[0], 5); start_event_thread(); if (pipe(pipefds) != 0) { FAIL_ERRNO("pipe"); } if (fcntl(pipefds[0], F_SETFL, O_NONBLOCK) != 0) { FAIL_ERRNO("set pipe nonblocking"); } evp = mevent_add(pipefds[0], EVF_READ, munch, cookie); ASSERT_PTR_NEQ(("mevent_add"), evp, NULL); for (int i = 0; cookie[i] != '\0'; i++) { ssize_t written; pthread_mutex_lock(&mtx); written = write(pipefds[1], cookie + i, 1); if (written < 0) { FAIL_ERRNO("bad write"); } ASSERT_INT64_EQ(("write byte %d of cookie", i), written, 1); /* Wait for it to be read */ pthread_cond_wait(&cv, &mtx); pthread_mutex_unlock(&mtx); } PASS(); } /* * 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. */ #include #include #include #include #include "testlib.h" #include "mevent.h" const char *testlib_prog; boolean_t testlib_verbose; static void timed_out(int signo) { ASSERT_INT_EQ(("timeout signal"), signo, SIGALRM); FAIL(("Timed out")); } void start_test(const char *argv0, uint32_t timeout) { char *val; testlib_prog = strrchr(argv0, '/'); if (testlib_prog == NULL) { testlib_prog = argv0; } else { testlib_prog++; } testlib_verbose = ((val = getenv("TEST_VERBOSE")) != NULL) && val[0] != '\0'; signal(SIGALRM, timed_out); alarm(timeout); } /* ARGSUSED */ static void * event_thread(void *arg) { mevent_dispatch(); return (NULL); } void start_event_thread(void) { pthread_t tid; if (pthread_create(&tid, NULL, event_thread, NULL) != 0) { FAIL_ERRNO("pthread_create"); } } /* * 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. */ #ifndef _TESTLIB_H_ #define _TESTLIB_H_ #include #include #include #include #include #include #include #include #include "mevent.h" #define EXIT_PASS 0 #define EXIT_FAIL 1 #define VERBOSE(msg) \ if (testlib_verbose) { \ (void) printf("VERBOSE %s: %s:%d %s: ", testlib_prog, \ __FILE__, __LINE__, __func__); \ (void) printf msg; \ (void) printf("\n"); \ } #define FAIL_PROLOGUE() \ (void) printf("FAIL %s: %s:%d: ", testlib_prog, __FILE__, __LINE__) #define FAIL(msg) \ { \ FAIL_PROLOGUE(); \ (void) printf msg; \ (void) printf("\n"); \ exit(EXIT_FAIL); \ } #define FAIL_ERRNO(msg) FAIL((msg ": %s", strerror(errno))) #define PASS() \ { \ (void) printf("PASS %s\n", testlib_prog); \ exit(EXIT_PASS); \ } #define ASSERT_CMP(msg, got, cmp, exp, nfmt) \ if (!(got cmp exp)) { \ FAIL_PROLOGUE(); \ (void) printf msg; \ (void) printf(": %s=" nfmt " %s %s=" nfmt "\n", \ #got, got, #cmp, #exp, exp); \ exit(EXIT_FAIL); \ } #define ASSERT_CHAR_EQ(msg, got, exp) ASSERT_CMP(msg, got, ==, exp, "%c") #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") #define ASSERT_STR_EQ(msg, got, exp) \ if (strcmp(got, exp) != 0) { \ FAIL_PROLOGUE(); \ (void) printf msg; \ (void) printf(": %s='%s' != %s='%s'\n", \ #got, got, #exp, exp); \ exit(EXIT_FAIL); \ } extern const char *testlib_prog; 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_ */ /* * 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 #include #include #include #include #include #include #include #include #include #include "testlib.h" #include "mevent.h" static char *cookie = "Shortcake"; 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; 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"); /* * Allow time for the callback to fire if it is going * to. */ VERBOSE(("Write within")); usleep(100); } pthread_mutex_lock(&mtx); written = write(fd, cookie + i, 1); if (written < 0) FAIL_ERRNO("bad write"); ASSERT_INT64_EQ(("write byte %d of cookie", i), written, 1); VERBOSE(("Write extend")); /* 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(100); } 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) { int fd; start_test(argv[0], 20); set_mevent_file_poll_interval_ms(500); start_event_thread(); /* 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(); } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #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); }