Logo Search packages:      
Sourcecode: maildrop version File versions  Download package

mail.c

/*
** Copyright 2002 Double Precision, Inc.  See COPYING for
** distribution information.
*/

#include    "config.h"
#include    "liblock.h"
#include    "mail.h"
#include    "../numlib/numlib.h"
#include    <stdio.h>
#include    <stdlib.h>
#include    <string.h>
#include    <unistd.h>
#include    <errno.h>
#include    <sys/types.h>
#include    <sys/stat.h>
#if HAVE_FCNTL_H
#include    <fcntl.h>
#endif

static const char rcsid[]="$Id: mail.c,v 1.9 2003/04/03 21:08:33 mrsam Exp $";

struct ll_mail *ll_mail_alloc(const char *filename)
{
      struct ll_mail *p=(struct ll_mail *)malloc(sizeof(struct ll_mail));

      if (!p)
            return NULL;

      if ((p->file=strdup(filename)) == NULL)
      {
            free(p);
            return NULL;
      }

      p->cclientfd= -1;
      p->cclientfile=NULL;

      p->dotlock=NULL;

      return p;
}

#define IDBUFSIZE 512

/*
** For extra credit, we mark our territory.
*/

static void getid(char *idbuf)
{
      char *p;

      libmail_str_pid_t(getpid(), idbuf);

      while (*idbuf)
            idbuf++;

      *idbuf++=':';

      idbuf[IDBUFSIZE-NUMBUFSIZE-10]=0;

      if (gethostname(idbuf, IDBUFSIZE-NUMBUFSIZE-10) < 0)
            strcpy(idbuf, "localhost");
}

static int writeid(char *idbuf, int fd)
{
      int l=strlen(idbuf);

      while (l)
      {
            int n=write(fd, idbuf, l);

            if (n <= 0)
                  return (-1);

            l -= n;
            idbuf += n;
      }
      return 0;
}

static int readid(char *p, int fd)
{
      int l=IDBUFSIZE-1;

      while (l)
      {
            int n=read(fd, p, l);

            if (n < 0)
                  return (-1);

            if (n == 0)
                  break;

            p += n;
            l -= n;
      }
      *p=0;
      return 0;
}

static pid_t getpidid(char *idbuf, char *myidbuf)
{
      pid_t p=atol(idbuf);

      if ((idbuf=strchr(idbuf, ':')) == NULL ||
          (myidbuf=strchr(myidbuf, ':')) == NULL ||
          strcmp(idbuf, myidbuf))
            return 0;

      return p;
}

int ll_mail_lock(struct ll_mail *p)
{
      struct stat stat_buf;
      char idbuf[IDBUFSIZE];
      char idbuf2[IDBUFSIZE];

      char fn[NUMBUFSIZE*2 + 20];
      char *f;
      int fd;

      getid(idbuf);

      if (p->cclientfd >= 0)
            return 0;

      if (stat(p->file, &stat_buf) < 0)
            return -1;

      if (snprintf(fn, sizeof(fn), "/tmp/.%lx.%lx",
                 (unsigned long)stat_buf.st_dev,
                 (unsigned long)stat_buf.st_ino) < 0)
      {
            errno=ENOSPC;
            return (-1);
      }

      if ((f=strdup(fn)) == NULL)
            return (-1);

      /* We do things a bit differently.  First, try O_EXCL */

      if ((fd=open(f, O_RDWR|O_CREAT|O_EXCL, 0644)) >= 0)
      {
            struct stat stat_buf2;

            if (ll_lockfd(fd, ll_writelock, ll_whence_start, 0) < 0 ||
                fcntl(fd, F_SETFD, FD_CLOEXEC) < 0 ||
                writeid(idbuf, fd) < 0)
            {
                  /* This shouldn't happen */

                  close(fd);
                  free(f);
                  return (-1);
            }

            /* Rare race condition: */

            if (fstat(fd, &stat_buf) < 0 ||
                lstat(f, &stat_buf2) < 0 ||
                stat_buf.st_dev != stat_buf2.st_dev ||
                stat_buf.st_ino != stat_buf2.st_ino)
            {
                  errno=EAGAIN;
                  close(fd);
                  free(f);
                  return (-1);
            }

            p->cclientfd=fd;
            p->cclientfile=f;
            return 0;
      }

      /*
      ** An existing lockfile.  See if it's tagged with another
      ** pid on this server, which no longer exists.
      */

      if ((fd=open(f, O_RDONLY)) >= 0)
      {
            pid_t p;

            if (readid(idbuf2, fd) == 0 &&
                (p=getpidid(idbuf2, idbuf)) != 0 &&
                kill(p, 0) < 0 && errno == ESRCH)
            {
                  errno=EAGAIN;
                  close(fd);
                  unlink(f); /* Don't try again right away */
                  free(f);
                  return (-1);
            }

            /* If we can't lock, someone must have it open, game over. */

            if (p == getpid() /* It's us! */

                || ll_lockfd(fd, ll_readlock, ll_whence_start, 0) < 0)
            {
                  errno=EEXIST;
                  close(fd);
                  free(f);
                  return (-1);
            }

            close(fd);
      }

      /* Stale 0-length lockfiles are blown away after 5 mins */

      if (lstat(f, &stat_buf) == 0 && stat_buf.st_size == 0 &&
          stat_buf.st_mtime + 300 < time(NULL))
      {
            errno=EAGAIN;
            unlink(f);
            free(f);
            return (-1);
      }

      errno=EAGAIN;
      free(f);
      return (-1);
}

/* Try to create a dot-lock */

static int try_dotlock(const char *tmpfile,
                   const char *dotlock,
                   char *idbuf);

static int try_mail_dotlock(const char *dotlock, char *idbuf)
{
      char timebuf[NUMBUFSIZE];
      char pidbuf[NUMBUFSIZE];
      char *tmpname;
      int rc;

      libmail_str_time_t(time(NULL), timebuf);
      libmail_str_pid_t(getpid(), pidbuf);

      tmpname=malloc(strlen(dotlock) + strlen(timebuf) + strlen(pidbuf) +
                   strlen(idbuf) + 10);

      if (!tmpname)
            return -1;

      strcpy(tmpname, dotlock);
      strcat(tmpname, ".");
      strcat(tmpname, timebuf);
      strcat(tmpname, ".");
      strcat(tmpname, pidbuf);
      strcat(tmpname, ".");
      strcat(tmpname, strchr(idbuf, ':')+1);

      rc=try_dotlock(tmpname, dotlock, idbuf);
      free(tmpname);
      return (rc);
}

static int try_dotlock(const char *tmpname,
                   const char *dotlock,
                   char *idbuf)
{
      struct stat stat_buf;

      int fd;

      fd=open(tmpname, O_RDWR | O_CREAT, 0644);

      if (fd < 0)
            return (-1);

      if (writeid(idbuf, fd))
      {
            close(fd);
            unlink(tmpname);
            return (-1);
      }
      close(fd);

      if (link(tmpname, dotlock) < 0 || stat(tmpname, &stat_buf) ||
          stat_buf.st_nlink != 2)
      {
            if (errno != EEXIST)
                  errno=EIO;

            unlink(tmpname);
            return (-1);
      }
      unlink(tmpname);
      return (0);
}

static void dotlock_exists(const char *dotlock, char *myidbuf,
                     int timeout)
{
      char idbuf[IDBUFSIZE];
      struct stat stat_buf;
      int fd;

      if ((fd=open(dotlock, O_RDONLY)) >= 0)
      {
            pid_t p;

            /*
            ** Where the locking process is on the same server,
            ** the decision is easy: does the process still exist,
            ** or not?
            */

            if (readid(idbuf, fd) == 0 && (p=getpidid(idbuf, myidbuf)))
            {
                  if (kill(p, 0) < 0 && errno == ESRCH)
                  {
                        close(fd);
                        if (unlink(dotlock) == 0)
                              errno=EAGAIN;
                        else
                              errno=EEXIST;
                        return;
                  }
            }
            else if (timeout > 0 && fstat(fd, &stat_buf) >= 0 &&
                   stat_buf.st_mtime < time(NULL) - timeout)
            {
                  close(fd);

                  if (unlink(dotlock) == 0)
                        errno=EAGAIN;
                  else
                        errno=EEXIST;
                  return;
            }

            close(fd);
      }

      errno=EEXIST;
}

static int ll_mail_open_do(struct ll_mail *p, int ro)
{
      char *dotlock;
      char myidbuf[IDBUFSIZE];
      int save_errno;
      int fd;

      getid(myidbuf);

      if (p->dotlock) /* Already locked */
      {
            fd=open(p->file, ro ? O_RDONLY:O_RDWR);

            if (fd >= 0 &&
                (ll_lockfd(fd, ro ? ll_readlock:ll_writelock, 0, 0) < 0 ||
                 fcntl(fd, F_SETFD, FD_CLOEXEC) < 0))
            {
                  close(fd);
                  fd= -1;
            }
            return fd;
      }

      if ((dotlock=malloc(strlen(p->file)+sizeof(".lock"))) == NULL)
            return -1;

      strcat(strcpy(dotlock, p->file), ".lock");

      if (try_mail_dotlock(dotlock, myidbuf) == 0)
      {
            fd=open(p->file, ro ? O_RDONLY:O_RDWR);

            if (fd >= 0 &&
                (ll_lockfd(fd, ro ? ll_readlock:ll_writelock, 0, 0) ||
                 fcntl(fd, F_SETFD, FD_CLOEXEC) < 0))
            {
                  close(fd);
                  fd= -1;
            }

            p->dotlock=dotlock;
            return fd;
      }

      save_errno=errno;

      /*
      ** Last fallback: for EEXIST, a read-only lock should suffice
      ** In all other instances, we'll fallback to read/write or read-only
      ** flock as last resort.
      */

      if ((errno == EEXIST && ro) || errno == EPERM || errno == EACCES)
      {
            fd=open(p->file, ro ? O_RDONLY:O_RDWR);

            if (fd >= 0)
            {
                  if (ll_lockfd(fd, ro ? ll_readlock:ll_writelock,
                              0, 0) == 0 &&
                      fcntl(fd, F_SETFD, FD_CLOEXEC) == 0)
                  {
                        free(dotlock);
                        return fd;
                  }
                  close(fd);
            }
      }

      /*
      ** If try_dotlock blew up for anything other than EEXIST, we don't
      ** know what the deal is, so punt.
      */

      if (save_errno != EEXIST)
      {
            free(dotlock);
            return (-1);
      }

      dotlock_exists(dotlock, myidbuf, 300);
      free(dotlock);
      return (-1);
}

int ll_mail_open_ro(struct ll_mail *p)
{
      return ll_mail_open_do(p, 1);
}

int ll_mail_open(struct ll_mail *p)
{
      return ll_mail_open_do(p, 0);
}

void ll_mail_free(struct ll_mail *p)
{
      char myid[IDBUFSIZE];
      char idbuf[IDBUFSIZE];

      getid(myid);

      if (p->cclientfd >= 0)
      {
            if (lseek(p->cclientfd, 0L, SEEK_SET) == 0 &&
                readid(idbuf, p->cclientfd) == 0 &&
                strcmp(myid, idbuf) == 0)
            {
                  ftruncate(p->cclientfd, 0);
                  unlink(p->cclientfile);
            }
            close(p->cclientfd);
            free(p->cclientfile);
      }

      if (p->dotlock)
      {
            int fd=open(p->dotlock, O_RDONLY);

            if (fd >= 0)
            {
                  if (readid(idbuf, fd) == 0 &&
                      strcmp(myid, idbuf) == 0)
                  {
                        close(fd);
                        unlink(p->dotlock);
                        free(p->dotlock);
                        free(p->file);
                        free(p);
                        return;
                  }
                  close(fd);
            }

            free(p->dotlock);
      }
      free(p->file);
      free(p);
}

int ll_dotlock(const char *dotlock, const char *tmpfile,
            int timeout)
{
      char myidbuf[IDBUFSIZE];

      getid(myidbuf);

      if (try_dotlock(tmpfile, dotlock, myidbuf))
      {
            if (errno == EEXIST)
                  dotlock_exists(dotlock, myidbuf, timeout);
            return -1;
      }
      return 0;
}



Generated by  Doxygen 1.6.0   Back to index