/* 
 * Mach Operating System
 * Copyright (c) 1993 Carnegie Mellon University
 * All Rights Reserved.
 * 
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 * 
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 * 
 * Carnegie Mellon requests users of this software to return to
 * 
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 * 
 * any improvements or extensions that they make and grant Carnegie Mellon
 * the rights to redistribute these changes.
 */
/*
 * HISTORY
 * $Log: ufs.c,v $
 * Revision 1.1.1.2  2007/08/16 07:04:23  vorlon
 * Import upstream "release" 1.0pre20040408 to CVS to facilitate rebasing
 * against the current upstream work and avoid copying patches one-by-one.
 *
 * Revision 1.2  2003/11/08 00:03:36  wgwoods
 * Reverted changes from 0.10, merged doc changes
 *
 * Revision 1.1.1.1.2.1  2003/03/21 23:32:23  wgwoods
 * Warning cleanups
 *
 * Revision 1.1.1.1  2001/10/08 23:03:52  wgwoods
 * initial import of CVS source from alphalinux.org, plus a couple bugfixes
 *
 * Revision 1.1.1.1  2000/05/03 03:58:22  dhd
 * Initial import (from 0.7 release)
 *
 * Revision 2.2  93/02/05  08:01:36  danner
 * 	Adapted for alpha.
 * 	[93/02/04            af]
 * 
 */
/*
 *	File: ufs.c
 * 	Author: David Golub, Carnegie Mellon University
 *	Date:	12/90
 *
 *	Stand-alone file reading package.
 *
 *	Modified for use by Linux/Alpha by David Mosberger
 *	(davidm@cs.arizona.edu)
 */
#include <linux/kernel.h>
#include <asm/stat.h>

#include "aboot.h"
#include "bootfs.h"
#include "cons.h"
#include "disklabel.h"
#include "ufs.h"
#include "utils.h"
#include "string.h"

#define MAX_OPEN_FILES	1

extern struct bootfs ufs;

static long dev;
static long partition_offset;
static struct fs *fs;
static struct file {
	int 		inuse;
	struct icommon	i_ic;		/* copy of on-disk inode */
	int		f_nindir[NIADDR+1];
					/* number of blocks mapped by
					   indirect block at level i */
	void		*f_blk[NIADDR];	/* buffer for indir block at level i */
	long		f_blksize[NIADDR];
					/* size of buffer */
	__kernel_daddr_t		f_blkno[NIADDR];
					/* disk address of block in buffer */
	void		*f_buf;		/* buffer for data block */
	long		f_buf_size;	/* size of data block */
	__kernel_daddr_t		f_buf_blkno;	/* block number of data block */
} inode_table[MAX_OPEN_FILES];


static int read_inode(__kernel_ino_t inumber, struct file *fp)
{
	__kernel_daddr_t disk_block;
	long offset;
	struct dinode *dp;
	int level;

	disk_block = itod(fs, inumber);

	offset = fsbtodb(fs, disk_block) * DEV_BSIZE + partition_offset;
	if (cons_read(dev, fp->f_buf, fs->fs_bsize, offset) != fs->fs_bsize) {
		printf("ufs_read_inode: read error\n");
		return 1;
	}
	dp = (struct dinode *)fp->f_buf;
	dp += itoo(fs, inumber);
	fp->i_ic = dp->di_ic;
	/*
	 * Clear out the old buffers
	 */
	for (level = 0; level < NIADDR; level++) {
		if (fp->f_blk[level]) {
			free(fp->f_blk[level]);
			fp->f_blk[level] = 0;
		}
		fp->f_blkno[level] = -1;
	}
	return 0;
}


/*
 * Given an offset in a file, find the disk block number that
 * contains that block.
 */
static __kernel_daddr_t block_map(struct file *fp, __kernel_daddr_t file_block)
{
	__kernel_daddr_t ind_block_num, *ind_p;
	int level, idx;
	long offset;
	/*
	 * Index structure of an inode:
	 *
	 * i_db[0..NDADDR-1]	hold block numbers for blocks
	 *			0..NDADDR-1
	 *
	 * i_ib[0]		index block 0 is the single indirect
	 *			block
	 *			holds block numbers for blocks
	 *			NDADDR .. NDADDR + NINDIR(fs)-1
	 *
	 * i_ib[1]		index block 1 is the double indirect
	 *			block
	 *			holds block numbers for INDEX blocks
	 *			for blocks
	 *			NDADDR + NINDIR(fs) ..
	 *			NDADDR + NINDIR(fs) + NINDIR(fs)**2 - 1
	 *
	 * i_ib[2]		index block 2 is the triple indirect
	 *			block
	 *			holds block numbers for double-indirect
	 *			blocks for blocks
	 *			NDADDR + NINDIR(fs) + NINDIR(fs)**2 ..
	 *			NDADDR + NINDIR(fs) + NINDIR(fs)**2
	 *				+ NINDIR(fs)**3 - 1
	 */
	if (file_block < NDADDR) {
		/* Direct block. */
		return fp->i_db[file_block];
	}

	file_block -= NDADDR;

	/*
	 * nindir[0] = NINDIR
	 * nindir[1] = NINDIR**2
	 * nindir[2] = NINDIR**3
	 *	etc
	 */
	for (level = 0; level < NIADDR; level++) {
		if (file_block < fp->f_nindir[level])
		  break;
		file_block -= fp->f_nindir[level];
	}
	if (level == NIADDR) {
		printf("ufs_block_map: block number too high\n");
		return -1;
	}

	ind_block_num = fp->i_ib[level];

	for (; level >= 0; level--) {
		if (ind_block_num == 0) {
			return 0;
		}

		if (fp->f_blkno[level] != ind_block_num) {
			if (fp->f_blk[level]) {
				free(fp->f_blk[level]);
			}

			offset = fsbtodb(fs, ind_block_num) * DEV_BSIZE
			  + partition_offset;
			fp->f_blk[level] = malloc(fs->fs_bsize);
			if (cons_read(dev, fp->f_blk[level], fs->fs_bsize,
				      offset)
			    != fs->fs_bsize) 
			{
				printf("ufs_block_map: read error\n");
				return -1;
			}
			fp->f_blkno[level] = ind_block_num;
		}

		ind_p = (__kernel_daddr_t *)fp->f_blk[level];

		if (level > 0) {
			idx = file_block / fp->f_nindir[level-1];
			file_block %= fp->f_nindir[level-1];
		} else {
			idx = file_block;
		}
		ind_block_num = ind_p[idx];
	}
	return ind_block_num;
}


static int breadi(struct file *fp, long blkno, long nblks, char *buffer)
{
	long block_size, offset, tot_bytes, nbytes, ncontig;
	__kernel_daddr_t disk_block;

	tot_bytes = 0;
	while (nblks) {
		/*
		 * Contiguous reads are a lot faster, so we try to group
		 * as many blocks as possible:
		 */
		ncontig = 0;	/* # of *fragments* that are contiguous */
		nbytes = 0;
		disk_block = block_map(fp, blkno);
		do {
			block_size = blksize(fs, fp, blkno);
			nbytes += block_size;
			ncontig += numfrags(fs, block_size);
			++blkno; --nblks;
		} while (nblks &&
			 block_map(fp, blkno) == disk_block + ncontig);

		if (!disk_block) {
			/* it's a hole... */
			memset(buffer, 0, nbytes);
		} else {
			offset = fsbtodb(fs, disk_block) * DEV_BSIZE
			  + partition_offset;
			if (cons_read(dev, buffer, nbytes, offset) != nbytes) {
				printf("ufs_breadi: read error\n");
				return -1;
			}
		}
		buffer    += nbytes;
		tot_bytes += nbytes;
	}
	return tot_bytes;
}


/*
 * Search a directory for a name and return its
 * i_number.
 */
static int search_dir(const char *name, struct file *fp, __kernel_ino_t *inumber_p)
{
	long offset, blockoffset;
	struct direct *dp;
	int len;

	len = strlen(name);

	offset = 0;
	while (offset < fp->i_size) {
		blockoffset = 0;
		if (breadi(fp, offset / fs->fs_bsize, 1, fp->f_buf) < 0) {
			return -1;
		}
		while (blockoffset < fs->fs_bsize) {
			dp = (struct direct *)((char*)fp->f_buf + blockoffset);
			if (dp->d_ino) {
				if (dp->d_namlen == len
				    && strcmp(name, dp->d_name) == 0)
				{
					/* found entry */
					*inumber_p = dp->d_ino;
					return 0;
				}
			}
			blockoffset += dp->d_reclen;
		}
		offset += fs->fs_bsize;
	}
	return -1;
}


/*
 * Initialize a BSD FFS 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.
 */
static int ufs_mount(long cons_dev, long p_offset, long quiet)
{
	static char buf[SBSIZE];	/* minimize frame size */
	long rc;

	memset(&inode_table, 0, sizeof(inode_table));

	dev = cons_dev;
	partition_offset = p_offset;

	rc = cons_read(dev, buf, SBSIZE, SBLOCK*DEV_BSIZE + partition_offset);
	if (rc != SBSIZE)
	{
		printf("ufs_mount: superblock read failed (retval=%ld)\n", rc);
		return -1;
	}

	fs = (struct fs *)buf;
	if (fs->fs_magic != FS_MAGIC ||
	    fs->fs_bsize > MAXBSIZE ||
	    fs->fs_bsize < (int) sizeof(struct fs))
	{
		if (!quiet) {
			printf("ufs_mount: invalid superblock "
			       "(magic=%x, bsize=%d)\n",
			       fs->fs_magic, fs->fs_bsize);
		}
		return -1;
	}
	ufs.blocksize = fs->fs_bsize;

	/* don't read cylinder groups - we aren't modifying anything */
	return 0;
}


static int ufs_open(const char *path)
{
	char *cp = 0, *component;
	int fd;
	__kernel_ino_t inumber, parent_inumber;
	int nlinks = 0;
	struct file *fp;
	static char namebuf[MAXPATHLEN+1];
	
	if (!path || !*path) {
		return -1;
	}

	for (fd = 0; inode_table[fd].inuse; ++fd) {
		if (fd >= MAX_OPEN_FILES) {
			return -1;
		}
	}
	fp = &inode_table[fd];
	fp->f_buf_size = fs->fs_bsize;
	fp->f_buf = malloc(fp->f_buf_size);

	/* copy name into buffer to allow modifying it: */
	memcpy(namebuf, path, (unsigned)(strlen(path) + 1));

	inumber = (__kernel_ino_t) ROOTINO;
	if (read_inode(inumber, fp) < 0) {
		return -1;
	}

	component = strtok(namebuf, "/");
	while (component) {
		/* verify that current node is a directory: */
		if ((fp->i_mode & IFMT) != IFDIR) {
			return -1;
		}
		/*
		 * Look up component in current directory.
		 * Save directory inumber in case we find a
		 * symbolic link.
		 */
		parent_inumber = inumber;
		if (search_dir(component, fp, &inumber))
		  return -1;

		/* open next component: */
		if (read_inode(inumber, fp))
		  return -1;

		/* check for symbolic link: */
		if ((fp->i_mode & IFMT) == IFLNK) {
			int link_len = fp->i_size;
			int len;

			len = strlen(cp) + 1;

			if (link_len + len >= MAXPATHLEN - 1) {
				return -1;
			}

			if (++nlinks > MAXSYMLINKS) {
				return FS_SYMLINK_LOOP;
			}
			memcpy(&namebuf[link_len], cp, len);
#ifdef IC_FASTLINK
			if ((fp->i_flags & IC_FASTLINK) != 0) {
				memcpy(namebuf, fp->i_symlink, link_len);
			} else
#endif /* IC_FASTLINK */
			{
				/* read file for symbolic link: */
				long rc, offset;
				__kernel_daddr_t	disk_block;

				disk_block = block_map(fp, (__kernel_daddr_t)0);
				offset = fsbtodb(fs, disk_block) * DEV_BSIZE
				  + partition_offset;
				rc = cons_read(dev, namebuf, sizeof(namebuf),
					       offset);
				if (rc != sizeof(namebuf)) {
					return -1;
				}
			}
			/*
			 * If relative pathname, restart at parent directory.
			 * If absolute pathname, restart at root.
			 */
			cp = namebuf;
			if (*cp != '/') {
				inumber = parent_inumber;
			} else
			  inumber = (__kernel_ino_t)ROOTINO;

			if (read_inode(inumber, fp))
			  return -1;
		}
		component = strtok(NULL, "/");
	}
	/* calculate indirect block levels: */
	{
		register int mult;
		register int level;

		mult = 1;
		for (level = 0; level < NIADDR; level++) {
			mult *= NINDIR(fs);
			fp->f_nindir[level] = mult;
		}
	}
	return fd;
}


static int ufs_bread(int fd, long blkno, long nblks, char *buffer)
{
	struct file *fp;

	fp = &inode_table[fd];
	return breadi(fp, blkno, nblks, buffer);
}


static void ufs_close(int fd)
{
	inode_table[fd].inuse = 0;
}

static const char *
ufs_readdir(int fd, int rewind)
{
	return NULL;
}

static int
ufs_fstat(int fd, struct stat* buf)
{
	return -1;
}

struct bootfs ufs = {
	FS_BSDFFS, 0,
	ufs_mount,
	ufs_open,  ufs_bread,  ufs_close, ufs_readdir, ufs_fstat
};
