/*
 * lockfile.c  -  File locking
 *
 * Copyright (C) 2003-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: lockfile.c,v 1.11 2007/01/06 18:31:38 gkminix Exp $
 */

#include <common.h>
#include <nblib.h>
#include "privlib.h"


/*
 * Check for flock() system call
 */
#ifdef HAVE_FLOCK
# ifdef HAVE_SYS_FILE_H
#  include <sys/file.h>
# else
#  undef HAVE_FLOCK
# endif
#endif



/*
 * Length of a PID string in a lock file
 */
#define PIDLEN	16



/*
 * Variables local to this module
 */
static int isinit = FALSE;			/* initialization flag */
static LISTHDL locklist;			/* lock file list */



/*
 * Compare lock file name
 */
static int findlock __F((handle, data, fname),
					LISTHDL handle AND
					voidstar *data AND
					const voidstar fname)
{
  return(!strcmp(*data, fname));
}



/*
 * Remove a lock file
 */
static void removelock __F((handle, data), LISTHDL handle AND voidstar data)
{
  /* Unlink the file */
  (void)unlink((char *)data);

  /* Release the memory occupied by the file name */
  free(data);
}



/*
 * Function to be called upon program termination
 */
static void unlockall __F_NOARGS
{
  releaselist(locklist);
}



/*
 * Initialize module
 */
static inline int initmodule __F_NOARGS
{
  int ret;

  if (!isinit) {
	if ((ret = nbatexit(&unlockall)) != 0) {
		nberror(EXIT_INTERNAL, "unable to set lock file exit function");
		return(FALSE);
	}
	locklist = createlist(&removelock);
	isinit = TRUE;
  }
  return(TRUE);
}



/*
 * Write our process PID into the lock file
 */
static int writepid __F((fd, curlock), FILE *fd AND const char *fname)
{
  char pidbuf[PIDLEN + 1];
  int i;
  pid_t pid;

  pid = getpid();
  for (i = PIDLEN - 1; i >= 0; i--) {
	pidbuf[i] = (char)((int)(pid % 10) + '0');
	pid /= 10;
  }
  pidbuf[PIDLEN] = '\0';
  if (fprintf(fd, "%s", pidbuf) != PIDLEN) {
	nberror(EXIT_LOCK, "error writing PID into file %s", fname);
	return(FALSE);
  }
  return(TRUE);
}



/*
 * Read a process PID out of a lock file
 */
static pid_t readpid __F((fd), FILE *fd)
{
  pid_t pid;
  int c;

  /* Check if we are at EOF already */
  if (feof(fd))
	return((pid_t)0);

  /* Read a PID out of the file */
  pid = (pid_t)0;
  while ((c = fgetc(fd)) != EOF) {
	if (c == '\n')
		break;
	if (!isdigit(c))
		return((pid_t)0);
	pid *= 10;
	pid += c - '0';
  }
  if (c == EOF && !feof(fd))
	return((pid_t)0);
  return(pid);
}



/*
 * Lock the lock file
 */
static int locklock __F((fd, curlock), FILE *fd AND const char *fname)
{
  /* Only lock the lock file if we have flock() */
#ifdef HAVE_FLOCK
  if (flock(fileno(fd), LOCK_EX | LOCK_NB) == -1) {
	nberror(EXIT_LOCK, "unable to lock file %s", fname);
	return(FALSE);
  }
#endif
  return(TRUE);
}



/*
 * Lock a file name
 */
int filelock __F((fname), const char *fname)
{
  FILE *fd;
  char *cp;
  int lockid;

  /* Initialize the module */
  if (!initmodule())
	return(FILELOCK_ERROR);

  /* Check that we have a lock file name */
  if (fname == NULL)
	return(FILELOCK_OK);

  /* If we can open the lock file for reading, the file is locked */
  if ((fd = fopen(fname, "r")) != NULL) {
	(void)fclose(fd);
	return(FILELOCK_SET);
  }

  /* Otherwise create a new lock file */
  if ((fd = fopen(fname, "w")) == NULL) {
	nberror(EXIT_LOCK, "error creating file %s", fname);
	return(FILELOCK_ERROR);
  }
  cp = NULL;
  copystr(&cp, fname);
  lockid = appendlist(locklist, cp);
  if (!locklock(fd, fname) || !writepid(fd, fname)) {
	(void)fclose(fd);
	(void)removeatlist(locklist, lockid);
	return(FILELOCK_ERROR);
  }

  /* Close the lock file again */
  if (fclose(fd) != 0) {
	(void)removeatlist(locklist, lockid);
	nberror(EXIT_LOCK, "error closing file %s", fname);
	return(FILELOCK_ERROR);
  }
  return(FILELOCK_OK);
}



/*
 * Unlock a file name
 */
int fileunlock __F((fname), const char *fname)
{
  FILE *fd;
  int lockid;

  /* Check that we have a lock file name */
  if (fname == NULL || (lockid = walklist(locklist, &findlock, fname)) < 0)
	return(TRUE);

  /* The lock file has to exist */
  if ((fd = fopen(fname, "r")) == NULL) {
	nberror(EXIT_LOCK, "error opening file %s", fname);
	return(FALSE);
  }
  if (!locklock(fd, fname)) {
	(void)fclose(fd);
	return(FALSE);
  }

  /* Read the PID from lock file */
  if (readpid(fd) != getpid()) {
	nberror(EXIT_LOCK, "invalid PID in file %s", fname);
	(void)fclose(fd);
	return(FALSE);
  }

  /* Delete the lock file */
  (void)removeatlist(locklist, lockid);

  /* Finally close the lock file */
  if (fclose(fd) != 0) {
	nberror(EXIT_LOCK, "error closing file %s", fname);
	return(FALSE);
  }
  return(TRUE);
}



/*
 * Check for a valid lock file. It returns FALSE if the lock file is not valid.
 */
int checklock __F((fname), const char *fname)
{
  FILE *fd;
  int ret, lockid;

  /* Check that we have a lock file name */
  if (fname == NULL || (lockid = walklist(locklist, &findlock, fname)) < 0)
	return(FILELOCK_OK);

  /* Check if lock file exists */
  if ((fd = fopen(fname, "r")) == NULL)
	return(FILELOCK_OK);

  /* Read the PID from lock file */
  ret = (readpid(fd) == getpid() ? FILELOCK_SET : FILELOCK_OK);

  /* Finally close the lock file */
  if (fclose(fd) != 0) {
	nberror(EXIT_LOCK, "error closing file %s", fname);
	return(FILELOCK_ERROR);
  }
  return(ret);
}

