/*
 *  linux/fs/affs/inode.c
 *
 *  (C) 1993  Ray Burr - Modified for Amiga FFS filesystem.
 *
 *  (C) 1992  Eric Youngdale Modified for ISO9660 filesystem.
 *
 *  (C) 1991  Linus Torvalds - minix filesystem
 */
#include <linux/config.h>
#include <linux/stat.h>
#include <linux/sched.h>
#include <linux/affs_fs.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/locks.h>

#include <asm/system.h>
#include <asm/segment.h>
#include <linux/errno.h>

#include <linux/genhd.h>

#include "amigaffs.h"

extern int check_cdrom_media_change(int, int);

#ifdef LEAK_CHECK
static int check_malloc = 0;
static int check_bread = 0;
#endif

void affs_put_super(struct super_block *sb)
{
	lock_super(sb);

#ifdef LEAK_CHECK
	printk("Outstanding mallocs:%d, outstanding buffers: %d\n",
	       check_malloc, check_bread);
#endif
	sb->s_dev = 0;
	unlock_super(sb);
	return;
}

static struct super_operations affs_sops = {
	affs_read_inode,
	NULL,			/* notify_change */
	NULL,			/* write_inode */
	NULL,			/* put_inode */
	affs_put_super,
	NULL,			/* write_super */
	affs_statfs
};

int affs_parent_ino(struct inode *dir)
{
	int root_ino = (dir->i_sb->u.affs_sb.s_root_block
			- dir->i_sb->u.affs_sb.s_partition_offset);

	if (!S_ISDIR (dir->i_mode)) {
		printk ("affs_parent_ino: argument is not a directory\n");
		return root_ino;
	}
	if (dir->i_ino == root_ino)
		return root_ino;
	return dir->u.affs_i.i_parent;
}

static int parse_options(char *options, struct affs_options *optp)
{
	char *this_opt,*value,*end;
	int n;

	optp->offset = 0;
	optp->size = 0;
	optp->root = 0;

	if (!options)
		return 1;
	for (this_opt = strtok(options,","); this_opt; this_opt = strtok(NULL,",")) {
		if ((value = strchr(this_opt,'='))) *value++ = 0;
		if (!strcmp(this_opt,"offset") && value) {
			n = simple_strtoul (value, &end, 10);
			if (end == value || *end != 0)
				return 0;
			optp->offset = n;
		}
		else if (!strcmp(this_opt,"size") && value) {
			n = simple_strtoul (value, &end, 10);
			if (end == value || *end != 0 || n <= 0)
				return 0;
			optp->size = n;
		}
		else if (!strcmp(this_opt,"root") && value) {
			n = simple_strtoul (value, &end, 10);
			if (end == value || *end != 0 || n <= 0)
				return 0;
			optp->root = n;
		}
		else return 0;
	}
	return 1;
}

/* Is this The Right Way?  Should I be locking something? */

static int get_device_size (dev_t dev)
{
	struct gendisk *gd_p;
	int dev_size = 0;

	for (gd_p = gendisk_head ; gd_p ; gd_p=gd_p->next) {
		if (gd_p->major != MAJOR(dev))
			continue;
		dev_size = gd_p->sizes[MINOR(dev)];
		break;
	}
	/* FIXME: Assumes 512 byte FFS blocks *and* 1024 byte dev blocks. */
	return dev_size * 2;
}

struct super_block *affs_read_super(struct super_block *s,void *data,
				    int silent)
{
	struct buffer_head *bh;
	int dev = s->s_dev;
	int root_block;
	int ptype, stype;
	void *root_data;
	struct affs_options *optp;

	optp = &s->u.affs_sb.s_options;
	if (!parse_options((char *) data, optp)) {
		s->s_dev = 0;
		printk ("AFFS: bad mount options\n");
		return NULL;
	}

	lock_super(s);

	root_block = 0;
	if (optp->size) {
		s->u.affs_sb.s_partition_size = optp->size;
	}
	else {
		int size = get_device_size (dev);
		if (size == 0) {
			s->s_dev = 0;
			unlock_super(s);
			printk ("affs_read_super: could not"
				"determine device size\n");
		}
		s->u.affs_sb.s_partition_size = size;
	}

	s->u.affs_sb.s_partition_offset = optp->offset;
	root_block = optp->root;

	if (!root_block)
		root_block = (s->u.affs_sb.s_partition_offset
			      + s->u.affs_sb.s_partition_size / 2);
	s->u.affs_sb.s_root_block = root_block;

	s->u.affs_sb.s_block_size = AFFS_BLOCK_SIZE;

#if 1
	printk ("affs_read_super: dev=0x%04x offset=%d "
		"size=%d root=%d blocksize=%d\n",
		dev,
		s->u.affs_sb.s_partition_offset,
		s->u.affs_sb.s_partition_size,
		s->u.affs_sb.s_root_block,
		s->u.affs_sb.s_block_size);
#endif

	bh = affs_sread (dev, root_block, &root_data);
	if (!bh) {
		s->s_dev = 0;
		unlock_super(s);
		printk ("AFFS: unable to read superblock\n");
		return NULL;
	}

	if (affs_checksum_block (AFFS_BLOCK_SIZE, root_data, &ptype, &stype)
	    || ptype != T_SHORT || stype != ST_ROOT) {
		printk ("AFFS: invalid root block %d on device 0x%04x\n",
			root_block, dev);
		goto out;
	}

#if 1
{
	char *name;
	int len;
	char buf[33];
	len = affs_get_file_name (AFFS_BLOCK_SIZE, root_data, &name);
	memcpy (buf,name,len);
	buf[len] = 0;
	printk ("affs_read_super: volume name \"%s\"\n", buf);
}
#endif

	s->s_magic = AFFS_SUPER_MAGIC;

	s->s_flags = MS_RDONLY | MS_NODEV | MS_NOSUID;

	brelse(bh);

	/* set up enough so that it can read an inode */
	s->s_dev = dev;
	s->s_op = &affs_sops;
	s->s_blocksize = AFFS_BUFFER_SIZE;
	s->s_mounted = iget (s, root_block - s->u.affs_sb.s_partition_offset);

	unlock_super(s);

	if (!(s->s_mounted)) {
		s->s_dev = 0;
		printk("AFFS: get root inode failed\n");
		return NULL;
	}
	return s;

 out: /* Kick out for various error conditions */
	brelse (bh);
	s->s_dev = 0;
	unlock_super(s);
	return NULL;
}

void affs_statfs (struct super_block *sb, struct statfs *buf)
{
#ifdef DEBUG
	printk ("AFFS: affs_statfs called\n");
#endif
	put_fs_long(AFFS_SUPER_MAGIC, &buf->f_type);
	put_fs_long(sb->u.affs_sb.s_block_size, &buf->f_bsize);
	put_fs_long(sb->u.affs_sb.s_partition_size, &buf->f_blocks);
	put_fs_long(0, &buf->f_bfree);
	put_fs_long(0, &buf->f_bavail);
	put_fs_long(0, &buf->f_files);
	put_fs_long(0, &buf->f_ffree);
	/* Don't know what value to put in buf->f_fsid */
}

void affs_read_inode(struct inode * inode)
{
	struct buffer_head *bh;
	int block;
	void *fh_data;
	struct file_front *file_front;
	struct file_end *file_end;

#ifdef DEBUG
	printk ("AFFS: entering affs_read_inode\n");
#endif

	block = inode->i_ino;
	if (!(bh=affs_pread (inode, block, &fh_data))) {
		printk("AFFS: unable to read i-node block %d\n", block);
		return;
	}

	file_front = (struct file_front *) fh_data;
	file_end = GET_END_PTR (struct file_end, fh_data,
				AFFS_I2BSIZE (inode));

	inode->u.affs_i.i_protect = file_end->protect;
	inode->u.affs_i.i_parent = swap_long (file_end->parent);

	inode->i_mode = 0444; /* Everybody gets to read the file. */
	if (!(inode->u.affs_i.i_protect & PROT_WRITE))
		inode->i_mode |= 0222;
	if (!(inode->u.affs_i.i_protect & PROT_EXECUTE))
		inode->i_mode |= 0111;
	inode->i_nlink = 1;

	if (file_end->secondary_type != swap_long ((ulong)ST_FILE)) {
		inode->i_mode |= 0111 | S_IFDIR;
		inode->i_nlink = 2; /* There are always at least 2.  It is
				       hard to figure out what is correct*/
		inode->i_size = 0;
	} else {
		inode->i_nlink = 1;
		inode->i_mode |= S_IFREG;
		inode->i_size = swap_long (file_end->byte_size);
	};
	inode->i_uid = 0;
	inode->i_gid = 0;

#ifdef DEBUG
	printk ("AFFS: read inode %d: size=%d\n", block, inode->i_size);
#endif
	inode->i_mtime = inode->i_atime = inode->i_ctime
		= (swap_long (file_end->created.ds_Days) * (24 * 60 * 60)
		   + swap_long (file_end->created.ds_Minute) * 60
		   + swap_long (file_end->created.ds_Tick) / 50
		   + ((8 * 365 + 2) * 24 * 60 * 60));

	brelse(bh);

	inode->i_op = NULL;
	if (S_ISREG(inode->i_mode))
		inode->i_op = &affs_file_inode_operations;
	else if (S_ISDIR(inode->i_mode))
		inode->i_op = &affs_dir_inode_operations;
}


#ifdef LEAK_CHECK
#undef malloc
#undef free_s
#undef bread
#undef brelse

void * leak_check_malloc(unsigned int size){
  void * tmp;
  check_malloc++;
  tmp = kmalloc(size, GFP_ATOMIC);
  return tmp;
}

void leak_check_free_s(void * obj, int size){
  check_malloc--;
  return kfree_s(obj, size);
}

struct buffer_head * leak_check_bread(int dev, int block, int size){
  check_bread++;
  return bread(dev, block, size);
}

void leak_check_brelse(struct buffer_head * bh){
  check_bread--;
  return brelse(bh);
}

#endif
