Commit b6bb9ca3 b6bb9ca37cb0fcf8e8a5131a49a21c5a41dc2e62 by Sergey Poznyakoff

Moved from ../

1 parent 11bc148b
/* 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;
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);
#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;
}
static int
mh_message_uid (message_t msg, size_t *puid)
{
struct _mh_message *mhm = message_get_owner (msg);
if (puid)
*puid = mhm->seq_number;
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)
{
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;
}
/* 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)
{
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_SENDER, &from))
return ENOSYS;
if (buf && len > 0)
{
int slen = strlen (from);
if (len < slen + 1)
slen = len - 1;
memcpy (buf, from, slen);
buf[slen] = 0;
}
else
len = 0;
if (psize)
*psize = len;
return 0;
}
#endif