/*	cvsnt lockserver
    Copyright (C) 2004-5 Tony Hoyle and March-Hare Software Ltd

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License version 2.1 as published by the Free Software Foundation.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#include "config.h"

#include <stdio.h>
#include <stdarg.h>
#if defined( __HP_aCC ) && defined ( __ia64 )
#include <unistd.h>
#endif
#include <time.h>
#ifdef HAVE_SYS_TIMES_H
#include <sys/times.h>
#endif
#include <vector>
#include <string>
#include <map>
#include <algorithm>
#include "../cvsapi/cvsapi.h"

#include "LockService.h"

#ifdef _WIN32
#define vsnprintf _vsnprintf
#endif

#ifdef HAVE_PTHREAD_H
#include <pthread.h>
#endif

static bool DoClient(CSocketIOPtr s,size_t client, char *param);
static bool DoLock(CSocketIOPtr s,size_t client, char *param);
static bool DoUnlock(CSocketIOPtr s,size_t client, char *param);
static bool DoMonitor(CSocketIOPtr s,size_t client, char *param);
static bool DoClients(CSocketIOPtr s,size_t client, char *param);
static bool DoLocks(CSocketIOPtr s,size_t client, char *param);
static bool DoModified(CSocketIOPtr s,size_t client, char *param);
static bool DoVersion(CSocketIOPtr s,size_t client, char *param);
static bool DoClose(CSocketIOPtr s,size_t client, char *param);

static bool request_lock(size_t client, const char *path, unsigned flags, size_t& lock_to_wait_for);

extern bool g_bTestMode;
#define DEBUG if(g_bTestMode) printf

static size_t global_lockId;

const char *StateString[] = { "Logging in", "Active", "Monitoring", "Closed" };
enum ClientState { lcLogin, lcActive, lcMonitor, lcClosed };
enum LockFlags { lfRead = 0x01, lfWrite = 0x02, lfAdvisory = 0x04, lfFull = 0x08, lfClosed = 0x10 };
enum MonitorFlags { lcMonitorClient=0x01, lcMonitorLock=0x02, lcMonitorVersion=0x04 };

typedef std::map<std::string,std::string> VersionMapType;

#ifdef _WIN32
CRITICAL_SECTION g_crit;

void InitServer()
{
	InitializeCriticalSection(&g_crit);
}

void CloseServer()
{
	DeleteCriticalSection(&g_crit);
}

struct ClientLock
{
	ClientLock() { EnterCriticalSection(&g_crit); }
	~ClientLock() { LeaveCriticalSection(&g_crit); }
};

#elif defined(HAVE_PTHREAD_H)
pthread_mutex_t crit_lock = PTHREAD_MUTEX_INITIALIZER;

void InitServer()
{
}

void CloseServer()
{
}

struct ClientLock
{
	ClientLock() { pthread_mutex_lock(&crit_lock); }
	~ClientLock() { pthread_mutex_unlock(&crit_lock); }
};

#else
void InitServer()
{
}

void CloseServer()
{
}

struct ClientLock
{
	ClientLock() { }
	~ClientLock() { }
};

#endif

struct locktime_t
{
	clock_t clock;
	unsigned long rollover;

	locktime_t() { clock=0; rollover=0; }
	locktime_t(const locktime_t& other) { clock=other.clock; rollover=other.rollover; }
	locktime_t(clock_t _clock, unsigned long _roll) { clock=_clock; rollover=_roll; }
	bool operator<(const locktime_t& other) const { return rollover<other.rollover || (rollover==other.rollover && clock<other.clock); }
	bool operator>(const locktime_t& other) const { return rollover>other.rollover || (rollover==other.rollover && clock>other.clock); }
	bool operator<=(const locktime_t& other) const { return rollover<other.rollover || (rollover==other.rollover && clock<=other.clock); }
	bool operator>=(const locktime_t& other) const { return rollover>other.rollover || (rollover==other.rollover && clock<=other.clock); }
	bool operator==(const locktime_t& other) const { return rollover==other.rollover && clock==other.clock; }
	bool operator!=(const locktime_t& other) const { return rollover!=other.rollover || clock!=other.clock; }
	bool iszero() const { return !rollover && !clock; };
};

struct Lock
{
	size_t owner;
	std::string path;
	unsigned flags;
	size_t length; /* length of path */
	VersionMapType versions;
};

struct TransactionStruct
{
	TransactionStruct();
	~TransactionStruct();
	size_t owner;
	std::string path;
	std::string branch;
	std::string version;
	std::string oldversion;
	char type;

	bool operator==(const char *oth) { return !strcmp(path.c_str(),oth)?true:false; }
};

typedef std::vector<TransactionStruct> TransactionListType;

struct LockClient
{
	LockClient() { state=lcClosed; flags=0; }
	std::string server_host;
	std::string client_host;
	std::string user;
	std::string root;
	ClientState state;
	unsigned flags;
	locktime_t starttime;
	locktime_t endtime;
};

typedef std::map<size_t,LockClient> LockClientMapType;
typedef std::map<size_t,Lock> LockMapType;
LockClientMapType LockClientMap;
LockMapType LockMap;
TransactionListType TransactionList;

TransactionStruct::TransactionStruct()
{
}

TransactionStruct::~TransactionStruct()
{
}

std::map<int,size_t> SockToClient;

size_t next_client_id;

/* predicate for partition below */
struct IsWantedTransaction { bool operator()(const TransactionStruct& t) { return LockClientMap.find(t.owner)!=LockClientMap.end(); } };

bool TimeIntersects(locktime_t start1, locktime_t end1, locktime_t start2, locktime_t end2)
{
   return (start1<=start2 && (end1.iszero() || end1>=start2)) || (start1>=start2 && (end2.iszero() || end2>=start1));
}

locktime_t lock_time()
{
	static locktime_t last_clock;
	clock_t this_clock;

#ifdef _WIN32
	this_clock = GetTickCount();
#else
	struct tms ignored;
	this_clock = times(&ignored);
#endif

	if(this_clock<last_clock.clock)
		last_clock.rollover++;
	last_clock.clock = this_clock;
	return locktime_t(last_clock);
}

bool RootOverlaps(size_t client, std::string& root1, std::string& root2)
{
	if(root1.length()>root2.length())
		return strncmp(root1.c_str(),root2.c_str(),root2.length())?false:true;
	else
		return strncmp(root1.c_str(),root2.c_str(),root1.length())?false:true;
}

// When we're parsing stuff, it's sometimes handy
// to have strchr return the final token as if it
// had an implicit delimiter at the end.
static char *lock_strchr(const char *s, int ch)
{
	if(!*s)
		return NULL;
	char *p=strchr(s,ch);
	if(p)
		return p;
	return (char*)s+strlen(s);
}

const char *FlagToString(unsigned flag)
{
	static char flg[1024];

	flg[0]='\0';
	if(flag&lfRead)
		strcat(flg,"Read ");
	if(flag&lfWrite)
		strcat(flg,"Write ");
	if(flag&lfAdvisory)
		strcat(flg,"Advisory ");
	if(flag&lfFull)
		strcat(flg,"Full ");
	if(flg[0])
		flg[strlen(flg)-1]='\0'; // Chop trailing space
	return flg;
}

bool OpenLockClient(CSocketIOPtr s)
{
	cvs::string host;
#if defined( __HP_aCC ) && defined ( __ia64 )
	host.resize(255);
	if(gethostname(host.data(),255)!=0)
#else
	if(!s->gethostname(host))
#endif
	{
		DEBUG("gethostname failed: %s\n",s->error());
		return false;
	}

	LockClient l;
	l.server_host=host;
	l.state=lcLogin;
	l.starttime=lock_time();

	{
		ClientLock lock;

		size_t client = ++next_client_id;
		SockToClient[s->getsocket()]=client;
		LockClientMap[client]=l;

		DEBUG("Lock Client #%d(%s) opened\n",(int)client,host.c_str());
	}


	s->printf("CVSLock 2.2 Ready\n");

	return true;
}

bool CloseLockClient(CSocketIOPtr s)
{
	ClientLock lock;

	if(SockToClient.find(s->getsocket())==SockToClient.end())
	   return true; // Already closed

	size_t client = SockToClient[s->getsocket()];
	int count=0;
	for(LockMapType::iterator i = LockMap.begin(); i!=LockMap.end();)
	{
		if(i->second.owner == client) 
		{
			count++;
			LockMap.erase(i++);
		}
		else
			++i;
	}
	DEBUG("Destroyed %d locks\n",count);
	SockToClient.erase(SockToClient.find(s->getsocket()));
	LockClientMap[client].state=lcClosed;
	LockClientMap[client].endtime=lock_time();
	if(LockClientMap.size()<=1)
	{
		// No clients, just empty the transaction store
		LockClientMap.clear();
		TransactionList.clear();
		DEBUG("No more clients\n");
	}
	else if(TransactionList.size())
	{
		// Find out which stored clients are redundant
		//
		// The rule is that as long as there's an active client
		// that started before our client finished, you can't
		// delete it.  This random overlapping is what makes
		// atomicity hard :)
		//
		// remove_if doesn't work on maps so we end up with trickery
		// like this...
/*		LockClientMapType::iterator c,d;
		for (c=LockClientMap.begin(); c!=LockClientMap.end();)
		{
		  if(!c->second.endtime.iszero())
		  {
		    bool can_delete = true;
		    for (d=LockClientMap.begin(); d!=LockClientMap.end(); ++d)
		    {
				if(c->first != d->first && d->second.endtime.iszero() &&
					TimeIntersects(c->second.starttime,c->second.endtime,d->second.starttime,d->second.endtime) &&
					RootOverlaps(d->first,c->second.root,d->second.root))
				can_delete = false;
		    }
		    if(can_delete)
				LockClientMap.erase(c++);
		    else
		      ++c;
		  }
		  else
		    ++c;
		}
		*/
		LockClientMap.erase(LockClientMap.find(client));
		DEBUG("%d clients left\n",(int)LockClientMap.size());
		/* Life is much easier on a vector */
		size_t pos = std::partition(TransactionList.begin(), TransactionList.end(), IsWantedTransaction()) - TransactionList.begin();
		TransactionList.resize(pos);
	}
	else
	{
	  /* No transactions, so just erase this client */
	  DEBUG("No pending transactions\n");
	  LockClientMap.erase(LockClientMap.find(client));
	}

	DEBUG("Lock Client #%d closed\n",(int)client);
	return true;
}

// Lock commands:
//
// Client <user>'|'<root>['|'<client_host>]
// Lock [Read] [Write] [Advisory|Full]'|'<path>['|'<branch>]
// Unlock <LockId>
// Version <LockId>|<Branch>
// Monitor [C][L][V]
// Clients
// Locks
// Modified [Added|Deleted]'|'LockId'|'<branch>'|'<version>['|'<oldversion>]
//
// Errors:
// 000 OK {message}
// 001 FAIL {message}
// 002 WAIT {message}
// 003 WARN (message)
// 010 MONITOR {message}

bool ParseLockCommand(CSocketIOPtr s, const char *command)
{
	char *cmd,*p;
	char *param;
	bool bRet = true;
	size_t client;

	if(!*command)
		return bRet; // Empty line

	cmd = strdup(command);
	{
		ClientLock lock;
		client = SockToClient[s->getsocket()];
	}
	p=lock_strchr(cmd,' ');
	if(!p)
	{
		s->printf("001 FAIL Syntax error\n");
	}
	else
	{
		if(!*p)
			param = p;
		else
			param = p+1;
		*p='\0';
		if(!strcmp(cmd,"Client"))
			DoClient(s,client,param);
		else if(!strcmp(cmd,"Lock"))
			DoLock(s,client,param);
		else if(!strcmp(cmd,"Unlock"))
			DoUnlock(s,client,param);
		else if(!strcmp(cmd,"Version"))
			DoVersion(s,client,param);
		else if(!strcmp(cmd,"Modified"))
			DoModified(s,client,param);
		else if(!strcmp(cmd,"Monitor"))
			DoMonitor(s,client,param);
		else if(!strcmp(cmd,"Clients"))
			DoClients(s,client,param);
		else if(!strcmp(cmd,"Locks"))
			DoLocks(s,client,param);
		else if(!strcmp(cmd,"Close"))
		{
			DoClose(s,client,param);
			bRet=false;
		}
		else
			s->printf("001 FAIL Unknown command '%s'\n",cmd);
	}
	free(cmd);
	return bRet;
}

bool DoClient(CSocketIOPtr s,size_t client, char *param)
{
	ClientLock lock;
	char *user, *root, *host, *flags;
	if(LockClientMap[client].state!=lcLogin)
	{
		s->printf("001 FAIL Unexpected 'Client' command\n");
		return false;
	}
	root = strchr(param,'|');
	if(!root)
	{
		s->printf("001 FAIL Client command expects <user>|<root>[[|<client host>]|Case]\n");
		return false;
	}
	*(root++)='\0';

	host = strchr(root,'|');
	if(host)
	{
		*(host++)='\0';
		flags = strchr(host,'|');
		if(flags)
			*(flags++)='\0';
		if(flags)
		{
			if(!strcmp(flags,"Case"))
			{
				s->printf("001 FAIL Case insensitive locks no longer supported\n");
				return false;
			}
		}
	}

	user = param;

	LockClientMap[client].user=user;
	LockClientMap[client].root=root;
	LockClientMap[client].client_host=host?host:"";
	LockClientMap[client].state=lcActive;

	DEBUG("(#%d) New client %s(%s) root %s\n",(int)client,user,host?host:"unknown",root);
	s->printf("000 OK Client registered\n");
	return true;
}

bool DoLock(CSocketIOPtr s,size_t client, char *param)
{
	ClientLock lock;
	char *flags, *path,*p;
	unsigned uFlags=0;
	size_t lock_to_wait_for;

	if(LockClientMap[client].state!=lcActive)
	{
		s->printf("001 FAIL Unexpected 'Lock' command\n");
		return false;
	}
	path = strchr(param,'|');
	if(!path)
	{
		s->printf("001 FAIL Lock command expects <flags>|<path>\n");
		return false;
	}
	*(path++)='\0';
	flags = param;
	while((p=lock_strchr(flags,' '))!=NULL)
	{
		char c=*p;
		*p='\0';
		if(!strcmp(flags,"Read"))
			uFlags|=lfRead;
		else if(!strcmp(flags,"Write"))
			uFlags|=lfWrite;
		else if(!strcmp(flags,"Advisory"))
			uFlags|=lfAdvisory;
		else if(!strcmp(flags,"Full"))
			uFlags|=lfFull;
		else
		{
			s->printf("001 FAIL Unknown flag '%s'\n",flags);
			return false;
		}
		if(c) flags=p+1;
		else break;
	}
	if(!(uFlags&lfRead) && !(uFlags&lfWrite))
	{
		s->printf("001 FAIL Must specify read or write\n");
		return false;
	}

	if(strncmp(path,LockClientMap[client].root.c_str(),LockClientMap[client].root.size()))
	{
		DEBUG("(#%d) Lock Fail %s not within %s\n",(int)client,path,LockClientMap[client].root.c_str());
		s->printf("001 FAIL Lock not within repository\n");
		return false;
	}

	if(request_lock(client,path,uFlags,lock_to_wait_for))
	{
		VersionMapType ver;
		size_t newId = ++global_lockId;
		if(((uFlags&lfFull) && (uFlags&lfRead)) || (uFlags&lfWrite))
		{
			TransactionListType::const_iterator i = std::find(TransactionList.begin(), TransactionList.end(), path);
			if(i!=TransactionList.end())
			{
				/* This is where atomicity really 'happens' */
				if(i->owner!=client &&   /* Not us */
				   LockClientMap.find(i->owner)!=LockClientMap.end() && /* Exists */
				   TimeIntersects(LockClientMap[i->owner].starttime,LockClientMap[i->owner].endtime,
					   LockClientMap[client].starttime,LockClientMap[client].endtime)) /* Overlaps us */
				{
					printf("Found version %s:%s\n",i->branch.c_str(),i->oldversion.c_str());
					ver[i->branch]=i->oldversion;

					/* If a transaction is still in progress and a version has been committed,
						we can't allow even an advisory write yet. */
					if(uFlags&lfWrite)
					{
						DEBUG("(#%d) Lock request on %s (%s) (wait for transaction by client %d)\n",(int)client,path,FlagToString(uFlags),(unsigned)i->owner);
						s->printf("002 WAIT Lock busy|%s|%s|%s\n",LockClientMap[i->owner].user.c_str(),LockClientMap[i->owner].client_host.c_str(),path);
						return true;
					}
				}
			}
		}
		DEBUG("(#%d) Lock request on %s (%s) (granted %u)\n",(int)client,path,FlagToString(uFlags),(unsigned)newId);
		s->printf("000 OK Lock granted (%u)\n",(unsigned)newId);
		LockMap[newId].flags=uFlags;
		LockMap[newId].path=path;
		LockMap[newId].length=strlen(path);
		LockMap[newId].owner=client;
		LockMap[newId].versions = ver;
	}
	else
	{
		DEBUG("(#%d) Lock request on %s (%s) (wait for %u)\n",(int)client,path,FlagToString(uFlags),(unsigned)lock_to_wait_for);
		s->printf("002 WAIT Lock busy|%s|%s|%s\n",LockClientMap[LockMap[lock_to_wait_for].owner].user.c_str(),LockClientMap[LockMap[lock_to_wait_for].owner].client_host.c_str(),LockMap[lock_to_wait_for].path.c_str());
	}

	return true;
}

bool DoUnlock(CSocketIOPtr s,size_t client, char *param)
{
	ClientLock lock;
	size_t lockId;
	unsigned helper;

	if(LockClientMap[client].state!=lcActive)
	{
		s->printf("001 FAIL Unexpected 'Unlock' command\n");
		return false;
	}
	sscanf(param,"%u",&helper); /* 64 bit aware */
	lockId = helper;

	if(LockMap.find(lockId)==LockMap.end())
	{
		s->printf("001 FAIL Unknown lock id %u\n",(unsigned)lockId);
		return false;
	}
	if(LockMap[lockId].owner != client)
	{
		s->printf("001 FAIL Do not own lock id %u\n",(unsigned)lockId);
		return false;
	}

	LockMap.erase(LockMap.find(lockId));

	DEBUG("(#%d) Unlock request on lock %u\n",(int)client,(unsigned)lockId);

	s->printf("000 OK Unlocked\n");
	return true;
}

bool DoMonitor(CSocketIOPtr s,size_t client, char *param)
{
	ClientLock lock;
	if(LockClientMap[client].state!=lcLogin)
	{
		s->printf("001 FAIL Unexpected 'Monitor' command\n");
		return false;
	}
	LockClientMap[client].state=lcMonitor;
	s->printf("000 OK Entering monitor mode\n");
	return true;
}

bool DoClients(CSocketIOPtr s,size_t client, char *param)
{
	ClientLock lock;
	if(LockClientMap[client].state!=lcMonitor)
	{
		s->printf("001 FAIL Unexpected 'Clients' command\n");
		return false;
	}
	LockClientMapType::const_iterator i;
	for(i = LockClientMap.begin(); !(i==LockClientMap.end()); ++i)
	{
		s->printf("(#%d) %s|%s|%s|%s|%s\n",
			(int)i->first,
			i->second.server_host.c_str(),
			i->second.client_host.c_str(),
			i->second.user.c_str(),
			i->second.root.c_str(),
			StateString[i->second.state]);
	}
	s->printf("000 OK\n");
	return true;
}

bool DoLocks(CSocketIOPtr s,size_t client, char *param)
{
	ClientLock lock;
	if(LockClientMap[client].state!=lcMonitor)
	{
		s->printf("001 FAIL Unexpected 'Locks' command\n");
		return false;
	}
	for(LockMapType::const_iterator i = LockMap.begin(); i!=LockMap.end(); ++i)
		s->printf("(#%d) %s|%s (%u)\n",(int)i->second.owner,i->second.path.c_str(),FlagToString(i->second.flags), (unsigned)i->first);

	s->printf("000 OK\n");
	return true;
}

bool DoModified(CSocketIOPtr s,size_t client, char *param)
{
	ClientLock lock;
	char *id,*branch,*version,*oldversion;
	char type;
	size_t lockId;
	unsigned helper;

	if(LockClientMap[client].state!=lcActive)
	{
		s->printf("001 FAIL Unexpected 'Modified' command\n");
		return false;
	}
	id = strchr(param,'|');
	if(!id)
	{
		s->printf("001 FAIL Modified command expects <flags>|<lockId>|<branch>|<version>|oldversion\n");
		return false;
	}
	(*id++)='\0';
	sscanf(id,"%u",&helper);
	lockId = helper;
	branch = strchr(id,'|');
	if(!branch)
	{
		s->printf("001 FAIL Modified command expects <flags>|<lockId>|<branch>|<version>|oldversion\n");
		return false;
	}
	*(branch++)='\0';
	version = strchr(branch,'|');
	if(!version)
	{
		s->printf("001 FAIL Modified command expects <flags>|<lockId>|<branch>|<version>|oldversion\n");
		return false;
	}
	*(version++)='\0';
	oldversion = strchr(version,'|');
	if(strchr(oldversion+1,'|'))
	{
		s->printf("001 FAIL Modified command expects <flags>|<lockId>|<branch>|<version>|oldversion\n");
		return false;
	}
	if(oldversion)
	  *(oldversion++)='\0';
	if(!*param)
		type='M';
	else if(!strcmp(param,"Added"))
		type='A';
	else  if(!strcmp(param,"Deleted"))
		type='D';
	else
	{
		s->printf("001 FAIL Modified command expects <flags>|<lockId>|<branch>|<version>\n");
		return false;
	}

	if(LockMap.find(lockId)==LockMap.end())
	{
		s->printf("001 FAIL Unknown lock id %u\n",(unsigned)lockId);
		return false;
	}
	if(LockMap[lockId].owner != client)
	{
		s->printf("001 FAIL Do not own lock id %u\n",(unsigned)lockId);
		return false;
	}

	if(!(LockMap[lockId].flags&lfWrite))
	{
		s->printf("001 FAIL No write lock on file\n");
		return false;
	}

	DEBUG("(#%d) Modified request on lock %u (%s:%s [%c])\n",(int)client,(unsigned)lockId,branch,version,type);

	s->printf("000 OK\n");

	TransactionStruct t;
	t.owner=client;
	t.branch=branch;
	t.path=LockMap[lockId].path.c_str();
	t.version=version;
	t.oldversion=oldversion;
	t.type=type;

	TransactionList.push_back(t);

	return true;
}

bool DoVersion(CSocketIOPtr s,size_t client, char *param)
{
	ClientLock lock;
	char *branch;
	size_t lockId;
	unsigned helper;

	if(LockClientMap[client].state!=lcActive)
	{
		s->printf("001 FAIL Unexpected 'Version' command\n");
		return false;
	}
	branch = strchr(param,'|');
	if(!branch)
	{
		s->printf("001 FAIL Version command expects <lockid>|<branch>\n");
		return false;
	}
	(*branch++)='\0';
	
	sscanf(param,"%u",&helper); /* 64 bit aware */
	lockId = helper;

	if(LockMap.find(lockId)==LockMap.end())
	{
		s->printf("001 FAIL Unknown lock id %u\n",(unsigned)lockId);
		return false;
	}
	if(LockMap[lockId].owner != client)
	{
		s->printf("001 FAIL Do not own lock id %u\n",(unsigned)lockId);
		return false;
	}

	VersionMapType& ver = LockMap[lockId].versions;

	if(ver.find(branch)==ver.end())
	{ 
		s->printf("000 OK\n");
		DEBUG("(#%d) Version request on lock %u (%s)\n",(int)client,(unsigned)lockId,branch);
	}
	else
	{
		s->printf("000 OK (%s)\n",ver[branch].c_str());
		DEBUG("(#%d) Version request on lock %u (%s) returned %s\n",(int)client,(unsigned)lockId,branch,ver[branch].c_str());
	}

	return true;
}

bool DoClose(CSocketIOPtr s,size_t client, char *param)
{
	CloseLockClient(s);
	s->printf("000 OK\n");
	DEBUG("(#%d) Close request\n",(int)client);

	return true;
}

bool request_lock(size_t client, const char *path, unsigned flags, size_t& lock_to_wait_for)
{
	LockMapType::const_iterator i;
	size_t pathlen = strlen(path);
	for(i=LockMap.begin(); i!=LockMap.end(); ++i)
	{
		size_t locklen = i->second.length;

		if((locklen==pathlen && !strcmp(path,i->second.path.c_str())))
		{
			// Locks are as follows:
			// max. 1 advisory write lock, any number of concurrent advisory read locks.
			// max. 1 full write lock cannot be shared with any read locks
			// any number of advisory read locks (provided it doesn't clash with a full write)
			// any number of full read locks (provided it doesn't clash with a full write)

			// As a special concession allow the same user to add multiple locks

			if(flags&lfWrite) /* Trying to add write lock */
			{
				/* Only one write lock (full or advisory) on any object */
				if(i->second.flags&lfWrite && i->second.owner!=client)
					break;
				/* If there is a full read lock on any object, can't write at the moment */
				if(((i->second.flags&(lfRead|lfFull))==(lfRead|lfFull)) && i->second.owner!=client)
					break;
			}
			else /* read lock */
			{
				/* If there is a full or advisory write lock on this object then fail */
				/* Because of the access patterns of CVS, we can't allow advisory write locks to allow
				   read.  This sucks, but without large amounts of farting around with the structure
				   there's nothing that can be done for now. */
				if(((i->second.flags&(/*lfFull|*/lfWrite))==(/*lfFull|*/lfWrite)) && i->second.owner!=client)
					break;
			}
		}
	}
	if(i!=LockMap.end())
	{
		lock_to_wait_for = i->first;
		return false;
	}
	return true;
}



syntax highlighted by Code2HTML, v. 0.9.1