# include <stdio.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <errno.h>
# include <fcntl.h>
# include <sys/time.h>
# include <unistd.h>
# include <stdlib.h>

# define LCK_ACCESS  1   /* can't get access to file */
# define LCK_TIMEOUT 2   /* timeout setting lock */
# define LCK_HARDLCK 3   /* error setting hard lockfile */
# define LCK_HUNLOCK 4   /* error clearing hard lockfile */

# define LCK_EMPTY   5   /* locked file is empty */
# define LCK_FULL    6   /* locked file is not empty */
# define LCK_UNKNOWN 7   /* can't stat file to get size */

# define LCK_SOFT    10  /* soft lock */
# define LCK_HARD    11  /* hard lock */
# define LCK_XCLD    12  /* exclusive soft lock */

# define DTIME(A,B) ((A.tv_sec - B.tv_sec) + 1e-6*(A.tv_usec - B.tv_usec))

extern int errno;
extern char *pathname (), *filebasename ();

# ifndef ALLOCATE
# define ALLOCATE(X,T,S)  \
  X=(T *)malloc((unsigned) ((S)*sizeof(T)));\
  if(X==NULL) \
    { \
      fprintf(stderr,"failed to malloc X\n");\
        exit(0);\
    } 
# define REALLOCATE(X,T,S) \
  X=(T *)realloc(X,(unsigned) ((S)*sizeof(T))); \
  if(X==NULL) \
    { \
       fprintf(stderr,"failed to realloc X\n"); \
       exit(0); \
    }
# endif /* ALLOCATE */

/* 
   set a lockfile on [path]/filename.  
   we allow two types of locks: hard & soft

   both lock types set a filesystem-level lock on the given file, 
    using exclusive for hard and shared for soft locks

   a hard lock also uses a lockfile to make the lock permanent:
    the lockfile must be actively removed and will not vanish 
    even if the locking program dies 

   a soft lock will vanish if the locking program dies.

   if a hard lock is set, both hard & soft locks will block
   if a soft lock is set, a hard lock will block, but not a soft lock

   the hard lockfile uses the file [path]/.filename.lck

   setlockfile returns a file descriptor to an open READ/WRITE access file,
     or -1 on failure, and sets state.
*/

int setlockfile2 (char *filename, double timeout, int type, int *state) {
  
  int i, status, done, nbytes;
  char *lockname, buffer[64];
  char *file, *path;
  int fd, flock;
  int filemode;
  struct stat filestat;
  struct flock filelock;
  struct timeval now, then;

  file = path = lockname = (char *) NULL;
  gettimeofday (&then, (void *) NULL);

  /* define lock type */
  filelock.l_start  = 0;
  filelock.l_whence = SEEK_SET;
  filelock.l_len    = 0;
  filelock.l_pid    = getpid ();
  switch (type) {
  case LCK_HARD:
  case LCK_XCLD:
    filelock.l_type   = F_WRLCK;  /* set an exclusive lock */
    break;
  case LCK_SOFT:
    filelock.l_type   = F_RDLCK;  /* set a shared lock */
    break;
  default:
    fprintf (stderr, "invalid lock type %d\n", type);
  }
  filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;

  /* try to open file */
  fd = open (filename, O_RDWR | O_CREAT, filemode);
  if (fd == -1) {
    *state = LCK_ACCESS;
    goto failure;
  }

  /* we first try to set a FS level lock on the file */
  for (done = 0; !done; ) {

    /* check for timeout */
    gettimeofday (&now, (void *) NULL);
    if (DTIME (now, then) > timeout) {
      *state = LCK_TIMEOUT;
      goto failure;
    }
    usleep (1000);

    /* try to lock file */
    if (fcntl (fd, F_SETLK, &filelock) != -1) done = 1;
  }

  if (type == LCK_HARD) {
    /* we've locked filename, now check lockname */ 

    /* locking the lockfile before checking / setting contents will 
       ensure the data is synced across NFS */
    
    /* set up name to lockfile */
    path = pathname (filename);
    file = filebasename (filename);
    ALLOCATE (lockname, char, strlen (path) + strlen (file) + 10);
    sprintf (lockname, "%s/.%s.lck", path, file);

    /* try to open lockfile */
    flock = open (lockname, O_RDWR | O_CREAT, filemode);
    if (flock == -1) {
      *state = LCK_HARDLCK;
      goto failure;
    }
    
    /* try a few times to lock lockfile */
    for (i = 0; (i < 20) && (fcntl (flock, F_SETLK, &filelock) == -1); i++) {
      usleep (10000);
    }
    if (i == 20) {
      *state = LCK_HARDLCK + 10;
      goto failure;
    }
    
    /* we've locked the file, now read the contents */
    nbytes = read (flock, buffer, 4);
    if (nbytes == 4) { /* lock file has a word in it */
      buffer[4] = 0;
      if (!strcmp (buffer, "BUSY")) { 
	*state = LCK_HARDLCK + 20;
	goto failure;
      }
    }
    
    /* we've really got the lock, write BUSY to protect it */
    lseek (flock, 0, SEEK_SET);
    write (flock, "BUSY", 4);
    
    /* now unlock lockfile */
    filelock.l_type = F_UNLCK;
    if (fcntl (flock, F_SETLK, &filelock) == -1) {
      *state = LCK_HARDLCK + 30;
      goto failure;
    }
    if (close (flock)) {
      fprintf (stderr, "error closing lock\n");
      goto failure;
    }
  }

  /* check if file is empty or not */
  if (fstat (fd, &filestat)) {
    *state = LCK_UNKNOWN;
  } else {
    if (filestat.st_size == 0) {
      *state = LCK_EMPTY;
    } else {
      *state = LCK_FULL;
    }
  }

  if (path != NULL) free (path);
  if (file != NULL) free (file);
  if (lockname != NULL) free (lockname);
  return (fd);

failure:
  if (path != NULL) free (path);
  if (file != NULL) free (file);
  if (lockname != NULL) free (lockname);
  return (-1);
}
  
/* if we pass in -1, all we can do is clear the hardlock */
int clearlockfile2 (char *filename, int fd, int type, int *state) {
  
  char *lockname;
  int i, status;
  char *path, *file;
  int flock, filemode;
  struct stat filestat;
  struct flock filelock;

  file = path = lockname = (char *) NULL;

  /* define lock */
  filelock.l_type   = F_UNLCK;
  filelock.l_start  = 0;
  filelock.l_whence = SEEK_SET;
  filelock.l_len    = 0;
  filelock.l_pid    = getpid ();
  filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;

  /* first clear hard lockfile */
  if (type == LCK_HARD) {

    /* define lockfile */
    path = pathname (filename);
    file = filebasename (filename);
    ALLOCATE (lockname, char, strlen (path) + strlen (file) + 10);
    sprintf (lockname, "%s/.%s.lck", path, file);
    
    /* check for lockfile existence */
    status = stat (lockname, &filestat);
    if (status == -1) {
      *state = LCK_HUNLOCK;
      goto failure;
    } 
  
    /* open lockfile */
    flock = open (lockname, O_RDWR | O_CREAT, filemode);
    if (flock == -1) {
      *state = LCK_HUNLOCK;
      goto failure;
    }

    /* try a few times to lock lockfile */
    filelock.l_type = F_WRLCK;
    for (i = 0; (i < 20) && (fcntl (flock, F_SETLK, &filelock) == -1); i++) {
      usleep (10000);
    }
    if (i == 20) {
      *state = LCK_HUNLOCK;
      goto failure;
    }

    /* set value to IDLE */
    lseek (flock, 0, SEEK_SET);
    write (flock, "IDLE", 4);

    /* unlock lockfile */
    filelock.l_type = F_UNLCK;
    if (fcntl (flock, F_SETLK, &filelock) == -1) {
      *state = LCK_HUNLOCK;
      goto failure;
    }
    if (close (flock)) {
      fprintf (stderr, "error closing lock\n");
      goto failure;
    }
  }
  
  /* now unlock the file */
  if (fd != -1) {
    if (fcntl (fd, F_SETLK, &filelock) == -1) {
      *state = LCK_HUNLOCK;
      goto failure;
    }
  }

  if (path != NULL) free (path);
  if (file != NULL) free (file);
  if (lockname != NULL) free (lockname);
  return (1);

failure:
  if (path != NULL) free (path);
  if (file != NULL) free (file);
  if (lockname != NULL) free (lockname);
  return (0);

}
