/*  
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2006 Free Software Foundation, Inc.
 *
 *  This program 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 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; If not, see <http://www.gnu.org/licenses/>.
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/* From Solaris usr/src/stand/lib/fs/ufs/ufsops.c */

#include <xenfsimage_grub.h>

#include "ufs.h"

/* These are the pools of buffers, etc. */

#define SUPERBLOCK ((struct fs *)(FSYS_BUF + 0x2000))
#define	INODE ((struct icommon *)(FSYS_BUF + 0x1000))
#define DIRENT (FSYS_BUF + 0x4000)
#define MAXBSIZE ((FSYS_BUFLEN - 0x4000) / 2)
#define INDIRBLK1 ((grub_daddr32_t *)(FSYS_BUF + 0x4000)) /* 2+ indir blk */
#define	INDIRBLK0 ((grub_daddr32_t *)(FSYS_BUF+ 0x4000 + MAXBSIZE))  /* 1st indirect blk */

#define	indirblk0 (*fsig_int1(ffi))
#define	indirblk1 (*fsig_int2(ffi))

static int openi(fsi_file_t *, grub_ino_t);
static grub_ino_t dlook(fsi_file_t *, grub_ino_t, char *);
static grub_daddr32_t sbmap(fsi_file_t *, grub_daddr32_t);

/* read superblock and check fs magic */
static int
ufs_mount(fsi_file_t *ffi, const char *options)
{
	if (/*! IS_PC_SLICE_TYPE_SOLARIS(current_slice) || */
	    !devread(ffi, UFS_SBLOCK, 0, UFS_SBSIZE, (char *)SUPERBLOCK) ||
	    SUPERBLOCK->fs_magic != UFS_MAGIC ||
	    MAXBSIZE < SUPERBLOCK->fs_bsize)
		return 0;

	return 1;
}


/*
 * searching for a file, if successful, inode will be loaded in INODE
 * The entry point should really be named ufs_open(char *pathname).
 * For now, keep it consistent with the rest of fsys modules.
 */
static int
ufs_dir(fsi_file_t *ffi, char *dirname)
{
	grub_ino_t inode = ROOTINO;	/* start from root */
	char *fname, ch;

	indirblk0 = indirblk1 = 0;

	/* skip leading slashes */
	while (*dirname == '/')
		dirname++;

	while (inode && *dirname && !isspace((uint8_t)*dirname)) {
		if (!openi(ffi, inode))
			return 0;

		/* parse for next path component */
		fname = dirname;
		while (*dirname && !isspace((uint8_t)*dirname) && *dirname != '/')
			dirname++;
		ch = *dirname;
		*dirname = 0;	/* ensure null termination */

		inode = dlook(ffi, inode, fname);
		*dirname = ch;
		while (*dirname == '/')
			dirname++;
	}

	/* return 1 only if inode exists and is a regular file */
	if  (! openi(ffi, inode))
		return (0);
	filepos = 0;
	filemax = INODE->ic_sizelo;
	return (inode && ((INODE->ic_smode & IFMT) == IFREG));
}

/*
 * This is the high-level read function.
 */
static int
ufs_read(fsi_file_t *ffi, char *buf, int len)
{
  	int off, size, ret = 0, ok;
	grub_daddr32_t lblk, dblk;

  	while (len) {
	  	off = blkoff(SUPERBLOCK, filepos);
		lblk = lblkno(SUPERBLOCK, filepos);
		size = SUPERBLOCK->fs_bsize;
		size -= off;
		if (size > len)
		  	size = len;

		if ((dblk = sbmap(ffi, lblk)) <= 0) {
			/* we are in a file hole, just zero the buf */
			grub_memset(buf, 0, size);
		} else {
			disk_read_func = disk_read_hook;
			ok = devread(ffi, fsbtodb(SUPERBLOCK, dblk),
				off, size, buf);
			disk_read_func = 0;
			if (!ok)
		  		return 0;
		}
		buf += size;
		len -= size;
		filepos += size;
		ret += size;
	}

	return (ret);
}

int
ufs_embed (int *start_sector, int needed_sectors)
{
	if (needed_sectors > 14)
        	return 0;

	*start_sector = 2;
	return 1;
}

/* read inode and place content in INODE */
static int
openi(fsi_file_t *ffi, grub_ino_t inode)
{
	grub_daddr32_t dblk;
	int off;

	/* get block and byte offset into the block */
	dblk = fsbtodb(SUPERBLOCK, itod(SUPERBLOCK, inode));
	off = itoo(SUPERBLOCK, inode) * sizeof (struct icommon);

	return (devread(ffi, dblk, off, sizeof (struct icommon), (char *)INODE));
}

/*
 * Performs fileblock mapping. Convert file block no. to disk block no.
 * Returns 0 when block doesn't exist and <0 when block isn't initialized
 * (i.e belongs to a hole in the file).
 */
grub_daddr32_t
sbmap(fsi_file_t *ffi, grub_daddr32_t bn)
{
  	int level, bound, i, index;
	grub_daddr32_t nb, blkno;
	grub_daddr32_t *db = INODE->ic_db;

	/* blocks 0..UFS_NDADDR are direct blocks */
	if (bn < UFS_NDADDR) {
		return db[bn];
	}

	/* determine how many levels of indirection. */
	level = 0;
	bn -= UFS_NDADDR;
	bound = UFS_NINDIR(SUPERBLOCK);
	while (bn >= bound) {
		level++;
		bn -= bound;
		bound *= UFS_NINDIR(SUPERBLOCK);
	}
	if (level >= UFS_NIADDR)	/* bn too big */
		return ((grub_daddr32_t)0);

	/* fetch the first indirect block */
	nb = INODE->ic_ib[level];
	if (nb == 0) {
		return ((grub_daddr32_t)0);
	}
	if (indirblk0 != nb) {
		indirblk0 = 0;
		blkno = fsbtodb(SUPERBLOCK, nb);
		if (!devread(ffi, blkno, 0, SUPERBLOCK->fs_bsize,
		    (char *)INDIRBLK0))
			return (0);
		indirblk0 = nb;
	}
	bound /= UFS_NINDIR(SUPERBLOCK);
	index = (bn / bound) % UFS_NINDIR(SUPERBLOCK);
	nb = INDIRBLK0[index];

	/* fetch through the indirect blocks */
	for (i = 1; i <= level; i++) {
		if (indirblk1 != nb) {
			blkno = fsbtodb(SUPERBLOCK, nb);
			if (!devread(ffi, blkno, 0, SUPERBLOCK->fs_bsize,
			    (char *)INDIRBLK1))
				return (0);
			indirblk1 = nb;
		}
		bound /= UFS_NINDIR(SUPERBLOCK);
		index = (bn / bound) % UFS_NINDIR(SUPERBLOCK);
		nb = INDIRBLK1[index];
		if (nb == 0)
			return ((grub_daddr32_t)0);
	}

	return (nb);
}

/* search directory content for name, return inode number */
static grub_ino_t
dlook(fsi_file_t *ffi, grub_ino_t dir_ino, char *name)
{
	int loc, off;
	grub_daddr32_t lbn, dbn, dblk;
	struct direct *dp;

	if ((INODE->ic_smode & IFMT) != IFDIR)
		return 0;

	loc = 0;
	while (loc < INODE->ic_sizelo) {
	  	/* offset into block */
		off = blkoff(SUPERBLOCK, loc);
		if (off == 0) {		/* need to read in a new block */
		  	/* get logical block number */
			lbn = lblkno(SUPERBLOCK, loc);
			/* resolve indrect blocks */
			dbn = sbmap(ffi, lbn);
			if (dbn == 0)
				return (0);

			dblk = fsbtodb(SUPERBLOCK, dbn);
			if (!devread(ffi, dblk, 0, SUPERBLOCK->fs_bsize,
			    (char *)DIRENT)) {
				return 0;
			}
		}

		dp = (struct direct *)(DIRENT + off);
		if (dp->d_ino && substring(name, dp->d_name) == 0)
			return (dp->d_ino);
		loc += dp->d_reclen;
	}
	return (0);
}

fsi_plugin_ops_t *
fsi_init_plugin(int version, fsi_plugin_t *fp, const char **name)
{
	static fsig_plugin_ops_t ops = {
		FSIMAGE_PLUGIN_VERSION,
		.fpo_mount = ufs_mount,
		.fpo_dir = ufs_dir,
		.fpo_read = ufs_read
	};

	*name = "ufs";
	return (fsig_init(fp, &ops));
}
