/* -*-C-*- */

/*  entry.c  */


/*
 * Author: Nikita Danilov <NikitaDanilov@yahoo.COM>
 * Keywords: dafs, vfs, open by inode, open by key
 *
 * Copyright (C) 2000 Namesys (Hans Reiser)
 *
 * This file is part of dafs.
 *
 * Dafs 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.
 *
 * This software 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 software; see the file COPYING.  If not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 */

#include "da.h"

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

/* externs */

/* global data definitions */

struct file_operations	file_ops = {
  readdir:		da_readdir,
};

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

LIST_HEAD( entries );

/* forward declarations */

/* definitions */

struct da_entry *allocate_entry( struct da_super_info *info )
{
  struct da_entry *result;
  result = ( struct da_entry * ) allocate( info, sizeof( struct da_entry ) );
#if defined( DA_DEBUG )
  result -> magic = DA_ENTRY_MAGIC;
#endif
  list_add( &result -> allocation_list, &entries );
  return result;
}

void recycle_entry( struct da_entry *entry )
{
  list_del( &HENTRY( entry ) -> allocation_list );
  recycle( entry -> info, entry, sizeof( struct da_entry ) );
}

void register_entry( struct da_entry *parent,
					 struct da_entry *child )
{
  struct da_entry *scan;

  HASSERT( parent != NULL );
  HASSERT( child != NULL );
  TRACE;

  child -> parent = parent;
  if( parent -> children != NULL )
	{
	  for( scan = parent -> children ; scan -> nextChild != NULL ; 
		   scan = scan -> nextChild )
		{}
	  scan -> nextChild = child;
	}
  else
	{
	  parent -> children = child;
	}
}

struct da_entry *create_entry( struct da_super_info *info, char *name )
{
  struct da_entry *entry;

  TRACE;
  HASSERT( name != NULL );
  entry = allocate_entry( info );
  if( entry != NULL )
	{
	  entry -> name = name;
	  entry -> len = strlen( name );
	  entry -> delete_name = 0;
	  entry -> info = info;
	}
  return entry;
}

void free_entry( struct da_entry *entry )
{
  TRACE;
  HASSERT( entry != NULL );
  if( entry -> delete_name )
	{
  	  TRACE;
	  recycle( entry -> info, entry -> name, sizeof( struct da_entry ) );
	}
  recycle_entry( entry );
}

struct da_entry *add_entry( struct da_entry *parent, char *name )
{
  struct da_entry *entry;

  TRACE;
  HASSERT( parent != NULL );
  HASSERT( name != NULL );

  entry = create_entry( parent -> info, name );
  if( entry != NULL )
	{
	  register_entry( parent, entry );
	}
  return entry;
}

void register_inode( struct da_entry *entry, struct inode *inode )
{
  HASSERT( inode != NULL );
  HASSERT( entry != NULL );

  TRACE;
  entry -> inode = inode;
  inode -> u.generic_ip = ( void * ) entry;
}

struct da_entry *add_with_inode( struct da_entry *parent, 
										char *name, umode_t mode,
										struct inode *inode )
{
  struct da_entry *entry;

  TRACE;
  HASSERT( parent != NULL );
  HASSERT( parent -> inode != NULL );
  HASSERT( name != NULL );
  
  entry = add_entry( parent, name );
  if( entry != NULL )
	{
	  if( inode != NULL )
		{
		  register_inode( entry, inode );
		  inode -> i_mode = mode;
		}
	}
  return entry;
}

struct da_entry *new_entry( struct da_entry *parent, 
							char *name, umode_t mode,
							struct inode_operations *i_suite,
							struct file_operations  *f_suite )
{
  struct da_entry *result;

  HASSERT( parent != NULL );

  result = add_with_inode( parent, name, mode, 
						   get_next_inode( parent -> info ) );
  if( result -> inode != NULL )
	{
	  result -> inode -> i_op = ( i_suite != NULL ) ? i_suite : &inode_ops;
#if defined( LINUX_2_3 )
	  result -> inode -> i_fop = ( f_suite != NULL ) ? f_suite : &file_ops;
#endif
	}
  if( S_ISDIR( mode ) )
	{
	  create_dot_dotdot( result );
	}
  return result;
}

struct dentry *lookup( struct inode *parent, struct dentry *entry )
{
  struct da_entry *child;

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

  HDEBUG( "looking for: `%*.*s'", entry -> d_name.len, entry -> d_name.len, 
		  entry -> d_name.name );
  HDEBUG( "in: " );
#if defined( DA_DEBUG )
  dump_entry( ( struct da_entry * ) parent -> u.generic_ip );
#endif

  for( child = 
		 ( ( struct da_entry * ) parent -> u.generic_ip ) -> children ;
	   child != NULL ; 
	   child = child -> nextChild )
	{
	  HDEBUG( "child:" );
#if defined( DA_DEBUG )
	  dump_entry( child );
#endif
	  if( ( child -> len == entry -> d_name.len ) &&
		  ( !memcmp( child -> name, entry -> d_name.name, child -> len ) ) )
		{
		  if( child -> inode != NULL )
			{
			  d_add( entry, child -> inode );
			}
		  break;
		}
	}
  return NULL;
}


int  da_readdir( struct file *filp, void *dirent, filldir_t filldir )
{
  struct da_entry *entry;
  int i;

  TRACE;
  HASSERT( filp != NULL );

  entry = HENTRY( filp -> f_dentry -> d_inode -> u.generic_ip );
  HASSERT( entry != NULL );

  i = 0;
  for( entry = entry -> children ; entry != NULL ; entry = entry -> nextChild )
	{
	  if( ( i < filp -> f_pos ) || ( entry -> hidden ) )
		{
		  continue;
		}
	  else if( filldir( dirent, entry -> name, strlen( entry -> name ),
						filp -> f_pos, entry -> inode -> i_ino ) < 0 )
		{
		  return 0;
		}
	  else
		{
		  ++( filp -> f_pos );
		  ++i;
		}
	}
  return 1;
}

void create_dot_dotdot( struct da_entry *dir )
{
  struct da_entry *dot_entry;

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

  dot_entry = add_with_inode( dir, ".", 
							  dir -> inode -> i_mode, dir -> inode );
  dot_entry -> children = dir -> children;
  dot_entry = add_with_inode( dir, "..",
							  dir -> parent -> inode -> i_mode, 
							  dir -> parent -> inode );
  dot_entry -> children = dir -> parent -> children;
}

const char *null = "<null>";

void dump_entry_brief( struct da_entry *entry )
{
  if( ( entry != NULL ) && ( entry -> name != NULL ) )
	{
	  printk( "`%*.*s'", entry -> len, entry -> len, entry -> name );
	}
  else
	{
	  printk( null );
	}
  printk( "(%p)\n", entry );
}

void dump_entry( struct da_entry *entry )
{
  printk( "entry: self: " );
  dump_entry_brief( entry );
  if( entry == NULL )
	{
	  return;
	}
  printk( "    inode: " );
  if( entry -> inode != NULL )
	{
	  printk( "%lx", entry -> inode -> i_ino );
	}
  else
	{
	  printk( null );
	}
  /* printk( "    dentry: %p ", entry -> dentry ); */
  printk( "    parent: " );
  dump_entry_brief( entry -> parent );
  printk( "    children:\n" );
  for( entry = entry -> children ; entry != NULL ; entry = entry -> nextChild )
	{
	  printk( "        " );
	  dump_entry_brief( entry );
	}
  printk( "\n---end entry dump---\n" );
}

/*
 * $Log$
 */

