/*- * Copyright (c) 2011 Advanced Computing Technologies LLC * Written by: John H. Baldwin * 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 THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR 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. */ #include __FBSDID("$FreeBSD$"); /* * Test application for /dev/echobuf and /dev/echostream demo drivers. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "echodev.h" struct poll_args { struct pollfd *pfd; int retval; }; static const char test_string [] = "0123456789abcdefghijklmnopqrstuvwxyz" "!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static int kq; #define START_POLL(pollfd) do { \ struct poll_args pa; \ pthread_t thread; \ int error; \ \ pa.pfd = (pollfd); \ error = pthread_create(&thread, NULL, polling_thread, &pa); \ if (error != 0) \ errc(1, error, "pthread_create"); \ usleep(25 * 1000) #define END_POLL(rval) \ error = pthread_join(thread, NULL); \ if (error != 0) \ errc(1, error, "pthread_join"); \ (rval) = pa.retval; \ } while (0) static void * polling_thread(void *arg) { struct poll_args *pa; pa = arg; pa->retval = poll(pa->pfd, 1, 1000); if (pa->retval < 0) err(1, "poll"); return (NULL); } static void echodev_reset(void) { size_t len; int fd; fd = open("/dev/echobuf", O_RDWR); if (fd < 0) err(1, "open(\"/dev/echobuf\")"); len = 0; if (ioctl(fd, ECHODEV_SBUFSIZE, &len) < 0) err(1, "ioctl(ECHODEV_SBUFSIZE)"); close(fd); fd = open("/dev/echostream", O_RDWR); if (fd < 0) err(1, "open(\"/dev/echostream\")"); if (ioctl(fd, ECHODEV_CLEAR) < 0) err(1, "ioctl(ECHODEV_CLEAR)"); close(fd); } static void echobuf_tests(void) { char buf[32]; struct kevent ev[2]; struct timespec timeout; struct pollfd pfd; size_t len; ssize_t retval; int fd; fd = open("/dev/echobuf", O_RDWR); if (fd < 0) err(1, "open(\"/dev/echobuf\")"); /* Should have initial size of zero. */ if (ioctl(fd, ECHODEV_GBUFSIZE, &len) < 0) err(1, "ioctl(ECHODEV_GBUFSIZE)"); assert(len == 0); /* Should not be readable or writable yet. */ pfd.fd = fd; pfd.events = POLLIN | POLLOUT; retval = poll(&pfd, 1, 0); if (retval < 0) err(1, "poll"); assert(retval == 0); EV_SET(&ev[0], fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, 0); EV_SET(&ev[1], fd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, 0); if (kevent(kq, ev, 2, NULL, 0, NULL) < 0) err(1, "kevent(EV_ADD)"); bzero(&timeout, sizeof(timeout)); bzero(&ev, sizeof(ev)); retval = kevent(kq, NULL, 0, ev, 2, &timeout); assert(retval == 0); /* Resize to 64 bytes. */ len = 64; if (ioctl(fd, ECHODEV_SBUFSIZE, &len) < 0) err(1, "ioctl(ECHODEV_SBUFSIZE)"); if (ioctl(fd, ECHODEV_GBUFSIZE, &len) < 0) err(1, "ioctl(ECHODEV_GBUFSIZE)"); assert(len == 64); /* Should be readable and writable now. */ pfd.fd = fd; pfd.events = POLLIN | POLLOUT; pfd.revents = 0; retval = poll(&pfd, 1, 0); if (retval < 0) err(1, "poll"); assert(retval == 1); assert(pfd.revents == (POLLIN | POLLOUT)); retval = kevent(kq, NULL, 0, ev, 2, &timeout); assert(retval == 2); assert(ev[0].ident == fd); assert(ev[0].filter == EVFILT_READ || ev[0].filter == EVFILT_WRITE); assert(ev[0].data == len); assert(ev[1].ident == fd); assert(ev[1].filter == (ev[0].filter == EVFILT_READ) ? EVFILT_WRITE : EVFILT_READ); assert(ev[1].data == len); /* Write some sample data. Should only write 64 bytes. */ retval = write(fd, test_string, sizeof(test_string) - 1); if (retval < 0) err(1, "write"); assert(retval == len); /* Read 32 bytes from the middle. */ retval = pread(fd, buf, sizeof(buf), 16); if (retval < 0) err(1, "pread"); assert(retval == sizeof(buf)); assert(memcmp(buf, test_string + 16, sizeof(buf)) == 0); /* * Trim the buffer back to zero and check that a thread stuck * in poll() awakens when the size increases. */ len = 0; if (ioctl(fd, ECHODEV_SBUFSIZE, &len) < 0) err(1, "ioctl(ECHODEV_SBUFSIZE)"); if (ioctl(fd, ECHODEV_GBUFSIZE, &len) < 0) err(1, "ioctl(ECHODEV_GBUFSIZE)"); assert(len == 0); pfd.fd = fd; pfd.events = POLLIN | POLLOUT; pfd.revents = 0; START_POLL(&pfd); len = 64; if (ioctl(fd, ECHODEV_SBUFSIZE, &len) < 0) err(1, "ioctl(ECHODEV_SBUFSIZE)"); END_POLL(retval); assert(retval == 1); assert(pfd.revents == (POLLIN | POLLOUT)); close(fd); printf("echobuf tests passed\n"); } static void handler(int sig) { } static void alarm_ms(int msec) { struct itimerval it; bzero(&it, sizeof(it)); it.it_value.tv_sec = msec / 1000; it.it_value.tv_usec = (msec % 1000) * 1000; if (setitimer(ITIMER_REAL, &it, NULL) < 0) err(1, "setitimer(ITIMER_REAL)"); } static void set_nonblock(int fd, int state) { int flags; flags = fcntl(fd, F_GETFL); if (flags == -1) err(1, "fcntl(F_GETFL)"); if (state) flags |= O_NONBLOCK; else flags &= ~O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) < 0) err(1, "fcntl(F_SETFL)"); } static void echostream_tests(void) { char buf[32]; struct kevent ev[2]; struct timespec timeout; struct pollfd pfd; ssize_t retval; int fd, len; fd = open("/dev/echostream", O_RDWR); if (fd < 0) err(1, "open(\"/dev/echostream\")"); /* Should not be readable yet, but should be writable. */ pfd.fd = fd; pfd.events = POLLIN | POLLOUT; retval = poll(&pfd, 1, 0); if (retval < 0) err(1, "poll"); assert(retval == 1); assert(pfd.revents == POLLOUT); EV_SET(&ev[0], fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, 0); EV_SET(&ev[1], fd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, 0); if (kevent(kq, ev, 2, NULL, 0, NULL) < 0) err(1, "kevent(EV_ADD)"); bzero(&timeout, sizeof(timeout)); bzero(&ev, sizeof(ev)); retval = kevent(kq, NULL, 0, ev, 2, &timeout); assert(retval == 1); assert(ev[0].ident == fd); assert(ev[0].filter == EVFILT_WRITE); /* Blocking read() should fail with EINTR. */ alarm_ms(25); retval = read(fd, buf, sizeof(buf)); assert(retval == -1 && errno == EINTR); /* Non-blocking read() should fail with EWOULDBLOCK. */ set_nonblock(fd, 1); alarm_ms(25); retval = read(fd, buf, sizeof(buf)); assert(retval == -1 && errno == EWOULDBLOCK); alarm_ms(0); /* Should be no space to read. */ if (ioctl(fd, FIONREAD, &len) < 0) err(1, "ioctl(FIONREAD)"); assert(len == 0); /* Find available write buffer space. */ if (ioctl(fd, FIONWRITE, &len) < 0) err(1, "ioctl(FIONWRITE)"); assert(len > sizeof(buf)); assert(len == ev[0].data); /* Write out some data. */ retval = write(fd, test_string, sizeof(buf)); assert(retval == sizeof(buf)); /* Should now be readable. */ pfd.fd = fd; pfd.events = POLLIN; pfd.revents = 0; retval = poll(&pfd, 1, 0); if (retval < 0) err(1, "poll"); assert(retval == 1); assert(pfd.revents == POLLIN); if (ioctl(fd, FIONREAD, &len) < 0) err(1, "ioctl(FIONREAD)"); assert(len == sizeof(buf)); retval = kevent(kq, NULL, 0, ev, 2, &timeout); assert(retval == 1); assert(ev[0].ident == fd); assert(ev[0].filter == EVFILT_READ); assert(ev[0].data == sizeof(buf)); /* Read back the data. */ retval = read(fd, buf, sizeof(buf)); assert(retval == sizeof(buf)); assert(memcmp(buf, test_string, sizeof(buf)) == 0); /* Fill the write buffer. */ for (;;) { if (ioctl(fd, FIONWRITE, &len) < 0) err(1, "ioctl(FIONWRITE)"); if (len == 0) break; len = MIN(len, sizeof(test_string) - 1); retval = write(fd, test_string, len); assert(retval == len); } /* Should no longer be writable. */ pfd.fd = fd; pfd.events = POLLOUT; pfd.revents = 0; retval = poll(&pfd, 1, 0); if (retval < 0) err(1, "poll"); assert(retval == 0); retval = kevent(kq, NULL, 0, ev, 2, &timeout); assert(retval == 1); assert(ev[0].ident == fd); assert(ev[0].filter == EVFILT_READ); assert(ev[0].data > 0); /* * Check that a thread stuck in poll(POLLOUT) awakens when * some data is read. */ pfd.fd = fd; pfd.events = POLLOUT; pfd.revents = 0; START_POLL(&pfd); retval = read(fd, buf, 1); assert(retval == 1); END_POLL(retval); assert(retval == 1); assert(pfd.revents == POLLOUT); /* * Fill it back up again and check that poll(POLLOUT) awakens * when the buffer is cleared. */ retval = write(fd, test_string, 1); assert(retval == 1); pfd.fd = fd; pfd.events = POLLOUT; pfd.revents = 0; START_POLL(&pfd); if (ioctl(fd, ECHODEV_CLEAR) < 0) err(1, "ioctl(ECHODEV_CLEAR)"); END_POLL(retval); assert(retval == 1); assert(pfd.revents == POLLOUT); /* * Check that a thread stuck in poll(POLLING) awakens when * some data is written. */ pfd.fd = fd; pfd.events = POLLIN; pfd.revents = 0; START_POLL(&pfd); retval = write(fd, test_string, 1); assert(retval == 1); END_POLL(retval); assert(retval == 1); assert(pfd.revents == POLLIN); close(fd); printf("echostream tests passed\n"); } int main(int ac, char **av) { if (siginterrupt(SIGALRM, 1) < 0) err(1, "siginterrupt(SIGALRM)"); if (signal(SIGALRM, handler) == SIG_ERR) err(1, "signal(SIGALRM)"); kq = kqueue(); if (kq < 0) err(1, "kqueue"); echodev_reset(); echobuf_tests(); echostream_tests(); close(kq); return (0); }