diff options
Diffstat (limited to 'tests/sys/file/flock_helper.c')
| -rw-r--r-- | tests/sys/file/flock_helper.c | 1596 | 
1 files changed, 1596 insertions, 0 deletions
| diff --git a/tests/sys/file/flock_helper.c b/tests/sys/file/flock_helper.c new file mode 100644 index 000000000000..cb864c90e378 --- /dev/null +++ b/tests/sys/file/flock_helper.c @@ -0,0 +1,1596 @@ +/*- + * 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. + */ + +#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 = NULL; +	} + +	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; +} | 
