/*
 * file locking support functions
 *
 * original code from liblockfile
 *
 * Much of the code is copied from liblockfile, which is written by
 * Miquel van Smoorenburg <miquels@cistron.nl>.
 *
 * This is only a quick hack to support file locking for libefs.
 * Changes made by Dietmar Maurer <dietmar@maurer-it.com>
 *
 *
 */

#include "efs_internal.h"

#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>


static GList *efs_lockfiles = NULL;
static gint atexit_registered = 0;

/**
 * efs_remove_all_lockfiles:
 *
 * Description: This function is called at program exit and removes
 * all open lockfiles. 
 *
 */

static void
efs_remove_all_lockfiles (void)
{
	GList *l;

	l = efs_lockfiles;
	while (l) {
		if (l->data) { unlink (l->data); g_free (l->data); }
		l = l->next;
	}

	g_list_free (efs_lockfiles);
	efs_lockfiles = NULL;
}

/**
 * efs_lock_create:
 * @lockfile: the lockfile name (filesystem path)
 * 
 * Description: Create a lockfile in a NFS save way.
 *
 * Returns: zero on success, or -1 on failure.
 */

EFSResult
efs_lock_create (const char *lockfile)
{
	struct stat	st, st1;
	char		tmplock[1024];
	char		sysname[256];
	char		buf[32];
	char		*p;
	int		sleeptime = 5;
	int		statfailed = 0;
	int		fd;
	int		i, len, res;
	int             retries = 2;

	if (!atexit_registered) {
		atexit_registered = 1;
		g_atexit(efs_remove_all_lockfiles);
	}

	/*
	 *	Safety measure.
	 */

	if (strlen(lockfile) + 32 > 1024) return -1;

	/*
	 *	Create a temp lockfile (hopefully unique) and write
	 *	our pid in it.
	 */

	if (gethostname(sysname, sizeof(sysname)) < 0) return -1;
	if ((p = strchr(sysname, '.')) != NULL)
		*p = 0;
	strcpy(tmplock, lockfile);
	if ((p = strrchr(tmplock, '/')) == NULL) p = tmplock;
	else p++;

	sprintf(p,".lk%05d%x%s",(int)getpid(), (int)time(NULL) & 15, sysname);
	
	i = umask(022);
	fd = open(tmplock, O_WRONLY|O_CREAT|O_EXCL, 0644);
	umask(i);
	if (fd < 0) return -1;
       
	sprintf(buf, "%d\n%s\n", (int)getpid(),sysname);
	p = buf;
	len = strlen(buf);

	i = write(fd, p, len);
	if (close(fd) != 0) i = -1;
       
	if (i != len) {
		unlink(tmplock);
		return -1;
	}

	/*
	 *	Now try to link the temporary lock to the lock.
	 */

	for (i = 0; i < retries && retries > 0; i++) {

		sleeptime = i > 12 ? 60 : 5 * i;
		if (sleeptime > 0) sleep(sleeptime);

		if ((res = efs_lock_check(lockfile)) == 1) return 0;
		if (res == -1) unlink(lockfile);

		link (tmplock, lockfile);

		if (lstat(tmplock, &st1) < 0) return -1;
		if (lstat(lockfile, &st) < 0) {
			if (statfailed++ > 5) {
				unlink(tmplock);
				return -1;
			}
			continue;
		}

		/*
		 *	See if we got the lock.
		 */
		if (st.st_rdev == st1.st_rdev && st.st_ino  == st1.st_ino) {
			unlink(tmplock);
			efs_lockfiles = g_list_prepend (efs_lockfiles, 
							g_strdup(lockfile));

			return 0;
		}
		statfailed = 0;
	}

	unlink(tmplock);
	return -1;
}

/**
 * efs_lock_check:
 * @lockfile: the lockfile name (filesystem path)
 * 
 * Description: See if another process has locked the file.
 *
 * Returns: Returns 0 if so, -1 if not, or 1 if this
 * process has locked the file by itself.
 */

EFSResult
efs_lock_check (const char *lockfile)
{
	struct stat	st;
	pid_t		pid;
	int		fd, len, r;
	char		buf[512], sn[512], sysname[256];
	
	if (stat(lockfile, &st) < 0) return -1;

	if (gethostname(sysname, sizeof(sysname)) < 0) return -1;

	pid = 0;
	if ((fd = open(lockfile, O_RDONLY)) >= 0) {
		len = read(fd, buf, sizeof(buf));
		close(fd);
		if (len > 0) {
			buf[len] = 0;
			sn[0] = 0;
			sscanf (buf, "%d\n%255s\n", &pid, sn);
			sn[sizeof(sn)-1] = 0;
		}
	}
	
	if (pid && !strcmp(sysname, sn)) {
		if (getpid() == pid) return 1;
		r = kill(pid, 0);
		if (r == 0 || errno == EPERM)
			return 0;
		if (r < 0 && errno == ESRCH)
			return -1;
		
	}

	return 0;
}

/**
 * efs_lock_remove:
 * @lockfile: the lockfile name (filesystem path)
 * 
 * Description: Remove a lock
 *
 * Returns: zero on success, or -1 on failure.
 */

EFSResult
efs_lock_remove (const char *lockfile)
{
	GList *l;

	if (!lockfile) return 0;

	l = efs_lockfiles;
	while (l) {
		if (!strcmp(l->data, lockfile)) {
			g_free (l->data);
			if (l->prev)
				l->prev->next = l->next;
			if (l->next)
				l->next->prev = l->prev;
	  
			if (efs_lockfiles == l)
				efs_lockfiles = l->next;
	  
			g_list_free_1 (l);

			break;
		} else l = l->next;
	}

	return unlink (lockfile);
}


syntax highlighted by Code2HTML, v. 0.9.1