1 /* $Cambridge: exim/src/src/transports/tf_maildir.c,v 1.8 2006/02/07 11:19:03 ph10 Exp $ */
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
7 /* Copyright (c) University of Cambridge 1995 - 2006 */
8 /* See the file NOTICE for conditions of use and distribution. */
10 /* Functions in support of the use of maildirsize files for handling quotas in
11 maildir directories. Some of the rules are a bit baroque:
13 http://www.inter7.com/courierimap/README.maildirquota.html
15 We try to follow most of that, except that the directories to skip for quota
16 calculations are not hard wired in, but are supplied as a regex. */
20 #include "appendfile.h"
21 #include "tf_maildir.h"
23 #define MAX_FILE_SIZE 5120
27 /*************************************************
28 * Ensure maildir directories exist *
29 *************************************************/
31 /* This function is called at the start of a maildir delivery, to ensure that
32 all the relevant directories exist.
35 path the base directory name
36 addr the address item (for setting an error message)
37 create_directory true if we are allowed to create missing directories
38 dirmode the mode for created directories
40 Returns: TRUE on success; FALSE on failure
43 BOOL maildir_ensure_directories(uschar *path, address_item *addr,
44 BOOL create_directory, int dirmode)
48 char *subdirs[] = { "/tmp", "/new", "/cur" };
51 debug_printf("ensuring maildir directories exist in %s\n", path);
53 /* First ensure that the path we have is a directory; if it does not exist,
54 create it. Then make sure the tmp, new & cur subdirs of the maildir are
55 there. If not, fail which aborts the delivery (even though the cur subdir is
56 not actually needed for delivery). Handle all 4 directory tests/creates in a
57 loop so that code can be shared. */
59 for (i = 0; i < 4; i++)
71 mdir = US subdirs[i-1];
75 /* Check an existing path is a directory. This is inside a loop because
76 there is a potential race condition when creating the directory - some
77 other process may get there first. Give up after trying several times,
80 for (j = 0; j < 10; j++)
82 if (Ustat(dir, &statbuf) == 0)
84 if (S_ISDIR(statbuf.st_mode)) break; /* out of the race loop */
85 addr->message = string_sprintf("%s%s is not a directory", path,
87 addr->basic_errno = ERRNO_NOTDIRECTORY;
91 /* Try to make if non-existent and configured to do so */
93 if (errno == ENOENT && create_directory)
95 if (!directory_make(NULL, dir, dirmode, FALSE))
97 if (errno == EEXIST) continue; /* repeat the race loop */
98 addr->message = string_sprintf("cannot create %s%s", path, mdir);
99 addr->basic_errno = errno;
103 debug_printf("created directory %s%s\n", path, mdir);
104 break; /* out of the race loop */
107 /* stat() error other than ENOENT, or ENOENT and not creatable */
109 addr->message = string_sprintf("stat() error for %s%s: %s", path, mdir,
111 addr->basic_errno = errno;
115 /* If we went round the loop 10 times, the directory was flickering in
116 and out of existence like someone in a malfunctioning Star Trek
121 addr->message = string_sprintf("existence of %s%s unclear\n", path,
123 addr->basic_errno = errno;
124 addr->special_action = SPECIAL_FREEZE;
128 /* First time through the directories loop, cd to the main directory */
130 if (i == 0 && Uchdir(path) != 0)
132 addr->message = string_sprintf ("cannot chdir to %s", path);
133 addr->basic_errno = errno;
138 return TRUE; /* All directories exist */
144 /*************************************************
145 * Update maildirsizefile for new file *
146 *************************************************/
148 /* This function is called to add a new line to the file, recording the length
149 of the newly added message. There isn't much we can do on failure...
152 fd the open file descriptor
153 size the size of the message
159 maildir_record_length(int fd, int size)
163 sprintf(CS buffer, "%d 1\n", size);
164 len = Ustrlen(buffer);
165 (void)lseek(fd, 0, SEEK_END);
166 (void)write(fd, buffer, len);
168 debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
173 /*************************************************
174 * Find the size of a maildir *
175 *************************************************/
177 /* This function is called when we have to recalculate the size of a maildir by
178 scanning all the files and directories therein. There are rules and conventions
179 about which files or directories are included. We support this by the use of a
180 regex to match directories that are to be included.
182 Maildirs can only be one level deep. However, this function recurses, so it
183 might cope with deeper nestings. We use the existing check_dir_size() function
184 to add up the sizes of the files in a directory that contains messages.
186 The function returns the most recent timestamp encountered. It can also be run
187 in a dummy mode in which it does not scan for sizes, but just returns the
191 path the path to the maildir
192 filecount where to store the count of messages
193 latest where to store the latest timestamp encountered
194 regex a regex for getting files sizes from file names
195 dir_regex a regex for matching directories to be included
196 timestamp_only don't actually compute any sizes
198 Returns: the sum of the sizes of the messages
202 maildir_compute_size(uschar *path, int *filecount, time_t *latest,
203 const pcre *regex, const pcre *dir_regex, BOOL timestamp_only)
210 dir = opendir(CS path);
211 if (dir == NULL) return 0;
213 while ((ent = readdir(dir)) != NULL)
215 uschar *name = US ent->d_name;
218 if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
220 /* We are normally supplied with a regex for choosing which directories to
221 scan. We do the regex match first, because that avoids a stat() for names
222 we aren't interested in. */
224 if (dir_regex != NULL &&
225 pcre_exec(dir_regex, NULL, CS name, Ustrlen(name), 0, 0, NULL, 0) < 0)
228 debug_printf("skipping %s/%s: dir_regex does not match\n", path, name);
232 /* The name is OK; stat it. */
234 if (!string_format(buffer, sizeof(buffer), "%s/%s", path, name))
237 debug_printf("maildir_compute_size: name too long: dir=%s name=%s\n",
242 if (Ustat(buffer, &statbuf) < 0)
245 debug_printf("maildir_compute_size: stat error %d for %s: %s\n", errno,
246 buffer, strerror(errno));
250 if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
253 debug_printf("skipping %s/%s: not a directory\n", path, name);
257 /* Keep the latest timestamp encountered */
259 if (statbuf.st_mtime > *latest) *latest = statbuf.st_mtime;
261 /* If this is a maildir folder, call this function recursively. */
265 sum += maildir_compute_size(buffer, filecount, latest, regex, dir_regex,
269 /* Otherwise it must be a folder that contains messages (e.g. new or cur), so
270 we need to get its size, unless all we are interested in is the timestamp. */
272 else if (!timestamp_only)
274 sum += check_dir_size(buffer, filecount, regex);
282 debug_printf("maildir_compute_size (timestamp_only): %ld\n",
285 debug_printf("maildir_compute_size: path=%s\n sum=" OFF_T_FMT
286 " filecount=%d timestamp=%ld\n",
287 path, sum, *filecount, (long int) *latest);
294 /*************************************************
295 * Create or update maildirsizefile *
296 *************************************************/
298 /* This function is called before a delivery if the option to use
299 maildirsizefile is enabled. Its function is to create the file if it does not
300 exist, or to update it if that is necessary.
302 The logic in this function follows the rules that are described in
304 http://www.inter7.com/courierimap/README.maildirquota.html
306 Or, at least, it is supposed to!
309 path the path to the maildir directory; this is already backed-up
310 to the parent if the delivery diretory is a maildirfolder
311 ob the appendfile options block
312 regex a compiled regex for getting a file's size from its name
313 dir_regex a compiled regex for selecting maildir directories
314 returned_size where to return the current size of the maildir, even if
315 the maildirsizefile is removed because of a race
317 Returns: >=0 a file descriptor for an open maildirsize file
318 -1 there was an error opening or accessing the file
319 -2 the file was removed because of a race
323 maildir_ensure_sizefile(uschar *path, appendfile_transport_options_block *ob,
324 const pcre *regex, const pcre *dir_regex, off_t *returned_size,
325 int *returned_filecount)
328 off_t cached_quota = 0;
329 int cached_quota_filecount = 0;
334 uschar buffer[MAX_FILE_SIZE];
335 uschar *ptr = buffer;
338 /* Try a few times to open or create the file, in case another process is doing
341 filename = string_sprintf("%s/maildirsize", path);
343 DEBUG(D_transport) debug_printf("looking for maildirsize in %s\n", path);
344 fd = Uopen(filename, O_RDWR|O_APPEND, 0);
347 if (errno != ENOENT) return -1;
349 debug_printf("%s does not exist: recalculating\n", filename);
353 /* The file has been successfully opened. Check that the cached quota value is
354 still correct, and that the size of the file is still small enough. If so,
355 compute the maildir size from the file. */
357 count = read(fd, buffer, sizeof(buffer));
358 if (count >= sizeof(buffer))
361 debug_printf("maildirsize file too big (%d): recalculating\n", count);
364 buffer[count] = 0; /* Ensure string terminated */
366 /* Read the quota parameters from the first line of the data. */
369 debug_printf("reading quota parameters from maildirsize data\n");
373 off_t n = (off_t)Ustrtod(ptr, &endptr);
375 /* Only two data items are currently defined; ignore any others that
376 may be present. The spec is for a number followed by a letter. Anything
377 else we reject and recalculate. */
379 if (*endptr == 'S') cached_quota = n;
380 else if (*endptr == 'C') cached_quota_filecount = (int)n;
381 if (!isalpha(*endptr++))
384 debug_printf("quota parameter number not followed by letter in "
385 "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
389 if (*endptr == '\n' || *endptr == 0) break;
390 if (*endptr++ != ',')
393 debug_printf("quota parameter not followed by comma in "
394 "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
401 /* Check the cached values against the current settings */
403 if (cached_quota != ob->quota_value ||
404 cached_quota_filecount != ob->quota_filecount_value)
407 debug_printf("cached quota is out of date: recalculating\n"
408 " quota=" OFF_T_FMT " cached_quota=" OFF_T_FMT " filecount_quota=%d "
409 "cached_quota_filecount=%d\n", ob->quota_value,
410 cached_quota, ob->quota_filecount_value, cached_quota_filecount);
414 /* Quota values agree; parse the rest of the data to get the sizes. At this
415 stage, *endptr points either to 0 or to '\n'. */
418 debug_printf("computing maildir size from maildirsize data\n");
420 while (*endptr++ == '\n')
422 if (*endptr == 0) break;
425 size += (off_t)Ustrtod(ptr, &endptr);
426 if (*endptr != ' ') break;
428 filecount += Ustrtol(ptr, &endptr, 10);
431 /* If *endptr is zero, we have successfully parsed the file, and we now have
432 the size of the mailbox as cached in the file. The "rules" say that if this
433 value indicates that the mailbox is over quota, we must recalculate if there is
434 more than one entry in the file, or if the file is older than 15 minutes. Also,
435 just in case there are weird values in the file, recalculate if either of the
436 values is negative. */
440 if (size < 0 || filecount < 0)
442 DEBUG(D_transport) debug_printf("negative value in maildirsize "
443 "(size=" OFF_T_FMT " count=%d): recalculating\n", size, filecount);
447 if (ob->quota_value > 0 &&
448 (size + (ob->quota_is_inclusive? message_size : 0) > ob->quota_value ||
449 (ob->quota_filecount_value > 0 &&
450 filecount + (ob->quota_is_inclusive ? 1:0) >
451 ob->quota_filecount_value)
457 DEBUG(D_transport) debug_printf("over quota and maildirsize has "
458 "more than 1 entry: recalculating\n");
462 if (fstat(fd, &statbuf) < 0) goto RECALCULATE; /* Should never occur */
464 if (time(NULL) - statbuf.st_mtime > 15*60)
466 DEBUG(D_transport) debug_printf("over quota and maildirsize is older "
467 "than 15 minutes: recalculating\n");
474 /* If *endptr is not zero, there was a syntax error in the file. */
479 time_t old_latest, new_latest;
486 while (p > buffer && p[-1] != '\n') p--;
489 debug_printf("error in maildirsizefile: unexpected character %d in "
490 "line %d (starting '%s'): recalculating\n",
491 *endptr, linecount + 1, string_printing(p));
494 /* Either there is no file, or the quota value has changed, or the file has
495 got too big, or there was some format error in the file. Recalculate the size
496 and write new contents to a temporary file; then rename it. After any
497 error, just return -1 as the file descriptor. */
501 if (fd >= 0) (void)close(fd);
504 size = maildir_compute_size(path, &filecount, &old_latest, regex, dir_regex,
507 (void)gettimeofday(&tv, NULL);
508 tempname = string_sprintf("%s/tmp/%lu.H%luP%lu.%s", path, tv.tv_sec,
509 tv.tv_usec, getpid(), primary_hostname);
511 fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, 0600);
514 (void)sprintf(CS buffer, OFF_T_FMT "S,%dC\n" OFF_T_FMT " %d\n",
515 ob->quota_value, ob->quota_filecount_value, size, filecount);
516 len = Ustrlen(buffer);
517 if (write(fd, buffer, len) != len || Urename(tempname, filename) < 0)
524 /* If any of the directories have been modified since the last timestamp we
525 saw, we have to junk this maildirsize file. */
527 DEBUG(D_transport) debug_printf("checking subdirectory timestamps\n");
529 (void)maildir_compute_size(path, NULL, &new_latest , NULL, dir_regex, TRUE);
530 if (new_latest > old_latest)
532 DEBUG(D_transport) debug_printf("abandoning maildirsize because of "
533 "a later subdirectory modification\n");
534 (void)Uunlink(filename);
540 /* Return the sizes and the file descriptor, if any */
542 DEBUG(D_transport) debug_printf("returning maildir size=" OFF_T_FMT
543 " filecount=%d\n", size, filecount);
544 *returned_size = size;
545 *returned_filecount = filecount;
549 /* End of tf_maildir.c */