/* -*-C-*- */

/*
 * Written by: Nikita Danilov <NikitaDanilov@yahoo.COM>
 *
 * Copyright (C) 2000 by Hans Reiser, licensing is governed by reiserfs/README.
 * It is GPL'd, but read that file before adding your code.
 *
 */

/* define what a host file system is here -Hans */
/* define what this filesystem does here -Hans */

/* So, here it goes... */

/* 
   OVERALL

   This is the main file of the dafs module. This module implements
   DAFS --- Direct Access File System. This file-system exists
   only in memory (like procfs) and provides access to some of the 
   VFS internals. Each mounted instance of dafs operates on top of
   `host' file-systems. Default host file-system is given through
   mount option `host', but some operations can take path to host
   file-system mount-point as a parameter.

   STRUCTURE AND PURPOSE

   In the top-level directory of mounted dafs instance there is a
   file named DA_README (macro #defined in da.h, "README" by default).
   Content of this file is taken from char *readme[] array defined
   later in this file. The rest of the file-system is under hidden
   directory DA_ENTRY_POINT_NAME ("*knock-before-entering*" by default).
   It is done this way to prevent find(1) and alikes from traversing
   through dafs which can be dangerous.

   *knock-before-entering* has sub-directory called DA_OPEN_BY_INODE_NAME
   ("open-by-inode" by default):

   <dafs-mount-point>
       README
	   *knock-before-entering*
	       open-by-inode

   open-by-inode doesn't contain any files by itself its function is to perform
   lookups: lookup of name 
      /<dafs-mount-point>/DA_ENTRY_POINT_NAME/DA_OPEN_BY_INODE_NAME/#<hex-number>
   will open file with the inode number <hex-number> on the default host
   file-system. Default host file-system is given as a path to its mount-point
   (by dafs mount option `host'). Another mount option `sbr' 
   (== super block resolution) governs how this path is interpreted. If sbr==e 
   (early) then path is traversed during mount operation and the result of this
   traversal should be mount-point of file-system that will be used as default
   host file-system. If sbr==l (late) than path will be traversed on each
   lookup. This is especially useful when host file-system is given by relative
   path (e.g., host=.).

   Lookup of name 
      /<dafs-mount-point>/DA_ENTRY_POINT_NAME/DA_OPEN_BY_INODE_NAME/@<path>#<hex-number>
   opens file with inode number <hex-number> on the host file-system whose
   mount-point is at <path> (<path> can be relative).

   This allows one to open files quickly (completely eliminating directory
   traversing) provided inode numbers are known in advance. It can be
   useful for the applications that want to use file-system just as storage
   of files and don't need any hierarchical name-space (database 
   management system or proxy servers like Squid (pushing considerable
   effort into attempts to subvert inefficient directory-based interfaces)).

   REISERFS SPECIFICS

   In linux-2.2 not all file-system can open file given inode number
   (formally: not all file-systems support iget()), reiserfs being an
   important example.

   If at the mount time dafs detects that default host file-system is
   reiserfs it creates additional files under entry-point directory:
   DA_OPEN_BY_KEY_NAME ("open-by-key" by default) and
   DA_MAGIC_FINGER_NAME ("magic-finger")

   <dafs-mount-point>
       README
	   *knock-before-entering*
	       open-by-inode
		   open-by-key
		   magic-finger

   Again open-by-key is directory with special lookup semantics. In stead 
   of opening files by inode number is allows to open files by reiserfs key
   which is an instance of struct key defined in linux/reiserfs_fs.h
   Lookups should be of the form:
      #<h1>:<h2>:<h3>:<h4> and
      @<path>#<h1>:<h2>:<h3>:<h4>
   where <h_i> is i-th field in the struct key (in the order of declaration)
   represented as 8-digit hex. digits. 
   (Reiserfs-specific parse of key representation is processed in the
   lookup_by_search_by_key().)

   Magic-finger implements special ioctl to obtain reiserfs key of the 
   open file (implemented in magic_finger_ioctl()).

   IMPLEMENTATION

   * Macros like HDEBUG, HASSERT etc. expands to nothing if DA_DEBUG is not set
   (but HPRINT and HPANIC work always).

   * After mount VFS supplied super-block contains reference to the instance of
   struct da_super_info in u.generic_sbp. Super block is set up in read_super().
   
   * Each dafs file and directory is represented by instance of struct da_entry
   declared in da.h. Functions working with da_entries are in entry.c. da_entries
   are organized into tree and lookup() (entry.c) is `normal' lookup function
   used by ordinary dafs directories.

   * Special directories use open_by_key_lookup() and open_by_key_reiser_lookup()
   lookup functions, which share general parsing code 


      open_by_key_reiser_lookup                   open_by_key_lookup
                            |                       |
 lookup_by_search_by_key()->|                       |<----------lookup_by_iget()
 						    V                       V      |
                           open_by_key_lookup_internal     +--it is callback
                            |                       |
 						    |                       |
 						    V                       V
               open_by_key_in_def_fs            open_by_key_in_fs
                            |                       |
                            +-----------+-----------+
                                        |
 									    V
                                    insert_inode
                                        |
                                (calling callback)
                                  |            |
                                  |            |
    							  V            V
                  lookup_by_search_by_key     lookup_by_iget

    If you think this comment is too long, consider it to be revenge
	for the cutting of lovely GNU copyright plate that used to be at
	the top of this file.
*/
#include "da.h"

#include <linux/module.h>

/* $Id$ */
static char cvsid[] = "@(#)$Id$";
static char debugFileId[] = __FILE__;

MODULE_AUTHOR( "Nikita Danilov NikitaDanilov@Yahoo.COM" );
MODULE_DESCRIPTION( "DaFS: direct access (to VFS) file system. "
					"Provides RAM-only FS to look up files in the host fs." );
/* externs */

/* forward declarations */

static struct dentry *open_by_key_lookup_internal( struct inode *parent, 
												   struct dentry *entry,
												   lookup_function lookup );

/* global data definitions */

static struct super_operations sops = {
  read_inode:	read_inode,
  put_inode:	put_inode,
  delete_inode:	delete_inode,
  put_super:	da_put_super,
  statfs:	da_statfs,
};

static struct inode_operations	open_by_key_inode_ops = {
#if defined( LINUX_2_2 )
  default_file_ops: &file_ops,
#endif
  lookup: open_by_key_lookup,
};

#if defined( DA_USE_REISERFS )
static struct inode_operations	open_by_key_key_ops = {
#if defined( LINUX_2_2 )
  default_file_ops: &file_ops,
#endif
  lookup: open_by_key_reiser_lookup,
};

static struct file_operations magic_finger_file_ops = {
  ioctl: magic_finger_ioctl,
};

static struct inode_operations	magic_finger_inode_ops = {
#if defined( LINUX_2_2 )
  default_file_ops: &magic_finger_file_ops,
#endif
  lookup: lookup,
};
#endif

static char fs_name[] = DA_FS_NAME;
static char reiserfs_name[] = DA_REISERFS_NAME;
static char entry_point[] = DA_ENTRY_POINT_NAME;
static char open_by_inode[] = DA_OPEN_BY_INODE_NAME;
static char open_by_key[] = DA_OPEN_BY_KEY_NAME;
static char root_name[] = DA_ROOT_NAME;
static char magic_finger_name[] = DA_MAGIC_FINGER_NAME;
static char readme_name[] = DA_README;

/*  A file containing readme is located in the top-level directory of DA filesystem. */
static char *readme[] = { 
  NULL,
  "\tThis  filesystem  provides  Direct  Access to the VFS  internals.\n"
  "\tAs such it has little or no security.\n"
  "\n"
  "\tRest of filesystem is under hidden directory `" DA_ENTRY_POINT_NAME "'.\n"
  "\n"
  "Have a nice day.\n",
  "porting to 2.2. kernel.\nrelax.\n",
  "You are surrounded by twisty little passages, all alike.\n",
  "Are you sure you want to enter?\n",
  "kernel: panic: " DA_README ": I/O trap triggered. Switching to "
  "MULTICS API personality.\n",
  NULL
};

static LIST_HEAD( mount_points );
static DECLARE_FSTYPE( fs_type, fs_name, read_super, 
					   0 
#if defined( LINUX_2_3 )
					   | FS_NO_DCACHE /* Doesn't work in 2.2 */
#endif
					   );

/* definitions */

#if defined( DA_DEBUG )

/* this is debug function used during umount to check for
   used inodes.
*/
static int check_list( struct list_head *head, 
					   struct super_block * sb, 
					   struct list_head * dispose )
{
  struct list_head *next;
  int busy = 0;

  next = head->next;
  for (;;) 
	{
	  struct list_head * tmp = next;
	  struct inode * inode;

	  next = next->next;
	  if (tmp == head)
		break;
	  inode = list_entry(tmp, struct inode, i_list);
	  if (inode->i_sb != sb)
		continue;
	  if (!inode->i_count) 
		{
		  continue;
		}
	  else
		{
		  HDEBUG( "busy inode: %li count: %i nlink: %i", 
				  inode -> i_ino, inode -> i_count, inode -> i_nlink );
		}
	  busy = 1;
	}
  return busy;
}

/*
  used to debug umounts.
 */
int check_umount( struct super_block * sb )
{
  int busy;
  LIST_HEAD(throw_away);

  busy = 0;
  spin_lock(&inode_lock);
  busy = check_list(&inode_in_use, sb, &throw_away);
  if( busy )
	{
	  HDEBUG( "busy inodes in inode_in_use" );
	}
  busy |= check_list(&sb->s_dirty, sb, &throw_away);
  if( busy )
	{
	  HDEBUG( "busy inodes in sb->s_dirty" );
	}
  spin_unlock(&inode_lock);

  return busy;
}
#endif

/* copied from procfs. */
void put_inode( struct inode * inode )
{
  TRACE;
  HDEBUG( "inode: %li count: %i nlink: %i", 
		  inode -> i_ino, inode -> i_count, inode -> i_nlink );
  if( inode -> i_count == 1 )
	{
	  inode -> i_nlink = 0;
	}
}

/* copied from procfs. */
void delete_inode( struct inode *inode )
{
  TRACE;
  HDEBUG( "inode: %li count: %i nlink: %i", 
		  inode -> i_ino, inode -> i_count, inode -> i_nlink );
#if defined( LINUX_2_3 )
  inode -> i_state = I_CLEAR;
#endif
  HENTRY( inode -> u.generic_ip ) -> inode = NULL;
}

/* release entry's inode */
static void clean_up_entry( struct da_entry *entry )
{
  TRACE;
  HDEBUG( "name: `%s'", entry -> name );
  if( entry -> inode != NULL )
	{
	  iput( entry -> inode );
	}
  free_entry( entry );
}

/*
  deallocate all entries still allocated.
  scan global list of allocated entries calling 
  clean_up_entry() for each.
 */
static void clean_up()
{
  struct list_head *scan;

  TRACE;

  /* first clean all entries but dots and dotdots */
  list_for_each( scan, &entries )
	{
	  struct da_entry *entry;

	  entry = HENTRY( scan );
	  if( strcmp( entry -> name, "." ) &&
		  strcmp( entry -> name, ".." ) )
		{
		  clean_up_entry( entry );
		}
	}
  /* now kill the rest (should be only .s and ..s) */
  while( entries.next != &entries )
	{
	  TRACE;
	  free_entry( HENTRY( entries.next ) );
	}
}

/* called by umount. deallocate da_entries, release inodes,
   deallocate some global per mount-point data and remove 
   mount point from global list. */
void da_put_super( struct super_block *sb )
{
  struct list_head *mount_point;

  TRACE;
  list_for_each( mount_point, &mount_points )
	{
	  if( HINFO( mount_point ) -> super == sb )
		{
		  struct da_super_info *info;

		  info = HINFO( mount_point );
		  HDEBUG( "found" );
		  list_del( mount_point );
		  clean_up();
#if defined( DA_DEBUG )
		  check_umount( info -> super );
#endif
		  if( info -> late_sbr )
			{
			  recycle( info, info -> sbr.mount_point, strlen( info -> sbr.mount_point ) + 1 );
			}
		  else
			{
			  dput( info -> sbr.host_super -> s_root );
			}
#if defined( DA_DEBUG )
		  HDEBUG( "Allocation stats: allocations: %li allocated: %li total: %li",
				  info -> allocations, info -> allocated, info -> total );
#endif		  
		  recycle( NULL, mount_point, sizeof( struct da_super_info ) );
		  MOD_DEC_USE_COUNT
		  return;
		}
	}
  HDEBUG( "not found" );
}

/* called by VFS. As dafs exists only in memory
   there is almost nothing to do. */
void read_inode( struct inode * inode )
{
  HASSERT( inode != NULL );
  HDEBUG( "%lx", inode -> i_ino );
  HASSERT( inode -> i_sb -> s_magic = DA_SUPER_MAGIC );
  inode -> i_mtime = inode -> i_atime = inode -> i_ctime = CURRENT_TIME;
  HDEBUG( "inode: %li count: %i nlink: %i", 
		  inode -> i_ino, inode -> i_count, inode -> i_nlink );
}

#if defined( LINUX_2_2 )
/* should return fs statistics. Complicated
   by the change of the interface in linux-2.3 */
int da_statfs( struct super_block *sb, 
			   struct statfs *user_space_buffer, int bufsiz )
{
  struct statfs tmp;
  struct statfs *buf = &tmp;
#else
int da_statfs( struct super_block *sb, struct statfs *buf )
{
#endif
  TRACE;
  buf -> f_type = DA_SUPER_MAGIC;
  buf -> f_bsize = PAGE_SIZE/sizeof(long);
  buf -> f_bfree = 0;
  buf -> f_bavail = 0;
  buf -> f_ffree = 0;
  buf -> f_namelen = NAME_MAX;
#if defined( LINUX_2_2 )
  return copy_to_user( user_space_buffer, buf, bufsiz ) ? -EFAULT : 0;
#else
  return 0;
#endif
}
 
/* Used as lookup callback by open_by_key_lookup() */
static struct inode *lookup_by_iget( struct da_super_info *info, 
									 struct super_block *super, void *inode_no )
{
  unsigned long ino;
  char *endp;

  ino = simple_strtoul( ( char * ) inode_no, &endp, 16 );
  if( ( ( ( char * ) inode_no )[ 0 ] == '\0' ) || *endp )
	{
	  HDEBUG( "filename `%s' doesn't look like hex number.", ( char * ) inode_no );
	  return NULL;
	}
  else
	{
	  HDEBUG( "inode number: `%lx'", ino );
	  return iget( super, ino );
	}
}

/*
  Now all information is gathered. Just call fs-specific
  lookup procedure and stuff result into dentry.

  Currently lookup can be either lookup_by_iget() or 
  lookup_by_search_by_key().

  Called from open_by_key_in_def_fs() and open_by_key_in_fs().
 */
static void insert_inode( struct da_super_info *info, 
						  struct super_block *sb,
						  char  *inode_name,
						  struct dentry *entry,
						  lookup_function lookup )
{
  TRACE;
  if( sb != NULL )
	{
	  struct inode *in;
	  
	  in = lookup( info, sb, inode_name );
	  if( in != NULL )
		{
		  d_add( entry, in );
		  HDEBUG( "Ok" );
		}
	  else
		{
		  HDEBUG( "Cannot resolve inode number\n" );
		}
	}
  else
	{
	  HDEBUG( "Cannot resolve mountpoint\n" );
	}
}

/* what does def stand for? aaah, default? -Hans */
/* Called from open_by_key_lookup_internal(). */
static void open_by_key_in_def_fs( struct da_super_info *info,
								   char *inode_num, struct dentry *entry,
								   lookup_function lookup )
{
  HASSERT( info != NULL );
  HASSERT( inode_num != NULL );
  HASSERT( entry != NULL );

  insert_inode( info, get_host_super( info ), inode_num, entry, lookup );
}

/* Called from open_by_key_lookup_internal().
   Parse `@<path>#...' part of the file name. Obtain super block
   of file-system mounted at <path>.
 */
static void open_by_key_in_fs( struct da_super_info *info,
							   char *inode_num, struct dentry *entry,
							   lookup_function lookup )
{
  char *scan;

  for( scan = inode_num + strlen( inode_num ) - 1 ; 
	   scan >= inode_num ; --scan )
	{
	  if( *scan == DA_START_KEY )
		{
		  *scan = '\0';
		  break;
		}
	}
  if( scan >= inode_num )
	{
	  struct super_block *sb;

	  sb = get_super_by_mount_point( inode_num );
	  insert_inode( info, sb, scan + 1, entry, lookup );
	  dput( sb -> s_root );
	  *scan = DA_START_KEY;
	}
  else
	{
	  HDEBUG( "filename `%s' doesn't look like `%cpath%cinode_number'.", 
			  inode_num, DA_START_INDIRECT, DA_START_KEY );
	}
}

struct dentry *open_by_key_lookup( struct inode *parent, struct dentry *entry )
{
  return open_by_key_lookup_internal( parent, entry, lookup_by_iget );
}

#if defined( DA_USE_REISERFS )

/* These macros are part of internal parsing machinery used by 
   lookup_by_search_by_key() before agreeing with the following
   comment of Hans look at the function code.
*/
#define DIR_ID_OFFSET_IN_KEY_STR   0
#define OBJ_ID_OFFSET_IN_KEY_STR   9
#define OFFSET_OFFSET_IN_KEY_STR  18
#define UNIQUE_OFFSET_IN_KEY_STR  27
#define LAST_OFFSET_IN_KEY_STR    36

/* this is amazingly ugly code -Hans */
#define CHECK_AND_SET( field, start, end ) \
   key . field = simple_strtoul( &key_string[ start ], &endp, 16 ); \
   if( endp != &key_string[ end - 1 ] ) \
     { \
       HPRINT( "%s(%s broken)", wrong_format, #field ); \
       return NULL; \
     }

/* 
   Used as lookup callback by open_by_key_reiser_lookup().
   Parse string encoding of reiserfs key and call
   reiserfs_iget() to obtain inode.
 */
static struct inode *lookup_by_search_by_key( struct da_super_info *info, 
											  struct super_block *super, 
											  void *opaque )
{
  struct key key;
  char *endp;
  char *key_string;

  static const char wrong_format[] = "Wrong key format ";

  TRACE;

  /* 
	 opaque contains reiserfs key encoded as four eight-digit
	 hex. numbers delimited by colons one per field in
	 struct key:
  */
  /* 0         1         2         3     */
  /* 01234567890123456789012345678901234 */
  /* ffffffff:ffffffff:ffffffff:ffffffff */
  /* dir_id  :obj_id  :offset  :unique   */

  key_string = ( char * ) opaque;
  HDEBUG( "key: `%s'", key_string );
  /* first some obvious checks */
  if( strlen( key_string ) != 35 )
	{
	  HPRINT( "%s(too short)", wrong_format );
	  return NULL;
	}
  else if( ( key_string[ OBJ_ID_OFFSET_IN_KEY_STR - 1 ] != DA_KEY_DELIMITER ) ||
		   ( key_string[ OFFSET_OFFSET_IN_KEY_STR - 1 ] != DA_KEY_DELIMITER ) ||
		   ( key_string[ UNIQUE_OFFSET_IN_KEY_STR - 1 ] != DA_KEY_DELIMITER ) )
	{
	  HPRINT( "%s(missing `%c')", wrong_format, DA_KEY_DELIMITER );
	  return NULL;
	}
  else
	{
	  /* fillup fields in binary struct key from key_string fields */
	  /* we need to pass both start and end offsets to check errors
		 after simple_strtoul() in CHECK_AND_SET()
	  */
	  CHECK_AND_SET( k_dir_id, 
					 DIR_ID_OFFSET_IN_KEY_STR, 
					 OBJ_ID_OFFSET_IN_KEY_STR );
	  CHECK_AND_SET( k_objectid, 
					 OBJ_ID_OFFSET_IN_KEY_STR, 
					 OFFSET_OFFSET_IN_KEY_STR );
	  CHECK_AND_SET( k_offset, 
					 OFFSET_OFFSET_IN_KEY_STR, 
					 UNIQUE_OFFSET_IN_KEY_STR );
	  CHECK_AND_SET( k_uniqueness, 
					 UNIQUE_OFFSET_IN_KEY_STR, 
					 LAST_OFFSET_IN_KEY_STR );

	  HDEBUG( "key: { %x, %x, %x, %x }", 
			  key.k_dir_id, key.k_objectid, key.k_offset, key.k_uniqueness );
	  return reiserfs_iget( super, &key );
	}
}

struct dentry *open_by_key_reiser_lookup( struct inode *parent, 
										  struct dentry *entry )
{
  return open_by_key_lookup_internal( parent, entry, lookup_by_search_by_key );
}

/*
  Protect environment (== namespace) from the pollution.
 */
#undef DIR_ID_OFFSET_IN_KEY_STR
#undef OBJ_ID_OFFSET_IN_KEY_STR
#undef OFFSET_OFFSET_IN_KEY_STR
#undef UNIQUE_OFFSET_IN_KEY_STR
#undef LAST_OFFSET_IN_KEY_STR

#endif

/*
  General (fs-independent) part of lookup procedure.
  Called from open_by_key_reiser_lookup() and open_by_key_lookup().
  Find should default host file-system have to be used or
  one supplied in the file name.

  lookup parameter will be used to obtain inode later when inode
  number or key will be parsed.
 */
struct dentry *open_by_key_lookup_internal( struct inode *parent, 
											struct dentry *entry,
											lookup_function lookup )
{
  int name_length;

  HASSERT( parent != NULL );
  HASSERT( entry != NULL );

  name_length = entry -> d_name.len;
  if( name_length == 0 )
	{
	  return NULL;
	}
  else
	{
	  /* Copy file name to key_string[] from entry -> d_name.name
		 because we are going to modify it during parsing.
	  */
	  char key_string[ MAX_KEY_STRING_LENGTH + 1 ];
	  struct da_super_info *info;

	  if( name_length >= MAX_KEY_STRING_LENGTH )
		{
		  HDEBUG( "filename `%*.*s' is too long. Truncated.",
				  entry -> d_name.len, entry -> d_name.len, entry -> d_name.name );
		  name_length = MAX_KEY_STRING_LENGTH;
		}
	  memcpy( key_string, entry -> d_name.name, name_length );
	  key_string[ name_length ] = '\0';

	  info = ( ( struct da_entry * ) parent -> u.generic_ip ) -> info;

	  /*
		File name should have form
		   #<hex-number> or
		   @<path>#<hex-number>
	   */
	  switch( key_string[ 0 ] )
		{
		  /* look up by key in default host fs  */
		case DA_START_KEY: /* DA_START_KEY == '#' */
		  open_by_key_in_def_fs( info, key_string + 1, entry, lookup );
		  break;
		  /* look up by key in fs at path  */
		case DA_START_INDIRECT: /* DA_START_INDIRECT == '@' */
		  open_by_key_in_fs( info, key_string + 1, entry, lookup );
		  break;
		default:
		  HDEBUG( "Unknown filename format" );
		  break;
		}
	}
  return NULL;
}

/* ioctl to obtain reiserfs key of open file
   either by file descriptor or by inode number.
*/
int magic_finger_ioctl( struct inode * inode, struct file * filp, 
						unsigned int cmd, unsigned long arg )
{
  struct da_magic_finger_ioctl *mf;
  struct inode *target;

  HASSERT( current -> files != NULL );

  TRACE;
  HASSERT( inode != NULL );
  HASSERT( filp != NULL );

  mf = ( struct da_magic_finger_ioctl * ) arg;
  target = NULL;
  switch( cmd )
	{
#if defined( DA_USE_REISERFS )
	case MAGIC_FINGER_BY_FD:
	  {
		int fd;
		struct file *file;

  		TRACE;
		if( get_user( fd, &mf -> u.fd ) )
		  {
			return -EFAULT;
		  }
  		HDEBUG( "fd: %i", fd );
		HDEBUG( "pid: %i", current -> pid );
		HASSERT( current -> files != NULL );
		file = fget( fd );
  		TRACE;
		if( ( file != NULL ) &&
			( file -> f_dentry != NULL ) )
		  {
			target = file -> f_dentry -> d_inode;
			fput( file );
		  }
		else
		  {
			fput( file );
			return -EBADF;
		  }
		break;
	  }
	case MAGIC_FINGER_BY_INO:
	  {
		unsigned long ino;

  		TRACE;
		if( get_user( ino, &mf -> u.ino ) )
		  {
			return -EFAULT;
		  }
  		TRACE;
		target = iget
		  ( get_host_super
			( ( ( struct da_entry * ) inode -> u.generic_ip ) -> info ), ino );
		break;
	  }
#endif
	  /* This is basically mis-guidede and untested attempt
		 to get a facility to clear buffer cache of  particular
		 file-system (it is not very useful because it is only
		 metadata that are stored in the buffer cache now).
		 One should clear page cache also...
	  */
	case MAGIC_FINGER_INVALIDATE_BUFFERS:
	  {
		invalidate_buffers
		  ( get_host_super( ( ( struct da_entry * ) inode -> u.generic_ip ) 
							-> info ) -> s_dev );
		return 0;
	  }
	case MAGIC_FINGER_DESTROY_BUFFERS:
	  {
		destroy_buffers
		  ( get_host_super( ( ( struct da_entry * ) inode -> u.generic_ip ) 
							-> info ) -> s_dev );
		return 0;
	  }
	default:
	  return -ENOTTY;
	}
  TRACE;
  if( target != NULL )
	{
	  if( strcmp( target -> i_sb -> s_type -> name, reiserfs_name ) )
		{
		  return -EINVAL;
		}
	  /* not using copy_to_user because of the alignment inside struct */
	  TRACE;
	  return 
		( put_user( target -> u.reiserfs_i.i_key[ 0 ], &mf -> key.k_dir_id ) ||
		  put_user( target -> u.reiserfs_i.i_key[ 1 ], &mf -> key.k_objectid ) ||
		  put_user( target -> u.reiserfs_i.i_key[ 2 ], &mf -> key.k_offset ) ||
		  put_user( target -> u.reiserfs_i.i_key[ 3 ], &mf -> key.k_uniqueness ) ) ?
		-EFAULT : 0;
	}
  else
	{
	  return -ENOENT;
	}
}

static struct wait_queue *queue;

/*
  create tree of da_entries starting from the root.
 */
void create_root( struct da_entry *root_dir, struct da_super_info *info )
{
  struct da_entry *entry;

  TRACE;
  HASSERT( root_dir != NULL );
  HASSERT( root_dir -> inode != NULL );
  HASSERT( root_dir -> parent == NULL );
  HASSERT( info != NULL );

#if defined( LINUX_2_3 )
  root_dir -> inode -> i_fop = &file_ops;
#endif
  root_dir -> inode -> i_op = &inode_ops;
  root_dir -> parent = root_dir;
  create_dot_dotdot( root_dir );
  /* create `README' */
  new_const_entry( root_dir, readme_name, S_IFREG | 0777, readme );
  /* create `*knock-before-entering*' */
  entry = new_entry( root_dir, entry_point, S_IFDIR | 0700, NULL, NULL );
  if( entry != NULL )
	{
	  entry -> hidden = 1;
	  /* create `open-by-inode' sub-directory */
	  new_entry( entry, open_by_inode, S_IFDIR | 0777, 
				 &open_by_key_inode_ops, NULL );
	  /* create `magic-finger' socket */
	  new_entry( entry, magic_finger_name, S_IFSOCK | 0777, 
				 &magic_finger_inode_ops, &magic_finger_file_ops );
	  /* no comments on this yet. (It doesn't work anyway.) */
	  new_cv_entry( entry, DA_GLOBAL_WAIT_QUEUE, 0777, &queue );

#if defined( DA_USE_REISERFS )
	  if( info -> over_reiserfs )
		{
		  /* if default host file-system is reiserfs, create
			 `open-by-key' sub-directory */
		  new_entry( entry, open_by_key, S_IFDIR | 0777, 
					 &open_by_key_key_ops, NULL );
		}
#endif
	}
}

/* 
   parse mount options.
   Recognised options are:

     host=<path> path to default host file-system. Mandatory.
	 sbr=[e|l]   Super-Block Resolution mode: early or late. 
	             Default is early.
 */
static int parse_options( struct da_super_info *info, char *options )
{
  char *this_char;
  char *value;

  char *path;

  TRACE;
  HASSERT( info != NULL );

  info -> late_sbr = 1;
  path = NULL;
  if( options != NULL )
	{
	  for( this_char = strtok( options, "," ) ; 
		   this_char != NULL; this_char = strtok( NULL, "," ) )
		{
		  if( ( value = strchr( this_char, '=' ) ) != NULL )
			{
			  *value++ = 0;
			}
		  if( !strcmp( this_char, "host" ) )
			{
			  if( !value || !*value )
				{
				  HPRINT( "host option requires a path" );
				}
			  else
				{
				  path = value;
				}
			} 
		  else if( !strcmp( this_char, "sbr" ) ) 
			{
			  if( !value || !*value )
				{
				  HPRINT( "sbr options requires a value" );
				}
			  else
				{
				  switch( value[ 0 ] )
					{
					case 'l':
					  info -> late_sbr = 1;
					  break;
					case 'e':
					  info -> late_sbr = 0;
					  break;
					default:
					  HPRINT( "unknown sbr parameter (should be in [le])" );
					  break;
					}
				}
			}
		  else 
			{
			  HPRINT( "unrecognized option: `%s'", this_char );
			  return 0;
			}
		}
	}
  if( path == NULL )
	{
	  HPRINT( "option `host' is mandatory" );
	  return 0;
	}
  if( !info -> late_sbr )
	{
	  info -> sbr.host_super = get_super_by_mount_point( path );
	  if( info -> sbr.host_super == NULL )
		{
		  HPRINT( "cannot resolve path: `%s'", path );
		  return 0;
		}
	}
  else
	{
	  info -> sbr.mount_point = allocate( info, strlen( path ) + 1 );
	  if( info -> sbr.mount_point == NULL )
		{
		  HPRINT( "cannot allocate mount point name" );
		  return 0;
		}
	  strcpy( info -> sbr.mount_point, path );
	}
  return 1;
}

/* Called by VFS during mount. There is no need to read super-block
   from the device. Just parse options, fill some fields and create tree
   of da_entries (in create_root()).
*/
struct super_block *read_super( struct super_block *s,
								void *data, int silent )
{
  struct inode *rootInode;
  struct da_super_info *info;
  struct da_entry *root_entry;

  HASSERT( s != NULL );

  TRACE;

  info = allocate( NULL, sizeof( struct da_super_info ) );
#if defined( DA_DEBUG )
  info -> magic = DA_INFO_MAGIC;
#endif
  if( ( info == NULL ) || !parse_options( info, ( char * ) data ) )
	{
	  return NULL;
	}

  MOD_INC_USE_COUNT;
  lock_super( s );

  s -> u.generic_sbp = info;
  info -> super = s;

#if defined( DA_USE_REISERFS )
  if( !info -> late_sbr && 
	  /* explicit string literal is ugly. 
		 Should be defined in reiserfs' includes. */
	  !strcmp( info -> sbr.host_super -> s_type -> name, reiserfs_name ) )
	{
	  HDEBUG( "reiserfs detected" );
	  info -> over_reiserfs = 1;
	}
  else
	{
	  info -> over_reiserfs = 0;
	}
#endif

  s -> s_blocksize = 1024;
  s -> s_blocksize_bits = 10;
  s -> s_magic = DA_SUPER_MAGIC;
  s -> s_op = &sops;

  rootInode = get_next_inode( info );
  rootInode -> i_mode = S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR;
	  
  if( rootInode == NULL )
	{
	  if( !silent )
		{
		  HPRINT( "get root inode failed" );
		}
	  recycle( info, info, sizeof( struct da_super_info ) );
	  MOD_DEC_USE_COUNT;
	  unlock_super( s );
	  return NULL;
	}

  HASSERT( s -> s_root == NULL );
#if defined( LINUX_2_2 )
  s -> s_root = d_alloc_root( rootInode, NULL );
#else
  s -> s_root = d_alloc_root( rootInode );
#endif
  if( s -> s_root != NULL )
	{
	  root_entry = create_entry( info, "*root*" );
	  info -> root_entry = root_entry;
	  if( root_entry != NULL )
		{
		  list_add( &( info -> chain ), &mount_points );
		  register_inode( root_entry, rootInode );
		  create_root( root_entry, info );
#if defined( DA_DEBUG )
		  dump_entry( root_entry );
#endif
		  unlock_super( s );
		  return s;
		}
	}
  iput( rootInode );
  recycle( info, info, sizeof( struct da_super_info ) );
  MOD_DEC_USE_COUNT;
  unlock_super( s );
  return NULL;
}

/* returns super-block of default host file-system.
   Handles super-block resolution mode (sbr).
*/
struct super_block *get_host_super( struct da_super_info *info )
{
  HASSERT( info != NULL );

  if( ! info -> late_sbr )
	{
	  /* sbr==early just return super-block found during mount */
	  HASSERT( info -> sbr.host_super != NULL );
	  return info -> sbr.host_super;
	}
  else
	{
	  /* if sbr==late resolve path to mount-point */
	  HASSERT( info -> sbr.mount_point != NULL );
	  return get_super_by_mount_point( info -> sbr.mount_point );
	}
}

/* traverse the path and return super-block of file-system
   mounted there.
*/
struct super_block *get_super_by_mount_point( const char *const mount_point )
{
  struct super_block *super;

#if defined( LINUX_2_2 )
  struct dentry *target;

  HASSERT( mount_point != NULL );
  HDEBUG( "path: `%s'", mount_point );

  super = NULL;
  target = lookup_dentry( mount_point, NULL, LOOKUP_FOLLOW | LOOKUP_DIRECTORY );
  if( !IS_ERR( target ) )
	{
	  HDEBUG( "__namei: ok" );
	  if( ! target -> d_inode )
		{
		  HDEBUG( "__namei: NOENT" );
		  dput( target );
		}
	  else
		{
		  super = target -> d_inode -> i_sb;
		}
	}
  else
	{
	  HDEBUG( "__namei error: %li", ( long ) target );
	}
#else
  int err;
  struct nameidata walk_info;

  HASSERT( mount_point != NULL );
  HDEBUG( "path: `%s'", mount_point );

  super = NULL;
  /*  lock_kernel(); */
  if( path_init( mount_point, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &walk_info ) )
	{
	  HDEBUG( "path_init: ok" );
	  if( !IS_ERR( err = path_walk( mount_point, &walk_info ) ) )
		{
		  HDEBUG( "path_walk: ok" );
		  super = walk_info.mnt -> mnt_sb;
		}
	  else
		{
		  HDEBUG( "path_walk returned: %i", err );
		}
	}
  /*  unlock_kernel(); */
#endif
  return super;
}

void *allocate( struct da_super_info *info, unsigned int size )
{
  void *result;

#if defined( DA_DEBUG )
  if( info != NULL )
	{
	  ++( info -> allocations );
	  info -> allocated += size;
	  info -> total += size;
	}
#endif
  result = kmalloc( size, GFP_KERNEL );
  if( result != NULL )
	{
	  memset( result, 0, size );
	}
  else
	{
	  HPRINT( "Cannot allocate %ui bytes", size );
	}
  return result;
}

void recycle( struct da_super_info *info, void *area, size_t size )
{
#if defined( DA_DEBUG )
  HASSERT( area != NULL );
  
  if( info != NULL )
	{
	  info -> allocated -= size;
	}
#endif
  kfree( area );
}

/*
  As dafs doesn't store inodes on the disk their
  numbers are just assigned sequentially.
 */
struct inode *get_next_inode( struct da_super_info *info )
{
  struct inode *result;

  HASSERT( info != NULL );
  HASSERT( info -> super != NULL );

  /* add inode_no locking here XXX */
  result = iget( info -> super, ++( info -> inode_no ) );
  return result;
}

#if defined( LINUX_2_2 )
int init_module(void)
#else
int __init da_init_fs()
#endif
{
  HPRINT( "Copyright (C) 2000 Namesys (Hans Reiser). Loading..." );
  return register_filesystem( &fs_type );
}

#if defined( LINUX_2_2 )
void cleanup_module(void)
#else
void __exit da_exit_fs()
#endif
{
  unregister_filesystem( &fs_type );
  HPRINT( "Unloaded." );
}

#if defined( LINUX_2_3 )
module_init( da_init_fs )
module_exit( da_exit_fs )
#endif

