/* xmlfs -- a translator for accessing XML documents

   Copyright (C) 2002, 2005 HurdFR.
   Written by  Marc de Saint Sauveur <marc@hurdfr.org>
           and Manuel Menal          <mmenal@hurdfr.org>
   
   xmlfs 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, or (at
   your option) any later version.

   xmlfs 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, USA. 
*/

#include "xmlfs.h"
#include "xml.h"

#include <mach.h>
#include <libxml/xpath.h>

#include <dirent.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <sys/types.h>
#include <sys/mman.h>

/* The user must define this function.  Make sure that NP->nn_stat is
   filled with the most current information.  CRED identifies the user
   responsible for the operation. NP is locked.  */
error_t 
netfs_validate_stat (struct node *np, struct iouser *cred)
{
  return 0; /* everything is good for us ;P */
}

/* This should attempt a chmod call for the user specified by CRED on 
   locked node NP, to change the owner to UID and the group to GID.  */
error_t 
netfs_attempt_chown (struct iouser *cred, struct node *np,
                             uid_t uid, uid_t gid)
{
  return EROFS;
} 

/* This should attempt a chauthor call for the user specified by CRED
   on locked node NP, thereby changing the author to AUTHOR.  */
error_t 
netfs_attempt_chauthor (struct iouser *cred, struct node *np,
                                uid_t author)
{
  return EROFS;
}

/* This should attempt a chmod call for the user specified by CRED on node
   NODE, to change the mode to MODE.  Unlike the normal Unix and Hurd meaning
   of chmod, this function is also used to attempt to change files into other
   types.  If such a transition is attempted which is impossible, then return
   EOPNOTSUP.  */
error_t 
netfs_attempt_chmod (struct iouser *cred, struct node *node,
		     mode_t mode)
{
  return EROFS;
}
/* The user must define this function.  Attempt to turn locked node NP
   (user CRED) into a symlink with target NAME.  */
error_t
netfs_attempt_mksymlink (struct iouser *cred, struct node *np,
			 const char *name)
{
  return EROFS;
}
/* The user must define this function.  Attempt to turn NODE (user
   CRED) into a device.  TYPE is either S_IFBLK or S_IFCHR.  NP is
   locked.  */
error_t netfs_attempt_mkdev (struct iouser *cred, struct node *np,
                             mode_t type, dev_t indexes)
{
  return EROFS;
}
/* The user must define this function.  This should attempt a chflags
   call for the user specified by CRED on locked node NP, to change
   the flags to FLAGS.  */
error_t netfs_attempt_chflags (struct iouser *cred, struct node *np,
                               int flags)
{
  return EROFS;
}
/* This should attempt a utimes call for the user specified by CRED on 
   locked node NP, to change the atime to ATIME and the mtime to MTIME.
   If ATIME or MTIME is null, then set to the current time.  */
error_t netfs_attempt_utimes (struct iouser *cred, struct node *np,
                              struct timespec *atime, struct timespec *mtime)
{
  return EROFS;
}

/* This should attempt to set the size of the locked file NP (for user CRED)
   to SIZE bytes long.  */
error_t netfs_attempt_set_size (struct iouser *cred, struct node *np,
                                loff_t size)
{
  return EROFS;
}

/* The user must define this function.  This should attempt to fetch
   filesystem status information for the remote filesystem, for the
   user CRED. NP is locked.  */
error_t netfs_attempt_statfs (struct iouser *cred, struct node *np, 
			      fsys_statfsbuf_t *st)
{
  return EOPNOTSUPP;
}
/* This should sync the locked file NP completely to disk, for the user CRED.
   If WAIT is set, return only after the sync is completely finished.  */
error_t 
netfs_attempt_sync (struct iouser *cred, struct node *np,
                            int wait)
{
  return 0;
}
/* The user must define this function.  This should sync the entire
   remote filesystem.  If WAIT is set, return only after the sync is
   completely finished.  */
error_t 
netfs_attempt_syncfs (struct iouser *cred, int wait)
{
  return 0;
}

/* Lookup NAME in DIR (which is locked) for USER; set *NP to the found 
   name upon return.  If the name was not found, then return ENOENT.
   On any error, clear *NP. (*NP, if found, should be locked and a 
   reference to it generated.
   This call should unlock DIR no matter what.)  */
error_t 
netfs_attempt_lookup (struct iouser *user, struct node *dir,
                              const char *name, struct node **np)
{
  struct node *nd;
  error_t err;
 
  DEBUG ("NOTICE: netfs_attempt_lookup (name: %s, dir: %p)\n", 
	 name, dir);
  
  if (!dir || dir->nn->type == CONTENT)
    {
      DEBUG ("ERROR: attempted to lookup() in a content node!");
      pthread_mutex_unlock (&dir->lock);
      return ENOTDIR;
    }

  pthread_mutex_unlock (&dir->lock); 

  /* Handle special cases without too much overhead. */
  if (*name == '\0' || strcmp (name, ".") == 0) 
    {
      *np = dir;
      pthread_mutex_lock (&dir->lock);
      netfs_nref (dir);
      pthread_mutex_unlock (&dir->lock);
      return 0;
    }
  else if (strcmp (name , "..") == 0)
    {
      *np = dir->nn->dir;
      pthread_mutex_lock (&dir->lock);
      netfs_nref (*np);
      pthread_mutex_unlock (&dir->lock);
      return 0;
    }

  if (!dir->nn->filled)
    {
      pthread_mutex_lock (&dir->lock); 

      err = fill_dirnode (dir);
      if (err)
	  return err;

      pthread_mutex_unlock (&dir->lock);
    }

  /* Make nd point to the entry we should begin at, according to FIRST_ENTRY. */
  for (nd = dir->nn->entries; nd && strcmp (name, nd->nn->name); nd = nd->next)
    ;
  
  if (nd) 
    {
      pthread_mutex_lock (&dir->lock);
      *np = nd;
      netfs_nref (*np);
      pthread_mutex_unlock (&dir->lock);
      err = 0;
    } 
  else	
    {
      err = ENOENT;
    }
  
  return err;  
}

/* Delete NAME in DIR (which is locked) for USER.  */
error_t
netfs_attempt_unlink (struct iouser *user, struct node *dir,
                              const char *name)
{
  return EROFS;
}

/* Attempt to rename the directory FROMDIR to TODIR. Note that neither 
of the specific nodes are locked.  */
error_t 
netfs_attempt_rename (struct iouser *user, struct node *fromdir,
                              const char *fromname, struct node *todir,
                              const char *toname, int excl)
{
  return EROFS;
}
/* Attempt to create a new directory named NAME in DIR (which is locked)
   for USER with mode MODE. */
error_t netfs_attempt_mkdir (struct iouser *user, struct node *dir,
                             const char *name, mode_t mode)
{
  return EROFS;
}
/* Attempt to remove directory named NAME in DIR (which is locked) for
 USER.  */
error_t netfs_attempt_rmdir (struct iouser *user,
                             struct node *dir, const char *name)
{
  return EROFS;
}
  
/* Create a link in DIR with name NAME to FILE for USER. Note that 
   neither DIR nor FILE are locked. If EXCL is set, do not delete 
   the target.  Return EEXIST if NAME is already found in DIR.  */
error_t netfs_attempt_link (struct iouser *user, struct node *dir,
                            struct node *file, const char *name, int excl)
{
  return EROFS;
}

/* Attempt to create an anonymous file related to DIR (which is locked)
   for USER with MODE.  Set *NP to the returned file upon success. No 
   matter what, unlock DIR.  */
error_t netfs_attempt_mkfile (struct iouser *user, struct node *dir,
                              mode_t mode, struct node **np)

{
  return EROFS;
}

/* Attempt to create a file named NAME in DIR (which is locked) for 
   USER with MODE.  Set *NP to the new node upon return.  On any 
   error, clear *NP.  *NP should be
   locked on success; no matter what, unlock DIR before returning.  */

error_t 
netfs_attempt_create_file (struct iouser *user, struct node *dir,
			   const char *name, mode_t mode, struct node **np)
{
  return EROFS;
}

/* The user must define this function.  Read the contents of locked
   node NP (a symlink), for USER, into BUF.  */
error_t netfs_attempt_readlink (struct iouser *user, struct node *np,
                                char *buf)
{
  return EOPNOTSUPP;
}

/* Node NODE is being opened by USER, with FLAGS.  NEWNODE is nonzero if we
   just created this node.  Return an error if we should not permit the open
   to complete because of a permission restriction. */
error_t
netfs_check_open_permissions (struct iouser *user, struct node *node,
			      int flags, int newnode)
{
  error_t err = 0;
  
  if (!err && (flags & O_READ)) 
    err = fshelp_access (&node->nn_stat, S_IREAD, user);
  if (!err && (flags & O_WRITE)) 
    err = fshelp_access (&node->nn_stat, S_IWRITE, user);
  if (!err && (flags & O_EXEC)) 
    err = fshelp_access (&node->nn_stat, S_IEXEC, user);
  return err;

}

/* Read from the locked file NP for user CRED starting at OFFSET and
   continuing for up to *LEN bytes.  Put the data at DATA.  Set *LEN
   to the amount successfully read upon return.  */
error_t netfs_attempt_read (struct iouser *cred, struct node *node,
                            loff_t offset, size_t *len, void *data)
{
  xmlNodePtr cur = NULL;
  char *content = NULL;
  size_t size = -1;
  error_t err;

  DEBUG ("NOTICE: %s (node: %s, offset: %lld, len: %zd)\n", 
	 __PRETTY_FUNCTION__, node->nn->name, offset, *len);

  cur = xml_find_node (node->nn->pathname);
  if (!cur)
    {
      DEBUG ("ERROR: couldn't find XML node for %s (path: %s)\n", 
	     node->nn->name, node->nn->pathname);
      return -1;
    }
  
  err = xml_dump_node (cur, &content, &size);
  if (err)
    return err;

  DEBUG ("INFO: got content %p\n", content);
  if (!*content)
    return -1;

  if (offset < size)
    {
      DEBUG ("INFO: copying the node\n");
      int gsize = size;

      /* We got more than requested. Copy only the first *len bytes. */
      if (size > *len)
	size = *len;
      
      memcpy (data, content+offset, size);

      /* Adding newline for user's convenience. */
      if (offset + size == gsize)
	  memcpy (data + size++, "\n", 1);

      *len = size;
    }
  else 
    *len = 0;
  
  DEBUG ("INFO: Free'ing the node\n");

  xml_dump_node (NULL, NULL, NULL);
  
  DEBUG ("INFO: Freeing (bis)\n");
  /* Free nodes... */
  xml_find_node (NULL);

  DEBUG ("NOTICE: read %zd bytes of %s successfully\n", *len, node->nn->pathname);

  return 0;
}

/* Write to the locked file NP for user CRED starting at OFSET and continuing
   for up to *LEN bytes from DATA.  Set *LEN to the amount successfully written
   upon return.  */
error_t netfs_attempt_write (struct iouser *cred, struct node *np,
			     loff_t offset, size_t *len, const void *data)
{
  return EROFS;
}

/* Return the valid access types (bitwise OR of O_READ, O_WRITE, and O_EXEC) in
   *TYPES for locked file NP and user CRED.  */
error_t netfs_report_access (struct iouser *cred, struct node *np,
			     int *types)
{
  return EROFS;
}

/* Create a new user from the specified UID and GID arrays. */
struct iouser *netfs_make_user (uid_t *uids, int nuids,
				       uid_t *gids, int ngids)
{
  return NULL;
}

/* Node NP has no more references; free all its associated storage. */
void netfs_node_norefs (struct node *np)
{
  return;
}

/* Returned directory entries are aligned to blocks this many bytes long.
   Must be a power of two.  */
#define DIRENT_ALIGN 4
#define DIRENT_NAME_OFFS offsetof (struct dirent, d_name)

/* Length is structure before the name + the name + '\0', all
   padded to a four-byte alignment.  */
#define DIRENT_LEN(name_len)						      \
  ((DIRENT_NAME_OFFS + (name_len) + 1 + (DIRENT_ALIGN - 1))		      \
   & ~(DIRENT_ALIGN - 1))

/* Fill the  array *DATA of  size MAX_DATA_LEN with up  to NUM_ENTRIES
   dirents from  DIR (which is  locked) starting with entry  ENTRY for
   user  CRED.   The number  of  entries in  the  array  is stored  in
   *DATA_ENTRIES  and  the  number  of  bytes in  *DATA_LEN.   If  the
   supplied buffer is not large enough  to hold the data, it should be
   grown.  */
error_t 
netfs_get_dirents (struct iouser *cred, struct node *dir, 
		   int first_entry, int num_entries, char **data,
		   mach_msg_type_number_t *data_len,
		   vm_size_t max_data_len, int *data_entries)
{
  struct node *nd;
  int count = 0;

  size_t size = 0;
  error_t err;

  /* Add the length of a directory entry for NAME to SIZE and return true,
     unless it would overflow MAX_DATA_LEN or NUM_ENTRIES, in which case
     return false.  */
  int bump_size (const char *name)
    {
      if (num_entries == -1 || count < num_entries)
	{
	  size_t new_size = size + DIRENT_LEN (strlen (name));
	  if (max_data_len > 0 && new_size > max_data_len)
	    return 0;
	  size = new_size;
	  count++;
	  return 1;
	}
      else
	return 0;
    }

  DEBUG ("NOTICE: %s (dir: [addr: %p, name: %s, path: %s], offset: %d, count: %d)\n",
	 __PRETTY_FUNCTION__, dir, dir->nn->name, dir->nn->pathname, 
	 first_entry, num_entries);

  if (!dir)
    return ENOTDIR;

  if (dir->nn->type != NODE)
    return ENOTDIR;

  pthread_mutex_unlock (&dir->lock); 

  if (!dir->nn->filled)
    {
      pthread_mutex_lock (&dir->lock);
      err = fill_dirnode (dir);
      if (err)
	  return err;

      pthread_mutex_unlock (&dir->lock); 
    }

  /* Set nd to the entry we should begin at, 
     according to FIRST_ENTRY. */
  for (nd = dir->nn->entries, count = 2; 
       nd && first_entry > count; nd=nd->next)
    count++;
  count = 0;

  if (first_entry == 0) 
    bump_size ("."); 
  if (first_entry <= 1) 
    bump_size (".."); 
  
  if (!nd || num_entries == 0)
    {
      *data_len = 0;
      *data_entries = 0;
      *data = NULL;
      return 0;
    }
  
  for (struct node *n = nd; n; n = n->next)
    if (!bump_size (n->nn->name))
      ;
  
  *data = mmap (0, size, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0);

  err = ((void *) *data == (void *) -1) ? errno : 0;
  if (!err)
    {
      char *p = *data;

      int add_dir_entry (const char *name, ino_t fileno, int type)
	{
	  if (num_entries == -1 || count < num_entries)
	    {
	      struct dirent hdr;
	      size_t name_len = strlen (name);
	      size_t sz = DIRENT_LEN (name_len);

	      DEBUG ("NOTICE: %s contains %s (fileno: %llu)\n", dir->nn->name, name, fileno);
	      
	      if (sz > size)
		return 0;
	      else
		size -= sz;
	      
	      hdr.d_fileno = fileno;
	      hdr.d_reclen = sz;
	      hdr.d_type = type;
	      hdr.d_namlen = name_len;
	      
	      memcpy (p, &hdr, DIRENT_NAME_OFFS);
	      strcpy (p + DIRENT_NAME_OFFS, name);

	      p += sz;

	      count++;

	      return 1;
	    }
	  else
	    return 0;
	}

      *data_len = size;
      *data_entries = count;
      
      count = 0;

      /* Add `.' and `..' entries.  */ 
      if (first_entry == 0)
	add_dir_entry (".", dir->nn_stat.st_ino, DT_DIR); 
      if (first_entry <= 1) 
	add_dir_entry ("..", 2, DT_DIR); 
      
      /* Fill in the real directory entries.  */
      for (struct node *n = nd; n; n = n->next)
	if (!add_dir_entry (n->nn->name, n->nn_stat.st_ino, 
			    n->nn->type == CONTENT ? DT_REG : DT_DIR))
	  break;
    }

  DEBUG ("NOTICE: quitting get_dirents with status %d\n", err);

  return err;
}
