/*	rawfs.c - Raw Minix file system support.	Author: Kees J. Bot
 *								23 Dec 1991
 *					     Based on readfs by Paul Polderman
 */
#define nil 0
#define _POSIX_SOURCE	1
#define _MINIX		1
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <minix/config.h>
#include <minix/const.h>
#include <minix/type.h>
#include <fs/const.h>
#include <fs/type.h>
#include <fs/buf.h>
#include <fs/super.h>
#include <fs/inode.h>
#include "rawfs.h"

void readblock(off_t blockno, char *buf);

/* The following code handles two file system types: Version 1 with small
 * inodes and 16-bit disk addresses and Version 2 with big inodes and 32-bit
 * disk addresses.
#ifdef FLEX
 * To make matters worse, Minix-vmd knows about the normal Unix Version 7
 * directories and directories with flexible entries.
#endif
 */

/* File system parameters. */
static unsigned nr_dzones;	/* Fill these in after reading superblock. */
static unsigned nr_indirects;
static unsigned inodes_per_block;
#ifdef FLEX
#include <dirent.h>
#define direct _v7_direct
#else
#include <sys/dir.h>
#endif

static struct super_block super;	/* Superblock of file system */
static struct inode curfil;		/* Inode of file under examination */
static char indir[BLOCK_SIZE];		/* Single indirect block. */
static char dindir[BLOCK_SIZE];		/* Double indirect block. */
static char dirbuf[BLOCK_SIZE];		/* Scratch/Directory block. */
#define scratch dirbuf

static block_t a_indir, a_dindir;	/* Addresses of the indirects. */
static off_t dirpos;			/* Reading pos in a dir. */

#define fsbuf(b)	(* (struct buf *) (b))

#define	zone_shift	(super.s_log_zone_size)	/* zone to block ratio */

off_t r_super(void)
/* Initialize variables, return size of file system in blocks,
 * (zero on error).
 */
{
	/* Read superblock. */
	readblock(SUPER_BLOCK, scratch);

	memcpy(&super, scratch, sizeof(super));

	/* Is it really a MINIX file system ? */
	if (super.s_magic == SUPER_V2) {
		nr_dzones= V2_NR_DZONES;
		nr_indirects= V2_INDIRECTS;
		inodes_per_block= V2_INODES_PER_BLOCK;
		return (off_t) super.s_zones << zone_shift;
	} else
	if (super.s_magic == SUPER_MAGIC) {
		nr_dzones= V1_NR_DZONES;
		nr_indirects= V1_INDIRECTS;
		inodes_per_block= V1_INODES_PER_BLOCK;
		return (off_t) super.s_nzones << zone_shift;
	} else {
		/* Filesystem not recognized as Minix. */
		return 0;
	}
}

void r_stat(Ino_t inum, struct stat *stp)
/* Return information about a file like stat(2) and remember it. */
{
	block_t block;
	block_t ino_block;
	ino_t ino_offset;

	/* Calculate start of i-list */
	block = SUPER_BLOCK + 1 + super.s_imap_blocks + super.s_zmap_blocks;

	/* Calculate block with inode inum */
	ino_block = ((inum - 1) / inodes_per_block);
	ino_offset = ((inum - 1) % inodes_per_block);
	block += ino_block;

	/* Fetch the block */
	readblock(block, scratch);

	if (super.s_magic == SUPER_V2) {
		d2_inode *dip;
		int i;

		dip= &fsbuf(scratch).b_v2_ino[ino_offset];

		curfil.i_mode= dip->d2_mode;
		curfil.i_nlinks= dip->d2_nlinks;
		curfil.i_uid= dip->d2_uid;
		curfil.i_gid= dip->d2_gid;
		curfil.i_size= dip->d2_size;
		curfil.i_atime= dip->d2_atime;
		curfil.i_mtime= dip->d2_mtime;
		curfil.i_ctime= dip->d2_ctime;
		for (i= 0; i < V2_NR_TZONES; i++)
			curfil.i_zone[i]= dip->d2_zone[i];
	} else {
		d1_inode *dip;
		int i;

		dip= &fsbuf(scratch).b_v1_ino[ino_offset];

		curfil.i_mode= dip->d1_mode;
		curfil.i_nlinks= dip->d1_nlinks;
		curfil.i_uid= dip->d1_uid;
		curfil.i_gid= dip->d1_gid;
		curfil.i_size= dip->d1_size;
		curfil.i_atime= dip->d1_mtime;
		curfil.i_mtime= dip->d1_mtime;
		curfil.i_ctime= dip->d1_mtime;
		for (i= 0; i < V1_NR_TZONES; i++)
			curfil.i_zone[i]= dip->d1_zone[i];
	}
	curfil.i_dev= -1;	/* Can't fill this in alas. */
	curfil.i_num= inum;

	stp->st_dev= curfil.i_dev;
	stp->st_ino= curfil.i_num;
	stp->st_mode= curfil.i_mode;
	stp->st_nlink= curfil.i_nlinks;
	stp->st_uid= curfil.i_uid;
	stp->st_gid= curfil.i_gid;
	stp->st_rdev= (dev_t) curfil.i_zone[0];
	stp->st_size= curfil.i_size;
	stp->st_atime= curfil.i_atime;
	stp->st_mtime= curfil.i_mtime;
	stp->st_ctime= curfil.i_ctime;

	a_indir= a_dindir= 0;
	dirpos= 0;
}

ino_t r_readdir(char *name)
/* Read next directory entry at "dirpos" from file "curfil". */
{
	ino_t inum= 0;
	int blkpos;
	struct direct *dp;

	if (!S_ISDIR(curfil.i_mode)) { errno= ENOTDIR; return -1; }

	while (inum == 0 && dirpos < curfil.i_size) {
		if ((blkpos= (int) (dirpos % BLOCK_SIZE)) == 0) {
			/* Need to fetch a new directory block. */

			readblock(r_vir2abs(dirpos / BLOCK_SIZE), dirbuf);
		}
#ifdef FLEX
		if (super.s_flags & S_FLEX) {
			struct _fl_direct *dp;

			dp= (struct _fl_direct *) (dirbuf + blkpos);
			if ((inum= dp->d_ino) != 0) strcpy(name, dp->d_name);

			dirpos+= (1 + dp->d_extent) * FL_DIR_ENTRY_SIZE;
			continue;
		}
#endif
		/* Let dp point to the next entry. */
		dp= (struct direct *) (dirbuf + blkpos);

		if ((inum= dp->d_ino) != 0) {
			/* This entry is occupied, return name. */
			strncpy(name, dp->d_name, sizeof(dp->d_name));
			name[sizeof(dp->d_name)]= 0;
		}
		dirpos+= DIR_ENTRY_SIZE;
	}
	return inum;
}

off_t r_vir2abs(off_t virblk)
/* Translate a block number in a file to an absolute disk block number.
 * Returns 0 for a hole and -1 if block is past end of file.
 */
{
	block_t b= virblk;
	zone_t zone, ind_zone;
	block_t z, zone_index;
	int i;

	/* Check if virblk within file. */
	if (virblk * BLOCK_SIZE >= curfil.i_size) return -1;

	/* Calculate zone in which the datablock number is contained */
	zone = (zone_t) (b >> zone_shift);

	/* Calculate index of the block number in the zone */
	zone_index = b - ((block_t) zone << zone_shift);

	/* Go get the zone */
	if (zone < (zone_t) nr_dzones) {	/* direct block */
		zone = curfil.i_zone[(int) zone];
		z = ((block_t) zone << zone_shift) + zone_index;
		return z;
	}

	/* The zone is not a direct one */
	zone -= (zone_t) nr_dzones;

	/* Is it single indirect ? */
	if (zone < (zone_t) nr_indirects) {	/* single indirect block */
		ind_zone = curfil.i_zone[nr_dzones];
	} else {			/* double indirect block */
		/* Fetch the double indirect block */
		if ((ind_zone = curfil.i_zone[nr_dzones + 1]) == 0) return 0;

		z = (block_t) ind_zone << zone_shift;
		if (a_dindir != z) {
			readblock(z, dindir);
			a_dindir= z;
		}
		/* Extract the indirect zone number from it */
		zone -= (zone_t) nr_indirects;

		i = zone / (zone_t) nr_indirects;
		ind_zone = super.s_magic == SUPER_V2
				?  fsbuf(dindir).b_v2_ind[i]
				: fsbuf(dindir).b_v1_ind[i];
		zone %= (zone_t) nr_indirects;
	}
	if (ind_zone == 0) return 0;

	/* Extract the datablock number from the indirect zone */
	z = (block_t) ind_zone << zone_shift;
	if (a_indir != z) {
		readblock(z, indir);
		a_indir= z;
	}
	zone = super.s_magic == SUPER_V2
		? fsbuf(indir).b_v2_ind[(int) zone]
		: fsbuf(indir).b_v1_ind[(int) zone];

	/* Calculate absolute datablock number */
	z = ((block_t) zone << zone_shift) + zone_index;
	return z;
}

ino_t r_lookup(Ino_t cwd, char *path)
/* Translates a pathname to an inode number.  This is just a nice utility
 * function, it only needs r_stat and r_readdir.
 */
{
	char name[NAME_MAX+1], r_name[NAME_MAX+1];
	char *n;
	struct stat st;
	ino_t ino;

	ino= path[0] == '/' ? ROOT_INO : cwd;

	for (;;) {
		if (ino == 0) {
			errno= ENOENT;
			return 0;
		}

		while (*path == '/') path++;

		if (*path == 0) return ino;

		r_stat(ino, &st);

		if (!S_ISDIR(st.st_mode)) {
			errno= ENOTDIR;
			return 0;
		}

		n= name;
		while (*path != 0 && *path != '/')
			if (n < name + NAME_MAX) *n++ = *path++;
		*n= 0;

		while ((ino= r_readdir(r_name)) != 0
					&& strcmp(name, r_name) != 0) {}
	}
}
