locker.c 5.2 KB
/* GNU mailutils - a suite of utilities for electronic mail
   Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Library Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   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 Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

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

#include <mailutils/locker.h>

#define LOCKFILE_ATTR           0444

/* First draft by Brian Edmond. */

struct _locker
{
  int fd;
  int refcnt;
  char *fname;
  int flags;
};

int
locker_create (locker_t *plocker, const char *filename, size_t len, int flags)
{
  locker_t l;

  if (plocker == NULL || filename == NULL || len == 0)
    return EINVAL;

  l = malloc (sizeof (*l));
  if (l == NULL)
    return ENOMEM;

  l->fname = calloc (len + 5 /*strlen(".lock")*/ + 1, sizeof (*(l->fname)));
  if (l->fname == NULL)
    {
      free (l);
      return ENOMEM;
    }
  memcpy (l->fname, filename, len);
  strcat (l->fname, ".lock");

  if (flags)
    l->flags = flags;
  else
    l->flags = MU_LOCKER_TIME; /* Default is time lock implementation.  */
  l->fd = -1;
  l->refcnt = 0;
  *plocker = l;
  return 0;
}

void
locker_destroy (locker_t *plocker)
{
  if (plocker && *plocker)
    {
      free ((*plocker)->fname);
      free (*plocker);
      *plocker = NULL;
    }
}

int
locker_lock (locker_t lock, int flags)
{
  int   fd = -1;
  char  buf[16];
  pid_t pid;
  int   removed = 0;

  (void)flags; /* Ignore for now.  */
  if (lock == NULL)
    return EINVAL;

  /* Is the lock already applied?
     FIXME: should we check flags != lock->flags ?? */
  if (lock->fd != -1)
    {
      lock->refcnt++;
      return 0;
    }

  /*
    Check for lock existance:
    if it exists but the process is gone the lock can be removed,
    if the lock is expired, remove it.  */
  fd = open (lock->fname, O_RDONLY);
  if (fd != -1)
    {
      /* Check to see if this process is still running.  */
      if (lock->flags & MU_LOCKER_PID)
        {
	  int nread = read (fd, buf, sizeof (buf) - 1);
          if (nread > 0)
            {
	      buf[nread] = '\0';
              pid = strtol (buf, NULL, 10);
              if (pid > 0)
                {
                  /* Process is gone so we try to remove the lock.  */
                  if (kill (pid, 0) == -1)
                    removed = 1;
                }
	      else
		removed = 1; /* Corrupted file, remove the lock.  */
            }
        }
      /* Check to see if the lock expired.  */
      if (lock->flags & MU_LOCKER_TIME)
        {
          struct stat stbuf;

          fstat (fd, &stbuf);
          /* The lock has expired.  */
          if ((time (NULL) - stbuf.st_mtime) > MU_LOCKER_EXPIRE_TIME)
            removed = 1;
        }

      close (fd);
      if (removed)
        unlink (lock->fname);
    }

  /* Try to create the lockfile.  */
  fd = open (lock->fname, O_WRONLY | O_CREAT | O_EXCL, LOCKFILE_ATTR);
  if (fd == -1)
    return errno;
  else
    {
      struct stat fn_stat;
      struct stat fd_stat;

      if (lstat (lock->fname, &fn_stat)
	  || fstat(fd, &fd_stat)
	  || fn_stat.st_nlink != 1
	  || fn_stat.st_dev != fd_stat.st_dev
	  || fn_stat.st_ino != fd_stat.st_ino
	  || fn_stat.st_uid != fd_stat.st_uid
	  || fn_stat.st_gid != fd_stat.st_gid)
	{
	  close (fd);
	  unlink (lock->fname);
	  return EPERM;
	}
    }

  /* Success.  */
  sprintf (buf, "%ld", (long)getpid ());
  write (fd, buf, strlen (buf));

  /* Try to get a file lock.  */
  if (lock->flags & MU_LOCKER_FCNTL)
    {
      struct flock fl;

      memset (&fl, 0, sizeof (struct flock));
      fl.l_type = F_WRLCK;
      if (fcntl (fd, F_SETLK, &fl) == -1)
        {
	  int err = errno;
          /* Could not get the file lock.  */
          close (fd);
          unlink (lock->fname); /* Remove the file I created.  */
          return err;
        }
    }

  lock->fd = fd;
  lock->refcnt++;
  return 0;
}

int
locker_touchlock (locker_t lock)
{
 if (!lock || !lock->fname || lock->fd == -1)
    return EINVAL;
  return utime (lock->fname, NULL);
}

int
locker_unlock (locker_t lock)
{
  if (!lock || !lock->fname || lock->fd == -1 || lock->refcnt <= 0)
    return EINVAL;

  if (--lock->refcnt > 0)
    return 0;

  if (lock->flags & MU_LOCKER_FCNTL)
    {
      struct flock fl;

      memset (&fl, 0, sizeof (struct flock));
      fl.l_type = F_UNLCK;
      /* Unlock failed?  */
      if (fcntl (lock->fd, F_SETLK, &fl) == -1)
          return errno;
    }
  close (lock->fd);
  lock->fd = -1;
  unlink (lock->fname);
  return 0;
}