/*
# _____     ___ ____     ___ ____
#  ____|   |    ____|   |        | |____|
# |     ___|   |____ ___|    ____| |    \    PS2DEV Open Source Project.
#-----------------------------------------------------------------------
# Copyright 2001-2004, ps2dev - http://www.ps2dev.org
# Licenced under Academic Free License version 2.0
# Review ps2sdk README & LICENSE files for further details.
#
# $Id: pfs_fioctl.c 577 2004-09-14 14:41:46Z pixel $
# PFS ioctl and devctl related routines
*/

#include "pfs.h"

int	pfsIoctl(iop_file_t *f, unsigned long arg, void *param)
{
	return -1;
}

int pfsDevctl(iop_file_t *f, const char *name, int cmd, void *arg, size_t arglen, void *buf, size_t buflen)
{
	pfs_mount_t *pfsMount;
	int rv=0;

	if(!(pfsMount=fioGetMountedUnit(f->unit)))
		return -ENODEV;

	switch(cmd)
	{
	case PFS_DEVCTL_GET_ZONE_SIZE:
		rv=pfsMount->zsize;
		break;

	case PFS_DEVCTL_GET_ZONE_FREE:
		rv=pfsMount->zfree;
		break;

	case PFS_DEVCTL_CLOSE_ALL:
		devctlCloseAll();
		hddFlushCache(pfsMount->fd);
		break;

	case PFS_DEVCTL_CLEAR_STAT:
		rv=devctlFsckStat(pfsMount, MODE_REMOVE_FLAG);
		break;

	case PFS_DEVCTL_GET_STAT:
		rv=devctlFsckStat(pfsMount, MODE_CHECK_FLAG);
		break;

	case PFS_DEVCTL_SET_UID:
		pfsMount->uid=*(u16 *)(arg);
		break;

	case PFS_DEVCTL_SET_GID:
		pfsMount->gid=*(u16 *)(arg);
		break;

	case PFS_DEVCTL_SHOW_BITMAP:
		bitmapShow(pfsMount);
		break;

	default:
		rv=-EINVAL;
		break;

	}
	SignalSema(pfsFioSema);

	return rv;
}

int pfsIoctl2(iop_file_t *f, int cmd, void *arg, size_t arglen,	void *buf, size_t buflen)
{
	int rv;
	pfs_file_slot_t *fileSlot = (pfs_file_slot_t *)f->privdata;
	pfs_mount_t *pfsMount;

	if(f->mode & O_DIROPEN)
		if(cmd==PFS_IOCTL2_ATTR_READ)
			return -EISDIR;

	if(!(f->mode & O_WRONLY)) {
		if(cmd!=PFS_IOCTL2_ATTR_LOOKUP)
			if(cmd!=PFS_IOCTL2_ATTR_READ)
				return -EACCES;
	}
	if((rv=checkFileSlot(fileSlot))<0)
		return rv;
	pfsMount=fileSlot->clink->pfsMount;

	switch(cmd)
	{
	case PFS_IOCTL2_MALLOC:
		rv=ioctl2Alloc(fileSlot->clink, *(int *)(arg), 1);
		break;

	case PFS_IOCTL2_FREE:
		ioctl2Free(fileSlot->clink);
		break;

	case PFS_IOCTL2_ATTR_ADD:
	case PFS_IOCTL2_ATTR_DEL:
	case PFS_IOCTL2_ATTR_LOOKUP:
	case PFS_IOCTL2_ATTR_READ:
		rv=ioctl2Attr(fileSlot->clink, cmd, arg, buf, &fileSlot->aentryOffset);
		break;

	default:
		rv=-EINVAL;
		break;
	}

	if(pfsMount->flags & FIO_ATTR_WRITEABLE)
		cacheFlushAllDirty(pfsMount);
	rv=checkForLastError(pfsMount, rv);
	SignalSema(pfsFioSema);

	return rv;
}

int ioctl2Attr(pfs_cache_t *clink, int cmd, void *arg, void *outbuf, u32 *offset)
{	// attr set, attr delete, attr lookup, attr read cmds
	int rv;
	pfs_cache_t *flink;

	if((flink=cacheGetData(clink->pfsMount, clink->sub, clink->sector+1
		,CACHE_FLAG_NOTHING, &rv))==NULL)
		return rv;

	switch(cmd)
	{
	case PFS_IOCTL2_ATTR_ADD:
		rv=ioctl2AttrAdd(flink, arg);
		break;

	case PFS_IOCTL2_ATTR_DEL:
		rv=ioctl2AttrDelete(flink, arg);
		break;

	case PFS_IOCTL2_ATTR_LOOKUP:
		rv=ioctl2AttrLoopUp(flink, arg, outbuf);
		break;

	case PFS_IOCTL2_ATTR_READ:
		rv=ioctl2AttrRead(flink, outbuf, offset);
		break;

	}
	cacheAdd(flink);

	return rv;
}

void devctlCloseAll()
{
	u32 i;

	for(i=0;i < pfsConfig.maxOpen;i++)
	{
		if(fileSlots[i].fd)
			closeFileSlot(&fileSlots[i]);
	}
	for(i=0;i < pfsConfig.maxOpen;i++)
	{
		pfs_mount_t *pfsMount;
		if((pfsMount=getMountedUnit(i))!=NULL)
			cacheFlushAllDirty(pfsMount);

	}
}

int devctlFsckStat(pfs_mount_t *pfsMount, int mode)
{
	int rv;
	pfs_cache_t *clink;

	if((clink=cacheAllocClean(&rv))!=NULL){
		rv=fsckStat(pfsMount, clink->u.superblock, FSCK_STAT_ERROR_0x02, mode);
		cacheAdd(clink);
	}
	return rv;
}

pfs_aentry_t *getAentry(pfs_cache_t *clink, char *key, char *value, int mode)
{	// mode 0=lookup, 1=add, 2=delete
	int kLen, fullsize;
	pfs_aentry_t *aentry=clink->u.aentry;
	pfs_aentry_t *aentryLast=NULL;
	u32 end;

	kLen=strlen(key);
	fullsize=(kLen+strlen(value)+7) & ~3;
	for(end=(u32)aentry+1024;(u32)end < (u32)(aentry); (char *)aentry+=aentry->aLen)
	{
		if(aentry->aLen & 3)
			printf("ps2fs: Error: attrib-entry allocated length/4 != 0\n");
		if(aentry->aLen < ((aentry->kLen+aentry->vLen+7) & ~3)) 
		{
			printf("ps2fs: Panic: attrib-entry is too small\n");
			return NULL;
		}
		if((u32)end < (u32)aentry+aentry->aLen) 
			printf("ps2fs: Error: attrib-emtru too big\n");

		switch(mode)
		{
		case 0:// lookup
			if(kLen==aentry->kLen)
				if(memcmp(key, aentry->str, kLen)==0)
					return aentry;
			break;

		case 1:// add
			if(aentry->kLen==0) 
			{
				if(aentry->aLen>=fullsize)
					return aentry;
			}
			if(aentry->aLen - ((aentry->kLen+aentry->vLen+7) & ~3) < fullsize)
				continue;
			return aentry;

		default:// delete
			if(kLen==aentry->kLen) 
			{
				if(memcmp(key, aentry->str, kLen)==0) 
				{
					if(aentryLast!=NULL) 
					{
						aentryLast->aLen+=aentry->aLen;
						return aentry;
					}
					// delete it :P
					aentry->kLen=0;
					aentry->vLen=0;
					return aentry;
				}
			}
			aentryLast=aentry;
			break;

		}
	}
	return NULL;
}

int ioctl2AttrAdd(pfs_cache_t *clink, pfs_ioctl2attr_t *attr)
{
	u32 kLen, vLen;
	pfs_aentry_t *aentry;
	u32 tmp;

	// input check
	kLen=strlen(attr->key);
	vLen=strlen(attr->value);
	if(kLen>=256 || vLen>=256)	// max size safe e check
		return -EINVAL;

	if(kLen==0 || vLen==0)		// no input check
		return -EINVAL;

	if(getAentry(clink, attr->key, NULL, 0))
		return -EEXIST;
	if(!(aentry=getAentry(clink, attr->key, attr->value, 1)))
		return -ENOSPC;

	if(aentry->kLen==0)
		tmp=aentry->aLen;
	else
		tmp=aentry->aLen-((aentry->kLen+(aentry->vLen + 7)) & ~3);

	aentry->aLen-=tmp;
	(char *)aentry+=aentry->aLen;
	aentry->kLen=kLen;
	aentry->vLen=vLen;
	aentry->aLen=tmp;
	memcpy(&aentry->str[0], attr->key, aentry->kLen);
	memcpy(&aentry->str[aentry->kLen], attr->value, aentry->vLen);
	clink->flags|=CACHE_FLAG_DIRTY;

	return 0;
}

int ioctl2AttrDelete(pfs_cache_t *clink, void *arg)
{
	pfs_aentry_t *aentry;

	if((aentry=getAentry(clink, arg, 0, 2))==NULL)
		return -ENOENT;
	clink->flags|=CACHE_FLAG_DIRTY;
	return 0;
}

int ioctl2AttrLoopUp(pfs_cache_t *clink, char *key, char *value)
{
	pfs_aentry_t *aentry;

	if((aentry=getAentry(clink, key, 0, 0))){
		memcpy(value, &aentry->str[aentry->kLen], aentry->vLen);
		value[aentry->vLen]=0;
		return aentry->vLen;
	}
	return -ENOENT;
}

int ioctl2AttrRead(pfs_cache_t *clink, pfs_ioctl2attr_t *attr, u32 *offset)
{
	pfs_aentry_t *aentry;

	if(*offset >= 1024)
		return 0;
	do {
		aentry=(pfs_aentry_t *)((u32)(clink->u.inode)+*offset);
		memcpy(attr->key, &aentry->str[0], aentry->kLen);
		attr->key[aentry->kLen]=0;
		memcpy(attr->value, &aentry->str[aentry->kLen], aentry->vLen);
		attr->value[aentry->vLen]=0;
		*offset+=aentry->aLen;		// next
		if(aentry->kLen!=0)
			break;
	} while(*offset < 1024);

	return aentry->kLen;
}


int ioctl2Alloc(pfs_cache_t *clink, int msize, int mode)
{
	pfs_blockpos_t blockpos;
	int result=0;
	u32 val;
	int zsize;

	zsize=clink->pfsMount->zsize;
	val=((msize-1 + zsize) & (-zsize)) / zsize;

	if(mode==0)
		if (((clink->u.inode->number_blocks-clink->u.inode->number_segdesg) *(u64)zsize) 
			>= (clink->u.inode->size + msize))
			return 0;

	if((blockpos.inode = blockGetLastSegmentDescriptorInode(clink, &result)))
	{
		blockpos.block_offset=blockpos.byte_offset=0;
		blockpos.block_segment=clink->u.inode->number_data-1;
		val-=blockExpandSegment(clink, &blockpos, val);
		while (val && ((result=blockAllocNewSegment(clink, &blockpos, val))>=0)){
			val-=result;
			result=0;
		}
		cacheAdd(blockpos.inode);
	}
	return result;
}

void ioctl2Free(pfs_cache_t *pfree)
{
	pfs_blockinfo b;
	int result;
	pfs_mount_t *pfsMount = pfree->pfsMount;
	pfs_inode *inode = pfree->u.inode;
	u32 nextsegdesc = 1, limit = inode->number_data, i, j = 0, zones;
	pfs_blockinfo *bi;
	pfs_cache_t *clink;

	zones = inode->size / pfsMount->zsize;
	if(inode->size % pfsMount->zsize)
		zones++;
	if(inode->number_segdesg + zones == inode->number_blocks)
		return;

	j = zones;
	b.number = 0;
	clink = cacheUsedAdd(pfree);

	// iterate through each of the block segments used by the inode
	for (i = 1; i < limit && j; i++)
	{
		if(fixIndex(i) == 0)
		{
			if ((clink = blockGetNextSegment(clink, &result)) == 0)	
				return;

			nextsegdesc++;
		}
		else
			if(j < clink->u.inode->data[fixIndex(i)].count)
			{
				clink->u.inode->data[fixIndex(i)].count -= j;
				b.subpart = clink->u.inode->data[fixIndex(i)].subpart;
				b.count = j;
				b.number = clink->u.inode->data[fixIndex(i)].number +
					clink->u.inode->data[fixIndex(i)].count;
				j = 0;
				clink->flags |= CACHE_FLAG_DIRTY;
			}
			else
				j -= clink->u.inode->data[fixIndex(i)].count;
	}

	pfree->u.inode->number_data = i;
	pfree->u.inode->number_blocks = zones + nextsegdesc;
	pfree->u.inode->number_segdesg = nextsegdesc;
	pfree->u.inode->last_segment.number = clink->u.inode->data[0].number;
	pfree->u.inode->last_segment.subpart= clink->u.inode->data[0].subpart;
	pfree->u.inode->last_segment.count  = clink->u.inode->data[0].count;
	pfree->flags |= CACHE_FLAG_DIRTY;

	if (b.number)
		bitmapFreeBlockSegment(pfsMount, &b);

	while(i < limit)
	{
		if (fixIndex(i) == 0)
		{
			if((clink = blockGetNextSegment(clink, &result)) == 0)	
				return;
		}
		bi = &clink->u.inode->data[fixIndex(i++)];
		bitmapFreeBlockSegment(pfsMount, bi);
		cacheMarkClean(pfsMount, bi->subpart, bi->number<<pfsMount->inode_scale,
			(bi->number+bi->count)<<pfsMount->inode_scale);
	}

	cacheAdd(clink);
}

