/* mboxfs - Creates a filesystem based on the contents of a mailbox.

   Copyright (C) 2002, Ludovic Courtès <ludo@type-z.org>
   Modified by Manuel Menal <mmenal@hurdfr.org>, 2005.

   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 */

/*
 * General filesystem node management facilities.
 */

#include "fsutils.h"


#include <hurd.h>
#include <hurd/netfs.h>
#include <stdio.h>
#include <unistd.h>
#include <maptime.h>
#include <fcntl.h>
#include <assert.h>

/* General info */
static pid_t pid;
static uid_t uid;
static gid_t gid;
static volatile struct mapped_time_value *curr_time;

/* fs_init ()
 * Initialization.
 */
int
fs_init ()
{
  error_t err;

  /* General stuff. */
  pid = getpid ();
  uid = getuid ();
  gid = getgid ();
  err = maptime_map (0, 0, &curr_time);
 
  return err;
}

/* filter_node_name()
 * Filters a node name, that is, remove '/' and chars lower than 32.
 * Returns NAME is no change has been made, or a pointer to a newly
 * malloced buffer otherwise.
 */
char*
filter_node_name (char* name)
{
  if (!name)
    return NULL;
  
  return strdup (name);
}

/* _find_node ()
 * Returns either NULL or a pointer to a node if found.
 */
static inline struct node*
_find_node (struct node *dir, char *name)
{
  struct node *node;
  for (node = dir->nn->entries;
       node != NULL;
       node = node->next)
  {
    if (node->nn->name)
      if (!strcmp (node->nn->name, name))
	break;
  }
  return node;
}

/* _make_node()
 * Creates a new node in directory DIR, with name NAME and mode M. If not NULL,
 * *N points to the newly created node.
 * Does not check whether it aloready exists.
 * NAME is *not* duplicated!
 */
static error_t
_make_node (struct node **n, struct node *dir, char* name, mode_t m)
{
  static ino_t id = 1;
  io_statbuf_t    st;
  struct netnode *nn;
  struct node*   newnode = NULL;

  DEBUG ("NOTICE: Entering %s\n", __PRETTY_FUNCTION__);

  /* Alloctes a new netnode */
  nn = (struct netnode*) calloc (1, sizeof (struct netnode));
  if (!nn)
    return ENOMEM;
  newnode = netfs_make_node (nn);
  if (!newnode)
    return ENOMEM;

  /* General stat */
  st.st_fstype  = FSTYPE_MISC;
  st.st_fsid    = pid;
  st.st_dev     = st.st_rdev = pid;	/* unique device id */
  st.st_uid     = st.st_author = uid;
  st.st_gid     = gid;
  st.st_mode    = m;
  st.st_ino     = id++;	/* unique inode number for each fs node */
  st.st_nlink   = 1;	/* number of subdir plus two, one otherwise. */
  st.st_size    = 0;
  st.st_blksize = 1;
  st.st_blocks  = 1;	/* XXX */
  st.st_gen     = 0;
  
  if (name)
    newnode->nn->name = strdup (name);
  else
    newnode->nn->name = NULL;
  
  if (S_ISREG(m))
    {
      newnode->nn->filled = true; /* This is a regular file; we never fill it,
				     so mark it as done  */
      newnode->nn->type = CONTENT;
    }
  else 
    {
      newnode->nn->filled = false;
      newnode->nn->type = NODE;
    }

  newnode->nn->entries = NULL;	/* ptr to the first entry of this node */
  newnode->nn_stat = st;

  newnode->next = NULL;
  newnode->prevp = NULL;

  if (dir)
    {
      struct node *p;

      netfs_nref (dir);
      
      /* Insert the new node *at the end* of the linked list of DIR entries. */
      if (dir->nn->entries)
	{
	  for (p = dir->nn->entries;
	       p->next;
	       p = p->next);
	  newnode->prevp = &p;
	  p->next = newnode;
	}
      else
	dir->nn->entries = newnode;
      
#if 0
      /* Insert the new node *at the beginning* of the linked list of DIR entries. */
      newnode->next  = dir->nn->entries;
      newnode->prevp = &dir->nn->entries;
      dir->nn->entries = newnode;
      if (newnode->next)
	newnode->next->prevp = &newnode->next;
#endif
      
      newnode->nn->dir = dir;
      
      /* Make sure that DIR is a directory. */
      dir->nn_stat.st_mode |= S_IFDIR;
    }

  fshelp_touch (&newnode->nn_stat,
      TOUCH_ATIME|TOUCH_CTIME|TOUCH_MTIME, curr_time);

  *n = newnode;

  DEBUG ("NOTICE: %s returns with 0.\n", __PRETTY_FUNCTION__);

  return 0;
}

/* fs_make_node()
 * Creates a new node in directory DIR, with name NAME and mode M. If not NULL,
 * *N points to the newly created node.
 * Checks whether there already exists such a node.
 * NAME is *not* duplicated!
 */
error_t
fs_make_node (struct node **n, struct node *dir, char *name, mode_t m)
{
  struct node *newnode = NULL;
  error_t err = 0;

  DEBUG ("NOTICE: Entering %s (name: %s, dir: %s, mode: %u)\n", 
	 __PRETTY_FUNCTION__, name, dir == NULL ? "" : dir->nn->name, m);

  /* DIR == NULL means that we are creating NETFS_ROOT_NODE. */
  if (dir)
    newnode = _find_node (dir, name);

  /* Creates a new one if not found. */
  if (!newnode)
    err = _make_node (&newnode, dir, name, m);

  /* Eventually return a pointer to the newly created node. */
  if (n)
    *n = newnode;

  DEBUG ("NOTICE: %s returns with %d\n", __PRETTY_FUNCTION__, err);

   return err;
}

/* fs_make_subdir ()
 * Used to add a sub-directory to DIR. If SUBDIRNAME already exists in DIR,
 * returns EEXIST; otherwise creates it and returns
 * zero. NEWDIR points to DIR/SUBDIRNAME.
 * It also checks whether SUBDIRNAME already exists.
 * SUBDIRNAME is *not* duplicated!
 */
unsigned long
fs_make_subdir (struct node **newdir, struct node *dir, char *subdirname)
{
  struct node *n;

  DEBUG ("NOTICE: Entering %s (dir: %s, name: %s)\n", 
	 __PRETTY_FUNCTION__, subdirname, dir->nn->name);

  /* Look for an existing dir */
  n = _find_node (dir, subdirname);

  if (!n)
  {
    /* Create a new sub-directory. */
    fs_make_node (&n, dir, subdirname, S_IFDIR|0555);

    /* Set st_nlink to the number of subdirs plus 2 */
    n->nn_stat.st_nlink = 2;
    dir->nn_stat.st_nlink++;
  }
  else
      return EEXIST;
  *newdir = n;
  
  
  DEBUG ("NOTICE: %s returns with 0\n", __PRETTY_FUNCTION__);
  return 0;
}


/* get_path_from_root()
 * Returns the path of a given node (relatively to the given root node).
 * This is a very funny function (see macro below). ;-)
 */
char*
get_path_from_root (struct node *root, struct node *node)
{
#define REVERSE_COPY(dst, src) \
	{ int i; \
	  for (i=0; i < strlen ((src)); i++) \
	    (dst)[i] = src[strlen ((src)) - 1 - i]; \
	  (dst)[strlen ((src))] = '\0'; \
	}

  struct node *n;
  size_t len = 256;
  char *path;
  char *ptr;

  path = (char*)calloc(len, sizeof(char));
  ptr  = path;

  for (n = node;
       (n != root) && (n != NULL);
       n = n->nn->dir)
  {
    /* Reallocate if necessary. */
    if (strlen (path) + strlen (n->nn->name) + 1 + 1 > len)
    {
      char* new;
      size_t offset = ptr - path;
      len *= 2;
      new = realloc (path, len);
      ptr = new + offset;
      path = new;
    }
    REVERSE_COPY (ptr, n->nn->name);
    ptr[strlen (n->nn->name)] = '/';
    ptr += strlen (n->nn->name) + 1;
  }

  /* Remove last slash. */
  assert (strlen (path) > 0);
  path[strlen (path) - 1] = '\0';

  /* Reverse-copy the final result. */
  ptr = (char*)calloc (strlen (path) + 1, sizeof (char));
  REVERSE_COPY (ptr, path);
/*   free (path); */

  return ptr;
}

/* get_path_to_root()
 * Returns the relavive path to the given root node.
 */
char*
get_path_to_root (struct node *root, struct node *node)
{
  struct node *n;
  size_t len = 256;
  char *path;
  char *ptr;

  /* Go to the parent dir if NODE is not a directory. */
  if (! (node->nn_stat.st_mode & S_IFDIR))
    node = node->nn->dir;

  path = (char*)calloc(len, sizeof(char));
  ptr  = path;

  for (n = node;
       (n != root) && (n != NULL);
       n = n->nn->dir)
  {
    /* Reallocate if necessary. */
    if (strlen (path) + 3 + 1 > len)
    {
      char* new;
      size_t offset = ptr - path;
      len *= 2;
      new = realloc (path, len);
      ptr = new + offset;
      path = new;
    }
    strncpy (ptr, "../", 3);
    ptr += 3;
  }

  /* Remove last slash. */
  assert (strlen (path) > 0);
  path[strlen (path) - 1] = '\0';

  /* Reverse-copy the final result. */
  ptr = (char*)calloc (strlen (path) + 1, sizeof (char));
  strcpy (ptr, path);
/*   free (path); */

  return ptr;
}

/* get_common_root()
 * Gets the first common directory.
 */
struct node*
get_common_root (struct node *node1, struct node *node2)
{
#define MAX_PATH_DEPTH 256
  struct node *n1 = node1, *n2 = node2;
  struct node *path1[MAX_PATH_DEPTH];
  struct node *path2[MAX_PATH_DEPTH];
  int i1 = 0, i2 = 0;

  if (n1 == n2)
    return n1;

  /* Save pathes to NETFS_ROOT_NODE in a stack. */
  do
  {
    assert (i1 < MAX_PATH_DEPTH);
    path1[i1++] = n1 = n1->nn->dir;
  }
  while (n1 != netfs_root_node);

  do
  {
    assert (i2 < MAX_PATH_DEPTH);
    path2[i2++] = n2 = n2->nn->dir;
  }
  while (n2 != netfs_root_node);

  /* Get to the last common node. */
  while (path1[--i1] == path2[--i2]);

  return path1[++i1];
}



