/*
 * Generic routines for accessing concatenated files.
 */
#include <../source/TAL/objbase.h>
#include <../source/vfs/iobase.h>
#include <../source/vfs/fbase.h>
#include <../source/vfs/mbase.h>
#include <../source/vfs/drivers/vfscat.h>

#include <ctype.h>	/* for tolower */

/*
 * Destructor for memory-mapped VFS IO port.
 * If there no more references, it decrements VFS volume's ref_count
 * before destruction of object.
 */
u32_t V_CALL vfs_mapport_destroy(vhandle_t handle)
{
	vfs_mapport_p self = (vfs_mapport_p)handle;
	if(self->section.vtbl.base.ref_count == 1)
	{
		mutexLock(self->volume->vfs_lock);
		self->volume->base.ref_count--;
		mutexUnlock(self->volume->vfs_lock);
	}
	return self->inherited_destroy(handle);
}

vfs_mapport_p V_CALL
vfsCreateMPort(vfs_dev_p volume,u32_t extra_memory,void *base,u32_t len)
{
	vfs_mapport_p port;
	port = (vfs_mapport_p)
		ioAllocSection(sizeof(vfs_mapport_t)+extra_memory,base,len);
	if(!port)
		return LIBV_INVALID_HANDLE;
	port->inherited_destroy = port->section.vtbl.base.destroy;
	port->section.vtbl.base.destroy = vfs_mapport_destroy;
	port->volume = volume;
	mutexLock(volume->vfs_lock);
	volume->base.ref_count++;
	mutexUnlock(volume->vfs_lock);
	return port;
}

V_LOCAL u32_t V_CALL
catfs_bigport_destroy(vhandle_t handle)
{
	vfs_catfs_bigport_t *self=(vfs_catfs_bigport_t *)handle;
	if(--self->vtbl.base.ref_count)
		return self->vtbl.base.ref_count;
	fileClose(self->dhandle);
	memFreeObject(self);
	return 0;
}

V_LOCAL u32_t V_CALL
catfs_bigport_read(vhandle_t handle,void *buf,u32_t len)
{
	u32_t rest;
	vfs_catfs_bigport_t *self=(vfs_catfs_bigport_t *)handle;
	rest = self->dentry->len - self->current_pos;
	if(len > rest)
		len = rest;
	if(len)
	{
		u32_t read = fileRead(self->dhandle,buf,len);
		self->current_pos += read;
		return read;
	}
	else
		return 0;
}

V_LOCAL void
catfs_bigport_seek_common(vfs_catfs_bigport_t *self,s32_t off,int whence)
{
	switch(whence)
	{
		case SEEK_SET:
			self->current_pos = (u32_t)off;
			break;
		case SEEK_CUR:
			self->current_pos = (u32_t)(self->current_pos + off);
			break;
		case SEEK_END:
			self->current_pos = (u32_t)(self->dentry->len + off);
			break;
		default:
			return;
	}
	if(self->current_pos > self->dentry->len)
		self->current_pos = self->dentry->len;
	return;
}

V_LOCAL u32_t V_CALL 
catfs_bigport_seek(vhandle_t handle,s32_t offset,int whence)
{
	vfs_catfs_bigport_t *self=(vfs_catfs_bigport_t *)handle;
	catfs_bigport_seek_common(self,offset,whence);
	return fileSeek(self->dhandle,
		self->dentry->pos+self->current_pos,SEEK_SET);
}

#ifdef	ARCH_FILE_SUPPORT

/*
 * TODO: after conversion of the source tree, enable this
 * cutting-edge functions.
 */

#include <../arch/include/file.h>

V_LOCAL u32_t V_CALL
catfs_bigport_arch_read(vhandle_t handle,void *buf,u32_t len)
{
	u32_t rest;
	vfs_catfs_bigport_t *self=(vfs_catfs_bigport_t *)handle;
	rest = self->dentry->len - current_pos;
	if(len > rest)
		len = rest;
	if(len)
		return archFileRead(self->ahandle,buf,len);
	else
		return 0;
}

V_LOCAL u32_t V_CALL
catfs_bigport_arch_seek(vhandle_t handle,s32_t offset,int whence)
{
	vfs_catfs_bigport_t *self=(vfs_catfs_bigport_t *)handle;
	catfs_bigport_seek_common(self,offset,whence);
	return archFileSeek(self->ahandle,
		self->dentry->pos+self->current_pos,SEEK_SET);
}

#endif	/* ARCH_FILE_SUPPORT */

V_LOCAL u32_t V_CALL
catfs_bigport_tell(vhandle_t handle)
{
	vfs_catfs_bigport_t *self=(vfs_catfs_bigport_t *)handle;
	return self->current_pos;
}

V_LOCAL u32_t V_CALL
catfs_bigport_size(vhandle_t handle)
{
	vfs_catfs_bigport_t *self=(vfs_catfs_bigport_t *)handle;
	return self->dentry->len;
}


V_LOCAL	vhandle_t
catfs_create_bigport(vfs_catfs_t *self,vfs_dentry_t *dirp)
{
	vhandle_t dhandle;
	vfs_catfs_bigport_p port;
	if(!fileControl(self->dhandle,pctl_duplicate_handle,&dhandle))
		return LIBV_INVALID_HANDLE;
	/*
	 * Now we have handle of local file, so no locking/checking necessary.
	 */
	port = (vfs_catfs_bigport_p)ioAllocPort(sizeof(vfs_catfs_bigport_t));

	port->vtbl.base.destroy = catfs_bigport_destroy;

	port->vtbl.read = catfs_bigport_read;
	port->vtbl.tell = catfs_bigport_tell;
	port->vtbl.seek = catfs_bigport_seek;
	port->vtbl.size = catfs_bigport_size;
	
	port->dhandle = dhandle;

#ifdef	ARCH_FILE_SUPPORT

	if(fileControl(port->dhandle,pctl_get_arch_handle,&(port->ahandle)))
	{
		port->vtbl.read = catfs_bigport_arch_read;
		port->vtbl.seek = catfs_bigport_arch_seek;
	}

#endif	/* ARCH_FILE_SUPPORT */

	port->dentry = dirp;
	port->current_pos = 0;
	
	/*
	 * archFileSeek isn't necessary (will be called indirectly)
	 */
	fileSeek(port->dhandle,port->dentry->pos,SEEK_SET);
	
	return port;
}

/*
 * Creates in-memory copy of all data; can be used for small blocks.
 * If block too large, returns 'bigport' object.
 */
vhandle_t	V_CALL
catfs_create_port(vfs_dev_t *handle,u32_t inode)
{
	vfs_mapport_p port = LIBV_INVALID_HANDLE;
    vfs_dentry_p dirp;
    vfs_catfs_p self = (vfs_catfs_p)handle;
	V_ASSERT(inode < self->num_dentries);
	mutexLock(self->dhandle_lock);
    dirp = VFS_DENTRY(self->dir,inode,self->dentry_size);
	/* Allocate memory and copy entire resource */
	if(dirp->len <= CONFIG_VFS_CATFS_MAX_SECTION_SIZE)
    	port = vfsCreateMPort(handle,dirp->len,NULL,dirp->len);
    if(port == LIBV_INVALID_HANDLE)
    {
    	/* Try to create slow 'bigport' */
		mutexUnlock(self->dhandle_lock);
    	return catfs_create_bigport(self,dirp);
    }
	port->section.base = (u8_t*)(port+1);
    fileSeek(self->dhandle,dirp->pos,SEEK_SET);
	fileRead(self->dhandle,port->section.base,dirp->len);
	mutexUnlock(self->dhandle_lock);
	return (vhandle_t)port;
}

/*
 * The best way: we allocate space only for vtable and then use
 * memory mapping for data access.
 */
V_LOCAL	file_t	V_CALL
catfs_create_mapport(vfs_dev_t *handle,u32_t inode)
{
    vfs_dentry_p dirp;
	vfs_mapport_p port;
	/* No locking needed here because we don't use 'dhandle' */
    vfs_catfs_p self = (vfs_catfs_p)handle;
	V_ASSERT(inode < self->num_dentries);
    dirp = VFS_DENTRY(self->dir,inode,self->dentry_size);
	/* Allocate memory, set data pointer */
    port = vfsCreateMPort(handle,0,self->base + dirp->pos,dirp->len);
    return port;
}

V_LOCAL	char_p V_CALL
catfs_get_volume_name(vfs_dev_t *handle)
{
	return ((vfs_catfs_t*)handle)->volume_name;
}

V_LOCAL	u32_t V_CALL
catfs_get_inode_count(vfs_dev_t *handle)
{
	return ((vfs_catfs_t*)handle)->num_dentries;
}

V_LOCAL	vboolean	V_CALL
catfs_get_file_name(vfs_dev_t *handle,u32_t inode,char *name_ret)
{
    vfs_catfs_p self = (vfs_catfs_p)handle;
	if(inode >= self->num_dentries)
    	return false;
	strCopy(name_ret,
		VFS_DENTRY(self->dir,inode,self->dentry_size)->name,_MAX_PATH);
    return true;
}

/*
 * CATFS destructor
 * Note: we, as usual, performs cleanup if ref_count == 1.
 * But if ref_count > 1, we leave it UNMODIFIED; this prevents
 * unmounting busy volume as the result of many libvCloseHandle()
 * calls. This is hack; see TODO in fsbase.h
 */
V_LOCAL	u32_t V_CALL
catfs_destroy(vhandle_t handle)
{
	mutex_t mut;
	vfs_catfs_p self=(vfs_catfs_p)handle;
	mutexLock(self->vtbl.vfs_lock);
	if(self->vtbl.base.ref_count > 1)
	{
		mutexUnlock(self->vtbl.vfs_lock);
		return self->vtbl.base.ref_count;
	}
	if(self->dhandle_lock != LIBV_INVALID_HANDLE)
	{
		mutexLock(self->dhandle_lock);
		fileClose(self->dhandle);
		mutexUnlock(self->dhandle_lock);
		mutexDestroy(self->dhandle_lock);
	}
	else
		fileClose(self->dhandle);
	mut = self->vtbl.vfs_lock;
	memFreeObject(self);
	mutexUnlock(mut);
	mutexDestroy(mut);
	return 0;
}

/*
 * CATFS factory.
 *
 * FS-specific directory must be read in after calling this routine.
 * No more actions necessary.
 * First parameter - 'in_stream' - used to mmap input space. File
 * pointer can be in any place at time of call.
 */
vfs_catfs_p	V_CALL
vfsCreateCatFS(vhandle_t in_stream,u32_t num_dentries, u32_t name_maxlen)
{
    vfs_catfs_p fs;
	void *base;
	u32_t dentry_size, dir_size;
	dentry_size = VFS_DENTRY_SIZE(name_maxlen);
	dir_size = num_dentries * dentry_size;
	fs = (vfs_catfs_p)vfsCreateFS(sizeof(vfs_catfs_t)+dir_size);
    fs->vtbl.base.destroy = catfs_destroy;
	fileControl(in_stream,pctl_get_name,fs->volume_name);
	fs->vtbl.get_volume_name = catfs_get_volume_name;
	fs->vtbl.get_inode_count = catfs_get_inode_count;
	fs->vtbl.get_file_name = catfs_get_file_name;
	fs->dhandle = in_stream;
	fs->num_dentries = num_dentries;
	fs->dentry_size = dentry_size;
	fs->dir = fs+1;
#ifndef	CONFIG_CATFS_DISABLE_MMAP
	if(!fileControl(in_stream,pctl_map_ispace,&base))
		base = NULL;
#else	/* !CONFIG_CATFS_DISABLE_MMAP */
	base = NULL;
#endif	/* !CONFIG_CATFS_DISABLE_MMAP */
	fs->base = (u8_t*)base;
	if(base)
	{
		/* Stream mmap()'ed; use fast creation routines */
		fs->dhandle_lock = LIBV_INVALID_HANDLE;
        fs->vtbl.create_file = catfs_create_mapport;
	}
	else
    {
	    fs->dhandle_lock = mutexCreate();
        fs->vtbl.create_file = catfs_create_port;
    }
	return fs;
}
 
#ifdef	CONFIG_VFS_PAKFS

/*
 * fspakfs.c: implemenation of Quake/QII filesystem.
 * Original PAK file format: Copyright (C) Id Software.
 */

#define	PACK_FOURCC		mmioFOURCC('P','A','C','K')

#define	PACK_NAMECHARS	56

V_LOCAL	void
pakfs_read_dentry(vhandle_t in_stream,vfs_dentry_t *ent)
{
	fileRead(in_stream,ent->name,PACK_NAMECHARS);
	ent->name[PACK_NAMECHARS-1]=0;
	ent->pos = fileGetLittleLong(in_stream);
	ent->len = fileGetLittleLong(in_stream);
}

vfs_dev_p	V_CALL
vfsMountPak(char *volume_name)
{
    vfs_catfs_p fs;
	vhandle_t dhandle;
	u32_t dirofs, dirlen, i, ino_len, num_ino;
	char ext[_MAX_EXT];
    fsSplitPath(volume_name,NULL,NULL,ext);
    if(strcasecmp(ext,"pak"))
    	return LIBV_INVALID_HANDLE;
	dhandle = lfsCreateFile(volume_name,file_mode_read);
    if(dhandle == LIBV_INVALID_HANDLE)
    	return LIBV_INVALID_HANDLE;
    if(fileGetLittleLong(dhandle) != PACK_FOURCC)
	{
		fileClose(dhandle);
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,"vfsMountPak('%s'): isn't Id's PAK file.\n",
			volume_name);
#endif	/* CHECKED_BUILD */
		return LIBV_INVALID_HANDLE;
	}
    dirofs = fileGetLittleLong(dhandle);
    dirlen = fileGetLittleLong(dhandle);
	if((dirofs+dirlen) < fileSize(dhandle))
	{
		fileClose(dhandle);
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"vfsMountPak('%s'): directory too small.\n",
			volume_name);
#endif	/* CHECKED_BUILD */
		return LIBV_INVALID_HANDLE;
	}
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_INFO,"vfsMountPak(): adding '%s' ...\n",volume_name);
#endif	/* CHECKED_BUILD */
	ino_len = VFS_DENTRY_SIZE(PACK_NAMECHARS);
	num_ino = dirlen / ino_len;
	fs = vfsCreateCatFS(dhandle,num_ino,PACK_NAMECHARS);
	if(fs == LIBV_INVALID_HANDLE)
	{
		fileClose(dhandle);
		return LIBV_INVALID_HANDLE;
	}
	/*
	 * Read in directory
	 */
	fileSeek(dhandle,dirofs,SEEK_SET);
	for(i=0;i<num_ino;i++)
		pakfs_read_dentry(dhandle,VFS_DENTRY(fs->dir,i,ino_len));
	return (vfs_dev_p)fs;
}

#endif	/* CONFIG_VFS_PAKFS */

#ifdef	CONFIG_VFS_WADFS

/*
 * WAD (DOOM) filesystem.
 * Original WAD file format: Copyright (C) Id Software.
 */

/*
 * TODO: current driver doesn't handle duplicate names; implement
 * conversion of sequence:
 *		res1
 *		s1_beg
 *		res1
 *		res2
 *		...
 *		resN
 *		s1_end
 *		res2
 * into sequence:
 *		res1
 *		s1/res1
 *		s1/res2
 *		...
 *		s1/resN
 *		res2
 * Note, that such implementation may require 2-pass algorithm (first
 * pass to determine maximum name lenght, second pass to read directory).
 */
 
#define	IWAD_FOURCC		mmioFOURCC('I','W','A','D')
#define	PWAD_FOURCC		mmioFOURCC('P','W','A','D')

/*
 * We increase name field to ensure that there will be space for trailing zero.
 */
#define	WAD_NAMECHARS	12

V_LOCAL	void
wadfs_read_dentry(vhandle_t in_stream,vfs_dentry_t *ent)
{
	ent->pos = fileGetLittleLong(in_stream);
	ent->len = fileGetLittleLong(in_stream);
	/* read 8-byte name */
	fileRead(in_stream,ent->name,8);
	/* ensure it is 0-terminated */
	ent->name[8]=0;
	/* convert to lower case */
	strLower(ent->name);
}

vfs_dev_p	V_CALL
vfsMountWad(char *volume_name)
{
    vfs_catfs_p fs;
	vhandle_t dhandle;
	u32_t id, dirofs, i, ino_len, num_ino;
	char ext[_MAX_EXT];
    fsSplitPath(volume_name,NULL,NULL,ext);
    if(strcasecmp(ext,"wad"))
    	return LIBV_INVALID_HANDLE;
	dhandle = lfsCreateFile(volume_name,file_mode_read);
    if(dhandle == LIBV_INVALID_HANDLE)
    	return LIBV_INVALID_HANDLE;
	id = fileGetLittleLong(dhandle);
    if(id != IWAD_FOURCC && id != PWAD_FOURCC)
	{
		fileClose(dhandle);
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,"vfsMountWad('%s'): isn't WAD file.\n",
			volume_name);
#endif	/* CHECKED_BUILD */
		return LIBV_INVALID_HANDLE;
	}
	num_ino = fileGetLittleLong(dhandle);
	ino_len = VFS_DENTRY_SIZE(WAD_NAMECHARS);
	dirofs = fileGetLittleLong(dhandle);
	if((dirofs+num_ino*(4+4+8)) < fileSize(dhandle))
	{
		fileClose(dhandle);
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"vfsMountWad('%s'): directory too small.\n",
			volume_name);
#endif	/* CHECKED_BUILD */
		return LIBV_INVALID_HANDLE;
	}
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_INFO,"vfsMountWad(): adding '%s' ...\n",volume_name);
#endif	/* CHECKED_BUILD */
	fs = vfsCreateCatFS(dhandle,num_ino,WAD_NAMECHARS);
	if(fs == LIBV_INVALID_HANDLE)
	{
		fileClose(dhandle);
		return LIBV_INVALID_HANDLE;
	}
	/*
	 * Read in directory
	 */
	fileSeek(dhandle,dirofs,SEEK_SET);
	for(i=0;i<num_ino;i++)
		wadfs_read_dentry(dhandle,VFS_DENTRY(fs->dir,i,ino_len));
	return (vfs_dev_p)fs;
}

#endif	/* CONFIG_VFS_WADFS */

#ifdef	CONFIG_VFS_GOBFS

#define	GOB_FOURCC		mmioFOURCC('G','O','B',0x0A)
#define	GOO_FOURCC		mmioFOURCC('G','O','B',0x20)

#define	GOB_NAMECHARS	(13+1)
#define	GOO_NAMECHARS	128

V_LOCAL	void
gobfs_read_dentry(vhandle_t in_stream,vfs_dentry_t *ent)
{
	/*
	 * GOB resource file (LEC DF)
	 */
	ent->pos = fileGetLittleLong(in_stream);
	ent->len = fileGetLittleLong(in_stream);
	/* read 13-byte name */
	fileRead(in_stream,ent->name,13);
	/* ensure it is 0-terminated */
	ent->name[GOB_NAMECHARS-1]=0;
	/* convert to lower case */
	strLower(ent->name);
}

V_LOCAL	void
goofs_read_dentry(vhandle_t in_stream,vfs_dentry_t *ent)
{
	/*
	 * GOB2 resource file (LEC JK/MOTS)
	 * Each direntry composed from 2 LE dwords and name of the entry.
	 * Offset of the block stored in first dword, length of the block
	 * stored in second. Name stored in remaing 128 bytes.
	 */
	char *ptr;
	ent->pos = fileGetLittleLong(in_stream);
	ent->len = fileGetLittleLong(in_stream);
	/* read 128-byte name */
	fileRead(in_stream,ent->name,128);
	/* ensure it is 0-terminated */
	ent->name[127]=0;
	/* convert all back slashes to forward slashes */
	for(ptr=ent->name;*ptr;ptr++)
		if(*ptr == '\\')
			*ptr = '/';
}

vfs_dev_p	V_CALL
vfsMountGob(char *volume_name)
{
	vhandle_t dhandle;
	vfs_catfs_p fs;
	u32_t id, i, num_ino, ino_len, dirofs;
	void (*read_dentry)(vhandle_t in_stream,vfs_dentry_t *ent);
	char ext[_MAX_EXT];
	u32_t name_chars;
    fsSplitPath(volume_name,NULL,NULL,ext);
    if(strcasecmp(ext,"gob") && strcasecmp(ext,"goo"))
    	return LIBV_INVALID_HANDLE;
	dhandle = lfsCreateFile(volume_name,file_mode_read);
    if(dhandle == LIBV_INVALID_HANDLE)
    	return LIBV_INVALID_HANDLE;
	id = fileGetLittleLong(dhandle);
	switch(id)
	{
		case GOB_FOURCC:
			read_dentry = gobfs_read_dentry;
			dirofs = fileGetLittleLong(dhandle);
			fileSeek(dhandle,dirofs,SEEK_SET);
			name_chars = GOB_NAMECHARS;
			num_ino = fileGetLittleLong(dhandle);
			if((fileSize(dhandle)-fileTell(dhandle)) < num_ino*0x15)
			{
#ifdef			CHECKED_BUILD
				conlPrintf(LOG_ERROR,
					"vfsMountGob('%s'): directory too small in GOB file.\n",
					volume_name);
#endif			/* CHECKED_BUILD */
				fileClose(dhandle);
				return LIBV_INVALID_HANDLE;
			}
			break;
		case GOO_FOURCC:
			read_dentry = goofs_read_dentry;
			name_chars = GOO_NAMECHARS;
			/* Skip 2 dwords (i have no idea for what they are) */
			fileSeek(dhandle,8,SEEK_CUR);
			/* next LE dword contains number of directory entries */
			num_ino = fileGetLittleLong(dhandle);
			if((fileTell(dhandle) + num_ino * (2 * 4 + 128)) > 
				fileSize(dhandle))
			{
#ifdef			CHECKED_BUILD
				conlPrintf(LOG_ERROR,
					"vfsMountGob('%s'): directory too small in GOB2 file.\n",
					volume_name);
#endif			/* CHECKED_BUILD */
				fileClose(dhandle);
				return LIBV_INVALID_HANDLE;
			}
			break;
		default:
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_WARNING,
				"vfsMountGob('%s'): isn't LEC GOB/GOB2 file.\n",
				volume_name);
#endif		/* CHECKED_BUILD */
			fileClose(dhandle);
			return LIBV_INVALID_HANDLE;
	}
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_INFO,"vfsMountGob(): adding '%s' ...\n",volume_name);
#endif	/* CHECKED_BUILD */
	ino_len = VFS_DENTRY_SIZE(name_chars);
	fs = vfsCreateCatFS(dhandle,num_ino,name_chars);
	if(fs == LIBV_INVALID_HANDLE)
	{
		fileClose(dhandle);
		return LIBV_INVALID_HANDLE;
	}
	/*
	 * Read in directory (pointer already positioned to 1st dentry)
	 */
	for(i=0;i<num_ino;i++)
		read_dentry(dhandle,VFS_DENTRY(fs->dir,i,ino_len));
	return (vfs_dev_p)fs;
}

#endif	/* CONFIG_VFS_GOBFS */

#ifdef	CONFIG_VFS_GRPFS

/*
 * TODO: still untested, debug it !
 */

/*
 * GRP (Build engine) filesystem.
 * by Ken Silverman (http://www.advsys.net/ken/build.htm):
 *
 *     The ".grp" file format is just a collection of a lot of files stored
 *     into 1 big one. I tried to make the format as simple as possible: The
 *     first 12 bytes contains my name, "KenSilverman". The next 4 bytes is
 *     the number of files that were compacted into the group file. Then for
 *     each file, there is a 16 byte structure, where the first 12 bytes are
 *     the filename, and the last 4 bytes are the file's size. The rest of
 *     the group file is just the raw data packed one after the other in the
 *     same order as the list of files.
 */

#define	GRP_FOURCC		mmioFOURCC('K','e','n','S')

#define	GRP_NAMECHARS	(12+2)

V_LOCAL	void
grpfs_read_dentry(vhandle_t in_stream,vfs_dentry_t *ent)
{
	/* read 12-byte name */
	fileRead(in_stream,ent->name,12);
	/* ensure it is 0-terminated */
	ent->name[13]=0;
	/* convert name to lower case */
	strLower(ent->name);
	/* read file's size */
	ent->len = fileGetLittleLong(in_stream);
	ent->pos = 0;	/* will be calculated later */
}

vfs_dev_p	V_CALL
vfsMountGrp(char *volume_name)
{
	vhandle_t dhandle;
	vfs_catfs_p fs;
	vfs_dentry_p dirp;
	u32_t i, num_ino, ino_len, ofs;
	char ext[_MAX_EXT];
    fsSplitPath(volume_name,NULL,NULL,ext);
    if(strcasecmp(ext,"grp"))
    	return LIBV_INVALID_HANDLE;
	dhandle = lfsCreateFile(volume_name,file_mode_read);
    if(dhandle == LIBV_INVALID_HANDLE)
    	return LIBV_INVALID_HANDLE;
	if(fileGetLittleLong(dhandle) != GRP_FOURCC)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,
			"vfsMountGrp('%s'): isn't KEN GRP file.\n",
			volume_name);
#endif	/* CHECKED_BUILD */
		fileClose(dhandle);
		return LIBV_INVALID_HANDLE;
	}
	fileSeek(dhandle,12,SEEK_SET);
	num_ino = fileGetLittleLong(dhandle);
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_INFO,"vfsMountGrp(): adding '%s' ...\n",volume_name);
#endif	/* CHECKED_BUILD */
	ino_len = VFS_DENTRY_SIZE(GRP_NAMECHARS);
	fs = vfsCreateCatFS(dhandle,num_ino,GRP_NAMECHARS);
	if(fs == LIBV_INVALID_HANDLE)
	{
		fileClose(dhandle);
		return LIBV_INVALID_HANDLE;
	}
	/*
	 * Read directory (pointer already positioned to 1st dentry)
	 */
	for(i=0;i<num_ino;i++)
		grpfs_read_dentry(dhandle,VFS_DENTRY(fs->dir,i,ino_len));
	/*
	 * Calculate offsets
	 */
	ofs = fileTell(dhandle);
	for(i=0;i<num_ino;i++)
	{
		dirp = VFS_DENTRY(fs->dir,i,ino_len);
		dirp->pos = ofs;
		ofs += dirp->len;
	}
	/*
	 * Consistency check
	 */
	if(ofs != fileSize(dhandle))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,"vfsMountGrp('%s'): "
			"last block end (%u) != EOF (%u): damaged file.\n",
			volume_name,ofs,fileSize(dhandle));
#endif	/* CHECKED_BUILD */
		vfsDestroy(fs);
		return LIBV_INVALID_HANDLE;
	}
	return (vfs_dev_p)fs;
}

#endif	/* CONFIG_VFS_GRPFS */

#ifdef	CONFIG_VFS_TARFS

/*
 * Generic TAR filesystem.
 */

#define	TAR_FOURCC		mmioFOURCC('u','s','t','a')
#define	TAR_NAMECHARS	100

#define TAR_REGTYPE	 '0'		/* regular file */
#define TAR_AREGTYPE '\0'		/* regular file */
#define TAR_LNKTYPE  '1'		/* link */
#define TAR_SYMTYPE  '2'		/* reserved */
#define TAR_CHRTYPE  '3'		/* character special */
#define TAR_BLKTYPE  '4'		/* block special */
#define TAR_DIRTYPE  '5'		/* directory */
#define TAR_FIFOTYPE '6'		/* FIFO special */
#define TAR_CONTTYPE '7'		/* reserved */

#define TAR_BLOCKSIZE 512

struct tar_header
{				/* byte offset */
  char name[100];		/*   0 */
  char mode[8];			/* 100 */
  char uid[8];			/* 108 */
  char gid[8];			/* 116 */
  char size[12];		/* 124 */
  char mtime[12];		/* 136 */
  char chksum[8];		/* 148 */
  char typeflag;		/* 156 */
  char linkname[100];		/* 157 */
  char magic[6];		/* 257 */
  char version[2];		/* 263 */
  char uname[32];		/* 265 */
  char gname[32];		/* 297 */
  char devmajor[8];		/* 329 */
  char devminor[8];		/* 337 */
  char prefix[155];		/* 345 */
};

union tar_buffer {
  char               buffer[TAR_BLOCKSIZE];
  struct tar_header  header;
};

V_LOCAL int
tar_getoct(char *p,int width)
{
	int result = 0;
	char c;
	while (width --)
    {
		c = *p++;
		if (c == ' ')
			continue;
		if (c == 0)
			break;
		result = result * 8 + (c - '0');
	}
	return result;
}

V_LOCAL void
tar_read_directory(vhandle_t in,bstack_t *dentries,vfs_dentry_p tmpbuf)
{
	union  tar_buffer buffer;
	int    tail, skip;
	u32_t	len, curpos = 0;
	vfs_dentry_p cur_node = tmpbuf;
	for(;;)
    {
		len = fileRead(in, &buffer, TAR_BLOCKSIZE);
		curpos += len;
		if(len != TAR_BLOCKSIZE)
			break;
		if(!buffer.header.name[0] || memcmp(buffer.header.magic,"ustar",5))
			continue;
		cur_node->len = tar_getoct(buffer.header.size,12);
		tail = cur_node->len % TAR_BLOCKSIZE;
		if(tail)
			tail = TAR_BLOCKSIZE - tail;
		skip = cur_node->len + tail;
		switch(buffer.header.typeflag)
		{
			case TAR_REGTYPE:
			case TAR_AREGTYPE:
				if(cur_node->len)
				{
					cur_node->pos = curpos;
					strCopy(cur_node->name,buffer.header.name,TAR_NAMECHARS);
					stackPush(dentries,cur_node);
				}
#ifdef	CHECKED_BUILD
				else
				{
					conlPrintf(LOG_WARNING,"tar_read_directory(): "
						"skipped dentry '%s' with zero length.\n",
						cur_node->name);
				}
#endif	/* CHECKED_BUILD */
				break;
			default:
				break;
		}
		if(skip)
		{
			fileSeek(in,skip,SEEK_CUR);
			curpos += skip;
		}
	}
}

vfs_dev_p	V_CALL
vfsMountTar(char *volume_name)
{
	vhandle_t dhandle;
	vfs_catfs_p fs;
	vfs_dentry_p dirp;
	u32_t i, num_ino, ino_len;
	char ext[_MAX_EXT];
	bstack_t tar_dir;
    fsSplitPath(volume_name,NULL,NULL,ext);
    if(strcasecmp(ext,"tar"))
    	return LIBV_INVALID_HANDLE;
	dhandle = lfsCreateFile(volume_name,file_mode_read);
    if(dhandle == LIBV_INVALID_HANDLE)
    	return LIBV_INVALID_HANDLE;
	/* check magic of the first block */
	fileSeek(dhandle,0x101,SEEK_SET);
	if(fileGetLittleLong(dhandle) != TAR_FOURCC)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,
			"vfsMountTar('%s'): isn't TAR file.\n",
			volume_name);
#endif	/* CHECKED_BUILD */
		fileClose(dhandle);
		return LIBV_INVALID_HANDLE;
	}
	fileSeek(dhandle,0,SEEK_SET);
	ino_len = VFS_DENTRY_SIZE(TAR_NAMECHARS);
	dirp = (vfs_dentry_p)memAlloc(ino_len);
	stackInit(&tar_dir,ino_len);
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_INFO,"vfsMountTar(): adding '%s' ...\n",volume_name);
#endif	/* CHECKED_BUILD */
	tar_read_directory(dhandle,&tar_dir,dirp);
	num_ino = stackCount(&tar_dir);
	if(!num_ino)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,
			"vfsMountTar('%s'): no regular files in TAR file.\n",
			volume_name);
#endif	/* CHECKED_BUILD */
		memFree(dirp);
		fileClose(dhandle);
		return LIBV_INVALID_HANDLE;
	}
	fs = vfsCreateCatFS(dhandle,num_ino,TAR_NAMECHARS);
	if(fs == LIBV_INVALID_HANDLE)
	{
		stackClear(&tar_dir);
		memFree(dirp);
		fileClose(dhandle);
		return LIBV_INVALID_HANDLE;
	}
	/*
	 * Copy directory
	 */
	for(i=0;stackPop(&tar_dir,dirp);i++)
		memCopy(VFS_DENTRY(fs->dir,i,ino_len),dirp,ino_len);
	memFree(dirp);
	stackClear(&tar_dir);
	return (vfs_dev_p)fs;
}

#endif	/* CONFIG_VFS_TARFS */
