Use RM_COMMAND everywhere during building.
[exim.git] / src / src / transports / tf_maildir.c
CommitLineData
f1e894f3 1/* $Cambridge: exim/src/src/transports/tf_maildir.c,v 1.7 2005/06/27 14:29:45 ph10 Exp $ */
0756eb3c
PH
2
3/*************************************************
4* Exim - an Internet mail transport agent *
5*************************************************/
6
c988f1f4 7/* Copyright (c) University of Cambridge 1995 - 2005 */
0756eb3c
PH
8/* See the file NOTICE for conditions of use and distribution. */
9
10/* Functions in support of the use of maildirsize files for handling quotas in
11maildir directories. Some of the rules are a bit baroque:
12
13http://www.inter7.com/courierimap/README.maildirquota.html
14
15We try to follow most of that, except that the directories to skip for quota
16calculations are not hard wired in, but are supplied as a regex. */
17
18
19#include "../exim.h"
20#include "appendfile.h"
21#include "tf_maildir.h"
22
23#define MAX_FILE_SIZE 5120
24
25
26
27/*************************************************
28* Ensure maildir directories exist *
29*************************************************/
30
31/* This function is called at the start of a maildir delivery, to ensure that
32all the relevant directories exist.
33
34Argument:
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
39
40Returns: TRUE on success; FALSE on failure
41*/
42
43BOOL maildir_ensure_directories(uschar *path, address_item *addr,
44 BOOL create_directory, int dirmode)
45{
46int i;
47struct stat statbuf;
48char *subdirs[] = { "/tmp", "/new", "/cur" };
49
50DEBUG(D_transport)
51 debug_printf("ensuring maildir directories exist in %s\n", path);
52
53/* First ensure that the path we have is a directory; if it does not exist,
54create it. Then make sure the tmp, new & cur subdirs of the maildir are
55there. If not, fail which aborts the delivery (even though the cur subdir is
56not actually needed for delivery). Handle all 4 directory tests/creates in a
57loop so that code can be shared. */
58
59for (i = 0; i < 4; i++)
60 {
61 int j;
62 uschar *dir, *mdir;
63
64 if (i == 0)
65 {
66 mdir = US"";
67 dir = path;
68 }
69 else
70 {
71 mdir = US subdirs[i-1];
72 dir = mdir + 1;
73 }
74
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,
78 though. */
79
80 for (j = 0; j < 10; j++)
81 {
82 if (Ustat(dir, &statbuf) == 0)
83 {
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,
86 mdir);
87 addr->basic_errno = ERRNO_NOTDIRECTORY;
88 return FALSE;
89 }
90
91 /* Try to make if non-existent and configured to do so */
92
93 if (errno == ENOENT && create_directory)
94 {
95 if (!directory_make(NULL, dir, dirmode, FALSE))
96 {
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;
100 return FALSE;
101 }
102 DEBUG(D_transport)
103 debug_printf("created directory %s%s\n", path, mdir);
104 break; /* out of the race loop */
105 }
106
107 /* stat() error other than ENOENT, or ENOENT and not creatable */
108
109 addr->message = string_sprintf("stat() error for %s%s: %s", path, mdir,
110 strerror(errno));
111 addr->basic_errno = errno;
112 return FALSE;
113 }
114
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
117 transporter. */
118
119 if (j >= 10)
120 {
121 addr->message = string_sprintf("existence of %s%s unclear\n", path,
122 mdir);
123 addr->basic_errno = errno;
124 addr->special_action = SPECIAL_FREEZE;
125 return FALSE;
126 }
127
128 /* First time through the directories loop, cd to the main directory */
129
130 if (i == 0 && Uchdir(path) != 0)
131 {
132 addr->message = string_sprintf ("cannot chdir to %s", path);
133 addr->basic_errno = errno;
134 return FALSE;
135 }
136 }
137
138return TRUE; /* All directories exist */
139}
140
141
142
143
144/*************************************************
145* Update maildirsizefile for new file *
146*************************************************/
147
148/* This function is called to add a new line to the file, recording the length
149of the newly added message. There isn't much we can do on failure...
150
151Arguments:
152 fd the open file descriptor
153 size the size of the message
154
155Returns: nothing
156*/
157
158void
159maildir_record_length(int fd, int size)
160{
161int len;
162uschar buffer[256];
163sprintf(CS buffer, "%d 1\n", size);
164len = Ustrlen(buffer);
165(void)lseek(fd, 0, SEEK_END);
166(void)write(fd, buffer, len);
167DEBUG(D_transport)
168 debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
169}
170
171
172
173/*************************************************
174* Find the size of a maildir *
175*************************************************/
176
177/* This function is called when we have to recalculate the size of a maildir by
178scanning all the files and directories therein. There are rules and conventions
179about which files or directories are included. We support this by the use of a
180regex to match directories that are to be included.
181
182Maildirs can only be one level deep. However, this function recurses, so it
183might cope with deeper nestings. We use the existing check_dir_size() function
184to add up the sizes of the files in a directory that contains messages.
185
186The function returns the most recent timestamp encountered. It can also be run
187in a dummy mode in which it does not scan for sizes, but just returns the
188timestamp.
189
190Arguments:
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
197
198Returns: the sum of the sizes of the messages
199*/
200
0d7eb84a 201off_t
0756eb3c
PH
202maildir_compute_size(uschar *path, int *filecount, time_t *latest,
203 const pcre *regex, const pcre *dir_regex, BOOL timestamp_only)
204{
205DIR *dir;
0d7eb84a 206off_t sum = 0;
0756eb3c
PH
207struct dirent *ent;
208struct stat statbuf;
209
210dir = opendir(CS path);
211if (dir == NULL) return 0;
212
213while ((ent = readdir(dir)) != NULL)
214 {
215 uschar *name = US ent->d_name;
216 uschar buffer[1024];
217
218 if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
219
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. */
223
224 if (dir_regex != NULL &&
225 pcre_exec(dir_regex, NULL, CS name, Ustrlen(name), 0, 0, NULL, 0) < 0)
226 {
227 DEBUG(D_transport)
228 debug_printf("skipping %s/%s: dir_regex does not match\n", path, name);
229 continue;
230 }
231
232 /* The name is OK; stat it. */
233
234 if (!string_format(buffer, sizeof(buffer), "%s/%s", path, name))
235 {
236 DEBUG(D_transport)
237 debug_printf("maildir_compute_size: name too long: dir=%s name=%s\n",
238 path, name);
239 continue;
240 }
241
242 if (Ustat(buffer, &statbuf) < 0)
243 {
244 DEBUG(D_transport)
245 debug_printf("maildir_compute_size: stat error %d for %s: %s\n", errno,
246 buffer, strerror(errno));
247 continue;
248 }
249
250 if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
251 {
252 DEBUG(D_transport)
253 debug_printf("skipping %s/%s: not a directory\n", path, name);
254 continue;
255 }
256
257 /* Keep the latest timestamp encountered */
258
259 if (statbuf.st_mtime > *latest) *latest = statbuf.st_mtime;
260
261 /* If this is a maildir folder, call this function recursively. */
262
263 if (name[0] == '.')
264 {
265 sum += maildir_compute_size(buffer, filecount, latest, regex, dir_regex,
266 timestamp_only);
267 }
268
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. */
271
272 else if (!timestamp_only)
273 {
274 sum += check_dir_size(buffer, filecount, regex);
275 }
276 }
277
278closedir(dir);
279DEBUG(D_transport)
280 {
281 if (timestamp_only)
282 debug_printf("maildir_compute_size (timestamp_only): %ld\n",
283 (long int) *latest);
284 else
b1c749bb
PH
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);
0756eb3c
PH
288 }
289return sum;
290}
291
292
293
294/*************************************************
295* Create or update maildirsizefile *
296*************************************************/
297
298/* This function is called before a delivery if the option to use
299maildirsizefile is enabled. Its function is to create the file if it does not
300exist, or to update it if that is necessary.
301
302The logic in this function follows the rules that are described in
303
304 http://www.inter7.com/courierimap/README.maildirquota.html
305
306Or, at least, it is supposed to!
307
308Arguments:
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
316
317Returns: >=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
320*/
321
322int
323maildir_ensure_sizefile(uschar *path, appendfile_transport_options_block *ob,
0d7eb84a 324 const pcre *regex, const pcre *dir_regex, off_t *returned_size,
0756eb3c
PH
325 int *returned_filecount)
326{
327int count, fd;
0d7eb84a 328off_t cached_quota = 0;
0756eb3c 329int cached_quota_filecount = 0;
0756eb3c
PH
330int filecount = 0;
331int linecount = 0;
0d7eb84a 332off_t size = 0;
0756eb3c
PH
333uschar *filename;
334uschar buffer[MAX_FILE_SIZE];
335uschar *ptr = buffer;
336uschar *endptr;
337
338/* Try a few times to open or create the file, in case another process is doing
339the same thing. */
340
341filename = string_sprintf("%s/maildirsize", path);
342
343DEBUG(D_transport) debug_printf("looking for maildirsize in %s\n", path);
344fd = Uopen(filename, O_RDWR|O_APPEND, 0);
345if (fd < 0)
346 {
347 if (errno != ENOENT) return -1;
348 DEBUG(D_transport)
349 debug_printf("%s does not exist: recalculating\n", filename);
350 goto RECALCULATE;
351 }
352
353/* The file has been successfully opened. Check that the cached quota value is
354still correct, and that the size of the file is still small enough. If so,
355compute the maildir size from the file. */
356
357count = read(fd, buffer, sizeof(buffer));
358if (count >= sizeof(buffer))
359 {
360 DEBUG(D_transport)
361 debug_printf("maildirsize file too big (%d): recalculating\n", count);
362 goto RECALCULATE;
363 }
364buffer[count] = 0; /* Ensure string terminated */
365
366/* Read the quota parameters from the first line of the data. */
367
368DEBUG(D_transport)
369 debug_printf("reading quota parameters from maildirsize data\n");
370
371for (;;)
372 {
0d7eb84a 373 off_t n = (off_t)Ustrtod(ptr, &endptr);
0756eb3c
PH
374
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. */
378
379 if (*endptr == 'S') cached_quota = n;
0d7eb84a 380 else if (*endptr == 'C') cached_quota_filecount = (int)n;
0756eb3c
PH
381 if (!isalpha(*endptr++))
382 {
383 DEBUG(D_transport)
384 debug_printf("quota parameter number not followed by letter in "
385 "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
386 buffer);
387 goto RECALCULATE;
388 }
389 if (*endptr == '\n' || *endptr == 0) break;
390 if (*endptr++ != ',')
391 {
392 DEBUG(D_transport)
393 debug_printf("quota parameter not followed by comma in "
394 "\"%.*s\": recalculating maildirsize\n", (int)(endptr - buffer),
395 buffer);
396 goto RECALCULATE;
397 }
398 ptr = endptr;
399 }
400
401/* Check the cached values against the current settings */
402
403if (cached_quota != ob->quota_value ||
404 cached_quota_filecount != ob->quota_filecount_value)
405 {
406 DEBUG(D_transport)
407 debug_printf("cached quota is out of date: recalculating\n"
b1c749bb
PH
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);
0756eb3c
PH
411 goto RECALCULATE;
412 }
413
414/* Quota values agree; parse the rest of the data to get the sizes. At this
415stage, *endptr points either to 0 or to '\n'. */
416
417DEBUG(D_transport)
418 debug_printf("computing maildir size from maildirsize data\n");
419
420while (*endptr++ == '\n')
421 {
422 if (*endptr == 0) break;
423 linecount++;
424 ptr = endptr;
0d7eb84a 425 size += (off_t)Ustrtod(ptr, &endptr);
0756eb3c
PH
426 if (*endptr != ' ') break;
427 ptr = endptr + 1;
428 filecount += Ustrtol(ptr, &endptr, 10);
429 }
430
431/* If *endptr is zero, we have successfully parsed the file, and we now have
432the size of the mailbox as cached in the file. The "rules" say that if this
433value indicates that the mailbox is over quota, we must recalculate if there is
62c0818f
PH
434more than one entry in the file, or if the file is older than 15 minutes. Also,
435just in case there are weird values in the file, recalculate if either of the
436values is negative. */
0756eb3c
PH
437
438if (*endptr == 0)
439 {
8e669ac1 440 if (size < 0 || filecount < 0)
62c0818f
PH
441 {
442 DEBUG(D_transport) debug_printf("negative value in maildirsize "
b1c749bb 443 "(size=" OFF_T_FMT " count=%d): recalculating\n", size, filecount);
8e669ac1
PH
444 goto RECALCULATE;
445 }
446
0756eb3c
PH
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)
452 ))
453 {
454 struct stat statbuf;
455 if (linecount > 1)
456 {
62c0818f 457 DEBUG(D_transport) debug_printf("over quota and maildirsize has "
0756eb3c
PH
458 "more than 1 entry: recalculating\n");
459 goto RECALCULATE;
460 }
461
462 if (fstat(fd, &statbuf) < 0) goto RECALCULATE; /* Should never occur */
463
464 if (time(NULL) - statbuf.st_mtime > 15*60)
465 {
466 DEBUG(D_transport) debug_printf("over quota and maildirsize is older "
467 "than 15 minutes: recalculating\n");
468 goto RECALCULATE;
469 }
470 }
471 }
472
473
474/* If *endptr is not zero, there was a syntax error in the file. */
475
476else
477 {
478 int len;
479 time_t old_latest, new_latest;
480 uschar *tempname;
481 struct timeval tv;
482
483 DEBUG(D_transport)
484 {
485 uschar *p = endptr;
486 while (p > buffer && p[-1] != '\n') p--;
487 endptr[1] = 0;
488
489 debug_printf("error in maildirsizefile: unexpected character %d in "
490 "line %d (starting '%s'): recalculating\n",
491 *endptr, linecount + 1, string_printing(p));
492 }
493
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. */
498
499 RECALCULATE:
500
f1e894f3 501 if (fd >= 0) (void)close(fd);
0756eb3c
PH
502 old_latest = 0;
503 filecount = 0;
504 size = maildir_compute_size(path, &filecount, &old_latest, regex, dir_regex,
505 FALSE);
506
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);
510
511 fd = Uopen(tempname, O_RDWR|O_CREAT|O_EXCL, 0600);
512 if (fd >= 0)
513 {
b1c749bb
PH
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);
0756eb3c
PH
516 len = Ustrlen(buffer);
517 if (write(fd, buffer, len) != len || Urename(tempname, filename) < 0)
518 {
f1e894f3 519 (void)close(fd);
0756eb3c
PH
520 fd = -1;
521 }
522 }
523
524 /* If any of the directories have been modified since the last timestamp we
525 saw, we have to junk this maildirsize file. */
526
527 DEBUG(D_transport) debug_printf("checking subdirectory timestamps\n");
528 new_latest = 0;
529 (void)maildir_compute_size(path, NULL, &new_latest , NULL, dir_regex, TRUE);
530 if (new_latest > old_latest)
531 {
532 DEBUG(D_transport) debug_printf("abandoning maildirsize because of "
533 "a later subdirectory modification\n");
534 (void)Uunlink(filename);
f1e894f3 535 (void)close(fd);
0756eb3c
PH
536 fd = -1;
537 }
538 }
539
540/* Return the sizes and the file descriptor, if any */
541
b1c749bb
PH
542DEBUG(D_transport) debug_printf("returning maildir size=" OFF_T_FMT
543 " filecount=%d\n", size, filecount);
0756eb3c
PH
544*returned_size = size;
545*returned_filecount = filecount;
546return fd;
547}
548
549/* End of tf_maildir.c */