/*
 *  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, see <http://www.gnu.org/licenses/>.
 */

/************************************************************************/
/*                                                                      */
/*  PROJECT : exFAT & FAT12/16/32 File System                           */
/*  FILE    : core_fat.c                                                */
/*  PURPOSE : FAT-fs core code for sdFAT                                */
/*                                                                      */
/*----------------------------------------------------------------------*/
/*  NOTES                                                               */
/*                                                                      */
/*                                                                      */
/************************************************************************/

#include <linux/version.h>
#include <linux/blkdev.h>
#include <linux/workqueue.h>
#include <linux/kernel.h>
#include <linux/log2.h>

#include "sdfat.h"
#include "core.h"
#include <asm/byteorder.h>
#include <asm/unaligned.h>

/*----------------------------------------------------------------------*/
/*  Constant & Macro Definitions                                        */
/*----------------------------------------------------------------------*/
#define	MAX_LFN_ORDER	(20)

/*
 * MAX_EST_AU_SECT should be changed according to 32/64bits.
 * On 32bit, 4KB page supports 512 clusters per AU.
 * But, on 64bit, 4KB page can handle a half of total list_head of 32bit's.
 * Bcause the size of list_head structure on 64bit increases twofold over 32bit.
 */
#if (BITS_PER_LONG == 64)
//#define MAX_EST_AU_SECT	(16384) /* upto 8MB */
#define MAX_EST_AU_SECT	(32768) /* upto 16MB, used more page for list_head */
#else
#define MAX_EST_AU_SECT	(32768) /* upto 16MB */
#endif

/*======================================================================*/
/*  Local Function Declarations                                         */
/*======================================================================*/
static s32 __extract_uni_name_from_ext_entry(EXT_DENTRY_T *, u16 *, s32);

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

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

/*======================================================================*/
/*  Local Function Definitions                                          */
/*======================================================================*/
static u32 __calc_default_au_size(struct super_block *sb)
{
	struct block_device *bdev = sb->s_bdev;
	struct gendisk *disk;
	struct request_queue *queue;
	struct queue_limits *limit;
	unsigned int est_au_sect = MAX_EST_AU_SECT;
	unsigned int est_au_size = 0;
	unsigned int queue_au_size = 0;
	sector_t total_sect = 0;

	/* we assumed that sector size is 512 bytes */

	disk = bdev->bd_disk;
	if (!disk)
		goto out;

	queue = disk->queue;
	if (!queue)
		goto out;

	limit = &queue->limits;
	queue_au_size = limit->discard_granularity;

	/* estimate function(x) =
	 * (total_sect / 2) * 512 / 1024
	 * => (total_sect >> 1) >> 1)
	 * => (total_sect >> 2)
	 * => estimated bytes size
	 *
	 * ex1) <=  8GB -> 4MB
	 * ex2)    16GB -> 8MB
	 * ex3) >= 32GB -> 16MB
	 */
	total_sect = get_capacity(disk);
	est_au_size = total_sect >> 2;

	/* au_size assumed that bytes per sector is 512 */
	est_au_sect = est_au_size >> 9;

	MMSG("DBG1: total_sect(%llu) est_au_size(%u) est_au_sect(%u)\n",
			(u64)total_sect, est_au_size, est_au_sect);

	if (est_au_sect <= 8192) {
		/* 4MB */
		est_au_sect = 8192;
	} else if (est_au_sect <= 16384) {
		/* 8MB */
		est_au_sect = 16384;
	} else {
		/* 8MB or 16MB */
		est_au_sect = MAX_EST_AU_SECT;
	}

	MMSG("DBG2: total_sect(%llu) est_au_size(%u) est_au_sect(%u)\n",
			(u64)total_sect, est_au_size, est_au_sect);

	if (est_au_size < queue_au_size &&
			queue_au_size <= (MAX_EST_AU_SECT << 9)) {
		DMSG("use queue_au_size(%u) instead of est_au_size(%u)\n",
				queue_au_size, est_au_size);
		est_au_sect = queue_au_size >> 9;
	}

out:
	if (sb->s_blocksize != 512) {
		ASSERT(sb->s_blocksize_bits > 9);
		sdfat_log_msg(sb, KERN_INFO,
			"adjustment est_au_size by logical block size(%lu)",
			sb->s_blocksize);
		est_au_sect >>= (sb->s_blocksize_bits - 9);
	}

	sdfat_log_msg(sb, KERN_INFO, "set default AU sectors   : %u "
		"(queue_au_size : %u KB, disk_size : %llu MB)",
		est_au_sect, queue_au_size >> 10, (u64)(total_sect >> 11));
	return est_au_sect;
}


/*
 *  Cluster Management Functions
 */
static s32 fat_free_cluster(struct super_block *sb, CHAIN_T *p_chain, s32 do_relse)
{
	s32 ret = -EIO;
	s32 num_clusters = 0;
	u32 clu, prev;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
	s32 i;
	u64 sector;

	/* invalid cluster number */
	if (IS_CLUS_FREE(p_chain->dir) || IS_CLUS_EOF(p_chain->dir))
		return 0;

	/* no cluster to truncate */
	if (!p_chain->size) {
		DMSG("%s: cluster(%u) truncation is not required.",
			__func__, p_chain->dir);
		return 0;
	}

	/* check cluster validation */
	if (!is_valid_clus(fsi, p_chain->dir)) {
		EMSG("%s: invalid start cluster (%u)\n", __func__, p_chain->dir);
		sdfat_debug_bug_on(1);
		return -EIO;
	}


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

	do {
		if (do_relse) {
			sector = CLUS_TO_SECT(fsi, clu);
			for (i = 0; i < fsi->sect_per_clus; i++) {
				if (dcache_release(sb, sector+i) == -EIO)
					goto out;
			}
		}

		prev = clu;
		if (get_next_clus_safe(sb, &clu)) {
			/* print more helpful log */
			if (IS_CLUS_BAD(clu)) {
				sdfat_log_msg(sb, KERN_ERR, "%s : "
					"deleting bad cluster (clu[%u]->BAD)",
					__func__, prev);
			} else if (IS_CLUS_FREE(clu)) {
				sdfat_log_msg(sb, KERN_ERR, "%s : "
					"deleting free cluster (clu[%u]->FREE)",
					__func__, prev);
			}
			goto out;
		}

		/* Free FAT chain */
		if (fat_ent_set(sb, prev, CLUS_FREE))
			goto out;

		/* Update AMAP if needed */
		if (fsi->amap) {
			if (amap_release_cluster(sb, prev))
				return -EIO;
		}

		num_clusters++;

	} while (!IS_CLUS_EOF(clu));

	/* success */
	ret = 0;
out:
	fsi->used_clusters -= num_clusters;
	return ret;
} /* end of fat_free_cluster */

static s32 fat_alloc_cluster(struct super_block *sb, u32 num_alloc, CHAIN_T *p_chain, s32 dest)
{
	s32 ret = -ENOSPC;
	u32 i, num_clusters = 0, total_cnt;
	u32 new_clu, last_clu = CLUS_EOF, read_clu;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	total_cnt = fsi->num_clusters - CLUS_BASE;

	if (unlikely(total_cnt < fsi->used_clusters)) {
		sdfat_fs_error_ratelimit(sb,
				"%s : invalid used clusters(t:%u,u:%u)\n",
				__func__, total_cnt, fsi->used_clusters);
		return -EIO;
	}

	if (num_alloc > total_cnt - fsi->used_clusters)
		return -ENOSPC;

	new_clu = p_chain->dir;
	if (IS_CLUS_EOF(new_clu))
		new_clu = fsi->clu_srch_ptr;
	else if (new_clu >= fsi->num_clusters)
		new_clu = CLUS_BASE;

	set_sb_dirty(sb);

	p_chain->dir = CLUS_EOF;

	for (i = CLUS_BASE; i < fsi->num_clusters; i++) {
		if (fat_ent_get(sb, new_clu, &read_clu)) {
			ret = -EIO;
			goto error;
		}

		if (IS_CLUS_FREE(read_clu)) {
			if (fat_ent_set(sb, new_clu, CLUS_EOF)) {
				ret = -EIO;
				goto error;
			}
			num_clusters++;

			if (IS_CLUS_EOF(p_chain->dir)) {
				p_chain->dir = new_clu;
			} else {
				if (fat_ent_set(sb, last_clu, new_clu)) {
					ret = -EIO;
					goto error;
				}
			}

			last_clu = new_clu;

			if ((--num_alloc) == 0) {
				fsi->clu_srch_ptr = new_clu;
				fsi->used_clusters += num_clusters;

				return 0;
			}
		}
		if ((++new_clu) >= fsi->num_clusters)
			new_clu = CLUS_BASE;
	}
error:
	if (num_clusters)
		fat_free_cluster(sb, p_chain, 0);
	return ret;
} /* end of fat_alloc_cluster */

static s32 fat_count_used_clusters(struct super_block *sb, u32 *ret_count)
{
	s32 i;
	u32 clu, count = 0;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	for (i = CLUS_BASE; i < fsi->num_clusters; i++) {
		if (fat_ent_get(sb, i, &clu))
			return -EIO;

		if (!IS_CLUS_FREE(clu))
			count++;
	}

	*ret_count = count;
	return 0;
} /* end of fat_count_used_clusters */


/*
 *  Directory Entry Management Functions
 */
static u32 fat_get_entry_type(DENTRY_T *p_entry)
{
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *)p_entry;

	/* first byte of 32bytes dummy */
	if (*(ep->name) == MSDOS_UNUSED)
		return TYPE_UNUSED;

	/* 0xE5 of Kanji Japanese is replaced to 0x05 */
	else if (*(ep->name) == MSDOS_DELETED)
		return TYPE_DELETED;

	/* 11th byte of 32bytes dummy */
	else if ((ep->attr & ATTR_EXTEND_MASK) == ATTR_EXTEND)
		return TYPE_EXTEND;

	else if (!(ep->attr & (ATTR_SUBDIR | ATTR_VOLUME)))
		return TYPE_FILE;

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

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

	return TYPE_INVALID;
} /* end of fat_get_entry_type */

static 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) = MSDOS_UNUSED; /* 0x0 */

	else if (type == TYPE_DELETED)
		*(ep->name) = MSDOS_DELETED; /* 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 */

static 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 */

static 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 */

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

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

static u32 fat_get_entry_clu0(DENTRY_T *p_entry)
{
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *)p_entry;
	/* FIXME : is ok? */
	return(((u32)(le16_to_cpu(ep->start_clu_hi)) << 16) | le16_to_cpu(ep->start_clu_lo));
} /* end of fat_get_entry_clu0 */

static void fat_set_entry_clu0(DENTRY_T *p_entry, u32 start_clu)
{
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *)p_entry;

	ep->start_clu_lo = cpu_to_le16(CLUSTER_16(start_clu));
	ep->start_clu_hi = cpu_to_le16(CLUSTER_16(start_clu >> 16));
} /* end of fat_set_entry_clu0 */

static u64 fat_get_entry_size(DENTRY_T *p_entry)
{
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *)p_entry;

	return (u64)le32_to_cpu(ep->size);
} /* end of fat_get_entry_size */

static void fat_set_entry_size(DENTRY_T *p_entry, u64 size)
{
	DOS_DENTRY_T *ep = (DOS_DENTRY_T *)p_entry;

	ep->size = cpu_to_le32((u32)size);
} /* end of fat_set_entry_size */

static 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 = le16_to_cpu(ep->create_time);
		d = le16_to_cpu(ep->create_date);
		break;
	case TM_MODIFY:
		t = le16_to_cpu(ep->modify_time);
		d = le16_to_cpu(ep->modify_date);
		break;
	}

	tp->tz.value = 0x00;
	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 */

static 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:
		ep->create_time = cpu_to_le16(t);
		ep->create_date = cpu_to_le16(d);
		break;
	case TM_MODIFY:
		ep->modify_time = cpu_to_le16(t);
		ep->modify_date = cpu_to_le16(d);
		break;
	}
} /* end of fat_set_entry_time */

static void __init_dos_entry(struct super_block *sb, DOS_DENTRY_T *ep, u32 type, u32 start_clu)
{
	TIMESTAMP_T tm, *tp;

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

	tp = tm_now_sb(sb, &tm);
	fat_set_entry_time((DENTRY_T *) ep, tp, TM_CREATE);
	fat_set_entry_time((DENTRY_T *) ep, tp, TM_MODIFY);
	ep->access_date = 0;
	ep->create_time_ms = 0;
} /* end of __init_dos_entry */

static void __init_ext_entry(EXT_DENTRY_T *ep, s32 order, u8 chksum, u16 *uniname)
{
	s32 i;
	u8 end = false;

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

	/* unaligned name */
	for (i = 0; i < 5; i++) {
		if (!end) {
			put_unaligned_le16(*uniname, &(ep->unicode_0_4[i<<1]));
			if (*uniname == 0x0)
				end = true;
			else
				uniname++;
		} else {
			put_unaligned_le16(0xFFFF, &(ep->unicode_0_4[i<<1]));
		}
	}

	/* aligned name */
	for (i = 0; i < 6; i++) {
		if (!end) {
			ep->unicode_5_10[i] = cpu_to_le16(*uniname);
			if (*uniname == 0x0)
				end = true;
			else
				uniname++;
		} else {
			ep->unicode_5_10[i] = cpu_to_le16(0xFFFF);
		}
	}

	/* aligned name */
	for (i = 0; i < 2; i++) {
		if (!end) {
			ep->unicode_11_12[i] = cpu_to_le16(*uniname);
			if (*uniname == 0x0)
				end = true;
			else
				uniname++;
		} else {
			ep->unicode_11_12[i] = cpu_to_le16(0xFFFF);
		}
	}
} /* end of __init_ext_entry */

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

	dos_ep = (DOS_DENTRY_T *) get_dentry_in_dir(sb, p_dir, entry, &sector);
	if (!dos_ep)
		return -EIO;

	__init_dos_entry(sb, dos_ep, type, start_clu);
	dcache_modify(sb, sector);

	return 0;
} /* end of fat_init_dir_entry */

static 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)
{
	s32 i;
	u64 sector;
	u8 chksum;
	u16 *uniname = p_uniname->name;
	DOS_DENTRY_T *dos_ep;
	EXT_DENTRY_T *ext_ep;

	dos_ep = (DOS_DENTRY_T *) get_dentry_in_dir(sb, p_dir, entry, &sector);
	if (!dos_ep)
		return -EIO;

	dos_ep->lcase = p_dosname->name_case;
	memcpy(dos_ep->name, p_dosname->name, DOS_NAME_LENGTH);
	if (dcache_modify(sb, sector))
		return -EIO;

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

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

			__init_ext_entry(ext_ep, i, chksum, uniname);
			if (dcache_modify(sb, sector))
				return -EIO;
			uniname += 13;
		}

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

		__init_ext_entry(ext_ep, i+MSDOS_LAST_LFN, chksum, uniname);
		if (dcache_modify(sb, sector))
			return -EIO;
	}

	return 0;
} /* end of fat_init_ext_entry */

static s32 fat_delete_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 order, s32 num_entries)
{
	s32 i;
	u64 sector;
	DENTRY_T *ep;

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

		fat_set_entry_type(ep, TYPE_DELETED);
		if (dcache_modify(sb, sector))
			return -EIO;
	}

	return 0;
}

/* return values of fat_find_dir_entry()
 * >= 0 : return dir entiry position with the name in dir
 * -EEXIST : (root dir, ".") it is the root dir itself
 * -ENOENT : entry with the name does not exist
 * -EIO    : I/O error
 */
static inline s32 __get_dentries_per_clu(FS_INFO_T *fsi, s32 clu)
{
	if (IS_CLUS_FREE(clu)) /* FAT16 root_dir */
		return fsi->dentries_in_root;

	return fsi->dentries_per_clu;
}

static s32 fat_find_dir_entry(struct super_block *sb, FILE_ID_T *fid,
		CHAIN_T *p_dir, UNI_NAME_T *p_uniname, s32 num_entries, DOS_NAME_T *p_dosname, u32 type)
{
	s32 i, rewind = 0, dentry = 0, end_eidx = 0;
	s32 chksum = 0, lfn_ord = 0, lfn_len = 0;
	s32 dentries_per_clu, num_empty = 0;
	u32 entry_type;
	u16 entry_uniname[14], *uniname = NULL;
	CHAIN_T clu;
	DENTRY_T *ep;
	HINT_T *hint_stat = &fid->hint_stat;
	HINT_FEMP_T candi_empty;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	/*
	 * REMARK:
	 * DOT and DOTDOT are handled by VFS layer
	 */

	dentries_per_clu = __get_dentries_per_clu(fsi, p_dir->dir);
	clu.dir = p_dir->dir;
	clu.flags = p_dir->flags;

	if (hint_stat->eidx) {
		clu.dir = hint_stat->clu;
		dentry = hint_stat->eidx;
		end_eidx = dentry;
	}

	candi_empty.eidx = -1;

	MMSG("lookup dir= %s\n", p_dosname->name);
rewind:
	while (!IS_CLUS_EOF(clu.dir)) {
		i = dentry % dentries_per_clu;
		for (; i < dentries_per_clu; i++, dentry++) {
			if (rewind && (dentry == end_eidx))
				goto not_found;

			ep = get_dentry_in_dir(sb, &clu, i, NULL);
			if (!ep)
				return -EIO;

			entry_type = fat_get_entry_type(ep);

			/*
			 * Most directory entries have long name,
			 * So, we check extend directory entry first.
			 */
			if (entry_type == TYPE_EXTEND) {
				EXT_DENTRY_T *ext_ep = (EXT_DENTRY_T *)ep;
				u32 cur_ord = (u32)ext_ep->order;
				u32 cur_chksum = (s32)ext_ep->checksum;
				u32 new_lfn = (cur_ord & MSDOS_LAST_LFN);
				s32 len = 13;
				u16 unichar;

				cur_ord &= ~(MSDOS_LAST_LFN);
				num_empty = 0;
				candi_empty.eidx = -1;

				/* check invalid lfn order */
				if (!cur_ord || (cur_ord > MAX_LFN_ORDER))
					goto reset_dentry_set;

				/* check whether new lfn or not */
				if (new_lfn) {
					chksum = cur_chksum;
					len = (13 * (cur_ord - 1));
					uniname = (p_uniname->name + len);
					lfn_ord = cur_ord + 1;
					lfn_len = 0;

					/* check minimum name length */
					if (len > p_uniname->name_len) {
						/* MISMATCHED NAME LENGTH */
						lfn_len = -1;
					}
					len = 0;
				}

				/* check if lfn is a consecutive number */
				if ((cur_ord + 1) != lfn_ord)
					goto reset_dentry_set;

				/* check checksum of directory entry set */
				if (cur_chksum != chksum)
					goto reset_dentry_set;

				/* update order for next dentry */
				lfn_ord = cur_ord;

				/* check whether mismatched lfn or not */
				if (lfn_len == -1) {
					/* MISMATCHED LFN DENTRY SET */
					continue;
				}

				if (!uniname) {
					sdfat_fs_error(sb,
						"%s : abnormal dentry "
						"(start_clu[%u], "
						"idx[%u])", __func__,
						p_dir->dir, dentry);
					sdfat_debug_bug_on(1);
					return -EIO;
				}

				/* update position of name buffer */
				uniname -= len;

				/* get utf16 characters saved on this entry */
				len = __extract_uni_name_from_ext_entry(ext_ep, entry_uniname, lfn_ord);

				/* replace last char to null */
				unichar = *(uniname+len);
				*(uniname+len) = (u16)0x0;

				/* uniname ext_dentry unit compare repeatdly */
				if (nls_cmp_uniname(sb, uniname, entry_uniname)) {
					/* DO HANDLE WRONG NAME */
					lfn_len = -1;
				} else {
					/* add matched chars length */
					lfn_len += len;
				}

				/* restore previous character */
				*(uniname+len) = unichar;

				/* jump to check next dentry */
				continue;

			} else if ((entry_type == TYPE_FILE) || (entry_type == TYPE_DIR)) {
				DOS_DENTRY_T *dos_ep = (DOS_DENTRY_T *)ep;
				u32 cur_chksum = (s32)calc_chksum_1byte(
							(void *) dos_ep->name,
							DOS_NAME_LENGTH, 0);

				num_empty = 0;
				candi_empty.eidx = -1;

				MMSG("checking dir= %c%c%c%c%c%c%c%c%c%c%c\n",
					dos_ep->name[0], dos_ep->name[1],
					dos_ep->name[2], dos_ep->name[3],
					dos_ep->name[4], dos_ep->name[5],
					dos_ep->name[6], dos_ep->name[7],
					dos_ep->name[8], dos_ep->name[9],
					dos_ep->name[10]);

				/*
				 * if there is no valid long filename,
				 * we should check short filename.
				 */
				if (!lfn_len || (cur_chksum != chksum)) {
					/* check shortname */
					if ((p_dosname->name[0] != '\0') &&
						!nls_cmp_sfn(sb,
							p_dosname->name,
							dos_ep->name)) {
						goto found;
					}
				/* check name length */
				} else if ((lfn_len > 0) &&
						((s32)p_uniname->name_len ==
						 lfn_len)) {
					goto found;
				}

				/* DO HANDLE MISMATCHED SFN, FALL THROUGH */
			} else if ((entry_type == TYPE_UNUSED) || (entry_type == TYPE_DELETED)) {
				num_empty++;
				if (candi_empty.eidx == -1) {
					if (num_empty == 1) {
						candi_empty.cur.dir = clu.dir;
						candi_empty.cur.size = clu.size;
						candi_empty.cur.flags = clu.flags;
					}

					if (num_empty >= num_entries) {
						candi_empty.eidx = dentry - (num_empty - 1);
						ASSERT(0 <= candi_empty.eidx);
						candi_empty.count = num_empty;

						if ((fid->hint_femp.eidx == -1) ||
								(candi_empty.eidx <= fid->hint_femp.eidx)) {
							memcpy(&fid->hint_femp,
									&candi_empty,
									sizeof(HINT_FEMP_T));
						}
					}
				}

				if (entry_type == TYPE_UNUSED)
					goto not_found;
				/* FALL THROUGH */
			}
reset_dentry_set:
			/* TYPE_DELETED, TYPE_VOLUME OR MISMATCHED SFN */
			lfn_ord = 0;
			lfn_len = 0;
			chksum = 0;
		}

		if (IS_CLUS_FREE(p_dir->dir))
			break; /* FAT16 root_dir */

		if (get_next_clus_safe(sb, &clu.dir))
			return -EIO;
	}

not_found:
	/* we started at not 0 index,so we should try to find target
	 * from 0 index to the index we started at.
	 */
	if (!rewind && end_eidx) {
		rewind = 1;
		dentry = 0;
		clu.dir = p_dir->dir;
		/* reset dentry set */
		lfn_ord = 0;
		lfn_len = 0;
		chksum = 0;
		/* reset empty hint_*/
		num_empty = 0;
		candi_empty.eidx = -1;
		goto rewind;
	}

	/* initialized hint_stat */
	hint_stat->clu = p_dir->dir;
	hint_stat->eidx = 0;
	return -ENOENT;

found:
	/* next dentry we'll find is out of this cluster */
	if (!((dentry + 1) % dentries_per_clu)) {
		int ret = 0;
		/* FAT16 root_dir */
		if (IS_CLUS_FREE(p_dir->dir))
			clu.dir = CLUS_EOF;
		else
			ret = get_next_clus_safe(sb, &clu.dir);

		if (ret || IS_CLUS_EOF(clu.dir)) {
			/* just initialized hint_stat */
			hint_stat->clu = p_dir->dir;
			hint_stat->eidx = 0;
			return dentry;
		}
	}

	hint_stat->clu = clu.dir;
	hint_stat->eidx = dentry + 1;
	return dentry;
} /* end of fat_find_dir_entry */

/* returns -EIO on error */
static 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;

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

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

		if ((fat_get_entry_type((DENTRY_T *)ext_ep) == TYPE_EXTEND) &&
			(ext_ep->checksum == chksum)) {
			count++;
			if (ext_ep->order > MSDOS_LAST_LFN)
				return count;
		} else {
			return count;
		}
	}

	return count;
}


/*
 *  Name Conversion Functions
 */
static s32 __extract_uni_name_from_ext_entry(EXT_DENTRY_T *ep, u16 *uniname, s32 order)
{
	s32 i, len = 0;

	for (i = 0; i < 5; i++) {
		*uniname = get_unaligned_le16(&(ep->unicode_0_4[i<<1]));
		if (*uniname == 0x0)
			return len;
		uniname++;
		len++;
	}

	if (order < 20) {
		for (i = 0; i < 6; i++) {
			/* FIXME : unaligned? */
			*uniname = le16_to_cpu(ep->unicode_5_10[i]);
			if (*uniname == 0x0)
				return len;
			uniname++;
			len++;
		}
	} else {
		for (i = 0; i < 4; i++) {
			/* FIXME : unaligned? */
			*uniname = le16_to_cpu(ep->unicode_5_10[i]);
			if (*uniname == 0x0)
				return len;
			uniname++;
			len++;
		}
		*uniname = 0x0; /* uniname[MAX_NAME_LENGTH] */
		return len;
	}

	for (i = 0; i < 2; i++) {
		/* FIXME : unaligned? */
		*uniname = le16_to_cpu(ep->unicode_11_12[i]);
		if (*uniname == 0x0)
			return len;
		uniname++;
		len++;
	}

	*uniname = 0x0;
	return len;

} /* end of __extract_uni_name_from_ext_entry */

static void fat_get_uniname_from_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u16 *uniname)
{
	u32 i;
	u16 *name = uniname;
	u32 chksum;

	DOS_DENTRY_T *dos_ep =
		(DOS_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, NULL);

	if (unlikely(!dos_ep))
		goto invalid_lfn;

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

	for (entry--, i = 1; entry >= 0; entry--, i++) {
		EXT_DENTRY_T *ep;

		ep = (EXT_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, NULL);
		if (!ep)
			goto invalid_lfn;

		if (fat_get_entry_type((DENTRY_T *) ep) != TYPE_EXTEND)
			goto invalid_lfn;

		if (chksum != (u32)ep->checksum)
			goto invalid_lfn;

		if (i != (u32)(ep->order & ~(MSDOS_LAST_LFN)))
			goto invalid_lfn;

		__extract_uni_name_from_ext_entry(ep, name, (s32)i);
		if (ep->order & MSDOS_LAST_LFN)
			return;

		name += 13;
	}
invalid_lfn:
	*uniname = (u16)0x0;
} /* end of fat_get_uniname_from_ext_entry */

/* Find if the shortname exists
 * and check if there are free entries
 */
static s32 __fat_find_shortname_entry(struct super_block *sb, CHAIN_T *p_dir,
		u8 *p_dosname, s32 *offset, __attribute__((unused))int n_entry_needed)
{
	u32 type;
	s32 i, dentry = 0;
	s32 dentries_per_clu;
	DENTRY_T *ep = NULL;
	DOS_DENTRY_T *dos_ep = NULL;
	CHAIN_T clu = *p_dir;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	if (offset)
		*offset = -1;

	if (IS_CLUS_FREE(clu.dir)) /* FAT16 root_dir */
		dentries_per_clu = fsi->dentries_in_root;
	else
		dentries_per_clu = fsi->dentries_per_clu;

	while (!IS_CLUS_EOF(clu.dir)) {
		for (i = 0; i < dentries_per_clu; i++, dentry++) {
			ep = get_dentry_in_dir(sb, &clu, i, NULL);
			if (!ep)
				return -EIO;

			type = fat_get_entry_type(ep);

			if ((type == TYPE_FILE) || (type == TYPE_DIR))  {
				dos_ep = (DOS_DENTRY_T *)ep;
				if (!nls_cmp_sfn(sb, p_dosname, dos_ep->name)) {
					if (offset)
						*offset = dentry;
					return 0;
				}
			}
		}

		/* fat12/16 root dir */
		if (IS_CLUS_FREE(clu.dir))
			break;

		if (get_next_clus_safe(sb, &clu.dir))
			return -EIO;
	}
	return -ENOENT;
}

#ifdef CONFIG_SDFAT_FAT32_SHORTNAME_SEQ
static void __fat_attach_count_to_dos_name(u8 *dosname, s32 count)
{
	s32 i, j, length;
	s8 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 __fat_attach_count_to_dos_name */
#endif

s32 fat_generate_dos_name_new(struct super_block *sb, CHAIN_T *p_dir, DOS_NAME_T *p_dosname, s32 n_entry_needed)
{
	s32 i;
	s32 baselen, err;
	u8 work[DOS_NAME_LENGTH], buf[5];
	u8 tail;

	baselen = 8;
	memset(work, ' ', DOS_NAME_LENGTH);
	memcpy(work, p_dosname->name, DOS_NAME_LENGTH);

	while (baselen && (work[--baselen] == ' ')) {
		/* DO NOTHING, JUST FOR CHECK_PATCH */
	}

	if (baselen > 6)
		baselen = 6;

	BUG_ON(baselen < 0);

#ifdef CONFIG_SDFAT_FAT32_SHORTNAME_SEQ
	/* example) namei_exfat.c -> NAMEI_~1 - NAMEI_~9 */
	work[baselen] = '~';
	for (i = 1; i < 10; i++) {
		// '0' + i = 1 ~ 9 ASCII
		work[baselen + 1] = '0' + i;
		err = __fat_find_shortname_entry(sb, p_dir, work, NULL, n_entry_needed);
		if (err == -ENOENT) {
			/* void return */
			__fat_attach_count_to_dos_name(p_dosname->name, i);
			return 0;
		}

		/* any other error */
		if (err)
			return err;
	}
#endif

	i = jiffies;
	tail = (jiffies >> 16) & 0x7;

	if (baselen > 2)
		baselen = 2;

	BUG_ON(baselen < 0);

	work[baselen + 4] = '~';
	// 1 ~ 8 ASCII
	work[baselen + 5] = '1' + tail;
	while (1) {
		snprintf(buf, sizeof(buf), "%04X", i & 0xffff);
		memcpy(&work[baselen], buf, 4);
		err = __fat_find_shortname_entry(sb, p_dir, work, NULL, n_entry_needed);
		if (err == -ENOENT) {
			memcpy(p_dosname->name, work, DOS_NAME_LENGTH);
			break;
		}

		/* any other error */
		if (err)
			return err;

		i -= 11;
	}
	return 0;
} /* end of generate_dos_name_new */

static 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 */

static s32 fat_check_max_dentries(FILE_ID_T *fid)
{
	if ((fid->size >> DENTRY_SIZE_BITS) >= MAX_FAT_DENTRIES) {
		/* FAT spec allows a dir to grow upto 65536 dentries */
		return -ENOSPC;
	}
	return 0;
} /* end of check_max_dentries */


/*
 *  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_uniname_from_ext_entry = fat_get_uniname_from_ext_entry,
	.count_ext_entries = fat_count_ext_entries,
	.calc_num_entries = fat_calc_num_entries,
	.check_max_dentries = fat_check_max_dentries,

	.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,
};

static FS_FUNC_T amap_fat_fs_func = {
	.alloc_cluster = amap_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_uniname_from_ext_entry = fat_get_uniname_from_ext_entry,
	.count_ext_entries = fat_count_ext_entries,
	.calc_num_entries = fat_calc_num_entries,
	.check_max_dentries = fat_check_max_dentries,

	.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,

	.get_au_stat = amap_get_au_stat,
};

static s32 mount_fat_common(struct super_block *sb, FS_INFO_T *fsi,
			    bpb_t *p_bpb, u32 root_sects)
{
	bool fat32 = root_sects == 0 ? true : false;

	fsi->sect_per_clus = p_bpb->sect_per_clus;
	if (!is_power_of_2(fsi->sect_per_clus)) {
		sdfat_msg(sb, KERN_ERR, "bogus sectors per cluster %u",
			  fsi->sect_per_clus);
		return -EINVAL;
	}

	fsi->sect_per_clus_bits = ilog2(p_bpb->sect_per_clus);
	fsi->cluster_size_bits = fsi->sect_per_clus_bits + sb->s_blocksize_bits;
	fsi->cluster_size = 1 << fsi->cluster_size_bits;
	fsi->dentries_per_clu = 1 <<
				(fsi->cluster_size_bits - DENTRY_SIZE_BITS);

	fsi->vol_flag = VOL_CLEAN;
	fsi->clu_srch_ptr = CLUS_BASE;
	fsi->used_clusters = (u32)~0;
	fsi->fs_func = &fat_fs_func;

	fsi->num_FAT_sectors = le16_to_cpu(p_bpb->num_fat_sectors);
	if (fat32) {
		u32 fat32_len = le32_to_cpu(p_bpb->f32.num_fat32_sectors);

		if (fat32_len) {
			fsi->num_FAT_sectors = fat32_len;
		} else if (fsi->num_FAT_sectors) {
			/* SPEC violation for compatibility */
			sdfat_msg(sb, KERN_WARNING,
				  "no fatsz32, try with fatsz16: %u",
				  fsi->num_FAT_sectors);
		}
	}

	if (!fsi->num_FAT_sectors) {
		sdfat_msg(sb, KERN_ERR, "bogus fat size");
		return -EINVAL;
	}

	if (!p_bpb->num_fats) {
		sdfat_msg(sb, KERN_ERR, "bogus number of FAT structure");
		return -EINVAL;
	}

	if (p_bpb->num_fats > 2) {
		sdfat_msg(sb, KERN_WARNING,
			"unsupported number of FAT structure :%u, try with 2",
			p_bpb->num_fats);
	}

	fsi->FAT1_start_sector = le16_to_cpu(p_bpb->num_reserved);
	if (p_bpb->num_fats == 1)
		fsi->FAT2_start_sector = fsi->FAT1_start_sector;
	else
		fsi->FAT2_start_sector = fsi->FAT1_start_sector +
					 fsi->num_FAT_sectors;

	fsi->root_start_sector = fsi->FAT2_start_sector + fsi->num_FAT_sectors;
	fsi->data_start_sector = fsi->root_start_sector + root_sects;

	/* SPEC violation for compatibility */
	fsi->num_sectors = get_unaligned_le16(p_bpb->num_sectors);
	if (!fsi->num_sectors)
		fsi->num_sectors = le32_to_cpu(p_bpb->num_huge_sectors);

	if (!fsi->num_sectors) {
		sdfat_msg(sb, KERN_ERR, "bogus number of total sector count");
		return -EINVAL;
	}

	/* because the cluster index starts with 2 */
	fsi->num_clusters = (u32)((fsi->num_sectors - fsi->data_start_sector) >>
				  fsi->sect_per_clus_bits) + CLUS_BASE;

	return 0;
}

s32 mount_fat16(struct super_block *sb, pbr_t *p_pbr)
{
	u32 num_root_sectors;
	bpb_t *p_bpb = &(p_pbr->bpb.fat);
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	fsi->vol_id = get_unaligned_le32(p_bpb->f16.vol_serial);
	fsi->root_dir = 0;
	fsi->dentries_in_root = get_unaligned_le16(p_bpb->num_root_entries);
	if (!fsi->dentries_in_root) {
		sdfat_msg(sb, KERN_ERR, "bogus number of max dentry count "
					"of the root directory");
		return -EINVAL;
	}

	num_root_sectors = fsi->dentries_in_root << DENTRY_SIZE_BITS;
	num_root_sectors = ((num_root_sectors - 1) >> sb->s_blocksize_bits) + 1;

	if (mount_fat_common(sb, fsi, p_bpb, num_root_sectors))
		return -EINVAL;

	fsi->vol_type = FAT16;
	if (fsi->num_clusters < FAT12_THRESHOLD)
		fsi->vol_type = FAT12;
	fat_ent_ops_init(sb);

	if (p_bpb->f16.state & FAT_VOL_DIRTY) {
		fsi->vol_flag |= VOL_DIRTY;
		sdfat_log_msg(sb, KERN_WARNING, "Volume was not properly "
			"unmounted. Some data may be corrupt. "
			"Please run fsck.");
	}

	return 0;
} /* end of mount_fat16 */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
static u8 bdev_get_partno(struct block_device *bdev)
{
	return bdev->bd_partno;
}
#else
static struct block_device *bdev_whole(struct block_device *bdev)
{
	return bdev->bd_contains;
}

static u8 bdev_get_partno(struct block_device *bdev)
{
	return bdev->bd_part->partno;
}
#endif

static sector_t __calc_hidden_sect(struct super_block *sb)
{
	struct block_device *bdev = sb->s_bdev;
	sector_t hidden = 0;

	if (!bdev)
		goto out;

	hidden = get_start_sect(bdev);
	/* a disk device, not a partition */
	if (!hidden) {
		if (bdev != bdev_whole(bdev))
			sdfat_log_msg(sb, KERN_WARNING,
				"hidden(0), but disk has a partition table");
		goto out;
	}

	if (sb->s_blocksize_bits != 9) {
		ASSERT(sb->s_blocksize_bits > 9);
		hidden >>= (sb->s_blocksize_bits - 9);
	}

out:
	sdfat_log_msg(sb, KERN_INFO, "start_sect of part(%d)    : %lld",
		bdev ? bdev_get_partno(bdev) : -1, (s64)hidden);
	return hidden;

}

s32 mount_fat32(struct super_block *sb, pbr_t *p_pbr)
{
	pbr32_t *p_bpb = (pbr32_t *)p_pbr;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	fsi->vol_id = get_unaligned_le32(p_bpb->bsx.vol_serial);
	fsi->root_dir = le32_to_cpu(p_bpb->bpb.f32.root_cluster);
	fsi->dentries_in_root = 0;

	if (mount_fat_common(sb, fsi, &p_bpb->bpb, 0))
		return -EINVAL;

	/* Should be initialized before calling amap_create() */
	fsi->vol_type = FAT32;
	fat_ent_ops_init(sb);

	/* Delayed / smart allocation related init */
	fsi->reserved_clusters = 0;

	/* AU Map Creation */
	if (SDFAT_SB(sb)->options.improved_allocation & SDFAT_ALLOC_SMART) {
		u32 hidden_sectors = le32_to_cpu(p_bpb->bpb.num_hid_sectors);
		u32 calc_hid_sect = 0;
		int ret;

		/* calculate hidden sector size */
		calc_hid_sect = __calc_hidden_sect(sb);
		if (calc_hid_sect != hidden_sectors) {
			sdfat_log_msg(sb, KERN_WARNING, "abnormal hidden "
				"sector   : bpb(%u) != ondisk(%u)",
				hidden_sectors, calc_hid_sect);
			if (SDFAT_SB(sb)->options.adj_hidsect) {
				sdfat_log_msg(sb, KERN_INFO,
					"adjustment hidden sector : "
					"bpb(%u) -> ondisk(%u)",
					hidden_sectors, calc_hid_sect);
				hidden_sectors = calc_hid_sect;
			}
		}

		SDFAT_SB(sb)->options.amap_opt.misaligned_sect = hidden_sectors;

		/* calculate AU size if it's not set */
		if (!SDFAT_SB(sb)->options.amap_opt.sect_per_au) {
			SDFAT_SB(sb)->options.amap_opt.sect_per_au =
				__calc_default_au_size(sb);
		}

		ret = amap_create(sb,
				SDFAT_SB(sb)->options.amap_opt.pack_ratio,
				SDFAT_SB(sb)->options.amap_opt.sect_per_au,
				SDFAT_SB(sb)->options.amap_opt.misaligned_sect);
		if (ret) {
			sdfat_log_msg(sb, KERN_WARNING, "failed to create AMAP."
				" disabling smart allocation. (err:%d)", ret);
			SDFAT_SB(sb)->options.improved_allocation &= ~(SDFAT_ALLOC_SMART);
		} else {
			fsi->fs_func = &amap_fat_fs_func;
		}
	}

	/* Check dependency of mount options */
	if (SDFAT_SB(sb)->options.improved_allocation !=
				(SDFAT_ALLOC_DELAY | SDFAT_ALLOC_SMART)) {
		sdfat_log_msg(sb, KERN_INFO, "disabling defragmentation because"
					" smart, delay options are disabled");
		SDFAT_SB(sb)->options.defrag = 0;
	}

	if (p_bpb->bsx.state & FAT_VOL_DIRTY) {
		fsi->vol_flag |= VOL_DIRTY;
		sdfat_log_msg(sb, KERN_WARNING, "Volume was not properly "
			"unmounted. Some data may be corrupt. "
			"Please run fsck.");
	}

	return 0;
} /* end of mount_fat32 */

/* end of core_fat.c */