/* Some of the source code in this file came from "linux/fs/fat/misc.c".  */
/*
 *  linux/fs/fat/misc.c
 *
 *  Written 1992,1993 by Werner Almesberger
 *  22/11/2000 - Fixed fat_date_unix2dos for dates earlier than 01/01/1980
 *         and date_dos2unix for date==0 by Igor Zhbanov(bsg@uniyar.ac.ru)
 */

/*
 *  Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

/************************************************************************/
/*                                                                      */
/*  PROJECT : exFAT & FAT12/16/32 File System                           */
/*  FILE    : exfat_core.c                                              */
/*  PURPOSE : exFAT File Manager                                        */
/*                                                                      */
/*----------------------------------------------------------------------*/
/*  NOTES                                                               */
/*                                                                      */
/*----------------------------------------------------------------------*/
/*  REVISION HISTORY (Ver 0.9)                                          */
/*                                                                      */
/*  - 2010.11.15 [Joosun Hahn] : first writing                          */
/*                                                                      */
/************************************************************************/

#include <linux/version.h>
#include <linux/param.h>
#include <linux/log2.h>

#include "exfat_bitmap.h"
#include "exfat_config.h"
#include "exfat_data.h"
#include "exfat_oal.h"
#include "exfat_blkdev.h"
#include "exfat_cache.h"
#include "exfat_nls.h"
#include "exfat_api.h"
#include "exfat_super.h"
#include "exfat_core.h"

#include <linux/blkdev.h>
#include <linux/slab.h>

static void __set_sb_dirty(struct super_block *sb)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
	sb->s_dirt = 1;
#else
	struct exfat_sb_info *sbi = EXFAT_SB(sb);
	sbi->s_dirt = 1;
#endif
}

/*----------------------------------------------------------------------*/
/*  Global Variable Definitions                                         */
/*----------------------------------------------------------------------*/

extern u8 uni_upcase[];

/*----------------------------------------------------------------------*/
/*  Local Variable Definitions                                          */
/*----------------------------------------------------------------------*/

static u8 name_buf[MAX_PATH_LENGTH * MAX_CHARSET_SIZE];

static char *reserved_names[] = {
	"AUX     ", "CON     ", "NUL     ", "PRN     ",
	"COM1    ", "COM2    ", "COM3    ", "COM4    ",
	"COM5    ", "COM6    ", "COM7    ", "COM8    ", "COM9    ",
	"LPT1    ", "LPT2    ", "LPT3    ", "LPT4    ",
	"LPT5    ", "LPT6    ", "LPT7    ", "LPT8    ", "LPT9    ",
	NULL
};

static u8 free_bit[] = {
	0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, /*   0 ~  19 */
	0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, /*  20 ~  39 */
	0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, /*  40 ~  59 */
	0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, /*  60 ~  79 */
	0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, /*  80 ~  99 */
	0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, /* 100 ~ 119 */
	0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, /* 120 ~ 139 */
	0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, /* 140 ~ 159 */
	0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, /* 160 ~ 179 */
	0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, /* 180 ~ 199 */
	0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, /* 200 ~ 219 */
	0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, /* 220 ~ 239 */
	0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0                 /* 240 ~ 254 */
};

static u8 used_bit[] = {
	0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, /*   0 ~  19 */
	2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, /*  20 ~  39 */
	2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, /*  40 ~  59 */
	4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, /*  60 ~  79 */
	2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, /*  80 ~  99 */
	3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, /* 100 ~ 119 */
	4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, /* 120 ~ 139 */
	3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, /* 140 ~ 159 */
	2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, /* 160 ~ 179 */
	4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, /* 180 ~ 199 */
	3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, /* 200 ~ 219 */
	5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, /* 220 ~ 239 */
	4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8              /* 240 ~ 255 */
};

/*======================================================================*/
/*  Global Function Definitions                                         */
/*======================================================================*/

/* ffsInit : roll back to the initial state of the file system */
s32 ffsInit(void)
{
	s32 ret;

	ret = bdev_init();
	if (ret)
		return ret;

	ret = fs_init();
	if (ret)
		return ret;

	return FFS_SUCCESS;
} /* end of ffsInit */

/* ffsShutdown : make free all memory-alloced global buffers */
s32 ffsShutdown(void)
{
	s32 ret;
	ret = fs_shutdown();
	if (ret)
		return ret;

	ret = bdev_shutdown();
	if (ret)
		return ret;

	return FFS_SUCCESS;
} /* end of ffsShutdown */

/* ffsMountVol : mount the file system volume */
s32 ffsMountVol(struct super_block *sb)
{
	int i, ret;
	PBR_SECTOR_T *p_pbr;
	struct buffer_head *tmp_bh = NULL;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);

	printk("[EXFAT] trying to mount...\n");

	sm_init(&p_fs->v_sem);
	p_fs->dev_ejected = FALSE;

	/* open the block device */
	if (bdev_open(sb))
		return FFS_MEDIAERR;

	if (p_bd->sector_size < sb->s_blocksize)
		return FFS_MEDIAERR;
	if (p_bd->sector_size > sb->s_blocksize)
		sb_set_blocksize(sb, p_bd->sector_size);

	/* read Sector 0 */
	if (sector_read(sb, 0, &tmp_bh, 1) != FFS_SUCCESS)
		return FFS_MEDIAERR;

	p_fs->PBR_sector = 0;

	p_pbr = (PBR_SECTOR_T *) tmp_bh->b_data;

	/* check the validity of PBR */
	if (GET16_A(p_pbr->signature) != PBR_SIGNATURE) {
		brelse(tmp_bh);
		bdev_close(sb);
		return FFS_FORMATERR;
	}

	/* fill fs_stuct */
	for (i = 0; i < 53; i++)
		if (p_pbr->bpb[i])
			break;

	if (i < 53) {
		if (GET16(p_pbr->bpb+11)) /* num_fat_sectors */
			ret = fat16_mount(sb, p_pbr);
		else
			ret = fat32_mount(sb, p_pbr);
	} else {
		ret = exfat_mount(sb, p_pbr);
	}

	brelse(tmp_bh);

	if (ret) {
		bdev_close(sb);
		return ret;
	}

	if (p_fs->vol_type == EXFAT) {
		ret = load_alloc_bitmap(sb);
		if (ret) {
			bdev_close(sb);
			return ret;
		}
		ret = load_upcase_table(sb);
		if (ret) {
			free_alloc_bitmap(sb);
			bdev_close(sb);
			return ret;
		}
	}

	if (p_fs->dev_ejected) {
		if (p_fs->vol_type == EXFAT) {
			free_upcase_table(sb);
			free_alloc_bitmap(sb);
		}
		bdev_close(sb);
		return FFS_MEDIAERR;
	}

	printk("[EXFAT] mounted successfully\n");

	return FFS_SUCCESS;
} /* end of ffsMountVol */

/* ffsUmountVol : umount the file system volume */
s32 ffsUmountVol(struct super_block *sb)
{
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	printk("[EXFAT] trying to unmount...\n");

	fs_sync(sb, 0);
	fs_set_vol_flags(sb, VOL_CLEAN);

	if (p_fs->vol_type == EXFAT) {
		free_upcase_table(sb);
		free_alloc_bitmap(sb);
	}

	FAT_release_all(sb);
	buf_release_all(sb);

	/* close the block device */
	bdev_close(sb);

	if (p_fs->dev_ejected) {
		printk("[EXFAT] unmounted with media errors. "
			"device's already ejected.\n");
		return FFS_MEDIAERR;
	}

	printk("[EXFAT] unmounted successfully\n");

	return FFS_SUCCESS;
} /* end of ffsUmountVol */

/* ffsGetVolInfo : get the information of a file system volume */
s32 ffsGetVolInfo(struct super_block *sb, VOL_INFO_T *info)
{
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	if (p_fs->used_clusters == (u32) ~0)
		p_fs->used_clusters = p_fs->fs_func->count_used_clusters(sb);

	info->FatType = p_fs->vol_type;
	info->ClusterSize = p_fs->cluster_size;
	info->NumClusters = p_fs->num_clusters - 2; /* clu 0 & 1 */
	info->UsedClusters = p_fs->used_clusters;
	info->FreeClusters = info->NumClusters - info->UsedClusters;

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	return FFS_SUCCESS;
} /* end of ffsGetVolInfo */

/* ffsSyncVol : synchronize all file system volumes */
s32 ffsSyncVol(struct super_block *sb, s32 do_sync)
{
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	/* synchronize the file system */
	fs_sync(sb, do_sync);
	fs_set_vol_flags(sb, VOL_CLEAN);

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	return FFS_SUCCESS;
} /* end of ffsSyncVol */

/*----------------------------------------------------------------------*/
/*  File Operation Functions                                            */
/*----------------------------------------------------------------------*/

/* ffsLookupFile : lookup a file */
s32 ffsLookupFile(struct inode *inode, char *path, FILE_ID_T *fid)
{
	s32 ret, dentry, num_entries;
	CHAIN_T dir;
	UNI_NAME_T uni_name;
	DOS_NAME_T dos_name;
	DENTRY_T *ep, *ep2;
	ENTRY_SET_CACHE_T *es = NULL;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	DPRINTK("ffsLookupFile entered\n");

	/* check the validity of directory name in the given pathname */
	ret = resolve_path(inode, path, &dir, &uni_name);
	if (ret)
		return ret;

	ret = get_num_entries_and_dos_name(sb, &dir, &uni_name, &num_entries, &dos_name);
	if (ret)
		return ret;

	/* search the file name for directories */
	dentry = p_fs->fs_func->find_dir_entry(sb, &dir, &uni_name, num_entries, &dos_name, TYPE_ALL);
	if (dentry < -1)
		return FFS_NOTFOUND;

	fid->dir.dir = dir.dir;
	fid->dir.size = dir.size;
	fid->dir.flags = dir.flags;
	fid->entry = dentry;

	if (dentry == -1) {
		fid->type = TYPE_DIR;
		fid->rwoffset = 0;
		fid->hint_last_off = -1;

		fid->attr = ATTR_SUBDIR;
		fid->flags = 0x01;
		fid->size = 0;
		fid->start_clu = p_fs->root_dir;
	} else {
		if (p_fs->vol_type == EXFAT) {
			es = get_entry_set_in_dir(sb, &dir, dentry, ES_2_ENTRIES, &ep);
			if (!es)
				return FFS_MEDIAERR;
			ep2 = ep+1;
		} else {
			ep = get_entry_in_dir(sb, &dir, dentry, NULL);
			if (!ep)
				return FFS_MEDIAERR;
			ep2 = ep;
		}

		fid->type = p_fs->fs_func->get_entry_type(ep);
		fid->rwoffset = 0;
		fid->hint_last_off = -1;
		fid->attr = p_fs->fs_func->get_entry_attr(ep);

		fid->size = p_fs->fs_func->get_entry_size(ep2);
		if ((fid->type == TYPE_FILE) && (fid->size == 0)) {
			fid->flags = (p_fs->vol_type == EXFAT) ? 0x03 : 0x01;
			fid->start_clu = CLUSTER_32(~0);
		} else {
			fid->flags = p_fs->fs_func->get_entry_flag(ep2);
			fid->start_clu = p_fs->fs_func->get_entry_clu0(ep2);
		}

		if (p_fs->vol_type == EXFAT)
			release_entry_set(es);
	}

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	DPRINTK("ffsLookupFile exited successfully\n");

	return FFS_SUCCESS;
} /* end of ffsLookupFile */

/* ffsCreateFile : create a file */
s32 ffsCreateFile(struct inode *inode, char *path, u8 mode, FILE_ID_T *fid)
{
	s32 ret/*, dentry*/;
	CHAIN_T dir;
	UNI_NAME_T uni_name;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	/* check the validity of directory name in the given pathname */
	ret = resolve_path(inode, path, &dir, &uni_name);
	if (ret)
		return ret;

	fs_set_vol_flags(sb, VOL_DIRTY);

	/* create a new file */
	ret = create_file(inode, &dir, &uni_name, mode, fid);

#ifdef CONFIG_EXFAT_DELAYED_SYNC
	fs_sync(sb, 0);
	fs_set_vol_flags(sb, VOL_CLEAN);
#endif

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	return ret;
} /* end of ffsCreateFile */

/* ffsReadFile : read data from a opened file */
s32 ffsReadFile(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *rcount)
{
	s32 offset, sec_offset, clu_offset;
	u32 clu;
	sector_t LogSector;
	u64 oneblkread, read_bytes;
	struct buffer_head *tmp_bh = NULL;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);

	/* check if the given file ID is opened */
	if (fid->type != TYPE_FILE)
		return FFS_PERMISSIONERR;

	if (fid->rwoffset > fid->size)
		fid->rwoffset = fid->size;

	if (count > (fid->size - fid->rwoffset))
		count = fid->size - fid->rwoffset;

	if (count == 0) {
		if (rcount != NULL)
			*rcount = 0;
		return FFS_EOF;
	}

	read_bytes = 0;

	while (count > 0) {
		clu_offset = (s32)(fid->rwoffset >> p_fs->cluster_size_bits);
		clu = fid->start_clu;

		if (fid->flags == 0x03) {
			clu += clu_offset;
		} else {
			/* hint information */
			if ((clu_offset > 0) && (fid->hint_last_off > 0) &&
				(clu_offset >= fid->hint_last_off)) {
				clu_offset -= fid->hint_last_off;
				clu = fid->hint_last_clu;
			}

			while (clu_offset > 0) {
				/* clu = FAT_read(sb, clu); */
				if (FAT_read(sb, clu, &clu) == -1)
					return FFS_MEDIAERR;

				clu_offset--;
			}
		}

		/* hint information */
		fid->hint_last_off = (s32)(fid->rwoffset >> p_fs->cluster_size_bits);
		fid->hint_last_clu = clu;

		offset = (s32)(fid->rwoffset & (p_fs->cluster_size-1)); /* byte offset in cluster   */
		sec_offset = offset >> p_bd->sector_size_bits;            /* sector offset in cluster */
		offset &= p_bd->sector_size_mask;                         /* byte offset in sector    */

		LogSector = START_SECTOR(clu) + sec_offset;

		oneblkread = (u64)(p_bd->sector_size - offset);
		if (oneblkread > count)
			oneblkread = count;

		if ((offset == 0) && (oneblkread == p_bd->sector_size)) {
			if (sector_read(sb, LogSector, &tmp_bh, 1) != FFS_SUCCESS)
				goto err_out;
			memcpy(((char *) buffer)+read_bytes, ((char *) tmp_bh->b_data), (s32) oneblkread);
		} else {
			if (sector_read(sb, LogSector, &tmp_bh, 1) != FFS_SUCCESS)
				goto err_out;
			memcpy(((char *) buffer)+read_bytes, ((char *) tmp_bh->b_data)+offset, (s32) oneblkread);
		}
		count -= oneblkread;
		read_bytes += oneblkread;
		fid->rwoffset += oneblkread;
	}
	brelse(tmp_bh);

err_out:
	/* set the size of read bytes */
	if (rcount != NULL)
		*rcount = read_bytes;

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	return FFS_SUCCESS;
} /* end of ffsReadFile */

/* ffsWriteFile : write data into a opened file */
s32 ffsWriteFile(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *wcount)
{
	s32 modified = FALSE, offset, sec_offset, clu_offset;
	s32 num_clusters, num_alloc, num_alloced = (s32) ~0;
	u32 clu, last_clu;
	sector_t LogSector, sector = 0;
	u64 oneblkwrite, write_bytes;
	CHAIN_T new_clu;
	TIMESTAMP_T tm;
	DENTRY_T *ep, *ep2;
	ENTRY_SET_CACHE_T *es = NULL;
	struct buffer_head *tmp_bh = NULL;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);

	/* check if the given file ID is opened */
	if (fid->type != TYPE_FILE)
		return FFS_PERMISSIONERR;

	if (fid->rwoffset > fid->size)
		fid->rwoffset = fid->size;

	if (count == 0) {
		if (wcount != NULL)
			*wcount = 0;
		return FFS_SUCCESS;
	}

	fs_set_vol_flags(sb, VOL_DIRTY);

	if (fid->size == 0)
		num_clusters = 0;
	else
		num_clusters = (s32)((fid->size-1) >> p_fs->cluster_size_bits) + 1;

	write_bytes = 0;

	while (count > 0) {
		clu_offset = (s32)(fid->rwoffset >> p_fs->cluster_size_bits);
		clu = last_clu = fid->start_clu;

		if (fid->flags == 0x03) {
			if ((clu_offset > 0) && (clu != CLUSTER_32(~0))) {
				last_clu += clu_offset - 1;

				if (clu_offset == num_clusters)
					clu = CLUSTER_32(~0);
				else
					clu += clu_offset;
			}
		} else {
			/* hint information */
			if ((clu_offset > 0) && (fid->hint_last_off > 0) &&
				(clu_offset >= fid->hint_last_off)) {
				clu_offset -= fid->hint_last_off;
				clu = fid->hint_last_clu;
			}

			while ((clu_offset > 0) && (clu != CLUSTER_32(~0))) {
				last_clu = clu;
				/* clu = FAT_read(sb, clu); */
				if (FAT_read(sb, clu, &clu) == -1)
					return FFS_MEDIAERR;

				clu_offset--;
			}
		}

		if (clu == CLUSTER_32(~0)) {
			num_alloc = (s32)((count-1) >> p_fs->cluster_size_bits) + 1;
			new_clu.dir = (last_clu == CLUSTER_32(~0)) ? CLUSTER_32(~0) : last_clu+1;
			new_clu.size = 0;
			new_clu.flags = fid->flags;

			/* (1) allocate a chain of clusters */
			num_alloced = p_fs->fs_func->alloc_cluster(sb, num_alloc, &new_clu);
			if (num_alloced == 0)
				break;
			else if (num_alloced < 0)
				return FFS_MEDIAERR;

			/* (2) append to the FAT chain */
			if (last_clu == CLUSTER_32(~0)) {
				if (new_clu.flags == 0x01)
					fid->flags = 0x01;
				fid->start_clu = new_clu.dir;
				modified = TRUE;
			} else {
				if (new_clu.flags != fid->flags) {
					exfat_chain_cont_cluster(sb, fid->start_clu, num_clusters);
					fid->flags = 0x01;
					modified = TRUE;
				}
				if (new_clu.flags == 0x01)
					FAT_write(sb, last_clu, new_clu.dir);
			}

			num_clusters += num_alloced;
			clu = new_clu.dir;
		}

		/* hint information */
		fid->hint_last_off = (s32)(fid->rwoffset >> p_fs->cluster_size_bits);
		fid->hint_last_clu = clu;

		offset = (s32)(fid->rwoffset & (p_fs->cluster_size-1)); /* byte offset in cluster   */
		sec_offset = offset >> p_bd->sector_size_bits;            /* sector offset in cluster */
		offset &= p_bd->sector_size_mask;                         /* byte offset in sector    */

		LogSector = START_SECTOR(clu) + sec_offset;

		oneblkwrite = (u64)(p_bd->sector_size - offset);
		if (oneblkwrite > count)
			oneblkwrite = count;

		if ((offset == 0) && (oneblkwrite == p_bd->sector_size)) {
			if (sector_read(sb, LogSector, &tmp_bh, 0) != FFS_SUCCESS)
				goto err_out;
			memcpy(((char *) tmp_bh->b_data), ((char *) buffer)+write_bytes, (s32) oneblkwrite);
			if (sector_write(sb, LogSector, tmp_bh, 0) != FFS_SUCCESS) {
				brelse(tmp_bh);
				goto err_out;
			}
		} else {
			if ((offset > 0) || ((fid->rwoffset+oneblkwrite) < fid->size)) {
				if (sector_read(sb, LogSector, &tmp_bh, 1) != FFS_SUCCESS)
					goto err_out;
			} else {
				if (sector_read(sb, LogSector, &tmp_bh, 0) != FFS_SUCCESS)
					goto err_out;
			}

			memcpy(((char *) tmp_bh->b_data)+offset, ((char *) buffer)+write_bytes, (s32) oneblkwrite);
			if (sector_write(sb, LogSector, tmp_bh, 0) != FFS_SUCCESS) {
				brelse(tmp_bh);
				goto err_out;
			}
		}

		count -= oneblkwrite;
		write_bytes += oneblkwrite;
		fid->rwoffset += oneblkwrite;

		fid->attr |= ATTR_ARCHIVE;

		if (fid->size < fid->rwoffset) {
			fid->size = fid->rwoffset;
			modified = TRUE;
		}
	}

	brelse(tmp_bh);

	/* (3) update the direcoty entry */
	if (p_fs->vol_type == EXFAT) {
		es = get_entry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep);
		if (es == NULL)
			goto err_out;
		ep2 = ep+1;
	} else {
		ep = get_entry_in_dir(sb, &(fid->dir), fid->entry, &sector);
		if (!ep)
			goto err_out;
		ep2 = ep;
	}

	p_fs->fs_func->set_entry_time(ep, tm_current(&tm), TM_MODIFY);
	p_fs->fs_func->set_entry_attr(ep, fid->attr);

	if (p_fs->vol_type != EXFAT)
		buf_modify(sb, sector);

	if (modified) {
		if (p_fs->fs_func->get_entry_flag(ep2) != fid->flags)
			p_fs->fs_func->set_entry_flag(ep2, fid->flags);

		if (p_fs->fs_func->get_entry_size(ep2) != fid->size)
			p_fs->fs_func->set_entry_size(ep2, fid->size);

		if (p_fs->fs_func->get_entry_clu0(ep2) != fid->start_clu)
			p_fs->fs_func->set_entry_clu0(ep2, fid->start_clu);

		if (p_fs->vol_type != EXFAT)
			buf_modify(sb, sector);
	}

	if (p_fs->vol_type == EXFAT) {
		update_dir_checksum_with_entry_set(sb, es);
		release_entry_set(es);
	}

#ifdef CONFIG_EXFAT_DELAYED_SYNC
	fs_sync(sb, 0);
	fs_set_vol_flags(sb, VOL_CLEAN);
#endif

err_out:
	/* set the size of written bytes */
	if (wcount != NULL)
		*wcount = write_bytes;

	if (num_alloced == 0)
		return FFS_FULL;

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	return FFS_SUCCESS;
} /* end of ffsWriteFile */

/* ffsTruncateFile : resize the file length */
s32 ffsTruncateFile(struct inode *inode, u64 old_size, u64 new_size)
{
	s32 num_clusters;
	u32 last_clu = CLUSTER_32(0);
	sector_t sector = 0;
	CHAIN_T clu;
	TIMESTAMP_T tm;
	DENTRY_T *ep, *ep2;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	FILE_ID_T *fid = &(EXFAT_I(inode)->fid);
	ENTRY_SET_CACHE_T *es = NULL;

	/* check if the given file ID is opened */
	if (fid->type != TYPE_FILE)
		return FFS_PERMISSIONERR;

	if (fid->size != old_size) {
		printk(KERN_ERR "[EXFAT] truncate : can't skip it because of "
				"size-mismatch(old:%lld->fid:%lld).\n"
				,old_size, fid->size);
	}

	if (old_size <= new_size)
		return FFS_SUCCESS;

	fs_set_vol_flags(sb, VOL_DIRTY);

	clu.dir = fid->start_clu;
	clu.size = (s32)((old_size-1) >> p_fs->cluster_size_bits) + 1;
	clu.flags = fid->flags;

	if (new_size > 0) {
		num_clusters = (s32)((new_size-1) >> p_fs->cluster_size_bits) + 1;

		if (clu.flags == 0x03) {
			clu.dir += num_clusters;
		} else {
			while (num_clusters > 0) {
				last_clu = clu.dir;
				if (FAT_read(sb, clu.dir, &(clu.dir)) == -1)
					return FFS_MEDIAERR;
				num_clusters--;
			}
		}

		clu.size -= num_clusters;
	}

	fid->size = new_size;
	fid->attr |= ATTR_ARCHIVE;
	if (new_size == 0) {
		fid->flags = (p_fs->vol_type == EXFAT) ? 0x03 : 0x01;
		fid->start_clu = CLUSTER_32(~0);
	}

	/* (1) update the directory entry */
	if (p_fs->vol_type == EXFAT) {
		es = get_entry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep);
		if (es == NULL)
			return FFS_MEDIAERR;
		ep2 = ep+1;
	} else {
		ep = get_entry_in_dir(sb, &(fid->dir), fid->entry, &sector);
		if (!ep)
			return FFS_MEDIAERR;
		ep2 = ep;
	}

	p_fs->fs_func->set_entry_time(ep, tm_current(&tm), TM_MODIFY);
	p_fs->fs_func->set_entry_attr(ep, fid->attr);

	p_fs->fs_func->set_entry_size(ep2, new_size);
	if (new_size == 0) {
		p_fs->fs_func->set_entry_flag(ep2, 0x01);
		p_fs->fs_func->set_entry_clu0(ep2, CLUSTER_32(0));
	}

	if (p_fs->vol_type != EXFAT)
		buf_modify(sb, sector);
	else {
		update_dir_checksum_with_entry_set(sb, es);
		release_entry_set(es);
	}

	/* (2) cut off from the FAT chain */
	if (last_clu != CLUSTER_32(0)) {
		if (fid->flags == 0x01)
			FAT_write(sb, last_clu, CLUSTER_32(~0));
	}

	/* (3) free the clusters */
	p_fs->fs_func->free_cluster(sb, &clu, 0);

	/* hint information */
	fid->hint_last_off = -1;
	if (fid->rwoffset > fid->size)
		fid->rwoffset = fid->size;

#ifdef CONFIG_EXFAT_DELAYED_SYNC
	fs_sync(sb, 0);
	fs_set_vol_flags(sb, VOL_CLEAN);
#endif

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	return FFS_SUCCESS;
} /* end of ffsTruncateFile */

static void update_parent_info(FILE_ID_T *fid, struct inode *parent_inode)
{
	FS_INFO_T *p_fs = &(EXFAT_SB(parent_inode->i_sb)->fs_info);
	FILE_ID_T *parent_fid = &(EXFAT_I(parent_inode)->fid);

	if (unlikely((parent_fid->flags != fid->dir.flags)
		|| (parent_fid->size != (fid->dir.size<<p_fs->cluster_size_bits))
		|| (parent_fid->start_clu != fid->dir.dir))) {

		fid->dir.dir = parent_fid->start_clu;
		fid->dir.flags = parent_fid->flags;
		fid->dir.size = ((parent_fid->size + (p_fs->cluster_size-1))
						>> p_fs->cluster_size_bits);
	}
}

/* ffsMoveFile : move(rename) a old file into a new file */
s32 ffsMoveFile(struct inode *old_parent_inode, FILE_ID_T *fid, struct inode *new_parent_inode, struct dentry *new_dentry)
{
	s32 ret;
	s32 dentry;
	CHAIN_T olddir, newdir;
	CHAIN_T *p_dir = NULL;
	UNI_NAME_T uni_name;
	DENTRY_T *ep;
	struct super_block *sb = old_parent_inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	u8 *new_path = (u8 *) new_dentry->d_name.name;
	struct inode *new_inode = new_dentry->d_inode;
	int num_entries;
	FILE_ID_T *new_fid = NULL;
	s32 new_entry = 0;

	/* check the validity of pointer parameters */
	if ((new_path == NULL) || (*new_path == '\0'))
		return FFS_ERROR;

	update_parent_info(fid, old_parent_inode);

	olddir.dir = fid->dir.dir;
	olddir.size = fid->dir.size;
	olddir.flags = fid->dir.flags;

	dentry = fid->entry;

	/* check if the old file is "." or ".." */
	if (p_fs->vol_type != EXFAT) {
		if ((olddir.dir != p_fs->root_dir) && (dentry < 2))
			return FFS_PERMISSIONERR;
	}

	ep = get_entry_in_dir(sb, &olddir, dentry, NULL);
	if (!ep)
		return FFS_MEDIAERR;

	if (p_fs->fs_func->get_entry_attr(ep) & ATTR_READONLY)
		return FFS_PERMISSIONERR;

	/* check whether new dir is existing directory and empty */
	if (new_inode) {
		u32 entry_type;

		ret = FFS_MEDIAERR;
		new_fid = &EXFAT_I(new_inode)->fid;

		update_parent_info(new_fid, new_parent_inode);

		p_dir = &(new_fid->dir);
		new_entry = new_fid->entry;
		ep = get_entry_in_dir(sb, p_dir, new_entry, NULL);
		if (!ep)
			goto out;

		entry_type = p_fs->fs_func->get_entry_type(ep);

		if (entry_type == TYPE_DIR) {
			CHAIN_T new_clu;
			new_clu.dir = new_fid->start_clu;
			new_clu.size = (s32)((new_fid->size-1) >> p_fs->cluster_size_bits) + 1;
			new_clu.flags = new_fid->flags;

			if (!is_dir_empty(sb, &new_clu))
				return FFS_FILEEXIST;
		}
	}

	/* check the validity of directory name in the given new pathname */
	ret = resolve_path(new_parent_inode, new_path, &newdir, &uni_name);
	if (ret)
		return ret;

	fs_set_vol_flags(sb, VOL_DIRTY);

	if (olddir.dir == newdir.dir)
		ret = rename_file(new_parent_inode, &olddir, dentry, &uni_name, fid);
	else
		ret = move_file(new_parent_inode, &olddir, dentry, &newdir, &uni_name, fid);

	if ((ret == FFS_SUCCESS) && new_inode) {
		/* delete entries of new_dir */
		ep = get_entry_in_dir(sb, p_dir, new_entry, NULL);
		if (!ep)
			goto out;

		num_entries = p_fs->fs_func->count_ext_entries(sb, p_dir, new_entry, ep);
		if (num_entries < 0)
			goto out;
		p_fs->fs_func->delete_dir_entry(sb, p_dir, new_entry, 0, num_entries+1);
	}
out:
#ifdef CONFIG_EXFAT_DELAYED_SYNC
	fs_sync(sb, 0);
	fs_set_vol_flags(sb, VOL_CLEAN);
#endif

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	return ret;
} /* end of ffsMoveFile */

/* ffsRemoveFile : remove a file */
s32 ffsRemoveFile(struct inode *inode, FILE_ID_T *fid)
{
	s32 dentry;
	CHAIN_T dir, clu_to_free;
	DENTRY_T *ep;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	dir.dir = fid->dir.dir;
	dir.size = fid->dir.size;
	dir.flags = fid->dir.flags;

	dentry = fid->entry;

	ep = get_entry_in_dir(sb, &dir, dentry, NULL);
	if (!ep)
		return FFS_MEDIAERR;

	if (p_fs->fs_func->get_entry_attr(ep) & ATTR_READONLY)
		return FFS_PERMISSIONERR;

	fs_set_vol_flags(sb, VOL_DIRTY);

	/* (1) update the directory entry */
	remove_file(inode, &dir, dentry);

	clu_to_free.dir = fid->start_clu;
	clu_to_free.size = (s32)((fid->size-1) >> p_fs->cluster_size_bits) + 1;
	clu_to_free.flags = fid->flags;

	/* (2) free the clusters */
	p_fs->fs_func->free_cluster(sb, &clu_to_free, 0);

	fid->size = 0;
	fid->start_clu = CLUSTER_32(~0);
	fid->flags = (p_fs->vol_type == EXFAT) ? 0x03 : 0x01;

#ifdef CONFIG_EXFAT_DELAYED_SYNC
	fs_sync(sb, 0);
	fs_set_vol_flags(sb, VOL_CLEAN);
#endif

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	return FFS_SUCCESS;
} /* end of ffsRemoveFile */

/* ffsSetAttr : set the attribute of a given file */
s32 ffsSetAttr(struct inode *inode, u32 attr)
{
	u32 type;
	sector_t sector = 0;
	DENTRY_T *ep;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	FILE_ID_T *fid = &(EXFAT_I(inode)->fid);
	u8 is_dir = (fid->type == TYPE_DIR) ? 1 : 0;
	ENTRY_SET_CACHE_T *es = NULL;

	if (fid->attr == attr) {
		if (p_fs->dev_ejected)
			return FFS_MEDIAERR;
		return FFS_SUCCESS;
	}

	if (is_dir) {
		if ((fid->dir.dir == p_fs->root_dir) &&
			(fid->entry == -1)) {
			if (p_fs->dev_ejected)
				return FFS_MEDIAERR;
			return FFS_SUCCESS;
		}
	}

	/* get the directory entry of given file */
	if (p_fs->vol_type == EXFAT) {
		es = get_entry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep);
		if (es == NULL)
			return FFS_MEDIAERR;
	} else {
		ep = get_entry_in_dir(sb, &(fid->dir), fid->entry, &sector);
		if (!ep)
			return FFS_MEDIAERR;
	}

	type = p_fs->fs_func->get_entry_type(ep);

	if (((type == TYPE_FILE) && (attr & ATTR_SUBDIR)) ||
		((type == TYPE_DIR) && (!(attr & ATTR_SUBDIR)))) {
		s32 err;
		if (p_fs->dev_ejected)
			err = FFS_MEDIAERR;
		else
			err = FFS_ERROR;

		if (p_fs->vol_type == EXFAT)
			release_entry_set(es);
		return err;
	}

	fs_set_vol_flags(sb, VOL_DIRTY);

	/* set the file attribute */
	fid->attr = attr;
	p_fs->fs_func->set_entry_attr(ep, attr);

	if (p_fs->vol_type != EXFAT)
		buf_modify(sb, sector);
	else {
		update_dir_checksum_with_entry_set(sb, es);
		release_entry_set(es);
	}

#ifdef CONFIG_EXFAT_DELAYED_SYNC
	fs_sync(sb, 0);
	fs_set_vol_flags(sb, VOL_CLEAN);
#endif

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	return FFS_SUCCESS;
} /* end of ffsSetAttr */

/* ffsGetStat : get the information of a given file */
s32 ffsGetStat(struct inode *inode, DIR_ENTRY_T *info)
{
	sector_t sector = 0;
	s32 count;
	CHAIN_T dir;
	UNI_NAME_T uni_name;
	TIMESTAMP_T tm;
	DENTRY_T *ep, *ep2;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	FILE_ID_T *fid = &(EXFAT_I(inode)->fid);
	ENTRY_SET_CACHE_T *es = NULL;
	u8 is_dir = (fid->type == TYPE_DIR) ? 1 : 0;

	DPRINTK("ffsGetStat entered\n");

	if (is_dir) {
		if ((fid->dir.dir == p_fs->root_dir) &&
			(fid->entry == -1)) {
			info->Attr = ATTR_SUBDIR;
			memset((char *) &info->CreateTimestamp, 0, sizeof(DATE_TIME_T));
			memset((char *) &info->ModifyTimestamp, 0, sizeof(DATE_TIME_T));
			memset((char *) &info->AccessTimestamp, 0, sizeof(DATE_TIME_T));
			strcpy(info->ShortName, ".");
			strcpy(info->Name, ".");

			dir.dir = p_fs->root_dir;
			dir.flags = 0x01;

			if (p_fs->root_dir == CLUSTER_32(0)) /* FAT16 root_dir */
				info->Size = p_fs->dentries_in_root << DENTRY_SIZE_BITS;
			else
				info->Size = count_num_clusters(sb, &dir) << p_fs->cluster_size_bits;

			count = count_dos_name_entries(sb, &dir, TYPE_DIR);
			if (count < 0)
				return FFS_MEDIAERR;
			info->NumSubdirs = count;

			if (p_fs->dev_ejected)
				return FFS_MEDIAERR;
			return FFS_SUCCESS;
		}
	}

	/* get the directory entry of given file or directory */
	if (p_fs->vol_type == EXFAT) {
		es = get_entry_set_in_dir(sb, &(fid->dir), fid->entry, ES_2_ENTRIES, &ep);
		if (es == NULL)
			return FFS_MEDIAERR;
		ep2 = ep+1;
	} else {
		ep = get_entry_in_dir(sb, &(fid->dir), fid->entry, &sector);
		if (!ep)
			return FFS_MEDIAERR;
		ep2 = ep;
		buf_lock(sb, sector);
	}

	/* set FILE_INFO structure using the acquired DENTRY_T */
	info->Attr = p_fs->fs_func->get_entry_attr(ep);

	p_fs->fs_func->get_entry_time(ep, &tm, TM_CREATE);
	info->CreateTimestamp.Year = tm.year;
	info->CreateTimestamp.Month = tm.mon;
	info->CreateTimestamp.Day = tm.day;
	info->CreateTimestamp.Hour = tm.hour;
	info->CreateTimestamp.Minute = tm.min;
	info->CreateTimestamp.Second = tm.sec;
	info->CreateTimestamp.MilliSecond = 0;

	p_fs->fs_func->get_entry_time(ep, &tm, TM_MODIFY);
	info->ModifyTimestamp.Year = tm.year;
	info->ModifyTimestamp.Month = tm.mon;
	info->ModifyTimestamp.Day = tm.day;
	info->ModifyTimestamp.Hour = tm.hour;
	info->ModifyTimestamp.Minute = tm.min;
	info->ModifyTimestamp.Second = tm.sec;
	info->ModifyTimestamp.MilliSecond = 0;

	memset((char *) &info->AccessTimestamp, 0, sizeof(DATE_TIME_T));

	*(uni_name.name) = 0x0;
	/* XXX this is very bad for exfat cuz name is already included in es.
	 API should be revised */
	p_fs->fs_func->get_uni_name_from_ext_entry(sb, &(fid->dir), fid->entry, uni_name.name);
	if (*(uni_name.name) == 0x0 && p_fs->vol_type != EXFAT)
		get_uni_name_from_dos_entry(sb, (DOS_DENTRY_T *) ep, &uni_name, 0x1);
	nls_uniname_to_cstring(sb, info->Name, &uni_name);

	if (p_fs->vol_type == EXFAT) {
		info->NumSubdirs = 2;
	} else {
		buf_unlock(sb, sector);
		get_uni_name_from_dos_entry(sb, (DOS_DENTRY_T *) ep, &uni_name, 0x0);
		nls_uniname_to_cstring(sb, info->ShortName, &uni_name);
		info->NumSubdirs = 0;
	}

	info->Size = p_fs->fs_func->get_entry_size(ep2);

	if (p_fs->vol_type == EXFAT)
		release_entry_set(es);

	if (is_dir) {
		dir.dir = fid->start_clu;
		dir.flags = 0x01;

		if (info->Size == 0)
			info->Size = (u64) count_num_clusters(sb, &dir) << p_fs->cluster_size_bits;

		count = count_dos_name_entries(sb, &dir, TYPE_DIR);
		if (count < 0)
			return FFS_MEDIAERR;
		info->NumSubdirs += count;
	}

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	DPRINTK("ffsGetStat exited successfully\n");
	return FFS_SUCCESS;
} /* end of ffsGetStat */

/* ffsSetStat : set the information of a given file */
s32 ffsSetStat(struct inode *inode, DIR_ENTRY_T *info)
{
	sector_t sector = 0;
	TIMESTAMP_T tm;
	DENTRY_T *ep, *ep2;
	ENTRY_SET_CACHE_T *es = NULL;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	FILE_ID_T *fid = &(EXFAT_I(inode)->fid);
	u8 is_dir = (fid->type == TYPE_DIR) ? 1 : 0;

	if (is_dir) {
		if ((fid->dir.dir == p_fs->root_dir) &&
			(fid->entry == -1)) {
			if (p_fs->dev_ejected)
				return FFS_MEDIAERR;
			return FFS_SUCCESS;
		}
	}

	fs_set_vol_flags(sb, VOL_DIRTY);

	/* get the directory entry of given file or directory */
	if (p_fs->vol_type == EXFAT) {
		es = get_entry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep);
		if (es == NULL)
			return FFS_MEDIAERR;
		ep2 = ep+1;
	} else {
		/* for other than exfat */
		ep = get_entry_in_dir(sb, &(fid->dir), fid->entry, &sector);
		if (!ep)
			return FFS_MEDIAERR;
		ep2 = ep;
	}


	p_fs->fs_func->set_entry_attr(ep, info->Attr);

	/* set FILE_INFO structure using the acquired DENTRY_T */
	tm.sec  = info->CreateTimestamp.Second;
	tm.min  = info->CreateTimestamp.Minute;
	tm.hour = info->CreateTimestamp.Hour;
	tm.day  = info->CreateTimestamp.Day;
	tm.mon  = info->CreateTimestamp.Month;
	tm.year = info->CreateTimestamp.Year;
	p_fs->fs_func->set_entry_time(ep, &tm, TM_CREATE);

	tm.sec  = info->ModifyTimestamp.Second;
	tm.min  = info->ModifyTimestamp.Minute;
	tm.hour = info->ModifyTimestamp.Hour;
	tm.day  = info->ModifyTimestamp.Day;
	tm.mon  = info->ModifyTimestamp.Month;
	tm.year = info->ModifyTimestamp.Year;
	p_fs->fs_func->set_entry_time(ep, &tm, TM_MODIFY);


	p_fs->fs_func->set_entry_size(ep2, info->Size);

	if (p_fs->vol_type != EXFAT) {
		buf_modify(sb, sector);
	} else {
		update_dir_checksum_with_entry_set(sb, es);
		release_entry_set(es);
	}

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	return FFS_SUCCESS;
} /* end of ffsSetStat */

s32 ffsMapCluster(struct inode *inode, s32 clu_offset, u32 *clu)
{
	s32 num_clusters, num_alloced, modified = FALSE;
	u32 last_clu;
	sector_t sector = 0;
	CHAIN_T new_clu;
	DENTRY_T *ep;
	ENTRY_SET_CACHE_T *es = NULL;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	FILE_ID_T *fid = &(EXFAT_I(inode)->fid);

	fid->rwoffset = (s64)(clu_offset) << p_fs->cluster_size_bits;

	if (EXFAT_I(inode)->mmu_private == 0)
		num_clusters = 0;
	else
		num_clusters = (s32)((EXFAT_I(inode)->mmu_private-1) >> p_fs->cluster_size_bits) + 1;

	*clu = last_clu = fid->start_clu;

	if (fid->flags == 0x03) {
		if ((clu_offset > 0) && (*clu != CLUSTER_32(~0))) {
			last_clu += clu_offset - 1;

			if (clu_offset == num_clusters)
				*clu = CLUSTER_32(~0);
			else
				*clu += clu_offset;
		}
	} else {
		/* hint information */
		if ((clu_offset > 0) && (fid->hint_last_off > 0) &&
			(clu_offset >= fid->hint_last_off)) {
			clu_offset -= fid->hint_last_off;
			*clu = fid->hint_last_clu;
		}

		while ((clu_offset > 0) && (*clu != CLUSTER_32(~0))) {
			last_clu = *clu;
			if (FAT_read(sb, *clu, clu) == -1)
				return FFS_MEDIAERR;
			clu_offset--;
		}
	}

	if (*clu == CLUSTER_32(~0)) {
		fs_set_vol_flags(sb, VOL_DIRTY);

		new_clu.dir = (last_clu == CLUSTER_32(~0)) ? CLUSTER_32(~0) : last_clu+1;
		new_clu.size = 0;
		new_clu.flags = fid->flags;

		/* (1) allocate a cluster */
		num_alloced = p_fs->fs_func->alloc_cluster(sb, 1, &new_clu);
		if (num_alloced < 0)
			return FFS_MEDIAERR;
		else if (num_alloced == 0)
			return FFS_FULL;

		/* (2) append to the FAT chain */
		if (last_clu == CLUSTER_32(~0)) {
			if (new_clu.flags == 0x01)
				fid->flags = 0x01;
			fid->start_clu = new_clu.dir;
			modified = TRUE;
		} else {
			if (new_clu.flags != fid->flags) {
				exfat_chain_cont_cluster(sb, fid->start_clu, num_clusters);
				fid->flags = 0x01;
				modified = TRUE;
			}
			if (new_clu.flags == 0x01)
				FAT_write(sb, last_clu, new_clu.dir);
		}

		num_clusters += num_alloced;
		*clu = new_clu.dir;

		if (p_fs->vol_type == EXFAT) {
			es = get_entry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep);
			if (es == NULL)
				return FFS_MEDIAERR;
			/* get stream entry */
			ep++;
		}

		/* (3) update directory entry */
		if (modified) {
			if (p_fs->vol_type != EXFAT) {
				ep = get_entry_in_dir(sb, &(fid->dir), fid->entry, &sector);
				if (!ep)
					return FFS_MEDIAERR;
			}

			if (p_fs->fs_func->get_entry_flag(ep) != fid->flags)
				p_fs->fs_func->set_entry_flag(ep, fid->flags);

			if (p_fs->fs_func->get_entry_clu0(ep) != fid->start_clu)
				p_fs->fs_func->set_entry_clu0(ep, fid->start_clu);

			if (p_fs->vol_type != EXFAT)
				buf_modify(sb, sector);
		}

		if (p_fs->vol_type == EXFAT) {
			update_dir_checksum_with_entry_set(sb, es);
			release_entry_set(es);
		}

		/* add number of new blocks to inode */
		inode->i_blocks += num_alloced << (p_fs->cluster_size_bits - 9);
	}

	/* hint information */
	fid->hint_last_off = (s32)(fid->rwoffset >> p_fs->cluster_size_bits);
	fid->hint_last_clu = *clu;

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	return FFS_SUCCESS;
} /* end of ffsMapCluster */

/*----------------------------------------------------------------------*/
/*  Directory Operation Functions                                       */
/*----------------------------------------------------------------------*/

/* ffsCreateDir : create(make) a directory */
s32 ffsCreateDir(struct inode *inode, char *path, FILE_ID_T *fid)
{
	s32 ret/*, dentry*/;
	CHAIN_T dir;
	UNI_NAME_T uni_name;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	DPRINTK("ffsCreateDir entered\n");

	/* check the validity of directory name in the given old pathname */
	ret = resolve_path(inode, path, &dir, &uni_name);
	if (ret)
		return ret;

	fs_set_vol_flags(sb, VOL_DIRTY);

	ret = create_dir(inode, &dir, &uni_name, fid);

#ifdef CONFIG_EXFAT_DELAYED_SYNC
	fs_sync(sb, 0);
	fs_set_vol_flags(sb, VOL_CLEAN);
#endif

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	return ret;
} /* end of ffsCreateDir */

/* ffsReadDir : read a directory entry from the opened directory */
s32 ffsReadDir(struct inode *inode, DIR_ENTRY_T *dir_entry)
{
	int i, dentry, clu_offset;
	s32 dentries_per_clu, dentries_per_clu_bits = 0;
	u32 type;
	sector_t sector;
	CHAIN_T dir, clu;
	UNI_NAME_T uni_name;
	TIMESTAMP_T tm;
	DENTRY_T *ep;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	FILE_ID_T *fid = &(EXFAT_I(inode)->fid);

	/* check if the given file ID is opened */
	if (fid->type != TYPE_DIR)
		return FFS_PERMISSIONERR;

	if (fid->entry == -1) {
		dir.dir = p_fs->root_dir;
		dir.flags = 0x01;
	} else {
		dir.dir = fid->start_clu;
		dir.size = (s32)(fid->size >> p_fs->cluster_size_bits);
		dir.flags = fid->flags;
	}

	dentry = (s32) fid->rwoffset;

	if (dir.dir == CLUSTER_32(0)) { /* FAT16 root_dir */
		dentries_per_clu = p_fs->dentries_in_root;

		if (dentry == dentries_per_clu) {
			clu.dir = CLUSTER_32(~0);
		} else {
			clu.dir = dir.dir;
			clu.size = dir.size;
			clu.flags = dir.flags;
		}
	} else {
		dentries_per_clu = p_fs->dentries_per_clu;
		dentries_per_clu_bits = ilog2(dentries_per_clu);

		clu_offset = dentry >> dentries_per_clu_bits;
		clu.dir = dir.dir;
		clu.size = dir.size;
		clu.flags = dir.flags;

		if (clu.flags == 0x03) {
			clu.dir += clu_offset;
			clu.size -= clu_offset;
		} else {
			/* hint_information */
			if ((clu_offset > 0) && (fid->hint_last_off > 0) &&
				(clu_offset >= fid->hint_last_off)) {
				clu_offset -= fid->hint_last_off;
				clu.dir = fid->hint_last_clu;
			}

			while (clu_offset > 0) {
				/* clu.dir = FAT_read(sb, clu.dir); */
				if (FAT_read(sb, clu.dir, &(clu.dir)) == -1)
					return FFS_MEDIAERR;

				clu_offset--;
			}
		}
	}

	while (clu.dir != CLUSTER_32(~0)) {
		if (p_fs->dev_ejected)
			break;

		if (dir.dir == CLUSTER_32(0)) /* FAT16 root_dir */
			i = dentry % dentries_per_clu;
		else
			i = dentry & (dentries_per_clu-1);

		for ( ; i < dentries_per_clu; i++, dentry++) {
			ep = get_entry_in_dir(sb, &clu, i, &sector);
			if (!ep)
				return FFS_MEDIAERR;

			type = p_fs->fs_func->get_entry_type(ep);

			if (type == TYPE_UNUSED)
				break;

			if ((type != TYPE_FILE) && (type != TYPE_DIR))
				continue;

			buf_lock(sb, sector);
			dir_entry->Attr = p_fs->fs_func->get_entry_attr(ep);

			p_fs->fs_func->get_entry_time(ep, &tm, TM_CREATE);
			dir_entry->CreateTimestamp.Year = tm.year;
			dir_entry->CreateTimestamp.Month = tm.mon;
			dir_entry->CreateTimestamp.Day = tm.day;
			dir_entry->CreateTimestamp.Hour = tm.hour;
			dir_entry->CreateTimestamp.Minute = tm.min;
			dir_entry->CreateTimestamp.Second = tm.sec;
			dir_entry->CreateTimestamp.MilliSecond = 0;

			p_fs->fs_func->get_entry_time(ep, &tm, TM_MODIFY);
			dir_entry->ModifyTimestamp.Year = tm.year;
			dir_entry->ModifyTimestamp.Month = tm.mon;
			dir_entry->ModifyTimestamp.Day = tm.day;
			dir_entry->ModifyTimestamp.Hour = tm.hour;
			dir_entry->ModifyTimestamp.Minute = tm.min;
			dir_entry->ModifyTimestamp.Second = tm.sec;
			dir_entry->ModifyTimestamp.MilliSecond = 0;

			memset((char *) &dir_entry->AccessTimestamp, 0, sizeof(DATE_TIME_T));

			*(uni_name.name) = 0x0;
			p_fs->fs_func->get_uni_name_from_ext_entry(sb, &dir, dentry, uni_name.name);
			if (*(uni_name.name) == 0x0 && p_fs->vol_type != EXFAT)
				get_uni_name_from_dos_entry(sb, (DOS_DENTRY_T *) ep, &uni_name, 0x1);
			nls_uniname_to_cstring(sb, dir_entry->Name, &uni_name);
			buf_unlock(sb, sector);

			if (p_fs->vol_type == EXFAT) {
				ep = get_entry_in_dir(sb, &clu, i+1, NULL);
				if (!ep)
					return FFS_MEDIAERR;
			} else {
				get_uni_name_from_dos_entry(sb, (DOS_DENTRY_T *) ep, &uni_name, 0x0);
				nls_uniname_to_cstring(sb, dir_entry->ShortName, &uni_name);
			}

			dir_entry->Size = p_fs->fs_func->get_entry_size(ep);

			/* hint information */
			if (dir.dir == CLUSTER_32(0)) { /* FAT16 root_dir */
			} else {
				fid->hint_last_off = dentry >> dentries_per_clu_bits;
				fid->hint_last_clu = clu.dir;
			}

			fid->rwoffset = (s64) ++dentry;

			if (p_fs->dev_ejected)
				return FFS_MEDIAERR;

			return FFS_SUCCESS;
		}

		if (dir.dir == CLUSTER_32(0))
			break; /* FAT16 root_dir */

		if (clu.flags == 0x03) {
			if ((--clu.size) > 0)
				clu.dir++;
			else
				clu.dir = CLUSTER_32(~0);
		} else {
			/* clu.dir = FAT_read(sb, clu.dir); */
			if (FAT_read(sb, clu.dir, &(clu.dir)) == -1)
				return FFS_MEDIAERR;
		}
	}

	*(dir_entry->Name) = '\0';

	fid->rwoffset = (s64) ++dentry;

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	return FFS_SUCCESS;
} /* end of ffsReadDir */

/* ffsRemoveDir : remove a directory */
s32 ffsRemoveDir(struct inode *inode, FILE_ID_T *fid)
{
	s32 dentry;
	CHAIN_T dir, clu_to_free;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	dir.dir = fid->dir.dir;
	dir.size = fid->dir.size;
	dir.flags = fid->dir.flags;

	dentry = fid->entry;

	/* check if the file is "." or ".." */
	if (p_fs->vol_type != EXFAT) {
		if ((dir.dir != p_fs->root_dir) && (dentry < 2))
			return FFS_PERMISSIONERR;
	}

	clu_to_free.dir = fid->start_clu;
	clu_to_free.size = (s32)((fid->size-1) >> p_fs->cluster_size_bits) + 1;
	clu_to_free.flags = fid->flags;

	if (!is_dir_empty(sb, &clu_to_free))
		return FFS_FILEEXIST;

	fs_set_vol_flags(sb, VOL_DIRTY);

	/* (1) update the directory entry */
	remove_file(inode, &dir, dentry);

	/* (2) free the clusters */
	p_fs->fs_func->free_cluster(sb, &clu_to_free, 1);

	fid->size = 0;
	fid->start_clu = CLUSTER_32(~0);
	fid->flags = (p_fs->vol_type == EXFAT)? 0x03: 0x01;

#ifdef CONFIG_EXFAT_DELAYED_SYNC
	fs_sync(sb, 0);
	fs_set_vol_flags(sb, VOL_CLEAN);
#endif

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	return FFS_SUCCESS;
} /* end of ffsRemoveDir */

/*======================================================================*/
/*  Local Function Definitions                                          */
/*======================================================================*/

/*
 *  File System Management Functions
 */

s32 fs_init(void)
{
	/* critical check for system requirement on size of DENTRY_T structure */
	if (sizeof(DENTRY_T) != DENTRY_SIZE)
		return FFS_ALIGNMENTERR;

	if (sizeof(DOS_DENTRY_T) != DENTRY_SIZE)
		return FFS_ALIGNMENTERR;

	if (sizeof(EXT_DENTRY_T) != DENTRY_SIZE)
		return FFS_ALIGNMENTERR;

	if (sizeof(FILE_DENTRY_T) != DENTRY_SIZE)
		return FFS_ALIGNMENTERR;

	if (sizeof(STRM_DENTRY_T) != DENTRY_SIZE)
		return FFS_ALIGNMENTERR;

	if (sizeof(NAME_DENTRY_T) != DENTRY_SIZE)
		return FFS_ALIGNMENTERR;

	if (sizeof(BMAP_DENTRY_T) != DENTRY_SIZE)
		return FFS_ALIGNMENTERR;

	if (sizeof(CASE_DENTRY_T) != DENTRY_SIZE)
		return FFS_ALIGNMENTERR;

	if (sizeof(VOLM_DENTRY_T) != DENTRY_SIZE)
		return FFS_ALIGNMENTERR;

	return FFS_SUCCESS;
} /* end of fs_init */

s32 fs_shutdown(void)
{
	return FFS_SUCCESS;
} /* end of fs_shutdown */

void fs_set_vol_flags(struct super_block *sb, u32 new_flag)
{
	PBR_SECTOR_T *p_pbr;
	BPBEX_T *p_bpb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	if (p_fs->vol_flag == new_flag)
		return;

	p_fs->vol_flag = new_flag;

	if (p_fs->vol_type == EXFAT) {
		if (p_fs->pbr_bh == NULL) {
			if (sector_read(sb, p_fs->PBR_sector, &(p_fs->pbr_bh), 1) != FFS_SUCCESS)
				return;
		}

		p_pbr = (PBR_SECTOR_T *) p_fs->pbr_bh->b_data;
		p_bpb = (BPBEX_T *) p_pbr->bpb;
		SET16(p_bpb->vol_flags, (u16) new_flag);

		/* XXX duyoung
		 what can we do here? (cuz fs_set_vol_flags() is void) */
		if ((new_flag == VOL_DIRTY) && (!buffer_dirty(p_fs->pbr_bh)))
			sector_write(sb, p_fs->PBR_sector, p_fs->pbr_bh, 1);
		else
			sector_write(sb, p_fs->PBR_sector, p_fs->pbr_bh, 0);
	}
} /* end of fs_set_vol_flags */

void fs_sync(struct super_block *sb, s32 do_sync)
{
	if (do_sync)
		bdev_sync(sb);
} /* end of fs_sync */

void fs_error(struct super_block *sb)
{
	struct exfat_mount_options *opts = &EXFAT_SB(sb)->options;

	if (opts->errors == EXFAT_ERRORS_PANIC)
		panic("[EXFAT] Filesystem panic from previous error\n");
	else if ((opts->errors == EXFAT_ERRORS_RO) && !(sb->s_flags & MS_RDONLY)) {
		sb->s_flags |= MS_RDONLY;
		printk(KERN_ERR "[EXFAT] Filesystem has been set read-only\n");
	}
}

/*
 *  Cluster Management Functions
 */

s32 clear_cluster(struct super_block *sb, u32 clu)
{
	sector_t s, n;
	s32 ret = FFS_SUCCESS;
	struct buffer_head *tmp_bh = NULL;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);

	if (clu == CLUSTER_32(0)) { /* FAT16 root_dir */
		s = p_fs->root_start_sector;
		n = p_fs->data_start_sector;
	} else {
		s = START_SECTOR(clu);
		n = s + p_fs->sectors_per_clu;
	}

	for (; s < n; s++) {
		ret = sector_read(sb, s, &tmp_bh, 0);
		if (ret != FFS_SUCCESS)
			return ret;

		memset((char *) tmp_bh->b_data, 0x0, p_bd->sector_size);
		ret = sector_write(sb, s, tmp_bh, 0);
		if (ret != FFS_SUCCESS)
			break;
	}

	brelse(tmp_bh);
	return ret;
} /* end of clear_cluster */

s32 fat_alloc_cluster(struct super_block *sb, s32 num_alloc, CHAIN_T *p_chain)
{
	int i, num_clusters = 0;
	u32 new_clu, last_clu = CLUSTER_32(~0), read_clu;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	new_clu = p_chain->dir;
	if (new_clu == CLUSTER_32(~0))
		new_clu = p_fs->clu_srch_ptr;
	else if (new_clu >= p_fs->num_clusters)
		new_clu = 2;

	__set_sb_dirty(sb);

	p_chain->dir = CLUSTER_32(~0);

	for (i = 2; i < p_fs->num_clusters; i++) {
		if (FAT_read(sb, new_clu, &read_clu) != 0)
			return -1;

		if (read_clu == CLUSTER_32(0)) {
			if (FAT_write(sb, new_clu, CLUSTER_32(~0)) < 0)
				return -1;
			num_clusters++;

			if (p_chain->dir == CLUSTER_32(~0))
				p_chain->dir = new_clu;
			else {
				if (FAT_write(sb, last_clu, new_clu) < 0)
					return -1;
			}

			last_clu = new_clu;

			if ((--num_alloc) == 0) {
				p_fs->clu_srch_ptr = new_clu;
				if (p_fs->used_clusters != (u32) ~0)
					p_fs->used_clusters += num_clusters;

				return num_clusters;
			}
		}
		if ((++new_clu) >= p_fs->num_clusters)
			new_clu = 2;
	}

	p_fs->clu_srch_ptr = new_clu;
	if (p_fs->used_clusters != (u32) ~0)
		p_fs->used_clusters += num_clusters;

	return num_clusters;
} /* end of fat_alloc_cluster */

s32 exfat_alloc_cluster(struct super_block *sb, s32 num_alloc, CHAIN_T *p_chain)
{
	s32 num_clusters = 0;
	u32 hint_clu, new_clu, last_clu = CLUSTER_32(~0);
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	hint_clu = p_chain->dir;
	if (hint_clu == CLUSTER_32(~0)) {
		hint_clu = test_alloc_bitmap(sb, p_fs->clu_srch_ptr-2);
		if (hint_clu == CLUSTER_32(~0))
			return 0;
	} else if (hint_clu >= p_fs->num_clusters) {
		hint_clu = 2;
		p_chain->flags = 0x01;
	}

	__set_sb_dirty(sb);

	p_chain->dir = CLUSTER_32(~0);

	while ((new_clu = test_alloc_bitmap(sb, hint_clu-2)) != CLUSTER_32(~0)) {
		if (new_clu != hint_clu) {
			if (p_chain->flags == 0x03) {
				exfat_chain_cont_cluster(sb, p_chain->dir, num_clusters);
				p_chain->flags = 0x01;
			}
		}

		if (set_alloc_bitmap(sb, new_clu-2) != FFS_SUCCESS)
			return -1;

		num_clusters++;

		if (p_chain->flags == 0x01) {
			if (FAT_write(sb, new_clu, CLUSTER_32(~0)) < 0)
				return -1;
		}

		if (p_chain->dir == CLUSTER_32(~0)) {
			p_chain->dir = new_clu;
		} else {
			if (p_chain->flags == 0x01) {
				if (FAT_write(sb, last_clu, new_clu) < 0)
					return -1;
			}
		}
		last_clu = new_clu;

		if ((--num_alloc) == 0) {
			p_fs->clu_srch_ptr = hint_clu;
			if (p_fs->used_clusters != (u32) ~0)
				p_fs->used_clusters += num_clusters;

			p_chain->size += num_clusters;
			return num_clusters;
		}

		hint_clu = new_clu + 1;
		if (hint_clu >= p_fs->num_clusters) {
			hint_clu = 2;

			if (p_chain->flags == 0x03) {
				exfat_chain_cont_cluster(sb, p_chain->dir, num_clusters);
				p_chain->flags = 0x01;
			}
		}
	}

	p_fs->clu_srch_ptr = hint_clu;
	if (p_fs->used_clusters != (u32) ~0)
		p_fs->used_clusters += num_clusters;

	p_chain->size += num_clusters;
	return num_clusters;
} /* end of exfat_alloc_cluster */

void fat_free_cluster(struct super_block *sb, CHAIN_T *p_chain, s32 do_relse)
{
	s32 num_clusters = 0;
	u32 clu, prev;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	int i;
	sector_t sector;

	if ((p_chain->dir == CLUSTER_32(0)) || (p_chain->dir == CLUSTER_32(~0)))
		return;
	__set_sb_dirty(sb);
	clu = p_chain->dir;

	if (p_chain->size <= 0)
		return;

	do {
		if (p_fs->dev_ejected)
			break;

		if (do_relse) {
			sector = START_SECTOR(clu);
			for (i = 0; i < p_fs->sectors_per_clu; i++)
				buf_release(sb, sector+i);
		}

		prev = clu;
		if (FAT_read(sb, clu, &clu) == -1)
			break;

		if (FAT_write(sb, prev, CLUSTER_32(0)) < 0)
			break;
		num_clusters++;

	} while (clu != CLUSTER_32(~0));

	if (p_fs->used_clusters != (u32) ~0)
		p_fs->used_clusters -= num_clusters;
} /* end of fat_free_cluster */

void exfat_free_cluster(struct super_block *sb, CHAIN_T *p_chain, s32 do_relse)
{
	s32 num_clusters = 0;
	u32 clu;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	int i;
	sector_t sector;

	if ((p_chain->dir == CLUSTER_32(0)) || (p_chain->dir == CLUSTER_32(~0)))
		return;

	if (p_chain->size <= 0) {
		printk(KERN_ERR "[EXFAT] free_cluster : skip free-req clu:%u, "
				"because of zero-size truncation\n"
				,p_chain->dir);
		return;
	}

	__set_sb_dirty(sb);
	clu = p_chain->dir;

	if (p_chain->flags == 0x03) {
		do {
			if (do_relse) {
				sector = START_SECTOR(clu);
				for (i = 0; i < p_fs->sectors_per_clu; i++)
					buf_release(sb, sector+i);
			}

			if (clr_alloc_bitmap(sb, clu-2) != FFS_SUCCESS)
				break;
			clu++;

			num_clusters++;
		} while (num_clusters < p_chain->size);
	} else {
		do {
			if (p_fs->dev_ejected)
				break;

			if (do_relse) {
				sector = START_SECTOR(clu);
				for (i = 0; i < p_fs->sectors_per_clu; i++)
					buf_release(sb, sector+i);
			}

			if (clr_alloc_bitmap(sb, clu-2) != FFS_SUCCESS)
				break;

			if (FAT_read(sb, clu, &clu) == -1)
				break;
			num_clusters++;
		} while ((clu != CLUSTER_32(0)) && (clu != CLUSTER_32(~0)));
	}

	if (p_fs->used_clusters != (u32) ~0)
		p_fs->used_clusters -= num_clusters;
} /* end of exfat_free_cluster */

u32 find_last_cluster(struct super_block *sb, CHAIN_T *p_chain)
{
	u32 clu, next;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	clu = p_chain->dir;

	if (p_chain->flags == 0x03) {
		clu += p_chain->size - 1;
	} else {
		while ((FAT_read(sb, clu, &next) == 0) && (next != CLUSTER_32(~0))) {
			if (p_fs->dev_ejected)
				break;
			clu = next;
		}
	}

	return clu;
} /* end of find_last_cluster */

s32 count_num_clusters(struct super_block *sb, CHAIN_T *p_chain)
{
	int i, count = 0;
	u32 clu;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	if ((p_chain->dir == CLUSTER_32(0)) || (p_chain->dir == CLUSTER_32(~0)))
		return 0;

	clu = p_chain->dir;

	if (p_chain->flags == 0x03) {
		count = p_chain->size;
	} else {
		for (i = 2; i < p_fs->num_clusters; i++) {
			count++;
			if (FAT_read(sb, clu, &clu) != 0)
				return 0;
			if (clu == CLUSTER_32(~0))
				break;
		}
	}

	return count;
} /* end of count_num_clusters */

s32 fat_count_used_clusters(struct super_block *sb)
{
	int i, count = 0;
	u32 clu;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	for (i = 2; i < p_fs->num_clusters; i++) {
		if (FAT_read(sb, i, &clu) != 0)
			break;
		if (clu != CLUSTER_32(0))
			count++;
	}

	return count;
} /* end of fat_count_used_clusters */

s32 exfat_count_used_clusters(struct super_block *sb)
{
	int i, map_i, map_b, count = 0;
	u8 k;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);

	map_i = map_b = 0;

	for (i = 2; i < p_fs->num_clusters; i += 8) {
		k = *(((u8 *) p_fs->vol_amap[map_i]->b_data) + map_b);
		count += used_bit[k];

		if ((++map_b) >= p_bd->sector_size) {
			map_i++;
			map_b = 0;
		}
	}

	return count;
} /* end of exfat_count_used_clusters */

void exfat_chain_cont_cluster(struct super_block *sb, u32 chain, s32 len)
{
	if (len == 0)
		return;

	while (len > 1) {
		if (FAT_write(sb, chain, chain+1) < 0)
			break;
		chain++;
		len--;
	}
	FAT_write(sb, chain, CLUSTER_32(~0));
} /* end of exfat_chain_cont_cluster */

/*
 *  Allocation Bitmap Management Functions
 */

s32 load_alloc_bitmap(struct super_block *sb)
{
	int i, j, ret;
	u32 map_size;
	u32 type;
	sector_t sector;
	CHAIN_T clu;
	BMAP_DENTRY_T *ep;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);

	clu.dir = p_fs->root_dir;
	clu.flags = 0x01;

	while (clu.dir != CLUSTER_32(~0)) {
		if (p_fs->dev_ejected)
			break;

		for (i = 0; i < p_fs->dentries_per_clu; i++) {
			ep = (BMAP_DENTRY_T *) get_entry_in_dir(sb, &clu, i, NULL);
			if (!ep)
				return FFS_MEDIAERR;

			type = p_fs->fs_func->get_entry_type((DENTRY_T *) ep);

			if (type == TYPE_UNUSED)
				break;
			if (type != TYPE_BITMAP)
				continue;

			if (ep->flags == 0x0) {
				p_fs->map_clu  = GET32_A(ep->start_clu);
				map_size = (u32) GET64_A(ep->size);

				p_fs->map_sectors = ((map_size-1) >> p_bd->sector_size_bits) + 1;

				p_fs->vol_amap = (struct buffer_head **) kmalloc(sizeof(struct buffer_head *) * p_fs->map_sectors, GFP_KERNEL);
				if (p_fs->vol_amap == NULL)
					return FFS_MEMORYERR;

				sector = START_SECTOR(p_fs->map_clu);

				for (j = 0; j < p_fs->map_sectors; j++) {
					p_fs->vol_amap[j] = NULL;
					ret = sector_read(sb, sector+j, &(p_fs->vol_amap[j]), 1);
					if (ret != FFS_SUCCESS) {
						/*  release all buffers and free vol_amap */
						i = 0;
						while (i < j)
							brelse(p_fs->vol_amap[i++]);

						if (p_fs->vol_amap)
							kfree(p_fs->vol_amap);
						p_fs->vol_amap = NULL;
						return ret;
					}
				}

				p_fs->pbr_bh = NULL;
				return FFS_SUCCESS;
			}
		}

		if (FAT_read(sb, clu.dir, &(clu.dir)) != 0)
			return FFS_MEDIAERR;
	}

	return FFS_FORMATERR;
} /* end of load_alloc_bitmap */

void free_alloc_bitmap(struct super_block *sb)
{
	int i;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	brelse(p_fs->pbr_bh);

	for (i = 0; i < p_fs->map_sectors; i++)
		__brelse(p_fs->vol_amap[i]);

	if (p_fs->vol_amap)
		kfree(p_fs->vol_amap);
	p_fs->vol_amap = NULL;
} /* end of free_alloc_bitmap */

s32 set_alloc_bitmap(struct super_block *sb, u32 clu)
{
	int i, b;
	sector_t sector;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);

	i = clu >> (p_bd->sector_size_bits + 3);
	b = clu & ((p_bd->sector_size << 3) - 1);

	sector = START_SECTOR(p_fs->map_clu) + i;

	exfat_bitmap_set((u8 *) p_fs->vol_amap[i]->b_data, b);

	return sector_write(sb, sector, p_fs->vol_amap[i], 0);
} /* end of set_alloc_bitmap */

s32 clr_alloc_bitmap(struct super_block *sb, u32 clu)
{
	int i, b;
	sector_t sector;
#ifdef CONFIG_EXFAT_DISCARD
	struct exfat_sb_info *sbi = EXFAT_SB(sb);
	struct exfat_mount_options *opts = &sbi->options;
	int ret;
#endif /* CONFIG_EXFAT_DISCARD */
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);

	i = clu >> (p_bd->sector_size_bits + 3);
	b = clu & ((p_bd->sector_size << 3) - 1);

	sector = START_SECTOR(p_fs->map_clu) + i;

	exfat_bitmap_clear((u8 *) p_fs->vol_amap[i]->b_data, b);

	return sector_write(sb, sector, p_fs->vol_amap[i], 0);

#ifdef CONFIG_EXFAT_DISCARD
	if (opts->discard) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37)
		ret = sb_issue_discard(sb, START_SECTOR(clu), (1 << p_fs->sectors_per_clu_bits));
#else
		ret = sb_issue_discard(sb, START_SECTOR(clu), (1 << p_fs->sectors_per_clu_bits), GFP_NOFS, 0);
#endif
		if (ret == -EOPNOTSUPP) {
			printk(KERN_WARNING "discard not supported by device, disabling");
			opts->discard = 0;
		}
	}
#endif /* CONFIG_EXFAT_DISCARD */
} /* end of clr_alloc_bitmap */

u32 test_alloc_bitmap(struct super_block *sb, u32 clu)
{
	int i, map_i, map_b;
	u32 clu_base, clu_free;
	u8 k, clu_mask;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);

	clu_base = (clu & ~(0x7)) + 2;
	clu_mask = (1 << (clu - clu_base + 2)) - 1;

	map_i = clu >> (p_bd->sector_size_bits + 3);
	map_b = (clu >> 3) & p_bd->sector_size_mask;

	for (i = 2; i < p_fs->num_clusters; i += 8) {
		k = *(((u8 *) p_fs->vol_amap[map_i]->b_data) + map_b);
		if (clu_mask > 0) {
			k |= clu_mask;
			clu_mask = 0;
		}
		if (k < 0xFF) {
			clu_free = clu_base + free_bit[k];
			if (clu_free < p_fs->num_clusters)
				return clu_free;
		}
		clu_base += 8;

		if (((++map_b) >= p_bd->sector_size) || (clu_base >= p_fs->num_clusters)) {
			if ((++map_i) >= p_fs->map_sectors) {
				clu_base = 2;
				map_i = 0;
			}
			map_b = 0;
		}
	}

	return CLUSTER_32(~0);
} /* end of test_alloc_bitmap */

void sync_alloc_bitmap(struct super_block *sb)
{
	int i;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	if (p_fs->vol_amap == NULL)
		return;

	for (i = 0; i < p_fs->map_sectors; i++)
		sync_dirty_buffer(p_fs->vol_amap[i]);
} /* end of sync_alloc_bitmap */

/*
 *  Upcase table Management Functions
 */
s32 __load_upcase_table(struct super_block *sb, sector_t sector, u32 num_sectors, u32 utbl_checksum)
{
	int i, ret = FFS_ERROR;
	u32 j;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);
	struct buffer_head *tmp_bh = NULL;
	sector_t end_sector = num_sectors + sector;

	u8	skip = FALSE;
	u32	index = 0;
	u16	uni = 0;
	u16 **upcase_table;

	u32 checksum = 0;

	upcase_table = p_fs->vol_utbl = (u16 **) kmalloc(UTBL_COL_COUNT * sizeof(u16 *), GFP_KERNEL);
	if (upcase_table == NULL)
		return FFS_MEMORYERR;
	memset(upcase_table, 0, UTBL_COL_COUNT * sizeof(u16 *));

	while (sector < end_sector) {
		ret = sector_read(sb, sector, &tmp_bh, 1);
		if (ret != FFS_SUCCESS) {
			DPRINTK("sector read (0x%llX)fail\n", (unsigned long long)sector);
			goto error;
		}
		sector++;

		for (i = 0; i < p_bd->sector_size && index <= 0xFFFF; i += 2) {
			uni = GET16(((u8 *) tmp_bh->b_data)+i);

			checksum = ((checksum & 1) ? 0x80000000 : 0) + (checksum >> 1) + *(((u8 *) tmp_bh->b_data)+i);
			checksum = ((checksum & 1) ? 0x80000000 : 0) + (checksum >> 1) + *(((u8 *) tmp_bh->b_data)+(i+1));

			if (skip) {
				DPRINTK("skip from 0x%X ", index);
				index += uni;
				DPRINTK("to 0x%X (amount of 0x%X)\n", index, uni);
				skip = FALSE;
			} else if (uni == index)
				index++;
			else if (uni == 0xFFFF)
				skip = TRUE;
			else { /* uni != index , uni != 0xFFFF */
				u16 col_index = get_col_index(index);

				if (upcase_table[col_index] == NULL) {
					DPRINTK("alloc = 0x%X\n", col_index);
					upcase_table[col_index] = (u16 *) kmalloc(UTBL_ROW_COUNT * sizeof(u16), GFP_KERNEL);
					if (upcase_table[col_index] == NULL) {
						ret = FFS_MEMORYERR;
						goto error;
					}

					for (j = 0; j < UTBL_ROW_COUNT; j++)
						upcase_table[col_index][j] = (col_index << LOW_INDEX_BIT) | j;
				}

				upcase_table[col_index][get_row_index(index)] = uni;
				index++;
			}
		}
	}
	if (index >= 0xFFFF && utbl_checksum == checksum) {
		if (tmp_bh)
			brelse(tmp_bh);
		return FFS_SUCCESS;
	}
	ret = FFS_ERROR;
error:
	if (tmp_bh)
		brelse(tmp_bh);
	free_upcase_table(sb);
	return ret;
}

s32 __load_default_upcase_table(struct super_block *sb)
{
	int i, ret = FFS_ERROR;
	u32 j;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	u8	skip = FALSE;
	u32	index = 0;
	u16	uni = 0;
	u16 **upcase_table;

	upcase_table = p_fs->vol_utbl = (u16 **) kmalloc(UTBL_COL_COUNT * sizeof(u16 *), GFP_KERNEL);
	if (upcase_table == NULL)
		return FFS_MEMORYERR;
	memset(upcase_table, 0, UTBL_COL_COUNT * sizeof(u16 *));

	for (i = 0; index <= 0xFFFF && i < NUM_UPCASE*2; i += 2) {
		uni = GET16(uni_upcase + i);
		if (skip) {
			DPRINTK("skip from 0x%X ", index);
			index += uni;
			DPRINTK("to 0x%X (amount of 0x%X)\n", index, uni);
			skip = FALSE;
		} else if (uni == index)
			index++;
		else if (uni == 0xFFFF)
			skip = TRUE;
		else { /* uni != index , uni != 0xFFFF */
			u16 col_index = get_col_index(index);

			if (upcase_table[col_index] == NULL) {
				DPRINTK("alloc = 0x%X\n", col_index);
				upcase_table[col_index] = (u16 *) kmalloc(UTBL_ROW_COUNT * sizeof(u16), GFP_KERNEL);
				if (upcase_table[col_index] == NULL) {
					ret = FFS_MEMORYERR;
					goto error;
				}

				for (j = 0; j < UTBL_ROW_COUNT; j++)
					upcase_table[col_index][j] = (col_index << LOW_INDEX_BIT) | j;
			}

			upcase_table[col_index][get_row_index(index)] = uni;
			index++;
		}
	}

	if (index >= 0xFFFF)
		return FFS_SUCCESS;

error:
	/* FATAL error: default upcase table has error */
	free_upcase_table(sb);
	return ret;
}

s32 load_upcase_table(struct super_block *sb)
{
	int i;
	u32 tbl_clu, tbl_size;
	sector_t sector;
	u32 type, num_sectors;
	CHAIN_T clu;
	CASE_DENTRY_T *ep;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);

	clu.dir = p_fs->root_dir;
	clu.flags = 0x01;

	if (p_fs->dev_ejected)
		return FFS_MEDIAERR;

	while (clu.dir != CLUSTER_32(~0)) {
		for (i = 0; i < p_fs->dentries_per_clu; i++) {
			ep = (CASE_DENTRY_T *) get_entry_in_dir(sb, &clu, i, NULL);
			if (!ep)
				return FFS_MEDIAERR;

			type = p_fs->fs_func->get_entry_type((DENTRY_T *) ep);

			if (type == TYPE_UNUSED)
				break;
			if (type != TYPE_UPCASE)
				continue;

			tbl_clu  = GET32_A(ep->start_clu);
			tbl_size = (u32) GET64_A(ep->size);

			sector = START_SECTOR(tbl_clu);
			num_sectors = ((tbl_size-1) >> p_bd->sector_size_bits) + 1;
			if (__load_upcase_table(sb, sector, num_sectors, GET32_A(ep->checksum)) != FFS_SUCCESS)
				break;
			else
				return FFS_SUCCESS;
		}
		if (FAT_read(sb, clu.dir, &(clu.dir)) != 0)
			return FFS_MEDIAERR;
	}
	/* load default upcase table */
	return __load_default_upcase_table(sb);
} /* end of load_upcase_table */

void free_upcase_table(struct super_block *sb)
{
	u32 i;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	u16 **upcase_table;

	upcase_table = p_fs->vol_utbl;
	for (i = 0; i < UTBL_COL_COUNT; i++) {
		if (upcase_table[i])
			kfree(upcase_table[i]);
	}

	if (p_fs->vol_utbl)
		kfree(p_fs->vol_utbl);
	p_fs->vol_utbl = NULL;
} /* end of free_upcase_table */

/*
 *  Directory Entry Management Functions
 */

u32 fat_get_entry_type(DENTRY_T *p_entry)
{
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;

	if (*(ep->name) == 0x0)
		return TYPE_UNUSED;

	else if (*(ep->name) == 0xE5)
		return TYPE_DELETED;

	else if (ep->attr == ATTR_EXTEND)
		return TYPE_EXTEND;

	else if ((ep->attr & (ATTR_SUBDIR|ATTR_VOLUME)) == ATTR_VOLUME)
		return TYPE_VOLUME;

	else if ((ep->attr & (ATTR_SUBDIR|ATTR_VOLUME)) == ATTR_SUBDIR)
		return TYPE_DIR;

	return TYPE_FILE;
} /* end of fat_get_entry_type */

u32 exfat_get_entry_type(DENTRY_T *p_entry)
{
	FILE_DENTRY_T *ep = (FILE_DENTRY_T *) p_entry;

	if (ep->type == 0x0) {
		return TYPE_UNUSED;
	} else if (ep->type < 0x80) {
		return TYPE_DELETED;
	} else if (ep->type == 0x80) {
		return TYPE_INVALID;
	} else if (ep->type < 0xA0) {
		if (ep->type == 0x81) {
			return TYPE_BITMAP;
		} else if (ep->type == 0x82) {
			return TYPE_UPCASE;
		} else if (ep->type == 0x83) {
			return TYPE_VOLUME;
		} else if (ep->type == 0x85) {
			if (GET16_A(ep->attr) & ATTR_SUBDIR)
				return TYPE_DIR;
			else
				return TYPE_FILE;
		}
		return TYPE_CRITICAL_PRI;
	} else if (ep->type < 0xC0) {
		if (ep->type == 0xA0)
			return TYPE_GUID;
		else if (ep->type == 0xA1)
			return TYPE_PADDING;
		else if (ep->type == 0xA2)
			return TYPE_ACLTAB;
		return TYPE_BENIGN_PRI;
	} else if (ep->type < 0xE0) {
		if (ep->type == 0xC0)
			return TYPE_STREAM;
		else if (ep->type == 0xC1)
			return TYPE_EXTEND;
		else if (ep->type == 0xC2)
			return TYPE_ACL;
		return TYPE_CRITICAL_SEC;
	}

	return TYPE_BENIGN_SEC;
} /* end of exfat_get_entry_type */

void fat_set_entry_type(DENTRY_T *p_entry, u32 type)
{
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;

	if (type == TYPE_UNUSED)
		*(ep->name) = 0x0;

	else if (type == TYPE_DELETED)
		*(ep->name) = 0xE5;

	else if (type == TYPE_EXTEND)
		ep->attr = ATTR_EXTEND;

	else if (type == TYPE_DIR)
		ep->attr = ATTR_SUBDIR;

	else if (type == TYPE_FILE)
		ep->attr = ATTR_ARCHIVE;

	else if (type == TYPE_SYMLINK)
		ep->attr = ATTR_ARCHIVE | ATTR_SYMLINK;
} /* end of fat_set_entry_type */

void exfat_set_entry_type(DENTRY_T *p_entry, u32 type)
{
	FILE_DENTRY_T *ep = (FILE_DENTRY_T *) p_entry;

	if (type == TYPE_UNUSED) {
		ep->type = 0x0;
	} else if (type == TYPE_DELETED) {
		ep->type &= ~0x80;
	} else if (type == TYPE_STREAM) {
		ep->type = 0xC0;
	} else if (type == TYPE_EXTEND) {
		ep->type = 0xC1;
	} else if (type == TYPE_BITMAP) {
		ep->type = 0x81;
	} else if (type == TYPE_UPCASE) {
		ep->type = 0x82;
	} else if (type == TYPE_VOLUME) {
		ep->type = 0x83;
	} else if (type == TYPE_DIR) {
		ep->type = 0x85;
		SET16_A(ep->attr, ATTR_SUBDIR);
	} else if (type == TYPE_FILE) {
		ep->type = 0x85;
		SET16_A(ep->attr, ATTR_ARCHIVE);
	} else if (type == TYPE_SYMLINK) {
		ep->type = 0x85;
		SET16_A(ep->attr, ATTR_ARCHIVE | ATTR_SYMLINK);
	}
} /* end of exfat_set_entry_type */

u32 fat_get_entry_attr(DENTRY_T *p_entry)
{
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
	return (u32) ep->attr;
} /* end of fat_get_entry_attr */

u32 exfat_get_entry_attr(DENTRY_T *p_entry)
{
	FILE_DENTRY_T *ep = (FILE_DENTRY_T *) p_entry;
	return (u32) GET16_A(ep->attr);
} /* end of exfat_get_entry_attr */

void fat_set_entry_attr(DENTRY_T *p_entry, u32 attr)
{
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
	ep->attr = (u8) attr;
} /* end of fat_set_entry_attr */

void exfat_set_entry_attr(DENTRY_T *p_entry, u32 attr)
{
	FILE_DENTRY_T *ep = (FILE_DENTRY_T *) p_entry;
	SET16_A(ep->attr, (u16) attr);
} /* end of exfat_set_entry_attr */

u8 fat_get_entry_flag(DENTRY_T *p_entry)
{
	return 0x01;
} /* end of fat_get_entry_flag */

u8 exfat_get_entry_flag(DENTRY_T *p_entry)
{
	STRM_DENTRY_T *ep = (STRM_DENTRY_T *) p_entry;
	return ep->flags;
} /* end of exfat_get_entry_flag */

void fat_set_entry_flag(DENTRY_T *p_entry, u8 flags)
{
} /* end of fat_set_entry_flag */

void exfat_set_entry_flag(DENTRY_T *p_entry, u8 flags)
{
	STRM_DENTRY_T *ep = (STRM_DENTRY_T *) p_entry;
	ep->flags = flags;
} /* end of exfat_set_entry_flag */

u32 fat_get_entry_clu0(DENTRY_T *p_entry)
{
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
	return ((u32) GET16_A(ep->start_clu_hi) << 16) | GET16_A(ep->start_clu_lo);
} /* end of fat_get_entry_clu0 */

u32 exfat_get_entry_clu0(DENTRY_T *p_entry)
{
	STRM_DENTRY_T *ep = (STRM_DENTRY_T *) p_entry;
	return GET32_A(ep->start_clu);
} /* end of exfat_get_entry_clu0 */

void fat_set_entry_clu0(DENTRY_T *p_entry, u32 start_clu)
{
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
	SET16_A(ep->start_clu_lo, CLUSTER_16(start_clu));
	SET16_A(ep->start_clu_hi, CLUSTER_16(start_clu >> 16));
} /* end of fat_set_entry_clu0 */

void exfat_set_entry_clu0(DENTRY_T *p_entry, u32 start_clu)
{
	STRM_DENTRY_T *ep = (STRM_DENTRY_T *) p_entry;
	SET32_A(ep->start_clu, start_clu);
} /* end of exfat_set_entry_clu0 */

u64 fat_get_entry_size(DENTRY_T *p_entry)
{
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
	return (u64) GET32_A(ep->size);
} /* end of fat_get_entry_size */

u64 exfat_get_entry_size(DENTRY_T *p_entry)
{
	STRM_DENTRY_T *ep = (STRM_DENTRY_T *) p_entry;
	return GET64_A(ep->valid_size);
} /* end of exfat_get_entry_size */

void fat_set_entry_size(DENTRY_T *p_entry, u64 size)
{
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;
	SET32_A(ep->size, (u32) size);
} /* end of fat_set_entry_size */

void exfat_set_entry_size(DENTRY_T *p_entry, u64 size)
{
	STRM_DENTRY_T *ep = (STRM_DENTRY_T *) p_entry;
	SET64_A(ep->valid_size, size);
	SET64_A(ep->size, size);
} /* end of exfat_set_entry_size */

void fat_get_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode)
{
	u16 t = 0x00, d = 0x21;
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;

	switch (mode) {
	case TM_CREATE:
		t = GET16_A(ep->create_time);
		d = GET16_A(ep->create_date);
		break;
	case TM_MODIFY:
		t = GET16_A(ep->modify_time);
		d = GET16_A(ep->modify_date);
		break;
	}

	tp->sec  = (t & 0x001F) << 1;
	tp->min  = (t >> 5) & 0x003F;
	tp->hour = (t >> 11);
	tp->day  = (d & 0x001F);
	tp->mon  = (d >> 5) & 0x000F;
	tp->year = (d >> 9);
} /* end of fat_get_entry_time */

void exfat_get_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode)
{
	u16 t = 0x00, d = 0x21;
	FILE_DENTRY_T *ep = (FILE_DENTRY_T *) p_entry;

	switch (mode) {
	case TM_CREATE:
		t = GET16_A(ep->create_time);
		d = GET16_A(ep->create_date);
		break;
	case TM_MODIFY:
		t = GET16_A(ep->modify_time);
		d = GET16_A(ep->modify_date);
		break;
	case TM_ACCESS:
		t = GET16_A(ep->access_time);
		d = GET16_A(ep->access_date);
		break;
	}

	tp->sec  = (t & 0x001F) << 1;
	tp->min  = (t >> 5) & 0x003F;
	tp->hour = (t >> 11);
	tp->day  = (d & 0x001F);
	tp->mon  = (d >> 5) & 0x000F;
	tp->year = (d >> 9);
} /* end of exfat_get_entry_time */

void fat_set_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode)
{
	u16 t, d;
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *) p_entry;

	t = (tp->hour << 11) | (tp->min << 5) | (tp->sec >> 1);
	d = (tp->year <<  9) | (tp->mon << 5) |  tp->day;

	switch (mode) {
	case TM_CREATE:
		SET16_A(ep->create_time, t);
		SET16_A(ep->create_date, d);
		break;
	case TM_MODIFY:
		SET16_A(ep->modify_time, t);
		SET16_A(ep->modify_date, d);
		break;
	}
} /* end of fat_set_entry_time */

void exfat_set_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode)
{
	u16 t, d;
	FILE_DENTRY_T *ep = (FILE_DENTRY_T *) p_entry;

	t = (tp->hour << 11) | (tp->min << 5) | (tp->sec >> 1);
	d = (tp->year <<  9) | (tp->mon << 5) |  tp->day;

	switch (mode) {
	case TM_CREATE:
		SET16_A(ep->create_time, t);
		SET16_A(ep->create_date, d);
		break;
	case TM_MODIFY:
		SET16_A(ep->modify_time, t);
		SET16_A(ep->modify_date, d);
		break;
	case TM_ACCESS:
		SET16_A(ep->access_time, t);
		SET16_A(ep->access_date, d);
		break;
	}
} /* end of exfat_set_entry_time */

s32 fat_init_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u32 type,
						 u32 start_clu, u64 size)
{
	sector_t sector;
	DOS_DENTRY_T *dos_ep;

	dos_ep = (DOS_DENTRY_T *) get_entry_in_dir(sb, p_dir, entry, &sector);
	if (!dos_ep)
		return FFS_MEDIAERR;

	init_dos_entry(dos_ep, type, start_clu);
	buf_modify(sb, sector);

	return FFS_SUCCESS;
} /* end of fat_init_dir_entry */

s32 exfat_init_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u32 type,
						   u32 start_clu, u64 size)
{
	sector_t sector;
	u8 flags;
	FILE_DENTRY_T *file_ep;
	STRM_DENTRY_T *strm_ep;

	flags = (type == TYPE_FILE) ? 0x01 : 0x03;

	/* we cannot use get_entry_set_in_dir here because file ep is not initialized yet */
	file_ep = (FILE_DENTRY_T *) get_entry_in_dir(sb, p_dir, entry, &sector);
	if (!file_ep)
		return FFS_MEDIAERR;

	strm_ep = (STRM_DENTRY_T *) get_entry_in_dir(sb, p_dir, entry+1, &sector);
	if (!strm_ep)
		return FFS_MEDIAERR;

	init_file_entry(file_ep, type);
	buf_modify(sb, sector);

	init_strm_entry(strm_ep, flags, start_clu, size);
	buf_modify(sb, sector);

	return FFS_SUCCESS;
} /* end of exfat_init_dir_entry */

s32 fat_init_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 num_entries,
						 UNI_NAME_T *p_uniname, DOS_NAME_T *p_dosname)
{
	int i;
	sector_t sector;
	u8 chksum;
	u16 *uniname = p_uniname->name;
	DOS_DENTRY_T *dos_ep;
	EXT_DENTRY_T *ext_ep;

	dos_ep = (DOS_DENTRY_T *) get_entry_in_dir(sb, p_dir, entry, &sector);
	if (!dos_ep)
		return FFS_MEDIAERR;

	dos_ep->lcase = p_dosname->name_case;
	memcpy(dos_ep->name, p_dosname->name, DOS_NAME_LENGTH);
	buf_modify(sb, sector);

	if ((--num_entries) > 0) {
		chksum = calc_checksum_1byte((void *) dos_ep->name, DOS_NAME_LENGTH, 0);

		for (i = 1; i < num_entries; i++) {
			ext_ep = (EXT_DENTRY_T *) get_entry_in_dir(sb, p_dir, entry-i, &sector);
			if (!ext_ep)
				return FFS_MEDIAERR;

			init_ext_entry(ext_ep, i, chksum, uniname);
			buf_modify(sb, sector);
			uniname += 13;
		}

		ext_ep = (EXT_DENTRY_T *) get_entry_in_dir(sb, p_dir, entry-i, &sector);
		if (!ext_ep)
			return FFS_MEDIAERR;

		init_ext_entry(ext_ep, i+0x40, chksum, uniname);
		buf_modify(sb, sector);
	}

	return FFS_SUCCESS;
} /* end of fat_init_ext_entry */

s32 exfat_init_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 num_entries,
						   UNI_NAME_T *p_uniname, DOS_NAME_T *p_dosname)
{
	int i;
	sector_t sector;
	u16 *uniname = p_uniname->name;
	FILE_DENTRY_T *file_ep;
	STRM_DENTRY_T *strm_ep;
	NAME_DENTRY_T *name_ep;

	file_ep = (FILE_DENTRY_T *) get_entry_in_dir(sb, p_dir, entry, &sector);
	if (!file_ep)
		return FFS_MEDIAERR;

	file_ep->num_ext = (u8)(num_entries - 1);
	buf_modify(sb, sector);

	strm_ep = (STRM_DENTRY_T *) get_entry_in_dir(sb, p_dir, entry+1, &sector);
	if (!strm_ep)
		return FFS_MEDIAERR;

	strm_ep->name_len = p_uniname->name_len;
	SET16_A(strm_ep->name_hash, p_uniname->name_hash);
	buf_modify(sb, sector);

	for (i = 2; i < num_entries; i++) {
		name_ep = (NAME_DENTRY_T *) get_entry_in_dir(sb, p_dir, entry+i, &sector);
		if (!name_ep)
			return FFS_MEDIAERR;

		init_name_entry(name_ep, uniname);
		buf_modify(sb, sector);
		uniname += 15;
	}

	update_dir_checksum(sb, p_dir, entry);

	return FFS_SUCCESS;
} /* end of exfat_init_ext_entry */

void init_dos_entry(DOS_DENTRY_T *ep, u32 type, u32 start_clu)
{
	TIMESTAMP_T tm, *tp;

	fat_set_entry_type((DENTRY_T *) ep, type);
	SET16_A(ep->start_clu_lo, CLUSTER_16(start_clu));
	SET16_A(ep->start_clu_hi, CLUSTER_16(start_clu >> 16));
	SET32_A(ep->size, 0);

	tp = tm_current(&tm);
	fat_set_entry_time((DENTRY_T *) ep, tp, TM_CREATE);
	fat_set_entry_time((DENTRY_T *) ep, tp, TM_MODIFY);
	SET16_A(ep->access_date, 0);
	ep->create_time_ms = 0;
} /* end of init_dos_entry */

void init_ext_entry(EXT_DENTRY_T *ep, s32 order, u8 chksum, u16 *uniname)
{
	int i;
	u8 end = FALSE;

	fat_set_entry_type((DENTRY_T *) ep, TYPE_EXTEND);
	ep->order = (u8) order;
	ep->sysid = 0;
	ep->checksum = chksum;
	SET16_A(ep->start_clu, 0);

	for (i = 0; i < 10; i += 2) {
		if (!end) {
			SET16(ep->unicode_0_4+i, *uniname);
			if (*uniname == 0x0)
				end = TRUE;
			else
				uniname++;
		} else {
			SET16(ep->unicode_0_4+i, 0xFFFF);
		}
	}

	for (i = 0; i < 12; i += 2) {
		if (!end) {
			SET16_A(ep->unicode_5_10+i, *uniname);
			if (*uniname == 0x0)
				end = TRUE;
			else
				uniname++;
		} else {
			SET16_A(ep->unicode_5_10+i, 0xFFFF);
		}
	}

	for (i = 0; i < 4; i += 2) {
		if (!end) {
			SET16_A(ep->unicode_11_12+i, *uniname);
			if (*uniname == 0x0)
				end = TRUE;
			else
				uniname++;
		} else {
			SET16_A(ep->unicode_11_12+i, 0xFFFF);
		}
	}
} /* end of init_ext_entry */

void init_file_entry(FILE_DENTRY_T *ep, u32 type)
{
	TIMESTAMP_T tm, *tp;

	exfat_set_entry_type((DENTRY_T *) ep, type);

	tp = tm_current(&tm);
	exfat_set_entry_time((DENTRY_T *) ep, tp, TM_CREATE);
	exfat_set_entry_time((DENTRY_T *) ep, tp, TM_MODIFY);
	exfat_set_entry_time((DENTRY_T *) ep, tp, TM_ACCESS);
	ep->create_time_ms = 0;
	ep->modify_time_ms = 0;
	ep->access_time_ms = 0;
} /* end of init_file_entry */

void init_strm_entry(STRM_DENTRY_T *ep, u8 flags, u32 start_clu, u64 size)
{
	exfat_set_entry_type((DENTRY_T *) ep, TYPE_STREAM);
	ep->flags = flags;
	SET32_A(ep->start_clu, start_clu);
	SET64_A(ep->valid_size, size);
	SET64_A(ep->size, size);
} /* end of init_strm_entry */

void init_name_entry(NAME_DENTRY_T *ep, u16 *uniname)
{
	int i;

	exfat_set_entry_type((DENTRY_T *) ep, TYPE_EXTEND);
	ep->flags = 0x0;

	for (i = 0; i < 30; i++, i++) {
		SET16_A(ep->unicode_0_14+i, *uniname);
		if (*uniname == 0x0)
			break;
		uniname++;
	}
} /* end of init_name_entry */

void fat_delete_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 order, s32 num_entries)
{
	int i;
	sector_t sector;
	DENTRY_T *ep;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	for (i = num_entries-1; i >= order; i--) {
		ep = get_entry_in_dir(sb, p_dir, entry-i, &sector);
		if (!ep)
			return;

		p_fs->fs_func->set_entry_type(ep, TYPE_DELETED);
		buf_modify(sb, sector);
	}
} /* end of fat_delete_dir_entry */

void exfat_delete_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 order, s32 num_entries)
{
	int i;
	sector_t sector;
	DENTRY_T *ep;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	for (i = order; i < num_entries; i++) {
		ep = get_entry_in_dir(sb, p_dir, entry+i, &sector);
		if (!ep)
			return;

		p_fs->fs_func->set_entry_type(ep, TYPE_DELETED);
		buf_modify(sb, sector);
	}
} /* end of exfat_delete_dir_entry */

void update_dir_checksum(struct super_block *sb, CHAIN_T *p_dir, s32 entry)
{
	int i, num_entries;
	sector_t sector;
	u16 chksum;
	FILE_DENTRY_T *file_ep;
	DENTRY_T *ep;

	file_ep = (FILE_DENTRY_T *) get_entry_in_dir(sb, p_dir, entry, &sector);
	if (!file_ep)
		return;

	buf_lock(sb, sector);

	num_entries = (s32) file_ep->num_ext + 1;
	chksum = calc_checksum_2byte((void *) file_ep, DENTRY_SIZE, 0, CS_DIR_ENTRY);

	for (i = 1; i < num_entries; i++) {
		ep = get_entry_in_dir(sb, p_dir, entry+i, NULL);
		if (!ep) {
			buf_unlock(sb, sector);
			return;
		}

		chksum = calc_checksum_2byte((void *) ep, DENTRY_SIZE, chksum, CS_DEFAULT);
	}

	SET16_A(file_ep->checksum, chksum);
	buf_modify(sb, sector);
	buf_unlock(sb, sector);
} /* end of update_dir_checksum */

void update_dir_checksum_with_entry_set(struct super_block *sb, ENTRY_SET_CACHE_T *es)
{
	DENTRY_T *ep;
	u16 chksum = 0;
	s32 chksum_type = CS_DIR_ENTRY, i;

	ep = (DENTRY_T *)&(es->__buf);
	for (i = 0; i < es->num_entries; i++) {
		DPRINTK("update_dir_checksum_with_entry_set ep %p\n", ep);
		chksum = calc_checksum_2byte((void *) ep, DENTRY_SIZE, chksum, chksum_type);
		ep++;
		chksum_type = CS_DEFAULT;
	}

	ep = (DENTRY_T *)&(es->__buf);
	SET16_A(((FILE_DENTRY_T *)ep)->checksum, chksum);
	write_whole_entry_set(sb, es);
}

static s32 _walk_fat_chain(struct super_block *sb, CHAIN_T *p_dir, s32 byte_offset, u32 *clu)
{
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	s32 clu_offset;
	u32 cur_clu;

	clu_offset = byte_offset >> p_fs->cluster_size_bits;
	cur_clu = p_dir->dir;

	if (p_dir->flags == 0x03) {
		cur_clu += clu_offset;
	} else {
		while (clu_offset > 0) {
			if (FAT_read(sb, cur_clu, &cur_clu) == -1)
				return FFS_MEDIAERR;
			clu_offset--;
		}
	}

	if (clu)
		*clu = cur_clu;
	return FFS_SUCCESS;
}
s32 find_location(struct super_block *sb, CHAIN_T *p_dir, s32 entry, sector_t *sector, s32 *offset)
{
	s32 off, ret;
	u32 clu = 0;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);

	off = entry << DENTRY_SIZE_BITS;

	if (p_dir->dir == CLUSTER_32(0)) { /* FAT16 root_dir */
		*offset = off & p_bd->sector_size_mask;
		*sector = off >> p_bd->sector_size_bits;
		*sector += p_fs->root_start_sector;
	} else {
		ret = _walk_fat_chain(sb, p_dir, off, &clu);
		if (ret != FFS_SUCCESS)
			return ret;

		off &= p_fs->cluster_size - 1;	/* byte offset in cluster */

		*offset = off & p_bd->sector_size_mask;	/* byte offset in sector    */
		*sector = off >> p_bd->sector_size_bits;	/* sector offset in cluster */
		*sector += START_SECTOR(clu);
	}
	return FFS_SUCCESS;
} /* end of find_location */

DENTRY_T *get_entry_with_sector(struct super_block *sb, sector_t sector, s32 offset)
{
	u8 *buf;

	buf = buf_getblk(sb, sector);

	if (buf == NULL)
		return NULL;

	return (DENTRY_T *)(buf + offset);
} /* end of get_entry_with_sector */

DENTRY_T *get_entry_in_dir(struct super_block *sb, CHAIN_T *p_dir, s32 entry, sector_t *sector)
{
	s32 off;
	sector_t sec;
	u8 *buf;

	if (find_location(sb, p_dir, entry, &sec, &off) != FFS_SUCCESS)
		return NULL;

	buf = buf_getblk(sb, sec);

	if (buf == NULL)
		return NULL;

	if (sector != NULL)
		*sector = sec;
	return (DENTRY_T *)(buf + off);
} /* end of get_entry_in_dir */


/* returns a set of dentries for a file or dir.
 * Note that this is a copy (dump) of dentries so that user should call write_entry_set()
 * to apply changes made in this entry set to the real device.
 * in:
 *   sb+p_dir+entry: indicates a file/dir
 *   type:  specifies how many dentries should be included.
 * out:
 *   file_ep: will point the first dentry(= file dentry) on success
 * return:
 *   pointer of entry set on success,
 *   NULL on failure.
 */

#define ES_MODE_STARTED				0
#define ES_MODE_GET_FILE_ENTRY			1
#define ES_MODE_GET_STRM_ENTRY			2
#define ES_MODE_GET_NAME_ENTRY			3
#define ES_MODE_GET_CRITICAL_SEC_ENTRY		4
ENTRY_SET_CACHE_T *get_entry_set_in_dir(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u32 type, DENTRY_T **file_ep)
{
	s32 off, ret, byte_offset;
	u32 clu = 0;
	sector_t sec;
	u32 entry_type;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);
	ENTRY_SET_CACHE_T *es = NULL;
	DENTRY_T *ep, *pos;
	u8 *buf;
	u8 num_entries;
	s32 mode = ES_MODE_STARTED;

	DPRINTK("get_entry_set_in_dir entered\n");
	DPRINTK("p_dir dir %u flags %x size %d\n", p_dir->dir, p_dir->flags, p_dir->size);

	byte_offset = entry << DENTRY_SIZE_BITS;
	ret = _walk_fat_chain(sb, p_dir, byte_offset, &clu);
	if (ret != FFS_SUCCESS)
		return NULL;


	byte_offset &= p_fs->cluster_size - 1;	/* byte offset in cluster */

	off = byte_offset & p_bd->sector_size_mask;	/* byte offset in sector    */
	sec = byte_offset >> p_bd->sector_size_bits;	/* sector offset in cluster */
	sec += START_SECTOR(clu);

	buf = buf_getblk(sb, sec);
	if (buf == NULL)
		goto err_out;


	ep = (DENTRY_T *)(buf + off);
	entry_type = p_fs->fs_func->get_entry_type(ep);

	if ((entry_type != TYPE_FILE)
		&& (entry_type != TYPE_DIR))
		goto err_out;

	if (type == ES_ALL_ENTRIES)
		num_entries = ((FILE_DENTRY_T *)ep)->num_ext+1;
	else
		num_entries = type;

	DPRINTK("trying to kmalloc %zx bytes for %d entries\n", offsetof(ENTRY_SET_CACHE_T, __buf) + (num_entries)  * sizeof(DENTRY_T), num_entries);
	es = kmalloc(offsetof(ENTRY_SET_CACHE_T, __buf) + (num_entries)  * sizeof(DENTRY_T), GFP_KERNEL);
	if (es == NULL)
		goto err_out;

	es->num_entries = num_entries;
	es->sector = sec;
	es->offset = off;
	es->alloc_flag = p_dir->flags;

	pos = (DENTRY_T *) &(es->__buf);

	while(num_entries) {
		/* instead of copying whole sector, we will check every entry.
		 * this will provide minimum stablity and consistancy.
		 */

		entry_type = p_fs->fs_func->get_entry_type(ep);

		if ((entry_type == TYPE_UNUSED) || (entry_type == TYPE_DELETED))
			goto err_out;

		switch (mode) {
		case ES_MODE_STARTED:
			if  ((entry_type == TYPE_FILE) || (entry_type == TYPE_DIR))
				mode = ES_MODE_GET_FILE_ENTRY;
			else
				goto err_out;
			break;
		case ES_MODE_GET_FILE_ENTRY:
			if (entry_type == TYPE_STREAM)
				mode = ES_MODE_GET_STRM_ENTRY;
			else
				goto err_out;
			break;
		case ES_MODE_GET_STRM_ENTRY:
			if (entry_type == TYPE_EXTEND)
				mode = ES_MODE_GET_NAME_ENTRY;
			else
				goto err_out;
			break;
		case ES_MODE_GET_NAME_ENTRY:
			if (entry_type == TYPE_EXTEND)
				break;
			else if (entry_type == TYPE_STREAM)
				goto err_out;
			else if (entry_type & TYPE_CRITICAL_SEC)
				mode = ES_MODE_GET_CRITICAL_SEC_ENTRY;
			else
				goto err_out;
			break;
		case ES_MODE_GET_CRITICAL_SEC_ENTRY:
			if ((entry_type == TYPE_EXTEND) || (entry_type == TYPE_STREAM))
				goto err_out;
			else if ((entry_type & TYPE_CRITICAL_SEC) != TYPE_CRITICAL_SEC)
				goto err_out;
			break;
		}

		memcpy(pos, ep, sizeof(DENTRY_T));

		if (--num_entries == 0)
			break;

		if (((off + DENTRY_SIZE) & p_bd->sector_size_mask) < (off &  p_bd->sector_size_mask)) {
			/* get the next sector */
			if (IS_LAST_SECTOR_IN_CLUSTER(sec)) {
				if (es->alloc_flag == 0x03) {
					clu++;
				} else {
					if (FAT_read(sb, clu, &clu) == -1)
						goto err_out;
				}
				sec = START_SECTOR(clu);
			} else {
				sec++;
			}
			buf = buf_getblk(sb, sec);
			if (buf == NULL)
				goto err_out;
			off = 0;
			ep = (DENTRY_T *)(buf);
		} else {
			ep++;
			off += DENTRY_SIZE;
		}
		pos++;
	}

	if (file_ep)
		*file_ep = (DENTRY_T *)&(es->__buf);

	DPRINTK("es sec %llu offset %d flags %d, num_entries %u buf ptr %p\n",
		   (unsigned long long)es->sector, es->offset, es->alloc_flag,
		   es->num_entries, &(es->__buf));
	DPRINTK("get_entry_set_in_dir exited %p\n", es);
	return es;
err_out:
	DPRINTK("get_entry_set_in_dir exited NULL (es %p)\n", es);
	if (es)
		kfree(es);
	return NULL;
}

void release_entry_set(ENTRY_SET_CACHE_T *es)
{
	DPRINTK("release_entry_set %p\n", es);
	if (es)
		kfree(es);
}


static s32 __write_partial_entries_in_entry_set(struct super_block *sb, ENTRY_SET_CACHE_T *es, sector_t sec, s32 off, u32 count)
{
	s32 num_entries, buf_off = (off - es->offset);
	u32 remaining_byte_in_sector, copy_entries;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);
	u32 clu;
	u8 *buf, *esbuf = (u8 *)&(es->__buf);

	DPRINTK("__write_partial_entries_in_entry_set entered\n");
	DPRINTK("es %p sec %llu off %d count %d\n", es, (unsigned long long)sec, off, count);
	num_entries = count;

	while (num_entries) {
		/* white per sector base */
		remaining_byte_in_sector = (1 << p_bd->sector_size_bits) - off;
		copy_entries = MIN(remaining_byte_in_sector >> DENTRY_SIZE_BITS , num_entries);
		buf = buf_getblk(sb, sec);
		if (buf == NULL)
			goto err_out;
		DPRINTK("es->buf %p buf_off %u\n", esbuf, buf_off);
		DPRINTK("copying %d entries from %p to sector %llu\n", copy_entries, (esbuf + buf_off), (unsigned long long)sec);
		memcpy(buf + off, esbuf + buf_off, copy_entries << DENTRY_SIZE_BITS);
		buf_modify(sb, sec);
		num_entries -= copy_entries;

		if (num_entries) {
			/* get next sector */
			if (IS_LAST_SECTOR_IN_CLUSTER(sec)) {
				clu = GET_CLUSTER_FROM_SECTOR(sec);
				if (es->alloc_flag == 0x03) {
					clu++;
				} else {
					if (FAT_read(sb, clu, &clu) == -1)
						goto err_out;
				}
				sec = START_SECTOR(clu);
			} else {
				sec++;
			}
			off = 0;
			buf_off += copy_entries << DENTRY_SIZE_BITS;
		}
	}

	DPRINTK("__write_partial_entries_in_entry_set exited successfully\n");
	return FFS_SUCCESS;
err_out:
	DPRINTK("__write_partial_entries_in_entry_set failed\n");
	return FFS_ERROR;
}

/* write back all entries in entry set */
s32 write_whole_entry_set(struct super_block *sb, ENTRY_SET_CACHE_T *es)
{
	return __write_partial_entries_in_entry_set(sb, es, es->sector, es->offset, es->num_entries);
}

/* write back some entries in entry set */
s32 write_partial_entries_in_entry_set (struct super_block *sb, ENTRY_SET_CACHE_T *es, DENTRY_T *ep, u32 count)
{
	s32 ret, byte_offset, off;
	u32 clu=0;
	sector_t sec;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);
	CHAIN_T dir;

	/* vaidity check */
	if (ep + count  > ((DENTRY_T *)&(es->__buf)) + es->num_entries)
		return FFS_ERROR;

	dir.dir = GET_CLUSTER_FROM_SECTOR(es->sector);
	dir.flags = es->alloc_flag;
	dir.size = 0xffffffff;		/* XXX */

	byte_offset = (es->sector - START_SECTOR(dir.dir)) << p_bd->sector_size_bits;
	byte_offset += ((void **)ep - &(es->__buf)) + es->offset;

	ret =_walk_fat_chain(sb, &dir, byte_offset, &clu);
	if (ret != FFS_SUCCESS)
		return ret;
	byte_offset &= p_fs->cluster_size - 1;	/* byte offset in cluster */
	off = byte_offset & p_bd->sector_size_mask;	/* byte offset in sector    */
	sec = byte_offset >> p_bd->sector_size_bits;	/* sector offset in cluster */
	sec += START_SECTOR(clu);
	return __write_partial_entries_in_entry_set(sb, es, sec, off, count);
}

/* search EMPTY CONTINUOUS "num_entries" entries */
s32 search_deleted_or_unused_entry(struct super_block *sb, CHAIN_T *p_dir, s32 num_entries)
{
	int i, dentry, num_empty = 0;
	s32 dentries_per_clu;
	u32 type;
	CHAIN_T clu;
	DENTRY_T *ep;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	if (p_dir->dir == CLUSTER_32(0)) /* FAT16 root_dir */
		dentries_per_clu = p_fs->dentries_in_root;
	else
		dentries_per_clu = p_fs->dentries_per_clu;

	if (p_fs->hint_uentry.dir == p_dir->dir) {
		if (p_fs->hint_uentry.entry == -1)
			return -1;

		clu.dir = p_fs->hint_uentry.clu.dir;
		clu.size = p_fs->hint_uentry.clu.size;
		clu.flags = p_fs->hint_uentry.clu.flags;

		dentry = p_fs->hint_uentry.entry;
	} else {
		p_fs->hint_uentry.entry = -1;

		clu.dir = p_dir->dir;
		clu.size = p_dir->size;
		clu.flags = p_dir->flags;

		dentry = 0;
	}

	while (clu.dir != CLUSTER_32(~0)) {
		if (p_fs->dev_ejected)
			break;

		if (p_dir->dir == CLUSTER_32(0)) /* FAT16 root_dir */
			i = dentry % dentries_per_clu;
		else
			i = dentry & (dentries_per_clu-1);

		for (; i < dentries_per_clu; i++, dentry++) {
			ep = get_entry_in_dir(sb, &clu, i, NULL);
			if (!ep)
				return -1;

			type = p_fs->fs_func->get_entry_type(ep);

			if (type == TYPE_UNUSED) {
				num_empty++;
				if (p_fs->hint_uentry.entry == -1) {
					p_fs->hint_uentry.dir = p_dir->dir;
					p_fs->hint_uentry.entry = dentry;

					p_fs->hint_uentry.clu.dir = clu.dir;
					p_fs->hint_uentry.clu.size = clu.size;
					p_fs->hint_uentry.clu.flags = clu.flags;
				}
			} else if (type == TYPE_DELETED) {
				num_empty++;
			} else {
				num_empty = 0;
			}

			if (num_empty >= num_entries) {
				p_fs->hint_uentry.dir = CLUSTER_32(~0);
				p_fs->hint_uentry.entry = -1;

				if (p_fs->vol_type == EXFAT)
					return dentry - (num_entries-1);
				else
					return dentry;
			}
		}

		if (p_dir->dir == CLUSTER_32(0))
			break; /* FAT16 root_dir */

		if (clu.flags == 0x03) {
			if ((--clu.size) > 0)
				clu.dir++;
			else
				clu.dir = CLUSTER_32(~0);
		} else {
			if (FAT_read(sb, clu.dir, &(clu.dir)) != 0)
				return -1;
		}
	}

	return -1;
} /* end of search_deleted_or_unused_entry */

s32 find_empty_entry(struct inode *inode, CHAIN_T *p_dir, s32 num_entries)
{
	s32 ret, dentry;
	u32 last_clu;
	sector_t sector;
	u64 size = 0;
	CHAIN_T clu;
	DENTRY_T *ep = NULL;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	FILE_ID_T *fid = &(EXFAT_I(inode)->fid);

	if (p_dir->dir == CLUSTER_32(0)) /* FAT16 root_dir */
		return search_deleted_or_unused_entry(sb, p_dir, num_entries);

	while ((dentry = search_deleted_or_unused_entry(sb, p_dir, num_entries)) < 0) {
		if (p_fs->dev_ejected)
			break;

		if (p_fs->vol_type == EXFAT) {
			if (p_dir->dir != p_fs->root_dir)
				size = i_size_read(inode);
		}

		last_clu = find_last_cluster(sb, p_dir);
		clu.dir = last_clu + 1;
		clu.size = 0;
		clu.flags = p_dir->flags;

		/* (1) allocate a cluster */
		ret = p_fs->fs_func->alloc_cluster(sb, 1, &clu);
		if (ret < 1)
			return -1;

		if (clear_cluster(sb, clu.dir) != FFS_SUCCESS)
			return -1;

		/* (2) append to the FAT chain */
		if (clu.flags != p_dir->flags) {
			exfat_chain_cont_cluster(sb, p_dir->dir, p_dir->size);
			p_dir->flags = 0x01;
			p_fs->hint_uentry.clu.flags = 0x01;
		}
		if (clu.flags == 0x01)
			if (FAT_write(sb, last_clu, clu.dir) < 0)
				return -1;

		if (p_fs->hint_uentry.entry == -1) {
			p_fs->hint_uentry.dir = p_dir->dir;
			p_fs->hint_uentry.entry = p_dir->size << (p_fs->cluster_size_bits - DENTRY_SIZE_BITS);

			p_fs->hint_uentry.clu.dir = clu.dir;
			p_fs->hint_uentry.clu.size = 0;
			p_fs->hint_uentry.clu.flags = clu.flags;
		}
		p_fs->hint_uentry.clu.size++;
		p_dir->size++;

		/* (3) update the directory entry */
		if (p_fs->vol_type == EXFAT) {
			if (p_dir->dir != p_fs->root_dir) {
				size += p_fs->cluster_size;

				ep = get_entry_in_dir(sb, &(fid->dir), fid->entry+1, &sector);
				if (!ep)
					return -1;
				p_fs->fs_func->set_entry_size(ep, size);
				p_fs->fs_func->set_entry_flag(ep, p_dir->flags);
				buf_modify(sb, sector);

				update_dir_checksum(sb, &(fid->dir), fid->entry);
			}
		}

		i_size_write(inode, i_size_read(inode)+p_fs->cluster_size);
		EXFAT_I(inode)->mmu_private += p_fs->cluster_size;
		EXFAT_I(inode)->fid.size += p_fs->cluster_size;
		EXFAT_I(inode)->fid.flags = p_dir->flags;
		inode->i_blocks += 1 << (p_fs->cluster_size_bits - 9);
	}

	return dentry;
} /* end of find_empty_entry */

/* return values of fat_find_dir_entry()
   >= 0 : return dir entiry position with the name in dir
   -1 : (root dir, ".") it is the root dir itself
   -2 : entry with the name does not exist */
s32 fat_find_dir_entry(struct super_block *sb, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, s32 num_entries, DOS_NAME_T *p_dosname, u32 type)
{
	int i, dentry = 0, lossy = FALSE, len;
	s32 order = 0, is_feasible_entry = TRUE, has_ext_entry = FALSE;
	s32 dentries_per_clu;
	u32 entry_type;
	u16 entry_uniname[14], *uniname = NULL, unichar;
	CHAIN_T clu;
	DENTRY_T *ep;
	DOS_DENTRY_T *dos_ep;
	EXT_DENTRY_T *ext_ep;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	if (p_dir->dir == p_fs->root_dir) {
		if ((!nls_uniname_cmp(sb, p_uniname->name, (u16 *) UNI_CUR_DIR_NAME)) ||
			(!nls_uniname_cmp(sb, p_uniname->name, (u16 *) UNI_PAR_DIR_NAME)))
			return -1; // special case, root directory itself
	}

	if (p_dir->dir == CLUSTER_32(0)) /* FAT16 root_dir */
		dentries_per_clu = p_fs->dentries_in_root;
	else
		dentries_per_clu = p_fs->dentries_per_clu;

	clu.dir = p_dir->dir;
	clu.flags = p_dir->flags;

	while (clu.dir != CLUSTER_32(~0)) {
		if (p_fs->dev_ejected)
			break;

		for (i = 0; i < dentries_per_clu; i++, dentry++) {
			ep = get_entry_in_dir(sb, &clu, i, NULL);
			if (!ep)
				return -2;

			entry_type = p_fs->fs_func->get_entry_type(ep);

			if ((entry_type == TYPE_FILE) || (entry_type == TYPE_DIR)) {
				if ((type == TYPE_ALL) || (type == entry_type)) {
					if (is_feasible_entry && has_ext_entry)
						return dentry;

					dos_ep = (DOS_DENTRY_T *) ep;
					if ((!lossy) && (!nls_dosname_cmp(sb, p_dosname->name, dos_ep->name)))
						return dentry;
				}
				is_feasible_entry = TRUE;
				has_ext_entry = FALSE;
			} else if (entry_type == TYPE_EXTEND) {
				if (is_feasible_entry) {
					ext_ep = (EXT_DENTRY_T *) ep;
					if (ext_ep->order > 0x40) {
						order = (s32)(ext_ep->order - 0x40);
						uniname = p_uniname->name + 13 * (order-1);
					} else {
						order = (s32) ext_ep->order;
						uniname -= 13;
					}

					len = extract_uni_name_from_ext_entry(ext_ep, entry_uniname, order);

					unichar = *(uniname+len);
					*(uniname+len) = 0x0;

					if (nls_uniname_cmp(sb, uniname, entry_uniname))
						is_feasible_entry = FALSE;

					*(uniname+len) = unichar;
				}
				has_ext_entry = TRUE;
			} else if (entry_type == TYPE_UNUSED) {
				return -2;
			} else {
				is_feasible_entry = TRUE;
				has_ext_entry = FALSE;
			}
		}

		if (p_dir->dir == CLUSTER_32(0))
			break; /* FAT16 root_dir */

		if (FAT_read(sb, clu.dir, &(clu.dir)) != 0)
			return -2;
	}

	return -2;
} /* end of fat_find_dir_entry */

/* return values of exfat_find_dir_entry()
   >= 0 : return dir entiry position with the name in dir
   -1 : (root dir, ".") it is the root dir itself
   -2 : entry with the name does not exist */
s32 exfat_find_dir_entry(struct super_block *sb, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, s32 num_entries, DOS_NAME_T *p_dosname, u32 type)
{
	int i = 0, dentry = 0, num_ext_entries = 0, len, step;
	s32 order = 0, is_feasible_entry = FALSE;
	s32 dentries_per_clu, num_empty = 0;
	u32 entry_type;
	u16 entry_uniname[16], *uniname = NULL, unichar;
	CHAIN_T clu;
	DENTRY_T *ep;
	FILE_DENTRY_T *file_ep;
	STRM_DENTRY_T *strm_ep;
	NAME_DENTRY_T *name_ep;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	if (p_dir->dir == p_fs->root_dir) {
		if ((!nls_uniname_cmp(sb, p_uniname->name, (u16 *) UNI_CUR_DIR_NAME)) ||
			(!nls_uniname_cmp(sb, p_uniname->name, (u16 *) UNI_PAR_DIR_NAME)))
			return -1; // special case, root directory itself
	}

	if (p_dir->dir == CLUSTER_32(0)) /* FAT16 root_dir */
		dentries_per_clu = p_fs->dentries_in_root;
	else
		dentries_per_clu = p_fs->dentries_per_clu;

	clu.dir = p_dir->dir;
	clu.size = p_dir->size;
	clu.flags = p_dir->flags;

	p_fs->hint_uentry.dir = p_dir->dir;
	p_fs->hint_uentry.entry = -1;

	while (clu.dir != CLUSTER_32(~0)) {
		if (p_fs->dev_ejected)
			break;

		while (i < dentries_per_clu) {
			ep = get_entry_in_dir(sb, &clu, i, NULL);
			if (!ep)
				return -2;

			entry_type = p_fs->fs_func->get_entry_type(ep);
			step = 1;

			if ((entry_type == TYPE_UNUSED) || (entry_type == TYPE_DELETED)) {
				is_feasible_entry = FALSE;

				if (p_fs->hint_uentry.entry == -1) {
					num_empty++;

					if (num_empty == 1) {
						p_fs->hint_uentry.clu.dir = clu.dir;
						p_fs->hint_uentry.clu.size = clu.size;
						p_fs->hint_uentry.clu.flags = clu.flags;
					}
					if ((num_empty >= num_entries) || (entry_type == TYPE_UNUSED))
						p_fs->hint_uentry.entry = dentry - (num_empty-1);
				}

				if (entry_type == TYPE_UNUSED)
					return -2;
			} else {
				num_empty = 0;

				if ((entry_type == TYPE_FILE) || (entry_type == TYPE_DIR)) {
					file_ep = (FILE_DENTRY_T *) ep;
					if ((type == TYPE_ALL) || (type == entry_type)) {
						num_ext_entries = file_ep->num_ext;
						is_feasible_entry = TRUE;
					} else {
						is_feasible_entry = FALSE;
						step = file_ep->num_ext + 1;
					}
				} else if (entry_type == TYPE_STREAM) {
					if (is_feasible_entry) {
						strm_ep = (STRM_DENTRY_T *) ep;
						if (p_uniname->name_hash == GET16_A(strm_ep->name_hash) &&
						    p_uniname->name_len == strm_ep->name_len) {
							order = 1;
						} else {
							is_feasible_entry = FALSE;
							step = num_ext_entries;
						}
					}
				} else if (entry_type == TYPE_EXTEND) {
					if (is_feasible_entry) {
						name_ep = (NAME_DENTRY_T *) ep;

						if ((++order) == 2)
							uniname = p_uniname->name;
						else
							uniname += 15;

						len = extract_uni_name_from_name_entry(name_ep, entry_uniname, order);

						unichar = *(uniname+len);
						*(uniname+len) = 0x0;

						if (nls_uniname_cmp(sb, uniname, entry_uniname)) {
							is_feasible_entry = FALSE;
							step = num_ext_entries - order + 1;
						} else if (order == num_ext_entries) {
							p_fs->hint_uentry.dir = CLUSTER_32(~0);
							p_fs->hint_uentry.entry = -1;
							return dentry - (num_ext_entries);
						}

						*(uniname+len) = unichar;
					}
				} else {
					is_feasible_entry = FALSE;
				}
			}

			i += step;
			dentry += step;
		}

		i -= dentries_per_clu;

		if (p_dir->dir == CLUSTER_32(0))
			break; /* FAT16 root_dir */

		if (clu.flags == 0x03) {
			if ((--clu.size) > 0)
				clu.dir++;
			else
				clu.dir = CLUSTER_32(~0);
		} else {
			if (FAT_read(sb, clu.dir, &(clu.dir)) != 0)
				return -2;
		}
	}

	return -2;
} /* end of exfat_find_dir_entry */

/* returns -1 on error */
s32 fat_count_ext_entries(struct super_block *sb, CHAIN_T *p_dir, s32 entry, DENTRY_T *p_entry)
{
	s32 count = 0;
	u8 chksum;
	DOS_DENTRY_T *dos_ep = (DOS_DENTRY_T *) p_entry;
	EXT_DENTRY_T *ext_ep;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	chksum = calc_checksum_1byte((void *) dos_ep->name, DOS_NAME_LENGTH, 0);

	for (entry--; entry >= 0; entry--) {
		ext_ep = (EXT_DENTRY_T *) get_entry_in_dir(sb, p_dir, entry, NULL);
		if (!ext_ep)
			return -1;

		if ((p_fs->fs_func->get_entry_type((DENTRY_T *) ext_ep) == TYPE_EXTEND) &&
			(ext_ep->checksum == chksum)) {
			count++;
			if (ext_ep->order > 0x40)
				return count;
		} else {
			return count;
		}
	}

	return count;
} /* end of fat_count_ext_entries */

/* returns -1 on error */
s32 exfat_count_ext_entries(struct super_block *sb, CHAIN_T *p_dir, s32 entry, DENTRY_T *p_entry)
{
	int i, count = 0;
	u32 type;
	FILE_DENTRY_T *file_ep = (FILE_DENTRY_T *) p_entry;
	DENTRY_T *ext_ep;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	for (i = 0, entry++; i < file_ep->num_ext; i++, entry++) {
		ext_ep = get_entry_in_dir(sb, p_dir, entry, NULL);
		if (!ext_ep)
			return -1;

		type = p_fs->fs_func->get_entry_type(ext_ep);
		if ((type == TYPE_EXTEND) || (type == TYPE_STREAM))
			count++;
		else
			return count;
	}

	return count;
} /* end of exfat_count_ext_entries */

/* returns -1 on error */
s32 count_dos_name_entries(struct super_block *sb, CHAIN_T *p_dir, u32 type)
{
	int i, count = 0;
	s32 dentries_per_clu;
	u32 entry_type;
	CHAIN_T clu;
	DENTRY_T *ep;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	if (p_dir->dir == CLUSTER_32(0)) /* FAT16 root_dir */
		dentries_per_clu = p_fs->dentries_in_root;
	else
		dentries_per_clu = p_fs->dentries_per_clu;

	clu.dir = p_dir->dir;
	clu.size = p_dir->size;
	clu.flags = p_dir->flags;

	while (clu.dir != CLUSTER_32(~0)) {
		if (p_fs->dev_ejected)
			break;

		for (i = 0; i < dentries_per_clu; i++) {
			ep = get_entry_in_dir(sb, &clu, i, NULL);
			if (!ep)
				return -1;

			entry_type = p_fs->fs_func->get_entry_type(ep);

			if (entry_type == TYPE_UNUSED)
				return count;
			if (!(type & TYPE_CRITICAL_PRI) && !(type & TYPE_BENIGN_PRI))
				continue;

			if ((type == TYPE_ALL) || (type == entry_type))
				count++;
		}

		if (p_dir->dir == CLUSTER_32(0))
			break; /* FAT16 root_dir */

		if (clu.flags == 0x03) {
			if ((--clu.size) > 0)
				clu.dir++;
			else
				clu.dir = CLUSTER_32(~0);
		} else {
			if (FAT_read(sb, clu.dir, &(clu.dir)) != 0)
				return -1;
		}
	}

	return count;
} /* end of count_dos_name_entries */

bool is_dir_empty(struct super_block *sb, CHAIN_T *p_dir)
{
	int i, count = 0;
	s32 dentries_per_clu;
	u32 type;
	CHAIN_T clu;
	DENTRY_T *ep;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	if (p_dir->dir == CLUSTER_32(0)) /* FAT16 root_dir */
		dentries_per_clu = p_fs->dentries_in_root;
	else
		dentries_per_clu = p_fs->dentries_per_clu;

	clu.dir = p_dir->dir;
	clu.size = p_dir->size;
	clu.flags = p_dir->flags;

	while (clu.dir != CLUSTER_32(~0)) {
		if (p_fs->dev_ejected)
			break;

		for (i = 0; i < dentries_per_clu; i++) {
			ep = get_entry_in_dir(sb, &clu, i, NULL);
			if (!ep)
				break;

			type = p_fs->fs_func->get_entry_type(ep);

			if (type == TYPE_UNUSED)
				return TRUE;
			if ((type != TYPE_FILE) && (type != TYPE_DIR))
				continue;

			if (p_dir->dir == CLUSTER_32(0)) { /* FAT16 root_dir */
				return FALSE;
			} else {
				if (p_fs->vol_type == EXFAT)
					return FALSE;
				if ((p_dir->dir == p_fs->root_dir) || ((++count) > 2))
					return FALSE;
			}
		}

		if (p_dir->dir == CLUSTER_32(0))
			break; /* FAT16 root_dir */

		if (clu.flags == 0x03) {
			if ((--clu.size) > 0)
				clu.dir++;
			else
				clu.dir = CLUSTER_32(~0);
		} else {
			if (FAT_read(sb, clu.dir, &(clu.dir)) != 0)
				break;
		}
	}

	return TRUE;
} /* end of is_dir_empty */

/*
 *  Name Conversion Functions
 */

/* input  : dir, uni_name
   output : num_of_entry, dos_name(format : aaaaaa~1.bbb) */
s32 get_num_entries_and_dos_name(struct super_block *sb, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, s32 *entries, DOS_NAME_T *p_dosname)
{
	s32 ret, num_entries, lossy = FALSE;
	char **r;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	num_entries = p_fs->fs_func->calc_num_entries(p_uniname);
	if (num_entries == 0)
		return FFS_INVALIDPATH;

	if (p_fs->vol_type != EXFAT) {
		nls_uniname_to_dosname(sb, p_dosname, p_uniname, &lossy);

		if (lossy) {
			ret = fat_generate_dos_name(sb, p_dir, p_dosname);
			if (ret)
				return ret;
		} else {
			for (r = reserved_names; *r; r++) {
				if (!strncmp((void *) p_dosname->name, *r, 8))
					return FFS_INVALIDPATH;
			}

			if (p_dosname->name_case != 0xFF)
				num_entries = 1;
		}

		if (num_entries > 1)
			p_dosname->name_case = 0x0;
	}

	*entries = num_entries;

	return FFS_SUCCESS;
} /* end of get_num_entries_and_dos_name */

void get_uni_name_from_dos_entry(struct super_block *sb, DOS_DENTRY_T *ep, UNI_NAME_T *p_uniname, u8 mode)
{
	DOS_NAME_T dos_name;

	if (mode == 0x0)
		dos_name.name_case = 0x0;
	else
		dos_name.name_case = ep->lcase;

	memcpy(dos_name.name, ep->name, DOS_NAME_LENGTH);
	nls_dosname_to_uniname(sb, p_uniname, &dos_name);
} /* end of get_uni_name_from_dos_entry */

void fat_get_uni_name_from_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u16 *uniname)
{
	int i;
	EXT_DENTRY_T *ep;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	for (entry--, i = 1; entry >= 0; entry--, i++) {
		ep = (EXT_DENTRY_T *) get_entry_in_dir(sb, p_dir, entry, NULL);
		if (!ep)
			return;

		if (p_fs->fs_func->get_entry_type((DENTRY_T *) ep) == TYPE_EXTEND) {
			extract_uni_name_from_ext_entry(ep, uniname, i);
			if (ep->order > 0x40)
				return;
		} else {
			return;
		}

		uniname += 13;
	}
} /* end of fat_get_uni_name_from_ext_entry */

void exfat_get_uni_name_from_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u16 *uniname)
{
	int i;
	DENTRY_T *ep;
	ENTRY_SET_CACHE_T *es;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	es = get_entry_set_in_dir(sb, p_dir, entry, ES_ALL_ENTRIES, &ep);
	if (es == NULL || es->num_entries < 3) {
		if (es)
			release_entry_set(es);
		return;
	}

	ep += 2;

	/*
	* First entry  : file entry
	* Second entry : stream-extension entry
	* Third entry  : first file-name entry
	* So, the index of first file-name dentry should start from 2.
	*/
	for (i = 2; i < es->num_entries; i++, ep++) {
		if (p_fs->fs_func->get_entry_type(ep) == TYPE_EXTEND)
			extract_uni_name_from_name_entry((NAME_DENTRY_T *)ep, uniname, i);
		else
			goto out;
		uniname += 15;
	}

out:
	release_entry_set(es);
} /* end of exfat_get_uni_name_from_ext_entry */

s32 extract_uni_name_from_ext_entry(EXT_DENTRY_T *ep, u16 *uniname, s32 order)
{
	int i, len = 0;

	for (i = 0; i < 10; i += 2) {
		*uniname = GET16(ep->unicode_0_4+i);
		if (*uniname == 0x0)
			return len;
		uniname++;
		len++;
	}

	if (order < 20) {
		for (i = 0; i < 12; i += 2) {
			*uniname = GET16_A(ep->unicode_5_10+i);
			if (*uniname == 0x0)
				return len;
			uniname++;
			len++;
		}
	} else {
		for (i = 0; i < 8; i += 2) {
			*uniname = GET16_A(ep->unicode_5_10+i);
			if (*uniname == 0x0)
				return len;
			uniname++;
			len++;
		}
		*uniname = 0x0; /* uniname[MAX_NAME_LENGTH-1] */
		return len;
	}

	for (i = 0; i < 4; i += 2) {
		*uniname = GET16_A(ep->unicode_11_12+i);
		if (*uniname == 0x0)
			return len;
		uniname++;
		len++;
	}

	*uniname = 0x0;
	return len;

} /* end of extract_uni_name_from_ext_entry */

s32 extract_uni_name_from_name_entry(NAME_DENTRY_T *ep, u16 *uniname, s32 order)
{
	int i, len = 0;

	for (i = 0; i < 30; i += 2) {
		*uniname = GET16_A(ep->unicode_0_14+i);
		if (*uniname == 0x0)
			return len;
		uniname++;
		len++;
	}

	*uniname = 0x0;
	return len;

} /* end of extract_uni_name_from_name_entry */

s32 fat_generate_dos_name(struct super_block *sb, CHAIN_T *p_dir, DOS_NAME_T *p_dosname)
{
	int i, j, count = 0, count_begin = FALSE;
	s32 dentries_per_clu;
	u32 type;
	u8 bmap[128/* 1 ~ 1023 */];
	CHAIN_T clu;
	DOS_DENTRY_T *ep;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	memset(bmap, 0, sizeof bmap);
	exfat_bitmap_set(bmap, 0);

	if (p_dir->dir == CLUSTER_32(0)) /* FAT16 root_dir */
		dentries_per_clu = p_fs->dentries_in_root;
	else
		dentries_per_clu = p_fs->dentries_per_clu;

	clu.dir = p_dir->dir;
	clu.flags = p_dir->flags;

	while (clu.dir != CLUSTER_32(~0)) {
		if (p_fs->dev_ejected)
			break;

		for (i = 0; i < dentries_per_clu; i++) {
			ep = (DOS_DENTRY_T *) get_entry_in_dir(sb, &clu, i, NULL);
			if (!ep)
				return FFS_MEDIAERR;

			type = p_fs->fs_func->get_entry_type((DENTRY_T *) ep);

			if (type == TYPE_UNUSED)
				break;
			if ((type != TYPE_FILE) && (type != TYPE_DIR))
				continue;

			count = 0;
			count_begin = FALSE;

			for (j = 0; j < 8; j++) {
				if (ep->name[j] == ' ')
					break;

				if (ep->name[j] == '~') {
					count_begin = TRUE;
				} else if (count_begin) {
					if ((ep->name[j] >= '0') && (ep->name[j] <= '9')) {
						count = count * 10 + (ep->name[j] - '0');
					} else {
						count = 0;
						count_begin = FALSE;
					}
				}
			}

			if ((count > 0) && (count < 1024))
				exfat_bitmap_set(bmap, count);
		}

		if (p_dir->dir == CLUSTER_32(0))
			break; /* FAT16 root_dir */

		if (FAT_read(sb, clu.dir, &(clu.dir)) != 0)
			return FFS_MEDIAERR;
	}

	count = 0;
	for (i = 0; i < 128; i++) {
		if (bmap[i] != 0xFF) {
			for (j = 0; j < 8; j++) {
				if (exfat_bitmap_test(&(bmap[i]), j) == 0) {
					count = (i << 3) + j;
					break;
				}
			}
			if (count != 0)
				break;
		}
	}

	if ((count == 0) || (count >= 1024))
		return FFS_FILEEXIST;
	else
		fat_attach_count_to_dos_name(p_dosname->name, count);

	/* Now dos_name has DOS~????.EXT */
	return FFS_SUCCESS;
} /* end of generate_dos_name */

void fat_attach_count_to_dos_name(u8 *dosname, s32 count)
{
	int i, j, length;
	char str_count[6];

	snprintf(str_count, sizeof str_count, "~%d", count);
	length = strlen(str_count);

	i = j = 0;
	while (j <= (8 - length)) {
		i = j;
		if (dosname[j] == ' ')
			break;
		if (dosname[j] & 0x80)
			j += 2;
		else
			j++;
	}

	for (j = 0; j < length; i++, j++)
		dosname[i] = (u8) str_count[j];

	if (i == 7)
		dosname[7] = ' ';

} /* end of attach_count_to_dos_name */

s32 fat_calc_num_entries(UNI_NAME_T *p_uniname)
{
	s32 len;

	len = p_uniname->name_len;
	if (len == 0)
		return 0;

	/* 1 dos name entry + extended entries */
	return (len-1) / 13 + 2;

} /* end of calc_num_enties */

s32 exfat_calc_num_entries(UNI_NAME_T *p_uniname)
{
	s32 len;

	len = p_uniname->name_len;
	if (len == 0)
		return 0;

	/* 1 file entry + 1 stream entry + name entries */
	return (len-1) / 15 + 3;

} /* end of exfat_calc_num_enties */

u8 calc_checksum_1byte(void *data, s32 len, u8 chksum)
{
	int i;
	u8 *c = (u8 *) data;

	for (i = 0; i < len; i++, c++)
		chksum = (((chksum & 1) << 7) | ((chksum & 0xFE) >> 1)) + *c;

	return chksum;
} /* end of calc_checksum_1byte */

u16 calc_checksum_2byte(void *data, s32 len, u16 chksum, s32 type)
{
	int i;
	u8 *c = (u8 *) data;

	switch (type) {
	case CS_DIR_ENTRY:
		for (i = 0; i < len; i++, c++) {
			if ((i == 2) || (i == 3))
				continue;
			chksum = (((chksum & 1) << 15) | ((chksum & 0xFFFE) >> 1)) + (u16) *c;
		}
		break;
	default
			:
		for (i = 0; i < len; i++, c++)
			chksum = (((chksum & 1) << 15) | ((chksum & 0xFFFE) >> 1)) + (u16) *c;
	}

	return chksum;
} /* end of calc_checksum_2byte */

u32 calc_checksum_4byte(void *data, s32 len, u32 chksum, s32 type)
{
	int i;
	u8 *c = (u8 *) data;

	switch (type) {
	case CS_PBR_SECTOR:
		for (i = 0; i < len; i++, c++) {
			if ((i == 106) || (i == 107) || (i == 112))
				continue;
			chksum = (((chksum & 1) << 31) | ((chksum & 0xFFFFFFFE) >> 1)) + (u32) *c;
		}
		break;
	default
			:
		for (i = 0; i < len; i++, c++)
			chksum = (((chksum & 1) << 31) | ((chksum & 0xFFFFFFFE) >> 1)) + (u32) *c;
	}

	return chksum;
} /* end of calc_checksum_4byte */

/*
 *  Name Resolution Functions
 */

/* return values of resolve_path()
   > 0 : return the length of the path
   < 0 : return error */
s32 resolve_path(struct inode *inode, char *path, CHAIN_T *p_dir, UNI_NAME_T *p_uniname)
{
	s32 lossy = FALSE;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	FILE_ID_T *fid = &(EXFAT_I(inode)->fid);

	if (strlen(path) >= (MAX_NAME_LENGTH * MAX_CHARSET_SIZE))
		return FFS_INVALIDPATH;

	strcpy(name_buf, path);

	nls_cstring_to_uniname(sb, p_uniname, name_buf, &lossy);
	if (lossy)
		return FFS_INVALIDPATH;

	fid->size = i_size_read(inode);

	p_dir->dir = fid->start_clu;
	p_dir->size = (s32)(fid->size >> p_fs->cluster_size_bits);
	p_dir->flags = fid->flags;

	return FFS_SUCCESS;
}

/*
 *  File Operation Functions
 */
static FS_FUNC_T fat_fs_func = {
	.alloc_cluster = fat_alloc_cluster,
	.free_cluster = fat_free_cluster,
	.count_used_clusters = fat_count_used_clusters,

	.init_dir_entry = fat_init_dir_entry,
	.init_ext_entry = fat_init_ext_entry,
	.find_dir_entry = fat_find_dir_entry,
	.delete_dir_entry = fat_delete_dir_entry,
	.get_uni_name_from_ext_entry = fat_get_uni_name_from_ext_entry,
	.count_ext_entries = fat_count_ext_entries,
	.calc_num_entries = fat_calc_num_entries,

	.get_entry_type = fat_get_entry_type,
	.set_entry_type = fat_set_entry_type,
	.get_entry_attr = fat_get_entry_attr,
	.set_entry_attr = fat_set_entry_attr,
	.get_entry_flag = fat_get_entry_flag,
	.set_entry_flag = fat_set_entry_flag,
	.get_entry_clu0 = fat_get_entry_clu0,
	.set_entry_clu0 = fat_set_entry_clu0,
	.get_entry_size = fat_get_entry_size,
	.set_entry_size = fat_set_entry_size,
	.get_entry_time = fat_get_entry_time,
	.set_entry_time = fat_set_entry_time,
};


s32 fat16_mount(struct super_block *sb, PBR_SECTOR_T *p_pbr)
{
	s32 num_reserved, num_root_sectors;
	BPB16_T *p_bpb = (BPB16_T *) p_pbr->bpb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);

	if (p_bpb->num_fats == 0)
		return FFS_FORMATERR;

	num_root_sectors = GET16(p_bpb->num_root_entries) << DENTRY_SIZE_BITS;
	num_root_sectors = ((num_root_sectors-1) >> p_bd->sector_size_bits) + 1;

	p_fs->sectors_per_clu = p_bpb->sectors_per_clu;
	p_fs->sectors_per_clu_bits = ilog2(p_bpb->sectors_per_clu);
	p_fs->cluster_size_bits = p_fs->sectors_per_clu_bits + p_bd->sector_size_bits;
	p_fs->cluster_size = 1 << p_fs->cluster_size_bits;

	p_fs->num_FAT_sectors = GET16(p_bpb->num_fat_sectors);

	p_fs->FAT1_start_sector = p_fs->PBR_sector + GET16(p_bpb->num_reserved);
	if (p_bpb->num_fats == 1)
		p_fs->FAT2_start_sector = p_fs->FAT1_start_sector;
	else
		p_fs->FAT2_start_sector = p_fs->FAT1_start_sector + p_fs->num_FAT_sectors;

	p_fs->root_start_sector = p_fs->FAT2_start_sector + p_fs->num_FAT_sectors;
	p_fs->data_start_sector = p_fs->root_start_sector + num_root_sectors;

	p_fs->num_sectors = GET16(p_bpb->num_sectors);
	if (p_fs->num_sectors == 0)
		p_fs->num_sectors = GET32(p_bpb->num_huge_sectors);

	num_reserved = p_fs->data_start_sector - p_fs->PBR_sector;
	p_fs->num_clusters = ((p_fs->num_sectors - num_reserved) >> p_fs->sectors_per_clu_bits) + 2;
	/* because the cluster index starts with 2 */

	if (p_fs->num_clusters < FAT12_THRESHOLD)
		p_fs->vol_type = FAT12;
	else
		p_fs->vol_type = FAT16;
	p_fs->vol_id = GET32(p_bpb->vol_serial);

	p_fs->root_dir = 0;
	p_fs->dentries_in_root = GET16(p_bpb->num_root_entries);
	p_fs->dentries_per_clu = 1 << (p_fs->cluster_size_bits - DENTRY_SIZE_BITS);

	p_fs->vol_flag = VOL_CLEAN;
	p_fs->clu_srch_ptr = 2;
	p_fs->used_clusters = (u32) ~0;

	p_fs->fs_func = &fat_fs_func;

	return FFS_SUCCESS;
} /* end of fat16_mount */

s32 fat32_mount(struct super_block *sb, PBR_SECTOR_T *p_pbr)
{
	s32 num_reserved;
	BPB32_T *p_bpb = (BPB32_T *) p_pbr->bpb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);

	if (p_bpb->num_fats == 0)
		return FFS_FORMATERR;

	p_fs->sectors_per_clu = p_bpb->sectors_per_clu;
	p_fs->sectors_per_clu_bits = ilog2(p_bpb->sectors_per_clu);
	p_fs->cluster_size_bits = p_fs->sectors_per_clu_bits + p_bd->sector_size_bits;
	p_fs->cluster_size = 1 << p_fs->cluster_size_bits;

	p_fs->num_FAT_sectors = GET32(p_bpb->num_fat32_sectors);

	p_fs->FAT1_start_sector = p_fs->PBR_sector + GET16(p_bpb->num_reserved);
	if (p_bpb->num_fats == 1)
		p_fs->FAT2_start_sector = p_fs->FAT1_start_sector;
	else
		p_fs->FAT2_start_sector = p_fs->FAT1_start_sector + p_fs->num_FAT_sectors;

	p_fs->root_start_sector = p_fs->FAT2_start_sector + p_fs->num_FAT_sectors;
	p_fs->data_start_sector = p_fs->root_start_sector;

	p_fs->num_sectors = GET32(p_bpb->num_huge_sectors);
	num_reserved = p_fs->data_start_sector - p_fs->PBR_sector;

	p_fs->num_clusters = ((p_fs->num_sectors-num_reserved) >> p_fs->sectors_per_clu_bits) + 2;
	/* because the cluster index starts with 2 */

	p_fs->vol_type = FAT32;
	p_fs->vol_id = GET32(p_bpb->vol_serial);

	p_fs->root_dir = GET32(p_bpb->root_cluster);
	p_fs->dentries_in_root = 0;
	p_fs->dentries_per_clu = 1 << (p_fs->cluster_size_bits - DENTRY_SIZE_BITS);

	p_fs->vol_flag = VOL_CLEAN;
	p_fs->clu_srch_ptr = 2;
	p_fs->used_clusters = (u32) ~0;

	p_fs->fs_func = &fat_fs_func;

	return FFS_SUCCESS;
} /* end of fat32_mount */

static FS_FUNC_T exfat_fs_func = {
	.alloc_cluster = exfat_alloc_cluster,
	.free_cluster = exfat_free_cluster,
	.count_used_clusters = exfat_count_used_clusters,

	.init_dir_entry = exfat_init_dir_entry,
	.init_ext_entry = exfat_init_ext_entry,
	.find_dir_entry = exfat_find_dir_entry,
	.delete_dir_entry = exfat_delete_dir_entry,
	.get_uni_name_from_ext_entry = exfat_get_uni_name_from_ext_entry,
	.count_ext_entries = exfat_count_ext_entries,
	.calc_num_entries = exfat_calc_num_entries,

	.get_entry_type = exfat_get_entry_type,
	.set_entry_type = exfat_set_entry_type,
	.get_entry_attr = exfat_get_entry_attr,
	.set_entry_attr = exfat_set_entry_attr,
	.get_entry_flag = exfat_get_entry_flag,
	.set_entry_flag = exfat_set_entry_flag,
	.get_entry_clu0 = exfat_get_entry_clu0,
	.set_entry_clu0 = exfat_set_entry_clu0,
	.get_entry_size = exfat_get_entry_size,
	.set_entry_size = exfat_set_entry_size,
	.get_entry_time = exfat_get_entry_time,
	.set_entry_time = exfat_set_entry_time,
};

s32 exfat_mount(struct super_block *sb, PBR_SECTOR_T *p_pbr)
{
	BPBEX_T *p_bpb = (BPBEX_T *) p_pbr->bpb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
	BD_INFO_T *p_bd = &(EXFAT_SB(sb)->bd_info);

	if (p_bpb->num_fats == 0)
		return FFS_FORMATERR;

	p_fs->sectors_per_clu = 1 << p_bpb->sectors_per_clu_bits;
	p_fs->sectors_per_clu_bits = p_bpb->sectors_per_clu_bits;
	p_fs->cluster_size_bits = p_fs->sectors_per_clu_bits + p_bd->sector_size_bits;
	p_fs->cluster_size = 1 << p_fs->cluster_size_bits;

	p_fs->num_FAT_sectors = GET32(p_bpb->fat_length);

	p_fs->FAT1_start_sector = p_fs->PBR_sector + GET32(p_bpb->fat_offset);
	if (p_bpb->num_fats == 1)
		p_fs->FAT2_start_sector = p_fs->FAT1_start_sector;
	else
		p_fs->FAT2_start_sector = p_fs->FAT1_start_sector + p_fs->num_FAT_sectors;

	p_fs->root_start_sector = p_fs->PBR_sector + GET32(p_bpb->clu_offset);
	p_fs->data_start_sector = p_fs->root_start_sector;

	p_fs->num_sectors = GET64(p_bpb->vol_length);
	p_fs->num_clusters = GET32(p_bpb->clu_count) + 2;
	/* because the cluster index starts with 2 */

	p_fs->vol_type = EXFAT;
	p_fs->vol_id = GET32(p_bpb->vol_serial);

	p_fs->root_dir = GET32(p_bpb->root_cluster);
	p_fs->dentries_in_root = 0;
	p_fs->dentries_per_clu = 1 << (p_fs->cluster_size_bits - DENTRY_SIZE_BITS);

	p_fs->vol_flag = (u32) GET16(p_bpb->vol_flags);
	p_fs->clu_srch_ptr = 2;
	p_fs->used_clusters = (u32) ~0;

	p_fs->fs_func = &exfat_fs_func;

	return FFS_SUCCESS;
} /* end of exfat_mount */

s32 create_dir(struct inode *inode, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, FILE_ID_T *fid)
{
	s32 ret, dentry, num_entries;
	u64 size;
	CHAIN_T clu;
	DOS_NAME_T dos_name, dot_name;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	ret = get_num_entries_and_dos_name(sb, p_dir, p_uniname, &num_entries, &dos_name);
	if (ret)
		return ret;

	/* find_empty_entry must be called before alloc_cluster */
	dentry = find_empty_entry(inode, p_dir, num_entries);
	if (dentry < 0)
		return FFS_FULL;

	clu.dir = CLUSTER_32(~0);
	clu.size = 0;
	clu.flags = (p_fs->vol_type == EXFAT) ? 0x03 : 0x01;

	/* (1) allocate a cluster */
	ret = p_fs->fs_func->alloc_cluster(sb, 1, &clu);
	if (ret < 0)
		return FFS_MEDIAERR;
	else if (ret == 0)
		return FFS_FULL;

	ret = clear_cluster(sb, clu.dir);
	if (ret != FFS_SUCCESS)
		return ret;

	if (p_fs->vol_type == EXFAT) {
		size = p_fs->cluster_size;
	} else {
		size = 0;

		/* initialize the . and .. entry
		   Information for . points to itself
		   Information for .. points to parent dir */

		dot_name.name_case = 0x0;
		memcpy(dot_name.name, DOS_CUR_DIR_NAME, DOS_NAME_LENGTH);

		ret = p_fs->fs_func->init_dir_entry(sb, &clu, 0, TYPE_DIR, clu.dir, 0);
		if (ret != FFS_SUCCESS)
			return ret;

		ret = p_fs->fs_func->init_ext_entry(sb, &clu, 0, 1, NULL, &dot_name);
		if (ret != FFS_SUCCESS)
			return ret;

		memcpy(dot_name.name, DOS_PAR_DIR_NAME, DOS_NAME_LENGTH);

		if (p_dir->dir == p_fs->root_dir)
			ret = p_fs->fs_func->init_dir_entry(sb, &clu, 1, TYPE_DIR, CLUSTER_32(0), 0);
		else
			ret = p_fs->fs_func->init_dir_entry(sb, &clu, 1, TYPE_DIR, p_dir->dir, 0);

		if (ret != FFS_SUCCESS)
			return ret;

		ret = p_fs->fs_func->init_ext_entry(sb, &clu, 1, 1, NULL, &dot_name);
		if (ret != FFS_SUCCESS)
			return ret;
	}

	/* (2) update the directory entry */
	/* make sub-dir entry in parent directory */
	ret = p_fs->fs_func->init_dir_entry(sb, p_dir, dentry, TYPE_DIR, clu.dir, size);
	if (ret != FFS_SUCCESS)
		return ret;

	ret = p_fs->fs_func->init_ext_entry(sb, p_dir, dentry, num_entries, p_uniname, &dos_name);
	if (ret != FFS_SUCCESS)
		return ret;

	fid->dir.dir = p_dir->dir;
	fid->dir.size = p_dir->size;
	fid->dir.flags = p_dir->flags;
	fid->entry = dentry;

	fid->attr = ATTR_SUBDIR;
	fid->flags = (p_fs->vol_type == EXFAT) ? 0x03 : 0x01;
	fid->size = size;
	fid->start_clu = clu.dir;

	fid->type = TYPE_DIR;
	fid->rwoffset = 0;
	fid->hint_last_off = -1;

	return FFS_SUCCESS;
} /* end of create_dir */

s32 create_file(struct inode *inode, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, u8 mode, FILE_ID_T *fid)
{
	s32 ret, dentry, num_entries;
	DOS_NAME_T dos_name;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	ret = get_num_entries_and_dos_name(sb, p_dir, p_uniname, &num_entries, &dos_name);
	if (ret)
		return ret;

	/* find_empty_entry must be called before alloc_cluster() */
	dentry = find_empty_entry(inode, p_dir, num_entries);
	if (dentry < 0)
		return FFS_FULL;

	/* (1) update the directory entry */
	/* fill the dos name directory entry information of the created file.
	   the first cluster is not determined yet. (0) */
	ret = p_fs->fs_func->init_dir_entry(sb, p_dir, dentry, TYPE_FILE | mode, CLUSTER_32(0), 0);
	if (ret != FFS_SUCCESS)
		return ret;

	ret = p_fs->fs_func->init_ext_entry(sb, p_dir, dentry, num_entries, p_uniname, &dos_name);
	if (ret != FFS_SUCCESS)
		return ret;

	fid->dir.dir = p_dir->dir;
	fid->dir.size = p_dir->size;
	fid->dir.flags = p_dir->flags;
	fid->entry = dentry;

	fid->attr = ATTR_ARCHIVE | mode;
	fid->flags = (p_fs->vol_type == EXFAT) ? 0x03 : 0x01;
	fid->size = 0;
	fid->start_clu = CLUSTER_32(~0);

	fid->type = TYPE_FILE;
	fid->rwoffset = 0;
	fid->hint_last_off = -1;

	return FFS_SUCCESS;
} /* end of create_file */

void remove_file(struct inode *inode, CHAIN_T *p_dir, s32 entry)
{
	s32 num_entries;
	sector_t sector;
	DENTRY_T *ep;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	ep = get_entry_in_dir(sb, p_dir, entry, &sector);
	if (!ep)
		return;

	buf_lock(sb, sector);

	/* buf_lock() before call count_ext_entries() */
	num_entries = p_fs->fs_func->count_ext_entries(sb, p_dir, entry, ep);
	if (num_entries < 0) {
		buf_unlock(sb, sector);
		return;
	}
	num_entries++;

	buf_unlock(sb, sector);

	/* (1) update the directory entry */
	p_fs->fs_func->delete_dir_entry(sb, p_dir, entry, 0, num_entries);
} /* end of remove_file */

s32 rename_file(struct inode *inode, CHAIN_T *p_dir, s32 oldentry, UNI_NAME_T *p_uniname, FILE_ID_T *fid)
{
	s32 ret, newentry = -1, num_old_entries, num_new_entries;
	sector_t sector_old, sector_new;
	DOS_NAME_T dos_name;
	DENTRY_T *epold, *epnew;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	epold = get_entry_in_dir(sb, p_dir, oldentry, &sector_old);
	if (!epold)
		return FFS_MEDIAERR;

	buf_lock(sb, sector_old);

	/* buf_lock() before call count_ext_entries() */
	num_old_entries = p_fs->fs_func->count_ext_entries(sb, p_dir, oldentry, epold);
	if (num_old_entries < 0) {
		buf_unlock(sb, sector_old);
		return FFS_MEDIAERR;
	}
	num_old_entries++;

	ret = get_num_entries_and_dos_name(sb, p_dir, p_uniname, &num_new_entries, &dos_name);
	if (ret) {
		buf_unlock(sb, sector_old);
		return ret;
	}

	if (num_old_entries < num_new_entries) {
		newentry = find_empty_entry(inode, p_dir, num_new_entries);
		if (newentry < 0) {
			buf_unlock(sb, sector_old);
			return FFS_FULL;
		}

		epnew = get_entry_in_dir(sb, p_dir, newentry, &sector_new);
		if (!epnew) {
			buf_unlock(sb, sector_old);
			return FFS_MEDIAERR;
		}

		memcpy((void *) epnew, (void *) epold, DENTRY_SIZE);
		if (p_fs->fs_func->get_entry_type(epnew) == TYPE_FILE) {
			p_fs->fs_func->set_entry_attr(epnew, p_fs->fs_func->get_entry_attr(epnew) | ATTR_ARCHIVE);
			fid->attr |= ATTR_ARCHIVE;
		}
		buf_modify(sb, sector_new);
		buf_unlock(sb, sector_old);

		if (p_fs->vol_type == EXFAT) {
			epold = get_entry_in_dir(sb, p_dir, oldentry+1, &sector_old);
			buf_lock(sb, sector_old);
			epnew = get_entry_in_dir(sb, p_dir, newentry+1, &sector_new);

			if (!epold || !epnew) {
				buf_unlock(sb, sector_old);
				return FFS_MEDIAERR;
			}

			memcpy((void *) epnew, (void *) epold, DENTRY_SIZE);
			buf_modify(sb, sector_new);
			buf_unlock(sb, sector_old);
		}

		ret = p_fs->fs_func->init_ext_entry(sb, p_dir, newentry, num_new_entries, p_uniname, &dos_name);
		if (ret != FFS_SUCCESS)
			return ret;

		p_fs->fs_func->delete_dir_entry(sb, p_dir, oldentry, 0, num_old_entries);
		fid->entry = newentry;
	} else {
		if (p_fs->fs_func->get_entry_type(epold) == TYPE_FILE) {
			p_fs->fs_func->set_entry_attr(epold, p_fs->fs_func->get_entry_attr(epold) | ATTR_ARCHIVE);
			fid->attr |= ATTR_ARCHIVE;
		}
		buf_modify(sb, sector_old);
		buf_unlock(sb, sector_old);

		ret = p_fs->fs_func->init_ext_entry(sb, p_dir, oldentry, num_new_entries, p_uniname, &dos_name);
		if (ret != FFS_SUCCESS)
			return ret;

		p_fs->fs_func->delete_dir_entry(sb, p_dir, oldentry, num_new_entries, num_old_entries);
	}

	return FFS_SUCCESS;
} /* end of rename_file */

s32 move_file(struct inode *inode, CHAIN_T *p_olddir, s32 oldentry, CHAIN_T *p_newdir, UNI_NAME_T *p_uniname, FILE_ID_T *fid)
{
	s32 ret, newentry, num_new_entries, num_old_entries;
	sector_t sector_mov, sector_new;
	CHAIN_T clu;
	DOS_NAME_T dos_name;
	DENTRY_T *epmov, *epnew;
	struct super_block *sb = inode->i_sb;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	epmov = get_entry_in_dir(sb, p_olddir, oldentry, &sector_mov);
	if (!epmov)
		return FFS_MEDIAERR;

	/* check if the source and target directory is the same */
	if (p_fs->fs_func->get_entry_type(epmov) == TYPE_DIR &&
		p_fs->fs_func->get_entry_clu0(epmov) == p_newdir->dir)
		return FFS_INVALIDPATH;

	buf_lock(sb, sector_mov);

	/* buf_lock() before call count_ext_entries() */
	num_old_entries = p_fs->fs_func->count_ext_entries(sb, p_olddir, oldentry, epmov);
	if (num_old_entries < 0) {
		buf_unlock(sb, sector_mov);
		return FFS_MEDIAERR;
	}
	num_old_entries++;

	ret = get_num_entries_and_dos_name(sb, p_newdir, p_uniname, &num_new_entries, &dos_name);
	if (ret) {
		buf_unlock(sb, sector_mov);
		return ret;
	}

	newentry = find_empty_entry(inode, p_newdir, num_new_entries);
	if (newentry < 0) {
		buf_unlock(sb, sector_mov);
		return FFS_FULL;
	}

	epnew = get_entry_in_dir(sb, p_newdir, newentry, &sector_new);
	if (!epnew) {
		buf_unlock(sb, sector_mov);
		return FFS_MEDIAERR;
	}

	memcpy((void *) epnew, (void *) epmov, DENTRY_SIZE);
	if (p_fs->fs_func->get_entry_type(epnew) == TYPE_FILE) {
		p_fs->fs_func->set_entry_attr(epnew, p_fs->fs_func->get_entry_attr(epnew) | ATTR_ARCHIVE);
		fid->attr |= ATTR_ARCHIVE;
	}
	buf_modify(sb, sector_new);
	buf_unlock(sb, sector_mov);

	if (p_fs->vol_type == EXFAT) {
		epmov = get_entry_in_dir(sb, p_olddir, oldentry+1, &sector_mov);
		buf_lock(sb, sector_mov);
		epnew = get_entry_in_dir(sb, p_newdir, newentry+1, &sector_new);
		if (!epmov || !epnew) {
			buf_unlock(sb, sector_mov);
			return FFS_MEDIAERR;
		}

		memcpy((void *) epnew, (void *) epmov, DENTRY_SIZE);
		buf_modify(sb, sector_new);
		buf_unlock(sb, sector_mov);
	} else if (p_fs->fs_func->get_entry_type(epnew) == TYPE_DIR) {
		/* change ".." pointer to new parent dir */
		clu.dir = p_fs->fs_func->get_entry_clu0(epnew);
		clu.flags = 0x01;

		epnew = get_entry_in_dir(sb, &clu, 1, &sector_new);
		if (!epnew)
			return FFS_MEDIAERR;

		if (p_newdir->dir == p_fs->root_dir)
			p_fs->fs_func->set_entry_clu0(epnew, CLUSTER_32(0));
		else
			p_fs->fs_func->set_entry_clu0(epnew, p_newdir->dir);
		buf_modify(sb, sector_new);
	}

	ret = p_fs->fs_func->init_ext_entry(sb, p_newdir, newentry, num_new_entries, p_uniname, &dos_name);
	if (ret != FFS_SUCCESS)
		return ret;

	p_fs->fs_func->delete_dir_entry(sb, p_olddir, oldentry, 0, num_old_entries);

	fid->dir.dir = p_newdir->dir;
	fid->dir.size = p_newdir->size;
	fid->dir.flags = p_newdir->flags;

	fid->entry = newentry;

	return FFS_SUCCESS;
} /* end of move_file */

/*
 *  Sector Read/Write Functions
 */

s32 sector_read(struct super_block *sb, sector_t sec, struct buffer_head **bh, s32 read)
{
	s32 ret = FFS_MEDIAERR;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	if ((sec >= (p_fs->PBR_sector+p_fs->num_sectors)) && (p_fs->num_sectors > 0)) {
		printk("[EXFAT] sector_read: out of range error! (sec = %llu)\n", (unsigned long long)sec);
		fs_error(sb);
		return ret;
	}

	if (!p_fs->dev_ejected) {
		ret = bdev_read(sb, sec, bh, 1, read);
		if (ret != FFS_SUCCESS)
			p_fs->dev_ejected = TRUE;
	}

	return ret;
} /* end of sector_read */

s32 sector_write(struct super_block *sb, sector_t sec, struct buffer_head *bh, s32 sync)
{
	s32 ret = FFS_MEDIAERR;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	if (sec >= (p_fs->PBR_sector+p_fs->num_sectors) && (p_fs->num_sectors > 0)) {
		printk("[EXFAT] sector_write: out of range error! (sec = %llu)\n", (unsigned long long)sec);
		fs_error(sb);
		return ret;
	}

	if (bh == NULL) {
		printk("[EXFAT] sector_write: bh is NULL!\n");
		fs_error(sb);
		return ret;
	}

	if (!p_fs->dev_ejected) {
		ret = bdev_write(sb, sec, bh, 1, sync);
		if (ret != FFS_SUCCESS)
			p_fs->dev_ejected = TRUE;
	}

	return ret;
} /* end of sector_write */

s32 multi_sector_read(struct super_block *sb, sector_t sec, struct buffer_head **bh, s32 num_secs, s32 read)
{
	s32 ret = FFS_MEDIAERR;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	if (((sec+num_secs) > (p_fs->PBR_sector+p_fs->num_sectors)) && (p_fs->num_sectors > 0)) {
		printk("[EXFAT] multi_sector_read: out of range error! (sec = %llu, num_secs = %d)\n",
		       (unsigned long long)sec, num_secs);
		fs_error(sb);
		return ret;
	}

	if (!p_fs->dev_ejected) {
		ret = bdev_read(sb, sec, bh, num_secs, read);
		if (ret != FFS_SUCCESS)
			p_fs->dev_ejected = TRUE;
	}

	return ret;
} /* end of multi_sector_read */

s32 multi_sector_write(struct super_block *sb, sector_t sec, struct buffer_head *bh, s32 num_secs, s32 sync)
{
	s32 ret = FFS_MEDIAERR;
	FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);

	if ((sec+num_secs) > (p_fs->PBR_sector+p_fs->num_sectors) && (p_fs->num_sectors > 0)) {
		printk("[EXFAT] multi_sector_write: out of range error! (sec = %llu, num_secs = %d)\n",
		       (unsigned long long)sec, num_secs);
		fs_error(sb);
		return ret;
	}
	if (bh == NULL) {
		printk("[EXFAT] multi_sector_write: bh is NULL!\n");
		fs_error(sb);
		return ret;
	}

	if (!p_fs->dev_ejected) {
		ret = bdev_write(sb, sec, bh, num_secs, sync);
		if (ret != FFS_SUCCESS)
			p_fs->dev_ejected = TRUE;
	}

	return ret;
} /* end of multi_sector_write */
