/* mboxfs - Creates a filesystem based on the contents of a mailbox.
   Copyright (C) 2002, Ludovic Courtès <ludo@chbouib.org>
 
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or * (at your option) any later version.
 
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.
 
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
   USA */

#include <hurd.h>
#include <hurd/netfs.h>
#include <error.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <dirent.h>
#include <fcntl.h>
#include <limits.h>
#include <argp.h>
#include <argz.h>
#include "backend.h"
#include "mboxfs_defs.h"
#include "mutt/mutt.h"
#include "mutt/mailbox.h"
#include "mutt/date.h"
#include "fs.h"

/* filesystem options */
struct mboxfs_opts mboxfs_options;
const char *argp_program_version = "mboxfs for the GNU Hurd"
   " (compiled: " __DATE__ ")";

const char *argp_program_bug_address = "Ludovic Courtès <ludo@chbouib.org>";
const char *args_doc = "MAILBOX";
const char *doc = "Hurd mailbox translator: parses a mailbox and creates a corresponding"
	"\ndirectory hierarchy with sorted emails and attachments.";
const struct argp_option fs_options[] =
{
  { "full-headers", 'f', NULL, 0,   "Ask to show full email headers" },
  { "force-mmdf",   'm', NULL, 0,   "Parse mailbox as an MMDF formatted mailbox." },
  { "no-timeout",   't', NULL, 0,   "Parse mailbox in a separate thread (thus avoiding startup timeouts)" },
  { 0 }
};

/* Netfs things */
char *netfs_server_name = "mboxfs";
char *netfs_server_version = "0.0";

/* The main directories. */
static struct node *dir_senders;
static struct node *dir_rcpt;
static struct node *dir_date;
static struct node *dir_threads;

/* The commonly used strings. */
static char*  str_dir_senders;
static char*  str_dir_rcpt;
static char*  str_dir_date;
static char*  str_dir_attach;
static char*  str_dir_threads;
static char*  str_nosubject;

/* Data used by netfs_get_dirents() */
static struct node *curr_dir;
static struct node *curr_node;
static int    curr_entry = 0;

/* general info */
static CONTEXT main_context;

/* mboxfs_parse_opts()
 * Argp options parser.
 */
error_t
mboxfs_parse_opts (int key, char *arg, struct argp_state *state)
{
  switch (key)
  {
    case 'f':
      mboxfs_options.show_headers = 1;
      break;
    case 'm':
      mboxfs_options.force_mmdf = 1;
      break;
    case 't':
      mboxfs_options.threaded = 1;
      break;
    case ARGP_KEY_ARG:
      mboxfs_options.file_name = strdup (arg);
      if (!mboxfs_options.file_name || !strlen (mboxfs_options.file_name))
	argp_error (state, "No mailbox specified.");
  }

  return 0;
}

/* mboxfs_get_argp()
 * Returns the mboxfs' struct argp.
 */
void
mboxfs_get_argp (struct argp *a)
{
  bzero (a, sizeof (struct argp));
  a->options  = fs_options;
  a->parser   = mboxfs_parse_opts;
  a->args_doc = args_doc;
  a->doc      = doc;
}

/* mboxfs_get_args()
 * Append to the malloced string *ARGZ of len *ARGZ_LEN a NULL-separated list
 * of arguments.
 */
error_t
mboxfs_get_args (char **argz, size_t *argz_len)
{
  error_t err = 0;

  if (mboxfs_options.show_headers)
    err = argz_add (argz, argz_len, "--full-headers");

  if (!err)
    err = argz_add (argz, argz_len, mboxfs_options.file_name);

  return err;
}

/* mboxfs_set_options ()
 * A basic set_options (). Only runtime options can be changed (using
 * fsysopts): for instance, --no-timeout won't work (it doesn't make
 * sense when mboxfs is already running).
 */
error_t
mboxfs_set_options (const char *argz, size_t argz_len)
{
  /* FIXME:
   * Changing the show_headers flag implies an st_size modification, which
   * is currently performed in mboxfs_read_text_node().
   * Also, it's impossible to remove the option.
   */

  /* only check the first entry. */
  if ((!strcmp (argz, "-f")) || (!strcmp (argz, "--full-headers")))
    mboxfs_options.show_headers = 1;
  else
    return EINVAL;
  return 0;
}


#define D(_s) strdup(_s)

/* mboxfs_add_attach()
 * Called by mboxfs_add_sender() and mboxfs_add_rcpt() in order to add
 * EMAIL's attachments in a subdir called DIR_ATTACH.
 * When N is not NULL, *N points to the newly created attach dir.
 */
int
mboxfs_add_attach (struct node **n, struct node *dir,
    struct node *email)
{
  struct node *attach_dir, *curr_subdir, *curr_attach;
  int  attach_num = 0;
  char name [256];
  char *attach_name;
  BODY *body;
  char *len;
  HEADER *hdr = NODE_INFO(email)->header;

  if (!hdr->content->parts)
  {
    if (n)
      *n = NULL;
    return 0;
  }

  /* Get a pointer to the attach dir. */
  fs_make_subdir (&attach_dir,  dir, str_dir_attach);
  NEW_NODE_INFO (attach_dir);
  NODE_INFO(attach_dir)->type  = N_ATTACH_BASE;

  /* Check whether there already exists an attach dir for this email. */
  if (NODE_INFO(email)->attach_dir)
  {
    fs_make_node (&curr_subdir, attach_dir,
	email->nn->name, S_IFREG|0777);
    fs_link_node (curr_subdir, NODE_INFO(email)->attach_dir);
    NEW_NODE_INFO (curr_subdir);
    NODE_INFO(curr_subdir)->type = N_ATTACH;
  }
  else
  {
    /* Get a pointer to the email subdir. */
    fs_make_subdir (&curr_subdir, attach_dir, email->nn->name);
    NEW_NODE_INFO (curr_subdir);
    NODE_INFO(curr_subdir)->type = N_ATTACH;

    for (body = hdr->content->parts;
	 body;
	 body = body->next)
    {
      if (! (attach_name = body->filename))
	if (! (attach_name = body->d_filename))
	{
	  sprintf (name, NAME_ATTACH"%02i", attach_num++);
	  attach_name = D(name);
	}
      
      fs_make_node (&curr_attach, curr_subdir, attach_name, S_IFREG|0444);
      /* FIXME: no parameter "length"! */
      if ((len = mutt_get_parameter ("length", body->parameter)) != NULL)
      {
	curr_attach->nn_stat.st_size = strtol (len, NULL, 0);
	assert (errno == 0);
      }
      else
	curr_attach->nn_stat.st_size = body->length;

      NEW_NODE_INFO (curr_attach);
      NODE_INFO(curr_attach)->header = NULL;
      NEW_ATTACH_INFO(curr_attach);
      NODE_INFO(curr_attach)->attach->body = body;
    }

    NODE_INFO(email)->attach_dir = curr_subdir;
  }

  if (n)
    *n = curr_subdir;

  return 0;
}

/* mboxfs_add_sender()
 * Called by mboxfs_add_header() to add a sender node to directory DIR.
 */
int
mboxfs_add_sender (struct node **n, struct node *dir,
    HEADER *hdr, struct node *link_target)
{
  char  *subdirname;		/* sender's dir name */
  struct node *thissubdir;	/* sender's dir */
  struct node *sender;		/* current header's node */
  unsigned long nodenum;	/* current sender's node number */
  char   nname[256];

  subdirname = hdr->env->from->personal;
  if (!subdirname)
    subdirname = hdr->env->from->mailbox;
  assert (subdirname != NULL);
  nodenum = fs_make_subdir (&thissubdir, dir, subdirname);
  NEW_NODE_INFO(thissubdir);
  NODE_INFO(thissubdir)->type = N_SENDER;
  NODE_INFO(dir)->type        = N_SENDERS_BASE;
  
  /* Add the node. */
  sprintf (nname, "%05lu.mail", nodenum);
  fs_make_node (&sender, thissubdir, D(nname), S_IFREG|0444);
  NEW_NODE_INFO (sender);

  /* Check whether this can be symlinked and set the appropriate info/stats */
  if (link_target)
  {
    *NODE_INFO(sender)        = *NODE_INFO(link_target);
    NODE_INFO(sender)->sender = link_target;
    fs_link_node (sender, link_target);
  }
  else
  {
    NODE_INFO(sender)->header = hdr;
    NODE_INFO(sender)->type   = N_NODE;
    sender->nn_stat.st_size = hdr->content->length;
    if (mboxfs_options.show_headers)
    {
      size_t s = hdr->content->offset - hdr->content->hdr_offset;
      sender->nn_stat.st_size += s;
    }
  }

  sender->nn_stat.st_atime = sender->nn_stat.st_mtime =
    sender->nn_stat.st_ctime = hdr->date_sent;

  /* Add related attachments. */
  mboxfs_add_attach (NULL, thissubdir, sender);

  if (n)
    *n = sender;
  return 0;
}

/* mboxfs_add_rcpt()
 * Called by mboxfs_add_header() to add a recipient node to directory DIR.
 */
int
mboxfs_add_rcpt (struct node **n, struct node *dir,
    HEADER *hdr, struct node *link_target)
{
  char  *subdirname;
  char   nname[256];
  struct node *thissubdir;
  struct node *rcpt;
  unsigned long nodenum;

  if (!hdr->env->to)
  {
    if (n)
      *n = NULL;
    return 0;
  }

  subdirname = hdr->env->to->personal;
  if (!subdirname)
    subdirname = hdr->env->to->mailbox;
  assert (subdirname != NULL);
  nodenum = fs_make_subdir (&thissubdir, dir, subdirname);
  NEW_NODE_INFO(thissubdir);
  NODE_INFO(thissubdir)->type = N_RCPT;
  NODE_INFO(dir)->type        = N_RCPT_BASE;
  
  /* Add the node. */
  sprintf (nname, "%05lu.mail", nodenum);
  fs_make_node (&rcpt, thissubdir, D(nname), S_IFREG|0444);
  NEW_NODE_INFO (rcpt);
  rcpt->nn_stat.st_atime = rcpt->nn_stat.st_mtime =
    rcpt->nn_stat.st_ctime = hdr->date_sent;

  /* Make it look like a symlink to ../DIR_SENDERS */
  fs_link_node (rcpt, link_target);//, dir->nn->dir);
  *NODE_INFO(rcpt) = *NODE_INFO(link_target);
  NODE_INFO(rcpt)->sender = link_target;

  /* Add related attachments. */
  mboxfs_add_attach (NULL, thissubdir, rcpt);

  if (n)
    *n = rcpt;
  return 0;
}

/* mboxfs_add_thread()
 * Called by mboxfs_add_header() to add a thread node to directory DIR.
 */
int
mboxfs_add_thread (struct node **n, struct node *dir,
    HEADER *hdr, struct node *link_target)
{
  char  *subdirname;
  char   nname[256];
  struct node *thissubdir;
  struct node *rcpt;
  unsigned long nodenum;

  if (!hdr->env->to)
  {
    if (n)
      *n = NULL;
    return 0;
  }

  /* subject without "Re:" */
  subdirname = hdr->env->real_subj;
  if (!subdirname)
    subdirname = hdr->env->subject;
  if (!subdirname)
    subdirname = str_nosubject;
  nodenum = fs_make_subdir (&thissubdir, dir, subdirname);
  NEW_NODE_INFO(thissubdir);
  NODE_INFO(thissubdir)->type = N_THREAD;
  NODE_INFO(dir)->type        = N_THREADS_BASE;
  
  /* Add the node. */
  sprintf (nname, "%05lu.mail", nodenum);
  fs_make_node (&rcpt, thissubdir, D(nname), S_IFREG|0444);
  NEW_NODE_INFO (rcpt);
  rcpt->nn_stat.st_atime = rcpt->nn_stat.st_mtime =
    rcpt->nn_stat.st_ctime = hdr->date_sent;

  /* Make it look like a symlink to ../DIR_SENDERS */
  fs_link_node (rcpt, link_target);//, dir->nn->dir);
  *NODE_INFO(rcpt)        = *NODE_INFO(link_target);
  NODE_INFO(rcpt)->sender = link_target;

  /* Add related attachments. */
  mboxfs_add_attach (NULL, thissubdir, rcpt);

  if (n)
    *n = rcpt;
  return 0;
}


/* mboxfs_add_date()
 * Called by mboxfs_add_header() to add a dated directory node to
 * directory DIR.
 */
int
mboxfs_add_date (struct node **n, struct node *dir,
    HEADER *hdr, struct node *link_target)
{
  char  subdirname [128];
  struct node *thissubdir;
  struct tm email_date;
  struct node *dir_date_senders, *dir_date_rcpt, *dir_date_threads, *sender;
  unsigned long nodenum;
  int err;

  /* Creates a date subdirectory if necessary.
   * Sub-directories are named "MmmYYYY" where "Mmm" is the month.
   */
  localtime_r (&hdr->date_sent, &email_date);
  sprintf (subdirname, "%s%04i",
      get_month (email_date.tm_mon), email_date.tm_year + 1900);
  nodenum    = fs_make_subdir (&thissubdir, dir, D(subdirname));
  NEW_NODE_INFO(thissubdir);
  NODE_INFO(thissubdir)->type = N_DATE;
  NODE_INFO(dir)->type        = N_DATES_BASE;

  /* Create or get senders and recipients directories. */
  fs_make_subdir (&dir_date_senders, thissubdir, str_dir_senders);
  fs_make_subdir (&dir_date_rcpt,    thissubdir, str_dir_rcpt);
  fs_make_subdir (&dir_date_threads, thissubdir, str_dir_threads);
  NEW_NODE_INFO(dir_date_senders);
  NEW_NODE_INFO(dir_date_rcpt);
  NEW_NODE_INFO(dir_date_threads);
  NODE_INFO(dir_date_senders)->type = N_SENDERS_BASE;
  NODE_INFO(dir_date_rcpt)->type    = N_RCPT_BASE;
  NODE_INFO(dir_date_threads)->type = N_THREADS_BASE;

 
  /* Add this email in the senders directory. */
  err = mboxfs_add_sender (&sender, dir_date_senders, hdr, link_target);

  /* Add this email in the reciptients directory
   * and make it appear as a symlink to the previously-created node.
   */
  if (!err)
    err = mboxfs_add_rcpt (NULL, dir_date_rcpt, hdr, sender);

  /* Add this email in the threads directory
   * and make it appear as a symlink to the previously-created node.
   */
  if (!err)
    err = mboxfs_add_thread (NULL, dir_date_threads, hdr, sender);

  if (n)
    *n = thissubdir;

  return err;
}

/* mboxfs_add_header()
 * This function is called by libmutt each time a new header has been
 * parsed. Creates the correponding nodes, that is, the basic tree.
 */
int
mboxfs_add_header (HEADER *hdr)
{
  int err;
  struct node *sender;
  
  assert (hdr != NULL);

  /* Add this email in the senders directory. */
  err = mboxfs_add_sender (&sender, dir_senders, hdr, NULL);

  /* Add this email in the reciptients directory
   * and make it appear as a symlink to the previously-created node.
   */
  if (!err)
    err = mboxfs_add_rcpt (NULL, dir_rcpt, hdr, sender);

  /* Add this email in the threads directory
   * and make it appear as a symlink to the previously-created node.
   */
  if (!err)
    err = mboxfs_add_thread (NULL, dir_threads, hdr, sender);

  /* Add this email in the date sub-directories.
   * and make it appear as a symlink to the previously-created node.
   */
  if (!err)
    err = mboxfs_add_date (NULL, dir_date, hdr, NULL);

  return 0;
}

static void *
read_mbox (void *foo)
{
  if (mbox_open_mailbox (&main_context) != 0)
    error (1, 0, "Provided mailbox could not be parsed (%s, type %s).",
	   main_context.path, mboxfs_options.force_mmdf ? "MMDF" : "MBOX");
  return NULL;
}

error_t
mboxfs_init (struct node **root)
{
  error_t err;

  if (!mboxfs_options.file_name)
    error (1, 0, "No mailbox specified!");

  /* Init. */
  err = fs_init ();
  assert (err == 0);

  /* Create root node */
  err = fs_make_node (&netfs_root_node, NULL, NULL, S_IFDIR | 0555);
  if (err)
    return err;
  netfs_root_node->nn_stat.st_nlink = 2;

  /* Allocate the commonly used strings. */
  str_dir_senders = D(DIR_SENDERS);
  str_dir_rcpt    = D(DIR_RCPT);
  str_dir_date    = D(DIR_DATE);
  str_dir_threads = D(DIR_THREADS);
  str_dir_attach  = D(DIR_ATTACH);
  str_nosubject   = D(NAME_NOSUBJECT);

  /* Create the main directories. Directories don't need the nn->info field. */
  err = fs_make_subdir (&dir_senders, netfs_root_node, str_dir_senders);
  if (err)
    return err;
  err = fs_make_subdir (&dir_rcpt, netfs_root_node, str_dir_rcpt);
  if (err)
    return err;
  err = fs_make_subdir (&dir_date, netfs_root_node, str_dir_date);
  if (err)
    return err;
  err = fs_make_subdir (&dir_threads, netfs_root_node, str_dir_threads);
  if (err)
    return err;

  /* Set the right info for those directories. */
  NEW_NODE_INFO(netfs_root_node);	/* undef */
  NEW_NODE_INFO(dir_senders);
  NEW_NODE_INFO(dir_rcpt);
  NEW_NODE_INFO(dir_date);
  NEW_NODE_INFO(dir_threads);
  NODE_INFO(dir_senders)->type = N_SENDERS_BASE;
  NODE_INFO(dir_rcpt)->type    = N_RCPT_BASE;
  NODE_INFO(dir_date)->type    = N_DATES_BASE;
  NODE_INFO(dir_threads)->type = N_THREADS_BASE;

  /* Set libmutt options */
  bzero (&main_context, sizeof (CONTEXT));	/* probably useless... */
  main_context.notify_new_header = mboxfs_add_header;
  main_context.path = mboxfs_options.file_name;
  main_context.readonly = 1;
  if (mboxfs_options.force_mmdf)
    main_context.magic = M_MMDF;
  else
    main_context.magic = M_MBOX;
  
  /* Load the mailbox. */
  if (mboxfs_options.threaded)
    {
      pthread_t t;
      pthread_create (&t, NULL, read_mbox, NULL);
    }
  else
    read_mbox (NULL);

  return 0;
}

int
mboxfs_set_cd (struct node *dir)
{
  curr_dir = dir;
  curr_node = dir->nn->entries;
  curr_entry = 0;
  return 0;
}

int
mboxfs_skip_entries (int n)
{
  assert (n >= 0);

  /* Skip N first DIR entries. */
  curr_node = curr_dir->nn->entries;

  if (n > 2)
  {
    /* Skip more than `,' and `,,' */
    curr_entry = 2;
    while ((curr_entry < n) && (curr_node))
    {
      curr_node = curr_node->next;
      curr_entry++;
    }
  }
  else
    curr_entry = n;
  
  /* Returns non-null if could not skip N entries. */
  return (curr_entry<=n)?0:1;
}

static int
mboxfs_new_dirent (struct dirent** e, const struct node *n, const char* nodename)
{
  size_t namelen;
  char*  name;
  kern_return_t err;

  assert (nodename != NULL);

  /* N==NULL means that we are considering the node on which the
   * translator is set. */
  namelen = (n) ? strlen (nodename) : 2;

  /* Allocate it. */
  err = vm_allocate (mach_task_self (),
      (vm_address_t*)e, sizeof(struct dirent)+namelen, 1);
  assert (err == KERN_SUCCESS);
  
  /* Copy node name */
  name = &(*e)->d_name[0];

  if (n == NULL)
  {
    /* `..' */
    memcpy(name, nodename, 3);
    namelen = 2;
    (*e)->d_type = DT_DIR;
  }
  else
  {
    memcpy (name, nodename, namelen+1);
    /* Set the type corresponding to n->nn_stat.st_mode */
    if (n->nn_stat.st_mode & S_IFREG)
      (*e)->d_type = DT_REG;
    else if (n->nn_stat.st_mode & S_IFDIR)
      (*e)->d_type = DT_DIR;
    else if (n->nn_stat.st_mode & S_IFLNK)
      (*e)->d_type = DT_LNK;
    else
      (*e)->d_type = DT_UNKNOWN;
  }

  assert (namelen != 0);

  /* if FILENO==0 then the node won't appear. */
  (*e)->d_fileno = 2;
  (*e)->d_namlen = namelen;
  (*e)->d_reclen = sizeof(struct dirent) + namelen;

  return 0;
}

int
mboxfs_get_next_entry (struct dirent **entry)
{
  switch (curr_entry++)
  {
    case 0:
      mboxfs_new_dirent (entry, curr_dir, ".");
      break;
    case 1:
      mboxfs_new_dirent (entry, curr_dir->nn->dir, "..");
      break;
    default:
      if (!curr_node)
	return 1;	/* no more entries */
      else
      {
	mboxfs_new_dirent (entry, curr_node, curr_node->nn->name);
	curr_node = curr_node->next;
      }
      break;
  }

  return 0;
}

/* exist_parent_type()
 * Return non-zero if there exists a parent of NODE of type TYPE.
 */
static int
exist_parent_type (struct node *node, mboxfs_node_t type)
{
  struct node *n;

  for (n = node->nn->dir;
       n != netfs_root_node;
       n = n->nn->dir)
    if (n->nn)
      if (NODE_INFO(n)->type == type)
	return 1;

  return 0;
}

/* mboxfs_lookup_node()
 * Looks up node named NAME. If NAME equals to DIR_*, then eventually create
 * it on the fly.
 */
error_t
mboxfs_lookup_node (struct node** node, struct node* dir, const char* name)
{
  struct node *n;
 
  /* Look for NAME in DIR entries. */
  for (n = dir->nn->entries;
       n && strcmp(name, n->nn->name);
       n = n->next);

  if (n)
    *node = n;
  else
  {
    /* Check whether we can create a DIR_* on the fly. */
    struct node *newdir;
    struct node *n;
    mboxfs_node_t type = NODE_INFO(dir)->type;

    /* Create a DIR_SENDERS dir on the fly. */
    if ( (!strcmp (name, DIR_SENDERS))
	&& ((type == N_RCPT) || (type == N_THREAD))
	&& (!exist_parent_type (dir, N_SENDERS_BASE)) )
    {
      fs_make_subdir (&newdir, dir, str_dir_senders);
      NEW_NODE_INFO (newdir);
      for (n = dir->nn->entries;
	   n != NULL;
	   n = n->next)
	if (! S_ISDIR(n->nn_stat.st_mode))	/* type == N_NODE */
	  mboxfs_add_sender (NULL, newdir, NODE_INFO(n)->header, n);

      *node = newdir;
    }
    /* Create a DIR_RCPT dir on the fly. */
    else if ( (!strcmp (name, DIR_RCPT))
	&& ((type == N_SENDER) || (type == N_THREAD))
	&& (!exist_parent_type (dir, N_RCPT_BASE)) )
    {
      fs_make_subdir (&newdir, dir, str_dir_rcpt);
      NEW_NODE_INFO (newdir);
      for (n = dir->nn->entries;
	   n != NULL;
	   n = n->next)
	if (! S_ISDIR(n->nn_stat.st_mode))	/* type == N_NODE */
	  mboxfs_add_rcpt (NULL, newdir, NODE_INFO(n)->header, n);

      *node = newdir;
    }
    /* Create a DIR_THREADS dir on the fly. */
    else if ( (!strcmp (name, DIR_THREADS))
	&& ((type == N_SENDER) || (type == N_RCPT))
	&& (!exist_parent_type (dir, N_THREAD)) )
    {
      fs_make_subdir (&newdir, dir, str_dir_threads);
      NEW_NODE_INFO (newdir);
      for (n = dir->nn->entries;
	   n != NULL;
	   n = n->next)
	if (! S_ISDIR(n->nn_stat.st_mode))	/* type == N_NODE */
	  mboxfs_add_thread (NULL, newdir, NODE_INFO(n)->header, n);

      *node = newdir;
    }
    /* Create a DIR_DATE dir on the fly. */
    else if ( (!strcmp (name, DIR_DATE))
	&& ((type == N_SENDER) || (type == N_RCPT))
	&& (!exist_parent_type (dir, N_DATE)) )
    {
      fs_make_subdir (&newdir, dir, str_dir_date);
      NEW_NODE_INFO (newdir);
      for (n = dir->nn->entries;
	   n != NULL;
	   n = n->next)
	if (! S_ISDIR(n->nn_stat.st_mode))	/* type == N_NODE */
	  mboxfs_add_date (NULL, newdir, NODE_INFO(n)->header, n);

      *node = newdir;
    }
    else
      return ENOENT;
  }

  return 0;
}

/* mboxfs_read_attach_node()
 * Read the attachment represented by NODE through its cache.
 */
static error_t
mboxfs_read_attach_node (struct node *node, off_t offs, size_t *len, void* data)
{
  static FILE *mboxfp;
  size_t bytes_read = 0, curr_offs = offs, curr_len = *len;
  size_t length = NODE_INFO(node)->attach->body->length;
  BUFFER buf;
  STATE  state;

  /* Initialize BUF and STATE. */
  if (!mboxfp)
  {
    mboxfp = fopen (mboxfs_options.file_name, "r");
    assert (mboxfp != NULL);
  }

  buf.dsize     = ATTACH_CACHE_SIZE;
  bzero (&state, sizeof (state));
  state.fpin    = mboxfp;
  state.bufout  = &buf;

  while (curr_len != 0)
  {
    size_t size = CACHE_INFO(node).end - CACHE_INFO(node).start;

    /* Check whether the requested data has akready been cached. */
    if ((!size) || (curr_offs < CACHE_INFO(node).start))
    {
      /* Update cache. */
      buf.data  = buf.dptr = CACHE_INFO(node).buffer;
      state.datacnt = ATTACH_CACHE_SIZE;
      if (!size)
      {
	/* Load next block. */
	state.offset  = CACHE_INFO(node).offs;
	CACHE_INFO(node).start = CACHE_INFO(node).end;
      }
      else
      {
	/* Start from scratch. */
	state.offset = CACHE_INFO(node).offs = 0;
	CACHE_INFO(node).start = 0;
      }

      mutt_decode_attachment (NODE_INFO(node)->attach->body, &state);

      CACHE_INFO(node).end   = CACHE_INFO(node).start + state.datacnt;
      CACHE_INFO(node).offs  = state.offset;
    }
    else
    {
      /* Read cached data. */
      if (curr_offs < CACHE_INFO(node).end)
      {
	off_t  o    = curr_offs - CACHE_INFO(node).start;
	size_t max  = (curr_len + o < size)?(curr_len):(size - o);

	assert (o < ATTACH_CACHE_SIZE);
	memcpy (data, &CACHE_INFO(node).buffer[o], max);
	data       += max;
	bytes_read += max;
	curr_offs  += max;
	curr_len   -= max;
      }
      else
      {
	/* Request next block when available. */
	if (CACHE_INFO(node).offs < length)
	  CACHE_INFO(node).start = CACHE_INFO(node).end;
	else
	  curr_len = 0;	/* that's it! */
      }
    }
  }

  assert (bytes_read <= *len);
  *len = bytes_read;

  return 0;
}

/* mboxfs_read_text_node()
 * Reads a regular text (email) node.
 */
static error_t
mboxfs_read_text_node (struct node *np, off_t offset, size_t *len, void *data)
{
  static file_t mbox = MACH_PORT_NULL;
  off_t start = NODE_INFO(np)->header->content->offset;
  off_t end   = NODE_INFO(np)->header->content->length + start;
  size_t size;
  error_t err = 0;

  if (mboxfs_options.show_headers)
  {
    start   = NODE_INFO(np)->header->content->hdr_offset;
    /* Make sure st_size is correct (because set_options() might
     * have changed the show_headers flag). */
    np->nn_stat.st_size = end - start;
  }


  if (start + offset >= end)
    *len = 0;
  else
  {
    void* d = data;
    mach_msg_type_number_t num = *len;

    if (mbox == MACH_PORT_NULL)
      mbox = file_name_lookup (main_context.path, O_READ, 0);

    assert (mbox != MACH_PORT_NULL);
    size = end - start - offset;
    size = (size > num)?(num):size;
    num = size;
    err  = io_read (mbox, (data_t*)&data, &num, start + offset, size);
    assert (num <= size);

    /* Checks whether io_read() has allocated a new buffer. */
    if (data != d)	// this shouldn't happen
    {
      memcpy (d, data, num);
      munmap (data, num);
    }

    *len = num;
  }
  return err;
}

error_t
mboxfs_read_node (struct node *np, off_t offset, size_t *len, void* data)
{
  if (S_ISDIR (np->nn_stat.st_mode))
  {
    *len = 0;
    return EISDIR;
  }
  else
  {
    if (NODE_INFO(np)->attach)
      return mboxfs_read_attach_node (np, offset, len, data);
    else
      return mboxfs_read_text_node (np, offset, len, data);
  }

  return 0;
}

/* Defines the debug backend. */
struct fs_backend mboxfs_backend =
{
  mboxfs_init,
  mboxfs_get_argp,
  mboxfs_get_args,
  mboxfs_set_options,
  mboxfs_set_cd,
  mboxfs_skip_entries,
  mboxfs_get_next_entry,
  mboxfs_lookup_node,
  mboxfs_read_node,
};
