mx.c 34.4 KB
Newer Older
Thomas Roessler's avatar
Thomas Roessler committed
1
/*
2
 * Copyright (C) 1996-2002,2010,2013 Michael R. Elkins <me@mutt.org>
3
 * Copyright (C) 1999-2003 Thomas Roessler <roessler@does-not-exist.org>
Thomas Roessler's avatar
Thomas Roessler committed
4 5 6 7 8 9 10 11 12 13 14 15 16
 * 
 *     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
17
 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
Thomas Roessler's avatar
Thomas Roessler committed
18 19
 */ 

20 21 22 23
#if HAVE_CONFIG_H
# include "config.h"
#endif

Thomas Roessler's avatar
Thomas Roessler committed
24 25 26 27 28 29 30
#include "mutt.h"
#include "mx.h"
#include "rfc2047.h"
#include "sort.h"
#include "mailbox.h"
#include "copy.h"
#include "keymap.h"
31
#include "url.h"
32 33 34
#ifdef USE_SIDEBAR
#include "sidebar.h"
#endif
Thomas Roessler's avatar
Thomas Roessler committed
35

36 37 38 39
#ifdef USE_COMPRESSED
#include "compress.h"
#endif

Thomas Roessler's avatar
Thomas Roessler committed
40 41 42
#ifdef USE_IMAP
#include "imap.h"
#endif
Thomas Roessler's avatar
Thomas Roessler committed
43

44 45 46 47
#ifdef USE_POP
#include "pop.h"
#endif

Thomas Roessler's avatar
Thomas Roessler committed
48 49
#include "buffy.h"

Thomas Roessler's avatar
Thomas Roessler committed
50 51 52 53
#ifdef USE_DOTLOCK
#include "dotlock.h"
#endif

54 55
#include "mutt_crypt.h"

Thomas Roessler's avatar
Thomas Roessler committed
56 57 58 59 60 61 62 63 64 65 66
#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>
#include <utime.h>

67
struct mx_ops* mx_get_ops (int magic)
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
{
  switch (magic)
  {
#ifdef USE_IMAP
    case MUTT_IMAP:
      return &mx_imap_ops;
#endif
    case MUTT_MAILDIR:
      return &mx_maildir_ops;
    case MUTT_MBOX:
      return &mx_mbox_ops;
    case MUTT_MH:
      return &mx_mh_ops;
    case MUTT_MMDF:
      return &mx_mmdf_ops;
#ifdef USE_POP
    case MUTT_POP:
      return &mx_pop_ops;
86 87 88 89
#endif
#ifdef USE_COMPRESSED
    case MUTT_COMPRESSED:
      return &mx_comp_ops;
90 91 92 93 94
#endif
    default:
      return NULL;
  }
}
Thomas Roessler's avatar
Thomas Roessler committed
95

96
#define mutt_is_spool(s)  (mutt_strcmp (Spoolfile, s) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
97 98 99 100 101 102 103

#ifdef USE_DOTLOCK
/* parameters: 
 * path - file to lock
 * retry - should retry if unable to lock?
 */

Thomas Roessler's avatar
Thomas Roessler committed
104
#ifdef DL_STANDALONE
Thomas Roessler's avatar
Thomas Roessler committed
105

Thomas Roessler's avatar
Thomas Roessler committed
106
static int invoke_dotlock (const char *path, int dummy, int flags, int retry)
Thomas Roessler's avatar
Thomas Roessler committed
107 108
{
  char cmd[LONG_STRING + _POSIX_PATH_MAX];
109
  char f[SHORT_STRING + _POSIX_PATH_MAX];
Thomas Roessler's avatar
Thomas Roessler committed
110 111
  char r[SHORT_STRING];
  
112 113
  if (flags & DL_FL_RETRY)
    snprintf (r, sizeof (r), "-r %d ", retry ? MAXLOCKATTEMPT : 0);
Thomas Roessler's avatar
Thomas Roessler committed
114
  
115
  mutt_quote_filename (f, sizeof (f), path);
116
  
117
  snprintf (cmd, sizeof (cmd),
Thomas Roessler's avatar
Thomas Roessler committed
118
	    "%s %s%s%s%s%s%s%s",
119 120 121 122 123
	    NONULL (MuttDotlock),
	    flags & DL_FL_TRY ? "-t " : "",
	    flags & DL_FL_UNLOCK ? "-u " : "",
	    flags & DL_FL_USEPRIV ? "-p " : "",
	    flags & DL_FL_FORCE ? "-f " : "",
Thomas Roessler's avatar
Thomas Roessler committed
124
	    flags & DL_FL_UNLINK ? "-d " : "",
125 126
	    flags & DL_FL_RETRY ? r : "",
	    f);
127
  
128
  return mutt_system (cmd);
Thomas Roessler's avatar
Thomas Roessler committed
129
}
Thomas Roessler's avatar
Thomas Roessler committed
130

Thomas Roessler's avatar
Thomas Roessler committed
131
#else 
Thomas Roessler's avatar
Thomas Roessler committed
132

Thomas Roessler's avatar
Thomas Roessler committed
133
#define invoke_dotlock dotlock_invoke
Thomas Roessler's avatar
Thomas Roessler committed
134

Thomas Roessler's avatar
Thomas Roessler committed
135
#endif
Thomas Roessler's avatar
Thomas Roessler committed
136

Thomas Roessler's avatar
Thomas Roessler committed
137
static int dotlock_file (const char *path, int fd, int retry)
Thomas Roessler's avatar
Thomas Roessler committed
138 139 140
{
  int r;
  int flags = DL_FL_USEPRIV | DL_FL_RETRY;
141

142
  if (retry) retry = 1;
Thomas Roessler's avatar
Thomas Roessler committed
143

Thomas Roessler's avatar
Thomas Roessler committed
144
retry_lock:
Thomas Roessler's avatar
Thomas Roessler committed
145
  if ((r = invoke_dotlock(path, fd, flags, retry)) == DL_EX_EXIST)
Thomas Roessler's avatar
Thomas Roessler committed
146
  {
147 148 149 150 151 152
    if (!option (OPTNOCURSES))
    {
      char msg[LONG_STRING];
      
      snprintf(msg, sizeof(msg), _("Lock count exceeded, remove lock for %s?"),
	       path);
153
      if(retry && mutt_yesorno(msg, MUTT_YES) == MUTT_YES)
154 155 156
      {
	flags |= DL_FL_FORCE;
	retry--;
157
	mutt_clear_error ();
158 159 160 161
	goto retry_lock;
      }
    } 
    else
Thomas Roessler's avatar
Thomas Roessler committed
162
    {
163
      mutt_error ( _("Can't dotlock %s.\n"), path);
Thomas Roessler's avatar
Thomas Roessler committed
164 165
    }
  }
Thomas Roessler's avatar
Thomas Roessler committed
166
  return (r == DL_EX_OK ? 0 : -1);
Thomas Roessler's avatar
Thomas Roessler committed
167 168
}

Thomas Roessler's avatar
Thomas Roessler committed
169
static int undotlock_file (const char *path, int fd)
Thomas Roessler's avatar
Thomas Roessler committed
170
{
Thomas Roessler's avatar
Thomas Roessler committed
171
  return (invoke_dotlock(path, fd, DL_FL_USEPRIV | DL_FL_UNLOCK, 0) == DL_EX_OK ? 
Thomas Roessler's avatar
Thomas Roessler committed
172
	  0 : -1);
Thomas Roessler's avatar
Thomas Roessler committed
173
}
Thomas Roessler's avatar
Thomas Roessler committed
174

Thomas Roessler's avatar
Thomas Roessler committed
175 176 177 178 179 180 181 182 183 184 185 186
#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;
187
  struct stat sb = { 0 }, prev_sb = { 0 }; /* silence gcc warnings */
Thomas Roessler's avatar
Thomas Roessler committed
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
#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)
  {
    dprint(1,(debugfile, "mx_lock_file(): fcntl errno %d.\n", errno));
    if (errno != EAGAIN && errno != EACCES)
    {
      mutt_perror ("fcntl");
206
      return -1;
Thomas Roessler's avatar
Thomas Roessler committed
207 208 209
    }

    if (fstat (fd, &sb) != 0)
210
      sb.st_size = 0;
Thomas Roessler's avatar
Thomas Roessler committed
211 212 213 214 215
    
    if (count == 0)
      prev_sb = sb;

    /* only unlock file if it is unchanged */
Thomas Roessler's avatar
Thomas Roessler committed
216
    if (prev_sb.st_size == sb.st_size && ++count >= (timeout?MAXLOCKATTEMPT:0))
Thomas Roessler's avatar
Thomas Roessler committed
217 218
    {
      if (timeout)
219
	mutt_error _("Timeout exceeded while attempting fcntl lock!");
220
      return -1;
Thomas Roessler's avatar
Thomas Roessler committed
221 222 223 224
    }

    prev_sb = sb;

225
    mutt_message (_("Waiting for fcntl lock... %d"), ++attempt);
Thomas Roessler's avatar
Thomas Roessler committed
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
    sleep (1);
  }
#endif /* USE_FCNTL */

#ifdef USE_FLOCK
  count = 0;
  attempt = 0;
  while (flock (fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1)
  {
    if (errno != EWOULDBLOCK)
    {
      mutt_perror ("flock");
      r = -1;
      break;
    }

242 243
    if (fstat(fd, &sb) != 0)
      sb.st_size = 0;
Thomas Roessler's avatar
Thomas Roessler committed
244 245
    
    if (count == 0)
246
      prev_sb = sb;
Thomas Roessler's avatar
Thomas Roessler committed
247 248

    /* only unlock file if it is unchanged */
Thomas Roessler's avatar
Thomas Roessler committed
249
    if (prev_sb.st_size == sb.st_size && ++count >= (timeout?MAXLOCKATTEMPT:0))
Thomas Roessler's avatar
Thomas Roessler committed
250 251
    {
      if (timeout)
252
	mutt_error _("Timeout exceeded while attempting flock lock!");
Thomas Roessler's avatar
Thomas Roessler committed
253 254 255 256 257 258
      r = -1;
      break;
    }

    prev_sb = sb;

259
    mutt_message (_("Waiting for flock attempt... %d"), ++attempt);
Thomas Roessler's avatar
Thomas Roessler committed
260 261 262 263 264 265
    sleep (1);
  }
#endif /* USE_FLOCK */

#ifdef USE_DOTLOCK
  if (r == 0 && dot)
Thomas Roessler's avatar
Thomas Roessler committed
266
    r = dotlock_file (path, fd, timeout);
Thomas Roessler's avatar
Thomas Roessler committed
267 268
#endif /* USE_DOTLOCK */

269
  if (r != 0)
Thomas Roessler's avatar
Thomas Roessler committed
270 271 272 273 274 275 276 277 278 279 280 281 282
  {
    /* 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 */
  }

283
  return r;
Thomas Roessler's avatar
Thomas Roessler committed
284 285
}

Thomas Roessler's avatar
Thomas Roessler committed
286
int mx_unlock_file (const char *path, int fd, int dot)
Thomas Roessler's avatar
Thomas Roessler committed
287 288
{
#ifdef USE_FCNTL
289
  struct flock unlockit = { F_UNLCK, 0, 0, 0, 0 };
Thomas Roessler's avatar
Thomas Roessler committed
290 291 292 293 294 295 296 297 298 299 300 301

  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
Thomas Roessler's avatar
Thomas Roessler committed
302
  if (dot)
Thomas Roessler's avatar
Thomas Roessler committed
303
    undotlock_file (path, fd);
Thomas Roessler's avatar
Thomas Roessler committed
304 305 306 307 308
#endif
  
  return 0;
}

309
static void mx_unlink_empty (const char *path)
Thomas Roessler's avatar
Thomas Roessler committed
310 311 312
{
  int fd;
#ifndef USE_DOTLOCK
313
  struct stat sb;
Thomas Roessler's avatar
Thomas Roessler committed
314 315 316 317
#endif

  if ((fd = open (path, O_RDWR)) == -1)
    return;
318

Thomas Roessler's avatar
Thomas Roessler committed
319 320 321 322 323 324 325
  if (mx_lock_file (path, fd, 1, 0, 1) == -1)
  {
    close (fd);
    return;
  }

#ifdef USE_DOTLOCK
Thomas Roessler's avatar
Thomas Roessler committed
326
  invoke_dotlock (path, fd, DL_FL_UNLINK, 1);
Thomas Roessler's avatar
Thomas Roessler committed
327
#else
328
  if (fstat (fd, &sb) == 0 && sb.st_size == 0)
Thomas Roessler's avatar
Thomas Roessler committed
329 330 331 332
    unlink (path);
#endif

  mx_unlock_file (path, fd, 0);
333
  close (fd);
Thomas Roessler's avatar
Thomas Roessler committed
334 335
}

Thomas Roessler's avatar
Thomas Roessler committed
336 337 338 339 340 341 342
/* try to figure out what type of mailbox ``path'' is
 *
 * return values:
 *	M_*	mailbox type
 *	0	not a mailbox
 *	-1	error
 */
343 344 345

#ifdef USE_IMAP

Thomas Roessler's avatar
Thomas Roessler committed
346
int mx_is_imap(const char *p)
347
{
348 349 350 351 352 353 354 355 356 357 358 359 360
  url_scheme_t scheme;

  if (!p)
    return 0;

  if (*p == '{')
    return 1;

  scheme = url_check_scheme (p);
  if (scheme == U_IMAP || scheme == U_IMAPS)
    return 1;

  return 0;
361 362 363 364
}

#endif

365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
#ifdef USE_POP
int mx_is_pop (const char *p)
{
  url_scheme_t scheme;

  if (!p)
    return 0;

  scheme = url_check_scheme (p);
  if (scheme == U_POP || scheme == U_POPS)
    return 1;

  return 0;
}
#endif

Thomas Roessler's avatar
Thomas Roessler committed
381 382 383 384 385 386 387 388
int mx_get_magic (const char *path)
{
  struct stat st;
  int magic = 0;
  char tmp[_POSIX_PATH_MAX];
  FILE *f;

#ifdef USE_IMAP
389
  if(mx_is_imap(path))
390
    return MUTT_IMAP;
Thomas Roessler's avatar
Thomas Roessler committed
391 392
#endif /* USE_IMAP */

393 394
#ifdef USE_POP
  if (mx_is_pop (path))
395
    return MUTT_POP;
396 397
#endif /* USE_POP */

Thomas Roessler's avatar
Thomas Roessler committed
398 399 400 401 402 403 404 405 406 407
  if (stat (path, &st) == -1)
  {
    dprint (1, (debugfile, "mx_get_magic(): unable to stat %s: %s (errno %d).\n",
		path, strerror (errno), errno));
    return (-1);
  }

  if (S_ISDIR (st.st_mode))
  {
    /* check for maildir-style mailbox */
408
    if (mx_is_maildir (path))
409
      return MUTT_MAILDIR;
Thomas Roessler's avatar
Thomas Roessler committed
410 411

    /* check for mh-style mailbox */
412
    if (mx_is_mh (path))
413
      return MUTT_MH;
Thomas Roessler's avatar
Thomas Roessler committed
414 415 416 417
  }
  else if (st.st_size == 0)
  {
    /* hard to tell what zero-length files are, so assume the default magic */
418
    if (DefaultMagic == MUTT_MBOX || DefaultMagic == MUTT_MMDF)
Thomas Roessler's avatar
Thomas Roessler committed
419 420
      return (DefaultMagic);
    else
421
      return (MUTT_MBOX);
Thomas Roessler's avatar
Thomas Roessler committed
422 423 424
  }
  else if ((f = fopen (path, "r")) != NULL)
  {
425 426 427
#ifdef HAVE_UTIMENSAT
    struct timespec ts[2];
#else
Thomas Roessler's avatar
Thomas Roessler committed
428
    struct utimbuf times;
429
#endif /* HAVE_UTIMENSAT */
430
    int ch;
431 432 433 434

    /* Some mailbox creation tools erroneously append a blank line to
     * a file before appending a mail message.  This allows mutt to
     * detect magic for and thus open those files. */
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
    while ((ch = fgetc (f)) != EOF)
    {
      if (ch != '\n' && ch != '\r')
      {
        ungetc (ch, f);
        break;
      }
    }

    if (fgets (tmp, sizeof (tmp), f))
    {
      if (mutt_strncmp ("From ", tmp, 5) == 0)
        magic = MUTT_MBOX;
      else if (mutt_strcmp (MMDF_SEP, tmp) == 0)
        magic = MUTT_MMDF;
    }
451
    safe_fclose (&f);
452 453 454 455 456 457 458

    if (!option(OPTCHECKMBOXSIZE))
    {
      /* 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.
       */
459 460 461 462 463
#ifdef HAVE_UTIMENSAT
      mutt_get_stat_timespec (&ts[0], &st, MUTT_STAT_ATIME);
      mutt_get_stat_timespec (&ts[1], &st, MUTT_STAT_MTIME);
      utimensat (0, path, ts, 0);
#else
464 465 466
      times.actime = st.st_atime;
      times.modtime = st.st_mtime;
      utime (path, &times);
467
#endif
468
    }
Thomas Roessler's avatar
Thomas Roessler committed
469 470 471 472 473 474 475 476
  }
  else
  {
    dprint (1, (debugfile, "mx_get_magic(): unable to open file %s for reading.\n",
		path));
    return (-1);
  }

477 478 479
#ifdef USE_COMPRESSED
  /* If there are no other matches, see if there are any
   * compress hooks that match */
480
  if ((magic == 0) && mutt_comp_can_read (path))
481 482
    return MUTT_COMPRESSED;
#endif
Thomas Roessler's avatar
Thomas Roessler committed
483 484 485 486 487 488 489 490
  return (magic);
}

/*
 * set DefaultMagic to the given value
 */
int mx_set_magic (const char *s)
{
491
  if (ascii_strcasecmp (s, "mbox") == 0)
492
    DefaultMagic = MUTT_MBOX;
493
  else if (ascii_strcasecmp (s, "mmdf") == 0)
494
    DefaultMagic = MUTT_MMDF;
495
  else if (ascii_strcasecmp (s, "mh") == 0)
496
    DefaultMagic = MUTT_MH;
497
  else if (ascii_strcasecmp (s, "maildir") == 0)
498
    DefaultMagic = MUTT_MAILDIR;
Thomas Roessler's avatar
Thomas Roessler committed
499 500 501 502 503 504
  else
    return (-1);

  return 0;
}

505 506 507 508 509 510 511
/* mx_access: Wrapper for access, checks permissions on a given mailbox.
 *   We may be interested in using ACL-style flags at some point, currently
 *   we use the normal access() flags. */
int mx_access (const char* path, int flags)
{
#ifdef USE_IMAP
  if (mx_is_imap (path))
512
    return imap_access (path);
513 514 515 516 517
#endif

  return access (path, flags);
}

518
static int mx_open_mailbox_append (CONTEXT *ctx, int flags)
Thomas Roessler's avatar
Thomas Roessler committed
519
{
520 521
  struct stat sb;

Thomas Roessler's avatar
Thomas Roessler committed
522
  ctx->append = 1;
523 524
  ctx->magic = mx_get_magic (ctx->path);
  if (ctx->magic == 0)
Thomas Roessler's avatar
Thomas Roessler committed
525
  {
526 527
    mutt_error (_("%s is not a mailbox."), ctx->path);
    return -1;
Thomas Roessler's avatar
Thomas Roessler committed
528 529
  }

530 531 532
  if (ctx->magic < 0)
  {
    if (stat (ctx->path, &sb) == -1)
Thomas Roessler's avatar
Thomas Roessler committed
533
    {
534
      if (errno == ENOENT)
Thomas Roessler's avatar
Thomas Roessler committed
535
      {
536 537 538 539 540 541
#ifdef USE_COMPRESSED
        if (mutt_comp_can_append (ctx))
          ctx->magic = MUTT_COMPRESSED;
        else
#endif
          ctx->magic = DefaultMagic;
542
        flags |= MUTT_APPENDNEW;
Thomas Roessler's avatar
Thomas Roessler committed
543 544 545
      }
      else
      {
546 547
        mutt_perror (ctx->path);
        return -1;
Thomas Roessler's avatar
Thomas Roessler committed
548 549
      }
    }
550 551
    else
      return -1;
Thomas Roessler's avatar
Thomas Roessler committed
552 553
  }

554 555 556
  ctx->mx_ops = mx_get_ops (ctx->magic);
  if (!ctx->mx_ops || !ctx->mx_ops->open_append)
    return -1;
Thomas Roessler's avatar
Thomas Roessler committed
557

558
  return ctx->mx_ops->open_append (ctx, flags);
Thomas Roessler's avatar
Thomas Roessler committed
559 560 561 562 563 564
}

/*
 * open a mailbox and parse it
 *
 * Args:
565 566 567 568
 *	flags	MUTT_NOSORT	do not sort mailbox
 *		MUTT_APPEND	open mailbox for appending
 *		MUTT_READONLY	open mailbox in read-only mode
 *		MUTT_QUIET		only print error messages
569
 *		MUTT_PEEK		revert atime where applicable
Thomas Roessler's avatar
Thomas Roessler committed
570 571 572 573 574 575 576 577 578 579
 *	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));
580

Thomas Roessler's avatar
Thomas Roessler committed
581
  ctx->path = safe_strdup (path);
582 583 584 585 586 587
  if (!ctx->path)
  {
    if (!pctx)
      FREE (&ctx);
    return NULL;
  }
588 589
  if (! (ctx->realpath = realpath (ctx->path, NULL)) )
    ctx->realpath = safe_strdup (ctx->path);
Thomas Roessler's avatar
Thomas Roessler committed
590 591

  ctx->msgnotreadyet = -1;
592
  ctx->collapsed = 0;
Rocco Rutte's avatar
Rocco Rutte committed
593 594 595 596

  for (rc=0; rc < RIGHTSMAX; rc++)
    mutt_bit_set(ctx->rights,rc);

597
  if (flags & MUTT_QUIET)
Thomas Roessler's avatar
Thomas Roessler committed
598
    ctx->quiet = 1;
599
  if (flags & MUTT_READONLY)
Thomas Roessler's avatar
Thomas Roessler committed
600
    ctx->readonly = 1;
601 602
  if (flags & MUTT_PEEK)
    ctx->peekonly = 1;
Thomas Roessler's avatar
Thomas Roessler committed
603

604
  if (flags & (MUTT_APPEND|MUTT_NEWFOLDER))
Thomas Roessler's avatar
Thomas Roessler committed
605
  {
606
    if (mx_open_mailbox_append (ctx, flags) != 0)
Thomas Roessler's avatar
Thomas Roessler committed
607 608 609
    {
      mx_fastclose_mailbox (ctx);
      if (!pctx)
610
	FREE (&ctx);
Thomas Roessler's avatar
Thomas Roessler committed
611 612 613 614 615
      return NULL;
    }
    return ctx;
  }

Thomas Roessler's avatar
Thomas Roessler committed
616
  ctx->magic = mx_get_magic (path);
617
  ctx->mx_ops = mx_get_ops (ctx->magic);
Thomas Roessler's avatar
Thomas Roessler committed
618

619
  if (ctx->magic <= 0 || !ctx->mx_ops)
Thomas Roessler's avatar
Thomas Roessler committed
620
  {
621
    if (ctx->magic == -1)
622
      mutt_perror(path);
623 624
    else if (ctx->magic == 0 || !ctx->mx_ops)
      mutt_error (_("%s is not a mailbox."), path);
625

Thomas Roessler's avatar
Thomas Roessler committed
626 627 628 629
    mx_fastclose_mailbox (ctx);
    if (!pctx)
      FREE (&ctx);
    return (NULL);
Thomas Roessler's avatar
Thomas Roessler committed
630
  }
631 632 633

  mutt_make_label_hash (ctx);

Thomas Roessler's avatar
Thomas Roessler committed
634 635 636 637 638 639 640 641
  /* 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);

  if (!ctx->quiet)
642
    mutt_message (_("Reading %s..."), ctx->path);
Thomas Roessler's avatar
Thomas Roessler committed
643

644
  rc = ctx->mx_ops->open(ctx);
Thomas Roessler's avatar
Thomas Roessler committed
645 646 647

  if (rc == 0)
  {
648
    if ((flags & MUTT_NOSORT) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
649 650 651 652 653 654 655 656 657 658 659 660 661 662
    {
      /* 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)
663
      FREE (&ctx);
Thomas Roessler's avatar
Thomas Roessler committed
664 665 666 667 668 669 670 671 672 673
  }

  unset_option (OPTFORCEREFRESH);
  return (ctx);
}

/* free up memory associated with the mailbox context */
void mx_fastclose_mailbox (CONTEXT *ctx)
{
  int i;
674 675 676 677 678
#ifdef HAVE_UTIMENSAT
    struct timespec ts[2];
#else
    struct utimbuf ut;
#endif /* HAVE_UTIMENSAT */
679 680 681

  if(!ctx) 
    return;
682

683
  /* fix up the times so buffy won't get confused */
684 685 686 687 688 689 690 691 692 693
  if (ctx->peekonly && ctx->path &&
      (mutt_timespec_compare (&ctx->mtime, &ctx->atime) > 0))
  {
#ifdef HAVE_UTIMENSAT
    ts[0] = ctx->atime;
    ts[1] = ctx->mtime;
    utimensat (0, ctx->path, ts, 0);
#else
    ut.actime  = ctx->atime.tv_sec;
    ut.modtime = ctx->mtime.tv_sec;
694
    utime (ctx->path, &ut);
695
#endif /* HAVE_UTIMENSAT */
696 697
  }

698 699
  /* never announce that a mailbox we've just left has new mail. #3290
   * XXX: really belongs in mx_close_mailbox, but this is a nice hook point */
700
  if (!ctx->peekonly)
701
    mutt_buffy_setnotified(ctx->path);
702

703 704
  if (ctx->mx_ops)
    ctx->mx_ops->close (ctx);
705

706 707 708 709
#ifdef USE_COMPRESSED
  mutt_free_compress_info (ctx);
#endif /* USE_COMPRESSED */

Thomas Roessler's avatar
Thomas Roessler committed
710 711 712 713
  if (ctx->subj_hash)
    hash_destroy (&ctx->subj_hash, NULL);
  if (ctx->id_hash)
    hash_destroy (&ctx->id_hash, NULL);
714
  hash_destroy (&ctx->label_hash, NULL);
715
  mutt_clear_threads (ctx);
Thomas Roessler's avatar
Thomas Roessler committed
716 717
  for (i = 0; i < ctx->msgcount; i++)
    mutt_free_header (&ctx->hdrs[i]);
718 719 720
  FREE (&ctx->hdrs);
  FREE (&ctx->v2r);
  FREE (&ctx->path);
721
  FREE (&ctx->realpath);
722
  FREE (&ctx->pattern);
Thomas Roessler's avatar
Thomas Roessler committed
723 724
  if (ctx->limit_pattern) 
    mutt_pattern_free (&ctx->limit_pattern);
725
  safe_fclose (&ctx->fp);
Thomas Roessler's avatar
Thomas Roessler committed
726 727 728 729
  memset (ctx, 0, sizeof (CONTEXT));
}

/* save changes to disk */
730
static int sync_mailbox (CONTEXT *ctx, int *index_hint)
Thomas Roessler's avatar
Thomas Roessler committed
731
{
732 733
  int rc;

734 735
  if (!ctx->mx_ops || !ctx->mx_ops->sync)
    return -1;
Thomas Roessler's avatar
Thomas Roessler committed
736 737

  if (!ctx->quiet)
738 739
  {
    /* L10N: Displayed before/as a mailbox is being synced */
740
    mutt_message (_("Writing %s..."), ctx->path);
741 742 743 744 745 746 747 748
  }

  rc = ctx->mx_ops->sync (ctx, index_hint);
  if (rc != 0 && !ctx->quiet)
  {
    /* L10N: Displayed if a mailbox sync fails */
    mutt_error (_("Unable to write %s!"), ctx->path);
  }
749

750
  return rc;
Thomas Roessler's avatar
Thomas Roessler committed
751 752
}

753 754 755
/* move deleted mails to the trash folder */
static int trash_append (CONTEXT *ctx)
{
756
  CONTEXT ctx_trash;
757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787
  int i;
  struct stat st, stc;
  int opt_confappend, rc;

  if (!TrashPath || !ctx->deleted ||
      (ctx->magic == MUTT_MAILDIR && option (OPTMAILDIRTRASH)))
    return 0;

  for (i = 0; i < ctx->msgcount; i++)
    if (ctx->hdrs[i]->deleted  && (!ctx->hdrs[i]->purge))
      break;
  if (i == ctx->msgcount)
    return 0; /* nothing to be done */

  /* avoid the "append messages" prompt */
  opt_confappend = option (OPTCONFIRMAPPEND);
  if (opt_confappend)
    unset_option (OPTCONFIRMAPPEND);
  rc = mutt_save_confirm (TrashPath, &st);
  if (opt_confappend)
    set_option (OPTCONFIRMAPPEND);
  if (rc != 0)
  {
    mutt_error _("message(s) not deleted");
    return -1;
  }

  if (lstat (ctx->path, &stc) == 0 && stc.st_ino == st.st_ino
      && stc.st_dev == st.st_dev && stc.st_rdev == st.st_rdev)
    return 0;  /* we are in the trash folder: simple sync */

788 789 790 791 792 793 794 795
#ifdef USE_IMAP
  if (Context->magic == MUTT_IMAP && mx_is_imap (TrashPath))
  {
    if (!imap_fast_trash (Context, TrashPath))
      return 0;
  }
#endif

796
  if (mx_open_mailbox (TrashPath, MUTT_APPEND, &ctx_trash) != NULL)
797 798 799 800 801
  {
    /* continue from initial scan above */
    for (; i < ctx->msgcount ; i++)
      if (ctx->hdrs[i]->deleted  && (!ctx->hdrs[i]->purge))
      {
802
        if (mutt_append_message (&ctx_trash, ctx, ctx->hdrs[i], 0, 0) == -1)
803
        {
804
          mx_close_mailbox (&ctx_trash, NULL);
805 806 807 808
          return -1;
        }
      }

809
    mx_close_mailbox (&ctx_trash, NULL);
810 811 812 813 814 815 816 817 818 819
  }
  else
  {
    mutt_error _("Can't open trash folder");
    return -1;
  }

  return 0;
}

Thomas Roessler's avatar
Thomas Roessler committed
820
/* save changes and close mailbox */
821
int mx_close_mailbox (CONTEXT *ctx, int *index_hint)
Thomas Roessler's avatar
Thomas Roessler committed
822 823
{
  int i, move_messages = 0, purge = 1, read_msgs = 0;
824
  int check;
Thomas Roessler's avatar
Thomas Roessler committed
825 826 827 828 829
  int isSpool = 0;
  CONTEXT f;
  char mbox[_POSIX_PATH_MAX];
  char buf[SHORT_STRING];

830 831
  if (!ctx) return 0;

832 833
  ctx->closing = 1;

834
  if (ctx->readonly || ctx->dontwrite || ctx->append)
Thomas Roessler's avatar
Thomas Roessler committed
835 836 837 838 839 840 841
  {
    mx_fastclose_mailbox (ctx);
    return 0;
  }

  for (i = 0; i < ctx->msgcount; i++)
  {
842 843
    if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read 
        && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
Thomas Roessler's avatar
Thomas Roessler committed
844 845 846
      read_msgs++;
  }

847
  if (read_msgs && quadoption (OPT_MOVE) != MUTT_NO)
Thomas Roessler's avatar
Thomas Roessler committed
848 849 850
  {
    char *p;

851
    if ((p = mutt_find_hook (MUTT_MBOXHOOK, ctx->path)))
Thomas Roessler's avatar
Thomas Roessler committed
852 853 854 855 856 857
    {
      isSpool = 1;
      strfcpy (mbox, p, sizeof (mbox));
    }
    else
    {
Thomas Roessler's avatar
Thomas Roessler committed
858
      strfcpy (mbox, NONULL(Inbox), sizeof (mbox));
Thomas Roessler's avatar
Thomas Roessler committed
859 860 861
      isSpool = mutt_is_spool (ctx->path) && !mutt_is_spool (mbox);
    }

862
    if (isSpool && *mbox)
Thomas Roessler's avatar
Thomas Roessler committed
863
    {
864
      mutt_expand_path (mbox, sizeof (mbox));
865 866
      snprintf (buf, sizeof (buf), _("Move %d read messages to %s?"),
                read_msgs, mbox);
Thomas Roessler's avatar
Thomas Roessler committed
867
      if ((move_messages = query_quadoption (OPT_MOVE, buf)) == -1)
Thomas Roessler's avatar
Thomas Roessler committed
868 869
      {
	ctx->closing = 0;
Thomas Roessler's avatar
Thomas Roessler committed
870
	return (-1);
Thomas Roessler's avatar
Thomas Roessler committed
871
      }
Thomas Roessler's avatar
Thomas Roessler committed
872 873 874
    }
  }

875 876 877 878
  /* 
   * There is no point in asking whether or not to purge if we are
   * just marking messages as "trash".
   */
879
  if (ctx->deleted && !(ctx->magic == MUTT_MAILDIR && option (OPTMAILDIRTRASH)))
Thomas Roessler's avatar
Thomas Roessler committed
880
  {
881 882 883
    snprintf (buf, sizeof (buf), ctx->deleted == 1
	     ? _("Purge %d deleted message?") : _("Purge %d deleted messages?"),
	      ctx->deleted);
Thomas Roessler's avatar
Thomas Roessler committed
884
    if ((purge = query_quadoption (OPT_DELETE, buf)) < 0)
Thomas Roessler's avatar
Thomas Roessler committed
885 886
    {
      ctx->closing = 0;
Thomas Roessler's avatar
Thomas Roessler committed
887
      return (-1);
Thomas Roessler's avatar
Thomas Roessler committed
888
    }
Thomas Roessler's avatar
Thomas Roessler committed
889 890 891 892 893 894
  }

  if (option (OPTMARKOLD))
  {
    for (i = 0; i < ctx->msgcount; i++)
    {
895
      if (!ctx->hdrs[i]->deleted && !ctx->hdrs[i]->old && !ctx->hdrs[i]->read)
896
	mutt_set_flag (ctx, ctx->hdrs[i], MUTT_OLD, 1);
Thomas Roessler's avatar
Thomas Roessler committed
897 898 899 900 901
    }
  }

  if (move_messages)
  {
902 903
    if (!ctx->quiet)
      mutt_message (_("Moving read messages to %s..."), mbox);
Thomas Roessler's avatar
Thomas Roessler committed
904

905 906 907 908
#ifdef USE_IMAP
    /* try to use server-side copy first */
    i = 1;
    
909
    if (ctx->magic == MUTT_IMAP && mx_is_imap (mbox))
Thomas Roessler's avatar
Thomas Roessler committed
910
    {
911 912
      /* tag messages for moving, and clear old tags, if any */
      for (i = 0; i < ctx->msgcount; i++)
913 914
	if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
            && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED))) 
915
	  ctx->hdrs[i]->tagged = 1;
916
	else
917 918 919 920 921 922 923 924
	  ctx->hdrs[i]->tagged = 0;
      
      i = imap_copy_messages (ctx, NULL, mbox, 1);
    }
    
    if (i == 0) /* success */
      mutt_clear_error ();
    else if (i == -1) /* horrible error, bail */
Thomas Roessler's avatar
Thomas Roessler committed
925 926
    {
      ctx->closing=0;
927
      return -1;
Thomas Roessler's avatar
Thomas Roessler committed
928
    }
929 930 931
    else /* use regular append-copy mode */
#endif
    {
932
      if (mx_open_mailbox (mbox, MUTT_APPEND, &f) == NULL)
Thomas Roessler's avatar
Thomas Roessler committed
933 934
      {
	ctx->closing = 0;
935
	return -1;
Thomas Roessler's avatar
Thomas Roessler committed
936
      }
937 938 939

      for (i = 0; i < ctx->msgcount; i++)
      {
940 941
	if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
            && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
942 943 944
        {
	  if (mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN) == 0)
	  {
945
	    mutt_set_flag (ctx, ctx->hdrs[i], MUTT_DELETE, 1);
946
	    mutt_set_flag (ctx, ctx->hdrs[i], MUTT_PURGE, 1);
947 948 949
	  }
	  else
	  {
950
	    mx_close_mailbox (&f, NULL);
Thomas Roessler's avatar
Thomas Roessler committed
951
	    ctx->closing = 0;
952 953
	    return -1;
	  }
954
	}
Thomas Roessler's avatar
Thomas Roessler committed
955
      }
956
    
957
      mx_close_mailbox (&f, NULL);
Thomas Roessler's avatar
Thomas Roessler committed
958
    }
959
    
Thomas Roessler's avatar
Thomas Roessler committed
960 961 962
  }
  else if (!ctx->changed && ctx->deleted == 0)
  {
963 964
    if (!ctx->quiet)
      mutt_message _("Mailbox is unchanged.");
965
    if (ctx->magic == MUTT_MBOX || ctx->magic == MUTT_MMDF)
966
      mbox_reset_atime (ctx, NULL);
Thomas Roessler's avatar
Thomas Roessler committed
967 968 969
    mx_fastclose_mailbox (ctx);
    return 0;
  }
970 971 972 973 974 975 976 977 978 979 980

  /* copy mails to the trash before expunging */
  if (purge && ctx->deleted && mutt_strcmp (ctx->path, TrashPath))
  {
    if (trash_append (ctx) != 0)
    {
      ctx->closing = 0;
      return -1;
    }
  }

981 982
#ifdef USE_IMAP
  /* allow IMAP to preserve the deleted flag across sessions */
983
  if (ctx->magic == MUTT_IMAP)
984
  {
985
    if ((check = imap_sync_mailbox (ctx, purge, index_hint)) != 0)
Thomas Roessler's avatar
Thomas Roessler committed
986 987
    {
      ctx->closing = 0;
988
      return check;
Thomas Roessler's avatar
Thomas Roessler committed
989
    }
990
  }
Thomas Roessler's avatar
Thomas Roessler committed
991
  else
992
#endif
Thomas Roessler's avatar
Thomas Roessler committed
993
  {
Thomas Roessler's avatar
Thomas Roessler committed
994 995 996
    if (!purge)
    {
      for (i = 0; i < ctx->msgcount; i++)
997
      {
Thomas Roessler's avatar
Thomas Roessler committed
998
        ctx->hdrs[i]->deleted = 0;
999 1000
        ctx->hdrs[i]->purge = 0;
      }
Thomas Roessler's avatar
Thomas Roessler committed
1001 1002
      ctx->deleted = 0;
    }
Thomas Roessler's avatar
Thomas Roessler committed
1003

Thomas Roessler's avatar
Thomas Roessler committed
1004 1005
    if (ctx->changed || ctx->deleted)
    {
1006
      if ((check = sync_mailbox (ctx, index_hint)) != 0)
Thomas Roessler's avatar
Thomas Roessler committed
1007 1008
      {
	ctx->closing = 0;
1009
	return check;
Thomas Roessler's avatar
Thomas Roessler committed
1010
      }
Thomas Roessler's avatar
Thomas Roessler committed
1011
    }
1012
  }
Thomas Roessler's avatar
Thomas Roessler committed
1013

1014 1015 1016 1017 1018 1019 1020 1021 1022
  if (!ctx->quiet)
  {
    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);
  }
Thomas Roessler's avatar
Thomas Roessler committed
1023

1024
  if (ctx->msgcount == ctx->deleted &&
1025
      (ctx->magic == MUTT_MMDF || ctx->magic == MUTT_MBOX) &&
1026 1027
      !mutt_is_spool(ctx->path) && !option (OPTSAVEEMPTY))
    mx_unlink_empty (ctx->path);
Thomas Roessler's avatar
Thomas Roessler committed
1028

1029
#ifdef USE_SIDEBAR
1030 1031
  if (purge && ctx->deleted)
  {
1032 1033
    int orig_msgcount = ctx->msgcount;

1034 1035 1036 1037 1038 1039 1040 1041
    for (i = 0; i < ctx->msgcount; i++)
    {
      if (ctx->hdrs[i]->deleted && !ctx->hdrs[i]->read)
        ctx->unread--;
      if (ctx->hdrs[i]->deleted && ctx->hdrs[i]->flagged)
        ctx->flagged--;
    }
    ctx->msgcount -= ctx->deleted;
1042 1043
    mutt_sb_set_buffystats (ctx);
    ctx->msgcount = orig_msgcount;
1044
  }
1045 1046
#endif

Thomas Roessler's avatar
Thomas Roessler committed
1047 1048 1049 1050 1051
  mx_fastclose_mailbox (ctx);

  return 0;
}

1052 1053 1054

/* update a Context structure's internal tables. */

1055
void mx_update_tables(CONTEXT *ctx, int committing)
1056
{
1057
  int i, j, padding;
1058 1059 1060 1061 1062 1063 1064 1065 1066 1067
  
  /* 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;
1068
  padding = mx_msg_padding_size (ctx);
1069 1070 1071
#define this_body ctx->hdrs[j]->content
  for (i = 0, j = 0; i < ctx->msgcount; i++)
  {
1072
    if ((committing && (!ctx->hdrs[i]->deleted || 
1073
			(ctx->magic == MUTT_MAILDIR && option (OPTMAILDIRTRASH)))) ||
1074
	(!committing && ctx->hdrs[i]->active))
1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
    {
      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 -
1087
	              this_body->hdr_offset + padding;
1088
      }
1089

1090
      if (committing)
1091
	ctx->hdrs[j]->changed = 0;
1092
      else if (ctx->hdrs[j]->changed)
1093
	ctx->changed = 1;
1094
      
1095
      if (!committing || (ctx->magic == MUTT_MAILDIR && option (OPTMAILDIRTRASH)))
1096 1097 1098 1099 1100
      {
	if (ctx->hdrs[j]->deleted)
	  ctx->deleted++;
      }

1101 1102 1103 1104 1105 1106 1107 1108 1109 1110
      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++;
      } 
1111

1112 1113 1114 1115
      j++;
    }
    else
    {
1116
      if (ctx->magic == MUTT_MH || ctx->magic == MUTT_MAILDIR)
1117 1118 1119 1120
	ctx->size -= (ctx->hdrs[i]->content->length +
		      ctx->hdrs[i]->content->offset -
		      ctx->hdrs[i]->content->hdr_offset);
      /* remove message from the hash tables */
1121
      if (ctx->subj_hash && ctx->hdrs[i]->env->real_subj)
1122
	hash_delete (ctx->subj_hash, ctx->hdrs[i]->env->real_subj, ctx->hdrs[i], NULL);
1123
      if (ctx->id_hash && ctx->hdrs[i]->env->message_id)
1124
	hash_delete (ctx->id_hash, ctx->hdrs[i]->env->message_id, ctx->hdrs[i], NULL);
1125
      mutt_label_hash_remove (ctx, ctx->hdrs[i]);
1126 1127 1128 1129 1130 1131 1132
      /* The path mx_check_mailbox() -> imap_check_mailbox() ->
       *          imap_expunge_mailbox() -> mx_update_tables()
       * can occur before a call to mx_sync_mailbox(), resulting in
       * last_tag being stale if it's not reset here.
       */
      if (ctx->last_tag == ctx->hdrs[i])
        ctx->last_tag = NULL;
1133 1134 1135 1136 1137 1138 1139 1140
      mutt_free_header (&ctx->hdrs[i]);
    }
  }
#undef this_body
  ctx->msgcount = j;
}


Thomas Roessler's avatar
Thomas Roessler committed
1141 1142 1143 1144 1145 1146
/* save changes to mailbox
 *
 * return values:
 *	0		success
 *	-1		error
 */
1147
int mx_sync_mailbox (CONTEXT *ctx, int *index_hint)
Thomas Roessler's avatar
Thomas Roessler committed
1148
{
1149
  int rc, i;
1150
  int purge = 1;
1151
  int msgcount, deleted;
Thomas Roessler's avatar
Thomas Roessler committed
1152 1153 1154 1155 1156 1157

  if (ctx->dontwrite)
  {
    char buf[STRING], tmp[STRING];
    if (km_expand_key (buf, sizeof(buf),
                       km_find_func (MENU_MAIN, OP_TOGGLE_WRITE)))
1158
      snprintf (tmp, sizeof(tmp), _(" Press '%s' to toggle write"), buf);
Thomas Roessler's avatar
Thomas Roessler committed
1159
    else
1160
      strfcpy (tmp, _("Use 'toggle-write' to re-enable write!"), sizeof(tmp));
Thomas Roessler's avatar
Thomas Roessler committed
1161

1162
    mutt_error (_("Mailbox is marked unwritable. %s"), tmp);
Thomas Roessler's avatar
Thomas Roessler committed
1163 1164 1165 1166
    return -1;
  }
  else if (ctx->readonly)
  {
1167
    mutt_error _("Mailbox is read-only.");
Thomas Roessler's avatar
Thomas Roessler committed
1168 1169 1170 1171 1172
    return -1;
  }

  if (!ctx->changed && !ctx->deleted)
  {
1173 1174
    if (!ctx->quiet)
      mutt_message _("Mailbox is unchanged.");
Thomas Roessler's avatar
Thomas Roessler committed
1175 1176 1177 1178 1179 1180 1181
    return (0);
  }

  if (ctx->deleted)
  {
    char buf[SHORT_STRING];

1182 1183 1184
    snprintf (buf, sizeof (buf), ctx->deleted == 1
	     ? _("Purge %d deleted message?") : _("Purge %d deleted messages?"),
	      ctx->deleted);
1185
    if ((purge = query_quadoption (OPT_DELETE, buf)) < 0)
Thomas Roessler's avatar
Thomas Roessler committed
1186
      return (-1);
1187
    else if (purge == MUTT_NO)
Thomas Roessler's avatar
Thomas Roessler committed
1188 1189 1190
    {
      if (!ctx->changed)
	return 0; /* nothing to do! */
1191
      /* let IMAP servers hold on to D flags */
1192
      if (ctx->magic != MUTT_IMAP)
1193 1194
      {
        for (i = 0 ; i < ctx->msgcount ; i++)
1195
        {
1196
          ctx->hdrs[i]->deleted = 0;
1197 1198
          ctx->hdrs[i]->purge = 0;
        }
1199 1200
        ctx->deleted = 0;
      }
Thomas Roessler's avatar
Thomas Roessler committed
1201
    }
1202 1203
    else if (ctx->last_tag && ctx->last_tag->deleted)
      ctx->last_tag = NULL; /* reset last tagged msg now useless */
Thomas Roessler's avatar
Thomas Roessler committed
1204 1205
  }

1206 1207 1208 1209 1210
  /* really only for IMAP - imap_sync_mailbox results in a call to
   * mx_update_tables, so ctx->deleted is 0 when it comes back */
  msgcount = ctx->msgcount;
  deleted = ctx->deleted;

1211 1212 1213 1214 1215 1216
  if (purge && ctx->deleted && mutt_strcmp (ctx->path, TrashPath))
  {
    if (trash_append (ctx) != 0)
      return -1;
  }

1217
#ifdef USE_IMAP
1218
  if (ctx->magic == MUTT_IMAP)
1219
    rc = imap_sync_mailbox (ctx, purge, index_hint);
1220 1221
  else
#endif
1222
    rc = sync_mailbox (ctx, index_hint);
1223
  if (rc == 0)
Thomas Roessler's avatar
Thomas Roessler committed
1224
  {
1225
#ifdef USE_IMAP
1226
    if (ctx->magic == MUTT_IMAP && !purge)
1227 1228 1229 1230
    {
      if (!ctx->quiet)
        mutt_message _("Mailbox checkpointed.");
    }
1231 1232
    else
#endif
1233 1234 1235 1236 1237
    {
      if (!ctx->quiet)
	mutt_message (_("%d kept, %d deleted."), msgcount - deleted,
		      deleted);
    }
1238

1239 1240
    mutt_sleep (0);
    
Thomas Roessler's avatar
Thomas Roessler committed
1241
    if (ctx->msgcount == ctx->deleted &&
1242
	(ctx->magic == MUTT_MBOX || ctx->magic == MUTT_MMDF) &&
Thomas Roessler's avatar
Thomas Roessler committed
1243
	!mutt_is_spool (ctx->path) && !option (OPTSAVEEMPTY))
Thomas Roessler's avatar
Thomas Roessler committed
1244 1245 1246 1247 1248 1249
    {
      unlink (ctx->path);
      mx_fastclose_mailbox (ctx);
      return 0;
    }

1250
    /* if we haven't deleted any messages, we don't need to resort */
Thomas Roessler's avatar
Thomas Roessler committed
1251 1252 1253 1254 1255 1256
    /* ... except for certain folder formats which need "unsorted" 
     * sort order in order to synchronize folders.
     * 
     * MH and maildir are safe.  mbox-style seems to need re-sorting,
     * at least with the new threading code.
     */
1257
    if (purge || (ctx->magic != MUTT_MAILDIR && ctx->magic != MUTT_MH))
1258
    {
1259
      /* IMAP does this automatically after handling EXPUNGE */
1260
      if (ctx->magic != MUTT_IMAP)
1261
      {
1262
	mx_update_tables (ctx, 1);
1263 1264
	mutt_sort_headers (ctx, 1); /* rethread from scratch */
      }
1265
    }
Thomas Roessler's avatar
Thomas Roessler committed
1266 1267 1268 1269 1270 1271
  }

  return (rc);
}

/* args:
Ondřej Bílka's avatar
Ondřej Bílka committed
1272
 *	dest	destination mailbox
Thomas Roessler's avatar
Thomas Roessler committed
1273 1274 1275 1276 1277 1278
 *	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)
{
  ADDRESS *p = NULL;
1279
  MESSAGE *msg;
Thomas Roessler's avatar
Thomas Roessler committed
1280

1281
  if (!dest->mx_ops || !dest->mx_ops->open_new_msg)
Thomas Roessler's avatar
Thomas Roessler committed
1282 1283
  {
      dprint (1, (debugfile, "mx_open_new_message(): function unimplemented for mailbox type %d.\n",
1284 1285
              dest->magic));
      return NULL;
Thomas Roessler's avatar
Thomas Roessler committed
1286 1287 1288 1289 1290
  }

  msg = safe_calloc (1, sizeof (MESSAGE));
  msg->write = 1;

1291 1292 1293 1294 1295
  if (hdr)
  {
    msg->flags.flagged = hdr->flagged;
    msg->flags.replied = hdr->replied;
    msg->flags.read    = hdr->read;
1296
    msg->flags.draft   = (flags & MUTT_SET_DRAFT) ? 1 : 0;
Michael Elkins's avatar
Michael Elkins committed
1297
    msg->received = hdr->received;
1298
  }
Michael Elkins's avatar
Michael Elkins committed
1299 1300 1301

  if(msg->received == 0)
    time(&msg->received);
1302

1303
  if (dest->mx_ops->open_new_msg (msg, dest, hdr) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
1304
  {
1305
    if (dest->magic == MUTT_MMDF)
Thomas Roessler's avatar
Thomas Roessler committed
1306 1307
      fputs (MMDF_SEP, msg->fp);

1308
    if ((dest->magic == MUTT_MBOX || dest->magic ==  MUTT_MMDF) &&
1309
	flags & MUTT_ADD_FROM)