Discussion:
[PATCH] add exFAT driver
Benjamin Valentin
2013-08-30 10:46:22 UTC
Permalink
Samsung released their exFAT (and FAT12/16/32) driver under the GPLv2, it is
based on the in-kernel FAT driver and can be obtained from [1].
This patch adds version 1.2.4 of the exfat driver, sans the ifdefs for older
kernel versions and ported to Linux 3.11.

[1] http://opensource.samsung.com search for exfat

original authors: Joosun Hahn
Sung-Kwan Kim

Signed-off-by: Benjamin Valentin <***@zedat.fu-berlin.de>
---
drivers/staging/Kconfig | 2 +
drivers/staging/Makefile | 1 +
drivers/staging/exfat/Kconfig | 25 +
drivers/staging/exfat/Makefile | 10 +
drivers/staging/exfat/TODO | 3 +
drivers/staging/exfat/exfat.c | 4941 +++++++++++++++++++++++++++++++++
drivers/staging/exfat/exfat.h | 613 ++++
drivers/staging/exfat/exfat_api.c | 458 +++
drivers/staging/exfat/exfat_api.h | 164 ++
drivers/staging/exfat/exfat_blkdev.c | 155 ++
drivers/staging/exfat/exfat_blkdev.h | 45 +
drivers/staging/exfat/exfat_cache.c | 741 +++++
drivers/staging/exfat/exfat_cache.h | 62 +
drivers/staging/exfat/exfat_data.c | 39 +
drivers/staging/exfat/exfat_data.h | 48 +
drivers/staging/exfat/exfat_global.c | 118 +
drivers/staging/exfat/exfat_global.h | 126 +
drivers/staging/exfat/exfat_nls.c | 353 +++
drivers/staging/exfat/exfat_nls.h | 69 +
drivers/staging/exfat/exfat_oal.c | 156 ++
drivers/staging/exfat/exfat_oal.h | 48 +
drivers/staging/exfat/exfat_part.h | 56 +
drivers/staging/exfat/exfat_super.c | 2009 ++++++++++++++
drivers/staging/exfat/exfat_super.h | 153 +
drivers/staging/exfat/exfat_upcase.c | 389 +++
drivers/staging/exfat/exfat_version.h | 1 +
26 files changed, 10785 insertions(+)
create mode 100644 drivers/staging/exfat/Kconfig
create mode 100644 drivers/staging/exfat/Makefile
create mode 100644 drivers/staging/exfat/TODO
create mode 100644 drivers/staging/exfat/exfat.c
create mode 100644 drivers/staging/exfat/exfat.h
create mode 100644 drivers/staging/exfat/exfat_api.c
create mode 100644 drivers/staging/exfat/exfat_api.h
create mode 100644 drivers/staging/exfat/exfat_blkdev.c
create mode 100644 drivers/staging/exfat/exfat_blkdev.h
create mode 100644 drivers/staging/exfat/exfat_cache.c
create mode 100644 drivers/staging/exfat/exfat_cache.h
create mode 100644 drivers/staging/exfat/exfat_data.c
create mode 100644 drivers/staging/exfat/exfat_data.h
create mode 100644 drivers/staging/exfat/exfat_global.c
create mode 100644 drivers/staging/exfat/exfat_global.h
create mode 100644 drivers/staging/exfat/exfat_nls.c
create mode 100644 drivers/staging/exfat/exfat_nls.h
create mode 100644 drivers/staging/exfat/exfat_oal.c
create mode 100644 drivers/staging/exfat/exfat_oal.h
create mode 100644 drivers/staging/exfat/exfat_part.h
create mode 100644 drivers/staging/exfat/exfat_super.c
create mode 100644 drivers/staging/exfat/exfat_super.h
create mode 100644 drivers/staging/exfat/exfat_upcase.c
create mode 100644 drivers/staging/exfat/exfat_version.h

diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 57d8b34..d4c3b4b 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -142,4 +142,6 @@ source "drivers/staging/lustre/Kconfig"

source "drivers/staging/btmtk_usb/Kconfig"

+source "drivers/staging/exfat/Kconfig"
+
endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index 429321f..370e314 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -63,3 +63,4 @@ obj-$(CONFIG_GOLDFISH) += goldfish/
obj-$(CONFIG_USB_DWC2) += dwc2/
obj-$(CONFIG_LUSTRE_FS) += lustre/
obj-$(CONFIG_USB_BTMTK) += btmtk_usb/
+obj-$(CONFIG_EXFAT_FS) += exfat/
diff --git a/drivers/staging/exfat/Kconfig b/drivers/staging/exfat/Kconfig
new file mode 100644
index 0000000..1f91227
--- /dev/null
+++ b/drivers/staging/exfat/Kconfig
@@ -0,0 +1,25 @@
+config EXFAT_FS
+ tristate "exFAT fs support"
+ select NLS
+ help
+ This adds the exFAT file system
+
+if EXFAT_FS
+
+config EXFAT_LITTLE_ENDIAN
+ bool "Little Endian Mode"
+ default y
+
+config EXFAT_DISCARD
+ bool "enable discard"
+ default y
+
+config EXFAT_KERNEL_DEBUG
+ bool "enable debugging"
+ default y
+
+config EXFAT_DEBUG_MSG
+ bool "print debug messages"
+ default n
+
+endif # EXFAT_FS
diff --git a/drivers/staging/exfat/Makefile b/drivers/staging/exfat/Makefile
new file mode 100644
index 0000000..d7fabfa
--- /dev/null
+++ b/drivers/staging/exfat/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for the Linux fat filesystem support.
+#
+
+obj-$(CONFIG_EXFAT_FS) += exfat_core.o exfat_fs.o
+
+exfat_fs-y := exfat_super.o
+
+exfat_core-y := exfat.o exfat_api.o exfat_blkdev.o exfat_cache.o \
+ exfat_data.o exfat_global.o exfat_nls.o exfat_oal.o exfat_upcase.o
diff --git a/drivers/staging/exfat/TODO b/drivers/staging/exfat/TODO
new file mode 100644
index 0000000..707310a
--- /dev/null
+++ b/drivers/staging/exfat/TODO
@@ -0,0 +1,3 @@
+* fix coding style
+* remove dead code
+* merge with existing FAT implementation
diff --git a/drivers/staging/exfat/exfat.c b/drivers/staging/exfat/exfat.c
new file mode 100644
index 0000000..c8f9719
--- /dev/null
+++ b/drivers/staging/exfat/exfat.c
@@ -0,0 +1,4941 @@
+/* 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(***@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.
+ */
+
+#include <linux/version.h>
+
+#include "exfat_global.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.h"
+
+#include <linux/blkdev.h>
+
+#define THERE_IS_MBR 0
+
+#if (THERE_IS_MBR == 1)
+#include "exfat_part.h"
+#endif
+
+#define DELAYED_SYNC 0
+
+#define ELAPSED_TIME 0
+
+#if (ELAPSED_TIME == 1)
+#include <linux/time.h>
+
+static UINT32 __t1, __t2;
+static UINT32 get_current_msec(void)
+{
+ struct timeval tm;
+ do_gettimeofday(&tm);
+ return (UINT32)(tm.tv_sec*1000000 + tm.tv_usec);
+}
+#define TIME_START() do {__t1 = get_current_msec(); } while (0)
+#define TIME_END() do {__t2 = get_current_msec(); } while (0)
+#define PRINT_TIME(n) do {printk("[EXFAT] Elapsed time %d = %d (usec)\n", n, (__t2 - __t1)); } while (0)
+#else
+#define TIME_START()
+#define TIME_END()
+#define PRINT_TIME(n)
+#endif
+
+static void __set_sb_dirty(struct super_block *sb)
+{
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+ sbi->s_dirt = 1;
+}
+
+extern UINT8 uni_upcase[];
+
+static UINT8 name_buf[MAX_PATH_LENGTH *MAX_CHARSET_SIZE];
+
+static INT8 *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 UINT8 free_bit[] = {
+ 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,
+ 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,
+ 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,
+ 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2,
+ 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,
+ 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5,
+ 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,
+ 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3,
+ 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,
+ 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,
+ 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
+};
+
+static UINT8 used_bit[] = {
+ 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3,
+ 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4,
+ 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5,
+ 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+ 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4,
+ 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6,
+ 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4,
+ 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+ 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5,
+ 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5,
+ 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6,
+ 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+ 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
+};
+
+INT32 ffsInit(void)
+{
+ INT32 ret;
+
+ ret = bdev_init();
+ if (ret)
+ return ret;
+
+ ret = fs_init();
+ if (ret)
+ return ret;
+
+ return FFS_SUCCESS;
+}
+
+INT32 ffsShutdown(void)
+{
+ INT32 ret;
+ ret = fs_shutdown();
+ if (ret)
+ return ret;
+
+ ret = bdev_shutdown();
+ if (ret)
+ return ret;
+
+ return FFS_SUCCESS;
+}
+
+INT32 ffsMountVol(struct super_block *sb, INT32 drv)
+{
+ INT32 i, ret;
+#if (THERE_IS_MBR == 1)
+ MBR_SECTOR_T *p_mbr;
+ PART_ENTRY_T *p_pte;
+#endif
+ 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");
+
+ p_fs->drv = drv;
+ p_fs->dev_ejected = FALSE;
+
+ 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);
+
+ if (sector_read(sb, 0, &tmp_bh, 1) != FFS_SUCCESS)
+ return FFS_MEDIAERR;
+
+#if (THERE_IS_MBR == 1)
+ if (buf[0] != 0xEB) {
+ p_mbr = (MBR_SECTOR_T *) tmp_bh->b_data;
+
+ if (GET16_A(p_mbr->signature) != MBR_SIGNATURE) {
+ brelse(tmp_bh);
+ bdev_close(sb);
+ return FFS_FORMATERR;
+ }
+
+ p_pte = (PART_ENTRY_T *) p_mbr->partition + 0;
+ p_fs->PBR_sector = GET32(p_pte->start_sector);
+ p_fs->num_sectors = GET32(p_pte->num_sectors);
+
+ if (p_fs->num_sectors == 0) {
+ brelse(tmp_bh);
+ bdev_close(sb);
+ return FFS_ERROR;
+ }
+
+ if (sector_read(sb, p_fs->PBR_sector, &tmp_bh, 1) != FFS_SUCCESS) {
+ bdev_close(sb);
+ return FFS_MEDIAERR;
+ }
+ } else {
+#endif
+ p_fs->PBR_sector = 0;
+#if (THERE_IS_MBR == 1)
+ }
+#endif
+
+ p_pbr = (PBR_SECTOR_T *) tmp_bh->b_data;
+
+ if (GET16_A(p_pbr->signature) != PBR_SIGNATURE) {
+ brelse(tmp_bh);
+ bdev_close(sb);
+ return FFS_FORMATERR;
+ }
+
+ for (i = 0; i < 53; i++)
+ if (p_pbr->bpb[i])
+ break;
+
+ if (i < 53) {
+ if (GET16(p_pbr->bpb+11))
+ 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;
+}
+
+INT32 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);
+
+ 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;
+}
+
+INT32 ffsGetVolInfo(struct super_block *sb, VOL_INFO_T *info)
+{
+ FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
+
+ if (p_fs->used_clusters == (UINT32) ~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;
+ info->UsedClusters = p_fs->used_clusters;
+ info->FreeClusters = info->NumClusters - info->UsedClusters;
+
+ if (p_fs->dev_ejected)
+ return FFS_MEDIAERR;
+
+ return FFS_SUCCESS;
+}
+
+INT32 ffsSyncVol(struct super_block *sb, INT32 do_sync)
+{
+ FS_INFO_T *p_fs = &(EXFAT_SB(sb)->fs_info);
+
+ fs_sync(sb, do_sync);
+ fs_set_vol_flags(sb, VOL_CLEAN);
+
+ if (p_fs->dev_ejected)
+ return FFS_MEDIAERR;
+
+ return FFS_SUCCESS;
+}
+
+INT32 ffsLookupFile(struct inode *inode, UINT8 *path, FILE_ID_T *fid)
+{
+ INT32 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);
+
+ PRINTK("ffsLookupFile entered\n");
+
+ 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;
+
+ 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;
+
+ PRINTK("ffsLookupFile exited successfully\n");
+
+ return FFS_SUCCESS;
+}
+
+INT32 ffsCreateFile(struct inode *inode, UINT8 *path, UINT8 mode, FILE_ID_T *fid)
+{
+ INT32 ret;
+ 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);
+
+ ret = resolve_path(inode, path, &dir, &uni_name);
+ if (ret)
+ return ret;
+
+ fs_set_vol_flags(sb, VOL_DIRTY);
+ ret = create_file(inode, &dir, &uni_name, mode, fid);
+
+#if (DELAYED_SYNC == 0)
+ fs_sync(sb, 0);
+ fs_set_vol_flags(sb, VOL_CLEAN);
+#endif
+
+ if (p_fs->dev_ejected)
+ return FFS_MEDIAERR;
+
+ return ret;
+}
+
+INT32 ffsReadFile(struct inode *inode, FILE_ID_T *fid, void *buffer, UINT64 count, UINT64 *rcount)
+{
+ INT32 offset, sec_offset, clu_offset;
+ UINT32 clu, LogSector;
+ UINT64 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);
+
+ 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 = (INT32)(fid->rwoffset >> p_fs->cluster_size_bits);
+ clu = fid->start_clu;
+
+ if (fid->flags == 0x03) {
+ clu += clu_offset;
+ } else {
+ 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) {
+ if (FAT_read(sb, clu, &clu) == -1)
+ return FFS_MEDIAERR;
+
+ clu_offset--;
+ }
+ }
+
+ fid->hint_last_off = (INT32)(fid->rwoffset >> p_fs->cluster_size_bits);
+ fid->hint_last_clu = clu;
+
+ offset = (INT32)(fid->rwoffset & (p_fs->cluster_size-1));
+ sec_offset = offset >> p_bd->sector_size_bits;
+ offset &= p_bd->sector_size_mask;
+
+ LogSector = START_SECTOR(clu) + sec_offset;
+
+ oneblkread = (UINT64)(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(((INT8 *) buffer)+read_bytes, ((INT8 *) tmp_bh->b_data), (INT32) oneblkread);
+ } else {
+ if (sector_read(sb, LogSector, &tmp_bh, 1) != FFS_SUCCESS)
+ goto err_out;
+ MEMCPY(((INT8 *) buffer)+read_bytes, ((INT8 *) tmp_bh->b_data)+offset, (INT32) oneblkread);
+ }
+ count -= oneblkread;
+ read_bytes += oneblkread;
+ fid->rwoffset += oneblkread;
+ }
+ brelse(tmp_bh);
+
+err_out:
+ if (rcount != NULL)
+ *rcount = read_bytes;
+
+ if (p_fs->dev_ejected)
+ return FFS_MEDIAERR;
+
+ return FFS_SUCCESS;
+}
+
+INT32 ffsWriteFile(struct inode *inode, FILE_ID_T *fid, void *buffer, UINT64 count, UINT64 *wcount)
+{
+ INT32 modified = FALSE, offset, sec_offset, clu_offset;
+ INT32 num_clusters, num_alloc, num_alloced = (INT32) ~0;
+ UINT32 clu, last_clu, LogSector, sector;
+ UINT64 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);
+
+ 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 = (INT32)((fid->size-1) >> p_fs->cluster_size_bits) + 1;
+
+ write_bytes = 0;
+
+ while (count > 0) {
+ clu_offset = (INT32)(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 {
+ 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)) {
+ num_alloc = (INT32)((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;
+
+ num_alloced = p_fs->fs_func->alloc_cluster(sb, num_alloc, &new_clu);
+ if (num_alloced == 0)
+ break;
+
+ 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;
+ }
+
+ fid->hint_last_off = (INT32)(fid->rwoffset >> p_fs->cluster_size_bits);
+ fid->hint_last_clu = clu;
+
+ offset = (INT32)(fid->rwoffset & (p_fs->cluster_size-1));
+ sec_offset = offset >> p_bd->sector_size_bits;
+ offset &= p_bd->sector_size_mask;
+
+ LogSector = START_SECTOR(clu) + sec_offset;
+
+ oneblkwrite = (UINT64)(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(((INT8 *) tmp_bh->b_data), ((INT8 *) buffer)+write_bytes, (INT32) 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(((INT8 *) tmp_bh->b_data)+offset, ((INT8 *) buffer)+write_bytes, (INT32) 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);
+
+ 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);
+ }
+
+#if (DELAYED_SYNC == 0)
+ fs_sync(sb, 0);
+ fs_set_vol_flags(sb, VOL_CLEAN);
+#endif
+
+err_out:
+ if (wcount != NULL)
+ *wcount = write_bytes;
+
+ if (num_alloced == 0)
+ return FFS_FULL;
+
+ if (p_fs->dev_ejected)
+ return FFS_MEDIAERR;
+
+ return FFS_SUCCESS;
+}
+
+INT32 ffsTruncateFile(struct inode *inode, UINT64 old_size, UINT64 new_size)
+{
+ INT32 num_clusters;
+ UINT32 last_clu = CLUSTER_32(0), sector;
+ 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;
+
+ 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 = (INT32)((old_size-1) >> p_fs->cluster_size_bits) + 1;
+ clu.flags = fid->flags;
+
+ if (new_size > 0) {
+ num_clusters = (INT32)((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);
+ }
+
+ 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);
+ }
+
+ if (last_clu != CLUSTER_32(0)) {
+ if (fid->flags == 0x01)
+ FAT_write(sb, last_clu, CLUSTER_32(~0));
+ }
+
+ p_fs->fs_func->free_cluster(sb, &clu, 0);
+
+ fid->hint_last_off = -1;
+ if (fid->rwoffset > fid->size) {
+ fid->rwoffset = fid->size;
+ }
+
+#if (DELAYED_SYNC == 0)
+ fs_sync(sb, 0);
+ fs_set_vol_flags(sb, VOL_CLEAN);
+#endif
+
+ if (p_fs->dev_ejected)
+ return FFS_MEDIAERR;
+
+ return FFS_SUCCESS;
+}
+
+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);
+ }
+}
+
+INT32 ffsMoveFile(struct inode *old_parent_inode, FILE_ID_T *fid, struct inode *new_parent_inode, struct dentry *new_dentry)
+{
+ INT32 ret;
+ INT32 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);
+ UINT8 *new_path = (UINT8 *) new_dentry->d_name.name;
+ struct inode *new_inode = new_dentry->d_inode;
+ int num_entries;
+ FILE_ID_T *new_fid =