Two-phase queue run perf: parallel processes for phase one
[users/jgh/exim.git] / src / src / transports / tf_maildir.c
index 6b9252f4ec6f23c4e47a806399f484e8f35cc2de..4d5c0c1a9a1960c0aa42e0c2c34f7cb5b6edd689 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/transports/tf_maildir.c,v 1.4 2005/02/17 11:58:27 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions in support of the use of maildirsize files for handling quotas in
@@ -29,46 +27,49 @@ calculations are not hard wired in, but are supplied as a regex. */
 *************************************************/
 
 /* This function is called at the start of a maildir delivery, to ensure that
-all the relevant directories exist.
+all the relevant directories exist. It also creates a maildirfolder file if the
+base directory matches a given pattern.
 
 Argument:
   path              the base directory name
   addr              the address item (for setting an error message)
   create_directory  true if we are allowed to create missing directories
   dirmode           the mode for created directories
+  maildirfolder_create_regex
+                    the pattern to match for maildirfolder creation
 
 Returns:            TRUE on success; FALSE on failure
 */
 
 BOOL maildir_ensure_directories(uschar *path, address_item *addr,
-  BOOL create_directory, int dirmode)
+  BOOL create_directory, int dirmode, uschar *maildirfolder_create_regex)
 {
 int i;
 struct stat statbuf;
-char *subdirs[] = { "/tmp", "/new", "/cur" };
+const char *subdirs[] = { "/tmp", "/new", "/cur" };
 
 DEBUG(D_transport)
   debug_printf("ensuring maildir directories exist in %s\n", path);
 
 /* First ensure that the path we have is a directory; if it does not exist,
 create it. Then make sure the tmp, new & cur subdirs of the maildir are
-there. If not, fail which aborts the delivery (even though the cur subdir is
+there. If not, fail. This aborts the delivery (even though the cur subdir is
 not actually needed for delivery). Handle all 4 directory tests/creates in a
 loop so that code can be shared. */
 
 for (i = 0; i < 4; i++)
   {
   int j;
-  uschar *dir, *mdir;
+  const uschar *dir, *mdir;
 
   if (i == 0)
     {
-    mdir = US"";
+    mdir = CUS"";
     dir = path;
     }
   else
     {
-    mdir = US subdirs[i-1];
+    mdir = CUS subdirs[i-1];
     dir = mdir + 1;
     }
 
@@ -135,7 +136,53 @@ for (i = 0; i < 4; i++)
     }
   }
 
-return TRUE;   /* All directories exist */
+/* If the basic path matches maildirfolder_create_regex, we are dealing with
+a subfolder, and should ensure that a maildirfolder file exists. */
+
+if (maildirfolder_create_regex != NULL)
+  {
+  const uschar *error;
+  int offset;
+  const pcre *regex;
+
+  DEBUG(D_transport) debug_printf("checking for maildirfolder requirement\n");
+
+  if (!(regex = pcre_compile(CS maildirfolder_create_regex, PCRE_COPT,
+    CCSS &error, &offset, NULL)))
+    {
+    addr->message = string_sprintf("appendfile: regular expression "
+      "error: %s at offset %d while compiling %s", error, offset,
+      maildirfolder_create_regex);
+    return FALSE;
+    }
+
+  if (pcre_exec(regex, NULL, CS path, Ustrlen(path), 0, 0, NULL, 0) >= 0)
+    {
+    uschar *fname = string_sprintf("%s/maildirfolder", path);
+    if (Ustat(fname, &statbuf) == 0)
+      {
+      DEBUG(D_transport) debug_printf("maildirfolder already exists\n");
+      }
+    else
+      {
+      int fd = Uopen(fname, O_WRONLY|O_APPEND|O_CREAT, 0600);
+      if (fd < 0)
+        {
+        addr->message = string_sprintf("appendfile: failed to create "
+          "maildirfolder file in %s directory: %s", path, strerror(errno));
+        return FALSE;
+        }
+      (void)close(fd);
+      DEBUG(D_transport) debug_printf("created maildirfolder file\n");
+      }
+    }
+  else
+    {
+    DEBUG(D_transport) debug_printf("maildirfolder file not required\n");
+    }
+  }
+
+return TRUE;   /* Everything exists that should exist */
 }
 
 
@@ -162,10 +209,12 @@ int len;
 uschar buffer[256];
 sprintf(CS buffer, "%d 1\n", size);
 len = Ustrlen(buffer);
-(void)lseek(fd, 0, SEEK_END);
-(void)write(fd, buffer, len);
-DEBUG(D_transport)
-  debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
+if (lseek(fd, 0, SEEK_END) >= 0)
+  {
+  len = write(fd, buffer, len);
+  DEBUG(D_transport)
+    debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
+  }
 }
 
 
@@ -198,22 +247,21 @@ Arguments:
 Returns:      the sum of the sizes of the messages
 */
 
-int
+off_t
 maildir_compute_size(uschar *path, int *filecount, time_t *latest,
   const pcre *regex, const pcre *dir_regex, BOOL timestamp_only)
 {
 DIR *dir;
-int sum = 0;
+off_t sum = 0;
 struct dirent *ent;
 struct stat statbuf;
 
-dir = opendir(CS path);
-if (dir == NULL) return 0;
+if (!(dir = opendir(CS path)))
+  return 0;
 
-while ((ent = readdir(dir)) != NULL)
+while ((ent = readdir(dir)))
   {
-  uschar *name = US ent->d_name;
-  uschar buffer[1024];
+  uschar * s, * name = US ent->d_name;
 
   if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
 
@@ -231,26 +279,19 @@ while ((ent = readdir(dir)) != NULL)
 
   /* The name is OK; stat it. */
 
-  if (!string_format(buffer, sizeof(buffer), "%s/%s", path, name))
-    {
-    DEBUG(D_transport)
-      debug_printf("maildir_compute_size: name too long: dir=%s name=%s\n",
-        path, name);
-    continue;
-    }
-
-  if (Ustat(buffer, &statbuf) < 0)
+  s = string_sprintf("%s/%s", path, name);
+  if (Ustat(s, &statbuf) < 0)
     {
     DEBUG(D_transport)
       debug_printf("maildir_compute_size: stat error %d for %s: %s\n", errno,
-        buffer, strerror(errno));
+        s, strerror(errno));
     continue;
     }
 
   if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
     {
     DEBUG(D_transport)
-      debug_printf("skipping %s/%s: not a directory\n", path, name);
+      debug_printf("skipping %s/%s: not a directory\n", s, name);
     continue;
     }
 
@@ -261,18 +302,14 @@ while ((ent = readdir(dir)) != NULL)
   /* If this is a maildir folder, call this function recursively. */
 
   if (name[0] == '.')
-    {
-    sum += maildir_compute_size(buffer, filecount, latest, regex, dir_regex,
+    sum += maildir_compute_size(s, filecount, latest, regex, dir_regex,
       timestamp_only);
-    }
 
   /* Otherwise it must be a folder that contains messages (e.g. new or cur), so
   we need to get its size, unless all we are interested in is the timestamp. */
 
   else if (!timestamp_only)
-    {
-    sum += check_dir_size(buffer, filecount, regex);
-    }
+    sum += check_dir_size(s, filecount, regex);
   }
 
 closedir(dir);
@@ -282,8 +319,9 @@ DEBUG(D_transport)
     debug_printf("maildir_compute_size (timestamp_only): %ld\n",
     (long int) *latest);
   else
-    debug_printf("maildir_compute_size: path=%s\n  sum=%d filecount=%d "
-      "timestamp=%ld\n", path, sum, *filecount, (long int) *latest);
+    debug_printf("maildir_compute_size: path=%s\n  sum=" OFF_T_FMT
+      " filecount=%d timestamp=%ld\n",
+      path, sum, *filecount, (long int) *latest);
   }
 return sum;
 }
@@ -306,7 +344,7 @@ Or, at least, it is supposed to!
 
 Arguments:
   path             the path to the maildir directory; this is already backed-up
-                     to the parent if the delivery diretory is a maildirfolder
+                     to the parent if the delivery directory is a maildirfolder
   ob               the appendfile options block
   regex            a compiled regex for getting a file's size from its name
   dir_regex        a compiled regex for selecting maildir directories
@@ -320,15 +358,15 @@ Returns:           >=0  a file descriptor for an open maildirsize file
 
 int
 maildir_ensure_sizefile(uschar *path, appendfile_transport_options_block *ob,
-  const pcre *regex, const pcre *dir_regex, int *returned_size,
+  const pcre *regex, const pcre *dir_regex, off_t *returned_size,
   int *returned_filecount)
 {
 int count, fd;
-int cached_quota = 0;
+off_t cached_quota = 0;
 int cached_quota_filecount = 0;
-int size = 0;
 int filecount = 0;
 int linecount = 0;
+off_t size = 0;
 uschar *filename;
 uschar buffer[MAX_FILE_SIZE];
 uschar *ptr = buffer;
@@ -340,8 +378,7 @@ the same thing. */
 filename = string_sprintf("%s/maildirsize", path);
 
 DEBUG(D_transport) debug_printf("looking for maildirsize in %s\n", path);
-fd = Uopen(filename, O_RDWR|O_APPEND, 0);
-if (fd < 0)
+if ((fd = Uopen(filename, O_RDWR|O_APPEND, ob->mode ? ob->mode : 0600)) < 0)
   {
   if (errno != ENOENT) return -1;
   DEBUG(D_transport)
@@ -353,8 +390,7 @@ if (fd < 0)
 still correct, and that the size of the file is still small enough. If so,
 compute the maildir size from the file. */
 
-count = read(fd, buffer, sizeof(buffer));
-if (count >= sizeof(buffer))
+if ((count = read(fd, buffer, sizeof(buffer))) >= sizeof(buffer))
   {
   DEBUG(D_transport)
     debug_printf("maildirsize file too big (%d): recalculating\n", count);
@@ -369,14 +405,14 @@ DEBUG(D_transport)
 
 for (;;)
   {
-  long int n = Ustrtol(ptr, &endptr, 10);
+  off_t n = (off_t)Ustrtod(ptr, &endptr);
 
   /* Only two data items are currently defined; ignore any others that
   may be present. The spec is for a number followed by a letter. Anything
   else we reject and recalculate. */
 
   if (*endptr == 'S') cached_quota = n;
-    else if (*endptr == 'C') cached_quota_filecount = n;
+    else if (*endptr == 'C') cached_quota_filecount = (int)n;
   if (!isalpha(*endptr++))
     {
     DEBUG(D_transport)
@@ -404,9 +440,9 @@ if (cached_quota != ob->quota_value ||
   {
   DEBUG(D_transport)
     debug_printf("cached quota is out of date: recalculating\n"
-      "  quota=%d cached_quota=%d filecount_quota=%d "
-      "cached_quota_filecount=%d\n", ob->quota_value, cached_quota,
-      ob->quota_filecount_value, cached_quota_filecount);
+      "  quota=" OFF_T_FMT " cached_quota=" OFF_T_FMT " filecount_quota=%d "
+      "cached_quota_filecount=%d\n", ob->quota_value,
+      cached_quota, ob->quota_filecount_value, cached_quota_filecount);
   goto RECALCULATE;
   }
 
@@ -421,7 +457,7 @@ while (*endptr++ == '\n')
   if (*endptr == 0) break;
   linecount++;
   ptr = endptr;
-  size += Ustrtol(ptr, &endptr, 10);
+  size += (off_t)Ustrtod(ptr, &endptr);
   if (*endptr != ' ') break;
   ptr = endptr + 1;
   filecount += Ustrtol(ptr, &endptr, 10);
@@ -439,7 +475,7 @@ if (*endptr == 0)
   if (size < 0 || filecount < 0)
     {
     DEBUG(D_transport) debug_printf("negative value in maildirsize "
-      "(size=%d count=%d): recalculating\n", size, filecount);
+      "(size=" OFF_T_FMT " count=%d): recalculating\n", size, filecount);
     goto RECALCULATE;
     }
 
@@ -497,25 +533,25 @@ else
 
   RECALCULATE:
 
-  if (fd >= 0) close(fd);
+  if (fd >= 0) (void)close(fd);
   old_latest = 0;
   filecount = 0;
   size = maildir_compute_size(path, &filecount, &old_latest, regex, dir_regex,
     FALSE);
 
   (void)gettimeofday(&tv, NULL);
-  tempname = string_sprintf("%s/tmp/%lu.H%luP%lu.%s", path, tv.tv_sec,
-    tv.tv_usec, getpid(), primary_hostname);
+  tempname = string_sprintf("%s/tmp/" TIME_T_FMT ".H%luP%lu.%s",
+    path, tv.tv_sec, tv.tv_usec, (long unsigned) getpid(), primary_hostname);
 
-  fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, 0600);
+  fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, ob->mode ? ob->mode : 0600);
   if (fd >= 0)
     {
-    (void)sprintf(CS buffer, "%dS,%dC\n%d %d\n", ob->quota_value,
-      ob->quota_filecount_value, size, filecount);
+    (void)sprintf(CS buffer, OFF_T_FMT "S,%dC\n" OFF_T_FMT " %d\n",
+      ob->quota_value, ob->quota_filecount_value, size, filecount);
     len = Ustrlen(buffer);
     if (write(fd, buffer, len) != len || Urename(tempname, filename) < 0)
       {
-      close(fd);
+      (void)close(fd);
       fd = -1;
       }
     }
@@ -531,15 +567,15 @@ else
     DEBUG(D_transport) debug_printf("abandoning maildirsize because of "
       "a later subdirectory modification\n");
     (void)Uunlink(filename);
-    close(fd);
-    fd = -1;
+    (void)close(fd);
+    fd = -2;
     }
   }
 
 /* Return the sizes and the file descriptor, if any */
 
-DEBUG(D_transport) debug_printf("returning maildir size=%d filecount=%d\n",
-  size, filecount);
+DEBUG(D_transport) debug_printf("returning maildir size=" OFF_T_FMT
+  " filecount=%d\n", size, filecount);
 *returned_size = size;
 *returned_filecount = filecount;
 return fd;