/*
 *   Creation Date: <2003/12/03 22:10:45 samuel>
 *   Time-stamp: <2004/01/07 19:17:45 samuel>
 *
 *	<disk-label.c>
 *
 *	Partition support
 *
 *   Copyright (C) 2003, 2004 Samuel Rydh (samuel@ibrium.se)
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   version 2
 *
 */

#include "config.h"
#include "libopenbios/bindings.h"
#include "libopenbios/load.h"
#include "libc/diskio.h"
#include "libc/vsprintf.h"
#include "packages.h"

//#define DEBUG_DISK_LABEL

#ifdef DEBUG_DISK_LABEL
#define DPRINTF(fmt, args...) \
do { printk("DISK-LABEL - %s: " fmt, __func__ , ##args); } while (0)
#else
#define DPRINTF(fmt, args...) do { } while (0)
#endif

typedef struct {
	xt_t 		parent_seek_xt;
	xt_t 		parent_tell_xt;
	xt_t		parent_read_xt;

        ucell	        offs_hi, offs_lo;
        ucell	        size_hi, size_lo;
	int		block_size;
	int		type;		/* partition type or -1 */

	ihandle_t	part_ih;
	phandle_t	filesystem_ph;
} dlabel_info_t;

DECLARE_NODE( dlabel, 0, sizeof(dlabel_info_t), "/packages/disk-label" );


/* ( -- ) */
static void
dlabel_close( __attribute__((unused))dlabel_info_t *di )
{
}

/* ( -- success? ) */
static void
dlabel_open( dlabel_info_t *di )
{
	char *path;
	char block0[512];
	phandle_t ph;
	int success=0;
	cell status;

	path = my_args_copy();

	DPRINTF("dlabel-open '%s'\n", path );

	di->part_ih = 0;

	/* Find parent methods */
	di->filesystem_ph = 0;
	di->parent_seek_xt = find_parent_method("seek");
	di->parent_tell_xt = find_parent_method("tell");
	di->parent_read_xt = find_parent_method("read");

	/* If arguments have been passed, determine the partition/filesystem type */
	if (path && strlen(path)) {

		/* Read first block from parent device */
		DPUSH(0);
		call_package(di->parent_seek_xt, my_parent());
		POP();

		PUSH(pointer2cell(block0));
		PUSH(sizeof(block0));
		call_package(di->parent_read_xt, my_parent());
		status = POP();
		if (status != sizeof(block0))
			goto out;

		/* Find partition handler */
		PUSH( pointer2cell(block0) );
		selfword("find-part-handler");
		ph = POP_ph();
		if( ph ) {
			/* We found a suitable partition handler, so interpose it */
			DPRINTF("Partition found on disk - scheduling interpose with ph " FMT_ucellx "\n", ph);

			push_str(path);
			PUSH_ph(ph);
			fword("interpose");	

			success = 1;
		} else {
			/* unknown (or missing) partition map,
			* try the whole disk
			*/

			DPRINTF("Unknown or missing partition map; trying whole disk\n");

			/* Probe for filesystem from start of device */
			DPUSH ( 0 );	
			PUSH_ih( my_self() );
			selfword("find-filesystem");
			ph = POP_ph();
			if( ph ) {
				/* If we have been asked to open a particular file, interpose the filesystem package with the passed filename as an argument */
				di->filesystem_ph = ph;

				DPRINTF("Located filesystem with ph " FMT_ucellx "\n", ph);
				DPRINTF("path: %s length: %d\n", path, strlen(path));

				if (path && strlen(path)) {
					DPRINTF("INTERPOSE!\n");

					push_str( path );
					PUSH_ph( ph );
					fword("interpose");
				}
			} else if (path && strcmp(path, "%BOOT") != 0) {
				goto out;
			}

			success = 1;
		}
	} else {
		/* No arguments were passed, so we just use the parent raw device directly */
		success = 1;
	}

out:
	if( path )
		free( path );
	if( !success ) {
		dlabel_close( di );
		RET(0);
	}
	PUSH(-1);
}

/* ( addr len -- actual ) */
static void
dlabel_read( dlabel_info_t *di )
{
	/* Call back up to parent */
	call_package(di->parent_read_xt, my_parent());
}

/* ( pos.d -- status ) */
static void
dlabel_seek( dlabel_info_t *di )
{
	/* Call back up to parent */
	call_package(di->parent_seek_xt, my_parent());
}

/* ( -- filepos.d ) */
static void
dlabel_tell( dlabel_info_t *di )
{
	/* Call back up to parent */
	call_package(di->parent_tell_xt, my_parent());
}

/* ( addr len -- actual ) */
static void
dlabel_write( __attribute__((unused)) dlabel_info_t *di )
{
	DDROP();
	PUSH( -1 );
}

/* ( addr -- size ) */
static void
dlabel_load( __attribute__((unused)) dlabel_info_t *di )
{
	/* Try the load method of the part package */
#ifdef DEBUG_DISK_LABEL
	char *buf;
#endif
	xt_t xt;

#ifdef DEBUG_DISK_LABEL
	buf = (char *)POP();
#else
        POP();
#endif

	DPRINTF("load invoked with address %p\n", buf);

	/* If we have a partition handle, invoke the load word on it */
	if (di->part_ih) {
		xt = find_ih_method("load", di->part_ih);
		if (!xt) {
			forth_printf("load currently not implemented for ihandle " FMT_ucellx "\n", di->part_ih);
			PUSH(0);
			return;
		}
	
		DPRINTF("calling load on ihandle " FMT_ucellx "\n", di->part_ih);

		call_package(xt, di->part_ih);
	} else {
		/* Otherwise attempt load directly on the raw disk */
		DPRINTF("calling load on raw disk ihandle " FMT_ucellx "\n", my_self());

		load(my_self());
	}
}

/* ( pathstr len -- ) */
static void
dlabel_dir( dlabel_info_t *di )
{
	if ( di->filesystem_ph ) {
		PUSH( my_self() );
		push_str("dir");
		PUSH( di->filesystem_ph );
		fword("find-method");
		POP();
		fword("execute");
 	} else {
		forth_printf("disk-label: Unable to determine filesystem\n");
		POP();
		POP();
	}
}

NODE_METHODS( dlabel ) = {
	{ "open",	dlabel_open 	},
	{ "close",	dlabel_close 	},
	{ "load",	dlabel_load 	},
	{ "read",	dlabel_read 	},
	{ "write",	dlabel_write 	},
	{ "seek",	dlabel_seek 	},
	{ "tell",	dlabel_tell 	},
	{ "dir",	dlabel_dir 	},
};

void
disklabel_init( void )
{
	REGISTER_NODE( dlabel );
}
