1
/* $Cambridge: exim/exim-src/src/transports/tf_maildir.c,v 1.14 2009/11/16 19:56:54 nm4 Exp $ */
3
/*************************************************
4
* Exim - an Internet mail transport agent *
5
*************************************************/
7
/* Copyright (c) University of Cambridge 1995 - 2009 */
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. It also creates a maildirfolder file if the
33
base directory matches a given pattern.
36
path the base directory name
37
addr the address item (for setting an error message)
38
create_directory true if we are allowed to create missing directories
39
dirmode the mode for created directories
40
maildirfolder_create_regex
41
the pattern to match for maildirfolder creation
43
Returns: TRUE on success; FALSE on failure
46
BOOL maildir_ensure_directories(uschar *path, address_item *addr,
47
BOOL create_directory, int dirmode, uschar *maildirfolder_create_regex)
51
char *subdirs[] = { "/tmp", "/new", "/cur" };
54
debug_printf("ensuring maildir directories exist in %s\n", path);
56
/* First ensure that the path we have is a directory; if it does not exist,
57
create it. Then make sure the tmp, new & cur subdirs of the maildir are
58
there. If not, fail. This aborts the delivery (even though the cur subdir is
59
not actually needed for delivery). Handle all 4 directory tests/creates in a
60
loop so that code can be shared. */
62
for (i = 0; i < 4; i++)
74
mdir = US subdirs[i-1];
78
/* Check an existing path is a directory. This is inside a loop because
79
there is a potential race condition when creating the directory - some
80
other process may get there first. Give up after trying several times,
83
for (j = 0; j < 10; j++)
85
if (Ustat(dir, &statbuf) == 0)
87
if (S_ISDIR(statbuf.st_mode)) break; /* out of the race loop */
88
addr->message = string_sprintf("%s%s is not a directory", path,
90
addr->basic_errno = ERRNO_NOTDIRECTORY;
94
/* Try to make if non-existent and configured to do so */
96
if (errno == ENOENT && create_directory)
98
if (!directory_make(NULL, dir, dirmode, FALSE))
100
if (errno == EEXIST) continue; /* repeat the race loop */
101
addr->message = string_sprintf("cannot create %s%s", path, mdir);
102
addr->basic_errno = errno;
106
debug_printf("created directory %s%s\n", path, mdir);
107
break; /* out of the race loop */
110
/* stat() error other than ENOENT, or ENOENT and not creatable */
112
addr->message = string_sprintf("stat() error for %s%s: %s", path, mdir,
114
addr->basic_errno = errno;
118
/* If we went round the loop 10 times, the directory was flickering in
119
and out of existence like someone in a malfunctioning Star Trek
124
addr->message = string_sprintf("existence of %s%s unclear\n", path,
126
addr->basic_errno = errno;
127
addr->special_action = SPECIAL_FREEZE;
131
/* First time through the directories loop, cd to the main directory */
133
if (i == 0 && Uchdir(path) != 0)
135
addr->message = string_sprintf ("cannot chdir to %s", path);
136
addr->basic_errno = errno;
141
/* If the basic path matches maildirfolder_create_regex, we are dealing with
142
a subfolder, and should ensure that a maildirfolder file exists. */
144
if (maildirfolder_create_regex != NULL)
150
DEBUG(D_transport) debug_printf("checking for maildirfolder requirement\n");
152
regex = pcre_compile(CS maildirfolder_create_regex, PCRE_COPT,
153
(const char **)&error, &offset, NULL);
157
addr->message = string_sprintf("appendfile: regular expression "
158
"error: %s at offset %d while compiling %s", error, offset,
159
maildirfolder_create_regex);
163
if (pcre_exec(regex, NULL, CS path, Ustrlen(path), 0, 0, NULL, 0) >= 0)
165
uschar *fname = string_sprintf("%s/maildirfolder", path);
166
if (Ustat(fname, &statbuf) == 0)
168
DEBUG(D_transport) debug_printf("maildirfolder already exists\n");
172
int fd = Uopen(fname, O_WRONLY|O_APPEND|O_CREAT, 0600);
175
addr->message = string_sprintf("appendfile: failed to create "
176
"maildirfolder file in %s directory: %s", path, strerror(errno));
180
DEBUG(D_transport) debug_printf("created maildirfolder file\n");
185
DEBUG(D_transport) debug_printf("maildirfolder file not required\n");
189
return TRUE; /* Everything exists that should exist */
195
/*************************************************
196
* Update maildirsizefile for new file *
197
*************************************************/
199
/* This function is called to add a new line to the file, recording the length
200
of the newly added message. There isn't much we can do on failure...
203
fd the open file descriptor
204
size the size of the message
210
maildir_record_length(int fd, int size)
214
sprintf(CS buffer, "%d 1\n", size);
215
len = Ustrlen(buffer);
216
(void)lseek(fd, 0, SEEK_END);
217
(void)write(fd, buffer, len);
219
debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
224
/*************************************************
225
* Find the size of a maildir *
226
*************************************************/
228
/* This function is called when we have to recalculate the size of a maildir by
229
scanning all the files and directories therein. There are rules and conventions
230
about which files or directories are included. We support this by the use of a
231
regex to match directories that are to be included.
233
Maildirs can only be one level deep. However, this function recurses, so it
234
might cope with deeper nestings. We use the existing check_dir_size() function
235
to add up the sizes of the files in a directory that contains messages.
237
The function returns the most recent timestamp encountered. It can also be run
238
in a dummy mode in which it does not scan for sizes, but just returns the
242
path the path to the maildir
243
filecount where to store the count of messages
244
latest where to store the latest timestamp encountered
245
regex a regex for getting files sizes from file names
246
dir_regex a regex for matching directories to be included
247
timestamp_only don't actually compute any sizes
249
Returns: the sum of the sizes of the messages
253
maildir_compute_size(uschar *path, int *filecount, time_t *latest,
254
const pcre *regex, const pcre *dir_regex, BOOL timestamp_only)
261
dir = opendir(CS path);
262
if (dir == NULL) return 0;
264
while ((ent = readdir(dir)) != NULL)
266
uschar *name = US ent->d_name;
269
if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
271
/* We are normally supplied with a regex for choosing which directories to
272
scan. We do the regex match first, because that avoids a stat() for names
273
we aren't interested in. */
275
if (dir_regex != NULL &&
276
pcre_exec(dir_regex, NULL, CS name, Ustrlen(name), 0, 0, NULL, 0) < 0)
279
debug_printf("skipping %s/%s: dir_regex does not match\n", path, name);
283
/* The name is OK; stat it. */
285
if (!string_format(buffer, sizeof(buffer), "%s/%s", path, name))
288
debug_printf("maildir_compute_size: name too long: dir=%s name=%s\n",
293
if (Ustat(buffer, &statbuf) < 0)
296
debug_printf("maildir_compute_size: stat error %d for %s: %s\n", errno,
297
buffer, strerror(errno));
301
if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
304
debug_printf("skipping %s/%s: not a directory\n", path, name);
308
/* Keep the latest timestamp encountered */
310
if (statbuf.st_mtime > *latest) *latest = statbuf.st_mtime;
312
/* If this is a maildir folder, call this function recursively. */
316
sum += maildir_compute_size(buffer, filecount, latest, regex, dir_regex,
320
/* Otherwise it must be a folder that contains messages (e.g. new or cur), so
321
we need to get its size, unless all we are interested in is the timestamp. */
323
else if (!timestamp_only)
325
sum += check_dir_size(buffer, filecount, regex);
333
debug_printf("maildir_compute_size (timestamp_only): %ld\n",
336
debug_printf("maildir_compute_size: path=%s\n sum=" OFF_T_FMT
337
" filecount=%d timestamp=%ld\n",
338
path, sum, *filecount, (long int) *latest);
345
/*************************************************
346
* Create or update maildirsizefile *
347
*************************************************/
349
/* This function is called before a delivery if the option to use
350
maildirsizefile is enabled. Its function is to create the file if it does not
351
exist, or to update it if that is necessary.
353
The logic in this function follows the rules that are described in
355
http://www.inter7.com/courierimap/README.maildirquota.html
357
Or, at least, it is supposed to!
360
path the path to the maildir directory; this is already backed-up
361
to the parent if the delivery diretory is a maildirfolder
362
ob the appendfile options block
363
regex a compiled regex for getting a file's size from its name
364
dir_regex a compiled regex for selecting maildir directories
365
returned_size where to return the current size of the maildir, even if
366
the maildirsizefile is removed because of a race
368
Returns: >=0 a file descriptor for an open maildirsize file
369
-1 there was an error opening or accessing the file
370
-2 the file was removed because of a race
374
maildir_ensure_sizefile(uschar *path, appendfile_transport_options_block *ob,
375
const pcre *regex, const pcre *dir_regex, off_t *returned_size,
376
int *returned_filecount)
379
off_t cached_quota = 0;
380
int cached_quota_filecount = 0;
385
uschar buffer[MAX_FILE_SIZE];
386
uschar *ptr = buffer;
389
/* Try a few times to open or create the file, in case another process is doing
392
filename = string_sprintf("%s/maildirsize", path);
394
DEBUG(D_transport) debug_printf("looking for maildirsize in %s\n", path);
395
fd = Uopen(filename, O_RDWR|O_APPEND, ob->mode ? ob->mode : 0600);
398
if (errno != ENOENT) return -1;
400
debug_printf("%s does not exist: recalculating\n", filename);
404
/* The file has been successfully opened. Check that the cached quota value is
405
still correct, and that the size of the file is still small enough. If so,
406
compute the maildir size from the file. */
408
count = read(fd, buffer, sizeof(buffer));
409
if (count >= sizeof(buffer))
412
debug_printf("maildirsize file too big (%d): recalculating\n", count);
415
buffer[count] = 0; /* Ensure string terminated */
417
/* Read the quota parameters from the first line of the data. */
420
debug_printf("reading quota parameters from maildirsize data\n");
424
off_t n = (off_t)Ustrtod(ptr, &endptr);
426
/* Only two data items are currently defined; ignore any others that
427
may be present. The spec is for a number followed by a letter. Anything
428
else we reject and recalculate. */
430
if (*endptr == 'S') cached_quota = n;
431
else if (*endptr == 'C') cached_quota_filecount = (int)n;
432
if (!isalpha(*endptr++))
435
debug_printf("quota parameter number not followed by letter in "
436
"\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
440
if (*endptr == '\n' || *endptr == 0) break;
441
if (*endptr++ != ',')
444
debug_printf("quota parameter not followed by comma in "
445
"\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
452
/* Check the cached values against the current settings */
454
if (cached_quota != ob->quota_value ||
455
cached_quota_filecount != ob->quota_filecount_value)
458
debug_printf("cached quota is out of date: recalculating\n"
459
" quota=" OFF_T_FMT " cached_quota=" OFF_T_FMT " filecount_quota=%d "
460
"cached_quota_filecount=%d\n", ob->quota_value,
461
cached_quota, ob->quota_filecount_value, cached_quota_filecount);
465
/* Quota values agree; parse the rest of the data to get the sizes. At this
466
stage, *endptr points either to 0 or to '\n'. */
469
debug_printf("computing maildir size from maildirsize data\n");
471
while (*endptr++ == '\n')
473
if (*endptr == 0) break;
476
size += (off_t)Ustrtod(ptr, &endptr);
477
if (*endptr != ' ') break;
479
filecount += Ustrtol(ptr, &endptr, 10);
482
/* If *endptr is zero, we have successfully parsed the file, and we now have
483
the size of the mailbox as cached in the file. The "rules" say that if this
484
value indicates that the mailbox is over quota, we must recalculate if there is
485
more than one entry in the file, or if the file is older than 15 minutes. Also,
486
just in case there are weird values in the file, recalculate if either of the
487
values is negative. */
491
if (size < 0 || filecount < 0)
493
DEBUG(D_transport) debug_printf("negative value in maildirsize "
494
"(size=" OFF_T_FMT " count=%d): recalculating\n", size, filecount);
498
if (ob->quota_value > 0 &&
499
(size + (ob->quota_is_inclusive? message_size : 0) > ob->quota_value ||
500
(ob->quota_filecount_value > 0 &&
501
filecount + (ob->quota_is_inclusive ? 1:0) >
502
ob->quota_filecount_value)
508
DEBUG(D_transport) debug_printf("over quota and maildirsize has "
509
"more than 1 entry: recalculating\n");
513
if (fstat(fd, &statbuf) < 0) goto RECALCULATE; /* Should never occur */
515
if (time(NULL) - statbuf.st_mtime > 15*60)
517
DEBUG(D_transport) debug_printf("over quota and maildirsize is older "
518
"than 15 minutes: recalculating\n");
525
/* If *endptr is not zero, there was a syntax error in the file. */
530
time_t old_latest, new_latest;
537
while (p > buffer && p[-1] != '\n') p--;
540
debug_printf("error in maildirsizefile: unexpected character %d in "
541
"line %d (starting '%s'): recalculating\n",
542
*endptr, linecount + 1, string_printing(p));
545
/* Either there is no file, or the quota value has changed, or the file has
546
got too big, or there was some format error in the file. Recalculate the size
547
and write new contents to a temporary file; then rename it. After any
548
error, just return -1 as the file descriptor. */
552
if (fd >= 0) (void)close(fd);
555
size = maildir_compute_size(path, &filecount, &old_latest, regex, dir_regex,
558
(void)gettimeofday(&tv, NULL);
559
tempname = string_sprintf("%s/tmp/%lu.H%luP%lu.%s", path, tv.tv_sec,
560
tv.tv_usec, getpid(), primary_hostname);
562
fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, ob->mode ? ob->mode : 0600);
565
(void)sprintf(CS buffer, OFF_T_FMT "S,%dC\n" OFF_T_FMT " %d\n",
566
ob->quota_value, ob->quota_filecount_value, size, filecount);
567
len = Ustrlen(buffer);
568
if (write(fd, buffer, len) != len || Urename(tempname, filename) < 0)
575
/* If any of the directories have been modified since the last timestamp we
576
saw, we have to junk this maildirsize file. */
578
DEBUG(D_transport) debug_printf("checking subdirectory timestamps\n");
580
(void)maildir_compute_size(path, NULL, &new_latest , NULL, dir_regex, TRUE);
581
if (new_latest > old_latest)
583
DEBUG(D_transport) debug_printf("abandoning maildirsize because of "
584
"a later subdirectory modification\n");
585
(void)Uunlink(filename);
591
/* Return the sizes and the file descriptor, if any */
593
DEBUG(D_transport) debug_printf("returning maildir size=" OFF_T_FMT
594
" filecount=%d\n", size, filecount);
595
*returned_size = size;
596
*returned_filecount = filecount;
600
/* End of tf_maildir.c */