Back to index

courier  0.68.2
mail.c
Go to the documentation of this file.
00001 /*
00002 ** Copyright 2006 Double Precision, Inc.  See COPYING for
00003 ** distribution information.
00004 */
00005 
00006 #include      "config.h"
00007 #include      "liblock.h"
00008 #include      "mail.h"
00009 #include      "../numlib/numlib.h"
00010 #include      <stdio.h>
00011 #include      <stdlib.h>
00012 #include      <string.h>
00013 #include      <unistd.h>
00014 #include      <errno.h>
00015 #include      <signal.h>
00016 #include      <sys/types.h>
00017 #include      <sys/stat.h>
00018 #if HAVE_FCNTL_H
00019 #include      <fcntl.h>
00020 #endif
00021 
00022 
00023 struct ll_mail *ll_mail_alloc(const char *filename)
00024 {
00025        struct ll_mail *p=(struct ll_mail *)malloc(sizeof(struct ll_mail));
00026 
00027        if (!p)
00028               return NULL;
00029 
00030        if ((p->file=strdup(filename)) == NULL)
00031        {
00032               free(p);
00033               return NULL;
00034        }
00035 
00036        p->cclientfd= -1;
00037        p->cclientfile=NULL;
00038 
00039        p->dotlock=NULL;
00040 
00041        return p;
00042 }
00043 
00044 #define IDBUFSIZE 512
00045 
00046 /*
00047 ** For extra credit, we mark our territory.
00048 */
00049 
00050 static void getid(char *idbuf)
00051 {
00052        libmail_str_pid_t(getpid(), idbuf);
00053 
00054        while (*idbuf)
00055               idbuf++;
00056 
00057        *idbuf++=':';
00058 
00059        idbuf[IDBUFSIZE-NUMBUFSIZE-10]=0;
00060 
00061        if (gethostname(idbuf, IDBUFSIZE-NUMBUFSIZE-10) < 0)
00062               strcpy(idbuf, "localhost");
00063 }
00064 
00065 static int writeid(char *idbuf, int fd)
00066 {
00067        int l=strlen(idbuf);
00068 
00069        while (l)
00070        {
00071               int n=write(fd, idbuf, l);
00072 
00073               if (n <= 0)
00074                      return (-1);
00075 
00076               l -= n;
00077               idbuf += n;
00078        }
00079        return 0;
00080 }
00081 
00082 static int readid(char *p, int fd)
00083 {
00084        int l=IDBUFSIZE-1;
00085 
00086        while (l)
00087        {
00088               int n=read(fd, p, l);
00089 
00090               if (n < 0)
00091                      return (-1);
00092 
00093               if (n == 0)
00094                      break;
00095 
00096               p += n;
00097               l -= n;
00098        }
00099        *p=0;
00100        return 0;
00101 }
00102 
00103 static pid_t getpidid(char *idbuf, char *myidbuf)
00104 {
00105        pid_t p=atol(idbuf);
00106 
00107        if ((idbuf=strchr(idbuf, ':')) == NULL ||
00108            (myidbuf=strchr(myidbuf, ':')) == NULL ||
00109            strcmp(idbuf, myidbuf))
00110               return 0;
00111 
00112        return p;
00113 }
00114 
00115 int ll_mail_lock(struct ll_mail *p)
00116 {
00117        struct stat stat_buf;
00118        char idbuf[IDBUFSIZE];
00119        char idbuf2[IDBUFSIZE];
00120 
00121        char fn[NUMBUFSIZE*2 + 20];
00122        char *f;
00123        int fd;
00124 
00125        getid(idbuf);
00126 
00127        if (p->cclientfd >= 0)
00128               return 0;
00129 
00130        if (stat(p->file, &stat_buf) < 0)
00131               return -1;
00132 
00133        if (snprintf(fn, sizeof(fn), "/tmp/.%lx.%lx",
00134                    (unsigned long)stat_buf.st_dev,
00135                    (unsigned long)stat_buf.st_ino) < 0)
00136        {
00137               errno=ENOSPC;
00138               return (-1);
00139        }
00140 
00141        if ((f=strdup(fn)) == NULL)
00142               return (-1);
00143 
00144        /* We do things a bit differently.  First, try O_EXCL */
00145 
00146        if ((fd=open(f, O_RDWR|O_CREAT|O_EXCL, 0644)) >= 0)
00147        {
00148               struct stat stat_buf2;
00149 
00150               if (ll_lockfd(fd, ll_writelock, ll_whence_start, 0) < 0 ||
00151                   fcntl(fd, F_SETFD, FD_CLOEXEC) < 0 ||
00152                   writeid(idbuf, fd) < 0)
00153               {
00154                      /* This shouldn't happen */
00155 
00156                      close(fd);
00157                      free(f);
00158                      return (-1);
00159               }
00160 
00161               /* Rare race condition: */
00162 
00163               if (fstat(fd, &stat_buf) < 0 ||
00164                   lstat(f, &stat_buf2) < 0 ||
00165                   stat_buf.st_dev != stat_buf2.st_dev ||
00166                   stat_buf.st_ino != stat_buf2.st_ino)
00167               {
00168                      errno=EAGAIN;
00169                      close(fd);
00170                      free(f);
00171                      return (-1);
00172               }
00173 
00174               p->cclientfd=fd;
00175               p->cclientfile=f;
00176               return 0;
00177        }
00178 
00179        /*
00180        ** An existing lockfile.  See if it's tagged with another
00181        ** pid on this server, which no longer exists.
00182        */
00183 
00184        if ((fd=open(f, O_RDONLY)) >= 0)
00185        {
00186               pid_t p=-1;
00187 
00188               if (readid(idbuf2, fd) == 0 &&
00189                   (p=getpidid(idbuf2, idbuf)) != 0 &&
00190                   kill(p, 0) < 0 && errno == ESRCH)
00191               {
00192                      errno=EAGAIN;
00193                      close(fd);
00194                      unlink(f); /* Don't try again right away */
00195                      free(f);
00196                      return (-1);
00197               }
00198 
00199               /* If we can't lock, someone must have it open, game over. */
00200 
00201               if (p == getpid() /* It's us! */
00202 
00203                   || ll_lockfd(fd, ll_readlock, ll_whence_start, 0) < 0)
00204               {
00205                      errno=EEXIST;
00206                      close(fd);
00207                      free(f);
00208                      return (-1);
00209               }
00210 
00211               close(fd);
00212        }
00213 
00214        /* Stale 0-length lockfiles are blown away after 5 mins */
00215 
00216        if (lstat(f, &stat_buf) == 0 && stat_buf.st_size == 0 &&
00217            stat_buf.st_mtime + 300 < time(NULL))
00218        {
00219               errno=EAGAIN;
00220               unlink(f);
00221               free(f);
00222               return (-1);
00223        }
00224 
00225        errno=EAGAIN;
00226        free(f);
00227        return (-1);
00228 }
00229 
00230 /* Try to create a dot-lock */
00231 
00232 static int try_dotlock(const char *tmpfile,
00233                      const char *dotlock,
00234                      char *idbuf);
00235 
00236 static int try_mail_dotlock(const char *dotlock, char *idbuf)
00237 {
00238        char timebuf[NUMBUFSIZE];
00239        char pidbuf[NUMBUFSIZE];
00240        char *tmpname;
00241        int rc;
00242 
00243        libmail_str_time_t(time(NULL), timebuf);
00244        libmail_str_pid_t(getpid(), pidbuf);
00245 
00246        tmpname=malloc(strlen(dotlock) + strlen(timebuf) + strlen(pidbuf) +
00247                      strlen(idbuf) + 10);
00248 
00249        if (!tmpname)
00250               return -1;
00251 
00252        strcpy(tmpname, dotlock);
00253        strcat(tmpname, ".");
00254        strcat(tmpname, timebuf);
00255        strcat(tmpname, ".");
00256        strcat(tmpname, pidbuf);
00257        strcat(tmpname, ".");
00258        strcat(tmpname, strchr(idbuf, ':')+1);
00259 
00260        rc=try_dotlock(tmpname, dotlock, idbuf);
00261        free(tmpname);
00262        return (rc);
00263 }
00264 
00265 static int try_dotlock(const char *tmpname,
00266                      const char *dotlock,
00267                      char *idbuf)
00268 {
00269        struct stat stat_buf;
00270 
00271        int fd;
00272 
00273        fd=open(tmpname, O_RDWR | O_CREAT, 0644);
00274 
00275        if (fd < 0)
00276               return (-1);
00277 
00278        if (writeid(idbuf, fd))
00279        {
00280               close(fd);
00281               unlink(tmpname);
00282               return (-1);
00283        }
00284        close(fd);
00285 
00286        if (link(tmpname, dotlock) < 0 || stat(tmpname, &stat_buf) ||
00287            stat_buf.st_nlink != 2)
00288        {
00289               if (errno != EEXIST)
00290                      errno=EIO;
00291 
00292               unlink(tmpname);
00293               return (-1);
00294        }
00295        unlink(tmpname);
00296        return (0);
00297 }
00298 
00299 static void dotlock_exists(const char *dotlock, char *myidbuf,
00300                         int timeout)
00301 {
00302        char idbuf[IDBUFSIZE];
00303        struct stat stat_buf;
00304        int fd;
00305 
00306        if ((fd=open(dotlock, O_RDONLY)) >= 0)
00307        {
00308               pid_t p;
00309 
00310               /*
00311               ** Where the locking process is on the same server,
00312               ** the decision is easy: does the process still exist,
00313               ** or not?
00314               */
00315 
00316               if (readid(idbuf, fd) == 0 && (p=getpidid(idbuf, myidbuf)))
00317               {
00318                      if (p == getpid() /* Possibly recycled PID */
00319                          || (kill(p, 0) < 0 && errno == ESRCH))
00320                      {
00321                             close(fd);
00322                             if (unlink(dotlock) == 0)
00323                                    errno=EAGAIN;
00324                             else
00325                                    errno=EEXIST;
00326                             return;
00327                      }
00328               }
00329               else if (timeout > 0 && fstat(fd, &stat_buf) >= 0 &&
00330                       stat_buf.st_mtime < time(NULL) - timeout)
00331               {
00332                      close(fd);
00333 
00334                      if (unlink(dotlock) == 0)
00335                             errno=EAGAIN;
00336                      else
00337                             errno=EEXIST;
00338                      return;
00339               }
00340 
00341               close(fd);
00342        }
00343 
00344        errno=EEXIST;
00345 }
00346 
00347 static int ll_mail_open_do(struct ll_mail *p, int ro)
00348 {
00349        char *dotlock;
00350        char myidbuf[IDBUFSIZE];
00351        int save_errno;
00352        int fd;
00353 
00354        getid(myidbuf);
00355 
00356        if (p->dotlock) /* Already locked */
00357        {
00358               fd=open(p->file, ro ? O_RDONLY:O_RDWR);
00359 
00360               if (fd >= 0 &&
00361                   (ll_lockfd(fd, ro ? ll_readlock:ll_writelock, 0, 0) < 0 ||
00362                    fcntl(fd, F_SETFD, FD_CLOEXEC) < 0))
00363               {
00364                      close(fd);
00365                      fd= -1;
00366               }
00367               return fd;
00368        }
00369 
00370        if ((dotlock=malloc(strlen(p->file)+sizeof(".lock"))) == NULL)
00371               return -1;
00372 
00373        strcat(strcpy(dotlock, p->file), ".lock");
00374 
00375        if (try_mail_dotlock(dotlock, myidbuf) == 0)
00376        {
00377               fd=open(p->file, ro ? O_RDONLY:O_RDWR);
00378 
00379               if (fd >= 0 &&
00380                   (ll_lockfd(fd, ro ? ll_readlock:ll_writelock, 0, 0) ||
00381                    fcntl(fd, F_SETFD, FD_CLOEXEC) < 0))
00382               {
00383                      close(fd);
00384                      fd= -1;
00385               }
00386 
00387               p->dotlock=dotlock;
00388               return fd;
00389        }
00390 
00391        save_errno=errno;
00392 
00393        /*
00394        ** Last fallback: for EEXIST, a read-only lock should suffice
00395        ** In all other instances, we'll fallback to read/write or read-only
00396        ** flock as last resort.
00397        */
00398 
00399        if ((errno == EEXIST && ro) || errno == EPERM || errno == EACCES)
00400        {
00401               fd=open(p->file, ro ? O_RDONLY:O_RDWR);
00402 
00403               if (fd >= 0)
00404               {
00405                      if (ll_lockfd(fd, ro ? ll_readlock:ll_writelock,
00406                                   0, 0) == 0 &&
00407                          fcntl(fd, F_SETFD, FD_CLOEXEC) == 0)
00408                      {
00409                             free(dotlock);
00410                             return fd;
00411                      }
00412                      close(fd);
00413               }
00414        }
00415 
00416        /*
00417        ** If try_dotlock blew up for anything other than EEXIST, we don't
00418        ** know what the deal is, so punt.
00419        */
00420 
00421        if (save_errno != EEXIST)
00422        {
00423               free(dotlock);
00424               return (-1);
00425        }
00426 
00427        dotlock_exists(dotlock, myidbuf, 300);
00428        free(dotlock);
00429        return (-1);
00430 }
00431 
00432 int ll_mail_open_ro(struct ll_mail *p)
00433 {
00434        return ll_mail_open_do(p, 1);
00435 }
00436 
00437 int ll_mail_open(struct ll_mail *p)
00438 {
00439        return ll_mail_open_do(p, 0);
00440 }
00441 
00442 void ll_mail_free(struct ll_mail *p)
00443 {
00444        char myid[IDBUFSIZE];
00445        char idbuf[IDBUFSIZE];
00446 
00447        getid(myid);
00448 
00449        if (p->cclientfd >= 0)
00450        {
00451               if (lseek(p->cclientfd, 0L, SEEK_SET) == 0 &&
00452                   readid(idbuf, p->cclientfd) == 0 &&
00453                   strcmp(myid, idbuf) == 0)
00454               {
00455                      if (ftruncate(p->cclientfd, 0) >= 0)
00456                             unlink(p->cclientfile);
00457               }
00458               close(p->cclientfd);
00459               free(p->cclientfile);
00460        }
00461 
00462        if (p->dotlock)
00463        {
00464               int fd=open(p->dotlock, O_RDONLY);
00465 
00466               if (fd >= 0)
00467               {
00468                      if (readid(idbuf, fd) == 0 &&
00469                          strcmp(myid, idbuf) == 0)
00470                      {
00471                             close(fd);
00472                             unlink(p->dotlock);
00473                             free(p->dotlock);
00474                             free(p->file);
00475                             free(p);
00476                             return;
00477                      }
00478                      close(fd);
00479               }
00480 
00481               free(p->dotlock);
00482        }
00483        free(p->file);
00484        free(p);
00485 }
00486 
00487 int ll_dotlock(const char *dotlock, const char *tmpfile,
00488               int timeout)
00489 {
00490        char myidbuf[IDBUFSIZE];
00491 
00492        getid(myidbuf);
00493 
00494        if (try_dotlock(tmpfile, dotlock, myidbuf))
00495        {
00496               if (errno == EEXIST)
00497                      dotlock_exists(dotlock, myidbuf, timeout);
00498               return -1;
00499        }
00500        return 0;
00501 }
00502 
00503