Commit 28d63fd9 28d63fd9026b85abd7f244d043d41ee6b7434140 by Sergey Poznyakoff

Re-implemented via AMD.

1 parent 5ba7cf41
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
Copyright (C) 1999, 2000, 2001, 2002, 2003,
2004 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
......@@ -61,951 +62,65 @@
#include <mailutils/observer.h>
#include <mailbox0.h>
#include <registrar0.h>
#include <amd.h>
#define MAX_OPEN_STREAMS 16
/* Notifications ADD_MESG. */
#define DISPATCH_ADD_MSG(mbox,mhd) \
do \
{ \
int bailing = 0; \
monitor_unlock (mbox->monitor); \
if (mbox->observable) \
bailing = observable_notify (mbox->observable, MU_EVT_MESSAGE_ADD); \
if (bailing != 0) \
{ \
if (pcount) \
*pcount = (mhd)->msg_count; \
locker_unlock (mbox->locker); \
return EINTR; \
} \
monitor_wrlock (mbox->monitor); \
} while (0);
/* Note: In this particular implementation the message sequence number
serves also as its UID. This allows to avoid many problems related
to keeping the uids in the headers of the messages. */
struct _mh_data;
struct _mh_message
{
struct _mh_message *next;
struct _mh_message *prev;
stream_t stream; /* Associated file stream */
off_t body_start; /* Offset of body start in the message file */
off_t body_end; /* Offset of body end (size of file, effectively)*/
size_t seq_number; /* message sequence number */
int attr_flags; /* Attribute flags */
int deleted; /* Was the message originally deleted */
time_t mtime; /* Time of last modification */
size_t header_lines; /* Number of lines in the header part */
size_t body_lines; /* Number of lines in the body */
message_t message; /* Corresponding message_t */
struct _mh_data *mhd; /* Back pointer. */
};
struct _mh_data
{
/* List of messages: */
size_t msg_count; /* number of messages in the list */
struct _mh_message *msg_head; /* First */
struct _mh_message *msg_tail; /* Last */
unsigned long uidvalidity;
char *name; /* Directory name */
/* Pool of open message streams */
struct _mh_message *msg_pool[MAX_OPEN_STREAMS];
int pool_first; /* Index to the first used entry in msg_pool */
int pool_last; /* Index to the first free entry in msg_pool */
time_t mtime; /* Time of last modification */
mailbox_t mailbox; /* Back pointer. */
};
static void mh_destroy __P((mailbox_t mailbox));
static int mh_open __P ((mailbox_t, int));
static int mh_close __P ((mailbox_t));
static int mh_get_message __P ((mailbox_t, size_t, message_t *));
static int mh_append_message __P ((mailbox_t, message_t));
static int mh_messages_count __P ((mailbox_t, size_t *));
static int mh_messages_recent __P ((mailbox_t, size_t *));
static int mh_message_unseen __P ((mailbox_t, size_t *));
static int mh_expunge __P ((mailbox_t));
static int mh_save_attributes __P ((mailbox_t));
static int mh_uidvalidity __P ((mailbox_t, unsigned long *));
static int mh_uidnext __P ((mailbox_t, size_t *));
static int mh_scan __P ((mailbox_t, size_t, size_t *));
static int mh_scan0 __P ((mailbox_t mailbox, size_t msgno, size_t *pcount,
int do_notify));
static int mh_is_updated __P ((mailbox_t));
static int mh_get_size __P ((mailbox_t, off_t *));
static int mh_body_read __P ((stream_t, char *, size_t, off_t, size_t *));
static int mh_body_readline __P ((stream_t, char *, size_t, off_t, size_t *));
static int mh_stream_size __P ((stream_t stream, off_t *psize));
static int mh_body_size __P ((body_t body, size_t *psize));
static int mh_body_lines __P ((body_t body, size_t *plines));
static int mh_message_uid __P ((message_t msg, size_t *puid));
static int mh_message_stream_open __P((struct _mh_message *mhm));
static void mh_message_stream_close __P((struct _mh_message *mhm));
static int mh_header_fill __P((header_t header, char *buffer, size_t len,
off_t off, size_t *pnread));
static int mh_header_size __P((header_t header, size_t *psize));
static int mh_header_lines __P((header_t header, size_t *plines));
static int mh_get_attr_flags __P((attribute_t attr, int *pflags));
static int mh_set_attr_flags __P((attribute_t attr, int flags));
static int mh_unset_attr_flags __P((attribute_t attr, int flags));
static void _mh_message_insert __P((struct _mh_data *mhd,
struct _mh_message *msg));
static void _mh_message_delete __P((struct _mh_data *mhd,
struct _mh_message *msg));
static int mh_pool_open __P((struct _mh_message *mhm));
static int mh_pool_open_count __P((struct _mh_data *mhd));
static struct _mh_message **mh_pool_lookup __P((struct _mh_message *mhm));
static int mh_envelope_date __P((envelope_t envelope, char *buf, size_t len,
size_t *psize));
static int mh_envelope_sender __P((envelope_t envelope, char *buf, size_t len,
size_t *psize));
/* Should be in other header file. */
extern int mh_message_number __P ((message_t msg, size_t *pnum));
/* Return filename for the message.
NOTE: Allocates memory. */
static char *
_mh_message_name (struct _mh_message *mhm, int deleted)
{
char *filename;
size_t len = strlen (mhm->mhd->name) + 32;
filename = malloc (len);
if (deleted)
snprintf (filename, len, "%s/,%lu", mhm->mhd->name,
(unsigned long) mhm->seq_number);
else
snprintf (filename, len, "%s/%lu", mhm->mhd->name,
(unsigned long) mhm->seq_number);
return filename;
}
int
_mailbox_mh_init (mailbox_t mailbox)
{
struct _mh_data *mhd;
size_t name_len;
if (mailbox == NULL)
return EINVAL;
mhd = mailbox->data = calloc (1, sizeof (*mhd));
if (mailbox->data == NULL)
return ENOMEM;
/* Back pointer. */
mhd->mailbox = mailbox;
url_get_path (mailbox->url, NULL, 0, &name_len);
mhd->name = calloc (name_len + 1, sizeof (char));
if (mhd->name == NULL)
{
free (mhd);
mailbox->data = NULL;
return ENOMEM;
}
url_get_path (mailbox->url, mhd->name, name_len + 1, NULL);
/* Overloading the defaults. */
mailbox->_destroy = mh_destroy;
mailbox->_open = mh_open;
mailbox->_close = mh_close;
/* Overloading of the entire mailbox object methods. */
mailbox->_get_message = mh_get_message;
mailbox->_append_message = mh_append_message;
mailbox->_messages_count = mh_messages_count;
mailbox->_messages_recent = mh_messages_recent;
mailbox->_message_unseen = mh_message_unseen;
mailbox->_expunge = mh_expunge;
mailbox->_save_attributes = mh_save_attributes;
mailbox->_uidvalidity = mh_uidvalidity;
mailbox->_uidnext = mh_uidnext;
mailbox->_scan = mh_scan;
mailbox->_is_updated = mh_is_updated;
mailbox->_get_size = mh_get_size;
/* Set our properties. */
{
property_t property = NULL;
mailbox_get_property (mailbox, &property);
property_set_value (property, "TYPE", "MH", 1);
}
MAILBOX_DEBUG1 (mailbox, MU_DEBUG_TRACE, "mh_init(%s)\n", mhd->name);
return 0;
}
static void
mh_destroy (mailbox_t mailbox)
{
struct _mh_data *mhd = mailbox->data;
struct _mh_message *msg, *next;
if (!mhd)
return;
monitor_wrlock (mailbox->monitor);
msg = mhd->msg_head;
while (msg)
{
next = msg->next;
message_destroy (&msg->message, msg);
free (msg);
msg = next;
}
if (mhd->name)
free (mhd->name);
free (mhd);
mailbox->data = NULL;
monitor_unlock (mailbox->monitor);
}
static int
mh_open (mailbox_t mailbox, int flags)
{
struct _mh_data *mhd = mailbox->data;
int status = 0;
struct stat st;
mailbox->flags = flags;
if (stat (mhd->name, &st) < 0)
return errno;
if (!S_ISDIR (st.st_mode))
return EINVAL;
mhd->mtime = st.st_mtime;
/* FIXME: is this the right kind of locking for mh folders? */
if (mailbox->locker == NULL)
status = locker_create (&mailbox->locker, mhd->name, 0);
return 0;
}
static int
mh_close (mailbox_t mailbox)
{
if (!mailbox)
return EINVAL;
return locker_unlock (mailbox->locker);
}
static struct _mh_message *
_mh_get_message (struct _mh_data *mhd, size_t msgno)
{
size_t n;
struct _mh_message *msg;
for (n = 1, msg = mhd->msg_head; msg && n < msgno; n++, msg = msg->next)
;
return msg;
}
/* Find the message with the given sequence number */
static struct _mh_message *
_mh_get_message_seq (struct _mh_data *mhd, size_t seq)
{
struct _mh_message *msg;
for (msg = mhd->msg_head; msg && msg->seq_number < seq; msg = msg->next)
;
if (msg)
return msg->seq_number == seq ? msg : NULL;
return NULL;
}
static int
_mh_attach_message (mailbox_t mailbox, struct _mh_message *mhm,
message_t *pmsg)
{
int status;
message_t msg;
/* Check if we already have it. */
if (mhm->message)
{
if (pmsg)
*pmsg = mhm->message;
return 0;
}
/* Get an empty message struct. */
status = message_create (&msg, mhm);
if (status != 0)
return status;
/* Set the header. */
{
header_t header = NULL;
status = header_create (&header, NULL, 0, msg);
if (status != 0)
{
message_destroy (&msg, mhm);
return status;
}
header_set_fill (header, mh_header_fill, msg);
header_set_size (header, mh_header_size, msg);
header_set_lines (header, mh_header_lines, msg);
/*FIXME:
header_set_get_fvalue (header, mh_header_get_fvalue, msg);
*/
message_set_header (msg, header, mhm);
}
/* Set the attribute. */
{
attribute_t attribute;
status = attribute_create (&attribute, msg);
if (status != 0)
{
message_destroy (&msg, mhm);
return status;
}
attribute_set_get_flags (attribute, mh_get_attr_flags, msg);
attribute_set_set_flags (attribute, mh_set_attr_flags, msg);
attribute_set_unset_flags (attribute, mh_unset_attr_flags, msg);
message_set_attribute (msg, attribute, mhm);
}
/* Prepare the body. */
{
body_t body = NULL;
stream_t stream = NULL;
if ((status = body_create (&body, msg)) != 0
|| (status = stream_create (&stream,
mailbox->flags | MU_STREAM_SEEKABLE,
body)) != 0)
{
body_destroy (&body, msg);
stream_destroy (&stream, body);
message_destroy (&msg, mhm);
return status;
}
stream_set_read (stream, mh_body_read, body);
stream_set_readline (stream, mh_body_readline, body);
stream_set_size (stream, mh_stream_size, body);
body_set_stream (body, stream, msg);
body_set_size (body, mh_body_size, msg);
body_set_lines (body, mh_body_lines, msg);
message_set_body (msg, body, mhm);
}
/* Set the envelope. */
{
envelope_t envelope = NULL;
status = envelope_create (&envelope, msg);
if (status != 0)
{
message_destroy (&msg, mhm);
return status;
}
envelope_set_sender (envelope, mh_envelope_sender, msg);
envelope_set_date (envelope, mh_envelope_date, msg);
message_set_envelope (msg, envelope, mhm);
}
/* Set the UID. */
message_set_uid (msg, mh_message_uid, mhm);
/* Attach the message to the mailbox mbox data. */
mhm->message = msg;
message_set_mailbox (msg, mailbox, mhm);
if (pmsg)
*pmsg = msg;
return 0;
}
static int
mh_get_message (mailbox_t mailbox, size_t msgno, message_t *pmsg)
{
int status;
struct _mh_data *mhd = mailbox->data;
struct _mh_message *mhm;
/* Sanity checks. */
if (pmsg == NULL || mhd == NULL)
return EINVAL;
/* If we did not start a scanning yet do it now. */
if (mhd->msg_count == 0)
{
status = mh_scan0 (mailbox, 1, NULL, 0);
if (status != 0)
return status;
}
if ((mhm = _mh_get_message (mhd, msgno)) == NULL)
return EINVAL;
return _mh_attach_message (mailbox, mhm, pmsg);
}
static size_t
_mh_next_seq (struct _mh_data *mhd)
{
return (mhd->msg_tail ? mhd->msg_tail->seq_number : 0) + 1;
}
static FILE *
_mh_tempfile(struct _mh_data *mhd, char **namep)
{
int fd = mu_tempfile (mhd->name, namep);
if (fd == -1)
return NULL;
return fdopen (fd, "w");
}
static int
_mh_delim (char *str)
{
if (str[0] == '-')
{
for (; *str == '-'; str++)
;
for (; *str == ' ' || *str == '\t'; str++)
;
}
return str[0] == '\n';
}
static int
_mh_message_save (struct _mh_data *mhd, struct _mh_message *mhm, int expunge)
{
stream_t stream = NULL;
char *name = NULL, *buf = NULL, *msg_name;
size_t n, off = 0;
size_t bsize;
size_t nlines, nbytes;
size_t new_body_start, new_header_lines;
FILE *fp;
message_t msg = mhm->message;
header_t hdr;
int status;
attribute_t attr;
body_t body;
char buffer[512];
envelope_t env = NULL;
fp = _mh_tempfile (mhm->mhd, &name);
if (!fp)
return errno;
message_size (msg, &bsize);
/* Try to allocate large buffer */
for (; bsize > 1; bsize /= 2)
if ((buf = malloc (bsize)))
break;
if (!bsize)
return ENOMEM;
/* Copy flags */
message_get_header (msg, &hdr);
header_get_stream (hdr, &stream);
off = 0;
nlines = nbytes = 0;
while ((status = stream_readline (stream, buf, bsize, off, &n)) == 0
&& n != 0)
{
if (_mh_delim(buf))
break;
if (!(strncasecmp (buf, "status:", 7) == 0
|| strncasecmp (buf, "x-imapbase:", 11) == 0
|| strncasecmp (buf, "x-uid:", 6) == 0
|| strncasecmp (buf, MU_HEADER_ENV_DATE ":", sizeof (MU_HEADER_ENV_DATE)) == 0
|| strncasecmp (buf, MU_HEADER_ENV_SENDER ":", sizeof (MU_HEADER_ENV_SENDER)) == 0))
{
nlines++;
nbytes += fprintf (fp, "%s", buf);
}
off += n;
}
/* Add imapbase */
if (!mhd->msg_head || (mhd->msg_head == mhm)) /*FIXME*/
{
nbytes += fprintf (fp, "X-IMAPbase: %lu %u\n",
(unsigned long) mhd->uidvalidity,
(unsigned) _mh_next_seq(mhd));
nlines++;
}
message_get_envelope (msg, &env);
if (envelope_date (env, buffer, sizeof buffer, &n) == 0 && n > 0)
{
/* NOTE: buffer is terminated with \n */
char *p = buffer;
while (isspace (*p))
p++;
nbytes += fprintf (fp, "%s: %s", MU_HEADER_ENV_DATE, p);
if (*p && p[strlen (p) - 1] != '\n')
nbytes += fprintf (fp, "\n");
nlines++;
}
if (envelope_sender (env, buffer, sizeof buffer, &n) == 0 && n > 0)
{
fprintf (fp, "%s: %s\n", MU_HEADER_ENV_SENDER, buffer);
nlines++;
}
/* Add status */
message_get_attribute (msg, &attr);
attribute_to_string (attr, buf, bsize, &n);
if (n)
{
nbytes += fprintf (fp, "%s", buf);
nlines++;
}
nbytes += fprintf (fp, "\n");
nlines++;
new_header_lines = nlines;
new_body_start = nbytes;
/* Copy message body */
message_get_body (msg, &body);
body_get_stream (body, &stream);
off = 0;
nlines = 0;
while (stream_read (stream, buf, bsize, off, &n) == 0 && n != 0)
{
char *p;
for (p = buf; p < buf + n; p++)
if (*p == '\n')
nlines++;
fwrite (buf, 1, n, fp);
off += n;
nbytes += n;
}
mhm->header_lines = new_header_lines;
mhm->body_start = new_body_start;
mhm->body_lines = nlines;
mhm->body_end = nbytes;
free (buf);
fclose (fp);
msg_name = _mh_message_name (mhm, mhm->deleted);
rename (name, msg_name);
free (name);
free (msg_name);
return 0;
}
static int
mh_append_message (mailbox_t mailbox, message_t msg)
{
int status;
struct _mh_data *mhd = mailbox->data;
struct _mh_message *mhm;
if (!mailbox || !msg)
return EINVAL;
mhm = calloc (1, sizeof(*mhm));
if (!mhm)
return ENOMEM;
/* If we did not start a scanning yet do it now. */
if (mhd->msg_count == 0)
{
status = mh_scan0 (mailbox, 1, NULL, 0);
if (status != 0)
return status;
}
mhm->mhd = mhd;
mhm->seq_number = _mh_next_seq (mhd);
mhm->message = msg;
status = _mh_message_save (mhd, mhm, 0);
mhm->message = NULL;
/* Insert and re-scan the message */
_mh_message_insert (mhd, mhm);
return status;
}
static int
mh_messages_count (mailbox_t mailbox, size_t *pcount)
{
struct _mh_data *mhd = mailbox->data;
if (mhd == NULL)
return EINVAL;
if (!mh_is_updated (mailbox))
return mh_scan0 (mailbox, mhd->msg_count, pcount, 0);
if (pcount)
*pcount = mhd->msg_count;
return 0;
}
/* A "recent" message is the one not marked with MU_ATTRIBUTE_SEEN
('O' in the Status header), i.e. a message that is first seen
by the current session (see attributes.h) */
static int
mh_messages_recent (mailbox_t mailbox, size_t *pcount)
{
struct _mh_data *mhd = mailbox->data;
struct _mh_message *mhm;
size_t count;
/* If we did not start a scanning yet do it now. */
if (mhd->msg_count == 0)
{
int status = mh_scan0 (mailbox, 1, NULL, 0);
if (status != 0)
return status;
}
count = 0;
for (mhm = mhd->msg_head; mhm; mhm = mhm->next)
{
if (MU_ATTRIBUTE_IS_UNSEEN(mhm->attr_flags))
count++;
}
*pcount = count;
return 0;
}
/* An "unseen" message is the one that has not been read yet */
static int
mh_message_unseen (mailbox_t mailbox, size_t *pmsgno)
{
struct _mh_data *mhd = mailbox->data;
struct _mh_message *mhm;
size_t i, unseen;
/* If we did not start a scanning yet do it now. */
if (mhd->msg_count == 0)
{
int status = mh_scan0 (mailbox, 1, NULL, 0);
if (status != 0)
return status;
}
for (unseen = i = 1, mhm = mhd->msg_head; mhm; i++, mhm = mhm->next)
{
if (MU_ATTRIBUTE_IS_UNREAD(mhm->attr_flags))
{
unseen = i;
break;
}
}
*pmsgno = unseen;
return 0;
}
static int
mh_expunge (mailbox_t mailbox)
{
struct _mh_data *mhd = mailbox->data;
struct _mh_message *mhm;
if (mhd == NULL)
return EINVAL;
if (mhd->msg_count == 0)
return 0;
/* Find the first dirty(modified) message. */
for (mhm = mhd->msg_head; mhm; mhm = mhm->next)
{
if ((mhm->attr_flags & MU_ATTRIBUTE_MODIFIED) ||
(mhm->attr_flags & MU_ATTRIBUTE_DELETED) ||
(mhm->message && message_is_modified (mhm->message)))
break;
}
if (!mhm)
return 0; /* Nothing changed, just return. */
while (mhm)
{
struct _mh_message *next = mhm->next;
if (mhm->attr_flags & MU_ATTRIBUTE_DELETED)
{
if (!mhm->deleted)
{
char *old_name, *new_name;
/* Rename original message */
old_name = _mh_message_name (mhm, 0);
new_name = _mh_message_name (mhm, 1);
rename (old_name, new_name);
free (old_name);
free (new_name);
}
_mh_message_delete (mhd, mhm);
}
else if ((mhm->attr_flags & MU_ATTRIBUTE_MODIFIED)
|| (mhm->message && message_is_modified (mhm->message)))
{
_mh_attach_message (mailbox, mhm, NULL);
mhm->deleted = mhm->attr_flags & MU_ATTRIBUTE_DELETED;
_mh_message_save (mhd, mhm, 1);
}
mhm = next;
}
return 0;
}
static int
mh_save_attributes (mailbox_t mailbox)
{
struct _mh_data *mhd = mailbox->data;
struct _mh_message *mhm;
if (mhd == NULL)
return EINVAL;
if (mhd->msg_count == 0)
return 0;
/* Find the first dirty(modified) message. */
for (mhm = mhd->msg_head; mhm; mhm = mhm->next)
{
if ((mhm->attr_flags & MU_ATTRIBUTE_MODIFIED)
|| (mhm->message && message_is_modified (mhm->message)))
break;
}
if (!mhm)
return 0; /* Nothing changed, just return. */
while (mhm)
{
struct _mh_message *next = mhm->next;
if ((mhm->attr_flags & MU_ATTRIBUTE_MODIFIED)
|| (mhm->message && message_is_modified (mhm->message)))
{
_mh_attach_message (mailbox, mhm, NULL);
mhm->deleted = mhm->attr_flags & MU_ATTRIBUTE_DELETED;
_mh_message_save (mhd, mhm, 0);
}
mhm = next;
}
return 0;
}
static int
mh_uidvalidity (mailbox_t mailbox, unsigned long *puidvalidity)
{
struct _mh_data *mhd = mailbox->data;
int status = mh_messages_count (mailbox, NULL);
if (status != 0)
return status;
/* If we did not start a scanning yet do it now. */
if (mhd->msg_count == 0)
{
status = mh_scan0 (mailbox, 1, NULL, 0);
if (status != 0)
return status;
}
if (puidvalidity)
*puidvalidity = mhd->uidvalidity;
return 0;
}
struct _mh_message
{
struct _amd_message amd_message;
size_t seq_number; /* message sequence number */
};
static int
mh_uidnext (mailbox_t mailbox, size_t *puidnext)
mh_message_cmp (struct _amd_message *a, struct _amd_message *b)
{
struct _mh_data *mhd = mailbox->data;
int status = mh_messages_count (mailbox, NULL);
if (status != 0)
return status;
/* If we did not start a scanning yet do it now. */
if (mhd->msg_count == 0)
{
status = mh_scan0 (mailbox, 1, NULL, 0);
if (status != 0)
return status;
}
if (puidnext)
*puidnext = _mh_next_seq(mhd);
struct _mh_message *ma = (struct _mh_message *) a;
struct _mh_message *mb = (struct _mh_message *) b;
if (ma->seq_number < mb->seq_number)
return -1;
else if (ma->seq_number > mb->seq_number)
return 1;
return 0;
}
/* FIXME: effectively the same as mbox_cleanup */
static void
mh_cleanup (void *arg)
{
mailbox_t mailbox = arg;
monitor_unlock (mailbox->monitor);
locker_unlock (mailbox->locker);
}
/* Insert message msg into the message list on the appropriate position */
static void
_mh_message_insert (struct _mh_data *mhd, struct _mh_message *msg)
static size_t
_mh_next_seq (struct _amd_data *amd)
{
struct _mh_message *p;
struct _mh_message *prev;
size_t n = msg->seq_number;
for (p = mhd->msg_head; p && p->seq_number < n; p = p->next)
;
if (!p)
{
msg->next = NULL;
msg->prev = mhd->msg_tail;
mhd->msg_tail = msg;
if (!mhd->msg_head)
mhd->msg_head = msg;
}
else
{
msg->next = p;
msg->prev = p->prev;
p->prev = msg;
}
if ((prev = msg->prev) != NULL)
prev->next = msg;
else
mhd->msg_head = msg;
msg->mhd = mhd;
mhd->msg_count++;
struct _mh_message *msg = (struct _mh_message *) amd->msg_tail;
return (msg ? msg->seq_number : 0) + 1;
}
static void
_mh_message_delete (struct _mh_data *mhd, struct _mh_message *msg)
/* Return filename for the message.
NOTE: Allocates memory. */
static char *
_mh_message_name (struct _amd_message *amsg, int deleted)
{
struct _mh_message *p;
struct _mh_message **pp = mh_pool_lookup (msg);
if (pp)
*pp = NULL;
if ((p = msg->next) != NULL)
p->prev = msg->prev;
else
mhd->msg_tail = msg->prev;
if ((p = msg->prev) != NULL)
p->next = msg->next;
struct _mh_message *mhm = (struct _mh_message *) amsg;
char *filename;
size_t len = strlen (amsg->amd->name) + 32;
filename = malloc (len);
if (deleted)
snprintf (filename, len, "%s/,%lu", amsg->amd->name,
(unsigned long) mhm->seq_number);
else
mhd->msg_head = msg->next;
message_destroy (&msg->message, msg);
free (msg);
mhd->msg_count--;
snprintf (filename, len, "%s/%lu", amsg->amd->name,
(unsigned long) mhm->seq_number);
return filename;
}
/* Scan given message and fill mh_message_t fields.
NOTE: the function assumes mhm->stream != NULL. */
static int
mh_scan_message (struct _mh_message *mhm)
/* Find the message with the given sequence number */
static struct _mh_message *
_mh_get_message_seq (struct _amd_data *amd, size_t seq)
{
stream_t stream = mhm->stream;
char buf[1024];
off_t off = 0;
size_t n;
int status;
int in_header = 1;
size_t hlines = 0;
size_t blines = 0;
size_t body_start = 0;
/* Check if the message was modified after the last scan */
if (mhm->mtime)
{
struct stat st;
char *msg_name = _mh_message_name (mhm, mhm->deleted);
if (stat (msg_name, &st) == 0 && st.st_mtime == mhm->mtime)
{
/* Nothing to do */
free (msg_name);
return 0;
}
free (msg_name);
}
while ((status = stream_readline (stream, buf, sizeof (buf), off, &n) == 0)
&& n != 0)
{
if (in_header)
{
if (buf[0] == '\n')
{
in_header = 0;
body_start = off+1;
}
if (buf[n - 1] == '\n')
hlines++;
struct _mh_message *msg;
/* Process particular attributes */
if (strncasecmp (buf, "status:", 7) == 0)
{
int deleted = mhm->attr_flags & MU_ATTRIBUTE_DELETED;
string_to_flags (buf, &mhm->attr_flags);
mhm->attr_flags |= deleted;
}
else if (strncasecmp (buf, "x-imapbase:", 11) == 0)
{
char *p;
mhm->mhd->uidvalidity = strtoul (buf + 11, &p, 10);
/* second number is next uid. Ignored */
}
}
else
{
if (buf[n - 1] == '\n')
blines++;
}
off += n;
}
for (msg = (struct _mh_message *) amd->msg_head;
msg && msg->seq_number < seq;
msg = (struct _mh_message *) msg->amd_message.next)
;
if (!body_start)
body_start = off;
mhm->header_lines = hlines;
mhm->body_lines = blines;
mhm->body_start = body_start;
mhm->body_end = off;
return 0;
if (msg)
return msg->seq_number == seq ? msg : NULL;
return NULL;
}
/* Scan the mailbox */
......@@ -1013,24 +128,24 @@ static int
mh_scan0 (mailbox_t mailbox, size_t msgno ARG_UNUSED, size_t *pcount,
int do_notify)
{
struct _mh_data *mhd = mailbox->data;
struct _amd_data *amd = mailbox->data;
struct _mh_message *msg;
DIR *dir;
struct dirent *entry;
int status = 0;
struct stat st;
if (mhd == NULL)
if (amd == NULL)
return EINVAL;
dir = opendir (mhd->name);
dir = opendir (amd->name);
if (!dir)
return errno;
monitor_wrlock (mailbox->monitor);
#ifdef WITH_PTHREAD
pthread_cleanup_push (mh_cleanup, (void *)mailbox);
pthread_cleanup_push (amd_cleanup, (void *)mailbox);
#endif
locker_lock (mailbox->locker);
......@@ -1069,316 +184,69 @@ mh_scan0 (mailbox_t mailbox, size_t msgno ARG_UNUSED, size_t *pcount,
if (namep[0])
continue;
msg = _mh_get_message_seq (mhd, num);
msg = _mh_get_message_seq (amd, num);
if (!msg)
{
msg = calloc (1, sizeof(*msg));
msg->seq_number = num;
msg->attr_flags = attr_flags;
msg->deleted = attr_flags & MU_ATTRIBUTE_DELETED;
_mh_message_insert (mhd, msg);
msg->amd_message.attr_flags = attr_flags;
msg->amd_message.deleted = attr_flags & MU_ATTRIBUTE_DELETED;
_amd_message_insert (amd, (struct _amd_message*) msg);
}
else
{
msg->attr_flags = attr_flags;
msg->amd_message.attr_flags = attr_flags;
}
}
closedir (dir);
if (do_notify)
for (msg = mhd->msg_head; msg; msg = msg->next)
{
DISPATCH_ADD_MSG(mailbox, mhd);
struct _amd_message *mp;
for (mp = amd->msg_head; mp; mp = mp->next)
{
DISPATCH_ADD_MSG(mailbox, amd);
}
}
if (stat (mhd->name, &st) == 0)
mhd->mtime = st.st_mtime;
if (stat (amd->name, &st) == 0)
amd->mtime = st.st_mtime;
if (pcount)
*pcount = mhd->msg_count;
*pcount = amd->msg_count;
/* Reset the uidvalidity. */
if (mhd->msg_count > 0)
if (amd->msg_count > 0)
{
if (mhd->uidvalidity == 0)
if (amd->uidvalidity == 0)
{
mhd->uidvalidity = (unsigned long)time (NULL);
/* FIXME mhd->uidnext = mhd->msg_count + 1;*/
amd->uidvalidity = (unsigned long)time (NULL);
/* FIXME amd->uidnext = amd->msg_count + 1;*/
/* Tell that we have been modified for expunging. */
if (mhd->msg_head)
if (amd->msg_head)
{
mh_message_stream_open (mhd->msg_head);
mh_message_stream_close (mhd->msg_head);
mhd->msg_head->attr_flags |= MU_ATTRIBUTE_MODIFIED;
amd_message_stream_open (amd->msg_head);
amd_message_stream_close (amd->msg_head);
amd->msg_head->attr_flags |= MU_ATTRIBUTE_MODIFIED;
}
}
}
/* Clean up the things */
mh_cleanup (mailbox);
#ifdef WITH_PTHREAD
pthread_cleanup_pop (0);
#endif
return status;
}
static int
mh_scan (mailbox_t mailbox, size_t msgno, size_t *pcount)
{
struct _mh_data *mhd = mailbox->data;
if (! mh_is_updated (mailbox))
return mh_scan0 (mailbox, msgno, pcount, 1);
if (pcount)
*pcount = mhd->msg_count;
return 0;
}
/* Is the internal representation of the mailbox up to date.
Return 1 if so, 0 otherwise. */
static int
mh_is_updated (mailbox_t mailbox)
{
struct stat st;
struct _mh_data *mhd = mailbox->data;
if (!mhd->msg_head)
return 0;
if (stat (mhd->name, &st) < 0)
return 1;
return mhd->mtime == st.st_mtime;
}
static int
mh_get_size (mailbox_t mailbox ARG_UNUSED, off_t *psize ARG_UNUSED)
{
/*FIXME*/
return ENOSYS;
}
/* Return number of open streams residing in a message pool */
static int
mh_pool_open_count (struct _mh_data *mhd)
{
int cnt = mhd->pool_last - mhd->pool_first;
if (cnt < 0)
cnt += MAX_OPEN_STREAMS;
return cnt;
}
/* Look up a _mh_message in the pool of open messages.
If the message is found in the pool, returns the address of
the pool slot occupied by it. Otherwise returns NULL. */
static struct _mh_message **
mh_pool_lookup (struct _mh_message *mhm)
{
struct _mh_data *mhd = mhm->mhd;
int i;
for (i = mhd->pool_first; i != mhd->pool_last; )
{
if (mhd->msg_pool[i] == mhm)
return &mhd->msg_pool[i];
if (++i == MAX_OPEN_STREAMS)
i = 0;
}
return NULL;
}
/* Open a stream associated with the message mhm. If the stream is
already open, do nothing.
NOTE: We could have reused the NULL holes in the msg_pool, but
that hardly is worth the effort, since the holes appear only when
expunging. On the other hand this may be useful when MAX_OPEN_STREAMS
size is very big. "Premature optimization is the root of all evil" */
static int
mh_pool_open (struct _mh_message *mhm)
{
struct _mh_data *mhd = mhm->mhd;
if (mh_pool_lookup (mhm))
return 0;
if (mh_pool_open_count(mhd) == MAX_OPEN_STREAMS-1)
{
mh_message_stream_close (mhd->msg_pool[mhd->pool_first++]);
mhd->pool_first %= MAX_OPEN_STREAMS;
}
mh_message_stream_open (mhm);
mhd->msg_pool[mhd->pool_last++] = mhm;
mhd->pool_last %= MAX_OPEN_STREAMS;
return 0;
}
/* Attach a stream to a given message structure. The latter is supposed
to be already added to the open message pool. */
int
mh_message_stream_open (struct _mh_message *mhm)
{
struct _mh_data *mhd = mhm->mhd;
char *filename = NULL;
int status;
int flags = MU_STREAM_ALLOW_LINKS;
filename = _mh_message_name (mhm, mhm->deleted);
if (!filename)
return ENOMEM;
/* The message should be at least readable */
if (mhd->mailbox->flags & (MU_STREAM_RDWR|MU_STREAM_WRITE|MU_STREAM_APPEND))
flags |= MU_STREAM_RDWR;
else
flags |= MU_STREAM_READ;
status = file_stream_create (&mhm->stream, filename, flags);
free (filename);
if (status != 0)
return status;
status = stream_open (mhm->stream);
if (status != 0)
stream_destroy (&mhm->stream, NULL);
if (status == 0)
status = mh_scan_message (mhm);
return status;
}
/* Close the stream associated with the given message. */
void
mh_message_stream_close (struct _mh_message *mhm)
{
if (mhm)
{
stream_close (mhm->stream);
mhm->stream = NULL;
}
}
void
mh_check_message (struct _mh_message *mhm)
{
if (mhm->body_end == 0)
mh_pool_open (mhm);
}
/* Reading functions */
static int
mh_readstream (struct _mh_message *mhm, char *buffer, size_t buflen,
off_t off, size_t *pnread, int isreadline,
off_t start, off_t end)
{
size_t nread = 0;
int status = 0;
off_t ln;
if (buffer == NULL || buflen == 0)
{
if (pnread)
*pnread = nread;
return 0;
}
monitor_rdlock (mhm->mhd->mailbox->monitor);
#ifdef WITH_PTHREAD
/* read() is cancellation point since we're doing a potentially
long operation. Lets make sure we clean the state. */
pthread_cleanup_push (mh_cleanup, (void *)mhm->mhd->mailbox);
#endif
ln = end - (start + off);
if (ln > 0)
{
/* Position the file pointer and the buffer. */
nread = ((size_t)ln < buflen) ? (size_t)ln : buflen;
if (isreadline)
status = stream_readline (mhm->stream, buffer, buflen,
start + off, &nread);
else
status = stream_read (mhm->stream, buffer, nread,
start + off, &nread);
}
monitor_unlock (mhm->mhd->mailbox->monitor);
amd_cleanup (mailbox);
#ifdef WITH_PTHREAD
pthread_cleanup_pop (0);
#endif
if (pnread)
*pnread = nread;
return status;
}
static int
mh_body_read (stream_t is, char *buffer, size_t buflen, off_t off,
size_t *pnread)
{
body_t body = stream_get_owner (is);
message_t msg = body_get_owner (body);
struct _mh_message *mhm = message_get_owner (msg);
mh_pool_open (mhm);
return mh_readstream (mhm, buffer, buflen, off, pnread, 0,
mhm->body_start, mhm->body_end);
}
static int
mh_body_readline (stream_t is, char *buffer, size_t buflen,
off_t off, size_t *pnread)
{
body_t body = stream_get_owner (is);
message_t msg = body_get_owner (body);
struct _mh_message *mhm = message_get_owner (msg);
mh_pool_open (mhm);
return mh_readstream (mhm, buffer, buflen, off, pnread, 1,
mhm->body_start, mhm->body_end);
}
/* Return corresponding sizes */
static int
mh_stream_size (stream_t stream, off_t *psize)
{
body_t body = stream_get_owner (stream);
return mh_body_size (body, (size_t*) psize);
}
static int
mh_body_size (body_t body, size_t *psize)
{
message_t msg = body_get_owner (body);
struct _mh_message *mhm = message_get_owner (msg);
if (mhm == NULL)
return EINVAL;
mh_check_message (mhm);
if (psize)
*psize = mhm->body_end - mhm->body_start;
return 0;
}
static int
mh_body_lines (body_t body, size_t *plines)
{
message_t msg = body_get_owner (body);
struct _mh_message *mhm = message_get_owner (msg);
if (mhm == NULL)
return EINVAL;
mh_check_message (mhm);
if (plines)
*plines = mhm->body_lines;
return 0;
}
/* Note: In this particular implementation the message sequence number
serves also as its UID. This allows to avoid many problems related
to keeping the uids in the headers of the messages. */
static int
mh_message_uid (message_t msg, size_t *puid)
......@@ -1389,153 +257,43 @@ mh_message_uid (message_t msg, size_t *puid)
return 0;
}
/* Headers */
static int
mh_header_fill (header_t header, char *buffer, size_t len,
off_t off, size_t *pnread)
{
message_t msg = header_get_owner (header);
struct _mh_message *mhm = message_get_owner (msg);
mh_pool_open (mhm);
return mh_readstream (mhm, buffer, len, off, pnread, 0,
0, mhm->body_start);
}
static int
mh_header_size (header_t header, size_t *psize)
{
message_t msg = header_get_owner (header);
struct _mh_message *mhm = message_get_owner (msg);
if (mhm == NULL)
return EINVAL;
mh_check_message (mhm);
if (psize)
*psize = mhm->body_start;
return 0;
}
static int
mh_header_lines (header_t header, size_t *plines)
{
message_t msg = header_get_owner (header);
struct _mh_message *mhm = message_get_owner (msg);
if (mhm == NULL)
return EINVAL;
mh_check_message (mhm);
if (plines)
*plines = mhm->header_lines;
return 0;
}
/* Attributes */
static int
mh_get_attr_flags (attribute_t attr, int *pflags)
{
message_t msg = attribute_get_owner (attr);
struct _mh_message *mhm = message_get_owner (msg);
if (mhm == NULL)
return EINVAL;
if (pflags)
*pflags = mhm->attr_flags;
return 0;
}
static int
mh_set_attr_flags (attribute_t attr, int flags)
{
message_t msg = attribute_get_owner (attr);
struct _mh_message *mhm = message_get_owner (msg);
if (mhm == NULL)
return EINVAL;
mhm->attr_flags |= flags;
return 0;
}
static int
mh_unset_attr_flags (attribute_t attr, int flags)
_mh_msg_init (struct _amd_data *amd, struct _amd_message *amm)
{
message_t msg = attribute_get_owner (attr);
struct _mh_message *mhm = message_get_owner (msg);
if (mhm == NULL)
return EINVAL;
mhm->attr_flags &= ~flags;
struct _mh_message *mhm = (struct _mh_message *) amm;
mhm->seq_number = _mh_next_seq (amd);
return 0;
}
/* Envelope */
static int
mh_envelope_date (envelope_t envelope, char *buf, size_t len,
size_t *psize)
{
message_t msg = envelope_get_owner (envelope);
struct _mh_message *mhm = message_get_owner (msg);
header_t hdr = NULL;
char *from;
int status;
if (mhm == NULL)
return EINVAL;
if ((status = message_get_header (msg, &hdr)) != 0)
return status;
if (header_aget_value (hdr, MU_HEADER_ENV_DATE, &from))
return ENOSYS;
/* Format: "sender date" */
if (buf && len > 0)
{
len--; /* Leave space for the null. */
strncpy (buf, from, len);
if (strlen (from) < len)
{
len = strlen (buf);
buf[len++] = '\n';
}
buf[len] = '\0';
}
else
len = 0;
if (psize)
*psize = len;
return 0;
}
static int
mh_envelope_sender (envelope_t envelope, char *buf, size_t len, size_t *psize)
int
_mailbox_mh_init (mailbox_t mailbox)
{
message_t msg = envelope_get_owner (envelope);
struct _mh_message *mhm = message_get_owner (msg);
header_t hdr = NULL;
char *from;
int status;
int rc;
struct _amd_data *amd;
if (mhm == NULL)
return EINVAL;
rc = amd_init_mailbox (mailbox, sizeof (struct _amd_data), &amd);
if (rc)
return rc;
if ((status = message_get_header (msg, &hdr)) != 0)
return status;
if (header_aget_value (hdr, MU_HEADER_ENV_SENDER, &from))
return ENOSYS;
amd->msg_size = sizeof (struct _mh_message);
amd->msg_free = NULL;
amd->msg_init_delivery = _mh_msg_init;
amd->msg_finish_delivery = NULL;
amd->msg_file_name = _mh_message_name;
amd->scan0 = mh_scan0;
amd->msg_cmp = mh_message_cmp;
amd->message_uid = mh_message_uid;
amd->next_uid = _mh_next_seq;
if (buf && len > 0)
/* Set our properties. */
{
int slen = strlen (from);
if (len < slen + 1)
slen = len - 1;
memcpy (buf, from, slen);
buf[slen] = 0;
property_t property = NULL;
mailbox_get_property (mailbox, &property);
property_set_value (property, "TYPE", "MH", 1);
}
else
len = 0;
if (psize)
*psize = len;
return 0;
}
......