summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2003-03-13 13:36:51 +0000
committerSergey Poznyakoff <gray@gnu.org.ua>2003-03-13 13:36:51 +0000
commitb6bb9ca37cb0fcf8e8a5131a49a21c5a41dc2e62 (patch)
tree5afdf096a4c3fc7f1b493a506e3cb86066c4e9a4
parent11bc148b1877b7c692f28d90847bc74737068416 (diff)
downloadmailutils-b6bb9ca37cb0fcf8e8a5131a49a21c5a41dc2e62.tar.gz
mailutils-b6bb9ca37cb0fcf8e8a5131a49a21c5a41dc2e62.tar.bz2
Moved from ../
-rw-r--r--mailbox/mh/mbox.c1533
1 files changed, 1533 insertions, 0 deletions
diff --git a/mailbox/mh/mbox.c b/mailbox/mh/mbox.c
new file mode 100644
index 000000000..e7c58b857
--- /dev/null
+++ b/mailbox/mh/mbox.c
@@ -0,0 +1,1533 @@
+/* GNU Mailutils -- a suite of utilities for electronic mail
+ Copyright (C) 1999, 2000, 2001, 2002, 2003 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+/* First draft by Sergey Poznyakoff */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef ENABLE_MH
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <dirent.h>
+
+#ifdef WITH_PTHREAD
+# ifdef HAVE_PTHREAD_H
+# define _XOPEN_SOURCE 500
+# include <pthread.h>
+# endif
+#endif
+
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+
+#include <mailutils/attribute.h>
+#include <mailutils/body.h>
+#include <mailutils/debug.h>
+#include <mailutils/envelope.h>
+#include <mailutils/error.h>
+#include <mailutils/header.h>
+#include <mailutils/locker.h>
+#include <mailutils/message.h>
+#include <mailutils/mutil.h>
+#include <mailutils/property.h>
+#include <mailutils/stream.h>
+#include <mailutils/url.h>
+#include <mailutils/observer.h>
+#include <mailbox0.h>
+#include <registrar0.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_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 an 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;
+}
+
+static int
+mh_uidnext (mailbox_t mailbox, size_t *puidnext)
+{
+ 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);
+ 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)
+{
+ 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++;
+}
+
+static void
+_mh_message_delete (struct _mh_data *mhd, struct _mh_message *msg)
+{
+ struct _mh_message *p;
+
+ if ((p = msg->next) != NULL)
+ p->prev = msg->prev;
+ else
+ mhd->msg_tail = msg->prev;
+
+ if ((p = msg->prev) != NULL)
+ p->next = msg->next;
+ else
+ mhd->msg_head = msg->next;
+
+ message_destroy (&msg->message, msg);
+ free (msg);
+ mhd->msg_count--;
+}
+
+/* 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)
+{
+ 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++;
+
+ /* 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;
+ }
+
+ 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;
+}
+
+/* Scan the mailbox */
+static int
+mh_scan0 (mailbox_t mailbox, size_t msgno, size_t *pcount, int do_notify)
+{
+ struct _mh_data *mhd = mailbox->data;
+ struct _mh_message *msg;
+ DIR *dir;
+ struct dirent *entry;
+ int status = 0;
+ struct stat st;
+ (void)msgno;
+
+ if (mhd == NULL)
+ return EINVAL;
+
+ dir = opendir (mhd->name);
+ if (!dir)
+ return errno;
+
+ monitor_wrlock (mailbox->monitor);
+
+#ifdef WITH_PTHREAD
+ pthread_cleanup_push (mh_cleanup, (void *)mailbox);
+#endif
+
+ locker_lock (mailbox->locker);
+
+ /* Do actual work. */
+
+ while ((entry = readdir (dir)))
+ {
+ char *namep;
+ int attr_flags;
+ size_t num;
+
+ attr_flags = 0;
+ switch (entry->d_name[0])
+ {
+ case '.':
+ /* FIXME: .mh_sequences */
+ continue;
+ case ',':
+ continue;
+#if 0
+ attr_flags |= MU_ATTRIBUTE_DELETED;
+ namep = entry->d_name+1;
+ break;
+#endif
+ case '0':case '1':case '2':case '3':case '4':
+ case '5':case '6':case '7':case '8':case '9':
+ namep = entry->d_name;
+ break;
+ default:
+ /*FIXME: Invalid entry. Report? */
+ continue;
+ }
+
+ num = strtoul (namep, &namep, 10);
+ if (namep[0])
+ continue;
+
+ msg = _mh_get_message_seq (mhd, 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);
+
+ }
+ else
+ {
+ msg->attr_flags = attr_flags;
+ }
+ }
+
+ closedir (dir);
+
+ if (do_notify)
+ for (msg = mhd->msg_head; msg; msg = msg->next)
+ {
+ DISPATCH_ADD_MSG(mailbox, mhd);
+ }
+
+ if (stat (mhd->name, &st) == 0)
+ mhd->mtime = st.st_mtime;
+
+ if (pcount)
+ *pcount = mhd->msg_count;
+
+ /* Reset the uidvalidity. */
+ if (mhd->msg_count > 0)
+ {
+ if (mhd->uidvalidity == 0)
+ {
+ mhd->uidvalidity = (unsigned long)time (NULL);
+ /* FIXME mhd->uidnext = mhd->msg_count + 1;*/
+ /* Tell that we have been modified for expunging. */
+ if (mhd->msg_head)
+ {
+ mh_message_stream_open (mhd->msg_head);
+ mh_message_stream_close (mhd->msg_head);
+ mhd->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, off_t *psize)
+{
+ /*FIXME*/
+ (void)mailbox;
+ (void)psize;
+ 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.
+ Returns 1 if the message is found in the pool, and 0 otherwise. */
+static int
+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 1;
+ if (++i == MAX_OPEN_STREAMS)
+ i = 0;
+ }
+ return 0;
+}
+
+/* Open a stream associated with the message mhm. If the stream is
+ already open, do nothing */
+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;
+