/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2003, 2004, 2005, 2006, 2007, 2008,
2009, 2010 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301 USA */
/* First draft by Alain Magloire.
* Completely rewritten by Sergey Poznyakoff.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <mbox0.h>
#include <mailutils/cstr.h>
#include <mailutils/io.h>
#include <mailutils/filter.h>
#define ATTRIBUTE_IS_DELETED(flag) (flag & MU_ATTRIBUTE_DELETED)
#define ATTRIBUTE_IS_EQUAL(flag1, flag2) (flag1 == flag2)
static int mbox_is_updated (mu_mailbox_t mailbox);
static int mbox_expunge0 (mu_mailbox_t mailbox, int remove_deleted);
/* Free all ressources associated with Unix concrete mailbox. */
static void
mbox_destroy (mu_mailbox_t mailbox)
{
if (mailbox->data)
{
size_t i;
mbox_data_t mud = mailbox->data;
MU_DEBUG1 (mailbox->debug, MU_DEBUG_TRACE1,
"mbox_destroy (%s)\n", mud->name);
mu_monitor_wrlock (mailbox->monitor);
for (i = 0; i < mud->umessages_count; i++)
{
mbox_message_t mum = mud->umessages[i];
if (mum)
{
mu_message_destroy (&mum->message, mum);
free (mum);
}
}
if (mud->umessages)
free (mud->umessages);
if (mud->name)
free (mud->name);
free (mud);
mailbox->data = NULL;
mu_monitor_unlock (mailbox->monitor);
}
}
/* Open the file. For MU_STREAM_READ, the code tries mmap() first and fall
back to normal file. */
static int
mbox_open (mu_mailbox_t mailbox, int flags)
{
mbox_data_t mud = mailbox->data;
int status = 0;
if (mud == NULL)
return EINVAL;
mailbox->flags = flags;
/* Get a stream. */
if (mailbox->stream == NULL)
{
/* We do not try to mmap for CREAT or APPEND, it is not supported. */
status = (flags & MU_STREAM_CREAT)
|| (mailbox->flags & MU_STREAM_APPEND);
/* Try to mmap () the file first. */
if (status == 0)
{
status = mu_mapfile_stream_create (&mailbox->stream, mud->name,
mailbox->flags);
}
/* Fall back to normal file if mmap() failed. */
if (status)
status = mu_file_stream_create (&mailbox->stream, mud->name,
mailbox->flags);
if (status)
return status;
}
mu_stream_set_buffer (mailbox->stream, mu_buffer_full, 0);
MU_DEBUG2 (mailbox->debug, MU_DEBUG_TRACE1, "mbox_open (%s, 0x%x)\n",
mud->name, mailbox->flags);
if (mailbox->locker == NULL &&
(flags & (MU_STREAM_WRITE | MU_STREAM_APPEND | MU_STREAM_CREAT)))
status = mu_locker_create (&mailbox->locker, mud->name, 0);
return status;
}
static int
mbox_close (mu_mailbox_t mailbox)
{
mbox_data_t mud = mailbox->data;
size_t i;
if (mud == NULL)
return EINVAL;
MU_DEBUG1 (mailbox->debug, MU_DEBUG_TRACE1,
"mbox_close (%s)\n", mud->name);
/* Make sure that we do not hold any file locking. */
mu_locker_unlock (mailbox->locker);
/* Alain: I'm not sure on the right approach especially if the client is
working in disconnected mode, where it can mu_mailbox_close/
mu_mailbox_open for each request, maybe we should keep them for a while.
Sergey: No, it actually breaks reopening the mailbox. We should make
sure that the sequence mu_mailbox_open();mu_mailbox_close() catches all
the changes that might have been done to the mailbox */
mu_monitor_wrlock (mailbox->monitor);
/* Before closing we need to remove all the messages
- to reclaim the memory
- to prepare for another scan. */
for (i = 0; i < mud->umessages_count; i++)
{
mbox_message_t mum = mud->umessages[i];
/* Destroy attached messages. */
if (mum)
{
mu_message_destroy (&(mum->message), mum);
free (mum);
}
}
if (mud->umessages)
free (mud->umessages);
mud->umessages = NULL;
mud->messages_count = mud->umessages_count = 0;
mud->size = 0;
mud->uidvalidity = 0;
mud->uidnext = 0;
mu_monitor_unlock (mailbox->monitor);
return mu_stream_close (mailbox->stream);
}
static int
mbox_remove (mu_mailbox_t mailbox)
{
mbox_data_t mud = mailbox->data;
MU_DEBUG1 (mailbox->debug, MU_DEBUG_TRACE1,
"mbox_remove (%s)\n", mud->name);
return unlink (mud->name) == 0 ? 0 : errno;
}
/* Cover function that calls the real thing, mbox_scan(), with
notification set. */
static int
mbox_scan (mu_mailbox_t mailbox, size_t msgno, size_t *pcount)
{
size_t i;
mbox_data_t mud = mailbox->data;
MU_DEBUG1 (mailbox->debug, MU_DEBUG_TRACE1, "mbox_scan (%s)\n", mud->name);
if (! mbox_is_updated (mailbox))
return mbox_scan0 (mailbox, msgno, pcount, 1);
/* Since the mailbox is already updated fake the scan. */
if (msgno > 0)
msgno--; /* The fist message is number "1", decrement for the C array. */
for (i = msgno; i < mud->messages_count; i++)
{
size_t tmp = i;
if (mu_observable_notify (mailbox->observable, MU_EVT_MESSAGE_ADD,
&tmp) != 0)
break;
/* FIXME: Hardcoded value! Must be configurable */
if (((i + 1) % 50) == 0)
mu_observable_notify (mailbox->observable, MU_EVT_MAILBOX_PROGRESS,
NULL);
}
*pcount = mud->messages_count;
return 0;
}
/* Alain: How to handle a shrink ? meaning, the &^$^@%#@^& user start two
browsers and deleted emails in one session. My views is that we should
scream bloody murder and hunt them with a machette. But for now just play
dumb, but maybe the best approach is to pack our things and leave
.i.e exit()/abort().
Sergey: Nope, we shouldn't abort. Handling it with MU_EVT_MAILBOX_CORRUPT
is sensible enough. The caller must decide what's the best approach
in this case. The simplest one is reopening the mailbox. Imap4d currently
does that.
And yet another thought: if a user reads his mail with a ... browser (!),
he is incurable anyway. */
static int
mbox_is_updated (mu_mailbox_t mailbox)
{
mu_off_t size = 0;
mbox_data_t mud = mailbox->data;
if (mu_stream_size (mailbox->stream, &size) != 0)
return 1;
if (size < mud->size)
{
mu_observable_notify (mailbox->observable, MU_EVT_MAILBOX_CORRUPT,
mailbox);
/* And be verbose. ? */
mu_diag_output (MU_DIAG_EMERG, _("mailbox corrupted, shrank in size"));
/* FIXME: should I crash? */
return 0;
}
return (mud->size == size);
}
static int
mbox_expunge (mu_mailbox_t mailbox)
{
return mbox_expunge0 (mailbox, 1);
}
static int
mbox_sync (mu_mailbox_t mailbox)
{
return mbox_expunge0 (mailbox, 0);
}
static int
mbox_message_uid (mu_message_t msg, size_t *puid)
{
mbox_message_t mum = mu_message_get_owner (msg);
if (puid)
*puid = mum->uid;
return 0;
}
static int
mbox_message_qid (mu_message_t msg, mu_message_qid_t *pqid)
{
mbox_message_t mum = mu_message_get_owner (msg);
return mu_asprintf (pqid, "%lu", (unsigned long) mum->envel_from);
}
/* Mbox message headers */
#ifdef MU_MBOX_SET_HEADER
static int
mbox_header_fill (void *data, char **pbuf, size_t *plen)
{
/* FIXME: This would be almost exact copy of _header_fill from
mailbox/message.c. So there's perhaps no use defining this
method. Instead we prefer to use the default header machinery,
which relies on the message stream (see #else below). */
abort ();
}
static int
_msg_header_setup (mu_message_t msg, mbox_message_t mum)
{
mu_header_t header;
int status = mu_header_create (&header, NULL, 0);
if (status == 0)
{
mu_header_set_fill (header, mbox_header_fill, msg);
mu_message_set_header (msg, header, mum);
}
return status;
}
#else
static int
_msg_stream_setup (mu_message_t msg, mbox_message_t mum)
{
mu_stream_t stream;
int status;
status = mu_streamref_create_abridged (&stream,
mum->mud->mailbox->stream,
mum->envel_from_end
|