/*
 * $Id: stdio_record_locking.c,v 1.13 2005-04-25 15:39:56 obarthel Exp $
 *
 * :ts=4
 *
 * Portable ISO 'C' (1994) runtime library for the Amiga computer
 * Copyright (c) 2002-2005 by Olaf Barthel <olsen@sourcery.han.de>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Neither the name of Olaf Barthel nor the names of contributors
 *     may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef _STDIO_HEADERS_H
#include "stdio_headers.h"
#endif /* _STDIO_HEADERS_H */

#ifndef _UNISTD_HEADERS_H
#include "unistd_headers.h"
#endif /* _UNISTD_HEADERS_H */

/****************************************************************************/

#ifndef _STDLIB_MEMORY_H
#include "stdlib_memory.h"
#endif /* _STDLIB_MEMORY_H */

/****************************************************************************/

#include <strings.h>
#include <limits.h>

/****************************************************************************/

#if defined(UNIX_PATH_SEMANTICS)

/****************************************************************************/

/* System-wide global data structure all programs which use advisory file record
 * locking with this runtime library will use.
 */
struct FileLockSemaphore
{
	struct SignalSemaphore	fls_Semaphore;	/* Standard signal semaphore part */
	UWORD					fls_Size;		/* Size of this data structure (may grow) */
	struct MinList			fls_LockList;	/* List of lock nodes */
};

/* Each file which has regions locked is registered through the following
 * data structure.
 */
struct FileLockNode
{
	struct MinNode	fln_MinNode;			/* Standard node */
	struct MinList	fln_LockedRegionList;	/* List of locked regions */

	BPTR			fln_FileParentDir;		/* Refers to the file's parent directory */
	UBYTE			fln_FileName[1];		/* Name of the file; together with the
											 * parent directory, this should uniquely
											 * identify the file.
											 */
};

/* A single locked region, as associated with a file. */
struct LockedRegionNode
{
	struct MinNode	lrn_MinNode;			/* Standard node */
	LONG			lrn_Start;				/* Where the region begins */
	LONG			lrn_Stop;				/* Where the region ends (inclusive) */
	LONG			lrn_Length;				/* Original length requested */
	pid_t			lrn_Owner;				/* Which process owns the region */
	BOOL			lrn_Shared;				/* Whether or not this region has been locked
											 * for shared access.
											 */
};

/****************************************************************************/

static struct FileLockSemaphore * FileLockSemaphore;

/****************************************************************************/

STATIC VOID release_file_lock_semaphore(struct FileLockSemaphore *fls);
STATIC struct FileLockSemaphore *obtain_file_lock_semaphore(BOOL shared);
STATIC VOID remove_locked_region_node(struct FileLockSemaphore *fls, struct fd *fd, LONG start, LONG stop, LONG original_length);
STATIC VOID delete_locked_region_node(struct LockedRegionNode *lrn);
STATIC LONG create_locked_region_node(struct LockedRegionNode **result_ptr);
STATIC VOID delete_file_lock_node(struct FileLockNode *fln);
STATIC LONG create_file_lock_node(struct fd *fd, struct FileLockNode **result_ptr);
STATIC LONG find_file_lock_node_by_file_handle(struct FileLockSemaphore *fls, BPTR file_handle, struct FileLockNode **result_ptr);
STATIC LONG find_file_lock_node_by_drawer_and_name(struct FileLockSemaphore *fls, BPTR dir_lock, STRPTR file_name, struct FileLockNode **result_ptr);
STATIC struct LockedRegionNode *find_colliding_region(struct FileLockNode *fln, LONG start, LONG stop, BOOL shared);
STATIC VOID cleanup_locked_records(struct fd * fd);

/****************************************************************************/

STATIC VOID
release_file_lock_semaphore(struct FileLockSemaphore * fls)
{
	ENTER();

	SHOWPOINTER(fls);

	if(fls != NULL)
		ReleaseSemaphore((struct SignalSemaphore *)fls);

	LEAVE();
}

/****************************************************************************/

STATIC struct FileLockSemaphore *
obtain_file_lock_semaphore(BOOL shared)
{
	struct FileLockSemaphore * result = NULL;

	ENTER();

	if(FileLockSemaphore == NULL && __file_lock_semaphore_name != NULL && __file_lock_semaphore_name[0] != '\0')
	{
		struct FileLockSemaphore * fls = NULL;
		char * semaphore_name_copy = NULL;

		/* We allocate the new semaphore first, so that we don't spend
		   any time in Forbid() allocating memory. */
		#if defined(__amigaos4__)
		{
			semaphore_name_copy = AllocVec(strlen(__file_lock_semaphore_name)+1,MEMF_ANY|MEMF_PUBLIC);
			if(semaphore_name_copy != NULL)
			{
				strcpy(semaphore_name_copy,__file_lock_semaphore_name);

				fls = AllocSysObjectTags(ASOT_SEMAPHORE,
					ASOSEM_Size,	sizeof(*fls),
					ASOSEM_Name,	semaphore_name_copy,
					ASOSEM_Public,	FALSE, /* we want to add it ourselves */
					ASOSEM_Pri,		1,
				TAG_END);
			}
		}
		#else
		{
			fls = AllocVec(sizeof(*fls) + strlen(__file_lock_semaphore_name) + 1,MEMF_ANY|MEMF_PUBLIC);
			if(fls != NULL)
			{
				memset(fls,0,sizeof(*fls));

				fls->fls_Semaphore.ss_Link.ln_Name = (char *)(fls + 1);
				strcpy(fls->fls_Semaphore.ss_Link.ln_Name,__file_lock_semaphore_name);

				fls->fls_Semaphore.ss_Link.ln_Pri = 1;
			}
		}
		#endif /* __amigaos4__ */

		SHOWMSG("try to find the locking semaphore");

		Forbid();

		FileLockSemaphore = (struct FileLockSemaphore *)FindSemaphore((STRPTR)__file_lock_semaphore_name);
		if(FileLockSemaphore == NULL)
		{
			SHOWMSG("didn't find it; we're going to add our own");

			if(fls != NULL)
			{
				SHOWMSG("adding our own semaphore");

				fls->fls_Size = sizeof(*fls);
				NewList((struct List *)&fls->fls_LockList);

				AddSemaphore(&fls->fls_Semaphore);

				FileLockSemaphore = fls;
				fls = NULL;

				semaphore_name_copy = NULL;
			}
			else
			{
				SHOWMSG("not enough memory");
			}
		}

		Permit();

		if(FileLockSemaphore != NULL && FileLockSemaphore->fls_Size < sizeof(*FileLockSemaphore))
		{
			SHOWMSG("semaphore found, but it's too short");

			SHOWVALUE(FileLockSemaphore->fls_Size);
			SHOWVALUE(sizeof(*FileLockSemaphore));

			FileLockSemaphore = NULL;
		}

		/* Release the memory allocated for the semaphore, in case
		   we didn't need it after all. */
		if(fls != NULL)
		{
			#if defined(__amigaos4__)
			{
				FreeVec(semaphore_name_copy);

				FreeSysObject(ASOT_SEMAPHORE,fls);
			}
			#else
			{
				FreeVec(fls);
			}
			#endif /* __amigaos4__ */
		}
	}

	if(FileLockSemaphore != NULL)
	{
		SHOWMSG("got a semaphore, using it...");

		PROFILE_OFF();

		if(shared)
		{
			#if defined(__amigaos4__)
			{
				ObtainSemaphoreShared((struct SignalSemaphore *)FileLockSemaphore);
			}
			#else
			{
				if(((struct Library *)SysBase)->lib_Version >= 39)
				{
					ObtainSemaphoreShared((struct SignalSemaphore *)FileLockSemaphore);
				}
				else
				{
					/* Workaround for shared semaphore nesting problem. */
					if(CANNOT AttemptSemaphoreShared((struct SignalSemaphore *)FileLockSemaphore))
					{
						if(CANNOT AttemptSemaphore((struct SignalSemaphore *)FileLockSemaphore))
							ObtainSemaphoreShared((struct SignalSemaphore *)FileLockSemaphore);
					}
				}
			}
			#endif /* __amigaos4__ */
		}
		else
		{
			ObtainSemaphore((struct SignalSemaphore *)FileLockSemaphore);
		}

		PROFILE_ON();

		result = FileLockSemaphore;
	}
	else
	{
		SHOWMSG("didn't get the semaphore");
	}

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC VOID
remove_locked_region_node(struct FileLockSemaphore * fls,struct fd * fd,LONG start,LONG UNUSED stop,LONG original_length)
{
	ENTER();

	assert( fls != NULL && fd != NULL );

	if(FLAG_IS_SET(fd->fd_Flags,FDF_IS_LOCKED))
	{
		struct FileLockNode * which_lock = NULL;

		SHOWMSG("found a locked file");

		/* Find the locked file this descriptor
		 * buffer belongs to.
		 */
		if(find_file_lock_node_by_file_handle(fls,fd->fd_DefaultFile,&which_lock) == OK)
		{
			struct LockedRegionNode * lrn;
			struct LockedRegionNode * lrn_next;
			pid_t this_task = getpid();

			assert( which_lock != NULL );

			D(("trying to unlock the region %ld..%ld",start,stop));

			/* Find the region to unlock and remove it. */
			for(lrn = (struct LockedRegionNode *)which_lock->fln_LockedRegionList.mlh_Head ;
			    (lrn_next = (struct LockedRegionNode *)lrn->lrn_MinNode.mln_Succ) != NULL ;
			    lrn = lrn_next)
			{
				if(lrn->lrn_Owner	== this_task &&
				   lrn->lrn_Start	== start &&
				   lrn->lrn_Length	== original_length)
				{
					SHOWMSG("unlocking all regions on this file");

					Remove((struct Node *)lrn);
					delete_locked_region_node(lrn);
				}
			}

			/* Check if there are any locked regions left.
			 * If not, mark the entire file as unlocked.
			 */
			if(IsListEmpty((struct List *)&which_lock->fln_LockedRegionList))
			{
				SHOWMSG("no more regions are locked; removing the file lock node");

				Remove((struct Node *)which_lock);
				delete_file_lock_node(which_lock);

				/* If this is an alias, move up to the real thing. */
				if(fd->fd_Original != NULL)
					fd = fd->fd_Original;

				do
				{
					CLEAR_FLAG(fd->fd_Flags,FDF_IS_LOCKED);
				}
				while((fd = fd->fd_NextLink) != NULL);
			}
		}
	}

	LEAVE();
}

/****************************************************************************/

STATIC VOID
delete_locked_region_node(struct LockedRegionNode * lrn)
{
	ENTER();

	SHOWPOINTER(lrn);

	FreeVec(lrn);

	LEAVE();
}

/****************************************************************************/

STATIC LONG
create_locked_region_node(struct LockedRegionNode ** result_ptr)
{
	struct LockedRegionNode * lrn;
	LONG error = OK;

	ENTER();

	assert( result_ptr != NULL );

	lrn = AllocVec(sizeof(*lrn),MEMF_ANY|MEMF_PUBLIC|MEMF_CLEAR);
	if(lrn == NULL)
	{
		SHOWMSG("not enough memory for locked region node");

		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	lrn->lrn_Owner = getpid();

 out:

	(*result_ptr) = lrn;

	RETURN(error);
	return(error);
}

/****************************************************************************/

STATIC VOID
delete_file_lock_node(struct FileLockNode * fln)
{
	ENTER();

	SHOWPOINTER(fln);

	if(fln != NULL)
	{
		PROFILE_OFF();
		UnLock(fln->fln_FileParentDir);
		PROFILE_ON();

		FreeVec(fln);
	}

	LEAVE();
}

/****************************************************************************/

STATIC LONG
create_file_lock_node(struct fd * fd,struct FileLockNode ** result_ptr)
{
	struct FileLockNode * result = NULL;
	struct FileLockNode * fln = NULL;
	D_S(struct FileInfoBlock,fib);
	LONG error = OK;

	ENTER();

	assert( fd != NULL && result_ptr != NULL );

	/* We store a lock on the file's parent directory
	 * and the name of the file for later use in
	 * comparisons.
	 */
	if(CANNOT __safe_examine_file_handle(fd->fd_DefaultFile,fib))
	{
		SHOWMSG("couldn't examine file handle");

		error = IoErr();
		goto out;
	}

	fln = AllocVec(sizeof(*fln) + strlen(fib->fib_FileName),MEMF_ANY|MEMF_PUBLIC);
	if(fln == NULL)
	{
		SHOWMSG("not enough memory for lock node");

		error = ERROR_NO_FREE_STORE;
		goto out;
	}

	memset(fln,0,sizeof(*fln));

	fln->fln_FileParentDir = __safe_parent_of_file_handle(fd->fd_DefaultFile);
	if(fln->fln_FileParentDir == ZERO)
	{
		SHOWMSG("couldn't get parent directory");

		error = IoErr();
		goto out;
	}

	strcpy(fln->fln_FileName,fib->fib_FileName);

	NewList((struct List *)&fln->fln_LockedRegionList);

	result = fln;
	fln = NULL;

 out:

	delete_file_lock_node(fln);

	(*result_ptr) = result;

	RETURN(error);
	return(error);
}

/****************************************************************************/

STATIC LONG
find_file_lock_node_by_drawer_and_name(
	struct FileLockSemaphore *	fls,
	BPTR						dir_lock,
	STRPTR						file_name,
	struct FileLockNode **		result_ptr)
{
	DECLARE_UTILITYBASE();
	struct FileLockNode * result = NULL;
	struct FileLockNode * fln;
	LONG status;
	LONG error;

	ENTER();

	assert( fls != NULL && dir_lock != ZERO && file_name != NULL && result_ptr != NULL );
	assert( UtilityBase != NULL );

	#if DEBUG
	{
		char name[FILENAME_MAX];

		if(NameFromLock(dir_lock,name,sizeof(name)))
		{
			if(AddPart(name,file_name,sizeof(name)))
				D(("Looking for a lock on file |%s|",name));
		}
	}
	#endif /* DEBUG */

	error = ERROR_OBJECT_NOT_FOUND;

	for(fln = (struct FileLockNode *)fls->fls_LockList.mlh_Head ;
	    fln->fln_MinNode.mln_Succ != NULL ;
	    fln = (struct FileLockNode *)fln->fln_MinNode.mln_Succ)
	{
		PROFILE_OFF();
		status = SameLock(fln->fln_FileParentDir,dir_lock);
		PROFILE_ON();

		if(status == LOCK_SAME)
		{
			if(Stricmp(fln->fln_FileName,file_name) == SAME)
			{
				error = OK;

				result = fln;

				break;
			}
		}
	}

	if(result != NULL)
		SHOWMSG("found one");
	else
		SHOWMSG("didn't find one");

	(*result_ptr) = result;

	RETURN(error);
	return(error);
}

/****************************************************************************/

STATIC LONG
find_file_lock_node_by_file_handle(
	struct FileLockSemaphore *	fls,
	BPTR						file_handle,
	struct FileLockNode **		result_ptr)
{
	D_S(struct FileInfoBlock,this_fib);
	BPTR parent_dir = ZERO;
	LONG error;

	ENTER();

	assert( fls != NULL && file_handle != ZERO && result_ptr != NULL );

	(*result_ptr) = NULL;

	if(CANNOT __safe_examine_file_handle(file_handle,this_fib))
	{
		SHOWMSG("couldn't examine file handle");

		error = IoErr();
		goto out;
	}

	/* Determine the file's parent directory and
	 * name. These will be compared against the
	 * global file lock data.
	 */
	parent_dir = __safe_parent_of_file_handle(file_handle);
	if(parent_dir == ZERO)
	{
		SHOWMSG("couldn't get parent directory");

		error = IoErr();
		goto out;
	}

	error = find_file_lock_node_by_drawer_and_name(fls,parent_dir,this_fib->fib_FileName,result_ptr);
	if(error != OK)
	{
		SHOWMSG("couldn't find a lock node for the file");
		goto out;
	}

 out:

	PROFILE_OFF();
	UnLock(parent_dir);
	PROFILE_ON();

	RETURN(error);
	return(error);
}

/****************************************************************************/

STATIC struct LockedRegionNode *
find_colliding_region(struct FileLockNode * fln,LONG start,LONG stop,BOOL shared)
{
	struct LockedRegionNode * result = NULL;
	struct LockedRegionNode * lrn;
	pid_t this_task = getpid();

	ENTER();

	assert( fln != NULL && start <= stop );

	SHOWVALUE(start);
	SHOWVALUE(stop);
	SHOWVALUE(shared);

	/* This routine looks for a locked region that overlaps
	 * with the specified region. It returns a pointer to the
	 * region that would collide with the specified region if
	 * a new lock were to be added.
	 */
	for(lrn = (struct LockedRegionNode *)fln->fln_LockedRegionList.mlh_Head ;
	    lrn->lrn_MinNode.mln_Succ != NULL ;
	    lrn = (struct LockedRegionNode *)lrn->lrn_MinNode.mln_Succ)
	{
		/* Do the regions overlap? */
		if(lrn->lrn_Start <= stop && start <= lrn->lrn_Stop)
		{
			/* Two shared regions may always overlap.
			 * How about the rest?
			 */
			if(NOT shared || NOT lrn->lrn_Shared)
			{
				/* The lock owner may add as many exclusive
				 * or shared locks to the same region as
				 * necessary.
				 */
				if(lrn->lrn_Owner == this_task)
				{
					/*kprintf("lock collision [%ld..%ld] : [%ld..%ld]\n",start,stop,lrn->lrn_Start,lrn->lrn_Stop);*/
					continue;
				}

				/* So we found a region that would
				 * cause a collision.
				 */
				result = lrn;
				break;
			}
		}
	}

	RETURN(result);
	return(result);
}

/****************************************************************************/

STATIC VOID
cleanup_locked_records(struct fd * fd)
{
	ENTER();

	assert( fd != NULL );

	/* This routine removes all locked regions from a file
	 * before it is eventually closed.
	 */

	if(FLAG_IS_SET(fd->fd_Flags,FDF_IS_LOCKED))
	{
		struct FileLockSemaphore * fls;

		fls = obtain_file_lock_semaphore(FALSE);
		if(fls != NULL)
		{
			BPTR file_handle = fd->fd_DefaultFile;
			struct FileLockNode * which_lock = NULL;
			pid_t this_task = getpid();
			LONG error;

			error = find_file_lock_node_by_file_handle(fls,file_handle,&which_lock);
			if(error == OK)
			{
				struct LockedRegionNode * lrn_this;
				struct LockedRegionNode * lrn_next;

				assert( which_lock != NULL );

				SHOWMSG("unlocking all regions on this file");

				for(lrn_this = (struct LockedRegionNode *)which_lock->fln_LockedRegionList.mlh_Head ;
				    (lrn_next = (struct LockedRegionNode *)lrn_this->lrn_MinNode.mln_Succ) != NULL ;
				    lrn_this = lrn_next)
				{
					if(lrn_this->lrn_Owner == this_task)
					{
						Remove((struct Node *)lrn_this);

						delete_locked_region_node(lrn_this);
					}
				}

				if(IsListEmpty((struct List *)&which_lock->fln_LockedRegionList))
				{
					SHOWMSG("no more regions are locked; removing the file lock node");

					Remove((struct Node *)which_lock);

					delete_file_lock_node(which_lock);
				}
			}

			if(error == OK || error == ERROR_OBJECT_NOT_FOUND)
			{
				/* If this is an alias, move up to the real thing. */
				if(fd->fd_Original != NULL)
					fd = fd->fd_Original;

				do
				{
					CLEAR_FLAG(fd->fd_Flags,FDF_IS_LOCKED);
				}
				while((fd = fd->fd_NextLink) != NULL);
			}

			release_file_lock_semaphore(fls);
		}
	}

	LEAVE();
}

/****************************************************************************/

int
__handle_record_locking(int cmd,struct flock * l,struct fd * fd,int * error_ptr)
{
	struct FileLockSemaphore * fls = NULL;
	BPTR file_handle = fd->fd_DefaultFile;
	struct LockedRegionNode * lrn = NULL;
	struct FileLockNode * fln = NULL;
	D_S(struct FileInfoBlock,fib);
	BOOL fib_is_valid = FALSE;
	BPTR parent_dir = ZERO;
	LONG seek_position;
	off_t current_position;
	int result = ERROR;
	off_t original_len;
	LONG error = OK;
	off_t start = 0;
	off_t len = 0;
	off_t stop;

	ENTER();

	/* This routine implements advisory file segment locking
	 * similar to 4.4BSD, but not quite the same. The functionality
	 * is a subset, somewhat similar to the functionality offered
	 * by the AmigaDOS LockRecord() and UnlockRecord() functions.
	 * This means for example that every unlock request must
	 * match the size and position of the corresponding locking
	 * request.
	 *
	 * This implementation was chosen because not every Amiga
	 * filing system implements record locking and Samba
	 * absolutely requires this functionality to work.
	 */
	assert( l != NULL && fd != NULL);
	assert( F_RDLCK <= l->l_type && l->l_type <= F_WRLCK );
	assert( SEEK_SET <= l->l_whence && l->l_whence <= SEEK_END );
	assert( error_ptr != NULL );
	assert( FLAG_IS_CLEAR(fd->fd_Flags,FDF_STDIO) );

	/* Remember to unlock any records before closing the file. */
	fd->fd_Cleanup = cleanup_locked_records;

	if((cmd == F_SETLK || cmd == F_SETLKW) && (l->l_type != F_UNLCK))
	{
		SHOWMSG("this is a lock request");

		error = create_file_lock_node(fd,&fln);
		if(error != OK)
		{
			SHOWMSG("could not create lock node");
			goto out;
		}

		error = create_locked_region_node(&lrn);
		if(error != OK)
		{
			SHOWMSG("could not create region node");
			goto out;
		}
	}
	else
	{
		SHOWMSG("this is not a lock request");
	}

	original_len = l->l_len;

	/* Now calculate the position of the first byte to lock and the number
	 * of bytes to lock.
	 */
	switch(l->l_whence)
	{
		case SEEK_SET:

			SHOWMSG("SEEK_SET");

			start = l->l_start;

			if(l->l_len == 0)
				len = LONG_MAX;
			else
				len = l->l_len;

			SHOWVALUE(start);
			SHOWVALUE(len);

			break;

		case SEEK_CUR:

			SHOWMSG("SEEK_CUR");

			PROFILE_OFF();
			seek_position = Seek(file_handle,0,OFFSET_CURRENT);
			PROFILE_ON();

			if(seek_position == SEEK_ERROR && IoErr() != OK)
			{
				SHOWMSG("could not obtain current seek position");

				error = IoErr();
				goto out;
			}

			current_position = (off_t)seek_position;

			start = current_position + l->l_start;

			if(l->l_len == 0)
				len = LONG_MAX;
			else
				len = l->l_len;

			SHOWVALUE(start);
			SHOWVALUE(len);

			break;

		case SEEK_END:

			SHOWMSG("SEEK_END");

			if(CANNOT __safe_examine_file_handle(file_handle,fib))
			{
				SHOWMSG("could not examine file");

				error = IoErr();
				goto out;
			}

			fib_is_valid = TRUE;

			start = (off_t)fib->fib_Size + l->l_start;

			if(l->l_len == 0)
				len = LONG_MAX;
			else
				len = l->l_len;

			SHOWVALUE(start);
			SHOWVALUE(len);

			break;
	}

	SHOWVALUE(start);
	SHOWVALUE(len);

	if(start < 0)
	{
		SHOWMSG("invalid start");

		(*error_ptr) = EINVAL;
		goto out;
	}

	if(len < 0)
	{
		start += len;
		if(start < 0)
		{
			SHOWMSG("invalid start");

			(*error_ptr) = EINVAL;
			goto out;
		}

		stop = start - len - 1;
		if(stop < start)
			stop = LONG_MAX;
	}
	else
	{
		stop = start - 1 + len;
		if(stop < start - 1) /* Check for overflow */
			stop = LONG_MAX;
	}

	if(l->l_type == F_UNLCK)
	{
		SHOWMSG("F_UNLCK");

		fls = obtain_file_lock_semaphore(FALSE);
		if(fls == NULL)
		{
			SHOWMSG("couldn't obtain file locking semaphore");
			(*error_ptr) = EBADF;
			goto out;
		}

		D(("unlocking %ld..%ld",start,stop));

		remove_locked_region_node(fls,fd,start,stop,original_len);
	}
	else if (cmd == F_SETLKW)
	{
		struct FileLockNode * existing_fln;
		BOOL locked;
		BOOL shared;

		SHOWMSG("F_SETLKW");

		D(("  locking %ld..%ld",start,stop));

		if(NOT fib_is_valid && CANNOT __safe_examine_file_handle(file_handle,fib))
		{
			SHOWMSG("couldn't read this file's name");

			error = IoErr();
			goto out;
		}

		parent_dir = __safe_parent_of_file_handle(file_handle);
		if(parent_dir == ZERO)
		{
			SHOWMSG("couldn't get a lock on the file's parent directory");

			error = IoErr();
			goto out;
		}

		shared = (BOOL)(l->l_type == F_RDLCK);

		if(shared)
			D(("this is a shared lock; waiting for completion"));
		else
			D(("this is an exclusive lock; waiting for completion"));

		/* Shared locks require readable files, exclusive locks require
		   writable files. */
		if((shared && FLAG_IS_CLEAR(fd->fd_Flags,FDF_READ)) ||
		   (NOT shared && FLAG_IS_CLEAR(fd->fd_Flags,FDF_WRITE)))
		{
			(*error_ptr) = EBADF;
			goto out;
		}

		lrn->lrn_Start	= start;
		lrn->lrn_Stop	= stop;
		lrn->lrn_Length	= original_len;
		lrn->lrn_Shared	= shared;

		/* Retry until we manage to lock the record. */
		locked = FALSE;

		do
		{
			fls = obtain_file_lock_semaphore(FALSE);
			if(fls == NULL)
			{
				SHOWMSG("couldn't obtain file locking semaphore");
				(*error_ptr) = EBADF;
				goto out;
			}

			if(find_file_lock_node_by_drawer_and_name(fls,parent_dir,fib->fib_FileName,&existing_fln) == OK)
			{
				SHOWMSG("that file is already locked by someone");

				if(find_colliding_region(existing_fln,start,stop,shared) == NULL)
				{
					SHOWMSG("but the locks don't collide");

					AddTail((struct List *)&existing_fln->fln_LockedRegionList,(struct Node *)lrn);
					lrn = NULL;

					locked = TRUE;
				}
				else
				{
					SHOWMSG("and the locks collide");
				}
			}
			else
			{
				SHOWMSG("nobody has any locks on this file");

				AddTail((struct List *)&fls->fls_LockList,(struct Node *)fln);
				AddTail((struct List *)&fln->fln_LockedRegionList,(struct Node *)lrn);

				fln = NULL;
				lrn = NULL;

				locked = TRUE;
			}

			release_file_lock_semaphore(fls);
			fls = NULL;

			if(NOT locked)
			{
				const int rand_max = RAND_MAX / 65536;
				int num_random_ticks;

				if(__check_abort_enabled && (SetSignal(0,0) & SIGBREAKF_CTRL_C) != 0)
				{
					SHOWMSG("lock polling loop stopped");

					delete_file_lock_node(fln);
					fln = NULL;

					delete_locked_region_node(lrn);
					lrn = NULL;

					PROFILE_OFF();
					UnLock(parent_dir);
					PROFILE_ON();

					parent_dir = ZERO;

					(*error_ptr) = EINTR;

					if(__check_abort_enabled)
						__check_abort();

					goto out;
				}

				/* Wait a little before retrying
				 * the locking operation. We add
				 * a little randomness here to
				 * reduce the likelihood of two
				 * competing processes trying to
				 * lock the same file at the
				 * same time.
				 */

				num_random_ticks = ((TICKS_PER_SECOND / 2) * (rand() / 65536)) / rand_max;

				if(num_random_ticks > 0)
				{
					PROFILE_OFF();
					Delay(num_random_ticks);
					PROFILE_ON();
				}
			}
		}
		while(NOT locked);

		SHOWMSG("the file now has a lock set");

		/* If this is an alias, move up to the real thing. */
		if(fd->fd_Original != NULL)
			fd = fd->fd_Original;

		do
		{
			SET_FLAG(fd->fd_Flags,FDF_IS_LOCKED);
		}
		while((fd = fd->fd_NextLink) != NULL);
	}
	else if (cmd == F_SETLK)
	{
		BOOL shared = (BOOL)(l->l_type == F_RDLCK);
		struct FileLockNode * existing_fln = NULL;

		SHOWMSG("F_SETLK");

		if(shared)
			D(("this is a shared lock"));
		else
			D(("this is an exclusive lock"));

		/* Shared locks require readable files, exclusive locks require
		   writable files. */
		if((shared && FLAG_IS_CLEAR(fd->fd_Flags,FDF_READ)) ||
		   (NOT shared && FLAG_IS_CLEAR(fd->fd_Flags,FDF_WRITE)))
		{
			(*error_ptr) = EBADF;
			goto out;
		}

		lrn->lrn_Start	= start;
		lrn->lrn_Stop	= stop;
		lrn->lrn_Length	= original_len;
		lrn->lrn_Shared	= shared;

		fls = obtain_file_lock_semaphore(FALSE);
		if(fls == NULL)
		{
			SHOWMSG("couldn't obtain file locking semaphore");
			(*error_ptr) = EBADF;
			goto out;
		}

		error = find_file_lock_node_by_file_handle(fls,file_handle,&existing_fln);
		if(error == OK)
		{
			SHOWMSG("that file is already locked by someone else");

			if(find_colliding_region(existing_fln,start,stop,shared) != NULL)
			{
				SHOWMSG("and the locks collide");

				(*error_ptr) = EACCES;
				goto out;
			}

			SHOWMSG("but the locks don't collide");

			AddTail((struct List *)&existing_fln->fln_LockedRegionList,(struct Node *)lrn);
			lrn = NULL;
		}
		else
		{
			if(error != ERROR_OBJECT_NOT_FOUND)
				goto out;

			SHOWMSG("nobody has any locks on this file");

			AddTail((struct List *)&fls->fls_LockList,(struct Node *)fln);
			AddTail((struct List *)&fln->fln_LockedRegionList,(struct Node *)lrn);

			fln = NULL;
			lrn = NULL;
		}

		SHOWMSG("the file now has a lock set");

		/* If this is an alias, move up to the real thing. */
		if(fd->fd_Original != NULL)
			fd = fd->fd_Original;

		do
		{
			SET_FLAG(fd->fd_Flags,FDF_IS_LOCKED);
		}
		while((fd = fd->fd_NextLink) != NULL);
	}
	else if (cmd == F_GETLK)
	{
		struct FileLockNode * fln_found = NULL;

		SHOWMSG("F_GETLK");

		fls = obtain_file_lock_semaphore(TRUE);
		if(fls == NULL)
		{
			SHOWMSG("couldn't obtain file locking semaphore");
			(*error_ptr) = EBADF;
			goto out;
		}

		SHOWMSG("checking for possible lock collision");

		error = find_file_lock_node_by_file_handle(fls,file_handle,&fln_found);
		if(error == OK)
		{
			BOOL shared = (BOOL)(l->l_type == F_RDLCK);
			struct LockedRegionNode * lrn_found;

			SHOWMSG("somebody has locked this file");

			lrn_found = find_colliding_region(fln_found,start,stop,shared);
			if(lrn_found != NULL)
			{
				SHOWMSG("there is a possible lock collision");

				l->l_type	= (lrn_found->lrn_Shared ? F_RDLCK : F_WRLCK);
				l->l_whence	= SEEK_SET;
				l->l_start	= lrn_found->lrn_Start;
				l->l_len	= lrn_found->lrn_Length;
				l->l_pid	= lrn_found->lrn_Owner;

				SHOWVALUE(l->l_type);
				SHOWVALUE(l->l_whence);
				SHOWVALUE(l->l_start);
				SHOWVALUE(l->l_len);
				SHOWVALUE(l->l_pid);
			}
			else
			{
				SHOWMSG("there is no lock collision");

				l->l_type = F_UNLCK;
			}
		}
		else
		{
			if(error != ERROR_OBJECT_NOT_FOUND)
				goto out;

			SHOWMSG("nobody has locked this file");

			l->l_type = F_UNLCK;
		}
	}

	result = OK;

 out:

	delete_file_lock_node(fln);
	delete_locked_region_node(lrn);

	release_file_lock_semaphore(fls);

	PROFILE_OFF();
	UnLock(parent_dir);
	PROFILE_ON();

	if(result != 0 && error != OK)
	{
		SetIoErr(error);

		(*error_ptr) = __translate_io_error_to_errno(error);
	}

	RETURN(result);
	return(result);
}

/****************************************************************************/

#endif /* UNIX_PATH_SEMANTICS */
