/* 2007-07-20: File changed by Sony Corporation */
/*
 *  linux/fs/fat/file.c
 *
 *  Written 1992,1993 by Werner Almesberger
 *
 *  regular file handling primitives for fat-based filesystems
 */

#include <linux/capability.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/msdos_fs.h>
#include <linux/smp_lock.h>
#include <linux/buffer_head.h>
#include <linux/writeback.h>
#include <linux/backing-dev.h>
#include <linux/blkdev.h>

int fat_generic_ioctl(struct inode *inode, struct file *filp,
		      unsigned int cmd, unsigned long arg)
{
	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
	u32 __user *user_attr = (u32 __user *)arg;

	switch (cmd) {
#ifdef CONFIG_SNSC_FS_FAT_BATCH_SYNC
	case FAT_IOCTL_CLOSE_NOSYNC:
	{
		filp->f_op = &fat_file_operations_nosync;
		return 0;
	}
#endif
	case FAT_IOCTL_GET_ATTRIBUTES:
	{
		u32 attr;

		if (inode->i_ino == MSDOS_ROOT_INO)
			attr = ATTR_DIR;
		else
			attr = fat_attr(inode);

		return put_user(attr, user_attr);
	}
	case FAT_IOCTL_SET_ATTRIBUTES:
	{
		u32 attr, oldattr;
		int err, is_dir = S_ISDIR(inode->i_mode);
		struct iattr ia;

		err = get_user(attr, user_attr);
		if (err)
			return err;

		mutex_lock(&inode->i_mutex);

		if (IS_RDONLY(inode)) {
			err = -EROFS;
			goto up;
		}

		/*
		 * ATTR_VOLUME and ATTR_DIR cannot be changed; this also
		 * prevents the user from turning us into a VFAT
		 * longname entry.  Also, we obviously can't set
		 * any of the NTFS attributes in the high 24 bits.
		 */
		attr &= 0xff & ~(ATTR_VOLUME | ATTR_DIR);
		/* Merge in ATTR_VOLUME and ATTR_DIR */
		attr |= (MSDOS_I(inode)->i_attrs & ATTR_VOLUME) |
			(is_dir ? ATTR_DIR : 0);
		oldattr = fat_attr(inode);

		/* Equivalent to a chmod() */
		ia.ia_valid = ATTR_MODE | ATTR_CTIME;
		if (is_dir) {
			ia.ia_mode = MSDOS_MKMODE(attr,
				S_IRWXUGO & ~sbi->options.fs_dmask)
				| S_IFDIR;
		} else {
			ia.ia_mode = MSDOS_MKMODE(attr,
				(S_IRUGO | S_IWUGO | (inode->i_mode & S_IXUGO))
				& ~sbi->options.fs_fmask)
				| S_IFREG;
		}

		/* The root directory has no attributes */
		if (inode->i_ino == MSDOS_ROOT_INO && attr != ATTR_DIR) {
			err = -EINVAL;
			goto up;
		}

		if (sbi->options.sys_immutable) {
			if ((attr | oldattr) & ATTR_SYS) {
				if (!capable(CAP_LINUX_IMMUTABLE)) {
					err = -EPERM;
					goto up;
				}
			}
		}

		/* This MUST be done before doing anything irreversible... */
		err = notify_change(filp->f_path.dentry, &ia);
		if (err)
			goto up;

		if (sbi->options.sys_immutable) {
			if (attr & ATTR_SYS)
				inode->i_flags |= S_IMMUTABLE;
			else
				inode->i_flags &= S_IMMUTABLE;
		}

		MSDOS_I(inode)->i_attrs = attr & ATTR_UNUSED;
		mark_inode_dirty(inode);
	up:
		mutex_unlock(&inode->i_mutex);
		return err;
	}
	default:
		return -ENOTTY;	/* Inappropriate ioctl for device */
	}
}

#ifdef CONFIG_SNSC_FS_FAT_BATCH_SYNC
static int fat_clean_inode(struct inode *inode)
{
	int ret = 0;

	if (!(inode->i_state & I_DIRTY))
		return 0;

	ret = fat_sync_inode(inode);
	if (!ret) {
		pr_debug("Sync inode ok, make it clean!\n");
		mark_inode_clean(inode);
	}

	return ret;
}
#endif

static int fat_file_release(struct inode *inode, struct file *filp)
{
#ifdef CONFIG_SNSC_FS_FAT_BATCH_SYNC
	struct msdos_sb_info *sbi = MSDOS_SB(filp->f_mapping->host->i_sb);
	int ret = 0;
#endif

	if ((filp->f_mode & FMODE_WRITE) &&
	     MSDOS_SB(inode->i_sb)->options.flush) {
		fat_flush_inodes(inode->i_sb, inode, NULL);
		congestion_wait(WRITE, HZ/10);
	}
#ifdef CONFIG_SNSC_FS_FAT_BATCH_SYNC
	if (sbi->options.batch_sync) {
		mutex_lock(&inode->i_mutex);
		ret = write_inode_now(inode, 1);
		mutex_unlock(&inode->i_mutex);
	}
	return ret;
#else
	return 0;
#endif
}

#ifdef CONFIG_SNSC_FS_FAT_BATCH_SYNC
static int fat_file_release_nosync(struct inode *inode, struct file *filp)
{
	if ((filp->f_mode & FMODE_WRITE) &&
	     MSDOS_SB(inode->i_sb)->options.flush) {
		fat_flush_inodes(inode->i_sb, inode, NULL);
		congestion_wait(WRITE, HZ/10);
	}
	return 0;
}
#endif

#ifdef CONFIG_SNSC_FS_FAT_BATCH_SYNC
static ssize_t fat_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
	struct inode *inode = filp->f_mapping->host;
	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
	ssize_t ret = do_sync_write(filp, buf, len, ppos);

	if (sbi->options.batch_sync && ret > 0) {
		filemap_flush(filp->f_mapping);
	}

	return ret;
}
#endif

#ifdef CONFIG_SNSC_FS_FAT_BATCH_SYNC
static int fat_fsync(struct file *filp, struct dentry *dentry, int datasync)
{
	struct inode *inode = filp->f_mapping->host;
	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);

	if (sbi->options.batch_sync)
		return fat_clean_inode(inode);
	else
		return file_fsync(filp, dentry, datasync);
}
#endif

const struct file_operations fat_file_operations = {
	.llseek		= generic_file_llseek,
	.read		= do_sync_read,
#ifdef CONFIG_SNSC_FS_FAT_BATCH_SYNC
	.write		= fat_sync_write,
#else
	.write		= do_sync_write,
#endif
	.aio_read	= generic_file_aio_read,
	.aio_write	= generic_file_aio_write,
	.mmap		= generic_file_mmap,
#ifdef CONFIG_SNSC_FS_FAT_BATCH_SYNC
	.open		= generic_file_open,
#endif
	.release	= fat_file_release,
	.ioctl		= fat_generic_ioctl,
#ifdef CONFIG_SNSC_FS_FAT_BATCH_SYNC
	.fsync		= fat_fsync,
#else
	.fsync		= file_fsync,
#endif
	.sendfile	= generic_file_sendfile,
};

#ifdef CONFIG_SNSC_FS_FAT_BATCH_SYNC
const struct file_operations fat_file_operations_nosync = {
	.llseek		= generic_file_llseek,
	.read		= do_sync_read,
	.write		= fat_sync_write,
	.aio_read	= generic_file_aio_read,
  	.aio_write	= generic_file_aio_write,
	.mmap		= generic_file_mmap,
	.open           = generic_file_open,
	.release	= fat_file_release_nosync,
	.ioctl		= fat_generic_ioctl,
	.fsync		= fat_fsync,
	.sendfile	= generic_file_sendfile,
};
#endif

static int fat_cont_expand(struct inode *inode, loff_t size)
{
	struct address_space *mapping = inode->i_mapping;
	loff_t start = inode->i_size, count = size - inode->i_size;
	int err;

	err = generic_cont_expand_simple(inode, size);
	if (err)
		goto out;

	inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC;
	mark_inode_dirty(inode);
	if (IS_SYNC(inode))
		err = sync_page_range_nolock(inode, mapping, start, count);
out:
	return err;
}

int fat_notify_change(struct dentry *dentry, struct iattr *attr)
{
	struct msdos_sb_info *sbi = MSDOS_SB(dentry->d_sb);
	struct inode *inode = dentry->d_inode;
	int mask, error = 0;
#ifdef CONFIG_SNSC_FS_VFAT_POSIX_ATTR
	int px_uid = sbi->options.fs_uid ? sbi->options.fs_uid : (u16)-1;
	int px_gid = sbi->options.fs_gid ? sbi->options.fs_gid : (u16)-1;
 	int need_size_change = 0;
#endif

	lock_kernel();

	/*
	 * Expand the file. Since inode_setattr() updates ->i_size
	 * before calling the ->truncate(), but FAT needs to fill the
	 * hole before it.
	 */
	if (attr->ia_valid & ATTR_SIZE) {
		if (attr->ia_size > inode->i_size) {
			error = fat_cont_expand(inode, attr->ia_size);
			if (error || attr->ia_valid == ATTR_SIZE)
				goto out;
			attr->ia_valid &= ~ATTR_SIZE;
		}
	}

#ifdef CONFIG_SNSC_FS_VFAT_POSIX_ATTR
	if (sbi->options.posix_attr) {
		if (attr->ia_valid & ATTR_UID) {
			/* root can change uid to any value */
			if (capable(CAP_CHOWN) &&
			    (attr->ia_uid && attr->ia_uid != px_uid))
				attr->ia_uid = px_uid;
			/* change-uid affects gid */
			attr->ia_valid |= ATTR_GID;
			attr->ia_gid = attr->ia_uid ? px_gid : 0;
		} else {
			/* chown syscall sets both uid and gid */
			if (attr->ia_valid & ATTR_GID) {
				/* root can change gid to any value */
				if (capable(CAP_CHOWN) &&
				    (attr->ia_gid && attr->ia_gid != px_gid))
					attr->ia_gid = px_gid;
				/* change-gid affects uid */
				attr->ia_valid |= ATTR_UID;
				attr->ia_uid = attr->ia_gid ? px_uid : 0;
			}
		}
		/* change-group-mode affects on others-mode */
		if (attr->ia_valid & ATTR_MODE) {
			int others_mode = (attr->ia_mode & S_IRWXG) >> 3;
			attr->ia_mode &=  ~S_IRWXO;
			attr->ia_mode |=  others_mode;
		}
	}
#endif
	error = inode_change_ok(inode, attr);
	if (error) {
#ifdef CONFIG_SNSC_FS_VFAT_POSIX_ATTR
		goto error_out;
#else
		if (sbi->options.quiet)
			error = 0;
		goto out;
#endif
	}
#ifdef CONFIG_SNSC_FS_VFAT_POSIX_ATTR
	if (sbi->options.posix_attr) {
		if (((attr->ia_valid & ATTR_UID) &&
		       ((attr->ia_uid) && (attr->ia_uid != px_uid))) ||
		    ((attr->ia_valid & ATTR_GID) &&
		       ((attr->ia_gid) && (attr->ia_gid != px_gid))) ||
		    ((attr->ia_valid & ATTR_MODE) &&
		     (attr->ia_mode & ~VFAT_POSIX_ATTR_VALID_MODE)))
			error = -EPERM;
	}
	else
#endif
	if (((attr->ia_valid & ATTR_UID) &&
	     (attr->ia_uid != sbi->options.fs_uid)) ||
	    ((attr->ia_valid & ATTR_GID) &&
	     (attr->ia_gid != sbi->options.fs_gid)) ||
	    ((attr->ia_valid & ATTR_MODE) &&
	     (attr->ia_mode & ~MSDOS_VALID_MODE)))
		error = -EPERM;

#ifdef CONFIG_SNSC_FS_VFAT_POSIX_ATTR
	if (error)
		goto error_out;

	if (attr->ia_valid & ATTR_SIZE) {
		/*
		 * Update i_size here, not in inode_setattr(),
		 * because inode_setattr() calls vmtruncate() and
		 * fat_truncate() consequently. This may release
		 * cluster chain, before updating size field of
		 * the dir entry on disk.
		 */
		if (IS_SWAPFILE(inode)){
			error = -ETXTBSY;
			goto out;
		}
		attr->ia_valid &= ~(ATTR_SIZE);
		if (attr->ia_size == inode->i_size) {
			attr->ia_valid |= ATTR_MTIME|ATTR_CTIME;
		} else {
			need_size_change = 1;
			i_size_write(inode, attr->ia_size);
		}
	}
	/*
	 * Here, no error returns from inode_setattr().
	 * Because ATTR_SIZE is cleared before calling it.
	 */
	error = inode_setattr(inode, attr);

	if (!sbi->options.posix_attr) {
		if (S_ISDIR(inode->i_mode))
			mask = sbi->options.fs_dmask;
		else
			mask = sbi->options.fs_fmask;
		inode->i_mode &= S_IFMT | (S_IRWXUGO & ~mask);
	}

	if (need_size_change) {
		/* inode is already marked dirty in inode_setattr() */
		if (IS_SYNC(inode))
			generic_osync_inode(inode, NULL, OSYNC_INODE);
		error = vmtruncate(inode, inode->i_size);
	}
#else
	if (error) {
		if (sbi->options.quiet)
			error = 0;
		goto out;
	}
	error = inode_setattr(inode, attr);
	if (error)
		goto out;

	if (S_ISDIR(inode->i_mode))
		mask = sbi->options.fs_dmask;
	else
		mask = sbi->options.fs_fmask;
	inode->i_mode &= S_IFMT | (S_IRWXUGO & ~mask);
#endif
out:
	unlock_kernel();
	return error;
#ifdef CONFIG_SNSC_FS_VFAT_POSIX_ATTR
error_out:
	if (sbi->options.quiet)
		error = 0;
	unlock_kernel();
	return error;
#endif
}

EXPORT_SYMBOL_GPL(fat_notify_change);

/* Free all clusters after the skip'th cluster. */
static int fat_free(struct inode *inode, int skip)
{
	struct super_block *sb = inode->i_sb;
	int err, wait, free_start, i_start, i_logstart;

	if (MSDOS_I(inode)->i_start == 0)
		return 0;

	fat_cache_inval_inode(inode);

#ifdef CONFIG_SNSC_FS_FAT_BATCH_SYNC
	wait = IS_DIRSYNC(inode) || MSDOS_SB(sb)->options.batch_sync;
#else
	wait = IS_DIRSYNC(inode);
#endif
	i_start = free_start = MSDOS_I(inode)->i_start;
	i_logstart = MSDOS_I(inode)->i_logstart;

	/* First, we write the new file size. */
	if (!skip) {
		MSDOS_I(inode)->i_start = 0;
		MSDOS_I(inode)->i_logstart = 0;
	}
	MSDOS_I(inode)->i_attrs |= ATTR_ARCH;
	inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC;
	if (wait) {
		err = fat_sync_inode(inode);
		if (err) {
			MSDOS_I(inode)->i_start = i_start;
			MSDOS_I(inode)->i_logstart = i_logstart;
			return err;
		}
	} else
		mark_inode_dirty(inode);

	/* Write a new EOF, and get the remaining cluster chain for freeing. */
	if (skip) {
		struct fat_entry fatent;
		int ret, fclus, dclus;

		ret = fat_get_cluster(inode, skip - 1, &fclus, &dclus);
		if (ret < 0)
			return ret;
		else if (ret == FAT_ENT_EOF)
			return 0;

		fatent_init(&fatent);
		ret = fat_ent_read(inode, &fatent, dclus);
		if (ret == FAT_ENT_EOF) {
			fatent_brelse(&fatent);
			return 0;
		} else if (ret == FAT_ENT_FREE) {
			fat_fs_panic(sb,
				     "%s: invalid cluster chain (i_pos %lld)",
				     __FUNCTION__, MSDOS_I(inode)->i_pos);
			ret = -EIO;
		} else if (ret > 0) {
			err = fat_ent_write(inode, &fatent, FAT_ENT_EOF, wait);
			if (err)
				ret = err;
		}
		fatent_brelse(&fatent);
		if (ret < 0)
			return ret;

		free_start = ret;
	}
	inode->i_blocks = skip << (MSDOS_SB(sb)->cluster_bits - 9);

	/* Freeing the remained cluster chain */
	return fat_free_clusters(inode, free_start);
}

void fat_truncate(struct inode *inode)
{
	struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
	const unsigned int cluster_size = sbi->cluster_size;
	int nr_clusters;

	/*
	 * This protects against truncating a file bigger than it was then
	 * trying to write into the hole.
	 */
	if (MSDOS_I(inode)->mmu_private > inode->i_size)
		MSDOS_I(inode)->mmu_private = inode->i_size;

	nr_clusters = (inode->i_size + (cluster_size - 1)) >> sbi->cluster_bits;

	lock_kernel();
	fat_free(inode, nr_clusters);
	unlock_kernel();
	fat_flush_inodes(inode->i_sb, inode, NULL);
}

int fat_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
{
	struct inode *inode = dentry->d_inode;
	generic_fillattr(inode, stat);
	stat->blksize = MSDOS_SB(inode->i_sb)->cluster_size;
	return 0;
}
EXPORT_SYMBOL_GPL(fat_getattr);

const struct inode_operations fat_file_inode_operations = {
	.truncate	= fat_truncate,
	.setattr	= fat_notify_change,
	.getattr	= fat_getattr,
};
#ifdef CONFIG_SNSC_FS_VFAT_POSIX_ATTR
struct inode_operations fat_symlink_inode_operations = {
       .readlink       = page_readlink,
       .follow_link    = page_follow_link_light,
       .setattr        = fat_notify_change,
};

EXPORT_SYMBOL(fat_symlink_inode_operations);
#endif
