/* Copyright (C) 1993,1994 by the author(s).
 
 This software is published in the hope that it will be useful, but
 WITHOUT ANY WARRANTY for any part of this software to work correctly
 or as described in the manuals. See the ShapeTools Public License
 for details.

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
 */
/*
 * AtFS -- Attribute Filesystem
 *
 * afarlock.c -- reader/writer locking on archive files
 *
 * Author: Andreas Lampen (Andreas.Lampen@cs.tu-berlin.de)
 *
 * $Header: afarlock.c[7.0] Sun Jan 23 16:05:51 1994 andy@cs.tu-berlin.de frozen $
 */

#include <errno.h>

#include "atfs.h"

extern int errno;

/* When this variable is set TRUE, no reader locking is performed */
EXPORT int af_noReadLock = FALSE;

/*==============================================
 * afOpenLock -- open archive file and set lock
 *==============================================*/

EXPORT FILE *afOpenLock (fileName, mode, list)
     char *fileName;
     int  mode;
     Af_revlist *list;
{
  int    fileDes, openMode, retries = 0;
  char   modeStr[4];
  mode_t fileMode;
  FILE   *resultFile;
#if defined(ATFS_OWN_LOCKING)
  char        *lckName;
  struct stat lckIbuf;
  int         lckFiledes;
#else
  struct flock lockInfo;
#endif

  if (mode == AF_WRITE) {
    openMode = O_WRONLY;
#if !defined(ATFS_OWN_LOCKING)
    lockInfo.l_type = F_WRLCK;
#endif
    strcpy (modeStr, "w+");
  }
  else {
    openMode = O_RDONLY;
#if !defined(ATFS_OWN_LOCKING)
    lockInfo.l_type = F_RDLCK;
#endif
    strcpy (modeStr, "r");
    if (af_noReadLock) {
      if ((resultFile = (fopen (fileName, modeStr))) == NULL)
	FAIL ("OpenLock", "fopen", AF_ESYSERR, NULL);
      return (resultFile);
    }
  }

  if ((fileDes = open (fileName, openMode)) < 0) {
    /* if file does not exist and shall be opened for writing, try to create it */
    if ((mode == AF_WRITE) && (errno == ENOENT)) {
      if ((fileDes = creat (fileName, 0600)) < 0)
	FATAL ("OpenLock", "creat", AF_ESYSERR, NULL);
      chown (fileName, geteuid(), list->af_owngid);
      if ((fileMode = afArchiveMode (list->af_arpath))) {
	fileMode &= ~(S_IXUSR|S_IXGRP|S_IXOTH|S_ISUID|S_ISGID);
	chmod (fileName, fileMode);
      }
    }
    else
      FAIL ("OpenLock", "open", AF_ESYSERR, NULL);
  }

#if defined(ATFS_OWN_LOCKING)
  /* build name for lock file */
  if (list->af_extent & AF_CACHE)
    lckName = afCacheFileName (list->af_arpath, AF_CACHELCKNAME);
  else
    lckName = afArchiveName (list->af_arpath, AF_LOCKDIR, list->af_cattrs.af_globname, list->af_cattrs.af_globtype, AF_WRITE);

 retry:
  /* check if lock file exists */
  if (stat (lckName, &lckIbuf) == ERROR) {
    if (mode == AF_WRITE) { /* no reader locking */
      /* create lock file */
      af_regtmpfile (lckName);
      if ((lckFiledes = creat (lckName,  0600)) < 0)
	FAIL ("OpenLock", "creat (lockfile)", AF_ESYSERR, NULL);
      close (lckFiledes);
    }
  }
  else {
    /* check if lock file is older than a 60 seconds */
    if ((af_acttime() - lckIbuf.st_ctime) > 60) {
      /* remove old lock file and create new one */
      af_wng ("WriteLock", "spurious lock file found -- removed.");
      if (unlink (lckName) == ERROR)
	FATAL ("OpenLock", "cannot unlink lock file", AF_EINTERNAL, NULL);
      if (mode == AF_WRITE) { /* no reader locking */
	/* create new one */
	af_regtmpfile (lckName);
	if ((lckFiledes = creat (lckName,  0600)) < 0)
	  FAIL ("OpenLock", "creat (lockfile)", AF_ESYSERR, NULL);
	close (lckFiledes);
      }
    }
    else {
      /* retry */
      if (retries == 3)
	FAIL ("OpenLock", "archive file is locked for writing", AF_EMISC, NULL);
      sleep (1);
      retries++;
      goto retry;
    }
  }
#else
  lockInfo.l_whence = SEEK_SET;
  lockInfo.l_start = (off_t) 0;
  lockInfo.l_len = (off_t) 0;
  lockInfo.l_pid = getpid();
  while (1) {
    if (fcntl (fileDes, F_SETLK, &lockInfo) != -1)
      break;
    if (retries++ == 10)
      FAIL ("OpenLock", "fcntl", AF_ESYSERR, NULL);
    sleep (1);
  }
#endif
  if (mode == AF_WRITE) {
    /* reopen and truncate the file */
    close (fileDes);
    if ((resultFile = fopen (fileName, modeStr)) == NULL) {
      FAIL ("OpenLock", "fopen", AF_ESYSERR, NULL);
    }
    return (resultFile);
  }
  /* else */
  if ((resultFile = (fdopen (fileDes, modeStr))) == NULL)
    FAIL ("OpenLock", "fdopen", AF_ESYSERR, NULL);
  return (resultFile);
}

EXPORT int afCloseUnlock (fileDes, mode, list)
     FILE *fileDes;
     int  mode;
     Af_revlist *list;
{
#if defined(ATFS_OWN_LOCKING)
  char        *lckName;

  if (mode == AF_WRITE) { /* no reader locking */
    if (list->af_extent & AF_CACHE)
      lckName = afCacheFileName (list->af_arpath, AF_CACHELCKNAME);
    else
      lckName = afArchiveName (list->af_arpath, AF_LOCKDIR, list->af_cattrs.af_globname, list->af_cattrs.af_globtype, AF_WRITE);
    if (unlink (lckName) == ERROR)
      FATAL ("afCloseUnlock", "cannot unlink lock file", AF_EINTERNAL, EOF);
    af_unregtmpfile (lckName);
  }
#else
  struct flock lockInfo;

  if (af_noReadLock) {
    return (fclose (fileDes));
  }

  lockInfo.l_type = F_UNLCK;
  lockInfo.l_whence = SEEK_SET;
  lockInfo.l_start = (off_t) 0;
  lockInfo.l_len = (off_t) 0;
  lockInfo.l_pid = (pid_t)0;

  if (fcntl (fileno(fileDes), F_SETLK, &lockInfo) == -1)
    FATAL ("CloseUnlock", "fcntl", AF_ESYSERR, EOF);
#endif
  return (fclose (fileDes));
}
