diff options
author | ngie <ngie@FreeBSD.org> | 2015-05-31 02:15:22 +0000 |
---|---|---|
committer | ngie <ngie@FreeBSD.org> | 2015-05-31 02:15:22 +0000 |
commit | 012598429178440158305ff4f36cf89ca8809ac7 (patch) | |
tree | 03292d685503e8a34568319ceca85df18e9955c2 | |
parent | 9eaf1cb6f619da8a59a184b59f1a514873014220 (diff) | |
parent | a7a4e853c7e47e9528e55bd1819e8f8da7c8557a (diff) | |
download | FreeBSD-src-012598429178440158305ff4f36cf89ca8809ac7.zip FreeBSD-src-012598429178440158305ff4f36cf89ca8809ac7.tar.gz |
Readd fresh copies of the directories from head using svn copy
34 files changed, 7300 insertions, 0 deletions
diff --git a/tests/sys/fifo/Makefile b/tests/sys/fifo/Makefile new file mode 100644 index 0000000..602bf1e --- /dev/null +++ b/tests/sys/fifo/Makefile @@ -0,0 +1,13 @@ +# $FreeBSD$ + +TESTSDIR= ${TESTSBASE}/sys/fifo + +PLAIN_TESTS_C+= fifo_create +PLAIN_TESTS_C+= fifo_io +PLAIN_TESTS_C+= fifo_misc +PLAIN_TESTS_C+= fifo_open + +TEST_METADATA.fifo_create+= required_user="root" +TEST_METADATA.fifo_open+= required_user="root" + +.include <bsd.test.mk> diff --git a/tests/sys/fifo/fifo_create.c b/tests/sys/fifo/fifo_create.c new file mode 100644 index 0000000..2eb01e5 --- /dev/null +++ b/tests/sys/fifo/fifo_create.c @@ -0,0 +1,282 @@ +/*- + * Copyright (c) 2005-2008 Robert N. M. Watson + * 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. + * + * $FreeBSD$ + */ + +#include <sys/stat.h> + +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* + * Simple regression test for the creation and destruction of POSIX fifos in + * the file system name space. Using a specially created directory, create + * a fifo in it and check that the following properties are present, as + * specified in IEEE Std 1003.1, 2004 Edition: + * + * - When mkfifo() or mknod(S_IFIFO) is called, on success, a fifo is + * created. + * + * - On an error, no fifo is created. (XXX: Not tested) + * + * - The mode bits on the fifo are a product of combining the umask and + * requested mode. + * + * - The fifo's owner will be the processes effective user ID. (XXX: Not + * tested) + * + * - The fifo's group will be the parent directory's group or the effective + * group ID of the process. For historical reasons, BSD prefers the group + * ID of the process, so we will generate an error if it's not that. (XXX: + * Not tested) + * + * - The st_atime, st_ctime, st_mtime of the fifo will be set appropriately, + * and st_ctime and st_mtime on the directory will be updated. (XXX: We + * test they are updated, not correct) + * + * - EEXIST is returned if the named file already exists. + * + * In addition, we check that we can unlink the fifo, and that if we do, it + * disappears. + * + * This test must run as root in order to usefully frob the process + * credential to test permission parts. + */ + +/* + * All activity occurs within a temporary directory created early in the + * test. + */ +static char temp_dir[PATH_MAX]; + +static void __unused +atexit_temp_dir(void) +{ + + rmdir(temp_dir); +} + +/* + * Basic creation tests: verify that mkfifo(2) (or mknod(2)) creates a fifo, + * that the time stamps on the directory are updated, that if we try twice we + * get EEXIST, and that we can unlink it. + */ +static void +fifo_create_test(int use_mkfifo) +{ + struct stat old_dirsb, dirsb, fifosb; + const char *testname; + char path[] = "testfifo"; + int error; + + if (use_mkfifo) + testname = "mkfifo"; + else + testname = "mknod"; + + /* + * Sleep to make sure that the time stamp on the directory will be + * updated. + */ + if (stat(".", &old_dirsb) < 0) + err(-1, "basic_create_test: %s: stat: %s", testname, + temp_dir); + + sleep(2); + + if (use_mkfifo) { + if (mkfifo(path, 0600) < 0) + err(-1, "basic_create_test: %s: %s", testname, path); + } else { + if (mknod(path, S_IFIFO | 0600, 0) < 0) + err(-1, "basic_create_test: %s: %s", testname, path); + } + + if (stat(path, &fifosb) < 0) { + error = errno; + (void)unlink(path); + errno = error; + err(-1, "basic_create_test: %s: stat: %s", testname, path); + } + + if (!(S_ISFIFO(fifosb.st_mode))) { + (void)unlink(path); + errx(-1, "basic_create_test: %s produced non-fifo", + testname); + } + + if (use_mkfifo) { + if (mkfifo(path, 0600) == 0) + errx(-1, "basic_create_test: dup %s succeeded", + testname); + } else { + if (mknod(path, S_IFIFO | 0600, 0) == 0) + errx(-1, "basic_create_test: dup %s succeeded", + testname); + } + + if (errno != EEXIST) + err(-1, "basic_create_test: dup %s unexpected error", + testname); + + if (stat(".", &dirsb) < 0) { + error = errno; + (void)unlink(path); + errno = error; + err(-1, "basic_create_test: %s: stat: %s", testname, + temp_dir); + } + + if (old_dirsb.st_ctime == dirsb.st_ctime) { + (void)unlink(path); + errx(-1, "basic_create_test: %s: old_dirsb.st_ctime == " + "dirsb.st_ctime", testname); + } + + if (old_dirsb.st_mtime == dirsb.st_mtime) { + (void)unlink(path); + errx(-1, "basic_create_test: %s: old_dirsb.st_mtime == " + "dirsb.st_mtime", testname); + } + + if (unlink(path) < 0) + err(-1, "basic_create_test: %s: unlink: %s", testname, path); + + if (stat(path, &fifosb) == 0) + errx(-1, "basic_create_test: %s: unlink failed to unlink", + testname); + if (errno != ENOENT) + err(-1, "basic_create_test: %s: unlink unexpected error", + testname); +} + +/* + * Having determined that basic create/remove/etc functionality is present + * for fifos, now make sure that the umask, requested permissions, and + * resulting mode are handled properly. + */ +static const struct permission_test { + mode_t pt_umask; + mode_t pt_reqmode; + mode_t pt_mode; +} permission_test[] = { + {0000, 0, S_IFIFO}, + {0000, S_IRWXU, S_IFIFO | S_IRWXU}, + {0000, S_IRWXU | S_IRWXG | S_IRWXO, S_IFIFO | S_IRWXU | S_IRWXG | + S_IRWXO }, + {0077, S_IRWXU, S_IFIFO | S_IRWXU}, + {0077, S_IRWXU | S_IRWXG | S_IRWXO, S_IFIFO | S_IRWXU}, +}; +static const int permission_test_count = sizeof(permission_test) / + sizeof(struct permission_test); + +static void +fifo_permission_test(int use_mkfifo) +{ + const struct permission_test *ptp; + mode_t __unused old_umask; + char path[] = "testfifo"; + const char *testname; + struct stat sb; + int error, i; + + if (use_mkfifo) + testname = "mkfifo"; + else + testname = "mknod"; + + old_umask = umask(0022); + for (i = 0; i < permission_test_count; i++) { + ptp = &permission_test[i]; + + umask(ptp->pt_umask); + if (use_mkfifo) { + if (mkfifo(path, ptp->pt_reqmode) < 0) + err(-1, "fifo_permission_test: %s: %08o " + "%08o %08o\n", testname, ptp->pt_umask, + ptp->pt_reqmode, ptp->pt_mode); + } else { + if (mknod(path, S_IFIFO | ptp->pt_reqmode, 0) < 0) + err(-1, "fifo_permission_test: %s: %08o " + "%08o %08o\n", testname, ptp->pt_umask, + ptp->pt_reqmode, ptp->pt_mode); + } + + if (stat(path, &sb) < 0) { + error = errno; + (void)unlink(path); + errno = error; + err(-1, "fifo_permission_test: %s: %s", testname, + path); + } + + if (sb.st_mode != ptp->pt_mode) { + (void)unlink(path); + errx(-1, "fifo_permission_test: %s: %08o %08o %08o " + "got %08o", testname, ptp->pt_umask, + ptp->pt_reqmode, ptp->pt_mode, sb.st_mode); + } + + if (unlink(path) < 0) + err(-1, "fifo_permission_test: %s: unlink: %s", + testname, path); + } + umask(old_umask); +} + +int +main(void) +{ + int i; + + if (geteuid() != 0) + errx(-1, "must be run as root"); + + strcpy(temp_dir, "fifo_create.XXXXXXXXXXX"); + if (mkdtemp(temp_dir) == NULL) + err(-1, "mkdtemp"); + atexit(atexit_temp_dir); + + if (chdir(temp_dir) < 0) + err(-1, "chdir"); + + /* + * Run each test twice, once with mknod(2) and a second time with + * mkfifo(2). Historically, BSD has not allowed mknod(2) to be used + * to create fifos, but the Single UNIX Specification requires it. + */ + for (i = 0; i < 2; i++) { + fifo_create_test(i); + fifo_permission_test(i); + } + + return (0); +} diff --git a/tests/sys/fifo/fifo_io.c b/tests/sys/fifo/fifo_io.c new file mode 100644 index 0000000..93d4be7 --- /dev/null +++ b/tests/sys/fifo/fifo_io.c @@ -0,0 +1,1399 @@ +/*- + * Copyright (c) 2005 Robert N. M. Watson + * 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. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/event.h> +#include <sys/ioctl.h> +#include <sys/select.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <poll.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* + * Regression test to exercise POSIX fifo I/O. + * + * We test a number of aspect of behavior, including: + * + * - If there's no data to read, then for blocking fifos, we block, and for + * non-blocking, we return EAGAIN. + * + * - If we write ten bytes, ten bytes can be read, and they're the same + * bytes, in the same order. + * + * - If we write two batches of five bytes, we can read the same ten bytes in + * one read of ten bytes. + * + * - If we write ten bytes, we can read the same ten bytes in two reads of + * five bytes each. + * + * - If we over-fill a buffer (by writing 512k, which we take to be a large + * number above default buffer sizes), we block if there is no reader. + * + * - That once 512k (ish) is read from the other end, the blocked writer + * wakes up. + * + * - When a fifo is empty, poll, select, kqueue, and fionread report it is + * writable but not readable. + * + * - When a fifo has data in it, poll, select, and kqueue report that it is + * writable. + * + * - XXX: blocked reader semantics? + * + * - XXX: event behavior on remote close? + * + * Although behavior of O_RDWR isn't defined for fifos by POSIX, we expect + * "reasonable" behavior, and run some additional tests relating to event + * management on O_RDWR fifo descriptors. + */ + +#define KQUEUE_MAX_EVENT 8 + +/* + * All activity occurs within a temporary directory created early in the + * test. + */ +static char temp_dir[PATH_MAX]; + +static void __unused +atexit_temp_dir(void) +{ + + rmdir(temp_dir); +} + +static void +makefifo(const char *fifoname, const char *testname) +{ + + if (mkfifo(fifoname, 0700) < 0) + err(-1, "%s: makefifo: mkfifo: %s", testname, fifoname); +} + +static void +cleanfifo2(const char *fifoname, int fd1, int fd2) +{ + + if (fd1 != -1) + close(fd1); + if (fd2 != -1) + close(fd2); + (void)unlink(fifoname); +} + +static void +cleanfifo3(const char *fifoname, int fd1, int fd2, int fd3) +{ + + if (fd3 != -1) + close(fd3); + cleanfifo2(fifoname, fd1, fd2); +} + +/* + * Open two different file descriptors for a fifo: one read, one write. Do + * so using non-blocking opens in order to avoid deadlocking the process. + */ +static int +openfifo(const char *fifoname, int *reader_fdp, int *writer_fdp) +{ + int error, fd1, fd2; + + fd1 = open(fifoname, O_RDONLY | O_NONBLOCK); + if (fd1 < 0) + return (-1); + fd2 = open(fifoname, O_WRONLY | O_NONBLOCK); + if (fd2 < 0) { + error = errno; + close(fd1); + errno = error; + return (-1); + } + *reader_fdp = fd1; + *writer_fdp = fd2; + + return (0); +} + +/* + * Open one file descriptor for the fifo, supporting both read and write. + */ +static int +openfifo_rw(const char *fifoname, int *fdp) +{ + int fd; + + fd = open(fifoname, O_RDWR); + if (fd < 0) + return (-1); + *fdp = fd; + + return (0); +} + +static int +set_nonblocking(int fd, const char *testname) +{ + int flags; + + flags = fcntl(fd, F_GETFL); + if (flags < 0) { + warn("%s: fcntl(fd, F_GETFL)", testname); + return(-1); + } + + flags |= O_NONBLOCK; + + if (fcntl(fd, F_SETFL, flags) < 0) { + warn("%s: fcntl(fd, 0x%x)", testname, flags); + return (-1); + } + + return (0); +} + +static int +set_blocking(int fd, const char *testname) +{ + int flags; + + flags = fcntl(fd, F_GETFL); + if (flags < 0) { + warn("%s: fcntl(fd, F_GETFL)", testname); + return(-1); + } + + flags &= ~O_NONBLOCK; + + if (fcntl(fd, F_SETFL, flags) < 0) { + warn("%s: fcntl(fd, 0x%x)", testname, flags); + return (-1); + } + + return (0); +} + +/* + * Drain a file descriptor (fifo) of any readable data. Note: resets the + * blocking state. + */ +static int +drain_fd(int fd, const char *testname) +{ + ssize_t len; + u_char ch; + + if (set_nonblocking(fd, testname) < 0) + return (-1); + + while ((len = read(fd, &ch, sizeof(ch))) > 0); + if (len < 0) { + switch (errno) { + case EAGAIN: + return (0); + default: + warn("%s: drain_fd: read", testname); + return (-1); + } + } + warn("%s: drain_fd: read: returned 0 bytes", testname); + return (-1); +} + +/* + * Simple I/O test: write ten integers, and make sure we get back the same + * integers in the same order. This assumes a minimum fifo buffer > 10 + * bytes in order to not block and deadlock. + */ +static void +test_simpleio(void) +{ + int i, reader_fd, writer_fd; + u_char buffer[10]; + ssize_t len; + + makefifo("testfifo", __func__); + if (openfifo("testfifo", &reader_fd, &writer_fd) + < 0) { + warn("test_simpleio: openfifo: testfifo"); + cleanfifo2("testfifo", -1, -1); + exit(-1); + } + + for (i = 0; i < 10; i++) + buffer[i] = i; + + len = write(writer_fd, (char *)buffer, sizeof(buffer)); + if (len < 0) { + warn("test_simpleio: write"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (len != sizeof(buffer)) { + warnx("test_simplio: tried %zu but wrote %zd", sizeof(buffer), + len); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + len = read(reader_fd, (char *)buffer, sizeof(buffer)); + if (len < 0) { + warn("test_simpleio: read"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (len != sizeof(buffer)) { + warnx("test_simpleio: tried %zu but read %zd", sizeof(buffer), + len); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + for (i = 0; i < 10; i++) { + if (buffer[i] == i) + continue; + warnx("test_simpleio: write byte %d as 0x%02x, but read " + "0x%02x", i, i, buffer[i]); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + cleanfifo2("testfifo", reader_fd, writer_fd); +} + +static volatile int alarm_fired; +/* + * Non-destructive SIGALRM handler. + */ +static void +sigalarm(int signum __unused) +{ + + alarm_fired = 1; +} + +/* + * Wrapper function for write, which uses a timer to interrupt any blocking. + * Because we can't reliably detect EINTR for blocking I/O, we also track + * whether or not our timeout fired. + */ +static int __unused +timed_write(int fd, void *data, size_t len, ssize_t *written_lenp, + int timeout, int *timedoutp, const char *testname) +{ + struct sigaction act, oact; + ssize_t written_len; + int error; + + alarm_fired = 0; + bzero(&act, sizeof(oact)); + act.sa_handler = sigalarm; + if (sigaction(SIGALRM, &act, &oact) < 0) { + warn("%s: timed_write: sigaction", testname); + return (-1); + } + alarm(timeout); + written_len = write(fd, data, len); + error = errno; + alarm(0); + if (sigaction(SIGALRM, &oact, NULL) < 0) { + warn("%s: timed_write: sigaction", testname); + return (-1); + } + if (alarm_fired) + *timedoutp = 1; + else + *timedoutp = 0; + + errno = error; + if (written_len < 0) + return (-1); + *written_lenp = written_len; + return (0); +} + +/* + * Wrapper function for read, which uses a timer to interrupt any blocking. + * Because we can't reliably detect EINTR for blocking I/O, we also track + * whether or not our timeout fired. + */ +static int +timed_read(int fd, void *data, size_t len, ssize_t *read_lenp, + int timeout, int *timedoutp, const char *testname) +{ + struct sigaction act, oact; + ssize_t read_len; + int error; + + alarm_fired = 0; + bzero(&act, sizeof(oact)); + act.sa_handler = sigalarm; + if (sigaction(SIGALRM, &act, &oact) < 0) { + warn("%s: timed_write: sigaction", testname); + return (-1); + } + alarm(timeout); + read_len = read(fd, data, len); + error = errno; + alarm(0); + if (sigaction(SIGALRM, &oact, NULL) < 0) { + warn("%s: timed_write: sigaction", testname); + return (-1); + } + if (alarm_fired) + *timedoutp = 1; + else + *timedoutp = 0; + + errno = error; + if (read_len < 0) + return (-1); + *read_lenp = read_len; + return (0); +} + +/* + * This test operates on blocking and non-blocking fifo file descriptors, in + * order to determine whether they block at good moments or not. By good we + * mean: don't block for non-blocking sockets, and do block for blocking + * ones, assuming there isn't I/O buffer to satisfy the request. + * + * We use a timeout of 5 seconds, concluding that in 5 seconds either all I/O + * that can take place will, and that if we reach the end of the timeout, + * then blocking has occurred. + * + * We assume that the buffer size on a fifo is <512K, and as such, that + * writing that much data without an active reader will result in blocking. + */ +static void +test_blocking_read_empty(void) +{ + int reader_fd, ret, timedout, writer_fd; + ssize_t len; + u_char ch; + + makefifo("testfifo", __func__); + if (openfifo("testfifo", &reader_fd, &writer_fd) + < 0) { + warn("test_blocking_read_empty: openfifo: testfifo"); + cleanfifo2("testfifo", -1, -1); + exit(-1); + } + + /* + * Read one byte from an empty blocking fifo, block as there is no + * data. + */ + if (set_blocking(reader_fd, __func__) < 0) { + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + ret = timed_read(reader_fd, &ch, sizeof(ch), &len, 5, &timedout, + __func__); + if (ret != -1) { + warnx("test_blocking_read_empty: timed_read: returned " + "success"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (errno != EINTR) { + warn("test_blocking_read_empty: timed_read"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + /* + * Read one byte from an empty non-blocking fifo, return EAGAIN as + * there is no data. + */ + if (set_nonblocking(reader_fd, __func__) < 0) { + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + ret = timed_read(reader_fd, &ch, sizeof(ch), &len, 5, &timedout, + __func__); + if (ret != -1) { + warnx("test_blocking_read_empty: timed_read: returned " + "success"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (errno != EAGAIN) { + warn("test_blocking_read_empty: timed_read"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + cleanfifo2("testfifo", reader_fd, writer_fd); +} + +/* + * Write one byte to an empty fifo, then try to read one byte and make sure + * we don't block in either the write or the read. This tests both for + * improper blocking in the send and receive code. + */ +static void +test_blocking_one_byte(void) +{ + int reader_fd, ret, timedout, writer_fd; + ssize_t len; + u_char ch; + + makefifo("testfifo", __func__); + if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) { + warn("test_blocking: openfifo: testfifo"); + cleanfifo2("testfifo", -1, -1); + exit(-1); + } + + if (set_blocking(writer_fd, __func__) < 0) { + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (set_blocking(reader_fd, __func__) < 0) { + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + ch = 0xfe; + ret = timed_write(writer_fd, &ch, sizeof(ch), &len, 5, &timedout, + __func__); + if (ret < 0) { + warn("test_blocking_one_byte: timed_write"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (len != sizeof(ch)) { + warnx("test_blocking_one_byte: timed_write: tried to write " + "%zu, wrote %zd", sizeof(ch), len); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + ch = 0xab; + ret = timed_read(reader_fd, &ch, sizeof(ch), &len, 5, &timedout, + __func__); + if (ret < 0) { + warn("test_blocking_one_byte: timed_read"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (len != sizeof(ch)) { + warnx("test_blocking_one_byte: timed_read: wanted %zu, " + "read %zd", sizeof(ch), len); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (ch != 0xfe) { + warnx("test_blocking_one_byte: timed_read: expected to read " + "0x%02x, read 0x%02x", 0xfe, ch); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + cleanfifo2("testfifo", reader_fd, writer_fd); +} + +/* + * Write one byte to an empty fifo, then try to read one byte and make sure + * we don't get back EAGAIN. + */ +static void +test_nonblocking_one_byte(void) +{ + int reader_fd, ret, timedout, writer_fd; + ssize_t len; + u_char ch; + + makefifo("testfifo", __func__); + if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) { + warn("test_nonblocking: openfifo: testfifo"); + cleanfifo2("testfifo", -1, -1); + exit(-1); + } + + if (set_nonblocking(reader_fd, __func__) < 0) { + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + ch = 0xfe; + ret = timed_write(writer_fd, &ch, sizeof(ch), &len, 5, &timedout, + __func__); + if (ret < 0) { + warn("test_nonblocking_one_byte: timed_write"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (len != sizeof(ch)) { + warnx("test_nonblocking_one_byte: timed_write: tried to write " + "%zu, wrote %zd", sizeof(ch), len); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + ch = 0xab; + ret = timed_read(reader_fd, &ch, sizeof(ch), &len, 5, &timedout, + __func__); + if (ret < 0) { + warn("test_nonblocking_one_byte: timed_read"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (len != sizeof(ch)) { + warnx("test_nonblocking_one_byte: timed_read: wanted %zu, read " + "%zd", sizeof(ch), len); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (ch != 0xfe) { + warnx("test_nonblocking_one_byte: timed_read: expected to read " + "0x%02x, read 0x%02x", 0xfe, ch); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + cleanfifo2("testfifo", reader_fd, writer_fd); +} + +/* + * First of two test cases involving a 512K buffer: write the buffer into a + * blocking file descriptor. We'd like to know it blocks, but the closest we + * can get is to see if SIGALRM fired during the I/O resulting in a partial + * write. + */ +static void +test_blocking_partial_write(void) +{ + int reader_fd, ret, timedout, writer_fd; + u_char *buffer; + ssize_t len; + + makefifo("testfifo", __func__); + if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) { + warn("test_blocking_partial_write: openfifo: testfifo"); + cleanfifo2("testfifo", -1, -1); + exit(-1); + } + + if (set_blocking(writer_fd, __func__) < 0) { + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + buffer = malloc(512*1024); + if (buffer == NULL) { + warn("test_blocking_partial_write: malloc"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + bzero(buffer, 512*1024); + + ret = timed_write(writer_fd, buffer, 512*1024, &len, 5, &timedout, + __func__); + if (ret < 0) { + warn("test_blocking_partial_write: timed_write"); + free(buffer); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if (!timedout) { + warnx("test_blocking_partial_write: timed_write: blocking " + "socket didn't time out"); + free(buffer); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + free(buffer); + + if (drain_fd(reader_fd, __func__) < 0) { + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + cleanfifo2("testfifo", reader_fd, writer_fd); +} + +/* + * Write a 512K buffer to an empty fifo using a non-blocking file descriptor, + * and make sure it doesn't block. + */ +static void +test_nonblocking_partial_write(void) +{ + int reader_fd, ret, timedout, writer_fd; + u_char *buffer; + ssize_t len; + + makefifo("testfifo", __func__); + if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) { + warn("test_blocking_partial_write: openfifo: testfifo"); + cleanfifo2("testfifo", -1, -1); + exit(-1); + } + + if (set_nonblocking(writer_fd, __func__) < 0) { + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + buffer = malloc(512*1024); + if (buffer == NULL) { + warn("test_blocking_partial_write: malloc"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + bzero(buffer, 512*1024); + + ret = timed_write(writer_fd, buffer, 512*1024, &len, 5, &timedout, + __func__); + if (ret < 0) { + warn("test_blocking_partial_write: timed_write"); + free(buffer); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if (timedout) { + warnx("test_blocking_partial_write: timed_write: " + "non-blocking socket timed out"); + free(buffer); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if (len == 0 || len >= 512*1024) { + warnx("test_blocking_partial_write: timed_write: requested " + "%d, sent %zd", 512*1024, len); + free(buffer); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + free(buffer); + + if (drain_fd(reader_fd, __func__) < 0) { + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + cleanfifo2("testfifo", reader_fd, writer_fd); +} + +/* + * test_coalesce_big_read() verifies that data mingles in the fifo across + * message boundaries by performing two small writes, then a bigger read + * that should return data from both writes. + */ +static void +test_coalesce_big_read(void) +{ + int i, reader_fd, writer_fd; + u_char buffer[10]; + ssize_t len; + + makefifo("testfifo", __func__); + if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) { + warn("test_coalesce_big_read: openfifo: testfifo"); + cleanfifo2("testfifo", -1, -1); + exit(-1); + } + + /* Write five, write five, read ten. */ + for (i = 0; i < 10; i++) + buffer[i] = i; + + len = write(writer_fd, buffer, 5); + if (len < 0) { + warn("test_coalesce_big_read: write 5"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (len != 5) { + warnx("test_coalesce_big_read: write 5 wrote %zd", len); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + len = write(writer_fd, buffer + 5, 5); + if (len < 0) { + warn("test_coalesce_big_read: write 5"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (len != 5) { + warnx("test_coalesce_big_read: write 5 wrote %zd", len); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + len = read(reader_fd, buffer, 10); + if (len < 0) { + warn("test_coalesce_big_read: read 10"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (len != 10) { + warnx("test_coalesce_big_read: read 10 read %zd", len); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + for (i = 0; i < 10; i++) { + if (buffer[i] == i) + continue; + warnx("test_coalesce_big_read: expected to read 0x%02x, " + "read 0x%02x", i, buffer[i]); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + cleanfifo2("testfifo", -1, -1); +} + +/* + * test_coalesce_big_write() verifies that data mingles in the fifo across + * message boundaries by performing one big write, then two smaller reads + * that should return sequential elements of data from the write. + */ +static void +test_coalesce_big_write(void) +{ + int i, reader_fd, writer_fd; + u_char buffer[10]; + ssize_t len; + + makefifo("testfifo", __func__); + if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) { + warn("test_coalesce_big_write: openfifo: testfifo"); + cleanfifo2("testfifo", -1, -1); + exit(-1); + } + + /* Write ten, read five, read five. */ + for (i = 0; i < 10; i++) + buffer[i] = i; + + len = write(writer_fd, buffer, 10); + if (len < 0) { + warn("test_coalesce_big_write: write 10"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (len != 10) { + warnx("test_coalesce_big_write: write 10 wrote %zd", len); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + len = read(reader_fd, buffer, 5); + if (len < 0) { + warn("test_coalesce_big_write: read 5"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (len != 5) { + warnx("test_coalesce_big_write: read 5 read %zd", len); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + len = read(reader_fd, buffer + 5, 5); + if (len < 0) { + warn("test_coalesce_big_write: read 5"); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (len != 5) { + warnx("test_coalesce_big_write: read 5 read %zd", len); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + for (i = 0; i < 10; i++) { + if (buffer[i] == i) + continue; + warnx("test_coalesce_big_write: expected to read 0x%02x, " + "read 0x%02x", i, buffer[i]); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + cleanfifo2("testfifo", -1, -1); +} + +static int +poll_status(int fd, int *readable, int *writable, int *exception, + const char *testname) +{ + struct pollfd fds[1]; + + fds[0].fd = fd; + fds[0].events = POLLIN | POLLOUT | POLLERR; + fds[0].revents = 0; + + if (poll(fds, 1, 0) < 0) { + warn("%s: poll", testname); + return (-1); + } + *readable = (fds[0].revents & POLLIN) ? 1 : 0; + *writable = (fds[0].revents & POLLOUT) ? 1 : 0; + *exception = (fds[0].revents & POLLERR) ? 1 : 0; + return (0); +} + +static int +select_status(int fd, int *readable, int *writable, int *exception, + const char *testname) +{ + struct fd_set readfds, writefds, exceptfds; + struct timeval timeout; + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + FD_SET(fd, &readfds); + FD_SET(fd, &writefds); + FD_SET(fd, &exceptfds); + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if (select(fd+1, &readfds, &writefds, &exceptfds, &timeout) < 0) { + warn("%s: select", testname); + return (-1); + } + *readable = FD_ISSET(fd, &readfds) ? 1 : 0; + *writable = FD_ISSET(fd, &writefds) ? 1 : 0; + *exception = FD_ISSET(fd, &exceptfds) ? 1 : 0; + return (0); +} + +/* + * Given an existing kqueue, set up read and write event filters for the + * passed file descriptor. Typically called once for the read endpoint, and + * once for the write endpoint. + */ +static int +kqueue_setup(int kqueue_fd, int fd, const char *testname) +{ + struct kevent kevent_changelist[2]; + struct kevent kevent_eventlist[KQUEUE_MAX_EVENT], *kp; + struct timespec timeout; + int i, ret; + + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + + bzero(&kevent_changelist, sizeof(kevent_changelist)); + EV_SET(&kevent_changelist[0], fd, EVFILT_READ, EV_ADD, 0, 0, 0); + EV_SET(&kevent_changelist[1], fd, EVFILT_WRITE, EV_ADD, 0, 0, 0); + + bzero(&kevent_eventlist, sizeof(kevent_eventlist)); + ret = kevent(kqueue_fd, kevent_changelist, 2, kevent_eventlist, + KQUEUE_MAX_EVENT, &timeout); + if (ret < 0) { + warn("%s:%s: kevent initial register", testname, __func__); + return (-1); + } + + /* + * Verify that the events registered alright. + */ + for (i = 0; i < ret; i++) { + kp = &kevent_eventlist[i]; + if (kp->flags != EV_ERROR) + continue; + errno = kp->data; + warn("%s:%s: kevent register index %d", testname, __func__, + i); + return (-1); + } + + return (0); +} + +static int +kqueue_status(int kqueue_fd, int fd, int *readable, int *writable, + int *exception, const char *testname) +{ + struct kevent kevent_eventlist[KQUEUE_MAX_EVENT], *kp; + struct timespec timeout; + int i, ret; + + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + + ret = kevent(kqueue_fd, NULL, 0, kevent_eventlist, KQUEUE_MAX_EVENT, + &timeout); + if (ret < 0) { + warn("%s: %s: kevent", testname, __func__); + return (-1); + } + + *readable = *writable = *exception = 0; + for (i = 0; i < ret; i++) { + kp = &kevent_eventlist[i]; + if (kp->ident != (u_int)fd) + continue; + if (kp->filter == EVFILT_READ) + *readable = 1; + if (kp->filter == EVFILT_WRITE) + *writable = 1; + } + + return (0); +} + +static int +fionread_status(int fd, int *readable, const char *testname) +{ + int i; + + if (ioctl(fd, FIONREAD, &i) < 0) { + warn("%s: ioctl(FIONREAD)", testname); + return (-1); + } + + if (i > 0) + *readable = 1; + else + *readable = 0; + return (0); +} + +#define READABLE 1 +#define WRITABLE 1 +#define EXCEPTION 1 + +#define NOT_READABLE 0 +#define NOT_WRITABLE 0 +#define NOT_EXCEPTION 0 + +static int +assert_status(int fd, int kqueue_fd, int assert_readable, + int assert_writable, int assert_exception, const char *testname, + const char *conditionname, const char *fdname) +{ + int readable, writable, exception; + + if (poll_status(fd, &readable, &writable, &exception, testname) < 0) + return (-1); + + if (readable != assert_readable || writable != assert_writable || + exception != assert_exception) { + warnx("%s: %s polls r:%d, w:%d, e:%d on %s", testname, + fdname, readable, writable, exception, conditionname); + return (-1); + } + + if (select_status(fd, &readable, &writable, &exception, testname) < 0) + return (-1); + + if (readable != assert_readable || writable != assert_writable || + exception != assert_exception) { + warnx("%s: %s selects r:%d, w:%d, e:%d on %s", testname, + fdname, readable, writable, exception, conditionname); + return (-1); + } + + if (kqueue_status(kqueue_fd, fd, &readable, &writable, &exception, + testname) < 0) + return (-1); + + if (readable != assert_readable || writable != assert_writable || + exception != assert_exception) { + warnx("%s: %s kevent r:%d, w:%d, e:%d on %s", testname, + fdname, readable, writable, exception, conditionname); + return (-1); + } + + if (fionread_status(fd, &readable, __func__) < 0) + return (-1); + + if (readable != assert_readable) { + warnx("%s: %s fionread r:%d on %s", testname, fdname, + readable, conditionname); + return (-1); + } + + return (0); +} + +/* + * test_events() uses poll(), select(), and kevent() to query the status of + * fifo file descriptors and determine whether they match expected state + * based on earlier semantic tests: specifically, whether or not poll/select/ + * kevent will correctly inform on readable/writable state following I/O. + * + * It would be nice to also test status changes as a result of closing of one + * or another fifo endpoint. + */ +static void +test_events_outofbox(void) +{ + int kqueue_fd, reader_fd, writer_fd; + + makefifo("testfifo", __func__); + if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) { + warn("test_events_outofbox: openfifo: testfifo"); + cleanfifo2("testfifo", -1, -1); + exit(-1); + } + + kqueue_fd = kqueue(); + if (kqueue_fd < 0) { + warn("%s: kqueue", __func__); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if (kqueue_setup(kqueue_fd, reader_fd, __func__) < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + if (kqueue_setup(kqueue_fd, writer_fd, __func__) < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + /* + * Make sure that fresh, out-of-the-box fifo file descriptors have + * good initial states. The reader_fd should have no active state, + * since it will not be readable (no data in pipe), writable (it's + * a read-only descriptor), and there's no reason for error yet. + */ + if (assert_status(reader_fd, kqueue_fd, NOT_READABLE, NOT_WRITABLE, + NOT_EXCEPTION, __func__, "create", "reader_fd") < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + /* + * Make sure that fresh, out-of-the-box fifo file descriptors have + * good initial states. The writer_fd should be ready to write. + */ + if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, WRITABLE, + NOT_EXCEPTION, __func__, "create", "writer_fd") < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); +} + +static void +test_events_write_read_byte(void) +{ + int kqueue_fd, reader_fd, writer_fd; + ssize_t len; + u_char ch; + + makefifo("testfifo", __func__); + if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) { + warn("test_events_write_read_byte: openfifo: testfifo"); + cleanfifo2("testfifo", -1, -1); + exit(-1); + } + + kqueue_fd = kqueue(); + if (kqueue_fd < 0) { + warn("%s: kqueue", __func__); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if (kqueue_setup(kqueue_fd, reader_fd, __func__) < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + if (kqueue_setup(kqueue_fd, writer_fd, __func__) < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + /* + * Write a byte to the fifo, and make sure that the read end becomes + * readable, and that the write end remains writable (small write). + */ + ch = 0x00; + len = write(writer_fd, &ch, sizeof(ch)); + if (len < 0) { + warn("%s: write", __func__); + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + if (assert_status(reader_fd, kqueue_fd, READABLE, NOT_WRITABLE, + NOT_EXCEPTION, __func__, "write", "reader_fd") < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + /* + * the writer_fd should remain writable. + */ + if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, WRITABLE, + NOT_EXCEPTION, __func__, "write", "writer_fd") < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + /* + * Read the byte from the reader_fd, and now confirm that that fifo + * becomes unreadable. + */ + len = read(reader_fd, &ch, sizeof(ch)); + if (len < 0) { + warn("%s: read", __func__); + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + if (assert_status(reader_fd, kqueue_fd, NOT_READABLE, NOT_WRITABLE, + NOT_EXCEPTION, __func__, "write+read", "reader_fd") < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + /* + * The writer_fd should remain writable. + */ + if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, WRITABLE, + NOT_EXCEPTION, __func__, "write+read", "writer_fd") < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); +} + +/* + * Write a 512k buffer to the fifo in non-blocking mode, and make sure that + * the write end becomes un-writable as a result of a partial write that + * fills the fifo buffer. + */ +static void +test_events_partial_write(void) +{ + int kqueue_fd, reader_fd, writer_fd; + u_char *buffer; + ssize_t len; + + makefifo("testfifo", __func__); + if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) { + warn("test_events_partial_write: openfifo: testfifo"); + cleanfifo2("testfifo", -1, -1); + exit(-1); + } + + kqueue_fd = kqueue(); + if (kqueue_fd < 0) { + warn("%s: kqueue", __func__); + cleanfifo2("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if (kqueue_setup(kqueue_fd, reader_fd, __func__) < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + if (kqueue_setup(kqueue_fd, writer_fd, __func__) < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + if (set_nonblocking(writer_fd, "test_events") < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + buffer = malloc(512*1024); + if (buffer == NULL) { + warn("test_events_partial_write: malloc"); + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + bzero(buffer, 512*1024); + + len = write(writer_fd, buffer, 512*1024); + if (len < 0) { + warn("test_events_partial_write: write"); + free(buffer); + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + free(buffer); + + if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, NOT_WRITABLE, + NOT_EXCEPTION, __func__, "big write", "writer_fd") < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + if (drain_fd(reader_fd, "test_events") < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + /* + * Test that the writer_fd has been restored to writable state after + * draining. + */ + if (assert_status(writer_fd, kqueue_fd, NOT_READABLE, WRITABLE, + NOT_EXCEPTION, __func__, "big write + drain", "writer_fd") < 0) { + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); + exit(-1); + } + + cleanfifo3("testfifo", reader_fd, writer_fd, kqueue_fd); +} + +/* + * We don't comprehensively test O_RDWR file descriptors, but do run a couple + * of event tests to make sure that the fifo implementation doesn't mixed up + * status checks. In particular, at least one past FreeBSD bug exists in + * which the FIONREAD test was performed on the wrong socket implementing the + * fifo, resulting in the fifo never returning readable. + */ +static void +test_events_rdwr(void) +{ + int fd, kqueue_fd; + ssize_t len; + char ch; + + makefifo("testfifo", __func__); + if (openfifo_rw("testfifo", &fd) < 0) { + warn("%s: openfifo_rw: testfifo", __func__); + cleanfifo2("testfifo", -1, -1); + exit(-1); + } + + kqueue_fd = kqueue(); + if (kqueue_fd < 0) { + warn("%s: kqueue", __func__); + cleanfifo2("testifo", fd, -1); + exit(-1); + } + + if (kqueue_setup(kqueue_fd, fd, __func__) < 0) { + cleanfifo2("testfifo", fd, kqueue_fd); + exit(-1); + } + + /* + * On first creation, the O_RDWR descriptor should be writable but + * not readable. + */ + if (assert_status(fd, kqueue_fd, NOT_READABLE, WRITABLE, + NOT_EXCEPTION, __func__, "create", "fd") < 0) { + cleanfifo2("testfifo", fd, kqueue_fd); + exit(-1); + } + + /* + * Write a byte, which should cause the file descriptor to become + * readable and writable. + */ + ch = 0x00; + len = write(fd, &ch, sizeof(ch)); + if (len < 0) { + warn("%s: write", __func__); + cleanfifo2("testfifo", fd, kqueue_fd); + exit(-1); + } + + if (assert_status(fd, kqueue_fd, READABLE, WRITABLE, NOT_EXCEPTION, + __func__, "write", "fd") < 0) { + cleanfifo2("testfifo", fd, kqueue_fd); + exit(-1); + } + + /* + * Read a byte, which should cause the file descriptor to return to + * simply being writable. + */ + len = read(fd, &ch, sizeof(ch)); + if (len < 0) { + warn("%s: read", __func__); + cleanfifo2("testfifo", fd, kqueue_fd); + exit(-1); + } + + if (assert_status(fd, kqueue_fd, NOT_READABLE, WRITABLE, + NOT_EXCEPTION, __func__, "write+read", "fd") < 0) { + cleanfifo2("testfifo", fd, kqueue_fd); + exit(-1); + } + + cleanfifo2("testfifo", fd, kqueue_fd); +} + +int +main(void) +{ + + strcpy(temp_dir, "fifo_io.XXXXXXXXXXX"); + if (mkdtemp(temp_dir) == NULL) + err(-1, "mkdtemp"); + atexit(atexit_temp_dir); + + if (chdir(temp_dir) < 0) + err(-1, "chdir %s", temp_dir); + + test_simpleio(); + test_blocking_read_empty(); + test_blocking_one_byte(); + test_nonblocking_one_byte(); + test_blocking_partial_write(); + test_nonblocking_partial_write(); + test_coalesce_big_read(); + test_coalesce_big_write(); + test_events_outofbox(); + test_events_write_read_byte(); + test_events_partial_write(); + test_events_rdwr(); + + return (0); +} diff --git a/tests/sys/fifo/fifo_misc.c b/tests/sys/fifo/fifo_misc.c new file mode 100644 index 0000000..888547e --- /dev/null +++ b/tests/sys/fifo/fifo_misc.c @@ -0,0 +1,335 @@ +/*- + * Copyright (c) 2005 Robert N. M. Watson + * Copyright (c) 2012 Jilles Tjoelker + * 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. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/event.h> +#include <sys/filio.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* + * Regression test for piddling details of fifos. + */ + +/* + * All activity occurs within a temporary directory created early in the + * test. + */ +static char temp_dir[PATH_MAX]; + +static void __unused +atexit_temp_dir(void) +{ + + rmdir(temp_dir); +} + +static void +makefifo(const char *fifoname, const char *testname) +{ + + if (mkfifo(fifoname, 0700) < 0) + err(-1, "%s: makefifo: mkfifo: %s", testname, fifoname); +} + +static void +cleanfifo(const char *fifoname, int fd1, int fd2) +{ + + if (fd1 != -1) + close(fd1); + if (fd2 != -1) + close(fd2); + (void)unlink(fifoname); +} + +static int +openfifo(const char *fifoname, int *reader_fdp, int *writer_fdp) +{ + int error, fd1, fd2; + + fd1 = open(fifoname, O_RDONLY | O_NONBLOCK); + if (fd1 < 0) + return (-1); + fd2 = open(fifoname, O_WRONLY | O_NONBLOCK); + if (fd2 < 0) { + error = errno; + close(fd1); + errno = error; + return (-1); + } + *reader_fdp = fd1; + *writer_fdp = fd2; + + return (0); +} + +/* + * POSIX does not allow lseek(2) on fifos, so we expect ESPIPE as a result. + */ +static void +test_lseek(void) +{ + int reader_fd, writer_fd; + + makefifo("testfifo", __func__); + + if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) { + warn("%s: openfifo", __func__); + cleanfifo("testfifo", -1, -1); + exit(-1); + } + + if (lseek(reader_fd, 1, SEEK_CUR) >= 0) { + warnx("%s: lseek succeeded instead of returning ESPIPE", + __func__); + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (errno != ESPIPE) { + warn("%s: lseek returned instead of ESPIPE", __func__); + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + cleanfifo("testfifo", reader_fd, writer_fd); +} + +/* + * truncate(2) on FIFO should silently return success. + */ +static void +test_truncate(void) +{ + + makefifo("testfifo", __func__); + + if (truncate("testfifo", 1024) != 0) { + warn("%s: truncate", __func__); + cleanfifo("testfifo", -1, -1); + exit(-1); + } + + cleanfifo("testfifo", -1, -1); +} + +static int +test_ioctl_setclearflag(int fd, int flag, const char *testname, + const char *fdname, const char *flagname) +{ + int i; + + i = 1; + if (ioctl(fd, flag, &i) < 0) { + warn("%s:%s: ioctl(%s, %s, 1)", testname, __func__, fdname, + flagname); + cleanfifo("testfifo", -1, -1); + exit(-1); + } + + i = 0; + if (ioctl(fd, flag, &i) < 0) { + warn("%s:%s: ioctl(%s, %s, 0)", testname, __func__, fdname, + flagname); + cleanfifo("testfifo", -1, -1); + exit(-1); + } + + return (0); +} + +/* + * Test that various ioctls can be issued against the file descriptor. We + * don't currently test the semantics of these changes here. + */ +static void +test_ioctl(void) +{ + int reader_fd, writer_fd; + + makefifo("testfifo", __func__); + + if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) { + warn("%s: openfifo", __func__); + cleanfifo("testfifo", -1, -1); + exit(-1); + } + + /* + * Set and remove the non-blocking I/O flag. + */ + if (test_ioctl_setclearflag(reader_fd, FIONBIO, __func__, + "reader_fd", "FIONBIO") < 0) { + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if (test_ioctl_setclearflag(writer_fd, FIONBIO, __func__, + "writer_fd", "FIONBIO") < 0) { + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + /* + * Set and remove the async I/O flag. + */ + if (test_ioctl_setclearflag(reader_fd, FIOASYNC, __func__, + "reader_fd", "FIOASYNC") < 0) { + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if (test_ioctl_setclearflag(writer_fd, FIOASYNC, __func__, + "writer_fd", "FIONASYNC") < 0) { + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + cleanfifo("testfifo", reader_fd, writer_fd); +} + +/* + * fchmod(2)/fchown(2) on FIFO should work. + */ +static void +test_chmodchown(void) +{ + struct stat sb; + int reader_fd, writer_fd; + uid_t u; + gid_t g; + + makefifo("testfifo", __func__); + + if (openfifo("testfifo", &reader_fd, &writer_fd) < 0) { + warn("%s: openfifo", __func__); + cleanfifo("testfifo", -1, -1); + exit(-1); + } + + if (fchmod(reader_fd, 0666) != 0) { + warn("%s: fchmod", __func__); + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if (stat("testfifo", &sb) != 0) { + warn("%s: stat", __func__); + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if ((sb.st_mode & 0777) != 0666) { + warnx("%s: stat chmod result", __func__); + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if (fstat(writer_fd, &sb) != 0) { + warn("%s: fstat", __func__); + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if ((sb.st_mode & 0777) != 0666) { + warnx("%s: fstat chmod result", __func__); + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if (fchown(reader_fd, -1, -1) != 0) { + warn("%s: fchown 1", __func__); + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + u = geteuid(); + if (u == 0) + u = 1; + g = getegid(); + if (fchown(reader_fd, u, g) != 0) { + warn("%s: fchown 2", __func__); + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + if (stat("testfifo", &sb) != 0) { + warn("%s: stat", __func__); + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if (sb.st_uid != u || sb.st_gid != g) { + warnx("%s: stat chown result", __func__); + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if (fstat(writer_fd, &sb) != 0) { + warn("%s: fstat", __func__); + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + if (sb.st_uid != u || sb.st_gid != g) { + warnx("%s: fstat chown result", __func__); + cleanfifo("testfifo", reader_fd, writer_fd); + exit(-1); + } + + cleanfifo("testfifo", -1, -1); +} + +int +main(void) +{ + + strcpy(temp_dir, "fifo_misc.XXXXXXXXXXX"); + if (mkdtemp(temp_dir) == NULL) + err(-1, "mkdtemp"); + atexit(atexit_temp_dir); + + if (chdir(temp_dir) < 0) + err(-1, "chdir %s", temp_dir); + + test_lseek(); + test_truncate(); + test_ioctl(); + test_chmodchown(); + + return (0); +} diff --git a/tests/sys/fifo/fifo_open.c b/tests/sys/fifo/fifo_open.c new file mode 100644 index 0000000..892f481 --- /dev/null +++ b/tests/sys/fifo/fifo_open.c @@ -0,0 +1,476 @@ +/*- + * Copyright (c) 2005 Robert N. M. Watson + * 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. + * + * $FreeBSD$ + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* + * Regression test to exercise various POSIX-defined parts of fifo behavior + * described for open(2): + * + * O_NONBLOCK + * When opening a FIFO with O_RDONLY or O_WRONLY set: + * + * - If O_NONBLOCK is set, an open() for reading-only shall return without + * delay. An open() for writing-only shall return an error if no process + * currently has the file open for reading. + * + * - If O_NONBLOCK is clear, an open() for reading-only shall block the + * calling thread until a thread opens the file for writing. An open() + * for writing-only shall block the calling thread until a thread opens + * the file for reading. + * + * When opening a block special or character special file that supports + * non-blocking opens: + * + * - If O_NONBLOCK is set, the open() function shall return without blocking + * for the device to be ready or available. Subsequent behavior of the + * device is device-specific. + * + * - If O_NONBLOCK is clear, the open() function shall block the calling + * thread until the device is ready or available before returning. + * + * Special errors: + * + * [ENXIO] + * O_NONBLOCK is set, the named file is a FIFO, O_WRONLY is set, and no + * process has the file open for reading. + */ + +/* + * In order to test blocking/non-blocking behavior, test processes must + * potentially block themselves until released. As bugs in blocking result + * in processes that won't un-block, we must sacrifice a process to the task, + * watching and potentially killing it after a time-out. The main test + * process is never used to open or act directly on a fifo (other than to + * create or unlink it) in order to avoid the main test process being + * blocked. + */ + +/* + * All activity occurs within a temporary directory created early in the + * test. + */ +static char temp_dir[PATH_MAX]; + +static void __unused +atexit_temp_dir(void) +{ + + rmdir(temp_dir); +} + +/* + * Run a function in a particular test process. + */ +static int +run_in_process(int (*func)(void), pid_t *pidp, const char *errstr) +{ + pid_t pid; + + pid = fork(); + if (pid < 0) { + warn("%s: run_in_process: fork", errstr); + return (-1); + } + + if (pid == 0) + exit(func()); + + if (pidp != NULL) + *pidp = pid; + + return (0); +} + +/* + * Wait for a process on a timeout, and if the timeout expires, kill the + * process. Test each second rather than waiting the full timeout at once to + * minimize the amount of time spent hanging around unnecessarily. + */ +static int +wait_and_timeout(pid_t pid, int timeout, int *status, const char *errstr) +{ + pid_t wpid; + int i; + + /* + * Count up to the timeout, but do a non-hanging waitpid() after each + * second so we can avoid waiting a lot of extra time. + */ + for (i = 0; i < timeout; i++) { + wpid = waitpid(pid, status, WNOHANG); + if (wpid < 0) { + warn("%s: wait_and_timeout: waitpid %d", errstr, pid); + return (-1); + } + + if (wpid == pid) + return (0); + + sleep(1); + } + + wpid = waitpid(pid, status, WNOHANG); + if (wpid < 0) { + warn("%s: wait_and_timeout: waitpid %d", errstr, pid); + return (-1); + } + + if (wpid == pid) + return (0); + + if (kill(pid, SIGTERM) < 0) { + warn("%s: wait_and_timeout: kill %d", errstr, pid); + return (-1); + } + + wpid = waitpid(pid, status, 0); + if (wpid < 0) { + warn("%s: wait_and_timeout: waitpid %d", errstr, pid); + return (-1); + } + + if (wpid != pid) { + warn("%s: waitpid: returned %d not %d", errstr, wpid, pid); + return (-1); + } + + warnx("%s: process blocked", errstr); + return (-1); +} + +static int +non_blocking_open_reader(void) +{ + int fd; + + fd = open("testfifo", O_RDONLY | O_NONBLOCK); + if (fd < 0) + return (errno); + close(fd); + + return (0); +} + +static int +non_blocking_open_writer(void) +{ + int fd; + + fd = open("testfifo", O_WRONLY | O_NONBLOCK); + if (fd < 0) + return (errno); + close(fd); + + return (0); +} + +static int +blocking_open_reader(void) +{ + int fd; + + fd = open("testfifo", O_RDONLY); + if (fd < 0) + return (errno); + close(fd); + + return (0); +} + +static int +blocking_open_writer(void) +{ + int fd; + + fd = open("testfifo", O_WRONLY); + if (fd < 0) + return (errno); + close(fd); + + return (0); +} + +static void +test_blocking_reader(void) +{ + pid_t reader_pid, writer_pid, wpid; + int error, status; + + if (mkfifo("testfifo", 0600) < 0) + err(-1, "test_blocking_reader: mkfifo: testfifo"); + + /* + * Block a process in opening the fifo. + */ + if (run_in_process(blocking_open_reader, &reader_pid, + "test_blocking_reader: blocking_open_reader") < 0) { + (void)unlink("testfifo"); + exit(-1); + } + + /* + * Test that it blocked. + */ + sleep(5); + wpid = waitpid(reader_pid, &status, WNOHANG); + if (wpid < 0) { + error = errno; + (void)unlink("testfifo"); + errno = error; + err(-1, "test_blocking_reader: waitpid %d", reader_pid); + } + + if (wpid != 0 && wpid != reader_pid) { + (void)unlink("testfifo"); + errx(-1, "test_blocking_reader: waitpid %d returned %d", + reader_pid, wpid); + } + + if (wpid == reader_pid) { + (void)unlink("testfifo"); + errx(-1, "test_blocking_reader: blocking child didn't " + "block"); + } + + /* + * Unblock the blocking reader. + */ + if (run_in_process(blocking_open_writer, &writer_pid, + "test_blocking_reader: blocking_open_writer") < 0) { + (void)unlink("testfifo"); + (void)kill(reader_pid, SIGTERM); + (void)waitpid(reader_pid, &status, 0); + exit(-1); + } + + /* + * Make sure both processes exited quickly (<1 second) to make sure + * they didn't block, and GC. + */ + if (wait_and_timeout(reader_pid, 1, &status, + "test_blocking_reader: blocking_open_reader") < 0) { + (void)unlink("testinfo"); + (void)kill(reader_pid, SIGTERM); + (void)kill(writer_pid, SIGTERM); + exit(-1); + } + + if (wait_and_timeout(writer_pid, 1, &status, + "test_blocking_reader: blocking_open_writer") < 0) { + (void)unlink("testinfo"); + (void)kill(writer_pid, SIGTERM); + exit(-1); + } + + if (unlink("testfifo") < 0) + err(-1, "test_blocking_reader: unlink: testfifo"); +} +static void +test_blocking_writer(void) +{ + pid_t reader_pid, writer_pid, wpid; + int error, status; + + if (mkfifo("testfifo", 0600) < 0) + err(-1, "test_blocking_writer: mkfifo: testfifo"); + + /* + * Block a process in opening the fifo. + */ + if (run_in_process(blocking_open_writer, &writer_pid, + "test_blocking_writer: blocking_open_writer") < 0) { + (void)unlink("testfifo"); + exit(-1); + } + + /* + * Test that it blocked. + */ + sleep(5); + wpid = waitpid(writer_pid, &status, WNOHANG); + if (wpid < 0) { + error = errno; + (void)unlink("testfifo"); + errno = error; + err(-1, "test_blocking_writer: waitpid %d", writer_pid); + } + + if (wpid != 0 && wpid != writer_pid) { + (void)unlink("testfifo"); + errx(-1, "test_blocking_writer: waitpid %d returned %d", + writer_pid, wpid); + } + + if (wpid == writer_pid) { + (void)unlink("testfifo"); + errx(-1, "test_blocking_writer: blocking child didn't " + "block"); + } + + /* + * Unblock the blocking writer. + */ + if (run_in_process(blocking_open_reader, &reader_pid, + "test_blocking_writer: blocking_open_reader") < 0) { + (void)unlink("testfifo"); + (void)kill(writer_pid, SIGTERM); + (void)waitpid(writer_pid, &status, 0); + exit(-1); + } + + /* + * Make sure both processes exited quickly (<1 second) to make sure + * they didn't block, and GC. + */ + if (wait_and_timeout(writer_pid, 1, &status, + "test_blocking_writer: blocking_open_writer") < 0) { + (void)unlink("testinfo"); + (void)kill(writer_pid, SIGTERM); + (void)kill(reader_pid, SIGTERM); + (void)waitpid(writer_pid, &status, 0); + (void)waitpid(reader_pid, &status, 0); + exit(-1); + } + + if (wait_and_timeout(reader_pid, 1, &status, + "test_blocking_writer: blocking_open_reader") < 0) { + (void)unlink("testinfo"); + (void)kill(reader_pid, SIGTERM); + (void)waitpid(reader_pid, &status, 0); + exit(-1); + } + + if (unlink("testfifo") < 0) + err(-1, "test_blocking_writer: unlink: testfifo"); +} + +static void +test_non_blocking_reader(void) +{ + int status; + pid_t pid; + + if (mkfifo("testfifo", 0600) < 0) + err(-1, "test_non_blocking_reader: mkfifo: testfifo"); + + if (run_in_process(non_blocking_open_reader, &pid, + "test_non_blocking_reader: non_blocking_open_reader") < 0) { + (void)unlink("testfifo"); + exit(-1); + } + + status = -1; + if (wait_and_timeout(pid, 5, &status, + "test_non_blocking_reader: non_blocking_open_reader") < 0) { + (void)unlink("testfifo"); + exit(-1); + } + + if (WEXITSTATUS(status) != 0) { + (void)unlink("testfifo"); + errno = WEXITSTATUS(status); + err(-1, "test_non_blocking_reader: " + "non_blocking_open_reader: open: testfifo"); + } + + if (unlink("testfifo") < 0) + err(-1, "test_non_blocking_reader: unlink: testfifo"); +} + +static void +test_non_blocking_writer(void) +{ + int status; + pid_t pid; + + if (mkfifo("testfifo", 0600) < 0) + err(-1, "test_non_blocking_writer: mkfifo: testfifo"); + + if (run_in_process(non_blocking_open_writer, &pid, + "test_non_blocking_writer: non_blocking_open_writer") < 0) { + (void)unlink("testfifo"); + exit(-1); + } + + status = -1; + if (wait_and_timeout(pid, 5, &status, + "test_non_blocking_writer: non_blocking_open_writer") < 0) { + (void)unlink("testfifo"); + exit(-1); + } + + if (WEXITSTATUS(status) != ENXIO) { + (void)unlink("testfifo"); + + errno = WEXITSTATUS(status); + if (errno == 0) + errx(-1, "test_non_blocking_writer: " + "non_blocking_open_writer: open succeeded"); + err(-1, "test_non_blocking_writer: " + "non_blocking_open_writer: open: testfifo"); + } + + if (unlink("testfifo") < 0) + err(-1, "test_non_blocking_writer: unlink: testfifo"); +} + +int +main(void) +{ + + if (geteuid() != 0) + errx(-1, "must be run as root"); + + strcpy(temp_dir, "fifo_open.XXXXXXXXXXX"); + if (mkdtemp(temp_dir) == NULL) + err(-1, "mkdtemp"); + if (chdir(temp_dir) < 0) + err(-1, "chdir: %s", temp_dir); + atexit(atexit_temp_dir); + + test_non_blocking_reader(); + test_non_blocking_writer(); + + test_blocking_reader(); + test_blocking_writer(); + + return (0); +} diff --git a/tests/sys/file/Makefile b/tests/sys/file/Makefile new file mode 100644 index 0000000..6151c9f --- /dev/null +++ b/tests/sys/file/Makefile @@ -0,0 +1,25 @@ +# $FreeBSD$ + +TESTSDIR= ${TESTSBASE}/sys/file + +BINDIR= ${TESTSDIR} + +TAP_TESTS_C+= closefrom_test +TAP_TESTS_C+= dup_test +TAP_TESTS_C+= fcntlflags_test +TAP_TESTS_SH+= flock_test +PLAIN_TESTS_C+= ftruncate_test +PLAIN_TESTS_C+= newfileops_on_fork_test + +PROGS+= flock_helper + +DPADD.closefrom_test= ${LIBUTIL} +LDADD.closefrom_test= -lutil + +DPADD.flock_helper= ${LIBPTHREAD} +LDADD.flock_helper= -lpthread + +DPADD.newfileops_on_fork_test= ${LIBPTHREAD} +LDADD.newfileops_on_fork_test= -lpthread + +.include <bsd.test.mk> diff --git a/tests/sys/file/closefrom_test.c b/tests/sys/file/closefrom_test.c new file mode 100644 index 0000000..78cfeec --- /dev/null +++ b/tests/sys/file/closefrom_test.c @@ -0,0 +1,275 @@ +/*- + * Copyright (c) 2009 Hudson River Trading LLC + * Written by: John H. Baldwin <jhb@FreeBSD.org> + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * Regression tests for the closefrom(2) system call. + */ + +#include <sys/param.h> +#include <sys/mman.h> +#include <sys/user.h> +#include <sys/wait.h> +#include <errno.h> +#include <fcntl.h> +#include <libutil.h> +#include <paths.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +struct shared_info { + int failed; + char tag[64]; + char message[0]; +}; + +static int test = 1; + +static void +ok(const char *descr) +{ + + printf("ok %d - %s\n", test, descr); + test++; +} + +static void +fail(const char *descr, const char *fmt, ...) +{ + va_list ap; + + printf("not ok %d - %s", test, descr); + test++; + if (fmt) { + va_start(ap, fmt); + printf(" # "); + vprintf(fmt, ap); + va_end(ap); + } + printf("\n"); + exit(1); +} + +#define fail_err(descr) fail((descr), "%s", strerror(errno)) + +static void +cok(struct shared_info *info, const char *descr) +{ + + info->failed = 0; + strlcpy(info->tag, descr, sizeof(info->tag)); + exit(0); +} + +static void +cfail(struct shared_info *info, const char *descr, const char *fmt, ...) +{ + va_list ap; + + info->failed = 1; + strlcpy(info->tag, descr, sizeof(info->tag)); + if (fmt) { + va_start(ap, fmt); + vsprintf(info->message, fmt, ap); + va_end(ap); + } + exit(0); +} + +#define cfail_err(info, descr) cfail((info), (descr), "%s", strerror(errno)) + +/* + * Use kinfo_getfile() to fetch the list of file descriptors and figure out + * the highest open file descriptor. + */ +static int +highest_fd(void) +{ + struct kinfo_file *kif; + int cnt, i, highest; + + kif = kinfo_getfile(getpid(), &cnt); + if (kif == NULL) + fail_err("kinfo_getfile"); + highest = INT_MIN; + for (i = 0; i < cnt; i++) + if (kif[i].kf_fd > highest) + highest = kif[i].kf_fd; + free(kif); + return (highest); +} + +static int +devnull(void) +{ + int fd; + + fd = open(_PATH_DEVNULL, O_RDONLY); + if (fd < 0) + fail_err("open(\" "_PATH_DEVNULL" \")"); + return (fd); +} + +int +main(void) +{ + struct shared_info *info; + pid_t pid; + int fd, i, start; + + printf("1..15\n"); + + /* We better start up with fd's 0, 1, and 2 open. */ + start = devnull(); + if (start == -1) + fail("open", "bad descriptor %d", start); + ok("open"); + + /* Make sure highest_fd() works. */ + fd = highest_fd(); + if (start != fd) + fail("highest_fd", "bad descriptor %d != %d", start, fd); + ok("highest_fd"); + + /* Try to use closefrom() for just closing fd 3. */ + closefrom(start + 1); + fd = highest_fd(); + if (fd != start) + fail("closefrom", "highest fd %d", fd); + ok("closefrom"); + + /* Eat up 16 descriptors. */ + for (i = 0; i < 16; i++) + (void)devnull(); + fd = highest_fd(); + if (fd != start + 16) + fail("open 16", "highest fd %d", fd); + ok("open 16"); + + /* Close half of them. */ + closefrom(11); + fd = highest_fd(); + if (fd != 10) + fail("closefrom", "highest fd %d", fd); + ok("closefrom"); + + /* Explicitly close descriptors 6 and 8 to create holes. */ + if (close(6) < 0 || close(8) < 0) + fail_err("close2 "); + ok("close 2"); + + /* Verify that close on 6 and 8 fails with EBADF. */ + if (close(6) == 0) + fail("close(6)", "did not fail"); + if (errno != EBADF) + fail_err("close(6)"); + ok("close(6)"); + if (close(8) == 0) + fail("close(8)", "did not fail"); + if (errno != EBADF) + fail_err("close(8)"); + ok("close(8)"); + + /* Close from 4 on. */ + closefrom(4); + fd = highest_fd(); + if (fd != 3) + fail("closefrom", "highest fd %d", fd); + ok("closefrom"); + + /* Allocate a small SHM region for IPC with our child. */ + info = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_ANON | + MAP_SHARED, -1, 0); + if (info == MAP_FAILED) + fail_err("mmap"); + ok("mmap"); + + /* Fork a child process to test closefrom(0). */ + pid = fork(); + if (pid < 0) + fail_err("fork"); + if (pid == 0) { + /* Child. */ + closefrom(0); + fd = highest_fd(); + if (fd >= 0) + cfail(info, "closefrom(0)", "highest fd %d", fd); + cok(info, "closefrom(0)"); + } + if (wait(NULL) < 0) + fail_err("wait"); + if (info->failed) + fail(info->tag, "%s", info->message); + ok(info->tag); + + /* Fork a child process to test closefrom(-1). */ + pid = fork(); + if (pid < 0) + fail_err("fork"); + if (pid == 0) { + /* Child. */ + closefrom(-1); + fd = highest_fd(); + if (fd >= 0) + cfail(info, "closefrom(-1)", "highest fd %d", fd); + cok(info, "closefrom(-1)"); + } + if (wait(NULL) < 0) + fail_err("wait"); + if (info->failed) + fail(info->tag, "%s", info->message); + ok(info->tag); + + /* Dup stdout to 6. */ + if (dup2(1, 6) < 0) + fail_err("dup2"); + fd = highest_fd(); + if (fd != 6) + fail("dup2", "highest fd %d", fd); + ok("dup2"); + + /* Do a closefrom() starting in a hole. */ + closefrom(4); + fd = highest_fd(); + if (fd != 3) + fail("closefrom", "highest fd %d", fd); + ok("closefrom"); + + /* Do a closefrom() beyond our highest open fd. */ + closefrom(32); + fd = highest_fd(); + if (fd != 3) + fail("closefrom", "highest fd %d", fd); + ok("closefrom"); + + return (0); +} diff --git a/tests/sys/file/dup_test.c b/tests/sys/file/dup_test.c new file mode 100644 index 0000000..8173818 --- /dev/null +++ b/tests/sys/file/dup_test.c @@ -0,0 +1,386 @@ +/* + * $OpenBSD: dup2test.c,v 1.3 2003/07/31 21:48:08 deraadt Exp $ + * $OpenBSD: dup2_self.c,v 1.3 2003/07/31 21:48:08 deraadt Exp $ + * $OpenBSD: fcntl_dup.c,v 1.2 2003/07/31 21:48:08 deraadt Exp $ + * + * Written by Artur Grabowski <art@openbsd.org> 2002 Public Domain. + * + * $FreeBSD$ + */ + +/* + * Test #1: check if dup(2) works. + * Test #2: check if dup2(2) works. + * Test #3: check if dup2(2) returned a fd we asked for. + * Test #4: check if dup2(2) cleared close-on-exec flag for duped fd. + * Test #5: check if dup2(2) allows to dup fd to itself. + * Test #6: check if dup2(2) returned a fd we asked for. + * Test #7: check if dup2(2) did not clear close-on-exec flag for duped fd. + * Test #8: check if fcntl(F_DUPFD) works. + * Test #9: check if fcntl(F_DUPFD) cleared close-on-exec flag for duped fd. + * Test #10: check if dup2() to a fd > current maximum number of open files + * limit work. + * Test #11: check if fcntl(F_DUP2FD) works. + * Test #12: check if fcntl(F_DUP2FD) returned a fd we asked for. + * Test #13: check if fcntl(F_DUP2FD) cleared close-on-exec flag for duped fd. + * Test #14: check if fcntl(F_DUP2FD) allows to dup fd to itself. + * Test #15: check if fcntl(F_DUP2FD) returned a fd we asked for. + * Test #16: check if fcntl(F_DUP2FD) did not clear close-on-exec flag for + * duped fd. + * Test #17: check if fcntl(F_DUP2FD) to a fd > current maximum number of open + * files limit work. + * Test #18: check if fcntl(F_DUPFD_CLOEXEC) works. + * Test #19: check if fcntl(F_DUPFD_CLOEXEC) set close-on-exec flag for duped + * fd. + * Test #20: check if fcntl(F_DUP2FD_CLOEXEC) works. + * Test #21: check if fcntl(F_DUP2FD_CLOEXEC) returned a fd we asked for. + * Test #22: check if fcntl(F_DUP2FD_CLOEXEC) set close-on-exec flag for duped + * fd. + * Test #23: check if fcntl(F_DUP2FD_CLOEXEC) to a fd > current maximum number + * of open files limit work. + * Test #24: check if dup3(O_CLOEXEC) works. + * Test #25: check if dup3(O_CLOEXEC) returned a fd we asked for. + * Test #26: check if dup3(O_CLOEXEC) set close-on-exec flag for duped fd. + * Test #27: check if dup3(0) works. + * Test #28: check if dup3(0) returned a fd we asked for. + * Test #29: check if dup3(0) cleared close-on-exec flag for duped fd. + * Test #30: check if dup3(O_CLOEXEC) fails if oldfd == newfd. + * Test #31: check if dup3(0) fails if oldfd == newfd. + * Test #32: check if dup3(O_CLOEXEC) to a fd > current maximum number of + * open files limit work. + */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> + +#include <err.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +static int getafile(void); + +static int +getafile(void) +{ + int fd; + + char temp[] = "/tmp/dup2XXXXXXXXX"; + if ((fd = mkstemp(temp)) < 0) + err(1, "mkstemp"); + remove(temp); + if (ftruncate(fd, 1024) != 0) + err(1, "ftruncate"); + return (fd); +} + +int +main(int __unused argc, char __unused *argv[]) +{ + struct rlimit rlp; + int orgfd, fd1, fd2, test = 0; + + orgfd = getafile(); + + printf("1..32\n"); + + /* If dup(2) ever work? */ + if ((fd1 = dup(orgfd)) < 0) + err(1, "dup"); + printf("ok %d - dup(2) works\n", ++test); + + /* Set close-on-exec */ + if (fcntl(fd1, F_SETFD, 1) != 0) + err(1, "fcntl(F_SETFD)"); + + /* If dup2(2) ever work? */ + if ((fd2 = dup2(fd1, fd1 + 1)) < 0) + err(1, "dup2"); + printf("ok %d - dup2(2) works\n", ++test); + + /* Do we get the right fd? */ + ++test; + if (fd2 != fd1 + 1) + printf("no ok %d - dup2(2) didn't give us the right fd\n", + test); + else + printf("ok %d - dup2(2) returned a correct fd\n", test); + + /* Was close-on-exec cleared? */ + ++test; + if (fcntl(fd2, F_GETFD) != 0) + printf("not ok %d - dup2(2) didn't clear close-on-exec\n", + test); + else + printf("ok %d - dup2(2) cleared close-on-exec\n", test); + + /* + * Dup to itself. + * + * We're testing a small tweak in dup2 semantics. + * Normally dup and dup2 will clear the close-on-exec + * flag on the new fd (which appears to be an implementation + * mistake from start and not some planned behavior). + * In today's implementations of dup and dup2 we have to make + * an effort to really clear that flag. But all tested + * implementations of dup2 have another tweak. If we + * dup2(old, new) when old == new, the syscall short-circuits + * and returns early (because there is no need to do all the + * work (and there is a risk for serious mistakes)). + * So although the docs say that dup2 should "take 'old', + * close 'new' perform a dup(2) of 'old' into 'new'" + * the docs are not really followed because close-on-exec + * is not cleared on 'new'. + * + * Since everyone has this bug, we pretend that this is + * the way it is supposed to be and test here that it really + * works that way. + * + * This is a fine example on where two separate implementation + * fuckups take out each other and make the end-result the way + * it was meant to be. + */ + if ((fd2 = dup2(fd1, fd1)) < 0) + err(1, "dup2"); + printf("ok %d - dup2(2) to itself works\n", ++test); + + /* Do we get the right fd? */ + ++test; + if (fd2 != fd1) + printf("not ok %d - dup2(2) didn't give us the right fd\n", + test); + else + printf("ok %d - dup2(2) to itself returned a correct fd\n", + test); + + /* Was close-on-exec cleared? */ + ++test; + if (fcntl(fd2, F_GETFD) == 0) + printf("not ok %d - dup2(2) cleared close-on-exec\n", test); + else + printf("ok %d - dup2(2) didn't clear close-on-exec\n", test); + + /* Does fcntl(F_DUPFD) work? */ + if ((fd2 = fcntl(fd1, F_DUPFD, 10)) < 0) + err(1, "fcntl(F_DUPFD)"); + if (fd2 < 10) + printf("not ok %d - fcntl(F_DUPFD) returned wrong fd %d\n", + ++test, fd2); + else + printf("ok %d - fcntl(F_DUPFD) works\n", ++test); + + /* Was close-on-exec cleared? */ + ++test; + if (fcntl(fd2, F_GETFD) != 0) + printf( + "not ok %d - fcntl(F_DUPFD) didn't clear close-on-exec\n", + test); + else + printf("ok %d - fcntl(F_DUPFD) cleared close-on-exec\n", test); + + ++test; + if (getrlimit(RLIMIT_NOFILE, &rlp) < 0) + err(1, "getrlimit"); + if ((fd2 = dup2(fd1, rlp.rlim_cur + 1)) >= 0) + printf("not ok %d - dup2(2) bypassed NOFILE limit\n", test); + else + printf("ok %d - dup2(2) didn't bypass NOFILE limit\n", test); + + /* If fcntl(F_DUP2FD) ever work? */ + if ((fd2 = fcntl(fd1, F_DUP2FD, fd1 + 1)) < 0) + err(1, "fcntl(F_DUP2FD)"); + printf("ok %d - fcntl(F_DUP2FD) works\n", ++test); + + /* Do we get the right fd? */ + ++test; + if (fd2 != fd1 + 1) + printf( + "no ok %d - fcntl(F_DUP2FD) didn't give us the right fd\n", + test); + else + printf("ok %d - fcntl(F_DUP2FD) returned a correct fd\n", + test); + + /* Was close-on-exec cleared? */ + ++test; + if (fcntl(fd2, F_GETFD) != 0) + printf( + "not ok %d - fcntl(F_DUP2FD) didn't clear close-on-exec\n", + test); + else + printf("ok %d - fcntl(F_DUP2FD) cleared close-on-exec\n", + test); + + /* Dup to itself */ + if ((fd2 = fcntl(fd1, F_DUP2FD, fd1)) < 0) + err(1, "fcntl(F_DUP2FD)"); + printf("ok %d - fcntl(F_DUP2FD) to itself works\n", ++test); + + /* Do we get the right fd? */ + ++test; + if (fd2 != fd1) + printf( + "not ok %d - fcntl(F_DUP2FD) didn't give us the right fd\n", + test); + else + printf( + "ok %d - fcntl(F_DUP2FD) to itself returned a correct fd\n", + test); + + /* Was close-on-exec cleared? */ + ++test; + if (fcntl(fd2, F_GETFD) == 0) + printf("not ok %d - fcntl(F_DUP2FD) cleared close-on-exec\n", + test); + else + printf("ok %d - fcntl(F_DUP2FD) didn't clear close-on-exec\n", + test); + + ++test; + if (getrlimit(RLIMIT_NOFILE, &rlp) < 0) + err(1, "getrlimit"); + if ((fd2 = fcntl(fd1, F_DUP2FD, rlp.rlim_cur + 1)) >= 0) + printf("not ok %d - fcntl(F_DUP2FD) bypassed NOFILE limit\n", + test); + else + printf("ok %d - fcntl(F_DUP2FD) didn't bypass NOFILE limit\n", + test); + + /* Does fcntl(F_DUPFD_CLOEXEC) work? */ + if ((fd2 = fcntl(fd1, F_DUPFD_CLOEXEC, 10)) < 0) + err(1, "fcntl(F_DUPFD_CLOEXEC)"); + if (fd2 < 10) + printf("not ok %d - fcntl(F_DUPFD_CLOEXEC) returned wrong fd %d\n", + ++test, fd2); + else + printf("ok %d - fcntl(F_DUPFD_CLOEXEC) works\n", ++test); + + /* Was close-on-exec cleared? */ + ++test; + if (fcntl(fd2, F_GETFD) != 1) + printf( + "not ok %d - fcntl(F_DUPFD_CLOEXEC) didn't set close-on-exec\n", + test); + else + printf("ok %d - fcntl(F_DUPFD_CLOEXEC) set close-on-exec\n", + test); + + /* If fcntl(F_DUP2FD_CLOEXEC) ever work? */ + if ((fd2 = fcntl(fd1, F_DUP2FD_CLOEXEC, fd1 + 1)) < 0) + err(1, "fcntl(F_DUP2FD_CLOEXEC)"); + printf("ok %d - fcntl(F_DUP2FD_CLOEXEC) works\n", ++test); + + /* Do we get the right fd? */ + ++test; + if (fd2 != fd1 + 1) + printf( + "no ok %d - fcntl(F_DUP2FD_CLOEXEC) didn't give us the right fd\n", + test); + else + printf("ok %d - fcntl(F_DUP2FD_CLOEXEC) returned a correct fd\n", + test); + + /* Was close-on-exec set? */ + ++test; + if (fcntl(fd2, F_GETFD) != FD_CLOEXEC) + printf( + "not ok %d - fcntl(F_DUP2FD_CLOEXEC) didn't set close-on-exec\n", + test); + else + printf("ok %d - fcntl(F_DUP2FD_CLOEXEC) set close-on-exec\n", + test); + + /* + * It is unclear what F_DUP2FD_CLOEXEC should do when duplicating a + * file descriptor onto itself. + */ + + ++test; + if (getrlimit(RLIMIT_NOFILE, &rlp) < 0) + err(1, "getrlimit"); + if ((fd2 = fcntl(fd1, F_DUP2FD_CLOEXEC, rlp.rlim_cur + 1)) >= 0) + printf("not ok %d - fcntl(F_DUP2FD_CLOEXEC) bypassed NOFILE limit\n", + test); + else + printf("ok %d - fcntl(F_DUP2FD_CLOEXEC) didn't bypass NOFILE limit\n", + test); + + /* Does dup3(O_CLOEXEC) ever work? */ + if ((fd2 = dup3(fd1, fd1 + 1, O_CLOEXEC)) < 0) + err(1, "dup3(O_CLOEXEC)"); + printf("ok %d - dup3(O_CLOEXEC) works\n", ++test); + + /* Do we get the right fd? */ + ++test; + if (fd2 != fd1 + 1) + printf( + "no ok %d - dup3(O_CLOEXEC) didn't give us the right fd\n", + test); + else + printf("ok %d - dup3(O_CLOEXEC) returned a correct fd\n", + test); + + /* Was close-on-exec set? */ + ++test; + if (fcntl(fd2, F_GETFD) != FD_CLOEXEC) + printf( + "not ok %d - dup3(O_CLOEXEC) didn't set close-on-exec\n", + test); + else + printf("ok %d - dup3(O_CLOEXEC) set close-on-exec\n", + test); + + /* Does dup3(0) ever work? */ + if ((fd2 = dup3(fd1, fd1 + 1, 0)) < 0) + err(1, "dup3(0)"); + printf("ok %d - dup3(0) works\n", ++test); + + /* Do we get the right fd? */ + ++test; + if (fd2 != fd1 + 1) + printf( + "no ok %d - dup3(0) didn't give us the right fd\n", + test); + else + printf("ok %d - dup3(0) returned a correct fd\n", + test); + + /* Was close-on-exec cleared? */ + ++test; + if (fcntl(fd2, F_GETFD) != 0) + printf( + "not ok %d - dup3(0) didn't clear close-on-exec\n", + test); + else + printf("ok %d - dup3(0) cleared close-on-exec\n", + test); + + /* dup3() does not allow duplicating to the same fd */ + ++test; + if (dup3(fd1, fd1, O_CLOEXEC) != -1) + printf( + "not ok %d - dup3(fd1, fd1, O_CLOEXEC) succeeded\n", test); + else + printf("ok %d - dup3(fd1, fd1, O_CLOEXEC) failed\n", test); + + ++test; + if (dup3(fd1, fd1, 0) != -1) + printf( + "not ok %d - dup3(fd1, fd1, 0) succeeded\n", test); + else + printf("ok %d - dup3(fd1, fd1, 0) failed\n", test); + + ++test; + if (getrlimit(RLIMIT_NOFILE, &rlp) < 0) + err(1, "getrlimit"); + if ((fd2 = dup3(fd1, rlp.rlim_cur + 1, O_CLOEXEC)) >= 0) + printf("not ok %d - dup3(O_CLOEXEC) bypassed NOFILE limit\n", + test); + else + printf("ok %d - dup3(O_CLOEXEC) didn't bypass NOFILE limit\n", + test); + + return (0); +} diff --git a/tests/sys/file/fcntlflags_test.c b/tests/sys/file/fcntlflags_test.c new file mode 100644 index 0000000..bcb3b54 --- /dev/null +++ b/tests/sys/file/fcntlflags_test.c @@ -0,0 +1,110 @@ +/*- + * Copyright (c) 2013 Jilles Tjoelker + * 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. + * + * $FreeBSD$ + */ + +#include <sys/cdefs.h> + +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> + +/* + * O_ACCMODE is currently defined incorrectly. This is what it should be. + * Various code depends on the incorrect value. + */ +#define CORRECT_O_ACCMODE (O_ACCMODE | O_EXEC) + +static int testnum; + +static void +subtests(const char *path, int omode, const char *omodetext) +{ + int fd, flags1, flags2, flags3; + + fd = open(path, omode); + if (fd == -1) + printf("not ok %d - open(\"%s\", %s) failed\n", + testnum++, path, omodetext); + else + printf("ok %d - open(\"%s\", %s) succeeded\n", + testnum++, path, omodetext); + flags1 = fcntl(fd, F_GETFL); + if (flags1 == -1) + printf("not ok %d - fcntl(F_GETFL) failed\n", testnum++); + else if ((flags1 & CORRECT_O_ACCMODE) == omode) + printf("ok %d - fcntl(F_GETFL) gave correct result\n", + testnum++); + else + printf("not ok %d - fcntl(F_GETFL) gave incorrect result " + "(%#x & %#x != %#x)\n", + testnum++, flags1, CORRECT_O_ACCMODE, omode); + if (fcntl(fd, F_SETFL, flags1) == -1) + printf("not ok %d - fcntl(F_SETFL) same flags failed\n", + testnum++); + else + printf("ok %d - fcntl(F_SETFL) same flags succeeded\n", + testnum++); + flags2 = fcntl(fd, F_GETFL); + if (flags2 == -1) + printf("not ok %d - fcntl(F_GETFL) failed\n", testnum++); + else if (flags2 == flags1) + printf("ok %d - fcntl(F_GETFL) gave same result\n", + testnum++); + else + printf("not ok %d - fcntl(F_SETFL) caused fcntl(F_GETFL) to " + "change from %#x to %#x\n", + testnum++, flags1, flags2); + if (fcntl(fd, F_SETFL, flags2 | O_NONBLOCK) == -1) + printf("not ok %d - fcntl(F_SETFL) O_NONBLOCK failed\n", + testnum++); + else + printf("ok %d - fcntl(F_SETFL) O_NONBLOCK succeeded\n", + testnum++); + flags3 = fcntl(fd, F_GETFL); + if (flags3 == -1) + printf("not ok %d - fcntl(F_GETFL) failed\n", testnum++); + else if (flags3 == (flags2 | O_NONBLOCK)) + printf("ok %d - fcntl(F_GETFL) gave expected result\n", + testnum++); + else + printf("not ok %d - fcntl(F_SETFL) gave unexpected result " + "(%#x != %#x)\n", + testnum++, flags3, flags2 | O_NONBLOCK); + (void)close(fd); +} + +int +main(int argc __unused, char **argv __unused) +{ + printf("1..24\n"); + testnum = 1; + subtests("/dev/null", O_RDONLY, "O_RDONLY"); + subtests("/dev/null", O_WRONLY, "O_WRONLY"); + subtests("/dev/null", O_RDWR, "O_RDWR"); + subtests("/bin/sh", O_EXEC, "O_EXEC"); + return (0); +} diff --git a/tests/sys/file/flock_helper.c b/tests/sys/file/flock_helper.c new file mode 100644 index 0000000..49e47b8 --- /dev/null +++ b/tests/sys/file/flock_helper.c @@ -0,0 +1,1598 @@ +/*- + * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ + * Authors: Doug Rabson <dfr@rabson.org> + * Developed with Red Inc: Alfred Perlstein <alfred@freebsd.org> + * + * 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. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/file.h> +#include <sys/time.h> +#ifdef __FreeBSD__ +#include <sys/mount.h> +#endif +#include <sys/stat.h> +#include <sys/wait.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef __FreeBSD__ +#if __FreeBSD_version >= 800028 +#define HAVE_SYSID +#endif +#include <sys/cdefs.h> +#else +#ifndef nitems +#define nitems(x) (sizeof((x)) / sizeof((x)[0])) +#endif + +#ifndef __unused +#ifdef __GNUC__ +#define __unused __attribute__((__unused__)) +#else +#define __unused +#endif +#endif +#endif + +static int verbose = 0; + +static int +make_file(const char *pathname, off_t sz) +{ + struct stat st; + const char *template = "/flocktempXXXXXX"; + size_t len; + char *filename; + int fd; + + if (stat(pathname, &st) == 0) { + if (S_ISREG(st.st_mode)) { + fd = open(pathname, O_RDWR); + if (fd < 0) + err(1, "open(%s)", pathname); + if (ftruncate(fd, sz) < 0) + err(1, "ftruncate"); + return (fd); + } + } + + len = strlen(pathname) + strlen(template) + 1; + filename = malloc(len); + strcpy(filename, pathname); + strcat(filename, template); + fd = mkstemp(filename); + if (fd < 0) + err(1, "mkstemp"); + if (ftruncate(fd, sz) < 0) + err(1, "ftruncate"); + if (unlink(filename) < 0) + err(1, "unlink"); + free(filename); + + return (fd); +} + +static void +ignore_alarm(int __unused sig) +{ +} + +static int +safe_waitpid(pid_t pid) +{ + int save_errno; + int status; + + save_errno = errno; + errno = 0; + while (waitpid(pid, &status, 0) != pid) { + if (errno == EINTR) + continue; + err(1, "waitpid"); + } + errno = save_errno; + + return (status); +} + +#define FAIL(test) \ + do { \ + if (test) { \ + printf("FAIL (%s)\n", #test); \ + return -1; \ + } \ + } while (0) + +#define SUCCEED \ + do { printf("SUCCEED\n"); return 0; } while (0) + +/* + * Test 1 - F_GETLK on unlocked region + * + * If no lock is found that would prevent this lock from being + * created, the structure is left unchanged by this function call + * except for the lock type which is set to F_UNLCK. + */ +static int +test1(int fd, __unused int argc, const __unused char **argv) +{ + struct flock fl1, fl2; + + memset(&fl1, 1, sizeof(fl1)); + fl1.l_type = F_WRLCK; + fl1.l_whence = SEEK_SET; + fl2 = fl1; + + if (fcntl(fd, F_GETLK, &fl1) < 0) + err(1, "F_GETLK"); + + printf("1 - F_GETLK on unlocked region: "); + FAIL(fl1.l_start != fl2.l_start); + FAIL(fl1.l_len != fl2.l_len); + FAIL(fl1.l_pid != fl2.l_pid); + FAIL(fl1.l_type != F_UNLCK); + FAIL(fl1.l_whence != fl2.l_whence); +#ifdef HAVE_SYSID + FAIL(fl1.l_sysid != fl2.l_sysid); +#endif + + SUCCEED; +} + +/* + * Test 2 - F_SETLK on locked region + * + * If a shared or exclusive lock cannot be set, fcntl returns + * immediately with EACCES or EAGAIN. + */ +static int +test2(int fd, __unused int argc, const __unused char **argv) +{ + /* + * We create a child process to hold the lock which we will + * test. We use a pipe to communicate with the child. + */ + int pid; + int pfd[2]; + struct flock fl; + char ch; + int res; + + if (pipe(pfd) < 0) + err(1, "pipe"); + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + + pid = fork(); + if (pid < 0) + err(1, "fork"); + + if (pid == 0) { + /* + * We are the child. We set a write lock and then + * write one byte back to the parent to tell it. The + * parent will kill us when its done. + */ + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_SETLK (child)"); + if (write(pfd[1], "a", 1) < 0) + err(1, "writing to pipe (child)"); + pause(); + exit(0); + } + + /* + * Wait until the child has set its lock and then perform the + * test. + */ + if (read(pfd[0], &ch, 1) != 1) + err(1, "reading from pipe (child)"); + + /* + * fcntl should return -1 with errno set to either EACCES or + * EAGAIN. + */ + printf("2 - F_SETLK on locked region: "); + res = fcntl(fd, F_SETLK, &fl); + kill(pid, SIGTERM); + safe_waitpid(pid); + close(pfd[0]); + close(pfd[1]); + FAIL(res == 0); + FAIL(errno != EACCES && errno != EAGAIN); + + SUCCEED; +} + +/* + * Test 3 - F_SETLKW on locked region + * + * If a shared or exclusive lock is blocked by other locks, the + * process waits until the request can be satisfied. + * + * XXX this test hangs on FreeBSD NFS filesystems due to limitations + * in FreeBSD's client (and server) lockd implementation. + */ +static int +test3(int fd, __unused int argc, const __unused char **argv) +{ + /* + * We create a child process to hold the lock which we will + * test. We use a pipe to communicate with the child. + */ + int pid; + int pfd[2]; + struct flock fl; + char ch; + int res; + + if (pipe(pfd) < 0) + err(1, "pipe"); + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + + pid = fork(); + if (pid < 0) + err(1, "fork"); + + if (pid == 0) { + /* + * We are the child. We set a write lock and then + * write one byte back to the parent to tell it. The + * parent will kill us when its done. + */ + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_SETLK (child)"); + if (write(pfd[1], "a", 1) < 0) + err(1, "writing to pipe (child)"); + pause(); + exit(0); + } + + /* + * Wait until the child has set its lock and then perform the + * test. + */ + if (read(pfd[0], &ch, 1) != 1) + err(1, "reading from pipe (child)"); + + /* + * fcntl should wait until the alarm and then return -1 with + * errno set to EINTR. + */ + printf("3 - F_SETLKW on locked region: "); + + alarm(1); + + res = fcntl(fd, F_SETLKW, &fl); + kill(pid, SIGTERM); + safe_waitpid(pid); + close(pfd[0]); + close(pfd[1]); + FAIL(res == 0); + FAIL(errno != EINTR); + + SUCCEED; +} + +/* + * Test 4 - F_GETLK on locked region + * + * Get the first lock that blocks the lock. + */ +static int +test4(int fd, __unused int argc, const __unused char **argv) +{ + /* + * We create a child process to hold the lock which we will + * test. We use a pipe to communicate with the child. + */ + int pid; + int pfd[2]; + struct flock fl; + char ch; + + if (pipe(pfd) < 0) + err(1, "pipe"); + + fl.l_start = 0; + fl.l_len = 99; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + + pid = fork(); + if (pid < 0) + err(1, "fork"); + + if (pid == 0) { + /* + * We are the child. We set a write lock and then + * write one byte back to the parent to tell it. The + * parent will kill us when its done. + */ + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_SETLK (child)"); + if (write(pfd[1], "a", 1) < 0) + err(1, "writing to pipe (child)"); + pause(); + exit(0); + } + + /* + * Wait until the child has set its lock and then perform the + * test. + */ + if (read(pfd[0], &ch, 1) != 1) + err(1, "reading from pipe (child)"); + + /* + * fcntl should return a lock structure reflecting the lock we + * made in the child process. + */ + if (fcntl(fd, F_GETLK, &fl) < 0) + err(1, "F_GETLK"); + + printf("4 - F_GETLK on locked region: "); + FAIL(fl.l_start != 0); + FAIL(fl.l_len != 99); + FAIL(fl.l_type != F_WRLCK); + FAIL(fl.l_pid != pid); +#ifdef HAVE_SYSID + FAIL(fl.l_sysid != 0); +#endif + + kill(pid, SIGTERM); + safe_waitpid(pid); + close(pfd[0]); + close(pfd[1]); + + SUCCEED; +} + +/* + * Test 5 - F_SETLKW simple deadlock + * + * If a blocking shared lock request would cause a deadlock (i.e. the + * lock request is blocked by a process which is itself blocked on a + * lock currently owned by the process making the new request), + * EDEADLK is returned. + */ +static int +test5(int fd, __unused int argc, const __unused char **argv) +{ + /* + * We create a child process to hold the lock which we will + * test. Because our test relies on the child process being + * blocked on the parent's lock, we can't easily use a pipe to + * synchronize so we just sleep in the parent to given the + * child a chance to setup. + * + * To create the deadlock condition, we arrange for the parent + * to lock the first byte of the file and the child to lock + * the second byte. After locking the second byte, the child + * will attempt to lock the first byte of the file, and + * block. The parent will then attempt to lock the second byte + * (owned by the child) which should cause deadlock. + */ + int pid; + struct flock fl; + int res; + + /* + * Lock the first byte in the parent. + */ + fl.l_start = 0; + fl.l_len = 1; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_SETLK 1 (parent)"); + + pid = fork(); + if (pid < 0) + err(1, "fork"); + + if (pid == 0) { + /* + * Lock the second byte in the child and then block on + * the parent's lock. + */ + fl.l_start = 1; + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_SETLK (child)"); + fl.l_start = 0; + if (fcntl(fd, F_SETLKW, &fl) < 0) + err(1, "F_SETLKW (child)"); + exit(0); + } + + /* + * Wait until the child has set its lock and then perform the + * test. + */ + sleep(1); + + /* + * fcntl should immediately return -1 with errno set to + * EDEADLK. If the alarm fires, we failed to detect the + * deadlock. + */ + alarm(1); + printf("5 - F_SETLKW simple deadlock: "); + + fl.l_start = 1; + res = fcntl(fd, F_SETLKW, &fl); + kill(pid, SIGTERM); + safe_waitpid(pid); + + FAIL(res == 0); + FAIL(errno != EDEADLK); + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_UNLCK; + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_UNLCK"); + + /* + * Cancel the alarm to avoid confusing later tests. + */ + alarm(0); + + SUCCEED; +} + +/* + * Test 6 - F_SETLKW complex deadlock. + * + * This test involves three process, P, C1 and C2. We set things up so + * that P locks byte zero, C1 locks byte 1 and C2 locks byte 2. We + * also block C2 by attempting to lock byte zero. Lastly, P attempts + * to lock a range including byte 1 and 2. This represents a deadlock + * (due to C2's blocking attempt to lock byte zero). + */ +static int +test6(int fd, __unused int argc, const __unused char **argv) +{ + /* + * Because our test relies on the child process being blocked + * on the parent's lock, we can't easily use a pipe to + * synchronize so we just sleep in the parent to given the + * children a chance to setup. + */ + int pid1, pid2; + struct flock fl; + int res; + + /* + * Lock the first byte in the parent. + */ + fl.l_start = 0; + fl.l_len = 1; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_SETLK 1 (parent)"); + + pid1 = fork(); + if (pid1 < 0) + err(1, "fork"); + + if (pid1 == 0) { + /* + * C1 + * Lock the second byte in the child and then sleep + */ + fl.l_start = 1; + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_SETLK (child1)"); + pause(); + exit(0); + } + + pid2 = fork(); + if (pid2 < 0) + err(1, "fork"); + + if (pid2 == 0) { + /* + * C2 + * Lock the third byte in the child and then block on + * the parent's lock. + */ + fl.l_start = 2; + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_SETLK (child2)"); + fl.l_start = 0; + if (fcntl(fd, F_SETLKW, &fl) < 0) + err(1, "F_SETLKW (child2)"); + exit(0); + } + + /* + * Wait until the children have set their locks and then + * perform the test. + */ + sleep(1); + + /* + * fcntl should immediately return -1 with errno set to + * EDEADLK. If the alarm fires, we failed to detect the + * deadlock. + */ + alarm(1); + printf("6 - F_SETLKW complex deadlock: "); + + fl.l_start = 1; + fl.l_len = 2; + res = fcntl(fd, F_SETLKW, &fl); + kill(pid1, SIGTERM); + safe_waitpid(pid1); + kill(pid2, SIGTERM); + safe_waitpid(pid2); + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_UNLCK; + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_UNLCK"); + + FAIL(res == 0); + FAIL(errno != EDEADLK); + + /* + * Cancel the alarm to avoid confusing later tests. + */ + alarm(0); + + SUCCEED; +} + +/* + * Test 7 - F_SETLK shared lock on exclusive locked region + * + * If a shared or exclusive lock cannot be set, fcntl returns + * immediately with EACCES or EAGAIN. + */ +static int +test7(int fd, __unused int argc, const __unused char **argv) +{ + /* + * We create a child process to hold the lock which we will + * test. We use a pipe to communicate with the child. + */ + int pid; + int pfd[2]; + struct flock fl; + char ch; + int res; + + if (pipe(pfd) < 0) + err(1, "pipe"); + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + + pid = fork(); + if (pid < 0) + err(1, "fork"); + + if (pid == 0) { + /* + * We are the child. We set a write lock and then + * write one byte back to the parent to tell it. The + * parent will kill us when its done. + */ + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_SETLK (child)"); + if (write(pfd[1], "a", 1) < 0) + err(1, "writing to pipe (child)"); + pause(); + exit(0); + } + + /* + * Wait until the child has set its lock and then perform the + * test. + */ + if (read(pfd[0], &ch, 1) != 1) + err(1, "reading from pipe (child)"); + + /* + * fcntl should wait until the alarm and then return -1 with + * errno set to EINTR. + */ + printf("7 - F_SETLK shared lock on exclusive locked region: "); + + fl.l_type = F_RDLCK; + res = fcntl(fd, F_SETLK, &fl); + kill(pid, SIGTERM); + safe_waitpid(pid); + close(pfd[0]); + close(pfd[1]); + + FAIL(res == 0); + FAIL(errno != EACCES && errno != EAGAIN); + + SUCCEED; +} + +/* + * Test 8 - F_SETLK shared lock on share locked region + * + * When a shared lock is set on a segment of a file, other processes + * shall be able to set shared locks on that segment or a portion of + * it. + */ +static int +test8(int fd, __unused int argc, const __unused char **argv) +{ + /* + * We create a child process to hold the lock which we will + * test. We use a pipe to communicate with the child. + */ + int pid; + int pfd[2]; + struct flock fl; + char ch; + int res; + + if (pipe(pfd) < 0) + err(1, "pipe"); + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + + pid = fork(); + if (pid < 0) + err(1, "fork"); + + if (pid == 0) { + /* + * We are the child. We set a write lock and then + * write one byte back to the parent to tell it. The + * parent will kill us when its done. + */ + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_SETLK (child)"); + if (write(pfd[1], "a", 1) < 0) + err(1, "writing to pipe (child)"); + pause(); + exit(0); + } + + /* + * Wait until the child has set its lock and then perform the + * test. + */ + if (read(pfd[0], &ch, 1) != 1) + err(1, "reading from pipe (child)"); + + /* + * fcntl should wait until the alarm and then return -1 with + * errno set to EINTR. + */ + printf("8 - F_SETLK shared lock on share locked region: "); + + fl.l_type = F_RDLCK; + res = fcntl(fd, F_SETLK, &fl); + + kill(pid, SIGTERM); + safe_waitpid(pid); + close(pfd[0]); + close(pfd[1]); + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_UNLCK; + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_UNLCK"); + + FAIL(res != 0); + + SUCCEED; +} + +/* + * Test 9 - F_SETLK exclusive lock on share locked region + * + * If a shared or exclusive lock cannot be set, fcntl returns + * immediately with EACCES or EAGAIN. + */ +static int +test9(int fd, __unused int argc, const __unused char **argv) +{ + /* + * We create a child process to hold the lock which we will + * test. We use a pipe to communicate with the child. + */ + int pid; + int pfd[2]; + struct flock fl; + char ch; + int res; + + if (pipe(pfd) < 0) + err(1, "pipe"); + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + + pid = fork(); + if (pid < 0) + err(1, "fork"); + + if (pid == 0) { + /* + * We are the child. We set a write lock and then + * write one byte back to the parent to tell it. The + * parent will kill us when its done. + */ + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_SETLK (child)"); + if (write(pfd[1], "a", 1) < 0) + err(1, "writing to pipe (child)"); + pause(); + exit(0); + } + + /* + * Wait until the child has set its lock and then perform the + * test. + */ + if (read(pfd[0], &ch, 1) != 1) + err(1, "reading from pipe (child)"); + + /* + * fcntl should wait until the alarm and then return -1 with + * errno set to EINTR. + */ + printf("9 - F_SETLK exclusive lock on share locked region: "); + + fl.l_type = F_WRLCK; + res = fcntl(fd, F_SETLK, &fl); + kill(pid, SIGTERM); + safe_waitpid(pid); + close(pfd[0]); + close(pfd[1]); + + FAIL(res == 0); + FAIL(errno != EACCES && errno != EAGAIN); + + SUCCEED; +} + +/* + * Test 10 - trying to set bogus pid or sysid values + * + * The l_pid and l_sysid fields are only used with F_GETLK to return + * the process ID of the process holding a blocking lock and the + * system ID of the system that owns that process + */ +static int +test10(int fd, __unused int argc, const __unused char **argv) +{ + /* + * We create a child process to hold the lock which we will + * test. We use a pipe to communicate with the child. + */ + int pid; + int pfd[2]; + struct flock fl; + char ch; + + if (pipe(pfd) < 0) + err(1, "pipe"); + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_pid = 9999; +#ifdef HAVE_SYSID + fl.l_sysid = 9999; +#endif + + pid = fork(); + if (pid < 0) + err(1, "fork"); + + if (pid == 0) { + /* + * We are the child. We set a write lock and then + * write one byte back to the parent to tell it. The + * parent will kill us when its done. + */ + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_SETLK (child)"); + if (write(pfd[1], "a", 1) < 0) + err(1, "writing to pipe (child)"); + pause(); + exit(0); + } + + /* + * Wait until the child has set its lock and then perform the + * test. + */ + if (read(pfd[0], &ch, 1) != 1) + err(1, "reading from pipe (child)"); + + printf("10 - trying to set bogus pid or sysid values: "); + + if (fcntl(fd, F_GETLK, &fl) < 0) + err(1, "F_GETLK"); + + kill(pid, SIGTERM); + safe_waitpid(pid); + close(pfd[0]); + close(pfd[1]); + + FAIL(fl.l_pid != pid); +#ifdef HAVE_SYSID + FAIL(fl.l_sysid != 0); +#endif + + SUCCEED; +} + +/* + * Test 11 - remote locks + * + * XXX temporary interface which will be removed when the kernel lockd + * is added. + */ +static int +test11(int fd, __unused int argc, const __unused char **argv) +{ +#ifdef F_SETLK_REMOTE + struct flock fl; + int res; + + if (geteuid() != 0) + return 0; + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_pid = 9999; + fl.l_sysid = 1001; + + printf("11 - remote locks: "); + + res = fcntl(fd, F_SETLK_REMOTE, &fl); + FAIL(res != 0); + + fl.l_sysid = 1002; + res = fcntl(fd, F_SETLK_REMOTE, &fl); + FAIL(res == 0); + FAIL(errno != EACCES && errno != EAGAIN); + + res = fcntl(fd, F_GETLK, &fl); + FAIL(res != 0); + FAIL(fl.l_pid != 9999); + FAIL(fl.l_sysid != 1001); + + fl.l_type = F_UNLCK; + fl.l_sysid = 1001; + fl.l_start = 0; + fl.l_len = 0; + res = fcntl(fd, F_SETLK_REMOTE, &fl); + FAIL(res != 0); + + fl.l_pid = 1234; + fl.l_sysid = 1001; + fl.l_start = 0; + fl.l_len = 1; + fl.l_whence = SEEK_SET; + fl.l_type = F_RDLCK; + res = fcntl(fd, F_SETLK_REMOTE, &fl); + FAIL(res != 0); + + fl.l_sysid = 1002; + res = fcntl(fd, F_SETLK_REMOTE, &fl); + FAIL(res != 0); + + fl.l_type = F_UNLCKSYS; + fl.l_sysid = 1001; + res = fcntl(fd, F_SETLK_REMOTE, &fl); + FAIL(res != 0); + + fl.l_type = F_WRLCK; + res = fcntl(fd, F_GETLK, &fl); + FAIL(res != 0); + FAIL(fl.l_pid != 1234); + FAIL(fl.l_sysid != 1002); + + fl.l_type = F_UNLCKSYS; + fl.l_sysid = 1002; + res = fcntl(fd, F_SETLK_REMOTE, &fl); + FAIL(res != 0); + + SUCCEED; +#else + return 0; +#endif +} + +/* + * Test 12 - F_SETLKW on locked region which is then unlocked + * + * If a shared or exclusive lock is blocked by other locks, the + * process waits until the request can be satisfied. + */ +static int +test12(int fd, __unused int argc, const __unused char **argv) +{ + /* + * We create a child process to hold the lock which we will + * test. We use a pipe to communicate with the child. + */ + int pid; + int pfd[2]; + struct flock fl; + char ch; + int res; + + if (pipe(pfd) < 0) + err(1, "pipe"); + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + + pid = fork(); + if (pid < 0) + err(1, "fork"); + + if (pid == 0) { + /* + * We are the child. We set a write lock and then + * write one byte back to the parent to tell it. The + * parent will kill us when its done. + */ + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_SETLK (child)"); + if (write(pfd[1], "a", 1) < 0) + err(1, "writing to pipe (child)"); + + sleep(1); + exit(0); + } + + /* + * Wait until the child has set its lock and then perform the + * test. + */ + if (read(pfd[0], &ch, 1) != 1) + err(1, "reading from pipe (child)"); + + /* + * fcntl should wait until the alarm and then return -1 with + * errno set to EINTR. + */ + printf("12 - F_SETLKW on locked region which is then unlocked: "); + + //alarm(1); + + res = fcntl(fd, F_SETLKW, &fl); + kill(pid, SIGTERM); + safe_waitpid(pid); + close(pfd[0]); + close(pfd[1]); + FAIL(res != 0); + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_UNLCK; + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_UNLCK"); + + SUCCEED; +} + +/* + * Test 13 - F_SETLKW on locked region, race with owner + * + * If a shared or exclusive lock is blocked by other locks, the + * process waits until the request can be satisfied. + */ +static int +test13(int fd, __unused int argc, const __unused char **argv) +{ + /* + * We create a child process to hold the lock which we will + * test. We use a pipe to communicate with the child. + */ + int i; + int pid; + int pfd[2]; + struct flock fl; + char ch; + int res; + struct itimerval itv; + + printf("13 - F_SETLKW on locked region, race with owner: "); + fflush(stdout); + + for (i = 0; i < 100; i++) { + if (pipe(pfd) < 0) + err(1, "pipe"); + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + + pid = fork(); + if (pid < 0) + err(1, "fork"); + + if (pid == 0) { + /* + * We are the child. We set a write lock and then + * write one byte back to the parent to tell it. The + * parent will kill us when its done. + */ + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_SETLK (child)"); + if (write(pfd[1], "a", 1) < 0) + err(1, "writing to pipe (child)"); + + usleep(1); + exit(0); + } + + /* + * Wait until the child has set its lock and then perform the + * test. + */ + while (read(pfd[0], &ch, 1) != 1) { + if (errno == EINTR) + continue; + err(1, "reading from pipe (child)"); + } + + /* + * fcntl should wait until the alarm and then return -1 with + * errno set to EINTR. + */ + itv.it_interval.tv_sec = 0; + itv.it_interval.tv_usec = 0; + itv.it_value.tv_sec = 0; + itv.it_value.tv_usec = 2; + setitimer(ITIMER_REAL, &itv, NULL); + + res = fcntl(fd, F_SETLKW, &fl); + kill(pid, SIGTERM); + safe_waitpid(pid); + close(pfd[0]); + close(pfd[1]); + FAIL(!(res == 0 || (res == -1 && errno == EINTR))); + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_UNLCK; + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "F_UNLCK"); + } + SUCCEED; +} + +/* + * Test 14 - soak test + */ +static int +test14(int fd, int argc, const char **argv) +{ +#define CHILD_COUNT 20 + /* + * We create a set of child processes and let each one run + * through a random sequence of locks and unlocks. + */ + int i, j, id, id_base; + int pids[CHILD_COUNT], pid; + char buf[128]; + char tbuf[128]; + int map[128]; + char outbuf[512]; + struct flock fl; + struct itimerval itv; + int status; + + id_base = 0; + if (argc >= 2) + id_base = strtol(argv[1], NULL, 0); + + printf("14 - soak test: "); + fflush(stdout); + + for (i = 0; i < 128; i++) + map[i] = F_UNLCK; + + for (i = 0; i < CHILD_COUNT; i++) { + + pid = fork(); + if (pid < 0) + err(1, "fork"); + if (pid) { + /* + * Parent - record the pid and continue. + */ + pids[i] = pid; + continue; + } + + /* + * Child - do some work and exit. + */ + id = id_base + i; + srandom(getpid()); + + for (j = 0; j < 50; j++) { + int start, end, len; + int set, wrlock; + + do { + start = random() & 127; + end = random() & 127; + } while (end <= start); + + set = random() & 1; + wrlock = random() & 1; + + len = end - start; + fl.l_start = start; + fl.l_len = len; + fl.l_whence = SEEK_SET; + if (set) + fl.l_type = wrlock ? F_WRLCK : F_RDLCK; + else + fl.l_type = F_UNLCK; + + itv.it_interval.tv_sec = 0; + itv.it_interval.tv_usec = 0; + itv.it_value.tv_sec = 0; + itv.it_value.tv_usec = 3000; + setitimer(ITIMER_REAL, &itv, NULL); + + if (fcntl(fd, F_SETLKW, &fl) < 0) { + if (errno == EDEADLK || errno == EINTR) { + if (verbose) { + snprintf(outbuf, sizeof(outbuf), + "%d[%d]: %s [%d .. %d] %s\n", + id, j, + set ? (wrlock ? "write lock" + : "read lock") + : "unlock", start, end, + errno == EDEADLK + ? "deadlock" + : "interrupted"); + write(1, outbuf, + strlen(outbuf)); + } + continue; + } else { + perror("fcntl"); + } + } + + itv.it_interval.tv_sec = 0; + itv.it_interval.tv_usec = 0; + itv.it_value.tv_sec = 0; + itv.it_value.tv_usec = 0; + setitimer(ITIMER_REAL, &itv, NULL); + + if (verbose) { + snprintf(outbuf, sizeof(outbuf), + "%d[%d]: %s [%d .. %d] succeeded\n", + id, j, + set ? (wrlock ? "write lock" : "read lock") + : "unlock", start, end); + write(1, outbuf, strlen(outbuf)); + } + + if (set) { + if (wrlock) { + /* + * We got a write lock - write + * our ID to each byte that we + * managed to claim. + */ + for (i = start; i < end; i++) + map[i] = F_WRLCK; + memset(&buf[start], id, len); + if (pwrite(fd, &buf[start], len, + start) != len) { + printf("%d: short write\n", id); + exit(1); + } + } else { + /* + * We got a read lock - read + * the bytes which we claimed + * so that we can check that + * they don't change + * unexpectedly. + */ + for (i = start; i < end; i++) + map[i] = F_RDLCK; + if (pread(fd, &buf[start], len, + start) != len) { + printf("%d: short read\n", id); + exit(1); + } + } + } else { + for (i = start; i < end; i++) + map[i] = F_UNLCK; + } + + usleep(1000); + + /* + * Read back the whole region so that we can + * check that all the bytes we have some kind + * of claim to have the correct value. + */ + if (pread(fd, tbuf, sizeof(tbuf), 0) != sizeof(tbuf)) { + printf("%d: short read\n", id); + exit(1); + } + + for (i = 0; i < 128; i++) { + if (map[i] != F_UNLCK && buf[i] != tbuf[i]) { + snprintf(outbuf, sizeof(outbuf), + "%d: byte %d expected %d, " + "got %d\n", id, i, buf[i], tbuf[i]); + write(1, outbuf, strlen(outbuf)); + exit(1); + } + } + } + if (verbose) + printf("%d[%d]: done\n", id, j); + + exit(0); + } + + status = 0; + for (i = 0; i < CHILD_COUNT; i++) { + status += safe_waitpid(pids[i]); + } + if (status) + FAIL(status != 0); + + SUCCEED; +} + +/* + * Test 15 - flock(2) semantcs + * + * When a lock holder has a shared lock and attempts to upgrade that + * shared lock to exclusive, it must drop the shared lock before + * blocking on the exclusive lock. + * + * To test this, we first arrange for two shared locks on the file, + * and then attempt to upgrade one of them to exclusive. This should + * drop one of the shared locks and block. We interrupt the blocking + * lock request and examine the lock state of the file after dropping + * the other shared lock - there should be no active locks at this + * point. + */ +static int +test15(int fd, __unused int argc, const __unused char **argv) +{ +#ifdef LOCK_EX + /* + * We create a child process to hold the lock which we will + * test. We use a pipe to communicate with the child. + * + * Since we only have one file descriptors and lock ownership + * for flock(2) goes with the file descriptor, we use fcntl to + * set the child's shared lock. + */ + int pid; + int pfd[2]; + struct flock fl; + char ch; + int res; + + if (pipe(pfd) < 0) + err(1, "pipe"); + + pid = fork(); + if (pid < 0) + err(1, "fork"); + + if (pid == 0) { + /* + * We are the child. We set a shared lock and then + * write one byte back to the parent to tell it. The + * parent will kill us when its done. + */ + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_RDLCK; + fl.l_whence = SEEK_SET; + if (fcntl(fd, F_SETLK, &fl) < 0) + err(1, "fcntl(F_SETLK) (child)"); + if (write(pfd[1], "a", 1) < 0) + err(1, "writing to pipe (child)"); + pause(); + exit(0); + } + + /* + * Wait until the child has set its lock and then perform the + * test. + */ + if (read(pfd[0], &ch, 1) != 1) + err(1, "reading from pipe (child)"); + + (void)dup(fd); + if (flock(fd, LOCK_SH) < 0) + err(1, "flock shared"); + + /* + * flock should wait until the alarm and then return -1 with + * errno set to EINTR. + */ + printf("15 - flock(2) semantics: "); + + alarm(1); + flock(fd, LOCK_EX); + + /* + * Kill the child to force it to drop its locks. + */ + kill(pid, SIGTERM); + safe_waitpid(pid); + + fl.l_start = 0; + fl.l_len = 0; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + res = fcntl(fd, F_GETLK, &fl); + + close(pfd[0]); + close(pfd[1]); + FAIL(res != 0); + FAIL(fl.l_type != F_UNLCK); + + SUCCEED; +#else + return 0; +#endif +} + +struct test_ctx { + struct flock tc_fl; + int tc_fd; +}; + +static void * +test16_func(void *tc_in) +{ + uintptr_t error; + struct test_ctx *tc = tc_in; + + error = fcntl(tc->tc_fd, F_SETLKW, &tc->tc_fl); + + pthread_exit((void *)error); +} + +#define THREADS 10 + +/* + * Test 16 - F_SETLKW from two threads + * + * If two threads within a process are blocked on a lock and the lock + * is granted, make sure things are sane. + */ +static int +test16(int fd, __unused int argc, const __unused char **argv) +{ + /* + * We create a child process to hold the lock which we will + * test. We use a pipe to communicate with the child. + */ + int pid; + int pfd[2]; + struct test_ctx tc = { .tc_fd = fd }; + char ch; + int i; + int error; + pthread_t thr[THREADS]; + + if (pipe(pfd) < 0) + err(1, "pipe"); + + tc.tc_fl.l_start = 0; + tc.tc_fl.l_len = 0; + tc.tc_fl.l_type = F_WRLCK; + tc.tc_fl.l_whence = SEEK_SET; + + pid = fork(); + if (pid < 0) + err(1, "fork"); + + if (pid == 0) { + /* + * We are the child. We set a write lock and then + * write one byte back to the parent to tell it. The + * parent will kill us when its done. + */ + if (fcntl(fd, F_SETLK, &tc.tc_fl) < 0) + err(1, "F_SETLK (child)"); + if (write(pfd[1], "a", 1) < 0) + err(1, "writing to pipe (child)"); + pause(); + exit(0); + } + + /* + * Wait until the child has set its lock and then perform the + * test. + */ + if (read(pfd[0], &ch, 1) != 1) + err(1, "reading from pipe (child)"); + + /* + * fcntl should wait until the alarm and then return -1 with + * errno set to EINTR. + */ + printf("16 - F_SETLKW on locked region by two threads: "); + + for (i = 0; i < THREADS; i++) { + error = pthread_create(&thr[i], NULL, test16_func, &tc); + if (error) + err(1, "pthread_create"); + } + + /* + * Sleep, then kill the child. This makes me a little sad, but it's + * tricky to tell whether the threads are all really blocked by this + * point. + */ + sleep(1); + kill(pid, SIGTERM); + safe_waitpid(pid); + close(pfd[0]); + close(pfd[1]); + + for (i = 0; i < THREADS; i++) { + void *res; + error = pthread_join(thr[i], &res); + if (error) + err(1, "pthread_join"); + FAIL((uintptr_t)res != 0); + } + + SUCCEED; +} + +struct test { + int (*testfn)(int, int, const char **); /* function to perform the test */ + int num; /* test number */ + int intr; /* non-zero if the test interrupts a lock */ +}; + +static struct test tests[] = { + { test1, 1, 0 }, + { test2, 2, 0 }, + { test3, 3, 1 }, + { test4, 4, 0 }, + { test5, 5, 1 }, + { test6, 6, 1 }, + { test7, 7, 0 }, + { test8, 8, 0 }, + { test9, 9, 0 }, + { test10, 10, 0 }, + { test11, 11, 1 }, + { test12, 12, 0 }, + { test13, 13, 1 }, + { test14, 14, 0 }, + { test15, 15, 1 }, + { test16, 16, 1 }, +}; + +int +main(int argc, const char *argv[]) +{ + int testnum; + int fd; + int nointr; + unsigned i; + struct sigaction sa; + int test_argc; + const char **test_argv; + + if (argc < 2) { + errx(1, "usage: flock <directory> [test number] ..."); + } + + fd = make_file(argv[1], 1024); + if (argc >= 3) { + testnum = strtol(argv[2], NULL, 0); + test_argc = argc - 2; + test_argv = argv + 2; + } else { + testnum = 0; + test_argc = 0; + test_argv = 0; + } + + sa.sa_handler = ignore_alarm; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGALRM, &sa, 0); + + nointr = 0; +#if defined(__FreeBSD__) && __FreeBSD_version < 800040 + { + /* + * FreeBSD with userland NLM can't interrupt a blocked + * lock request on an NFS mounted filesystem. + */ + struct statfs st; + fstatfs(fd, &st); + nointr = !strcmp(st.f_fstypename, "nfs"); + } +#endif + + for (i = 0; i < nitems(tests); i++) { + if (tests[i].intr && nointr) + continue; + if (!testnum || tests[i].num == testnum) + tests[i].testfn(fd, test_argc, test_argv); + } + + return 0; +} diff --git a/tests/sys/file/flock_test.sh b/tests/sys/file/flock_test.sh new file mode 100755 index 0000000..ac3e799 --- /dev/null +++ b/tests/sys/file/flock_test.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# +# Copyright 2014 EMC Corp. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of Google Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT +# OWNER 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. +# +# $FreeBSD$ + +# Testcase # 11 is racy; uses an undocumented kernel interface for testing +# locking +# Testcase # 16 is racy/doesn't handle EINTR properly +last_testcase=16 + +echo "1..$last_testcase" + +for n in `seq 1 $last_testcase`; do + todomsg="" + + if [ $n -eq 11 ]; then + todomsg=" # TODO: racy testcase" + # Test 16 fails: + # F_SETLKW on locked region by two threads: FAIL ((uintptr_t)res != 0) + elif [ $n -eq 16 ]; then + todomsg=" # TODO: racy testcase (doesn't handle EINTR properly)" + fi + + $(dirname $0)/flock_helper . $n | grep -q SUCCEED + if [ $? -eq 0 ]; then + echo "ok $n$todomsg" + else + echo "not ok $n$todomsg" + fi +done diff --git a/tests/sys/file/ftruncate_test.c b/tests/sys/file/ftruncate_test.c new file mode 100644 index 0000000..7eaba14 --- /dev/null +++ b/tests/sys/file/ftruncate_test.c @@ -0,0 +1,177 @@ +/*- + * Copyright (c) 2006 Robert N. M. Watson + * 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. + * + * $FreeBSD$ + */ + +/* + * Very simple regression test. + * + * Future tests that might be of interest: + * + * - Make sure we get EISDIR on a directory. + */ + +#include <sys/types.h> +#include <sys/event.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <stdio.h> +#include <unistd.h> + +/* + * Select various potentially interesting lengths at and around power of 2 + * edges. + */ +static off_t lengths[] = {0, 1, 2, 3, 4, 127, 128, 129, 511, 512, 513, 1023, + 1024, 1025, 2047, 2048, 2049, 4095, 4096, 4097, 8191, 8192, 8193, 16383, + 16384, 16385}; +static int lengths_count = sizeof(lengths) / sizeof(off_t); + +int +main(int argc, char *argv[]) +{ + int error, fd, fds[2], i, read_only_fd; + char path[PATH_MAX]; + struct stat sb; + ssize_t size; + off_t len; + char ch; + + /* + * Tests using a writable temporary file: grow and then shrink a file + * using ftruncate and various lengths. Make sure that a negative + * file length is rejected. Make sure that when we grow the file, + * bytes now in the range of the file size return 0. + * + * Save a read-only reference to the file to use later for read-only + * descriptor tests. + */ + snprintf(path, PATH_MAX, "/tmp/ftruncate.XXXXXXXXXXXXX"); + fd = mkstemp(path); + if (fd < 0) + err(-1, "mkstemp"); + read_only_fd = open(path, O_RDONLY); + if (read_only_fd < 0) { + error = errno; + (void)unlink(path); + errno = error; + err(-1, "open(%s, O_RDONLY)", path); + } + (void)unlink(path); + + if (ftruncate(fd, -1) == 0) + errx(-1, "ftruncate(fd, -1) succeeded"); + if (errno != EINVAL) + err(-1, "ftruncate(fd, -1) returned wrong error"); + + for (i = 0; i < lengths_count; i++) { + len = lengths[i]; + if (ftruncate(fd, len) < 0) + err(-1, "ftruncate(%jd) up", (intmax_t)len); + if (fstat(fd, &sb) < 0) + err(-1, "stat"); + if (sb.st_size != len) + errx(-1, "fstat with len=%jd returned len %jd up", + (intmax_t)len, (intmax_t)sb.st_size); + if (len != 0) { + size = pread(fd, &ch, sizeof(ch), len - 1); + if (size < 0) + err(-1, "pread on len %jd up", (intmax_t)len); + if (size != sizeof(ch)) + errx(-1, "pread len %jd size %jd up", + (intmax_t)len, (intmax_t)size); + if (ch != 0) + errx(-1, + "pread length %jd size %jd ch %d up", + (intmax_t)len, (intmax_t)size, ch); + } + } + + for (i = lengths_count - 1; i >= 0; i--) { + len = lengths[i]; + if (ftruncate(fd, len) < 0) + err(-1, "ftruncate(%jd) down", (intmax_t)len); + if (fstat(fd, &sb) < 0) + err(-1, "stat"); + if (sb.st_size != len) + errx(-1, "fstat(%jd) returned %jd down", (intmax_t)len, + sb.st_size); + } + close(fd); + + /* + * Make sure that a read-only descriptor can't be truncated. + */ + if (ftruncate(read_only_fd, 0) == 0) + errx(-1, "ftruncate(read_only_fd) succeeded"); + if (errno != EINVAL) + err(-1, "ftruncate(read_only_fd) returned wrong error"); + close(read_only_fd); + + /* + * Make sure that ftruncate on sockets doesn't work. + */ + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + err(-1, "socket(PF_UNIX, SOCK_STREAM, 0)"); + if (ftruncate(fd, 0) == 0) + errx(-1, "ftruncate(socket) succeeded"); + if (errno != EINVAL) + err(-1, "ftruncate(socket) returned wrong error"); + close(fd); + + /* + * Make sure that ftruncate on pipes doesn't work. + */ + if (pipe(fds) < 0) + err(-1, "pipe"); + if (ftruncate(fds[0], 0) == 0) + errx(-1, "ftruncate(pipe) succeeded"); + if (errno != EINVAL) + err(-1, "ftruncate(pipe) returned wrong error"); + close(fds[0]); + close(fds[1]); + + /* + * Make sure that ftruncate on kqueues doesn't work. + */ + fd = kqueue(); + if (fd < 0) + err(-1, "kqueue"); + if (ftruncate(fds[0], 0) == 0) + errx(-1, "ftruncate(kqueue) succeeded"); + if (errno != EINVAL) + err(-1, "ftruncate(kqueue) returned wrong error"); + close(fd); + + return (0); +} diff --git a/tests/sys/file/newfileops_on_fork_test.c b/tests/sys/file/newfileops_on_fork_test.c new file mode 100644 index 0000000..8713a82 --- /dev/null +++ b/tests/sys/file/newfileops_on_fork_test.c @@ -0,0 +1,121 @@ +/*- + * Copyright (c) 2009 Robert N. M. Watson + * All rights reserved. + * + * This software was developed at the University of Cambridge Computer + * Laboratory with support from a grant from Google, Inc. + * + * 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. + * + * $FreeBSD$ + */ + +/* + * When a multi-threaded application calls fork(2) from one thread while + * another thread is blocked in accept(2), we prefer that the file descriptor + * to be returned by accept(2) not appear in the child process. Test this by + * creating a thread blocked in accept(2), then forking a child and seeing if + * the fd it would have returned is defined in the child or not. + */ + +#include <sys/socket.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <err.h> +#include <errno.h> +#include <pthread.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define PORT 9000 + +static int listen_fd; + +static void * +do_accept(__unused void *arg) +{ + int accept_fd; + + accept_fd = accept(listen_fd, NULL, NULL); + if (accept_fd < 0) + err(-1, "accept"); + + return (NULL); +} + +static void +do_fork(void) +{ + int pid; + + pid = fork(); + if (pid < 0) + err(-1, "fork"); + if (pid > 0) { + waitpid(pid, NULL, 0); + exit(0); + } + + /* + * We will call ftruncate(2) on the next available file descriptor, + * listen_fd+1, and get back EBADF if it's not a valid descriptor, + * and EINVAL if it is. This (currently) works fine in practice. + */ + if (ftruncate(listen_fd + 1, 0 < 0)) { + if (errno == EBADF) + exit(0); + else if (errno == EINVAL) + errx(-1, "file descriptor still open in child"); + else + err(-1, "unexpected error"); + } else + errx(-1, "ftruncate succeeded"); +} + +int +main(__unused int argc, __unused char *argv[]) +{ + struct sockaddr_in sin; + pthread_t accept_thread; + + listen_fd = socket(PF_INET, SOCK_STREAM, 0); + if (listen_fd < 0) + err(-1, "socket"); + bzero(&sin, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_len = sizeof(sin); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin.sin_port = htons(PORT); + if (bind(listen_fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) + err(-1, "bind"); + if (listen(listen_fd, -1) <0) + err(-1, "listen"); + if (pthread_create(&accept_thread, NULL, do_accept, NULL) != 0) + err(-1, "pthread_create"); + sleep(1); /* Easier than using a CV. */; + do_fork(); + exit(0); +} diff --git a/tests/sys/kern/execve/Makefile b/tests/sys/kern/execve/Makefile new file mode 100644 index 0000000..82c5d4b --- /dev/null +++ b/tests/sys/kern/execve/Makefile @@ -0,0 +1,39 @@ +# $FreeBSD$ + +TESTSDIR= ${TESTSBASE}/sys/kern/execve + +BINDIR= ${TESTSDIR} + +MAN= + +ATF_TESTS_SH+= execve_test + +PROGS+= good_aout +PROGS+= execve_helper + +LDFLAGS.goodaout+= -static + +CLEANFILES+= empty +CLEANFILES+= sparse_aout +CLEANFILES+= trunc_aout + +SCRIPTS+= bad_interp_len +SCRIPTS+= dev_null_script +SCRIPTS+= empty +SCRIPTS+= good_script +SCRIPTS+= non_exist_shell +SCRIPTS+= script_arg +SCRIPTS+= script_arg_nospace +SCRIPTS+= sparse_aout +SCRIPTS+= trunc_aout + +empty: + @touch $@ + +sparse_aout: + @truncate -s 20480 $@ + +trunc_aout: + @truncate -s 16 $@ + +.include <bsd.test.mk> diff --git a/tests/sys/kern/execve/bad_interp_len b/tests/sys/kern/execve/bad_interp_len new file mode 100644 index 0000000..96c049f --- /dev/null +++ b/tests/sys/kern/execve/bad_interp_len @@ -0,0 +1,4 @@ +#! 456789012345678 0123456789012345 789012345678 012345678901234 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 +# $FreeBSD$ + +echo succeeded diff --git a/tests/sys/kern/execve/dev_null_script b/tests/sys/kern/execve/dev_null_script new file mode 100644 index 0000000..73b1020 --- /dev/null +++ b/tests/sys/kern/execve/dev_null_script @@ -0,0 +1,4 @@ +#! /dev/null +# $FreeBSD$ + +echo succeeded diff --git a/tests/sys/kern/execve/execve_helper.c b/tests/sys/kern/execve/execve_helper.c new file mode 100644 index 0000000..164a8f3 --- /dev/null +++ b/tests/sys/kern/execve/execve_helper.c @@ -0,0 +1,54 @@ +/* $NetBSD: doexec.c,v 1.8 2003/07/26 19:38:48 salo Exp $ */ + +/* + * Copyright (c) 1993 Christopher G. Demetriou + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the + * NetBSD Project. See http://www.NetBSD.org/ for + * information about NetBSD. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. + * + * $FreeBSD$ + */ + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +int +main(int argc, char **argv) +{ + + if (argc != 2) { + fprintf(stderr, "usage: %s <progname>\n", argv[0]); + exit(2); + } + + execve(argv[1], &argv[1], NULL); + err(1, ""); +} diff --git a/tests/sys/kern/execve/execve_test.sh b/tests/sys/kern/execve/execve_test.sh new file mode 100644 index 0000000..ef803a1 --- /dev/null +++ b/tests/sys/kern/execve/execve_test.sh @@ -0,0 +1,115 @@ + +bad_interp_len_head() +{ + atf_set "descr" "Bad interpreter length" +} +bad_interp_len_body() +{ + atf_check -s exit:1 -e 'match:No such file or directory' -o empty \ + -x "cd $(atf_get_srcdir) && ./execve_helper bad_interp_len" +} + +empty_head() +{ + atf_set "descr" "Empty file" +} +empty_body() +{ + atf_check -s exit:1 -e 'match:Exec format error' -o empty \ + -x "cd $(atf_get_srcdir) && ./execve_helper empty" +} + +good_aout_head() +{ + atf_set "descr" "Good a.out" +} +good_aout_body() +{ + atf_check -s exit:0 -e empty -o 'match:succeeded' \ + -x "cd $(atf_get_srcdir) && ./execve_helper ./good_aout" +} + +good_script_head() +{ + atf_set "descr" "Good script" +} +good_script_body() +{ + atf_check -s exit:0 -e empty -o 'match:succeeded' \ + -x "cd $(atf_get_srcdir) && ./execve_helper good_script" +} + +non_exist_head() +{ + atf_set "descr" "Non-existent file" +} +non_exist_body() +{ + atf_check -s exit:1 -e 'match:No such file or directory' -o empty \ + -x "cd $(atf_get_srcdir) && ./execve_helper non_exist" +} + +non_exist_shell_head() +{ + atf_set "descr" "Non-existent shell" +} +non_exist_shell_body() +{ + atf_check -s exit:1 -e 'match:No such file or directory' -o empty \ + -x "cd $(atf_get_srcdir) && ./execve_helper non_exist_shell" +} + +script_arg_head() +{ + atf_set "descr" "-x in the shebang" +} +script_arg_body() +{ + atf_check -s exit:0 -e 'match:\+ echo succeeded' -o 'match:succeeded' \ + -x "cd $(atf_get_srcdir) && ./execve_helper script_arg" +} + +script_arg_nospace_head() +{ + atf_set "descr" '-x in the shebang; no space between #! and /bin/sh' +} +script_arg_nospace_body() +{ + atf_check -s exit:0 -e 'match:\+ echo succeeded' -o 'match:succeeded' \ + -x "cd $(atf_get_srcdir) && ./execve_helper script_arg_nospace" +} + +sparse_aout_head() +{ + atf_set "descr" 'Sparse file' +} +sparse_aout_body() +{ + atf_check -s exit:1 -e 'match:Exec format error' -o empty \ + -x "cd $(atf_get_srcdir) && ./execve_helper sparse_aout" +} + +trunc_aout_head() +{ + atf_set "descr" 'Truncated file' +} +trunc_aout_body() +{ + atf_check -s exit:1 -e 'match:Exec format error' -o empty \ + -x "cd $(atf_get_srcdir) && ./execve_helper trunc_aout" +} + +atf_init_test_cases() +{ + atf_add_test_case bad_interp_len + atf_add_test_case empty + atf_add_test_case good_aout + atf_add_test_case good_script + atf_add_test_case non_exist + atf_add_test_case non_exist_shell + atf_add_test_case script_arg + atf_add_test_case script_arg_nospace + atf_add_test_case sparse_aout + atf_add_test_case trunc_aout + +} diff --git a/tests/sys/kern/execve/good_aout.c b/tests/sys/kern/execve/good_aout.c new file mode 100644 index 0000000..39e9867 --- /dev/null +++ b/tests/sys/kern/execve/good_aout.c @@ -0,0 +1,45 @@ +/* $NetBSD: goodaout.c,v 1.8 2003/07/26 19:38:49 salo Exp $ */ + +/*- + * Copyright (c) 1993 Christopher G. Demetriou + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the + * NetBSD Project. See http://www.NetBSD.org/ for + * information about NetBSD. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. + * + * $FreeBSD$ + */ + +#include <stdio.h> +#include <stdlib.h> + +int +main(void) +{ + printf("succeeded\n"); + exit(0); +} diff --git a/tests/sys/kern/execve/good_script b/tests/sys/kern/execve/good_script new file mode 100644 index 0000000..11c7689 --- /dev/null +++ b/tests/sys/kern/execve/good_script @@ -0,0 +1,4 @@ +#!/bin/sh +# $FreeBSD$ + +echo succeeded diff --git a/tests/sys/kern/execve/non_exist_shell b/tests/sys/kern/execve/non_exist_shell new file mode 100644 index 0000000..f9ee705 --- /dev/null +++ b/tests/sys/kern/execve/non_exist_shell @@ -0,0 +1,4 @@ +#! /foo/bar/baz +# $FreeBSD$ + +echo foo diff --git a/tests/sys/kern/execve/script_arg b/tests/sys/kern/execve/script_arg new file mode 100644 index 0000000..2700f1c --- /dev/null +++ b/tests/sys/kern/execve/script_arg @@ -0,0 +1,4 @@ +#! /bin/sh -x +# $FreeBSD$ + +echo succeeded diff --git a/tests/sys/kern/execve/script_arg_nospace b/tests/sys/kern/execve/script_arg_nospace new file mode 100644 index 0000000..6731ad5 --- /dev/null +++ b/tests/sys/kern/execve/script_arg_nospace @@ -0,0 +1,4 @@ +#!/bin/sh -x +# $FreeBSD$ + +echo succeeded diff --git a/tests/sys/kqueue/Makefile b/tests/sys/kqueue/Makefile new file mode 100644 index 0000000..43277ca --- /dev/null +++ b/tests/sys/kqueue/Makefile @@ -0,0 +1,26 @@ +# $FreeBSD$ +# +# svn://mark.heily.com/libkqueue/trunk/test +# Last update: r114 +# +# libkqueue and test suite by Mark Heily <mark@heily.com> +# + +TAP_TESTS_SH= kqueue_test + +TESTSDIR= ${TESTSBASE}/sys/kqueue +BINDIR= ${TESTSDIR} + +PROGS= kqtest + +SRCS.kqtest= \ + main.c \ + read.c \ + timer.c \ + vnode.c \ + proc.c \ + signal.c \ + user.c +WARNS?= 2 + +.include <bsd.test.mk> diff --git a/tests/sys/kqueue/common.h b/tests/sys/kqueue/common.h new file mode 100644 index 0000000..aada778 --- /dev/null +++ b/tests/sys/kqueue/common.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2009 Mark Heily <mark@heily.com> + * + * 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. + * + * $FreeBSD$ + */ + +#ifndef _COMMON_H +#define _COMMON_H + + +#if HAVE_ERR_H +# include <err.h> +#else +# define err(rc,msg,...) do { perror(msg); exit(rc); } while (0) +# define errx(rc,msg,...) do { puts(msg); exit(rc); } while (0) +#endif +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <sys/event.h> + +#include "config.h" + +extern char *cur_test_id; +int vnode_fd; + +extern const char * kevent_to_str(struct kevent *); +struct kevent * kevent_get(int); + + +void kevent_cmp(struct kevent *, struct kevent *); + +void +kevent_add(int kqfd, struct kevent *kev, + uintptr_t ident, + short filter, + u_short flags, + u_int fflags, + intptr_t data, + void *udata); + +/* DEPRECATED: */ +#define KEV_CMP(kev,_ident,_filter,_flags) do { \ + if (kev.ident != (_ident) || \ + kev.filter != (_filter) || \ + kev.flags != (_flags)) \ + err(1, "kevent mismatch: got [%d,%d,%d] but expecting [%d,%d,%d]", \ + (int)_ident, (int)_filter, (int)_flags,\ + (int)kev.ident, kev.filter, kev.flags);\ +} while (0); + +/* Checks if any events are pending, which is an error. */ +extern void test_no_kevents(void); + +extern void test_begin(const char *); +extern void success(void); + +#endif /* _COMMON_H */ diff --git a/tests/sys/kqueue/config.h b/tests/sys/kqueue/config.h new file mode 100644 index 0000000..a204092 --- /dev/null +++ b/tests/sys/kqueue/config.h @@ -0,0 +1,13 @@ +/* $FreeBSD$ */ + +#define HAVE_ERR_H 1 +#define HAVE_SYS_EVENT_H 1 +#define HAVE_EV_DISPATCH 1 +#define HAVE_EV_RECEIPT 1 +#undef HAVE_NOTE_TRUNCATE +#define HAVE_EVFILT_TIMER 1 +#define HAVE_EVFILT_USER 1 +#define PROGRAM "libkqueue-test" +#define VERSION "0.1" +#define TARGET "freebsd" +#define CFLAGS "-g -O0 -Wall -Werror" diff --git a/tests/sys/kqueue/kqueue_test.sh b/tests/sys/kqueue/kqueue_test.sh new file mode 100755 index 0000000..62a7e23 --- /dev/null +++ b/tests/sys/kqueue/kqueue_test.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +cd $(dirname $0) +i=1 +./kqtest | while read line; do + echo $line | grep -q passed + if [ $? -eq 0 ]; then + echo "ok - $i $line" + : $(( i += 1 )) + fi + + echo $line | grep -q 'tests completed' + if [ $? -eq 0 ]; then + echo -n "1.." + echo $line | cut -d' ' -f3 + fi +done diff --git a/tests/sys/kqueue/main.c b/tests/sys/kqueue/main.c new file mode 100644 index 0000000..f76c4e2 --- /dev/null +++ b/tests/sys/kqueue/main.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2009 Mark Heily <mark@heily.com> + * + * 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. + * + * $FreeBSD$ + */ + +#include <sys/types.h> + +#include "config.h" +#include "common.h" + +int testnum = 1; +char *cur_test_id = NULL; +int kqfd; + +extern void test_evfilt_read(); +extern void test_evfilt_signal(); +extern void test_evfilt_vnode(); +extern void test_evfilt_timer(); +extern void test_evfilt_proc(); +#if HAVE_EVFILT_USER +extern void test_evfilt_user(); +#endif + +/* Checks if any events are pending, which is an error. */ +void +test_no_kevents(void) +{ + int nfds; + struct timespec timeo; + struct kevent kev; + + puts("confirming that there are no events pending"); + memset(&timeo, 0, sizeof(timeo)); + nfds = kevent(kqfd, NULL, 0, &kev, 1, &timeo); + if (nfds != 0) { + puts("\nUnexpected event:"); + puts(kevent_to_str(&kev)); + errx(1, "%d event(s) pending, but none expected:", nfds); + } +} + +/* Retrieve a single kevent */ +struct kevent * +kevent_get(int kqfd) +{ + int nfds; + struct kevent *kev; + + if ((kev = calloc(1, sizeof(*kev))) == NULL) + err(1, "out of memory"); + + nfds = kevent(kqfd, NULL, 0, kev, 1, NULL); + if (nfds < 1) + err(1, "kevent(2)"); + + return (kev); +} + +char * +kevent_fflags_dump(struct kevent *kev) +{ + char *buf; + +#define KEVFFL_DUMP(attrib) \ + if (kev->fflags & attrib) \ + strncat(buf, #attrib" ", 64); + + if ((buf = calloc(1, 1024)) == NULL) + abort(); + + /* Not every filter has meaningful fflags */ + if (kev->filter != EVFILT_VNODE) { + snprintf(buf, 1024, "fflags = %d", kev->fflags); + return (buf); + } + + snprintf(buf, 1024, "fflags = %d (", kev->fflags); + KEVFFL_DUMP(NOTE_DELETE); + KEVFFL_DUMP(NOTE_WRITE); + KEVFFL_DUMP(NOTE_EXTEND); +#if HAVE_NOTE_TRUNCATE + KEVFFL_DUMP(NOTE_TRUNCATE); +#endif + KEVFFL_DUMP(NOTE_ATTRIB); + KEVFFL_DUMP(NOTE_LINK); + KEVFFL_DUMP(NOTE_RENAME); +#if HAVE_NOTE_REVOKE + KEVFFL_DUMP(NOTE_REVOKE); +#endif + buf[strlen(buf) - 1] = ')'; + + return (buf); +} + +char * +kevent_flags_dump(struct kevent *kev) +{ + char *buf; + +#define KEVFL_DUMP(attrib) \ + if (kev->flags & attrib) \ + strncat(buf, #attrib" ", 64); + + if ((buf = calloc(1, 1024)) == NULL) + abort(); + + snprintf(buf, 1024, "flags = %d (", kev->flags); + KEVFL_DUMP(EV_ADD); + KEVFL_DUMP(EV_ENABLE); + KEVFL_DUMP(EV_DISABLE); + KEVFL_DUMP(EV_DELETE); + KEVFL_DUMP(EV_ONESHOT); + KEVFL_DUMP(EV_CLEAR); + KEVFL_DUMP(EV_EOF); + KEVFL_DUMP(EV_ERROR); +#if HAVE_EV_DISPATCH + KEVFL_DUMP(EV_DISPATCH); +#endif +#if HAVE_EV_RECEIPT + KEVFL_DUMP(EV_RECEIPT); +#endif + buf[strlen(buf) - 1] = ')'; + + return (buf); +} + +/* Copied from ../kevent.c kevent_dump() and improved */ +const char * +kevent_to_str(struct kevent *kev) +{ + char buf[512]; + + snprintf(&buf[0], sizeof(buf), + "[ident=%d, filter=%d, %s, %s, data=%d, udata=%p]", + (u_int) kev->ident, + kev->filter, + kevent_flags_dump(kev), + kevent_fflags_dump(kev), + (int) kev->data, + kev->udata); + + return (strdup(buf)); +} + +void +kevent_add(int kqfd, struct kevent *kev, + uintptr_t ident, + short filter, + u_short flags, + u_int fflags, + intptr_t data, + void *udata) +{ + EV_SET(kev, ident, filter, flags, fflags, data, NULL); + if (kevent(kqfd, kev, 1, NULL, 0, NULL) < 0) { + printf("Unable to add the following kevent:\n%s\n", + kevent_to_str(kev)); + err(1, "kevent(): %s", strerror(errno)); + } +} + +void +kevent_cmp(struct kevent *k1, struct kevent *k2) +{ +/* XXX- + Workaround for inconsistent implementation of kevent(2) + */ +#ifdef __FreeBSD__ + if (k1->flags & EV_ADD) + k2->flags |= EV_ADD; +#endif + if (memcmp(k1, k2, sizeof(*k1)) != 0) { + printf("kevent_cmp: mismatch:\n %s !=\n %s\n", + kevent_to_str(k1), kevent_to_str(k2)); + abort(); + } +} + +void +test_begin(const char *func) +{ + if (cur_test_id) + free(cur_test_id); + cur_test_id = strdup(func); + if (!cur_test_id) + err(1, "strdup failed"); + + printf("\n\nTest %d: %s\n", testnum++, func); +} + +void +success(void) +{ + printf("%-70s %s\n", cur_test_id, "passed"); + free(cur_test_id); + cur_test_id = NULL; +} + +void +test_kqueue(void) +{ + test_begin("kqueue()"); + if ((kqfd = kqueue()) < 0) + err(1, "kqueue()"); + test_no_kevents(); + success(); +} + +void +test_kqueue_close(void) +{ + test_begin("close(kq)"); + if (close(kqfd) < 0) + err(1, "close()"); + success(); +} + +int +main(int argc, char **argv) +{ + int test_proc = 1; + int test_socket = 1; + int test_signal = 1; + int test_vnode = 1; + int test_timer = 1; +#ifdef __FreeBSD__ + int test_user = 1; +#else + /* XXX-FIXME temporary */ + int test_user = 0; +#endif + + while (argc) { + if (strcmp(argv[0], "--no-proc") == 0) + test_proc = 0; + if (strcmp(argv[0], "--no-socket") == 0) + test_socket = 0; + if (strcmp(argv[0], "--no-timer") == 0) + test_timer = 0; + if (strcmp(argv[0], "--no-signal") == 0) + test_signal = 0; + if (strcmp(argv[0], "--no-vnode") == 0) + test_vnode = 0; + if (strcmp(argv[0], "--no-user") == 0) + test_user = 0; + argv++; + argc--; + } + + test_kqueue(); + test_kqueue_close(); + + if (test_socket) + test_evfilt_read(); + if (test_signal) + test_evfilt_signal(); + if (test_vnode) + test_evfilt_vnode(); +#if HAVE_EVFILT_USER + if (test_user) + test_evfilt_user(); +#endif + if (test_timer) + test_evfilt_timer(); + if (test_proc) + test_evfilt_proc(); + + printf("\n---\n" + "+OK All %d tests completed.\n", testnum - 1); + return (0); +} diff --git a/tests/sys/kqueue/proc.c b/tests/sys/kqueue/proc.c new file mode 100644 index 0000000..6288ee6 --- /dev/null +++ b/tests/sys/kqueue/proc.c @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2009 Mark Heily <mark@heily.com> + * + * 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. + * + * $FreeBSD$ + */ + +#include <sys/stat.h> + +#include <err.h> + +#include "config.h" +#include "common.h" + +static int sigusr1_caught = 0; + +int kqfd; + +static void +sig_handler(int signum) +{ + sigusr1_caught = 1; +} + +static void +add_and_delete(void) +{ + struct kevent kev; + pid_t pid; + + /* Create a child that waits to be killed and then exits */ + pid = fork(); + if (pid == 0) { + struct stat s; + if (fstat(kqfd, &s) != -1) + errx(1, "kqueue inherited across fork! (%s() at %s:%d)", + __func__, __FILE__, __LINE__); + + pause(); + exit(2); + } + printf(" -- child created (pid %d)\n", (int) pid); + + test_begin("kevent(EVFILT_PROC, EV_ADD)"); + + test_no_kevents(); + kevent_add(kqfd, &kev, pid, EVFILT_PROC, EV_ADD, 0, 0, NULL); + test_no_kevents(); + + success(); + + test_begin("kevent(EVFILT_PROC, EV_DELETE)"); + + sleep(1); + test_no_kevents(); + kevent_add(kqfd, &kev, pid, EVFILT_PROC, EV_DELETE, 0, 0, NULL); + if (kill(pid, SIGKILL) < 0) + err(1, "kill"); + sleep(1); + test_no_kevents(); + + success(); + +} + +#ifdef TODO +static void +event_trigger(void) +{ + struct kevent kev; + pid_t pid; + + test_begin("kevent(EVFILT_PROC, wait)"); + + /* Create a child that waits to be killed and then exits */ + pid = fork(); + if (pid == 0) { + pause(); + printf(" -- child caught signal, exiting\n"); + exit(2); + } + printf(" -- child created (pid %d)\n", (int) pid); + + test_no_kevents(); + kevent_add(kqfd, &kev, pid, EVFILT_PROC, EV_ADD, 0, 0, NULL); + + /* Cause the child to exit, then retrieve the event */ + printf(" -- killing process %d\n", (int) pid); + if (kill(pid, SIGUSR1) < 0) + err(1, "kill"); + kevent_cmp(&kev, kevent_get(kqfd)); + test_no_kevents(); + + success(); +} + +void +test_kevent_signal_disable(void) +{ + const char *test_id = "kevent(EVFILT_SIGNAL, EV_DISABLE)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DISABLE, 0, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Block SIGUSR1, then send it to ourselves */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) + err(1, "sigprocmask"); + if (kill(getpid(), SIGKILL) < 0) + err(1, "kill"); + + test_no_kevents(); + + success(); +} + +void +test_kevent_signal_enable(void) +{ + const char *test_id = "kevent(EVFILT_SIGNAL, EV_ENABLE)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ENABLE, 0, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Block SIGUSR1, then send it to ourselves */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) + err(1, "sigprocmask"); + if (kill(getpid(), SIGUSR1) < 0) + err(1, "kill"); + + kev.flags = EV_ADD | EV_CLEAR; +#if LIBKQUEUE + kev.data = 1; /* WORKAROUND */ +#else + kev.data = 2; // one extra time from test_kevent_signal_disable() +#endif + kevent_cmp(&kev, kevent_get(kqfd)); + + /* Delete the watch */ + kev.flags = EV_DELETE; + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(); +} + +void +test_kevent_signal_del(void) +{ + const char *test_id = "kevent(EVFILT_SIGNAL, EV_DELETE)"; + struct kevent kev; + + test_begin(test_id); + + /* Delete the kevent */ + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DELETE, 0, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Block SIGUSR1, then send it to ourselves */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) + err(1, "sigprocmask"); + if (kill(getpid(), SIGUSR1) < 0) + err(1, "kill"); + + test_no_kevents(); + success(); +} + +void +test_kevent_signal_oneshot(void) +{ + const char *test_id = "kevent(EVFILT_SIGNAL, EV_ONESHOT)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD | EV_ONESHOT, 0, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Block SIGUSR1, then send it to ourselves */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) + err(1, "sigprocmask"); + if (kill(getpid(), SIGUSR1) < 0) + err(1, "kill"); + + kev.flags |= EV_CLEAR; + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + + /* Send another one and make sure we get no events */ + if (kill(getpid(), SIGUSR1) < 0) + err(1, "kill"); + test_no_kevents(); + + success(); +} +#endif + +void +test_evfilt_proc() +{ + kqfd = kqueue(); + + signal(SIGUSR1, sig_handler); + + add_and_delete(); + +#if TODO + event_trigger(); +#endif + + signal(SIGUSR1, SIG_DFL); + +#if TODO + test_kevent_signal_add(); + test_kevent_signal_del(); + test_kevent_signal_get(); + test_kevent_signal_disable(); + test_kevent_signal_enable(); + test_kevent_signal_oneshot(); +#endif + close(kqfd); +} diff --git a/tests/sys/kqueue/read.c b/tests/sys/kqueue/read.c new file mode 100644 index 0000000..cc65427 --- /dev/null +++ b/tests/sys/kqueue/read.c @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2009 Mark Heily <mark@heily.com> + * + * 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. + * + * $FreeBSD$ + */ + +#include "common.h" + +int kqfd; +int sockfd[2]; + +static void +kevent_socket_drain(void) +{ + char buf[1]; + + /* Drain the read buffer, then make sure there are no more events. */ + puts("draining the read buffer"); + if (read(sockfd[0], &buf[0], 1) < 1) + err(1, "read(2)"); +} + +static void +kevent_socket_fill(void) +{ + puts("filling the read buffer"); + if (write(sockfd[1], ".", 1) < 1) + err(1, "write(2)"); +} + + +void +test_kevent_socket_add(void) +{ + const char *test_id = "kevent(EVFILT_READ, EV_ADD)"; + struct kevent kev; + + test_begin(test_id); + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(); +} + +void +test_kevent_socket_get(void) +{ + const char *test_id = "kevent(EVFILT_READ) wait"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + kevent_socket_fill(); + + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + + kevent_socket_drain(); + test_no_kevents(); + + kev.flags = EV_DELETE; + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(); +} + +void +test_kevent_socket_clear(void) +{ + const char *test_id = "kevent(EVFILT_READ, EV_CLEAR)"; + struct kevent kev; + + test_begin(test_id); + + test_no_kevents(); + + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + kevent_socket_fill(); + kevent_socket_fill(); + + kev.data = 2; + kevent_cmp(&kev, kevent_get(kqfd)); + + /* We filled twice, but drain once. Edge-triggered would not generate + additional events. + */ + kevent_socket_drain(); + test_no_kevents(); + + kevent_socket_drain(); + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(); +} + +void +test_kevent_socket_disable_and_enable(void) +{ + const char *test_id = "kevent(EVFILT_READ, EV_DISABLE)"; + struct kevent kev; + + test_begin(test_id); + + /* Add an event, then disable it. */ + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DISABLE, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + kevent_socket_fill(); + test_no_kevents(); + + /* Re-enable the knote, then see if an event is generated */ + kev.flags = EV_ENABLE; + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + kev.flags = EV_ADD; + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + + kevent_socket_drain(); + + kev.flags = EV_DELETE; + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(); +} + +void +test_kevent_socket_del(void) +{ + const char *test_id = "kevent(EVFILT_READ, EV_DELETE)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + kevent_socket_fill(); + test_no_kevents(); + kevent_socket_drain(); + + success(); +} + +void +test_kevent_socket_oneshot(void) +{ + const char *test_id = "kevent(EVFILT_READ, EV_ONESHOT)"; + struct kevent kev; + + test_begin(test_id); + + /* Re-add the watch and make sure no events are pending */ + puts("-- re-adding knote"); + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + test_no_kevents(); + + puts("-- getting one event"); + kevent_socket_fill(); + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + + puts("-- checking knote disabled"); + test_no_kevents(); + + /* Try to delete the knote, it should already be deleted */ + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) == 0) + err(1, "%s", test_id); + + kevent_socket_drain(); + + success(); +} + + +#if HAVE_EV_DISPATCH +void +test_kevent_socket_dispatch(void) +{ + const char *test_id = "kevent(EVFILT_READ, EV_DISPATCH)"; + + test_begin(test_id); + + struct kevent kev; + + /* Re-add the watch and make sure no events are pending */ + puts("-- re-adding knote"); + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD | EV_DISPATCH, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + test_no_kevents(); + + /* The event will occur only once, even though EV_CLEAR is not + specified. */ + kevent_socket_fill(); + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + test_no_kevents(); + + /* Since the knote is disabled, the EV_DELETE operation succeeds. */ + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + kevent_socket_drain(); + + success(); +} +#endif /* HAVE_EV_DISPATCH */ + +#if BROKEN +void +test_kevent_socket_lowat(void) +{ + const char *test_id = "kevent(EVFILT_READ, NOTE_LOWAT)"; + struct kevent kev; + + test_begin(test_id); + + /* Re-add the watch and make sure no events are pending */ + puts("-- re-adding knote, setting low watermark to 2 bytes"); + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD | EV_ONESHOT, NOTE_LOWAT, 2, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + test_no_kevents(); + + puts("-- checking that one byte does not trigger an event.."); + kevent_socket_fill(); + test_no_kevents(); + + puts("-- checking that two bytes triggers an event.."); + kevent_socket_fill(); + if (kevent(kqfd, NULL, 0, &kev, 1, NULL) != 1) + err(1, "%s", test_id); + KEV_CMP(kev, sockfd[0], EVFILT_READ, 0); + test_no_kevents(); + + kevent_socket_drain(); + kevent_socket_drain(); + + success(); +} +#endif + +void +test_kevent_socket_eof(void) +{ + const char *test_id = "kevent(EVFILT_READ, EV_EOF)"; + struct kevent kev; + + test_begin(test_id); + + /* Re-add the watch and make sure no events are pending */ + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + test_no_kevents(); + + if (close(sockfd[1]) < 0) + err(1, "close(2)"); + + kev.flags |= EV_EOF; + kevent_cmp(&kev, kevent_get(kqfd)); + + /* Delete the watch */ + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(); +} + +void +test_evfilt_read() +{ + /* Create a connected pair of full-duplex sockets for testing socket events */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd) < 0) + abort(); + + kqfd = kqueue(); + test_kevent_socket_add(); + test_kevent_socket_del(); + test_kevent_socket_get(); + test_kevent_socket_disable_and_enable(); + test_kevent_socket_oneshot(); + test_kevent_socket_clear(); +#if HAVE_EV_DISPATCH + test_kevent_socket_dispatch(); +#endif + test_kevent_socket_eof(); + close(kqfd); +} diff --git a/tests/sys/kqueue/signal.c b/tests/sys/kqueue/signal.c new file mode 100644 index 0000000..14e751d --- /dev/null +++ b/tests/sys/kqueue/signal.c @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2009 Mark Heily <mark@heily.com> + * + * 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. + * + * $FreeBSD$ + */ + +#include "common.h" + +int kqfd; + +void +test_kevent_signal_add(void) +{ + const char *test_id = "kevent(EVFILT_SIGNAL, EV_ADD)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(); +} + +void +test_kevent_signal_get(void) +{ + const char *test_id = "kevent(EVFILT_SIGNAL, wait)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Block SIGUSR1, then send it to ourselves */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) + err(1, "sigprocmask"); + if (kill(getpid(), SIGUSR1) < 0) + err(1, "kill"); + + kev.flags |= EV_CLEAR; + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + + success(); +} + +void +test_kevent_signal_disable(void) +{ + const char *test_id = "kevent(EVFILT_SIGNAL, EV_DISABLE)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DISABLE, 0, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Block SIGUSR1, then send it to ourselves */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) + err(1, "sigprocmask"); + if (kill(getpid(), SIGUSR1) < 0) + err(1, "kill"); + + test_no_kevents(); + + success(); +} + +void +test_kevent_signal_enable(void) +{ + const char *test_id = "kevent(EVFILT_SIGNAL, EV_ENABLE)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ENABLE, 0, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Block SIGUSR1, then send it to ourselves */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) + err(1, "sigprocmask"); + if (kill(getpid(), SIGUSR1) < 0) + err(1, "kill"); + + kev.flags = EV_ADD | EV_CLEAR; +#if LIBKQUEUE + kev.data = 1; /* WORKAROUND */ +#else + kev.data = 2; // one extra time from test_kevent_signal_disable() +#endif + kevent_cmp(&kev, kevent_get(kqfd)); + + /* Delete the watch */ + kev.flags = EV_DELETE; + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(); +} + +void +test_kevent_signal_del(void) +{ + const char *test_id = "kevent(EVFILT_SIGNAL, EV_DELETE)"; + struct kevent kev; + + test_begin(test_id); + + /* Delete the kevent */ + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DELETE, 0, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Block SIGUSR1, then send it to ourselves */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) + err(1, "sigprocmask"); + if (kill(getpid(), SIGUSR1) < 0) + err(1, "kill"); + + test_no_kevents(); + success(); +} + +void +test_kevent_signal_oneshot(void) +{ + const char *test_id = "kevent(EVFILT_SIGNAL, EV_ONESHOT)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD | EV_ONESHOT, 0, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Block SIGUSR1, then send it to ourselves */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) + err(1, "sigprocmask"); + if (kill(getpid(), SIGUSR1) < 0) + err(1, "kill"); + + kev.flags |= EV_CLEAR; + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + + /* Send another one and make sure we get no events */ + if (kill(getpid(), SIGUSR1) < 0) + err(1, "kill"); + test_no_kevents(); + + success(); +} + +void +test_evfilt_signal() +{ + kqfd = kqueue(); + test_kevent_signal_add(); + test_kevent_signal_del(); + test_kevent_signal_get(); + test_kevent_signal_disable(); + test_kevent_signal_enable(); + test_kevent_signal_oneshot(); + close(kqfd); +} diff --git a/tests/sys/kqueue/timer.c b/tests/sys/kqueue/timer.c new file mode 100644 index 0000000..766125d --- /dev/null +++ b/tests/sys/kqueue/timer.c @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2009 Mark Heily <mark@heily.com> + * + * 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. + * + * $FreeBSD$ + */ + +#include "common.h" + +int kqfd; + +void +test_kevent_timer_add(void) +{ + const char *test_id = "kevent(EVFILT_TIMER, EV_ADD)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, 1, EVFILT_TIMER, EV_ADD, 0, 1000, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(); +} + +void +test_kevent_timer_del(void) +{ + const char *test_id = "kevent(EVFILT_TIMER, EV_DELETE)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + test_no_kevents(); + + success(); +} + +void +test_kevent_timer_get(void) +{ + const char *test_id = "kevent(EVFILT_TIMER, wait)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, 1, EVFILT_TIMER, EV_ADD, 0, 1000, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + kev.flags |= EV_CLEAR; + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + + EV_SET(&kev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(); +} + +static void +test_oneshot(void) +{ + const char *test_id = "kevent(EVFILT_TIMER, EV_ONESHOT)"; + struct kevent kev; + + test_begin(test_id); + + test_no_kevents(); + + EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 500,NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Retrieve the event */ + kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT; + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + + /* Check if the event occurs again */ + sleep(3); + test_no_kevents(); + + + success(); +} + +static void +test_periodic(void) +{ + const char *test_id = "kevent(EVFILT_TIMER, periodic)"; + struct kevent kev; + + test_begin(test_id); + + test_no_kevents(); + + EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD, 0, 1000,NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Retrieve the event */ + kev.flags = EV_ADD | EV_CLEAR; + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + + /* Check if the event occurs again */ + sleep(1); + kevent_cmp(&kev, kevent_get(kqfd)); + + /* Delete the event */ + kev.flags = EV_DELETE; + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(); +} + +static void +disable_and_enable(void) +{ + const char *test_id = "kevent(EVFILT_TIMER, EV_DISABLE and EV_ENABLE)"; + struct kevent kev; + + test_begin(test_id); + + test_no_kevents(); + + /* Add the watch and immediately disable it */ + EV_SET(&kev, vnode_fd, EVFILT_TIMER, EV_ADD | EV_ONESHOT, 0, 2000,NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + kev.flags = EV_DISABLE; + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + test_no_kevents(); + + /* Re-enable and check again */ + kev.flags = EV_ENABLE; + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + kev.flags = EV_ADD | EV_CLEAR | EV_ONESHOT; + kev.data = 1; + kevent_cmp(&kev, kevent_get(kqfd)); + + success(); +} + +void +test_evfilt_timer() +{ + kqfd = kqueue(); + test_kevent_timer_add(); + test_kevent_timer_del(); + test_kevent_timer_get(); + test_oneshot(); + test_periodic(); + disable_and_enable(); + close(kqfd); +} diff --git a/tests/sys/kqueue/user.c b/tests/sys/kqueue/user.c new file mode 100644 index 0000000..9ba25f9 --- /dev/null +++ b/tests/sys/kqueue/user.c @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2009 Mark Heily <mark@heily.com> + * + * 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. + * + * $FreeBSD$ + */ + +#include "common.h" + +int kqfd; + +static void +add_and_delete(void) +{ + const char *test_id = "kevent(EVFILT_USER, EV_ADD and EV_DELETE)"; + struct kevent kev; + + test_begin(test_id); + + kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ADD, 0, 0, NULL); + test_no_kevents(); + + kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_DELETE, 0, 0, NULL); + test_no_kevents(); + + success(); +} + +static void +event_wait(void) +{ + const char *test_id = "kevent(EVFILT_USER, wait)"; + struct kevent kev; + + test_begin(test_id); + + test_no_kevents(); + + /* Add the event, and then trigger it */ + kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, NULL); + kevent_add(kqfd, &kev, 1, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL); + + kev.fflags &= ~NOTE_FFCTRLMASK; + kev.fflags &= ~NOTE_TRIGGER; + kev.flags = EV_CLEAR; + kevent_cmp(&kev, kevent_get(kqfd)); + + test_no_kevents(); + + success(); +} + +static void +disable_and_enable(void) +{ + const char *test_id = "kevent(EVFILT_USER, EV_DISABLE and EV_ENABLE)"; + struct kevent kev; + + test_begin(test_id); + + test_no_kevents(); + + kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ADD, 0, 0, NULL); + kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_DISABLE, 0, 0, NULL); + + /* Trigger the event, but since it is disabled, nothing will happen. */ + kevent_add(kqfd, &kev, 1, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL); + test_no_kevents(); + + kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ENABLE, 0, 0, NULL); + kevent_add(kqfd, &kev, 1, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL); + + kev.flags = EV_CLEAR; + kev.fflags &= ~NOTE_FFCTRLMASK; + kev.fflags &= ~NOTE_TRIGGER; + kevent_cmp(&kev, kevent_get(kqfd)); + + success(); +} + +static void +oneshot(void) +{ + const char *test_id = "kevent(EVFILT_USER, EV_ONESHOT)"; + struct kevent kev; + + test_begin(test_id); + + test_no_kevents(); + + kevent_add(kqfd, &kev, 2, EVFILT_USER, EV_ADD | EV_ONESHOT, 0, 0, NULL); + + puts(" -- event 1"); + kevent_add(kqfd, &kev, 2, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL); + + kev.flags = EV_ONESHOT; + kev.fflags &= ~NOTE_FFCTRLMASK; + kev.fflags &= ~NOTE_TRIGGER; + kevent_cmp(&kev, kevent_get(kqfd)); + + test_no_kevents(); + + success(); +} + +void +test_evfilt_user() +{ + kqfd = kqueue(); + + add_and_delete(); + event_wait(); + disable_and_enable(); + oneshot(); + /* TODO: try different fflags operations */ + + close(kqfd); +} diff --git a/tests/sys/kqueue/vnode.c b/tests/sys/kqueue/vnode.c new file mode 100644 index 0000000..dfa0b5e --- /dev/null +++ b/tests/sys/kqueue/vnode.c @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2009 Mark Heily <mark@heily.com> + * + * 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. + * + * $FreeBSD$ + */ + +#include "common.h" + +int kqfd; +int vnode_fd; + +void +test_kevent_vnode_add(void) +{ + const char *test_id = "kevent(EVFILT_VNODE, EV_ADD)"; + const char *testfile = "/tmp/kqueue-test.tmp"; + struct kevent kev; + + test_begin(test_id); + + system("touch /tmp/kqueue-test.tmp"); + vnode_fd = open(testfile, O_RDONLY); + if (vnode_fd < 0) + err(1, "open of %s", testfile); + else + printf("vnode_fd = %d\n", vnode_fd); + + EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD, + NOTE_WRITE | NOTE_ATTRIB | NOTE_RENAME | NOTE_DELETE, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(); +} + +void +test_kevent_vnode_note_delete(void) +{ + const char *test_id = "kevent(EVFILT_VNODE, NOTE_DELETE)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_DELETE, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + if (unlink("/tmp/kqueue-test.tmp") < 0) + err(1, "unlink"); + + kevent_cmp(&kev, kevent_get(kqfd)); + + success(); +} + +void +test_kevent_vnode_note_write(void) +{ + const char *test_id = "kevent(EVFILT_VNODE, NOTE_WRITE)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_WRITE, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + if (system("echo hello >> /tmp/kqueue-test.tmp") < 0) + err(1, "system"); + + /* BSD kqueue adds NOTE_EXTEND even though it was not requested */ + /* BSD kqueue removes EV_ENABLE */ + kev.flags &= ~EV_ENABLE; // XXX-FIXME compatibility issue + kev.fflags |= NOTE_EXTEND; // XXX-FIXME compatibility issue + kevent_cmp(&kev, kevent_get(kqfd)); + + success(); +} + +void +test_kevent_vnode_note_attrib(void) +{ + const char *test_id = "kevent(EVFILT_VNODE, NOTE_ATTRIB)"; + struct kevent kev; + int nfds; + + test_begin(test_id); + + EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_ATTRIB, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + if (system("touch /tmp/kqueue-test.tmp") < 0) + err(1, "system"); + + nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL); + if (nfds < 1) + err(1, "%s", test_id); + if (kev.ident != vnode_fd || + kev.filter != EVFILT_VNODE || + kev.fflags != NOTE_ATTRIB) + err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)", + test_id, (unsigned int)kev.ident, kev.filter, kev.flags); + + success(); +} + +void +test_kevent_vnode_note_rename(void) +{ + const char *test_id = "kevent(EVFILT_VNODE, NOTE_RENAME)"; + struct kevent kev; + int nfds; + + test_begin(test_id); + + EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_RENAME, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + if (system("mv /tmp/kqueue-test.tmp /tmp/kqueue-test2.tmp") < 0) + err(1, "system"); + + nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL); + if (nfds < 1) + err(1, "%s", test_id); + if (kev.ident != vnode_fd || + kev.filter != EVFILT_VNODE || + kev.fflags != NOTE_RENAME) + err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)", + test_id, (unsigned int)kev.ident, kev.filter, kev.flags); + + if (system("mv /tmp/kqueue-test2.tmp /tmp/kqueue-test.tmp") < 0) + err(1, "system"); + + success(); +} + +void +test_kevent_vnode_del(void) +{ + const char *test_id = "kevent(EVFILT_VNODE, EV_DELETE)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(); +} + +void +test_kevent_vnode_disable_and_enable(void) +{ + const char *test_id = "kevent(EVFILT_VNODE, EV_DISABLE and EV_ENABLE)"; + struct kevent kev; + int nfds; + + test_begin(test_id); + + test_no_kevents(); + + /* Add the watch and immediately disable it */ + EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_ONESHOT, NOTE_ATTRIB, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + kev.flags = EV_DISABLE; + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Confirm that the watch is disabled */ + if (system("touch /tmp/kqueue-test.tmp") < 0) + err(1, "system"); + test_no_kevents(); + + /* Re-enable and check again */ + kev.flags = EV_ENABLE; + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + if (system("touch /tmp/kqueue-test.tmp") < 0) + err(1, "system"); + nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL); + if (nfds < 1) + err(1, "%s", test_id); + if (kev.ident != vnode_fd || + kev.filter != EVFILT_VNODE || + kev.fflags != NOTE_ATTRIB) + err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)", + test_id, (unsigned int)kev.ident, kev.filter, kev.flags); + + success(); +} + +#if HAVE_EV_DISPATCH +void +test_kevent_vnode_dispatch(void) +{ + const char *test_id = "kevent(EVFILT_VNODE, EV_DISPATCH)"; + struct kevent kev; + int nfds; + + test_begin(test_id); + + test_no_kevents(); + + EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD | EV_DISPATCH, NOTE_ATTRIB, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + if (system("touch /tmp/kqueue-test.tmp") < 0) + err(1, "system"); + + nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL); + if (nfds < 1) + err(1, "%s", test_id); + if (kev.ident != vnode_fd || + kev.filter != EVFILT_VNODE || + kev.fflags != NOTE_ATTRIB) + err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)", + test_id, (unsigned int)kev.ident, kev.filter, kev.flags); + + /* Confirm that the watch is disabled automatically */ + puts("-- checking that watch is disabled"); + if (system("touch /tmp/kqueue-test.tmp") < 0) + err(1, "system"); + test_no_kevents(); + + /* Delete the watch */ + EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_DELETE, NOTE_ATTRIB, 0, NULL); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "remove watch failed: %s", test_id); + + success(); +} +#endif /* HAVE_EV_DISPATCH */ + +void +test_evfilt_vnode() +{ + kqfd = kqueue(); + test_kevent_vnode_add(); + test_kevent_vnode_del(); + test_kevent_vnode_disable_and_enable(); +#if HAVE_EV_DISPATCH + test_kevent_vnode_dispatch(); +#endif + test_kevent_vnode_note_write(); + test_kevent_vnode_note_attrib(); + test_kevent_vnode_note_rename(); + test_kevent_vnode_note_delete(); + close(kqfd); +} |