/* 
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (C) Hewlett-Packard (Paul Bame) paul_bame@hp.com
 */
/*
 * This is a set of functions that provides minimal filesystem
 * functionality to the Linux bootstrapper.  All we can do is
 * open and read files... but that's all we need 8-)
 *
 * This file has been ported from the DEC 32-bit Linux version
 * by David Mosberger (davidm@cs.arizona.edu).
 */

/* The following defines are necessary to satisfy ext2_fs.h and
 * things it pulls in.
 */

#include <sys/stat.h>
#include <linux/types.h>

#include "ext2_fs.h"
#include "bootloader.h"

#define MAX_OPEN_FILES		5
#define EXTENT_MAX_DEPTH	5

static int ext2_blocksize;

static struct ext2_super_block sb;
static struct ext2_group_desc *gds;
static struct ext2_inode *root_inode = NULL;
static int ngroups = 0;
static int group_size;
static int directlim;			/* Maximum direct blkno */
static int ind1lim;			/* Maximum single-indir blkno */
static int ind2lim;			/* Maximum double-indir blkno */
static int ptrs_per_blk;		/* ptrs/indirect block */
static char blkbuf[EXT2_MAX_BLOCK_SIZE];
static int cached_iblkno = -1;
static char iblkbuf[EXT2_MAX_BLOCK_SIZE];
static int cached_diblkno = -1;
static char diblkbuf[EXT2_MAX_BLOCK_SIZE];
static long dev = -1;
static __u64 partition_offset;

struct ext2_inode *fd2inode[MAX_FD];

static struct inode_table_entry {
	struct	ext2_inode	inode;
	int			inumber;
	int			free;
	unsigned short		old_mode;
} inode_table[MAX_OPEN_FILES];

/* to work with existing palo I/O */
#define cons_read(dev, addr, length, seekptr) \
	seekread(dev, (void *)addr, length, seekptr)

#define inplace(xform, var) (var) = xform((var))

#undef DEBUG
#define Debug 0

static __u64 div_blocksize(__u64 devaddr, unsigned blocksize)
{
	return devaddr / blocksize;
}
static __u64 mul_blocksize(__u64 devaddr, unsigned blocksize)
{
	return devaddr * blocksize;
}

static struct ext3_extent_header *ext3_extent_header(struct ext2_inode *i)
{
	return (struct ext3_extent_header *)&i->i_block[0];
}


static void swapsb(struct ext2_super_block *sb)
{
    inplace(__le32_to_cpu, sb->s_inodes_count);
    inplace(__le32_to_cpu, sb->s_blocks_count);
    inplace(__le32_to_cpu, sb->s_r_blocks_count);
    inplace(__le32_to_cpu, sb->s_free_blocks_count);
    inplace(__le32_to_cpu, sb->s_free_inodes_count);
    inplace(__le32_to_cpu, sb->s_first_data_block);
    inplace(__le32_to_cpu, sb->s_log_block_size);
    inplace(__le32_to_cpu, sb->s_log_frag_size);
    inplace(__le32_to_cpu, sb->s_blocks_per_group);
    inplace(__le32_to_cpu, sb->s_frags_per_group);
    inplace(__le32_to_cpu, sb->s_inodes_per_group);
    inplace(__le32_to_cpu, sb->s_mtime);
    inplace(__le32_to_cpu, sb->s_wtime);

    inplace(__le16_to_cpu, sb->s_mnt_count);
    inplace(__le16_to_cpu, sb->s_max_mnt_count);
    inplace(__le16_to_cpu, sb->s_magic);
    inplace(__le16_to_cpu, sb->s_state);
    inplace(__le16_to_cpu, sb->s_errors);
    inplace(__le16_to_cpu, sb->s_minor_rev_level);

    inplace(__le32_to_cpu, sb->s_lastcheck);
    inplace(__le32_to_cpu, sb->s_checkinterval);
    inplace(__le32_to_cpu, sb->s_creator_os);
    inplace(__le32_to_cpu, sb->s_rev_level);

    inplace(__le16_to_cpu, sb->s_def_resuid);
    inplace(__le16_to_cpu, sb->s_def_resgid);

    inplace(__le32_to_cpu, sb->s_first_ino);

    inplace(__le16_to_cpu, sb->s_inode_size);
    inplace(__le16_to_cpu, sb->s_block_group_nr);

    inplace(__le32_to_cpu, sb->s_feature_compat);
    inplace(__le32_to_cpu, sb->s_feature_incompat);
    inplace(__le32_to_cpu, sb->s_feature_ro_compat);
    inplace(__le32_to_cpu, sb->s_algorithm_usage_bitmap);

    inplace(__le16_to_cpu, sb->s_desc_size);

    /* whew! */
}

static void swapgrp(struct ext2_group_desc *g)
{
    inplace(__le32_to_cpu, g->bg_block_bitmap);
    inplace(__le32_to_cpu, g->bg_inode_bitmap);
    inplace(__le32_to_cpu, g->bg_inode_table);

    inplace(__le16_to_cpu, g->bg_free_blocks_count);
    inplace(__le16_to_cpu, g->bg_free_inodes_count);
    inplace(__le16_to_cpu, g->bg_used_dirs_count);

}

static void swapextenthdr(struct ext3_extent_header *hdr)
{
		inplace(__le16_to_cpu, hdr->eh_magic);
		inplace(__le16_to_cpu, hdr->eh_entries);
		inplace(__le16_to_cpu, hdr->eh_max);
		inplace(__le16_to_cpu, hdr->eh_depth);
		inplace(__le32_to_cpu, hdr->eh_generation);
}

static void swapino(struct ext2_inode *i)
{
	int n;

	inplace(__le16_to_cpu, i->i_mode);
	inplace(__le16_to_cpu, i->i_uid);
	inplace(__le32_to_cpu, i->i_size);
	inplace(__le32_to_cpu, i->i_atime);
	inplace(__le32_to_cpu, i->i_ctime);
	inplace(__le32_to_cpu, i->i_mtime);
	inplace(__le32_to_cpu, i->i_dtime);
	inplace(__le16_to_cpu, i->i_gid);
	inplace(__le16_to_cpu, i->i_links_count);
	inplace(__le32_to_cpu, i->i_blocks);
	inplace(__le32_to_cpu, i->i_flags);
	/* Don't know how to handle the unions right now -- ignore them */
#if 0
	union {
		struct {
			__u32  l_i_reserved1;
		} linux1;
		struct {
			__u32  h_i_translator;
		} hurd1;
		struct {
			__u32  m_i_reserved1;
		} masix1;
	} osd1;				/* OS dependent 1 */
#endif
	if ((i->i_flags & EXT3_EXTENTS_FL)) {
		/* the extent header is in the i_block array */
		struct ext3_extent_header *hdr = ext3_extent_header(i);

		swapextenthdr(hdr);
		if (Debug)
			printf("ext4: extent based inode; depth %d, size %d\n",
			       hdr->eh_depth, hdr->eh_entries);
	} else {
		for (n = 0; n < EXT2_N_BLOCKS; n++) {
			inplace(__le32_to_cpu, i->i_block[n]);
		}
	}
	inplace(__le32_to_cpu, i->i_generation);
	inplace(__le32_to_cpu, i->i_file_acl);
	inplace(__le32_to_cpu, i->i_dir_acl);
	inplace(__le32_to_cpu, i->i_faddr);
#if 0
	union {
		struct {
			__u8	l_i_frag;	/* Fragment number */
			__u8	l_i_fsize;	/* Fragment size */
			__u16	i_pad1;
			__u32	l_i_reserved2[2];
		} linux2;
		struct {
			__u8	h_i_frag;	/* Fragment number */
			__u8	h_i_fsize;	/* Fragment size */
			__u16	h_i_mode_high;
			__u16	h_i_uid_high;
			__u16	h_i_gid_high;
			__u32	h_i_author;
		} hurd2;
		struct {
			__u8	m_i_frag;	/* Fragment number */
			__u8	m_i_fsize;	/* Fragment size */
			__u16	m_pad1;
			__u32	m_i_reserved2[2];
		} masix2;
	} osd2;				/* OS dependent 2 */
#endif
}

static struct ext2_dir_entry_2 de_temp;
static struct ext2_dir_entry_2 *swapde(struct ext2_dir_entry_2 *source)
{
    struct ext2_dir_entry_2 *de = &de_temp;

    memcpy(de, source, sizeof(de_temp));
    if (Debug) printf("swapde: 0x%x 0x%x / ",
				de->inode, de->rec_len);
    inplace(__le32_to_cpu, de->inode);
    inplace(__le16_to_cpu, de->rec_len);
    if (Debug) printf("0x%x 0x%x %d %d %.*s\n",
				de->inode, de->rec_len,
				de->name_len, de->file_type,
				de->name_len, de->name);
    return de;
}

static struct ext2_group_desc *ext2_gds(int i)
{
	char *ptr = (char *)gds;

	return (struct ext2_group_desc *)(ptr + group_size * i);
}

/*
 * Initialize an ext2 partition starting at offset P_OFFSET; this is
 * sort-of the same idea as "mounting" it.  Read in the relevant
 * control structures and make them available to the user.  Returns 0
 * if successful, -1 on failure.
 */
int ext2_mount(long cons_dev, long p_offset, long quiet)
{
	long sb_block = 1;
	long sb_offset;
	int i;

	dev = cons_dev;
	partition_offset = p_offset;

	/* initialize the inode table */
	for (i = 0; i < MAX_OPEN_FILES; i++) {
		inode_table[i].free = 1;
		inode_table[i].inumber = 0;
	}
	/* clear the root inode pointer (very important!) */
	root_inode = NULL;
	
	/* read in the first superblock */
	sb_offset = sb_block * EXT2_MIN_BLOCK_SIZE;
	if (cons_read(dev, (char *)&sb, sizeof(sb), partition_offset + sb_offset)
	    != sizeof(sb))
	{
		printf("ext2 sb read failed\n");
		return -1;
	}
	
	if (__le16_to_cpu(sb.s_magic) != EXT2_SUPER_MAGIC) {
		if (!quiet) {
			printf("This is not a ext2 filesystem (magic 0x%x)\n", sb.s_magic);
		}
		return -1;
	}

	swapsb(&sb);

	ngroups = (sb.s_blocks_count -
		   sb.s_first_data_block +
		   EXT2_BLOCKS_PER_GROUP(&sb) - 1)
		/ EXT2_BLOCKS_PER_GROUP(&sb);

	if (sb.s_feature_incompat & EXT4_FEATURE_INCOMPAT_64BIT)
		group_size = sb.s_desc_size;
	else
		group_size = sizeof(struct ext2_group_desc);

	if (Debug) printf("filesystem group size %d\n", group_size);

	gds = (struct ext2_group_desc *)malloc(ngroups * group_size);

	ext2_blocksize = EXT2_BLOCK_SIZE(&sb);
	if (Debug) printf("ext2 block size %d\n", ext2_blocksize);

	/* read in the group descriptors (immediately follows superblock) */
	cons_read(dev, gds, ngroups * group_size,
		  partition_offset +
                  ext2_blocksize * (EXT2_MIN_BLOCK_SIZE/ext2_blocksize + 1));
	for (i = 0; i < ngroups; i++)
	{
		swapgrp(ext2_gds(i));
	}
	/*
	 * Calculate direct/indirect block limits for this file system
	 * (ext2_blocksize dependent):
	 */
	ext2_blocksize = EXT2_BLOCK_SIZE(&sb);
	if (Debug) printf("ext2 block size %d\n", ext2_blocksize);

	directlim = EXT2_NDIR_BLOCKS - 1;
	ptrs_per_blk = ext2_blocksize/sizeof(unsigned int);
	ind1lim = ptrs_per_blk + directlim;
	ind2lim = (ptrs_per_blk * ptrs_per_blk) + directlim;

	return 0;
}

static void printinode(const char *s, struct ext2_inode *ip)
{
    int i;

    printf("%s: inode %p mode 0%o uid %d gid %d size %d\n",
	s, ip, ip->i_mode, ip->i_uid, ip->i_gid, ip->i_size);

    for (i = 0; i < EXT2_NDIR_BLOCKS; i++)
    {
	printf("\tblock %d at %d (0x%x)\n", i, ip->i_block[i],
		ip->i_block[i]);
    }
}

/*
 * Read the specified inode from the disk and return it to the user.
 * Returns NULL if the inode can't be read...
 */
static struct ext2_inode *ext2_iget(int ino)
{
	int i;
	struct ext2_inode *ip;
	struct inode_table_entry *itp = 0;
	int group;
	__u64 offset;


	ip = 0;
	for (i = 0; i < MAX_OPEN_FILES; i++) {
#ifdef DEBUG
		printf("ext2_iget: looping, entry %d inode %d free %d\n",
		       i, inode_table[i].inumber, inode_table[i].free);
#endif
		if (inode_table[i].free) {
			itp = &inode_table[i];
			ip = &itp->inode;
			break;
		}
	}
	if (!ip) {
		printf("ext2_iget: no free inodes\n");
		return NULL;
	}

	group = (ino-1) / sb.s_inodes_per_group;
#ifdef DEBUG
	printf("group is %d\n", group);
#endif
	offset = partition_offset
		+ mul_blocksize(ext2_gds(group)->bg_inode_table, ext2_blocksize)
		+ (((ino - 1) % EXT2_INODES_PER_GROUP(&sb))
		   * EXT2_INODE_SIZE(&sb));
#ifdef DEBUG
	printf("ext2_iget: reading %ld bytes at offset %lld "
	       "(%lld + (%d * %d) + ((%d) %% %d) * %d) "
	       "(inode %d -> table %d)\n", 
	       sizeof(struct ext2_inode), offset, partition_offset,
	       ext2_gds(group)->bg_inode_table, ext2_blocksize,
	       ino - 1, EXT2_INODES_PER_GROUP(&sb), EXT2_INODE_SIZE(&sb),
	       ino, (int) (itp - inode_table));
#endif
	if (cons_read(dev, ip, sizeof(struct ext2_inode), offset) 
	    != sizeof(struct ext2_inode))
	{
		printf("ext2_iget: read error\n");
		return NULL;
	}

	swapino(ip);
	if (Debug) printinode("iget", ip);
	if (ip->i_flags & EXT3_EXTENTS_FL) {
		struct ext3_extent_header *hdr = ext3_extent_header(ip);

		if (hdr->eh_magic != EXT3_EXT_MAGIC) {
			printf("ext2_iget: wrong extent magic in inode\n");
			return NULL;
		}
		if (hdr->eh_depth > EXTENT_MAX_DEPTH) {
			printf("ext2_iget: file has too deep an extent tree]n");
			return NULL;
		}
	}

	itp->free = 0;
	itp->inumber = ino;
	itp->old_mode = ip->i_mode;

	return ip;
}


/*
 * Release our hold on an inode.  Since this is a read-only application,
 * don't worry about putting back any changes...
 */
static void ext2_iput(struct ext2_inode *ip)
{
	struct inode_table_entry *itp;

	/* Find and free the inode table slot we used... */
	itp = (struct inode_table_entry *)ip;

#ifdef DEBUG
	printf("ext2_iput: inode %d table %d\n", itp->inumber,
	       (int) (itp - inode_table));
#endif
	itp->inumber = 0;
	itp->free = 1;
}

/*
 * Recursive function to find the mapping of a block in the extent
 * tree We make a load of assumptions here, firstly, since we're
 * dealing with filesystems < 2GB we assume all the _hi elements are
 * zero.  Secondly we assume monotonic logical block traversal for
 * kernel/initrd loading, so the cache is static per depth in the
 * tree.
 */

static int ext3_extent_leaf_find(struct ext3_extent_header *hdr, int blkoff)
{
	struct ext3_extent *leaf = (struct ext3_extent *)(hdr + 1);
	int i;

	for (i = 0; i < hdr->eh_entries; i++) {
		__u32 block = __le32_to_cpu(leaf[i].ee_block);
		__u16 len = __le16_to_cpu(leaf[i].ee_len);
		__u32 start = __le32_to_cpu(leaf[i].ee_start);

		if (block <= blkoff && block + len > blkoff)
			return blkoff - block + start;
	}

	/* block is not in map: this means a hole */
	return 0;
}

static int ext3_extent_load_find(struct ext2_inode *ip, int leaf, int d,
				 int blkoff);

static int ext3_extent_node_find(struct ext2_inode *ip,
				 struct ext3_extent_header *hdr, int blkoff)
{
	struct ext3_extent_idx *node = (struct ext3_extent_idx *)(hdr + 1);
	struct ext3_extent_idx *prev = node;
	int i;
	__u32 start = __le32_to_cpu(prev->ei_block);
	__u32 leaf = __le32_to_cpu(prev->ei_leaf);

	for (i = 1; i < hdr->eh_entries; i++) {
		__u32 block = __le32_to_cpu(node[i].ei_block);

		if (start <= blkoff && block > blkoff)
			break;

		prev = &node[i];
		start = __le32_to_cpu(prev->ei_block);
		leaf = __le32_to_cpu(prev->ei_leaf);
	}

	return ext3_extent_load_find(ip, leaf, hdr->eh_depth - 1, blkoff);
}

static int ext3_extent_load_find(struct ext2_inode *ip, int leaf, int d,
				 int blkoff)
{
	static char *blockbuf;
	static int cached_blockno[EXTENT_MAX_DEPTH];
	struct ext3_extent_header *hdr;

	if (!blockbuf) {
		blockbuf = malloc(EXTENT_MAX_DEPTH * EXT2_MAX_BLOCK_SIZE);
		if (!blockbuf) {
			printf("Failed to allocate memory for block buffer\n");
			return -1;
		}
	}

	hdr = (struct ext3_extent_header *) &blockbuf[d * EXT2_MAX_BLOCK_SIZE];
	if (cached_blockno[d] != leaf) {
		if (Debug)
			printf("load extent tree[%d] block at %d\n", d, leaf);

		if (cons_read(dev, hdr, EXT2_MAX_BLOCK_SIZE,
			      mul_blocksize(leaf, ext2_blocksize)) !=
		    EXT2_MAX_BLOCK_SIZE) {
			printf("ext3_extent_load_find: read error\n");
			return -1;
		}
		cached_blockno[d] = leaf;
		swapextenthdr(hdr);
	}

	/* these checks could be done once after load, but belt and braces */
	if (hdr->eh_magic != EXT3_EXT_MAGIC) {
		printf("ext3_extent_load_find: wrong extent magic in block\n");
		return -1;
	}
	if (hdr->eh_depth != d) {
		printf("ext3_extent_load_find: wrong depth %d!=%d\n",
		       hdr->eh_depth, d);
		return -1;
	}
	if (sizeof(hdr) + sizeof(struct ext3_extent)*hdr->eh_entries >
	    EXT2_MAX_BLOCK_SIZE) {
		printf("ext3_extent_load_find: extent is larger than buffer\n");
		return -1;
	}

	if (hdr->eh_depth == 0)
		return ext3_extent_leaf_find(hdr, blkoff);
	else
		return ext3_extent_node_find(ip, hdr, blkoff);
}

/*
 * Map a block using the extents tree
 */
static int ext3_extent_blkno(struct ext2_inode *ip, int blkoff)
{
	struct ext3_extent_header *hdr = ext3_extent_header(ip);


	if (hdr->eh_depth == 0)
		return ext3_extent_leaf_find(hdr, blkoff);
	else
		return ext3_extent_node_find(ip, hdr, blkoff);
}


/*
 * Map a block offset into a file into an absolute block number.
 * (traverse the indirect blocks if necessary).  Note: Double-indirect
 * blocks allow us to map over 64Mb on a 1k file system.  Therefore, for
 * our purposes, we will NOT bother with triple indirect blocks.
 *
 * The "allocate" argument is set if we want to *allocate* a block
 * and we don't already have one allocated.
 */
static int ext2_blkno(struct ext2_inode *ip, int blkoff)
{
	unsigned int *ilp;
	unsigned int *dlp;
	int blkno;
	int iblkno;
	int diblkno;
	__u64 offset;

	if (ip->i_flags & EXT3_EXTENTS_FL)
		return ext3_extent_blkno(ip, blkoff);

	ilp = (unsigned int *)iblkbuf;
	dlp = (unsigned int *)diblkbuf;

	/* If it's a direct block, it's easy! */
	if (blkoff <= directlim) {
		return ip->i_block[blkoff];
	}

	/* Is it a single-indirect? */
	if (blkoff <= ind1lim) {
		iblkno = ip->i_block[EXT2_IND_BLOCK];

		if (iblkno == 0) {
			return 0;
		}

		/* Read the indirect block */
		if (cached_iblkno != iblkno) {
			offset = partition_offset + mul_blocksize(iblkno, ext2_blocksize);
			if (cons_read(dev, iblkbuf, ext2_blocksize, offset)
			    != ext2_blocksize)
			{
				printf("ext2_blkno: error on iblk read\n");
				return 0;
			}
			cached_iblkno = iblkno;
		}

		blkno = ilp[blkoff-(directlim+1)];
		inplace(__le32_to_cpu, blkno);

		return blkno;
	}

	/* Is it a double-indirect? */
	if (blkoff <= ind2lim) {
		/* Find the double-indirect block */
		diblkno = ip->i_block[EXT2_DIND_BLOCK];

		if (diblkno == 0) {
			return 0;
		}

		/* Read in the double-indirect block */
		if (cached_diblkno != diblkno) {
			offset = partition_offset + mul_blocksize(diblkno, ext2_blocksize);
			if (cons_read(dev, diblkbuf, ext2_blocksize, offset)
			    != ext2_blocksize)
			{
				printf("ext2_blkno: err reading dindr blk\n");
				return 0;
			}
			cached_diblkno = diblkno;
		}

		/* Find the single-indirect block pointer ... */
		iblkno = dlp[(blkoff - (ind1lim+1)) / ptrs_per_blk];
		inplace(__le32_to_cpu, iblkno);

		if (iblkno == 0) {
			return 0;
		}

		/* Read the indirect block */
    
		if (cached_iblkno != iblkno) {
			offset = partition_offset + mul_blocksize(iblkno, ext2_blocksize);
			if (cons_read(dev, iblkbuf, ext2_blocksize, offset)
			    != ext2_blocksize)
			{
				printf("ext2_blkno: err on iblk read\n");
				return 0;
			}
			cached_iblkno = iblkno;
		}

		/* Find the block itself. */
		blkno = ilp[(blkoff-(ind1lim+1)) % ptrs_per_blk];
		inplace(__le32_to_cpu, blkno);
		return blkno;
	}

	if (blkoff > ind2lim) {
		printf("ext2_blkno: block number too large: %d\n", blkoff);
		return 0;
	}
	return -1;
}

static int print_ext2_blkno(struct ext2_inode *ip, int blkoff)
{
    int r = ext2_blkno(ip, blkoff);
    if (0) printf("ext2_blkno(%p, blk %d) ==> %d\n",
	ip, blkoff, r);

    return r;
}


static int ext2_breadi(struct ext2_inode *ip, long blkno, long nblks,
		       char *buffer)
{
	long dev_blkno, ncontig, nbytes, tot_bytes;
	__u64 offset;

	if (Debug) printf("ext2_breadi(%p, %ld, %ld, %p)\n",
		ip, blkno, nblks, buffer);

	tot_bytes = 0;
	if ((blkno+nblks)*ext2_blocksize > ip->i_size)
		nblks = (ip->i_size + ext2_blocksize) / ext2_blocksize - blkno;

	if (Debug) printf("nblks = %ld\n", nblks);
	while (nblks) {
		/*
		 * Contiguous reads are a lot faster, so we try to group
		 * as many blocks as possible:
		 */
		ncontig = 0; nbytes = 0;
		dev_blkno = print_ext2_blkno(ip, blkno);
		if (Debug) printf("dev_blkno = %ld\n", dev_blkno);
		do {
			++blkno; ++ncontig; --nblks;
			nbytes += ext2_blocksize;
		} while (nblks &&
			 print_ext2_blkno(ip, blkno) == dev_blkno + ncontig);

		if (Debug) printf("dev_blkno = %ld\n", dev_blkno);

		if (dev_blkno == 0) {
			/* This is a "hole" */
			memset(buffer, 0, nbytes);
		} else {
			/* Read it for real */
			offset = partition_offset + mul_blocksize(dev_blkno, ext2_blocksize);
#ifdef DEBUG
			printf("ext2_bread: reading %ld bytes at offset %lld\n",
			       nbytes, offset);
#endif
			if (cons_read(dev, buffer, nbytes, offset)
			    != nbytes)
			{
				printf("ext2_bread: read error\n");
				return -1;
			}

			if (Debug) blockprint(offset, buffer, 128);
		}
		buffer    += nbytes;
		tot_bytes += nbytes;
	}
	return tot_bytes;
}

static struct ext2_dir_entry_2 *ext2_readdiri(struct ext2_inode *dir_inode,
					      int rewind)
{
	struct ext2_dir_entry_2 *dp;
	static int diroffset = 0, blockoffset = 0;

	/* Reading a different directory, invalidate previous state */
	if (rewind) {
		diroffset = 0;
		blockoffset = 0;
		/* read first block */
		if (ext2_breadi(dir_inode, 0, 1, blkbuf) < 0)
			return NULL;
	}

 repeat:
#ifdef DEBUG
	printf("ext2_readdiri: blkoffset %d diroffset %d len %d\n",
		blockoffset, diroffset, dir_inode->i_size);
#endif
	if (blockoffset >= ext2_blocksize) {
		diroffset += ext2_blocksize;
		if (diroffset >= dir_inode->i_size)
			return NULL;
#ifdef DEBUG
		printf("ext2_readdiri: reading block at %d\n",
			diroffset);
#endif
		/* assume that this will read the whole block */
		if (ext2_breadi(dir_inode,
				diroffset / ext2_blocksize,
				1, blkbuf) < 0)
			return NULL;
		blockoffset = 0;
	}

	dp = (struct ext2_dir_entry_2 *) (blkbuf + blockoffset);
	dp = swapde(dp);
	blockoffset += dp->rec_len;
	/* ext2 deletes a file by zeroing its inode.  We skip deleted
	 * files, corrupt entries and entries that aren't a regular
	 * file or a symlink */
	if (dp->name_len == 0 || dp->inode == 0)
		goto repeat;
	if (dp->file_type != EXT2_FT_REG_FILE &&
	    dp->file_type != EXT2_FT_SYMLINK &&
	    dp->file_type != EXT2_FT_DIR)
		goto repeat;
#ifdef DEBUG
	printf("ext2_readdiri: returning %p = %.*s\n", dp, dp->name_len, dp->name);
#endif
	return dp;
}

static struct ext2_inode *ext2_namei(const char *name)
{
	char namebuf[256];
	char *component;
	struct ext2_inode *dir_inode;
	struct ext2_dir_entry_2 *dp;
	int next_ino;

	/* squirrel away a copy of "namebuf" that we can modify: */
	strcpy(namebuf, name);

	/* start at the root: */
	if (!root_inode)
		root_inode = ext2_iget(EXT2_ROOT_INO);
	dir_inode = root_inode;
	if (!dir_inode)
	  return NULL;

	component = strtok(namebuf, "/");
	while (component) {
		int component_length;
		int rewind = 0;
		/*
		 * Search for the specified component in the current
		 * directory inode.
		 */
		next_ino = -1;
		component_length = strlen(component);

		/* rewind the first time through */
		while ((dp = ext2_readdiri(dir_inode, !rewind++))) {
			if ((dp->name_len == component_length) &&
			    (strncmp(component, dp->name,
				     component_length) == 0))
			{
				/* Found it! */
#ifdef DEBUG
				printf("ext2_namei: found entry %s\n",
					component);
#endif
				next_ino = dp->inode;
				break;
			}
#ifdef DEBUG
			printf("ext2_namei: looping\n");
#endif
		}
	
#ifdef DEBUG
		printf("ext2_namei: next_ino = %d\n", next_ino);
#endif

		/*
		 * At this point, we're done with this directory whether
		 * we've succeeded or failed...
		 */
		if (dir_inode != root_inode)
			ext2_iput(dir_inode);

		/*
		 * If next_ino is negative, then we've failed (gone
		 * all the way through without finding anything)
		 */
		if (next_ino < 0) {
			return NULL;
		}

		/*
		 * Otherwise, we can get this inode and find the next
		 * component string...
		 */
		dir_inode = ext2_iget(next_ino);
		if (!dir_inode)
		  return NULL;

		component = strtok(NULL, "/");
	}

	/*
	 * If we get here, then we got through all the components.
	 * Whatever we got must match up with the last one.
	 */
	return dir_inode;
}


/*
 * Note: don't mix any kind of file lookup or other I/O with this or
 * you will lose horribly (as it reuses blkbuf)
 */
const char * ext2_readdir(int fd, int rewind, unsigned char *file_type)
{
	struct ext2_inode * ip = fd2inode[fd];
	struct ext2_dir_entry_2 * ent;
	if (file_type)
		*file_type = 0;
	if (!S_ISDIR(ip->i_mode)) {
		printf("fd %d (inode %d) is not a directory (mode %x)\n",
		       fd, inode_table[fd].inumber, ip->i_mode);
		return NULL;
	}
	ent = ext2_readdiri(ip, rewind);
	if (ent) {
		ent->name[ent->name_len] = '\0';
		if (file_type)
			*file_type = ent->file_type;
		return ent->name;
	} else { 
		return NULL;
	}
}

int ext2_filesize(int fd)
{
	struct ext2_inode * ip = fd2inode[fd];

	return ip->i_size;
}

#if 0
static int ext2_fstat(int fd, struct stat* buf)
{
	struct ext2_inode * ip = &inode_table[fd].inode;

	if (fd >= MAX_OPEN_FILES)
		return -1;
	memset(buf, 0, sizeof(struct stat));
	/* fill in relevant fields */
	buf->st_ino = inode_table[fd].inumber;
	buf->st_mode = ip->i_mode;
	buf->st_flags = ip->i_flags;
	buf->st_nlink = ip->i_links_count;
	buf->st_uid = ip->i_uid;
	buf->st_gid = ip->i_gid;
	buf->st_size = ip->i_size;
	buf->st_blocks = ip->i_blocks;
	buf->st_atime = ip->i_atime;
	buf->st_mtime = ip->i_mtime;
	buf->st_ctime = ip->i_ctime;

	return 0; /* NOTHING CAN GO WROGN! */
}
#endif

static struct ext2_inode * ext2_follow_link(struct ext2_inode * from,
					    const char * base)
{
	char *linkto;

	if (from->i_blocks) {
		linkto = blkbuf;
		if (ext2_breadi(from, 0, 1, blkbuf) == -1)
			return NULL;
#ifdef DEBUG
		printf("long link!\n");
#endif
	} else {
		int n;
		for (n = 0; n < EXT2_N_BLOCKS; n++) {
			inplace(__le32_to_cpu, from->i_block[n]);
		}
		linkto = (char*)from->i_block;
	}
#ifdef DEBUG
	printf("symlink to %s\n", linkto);
#endif

	/* Resolve relative links */
	if (linkto[0] != '/') {
		char *end = strrchr(base, '/');
		if (end) {
			char fullname[(end - base + 1) + strlen(linkto) + 1];
			strncpy(fullname, base, end - base + 1);
			fullname[end - base + 1] = '\0';
			strcat(fullname, linkto);
#ifdef DEBUG
			printf("resolved to %s\n", fullname);
#endif
			return ext2_namei(fullname);
		} else {
			/* Assume it's in the root */
			return ext2_namei(linkto);
		}
	} else {
		return ext2_namei(linkto);
	}
}

static int ext2_read(int fd, char *buf, unsigned count, __u64 devaddr)
{
	struct ext2_inode * ip;
	ip = fd2inode[fd];

	if (Debug) printf("ext2_read(%d, 0x%p, %d, %lld)\n",
		fd, buf, count, devaddr);

	return ext2_breadi(ip,
		div_blocksize(devaddr, ext2_blocksize),
		count / ext2_blocksize,
		buf);
}

static void ext2_describe(int fd, int *bufalign,
                                int *blocksize)
{
    describe(dev, bufalign, blocksize);
    if (blocksize != 0)
	*blocksize = ext2_blocksize;
}

int ext2_open(const char *filename)
{
	/*
	 * Unix-like open routine.  Returns a small integer (actually
	 * an index into the inode table...
	 */
	struct ext2_inode * ip;
	int fd = -1;

	ip = ext2_namei(filename);
	if (ip) {
		while (S_ISLNK(ip->i_mode)) {
			ip = ext2_follow_link(ip, filename);
			if (!ip) return -1;
		}
		fd = fileio_open(ext2_describe, ext2_read);
		if (fd >= 0)
		{
		    fd2inode[fd] = ip;
		}
	}

	return fd;
}


void ext2_close(int fd)
{
	/* blah, hack, don't close the root inode ever */
	if (&inode_table[fd].inode != root_inode)
		ext2_iput(&inode_table[fd].inode);

	fileio_close(fd);
}
