/*- * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ * Authors: Doug Rabson * Developed with Red Inc: Alfred Perlstein * * 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: src/tools/regression/file/flock/flock.c,v 1.3 2008/06/26 10:21:54 dfr Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include "util.h" int verbose = 0; static void ignore_alarm(int __unused sig) { } static int safe_kill(pid_t pid, int sig) { int save_errno; int status; save_errno = errno; errno = 0; status = kill(pid, sig); errno = save_errno; return (status); } /* * 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) { 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"); if (verbose) 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); 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) { /* * 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. */ if (verbose) printf("2 - F_SETLK on locked region: "); res = fcntl(fd, F_SETLK, &fl); safe_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) { /* * 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. */ if (verbose) printf("3 - F_SETLKW on locked region: "); alarm(1); res = fcntl(fd, F_SETLKW, &fl); safe_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) { /* * 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"); if (verbose) 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); safe_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) { /* * 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); if (verbose) printf("5 - F_SETLKW simple deadlock: "); fl.l_start = 1; res = fcntl(fd, F_SETLKW, &fl); safe_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) { /* * 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); if (verbose) printf("6 - F_SETLKW complex deadlock: "); fl.l_start = 1; fl.l_len = 2; res = fcntl(fd, F_SETLKW, &fl); safe_kill(pid1, SIGTERM); safe_waitpid(pid1); safe_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) { /* * 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. */ if (verbose) printf("7 - F_SETLK shared lock on exclusive locked region: "); fl.l_type = F_RDLCK; res = fcntl(fd, F_SETLK, &fl); safe_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) { /* * 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. */ if (verbose) printf("8 - F_SETLK shared lock on share locked region: "); fl.l_type = F_RDLCK; res = fcntl(fd, F_SETLK, &fl); safe_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) { /* * 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. */ if (verbose) printf("9 - F_SETLK exclusive lock on share locked region: "); fl.l_type = F_WRLCK; res = fcntl(fd, F_SETLK, &fl); safe_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) { /* * 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; 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)"); if (verbose) printf("10 - trying to set bogus pid or sysid values: "); if (fcntl(fd, F_GETLK, &fl) < 0) err(1, "F_GETLK"); safe_kill(pid, SIGTERM); safe_waitpid(pid); close(pfd[0]); close(pfd[1]); FAIL(fl.l_pid != pid); SUCCEED; } /* * Test 11 - remote locks * * XXX temporary interface which will be removed when the kernel lockd * is added. */ static int test11(int fd) { #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; if (verbose) 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) { /* * 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. */ if (verbose) printf("12 - F_SETLKW on locked region which is then unlocked: "); //alarm(1); res = fcntl(fd, F_SETLKW, &fl); safe_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) { /* * 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; if (verbose) 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)"); sleep(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); safe_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) { #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; 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; int id_base = 0; if (verbose) 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; for (j = 0; j < 50; j++) { int start, end, len; int set, wrlock; do { start = arc4random_uniform(128); end = arc4random_uniform(128); } while (end <= start); set = arc4random_uniform(2); wrlock = arc4random_uniform(2); 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) { #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]; int fd2; 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)"); fd2 = 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. */ if (verbose) printf("15 - flock(2) semantics: "); alarm(1); flock(fd, LOCK_EX); /* * Kill the child to force it to drop its locks. */ safe_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 } /* * Test 16 - double free regression * * Not applicable anymore due to stricter bounds validation. */ static int test16(int fd) { #if 0 struct flock fl; int res; fl.l_pid = 0; fl.l_type = 1; fl.l_whence = 0; fl.l_start = 0; fl.l_len = 0x8000000000000000; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); fl.l_start = 0x10000; fl.l_len = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); fl.l_start = 0; fl.l_len = 0x8000000000000000; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); fl.l_start = 0x10000; fl.l_len = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); #endif SUCCEED; } /* * Test 17 - lf_findoverlap() case 0 * * No overlap. */ static int test17(int fd) { struct flock fl; int nfd, res; /* First lock. */ { nfd = dup(fd); FAIL(nfd == -1); fl.l_start = 0; fl.l_len = 100; fl.l_pid = 0; fl.l_type = F_RDLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); close(nfd); } /* Insert at end. */ { nfd = dup(fd); FAIL(nfd == -1); fl.l_start = 100; fl.l_len = 100; fl.l_pid = 0; fl.l_type = F_RDLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); fl.l_start = 200; fl.l_len = 100; fl.l_pid = 0; fl.l_type = F_RDLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); close(nfd); } /* Insert before overlap. */ { nfd = dup(fd); FAIL(nfd == -1); fl.l_start = 300; fl.l_len = 100; fl.l_pid = 0; fl.l_type = F_RDLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); fl.l_start = 500; fl.l_len = 100; fl.l_pid = 0; fl.l_type = F_RDLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); fl.l_start = 400; fl.l_len = 100; fl.l_pid = 0; fl.l_type = F_RDLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); close(nfd); } SUCCEED; } /* * Test 18 - lf_findoverlap() case 1 * * Overlap and lock are equal. */ static int test18(int fd) { struct flock fl; int res; fl.l_start = 0; fl.l_len = 100; fl.l_pid = 0; fl.l_type = F_RDLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); SUCCEED; } /* * Test 19 - lf_findoverlap() case 2 * * Overlap contains lock. */ static int test19(int fd) { struct flock fl; int nfd, res; /* Same type. */ { nfd = dup(fd); FAIL(nfd == -1); fl.l_start = 0; fl.l_len = 100; fl.l_pid = 0; fl.l_type = F_RDLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); fl.l_start = 0; fl.l_len = 50; fl.l_pid = 0; fl.l_type = F_RDLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); close(nfd); } /* Different type, same start offset. */ { nfd = dup(fd); FAIL(nfd == -1); fl.l_start = 100; fl.l_len = 100; fl.l_pid = 0; fl.l_type = F_RDLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); fl.l_start = 100; fl.l_len = 50; fl.l_pid = 0; fl.l_type = F_WRLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); close(nfd); } /* Split fallback. */ { nfd = dup(fd); FAIL(nfd == -1); fl.l_start = 100; fl.l_len = 100; fl.l_pid = 0; fl.l_type = F_RDLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); fl.l_start = 110; fl.l_len = 50; fl.l_pid = 0; fl.l_type = F_WRLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); close(nfd); } SUCCEED; } /* * Test 20 - lf_findoverlap() case 3 * * Lock contains overlap. */ static int test20(int fd) { struct flock fl; int res; fl.l_start = 0; fl.l_len = 100; fl.l_pid = 0; fl.l_type = F_WRLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); fl.l_start = 0; fl.l_len = 200; fl.l_pid = 0; fl.l_type = F_WRLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); SUCCEED; } /* * Test 21 - lf_findoverlap() case 4 * * Overlap starts before lock. */ static int test21(int fd) { struct flock fl; int res; fl.l_start = 0; fl.l_len = 100; fl.l_pid = 0; fl.l_type = F_WRLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); fl.l_start = 50; fl.l_len = 100; fl.l_pid = 0; fl.l_type = F_WRLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); SUCCEED; } /* * Test 22 - lf_findoverlap() case 5 * * Overlap ends after lock. */ static int test22(int fd) { struct flock fl; int res; fl.l_start = 10; fl.l_len = 100; fl.l_pid = 0; fl.l_type = F_WRLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); fl.l_start = 0; fl.l_len = 50; fl.l_pid = 0; fl.l_type = F_WRLCK; fl.l_whence = 0; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); SUCCEED; } /* * Test 23 - positive length overflow */ static int test23(int fd) { struct flock fl; int res; fl.l_pid = 0; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 2; fl.l_len = LLONG_MAX; res = fcntl(fd, F_SETLK, &fl); FAIL(res != -1); FAIL(errno != EOVERFLOW); SUCCEED; } /* * Test 24 - negative length */ static int test24(int fd) { struct flock fl; pid_t pid; int res, status; fl.l_pid = 0; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; /* Start offset plus length must be positive. */ fl.l_start = 0; fl.l_len = LLONG_MIN; res = fcntl(fd, F_SETLK, &fl); FAIL(res != -1); FAIL(errno != EINVAL); /* Set exclusive lock on range [2,3] */ fl.l_start = 4; fl.l_len = -2; res = fcntl(fd, F_SETLK, &fl); FAIL(res != 0); /* Another process must not be able to lock the same range. */ pid = fork(); if (pid == -1) err(1, "fork"); if (pid == 0) { fl.l_start = 2; fl.l_len = 2; res = fcntl(fd, F_GETLK, &fl); FAIL(res != 0); FAIL(fl.l_type == F_UNLCK); _exit(0); } status = safe_waitpid(pid); FAIL(status != 0); SUCCEED; } static struct test tests[] = { { test1, 0 }, { test2, 0 }, { test3, 1 }, { test4, 0 }, { test5, 1 }, { test6, 1 }, { test7, 0 }, { test8, 0 }, { test9, 0 }, { test10, 0 }, { test11, 1 }, { test12, 0 }, { test13, 1 }, { test14, 0 }, { test15, 1 }, { test16, 0 }, { test17, 0 }, { test18, 0 }, { test19, 0 }, { test20, 0 }, { test21, 0 }, { test22, 0 }, { test23, 0 }, { test24, 0 }, }; static int test_count = sizeof(tests) / sizeof(tests[0]); int main(int argc, char *argv[]) { struct sigaction sa; const char *errstr; int c, fd, i; int error = 0; int testnum = 0; while ((c = getopt(argc, argv, "v")) != -1) switch (c) { case 'v': verbose = 1; break; default: usage(); } argc -= optind; argv += optind; if (argc > 1) usage(); if (argc == 1) { testnum = strtonum(argv[0], 1, test_count, &errstr); if (testnum == 0) errx(1, "test number %s", errstr); } fd = make_file(1024); sa.sa_handler = ignore_alarm; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGALRM, &sa, 0); for (i = 0; i < test_count; i++) { if (testnum == 0 || testnum == i + 1) error |= tests[i].testfn(fd); } return (error ? 1 : 0); }