/*
 *  linux/fs/affs/inode.c
 *
 *  (c) 1996  Hans-Joachim Widmaier - Rewritten
 *
 *  (C) 1993  Ray Burr - Modified for Amiga FFS filesystem.
 *
 *  (C) 1992  Eric Youngdale Modified for ISO9660 filesystem.
 *
 *  (C) 1991  Linus Torvalds - minix filesystem
 */

#define DEBUG 0
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/malloc.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 <linux/errno.h>
#include <linux/genhd.h>
#include <linux/amigaffs.h>
#include <linux/major.h>
#include <linux/blkdev.h>
#include <linux/init.h>
#include <asm/system.h>
#include <asm/uaccess.h>

extern int *blk_size[];
extern struct timezone sys_tz;

#define MIN(a,b) (((a)<(b))?(a):(b))

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

	if (!S_ISDIR(dir->i_mode)) {
		affs_error(dir->i_sb,"parent_ino","Trying to get parent of non-directory");
		return root_ino;
	}
	if (dir->i_ino == root_ino)
		return root_ino;
	return dir->u.affs_i.i_parent;
}

void
affs_read_inode(struct inode *inode)
{
	struct buffer_head	*bh;
	struct file_front	*file_front;
	struct file_end		*file_end;
	s32			 block;
	unsigned long		 prot;
	s32			 ptype, stype;
	unsigned short		 id;

	pr_debug("AFFS: read_inode(%lu)\n",inode->i_ino);

	block = inode->i_ino;
	if (!(bh = affs_bread(inode->i_dev,block,AFFS_I2BSIZE(inode)))) {
		affs_error(inode->i_sb,"read_inode","Cannot read block %d",block);
		return;
	}
	if (affs_checksum_block(AFFS_I2BSIZE(inode),bh->b_data,&ptype,&stype) || ptype != T_SHORT) {
		affs_error(inode->i_sb,"read_inode",
			   "Checksum or type (ptype=%d) error on inode %d",ptype,block);
		affs_brelse(bh);
		return;
	}

	file_front = (struct file_front *)bh->b_data;
	file_end   = GET_END_PTR(struct file_end, bh->b_data,AFFS_I2BSIZE(inode));
	prot       = (be32_to_cpu(file_end->protect) & ~0x10) ^ FIBF_OWNER;

	inode->u.affs_i.i_protect      = prot;
	inode->u.affs_i.i_parent       = be32_to_cpu(file_end->parent);
	inode->u.affs_i.i_original     = 0;
	inode->u.affs_i.i_zone         = 0;
	inode->u.affs_i.i_hlink        = 0;
	inode->u.affs_i.i_pa_cnt       = 0;
	inode->u.affs_i.i_pa_next      = 0;
	inode->u.affs_i.i_pa_last      = 0;
	inode->u.affs_i.i_ec           = NULL;
	inode->u.affs_i.i_lastblock    = -1;
	inode->i_nlink                 = 1;
	inode->i_mode                  = 0;

	if (inode->i_sb->u.affs_sb.s_flags & SF_SETMODE)
		inode->i_mode = inode->i_sb->u.affs_sb.s_mode;
	else
		inode->i_mode = prot_to_mode(prot);

	if (inode->i_sb->u.affs_sb.s_flags & SF_SETUID)
		inode->i_uid = inode->i_sb->u.affs_sb.s_uid;
	id = be16_to_cpu(file_end->owner_uid);
	if (id == 0 || inode->i_sb->u.affs_sb.s_flags & SF_SETUID)
		inode->i_uid = inode->i_sb->u.affs_sb.s_uid;
	else if (id == 0xFFFF && inode->i_sb->u.affs_sb.s_flags & SF_MUFS)
		inode->i_uid = 0;
	else 
		inode->i_uid = id;

	id = be16_to_cpu(file_end->owner_gid);
	if (id == 0 || inode->i_sb->u.affs_sb.s_flags & SF_SETGID)
		inode->i_gid = inode->i_sb->u.affs_sb.s_gid;
	else if (id == 0xFFFF && inode->i_sb->u.affs_sb.s_flags & SF_MUFS)
		inode->i_gid = 0;
	else
		inode->i_gid = id;

	switch (be32_to_cpu(file_end->secondary_type)) {
		case ST_ROOT:
			inode->i_uid   = inode->i_sb->u.affs_sb.s_uid;
			inode->i_gid   = inode->i_sb->u.affs_sb.s_gid;
		case ST_USERDIR:
			if (be32_to_cpu(file_end->secondary_type) == ST_USERDIR ||
			    inode->i_sb->u.affs_sb.s_flags & SF_SETMODE) {
				if (inode->i_mode & S_IRUSR)
					inode->i_mode |= S_IXUSR;
				if (inode->i_mode & S_IRGRP)
					inode->i_mode |= S_IXGRP;
				if (inode->i_mode & S_IROTH)
					inode->i_mode |= S_IXOTH;
				inode->i_mode |= S_IFDIR;
			} else
				inode->i_mode = S_IRUGO | S_IXUGO | S_IWUSR | S_IFDIR;
			inode->i_size  = 0;
			break;
		case ST_LINKDIR:
			affs_error(inode->i_sb,"read_inode","inode is LINKDIR");
			affs_brelse(bh);
			return;
		case ST_LINKFILE:
			affs_error(inode->i_sb,"read_inode","inode is LINKFILE");
			affs_brelse(bh);
			return;
		case ST_FILE:
			inode->i_mode |= S_IFREG;
			inode->i_size  = be32_to_cpu(file_end->byte_size);
			if (inode->i_sb->u.affs_sb.s_flags & SF_OFS)
				block = AFFS_I2BSIZE(inode) - 24;
			else
				block = AFFS_I2BSIZE(inode);
			inode->u.affs_i.i_lastblock = ((inode->i_size + block - 1) / block) - 1;
			break;
		case ST_SOFTLINK:
			inode->i_mode |= S_IFLNK;
			inode->i_size  = 0;
			break;
	}

	inode->i_mtime = inode->i_atime = inode->i_ctime
		       = (be32_to_cpu(file_end->created.ds_Days) * (24 * 60 * 60) +
		         be32_to_cpu(file_end->created.ds_Minute) * 60 +
			 be32_to_cpu(file_end->created.ds_Tick) / 50 +
			 ((8 * 365 + 2) * 24 * 60 * 60)) +
			 sys_tz.tz_minuteswest * 60;
	affs_brelse(bh);

	inode->i_op = NULL;
	if (S_ISREG(inode->i_mode)) {
		if (inode->i_sb->u.affs_sb.s_flags & SF_OFS) {
			inode->i_op = &affs_file_inode_operations_ofs;
		} else {
			inode->i_op = &affs_file_inode_operations;
		}
	} else if (S_ISDIR(inode->i_mode)) {
		/* Maybe it should be controlled by mount parameter? */
		inode->i_mode |= S_ISVTX;
		inode->i_op = &affs_dir_inode_operations;
	}
	else if (S_ISLNK(inode->i_mode))
		inode->i_op = &affs_symlink_inode_operations;
}

void
affs_write_inode(struct inode *inode)
{
	struct buffer_head	*bh;
	struct file_end		*file_end;
	uid_t			 uid;
	gid_t			 gid;

	pr_debug("AFFS: write_inode(%lu)\n",inode->i_ino);

	if (!inode->i_nlink)
		return;
	if (!(bh = bread(inode->i_dev,inode->i_ino,AFFS_I2BSIZE(inode)))) {
		affs_error(inode->i_sb,"write_inode","Cannot read block %lu",inode->i_ino);
		return;
	}
	file_end = GET_END_PTR(struct file_end, bh->b_data,AFFS_I2BSIZE(inode));
	if (file_end->secondary_type == be32_to_cpu(ST_ROOT)) {
		secs_to_datestamp(inode->i_mtime,&ROOT_END(bh->b_data,inode)->disk_altered);
	} else {
		file_end->protect   = cpu_to_be32(inode->u.affs_i.i_protect ^ FIBF_OWNER);
		file_end->byte_size = cpu_to_be32(inode->i_size);
		secs_to_datestamp(inode->i_mtime,&file_end->created);
		if (!(inode->i_ino == inode->i_sb->u.affs_sb.s_root_block)) {
			uid = inode->i_uid;
			gid = inode->i_gid;
			if (inode->i_sb->u.affs_sb.s_flags & SF_MUFS) {
				if (inode->i_uid == 0 || inode->i_uid == 0xFFFF)
					uid = inode->i_uid ^ ~0;
				if (inode->i_gid == 0 || inode->i_gid == 0xFFFF)
					gid = inode->i_gid ^ ~0;
			}
			if (!(inode->i_sb->u.affs_sb.s_flags & SF_SETUID))
				file_end->owner_uid = cpu_to_be16(uid);
			if (!(inode->i_sb->u.affs_sb.s_flags & SF_SETGID))
				file_end->owner_gid = cpu_to_be16(gid);
		}
	}
	affs_fix_checksum(AFFS_I2BSIZE(inode),bh->b_data,5);
	mark_buffer_dirty(bh,1);
	brelse(bh);
}

int
affs_notify_change(struct dentry *dentry, struct iattr *attr)
{
	struct inode *inode = dentry->d_inode;
	int error;

	pr_debug("AFFS: notify_change(%lu,0x%x)\n",inode->i_ino,attr->ia_valid);

	error = inode_change_ok(inode,attr);
	if (error)
		goto out;

	if (((attr->ia_valid & ATTR_UID) && (inode->i_sb->u.affs_sb.s_flags & SF_SETUID)) ||
	    ((attr->ia_valid & ATTR_GID) && (inode->i_sb->u.affs_sb.s_flags & SF_SETGID)) ||
	    ((attr->ia_valid & ATTR_MODE) &&
	     (inode->i_sb->u.affs_sb.s_flags & (SF_SETMODE | SF_IMMUTABLE)))) {
		if (!(inode->i_sb->u.affs_sb.s_flags & SF_QUIET))
			error = -EPERM;
		goto out;
	}

	if (attr->ia_valid & ATTR_MODE)
		inode->u.affs_i.i_protect = mode_to_prot(attr->ia_mode);

	inode_setattr(inode, attr);
	mark_inode_dirty(inode);
	error = 0;
out:
	return error;
}

void
affs_put_inode(struct inode *inode)
{
	pr_debug("AFFS: put_inode(ino=%lu, nlink=%u)\n",
		inode->i_ino,inode->i_nlink);

	affs_free_prealloc(inode);
	if (inode->i_count == 1) {
		unsigned long cache_page = (unsigned long) inode->u.affs_i.i_ec;
		if (cache_page) {
			pr_debug("AFFS: freeing ext cache\n");
			inode->u.affs_i.i_ec = NULL;
			free_page(cache_page);
		}
	}
}

void
affs_delete_inode(struct inode *inode)
{
	pr_debug("AFFS: delete_inode(ino=%lu, nlink=%u)\n",inode->i_ino,inode->i_nlink);
	inode->i_size = 0;
	if (S_ISREG(inode->i_mode) && !inode->u.affs_i.i_hlink)
		affs_truncate(inode);
	affs_free_block(inode->i_sb,inode->i_ino);
	clear_inode(inode);
}

struct inode *
affs_new_inode(const struct inode *dir)
{
	struct inode		*inode;
	struct super_block	*sb;
	s32			 block;

	if (!dir || !(inode = get_empty_inode()))
		return NULL;

	sb = dir->i_sb;
	inode->i_sb    = sb;
	inode->i_flags = sb->s_flags;

	if (!(block = affs_new_header((struct inode *)dir))) {
		iput(inode);
		return NULL;
	}

	inode->i_count   = 1;
	inode->i_nlink   = 1;
	inode->i_dev     = sb->s_dev;
	inode->i_uid     = current->fsuid;
	inode->i_gid     = current->fsgid;
	inode->i_ino     = block;
	inode->i_op      = NULL;
	inode->i_blocks  = 0;
	inode->i_size    = 0;
	inode->i_mode    = 0;
	inode->i_blksize = 0;
	inode->i_mtime   = inode->i_atime = inode->i_ctime = CURRENT_TIME;

	inode->u.affs_i.i_original  = 0;
	inode->u.affs_i.i_parent    = dir->i_ino;
	inode->u.affs_i.i_zone      = 0;
	inode->u.affs_i.i_hlink     = 0;
	inode->u.affs_i.i_pa_cnt    = 0;
	inode->u.affs_i.i_pa_next   = 0;
	inode->u.affs_i.i_pa_last   = 0;
	inode->u.affs_i.i_ec        = NULL;
	inode->u.affs_i.i_lastblock = -1;

	insert_inode_hash(inode);
	mark_inode_dirty(inode);

	return inode;
}

/*
 * Add an entry to a directory. Create the header block
 * and insert it into the hash table.
 */

int
affs_add_entry(struct inode *dir, struct inode *link, struct inode *inode,
	       struct dentry *dentry, int type)
{
	struct buffer_head	*dir_bh;
	struct buffer_head	*inode_bh;
	struct buffer_head	*link_bh;
	int			 retval;
	const unsigned char	*name = dentry->d_name.name;
	int			 len  = dentry->d_name.len;

	pr_debug("AFFS: add_entry(dir=%lu,inode=%lu,\"%*s\",type=%d)\n",dir->i_ino,inode->i_ino,
		 len,name,type);

	if ((retval = affs_check_name(name,len)))
		return retval;
	if (len > 30)
		len = 30;

	dir_bh   = affs_bread(dir->i_dev,dir->i_ino,AFFS_I2BSIZE(dir));
	inode_bh = affs_bread(inode->i_dev,inode->i_ino,AFFS_I2BSIZE(inode));
	link_bh  = NULL;
	retval   = -EIO;
	if (!dir_bh || !inode_bh)
		goto addentry_done;
	if (link) {
		link_bh = affs_bread(link->i_dev,link->i_ino,AFFS_I2BSIZE(link));
		if (!link_bh)
			goto addentry_done;
	}
	((struct dir_front *)inode_bh->b_data)->primary_type = cpu_to_be32(T_SHORT);
	((struct dir_front *)inode_bh->b_data)->own_key      = cpu_to_be32(inode->i_ino);
	DIR_END(inode_bh->b_data,inode)->dir_name[0]         = len;
	strncpy(DIR_END(inode_bh->b_data,inode)->dir_name + 1,name,len);
	DIR_END(inode_bh->b_data,inode)->secondary_type = cpu_to_be32(type);
	DIR_END(inode_bh->b_data,inode)->parent         = cpu_to_be32(dir->i_ino);

	lock_super(inode->i_sb);
	retval = affs_insert_hash(dir->i_ino,inode_bh,dir);

	if (link_bh) {
		LINK_END(inode_bh->b_data,inode)->original   = cpu_to_be32(link->i_ino);
		LINK_END(inode_bh->b_data,inode)->link_chain =
						FILE_END(link_bh->b_data,link)->link_chain;
		FILE_END(link_bh->b_data,link)->link_chain   = cpu_to_be32(inode->i_ino);
		affs_fix_checksum(AFFS_I2BSIZE(link),link_bh->b_data,5);
		link->i_version = ++event;
		mark_inode_dirty(link);
		mark_buffer_dirty(link_bh,1);
	}
	affs_fix_checksum(AFFS_I2BSIZE(inode),inode_bh->b_data,5);
	affs_fix_checksum(AFFS_I2BSIZE(dir),dir_bh->b_data,5);
	dir->i_version = ++event;
	dir->i_mtime   = dir->i_atime = dir->i_ctime = CURRENT_TIME;
	unlock_super(inode->i_sb);

	mark_inode_dirty(dir);
	mark_inode_dirty(inode);
	mark_buffer_dirty(dir_bh,1);
	mark_buffer_dirty(inode_bh,1);

addentry_done:
	affs_brelse(dir_bh);
	affs_brelse(inode_bh);
	affs_brelse(link_bh);

	return retval;
}
