// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2000-2003 Silicon Graphics, Inc.
 * Copyright (c) 2019 Intel Corp.
 * All Rights Reserved.
 */

/*
 * Synchronized byte range lock and lease exerciser
 */

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

#include <unistd.h>
#include <sys/types.h>
#include <netdb.h>
#include <endian.h>
#include <byteswap.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define     HEX_2_ASC(x)    ((x) > 9) ? (x)-10+'a' : (x)+'0'
#define 	FILE_SIZE	1024
#define PLATFORM_INIT()     /*no-op*/
#define PLATFORM_CLEANUP()  /*no-op*/
#define LL                  "ll"

extern int h_errno;

#define inet_aton(STRING, INADDRP) \
    (((INADDRP)->s_addr = inet_addr(STRING)) == -1 ? 0 : 1)

/* this assumes 32 bit pointers */
#define PTR_TO_U64(P) ((unsigned __int64)(unsigned int)(P))
#define U64_TO_PTR(T,U) ((T)(void *)(unsigned int)(U))

#if __BYTE_ORDER == __LITTLE_ENDIAN
#define bswap_uint16(x)         (uint16_t)bswap_16(x)
#define bswap_uint32(x)         (uint32_t)bswap_32(x)
#define bswap_uint64(x)         (uint64_t)bswap_64(x)
#else
#define bswap_uint16(x)         x
#define bswap_uint32(x)         x
#define bswap_uint64(x)         x
#endif

#define SOCKET              int
#define SOCKET_READ         read
#define SOCKET_WRITE        write
#define SOCKET_CLOSE(S)     (close(S))
#define INVALID_SOCKET      -1

#define O_BINARY            0
       
#define HANDLE              int
#define INVALID_HANDLE      -1
#define SEEK(H, O)          (lseek(H, O, SEEK_SET))
#define READ(H, B, L)       (read(H, B, L))
#define WRITE(H, B, L)      (write(H, B, L))
#define CLOSE(H)            (close(H))

#define RAND()              (rand())
#define SRAND(s)            (srand(s))

#define MIN(A,B)            (((A)<(B))?(A):(B))
#define MAX(A,B)            (((A)>(B))?(A):(B))

#define ALLOC_ALIGNED(S)    (memalign(65536, S)) 
#define FREE_ALIGNED(P)     (free(P)) 

static char	*prog;
static char	*filename = 0;
static int	debug = 0;
static int	server = 1;
static int	port = 0;
static int 	testnumber = -1;
static int	saved_errno = 0;
static int      got_sigio = 0;

static SOCKET	s_fd = -1;              /* listen socket    */
static SOCKET	c_fd = -1;	        /* IPC socket       */
static HANDLE	f_fd = INVALID_HANDLE;	/* shared file      */

#define 	CMD_WRLOCK	0
#define 	CMD_RDLOCK	1
#define		CMD_UNLOCK	2
#define		CMD_CLOSE	3
#define		CMD_OPEN	4
#define		CMD_WRTEST	5
#define		CMD_RDTEST	6
#define 	CMD_SETLEASE	7
#define 	CMD_GETLEASE	8
#define		CMD_SIGIO	9
#define		CMD_WAIT_SIGIO	10
#define		CMD_TRUNCATE	11

#define		PASS 	1
#define		FAIL	0

#define		SERVER	0
#define		CLIENT	1

#define		TEST_NUM	0
#define		COMMAND		1
#define		OFFSET		2
#define		LENGTH		3
#define		RESULT		4
#define		WHO		5
#define		FLAGS		2 /* index 2 is also used for do_open() flag, see below */
#define		ARG		FLAGS /* Arguments for Lease operations */
#define		TIME		FLAGS /* Time for waiting on sigio */

static char *get_cmd_str(int cmd)
{
	switch (cmd) {
		case CMD_WRLOCK: return "write lock"; break;
		case CMD_RDLOCK: return "read lock"; break;
		case CMD_UNLOCK: return "unlock"; break;
		case CMD_CLOSE:  return "close"; break;
		case CMD_OPEN:   return "open"; break;
		case CMD_WRTEST: return "Wait for SIGIO"; break;
		case CMD_RDTEST: return "Truncate"; break;
		case CMD_SETLEASE: return "Set Lease"; break;
		case CMD_GETLEASE: return "Get Lease"; break;
		case CMD_SIGIO:    return "Setup SIGIO"; break;
		case CMD_WAIT_SIGIO: return "Wait for SIGIO"; break;
		case CMD_TRUNCATE: return "Truncate"; break;
	}
	return "unknown";
}
/* 
 * flags for Mac OS X do_open() 
 * O_RDONLY	0x0000
 * O_WRONLY	0x0001	
 * O_RDWR	0x0002
 * O_NONBLOCK	0x0004	
 * O_APPEND	0x0008
 * O_SHLOCK	0x0010	
 * O_EXLOCK	0x0020
 * O_ASYNC	0x0040	
 * O_FSYNC	0x0080
 * O_NOFOLLOW  	0x0100  
 * O_CREAT	0x0200
 * O_TRUNC	0x0400
 * O_EXCL	0x0800
 */

/*
 * When adding tests be sure to add to both the descriptions AND tests array. 
 * Also, be sure to undo whatever is set for each test (eg unlock any locks)
 * There is no need to have a matching client command for each server command
 * (or vice versa)
 */

char *lock_descriptions[] = {
    /* 1 */"Add a lock to an empty lock list",
    /* 2 */"Add a lock to the start and end of a list - no overlaps",
    /* 3 */"Add a lock to the middle of a list - no overlap",
    /* 4 */"Add different lock types to middle of the list - overlap exact match", 
    /* 5 */"Add new lock which completely overlaps any old lock in the list", 
    /* 6 */"Add new lock which itself is completely overlaped by any old lock in the list",
    /* 7 */"Add new lock which starts before any old lock in the list",
    /* 8 */"Add new lock which starts in the middle of any old lock in the list and ends after",
    /* 9 */"Add different new lock types which completely overlaps any old lock in the list",
    /* 10 */"Add different new locks which are completely overlaped by an old lock in the list",
    /* 11 */"Add different new lock types which start before the old lock in the list",
    /* 12 */"Add different new lock types which start in the middle of an old lock in the list and end after",
    /* 13 */"Add new lock, differing types and processes, to middle of the list - exact overlap match",
    /* 14 */"Add new lock, differing types and processes, which completely overlap any of the locks in the list",
    /* 15 */"Add new lock, differing types and processes, which are completely overlaped by locks in the list",
    /* 16 */"Add new lock, differing types and processes, which start before a lock in the list",
    /* 17 */"Add new lock, differing types and processes, which starts in the middle of a lock, and ends after",
    /* 18 */"Acquire write locks with overlapping ranges",
    /* 19 */"Acquire write locks with non-overlapping ranges extending beyond EOF",
    /* 20 */"Acquire write locks with overlapping ranges extending beyond EOF",
    /* 21 */"Acquire write locks on whole files",
    /* 22 */"Acquire write lock on whole file and range write lock",
    /* 23 */"Acquire read locks with non-overlapping ranges",
    /* 24 */"Acquire read locks with overlapping ranges",
    /* 25 */"Acquire read and write locks with no overlapping ranges",
    /* 26 */"Acquire read and write locks with overlapping ranges",
    /* 27 */"Acquire whole file write lock and then close without unlocking (and attempt a lock)",
    /* 28 */"Acquire two read locks, close and reopen the file, and test if the inital lock is still there",
    /* 29 */"Verify that F_GETLK for F_WRLCK doesn't require that file be opened for write",
    #if defined(macosx)
    /* 30 */"Close the opened file and open the file with SHLOCK, other client will try to open with SHLOCK too",
    /* 31 */"Close the opened file and open the file with SHLOCK, other client will try to open with EXLOCK",
    /* 32 */"Close the opened file and open the file with EXLOCK, other client will try to open with EXLOCK too"
    #endif
};

static int64_t lock_tests[][6] =
	/*	test #	Action	[offset|flags]	length		expected	server/client */
	{ 	
	/* Various simple tests exercising the list */

/* SECTION 1: WRITE and CMD_UNLOCK with the same process (SERVER) */
	/* Add a lock to an empty list */
		{1,	CMD_WRLOCK,	1,		10,		PASS,		SERVER	},
		{1,	CMD_UNLOCK,	1,		10,		PASS,		SERVER	},
		
	/* Add a lock to the start and end of a list - 1, 13 - no overlap */
		{2,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{2,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		{2,	CMD_WRLOCK,	50,		10,		PASS,		SERVER	},
		{2,	CMD_WRLOCK,	1,		5,		PASS,		SERVER	},
		{2,	CMD_WRLOCK,	70,		5,		PASS,		SERVER	},
		
		{2,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{2,	CMD_UNLOCK,	30,		10,		PASS,		SERVER	},
		{2,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},
		{2,	CMD_UNLOCK,	1,		5,		PASS,		SERVER	},
		{2,	CMD_UNLOCK,	70,		5,		PASS,		SERVER	},
		
	/* Add a lock to the middle of a list - no overlap */
		{3,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{3,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		{3,	CMD_WRLOCK,	50,		10,		PASS,		SERVER	},
		{3,	CMD_WRLOCK,	42,		5,		PASS,		SERVER	},
		
		{3,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{3,	CMD_UNLOCK,	30,		10,		PASS,		SERVER	},
		{3,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},
		{3,	CMD_UNLOCK,	42,		5,		PASS,		SERVER	},
		
	/* Add different lock types to middle of the list - overlap exact match - 3 */
		{4,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{4,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		{4,	CMD_WRLOCK,	50,		10,		PASS,		SERVER	},
		/* Exact match - same lock type */
		{4,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		/* Exact match - different lock type */
		{4,	CMD_RDLOCK,	30,		10,		PASS,		SERVER	},
		/* Exact match - unlock */
		{4,	CMD_UNLOCK,	30,		10,		PASS,		SERVER	},
		/* New lock - as above, inserting in the list again */
		{4,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		
		{4,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{4,	CMD_UNLOCK,	30,		10,		PASS,		SERVER	},
		{4,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},
		
	/* Add new lock which completely overlaps any old lock in the list - 4,5,6 */
		{5,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{5,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		{5,	CMD_WRLOCK,	50,		10,		PASS,		SERVER	},
		/* The start is the same, end overlaps */
		{5,	CMD_WRLOCK,	30,		15,		PASS,		SERVER	},
		/* The start is before, end is the same */
		{5,	CMD_WRLOCK,	25,		20,		PASS,		SERVER	},
		/* Both start and end overlap */
		{5,	CMD_WRLOCK,	22,		26,		PASS,		SERVER	},
		
		{5,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{5,	CMD_UNLOCK,	22,		26,		PASS,		SERVER	},
		{5,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},
		
	/* Add new lock which itself is completely overlaped by any old lock in the list - 7,8,10 */
		{6,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{6,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		{6,	CMD_WRLOCK,	50,		10,		PASS,		SERVER	},
		/* The start is the same, end is in the middle of old lock - NOP */
		{6,	CMD_WRLOCK,	30,		5,		PASS,		SERVER	},
		/* The start and end are in the middle of old lock - NOP */
		{6,	CMD_WRLOCK,	32,		6,		PASS,		SERVER	},
		/* Start in the middle and end is the same - NOP */
		{6,	CMD_WRLOCK,	32,		8,		PASS,		SERVER	},
		
		{6,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{6,	CMD_UNLOCK,	30,		10,		PASS,		SERVER	},
		{6,	CMD_UNLOCK,	32,		8,		PASS,		SERVER	},
		{6,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},
		
	/* Add new lock which starts before any old lock in the list - 2,9 */
		{7,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{7,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		{7,	CMD_WRLOCK,	50,		10,		PASS,		SERVER	},
		/* Here is the new lock */
		{7,	CMD_WRLOCK,	27,		10,		PASS,		SERVER	},
		/* Go again with the end of the new lock matching the start of old lock */
		{7,	CMD_WRLOCK,	25,		2,		PASS,		SERVER	},
		
		{7,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{7,	CMD_UNLOCK,	25,		15,		PASS,		SERVER	},
		{7,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},
	
	/* Add new lock which starts in the middle of any old lock in the list and ends after - 11,12 */
		{8,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{8,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		{8,	CMD_WRLOCK,	50,		10,		PASS,		SERVER	},
		/* Here is the new lock */
		{8,	CMD_WRLOCK,	35,		10,		PASS,		SERVER	},
		/* Go again with the end of the new lock matching the start of old lock */
		{8,	CMD_WRLOCK,	45,		2,		PASS,		SERVER	},
		
		{8,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{8,	CMD_UNLOCK,	30,		17,		PASS,		SERVER	},
		{8,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},
/* SECTION 2: Overlapping READ and WRITE and CMD_UNLOCK with the same process (SERVER) */
	/* Add different new lock types which completely overlaps any old lock in the list - 4,5,6 */
		{9,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{9,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		{9,	CMD_WRLOCK,	50,		10,		PASS,		SERVER	},
		/* The start is the same, end overlaps */
		{9,	CMD_RDLOCK,	30,		15,		PASS,		SERVER	},
		/* The start is before, end is the same */
		{9,	CMD_WRLOCK,	25,		20,		PASS,		SERVER	},
		/* Both start and end overlap */
		{9,	CMD_RDLOCK,	22,		26,		PASS,		SERVER	},
		
		{9,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{9,	CMD_UNLOCK,	22,		26,		PASS,		SERVER	},
		{9,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},
		
	/* Add different new locks which are completely overlaped by an old lock in the list - 7,8,10 */
		{10,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{10,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		{10,	CMD_WRLOCK,	50,		10,		PASS,		SERVER	},
		/* The start is the same, end is in the middle of old lock */
		{10,	CMD_RDLOCK,	30,		5,		PASS,		SERVER	},
		/* The start and end are in the middle of a lock */
		{10,	CMD_WRLOCK,	32,		2,		PASS,		SERVER	},
		/* Start in the middle and end is the same */
		{10,	CMD_RDLOCK,	36,		5,		PASS,		SERVER	},
		
		{10,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{10,	CMD_UNLOCK,	30,		11,		PASS,		SERVER	},
		{10,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},
		
	/* Add different new lock types which start before the old lock in the list - 2,9 */
		{11,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{11,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		{11,	CMD_WRLOCK,	50,		10,		PASS,		SERVER	},
		/* Here is the new lock */
		{11,	CMD_RDLOCK,	27,		10,		PASS,		SERVER	},
		/* Go again with the end of the new lock matching the start of lock */
		{11,	CMD_WRLOCK,	25,		3,		PASS,		SERVER	},
		
		{11,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{11,	CMD_UNLOCK,	25,		15,		PASS,		SERVER	},
		{11,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},
	
	/* Add different new lock types which start in the middle of an old lock in the list and end after - 11,12 */
		{12,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{12,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		{12,	CMD_WRLOCK,	50,		10,		PASS,		SERVER	},
		/* Here is the new lock */
		{12,	CMD_RDLOCK,	35,		10,		PASS,		SERVER	},
		/* Go again with the end of the new lock matching the start of old lock */
		{12,	CMD_WRLOCK,	44,		3,		PASS,		SERVER	},
		
		{12,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{12,	CMD_UNLOCK,	30,		18,		PASS,		SERVER	},
		{12,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},

/* SECTION 3: Overlapping READ and WRITE and CMD_UNLOCK with the different processes (CLIENT/SERVER) */
	/* Add new lock, differing types and processes, to middle of the list - exact overlap match - 3 */
		{13,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{13,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		{13,	CMD_RDLOCK,	50,		10,		PASS,		SERVER	},
		/* Same lock type, different process */
		{13,	CMD_WRLOCK,	30,		10,		FAIL,		CLIENT	},
		{13,	CMD_RDLOCK,	50,		10,		PASS,		CLIENT	},
		/* Exact match - different lock type, different process */
		{13,	CMD_RDLOCK,	30,		10,		FAIL,		CLIENT	},
		/* Exact match - unlock */
		{13,	CMD_UNLOCK,	30,		10,		PASS,		CLIENT	},
		/* New lock - as above, inserting in the list again */
		{13,	CMD_UNLOCK,	30,		10,		PASS,		SERVER	},
		/* Exact match - same lock type, different process */
		{13,	CMD_WRLOCK,	30,		10,		PASS,		CLIENT	},
		
		{13,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{13,	CMD_UNLOCK,	30,		10,		PASS,		CLIENT	},
		{13,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},
		
	/* Add new lock, differing types and processes, which completely overlap any of the locks in the list - 4,5,6 */
		{14,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{14,	CMD_WRLOCK,	30,		10,		PASS,		SERVER	},
		{14,	CMD_RDLOCK,	50,		10,		PASS,		SERVER	},
		/* The start is the same, end overlaps */
		{14,	CMD_RDLOCK,	30,		15,		FAIL,		CLIENT	},
		{14,	CMD_WRLOCK,	30,		15,		FAIL,		CLIENT	},
		/* The start is before, end is the same */
		{14,	CMD_RDLOCK,	25,		20,		FAIL,		CLIENT	},
		{14,	CMD_WRLOCK,	25,		20,		FAIL,		CLIENT	},
		/* Both start and end overlap */
		{14,	CMD_RDLOCK,	22,		26,		FAIL,		CLIENT	},
		{14,	CMD_WRLOCK,	22,		26,		FAIL,		CLIENT	},
		
		/* The start is the same, end overlaps */
		{14,	CMD_RDLOCK,	50,		15,		PASS,		CLIENT	},
		{14,	CMD_WRLOCK,	50,		17,		FAIL,		CLIENT	},
		/* The start is before, end is the same */
		{14,	CMD_RDLOCK,	45,		20,		PASS,		CLIENT	},
		{14,	CMD_WRLOCK,	43,		22,		FAIL,		CLIENT	},
		/* Both start and end overlap */
		{14,	CMD_RDLOCK,	42,		26,		PASS,		CLIENT	},
		{14,	CMD_WRLOCK,	41,		28,		FAIL,		CLIENT	},
		
		{14,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{14,	CMD_UNLOCK,	22,		26,		PASS,		SERVER	},
		{14,	CMD_UNLOCK,	42,		26,		PASS,		CLIENT	},

	/* Add new lock, differing types and processes, which are completely overlaped by an old lock in the list - 7,8,10 */
		{15,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{15,	CMD_RDLOCK,	30,		10,		PASS,		SERVER	},
		{15,	CMD_WRLOCK,	50,		10,		PASS,		SERVER	},
		/* The start is the same, end is in the middle of old lock */
		{15,	CMD_RDLOCK,	50,		5,		FAIL,		CLIENT	},
		{15,	CMD_WRLOCK,	50,		5,		FAIL,		CLIENT	},
		/* The start and end are in the middle of old lock */
		{15,	CMD_RDLOCK,	52,		6,		FAIL,		CLIENT	},
		{15,	CMD_WRLOCK,	52,		6,		FAIL,		CLIENT	},
		/* Start in the middle and end is the same */
		{15,	CMD_RDLOCK,	52,		8,		FAIL,		CLIENT	},
		{15,	CMD_WRLOCK,	52,		8,		FAIL,		CLIENT	},
		/* The start is the same, end is in the middle of old lock */
		{15,	CMD_RDLOCK,	30,		5,		PASS,		CLIENT	},
		{15,	CMD_WRLOCK,	30,		5,		FAIL,		CLIENT	},
		/* The start and end are in the middle of old lock */
		{15,	CMD_RDLOCK,	32,		6,		PASS,		CLIENT	},
		{15,	CMD_WRLOCK,	32,		6,		FAIL,		CLIENT	},
		/* Start in the middle and end is the same */
		{15,	CMD_RDLOCK,	32,		8,		PASS,		CLIENT	},
		{15,	CMD_WRLOCK,	32,		8,		FAIL,		CLIENT	},
		
		{15,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{15,	CMD_UNLOCK,	30,		10,		PASS,		SERVER	},
		{15,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},
	/* Add new lock, differing types and processes, which start before a lock in the list - 2,9 */
		{16,	CMD_RDLOCK,	10,		10,		PASS,		SERVER	},
		{16,	CMD_WRLOCK,	50,		10,		PASS,		SERVER	},
		/* Start is before, end is the start of the old lock in list */
		{16,	CMD_RDLOCK,	5,		6,		PASS,		CLIENT	},
		{16,	CMD_WRLOCK,	5,		6,		FAIL,		CLIENT	},
		/* Start is before, end is in the middle of the old lock */
		{16,	CMD_RDLOCK,	5,		10,		PASS,		CLIENT	},
		{16,	CMD_WRLOCK,	5,		10,		FAIL,		CLIENT	},
		/* Start is before, end is the start of the old lock in list */
		{16,	CMD_RDLOCK,	45,		6,		FAIL,		CLIENT	},
		{16,	CMD_WRLOCK,	45,		6,		FAIL,		CLIENT	},
		/* Start is before, end is in the middle of the old lock */
		{16,	CMD_RDLOCK,	45,		10,		FAIL,		CLIENT	},
		{16,	CMD_WRLOCK,	45,		10,		FAIL,		CLIENT	},
		
		{16,	CMD_UNLOCK,	5,		15,		PASS,		CLIENT	},
		{16,	CMD_UNLOCK,	30,		10,		PASS,		SERVER	},
		{16,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},

	/* Add new lock, differing types and processes, which starts in the middle of a lock, and ends after - 11,12 */
		{17,	CMD_WRLOCK,	10,		10,		PASS,		SERVER	},
		{17,	CMD_RDLOCK,	30,		10,		PASS,		SERVER	},
		{17,	CMD_WRLOCK,	50,		10,		PASS,		SERVER	},
		/* Start in the middle, end after lock in list */
		{17,	CMD_WRLOCK,	35,		10,		FAIL,		CLIENT	},
		/* Start matches end of lock in list  */
		{17,	CMD_RDLOCK,	35,		10,		PASS,		CLIENT	},
		{17,	CMD_RDLOCK,	44,		2,		PASS,		CLIENT	},
		/* Start in the middle, end after lock in list */
		{17,	CMD_RDLOCK,	55,		10,		FAIL,		CLIENT	},
		{17,	CMD_WRLOCK,	55,		10,		FAIL,		CLIENT	},
		/* Start matches end of lock in list  */
		{17,	CMD_RDLOCK,	59,		5,		FAIL,		CLIENT	},
		{17,	CMD_WRLOCK,	59,		5,		FAIL,		CLIENT	},
		
		{17,	CMD_UNLOCK,	10,		10,		PASS,		SERVER	},
		{17,	CMD_UNLOCK,	30,		16,		PASS,		CLIENT	},
		{17,	CMD_UNLOCK,	50,		10,		PASS,		SERVER	},

/* SECTION 4: overlapping and EOF tests */
	/* Acquire overlapping ranges */
		{18,	CMD_WRLOCK,	11,		7,		PASS,		SERVER	},
		{18,	CMD_WRLOCK,	13,		8,		FAIL,		CLIENT	},
		{18,	CMD_UNLOCK,	11,		7,		PASS,		SERVER	},
	/* Acquire different ranges beyond EOF */
		{19,	CMD_WRLOCK,	10,		FILE_SIZE,	PASS,		SERVER	},
		{19,	CMD_WRLOCK,	FILE_SIZE + 10,	10,		PASS,		CLIENT	},
		{19,	CMD_UNLOCK,	10,		FILE_SIZE,	PASS,		SERVER	},
		{19,	CMD_UNLOCK,	FILE_SIZE + 10,	10,		PASS,		CLIENT	},
	/* Acquire same range beyong EOF */
		{20,	CMD_WRLOCK,	10,		FILE_SIZE,	PASS,		SERVER,	},
		{20,	CMD_WRLOCK,	10,		FILE_SIZE,	FAIL,		CLIENT,	},
		{20,	CMD_UNLOCK,	10,		FILE_SIZE,	PASS,		SERVER,	},
	/* Acquire whole file lock */
		{21,	CMD_WRLOCK,	0,		0,		PASS,		SERVER,	},
		{21,	CMD_WRLOCK,	0,		0,		FAIL,		CLIENT,	},
		{21,	CMD_UNLOCK,	0,		0,		PASS,		SERVER,	},
	/* Acquire whole file lock, then range */
		{22,	CMD_WRLOCK,	0,		0,		PASS,		SERVER,	},
		{22,	CMD_WRLOCK,	1,		5,		FAIL,		CLIENT,	},
		{22,	CMD_UNLOCK,	0,		0,		PASS,		SERVER,	},
	/* Acquire non-overlapping read locks */
		{23,	CMD_RDLOCK, 1,		5,		PASS,		SERVER, },
		{23,	CMD_RDLOCK, 7,		6,		PASS,		CLIENT, },
		{23,	CMD_UNLOCK, 1, 		5,		PASS,		SERVER, },
		{23,	CMD_UNLOCK, 7,		6,		PASS,		CLIENT, },
	/* Acquire overlapping read locks */
		{24,	CMD_RDLOCK, 1,		5,		PASS,		SERVER, },
		{24,	CMD_RDLOCK, 2,		6,		PASS,		CLIENT, },
		{24,	CMD_UNLOCK, 1, 		5,		PASS,		SERVER, },
		{24,	CMD_UNLOCK, 1,		7,		PASS,		CLIENT, },
	/* Acquire non-overlapping read and write locks */
		{25,	CMD_RDLOCK, 1,		5,		PASS,		SERVER, },
		{25,	CMD_WRLOCK, 7,		6,		PASS,		CLIENT, },
		{25,	CMD_UNLOCK, 1, 		5,		PASS,		SERVER, },
		{25,	CMD_UNLOCK, 7,		6,		PASS,		CLIENT, },
	/* Acquire overlapping read and write locks */
		{26,	CMD_RDLOCK, 1,		5,		PASS,		SERVER, },
		{26,	CMD_WRLOCK, 2,		6,		FAIL,		CLIENT, },
		{26,	CMD_UNLOCK, 1, 		5,		PASS,		SERVER, },
	/* Acquire whole file lock, then close (without unlocking) */
		{27,	CMD_WRLOCK,	0,		0,		PASS,		SERVER,	},
		{27,	CMD_WRLOCK,	1,		5,		FAIL,		CLIENT,	},
		{27,	CMD_CLOSE,0,		0,		PASS,		SERVER,	},
		{27,	CMD_WRLOCK,	1,		5,		PASS,		CLIENT,	},
		{27,	CMD_OPEN,	O_RDWR,		0,		PASS,		SERVER,	},
		{27,	CMD_UNLOCK,	1,		5,		PASS,		CLIENT,	},
	/* Acquire two read locks, close one file and then reopen to check that first lock still exists */
		{28,	CMD_RDLOCK,	1,		5,		PASS,		SERVER,	},
		{28,	CMD_RDLOCK,	1,		5,		PASS,		CLIENT,	},
		{28,	CMD_CLOSE,0,		0,		PASS,		SERVER,	},
		{28,	CMD_OPEN,	O_RDWR,		0,		PASS,		SERVER,	},
		{28,	CMD_WRLOCK,	0,		0,		FAIL,		SERVER,	},
		{28,	CMD_UNLOCK,	1,		5,		PASS,		SERVER,	},
	/* Verify that F_GETLK for F_WRLCK doesn't require that file be opened for write */
		{29,	CMD_CLOSE, 0,		0,		PASS,		SERVER, },
		{29,	CMD_OPEN, O_RDONLY,	0,		PASS,		SERVER, },
		{29,	CMD_WRTEST, 0,		0,		PASS,		SERVER, },
		{29,	CMD_CLOSE,0,		0,		PASS,		SERVER,	},
		{29,	CMD_OPEN,	O_RDWR,		0,		PASS,		SERVER,	},
#ifdef macosx
	/* Close the opened file and open the file with SHLOCK, other client will try to open with SHLOCK too */
		{30,	CMD_CLOSE,0,		0,		PASS,		SERVER,	},
		{30,	CMD_OPEN,	O_RDWR|O_SHLOCK|O_NONBLOCK,	0,	PASS,		SERVER,	},
		{30,	CMD_CLOSE,0,		0,		PASS,		CLIENT,	},
		{30,	CMD_OPEN,	O_RDWR|O_SHLOCK|O_NONBLOCK,	0,	PASS,		CLIENT,	},
	/* Close the opened file and open the file with SHLOCK, other client will try to open with EXLOCK */
		{31,	CMD_CLOSE,0,		0,		PASS,		SERVER,	},
		{31,	CMD_CLOSE,0,		0,		PASS,		CLIENT,	},
		{31,	CMD_OPEN,	O_RDWR|O_SHLOCK|O_NONBLOCK,	0,	PASS,		SERVER,	},
		{31,	CMD_OPEN,	O_RDWR|O_EXLOCK|O_NONBLOCK,	0,	FAIL,		CLIENT,	},
	/* Close the opened file and open the file with EXLOCK, other client will try to open with EXLOCK too */
		{32,	CMD_CLOSE,0,		0,		PASS,		SERVER,	},
		{32,	CMD_CLOSE,0,		0,		FAIL,		CLIENT,	},
		{32,	CMD_OPEN,	O_RDWR|O_EXLOCK|O_NONBLOCK,	0,	PASS,		SERVER,	},
		{32,	CMD_OPEN,	O_RDWR|O_EXLOCK|O_NONBLOCK,	0,	FAIL,		CLIENT,	},
		{32,	CMD_CLOSE,0,		0,		PASS,		SERVER,	},
		{32,	CMD_CLOSE,0,		0,		FAIL,		CLIENT,	},
		{32,	CMD_OPEN,	O_RDWR,		0,		PASS,		SERVER,	},
		{32,	CMD_OPEN,	O_RDWR,		0,		PASS,		CLIENT,	},
#endif /* macosx */

	/* indicate end of array */
		{0,0,0,0,0,SERVER},
		{0,0,0,0,0,CLIENT}
	};


char *lease_descriptions[] = {
    /*  1 */"Take Read Lease",
    /*  2 */"Take Write Lease",
    /*  3 */"Fail Write Lease if file is open somewhere else",
    /*  4 */"Fail Read Lease if opened with write permissions",
    /*  5 */"Read lease gets SIGIO on write open",
    /*  6 */"Write lease gets SIGIO on read open",
    /*  7 */"Read lease does _not_ get SIGIO on read open",
    /*  8 */"Read lease gets SIGIO on write open",
    /*  9 */"Write lease gets SIGIO on truncate",
    /* 10 */"Read lease gets SIGIO on truncate",
};

static int64_t lease_tests[][6] =
	/*	test #	Action	[offset|flags|arg]	length		expected	server/client */
	/*			[sigio_wait_time]						*/
	{
	/* Various tests to exercise leases */

/* SECTION 1: Simple verification of being able to take leases */
	/* Take Read Lease */
		{1,	CMD_CLOSE,	0,		0,	PASS,		CLIENT	},
		{1,	CMD_OPEN,	O_RDONLY,	0,	PASS,		CLIENT	},
		{1,	CMD_CLOSE,	0,		0,	PASS,		SERVER	},
		{1,	CMD_OPEN,	O_RDONLY,	0,	PASS,		SERVER	},
		{1,	CMD_SETLEASE,	F_RDLCK,	0,	PASS,		SERVER	},
		{1,	CMD_GETLEASE,	F_RDLCK,	0,	PASS,		SERVER	},
		{1,	CMD_SETLEASE,	F_UNLCK,	0,	PASS,		SERVER	},
		{1,	CMD_CLOSE,	0,		0,	PASS,		SERVER	},
		{1,	CMD_CLOSE,	0,		0,	PASS,		CLIENT	},

	/* Take Write Lease */
		{2,	CMD_OPEN,	O_RDWR,		0,	PASS,		SERVER	},
		{2,	CMD_SETLEASE,	F_WRLCK,	0,	PASS,		SERVER	},
		{2,	CMD_GETLEASE,	F_WRLCK,	0,	PASS,		SERVER	},
		{2,	CMD_SETLEASE,	F_UNLCK,	0,	PASS,		SERVER	},
		{2,	CMD_CLOSE,	0,		0,	PASS,		SERVER	},
	/* Fail Write Lease with other users */
		{3,	CMD_OPEN,	O_RDONLY,	0,	PASS,		CLIENT  },
		{3,	CMD_OPEN,	O_RDWR,		0,	PASS,		SERVER	},
		{3,	CMD_SETLEASE,	F_WRLCK,	0,	FAIL,		SERVER	},
		{3,	CMD_GETLEASE,	F_WRLCK,	0,	FAIL,		SERVER	},
		{3,	CMD_CLOSE,	0,		0,	PASS,		SERVER	},
		{3,	CMD_CLOSE,	0,		0,	PASS,		CLIENT	},
	/* Fail Read Lease if opened for write */
		{4,	CMD_OPEN,	O_RDWR,		0,	PASS,		SERVER	},
		{4,	CMD_SETLEASE,	F_RDLCK,	0,	FAIL,		SERVER	},
		{4,	CMD_GETLEASE,	F_RDLCK,	0,	FAIL,		SERVER	},
		{4,	CMD_CLOSE,	0,		0,	PASS,		SERVER	},

/* SECTION 2: Proper SIGIO notifications */
	/* Get SIGIO when read lease is broken by write */
		{5,	CMD_OPEN,	O_RDONLY,	0,	PASS,		CLIENT	},
		{5,	CMD_SETLEASE,	F_RDLCK,	0,	PASS,		CLIENT	},
		{5,	CMD_GETLEASE,	F_RDLCK,	0,	PASS,		CLIENT	},
		{5,	CMD_SIGIO,	0,		0,	PASS,		CLIENT	},
		{5,	CMD_OPEN,	O_RDWR,		0,	PASS,		SERVER	},
		{5,	CMD_WAIT_SIGIO,	5,		0,	PASS,		CLIENT	},
		{5,	CMD_CLOSE,	0,		0,	PASS,		SERVER	},
		{5,	CMD_CLOSE,	0,		0,	PASS,		CLIENT	},

	/* Get SIGIO when write lease is broken by read */
		{6,	CMD_OPEN,	O_RDWR,		0,	PASS,		CLIENT	},
		{6,	CMD_SETLEASE,	F_WRLCK,	0,	PASS,		CLIENT	},
		{6,	CMD_GETLEASE,	F_WRLCK,	0,	PASS,		CLIENT	},
		{6,	CMD_SIGIO,	0,		0,	PASS,		CLIENT	},
		{6,	CMD_OPEN,	O_RDONLY,	0,	PASS,		SERVER	},
		{6,	CMD_WAIT_SIGIO,	5,		0,	PASS,		CLIENT	},
		{6,	CMD_CLOSE,	0,		0,	PASS,		SERVER	},
		{6,	CMD_CLOSE,	0,		0,	PASS,		CLIENT	},

	/* Don't get SIGIO when read lease is taken by read */
		{7,	CMD_OPEN,	O_RDONLY,	0,	PASS,		CLIENT	},
		{7,	CMD_SETLEASE,	F_RDLCK,	0,	PASS,		CLIENT	},
		{7,	CMD_GETLEASE,	F_RDLCK,	0,	PASS,		CLIENT	},
		{7,	CMD_SIGIO,	0,		0,	PASS,		CLIENT	},
		{7,	CMD_OPEN,	O_RDONLY,	0,	PASS,		SERVER	},
		{7,	CMD_WAIT_SIGIO,	5,		0,	FAIL,		CLIENT	},
		{7,	CMD_CLOSE,	0,		0,	PASS,		SERVER	},
		{7,	CMD_CLOSE,	0,		0,	PASS,		CLIENT	},

	/* Get SIGIO when Read lease is broken by Write */
		{8,	CMD_OPEN,	O_RDONLY,	0,	PASS,		CLIENT	},
		{8,	CMD_SETLEASE,	F_RDLCK,	0,	PASS,		CLIENT	},
		{8,	CMD_GETLEASE,	F_RDLCK,	0,	PASS,		CLIENT	},
		{8,	CMD_SIGIO,	0,		0,	PASS,		CLIENT	},
		{8,	CMD_OPEN,	O_RDWR,		0,	PASS,		SERVER	},
		{8,	CMD_WAIT_SIGIO,	5,		0,	PASS,		CLIENT	},
		{8,	CMD_CLOSE,	0,		0,	PASS,		SERVER	},
		{8,	CMD_CLOSE,	0,		0,	PASS,		CLIENT	},

	/* Get SIGIO when Write lease is broken by Truncate */
		{9,	CMD_OPEN,	O_RDWR,		0,	PASS,		CLIENT	},
		{9,	CMD_SETLEASE,	F_WRLCK,	0,	PASS,		CLIENT	},
		{9,	CMD_GETLEASE,	F_WRLCK,	0,	PASS,		CLIENT	},
		{9,	CMD_SIGIO,	0,		0,	PASS,		CLIENT	},
		{9,	CMD_TRUNCATE,	FILE_SIZE/2,	0,	PASS,		CLIENT	},
		{9,	CMD_WAIT_SIGIO,	5,		0,	PASS,		CLIENT	},
		{9,	CMD_CLOSE,	0,		0,	PASS,		CLIENT	},

	/* Get SIGIO when Read lease is broken by Truncate */
		{10,	CMD_OPEN,	O_RDONLY,	0,	PASS,		CLIENT	},
		{10,	CMD_SETLEASE,	F_RDLCK,	0,	PASS,		CLIENT	},
		{10,	CMD_GETLEASE,	F_RDLCK,	0,	PASS,		CLIENT	},
		{10,	CMD_SIGIO,	0,		0,	PASS,		CLIENT	},
		{10,	CMD_TRUNCATE,	FILE_SIZE/2,	0,	PASS,		SERVER	},
		{10,	CMD_WAIT_SIGIO,	5,		0,	PASS,		CLIENT	},
		{10,	CMD_CLOSE,	0,		0,	PASS,		CLIENT	},


	/* indicate end of array */
		{0,0,0,0,0,SERVER},
		{0,0,0,0,0,CLIENT}
	};

static struct {
    int32_t		test;
    int32_t		command;
    int64_t	        offset;
    int64_t		length;
    int32_t		result;
    int32_t		index;
    int32_t		error;
    int32_t		padding; /* So mac and irix have the same size struct (bloody alignment) */
} ctl;


void
usage(void)
{
    fprintf(stderr, "Usage: %s [options] sharedfile\n\
\n\
options:\n\
  -p port	TCP/IP port number for client-server communication\n\
  -d		enable debug tracing\n\
  -n #		test number to run\n\
  -h host	run as client and connect to server on remote host\n\
  		[default run as server]\n", prog);
    exit(1);
}

#define INIT_BUFSZ 512 

void
initialize(HANDLE fd)
{
    char*	ibuf;
    int		j=0;
    int		nwrite;
    int 	offset = 0;
    int 	togo = FILE_SIZE;

    ibuf = (char*)malloc(INIT_BUFSZ);
    memset(ibuf, ':', INIT_BUFSZ);

    SEEK(fd, 0L);
    while (togo) {
	offset+=j;
	j = togo > INIT_BUFSZ ? INIT_BUFSZ : togo;

	if ((nwrite = WRITE(fd, ibuf, j)) != j) {
	    if (nwrite < 0)
		perror("initialize write:");
	    else
		fprintf(stderr, "initialize: write() returns %d, not %d as expected\n", 
                        nwrite, j);
	    exit(1);
	    /*NOTREACHED*/
	}
	togo -= j;
    }
}

static int do_truncate(size_t length)
{
    int rc;

    if (debug)
	fprintf(stderr, "truncating to %ld\n", length);

again:
    rc = truncate(filename, length);
    if (rc && errno == EINTR)
	goto again;

    saved_errno = errno;

    if (rc)
	fprintf(stderr, "%s %d : %s\n",
		__FILE__, errno, strerror(errno));

    return (rc == 0 ? PASS:FAIL);
}

void release_lease(int fd)
{
	int rc;

	rc = fcntl(fd, F_SETLEASE, F_UNLCK);
	if (rc != 0)
		fprintf(stderr, "%s Failed to remove lease %d : %d %s\n",
			__FILE__, rc, errno, strerror(errno));
}

void lease_break(int sig, siginfo_t *info, void *p)
{
    if (debug)
	fprintf(stderr, "lease break %d %p fd %d\n",
		sig, info, info->si_fd);
    got_sigio = 1;
    release_lease(f_fd);
}

struct sigaction lease_break_action = {
	.sa_sigaction = lease_break,
	.sa_flags = SA_SIGINFO,
};

int do_setup_sigio(int fd)
{
	int rc;

	got_sigio = 0;

	rc = sigaction(SIGIO, &lease_break_action, NULL);
	if (rc != 0) {
		fprintf(stderr, "%s Set '%s' sigaction failed %d\n",
			__FILE__, strsignal(SIGIO), rc);
		return FAIL;
	}

	if (debug)
		fprintf(stderr, "Set '%s' sigaction on %d\n",
			strsignal(SIGIO), fd);

	rc = fcntl(fd, F_SETSIG, SIGIO);
	if (rc)
		fprintf(stderr, "%s Set '%s' sigaction failed %d\n",
			__FILE__, strsignal(SIGIO), rc);

	return (rc == 0 ? PASS : FAIL);
}

int do_wait_sigio(int32_t time)
{
    if (time <= 0)
	return FAIL;

    while (!got_sigio && time--) {
	sleep(1);
    }

    if (debug > 1 && !got_sigio)
	fprintf(stderr, "%s failed to get sigio\n",
		__FILE__);

    return (got_sigio ? PASS: FAIL);
}

int do_open(int flag)
{
    int flags = flag|O_CREAT|O_BINARY;

    if(debug > 1)
	fprintf(stderr, "do_open %s 0x%x\n", filename, flags);

    if ((f_fd = open(filename, flags, 0666)) == INVALID_HANDLE) {
	perror("shared file create");
	return FAIL;
	/*NOTREACHED*/
    }
    return PASS;
}

static int do_lock(int cmd, int type, int start, int length)
{
    int ret;
    int filedes = f_fd;
    struct flock fl;

    if(debug > 1) {
	fprintf(stderr, "do_lock: cmd=%d type=%d start=%d, length=%d\n", cmd, type, start, length);
    }

    if (f_fd < 0)
	return f_fd;
    
    fl.l_start = start;
    fl.l_len = length;
    fl.l_whence = SEEK_SET;
    fl.l_pid = getpid();
    fl.l_type = type;

    errno = 0;

    ret = fcntl(filedes, cmd, &fl);
    saved_errno = errno;	    

    if(ret)
	fprintf(stderr, "do_lock: ret = %d, errno = %d (%s)\n", ret, errno, strerror(errno));

    return(ret==0?PASS:FAIL);
}

static int do_lease(int cmd, int arg, int expected)
{
    int ret;

    if(debug > 1)
	fprintf(stderr, "do_lease: cmd=%d arg=%d exp=%X\n",
		cmd, arg, expected);

    if (f_fd < 0)
	return f_fd;

    errno = 0;

    ret = fcntl(f_fd, cmd, arg);
    saved_errno = errno;

    if (expected && (expected == ret))
	ret = 0;

    if(ret)
	fprintf(stderr, "%s do_lease: ret = %d, errno = %d (%s)\n",
		__FILE__, ret, errno, strerror(errno));

    return(ret==0?PASS:FAIL);
}

int do_close(void)
{	
    if(debug > 1) {
	fprintf(stderr, "do_close\n");
    }

    errno =0;
    CLOSE(f_fd);
    f_fd = INVALID_HANDLE;

    saved_errno = errno;	    
	
    if (errno) {
	fprintf(stderr, "%s errno = %d (%s)\n",
		__FILE__, errno, strerror(errno));
	return FAIL;
    }
    return PASS;
}

static void init_ctl(int64_t tests[][6], int32_t index)
{
    ctl.test= (int32_t)tests[index][TEST_NUM];
    ctl.command = (int32_t)tests[index][COMMAND];
    ctl.offset = tests[index][OFFSET];
    ctl.length = tests[index][LENGTH];
    ctl.index = index;
    ctl.result = (int32_t)tests[index][RESULT];
    ctl.error = 0;
}

void
send_ctl(void)
{
    int         nwrite;

    if (debug) {
	fprintf(stderr, "send_ctl: test=%d, command=%d offset=%"LL"d, length=%"LL"d, result=%d, error=%d\n", 
                ctl.test, ctl.command, (long long)ctl.offset, (long long)ctl.length,ctl.result, ctl.error);
    }

    ctl.test= bswap_uint32(ctl.test);
    ctl.command = bswap_uint32(ctl.command);
    ctl.offset = bswap_uint64(ctl.offset);
    ctl.length = bswap_uint64(ctl.length);
    ctl.result = bswap_uint32(ctl.result);
    ctl.index= bswap_uint32(ctl.index);
    ctl.error = bswap_uint32(ctl.error);
    nwrite = SOCKET_WRITE(c_fd, (char*)&ctl, sizeof(ctl));

    ctl.test= bswap_uint32(ctl.test);
    ctl.command = bswap_uint32(ctl.command);
    ctl.offset = bswap_uint64(ctl.offset);
    ctl.length = bswap_uint64(ctl.length);
    ctl.result = bswap_uint32(ctl.result);
    ctl.index= bswap_uint32(ctl.index);
    ctl.error= bswap_uint32(ctl.error);
    if (nwrite != sizeof(ctl)) {
        if (nwrite < 0)
            perror("send_ctl: write");
        else
            fprintf(stderr, "send_ctl[%d]: write() returns %d, not %zu as expected\n", 
                    ctl.test, nwrite, sizeof(ctl));
        exit(1);
        /*NOTREACHED*/
    }
}

void recv_ctl(void)
{
    int         nread;

again:
    if ((nread = SOCKET_READ(c_fd, (char*)&ctl, sizeof(ctl))) != sizeof(ctl)) {
        if (nread < 0) {
	    if (errno == EINTR)
		goto again;
            perror("recv_ctl: read");
        } else {
            fprintf(stderr, "recv_ctl[%d]: read() returns %d, not %zu as expected\n", 
                    ctl.test, nread, sizeof(ctl));
	    fprintf(stderr, "socket might has been closed by other locktest\n");
	} 
        exit(1);
        /*NOTREACHED*/
    }
    ctl.test= bswap_uint32(ctl.test);
    ctl.command = bswap_uint32(ctl.command);
    ctl.offset = bswap_uint64(ctl.offset);
    ctl.length = bswap_uint64(ctl.length);
    ctl.result = bswap_uint32(ctl.result);
    ctl.index= bswap_uint32(ctl.index);
    ctl.error= bswap_uint32(ctl.error);

    if (debug) {
	fprintf(stderr, "recv_ctl: test=%d, command=%d offset=%"LL"d, length=%"LL"d, result=%d, error=%d\n", 
                ctl.test, ctl.command, (long long)ctl.offset, (long long)ctl.length, ctl.result, ctl.error);
    }
}

void
cleanup(void)
{
    if (f_fd>=0)
        CLOSE(f_fd);
    
    if (c_fd>=0)
        SOCKET_CLOSE(c_fd);
    
    if (s_fd>=0)
        SOCKET_CLOSE(s_fd);
    
    PLATFORM_CLEANUP();
}

int
run(int64_t tests[][6], char *descriptions[]);

int
main(int argc, char *argv[])
{
    int		i, sts;
    int		c;
    struct sockaddr_in	myAddr;
    struct linger	noLinger = {1, 0};
    char	*host = NULL;
    char	*endnum;
    int		errflag = 0;
    char	*p;
    extern char	*optarg;
    extern int	optind;
    int fail_count = 0;
    int run_leases = 0;
    int test_setlease = 0;
    
    atexit(cleanup);
    
    PLATFORM_INIT();

    /* trim command name of leading directory components */
    prog = argv[0];
    for (p = prog; *p; p++) {
	if (*p == '/')
	    prog = p+1;
    }

    while ((c = getopt(argc, argv, "dLn:h:p:t?")) != EOF) {
	switch (c) {

	case 'd':	/* debug flag */
	    debug++;
	    break;

	case 'L':	/* Lease testing */
	    run_leases = 1;
	    break;

	case 'h':	/* (server) hostname */
	    server = 0;
	    host = optarg;
	    break;

	case 'n':
	    testnumber = atoi(optarg);
	    break;

	case 'p':	/* TCP/IP port */
	    port = (int)strtol(optarg, &endnum, 10);
	    if (*endnum != '\0') {
		fprintf(stderr, "%s: -p argument must be a numeric\n", 
                        prog);
		exit(1);
		/*NOTREACHED*/
	    }
	    break;

	case 't':
	    test_setlease = 1;
	    break;

	case '?':
	default:
	    errflag++;
	    break;
	}
    }

    if (errflag || optind != argc-1) {
	usage();
	/*NOTREACHED*/
    }

    filename=argv[optind];
    if (debug)
	fprintf(stderr, "Working on file : %s\n", filename);
    if (do_open(O_RDWR) == FAIL)
	exit(1);

    if (test_setlease == 1) {
	fcntl(f_fd, F_SETLEASE, F_UNLCK);
	saved_errno = errno;
	close(f_fd);
	exit(saved_errno);
    }

    setbuf(stderr, NULL);

    if (server) {
        int one = 1;
        
	s_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (s_fd == INVALID_SOCKET) {
	    perror("socket");
	    exit(1);
	    /*NOTREACHED*/
	}
	if (setsockopt(s_fd, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(one)) < 0) {
	    perror("setsockopt(nodelay)");
	    exit(1);
	    /*NOTREACHED*/
	}
        if (setsockopt(s_fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one))<0) {
            perror("setsockopt(reuseaddr)");
            exit(1);
            /*NOTREACHED*/
        }
#ifdef SO_REUSEPORT
        if (setsockopt(s_fd, SOL_SOCKET, SO_REUSEPORT, (char*)&one, sizeof(one))<0) {
            perror("setsockopt(reuseport)");
            exit(1);
            /*NOTREACHED*/
        }
#endif

	memset(&myAddr, 0, sizeof(myAddr));
	myAddr.sin_family = AF_INET;
	myAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	myAddr.sin_port = htons((short)port);
	sts = bind(s_fd, (struct sockaddr*)&myAddr, sizeof(myAddr));
	if (sts < 0) {
	    perror("bind");
	    exit(1);
	    /*NOTREACHED*/
	}

	sts = listen(s_fd, 5);	/* Max. of 5 pending connection requests */
	if (sts == -1) {
	    perror("listen");
	    exit(1);
	    /*NOTREACHED*/
	}

	if (port == 0) {
	        socklen_t addr_len = sizeof(myAddr);

		if (getsockname(s_fd, &myAddr, &addr_len)) {
		    perror("getsockname");
		    exit(1);
		}

		port = ntohs(myAddr.sin_port);
	}

	printf("server port: %d\n", port);
	fflush(stdout);

	c_fd = accept(s_fd, NULL, NULL);
	if (c_fd == INVALID_SOCKET) {
	    perror("accept");
	    exit(1);
	    /*NOTREACHED*/
	}

	if (debug) fprintf(stderr, "Client accepted\n");
	SRAND(12345L);
    }
    else {
        struct hostent  *servInfo;

        if ((servInfo = gethostbyname(host)) == NULL) {
	    fprintf(stderr, "Couldn't get hostbyname for %s", host);
	    if (h_errno == HOST_NOT_FOUND)
		fprintf(stderr, ": host not found");
	    fprintf(stderr, "\n");
            exit(1);
            /*NOTREACHED*/
        }

        c_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (c_fd == INVALID_SOCKET) {
            perror("socket");
            exit(1);
            /*NOTREACHED*/
        }
        /* avoid 200 ms delay */
        if (setsockopt(c_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&i, sizeof(i)) < 0) {
            perror("setsockopt(nodelay)");
            exit(1);
            /*NOTREACHED*/
        }
        /* Don't linger on close */
        if (setsockopt(c_fd, SOL_SOCKET, SO_LINGER, (char *)&noLinger, sizeof(noLinger)) < 0) {
            perror("setsockopt(nolinger)");
            exit(1);
            /*NOTREACHED*/
        }

	memset(&myAddr, 0, sizeof(myAddr));	/* Arrgh! &myAddr, not myAddr */
	myAddr.sin_family = AF_INET;
	memcpy(&myAddr.sin_addr, servInfo->h_addr, servInfo->h_length);
	myAddr.sin_port = htons((short)port);

	if (connect(c_fd, (struct sockaddr*)&myAddr, sizeof(myAddr)) < 0) {
            perror("unable to connect");
            fprintf(stderr, "Server might still initializing the shared file\n ");
            exit(1);
            /*NOTREACHED*/
	}

	if (debug) fprintf(stderr, "Connected to server\n");
	SRAND(6789L);
    }

    if (server)
	/* only server need do shared file */
	initialize(f_fd);

    /*
     * TCP/IP connection to be established, safe to proceed.
     *
     * real work is in here ...
     */
    if (run_leases)
	fail_count = run(lease_tests, lease_descriptions);
    else
	fail_count = run(lock_tests, lock_descriptions);

    exit(fail_count);
    /*NOTREACHED*/
}

int run(int64_t tests[][6], char *descriptions[])
{
    int index = 0;
    int end = 0;
    int result = 0;
    int last_test = 0;
    int test_count = -1;
    int fail_flag = 0;
    int fail_count = 0;
    while(!end) {
	if (server) {
	    if(testnumber > 0) {
		last_test = testnumber - 1;
		while(tests[index][TEST_NUM] != testnumber && tests[index][TEST_NUM] != 0) {
		    index++;
		}
	    }
	    /* If we have a server command, deal with it */
	    if(tests[index][WHO] == SERVER) {
		if(debug)
		    fprintf(stderr, "Got a server command (%d)\n", index);
		if(tests[index][TEST_NUM] == 0) {
		    index++;
		    continue;
		} 
		memset(&ctl, 0, sizeof(ctl));
		ctl.test = tests[index][TEST_NUM];

		if(tests[index][TEST_NUM] != 0) {
		    switch(tests[index][COMMAND]) {
			case CMD_WRLOCK:
			    result = do_lock(F_SETLK, F_WRLCK, tests[index][OFFSET], tests[index][LENGTH]);
			    break;
			case CMD_RDLOCK:
			    result = do_lock(F_SETLK, F_RDLCK, tests[index][OFFSET], tests[index][LENGTH]);
			    break;
			case CMD_UNLOCK:
			    result = do_lock(F_SETLK, F_UNLCK, tests[index][OFFSET], tests[index][LENGTH]);
			    break;
			case CMD_CLOSE:
			    result = do_close();
			    break;
			case CMD_OPEN:
			    result = do_open(tests[index][FLAGS]);
			    break;
			case CMD_WRTEST:
			    result = do_lock(F_GETLK, F_WRLCK, tests[index][OFFSET], tests[index][LENGTH]);
			    break;
			case CMD_RDTEST:
			    result = do_lock(F_GETLK, F_RDLCK, tests[index][OFFSET], tests[index][LENGTH]);
			    break;
			case CMD_SETLEASE:
			    result = do_lease(F_SETLEASE, tests[index][ARG], 0);
			    break;
			case CMD_GETLEASE:
			    result = do_lease(F_GETLEASE, tests[index][ARG], tests[index][ARG]);
			    break;
			case CMD_SIGIO:
			    result = do_setup_sigio(f_fd);
			    break;
			case CMD_WAIT_SIGIO:
			    result = do_wait_sigio(tests[index][TIME]);
			    break;
			case CMD_TRUNCATE:
			    result = do_truncate(tests[index][OFFSET]);
			    break;
		    }
		    if( result != tests[index][RESULT]) {
			fail_flag++;
			/* We have a failure */
			fprintf(stderr, "     ***** Server failure *****\n");
			fprintf(stderr, "     in test %d, while %sing using offset %lld, length %lld - err = %d:%s\n",
				ctl.test, get_cmd_str(tests[index][COMMAND]),
				(long long)tests[index][OFFSET],
				(long long)tests[index][LENGTH],
				saved_errno, strerror(saved_errno));
			fprintf(stderr, "     %d:%s\n",
					ctl.test, descriptions[ctl.test - 1]);
		    }
		}
	    /* else send it off to the client */
	    } else if (tests[index][WHO] == CLIENT) {
		if(tests[index][TEST_NUM] == 0) {
		    ctl.test = 0;
		    end=1;
		} 
		/* get the client to do something */
		init_ctl(tests, index);
		if(debug)
		    fprintf(stderr, "Sending command to client (%d) - %s - %lld:%lld\n", 
					index,
					get_cmd_str(ctl.command),
					(long long)tests[index][OFFSET],
					(long long)tests[index][LENGTH]);
		/* get the client to do something */
		ctl.index = index;
		send_ctl();
		if(ctl.test != 0) {
		    /* Get the clients response */
		    recv_ctl();
		    /* this is the whether the test passed or failed,
		     * not what the command returned */
		    if( ctl.result == FAIL ) {
			fail_flag++;
			fprintf(stderr, "     ***** Client failure *****\n");
			fprintf(stderr, "     in test %d, while %sing using offset %lld, length %lld - err = %d:%s\n",
					ctl.test, get_cmd_str(ctl.command),
					(long long)ctl.offset, (long long)ctl.length,
					ctl.error, strerror(ctl.error));
			fprintf(stderr, "     %d:%s\n",
					ctl.test, descriptions[ctl.test - 1]);
		    }
		}
	    }

	    if(last_test != tests[index][TEST_NUM]) {
	        test_count++;
	        if(fail_flag)
		    fail_count++;
	        fail_flag = 0;
	        last_test = tests[index][TEST_NUM];
	    }
		
	    index++;
	} else { /* CLIENT */
	    if(debug)
		fprintf(stderr,"client: waiting...\n");
	    /* wait for the server to do something */
	    recv_ctl();

	    /* check for a client command */
	    index = ctl.index;
	    if (tests[index][WHO] != CLIENT) { 
		fprintf(stderr, "not a client command index (%d)\n", index);
		exit(1);
	    }
		
	    if(ctl.test == 0) {
		end = 1;
		break;
	    }

	    switch(ctl.command) {
		case CMD_WRLOCK:
		    result = do_lock(F_SETLK, F_WRLCK, ctl.offset, ctl.length);
		    break;
		case CMD_RDLOCK:
		    result = do_lock(F_SETLK, F_RDLCK, ctl.offset, ctl.length);
		    break;
		case CMD_UNLOCK:
		    result = do_lock(F_SETLK, F_UNLCK, ctl.offset, ctl.length);
		    break;
		case CMD_CLOSE:
		    result = do_close();
		    break;
		case CMD_OPEN:
		    result = do_open(tests[index][FLAGS]);
		    break;
		case CMD_WRTEST:
		    result = do_lock(F_GETLK, F_WRLCK, ctl.offset, ctl.length);
		    break;
		case CMD_RDTEST:
		    result = do_lock(F_GETLK, F_RDLCK, ctl.offset, ctl.length);
		    break;
		/* NOTE offset carries the argument values */
		case CMD_SETLEASE:
		    result = do_lease(F_SETLEASE, ctl.offset, 0);
		    break;
		case CMD_GETLEASE:
		    result = do_lease(F_GETLEASE, ctl.offset, ctl.offset);
		    break;
		case CMD_SIGIO:
		    result = do_setup_sigio(f_fd);
		    break;
		case CMD_WAIT_SIGIO:
		    result = do_wait_sigio(ctl.offset);
		    break;
		case CMD_TRUNCATE:
		    result = do_truncate(ctl.offset);
		    break;
	    }
	    if( result != ctl.result ) {
		fprintf(stderr,"Failure in %d:%s\n",
			ctl.test, descriptions[ctl.test - 1]);
		fprintf(stderr,"     Got %d, wanted %d\n",
			result, ctl.result);
		ctl.result = FAIL;
		ctl.error = saved_errno;
		fail_count++;
	    } else {
		ctl.result = PASS;
		ctl.error = 0;
	    }
	    if(debug)
		fprintf(stderr,"client: sending result to server (%d)\n", ctl.index);
	    /* Send result to the server */
	    send_ctl();
	    if(last_test != tests[index][TEST_NUM]) {
		test_count++;
		last_test = tests[index][TEST_NUM];
	    }
	}
    }
    fprintf(stderr, "%d tests run, %d failed\n", test_count, fail_count);

    return fail_count;
}
