nail.h: use MAXPATHLEN as fallback for struct cw
[s-mailx.git] / imap_cache.c
blob998f67021f74c631e3beae79afc3d69fbb2612f6
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ A cache for IMAP.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
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 HAVE_IMAP
43 typedef int avoid_empty_file_compiler_warning;
44 #else
45 #include "nail.h"
47 #include <dirent.h>
48 #include <fcntl.h>
50 static char *encname(struct mailbox *mp, const char *name, int same,
51 const char *box);
52 static char *encuid(struct mailbox *mp, unsigned long uid);
53 static FILE *clean(struct mailbox *mp, struct cw *cw);
54 static unsigned long *builds(long *contentelem);
55 static void purge(struct mailbox *mp, struct message *m, long mc,
56 struct cw *cw, const char *name);
57 static int longlt(const void *a, const void *b);
58 static void remve(unsigned long n);
59 static FILE *cache_queue1(struct mailbox *mp, char const *mode, char **xname);
60 static enum okay dequeue1(struct mailbox *mp);
62 static const char infofmt[] = "%c %lu %d %lu %ld";
63 #define INITSKIP 128L
64 #define USEBITS(f) \
65 ((f) & (MSAVED|MDELETED|MREAD|MBOXED|MNEW|MFLAGGED|MANSWERED|MDRAFTED))
67 static const char README1[] = "\
68 This is a cache directory maintained by s-nail(1). You should not change any\n\
69 files within. Nevertheless, the structure is as follows: Each subdirectory\n\
70 of the current directory represents an IMAP account, and each subdirectory\n\
71 below that represents a mailbox. Each mailbox directory contains a file\n\
72 named UIDVALIDITY which describes the validity in relation to the version\n\
73 on the server. Other files have names corresponding to their IMAP UID.\n";
74 static const char README2[] = "\n\
75 The first 128 bytes of these files are used to store message attributes; the\n\
76 following data is equivalent to compress(1) output. So if you have to save a\n\
77 message by hand because of an emergency, throw away the first 128 bytes and\n\
78 decompress the rest, as e.g. 'dd if=MESSAGEFILE skip=1 bs=128 | zcat' does.\n";
79 static const char README3[] = "\n\
80 Files named QUEUE contain data that will be sent do the IMAP server next\n\
81 time a connection is made in online mode.\n";
82 static const char README4[] = "\n\
83 You can safely delete any file or directory here, unless it contains a QUEUE\n\
84 file that is not empty; mailx(1) will download the data again and will also\n\
85 write new cache entries if configured in this way. If you do not wish to use\n\
86 the cache anymore, delete the entire directory and unset the 'imap-cache'\n\
87 variable in mailx(1).\n";
88 static const char README5[] = "\n\
89 For more information about s-nail(1), visit\n\
90 <http://sdaoden.users.sourceforge.net/code.html>.\n";
92 static char *
93 encname(struct mailbox *mp, const char *name, int same, const char *box)
95 char *cachedir, *eaccount, *ename, *res;
96 char const *emailbox;
97 int resz;
99 ename = urlxenc(name);
100 if (mp->mb_cache_directory && same && box == NULL) {
101 res = salloc(resz = strlen(mp->mb_cache_directory) +
102 strlen(ename) + 2);
103 snprintf(res, resz, "%s%s%s", mp->mb_cache_directory,
104 *ename ? "/" : "", ename);
105 } else {
106 if ((cachedir = value("imap-cache")) == NULL ||
107 (cachedir = file_expand(cachedir)) == NULL)
108 return NULL;
109 eaccount = urlxenc(mp->mb_imap_account);
110 if (box)
111 emailbox = urlxenc(box);
112 else if (asccasecmp(mp->mb_imap_mailbox, "INBOX"))
113 emailbox = urlxenc(mp->mb_imap_mailbox);
114 else
115 emailbox = "INBOX";
116 res = salloc(resz = strlen(cachedir) + strlen(eaccount) +
117 strlen(emailbox) + strlen(ename) + 4);
118 snprintf(res, resz, "%s/%s/%s%s%s",
119 cachedir, eaccount, emailbox,
120 *ename ? "/" : "", ename);
122 return res;
125 static char *
126 encuid(struct mailbox *mp, unsigned long uid)
128 char buf[30];
130 snprintf(buf, sizeof buf, "%lu", uid);
131 return encname(mp, buf, 1, NULL);
134 enum okay
135 getcache1(struct mailbox *mp, struct message *m, enum needspec need,
136 int setflags)
138 FILE *fp;
139 long n = 0, size = 0, xsize, xtime, xlines = -1, lines = 0;
140 int lastc = EOF, i, xflag, inheader = 1;
141 char b, iob[32768];
142 off_t offset;
143 void *zp;
145 if (setflags == 0 && ((mp->mb_type != MB_IMAP &&
146 mp->mb_type != MB_CACHE) ||
147 m->m_uid == 0))
148 return STOP;
149 if ((fp = Fopen(encuid(mp, m->m_uid), "r")) == NULL)
150 return STOP;
151 (void)fcntl_lock(fileno(fp), F_RDLCK);
152 if (fscanf(fp, infofmt, &b, (unsigned long*)&xsize, &xflag,
153 (unsigned long*)&xtime, &xlines) < 4)
154 goto fail;
155 if (need != NEED_UNSPEC) {
156 switch (b) {
157 case 'H':
158 if (need == NEED_HEADER)
159 goto success;
160 goto fail;
161 case 'B':
162 if (need == NEED_HEADER || need == NEED_BODY)
163 goto success;
164 goto fail;
165 default:
166 goto fail;
169 success:
170 if (b == 'N')
171 goto flags;
172 if (fseek(fp, INITSKIP, SEEK_SET) < 0)
173 goto fail;
174 zp = zalloc(fp);
175 if (fseek(mp->mb_otf, 0L, SEEK_END) < 0) {
176 (void)zfree(zp);
177 goto fail;
179 offset = ftell(mp->mb_otf);
180 while (inheader && (n = zread(zp, iob, sizeof iob)) > 0) {
181 size += n;
182 for (i = 0; i < n; i++) {
183 if (iob[i] == '\n') {
184 lines++;
185 if (lastc == '\n')
186 inheader = 0;
188 lastc = iob[i]&0377;
190 fwrite(iob, 1, n, mp->mb_otf);
192 if (n > 0 && need == NEED_BODY) {
193 while ((n = zread(zp, iob, sizeof iob)) > 0) {
194 size += n;
195 for (i = 0; i < n; i++)
196 if (iob[i] == '\n')
197 lines++;
198 fwrite(iob, 1, n, mp->mb_otf);
201 fflush(mp->mb_otf);
202 if (zfree(zp) < 0 || n < 0 || ferror(fp) || ferror(mp->mb_otf))
203 goto fail;
204 m->m_size = size;
205 m->m_lines = lines;
206 m->m_block = mailx_blockof(offset);
207 m->m_offset = mailx_offsetof(offset);
208 flags: if (setflags) {
209 m->m_xsize = xsize;
210 m->m_time = xtime;
211 if (setflags & 2) {
212 m->m_flag = xflag | MNOFROM;
213 if (b != 'B')
214 m->m_flag |= MHIDDEN;
217 if (xlines > 0 && m->m_xlines <= 0)
218 m->m_xlines = xlines;
219 switch (b) {
220 case 'B':
221 m->m_xsize = xsize;
222 if (xflag == MREAD && xlines > 0)
223 m->m_flag |= MFULLYCACHED;
224 if (need == NEED_BODY) {
225 m->m_have |= HAVE_HEADER|HAVE_BODY;
226 if (m->m_lines > 0)
227 m->m_xlines = m->m_lines;
228 break;
230 /*FALLTHRU*/
231 case 'H':
232 m->m_have |= HAVE_HEADER;
233 break;
234 case 'N':
235 break;
237 Fclose(fp);
238 return OKAY;
239 fail:
240 Fclose(fp);
241 return STOP;
244 enum okay
245 getcache(struct mailbox *mp, struct message *m, enum needspec need)
247 return getcache1(mp, m, need, 0);
250 void
251 putcache(struct mailbox *mp, struct message *m)
253 FILE *ibuf, *obuf;
254 char *name, ob;
255 int c, oflag;
256 long n, cnt, oldoffset, osize, otime, olines = -1;
257 char iob[32768];
258 void *zp;
260 if ((mp->mb_type != MB_IMAP && mp->mb_type != MB_CACHE) ||
261 m->m_uid == 0 || m->m_time == 0 ||
262 (m->m_flag & (MTOUCH|MFULLYCACHED)) == MFULLYCACHED)
263 return;
264 if (m->m_have & HAVE_BODY)
265 c = 'B';
266 else if (m->m_have & HAVE_HEADER)
267 c = 'H';
268 else if (m->m_have == HAVE_NOTHING)
269 c = 'N';
270 else
271 return;
272 if ((oldoffset = ftell(mp->mb_itf)) < 0) /* XXX weird err hdling */
273 oldoffset = 0;
274 if ((obuf = Fopen(name = encuid(mp, m->m_uid), "r+")) == NULL) {
275 if ((obuf = Fopen(name, "w")) == NULL)
276 return;
277 (void)fcntl_lock(fileno(obuf), F_WRLCK); /* XXX err hdl */
278 } else {
279 (void)fcntl_lock(fileno(obuf), F_WRLCK); /* XXX err hdl */
280 if (fscanf(obuf, infofmt, &ob, (unsigned long*)&osize, &oflag,
281 (unsigned long*)&otime, &olines) >= 4 &&
282 ob != '\0' && (ob == 'B' ||
283 (ob == 'H' && c != 'B'))) {
284 if (m->m_xlines <= 0 && olines > 0)
285 m->m_xlines = olines;
286 if ((c != 'N' && (size_t)osize != m->m_xsize) ||
287 oflag != (int)USEBITS(m->m_flag) ||
288 otime != m->m_time ||
289 (m->m_xlines > 0 &&
290 olines != m->m_xlines)) {
291 fflush(obuf);
292 rewind(obuf);
293 fprintf(obuf, infofmt, ob,
294 (unsigned long)m->m_xsize,
295 USEBITS(m->m_flag),
296 (unsigned long)m->m_time,
297 m->m_xlines);
298 putc('\n', obuf);
300 Fclose(obuf);
301 return;
303 fflush(obuf);
304 rewind(obuf);
305 ftruncate(fileno(obuf), 0);
307 if ((ibuf = setinput(mp, m, NEED_UNSPEC)) == NULL) {
308 Fclose(obuf);
309 return;
311 if (c == 'N')
312 goto done;
313 fseek(obuf, INITSKIP, SEEK_SET);
314 zp = zalloc(obuf);
315 cnt = m->m_size;
316 while (cnt > 0) {
317 n = cnt > (long)sizeof iob ? (long)sizeof iob : cnt;
318 cnt -= n;
319 if ((size_t)n != fread(iob, 1, n, ibuf) ||
320 n != (long)zwrite(zp, iob, n)) {
321 unlink(name);
322 zfree(zp);
323 goto out;
326 if (zfree(zp) < 0) {
327 unlink(name);
328 goto out;
330 done: rewind(obuf);
331 fprintf(obuf, infofmt, c, (unsigned long)m->m_xsize,
332 USEBITS(m->m_flag),
333 (unsigned long)m->m_time,
334 m->m_xlines);
335 putc('\n', obuf);
336 if (ferror(obuf)) {
337 unlink(name);
338 goto out;
340 if (c == 'B' && USEBITS(m->m_flag) == MREAD)
341 m->m_flag |= MFULLYCACHED;
342 out: if (Fclose(obuf) != 0) {
343 m->m_flag &= ~MFULLYCACHED;
344 unlink(name);
346 (void)fseek(mp->mb_itf, oldoffset, SEEK_SET);
349 void
350 initcache(struct mailbox *mp)
352 char *name, *uvname;
353 FILE *uvfp;
354 unsigned long uv;
355 struct cw cw;
357 if (mp->mb_cache_directory != NULL)
358 free(mp->mb_cache_directory);
359 mp->mb_cache_directory = NULL;
360 if ((name = encname(mp, "", 1, NULL)) == NULL)
361 return;
362 mp->mb_cache_directory = sstrdup(name);
363 if ((uvname = encname(mp, "UIDVALIDITY", 1, NULL)) == NULL)
364 return;
365 if (cwget(&cw) == STOP)
366 return;
367 if ((uvfp = Fopen(uvname, "r+")) == NULL ||
368 (fcntl_lock(fileno(uvfp), F_RDLCK), 0) ||
369 fscanf(uvfp, "%lu", &uv) != 1 ||
370 uv != mp->mb_uidvalidity) {
371 if ((uvfp = clean(mp, &cw)) == NULL)
372 goto out;
373 } else {
374 fflush(uvfp);
375 rewind(uvfp);
377 fcntl_lock(fileno(uvfp), F_WRLCK);
378 fprintf(uvfp, "%lu\n", mp->mb_uidvalidity);
379 if (ferror(uvfp) || Fclose(uvfp) != 0) {
380 unlink(uvname);
381 mp->mb_uidvalidity = 0;
383 out: cwrelse(&cw);
386 void
387 purgecache(struct mailbox *mp, struct message *m, long mc)
389 char *name;
390 struct cw cw;
392 if ((name = encname(mp, "", 1, NULL)) == NULL)
393 return;
394 if (cwget(&cw) == STOP)
395 return;
396 purge(mp, m, mc, &cw, name);
397 cwrelse(&cw);
400 static FILE *
401 clean(struct mailbox *mp, struct cw *cw)
403 char *cachedir, *eaccount, *buf;
404 char const *emailbox;
405 int bufsz;
406 DIR *dirp;
407 struct dirent *dp;
408 FILE *fp = NULL;
410 if ((cachedir = value("imap-cache")) == NULL ||
411 (cachedir = file_expand(cachedir)) == NULL)
412 return NULL;
413 eaccount = urlxenc(mp->mb_imap_account);
414 if (asccasecmp(mp->mb_imap_mailbox, "INBOX"))
415 emailbox = urlxenc(mp->mb_imap_mailbox);
416 else
417 emailbox = "INBOX";
418 buf = salloc(bufsz = strlen(cachedir) + strlen(eaccount) +
419 strlen(emailbox) + 40);
420 if (makedir(cachedir) != OKAY)
421 return NULL;
422 snprintf(buf, bufsz, "%s/README", cachedir);
423 if ((fp = Fopen(buf, "wx")) != NULL) {
424 fputs(README1, fp);
425 fputs(README2, fp);
426 fputs(README3, fp);
427 fputs(README4, fp);
428 fputs(README5, fp);
429 Fclose(fp);
431 snprintf(buf, bufsz, "%s/%s", cachedir, eaccount);
432 if (makedir(buf) != OKAY)
433 return NULL;
434 snprintf(buf, bufsz, "%s/%s/%s", cachedir, eaccount, emailbox);
435 if (makedir(buf) != OKAY)
436 return NULL;
437 if (chdir(buf) < 0)
438 return NULL;
439 if ((dirp = opendir(".")) == NULL)
440 goto out;
441 while ((dp = readdir(dirp)) != NULL) {
442 if (dp->d_name[0] == '.' &&
443 (dp->d_name[1] == '\0' ||
444 (dp->d_name[1] == '.' &&
445 dp->d_name[2] == '\0')))
446 continue;
447 unlink(dp->d_name);
449 closedir(dirp);
450 fp = Fopen("UIDVALIDITY", "w");
451 out: if (cwret(cw) == STOP) {
452 fputs("Fatal: Cannot change back to current directory.\n",
453 stderr);
454 abort();
456 return fp;
459 static unsigned long *
460 builds(long *contentelem)
462 unsigned long n, *contents = NULL;
463 long contentalloc = 0;
464 char *x;
465 DIR *dirp;
466 struct dirent *dp;
468 *contentelem = 0;
469 if ((dirp = opendir(".")) == NULL)
470 return NULL;
471 while ((dp = readdir(dirp)) != NULL) {
472 if (dp->d_name[0] == '.' &&
473 (dp->d_name[1] == '\0' ||
474 (dp->d_name[1] == '.' &&
475 dp->d_name[2] == '\0')))
476 continue;
477 n = strtoul(dp->d_name, &x, 10);
478 if (*x != '\0')
479 continue;
480 if (*contentelem >= contentalloc - 1)
481 contents = srealloc(contents,
482 (contentalloc += 200) * sizeof *contents);
483 contents[(*contentelem)++] = n;
485 closedir(dirp);
486 if (*contentelem > 0) {
487 contents[*contentelem] = 0;
488 qsort(contents, *contentelem, sizeof *contents, longlt);
490 return contents;
493 static void
494 purge(struct mailbox *mp, struct message *m, long mc, struct cw *cw,
495 const char *name)
497 unsigned long *contents;
498 long i, j, contentelem;
499 (void)mp;
501 if (chdir(name) < 0)
502 return;
503 contents = builds(&contentelem);
504 if (contents) {
505 i = j = 0;
506 while (j < contentelem) {
507 if (i < mc && m[i].m_uid == contents[j]) {
508 i++;
509 j++;
510 } else if (i < mc && m[i].m_uid < contents[j])
511 i++;
512 else
513 remve(contents[j++]);
516 if (cwret(cw) == STOP) {
517 fputs("Fatal: Cannot change back to current directory.\n",
518 stderr);
519 abort();
521 free(contents);
524 static int
525 longlt(const void *a, const void *b)
527 return *(const long*)a - *(const long*)b;
530 static void
531 remve(unsigned long n)
533 char buf[30];
535 snprintf(buf, sizeof buf, "%lu", n);
536 unlink(buf);
539 void
540 delcache(struct mailbox *mp, struct message *m)
542 char *fn;
544 fn = encuid(mp, m->m_uid);
545 if (fn && unlink(fn) == 0)
546 m->m_flag |= MUNLINKED;
549 enum okay
550 cache_setptr(int transparent)
552 int i;
553 struct cw cw;
554 char *name;
555 unsigned long *contents;
556 long contentelem;
557 enum okay ok = STOP;
558 struct message *omessage = NULL;
559 int omsgCount = 0;
561 if (transparent) {
562 omessage = message;
563 omsgCount = msgCount;
565 free(mb.mb_cache_directory);
566 mb.mb_cache_directory = NULL;
567 if ((name = encname(&mb, "", 1, NULL)) == NULL)
568 return STOP;
569 mb.mb_cache_directory = sstrdup(name);
570 if (cwget(&cw) == STOP)
571 return STOP;
572 if (chdir(name) < 0)
573 return STOP;
574 contents = builds(&contentelem);
575 msgCount = contentelem;
576 message = scalloc(msgCount + 1, sizeof *message);
577 if (cwret(&cw) == STOP) {
578 fputs("Fatal: Cannot change back to current directory.\n",
579 stderr);
580 abort();
582 cwrelse(&cw);
583 for (i = 0; i < msgCount; i++) {
584 message[i].m_uid = contents[i];
585 getcache1(&mb, &message[i], NEED_UNSPEC, 3);
587 ok = OKAY;
588 if (ok == OKAY) {
589 mb.mb_type = MB_CACHE;
590 mb.mb_perm = (options & OPT_R_FLAG) ? 0 : MB_DELE;
591 if (transparent)
592 transflags(omessage, omsgCount, 1);
593 else
594 setdot(message);
596 return ok;
599 enum okay
600 cache_list(struct mailbox *mp, const char *base, int strip, FILE *fp)
602 char *name, *cachedir, *eaccount;
603 DIR *dirp;
604 struct dirent *dp;
605 const char *cp, *bp, *sp;
606 int namesz;
608 if ((cachedir = value("imap-cache")) == NULL ||
609 (cachedir = file_expand(cachedir)) == NULL)
610 return STOP;
611 eaccount = urlxenc(mp->mb_imap_account);
612 name = salloc(namesz = strlen(cachedir) + strlen(eaccount) + 2);
613 snprintf(name, namesz, "%s/%s", cachedir, eaccount);
614 if ((dirp = opendir(name)) == NULL)
615 return STOP;
616 while ((dp = readdir(dirp)) != NULL) {
617 if (dp->d_name[0] == '.')
618 continue;
619 cp = sp = urlxdec(dp->d_name);
620 for (bp = base; *bp && *bp == *sp; bp++)
621 sp++;
622 if (*bp)
623 continue;
624 cp = strip ? sp : cp;
625 fprintf(fp, "%s\n", *cp ? cp : "INBOX");
627 closedir(dirp);
628 return OKAY;
631 enum okay
632 cache_remove(const char *name)
634 struct stat st;
635 DIR *dirp;
636 struct dirent *dp;
637 char *path;
638 int pathsize, pathend, n;
639 char *dir;
641 if ((dir = encname(&mb, "", 0, imap_fileof(name))) == NULL)
642 return OKAY;
643 pathend = strlen(dir);
644 path = smalloc(pathsize = pathend + 30);
645 memcpy(path, dir, pathend);
646 path[pathend++] = '/';
647 path[pathend] = '\0';
648 if ((dirp = opendir(path)) == NULL) {
649 free(path);
650 return OKAY;
652 while ((dp = readdir(dirp)) != NULL) {
653 if (dp->d_name[0] == '.' &&
654 (dp->d_name[1] == '\0' ||
655 (dp->d_name[1] == '.' &&
656 dp->d_name[2] == '\0')))
657 continue;
658 n = strlen(dp->d_name) + 1;
659 if (pathend + n > pathsize)
660 path = srealloc(path, pathsize = pathend + n + 30);
661 memcpy(path + pathend, dp->d_name, n);
662 if (stat(path, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)
663 continue;
664 if (unlink(path) < 0) {
665 perror(path);
666 closedir(dirp);
667 free(path);
668 return STOP;
671 closedir(dirp);
672 path[pathend] = '\0';
673 rmdir(path); /* no error on failure, might contain submailboxes */
674 free(path);
675 return OKAY;
678 enum okay
679 cache_rename(const char *old, const char *new)
681 char *olddir, *newdir;
683 if ((olddir = encname(&mb, "", 0, imap_fileof(old))) == NULL ||
684 (newdir = encname(&mb, "",0, imap_fileof(new))) == NULL)
685 return OKAY;
686 if (rename(olddir, newdir) < 0) {
687 perror(olddir);
688 return STOP;
690 return OKAY;
693 unsigned long
694 cached_uidvalidity(struct mailbox *mp)
696 FILE *uvfp;
697 char *uvname;
698 unsigned long uv;
700 if ((uvname = encname(mp, "UIDVALIDITY", 1, NULL)) == NULL)
701 return 0;
702 if ((uvfp = Fopen(uvname, "r")) == NULL ||
703 (fcntl_lock(fileno(uvfp), F_RDLCK), 0) ||
704 fscanf(uvfp, "%lu", &uv) != 1)
705 uv = 0;
706 if (uvfp != NULL)
707 Fclose(uvfp);
708 return uv;
711 static FILE *
712 cache_queue1(struct mailbox *mp, char const *mode, char **xname)
714 char *name;
715 FILE *fp;
717 if ((name = encname(mp, "QUEUE", 0, NULL)) == NULL)
718 return NULL;
719 if ((fp = Fopen(name, mode)) != NULL)
720 fcntl_lock(fileno(fp), F_WRLCK);
721 if (xname)
722 *xname = name;
723 return fp;
726 FILE *
727 cache_queue(struct mailbox *mp)
729 FILE *fp;
731 fp = cache_queue1(mp, "a", NULL);
732 if (fp == NULL)
733 fputs("Cannot queue IMAP command. Retry when online.\n",
734 stderr);
735 return fp;
738 enum okay
739 cache_dequeue(struct mailbox *mp)
741 int bufsz;
742 char *cachedir, *eaccount, *buf, *oldbox;
743 DIR *dirp;
744 struct dirent *dp;
746 if ((cachedir = value("imap-cache")) == NULL ||
747 (cachedir = file_expand(cachedir)) == NULL)
748 return OKAY;
749 eaccount = urlxenc(mp->mb_imap_account);
750 buf = salloc(bufsz = strlen(cachedir) + strlen(eaccount) + 2);
751 snprintf(buf, bufsz, "%s/%s", cachedir, eaccount);
752 if ((dirp = opendir(buf)) == NULL)
753 return OKAY;
754 oldbox = mp->mb_imap_mailbox;
755 while ((dp = readdir(dirp)) != NULL) {
756 if (dp->d_name[0] == '.')
757 continue;
758 mp->mb_imap_mailbox = urlxdec(dp->d_name);
759 dequeue1(mp);
761 closedir(dirp);
762 mp->mb_imap_mailbox = oldbox;
763 return OKAY;
766 static enum okay
767 dequeue1(struct mailbox *mp)
769 FILE *fp = NULL, *uvfp = NULL;
770 char *qname, *uvname;
771 unsigned long uv;
772 off_t is_size;
773 int is_count;
775 fp = cache_queue1(mp, "r+", &qname);
776 if (fp != NULL && fsize(fp) > 0) {
777 if (imap_select(mp, &is_size, &is_count,
778 mp->mb_imap_mailbox) != OKAY) {
779 fprintf(stderr, "Cannot select \"%s\" for dequeuing.\n",
780 mp->mb_imap_mailbox);
781 goto save;
783 if ((uvname = encname(mp, "UIDVALIDITY", 0, NULL)) == NULL ||
784 (uvfp = Fopen(uvname, "r")) == NULL ||
785 (fcntl_lock(fileno(uvfp), F_RDLCK), 0) ||
786 fscanf(uvfp, "%lu", &uv) != 1 ||
787 uv != mp->mb_uidvalidity) {
788 fprintf(stderr,
789 "Unique identifiers for \"%s\" are out of date. "
790 "Cannot commit IMAP commands.\n",
791 mp->mb_imap_mailbox);
792 save: fputs("Saving IMAP commands to dead.letter\n", stderr);
793 savedeadletter(fp, 0);
794 ftruncate(fileno(fp), 0);
795 Fclose(fp);
796 if (uvfp)
797 Fclose(uvfp);
798 return STOP;
800 Fclose(uvfp);
801 printf("Committing IMAP commands for \"%s\"\n",
802 mp->mb_imap_mailbox);
803 imap_dequeue(mp, fp);
805 if (fp) {
806 Fclose(fp);
807 unlink(qname);
809 return OKAY;
811 #endif /* HAVE_IMAP */