#include <../source/TAL/objbase.h>
#include <../source/vfs/fsops.h>
#include <../source/vfs/drivers/fslist.h>
#include <ctype.h>

/*
 * TODOs
 * - provide means for 'connecting' vfs trees for multiple processes;
 * - consider using hash table for every tree / one fs;
 * - consider using inode sorting for read-only fs: we can use bsearch().
 */

/*
 *	Allocates memory for leaf and stores passed fields
 */
V_LOCAL vfs_leaf_p
vfsAllocLeaf(char *leaf_name,vfs_dev_t *volume,u32_t inode)
{
	u32_t n_size;
	vfs_leaf_p leaf;
	n_size=strlen(leaf_name)+1;
	leaf=(vfs_leaf_t*)memAlloc(sizeof(vfs_leaf_t)+INT_ALIGN(n_size));
	leaf->name=(char_p)(leaf+1);
	memCopy(leaf->name,leaf_name,n_size);
	leaf->volume=volume;
	leaf->inode=inode;
	return leaf;
}

/*
 *	Allocates empty node.
 */
V_LOCAL	vfs_node_p
vfsAllocNode(char *node_name)
{
	vfs_node_p node;
	u32_t n_size=strlen(node_name)+1;
	node=(vfs_node_p)memAlloc(sizeof(vfs_node_t)+INT_ALIGN(n_size));
	node->name=(char_p)(node+1);
	memCopy(node->name,node_name,n_size);
	vpsInit(&node->files);
	vpsInit(&node->volumes);
	return node;
}

/*
 *	Removes all leafs and memFree()'s node.
 */
V_LOCAL	void	V_CALL
vfsDeleteNode(/*vfs_node_p*/void *node)
{
	u32_t i,nleafs;
	vfs_leaf_t **leafs;
	nleafs=vpsCount(&((vfs_node_t*)node)->files);
	leafs=(vfs_leaf_t**)vpsGetStorage(&((vfs_node_t*)node)->files);
	for(i=0;i<nleafs;i++)
		if(leafs[i])
			memFree(leafs[i]);
	vpsClear(&((vfs_node_t*)node)->files);
	vpsClear(&((vfs_node_t*)node)->volumes);
	memFree(node);
}

/*
 *	Searches in the directory for file with given name.
 *	Search is CASE - SENSITIVE !
 */
V_LOCAL	vfs_leaf_p
vfsFindLeafInNode(vfs_node_t *dir,char *name)
{
	u32_t i,nleafs;
	vfs_leaf_t **leafs;
	nleafs=vpsCount(&dir->files);
	leafs=(vfs_leaf_t**)vpsGetStorage(&dir->files);
	for(i=0;i<nleafs;i++)
		if(!strcmp(name,leafs[i]->name))
			break;
	return i==nleafs ? NULL : leafs[i];
}

/*
 *	Finds child with given name.
 */
V_LOCAL ntree_p
vfsFindChildNode(ntree_p parent,char *node_name)
{
	u32_t i,nc=vpsCount(&parent->children);
	ntree_t **cnodes=(ntree_t**)vpsGetStorage(&parent->children);
	for(i=0;i<nc;i++)
		if(!strcmp(((vfs_node_t*)cnodes[i]->data)->name,node_name))
			break;
	if(i==nc)
		return NULL;
	return cnodes[i];
}

/*
 * Converts relative path to absolute path.
 * Looks too complex for such simple task ... :(
 * Minor hacks: 22.12.2007
 */
vboolean V_CALL
lfsEnsurePathAbsolute(char *dest,char *src)
{
	CALLER_NAME(lfsEnsurePathAbsolute)
	u32_t i;
	char curpath_store[_MAX_PATH], *curpath;
	vhandle_t pp;
	char *strptr, *dname;
	vp_stack_t dirs = TYPED_STACK_EMPTY_INITIALIZER;
   	u8_stack_t path = TYPED_STACK_EMPTY_INITIALIZER;
	if(!src || !*src)
	{
		/* Assume current working directory */
		lfsGetDir(dest,_MAX_PATH);
		return true;
	}
	/* Check if path starts with slash */
	if(*src == '/')
	{
		/* Both under UNIX and NT (cygwin-style), this meens _absolute_ path */
		/* Only clear the syntax */
		curpath = src;
#ifndef	__UNIX__
		if(src[1]=='/')
		/* Drive letter present: temporary skip it */
			curpath += 3;
#endif	/* !__UNIX__ */
		pp = lexParseString(curpath,"/");
		lexDisable(pp,LEX_DEFAULT_DELIMITERS);
		dname = lexNext(pp);
		while(dname)
		{
			if(dname[0]!='/')
				vpsPush(&dirs,strDup(dname));
			dname = lexNext(pp);
		}
		lexDestroy(pp);
#ifndef	__UNIX__
		if(src[1]=='/') /* Drive letter present */
		/* Store drive letter (without following slash) */
			u8sPushBuffer(&path,(u8_t*)src,3);
#endif	/* !__UNIX__ */
		/* Get all dirs in FIFO order */
		if(vpsCount(&dirs))
		{
			for(i = 0;i < vpsCount(&dirs);i++)
			{
		    	strptr = (char*)vpsGet(&dirs,i);
				u8sPush(&path,'/');
				u8sPushBuffer(&path,(u8_t*)strptr,strlen(strptr));
				memFree(strptr);
			}
			vpsClear(&dirs);
		}
		else
			u8sPush(&path,'/'); /* root */
		u8sPush(&path,0);
		strCopy(dest,(char*)u8sGetStorage(&path),_MAX_PATH);
		u8sClear(&path);
		return true;
	}
	/*
	 * The path is relative. Have to translate it to absolute one.
	 */
	lfsGetDir(curpath_store,_MAX_PATH);
	curpath = curpath_store;
#ifndef	__UNIX__
	/* Temporary skip drive letter */
	curpath += 3;
#endif	/* !__UNIX__ */
	/* Split current local FS path */
	pp = lexParseString(curpath,"/");
	lexDisable(pp,LEX_DEFAULT_DELIMITERS);
	while(lexNext(pp))
		if(lexLast(pp)[0]!='/')
			vpsPush(&dirs,strDup(lexLast(pp)));
	/* Parse given path */
	lexSetString(pp,LEX_SOURCE_STRING,src);
	while(lexNext(pp))
	{
		dname = lexLast(pp);
		if(dname[0]=='/' && dname[1]==0) /* Skip delimiter, even if it can't be here */
			continue;
		if(dname[0]=='.' && dname[1]==0) /* Dummy reference: do nothing */
			continue;
		if(dname[0]=='.' && dname[1]=='.' && dname[2]==0)
		{
			/* move to up to root */
			if(vpsPop(&dirs,(void**)&strptr))
			{
   				memFree(strptr);
				continue;
			}
			/* Error: malformed path */
			lexDestroy(pp);
			vpsClear(&dirs);
			ERROR_GENERIC_2ARG(src,": path points higher than root");
			return false;
		}
		/* Add next dir to list */
		vpsPush(&dirs,strDup(dname));
	}
	lexDestroy(pp);
	/* Put all together */
#ifndef	__UNIX__
	/* Store drive letter */
	curpath -= 3;
	u8sPushBuffer(&path,(u8_t*)curpath,3);
#endif	/* !__UNIX__ */
	/* Get all strings in FIFO order */
	for(i = 0;i < vpsCount(&dirs);i++)
	{
    	strptr = (char*)vpsGet(&dirs,i);
		u8sPush(&path,'/');
		u8sPushBuffer(&path,(u8_t*)strptr,strlen(strptr));
		memFree(strptr);
	}
	vpsClear(&dirs);
	u8sPush(&path,0);
	strCopy(dest,(char*)u8sGetStorage(&path),_MAX_PATH);
	u8sClear(&path);
	return true;
}

/*
 *	Finds given directory, optionally creates one ore more
 *	directories. Under NT absolute 'dir_name' MUST
 *	contain drive letter (for example, "//c/ ... ").
 */
ntree_p V_CALL
vfsFindNode(ntree_t *root,char *dir_name,vboolean force_creation)
{
	vhandle_t pp;		/* path parser */
	ntree_t *node, *child=NULL;
#ifndef	__UNIX__
	char drive[4];
#endif	/* !__UNIX__ */
	char udir[_MAX_PATH];
	if(!lfsEnsurePathAbsolute(udir,dir_name))
		return NULL;
	dir_name=udir;
#ifndef	__UNIX__
	/* Find drive */
	drive[0]=dir_name[2];
	if(*drive >= 'a' && *drive <= 'z')
		*drive = (char)(*drive + 'A' - 'a');		/* make drive uppercase */
	drive[1]=':';
	drive[2]=0;
	node=vfsFindChildNode(root,drive);
	if(!node)
	{
		if(!force_creation)
			return NULL;
		/* Drive not exists: create it and set 'root' to it's root */
		node=treeAllocNode(vfsAllocNode(drive),root);
		root=treeAllocNode(vfsAllocNode("/"),node);
	}
	else
		root=vfsFindChildNode(node,"/");
	if(strlen(dir_name)<=4)
		return root;
	pp = lexParseString(dir_name+4,"/");
#else	/* __UNIX__ */
	if(strlen(dir_name)<=1)
		return root;
	pp = lexParseString(dir_name+1,"/");
#endif	/* __UNIX__ */
	/*	Parse path */
	lexDisable(pp,LEX_DEFAULT_DELIMITERS);
	node = root;
	if(force_creation)
	{
		while(lexNext(pp))
		{
			if(lexLast(pp)[0]=='/')
				continue;
			child=vfsFindChildNode(node,lexLast(pp));
			if(!child)
				child=treeAllocNode(vfsAllocNode(lexLast(pp)),node);
			node=child;
		}
	}
	else
	{
		while(lexNext(pp))
		{
			if(lexLast(pp)[0]=='/')
				continue;
			child=vfsFindChildNode(node,lexLast(pp));
			if(!child)
				break;
			node=child;
		}
	}
	lexDestroy(pp);
	return child;
}

/*
 *	----------------------------------------------------------------------
 *	VFS tree is a n-way tree with data types of vfs_node_p. Root node
 *	contains either normal dirs (UNIX), or drive names like "\\c\' (NT).
 *	----------------------------------------------------------------------
 */

vboolean	V_CALL
vfsAddNode(ntree_p root,char *full_path,vfs_dev_t *volume,u32_t inode)
{
	CALLER_NAME(vfsAddNode)
	ntree_t *node;
	vfs_leaf_t *leaf;
	vfs_node_t *dirp;
	char dir[_MAX_PATH], name[_MAX_PATH];
	vfsSplitDir(full_path,dir,name);
	node=vfsFindNode(root,dir,true);
	dirp=(vfs_node_t*)node->data;
	leaf=vfsFindLeafInNode(dirp,name);
	if(!leaf)
	{
		leaf=vfsAllocLeaf(name,volume,inode);
		vpsPush(&dirp->files,leaf);
	}
	else
	{
		u32_t volinfo=0;
		vfs_dev_t *lastv=NULL;
		/*
		 *	WARNING: new entry hides old one
		 */
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_TRACE,
			"%s(%s): entry from '%s' hides old from '%s'.\n",
			caller_name,
			full_path,
			volume->get_volume_name(volume),
			leaf->volume->get_volume_name(leaf->volume));
#endif	/* CHECKED_BUILD */
		strCopy(leaf->name,name,_MAX_PATH); /* FIXME ??? */
		leaf->inode=inode;
		/* Remove reference to volume from directory' volume list */
		vpsFind(&dirp->volumes,leaf->volume,&volinfo);
		vpsPop(&dirp->volumes,(void**)&lastv);
		if(volinfo < vpsCount(&dirp->volumes))
			vpsSet(&dirp->volumes,volinfo,lastv);
		/*	Set new node volume information */
		leaf->volume=volume;
	}
	/*	Store volume information in directory volume list */
	if(!vpsFind(&dirp->volumes,volume,NULL))
		vpsPush(&dirp->volumes,volume);
	return true;
}

vboolean	V_CALL
vfsGetNodeData(ntree_p root,char *full_path,vfs_dev_t **volume_ret,u32_t *inode_ret)
{
	ntree_t *node;
	vfs_leaf_t *leaf;
	char dir[_MAX_PATH], name[_MAX_PATH];
	vfsSplitDir(full_path,dir,name);
	node=vfsFindNode(root,dir,false);
	if(!node)
		return false;
	leaf=vfsFindLeafInNode((vfs_node_t*)node->data,name);
	if(!leaf)
		return false;
	if(volume_ret)
		*volume_ret=leaf->volume;
	if(inode_ret)
		*inode_ret=leaf->inode;
	return true;
}

/*	----------------------------------------------------------------------
 *	Mounting and unmounting ROMFS'
 */
/*
 * User-defined filesystem recognizers.
 * Actually, _not_ used at least for 7 years ...
 */
vp_stack_t	vfs_mount_procs=TYPED_STACK_EMPTY_INITIALIZER;
/*
 *	The VFS tree.
 */
V_LOCAL		vfs_node_t	default_root_node=
{
	"/",
    TYPED_STACK_EMPTY_INITIALIZER,
    TYPED_STACK_EMPTY_INITIALIZER
};
V_LOCAL		ntree_t		empty_vfs_tree=
{
	&default_root_node,
	NULL,
	TYPED_STACK_EMPTY_INITIALIZER
};
ntree_p		vfs_root=&empty_vfs_tree;
vp_stack_t	vfs_volumes=TYPED_STACK_EMPTY_INITIALIZER;

/*
 *	Deletes all VFS tree nodes except root (root node wasn't
 *	dynamically allocated).
 */
V_LOCAL	void
vfsTruncateTree(void)
{
	u32_t ind,count;
	ntree_p *ch;
	ch=(ntree_p*)vpsGetStorage(&vfs_root->children);
	count=vpsCount(&vfs_root->children);
	for(ind=0;ind<count;ind++)
		treeDeleteAllNodes(ch[ind],vfsDeleteNode);
	vpsClear(&vfs_root->children);
	return;
}

V_LOCAL	vboolean
vfsCallMountProc(vfs_mount_proc_t mnt,char *volume_name)
{
	vfs_dev_t *fs;
	int i,n_inodes;
	char node_name[_MAX_PATH];
	fs=mnt(volume_name);
	if(fs)
	{
		vpsPush(&vfs_volumes,fs);
		n_inodes=fs->get_inode_count(fs);
		for(i=0;i<n_inodes;i++)
		{
			fs->get_file_name(fs,i,node_name);
			vfsAddNode(vfs_root,node_name,fs,i);
		}
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_TRACE,
			"vfsCallMountProc(): mounted '%s' (%d inodes).\n",
			volume_name,n_inodes);
#endif	/* CHECKED_BUILD */
		return true;
	}
	return false;
}

#ifdef	CONFIG_VFS_ADD_DUMMY_VOLUME

/*
 * TODO:
 *  - interface for general vfs information (number / names of mounted volumes, etc.)
 *  - more ideas ?
 *
 * Have fun ...
 */

typedef struct vfs_rootfs_s
{
	vfs_dev_t	vtbl;
	u32_t		num_ino;
	char_p		*ino_names;
	char		volume_name[64];
}	vfs_rootfs_t, *vfs_rootfs_p;

V_LOCAL char_p	vfs_rootfs_ino_names[] = {"$INFO","$TRACE",NULL};

V_LOCAL char	vfs_root_info[] = "LibV virtual filesystem root node";

V_LOCAL vhandle_t V_CALL
rootfs_create_file(vfs_dev_p handle,u32_t inode)
{
	io_section_p port;
	vfs_rootfs_p self = (vfs_rootfs_p)handle;
	switch(inode)
	{
		case 0:	/* $INFO */
			port = ioAllocSection(sizeof(io_section_t),
				vfs_root_info, sizeof(vfs_root_info));
			return port;
		case 1: /* $TRACE */
			conlPrintf(LOG_TRACE,"[%s]->create_file(self,%s)\n",
				self->volume_name,self->ino_names[1]);
			return LIBV_INVALID_HANDLE;
		default:
			break;
	}
	return LIBV_INVALID_HANDLE;
}

V_LOCAL char_p	V_CALL
rootfs_get_volume_name(vfs_dev_p handle)
{
	vfs_rootfs_p self = (vfs_rootfs_p)handle;
	return self->volume_name;
}

V_LOCAL u32_t	V_CALL
rootfs_get_inode_count(vfs_dev_p handle)
{
	vfs_rootfs_p self = (vfs_rootfs_p)handle;
	return self->num_ino;
}

V_LOCAL vboolean V_CALL
rootfs_get_file_name(vfs_dev_p handle,u32_t inode,char *name_ret)
{
	vfs_rootfs_p self = (vfs_rootfs_p)handle;
	if(inode >= self->num_ino)
		return false;
	strCopy(name_ret,self->ino_names[inode],_MAX_PATH);
	return true;
}

V_LOCAL	vfs_dev_p
vfsCreateRoot(char *alias)
{
	int i;
	vfs_rootfs_p fs;
	fs = (vfs_rootfs_p)vfsCreateFS(sizeof(vfs_rootfs_t));
	/* Leave default destructor */
	fs->vtbl.get_volume_name = rootfs_get_volume_name;
	fs->vtbl.get_inode_count = rootfs_get_inode_count;
	fs->vtbl.get_file_name = rootfs_get_file_name;
	fs->vtbl.create_file = rootfs_create_file;
	fs->ino_names = vfs_rootfs_ino_names;
	for(i=0;vfs_rootfs_ino_names[i];i++);
	fs->num_ino=i;
	strCopy(fs->volume_name,alias,64);
	return (vfs_dev_p)fs;
}

V_LOCAL	void
vfsAddRootDummy()
{
	vfs_dev_t *fs;
	int i,n_inodes;
	char node_name[_MAX_PATH];
	fs = vfsCreateRoot("$VFS");
	if(fs)
	{
		vpsPush(&vfs_volumes,fs);
		n_inodes = fs->get_inode_count(fs);
		for(i=0;i<n_inodes;i++)
		{
			fs->get_file_name(fs,i,node_name);
			vfsAddNode(vfs_root,node_name,fs,i);
		}
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_TRACE,
			"vfsAddRootDummy(): mounted dummy (%d inodes).\n",
			n_inodes);
#endif	/* CHECKED_BUILD */
	}
	return;
}

#endif 	/* CONFIG_VFS_ADD_DUMMY_VOLUME */

#ifdef	_MSC_VER
#pragma warning(disable:4054)	/* Cast between data and function pointers */
#pragma warning(disable:4055)
#endif	/* _MSC_VER */

void	V_CALL
vfsRegisterMountProcedure(vfs_mount_proc_t mount_proc)
{
	vpsPush(&vfs_mount_procs,(void*)mount_proc);
}

void	V_CALL
vfsMountVolume(char *volume_name)
{
	u32_t nm,i;
	vfs_mount_proc_t mnt;
	
    for(i=0;def_mnt_proc[i];i++)
		if(vfsCallMountProc(def_mnt_proc[i],volume_name))
        	return; /* mounted as build-in FS */

	nm=vpsCount(&vfs_mount_procs);
	for(i=0;i<nm;i++)
	{
		mnt=(vfs_mount_proc_t)vpsGet(&vfs_mount_procs,i);
		if(mnt)
        	if(vfsCallMountProc(mnt,volume_name))
            	return; /* mounted as client-defined FS */
	}
}

#ifdef	_MSC_VER
#pragma warning(default:4054)
#pragma warning(default:4055)
#endif	/* _MSC_VER */

void	V_CALL
vfsInit()
{
	CALLER_NAME(vfsInit)
	if(!libvSafeThread())
	{
		ERROR_GENERIC("Can _NOT_ init VFS: other threads already spawned");
		return;
	}
#ifdef	CONFIG_VFS_ADD_DUMMY_VOLUME
	/*
	 * Add dummy volume with one node at current directory
	 */
	vfsAddRootDummy();
#endif 	/* CONFIG_VFS_ADD_DUMMY_VOLUME */
	lfsEnumFiles(NULL,vfsMountVolume,true);
}

void	V_CALL
vfsShutdown(void)
{
	u32_t i,nfs;
	vfs_dev_t **fs;
	if(!libvSafeThread())
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"fsShutdown(): can't unmount filesystems, "
			"other threads still running.\n");
#endif	/* CHECKED_BUILD */
		return;
	}
	nfs=vpsCount(&vfs_volumes);
	fs=(vfs_dev_t**)vpsGetStorage(&vfs_volumes);
	for(i=0;i<nfs;i++)
		if(fs[i])
			vfsDestroy(fs[i]);
	vpsClear(&vfs_volumes);
	vpsClear(&vfs_mount_procs);
	vfsTruncateTree();
}

/*
 * The gate
 */
vhandle_t	V_CALL
vfsCreateFile(char *path)
{
	u32_t inode;
	vfs_dev_t *volume;
	char abspath[_MAX_PATH];
	/* Make absolute path */
	lfsEnsurePathAbsolute(abspath,path);
	/* Parse path, find node in tree */
	if(vfsGetNodeData(vfs_root,abspath,&volume,&inode))
	{
		/* If node exists, tell fs driver to create file */
		return volume->create_file(volume,inode);
	}
#ifdef	CHECKED_BUILD
	/* 09/29/2004: LOG_ERROR replaced by LOG_TRACE */
	conlPrintf(LOG_TRACE,"vfsCreateFile(%s): no such inode.\n",path);
#endif	/* CHECKED_BUILD */
	return LIBV_INVALID_HANDLE;
}

/*	----------------------------------------------------------------------
 *	Enumeration functions
 */

/*
 *	Stores path from 'root' to given file. Size of 'path_ret' 
 *	must be al least _MAX_PATH.
 */
vboolean	V_CALL
vfsGetNodePath(char *path_ret,ntree_t *node)
{
	char trail;
	vfs_node_t *dir;
	u8_stack_t dest=TYPED_STACK_EMPTY_INITIALIZER;
	vp_stack_t names=TYPED_STACK_EMPTY_INITIALIZER;
	/* store node list: from current up to root */
	while(node)
	{
		vpsPush(&names,node->data);
		node=node->parent;
	}
	while(vpsPop(&names,(void_p*)&dir))
	{
        if(u8sCount(&dest))
        	trail=(char)u8sGet(&dest,u8sCount(&dest)-1);
		else
        	trail=0;
    	if(trail!='/' && dir->name[0]!='/')
			u8sPush(&dest,'/');
		u8sPushBuffer(&dest,(u8_t*)dir->name,(u32_t)strlen(dir->name));
	}
	vpsClear(&names);
    u8sPush(&dest,0);
	strCopy(path_ret,(char*)u8sGetStorage(&dest),_MAX_PATH);
	u8sClear(&dest);
	if(path_ret[0]==0)
		return false;
#ifndef	__UNIX__
	/* Correct drive part ("/c:/" -> "//c/") */
	path_ret[2]=path_ret[1];
	path_ret[1]='/';
#endif	/* !__UNIX__ */
	return true;
}

vfs_enum_p	V_CALL
vfsOpenDir(char *dir_name)
{
	ntree_p node;
	vfs_node_t *dir;
	vfs_enum_t *dir_enum;
	node = vfsFindNode(vfs_root,dir_name,false);
	if(!node)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_TRACE,"vfsOpenDir(%s): no path to given node.\n",
			dir_name);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	dir=(vfs_node_t*)node->data;
	dir_enum=(vfs_enum_t*)memAlloc(sizeof(vfs_enum_t));
	dir_enum->node=node;
	/* Make path to node */
	vfsGetNodePath(dir_enum->node_path,node);
	dir_enum->num_nodes=vpsCount(&node->children);
	dir_enum->num_leafs=vpsCount(&dir->files);
	dir_enum->node_num=0;
	dir_enum->leaf_num=0;
	/*
	 *	TODO:	for(all fs in dir)
	 *				fs->addref();
	 *	OR BETTER:	for dir
	 *				dir->addref();
	 */
	return dir_enum;
}

vboolean	V_CALL
vfsReadDir(vfs_enum_p dir_enum)
{
	ntree_p child;
	vfs_node_t *dir;
	vfs_leaf_t *leaf;
	if(dir_enum->leaf_num==dir_enum->num_leafs)
    {
		u8_stack_t c_name=TYPED_STACK_EMPTY_INITIALIZER;
		if(dir_enum->node_num==dir_enum->num_nodes)
			return false; /* Error: end of directory */
		/* Read next child node */
		child=(ntree_p)vpsGet(&dir_enum->node->children,
			dir_enum->node_num);
		dir=((vfs_node_t*)child->data);
		dir_enum->vfs_mode=vfs_mode_directory;
		dir_enum->vfs_dev=NULL;
		dir_enum->vfs_ino=0;
		u8sPushBuffer(&c_name,(u8_t*)dir_enum->node_path,
        	strlen(dir_enum->node_path));
		u8sPush(&c_name,'/');
		u8sPushBuffer(&c_name,(u8_t*)dir->name,strlen(dir->name));
		u8sPush(&c_name,0);
		strCopy(dir_enum->vfs_name,(char*)u8sGetStorage(&c_name),_MAX_PATH);
		u8sClear(&c_name);
		/* Advance directory pointer */
		dir_enum->node_num++;
	}
	else
	{
		/* Read next leaf from this node */
		dir=((vfs_node_t*)dir_enum->node->data);
		leaf=(vfs_leaf_t*)vpsGet(&dir->files,dir_enum->leaf_num);
		dir_enum->vfs_mode=vfs_mode_normal;
		dir_enum->vfs_dev=leaf->volume;
		dir_enum->vfs_ino=leaf->inode;
		/* Concat path and file name */
		vfsMakePath(dir_enum->vfs_name,dir_enum->node_path,leaf->name,NULL);
		/* Advance leaf pointer */
		dir_enum->leaf_num++;
	}
	return true;
}

vboolean	V_LOCAL
vfsSeekDirEnum(vfs_enum_p dir_enum,int offset,int limit)
{
	int off_node=offset-dir_enum->num_leafs;
	if(offset>limit)
		return false;
	if(off_node>=0)
	{
		dir_enum->leaf_num=(u32_t)dir_enum->num_leafs;
		dir_enum->node_num=(u32_t)off_node;
	}
	else
	{
		dir_enum->leaf_num=(u32_t)offset;
		dir_enum->node_num=0;
	}
	return true;
}

vboolean	V_CALL
vfsSeekDir(vfs_enum_p dir_enum,int offset,int whence)
{
	int total=(int)(dir_enum->num_nodes + dir_enum->num_leafs);
	int passed=(int)(dir_enum->leaf_num + dir_enum->node_num);
	switch(whence)
	{
		case SEEK_SET:
			return vfsSeekDirEnum(dir_enum,offset,total);
		case SEEK_CUR:
			return vfsSeekDirEnum(dir_enum,offset+passed,total);
		case SEEK_END:
			return vfsSeekDirEnum(dir_enum,offset+total,total);
	}
	return false;
}

int			V_CALL
vfsTellDir(vfs_enum_p dir_enum)
{
	return (int)(dir_enum->leaf_num + dir_enum->node_num);
}

void		V_CALL
vfsCloseDir(vfs_enum_p dir_enum)
{
	/*
	 *	TODO:	for(all fs in dir)
	 *				fs->release();
	 */
	memFree(dir_enum);
}

V_LOCAL	void
vfsEnumFiles_r(vfs_enum_t *dir,void (V_CALL *dcb)(char_p),
	void (V_CALL *fcb)(char_p),vboolean recursive,vboolean show_hidden)
{
	u32_t i,len;
	if(!dir)
		return;
	vfsSeekDir(dir,0,SEEK_END);
	len=vfsTellDir(dir);
	vfsSeekDir(dir,0,SEEK_SET);
	/* Optimize me ! */
	for(i=0;i<len;i++)
	{
		vfsReadDir(dir);
		if(dir->vfs_mode==vfs_mode_normal)
		{
			if(fcb)
			{
				if(!show_hidden)
				{
					char *ptr = dir->vfs_name + strlen(dir->vfs_name);
					while(ptr >= dir->vfs_name && *ptr!='/')
						ptr--;
					ptr++;
					if(*ptr=='$' || *ptr=='.')
						continue;	/* hidden file */
				}
				fcb(dir->vfs_name);
			}
		}
		else
		{
			if(dcb)
				dcb(dir->vfs_name);
			if(recursive)
			{
				vfs_enum_p cd;
				cd=vfsOpenDir(dir->vfs_name);
				vfsEnumFiles_r(cd,dcb,fcb,true,show_hidden);
				vfsCloseDir(cd);
			}
		}
	}
}

#include <../source/PAL/argsused.h>
V_LOCAL	void	V_CALL	vfs_dummy_callback(char *vfs_name)
{
}

void	V_CALL
vfsEnumFilesEx(
	char *vfs_path,
	void (V_CALL *dir_callback)(char *dir_name),
	void (V_CALL *file_callback)(char *file_name),
	vboolean recursive,
	vboolean show_hidden)
{
	CALLER_NAME(vfsEnumFilesEx)
	vfs_enum_p dir;
	char curpath[_MAX_PATH];
	if(!dir_callback && !file_callback)
		return;
	if(!vfs_path)
	{
		lfsGetDir(curpath,_MAX_PATH);
		vfs_path=curpath;
	}
	dir = vfsOpenDir(vfs_path);
	if(!dir)
	{
		ERROR_GENERIC_2ARG("path not found in VFS tree",vfs_path);
		return;
	}
	if(!dir_callback)
		dir_callback=vfs_dummy_callback;
	else
    	dir_callback(dir->node_path);
	if(!file_callback)
		file_callback=vfs_dummy_callback;
    vfsEnumFiles_r(dir,dir_callback,file_callback,recursive,show_hidden);
	vfsCloseDir(dir);
}

void	V_CALL
vfsEnumFiles(
	char *vfs_path,
	void (V_CALL *dir_callback)(char *dir_name),
	void (V_CALL *file_callback)(char *file_name),
	vboolean recursive)
{
	vfsEnumFilesEx(vfs_path,dir_callback,file_callback,recursive,false);
}

#ifdef	VFS_TREE_DEBUG

V_LOCAL void
vfsDumpNode_r(FILE *out,ntree_t *node)
{
	u32_t i,len;
	vfs_node_t *dir;
	ntree_t **cnodes;
	vfs_leaf_t **files;
	char node_name[_MAX_PATH];
	if(!node)
		return;
	dir=(vfs_node_t*)node->data;
	vfsGetNodePath(node_name,node);
	fprintf(out,"DIR [%s]\n",node_name);
	len=vpsCount(&dir->files);
	files=(vfs_leaf_t**)vpsGetStorage(&dir->files);
	for(i=0;i<len;i++)
		fprintf(out,"%s\n",files[i]->name);
	len=vpsCount(&node->children);
	fprintf(out,"(VFS node '%s' has %d child nodes)\n",
		dir->name,(int)len);
	cnodes=(ntree_t**)vpsGetStorage(&node->children);
	for(i=0;i<len;i++)
		vfsDumpNode_r(out,cnodes[i]);
	return;
}

void	V_CALL
vfsDumpTree(FILE *out)
{
	if(!out)
		out=stdout;
	vfsDumpNode_r(out,vfs_root);
}

#if 0

/*
 *	Is this needed ?
 */
void	V_CALL
fsEnumFilesEx(
	char *vfs_path,
	void (V_CALL *dir_callback)(char *dir_name),
	void (V_CALL *file_callback)(char *file_name),
	vboolean recursive,
	vboolean show_hidden)
{
	/*
	 * Open local FS directory and VFS directory
	 */
	/*
	 * If local directory exists, enum all files ...
	 */
	/*
	 * If VFS directory exists, enum all files ...
	 */
	/*
	 * If local directory exists, descend into all subdirs ...
	 */
	/*
	 * If VFS directory exists, descend into all subdirs ...
	 */
	/* TODO :) */
}

#endif

#endif	/* VFS_TREE_DEBUG */
