You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1584 lines
40 KiB
1584 lines
40 KiB
/*
|
|
* 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_exfat.c */
|
|
/* PURPOSE : exFAT-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 */
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* Global Variable Definitions */
|
|
/*----------------------------------------------------------------------*/
|
|
|
|
/*----------------------------------------------------------------------*/
|
|
/* Local Variable Definitions */
|
|
/*----------------------------------------------------------------------*/
|
|
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*/
|
|
};
|
|
|
|
|
|
/*======================================================================*/
|
|
/* Local Function Definitions */
|
|
/*======================================================================*/
|
|
/*
|
|
* Directory Entry Management Functions
|
|
*/
|
|
static u32 exfat_get_entry_type(DENTRY_T *p_entry)
|
|
{
|
|
FILE_DENTRY_T *ep = (FILE_DENTRY_T *) p_entry;
|
|
|
|
if (ep->type == EXFAT_UNUSED)
|
|
return TYPE_UNUSED;
|
|
if (ep->type < 0x80)
|
|
return TYPE_DELETED;
|
|
if (ep->type == 0x80)
|
|
return TYPE_INVALID;
|
|
if (ep->type < 0xA0) {
|
|
if (ep->type == 0x81)
|
|
return TYPE_BITMAP;
|
|
if (ep->type == 0x82)
|
|
return TYPE_UPCASE;
|
|
if (ep->type == 0x83)
|
|
return TYPE_VOLUME;
|
|
if (ep->type == 0x85) {
|
|
if (le16_to_cpu(ep->attr) & ATTR_SUBDIR)
|
|
return TYPE_DIR;
|
|
return TYPE_FILE;
|
|
}
|
|
return TYPE_CRITICAL_PRI;
|
|
}
|
|
if (ep->type < 0xC0) {
|
|
if (ep->type == 0xA0)
|
|
return TYPE_GUID;
|
|
if (ep->type == 0xA1)
|
|
return TYPE_PADDING;
|
|
if (ep->type == 0xA2)
|
|
return TYPE_ACLTAB;
|
|
return TYPE_BENIGN_PRI;
|
|
}
|
|
if (ep->type < 0xE0) {
|
|
if (ep->type == 0xC0)
|
|
return TYPE_STREAM;
|
|
if (ep->type == 0xC1)
|
|
return TYPE_EXTEND;
|
|
if (ep->type == 0xC2)
|
|
return TYPE_ACL;
|
|
return TYPE_CRITICAL_SEC;
|
|
}
|
|
return TYPE_BENIGN_SEC;
|
|
} /* end of exfat_get_entry_type */
|
|
|
|
static 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;
|
|
ep->attr = cpu_to_le16(ATTR_SUBDIR);
|
|
} else if (type == TYPE_FILE) {
|
|
ep->type = 0x85;
|
|
ep->attr = cpu_to_le16(ATTR_ARCHIVE);
|
|
} else if (type == TYPE_SYMLINK) {
|
|
ep->type = 0x85;
|
|
ep->attr = cpu_to_le16(ATTR_ARCHIVE | ATTR_SYMLINK);
|
|
}
|
|
} /* end of exfat_set_entry_type */
|
|
|
|
static u32 exfat_get_entry_attr(DENTRY_T *p_entry)
|
|
{
|
|
FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry;
|
|
|
|
return (u32)le16_to_cpu(ep->attr);
|
|
} /* end of exfat_get_entry_attr */
|
|
|
|
static void exfat_set_entry_attr(DENTRY_T *p_entry, u32 attr)
|
|
{
|
|
FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry;
|
|
|
|
ep->attr = cpu_to_le16((u16) attr);
|
|
} /* end of exfat_set_entry_attr */
|
|
|
|
static 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 */
|
|
|
|
static 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 */
|
|
|
|
static u32 exfat_get_entry_clu0(DENTRY_T *p_entry)
|
|
{
|
|
STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry;
|
|
|
|
return (u32)le32_to_cpu(ep->start_clu);
|
|
} /* end of exfat_get_entry_clu0 */
|
|
|
|
static void exfat_set_entry_clu0(DENTRY_T *p_entry, u32 start_clu)
|
|
{
|
|
STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry;
|
|
|
|
ep->start_clu = cpu_to_le32(start_clu);
|
|
} /* end of exfat_set_entry_clu0 */
|
|
|
|
static u64 exfat_get_entry_size(DENTRY_T *p_entry)
|
|
{
|
|
STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry;
|
|
|
|
return le64_to_cpu(ep->valid_size);
|
|
} /* end of exfat_get_entry_size */
|
|
|
|
static void exfat_set_entry_size(DENTRY_T *p_entry, u64 size)
|
|
{
|
|
STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry;
|
|
|
|
ep->valid_size = cpu_to_le64(size);
|
|
ep->size = cpu_to_le64(size);
|
|
} /* end of exfat_set_entry_size */
|
|
|
|
|
|
#define TENS_MS_PER_SEC (100)
|
|
#define SEC_TO_TENS_MS(sec) (((sec) & 0x01) ? TENS_MS_PER_SEC : 0)
|
|
#define TENS_MS_TO_SEC(tens_ms) (((tens_ms) / TENS_MS_PER_SEC) ? 1 : 0)
|
|
|
|
static void exfat_get_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode)
|
|
{
|
|
u16 t = 0x00, d = 0x21, tz = 0x00, s = 0x00;
|
|
FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry;
|
|
|
|
switch (mode) {
|
|
case TM_CREATE:
|
|
t = le16_to_cpu(ep->create_time);
|
|
d = le16_to_cpu(ep->create_date);
|
|
s = TENS_MS_TO_SEC(ep->create_time_ms);
|
|
tz = ep->create_tz;
|
|
break;
|
|
case TM_MODIFY:
|
|
t = le16_to_cpu(ep->modify_time);
|
|
d = le16_to_cpu(ep->modify_date);
|
|
s = TENS_MS_TO_SEC(ep->modify_time_ms);
|
|
tz = ep->modify_tz;
|
|
break;
|
|
case TM_ACCESS:
|
|
t = le16_to_cpu(ep->access_time);
|
|
d = le16_to_cpu(ep->access_date);
|
|
tz = ep->access_tz;
|
|
break;
|
|
}
|
|
|
|
tp->tz.value = tz;
|
|
tp->sec = ((t & 0x001F) << 1) + s;
|
|
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 */
|
|
|
|
static 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:
|
|
ep->create_time = cpu_to_le16(t);
|
|
ep->create_time_ms = SEC_TO_TENS_MS(tp->sec);
|
|
ep->create_date = cpu_to_le16(d);
|
|
ep->create_tz = tp->tz.value;
|
|
break;
|
|
case TM_MODIFY:
|
|
ep->modify_time = cpu_to_le16(t);
|
|
ep->modify_date = cpu_to_le16(d);
|
|
ep->modify_time_ms = (tp->sec & 0x1) ? TENS_MS_PER_SEC : 0;
|
|
ep->modify_tz = tp->tz.value;
|
|
break;
|
|
case TM_ACCESS:
|
|
ep->access_time = cpu_to_le16(t);
|
|
ep->access_date = cpu_to_le16(d);
|
|
ep->access_tz = tp->tz.value;
|
|
break;
|
|
}
|
|
} /* end of exfat_set_entry_time */
|
|
|
|
|
|
static void __init_file_entry(struct super_block *sb, FILE_DENTRY_T *ep, u32 type)
|
|
{
|
|
TIMESTAMP_T tm, *tp;
|
|
|
|
exfat_set_entry_type((DENTRY_T *) ep, type);
|
|
|
|
tp = tm_now_sb(sb, &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);
|
|
} /* end of __init_file_entry */
|
|
|
|
static 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;
|
|
ep->start_clu = cpu_to_le32(start_clu);
|
|
ep->valid_size = cpu_to_le64(size);
|
|
ep->size = cpu_to_le64(size);
|
|
} /* end of __init_strm_entry */
|
|
|
|
static void __init_name_entry(NAME_DENTRY_T *ep, u16 *uniname)
|
|
{
|
|
s32 i;
|
|
|
|
exfat_set_entry_type((DENTRY_T *) ep, TYPE_EXTEND);
|
|
ep->flags = 0x0;
|
|
|
|
for (i = 0; i < 15; i++) {
|
|
ep->unicode_0_14[i] = cpu_to_le16(*uniname);
|
|
if (*uniname == 0x0)
|
|
break;
|
|
uniname++;
|
|
}
|
|
} /* end of __init_name_entry */
|
|
|
|
static s32 exfat_init_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u32 type, u32 start_clu, u64 size)
|
|
{
|
|
u64 sector;
|
|
u8 flags;
|
|
FILE_DENTRY_T *file_ep;
|
|
STRM_DENTRY_T *strm_ep;
|
|
|
|
flags = (type == TYPE_FILE) ? 0x01 : 0x03;
|
|
|
|
/* we cannot use get_dentry_set_in_dir here because file ep is not initialized yet */
|
|
file_ep = (FILE_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, §or);
|
|
if (!file_ep)
|
|
return -EIO;
|
|
|
|
strm_ep = (STRM_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry+1, §or);
|
|
if (!strm_ep)
|
|
return -EIO;
|
|
|
|
__init_file_entry(sb, file_ep, type);
|
|
if (dcache_modify(sb, sector))
|
|
return -EIO;
|
|
|
|
__init_strm_entry(strm_ep, flags, start_clu, size);
|
|
if (dcache_modify(sb, sector))
|
|
return -EIO;
|
|
|
|
return 0;
|
|
} /* end of exfat_init_dir_entry */
|
|
|
|
s32 update_dir_chksum(struct super_block *sb, CHAIN_T *p_dir, s32 entry)
|
|
{
|
|
s32 ret = -EIO;
|
|
s32 i, num_entries;
|
|
u64 sector;
|
|
u16 chksum;
|
|
FILE_DENTRY_T *file_ep;
|
|
DENTRY_T *ep;
|
|
|
|
file_ep = (FILE_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, §or);
|
|
if (!file_ep)
|
|
return -EIO;
|
|
|
|
dcache_lock(sb, sector);
|
|
|
|
num_entries = (s32) file_ep->num_ext + 1;
|
|
chksum = calc_chksum_2byte((void *) file_ep, DENTRY_SIZE, 0, CS_DIR_ENTRY);
|
|
|
|
for (i = 1; i < num_entries; i++) {
|
|
ep = get_dentry_in_dir(sb, p_dir, entry+i, NULL);
|
|
if (!ep)
|
|
goto out_unlock;
|
|
|
|
chksum = calc_chksum_2byte((void *) ep, DENTRY_SIZE, chksum, CS_DEFAULT);
|
|
}
|
|
|
|
file_ep->checksum = cpu_to_le16(chksum);
|
|
ret = dcache_modify(sb, sector);
|
|
out_unlock:
|
|
dcache_unlock(sb, sector);
|
|
return ret;
|
|
|
|
} /* end of update_dir_chksum */
|
|
|
|
|
|
static 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)
|
|
{
|
|
s32 i;
|
|
u64 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_dentry_in_dir(sb, p_dir, entry, §or);
|
|
if (!file_ep)
|
|
return -EIO;
|
|
|
|
file_ep->num_ext = (u8)(num_entries - 1);
|
|
dcache_modify(sb, sector);
|
|
|
|
strm_ep = (STRM_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry+1, §or);
|
|
if (!strm_ep)
|
|
return -EIO;
|
|
|
|
strm_ep->name_len = p_uniname->name_len;
|
|
strm_ep->name_hash = cpu_to_le16(p_uniname->name_hash);
|
|
dcache_modify(sb, sector);
|
|
|
|
for (i = 2; i < num_entries; i++) {
|
|
name_ep = (NAME_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry+i, §or);
|
|
if (!name_ep)
|
|
return -EIO;
|
|
|
|
__init_name_entry(name_ep, uniname);
|
|
dcache_modify(sb, sector);
|
|
uniname += 15;
|
|
}
|
|
|
|
update_dir_chksum(sb, p_dir, entry);
|
|
|
|
return 0;
|
|
} /* end of exfat_init_ext_entry */
|
|
|
|
|
|
static s32 exfat_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 = order; i < num_entries; i++) {
|
|
ep = get_dentry_in_dir(sb, p_dir, entry+i, §or);
|
|
if (!ep)
|
|
return -EIO;
|
|
|
|
exfat_set_entry_type(ep, TYPE_DELETED);
|
|
if (dcache_modify(sb, sector))
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static s32 __write_partial_entries_in_entry_set(struct super_block *sb,
|
|
ENTRY_SET_CACHE_T *es, u64 sec, u32 off, u32 count)
|
|
{
|
|
s32 num_entries;
|
|
u32 buf_off = (off - es->offset);
|
|
u32 remaining_byte_in_sector, copy_entries;
|
|
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
|
|
u32 clu;
|
|
u8 *buf, *esbuf = (u8 *)&(es->__buf);
|
|
|
|
TMSG("%s entered\n", __func__);
|
|
MMSG("%s: es %p sec %llu off %u cnt %d\n", __func__, es, sec, off, count);
|
|
num_entries = count;
|
|
|
|
while (num_entries) {
|
|
/* write per sector base */
|
|
remaining_byte_in_sector = (1 << sb->s_blocksize_bits) - off;
|
|
copy_entries = min((s32)(remaining_byte_in_sector >> DENTRY_SIZE_BITS), num_entries);
|
|
buf = dcache_getblk(sb, sec);
|
|
if (!buf)
|
|
goto err_out;
|
|
MMSG("es->buf %p buf_off %u\n", esbuf, buf_off);
|
|
MMSG("copying %d entries from %p to sector %llu\n", copy_entries, (esbuf + buf_off), sec);
|
|
memcpy(buf + off, esbuf + buf_off, copy_entries << DENTRY_SIZE_BITS);
|
|
dcache_modify(sb, sec);
|
|
num_entries -= copy_entries;
|
|
|
|
if (num_entries) {
|
|
// get next sector
|
|
if (IS_LAST_SECT_IN_CLUS(fsi, sec)) {
|
|
clu = SECT_TO_CLUS(fsi, sec);
|
|
if (es->alloc_flag == 0x03)
|
|
clu++;
|
|
else if (get_next_clus_safe(sb, &clu))
|
|
goto err_out;
|
|
sec = CLUS_TO_SECT(fsi, clu);
|
|
} else {
|
|
sec++;
|
|
}
|
|
off = 0;
|
|
buf_off += copy_entries << DENTRY_SIZE_BITS;
|
|
}
|
|
}
|
|
|
|
TMSG("%s exited successfully\n", __func__);
|
|
return 0;
|
|
err_out:
|
|
TMSG("%s failed\n", __func__);
|
|
return -EIO;
|
|
}
|
|
|
|
/* write back all entries in entry set */
|
|
static 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);
|
|
}
|
|
|
|
s32 update_dir_chksum_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++) {
|
|
MMSG("%s %p\n", __func__, ep);
|
|
chksum = calc_chksum_2byte((void *) ep, DENTRY_SIZE, chksum, chksum_type);
|
|
ep++;
|
|
chksum_type = CS_DEFAULT;
|
|
}
|
|
|
|
ep = (DENTRY_T *)&(es->__buf);
|
|
((FILE_DENTRY_T *)ep)->checksum = cpu_to_le16(chksum);
|
|
return __write_whole_entry_set(sb, es);
|
|
}
|
|
|
|
/* 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_dentry_set_in_dir(struct super_block *sb,
|
|
CHAIN_T *p_dir, s32 entry, u32 type, DENTRY_T **file_ep)
|
|
{
|
|
s32 ret;
|
|
u32 off, byte_offset, clu = 0;
|
|
u32 entry_type;
|
|
u64 sec;
|
|
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
|
|
ENTRY_SET_CACHE_T *es = NULL;
|
|
DENTRY_T *ep, *pos;
|
|
u8 *buf;
|
|
u8 num_entries;
|
|
s32 mode = ES_MODE_STARTED;
|
|
|
|
/* FIXME : is available in error case? */
|
|
if (p_dir->dir == DIR_DELETED) {
|
|
EMSG("%s : access to deleted dentry\n", __func__);
|
|
BUG_ON(!fsi->prev_eio);
|
|
return NULL;
|
|
}
|
|
|
|
TMSG("%s entered\n", __func__);
|
|
MMSG("p_dir dir %u flags %x size %d\n", p_dir->dir, p_dir->flags, p_dir->size);
|
|
MMSG("entry %d type %d\n", entry, type);
|
|
|
|
byte_offset = entry << DENTRY_SIZE_BITS;
|
|
ret = walk_fat_chain(sb, p_dir, byte_offset, &clu);
|
|
if (ret)
|
|
return NULL;
|
|
|
|
/* byte offset in cluster */
|
|
byte_offset &= fsi->cluster_size - 1;
|
|
|
|
/* byte offset in sector */
|
|
off = byte_offset & (u32)(sb->s_blocksize - 1);
|
|
|
|
/* sector offset in cluster */
|
|
sec = byte_offset >> (sb->s_blocksize_bits);
|
|
sec += CLUS_TO_SECT(fsi, clu);
|
|
|
|
buf = dcache_getblk(sb, sec);
|
|
if (!buf)
|
|
goto err_out;
|
|
|
|
ep = (DENTRY_T *)(buf + off);
|
|
entry_type = exfat_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;
|
|
|
|
MMSG("trying to malloc %lx bytes for %d entries\n",
|
|
(unsigned long)(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) {
|
|
EMSG("%s: failed to alloc entryset\n", __func__);
|
|
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 consistency.
|
|
entry_type = exfat_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;
|
|
}
|
|
|
|
/* copy dentry */
|
|
memcpy(pos, ep, sizeof(DENTRY_T));
|
|
|
|
if (--num_entries == 0)
|
|
break;
|
|
|
|
if (((off + DENTRY_SIZE) & (u32)(sb->s_blocksize - 1)) <
|
|
(off & (u32)(sb->s_blocksize - 1))) {
|
|
// get the next sector
|
|
if (IS_LAST_SECT_IN_CLUS(fsi, sec)) {
|
|
if (es->alloc_flag == 0x03)
|
|
clu++;
|
|
else if (get_next_clus_safe(sb, &clu))
|
|
goto err_out;
|
|
sec = CLUS_TO_SECT(fsi, clu);
|
|
} else {
|
|
sec++;
|
|
}
|
|
buf = dcache_getblk(sb, sec);
|
|
if (!buf)
|
|
goto err_out;
|
|
off = 0;
|
|
ep = (DENTRY_T *)(buf);
|
|
} else {
|
|
ep++;
|
|
off += DENTRY_SIZE;
|
|
}
|
|
pos++;
|
|
}
|
|
|
|
if (file_ep)
|
|
*file_ep = (DENTRY_T *)&(es->__buf);
|
|
|
|
MMSG("es sec %llu offset %u flags %d, num_entries %u buf ptr %p\n",
|
|
es->sector, es->offset, es->alloc_flag, es->num_entries, &(es->__buf));
|
|
TMSG("%s exited %p\n", __func__, es);
|
|
return es;
|
|
err_out:
|
|
TMSG("%s exited (return NULL) (es %p)\n", __func__, es);
|
|
|
|
/* kfree(NULL) is safe */
|
|
kfree(es);
|
|
es = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
void release_dentry_set(ENTRY_SET_CACHE_T *es)
|
|
{
|
|
TMSG("%s %p\n", __func__, es);
|
|
|
|
/* kfree(NULL) is safe */
|
|
kfree(es);
|
|
es = NULL;
|
|
}
|
|
|
|
static s32 __extract_uni_name_from_name_entry(NAME_DENTRY_T *ep, u16 *uniname, s32 order)
|
|
{
|
|
s32 i, len = 0;
|
|
|
|
for (i = 0; i < 15; i++) {
|
|
/* FIXME : unaligned? */
|
|
*uniname = le16_to_cpu(ep->unicode_0_14[i]);
|
|
if (*uniname == 0x0)
|
|
return len;
|
|
uniname++;
|
|
len++;
|
|
}
|
|
|
|
*uniname = 0x0;
|
|
return len;
|
|
|
|
} /* end of __extract_uni_name_from_name_entry */
|
|
|
|
#define DIRENT_STEP_FILE (0)
|
|
#define DIRENT_STEP_STRM (1)
|
|
#define DIRENT_STEP_NAME (2)
|
|
#define DIRENT_STEP_SECD (3)
|
|
|
|
/* return values of exfat_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 s32 exfat_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 *unused, u32 type)
|
|
{
|
|
s32 i, rewind = 0, dentry = 0, end_eidx = 0, num_ext = 0, len;
|
|
s32 order, step, name_len;
|
|
s32 dentries_per_clu, num_empty = 0;
|
|
u32 entry_type;
|
|
u16 entry_uniname[16], *uniname = NULL, unichar;
|
|
CHAIN_T clu;
|
|
DENTRY_T *ep;
|
|
HINT_T *hint_stat = &fid->hint_stat;
|
|
HINT_FEMP_T candi_empty;
|
|
FILE_DENTRY_T *file_ep;
|
|
STRM_DENTRY_T *strm_ep;
|
|
NAME_DENTRY_T *name_ep;
|
|
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
|
|
|
|
/*
|
|
* REMARK:
|
|
* DOT and DOTDOT are handled by VFS layer
|
|
*/
|
|
|
|
if (IS_CLUS_FREE(p_dir->dir))
|
|
return -EIO;
|
|
|
|
dentries_per_clu = fsi->dentries_per_clu;
|
|
|
|
clu.dir = p_dir->dir;
|
|
clu.size = p_dir->size;
|
|
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;
|
|
rewind:
|
|
order = 0;
|
|
step = DIRENT_STEP_FILE;
|
|
while (!IS_CLUS_EOF(clu.dir)) {
|
|
i = dentry & (dentries_per_clu - 1);
|
|
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 = exfat_get_entry_type(ep);
|
|
|
|
if ((entry_type == TYPE_UNUSED) || (entry_type == TYPE_DELETED)) {
|
|
step = DIRENT_STEP_FILE;
|
|
|
|
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;
|
|
continue;
|
|
}
|
|
|
|
num_empty = 0;
|
|
candi_empty.eidx = -1;
|
|
|
|
if ((entry_type == TYPE_FILE) || (entry_type == TYPE_DIR)) {
|
|
step = DIRENT_STEP_FILE;
|
|
if ((type == TYPE_ALL) || (type == entry_type)) {
|
|
file_ep = (FILE_DENTRY_T *) ep;
|
|
num_ext = file_ep->num_ext;
|
|
step = DIRENT_STEP_STRM;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (entry_type == TYPE_STREAM) {
|
|
if (step != DIRENT_STEP_STRM) {
|
|
step = DIRENT_STEP_FILE;
|
|
continue;
|
|
}
|
|
step = DIRENT_STEP_FILE;
|
|
strm_ep = (STRM_DENTRY_T *) ep;
|
|
if ((p_uniname->name_hash == le16_to_cpu(strm_ep->name_hash)) &&
|
|
(p_uniname->name_len == strm_ep->name_len)) {
|
|
step = DIRENT_STEP_NAME;
|
|
order = 1;
|
|
name_len = 0;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (entry_type == TYPE_EXTEND) {
|
|
if (step != DIRENT_STEP_NAME) {
|
|
step = DIRENT_STEP_FILE;
|
|
continue;
|
|
}
|
|
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);
|
|
name_len += len;
|
|
|
|
unichar = *(uniname+len);
|
|
*(uniname+len) = 0x0;
|
|
|
|
if (nls_cmp_uniname(sb, uniname, entry_uniname)) {
|
|
step = DIRENT_STEP_FILE;
|
|
} else if (name_len == p_uniname->name_len) {
|
|
if (order == num_ext) {
|
|
//fid->hint_femp.eidx = -1;
|
|
goto found;
|
|
}
|
|
step = DIRENT_STEP_SECD;
|
|
}
|
|
|
|
*(uniname+len) = unichar;
|
|
continue;
|
|
}
|
|
|
|
if (entry_type & (TYPE_CRITICAL_SEC | TYPE_BENIGN_SEC)) {
|
|
if (step == DIRENT_STEP_SECD) {
|
|
if (++order == num_ext)
|
|
goto found;
|
|
continue;
|
|
}
|
|
}
|
|
step = DIRENT_STEP_FILE;
|
|
}
|
|
|
|
if (clu.flags == 0x03) {
|
|
if ((--clu.size) > 0)
|
|
clu.dir++;
|
|
else
|
|
clu.dir = CLUS_EOF;
|
|
} else {
|
|
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 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-1))) {
|
|
int ret = 0;
|
|
|
|
if (clu.flags == 0x03) {
|
|
if ((--clu.size) > 0)
|
|
clu.dir++;
|
|
else
|
|
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 - num_ext);
|
|
}
|
|
}
|
|
|
|
hint_stat->clu = clu.dir;
|
|
hint_stat->eidx = dentry + 1;
|
|
return (dentry - num_ext);
|
|
} /* end of exfat_find_dir_entry */
|
|
|
|
/* returns -EIO on error */
|
|
static s32 exfat_count_ext_entries(struct super_block *sb, CHAIN_T *p_dir, s32 entry, DENTRY_T *p_entry)
|
|
{
|
|
s32 i, count = 0;
|
|
u32 type;
|
|
FILE_DENTRY_T *file_ep = (FILE_DENTRY_T *) p_entry;
|
|
DENTRY_T *ext_ep;
|
|
|
|
for (i = 0, entry++; i < file_ep->num_ext; i++, entry++) {
|
|
ext_ep = get_dentry_in_dir(sb, p_dir, entry, NULL);
|
|
if (!ext_ep)
|
|
return -EIO;
|
|
|
|
type = exfat_get_entry_type(ext_ep);
|
|
if ((type == TYPE_EXTEND) || (type == TYPE_STREAM))
|
|
count++;
|
|
else
|
|
return count;
|
|
}
|
|
|
|
return count;
|
|
} /* end of exfat_count_ext_entries */
|
|
|
|
|
|
/*
|
|
* Name Conversion Functions
|
|
*/
|
|
static void exfat_get_uniname_from_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u16 *uniname)
|
|
{
|
|
s32 i;
|
|
DENTRY_T *ep;
|
|
ENTRY_SET_CACHE_T *es;
|
|
|
|
es = get_dentry_set_in_dir(sb, p_dir, entry, ES_ALL_ENTRIES, &ep);
|
|
if (!es)
|
|
return;
|
|
|
|
if (es->num_entries < 3)
|
|
goto out;
|
|
|
|
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++) {
|
|
/* end of name entry */
|
|
if (exfat_get_entry_type(ep) != TYPE_EXTEND)
|
|
goto out;
|
|
|
|
__extract_uni_name_from_name_entry((NAME_DENTRY_T *)ep, uniname, i);
|
|
uniname += 15;
|
|
}
|
|
|
|
out:
|
|
release_dentry_set(es);
|
|
} /* end of exfat_get_uniname_from_ext_entry */
|
|
|
|
static 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_entries */
|
|
|
|
static s32 exfat_check_max_dentries(FILE_ID_T *fid)
|
|
{
|
|
if ((fid->size >> DENTRY_SIZE_BITS) >= MAX_EXFAT_DENTRIES) {
|
|
/* exFAT spec allows a dir to grow upto 8388608(256MB) dentries */
|
|
return -ENOSPC;
|
|
}
|
|
return 0;
|
|
} /* end of check_max_dentries */
|
|
|
|
/*
|
|
* Allocation Bitmap Management Functions
|
|
*/
|
|
s32 load_alloc_bmp(struct super_block *sb)
|
|
{
|
|
s32 ret;
|
|
u32 i, j, map_size, type, need_map_size;
|
|
u64 sector;
|
|
CHAIN_T clu;
|
|
BMAP_DENTRY_T *ep;
|
|
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
|
|
|
|
clu.dir = fsi->root_dir;
|
|
clu.flags = 0x01;
|
|
|
|
while (!IS_CLUS_EOF(clu.dir)) {
|
|
for (i = 0; i < fsi->dentries_per_clu; i++) {
|
|
ep = (BMAP_DENTRY_T *) get_dentry_in_dir(sb, &clu, i, NULL);
|
|
if (!ep)
|
|
return -EIO;
|
|
|
|
type = exfat_get_entry_type((DENTRY_T *) ep);
|
|
|
|
if (type == TYPE_UNUSED)
|
|
break;
|
|
if (type != TYPE_BITMAP)
|
|
continue;
|
|
|
|
if (ep->flags == 0x0) {
|
|
fsi->map_clu = le32_to_cpu(ep->start_clu);
|
|
map_size = (u32) le64_to_cpu(ep->size);
|
|
|
|
need_map_size = (((fsi->num_clusters - CLUS_BASE) - 1) >> 3) + 1;
|
|
if (need_map_size != map_size) {
|
|
sdfat_log_msg(sb, KERN_ERR,
|
|
"bogus allocation bitmap size(need : %u, cur : %u)",
|
|
need_map_size, map_size);
|
|
/* Only allowed when bogus allocation bitmap size is large */
|
|
if (need_map_size > map_size)
|
|
return -EIO;
|
|
}
|
|
fsi->map_sectors = ((need_map_size - 1) >> (sb->s_blocksize_bits)) + 1;
|
|
fsi->vol_amap =
|
|
kmalloc((sizeof(struct buffer_head *) * fsi->map_sectors), GFP_KERNEL);
|
|
if (!fsi->vol_amap)
|
|
return -ENOMEM;
|
|
|
|
sector = CLUS_TO_SECT(fsi, fsi->map_clu);
|
|
|
|
/* trigger read amap ahead */
|
|
bdev_readahead(sb, sector, fsi->map_sectors);
|
|
|
|
for (j = 0; j < fsi->map_sectors; j++) {
|
|
fsi->vol_amap[j] = NULL;
|
|
ret = read_sect(sb, sector+j, &(fsi->vol_amap[j]), 1);
|
|
if (ret) {
|
|
/* release all buffers and free vol_amap */
|
|
i = 0;
|
|
while (i < j)
|
|
brelse(fsi->vol_amap[i++]);
|
|
|
|
/* kfree(NULL) is safe */
|
|
kfree(fsi->vol_amap);
|
|
fsi->vol_amap = NULL;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
fsi->pbr_bh = NULL;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (get_next_clus_safe(sb, &clu.dir))
|
|
return -EIO;
|
|
}
|
|
|
|
return -EINVAL;
|
|
} /* end of load_alloc_bmp */
|
|
|
|
void free_alloc_bmp(struct super_block *sb)
|
|
{
|
|
s32 i;
|
|
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
|
|
|
|
brelse(fsi->pbr_bh);
|
|
|
|
for (i = 0; i < fsi->map_sectors; i++)
|
|
__brelse(fsi->vol_amap[i]);
|
|
|
|
/* kfree(NULL) is safe */
|
|
kfree(fsi->vol_amap);
|
|
fsi->vol_amap = NULL;
|
|
}
|
|
|
|
/* WARN :
|
|
* If the value of "clu" is 0, it means cluster 2 which is
|
|
* the first cluster of cluster heap.
|
|
*/
|
|
static s32 set_alloc_bitmap(struct super_block *sb, u32 clu)
|
|
{
|
|
s32 i, b;
|
|
u64 sector;
|
|
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
|
|
|
|
i = clu >> (sb->s_blocksize_bits + 3);
|
|
b = clu & (u32)((sb->s_blocksize << 3) - 1);
|
|
|
|
sector = CLUS_TO_SECT(fsi, fsi->map_clu) + i;
|
|
bitmap_set((unsigned long *)(fsi->vol_amap[i]->b_data), b, 1);
|
|
|
|
return write_sect(sb, sector, fsi->vol_amap[i], 0);
|
|
} /* end of set_alloc_bitmap */
|
|
|
|
/* WARN :
|
|
* If the value of "clu" is 0, it means cluster 2 which is
|
|
* the first cluster of cluster heap.
|
|
*/
|
|
static s32 clr_alloc_bitmap(struct super_block *sb, u32 clu)
|
|
{
|
|
s32 ret;
|
|
s32 i, b;
|
|
u64 sector;
|
|
struct sdfat_sb_info *sbi = SDFAT_SB(sb);
|
|
struct sdfat_mount_options *opts = &sbi->options;
|
|
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
|
|
|
|
i = clu >> (sb->s_blocksize_bits + 3);
|
|
b = clu & (u32)((sb->s_blocksize << 3) - 1);
|
|
|
|
sector = CLUS_TO_SECT(fsi, fsi->map_clu) + i;
|
|
|
|
bitmap_clear((unsigned long *)(fsi->vol_amap[i]->b_data), b, 1);
|
|
|
|
ret = write_sect(sb, sector, fsi->vol_amap[i], 0);
|
|
|
|
if (opts->discard) {
|
|
s32 ret_discard;
|
|
|
|
TMSG("discard cluster(%08x)\n", clu+2);
|
|
ret_discard = sb_issue_discard(sb, CLUS_TO_SECT(fsi, clu+2),
|
|
(1 << fsi->sect_per_clus_bits), GFP_NOFS, 0);
|
|
|
|
if (ret_discard == -EOPNOTSUPP) {
|
|
sdfat_msg(sb, KERN_ERR,
|
|
"discard not supported by device, disabling");
|
|
opts->discard = 0;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
} /* end of clr_alloc_bitmap */
|
|
|
|
/* WARN :
|
|
* If the value of "clu" is 0, it means cluster 2 which is
|
|
* the first cluster of cluster heap.
|
|
*/
|
|
static u32 test_alloc_bitmap(struct super_block *sb, u32 clu)
|
|
{
|
|
u32 i, map_i, map_b;
|
|
u32 clu_base, clu_free;
|
|
u8 k, clu_mask;
|
|
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
|
|
|
|
clu_base = (clu & ~(0x7)) + 2;
|
|
clu_mask = (1 << (clu - clu_base + 2)) - 1;
|
|
|
|
map_i = clu >> (sb->s_blocksize_bits + 3);
|
|
map_b = (clu >> 3) & (u32)(sb->s_blocksize - 1);
|
|
|
|
for (i = 2; i < fsi->num_clusters; i += 8) {
|
|
k = *(((u8 *) fsi->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 < fsi->num_clusters)
|
|
return clu_free;
|
|
}
|
|
clu_base += 8;
|
|
|
|
if (((++map_b) >= (u32)sb->s_blocksize) ||
|
|
(clu_base >= fsi->num_clusters)) {
|
|
if ((++map_i) >= fsi->map_sectors) {
|
|
clu_base = 2;
|
|
map_i = 0;
|
|
}
|
|
map_b = 0;
|
|
}
|
|
}
|
|
|
|
return CLUS_EOF;
|
|
} /* end of test_alloc_bitmap */
|
|
|
|
void sync_alloc_bmp(struct super_block *sb)
|
|
{
|
|
s32 i;
|
|
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
|
|
|
|
if (fsi->vol_amap == NULL)
|
|
return;
|
|
|
|
for (i = 0; i < fsi->map_sectors; i++)
|
|
sync_dirty_buffer(fsi->vol_amap[i]);
|
|
}
|
|
|
|
static s32 exfat_chain_cont_cluster(struct super_block *sb, u32 chain, u32 len)
|
|
{
|
|
if (!len)
|
|
return 0;
|
|
|
|
while (len > 1) {
|
|
if (fat_ent_set(sb, chain, chain+1))
|
|
return -EIO;
|
|
chain++;
|
|
len--;
|
|
}
|
|
|
|
if (fat_ent_set(sb, chain, CLUS_EOF))
|
|
return -EIO;
|
|
return 0;
|
|
}
|
|
|
|
s32 chain_cont_cluster(struct super_block *sb, u32 chain, u32 len)
|
|
{
|
|
return exfat_chain_cont_cluster(sb, chain, len);
|
|
}
|
|
|
|
|
|
static s32 exfat_free_cluster(struct super_block *sb, CHAIN_T *p_chain, s32 do_relse)
|
|
{
|
|
s32 ret = -EIO;
|
|
u32 num_clusters = 0;
|
|
u32 clu;
|
|
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 == 0) {
|
|
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;
|
|
|
|
if (p_chain->flags == 0x03) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (clr_alloc_bitmap(sb, clu-2))
|
|
goto out;
|
|
clu++;
|
|
|
|
num_clusters++;
|
|
} while (num_clusters < p_chain->size);
|
|
} else {
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (clr_alloc_bitmap(sb, (clu - CLUS_BASE)))
|
|
goto out;
|
|
|
|
if (get_next_clus_safe(sb, &clu))
|
|
goto out;
|
|
|
|
num_clusters++;
|
|
} while (!IS_CLUS_EOF(clu));
|
|
}
|
|
|
|
/* success */
|
|
ret = 0;
|
|
out:
|
|
|
|
fsi->used_clusters -= num_clusters;
|
|
return ret;
|
|
} /* end of exfat_free_cluster */
|
|
|
|
static s32 exfat_alloc_cluster(struct super_block *sb, u32 num_alloc, CHAIN_T *p_chain, s32 dest)
|
|
{
|
|
s32 ret = -ENOSPC;
|
|
u32 num_clusters = 0, total_cnt;
|
|
u32 hint_clu, new_clu, last_clu = CLUS_EOF;
|
|
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;
|
|
|
|
hint_clu = p_chain->dir;
|
|
/* find new cluster */
|
|
if (IS_CLUS_EOF(hint_clu)) {
|
|
if (fsi->clu_srch_ptr < CLUS_BASE) {
|
|
EMSG("%s: fsi->clu_srch_ptr is invalid (%u)\n",
|
|
__func__, fsi->clu_srch_ptr);
|
|
ASSERT(0);
|
|
fsi->clu_srch_ptr = CLUS_BASE;
|
|
}
|
|
|
|
hint_clu = test_alloc_bitmap(sb, fsi->clu_srch_ptr - CLUS_BASE);
|
|
if (IS_CLUS_EOF(hint_clu))
|
|
return -ENOSPC;
|
|
}
|
|
|
|
/* check cluster validation */
|
|
if (!is_valid_clus(fsi, hint_clu)) {
|
|
/* "last + 1" can be passed as hint_clu. Otherwise, bug_on */
|
|
if (hint_clu != fsi->num_clusters) {
|
|
EMSG("%s: hint_cluster is invalid (%u)\n",
|
|
__func__, hint_clu);
|
|
sdfat_debug_bug_on(1);
|
|
}
|
|
hint_clu = CLUS_BASE;
|
|
if (p_chain->flags == 0x03) {
|
|
if (exfat_chain_cont_cluster(sb, p_chain->dir, num_clusters))
|
|
return -EIO;
|
|
p_chain->flags = 0x01;
|
|
}
|
|
}
|
|
|
|
set_sb_dirty(sb);
|
|
|
|
p_chain->dir = CLUS_EOF;
|
|
|
|
while ((new_clu = test_alloc_bitmap(sb, hint_clu - CLUS_BASE)) != CLUS_EOF) {
|
|
if ((new_clu != hint_clu) && (p_chain->flags == 0x03)) {
|
|
if (exfat_chain_cont_cluster(sb, p_chain->dir, num_clusters)) {
|
|
ret = -EIO;
|
|
goto error;
|
|
}
|
|
p_chain->flags = 0x01;
|
|
}
|
|
|
|
/* update allocation bitmap */
|
|
if (set_alloc_bitmap(sb, new_clu - CLUS_BASE)) {
|
|
ret = -EIO;
|
|
goto error;
|
|
}
|
|
|
|
num_clusters++;
|
|
|
|
/* update FAT table */
|
|
if (p_chain->flags == 0x01) {
|
|
if (fat_ent_set(sb, new_clu, CLUS_EOF)) {
|
|
ret = -EIO;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (IS_CLUS_EOF(p_chain->dir)) {
|
|
p_chain->dir = new_clu;
|
|
} else if (p_chain->flags == 0x01) {
|
|
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 = hint_clu;
|
|
fsi->used_clusters += num_clusters;
|
|
|
|
p_chain->size += num_clusters;
|
|
return 0;
|
|
}
|
|
|
|
hint_clu = new_clu + 1;
|
|
if (hint_clu >= fsi->num_clusters) {
|
|
hint_clu = CLUS_BASE;
|
|
|
|
if (p_chain->flags == 0x03) {
|
|
if (exfat_chain_cont_cluster(sb, p_chain->dir, num_clusters)) {
|
|
ret = -EIO;
|
|
goto error;
|
|
}
|
|
p_chain->flags = 0x01;
|
|
}
|
|
}
|
|
}
|
|
error:
|
|
if (num_clusters)
|
|
exfat_free_cluster(sb, p_chain, 0);
|
|
return ret;
|
|
} /* end of exfat_alloc_cluster */
|
|
|
|
static s32 exfat_count_used_clusters(struct super_block *sb, u32 *ret_count)
|
|
{
|
|
u32 count = 0;
|
|
u32 i, map_i, map_b;
|
|
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
|
|
u32 total_clus = fsi->num_clusters - 2;
|
|
|
|
map_i = map_b = 0;
|
|
|
|
for (i = 0; i < total_clus; i += 8) {
|
|
u8 k = *(((u8 *) fsi->vol_amap[map_i]->b_data) + map_b);
|
|
|
|
count += used_bit[k];
|
|
if ((++map_b) >= (u32)sb->s_blocksize) {
|
|
map_i++;
|
|
map_b = 0;
|
|
}
|
|
}
|
|
|
|
/* FIXME : abnormal bitmap count should be handled as more smart */
|
|
if (total_clus < count)
|
|
count = total_clus;
|
|
|
|
*ret_count = count;
|
|
return 0;
|
|
} /* end of exfat_count_used_clusters */
|
|
|
|
|
|
/*
|
|
* File Operation Functions
|
|
*/
|
|
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_uniname_from_ext_entry = exfat_get_uniname_from_ext_entry,
|
|
.count_ext_entries = exfat_count_ext_entries,
|
|
.calc_num_entries = exfat_calc_num_entries,
|
|
.check_max_dentries = exfat_check_max_dentries,
|
|
|
|
.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 mount_exfat(struct super_block *sb, pbr_t *p_pbr)
|
|
{
|
|
pbr64_t *p_bpb = (pbr64_t *)p_pbr;
|
|
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
|
|
|
|
fsi->sect_per_clus = 1 << p_bpb->bsx.sect_per_clus_bits;
|
|
fsi->sect_per_clus_bits = p_bpb->bsx.sect_per_clus_bits;
|
|
fsi->cluster_size_bits = fsi->sect_per_clus_bits + sb->s_blocksize_bits;
|
|
fsi->cluster_size = 1 << fsi->cluster_size_bits;
|
|
|
|
if (!p_bpb->bsx.num_fats) {
|
|
sdfat_msg(sb, KERN_ERR, "bogus number of FAT structure");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (p_bpb->bsx.num_fats >= 2) {
|
|
sdfat_msg(sb, KERN_WARNING,
|
|
"unsupported number of FAT structure :%u, try with 1",
|
|
p_bpb->bsx.num_fats);
|
|
}
|
|
|
|
fsi->num_FAT_sectors = le32_to_cpu(p_bpb->bsx.fat_length);
|
|
if (!fsi->num_FAT_sectors) {
|
|
sdfat_msg(sb, KERN_ERR, "bogus fat size");
|
|
return -EINVAL;
|
|
}
|
|
|
|
fsi->FAT1_start_sector = le32_to_cpu(p_bpb->bsx.fat_offset);
|
|
fsi->FAT2_start_sector = fsi->FAT1_start_sector;
|
|
|
|
fsi->root_start_sector = le32_to_cpu(p_bpb->bsx.clu_offset);
|
|
fsi->data_start_sector = fsi->root_start_sector;
|
|
|
|
fsi->num_sectors = le64_to_cpu(p_bpb->bsx.vol_length);
|
|
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 = le32_to_cpu(p_bpb->bsx.clu_count) + CLUS_BASE;
|
|
|
|
fsi->vol_id = le32_to_cpu(p_bpb->bsx.vol_serial);
|
|
fsi->root_dir = le32_to_cpu(p_bpb->bsx.root_cluster);
|
|
fsi->dentries_in_root = 0;
|
|
fsi->dentries_per_clu = 1 << (fsi->cluster_size_bits - DENTRY_SIZE_BITS);
|
|
fsi->vol_flag = (u32) le16_to_cpu(p_bpb->bsx.vol_flags);
|
|
fsi->clu_srch_ptr = CLUS_BASE;
|
|
fsi->used_clusters = (u32) ~0;
|
|
|
|
fsi->fs_func = &exfat_fs_func;
|
|
fsi->vol_type = EXFAT;
|
|
fat_ent_ops_init(sb);
|
|
|
|
if (p_bpb->bsx.vol_flags & 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_exfat */
|
|
|
|
/* end of core_exfat.c */
|
|
|