/* $Header: lock.c,v 2.2 90/05/03 10:13:56 chip Exp $
 *
 * Mailbox locking.
 * Local hacks for mailbox access should be grafted here.
 *
 * $Log:	lock.c,v $
 * Revision 2.2  90/05/03  10:13:56  chip
 * Really unlock file descriptors in fd_unlock().  (!)
 * 
 * Revision 2.1  89/06/09  12:25:30  network
 * Update RCS revisions.
 * 
 * Revision 1.6  89/06/09  12:23:52  network
 * Baseline for 2.0 release.
 * 
 */

#include "deliver.h"

/*
 * Validate the locking configuration.
 */

#if (defined(ML_FCNTL) + defined(ML_LOCKF) + defined(ML_LOCKING)) > 1
  lose! "Only one of ML_FCNTL, ML_LOCKF and ML_LOCKING may be defined.";
#endif

/*
 * Support for the lockf() system call.
 */

#ifdef ML_LOCKF
#include <unistd.h>
#define SIMPLE_LOCK "lockf"
#define LOCKFD(fd, size)    lockf(fd, F_LOCK, size)
#define UNLOCKFD(fd, size)  lockf(fd, F_ULOCK, size)
#endif /* ML_LOCKF */

/*
 * Setup for the locking() system call.
 */

#ifdef ML_LOCKING
#include <sys/locking.h>
#define SIMPLE_LOCK "locking"
#define LOCKFD(fd, size)    locking(fd, LK_LOCK, size)
#define UNLOCKFD(fd, size)  locking(fd, LK_UNLCK, size)
#endif

/*
 * Local functions.
 */

#ifdef ML_DOTLOCK
static  char    *dotlock_name();
#endif
#ifdef ML_DOTMLK
static  char    *dotmlk_name();
#endif

/*----------------------------------------------------------------------
 * Lock a mailbox by name.
 *
 * This code looks quite hairy with all the ifdefs.  In fact, the only
 * somewhat strange thing here is that neither, either, or both of
 * ML_DOTLOCK and ML_DOTMLK may be defined, and we have to allow for it.
 */

int
name_lock(name)
char    *name;
{
#ifdef ML_DOTLOCK
	char    *dotlock;
#endif
#ifdef ML_DOTMLK
	char    *dotmlk;
#endif

#ifdef ML_DOTLOCK
	if ((dotlock = dotlock_name(name)) == NULL
	 || create_lockfile(dotlock) < 0)
		return -1;
#endif /* ML_DOTLOCK */

#ifdef ML_DOTMLK
	if ((dotmlk = dotmlk_name(name)) == NULL
	 || create_lockfile(dotmlk) < 0)
	{
#ifdef ML_DOTLOCK
		(void) remove_lockfile(dotlock); /* don't leave me hanging */
#endif
		return -1;
	}
#endif /* ML_DOTMLK */

	return 0;
}

/*----------------------------------------------------------------------
 * Unlock a mailbox by name.
 */

int
name_unlock(name)
char    *name;
{
	int     ret = 0;

#ifdef ML_DOTLOCK
	char    *dotlock;
#endif
#ifdef ML_DOTMLK
	char    *dotmlk;
#endif

#ifdef ML_DOTLOCK
	if ((dotlock = dotlock_name(name)) == NULL
	 || remove_lockfile(dotlock) < 0)
		ret = -1;
#endif /* ML_DOTLOCK */

#ifdef ML_DOTMLK
	if ((dotmlk = dotmlk_name(name)) == NULL
	 || remove_lockfile(dotmlk) < 0)
		ret = -1;
#endif /* ML_DOTMLK */

	return ret;
}

/*----------------------------------------------------------------------
 * Lock a file descriptor.
 */

int
fd_lock(fd)
int     fd;
{
#ifdef ML_FCNTL
	struct flock fl;

	fl.l_type = F_WRLCK;
	fl.l_whence = 0;
	fl.l_start = 0L;
	fl.l_len = 0L;

	if (fcntl(fd, F_SETLKW, &fl) == -1)
	{
		syserr("can't lock with fcntl()");
		return -1;
	}

	if (verbose)
		message("locked mailbox with fcntl()\n");
#endif /* ML_FCNTL */

#ifdef SIMPLE_LOCK
	long    pos;

	if ((pos = lseek(fd, 0L, 0)) == -1)
	{
		syserr("can't seek in mailbox");
		return -1;
	}
	if (LOCKFD(fd, 0L) == -1)
	{
		syserr("can't lock with %s()", SIMPLE_LOCK);
		return -1;
	}
	if (lseek(fd, pos, 0) == -1)
	{
		syserr("can't seek in mailbox");
		return -1;
	}

	if (verbose)
		message("locked mailbox with %s()\n", SIMPLE_LOCK);
#endif /* SIMPLE_LOCK */

	/* Default: success */
	return 0;
}

/*----------------------------------------------------------------------
 * Unlock a file descriptor.
 */

int
fd_unlock(fd)
int     fd;
{
#ifdef ML_FCNTL
	struct flock fl;

	fl.l_type = F_UNLCK;
	fl.l_whence = 0;
	fl.l_start = 0L;
	fl.l_len = 0L;

	if (fcntl(fd, F_SETLKW, &fl) == -1)
	{
		syserr("can't unlock with fcntl()");
		return -1;
	}

	if (verbose)
		message("unlocked mailbox with fcntl()\n");
#endif /* ML_FCNTL */

#ifdef SIMPLE_LOCK
	long    pos;

	if ((pos = lseek(fd, 0L, 0)) == -1)
	{
		syserr("can't seek in mailbox");
		return -1;
	}
	if (UNLOCKFD(fd, 0L) == -1)
	{
		syserr("can't unlock with %s()", SIMPLE_LOCK);
		return -1;
	}
	if (lseek(fd, pos, 0) == -1)
	{
		syserr("can't seek in mailbox");
		return -1;
	}

	if (verbose)
		message("unlocked mailbox with %s()\n", SIMPLE_LOCK);
#endif /* SIMPLE_LOCK */

	/* Default: success */
	return 0;
}

/*----------------------------------------------------------------------
 * Return the name of the appropriate ".lock" file for a mailbox.
 */

#ifdef ML_DOTLOCK

static char *
dotlock_name(name)
char    *name;
{
	static char *lname = NULL;
	static int lsize = 0;
	char    *p;
	int     n, i;

	n = strlen(name);
	if (lsize < n + 8)
	{
		if (lname)
			free(lname);
		lsize = n + 32;
		lname = zalloc(lsize);
	}

	(void) strcpy(lname, name);

	/*
	 * We want as much of `basename.lock' as will fit in a string
	 * MAX_NAMESIZE long.
	 */
	for (i = 0, p = basename(lname); (i < MAX_NAMESIZE - 5) && (*p); ++i)
		++p;
	(void) strcpy(p, ".lock");

	return lname;
}

#endif /* ML_DOTLOCK */

/*----------------------------------------------------------------------
 * Return the name of the appropriate ".mlk" file for a mailbox.
 */

#ifdef ML_DOTMLK

static char *
dotmlk_name(name)
char    *name;
{
	static char lname[MAX_NAMESIZE + 16];
	char    *p, *d;
	int     i;

	/*
	 * To explain the below:  If we ass_u_me that MAX_NAMESIZE is 14,
	 * then this code is like `printf(lname, "/tmp/%.10s.mlk", ...)'.
	 * In other words, we want as much of `basename.mlk' as will fit
	 * in a string MAX_NAMESIZE long.
	 */
	d = lname;
	for (p = "/tmp/"; *p; )
		*d++ = *p++;
	for (i = 0, p = basename(name); (i < MAX_NAMESIZE - 4) && (*p); ++i)
		*d++ = *p++;
	(void) strcpy(d, ".mlk");

	return lname;
}

#endif /* ML_DOTMLK */

/*----------------------------------------------------------------------
 * Create a lockfile.
 */

int
create_lockfile(name)
char    *name;
{
#ifndef O_CREAT
	char    *othername, *p;
#endif
	int     fd, tries;

#ifndef O_CREAT
	othername = zalloc(strlen(name) + 20);  /* fudge (???) */
	(void) strcpy(othername, name);
	(void) sprintf(basename(othername), ".dl.%d", getpid());
	if ((fd = creat(othername, 0)) == -1)
	{
		syserr("can't create %s", othername);
		return -1;
	}
	(void) close(fd);
	if (verbose)
		message("created pre-lockfile %s\n", name);
#endif

	for (tries = 0; tries < 10; ++tries)
	{
		if (tries)
			snooze(3);

#ifdef O_CREAT

		if ((fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0)) >= 0)
		{
			(void) close(fd);
			if (verbose)
				message("created lockfile %s\n", name);
			return 0;
		}

#else /* not O_CREAT */

		if (link(othername, name) == 0)
		{
			if (unlink(othername) == -1)
				syserr("can't remove %s", othername);
			free(othername);
			if (verbose)
				message("created lockfile %s\n", name);
			return 0;
		}

#endif /* not O_CREAT */

		if (verbose && (tries == 0))
			message("Waiting to create %s\n", name);
	}

	syserr("can't create lockfile %s", name);
	return -1;
}

/*----------------------------------------------------------------------
 * Remove a lockfile.
 */

int
remove_lockfile(name)
char    *name;
{
	if (unlink(name) == -1)
	{
		syserr("can't remove lockfile %s", name);
		return -1;
	}

	if (verbose)
		message("removed lockfile %s\n", name);

	return 0;
}
