/*
 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
 * Copyright (c) 1991-1998 University of Maryland at College Park
 * Copyright (c) 2007-2012 Zmanda, Inc.  All Rights Reserved.
 * Copyright (c) 2013-2016 Carbonite, Inc.  All Rights Reserved.
 * All Rights Reserved.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of U.M. not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  U.M. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Authors: the Amanda Development Team.  Its members are listed in a
 * file named AUTHORS, in the root directory of this distribution.
 */

/* moved from amflock.c by Dustin J. Mitchell <dustin@zmanda.com> */

#include "amanda.h"

static int ln_lock(char *res, int op);
char *_lnlock_dir = AMANDA_TMPDIR; /* amflock-test changes this; it's a constant otherwise */

/* XXX - error checking in this section needs to be tightened up */

/* Delete a lock file.
*/
static int
delete_lock(
    char *fn)
{
	int rc;

	rc = unlink(fn);
	if (rc != 0 && errno == ENOENT) rc = 0;

	return rc;
}

/* Create a lock file.
*/
static int
create_lock(
    char *fn,
    long pid)
{
	int fd;
	FILE *f;
	int mask;

	(void)delete_lock(fn);			/* that's MY file! */

	mask = umask(0027);
	fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0640);
	umask(mask);
	if (fd == -1) return -1;

	if((f = fdopen(fd, "w")) == NULL) {
	    aclose(fd);
	    return -1;
	}
	g_fprintf(f, "%ld\n", pid);
	if (fclose(f) == EOF)
	    return -1;
	return 0;
}

/* Read the pid out of a lock file.
**   -1=error, otherwise pid.
*/
static long
read_lock(
    char *	fn) /* name of lock file */
{
	int save_errno;
	FILE *f;
	long pid;

	if ((f = fopen(fn, "r")) == NULL) {
		return -1;
	}
	if (fscanf(f, "%ld", &pid) != 1) {
		save_errno = errno;
		afclose(f);
		errno = save_errno;
		return -1;
	}
	if (fclose(f) != 0) {
		return -1;
	}
	return pid;
}

/* Link a lock if we can.
**   0=done, 1=already locked, -1=error.
*/
static int
link_lock(
    char *	lk,	/* real lock file */
    char *	tlk)	/* temp lock file */
{
	int rc;
	int serrno;	/* saved errno */
	struct stat lkstat, tlkstat;

	/* an atomic check and set operation */
	rc = link(tlk, lk);
	if (rc == 0) return 0; /* XXX do we trust it? */

	/* link() says it failed - don't beleive it */
	serrno = errno;

	if (stat(lk, &lkstat) == 0 &&
	    stat(tlk, &tlkstat) == 0 &&
	    lkstat.st_ino == tlkstat.st_ino)
		return 0;	/* it did work! */

	errno = serrno;

	if (errno == EEXIST) rc = 1;

	return rc;
}

/* Steal a lock if we can.
**   0=done; 1=still in use; -1 = error.
*/
static int
steal_lock(
    char *	fn,	/* name of lock file to steal */
    long	mypid,	/* my process id */
    char *	sres)	/* name of steal-resource to lock */
{
	long pid;
	int rc;

	/* prevent a race with another stealer */
	rc = ln_lock(sres, 1);
	if (rc != 0) goto error;

	pid = read_lock(fn);
	if (pid == -1) {
		if (errno == ENOENT) goto done;
		goto error;
	}

	if (pid == mypid) goto steal; /* i'm the locker! */

	/* are they still there ? */
	rc = kill((pid_t)pid, 0);
	if (rc != 0) {
		if (errno == ESRCH) goto steal; /* locker has gone */
		goto error;
	}

	rc = ln_lock(sres, 0);
	if (rc != 0) goto error;

	return 1;

steal:
	rc = delete_lock(fn);
	if (rc != 0) goto error;

done:
	rc = ln_lock(sres, 0);
	if (rc != 0) goto error;

	return 0;

error:
	rc = ln_lock(sres, 0);

	return -1;
}

static int
ln_lock(
    char *	res, /* name of resource to lock */
    int		op)  /* true to lock; false to unlock */
{
	long mypid;
	char *lockfile = NULL;
	char *tlockfile = NULL;
	char *mres = NULL;
	int rc;
	char pid_str[NUM_STR_SIZE];

	mypid = (long)getpid();

	lockfile = g_strjoin(NULL, _lnlock_dir, "/am", res, ".lock", NULL);

	if (!op) {
		/* unlock the resource */
		assert(read_lock(lockfile) == mypid);

		(void)delete_lock(lockfile);
		amfree(lockfile);
		return 0;
	}

	/* lock the resource */

	g_snprintf(pid_str, sizeof(pid_str), "%ld", mypid);
	tlockfile = g_strjoin(NULL, _lnlock_dir, "/am", res, ".", pid_str, NULL);

	(void)create_lock(tlockfile, mypid);

	mres = g_strconcat(res, ".", NULL);

	while(1) {
		rc = link_lock(lockfile, tlockfile);
		if (rc == -1) break;
		if (rc == 0) break;

		rc = steal_lock(lockfile, mypid, mres);
		if (rc == -1) break;
		if (rc == 0) continue;
		sleep(1);
	}

	(void) delete_lock(tlockfile);

	amfree(mres);
	amfree(tlockfile);
	amfree(lockfile);

	return rc;
}

static int
lnlock_lock(
    G_GNUC_UNUSED int fd,
    char *resource)
{
    return ln_lock(resource, 1);
}

static int
lnlock_unlock(
    G_GNUC_UNUSED int fd,
    char *resource)
{
    return ln_lock(resource, 0);
}

amflock_impl_t amflock_lnlock_impl = {
    lnlock_lock,
    lnlock_lock, /* no read-only support */
    lnlock_unlock,
    "lnlock"
};
