README: document *next* branch etc.
[s-mailx.git] / cache.c
blob4288cfecc731ec7a0604f3f8fac123e57a9c2a9f
1 /*
2 * S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
6 */
7 /*
8 * Copyright (c) 2004
9 * Gunnar Ritter. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by Gunnar Ritter
22 * and his contributors.
23 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #include "config.h"
42 #ifndef USE_IMAP
43 typedef int avoid_empty_file_compiler_warning;
44 #else
46 #include "rcv.h"
47 #include "extern.h"
48 #include <errno.h>
49 #include <sys/stat.h>
50 #include <unistd.h>
51 #include <time.h>
52 #include <dirent.h>
53 #include <fcntl.h>
55 #include <termios.h>
58 * Mail -- a mail program
60 * A cache for IMAP.
63 static char *encname(struct mailbox *mp, const char *name, int same,
64 const char *box);
65 static char *encuid(struct mailbox *mp, unsigned long uid);
66 static FILE *clean(struct mailbox *mp, struct cw *cw);
67 static unsigned long *builds(long *contentelem);
68 static void purge(struct mailbox *mp, struct message *m, long mc,
69 struct cw *cw, const char *name);
70 static int longlt(const void *a, const void *b);
71 static void remve(unsigned long n);
72 static FILE *cache_queue1(struct mailbox *mp, char *mode, char **xname);
73 static enum okay dequeue1(struct mailbox *mp);
75 static const char infofmt[] = "%c %lu %d %lu %ld";
76 #define INITSKIP 128L
77 #define USEBITS(f) \
78 ((f) & (MSAVED|MDELETED|MREAD|MBOXED|MNEW|MFLAGGED|MANSWERED|MDRAFTED))
80 static const char README1[] = "\
81 This is a cache directory maintained by mailx(1). You should not change any\n\
82 files within. Nevertheless, the structure is as follows: Each subdirectory\n\
83 of the current directory represents an IMAP account, and each subdirectory\n\
84 below that represents a mailbox. Each mailbox directory contains a file\n\
85 named UIDVALIDITY which describes the validity in relation to the version\n\
86 on the server. Other files have names corresponding to their IMAP UID.\n";
87 static const char README2[] = "\n\
88 The first 128 bytes of these files are used to store message attributes; the\n\
89 following data is equivalent to compress(1) output. So if you have to save a\n\
90 message by hand because of an emergency, throw away the first 128 bytes and\n\
91 decompress the rest, as e.g. 'dd if=MESSAGEFILE skip=1 bs=128 | zcat' does.\n";
92 static const char README3[] = "\n\
93 Files named QUEUE contain data that will be sent do the IMAP server next\n\
94 time a connection is made in online mode.\n";
95 static const char README4[] = "\n\
96 You can safely delete any file or directory here, unless it contains a QUEUE\n\
97 file that is not empty; mailx(1) will download the data again and will also\n\
98 write new cache entries if configured in this way. If you do not wish to use\n\
99 the cache anymore, delete the entire directory and unset the 'imap-cache'\n\
100 variable in mailx(1).\n";
101 static const char README5[] = "\n\
102 For more information about mailx(1), visit\n\
103 <http://heirloom.sourceforge.net/mailx.html>.\n";
105 static char *
106 encname(struct mailbox *mp, const char *name, int same, const char *box)
108 char *cachedir, *eaccount, *emailbox, *ename, *res;
109 int resz;
111 ename = strenc(name);
112 if (mp->mb_cache_directory && same && box == NULL) {
113 res = salloc(resz = strlen(mp->mb_cache_directory) +
114 strlen(ename) + 2);
115 snprintf(res, resz, "%s%s%s", mp->mb_cache_directory,
116 *ename ? "/" : "", ename);
117 } else {
118 if ((cachedir = value("imap-cache")) == NULL)
119 return NULL;
120 cachedir = file_expand(cachedir);
121 eaccount = strenc(mp->mb_imap_account);
122 if (box)
123 emailbox = strenc(box);
124 else if (asccasecmp(mp->mb_imap_mailbox, "INBOX"))
125 emailbox = strenc(mp->mb_imap_mailbox);
126 else
127 emailbox = "INBOX";
128 res = salloc(resz = strlen(cachedir) + strlen(eaccount) +
129 strlen(emailbox) + strlen(ename) + 4);
130 snprintf(res, resz, "%s/%s/%s%s%s",
131 cachedir, eaccount, emailbox,
132 *ename ? "/" : "", ename);
134 return res;
137 static char *
138 encuid(struct mailbox *mp, unsigned long uid)
140 char buf[30];
142 snprintf(buf, sizeof buf, "%lu", uid);
143 return encname(mp, buf, 1, NULL);
146 enum okay
147 getcache1(struct mailbox *mp, struct message *m, enum needspec need,
148 int setflags)
150 FILE *fp;
151 long n = 0, size = 0, xsize, xtime, xlines = -1, lines = 0;
152 int lastc = EOF, i, xflag, inheader = 1;
153 char b, iob[32768];
154 off_t offset;
155 void *zp;
157 if (setflags == 0 && ((mp->mb_type != MB_IMAP &&
158 mp->mb_type != MB_CACHE) ||
159 m->m_uid == 0))
160 return STOP;
161 if ((fp = Fopen(encuid(mp, m->m_uid), "r")) == NULL)
162 return STOP;
163 fcntl_lock(fileno(fp), F_RDLCK);
164 if (fscanf(fp, infofmt, &b, (unsigned long*)&xsize, &xflag,
165 (unsigned long*)&xtime, &xlines) < 4)
166 goto fail;
167 if (need != NEED_UNSPEC) {
168 switch (b) {
169 case 'H':
170 if (need == NEED_HEADER)
171 goto success;
172 goto fail;
173 case 'B':
174 if (need == NEED_HEADER || need == NEED_BODY)
175 goto success;
176 goto fail;
177 default:
178 goto fail;
181 success:
182 if (b == 'N')
183 goto flags;
184 fseek(fp, INITSKIP, SEEK_SET);
185 zp = zalloc(fp);
186 fseek(mp->mb_otf, 0L, SEEK_END);
187 offset = ftell(mp->mb_otf);
188 while (inheader && (n = zread(zp, iob, sizeof iob)) > 0) {
189 size += n;
190 for (i = 0; i < n; i++) {
191 if (iob[i] == '\n') {
192 lines++;
193 if (lastc == '\n')
194 inheader = 0;
196 lastc = iob[i]&0377;
198 fwrite(iob, 1, n, mp->mb_otf);
200 if (n > 0 && need == NEED_BODY) {
201 while ((n = zread(zp, iob, sizeof iob)) > 0) {
202 size += n;
203 for (i = 0; i < n; i++)
204 if (iob[i] == '\n')
205 lines++;
206 fwrite(iob, 1, n, mp->mb_otf);
209 fflush(mp->mb_otf);
210 if (zfree(zp) < 0 || n < 0 || ferror(fp) || ferror(mp->mb_otf))
211 goto fail;
212 m->m_size = size;
213 m->m_lines = lines;
214 m->m_block = mailx_blockof(offset);
215 m->m_offset = mailx_offsetof(offset);
216 flags: if (setflags) {
217 m->m_xsize = xsize;
218 m->m_time = xtime;
219 if (setflags & 2) {
220 m->m_flag = xflag | MNOFROM;
221 if (b != 'B')
222 m->m_flag |= MHIDDEN;
225 if (xlines > 0 && m->m_xlines <= 0)
226 m->m_xlines = xlines;
227 switch (b) {
228 case 'B':
229 m->m_xsize = xsize;
230 if (xflag == MREAD && xlines > 0)
231 m->m_flag |= MFULLYCACHED;
232 if (need == NEED_BODY) {
233 m->m_have |= HAVE_HEADER|HAVE_BODY;
234 if (m->m_lines > 0)
235 m->m_xlines = m->m_lines;
236 break;
238 /*FALLTHRU*/
239 case 'H':
240 m->m_have |= HAVE_HEADER;
241 break;
242 case 'N':
243 break;
245 Fclose(fp);
246 return OKAY;
247 fail:
248 Fclose(fp);
249 return STOP;
252 enum okay
253 getcache(struct mailbox *mp, struct message *m, enum needspec need)
255 return getcache1(mp, m, need, 0);
258 void
259 putcache(struct mailbox *mp, struct message *m)
261 FILE *ibuf, *obuf;
262 char *name, ob;
263 int c, oflag;
264 long n, count, oldoffset, osize, otime, olines = -1;
265 char iob[32768];
266 void *zp;
268 if ((mp->mb_type != MB_IMAP && mp->mb_type != MB_CACHE) ||
269 m->m_uid == 0 || m->m_time == 0 ||
270 (m->m_flag & (MTOUCH|MFULLYCACHED)) == MFULLYCACHED)
271 return;
272 if (m->m_have & HAVE_BODY)
273 c = 'B';
274 else if (m->m_have & HAVE_HEADER)
275 c = 'H';
276 else if (m->m_have == HAVE_NOTHING)
277 c = 'N';
278 else
279 return;
280 oldoffset = ftell(mp->mb_itf);
281 if ((obuf = Fopen(name = encuid(mp, m->m_uid), "r+")) == NULL) {
282 if ((obuf = Fopen(name, "w")) == NULL)
283 return;
284 fcntl_lock(fileno(obuf), F_WRLCK);
285 } else {
286 fcntl_lock(fileno(obuf), F_WRLCK);
287 if (fscanf(obuf, infofmt, &ob, (unsigned long*)&osize, &oflag,
288 (unsigned long*)&otime, &olines) >= 4 &&
289 ob != '\0' && (ob == 'B' ||
290 (ob == 'H' && c != 'B'))) {
291 if (m->m_xlines <= 0 && olines > 0)
292 m->m_xlines = olines;
293 if ((c != 'N' && (size_t)osize != m->m_xsize) ||
294 oflag != (int)USEBITS(m->m_flag) ||
295 otime != m->m_time ||
296 (m->m_xlines > 0 &&
297 olines != m->m_xlines)) {
298 fflush(obuf);
299 rewind(obuf);
300 fprintf(obuf, infofmt, ob,
301 (unsigned long)m->m_xsize,
302 USEBITS(m->m_flag),
303 (unsigned long)m->m_time,
304 m->m_xlines);
305 putc('\n', obuf);
307 Fclose(obuf);
308 return;
310 fflush(obuf);
311 rewind(obuf);
312 ftruncate(fileno(obuf), 0);
314 if ((ibuf = setinput(mp, m, NEED_UNSPEC)) == NULL) {
315 Fclose(obuf);
316 return;
318 if (c == 'N')
319 goto done;
320 fseek(obuf, INITSKIP, SEEK_SET);
321 zp = zalloc(obuf);
322 count = m->m_size;
323 while (count > 0) {
324 n = count > (long)sizeof iob ? (long)sizeof iob : count;
325 count -= n;
326 if ((size_t)n != fread(iob, 1, n, ibuf) ||
327 n != (long)zwrite(zp, iob, n)) {
328 unlink(name);
329 zfree(zp);
330 goto out;
333 if (zfree(zp) < 0) {
334 unlink(name);
335 goto out;
337 done: rewind(obuf);
338 fprintf(obuf, infofmt, c, (unsigned long)m->m_xsize,
339 USEBITS(m->m_flag),
340 (unsigned long)m->m_time,
341 m->m_xlines);
342 putc('\n', obuf);
343 if (ferror(obuf)) {
344 unlink(name);
345 goto out;
347 if (c == 'B' && USEBITS(m->m_flag) == MREAD)
348 m->m_flag |= MFULLYCACHED;
349 out: if (Fclose(obuf) != 0) {
350 m->m_flag &= ~MFULLYCACHED;
351 unlink(name);
353 fseek(mp->mb_itf, oldoffset, SEEK_SET);
356 void
357 initcache(struct mailbox *mp)
359 char *name, *uvname;
360 FILE *uvfp;
361 unsigned long uv;
362 struct cw cw;
364 free(mp->mb_cache_directory);
365 mp->mb_cache_directory = NULL;
366 if ((name = encname(mp, "", 1, NULL)) == NULL)
367 return;
368 mp->mb_cache_directory = sstrdup(name);
369 if ((uvname = encname(mp, "UIDVALIDITY", 1, NULL)) == NULL)
370 return;
371 if (cwget(&cw) == STOP)
372 return;
373 if ((uvfp = Fopen(uvname, "r+")) == NULL ||
374 (fcntl_lock(fileno(uvfp), F_RDLCK), 0) ||
375 fscanf(uvfp, "%lu", &uv) != 1 ||
376 uv != mp->mb_uidvalidity) {
377 if ((uvfp = clean(mp, &cw)) == NULL)
378 goto out;
379 } else {
380 fflush(uvfp);
381 rewind(uvfp);
383 fcntl_lock(fileno(uvfp), F_WRLCK);
384 fprintf(uvfp, "%lu\n", mp->mb_uidvalidity);
385 if (ferror(uvfp) || Fclose(uvfp) != 0) {
386 unlink(uvname);
387 mp->mb_uidvalidity = 0;
389 out: cwrelse(&cw);
392 void
393 purgecache(struct mailbox *mp, struct message *m, long mc)
395 char *name;
396 struct cw cw;
398 if ((name = encname(mp, "", 1, NULL)) == NULL)
399 return;
400 if (cwget(&cw) == STOP)
401 return;
402 purge(mp, m, mc, &cw, name);
403 cwrelse(&cw);
406 static FILE *
407 clean(struct mailbox *mp, struct cw *cw)
409 char *cachedir, *eaccount, *emailbox, *buf;
410 int bufsz;
411 DIR *dirfd;
412 struct dirent *dp;
413 FILE *fp = NULL;
415 if ((cachedir = value("imap-cache")) == NULL)
416 return NULL;
417 cachedir = file_expand(cachedir);
418 eaccount = strenc(mp->mb_imap_account);
419 if (asccasecmp(mp->mb_imap_mailbox, "INBOX"))
420 emailbox = strenc(mp->mb_imap_mailbox);
421 else
422 emailbox = "INBOX";
423 buf = salloc(bufsz = strlen(cachedir) + strlen(eaccount) +
424 strlen(emailbox) + 40);
425 if (makedir(cachedir) != OKAY)
426 return NULL;
427 snprintf(buf, bufsz, "%s/README", cachedir);
428 if ((fp = Fopen(buf, "wx")) != NULL) {
429 fputs(README1, fp);
430 fputs(README2, fp);
431 fputs(README3, fp);
432 fputs(README4, fp);
433 fputs(README5, fp);
434 Fclose(fp);
436 snprintf(buf, bufsz, "%s/%s", cachedir, eaccount);
437 if (makedir(buf) != OKAY)
438 return NULL;
439 snprintf(buf, bufsz, "%s/%s/%s", cachedir, eaccount, emailbox);
440 if (makedir(buf) != OKAY)
441 return NULL;
442 if (chdir(buf) < 0)
443 return NULL;
444 if ((dirfd = opendir(".")) == NULL)
445 goto out;
446 while ((dp = readdir(dirfd)) != NULL) {
447 if (dp->d_name[0] == '.' &&
448 (dp->d_name[1] == '\0' ||
449 (dp->d_name[1] == '.' &&
450 dp->d_name[2] == '\0')))
451 continue;
452 unlink(dp->d_name);
454 closedir(dirfd);
455 fp = Fopen("UIDVALIDITY", "w");
456 out: if (cwret(cw) == STOP) {
457 fputs("Fatal: Cannot change back to current directory.\n",
458 stderr);
459 abort();
461 return fp;
464 static unsigned long *
465 builds(long *contentelem)
467 unsigned long n, *contents = NULL;
468 long contentalloc = 0;
469 char *x;
470 DIR *dirfd;
471 struct dirent *dp;
473 *contentelem = 0;
474 if ((dirfd = opendir(".")) == NULL)
475 return NULL;
476 while ((dp = readdir(dirfd)) != NULL) {
477 if (dp->d_name[0] == '.' &&
478 (dp->d_name[1] == '\0' ||
479 (dp->d_name[1] == '.' &&
480 dp->d_name[2] == '\0')))
481 continue;
482 n = strtoul(dp->d_name, &x, 10);
483 if (*x != '\0')
484 continue;
485 if (*contentelem >= contentalloc - 1)
486 contents = srealloc(contents,
487 (contentalloc += 200) * sizeof *contents);
488 contents[(*contentelem)++] = n;
490 closedir(dirfd);
491 if (*contentelem > 0) {
492 contents[*contentelem] = 0;
493 qsort(contents, *contentelem, sizeof *contents, longlt);
495 return contents;
498 static void
499 purge(struct mailbox *mp, struct message *m, long mc, struct cw *cw,
500 const char *name)
502 unsigned long *contents;
503 long i, j, contentelem;
504 (void)mp;
506 if (chdir(name) < 0)
507 return;
508 contents = builds(&contentelem);
509 if (contents) {
510 i = j = 0;
511 while (j < contentelem) {
512 if (i < mc && m[i].m_uid == contents[j]) {
513 i++;
514 j++;
515 } else if (i < mc && m[i].m_uid < contents[j])
516 i++;
517 else
518 remve(contents[j++]);
521 if (cwret(cw) == STOP) {
522 fputs("Fatal: Cannot change back to current directory.\n",
523 stderr);
524 abort();
526 free(contents);
529 static int
530 longlt(const void *a, const void *b)
532 return *(long *)a - *(long *)b;
535 static void
536 remve(unsigned long n)
538 char buf[30];
540 snprintf(buf, sizeof buf, "%lu", n);
541 unlink(buf);
544 void
545 delcache(struct mailbox *mp, struct message *m)
547 char *fn;
549 fn = encuid(mp, m->m_uid);
550 if (fn && unlink(fn) == 0)
551 m->m_flag |= MUNLINKED;
554 enum okay
555 cache_setptr(int transparent)
557 int i;
558 struct cw cw;
559 char *name;
560 unsigned long *contents;
561 long contentelem;
562 enum okay ok = STOP;
563 struct message *omessage = NULL;
564 int omsgCount = 0;
566 if (transparent) {
567 omessage = message;
568 omsgCount = msgCount;
570 free(mb.mb_cache_directory);
571 mb.mb_cache_directory = NULL;
572 if ((name = encname(&mb, "", 1, NULL)) == NULL)
573 return STOP;
574 mb.mb_cache_directory = sstrdup(name);
575 if (cwget(&cw) == STOP)
576 return STOP;
577 if (chdir(name) < 0)
578 return STOP;
579 contents = builds(&contentelem);
580 msgCount = contentelem;
581 message = scalloc(msgCount + 1, sizeof *message);
582 if (cwret(&cw) == STOP) {
583 fputs("Fatal: Cannot change back to current directory.\n",
584 stderr);
585 abort();
587 cwrelse(&cw);
588 for (i = 0; i < msgCount; i++) {
589 message[i].m_uid = contents[i];
590 getcache1(&mb, &message[i], NEED_UNSPEC, 3);
592 ok = OKAY;
593 if (ok == OKAY) {
594 mb.mb_type = MB_CACHE;
595 mb.mb_perm = Rflag ? 0 : MB_DELE;
596 if (transparent)
597 transflags(omessage, omsgCount, 1);
598 else
599 setdot(message);
601 return ok;
604 enum okay
605 cache_list(struct mailbox *mp, const char *base, int strip, FILE *fp)
607 char *name, *cachedir, *eaccount;
608 DIR *dirfd;
609 struct dirent *dp;
610 const char *cp, *bp, *sp;
611 int namesz;
613 if ((cachedir = value("imap-cache")) == NULL)
614 return STOP;
615 cachedir = file_expand(cachedir);
616 eaccount = strenc(mp->mb_imap_account);
617 name = salloc(namesz = strlen(cachedir) + strlen(eaccount) + 2);
618 snprintf(name, namesz, "%s/%s", cachedir, eaccount);
619 if ((dirfd = opendir(name)) == NULL)
620 return STOP;
621 while ((dp = readdir(dirfd)) != NULL) {
622 if (dp->d_name[0] == '.')
623 continue;
624 cp = sp = strdec(dp->d_name);
625 for (bp = base; *bp && *bp == *sp; bp++)
626 sp++;
627 if (*bp)
628 continue;
629 cp = strip ? sp : cp;
630 fprintf(fp, "%s\n", *cp ? cp : "INBOX");
632 closedir(dirfd);
633 return OKAY;
636 enum okay
637 cache_remove(const char *name)
639 struct stat st;
640 DIR *dirfd;
641 struct dirent *dp;
642 char *path;
643 int pathsize, pathend, n;
644 char *dir;
646 if ((dir = encname(&mb, "", 0, protfile(name))) == NULL)
647 return OKAY;
648 pathend = strlen(dir);
649 path = smalloc(pathsize = pathend + 30);
650 strcpy(path, dir);
651 path[pathend++] = '/';
652 path[pathend] = '\0';
653 if ((dirfd = opendir(path)) == NULL) {
654 free(path);
655 return OKAY;
657 while ((dp = readdir(dirfd)) != NULL) {
658 if (dp->d_name[0] == '.' &&
659 (dp->d_name[1] == '\0' ||
660 (dp->d_name[1] == '.' &&
661 dp->d_name[2] == '\0')))
662 continue;
663 n = strlen(dp->d_name);
664 if (pathend + n + 1 > pathsize)
665 path = srealloc(path, pathsize = pathend + n + 30);
666 strcpy(&path[pathend], dp->d_name);
667 if (stat(path, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)
668 continue;
669 if (unlink(path) < 0) {
670 perror(path);
671 closedir(dirfd);
672 free(path);
673 return STOP;
676 closedir(dirfd);
677 path[pathend] = '\0';
678 rmdir(path); /* no error on failure, might contain submailboxes */
679 free(path);
680 return OKAY;
683 enum okay
684 cache_rename(const char *old, const char *new)
686 char *olddir, *newdir;
688 if ((olddir = encname(&mb, "", 0, protfile(old))) == NULL ||
689 (newdir = encname(&mb, "", 0, protfile(new))) == NULL)
690 return OKAY;
691 if (rename(olddir, newdir) < 0) {
692 perror(olddir);
693 return STOP;
695 return OKAY;
698 unsigned long
699 cached_uidvalidity(struct mailbox *mp)
701 FILE *uvfp;
702 char *uvname;
703 unsigned long uv;
705 if ((uvname = encname(mp, "UIDVALIDITY", 1, NULL)) == NULL)
706 return 0;
707 if ((uvfp = Fopen(uvname, "r")) == NULL ||
708 (fcntl_lock(fileno(uvfp), F_RDLCK), 0) ||
709 fscanf(uvfp, "%lu", &uv) != 1)
710 uv = 0;
711 Fclose(uvfp);
712 return uv;
715 static FILE *
716 cache_queue1(struct mailbox *mp, char *mode, char **xname)
718 char *name;
719 FILE *fp;
721 if ((name = encname(mp, "QUEUE", 0, NULL)) == NULL)
722 return NULL;
723 if ((fp = Fopen(name, mode)) != NULL)
724 fcntl_lock(fileno(fp), F_WRLCK);
725 if (xname)
726 *xname = name;
727 return fp;
730 FILE *
731 cache_queue(struct mailbox *mp)
733 FILE *fp;
735 fp = cache_queue1(mp, "a", NULL);
736 if (fp == NULL)
737 fputs("Cannot queue IMAP command. Retry when online.\n",
738 stderr);
739 return fp;
742 enum okay
743 cache_dequeue(struct mailbox *mp)
745 int bufsz;
746 char *cachedir, *eaccount, *buf, *oldbox;
747 DIR *dirfd;
748 struct dirent *dp;
750 if ((cachedir = value("imap-cache")) == NULL)
751 return OKAY;
752 cachedir = file_expand(cachedir);
753 eaccount = strenc(mp->mb_imap_account);
754 buf = salloc(bufsz = strlen(cachedir) + strlen(eaccount) + 2);
755 snprintf(buf, bufsz, "%s/%s", cachedir, eaccount);
756 if ((dirfd = opendir(buf)) == NULL)
757 return OKAY;
758 oldbox = mp->mb_imap_mailbox;
759 while ((dp = readdir(dirfd)) != NULL) {
760 if (dp->d_name[0] == '.')
761 continue;
762 mp->mb_imap_mailbox = strdec(dp->d_name);
763 dequeue1(mp);
765 closedir(dirfd);
766 mp->mb_imap_mailbox = oldbox;
767 return OKAY;
770 static enum okay
771 dequeue1(struct mailbox *mp)
773 FILE *fp = NULL, *uvfp = NULL;
774 char *qname, *uvname;
775 unsigned long uv;
776 off_t is_size;
777 int is_count;
779 fp = cache_queue1(mp, "r+", &qname);
780 if (fp != NULL && fsize(fp) > 0) {
781 if (imap_select(mp, &is_size, &is_count,
782 mp->mb_imap_mailbox) != OKAY) {
783 fprintf(stderr, "Cannot select \"%s\" for dequeuing.\n",
784 mp->mb_imap_mailbox);
785 goto save;
787 if ((uvname = encname(mp, "UIDVALIDITY", 0, NULL)) == NULL ||
788 (uvfp = Fopen(uvname, "r")) == NULL ||
789 (fcntl_lock(fileno(uvfp), F_RDLCK), 0) ||
790 fscanf(uvfp, "%lu", &uv) != 1 ||
791 uv != mp->mb_uidvalidity) {
792 fprintf(stderr,
793 "Unique identifiers for \"%s\" are out of date. "
794 "Cannot commit IMAP commands.\n",
795 mp->mb_imap_mailbox);
796 save: fputs("Saving IMAP commands to dead.letter\n", stderr);
797 savedeadletter(fp);
798 ftruncate(fileno(fp), 0);
799 Fclose(fp);
800 if (uvfp)
801 Fclose(uvfp);
802 return STOP;
804 Fclose(uvfp);
805 printf("Committing IMAP commands for \"%s\"\n",
806 mp->mb_imap_mailbox);
807 imap_dequeue(mp, fp);
809 if (fp) {
810 Fclose(fp);
811 unlink(qname);
813 return OKAY;
815 #endif /* USE_IMAP */