diff options
author | Thomas Roessler <roessler@does-not-exist.org> | 1998-06-08 09:16:03 +0000 |
---|---|---|
committer | Thomas Roessler <roessler@does-not-exist.org> | 1998-06-08 09:16:03 +0000 |
commit | 1a5381e07e97fe482c2b3a7c75f99938f0b105d4 (patch) | |
tree | b4fa4088bbbf5fc9217ee6f87ab60034175e6899 /mx.c |
Initial revision
Diffstat (limited to 'mx.c')
-rw-r--r-- | mx.c | 1519 |
1 files changed, 1519 insertions, 0 deletions
@@ -0,0 +1,1519 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "mutt.h" +#include "mx.h" +#include "rfc2047.h" +#include "sort.h" +#include "mailbox.h" +#include "copy.h" +#include "keymap.h" + + +#ifdef _PGPPATH +#include "pgp.h" +#endif + + + + +#ifdef BUFFY_SIZE +#include "buffy.h" +#endif + +#include <dirent.h> +#include <fcntl.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#ifndef BUFFY_SIZE +#include <utime.h> +#endif + +/* HP-UX and ConvexOS don't have this macro */ +#ifndef S_ISLNK +#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK ? 1 : 0) +#endif + +#define mutt_is_spool(s) (strcmp (Spoolfile, s) == 0) + +#define MAXLOCKATTEMPT 5 + +#ifdef USE_DOTLOCK +/* parameters: + * path - file to lock + * retry - should retry if unable to lock? + */ +static int dotlock_file (const char *path, int retry) +{ + const char *pathptr = path; + char lockfile[_POSIX_PATH_MAX]; + char nfslockfile[_POSIX_PATH_MAX]; + char realpath[_POSIX_PATH_MAX]; + struct stat sb; + size_t prev_size = 0; + int count = 0; + int attempt = 0; + int fd; + + /* if the file is a symlink, find the real file to which it refers */ + FOREVER + { + dprint(2,(debugfile,"dotlock_file(): locking %s\n", pathptr)); + + if (lstat (pathptr, &sb) != 0) + { + mutt_perror (pathptr); + return (-1); + } + + if (S_ISLNK (sb.st_mode)) + { + char linkfile[_POSIX_PATH_MAX]; + char linkpath[_POSIX_PATH_MAX]; + + if ((count = readlink (pathptr, linkfile, sizeof (linkfile))) == -1) + { + mutt_perror (path); + return (-1); + } + linkfile[count] = 0; /* readlink() does not NUL terminate the string! */ + mutt_expand_link (linkpath, pathptr, linkfile); + strfcpy (realpath, linkpath, sizeof (realpath)); + pathptr = realpath; + } + else + break; + } + + snprintf (nfslockfile, sizeof (nfslockfile), "%s.%s.%d", pathptr, Hostname, (int) getpid ()); + snprintf (lockfile, sizeof (lockfile), "%s.lock", pathptr); + unlink (nfslockfile); + + while ((fd = open (nfslockfile, O_WRONLY | O_EXCL | O_CREAT, 0)) < 0) + if (errno != EAGAIN) + { + mutt_perror ("cannot open NFS lock file!"); + return (-1); + } + + close (fd); + + count = 0; + FOREVER + { + link (nfslockfile, lockfile); + if (stat (nfslockfile, &sb) != 0) + { + mutt_perror ("stat"); + return (-1); + } + + if (sb.st_nlink == 2) + break; + + if (stat (path, &sb) != 0) + sb.st_size = 0; + + if (count == 0) + prev_size = sb.st_size; + + /* only try to remove the lock if the file is not changing */ + if (prev_size == sb.st_size && ++count >= retry ? MAXLOCKATTEMPT : 0) + { + if (retry && mutt_yesorno ("Lock count exceeded, remove lock?", 1) == 1) + { + unlink (lockfile); + count = 0; + attempt = 0; + continue; + } + else + return (-1); + } + + prev_size = sb.st_size; + + mutt_message ("Waiting for lock attempt #%d...", ++attempt); + sleep (1); + } + + unlink (nfslockfile); + + return 0; +} + +static int undotlock_file (const char *path) +{ + const char *pathptr = path; + char lockfile[_POSIX_PATH_MAX]; + char realpath[_POSIX_PATH_MAX]; + struct stat sb; + int n; + + FOREVER + { + dprint (2,(debugfile,"undotlock: unlocking %s\n",path)); + + if (lstat (pathptr, &sb) != 0) + { + mutt_perror (pathptr); + return (-1); + } + + if (S_ISLNK (sb.st_mode)) + { + char linkfile[_POSIX_PATH_MAX]; + char linkpath[_POSIX_PATH_MAX]; + + if ((n = readlink (pathptr, linkfile, sizeof (linkfile))) == -1) + { + mutt_perror (pathptr); + return (-1); + } + linkfile[n] = 0; /* readlink() does not NUL terminate the string! */ + mutt_expand_link (linkpath, pathptr, linkfile); + strfcpy (realpath, linkpath, sizeof (realpath)); + pathptr = realpath; + continue; + } + else + break; + } + + snprintf (lockfile, sizeof (lockfile), "%s.lock", pathptr); + unlink (lockfile); + return 0; +} +#endif /* USE_DOTLOCK */ + +/* Args: + * excl if excl != 0, request an exclusive lock + * dot if dot != 0, try to dotlock the file + * timeout should retry locking? + */ +int mx_lock_file (const char *path, int fd, int excl, int dot, int timeout) +{ +#if defined (USE_FCNTL) || defined (USE_FLOCK) + int count; + int attempt; + struct stat prev_sb; +#endif + int r = 0; + +#ifdef USE_FCNTL + struct flock lck; + + + memset (&lck, 0, sizeof (struct flock)); + lck.l_type = excl ? F_WRLCK : F_RDLCK; + lck.l_whence = SEEK_SET; + + count = 0; + attempt = 0; + while (fcntl (fd, F_SETLK, &lck) == -1) + { + struct stat sb; + dprint(1,(debugfile, "mx_lock_file(): fcntl errno %d.\n", errno)); + if (errno != EAGAIN && errno != EACCES) + { + mutt_perror ("fcntl"); + return (-1); + } + + if (fstat (fd, &sb) != 0) + sb.st_size = 0; + + if (count == 0) + prev_sb = sb; + + /* only unlock file if it is unchanged */ + if (prev_sb.st_size == sb.st_size && ++count >= timeout?MAXLOCKATTEMPT:0) + { + if (timeout) + mutt_error ("Timeout exceeded while attempting fcntl lock!"); + return (-1); + } + + prev_sb = sb; + + mutt_message ("Waiting for fcntl lock... %d", ++attempt); + sleep (1); + } +#endif /* USE_FCNTL */ + +#ifdef USE_FLOCK + count = 0; + attempt = 0; + while (flock (fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1) + { + struct stat sb; + if (errno != EWOULDBLOCK) + { + mutt_perror ("flock"); + r = -1; + break; + } + + if (fstat(fd,&sb) != 0 ) + sb.st_size=0; + + if (count == 0) + prev_sb=sb; + + /* only unlock file if it is unchanged */ + if (prev_sb.st_size == sb.st_size && ++count >= timeout?MAXLOCKATTEMPT:0) + { + if (timeout) + mutt_error ("Timeout exceeded while attempting flock lock!"); + r = -1; + break; + } + + prev_sb = sb; + + mutt_message ("Waiting for flock attempt... %d", ++attempt); + sleep (1); + } +#endif /* USE_FLOCK */ + +#ifdef USE_DOTLOCK + if (r == 0 && dot) + r = dotlock_file (path,timeout); +#endif /* USE_DOTLOCK */ + + if (r == -1) + { + /* release any other locks obtained in this routine */ + +#ifdef USE_FCNTL + lck.l_type = F_UNLCK; + fcntl (fd, F_SETLK, &lck); +#endif /* USE_FCNTL */ + +#ifdef USE_FLOCK + flock (fd, LOCK_UN); +#endif /* USE_FLOCK */ + + return (-1); + } + + return 0; +} + +int mx_unlock_file (const char *path, int fd) +{ +#ifdef USE_FCNTL + struct flock unlockit = { F_UNLCK, 0, 0, 0 }; + + memset (&unlockit, 0, sizeof (struct flock)); + unlockit.l_type = F_UNLCK; + unlockit.l_whence = SEEK_SET; + fcntl (fd, F_SETLK, &unlockit); +#endif + +#ifdef USE_FLOCK + flock (fd, LOCK_UN); +#endif + +#ifdef USE_DOTLOCK + undotlock_file (path); +#endif + + return 0; +} + +/* open a file and lock it */ +FILE *mx_open_file_lock (const char *path, const char *mode) +{ + FILE *f; + + if ((f = safe_fopen (path, mode)) != NULL) + { + if (mx_lock_file (path, fileno (f), *mode != 'r', 1, 1) != 0) + { + fclose (f); + f = NULL; + } + } + + return (f); +} + +/* try to figure out what type of mailbox ``path'' is + * + * return values: + * M_* mailbox type + * 0 not a mailbox + * -1 error + */ +int mx_get_magic (const char *path) +{ + struct stat st; + int magic = 0; + char tmp[_POSIX_PATH_MAX]; + FILE *f; + +#ifdef USE_IMAP + if (*path == '{') + return M_IMAP; +#endif /* USE_IMAP */ + + if (stat (path, &st) == -1) + { + dprint (1, (debugfile, "mx_get_magic(): unable to stat %s: %s (errno %d).\n", + path, strerror (errno), errno)); + mutt_perror (path); + return (-1); + } + + if (S_ISDIR (st.st_mode)) + { + /* check for maildir-style mailbox */ + + snprintf (tmp, sizeof (tmp), "%s/cur", path); + if (stat (tmp, &st) == 0 && S_ISDIR (st.st_mode)) + return (M_MAILDIR); + + /* check for mh-style mailbox */ + + snprintf (tmp, sizeof (tmp), "%s/.mh_sequences", path); + if (access (tmp, F_OK) == 0) + return (M_MH); + + snprintf (tmp, sizeof (tmp), "%s/.xmhcache", path); + if (access (tmp, F_OK) == 0) + return (M_MH); + } + else if (st.st_size == 0) + { + /* hard to tell what zero-length files are, so assume the default magic */ + if (DefaultMagic == M_MBOX || DefaultMagic == M_MMDF) + return (DefaultMagic); + else + return (M_MBOX); + } + else if ((f = fopen (path, "r")) != NULL) + { +#ifndef BUFFY_SIZE + struct utimbuf times; +#endif + + fgets (tmp, sizeof (tmp), f); + if (strncmp ("From ", tmp, 5) == 0) + magic = M_MBOX; + else if (strcmp (MMDF_SEP, tmp) == 0) + magic = M_MMDF; + fclose (f); +#ifndef BUFFY_SIZE + /* need to restore the times here, the file was not really accessed, + * only the type was accessed. This is important, because detection + * of "new mail" depends on those times set correctly. + */ + times.actime = st.st_atime; + times.modtime = st.st_mtime; + utime (path, ×); +#endif + } + else + { + dprint (1, (debugfile, "mx_get_magic(): unable to open file %s for reading.\n", + path)); + mutt_perror (path); + return (-1); + } + + return (magic); +} + +/* + * set DefaultMagic to the given value + */ +int mx_set_magic (const char *s) +{ + if (strcasecmp (s, "mbox") == 0) + DefaultMagic = M_MBOX; + else if (strcasecmp (s, "mmdf") == 0) + DefaultMagic = M_MMDF; + else if (strcasecmp (s, "mh") == 0) + DefaultMagic = M_MH; + else if (strcasecmp (s, "maildir") == 0) + DefaultMagic = M_MAILDIR; + else + return (-1); + + return 0; +} + +static int mx_open_mailbox_append (CONTEXT *ctx) +{ + ctx->append = 1; + if (access (ctx->path, W_OK) == 0) + { + switch (ctx->magic = mx_get_magic (ctx->path)) + { + case 0: + mutt_error ("%s is not a mailbox.", ctx->path); + /* fall through */ + case -1: + return (-1); + } + } + else if (errno == ENOENT) + { + ctx->magic = DefaultMagic; + + if (ctx->magic == M_MH || ctx->magic == M_MAILDIR) + { + char tmp[_POSIX_PATH_MAX]; + + if (mkdir (ctx->path, S_IRWXU)) + { + mutt_perror (tmp); + return (-1); + } + + if (ctx->magic == M_MAILDIR) + { + snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path); + if (mkdir (tmp, S_IRWXU)) + { + mutt_perror (tmp); + rmdir (ctx->path); + return (-1); + } + + snprintf (tmp, sizeof (tmp), "%s/new", ctx->path); + if (mkdir (tmp, S_IRWXU)) + { + mutt_perror (tmp); + snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path); + rmdir (tmp); + rmdir (ctx->path); + return (-1); + } + snprintf (tmp, sizeof (tmp), "%s/tmp", ctx->path); + if (mkdir (tmp, S_IRWXU)) + { + mutt_perror (tmp); + snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path); + rmdir (tmp); + snprintf (tmp, sizeof (tmp), "%s/new", ctx->path); + rmdir (tmp); + rmdir (ctx->path); + return (-1); + } + } + else + { + int i; + + snprintf (tmp, sizeof (tmp), "%s/.mh_sequences", ctx->path); + if ((i = creat (tmp, S_IRWXU)) == -1) + { + mutt_perror (tmp); + rmdir (ctx->path); + return (-1); + } + close (i); + } + } + } + else + { + mutt_perror (ctx->path); + return (-1); + } + + switch (ctx->magic) + { + case M_MBOX: + case M_MMDF: + if ((ctx->fp = fopen (ctx->path, "a")) == NULL || + mbox_lock_mailbox (ctx, 1, 1) != 0) + { + if (!ctx->fp) + mutt_perror (ctx->path); + return (-1); + } + fseek (ctx->fp, 0, 2); + break; + + case M_MH: + case M_MAILDIR: + /* nothing to do */ + break; + + default: + return (-1); + } + + return 0; +} + +/* + * open a mailbox and parse it + * + * Args: + * flags M_NOSORT do not sort mailbox + * M_APPEND open mailbox for appending + * M_READONLY open mailbox in read-only mode + * M_QUIET only print error messages + * ctx if non-null, context struct to use + */ +CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx) +{ + CONTEXT *ctx = pctx; + int rc; + + if (!ctx) + ctx = safe_malloc (sizeof (CONTEXT)); + memset (ctx, 0, sizeof (CONTEXT)); + ctx->path = safe_strdup (path); + + ctx->msgnotreadyet = -1; + + if (flags & M_QUIET) + ctx->quiet = 1; + if (flags & M_READONLY) + ctx->readonly = 1; + + if (flags & M_APPEND) + { + if (mx_open_mailbox_append (ctx) != 0) + { + mx_fastclose_mailbox (ctx); + if (!pctx) + safe_free ((void **) &ctx); + return NULL; + } + return ctx; + } + + switch (ctx->magic = mx_get_magic (path)) + { + case 0: + mutt_error ("%s is not a mailbox.", path); + /* fall through */ + + case -1: + mx_fastclose_mailbox (ctx); + if (!pctx) + free (ctx); + return (NULL); + } + + /* if the user has a `push' command in their .muttrc, or in a folder-hook, + * it will cause the progress messages not to be displayed because + * mutt_refresh() will think we are in the middle of a macro. so set a + * flag to indicate that we should really refresh the screen. + */ + set_option (OPTFORCEREFRESH); + + /* create hash tables */ + ctx->id_hash = hash_create (257); + ctx->subj_hash = hash_create (257); + + if (!ctx->quiet) + mutt_message ("Reading %s...", ctx->path); + + switch (ctx->magic) + { + case M_MH: + rc = mh_read_dir (ctx, NULL); + break; + + case M_MAILDIR: + rc = maildir_read_dir (ctx); + break; + + case M_MMDF: + case M_MBOX: + rc = mbox_open_mailbox (ctx); + break; + +#ifdef USE_IMAP + case M_IMAP: + rc = imap_open_mailbox (ctx); + break; +#endif /* USE_IMAP */ + + default: + rc = -1; + break; + } + + if (rc == 0) + { + if ((flags & M_NOSORT) == 0) + { + /* avoid unnecessary work since the mailbox is completely unthreaded + to begin with */ + unset_option (OPTSORTSUBTHREADS); + unset_option (OPTNEEDRESCORE); + mutt_sort_headers (ctx, 1); + } + if (!ctx->quiet) + mutt_clear_error (); + } + else + { + mx_fastclose_mailbox (ctx); + if (!pctx) + safe_free ((void **) &ctx); + } + + unset_option (OPTFORCEREFRESH); + return (ctx); +} + +/* free up memory associated with the mailbox context */ +void mx_fastclose_mailbox (CONTEXT *ctx) +{ + int i; + +#ifdef USE_IMAP + if (ctx->magic == M_IMAP) + imap_fastclose_mailbox (ctx); +#endif /* USE_IMAP */ + if (ctx->subj_hash) + hash_destroy (&ctx->subj_hash, NULL); + if (ctx->id_hash) + hash_destroy (&ctx->id_hash, NULL); + for (i = 0; i < ctx->msgcount; i++) + mutt_free_header (&ctx->hdrs[i]); + safe_free ((void **) &ctx->hdrs); + safe_free ((void **) &ctx->v2r); + safe_free ((void **) &ctx->path); + safe_free ((void **) &ctx->pattern); + if (ctx->limit_pattern) + mutt_pattern_free (&ctx->limit_pattern); + if (ctx->fp) + fclose (ctx->fp); + memset (ctx, 0, sizeof (CONTEXT)); +} + +/* save changes to disk */ +static int sync_mailbox (CONTEXT *ctx) +{ +#ifdef BUFFY_SIZE + BUFFY *tmp = NULL; +#endif + int rc = -1; + + if (!ctx->quiet) + mutt_message ("Writing %s...", ctx->path); + switch (ctx->magic) + { + case M_MBOX: + case M_MMDF: + rc = mbox_sync_mailbox (ctx); +#ifdef BUFFY_SIZE + tmp = mutt_find_mailbox (ctx->path); +#endif + break; + + case M_MH: + case M_MAILDIR: + rc = mh_sync_mailbox (ctx); + break; + +#ifdef USE_IMAP + case M_IMAP: + rc = imap_sync_mailbox (ctx); + break; +#endif /* USE_IMAP */ + } + +#ifdef BUFFY_SIZE + if (tmp && tmp->new == 0) + mutt_update_mailbox (tmp); +#endif + return rc; +} + +/* save changes and close mailbox */ +int mx_close_mailbox (CONTEXT *ctx) +{ + int i, move_messages = 0, purge = 1, read_msgs = 0; + int isSpool = 0; + CONTEXT f; + char mbox[_POSIX_PATH_MAX]; + char buf[SHORT_STRING]; + + if (ctx->readonly || ctx->dontwrite) + { + /* mailbox is readonly or we don't want to write */ + mx_fastclose_mailbox (ctx); + return 0; + } + + if (ctx->append) + { + /* mailbox was opened in write-mode */ + if (ctx->magic == M_MBOX || ctx->magic == M_MMDF) + mbox_close_mailbox (ctx); + else + mx_fastclose_mailbox (ctx); + return 0; + } + + for (i = 0; i < ctx->msgcount; i++) + { + if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read) + read_msgs++; + } + + if (read_msgs && quadoption (OPT_MOVE) != M_NO) + { + char *p; + + if ((p = mutt_find_hook (M_MBOXHOOK, ctx->path))) + { + isSpool = 1; + strfcpy (mbox, p, sizeof (mbox)); + } + else + { + strfcpy (mbox, Inbox, sizeof (mbox)); + isSpool = mutt_is_spool (ctx->path) && !mutt_is_spool (mbox); + } + mutt_expand_path (mbox, sizeof (mbox)); + + if (isSpool) + { + snprintf (buf, sizeof (buf), "Move read messages to %s?", mbox); + if ((move_messages = query_quadoption (OPT_MOVE, buf)) == -1) + return (-1); + } + } + + if (ctx->deleted) + { + snprintf (buf, sizeof (buf), "Purge %d deleted message%s?", + ctx->deleted, ctx->deleted == 1 ? "" : "s"); + if ((purge = query_quadoption (OPT_DELETE, buf)) < 0) + return (-1); + } + + if (option (OPTMARKOLD)) + { + for (i = 0; i < ctx->msgcount; i++) + { + if (!ctx->hdrs[i]->deleted && !ctx->hdrs[i]->old) + mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, 1); + } + } + + if (move_messages) + { + if (mx_open_mailbox (mbox, M_APPEND, &f) == NULL) + return (-1); + + mutt_message ("Moving read messages to %s...", mbox); + + for (i = 0; i < ctx->msgcount; i++) + { + if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted) + { + mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN); + ctx->hdrs[i]->deleted = 1; + ctx->deleted++; + } + } + + mx_close_mailbox (&f); + } + else if (!ctx->changed && ctx->deleted == 0) + { + mutt_message ("Mailbox is unchanged."); + mx_fastclose_mailbox (ctx); + return 0; + } + + if (!purge) + { + for (i = 0; i < ctx->msgcount; i++) + ctx->hdrs[i]->deleted = 0; + ctx->deleted = 0; + } + + if (ctx->changed || ctx->deleted) + { + if (sync_mailbox (ctx) == -1) + return (-1); + } + + if (move_messages) + mutt_message ("%d kept, %d moved, %d deleted.", + ctx->msgcount - ctx->deleted, read_msgs, ctx->deleted); + else + mutt_message ("%d kept, %d deleted.", + ctx->msgcount - ctx->deleted, ctx->deleted); + + if (ctx->msgcount == ctx->deleted && + (ctx->magic == M_MMDF || ctx->magic == M_MBOX) && + strcmp (ctx->path, Spoolfile) != 0 && !option (OPTSAVEEMPTY)) + unlink (ctx->path); + + mx_fastclose_mailbox (ctx); + + return 0; +} + +/* save changes to mailbox + * + * return values: + * 0 success + * -1 error + */ +int mx_sync_mailbox (CONTEXT *ctx) +{ + int rc, i, j; + + if (ctx->dontwrite) + { + char buf[STRING], tmp[STRING]; + if (km_expand_key (buf, sizeof(buf), + km_find_func (MENU_MAIN, OP_TOGGLE_WRITE))) + snprintf (tmp, sizeof(tmp), " Press '%s' to toggle write", buf); + else + strfcpy (tmp, "Use 'toggle-write' to re-enable write!", sizeof(tmp)); + + mutt_error ("Mailbox is marked unwritable. %s", tmp); + return -1; + } + else if (ctx->readonly) + { + mutt_error ("Mailbox is read-only."); + return -1; + } + + if (!ctx->changed && !ctx->deleted) + { + mutt_message ("Mailbox is unchanged."); + return (0); + } + + if (ctx->deleted) + { + char buf[SHORT_STRING]; + + snprintf (buf, sizeof (buf), "Purge %d deleted message%s?", + ctx->deleted, ctx->deleted == 1 ? "" : "s"); + if ((rc = query_quadoption (OPT_DELETE, buf)) < 0) + return (-1); + else if (rc == M_NO) + { + if (!ctx->changed) + return 0; /* nothing to do! */ + for (i = 0 ; i < ctx->msgcount ; i++) + ctx->hdrs[i]->deleted = 0; + ctx->deleted = 0; + } + } + + if ((rc = sync_mailbox (ctx)) == 0) + { + mutt_message ("%d kept, %d deleted.", ctx->msgcount - ctx->deleted, + ctx->deleted); + sleep (1); /* allow the user time to read the message */ + + if (ctx->msgcount == ctx->deleted && + (ctx->magic == M_MBOX || ctx->magic == M_MMDF) && + strcmp (ctx->path, Spoolfile) != 0 && !option (OPTSAVEEMPTY)) + { + unlink (ctx->path); + mx_fastclose_mailbox (ctx); + return 0; + } + + /* update memory to reflect the new state of the mailbox */ + ctx->vcount = 0; + ctx->vsize = 0; + ctx->tagged = 0; + ctx->deleted = 0; + ctx->new = 0; + ctx->unread = 0; + ctx->changed = 0; + ctx->flagged = 0; +#define this_body ctx->hdrs[j]->content + for (i = 0, j = 0; i < ctx->msgcount; i++) + { + if (!ctx->hdrs[i]->deleted) + { + if (i != j) + { + ctx->hdrs[j] = ctx->hdrs[i]; + ctx->hdrs[i] = NULL; + } + ctx->hdrs[j]->msgno = j; + if (ctx->hdrs[j]->virtual != -1) + { + ctx->v2r[ctx->vcount] = j; + ctx->hdrs[j]->virtual = ctx->vcount++; + ctx->vsize += this_body->length + this_body->offset - + this_body->hdr_offset; + } + ctx->hdrs[j]->changed = 0; + if (ctx->hdrs[j]->tagged) + ctx->tagged++; + if (ctx->hdrs[j]->flagged) + ctx->flagged++; + if (!ctx->hdrs[j]->read) + { + ctx->unread++; + if (!ctx->hdrs[j]->old) + ctx->new++; + } + j++; + } + else + { + if (ctx->magic == M_MH || ctx->magic == M_MAILDIR) + ctx->size -= (ctx->hdrs[i]->content->length + + ctx->hdrs[i]->content->offset - + ctx->hdrs[i]->content->hdr_offset); + /* remove message from the hash tables */ + if (ctx->hdrs[i]->env->real_subj) + hash_delete (ctx->subj_hash, ctx->hdrs[i]->env->real_subj, ctx->hdrs[i], NULL); + if (ctx->hdrs[i]->env->message_id) + hash_delete (ctx->id_hash, ctx->hdrs[i]->env->message_id, ctx->hdrs[i], NULL); + mutt_free_header (&ctx->hdrs[i]); + } + } +#undef this_body + ctx->msgcount = j; + + mutt_sort_headers (ctx, 1); /* rethread from scratch */ + } + + return (rc); +} + +int mh_open_new_message (MESSAGE *msg, CONTEXT *dest, HEADER *hdr) +{ + int hi = 1; + int fd, n; + char *cp; + char path[_POSIX_PATH_MAX]; + DIR *dirp; + struct dirent *de; + + do + { + if ((dirp = opendir (dest->path)) == NULL) + { + mutt_perror (dest->path); + return (-1); + } + + /* figure out what the next message number is */ + while ((de = readdir (dirp)) != NULL) + { + cp = de->d_name; + while (*cp) + { + if (!isdigit (*cp)) + break; + cp++; + } + if (!*cp) + { + n = atoi (de->d_name); + if (n > hi) + hi = n; + } + } + closedir (dirp); + hi++; + snprintf (path, sizeof (path), "%s/%d", dest->path, hi); + if ((fd = open (path, O_WRONLY | O_EXCL | O_CREAT, 0600)) == -1) + { + if (errno != EEXIST) + { + mutt_perror (path); + return (-1); + } + } + } + while (fd < 0); + + if ((msg->fp = fdopen (fd, "w")) == NULL) + return (-1); + + return 0; +} + +int maildir_open_new_message (MESSAGE *msg, CONTEXT *dest, HEADER *hdr) +{ + char tmp[_POSIX_PATH_MAX]; + char path[_POSIX_PATH_MAX]; + + maildir_create_filename (dest->path, hdr, path, tmp); + if ((msg->fp = safe_fopen (tmp, "w")) == NULL) + return (-1); + return 0; +} + +int mbox_open_new_message (MESSAGE *msg, CONTEXT *dest, HEADER *hdr) +{ + msg->fp = dest->fp; + return 0; +} + +/* args: + * dest destintation mailbox + * hdr message being copied (required for maildir support, because + * the filename depends on the message flags) + */ +MESSAGE *mx_open_new_message (CONTEXT *dest, HEADER *hdr, int flags) +{ + MESSAGE *msg; + int (*func) (MESSAGE *, CONTEXT *, HEADER *); + ADDRESS *p = NULL; + time_t t; + + switch (dest->magic) + { + case M_MMDF: + case M_MBOX: + func = mbox_open_new_message; + break; + case M_MAILDIR: + func = maildir_open_new_message; + break; + case M_MH: + func = mh_open_new_message; + break; + default: + dprint (1, (debugfile, "mx_open_new_message(): function unimplemented for mailbox type %d.\n", + dest->magic)); + return (NULL); + } + + msg = safe_calloc (1, sizeof (MESSAGE)); + msg->magic = dest->magic; + msg->write = 1; + + if (func (msg, dest, hdr) == 0) + { + if (dest->magic == M_MMDF) + fputs (MMDF_SEP, msg->fp); + + if (msg->magic != M_MAILDIR && (flags & M_ADD_FROM)) + { + if (hdr) + { + if (hdr->env->return_path) + p = hdr->env->return_path; + else if (hdr->env->sender) + p = hdr->env->sender; + else + p = hdr->env->from; + + if (!hdr->received) + hdr->received = time (NULL); + t = hdr->received; + } + else + t = time (NULL); + + fprintf (msg->fp, "From %s %s", p ? p->mailbox : Username, ctime (&t)); + } + } + else + safe_free ((void **) &msg); + + return msg; +} + +int mutt_reopen_mailbox (CONTEXT *ctx, int *index_hint) +{ + int (*cmp_headers) (const HEADER *, const HEADER *) = NULL; + HEADER **old_hdrs; + int old_msgcount; + int msg_mod = 0; + int index_hint_set; + int i, j; + int rc = -1; + + /* silent operations */ + ctx->quiet = 1; + + mutt_message ("Reopening mailbox..."); + + /* our heuristics require the old mailbox to be unsorted */ + if (Sort != SORT_ORDER) + { + short old_sort; + + old_sort = Sort; + Sort = SORT_ORDER; + mutt_sort_headers (ctx, 1); + Sort = old_sort; + } + + /* save the old headers */ + old_msgcount = ctx->msgcount; + old_hdrs = ctx->hdrs; + + /* simulate a close */ + hash_destroy (&ctx->id_hash, NULL); + hash_destroy (&ctx->subj_hash, NULL); + safe_free ((void **) &ctx->v2r); + if (ctx->readonly) + { + for (i = 0; i < ctx->msgcount; i++) + mutt_free_header (&(ctx->hdrs[i])); /* nothing to do! */ + safe_free ((void **) &ctx->hdrs); + } + else + ctx->hdrs = NULL; + + ctx->hdrmax = 0; /* force allocation of new headers */ + ctx->msgcount = 0; + ctx->vcount = 0; + ctx->tagged = 0; + ctx->deleted = 0; + ctx->new = 0; + ctx->unread = 0; + ctx->flagged = 0; + ctx->changed = 0; + ctx->id_hash = hash_create (257); + ctx->subj_hash = hash_create (257); + + switch (ctx->magic) + { + case M_MBOX: + fseek (ctx->fp, 0, 0); + cmp_headers = mbox_strict_cmp_headers; + rc = mbox_parse_mailbox (ctx); + break; + + case M_MMDF: + fseek (ctx->fp, 0, 0); + cmp_headers = mbox_strict_cmp_headers; + rc = mmdf_parse_mailbox (ctx); + break; + + case M_MH: + /* cmp_headers = mh_strict_cmp_headers; */ + rc = mh_read_dir (ctx, NULL); + break; + + case M_MAILDIR: + /* cmp_headers = maildir_strict_cmp_headers; */ + rc = maildir_read_dir (ctx); + break; + + default: + rc = -1; + break; + } + + if (rc == -1) + { + /* free the old headers */ + for (j = 0; j < old_msgcount; j++) + mutt_free_header (&(old_hdrs[j])); + safe_free ((void **) &old_hdrs); + + ctx->quiet = 0; + return (-1); + } + + /* now try to recover the old flags */ + + index_hint_set = (index_hint == NULL); + + if (!ctx->readonly) + { + for (i = 0; i < ctx->msgcount; i++) + { + int found = 0; + + /* some messages have been deleted, and new messages have been + * appended at the end; the heuristic is that old messages have then + * "advanced" towards the beginning of the folder, so we begin the + * search at index "i" + */ + for (j = i; j < old_msgcount; j++) + { + if (old_hdrs[j] == NULL) + continue; + if (cmp_headers (ctx->hdrs[i], old_hdrs[j])) + { + found = 1; + break; + } + } + if (!found) + { + for (j = 0; j < i; j++) + { + if (old_hdrs[j] == NULL) + continue; + if (cmp_headers (ctx->hdrs[i], old_hdrs[j])) + { + found = 1; + break; + } + } + } + + if (found) + { + /* this is best done here */ + if (!index_hint_set && *index_hint == j) + *index_hint = i; + + if (old_hdrs[j]->changed) + { + /* Only update the flags if the old header was changed; + * otherwise, the header ma |