/*
 * Copyright (C) 1993, 1994 by Ralf Baechle (linux@uni-koblenz.de)
 *
 * This file is part of Minix-Handler, an AmigaDOS Filehandler to access
 * Minix filesystems via AmigaDOS.
 *
 * Minix-Handler 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.
 *
 * Minix-Handler 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.
 */

#include <stdio.h>
#include <string.h>

#include <exec/types.h>
#include <exec/lists.h>
#include <exec/nodes.h>
#include <exec/memory.h>
#include <devices/trackdisk.h>

#include <proto/exec.h>
#include <proto/dos.h>

#include "minix.h"

#include "proto.h"
#include "volume.h"
#include "td.h"
#include "debug.h"

struct VolumeNode		*CVol;
static struct VolumeList	vl;

/*
 * static prototypes
 */
static BOOL Volume_ReadSuper(struct VolumeNode *Vol);
static VOID Volume_FreeSuper(struct VolumeNode *Vol);
static VOID CountUsedZones(struct VolumeNode *Vol);
static BOOL ReadInodeMap(struct VolumeNode *Vol);
static VOID FreeInodeMap(struct VolumeNode *Vol);
static BOOL ReadZoneMap(struct VolumeNode *Vol);
static VOID FreeZoneMap(struct VolumeNode *Vol);
static BOOL ReadInodes(struct VolumeNode *Vol);
static VOID FreeInodes(struct VolumeNode *Vol);
static int set_bit(int nr,void * vaddr);
static int clear_bit(int nr, void * vaddr);
static int test_bit(int nr, void * vaddr);

/*
 * List containing information about all mounted volumes
 */

VOID Volume_Init(VOID)
{
	NewList((struct List *) &vl.v_List);
}

struct VolumeNode *Volume_Open(VOID)
{
	struct VolumeNode	*Vol, *Scan;

	if(!(Vol=AllocMem(sizeof(struct VolumeNode), MEMF_ANY|MEMF_CLEAR))) {
		DMSG("  No memory for VolumeNode.\n");
		return NULL;
		}
	NewList((struct List *)&Vol->MinixList);

	/*
	 * Read the Superblock.
	 */
	if(Volume_ReadSuper(Vol) == FALSE) goto fail;

        /*
	 * Read the InodeMap
	 */
	if(ReadInodeMap(Vol) == FALSE) goto fail;

	/*
	 * Read the Zonemap
	 */
	if(ReadZoneMap(Vol) == FALSE) goto fail;

        /*
         * How full is the disk?
	 */
	CountUsedZones(Vol);

	/*
	 * Read the Inodes.
	 */
	if(ReadInodes(Vol) == FALSE) goto fail;

	/*
	 * Compute VolumeID
	 */
	Vol->VolumeID = Vol->Inodes[MINIX_ROOT_INO-1].i_time;


	/*
	 * Find volume in list. If found return pointer to old volume data.
	 * Else add volume to list of volumes.
	 */
	Scan = (struct VolumeNode *)vl.v_List.mlh_Head;
	while(Scan->Node.mln_Succ) {
		if(Scan->VolumeID == Vol->VolumeID) {
			/*
			 * Volume exists
			 */
			AddHead((struct List *)&vl, (struct Node *)Vol);
			Volume_Close(Vol);
			return Scan;
			}
		Scan = (struct VolumeNode *)Scan->Node.mln_Succ;
		}
	/*
         * Setup our DOSEntry
	 */
	Vol->VolumeName[0] = 8;
	sprintf(Vol->VolumeName+1, "%p", Vol->VolumeID);
	Vol->Device.dl_Type     = DLT_VOLUME;
	Vol->Device.dl_DiskType = ID_MINIX_DISK;
	Vol->Device.dl_Name     = MKBADDR(Vol->VolumeName);	// BSTR !!!
	Vol->Device.dl_Task     = &((struct Process *)FindTask(0L))->pr_MsgPort;
	DMSG("  Computed new DosEntry.\n");
	if(AddDosEntry((struct DosList *) &Vol->Device)) {
		DMSG("Added DosEntry.\n");
		}
	else {
		DMSG("Couldn't add DosEntry.\n");
		goto fail;
		}

	/*
	 * Volume is new
	 */
DMSG("Trying to add volume to list.\n");
	AddHead((struct List *)&vl, (struct Node *)Vol);
	DMSG("  Opened new volume.\n");

	return Vol;

fail:
	DMSG("  Opening volume failed.\n");
	Volume_Close(Vol);

	return NULL;
}

VOID Volume_Close(struct VolumeNode *Vol)
{
	if(Vol &&
	   IsListEmpty((struct List *)&Vol->MinixList) &&
	   RemDosEntry((struct DosList *) &Vol->Device)) {
		/*
		 * Case1: Volume is no longer needed.
		 */
		Remove((struct Node *)&Vol);
		Volume_FreeSuper(Vol);
		FreeInodeMap(Vol);
		FreeZoneMap(Vol);
		FreeInodes(Vol);
		FreeMem(Vol, sizeof(struct VolumeNode));
		return;
		}
	if(Vol) {
		/*
		 * Recompute Volume-ID, someone might have changed the
		 * root dir's i_time.
		 */
		Vol->VolumeID = Vol->Inodes[MINIX_ROOT_INO-1].i_time;
		return;
		}
}

static BOOL Volume_ReadSuper(struct VolumeNode *Vol)
{

	/*
	 * Allocate memory for the Superblock.
	 */
	if(!(Vol->SuperBlock=AllocMem(BLOCK_SIZE,MEMF_ANY))) {
		DMSG("  No memory for Superblock.\n");
		return FALSE;
		}

	DMSG("Reading SuperBlock.\n");

	if(TD_Read(Vol->SuperBlock, BLOCK_SIZE, BLOCK_SIZE)) {
		DMSG("  Error reading Superblock.\n");
		return FALSE;
		}
	DMSG("  Superblock read successful.\n");
	TD_Motor(MOTOR_OFF);

	switch(Vol->SuperBlock->s_magic) {
		case MINIX_SUPER_MAGIC:
			DMSG("  Magic match OK.\n");
			return TRUE;
		default:
			DMSG("  Magic match failed.\n");
			return FALSE;
		}
	/*
	 * Never reached.
	 */
}

static VOID Volume_FreeSuper(struct VolumeNode *Vol)
{
	if(Vol && Vol->SuperBlock) FreeMem(Vol->SuperBlock, BLOCK_SIZE);
}

static VOID CountUsedZones(struct VolumeNode *Vol)
{
	UWORD	i;

	Vol->ZonesUsed = 0;
	DMSG1("%d Zones total.\n", Vol->SuperBlock->s_nzones);
	Vol->ZonesUsed = Vol->SuperBlock->s_firstdatazone;
	for(i=0; i<Vol->SuperBlock->s_nzones - Vol->SuperBlock->s_firstdatazone; i++)
		if(test_bit(i, Vol->ZoneMap))
			Vol->ZonesUsed++;
	DMSG1("%d Zones used.\n", Vol->ZonesUsed);
}

UWORD ZonesFree(struct VolumeNode *Vol)
{
	return (UWORD) (Vol->SuperBlock->s_nzones - Vol->ZonesUsed);
}

static BOOL ReadInodeMap(struct VolumeNode *Vol)
{
	BOOL	ret;

	DMSG("Reading inodemap from disk.\n");

	/*
	 * Calculate size of Zonemap
	 */
	Vol->InodeMapSize = Vol->SuperBlock->s_imap_blocks << BLOCK_SIZE_BITS;

	/*
	 * Allocate Zonemap.
	 */
	DMSG("  Allocating core for inodemap.\n");
	if(!(Vol->InodeMap=AllocMem(Vol->InodeMapSize, MEMF_ANY))) {
		DMSG("  No core for inodemap.\n");
		return FALSE;
		}

	if(TD_Read(Vol->InodeMap, Vol->InodeMapSize, 2 * BLOCK_SIZE)) {
		DMSG("  Error reading inodemap.\n");
		FreeMem(Vol->InodeMap, Vol->InodeMapSize);
		ret = FALSE;
		}
	else {
		DMSG("  Inodemap read successful.\n");
		ret = TRUE;
		}
	TD_Motor(MOTOR_OFF);

	return ret;
}

static VOID FreeInodeMap(struct VolumeNode *Vol)
{
	if(Vol && Vol->InodeMap) FreeMem(Vol->InodeMap, Vol->SuperBlock->s_imap_blocks << BLOCK_SIZE_BITS);
}

BOOL WriteInodeMap(struct VolumeNode *Vol)
{
	BOOL	ret;

	DMSG("  Writing inodemap to disk.\n");

	/*
	 * Offset is currently hardwired
	 */

	if(TD_Write(Vol->InodeMap, Vol->InodeMapSize, 2 * BLOCK_SIZE)) {
		DMSG("  Error writing inodemap.\n");
		ret = FALSE;
		}
	else {
		DMSG("  Inodemap written successful.\n");
		ret = TRUE;
		}
	TD_Motor(MOTOR_OFF);

	return ret;
}

UWORD AllocInode(struct VolumeNode *Vol)
{
	UWORD	i;

	DMSG("  Trying to allocate inode.\n");

	for(i = 0;i < Vol->SuperBlock->s_ninodes;i++)
		if(set_bit(i, Vol->InodeMap) == 0) {
			DMSG1("  Allocated Inode %d.\n", i);
			return i;
			}

	DMSG("  AllocInode(): No inode free.\n");
	return 0;
}

VOID FreeInode(struct VolumeNode *Vol, UWORD ino)
{
	if(ino <= MINIX_ROOT_INO || ino > Vol->SuperBlock->s_ninodes) {
		DMSG("  Trying to free strange inode #.\n");
		return;
		}

	if(clear_bit(ino, Vol->InodeMap))
		DMSG1("  FreeInode(): Inode %d already free.\n", ino);

	memset(&Vol->Inodes[ino], 0, sizeof(struct minix_inode));
}

static BOOL ReadZoneMap(struct VolumeNode *Vol)
{
	BOOL	ret;

	DMSG("Reading Zonemap from disk.\n");

	/*
	 * Calculate size of Zonemap
	 */
	Vol->ZoneMapSize = Vol->SuperBlock->s_zmap_blocks << BLOCK_SIZE_BITS;

	/*
	 * Allocate Zonemap.
	 */
	DMSG("  Allocating core for Zonemap.\n");
	if(!(Vol->ZoneMap=AllocMem(Vol->ZoneMapSize, MEMF_ANY))) {
		DMSG("  No core for Zonemap.\n");
		return FALSE;
		}

	if(TD_Read(Vol->ZoneMap, Vol->ZoneMapSize, 2 * BLOCK_SIZE + Vol->InodeMapSize)) {
		DMSG("  Error reading zonemap.\n");
		ret = FALSE;
		}
	else {
		DMSG("  Zonemap read successfull.\n");
		ret = TRUE;
		}
	TD_Motor(MOTOR_OFF);

	return ret;
}


static VOID FreeZoneMap(struct VolumeNode *Vol)
{
	if(Vol && Vol->ZoneMap) FreeMem(Vol->ZoneMap, Vol->ZoneMapSize);
}

BOOL WriteZoneMap(struct VolumeNode *Vol)
{
	BOOL	ret;

	DMSG("  Writing ZoneMap to disk.\n");

	if(TD_Write(Vol->ZoneMap, Vol->ZoneMapSize, 2 * BLOCK_SIZE + Vol->InodeMapSize)) {
		DMSG("  Error writing Inodes.\n");
		ret = FALSE;
		}
	else {
		DMSG("  Inodes written successful.\n");
		ret = TRUE;
		}
	TD_Motor(MOTOR_OFF);

	return ret;
}

UWORD AllocZone(struct VolumeNode *Vol)
{
	UWORD	i;

	DMSG("  Trying to allocate zone.\n");

	for(i = 0;i < Vol->SuperBlock->s_ninodes;i++)
		if(set_bit(i, Vol->InodeMap) == 0) {
			DMSG1("  Allocated zone %d.\n", i + Vol->SuperBlock->s_firstdatazone);
			return  (UWORD)(i + Vol->SuperBlock->s_firstdatazone);
			}

	DMSG("  AllocZone(): No zone free.\n");
	return 0;
}

VOID FreeZone(struct VolumeNode *Vol, ULONG block)
{
	if (block < Vol->SuperBlock->s_firstdatazone || block > Vol->SuperBlock->s_nzones) {
		DMSG("trying to free block not in datazone.\n");
		return;
		}

	if(clear_bit(block - Vol->SuperBlock->s_firstdatazone, Vol->ZoneMap)) {
		DMSG1( "free_block: %d bit already cleared\n", block );
		return;
		}

	DMSG1(" freed zone: %d\n", block);
	Vol->ZonesUsed--;

	return;
}

static BOOL ReadInodes(struct VolumeNode *Vol)
{
	BOOL	ret;

	DMSG("Reading Inodes from disk.\n");

	/*
	 * Calculate size of inode table blocks
	 */
	Vol->InodeSize = Vol->SuperBlock->s_ninodes*sizeof(struct minix_inode);
	Vol->InodeSize = (Vol->InodeSize + (BLOCK_SIZE-1)) & ~(BLOCK_SIZE-1);

	/*
	 * Allocate Inodetable.
	 */
	DMSG("  Allocating core for Inodes.\n");
	if(!(Vol->Inodes=AllocMem(Vol->InodeSize, MEMF_ANY))) {
		DMSG("  No core for Inodes.\n");
		return FALSE;
		}

	if(TD_Read(Vol->Inodes, Vol->InodeSize, 2 * BLOCK_SIZE + Vol->InodeMapSize + Vol->ZoneMapSize)) {
		DMSG("  Error reading Inodes.\n");
		ret = FALSE;
		}
	else {
		DMSG("  Inodes read successful.\n");
		ret = TRUE;
		}
	TD_Motor(MOTOR_OFF);

	return ret;
}

static VOID FreeInodes(struct VolumeNode *Vol)
{
	if(Vol && Vol->Inodes) FreeMem(Vol->Inodes, Vol->InodeSize);
}

BOOL WriteInodes(struct VolumeNode *Vol)
{
	BOOL	ret;

	DMSG("  Writing Inodes to disk.\n");

	if(TD_Write(Vol->Inodes, Vol->InodeSize,  2 * BLOCK_SIZE + Vol->InodeMapSize + Vol->ZoneMapSize)) {
		DMSG("  Error writing Inodes.\n");
		ret = FALSE;
		}
	else {
		DMSG("  Inodes written successful.\n");
		ret = TRUE;
		}
	TD_Motor(MOTOR_OFF);

	return ret;
}

ULONG Volume_IsInUse(struct VolumeNode *Vol)
{
	if(Vol)
		return (ULONG) (IsListEmpty((struct List *)&Vol->MinixList) ? 0 : 1);
	else
		return 0;
}

static int set_bit(int nr,void * vaddr)
{
	int	      mask, retval;
	unsigned long *addr = (unsigned long *)vaddr;

	addr += nr >> 5;
	mask = 1 << (nr & 0x1f);
	retval = (mask & *addr) != 0;
	*addr |= mask;
	return retval;
}

static int clear_bit(int nr, void * vaddr)
{
	int	mask, retval;
	unsigned long *addr = (unsigned long *)vaddr;

	addr += nr >> 5;
	mask = 1 << (nr & 0x1f);
	retval = (mask & *addr) == 0;
	*addr &= ~mask;
	return retval;
}

static int test_bit(int nr, void * vaddr)
{
	int	mask;
	unsigned long *addr = (unsigned long *)vaddr;

	addr += nr >> 5;
	mask = 1 << (nr & 0x1f);
	return ((mask & *addr) != 0);
}
