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

#ifndef _SDFAT_H
#define _SDFAT_H

#include <linux/buffer_head.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/nls.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/ratelimit.h>
#include <linux/version.h>
#include <linux/kobject.h>
#include "api.h"

#ifdef CONFIG_SDFAT_DFR
#include "dfr.h"
#endif

/*
 * sdfat error flags
 */
#define SDFAT_ERRORS_CONT	(1)    /* ignore error and continue */
#define SDFAT_ERRORS_PANIC	(2)    /* panic on error */
#define SDFAT_ERRORS_RO		(3)    /* remount r/o on error */

/*
 * sdfat allocator flags
 */
#define SDFAT_ALLOC_DELAY	(1)    /* Delayed allocation */
#define SDFAT_ALLOC_SMART	(2)    /* Smart allocation */

/*
 * sdfat allocator destination for smart allocation
 */
#define ALLOC_NOWHERE		(0)
#define ALLOC_COLD		(1)
#define ALLOC_HOT		(16)
#define ALLOC_COLD_ALIGNED	(1)
#define ALLOC_COLD_PACKING	(2)
#define ALLOC_COLD_SEQ		(4)

/*
 * sdfat nls lossy flag
 */
#define NLS_NAME_NO_LOSSY	(0x00) /* no lossy */
#define NLS_NAME_LOSSY		(0x01) /* just detected incorrect filename(s) */
#define NLS_NAME_OVERLEN	(0x02) /* the length is over than its limit */

/*
 * sdfat common MACRO
 */
#define CLUSTER_16(x)	((u16)((x) & 0xFFFFU))
#define CLUSTER_32(x)	((u32)((x) & 0xFFFFFFFFU))
#define CLUS_EOF	CLUSTER_32(~0)
#define CLUS_BAD	(0xFFFFFFF7U)
#define CLUS_FREE	(0)
#define CLUS_BASE	(2)
#define IS_CLUS_EOF(x)	((x) == CLUS_EOF)
#define IS_CLUS_BAD(x)	((x) == CLUS_BAD)
#define IS_CLUS_FREE(x)	((x) == CLUS_FREE)
#define IS_LAST_SECT_IN_CLUS(fsi, sec)				\
	((((sec) - (fsi)->data_start_sector + 1)		\
	& ((1 << (fsi)->sect_per_clus_bits) - 1)) == 0)

#define CLUS_TO_SECT(fsi, x)	\
	((((unsigned long long)(x) - CLUS_BASE) << (fsi)->sect_per_clus_bits) + (fsi)->data_start_sector)

#define SECT_TO_CLUS(fsi, sec)	\
	((u32)((((sec) - (fsi)->data_start_sector) >> (fsi)->sect_per_clus_bits) + CLUS_BASE))

/* variables defined at sdfat.c */
extern const char *FS_TYPE_STR[];

enum {
	FS_TYPE_AUTO,
	FS_TYPE_EXFAT,
	FS_TYPE_VFAT,
	FS_TYPE_MAX
};

/*
 * sdfat mount in-memory data
 */
struct sdfat_mount_options {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
	kuid_t fs_uid;
	kgid_t fs_gid;
#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0) */
	uid_t fs_uid;
	gid_t fs_gid;
#endif
	unsigned short fs_fmask;
	unsigned short fs_dmask;
	unsigned short allow_utime; /* permission for setting the [am]time */
	unsigned short codepage;    /* codepage for shortname conversions */
	char *iocharset;            /* charset for filename input/display */
	struct {
		unsigned int pack_ratio;
		unsigned int sect_per_au;
		unsigned int misaligned_sect;
	} amap_opt;		    /* AMAP-related options (see amap.c) */

	unsigned char utf8;
	unsigned char casesensitive;
	unsigned char adj_hidsect;
	unsigned char tz_utc;
	unsigned char improved_allocation;
	unsigned char defrag;
	unsigned char symlink;      /* support symlink operation */
	unsigned char errors;       /* on error: continue, panic, remount-ro */
	unsigned char discard;      /* flag on if -o dicard specified and device support discard() */
	unsigned char fs_type;      /* fs_type that user specified */
	unsigned short adj_req;     /* support aligned mpage write */
};

#define SDFAT_HASH_BITS    8
#define SDFAT_HASH_SIZE    (1UL << SDFAT_HASH_BITS)

/*
 * SDFAT file system superblock in-memory data
 */
struct sdfat_sb_info {
	FS_INFO_T fsi;	/* private filesystem info */

	struct mutex s_vlock;   /* volume lock */
	int use_vmalloc;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)
	struct rcu_head rcu;
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)
	int s_dirt;
	struct mutex s_lock;    /* superblock lock */
	int write_super_queued;			/* Write_super work is pending? */
	struct delayed_work write_super_work;   /* Work_queue data structrue for write_super() */
	spinlock_t work_lock;			/* Lock for WQ */
#endif
	struct super_block *host_sb;		/* sb pointer */
	struct sdfat_mount_options options;
	struct nls_table *nls_disk; /* Codepage used on disk */
	struct nls_table *nls_io;   /* Charset used for input and display */
	struct ratelimit_state ratelimit;

	spinlock_t inode_hash_lock;
	struct hlist_head inode_hashtable[SDFAT_HASH_SIZE];
	struct kobject sb_kobj;
#ifdef CONFIG_SDFAT_DBG_IOCTL
	long debug_flags;
#endif /* CONFIG_SDFAT_DBG_IOCTL */

#ifdef	CONFIG_SDFAT_DFR
	struct defrag_info dfr_info;
	struct completion dfr_complete;
	unsigned int *dfr_new_clus;
	int dfr_new_idx;
	unsigned int *dfr_page_wb;
	void **dfr_pagep;
	unsigned int dfr_hint_clus;
	unsigned int dfr_hint_idx;
	int dfr_reserved_clus;

#ifdef	CONFIG_SDFAT_DFR_DEBUG
	int dfr_spo_flag;
#endif  /* CONFIG_SDFAT_DFR_DEBUG */

#endif  /* CONFIG_SDFAT_DFR */

#ifdef CONFIG_SDFAT_TRACE_IO
	/* Statistics for allocator */
	unsigned int stat_n_pages_written;	/* # of written pages in total */
	unsigned int stat_n_pages_added;	/* # of added blocks in total */
	unsigned int stat_n_bdev_pages_written;	/* # of written pages owned by bdev inode */
	unsigned int stat_n_pages_confused;
#endif
	atomic_t stat_n_pages_queued;	/* # of pages in the request queue (approx.) */
};

/*
 * SDFAT file system inode in-memory data
 */
struct sdfat_inode_info {
	FILE_ID_T fid;
	char  *target;
	/* NOTE: i_size_ondisk is 64bits, so must hold ->inode_lock to access */
	loff_t i_size_ondisk;         /* physically allocated size */
	loff_t i_size_aligned;          /* block-aligned i_size (used in cont_write_begin) */
	loff_t i_pos;               /* on-disk position of directory entry or 0 */
	struct hlist_node i_hash_fat;    /* hash by i_location */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0)
	struct rw_semaphore truncate_lock; /* protect bmap against truncate */
#endif
#ifdef	CONFIG_SDFAT_DFR
	struct defrag_info dfr_info;
#endif
	struct inode vfs_inode;
};

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0)
typedef struct timespec64	sdfat_timespec_t;
#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 18, 0) */
typedef struct timespec		sdfat_timespec_t;
#endif


#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)

#else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) */
/*
 * sb->s_flags.  Note that these mirror the equivalent MS_* flags where
 * represented in both.
 */
#define SB_RDONLY	1	/* Mount read-only */
#define SB_NODIRATIME	2048	/* Do not update directory access times */
static inline bool sb_rdonly(const struct super_block *sb)
{
	return sb->s_flags & MS_RDONLY;
}
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
	/* EMPTY */
#else
static inline sdfat_timespec_t current_time(struct inode *inode)
{
	return CURRENT_TIME_SEC;
}
#endif
/*
 * FIXME : needs on-disk-slot in-memory data
 */

/* static inline functons */
static inline const char *sdfat_get_vol_type_str(unsigned int type)
{
	if (type == EXFAT)
		return "exfat";
	else if (type == FAT32)
		return "vfat:32";
	else if (type == FAT16)
		return "vfat:16";
	else if (type == FAT12)
		return "vfat:12";

	return "unknown";
}

static inline struct sdfat_sb_info *SDFAT_SB(struct super_block *sb)
{
	return (struct sdfat_sb_info *)sb->s_fs_info;
}

static inline struct sdfat_inode_info *SDFAT_I(struct inode *inode)
{
	return container_of(inode, struct sdfat_inode_info, vfs_inode);
}

/*
 * If ->i_mode can't hold S_IWUGO (i.e. ATTR_RO), we use ->i_attrs to
 * save ATTR_RO instead of ->i_mode.
 *
 * If it's directory and !sbi->options.rodir, ATTR_RO isn't read-only
 * bit, it's just used as flag for app.
 */
static inline int sdfat_mode_can_hold_ro(struct inode *inode)
{
	struct sdfat_sb_info *sbi = SDFAT_SB(inode->i_sb);

	if (S_ISDIR(inode->i_mode))
		return 0;

	if ((~sbi->options.fs_fmask) & S_IWUGO)
		return 1;
	return 0;
}

/*
 * FIXME : needs to check symlink option.
 */
/* Convert attribute bits and a mask to the UNIX mode. */
static inline mode_t sdfat_make_mode(struct sdfat_sb_info *sbi,
					u32 attr, mode_t mode)
{
	if ((attr & ATTR_READONLY) && !(attr & ATTR_SUBDIR))
		mode &= ~S_IWUGO;

	if (attr & ATTR_SUBDIR)
		return (mode & ~sbi->options.fs_dmask) | S_IFDIR;
	else if (attr & ATTR_SYMLINK)
		return (mode & ~sbi->options.fs_dmask) | S_IFLNK;
	else
		return (mode & ~sbi->options.fs_fmask) | S_IFREG;
}

/* Return the FAT attribute byte for this inode */
static inline u32 sdfat_make_attr(struct inode *inode)
{
	u32 attrs = SDFAT_I(inode)->fid.attr;

	if (S_ISDIR(inode->i_mode))
		attrs |= ATTR_SUBDIR;
	if (sdfat_mode_can_hold_ro(inode) && !(inode->i_mode & S_IWUGO))
		attrs |= ATTR_READONLY;
	return attrs;
}

static inline void sdfat_save_attr(struct inode *inode, u32 attr)
{
	if (sdfat_mode_can_hold_ro(inode))
		SDFAT_I(inode)->fid.attr = attr & ATTR_RWMASK;
	else
		SDFAT_I(inode)->fid.attr = attr & (ATTR_RWMASK | ATTR_READONLY);
}

/* sdfat/statistics.c */
/* bigdata function */
#ifdef CONFIG_SDFAT_STATISTICS
extern int sdfat_statistics_init(struct kset *sdfat_kset);
extern void sdfat_statistics_uninit(void);
extern void sdfat_statistics_set_mnt(FS_INFO_T *fsi);
extern void sdfat_statistics_set_mnt_ro(void);
extern void sdfat_statistics_set_mkdir(u8 flags);
extern void sdfat_statistics_set_create(u8 flags);
extern void sdfat_statistics_set_rw(u8 flags, u32 clu_offset, s32 create);
extern void sdfat_statistics_set_trunc(u8 flags, CHAIN_T *clu);
extern void sdfat_statistics_set_vol_size(struct super_block *sb);
#else
static inline int sdfat_statistics_init(struct kset *sdfat_kset)
{
	return 0;
}
static inline void sdfat_statistics_uninit(void) {};
static inline void sdfat_statistics_set_mnt(FS_INFO_T *fsi) {};
static inline void sdfat_statistics_set_mnt_ro(void) {};
static inline void sdfat_statistics_set_mkdir(u8 flags) {};
static inline void sdfat_statistics_set_create(u8 flags) {};
static inline void sdfat_statistics_set_rw(u8 flags, u32 clu_offset, s32 create) {};
static inline void sdfat_statistics_set_trunc(u8 flags, CHAIN_T *clu) {};
static inline void sdfat_statistics_set_vol_size(struct super_block *sb) {};
#endif

/* sdfat/nls.c */
/* NLS management function */
s32  nls_cmp_sfn(struct super_block *sb, u8 *a, u8 *b);
s32  nls_cmp_uniname(struct super_block *sb, u16 *a, u16 *b);
s32  nls_uni16s_to_sfn(struct super_block *sb, UNI_NAME_T *p_uniname, DOS_NAME_T *p_dosname, s32 *p_lossy);
s32  nls_sfn_to_uni16s(struct super_block *sb, DOS_NAME_T *p_dosname, UNI_NAME_T *p_uniname);
s32  nls_uni16s_to_vfsname(struct super_block *sb, UNI_NAME_T *uniname, u8 *p_cstring, s32 len);
s32  nls_vfsname_to_uni16s(struct super_block *sb, const u8 *p_cstring,
			const s32 len, UNI_NAME_T *uniname, s32 *p_lossy);

/* sdfat/mpage.c */
#ifdef CONFIG_SDFAT_ALIGNED_MPAGE_WRITE
int sdfat_mpage_writepages(struct address_space *mapping,
			struct writeback_control *wbc, get_block_t *get_block);
#endif

/* sdfat/xattr.c */
#ifdef CONFIG_SDFAT_VIRTUAL_XATTR
void setup_sdfat_xattr_handler(struct super_block *sb);
extern int sdfat_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags);
extern ssize_t sdfat_getxattr(struct dentry *dentry, const char *name, void *value, size_t size);
extern ssize_t sdfat_listxattr(struct dentry *dentry, char *list, size_t size);
extern int sdfat_removexattr(struct dentry *dentry, const char *name);
#else
static inline void setup_sdfat_xattr_handler(struct super_block *sb) {};
#endif

/* sdfat/misc.c */
#ifdef CONFIG_SDFAT_UEVENT
extern int sdfat_uevent_init(struct kset *sdfat_kset);
extern void sdfat_uevent_uninit(void);
extern void sdfat_uevent_ro_remount(struct super_block *sb);
#else
static inline int sdfat_uevent_init(struct kset *sdfat_kset)
{
	return 0;
}
static inline void sdfat_uevent_uninit(void) {};
static inline void sdfat_uevent_ro_remount(struct super_block *sb) {};
#endif
extern void
__sdfat_fs_error(struct super_block *sb, int report, const char *fmt, ...)
	__printf(3, 4) __cold;
#define sdfat_fs_error(sb, fmt, args...)          \
	__sdfat_fs_error(sb, 1, fmt, ## args)
#define sdfat_fs_error_ratelimit(sb, fmt, args...) \
	__sdfat_fs_error(sb, __ratelimit(&SDFAT_SB(sb)->ratelimit), fmt, ## args)
extern void
__sdfat_msg(struct super_block *sb, const char *lv, int st, const char *fmt, ...)
	__printf(4, 5) __cold;
#define sdfat_msg(sb, lv, fmt, args...)          \
	__sdfat_msg(sb, lv, 0, fmt, ## args)
#define sdfat_log_msg(sb, lv, fmt, args...)          \
	__sdfat_msg(sb, lv, 1, fmt, ## args)
extern void sdfat_log_version(void);
extern void sdfat_time_fat2unix(struct sdfat_sb_info *sbi, sdfat_timespec_t *ts,
				DATE_TIME_T *tp);
extern void sdfat_time_unix2fat(struct sdfat_sb_info *sbi, sdfat_timespec_t *ts,
				DATE_TIME_T *tp);
extern TIMESTAMP_T *tm_now(struct inode *inode, TIMESTAMP_T *tm);
static inline TIMESTAMP_T *tm_now_sb(struct super_block *sb, TIMESTAMP_T *tm)
{
	struct inode fake_inode;

	fake_inode.i_sb = sb;
	return tm_now(&fake_inode, tm);
}

#ifdef CONFIG_SDFAT_DEBUG

#ifdef CONFIG_SDFAT_DBG_CAREFUL
void sdfat_debug_check_clusters(struct inode *inode);
#else
#define sdfat_debug_check_clusters(inode)
#endif /* CONFIG_SDFAT_DBG_CAREFUL */

#ifdef CONFIG_SDFAT_DBG_BUGON
#define sdfat_debug_bug_on(expr)        BUG_ON(expr)
#else
#define sdfat_debug_bug_on(expr)
#endif

#ifdef CONFIG_SDFAT_DBG_WARNON
#define sdfat_debug_warn_on(expr)        WARN_ON(expr)
#else
#define sdfat_debug_warn_on(expr)
#endif

#else /* CONFIG_SDFAT_DEBUG */

#define sdfat_debug_check_clusters(inode)
#define sdfat_debug_bug_on(expr)

#endif /* CONFIG_SDFAT_DEBUG */

#ifdef CONFIG_SDFAT_TRACE_ELAPSED_TIME
u32 sdfat_time_current_usec(struct timeval *tv);
extern struct timeval __t1;
extern struct timeval __t2;

#define TIME_GET(tv)	sdfat_time_current_usec(tv)
#define TIME_START(s)	sdfat_time_current_usec(s)
#define TIME_END(e)	sdfat_time_current_usec(e)
#define TIME_ELAPSED(s, e) ((u32)(((e)->tv_sec - (s)->tv_sec) * 1000000 + \
			((e)->tv_usec - (s)->tv_usec)))
#define PRINT_TIME(n)	pr_info("[SDFAT] Elapsed time %d = %d (usec)\n", n, (__t2 - __t1))
#else /* CONFIG_SDFAT_TRACE_ELAPSED_TIME */
#define TIME_GET(tv)    (0)
#define TIME_START(s)
#define TIME_END(e)
#define TIME_ELAPSED(s, e)      (0)
#define PRINT_TIME(n)
#endif /* CONFIG_SDFAT_TRACE_ELAPSED_TIME */

#define	SDFAT_MSG_LV_NONE	(0x00000000)
#define SDFAT_MSG_LV_ERR	(0x00000001)
#define SDFAT_MSG_LV_INFO	(0x00000002)
#define SDFAT_MSG_LV_DBG	(0x00000003)
#define SDFAT_MSG_LV_MORE	(0x00000004)
#define SDFAT_MSG_LV_TRACE	(0x00000005)
#define SDFAT_MSG_LV_ALL	(0x00000006)

#define SDFAT_MSG_LEVEL		SDFAT_MSG_LV_INFO

#define SDFAT_TAG_NAME	"SDFAT"
#define __S(x) #x
#define _S(x) __S(x)

extern void __sdfat_dmsg(int level, const char *fmt, ...) __printf(2, 3) __cold;

#define SDFAT_EMSG_T(level, ...)	\
	__sdfat_dmsg(level, KERN_ERR "[" SDFAT_TAG_NAME "] [" _S(__FILE__) "(" _S(__LINE__) ")] " __VA_ARGS__)
#define SDFAT_DMSG_T(level, ...)	\
	__sdfat_dmsg(level, KERN_INFO "[" SDFAT_TAG_NAME "] " __VA_ARGS__)

#define SDFAT_EMSG(...) SDFAT_EMSG_T(SDFAT_MSG_LV_ERR, __VA_ARGS__)
#define SDFAT_IMSG(...) SDFAT_DMSG_T(SDFAT_MSG_LV_INFO, __VA_ARGS__)
#define SDFAT_DMSG(...) SDFAT_DMSG_T(SDFAT_MSG_LV_DBG, __VA_ARGS__)
#define SDFAT_MMSG(...) SDFAT_DMSG_T(SDFAT_MSG_LV_MORE, __VA_ARGS__)
#define SDFAT_TMSG(...) SDFAT_DMSG_T(SDFAT_MSG_LV_TRACE, __VA_ARGS__)

#define EMSG(...)
#define IMSG(...)
#define DMSG(...)
#define MMSG(...)
#define TMSG(...)

#define EMSG_VAR(exp)
#define IMSG_VAR(exp)
#define DMSG_VAR(exp)
#define MMSG_VAR(exp)
#define TMSG_VAR(exp)

#ifdef CONFIG_SDFAT_DBG_MSG


#if (SDFAT_MSG_LEVEL >= SDFAT_MSG_LV_ERR)
#undef EMSG
#undef EMSG_VAR
#define EMSG(...)	SDFAT_EMSG(__VA_ARGS__)
#define EMSG_VAR(exp)	exp
#endif

#if (SDFAT_MSG_LEVEL >= SDFAT_MSG_LV_INFO)
#undef IMSG
#undef IMSG_VAR
#define IMSG(...)	SDFAT_IMSG(__VA_ARGS__)
#define IMSG_VAR(exp)	exp
#endif

#if (SDFAT_MSG_LEVEL >= SDFAT_MSG_LV_DBG)
#undef DMSG
#undef DMSG_VAR
#define DMSG(...)	SDFAT_DMSG(__VA_ARGS__)
#define DMSG_VAR(exp)	exp
#endif

#if (SDFAT_MSG_LEVEL >= SDFAT_MSG_LV_MORE)
#undef MMSG
#undef MMSG_VAR
#define MMSG(...)	SDFAT_MMSG(__VA_ARGS__)
#define MMSG_VAR(exp)	exp
#endif

/* should replace with trace function */
#if (SDFAT_MSG_LEVEL >= SDFAT_MSG_LV_TRACE)
#undef TMSG
#undef TMSG_VAR
#define TMSG(...)	SDFAT_TMSG(__VA_ARGS__)
#define TMSG_VAR(exp)	exp
#endif

#endif /* CONFIG_SDFAT_DBG_MSG */


#define ASSERT(expr)	{					\
	if (!(expr)) {						\
		pr_err("Assertion failed! %s\n", #expr);	\
		BUG_ON(1);					\
	}							\
}

#endif /* !_SDFAT_H */