Viewing: flocks_test.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright (c) 2011, 2014, Intel Corporation.
 */
/*
 * This file is part of Lustre, http://www.lustre.org/
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <stdarg.h>
#include <ctype.h>
#include <sys/time.h>
#include <sys/resource.h>

#define MAX_PATH_LENGTH 4096


static double now(void)
{
	struct timeval tv;

	gettimeofday(&tv, NULL);
	return (double)tv.tv_sec + (double)tv.tv_usec / 1000000;
}

/* helper functions */
static int t_fcntl(int fd, int cmd, ...)
{
	va_list ap;
	long arg;
	struct flock *lock;
	int rc = -1;

	va_start(ap, cmd);
	switch (cmd) {
	case F_GETFL:
		va_end(ap);
		rc = fcntl(fd, cmd);
		if (rc == -1) {
			rc = -errno;
			fprintf(stderr, "fcntl GETFL failed: %s\n",
				strerror(errno));
			return rc;
		}
		break;
	case F_SETFL:
		arg = va_arg(ap, long);
		va_end(ap);
		rc = fcntl(fd, cmd, arg);
		if (rc == -1) {
			rc = -errno;
			fprintf(stderr, "fcntl SETFL %ld failed: %s\n",
				arg, strerror(errno));
			return rc;
		}
		break;
	case F_GETLK:
	case F_SETLK:
	case F_SETLKW:
	case F_OFD_GETLK:
	case F_OFD_SETLKW:
		lock = va_arg(ap, struct flock *);
		va_end(ap);
		rc = fcntl(fd, cmd, lock);
		if (rc == -1) {
			rc = -errno;
			fprintf(stderr, "fcntl cmd %d failed: %s\n",
				cmd, strerror(errno));
			return rc;
		}
		break;
	case F_DUPFD:
		arg = va_arg(ap, long);
		va_end(ap);
		rc = fcntl(fd, cmd, arg);
		if (rc == -1) {
			rc = -errno;
			fprintf(stderr, "fcntl F_DUPFD %d failed: %s\n",
				(int)arg, strerror(errno));
			return rc;
		}
		break;
	default:
		va_end(ap);
		fprintf(stderr, "fcntl cmd %d not supported\n", cmd);
		return rc;
	}
	return rc;
}

static int t_unlink(const char *path)
{
	int rc;

	rc = unlink(path);
	if (rc)
		fprintf(stderr,
			"unlink(%s) error: %s\n", path, strerror(errno));
	return rc;
}

/*
 * test number 1
 * normal flock test
 */
static void t1_usage(void)
{
	fprintf(stderr,
		"usage: flocks_test 1 {on|off} {-c|-f|-l} /path/to/file\n");
}

static int t1(int argc, char *argv[])
{
	int fd;
	int mount_with_flock = 0;
	int error = 0;
	int rc = 0;

	if (argc != 5) {
		t1_usage();
		return EXIT_FAILURE;
	}

	if (!strncmp(argv[2], "on", 3)) {
		mount_with_flock = 1;
	} else if (!strncmp(argv[2], "off", 4)) {
		mount_with_flock = 0;
	} else {
		t1_usage();
		return EXIT_FAILURE;
	}

	fd = open(argv[4], O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "Couldn't open file '%s': %s\n", argv[4],
			strerror(errno));
		return EXIT_FAILURE;
	}

	if (!strncmp(argv[3], "-c", 3)) {
		struct flock fl;

		fl.l_type = F_RDLCK;
		fl.l_whence = SEEK_SET;
		fl.l_start = 0;
		fl.l_len = 1;

		error = fcntl(fd, F_SETLK, &fl);
	} else if (!strncmp(argv[3], "-l", 3)) {
		error = lockf(fd, F_LOCK, 1);
	} else if (!strncmp(argv[3], "-f", 3)) {
		error = flock(fd, LOCK_EX);
	} else {
		t1_usage();
		rc = EXIT_FAILURE;
		goto out;
	}

	if (mount_with_flock)
		rc = ((error == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
	else
		rc = ((error == 0) ? EXIT_FAILURE : EXIT_SUCCESS);

out:
	if (fd >= 0)
		close(fd);
	return rc;
}

/*
 * test number 2
 * 2 threads flock ops interweave
 */
struct thread_info {
	struct flock *lock;
	int fd;
	int rc;
} th_data;

static void *t2_thread1(void *arg)
{
	struct thread_info *ti = arg;
	struct flock *lock = ti->lock;
	int fd = ti->fd;

	printf("thread 1: set write lock (blocking): rc = %d\n", ti->rc);
	lock->l_type = F_WRLCK;
	t_fcntl(fd, F_SETLKW, lock);
	printf("thread 1: set write lock done: rc = %d\n", ti->rc);
	(void)t_fcntl(fd, F_GETLK, lock); /* ignore this, operation will fail */
	printf("thread 1: unlock: rc = %d\n", ti->rc);
	lock->l_type = F_UNLCK;
	ti->rc += t_fcntl(fd, F_SETLK, lock);
	printf("thread 1: unlock done: rc = %d\n", ti->rc);

	if (ti->rc)
		fprintf(stdout, "thread1 exiting with rc = %d\n", ti->rc);
	return &ti->rc;
}

static void *t2_thread2(void *arg)
{
	struct thread_info *ti = arg;
	struct flock *lock = ti->lock;
	int fd = ti->fd;

	sleep(2);
	printf("thread 2: unlock: rc = %d\n", ti->rc);
	lock->l_type = F_UNLCK;
	ti->rc += t_fcntl(fd, F_SETLK, lock);
	printf("thread 2: unlock done: rc = %d\n", ti->rc);
	printf("thread 2: set write lock (non-blocking): rc = %d\n", ti->rc);
	lock->l_type = F_WRLCK;
	ti->rc += t_fcntl(fd, F_SETLK, lock);
	printf("thread 2: set write lock done: rc = %d\n", ti->rc);
	(void)t_fcntl(fd, F_GETLK, lock); /* ignore this, operation will fail */

	if (ti->rc)
		fprintf(stdout, "thread2 exiting with rc = %d\n", ti->rc);
	return &ti->rc;
}

static int t2(int argc, char *argv[])
{
	struct flock lock = {
		.l_type = F_RDLCK,
		.l_whence = SEEK_SET,
	};
	char file[MAX_PATH_LENGTH] = "";
	int  fd, rc;
	pthread_t th1, th2;
	struct thread_info ti;

	snprintf(file, MAX_PATH_LENGTH, "%s/test_t2_file", argv[2]);

	fd = open(file, O_RDWR|O_CREAT, (mode_t)0666);
	if (fd < 0) {
		fprintf(stderr, "error open file '%s': %s\n", file,
			strerror(errno));
		return EXIT_FAILURE;
	}

	t_fcntl(fd, F_SETFL, O_APPEND);
	rc = t_fcntl(fd, F_GETFL);
	if ((rc < 0) || (rc & O_APPEND) == 0) {
		fprintf(stderr, "error get flag: ret %x\n", rc);
		rc = EXIT_FAILURE;
		goto out;
	}

	ti.lock = &lock;
	ti.fd   = fd;
	ti.rc   = 0;
	rc = pthread_create(&th1, NULL, t2_thread1, &ti);
	if (rc) {
		fprintf(stderr, "error create thread 1\n");
		rc = EXIT_FAILURE;
		goto out;
	}
	rc = pthread_create(&th2, NULL, t2_thread2, &ti);
	if (rc) {
		fprintf(stderr, "error create thread 2\n");
		rc = EXIT_FAILURE;
		goto out;
	}
	pthread_join(th1, NULL);
	pthread_join(th2, NULL);
	if (ti.rc)
		rc = EXIT_FAILURE;
out:
	t_unlink(file);
	close(fd);

	return rc;
}

/*
 * test number 3
 *
 * Bug 24040: Two conflicting flocks from same process different fds should fail
 *            two conflicting flocks from different processes but same fs
 *            should succeed.
 */
static int t3(int argc, char *argv[])
{
	int fd, fd2;
	int pid;
	int rc = EXIT_SUCCESS;

	if (argc != 3) {
		fprintf(stderr, "usage: flocks_test 3 filename\n");
		return EXIT_FAILURE;
	}

	fd = open(argv[2], O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "Couldn't open file '%s': %s\n", argv[2],
			strerror(errno));
		return EXIT_FAILURE;
	}
	if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
		perror("first flock failed");
		rc = EXIT_FAILURE;
		goto out;
	}
	fd2 = open(argv[2], O_RDWR);
	if (fd2 < 0) {
		fprintf(stderr, "Couldn't open file '%s': %s\n", argv[2],
			strerror(errno));
		rc = EXIT_FAILURE;
		goto out;
	}
	if (flock(fd2, LOCK_EX | LOCK_NB) >= 0) {
		fprintf(stderr, "Second flock succeeded - FAIL\n");
		rc = EXIT_FAILURE;
		close(fd2);
		goto out;
	}

	close(fd2);

	pid = fork();
	if (pid == -1) {
		perror("fork");
		rc = EXIT_FAILURE;
		goto out;
	}

	if (pid == 0) {
		fd2 = open(argv[2], O_RDWR);
		if (fd2 < 0) {
			fprintf(stderr, "Couldn't open file '%s': %s\n",
				argv[1], strerror(errno));
			rc = EXIT_FAILURE;
			goto out;
		}
		if (flock(fd2, LOCK_EX | LOCK_NB) >= 0) {
			fprintf(stderr, "Second flock succeeded - FAIL\n");
			rc = EXIT_FAILURE;
			goto out_child;
		}
		if (flock(fd, LOCK_UN) == -1) {
			fprintf(stderr, "Child unlock on parent fd failed\n");
			rc = EXIT_FAILURE;
			goto out_child;
		}
		if (flock(fd2, LOCK_EX | LOCK_NB) == -1) {
			fprintf(stderr, "Relock after parent unlock failed!\n");
			rc = EXIT_FAILURE;
			goto out_child;
		}
	out_child:
		close(fd2);
		exit(rc);
	}

	waitpid(pid, &rc, 0);
out:
	close(fd);
	return rc;
}

static int t4(int argc, char *argv[])
{
	struct flock lock = {
		.l_type = F_WRLCK,
		.l_whence = SEEK_SET,
		.l_start = 0,
		.l_len = 10,
	};

	int fd, fd2;
	pid_t child_pid;
	int child_status;
	int rc = EXIT_SUCCESS;

	if (argc != 4) {
		fprintf(stderr, "usage: flocks_test 4 file1 file2\n");
		return EXIT_FAILURE;
	}

	fd = open(argv[2], O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "Couldn't open file: %s\n", argv[2]);
		return EXIT_FAILURE;
	}
	fd2 = open(argv[3], O_RDWR);
	if (fd2 < 0) {
		fprintf(stderr, "Couldn't open file: %s\n", argv[3]);
		rc = EXIT_FAILURE;
		goto out;
	}

	child_pid = fork();
	if (child_pid < 0) {
		perror("fork");
		rc = EXIT_FAILURE;
		goto out;
	}

	if (child_pid == 0) {
		printf("%d: get lock1\n", getpid());
		fflush(stdout);
		if (t_fcntl(fd, F_SETLKW, &lock) < 0) {
			fprintf(stderr, "%d: cannot get lock1: %s\n",
				getpid(), strerror(errno));
			rc = EXIT_FAILURE;
			goto out_child;
		}
		printf("%d: done\n", getpid());
		sleep(3);
		printf("%d: get lock2\n", getpid());
		fflush(stdout);
		if (t_fcntl(fd2, F_SETLKW, &lock) < 0) {
			fprintf(stderr, "%d: cannot get lock2: %s\n",
				getpid(), strerror(errno));

			if (errno == EDEADLK)
				rc = EXIT_SUCCESS;
			else
				rc = EXIT_FAILURE;

			goto out_child;
		}
		printf("%d: done\n", getpid());
out_child:
		printf("%d: exit rc=%d\n", getpid(), rc);
		exit(rc);
	} else {
		printf("%d: get lock2\n", getpid());
		fflush(stdout);
		if (t_fcntl(fd2, F_SETLKW, &lock) < 0) {
			fprintf(stderr, "%d: cannot get lock2: %s\n",
				getpid(), strerror(errno));
			rc = EXIT_FAILURE;
			goto out;
		}
		printf("%d: done\n", getpid());
		sleep(3);
		printf("%d: get lock1\n", getpid());
		fflush(stdout);
		if (t_fcntl(fd, F_SETLKW, &lock) < 0) {
			fprintf(stderr, "%d: cannot get lock1: %s\n",
				getpid(), strerror(errno));

			if (errno != EDEADLK) {
				rc = EXIT_FAILURE;
				goto out;
			}
		}
		printf("%d: done\n", getpid());
	}

	sleep(1);

	if (close(fd) < 0) {
		fprintf(stderr, "%d: error closing file1: %s\n",
			getpid(), strerror(errno));
		rc = EXIT_FAILURE;
	}

	if (close(fd2) < 0) {
		fprintf(stderr, "%d: error closing file2: %s\n",
			getpid(), strerror(errno));
		rc = EXIT_FAILURE;
	}

	if (waitpid(child_pid, &child_status, 0) < 0) {
		fprintf(stderr, "%d: cannot get termination status of %d: %s\n",
			getpid(), child_pid, strerror(errno));
		rc = EXIT_FAILURE;
	} else if (!WIFEXITED(child_status)) {
		fprintf(stderr, "%d: child %d terminated with status %d\n",
			getpid(), child_pid, child_status);
		rc = EXIT_FAILURE;
	} else {
		rc = WEXITSTATUS(child_status);
	}

out:
	printf("%d: exit rc=%d\n", getpid(), rc);
	return rc;
}

#define T5_USAGE							      \
"usage: flocks_test 5 {set|get|unlock} [read|write] [sleep N] file1\n"	      \
"       set: F_SETLKW F_WRLCK\n"					      \
"       get: F_GETLK F_WRLCK  (conflict)\n"				      \
"       unlock: F_SETLKW F_UNLCK\n"					      \
"       read|write: lock mode, write by default\n"			      \
"       sleep N: sleep for N secs after fcntl\n"			      \
"       file1: fcntl is called for this file\n"

static int t5(int argc, char *argv[])
{
	struct flock lock = {
		.l_type = F_WRLCK,
		.l_whence = SEEK_SET,
	};

	int setlk = 0, getlk = 0, unlk = 0, secs = 0;
	int pos;
	int fd;
	int rc = 0;

	if (argc < 4 || argc > 7) {
		fprintf(stderr, T5_USAGE);
		return EXIT_FAILURE;
	}

	if (!strncmp(argv[2], "set", 4))
		setlk = 1;
	else if (!strncmp(argv[2], "get", 4))
		getlk = 1;
	else if (!strncmp(argv[2], "unlock", 7))
		unlk = 1;
	else {
		fprintf(stderr, "Wrong 2nd argument: %s\n", argv[2]);
		return EXIT_FAILURE;
	}

	pos = 3;

	if (!strncmp(argv[pos], "read", 5)) {
		lock.l_type = F_RDLCK;
		pos++;
	} else if (!strncmp(argv[pos], "write", 6)) {
		lock.l_type = F_WRLCK;
		pos++;
	}

	if (!strncmp(argv[pos], "sleep", 6)) {
		secs = atoi(argv[pos + 1]);
		if (secs < 0 || secs > 10) {
			fprintf(stderr, "Sleep argument is wrong: %s\n",
				argv[pos + 1]);
			return EXIT_FAILURE;
		}
		pos += 2;
	}

	fd = open(argv[pos], O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "Couldn't open file: %s\n", argv[pos]);
		return EXIT_FAILURE;
	}

	fprintf(stderr, "\nFLOCKS_TEST 5: %s %s flock\n",
		setlk ? "SET" : getlk ? "GET" : "UNLOCK",
		lock.l_type == F_WRLCK ? "write" : "read");

	if (setlk) {
		rc = t_fcntl(fd, F_SETLKW, &lock);
	} else if (getlk) {
		rc = t_fcntl(fd, F_GETLK, &lock);
	} else if (unlk) {
		lock.l_type = F_UNLCK;
		rc = t_fcntl(fd, F_SETLKW, &lock);
	}

	if (secs)
		sleep(secs);

	close(fd);
	return rc < 0 ? -rc : 0;

}

#define	_OPS_FDS	-1

int _op_fds(int num, void *ptr)
{
	static int *fds;
	static int fdn;
	static int cfd;
	static char *name;
	struct rlimit rlb;
	int i;

	if (num == _OPS_FDS)
		return cfd;

	if (ptr) {
		name = ptr;
		cfd = open(name, O_RDWR);
		if (cfd < 0) {
			fprintf(stderr, "Couldn't open file: %s\n", name);
			return -1;
		}
		return 0;
	}

	if (num == 0 && ptr == NULL) {
		for (i = 0; i < fdn; i++)
			if (fds[i])
				close(i);
		free(fds);
		fdn = 0;
		return 0;
	}
	if (num <= 0) /* Invlid value, ignore */
		return 0;

	if (getrlimit(RLIMIT_NOFILE, &rlb)) {
		fprintf(stderr, "Getlimit error: %s|n", strerror(errno));
		return -1;
	}
	if (fdn < rlb.rlim_cur) {
		int count = rlb.rlim_cur;

		fds = realloc(fds, sizeof(int) * count);
		if (fds == NULL) {
			printf("error\n");
			return -1;
		}
		for (i = fdn; i < count; i++)
			fds[i] = 0;
		fdn = count;
	}
	for (i = 0; i < fdn; i++) {
		if (fds[i] == num) {
			cfd = i; /* Already open */
			return 0;
		}
	}
	cfd = open(name, O_RDWR);
	if (cfd < 0) {
		fprintf(stderr, "Couldn't open file: %s(%s)\n",
			name, strerror(errno));
		return -1;
	}
	if (cfd >= fdn) {
		fprintf(stderr, "Open too many files(cur:%d, lmt:%d)\n",
			cfd, fdn);
		return -1;
	}
	fds[cfd] = num;

	return 0;
}

int set_cfd(int fno)
{
	if (fno >= 0)
		return _op_fds(fno + 1, NULL);
	return -1;
}

#define	new_fds(name)	_op_fds(0, name)
#define	get_cfd()	_op_fds(_OPS_FDS, NULL)
#define	put_fds()	_op_fds(0, NULL)

#define	T6BUF_SIZE	200

int set_lock(struct flock *lock, char *buf)
{
	int i, v;
	struct tag_node {
		char    tag;
		int     mode;
	} tags[] = {
		{ 'W', F_WRLCK },
		{ 'R', F_RDLCK },
		{ 'U', F_UNLCK },
		{ 'S', 0 },	// setrlimit
		{ 'F', 0 },	// file select
		{ 'T', 0 },     // test lock
		{ 'P', 0 },     // pause
		{ 0, 0 }
	};

	for (i = 0; isspace(buf[i]) && i < T6BUF_SIZE;)
		i++;
	for (v = 0; tags[v].tag && i < T6BUF_SIZE; v++) {
		if (buf[i] == tags[v].tag) {
			char *head;

			head = buf + i + 1;
			for (; buf[i] != ','; i++)
				if (i >= T6BUF_SIZE)
					break;
			buf[i] = '\0';
			lock->l_start = atol(head);
			if (lock->l_start < 0)
				break;
			/* for special tag */
			if (tags[v].tag == 'S') {
				struct rlimit rlb;

				rlb.rlim_cur = lock->l_start;
				rlb.rlim_max = lock->l_start + 1;
				if (setrlimit(RLIMIT_NOFILE, &rlb)) {
					fprintf(stderr, "Setlimit error: %s\n",
						strerror(errno));
					return -1;
				}
				return 0;
			}
			if (tags[v].tag == 'F')
				return set_cfd(lock->l_start);

			if (tags[v].tag == 'T')
				return 2;

			if (tags[v].tag == 'P') {
				sleep(lock->l_start);
				return 0;
			}

			for (; !isdigit(buf[i]); i++)
				if (i >= T6BUF_SIZE)
					break;
			lock->l_len = atol(buf + i);
			if (lock->l_len <= 0)
				break;
			lock->l_type = tags[v].mode;
			return 1;
		}
	}
	fprintf(stderr, "Invalid line: %s\n", buf);
	return 0;
}

const char * fmode2str(int mode)
{
	static char buf[10];

	if (mode == F_WRLCK) return "W";
	if (mode == F_RDLCK) return "R";
	sprintf(buf, "%d", mode);
	return buf;
}

/*
 *	Read command from stdin then enqueue a lock
 *
 *	[W|R|U]sss,lll
 *	W: write R: read U: unlock
 *	sss: start of range
 *	lll: length of range
 *
 *	for example:
 *		F1		# open/select a fd as current one
 *		W1,100		# add a write lock from 1 to 100
 *		R100,100	# add a read lock from 100 to 199
 *		Gn		# dump lock info(output count n, 0 is all)
 */
static int t6(int argc, char *argv[])
{
	int rc = 0;
	FILE *fp;
	double stime;
	char buf[T6BUF_SIZE+1];
	struct flock lock = {
		.l_whence = SEEK_SET,
	};

	if (argc < 3) {
		fprintf(stderr, "usage: flocks_test 6 file [cmdfile]\n");
		return EXIT_FAILURE;
	}

	new_fds(argv[2]);
	if (get_cfd() < 0)
		return EXIT_FAILURE;

	fp = stdin;
	if (argc >= 4 && strcmp(argv[3], "-")) {
		fp = fopen(argv[3], "r");
		if (!fp) {
			fprintf(stderr, "Open file %s error(%s)\n",
				argv[3], strerror(errno));
			put_fds();
			return EXIT_FAILURE;
		}
	}

	memset(buf, '\0', T6BUF_SIZE + 1);
	stime = now();
	while (fgets(buf, T6BUF_SIZE, fp)) {
		lock.l_whence = SEEK_SET,
		rc = set_lock(&lock, buf);
		if (rc == 0)
			continue;
		if (rc == -1)
			break;
		if (rc == 2) {
			int fd, i, cnt;

			fd = open(argv[2], O_RDWR);
			if (fd < 0) {
				fprintf(stderr, "Couldn't open file: %s\n",
					argv[2]);
				rc = EXIT_FAILURE;
				break;
			}
			cnt = lock.l_start;
			lock.l_start = 0;
			for (i = 0; cnt == 0 || i < cnt; i++) {
				lock.l_type = F_WRLCK;
				lock.l_len = 0;
				lock.l_pid = 0;
				rc = t_fcntl(fd, F_OFD_GETLK, &lock);
				if (rc != 0) {
					fprintf(stderr, "%d: get lock: %s\n",
						getpid(), strerror(errno));
					rc = EXIT_FAILURE;
					break;
				}
				if (lock.l_type == F_UNLCK)
					break;
				if (i > 0)
					printf(";");
				printf("%s%ld,%ld", fmode2str(lock.l_type),
				       lock.l_start, lock.l_len);
				lock.l_start += lock.l_len;
			}
			if (lock.l_start > 0)
				printf(".\n");
			close(fd);
			if (rc == EXIT_FAILURE)
				break;
			continue;
		}
		rc = t_fcntl(get_cfd(), F_OFD_SETLKW, &lock);
		if (rc != 0) {
			fprintf(stderr, "%s: cannot set lock(%d): %s\n",
				argv[0], errno, strerror(errno));
			rc = EXIT_FAILURE;
			break;
		}
	}
	put_fds();
	fprintf(stderr, "Time for processing %.03lfs\n", now() - stime);
	return rc;
}

static void usage(void)
{
	fprintf(stderr,
		"usage: flocks_test test# [corresponding arguments]\n");
}

/* program entry */
int main(int argc, char *argv[])
{
	int rc = EXIT_SUCCESS;

	if (argc < 2) {
		usage();
		exit(EXIT_FAILURE);
	}

	switch (atoi(argv[1])) {
	case 1:
		rc = t1(argc, argv);
		break;
	case 2:
		rc = t2(argc, argv);
		break;
	case 3:
		rc = t3(argc, argv);
		break;
	case 4:
		rc = t4(argc, argv);
		break;
	case 5:
		rc = t5(argc, argv);
		break;
	case 6:
		rc = t6(argc, argv);
		break;
	default:
		fprintf(stderr, "unknown test number '%s'\n", argv[1]);
		break;
	}

	if (rc)
		fprintf(stderr, "exiting with rc = %d\n", rc);
	return rc;
}