kernel_samsung_a34x-permissive/fs/sdfat/core.c
2024-04-28 15:49:01 +02:00

3704 lines
92 KiB
C
Executable file

/*
* 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.c */
/* PURPOSE : FAT & exFAT common core code for sdFAT */
/* */
/*----------------------------------------------------------------------*/
/* NOTES */
/* */
/* */
/************************************************************************/
#include <linux/version.h>
#include <linux/blkdev.h>
#include <linux/workqueue.h>
#include <linux/writeback.h>
#include <linux/kernel.h>
#include <linux/log2.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0)
#include <linux/iversion.h>
#endif
#include "sdfat.h"
#include "core.h"
#include <asm/byteorder.h>
#include <asm/unaligned.h>
/*************************************************************************
* FUNCTIONS WHICH HAS KERNEL VERSION DEPENDENCY
*************************************************************************/
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)
static inline u64 inode_peek_iversion(struct inode *inode)
{
return inode->i_version;
}
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 11, 0)
static u64 bdev_nr_sectors(struct block_device *bdev)
{
struct hd_struct *part = bdev->bd_part;
if (!part)
return 0;
return (u64)(part->nr_sects);
}
#endif
/*----------------------------------------------------------------------*/
/* Constant & Macro Definitions */
/*----------------------------------------------------------------------*/
static inline void __set_sb_dirty(struct super_block *sb)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0)
sb->s_dirt = 1;
#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) */
struct sdfat_sb_info *sbi = SDFAT_SB(sb);
sbi->s_dirt = 1;
/* Insert work */
spin_lock(&sbi->work_lock);
if (!sbi->write_super_queued) {
unsigned long delay;
delay = msecs_to_jiffies(CONFIG_SDFAT_WRITE_SB_INTERVAL_CSECS * 10);
queue_delayed_work(system_long_wq, &sbi->write_super_work, delay);
sbi->write_super_queued = 1;
}
spin_unlock(&sbi->work_lock);
#endif
}
void set_sb_dirty(struct super_block *sb)
{
__set_sb_dirty(sb);
// XXX: to be removed later, prints too much output
//TMSG("%s finished.\n", __func__);
}
/*----------------------------------------------------------------------*/
/* Global Variable Definitions */
/*----------------------------------------------------------------------*/
/*----------------------------------------------------------------------*/
/* Local Variable Definitions */
/*----------------------------------------------------------------------*/
static s8 *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
};
/*======================================================================*/
/* Local Function Definitions */
/*======================================================================*/
/*
* File System Management Functions
*/
static s32 check_type_size(void)
{
/* critical check for system requirement on size of DENTRY_T structure */
if (sizeof(DENTRY_T) != DENTRY_SIZE)
return -EINVAL;
if (sizeof(DOS_DENTRY_T) != DENTRY_SIZE)
return -EINVAL;
if (sizeof(EXT_DENTRY_T) != DENTRY_SIZE)
return -EINVAL;
if (sizeof(FILE_DENTRY_T) != DENTRY_SIZE)
return -EINVAL;
if (sizeof(STRM_DENTRY_T) != DENTRY_SIZE)
return -EINVAL;
if (sizeof(NAME_DENTRY_T) != DENTRY_SIZE)
return -EINVAL;
if (sizeof(BMAP_DENTRY_T) != DENTRY_SIZE)
return -EINVAL;
if (sizeof(CASE_DENTRY_T) != DENTRY_SIZE)
return -EINVAL;
if (sizeof(VOLM_DENTRY_T) != DENTRY_SIZE)
return -EINVAL;
return 0;
}
static s32 __fs_set_vol_flags(struct super_block *sb, u16 new_flag, s32 always_sync)
{
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
s32 err;
s32 sync = 0;
/* flags are not changed */
if (fsi->vol_flag == new_flag)
return 0;
fsi->vol_flag = new_flag;
/* skip updating volume dirty flag,
* if this volume has been mounted with read-only
*/
if (sb_rdonly(sb))
return 0;
if (!fsi->pbr_bh) {
err = read_sect(sb, 0, &(fsi->pbr_bh), 1);
if (err) {
EMSG("%s : failed to read boot sector\n", __func__);
return err;
}
}
if (fsi->vol_type == EXFAT) {
pbr64_t *bpb = (pbr64_t *)fsi->pbr_bh->b_data;
bpb->bsx.vol_flags = cpu_to_le16(new_flag);
} else if (fsi->vol_type == FAT32) {
pbr32_t *bpb = (pbr32_t *)fsi->pbr_bh->b_data;
bpb->bsx.state = new_flag & VOL_DIRTY ? FAT_VOL_DIRTY : 0x00;
} else { /* FAT16/12 */
pbr16_t *bpb = (pbr16_t *) fsi->pbr_bh->b_data;
bpb->bpb.f16.state = new_flag & VOL_DIRTY ?
FAT_VOL_DIRTY : 0x00;
}
if (always_sync)
sync = 1;
else if ((new_flag == VOL_DIRTY) && (!buffer_dirty(fsi->pbr_bh)))
sync = 1;
else
sync = 0;
err = write_sect(sb, 0, fsi->pbr_bh, sync);
if (err)
EMSG("%s : failed to modify volume flag\n", __func__);
return err;
}
static s32 fs_set_vol_flags(struct super_block *sb, u16 new_flag)
{
return __fs_set_vol_flags(sb, new_flag, 0);
}
s32 fscore_set_vol_flags(struct super_block *sb, u16 new_flag, s32 always_sync)
{
return __fs_set_vol_flags(sb, new_flag, always_sync);
}
static inline s32 __fs_meta_sync(struct super_block *sb, s32 do_sync)
{
#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
if (fsi->vol_type != EXFAT) {
MMSG("meta flush in fs_sync(sync=%d)\n", do_sync);
fcache_flush(sb, 0);
dcache_flush(sb, 0);
}
#else
/* DO NOTHING */
#endif
return 0;
}
static s32 fs_sync(struct super_block *sb, s32 do_sync)
{
s32 err;
if (!do_sync)
return 0;
err = __fs_meta_sync(sb, do_sync);
if (!err)
err = bdev_sync_all(sb);
if (err)
EMSG("%s : failed to sync. (err:%d)\n", __func__, err);
return err;
}
/*
* Cluster Management Functions
*/
static s32 __clear_cluster(struct inode *inode, u32 clu)
{
u64 s, n;
struct super_block *sb = inode->i_sb;
u32 sect_size = (u32)sb->s_blocksize;
s32 ret = 0;
struct buffer_head *tmp_bh = NULL;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
if (IS_CLUS_FREE(clu)) { /* FAT16 root_dir */
s = fsi->root_start_sector;
n = fsi->data_start_sector;
} else {
s = CLUS_TO_SECT(fsi, clu);
n = s + fsi->sect_per_clus;
}
if (IS_DIRSYNC(inode)) {
ret = write_msect_zero(sb, s, (u64)fsi->sect_per_clus);
if (ret != -EAGAIN)
return ret;
}
/* Trying buffered zero writes
* if it doesn't have DIRSYNC or write_msect_zero() returned -EAGAIN
*/
for ( ; s < n; s++) {
#if 0
dcache_release(sb, s);
#endif
ret = read_sect(sb, s, &tmp_bh, 0);
if (ret)
goto out;
memset((u8 *)tmp_bh->b_data, 0x0, sect_size);
ret = write_sect(sb, s, tmp_bh, 0);
if (ret)
goto out;
}
out:
brelse(tmp_bh);
return ret;
} /* end of __clear_cluster */
static s32 __find_last_cluster(struct super_block *sb, CHAIN_T *p_chain, u32 *ret_clu)
{
u32 clu, next;
u32 count = 0;
next = p_chain->dir;
if (p_chain->flags == 0x03) {
*ret_clu = next + p_chain->size - 1;
return 0;
}
do {
count++;
clu = next;
if (fat_ent_get_safe(sb, clu, &next))
return -EIO;
} while (!IS_CLUS_EOF(next));
if (p_chain->size != count) {
sdfat_fs_error(sb, "bogus directory size "
"(clus : ondisk(%d) != counted(%d))",
p_chain->size, count);
sdfat_debug_bug_on(1);
return -EIO;
}
*ret_clu = clu;
return 0;
}
static s32 __count_num_clusters(struct super_block *sb, CHAIN_T *p_chain, u32 *ret_count)
{
u32 i, count;
u32 clu;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
if (!p_chain->dir || IS_CLUS_EOF(p_chain->dir)) {
*ret_count = 0;
return 0;
}
if (p_chain->flags == 0x03) {
*ret_count = p_chain->size;
return 0;
}
clu = p_chain->dir;
count = 0;
for (i = CLUS_BASE; i < fsi->num_clusters; i++) {
count++;
if (fat_ent_get_safe(sb, clu, &clu))
return -EIO;
if (IS_CLUS_EOF(clu))
break;
}
*ret_count = count;
return 0;
}
/*
* Upcase table Management Functions
*/
static void free_upcase_table(struct super_block *sb)
{
u32 i;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
u16 **upcase_table;
upcase_table = fsi->vol_utbl;
for (i = 0 ; i < UTBL_COL_COUNT ; i++) {
/* kfree(NULL) is safe */
kfree(upcase_table[i]);
upcase_table[i] = NULL;
}
/* kfree(NULL) is safe */
kfree(fsi->vol_utbl);
fsi->vol_utbl = NULL;
}
static s32 __load_upcase_table(struct super_block *sb, u64 sector, u64 num_sectors, u32 utbl_checksum)
{
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
struct buffer_head *tmp_bh = NULL;
u32 sect_size = (u32)sb->s_blocksize;
s32 ret = -EIO;
u32 i, j;
u8 skip = false;
u32 index = 0;
u32 checksum = 0;
u16 **upcase_table = kzalloc((UTBL_COL_COUNT * sizeof(u16 *)), GFP_KERNEL);
if (!upcase_table)
return -ENOMEM;
/* thanks for kzalloc
* memset(upcase_table, 0, UTBL_COL_COUNT * sizeof(u16 *));
*/
/* trigger read upcase_table ahead */
bdev_readahead(sb, sector, num_sectors);
fsi->vol_utbl = upcase_table;
num_sectors += sector;
while (sector < num_sectors) {
ret = read_sect(sb, sector, &tmp_bh, 1);
if (ret) {
EMSG("%s: failed to read sector(0x%llx)\n",
__func__, sector);
goto error;
}
sector++;
for (i = 0; i < sect_size && index <= 0xFFFF; i += 2) {
/* FIXME : is __le16 ok? */
//u16 uni = le16_to_cpu(((__le16*)(tmp_bh->b_data))[i]);
u16 uni = get_unaligned_le16((u8 *)tmp_bh->b_data+i);
checksum = ((checksum & 1) ? 0x80000000 : 0) +
(checksum >> 1) + *(((u8 *)tmp_bh->b_data)+i);
checksum = ((checksum & 1) ? 0x80000000 : 0) +
(checksum >> 1) + *(((u8 *)tmp_bh->b_data)+(i+1));
if (skip) {
MMSG("skip from 0x%X to 0x%X(amount of 0x%X)\n",
index, index+uni, uni);
index += uni;
skip = false;
} else if (uni == index) {
index++;
} else if (uni == 0xFFFF) {
skip = true;
} else { /* uni != index , uni != 0xFFFF */
u16 col_index = get_col_index(index);
if (!upcase_table[col_index]) {
upcase_table[col_index] =
kmalloc((UTBL_ROW_COUNT * sizeof(u16)), GFP_KERNEL);
if (!upcase_table[col_index]) {
EMSG("failed to allocate memory"
" for column 0x%X\n",
col_index);
ret = -ENOMEM;
goto error;
}
for (j = 0; j < UTBL_ROW_COUNT; j++)
upcase_table[col_index][j] = (col_index << LOW_INDEX_BIT) | j;
}
upcase_table[col_index][get_row_index(index)] = uni;
index++;
}
}
}
if (index >= 0xFFFF && utbl_checksum == checksum) {
DMSG("%s: load upcase table successfully"
"(idx:0x%08x, utbl_chksum:0x%08x)\n",
__func__, index, utbl_checksum);
if (tmp_bh)
brelse(tmp_bh);
return 0;
}
EMSG("%s: failed to load upcase table"
"(idx:0x%08x, chksum:0x%08x, utbl_chksum:0x%08x)\n",
__func__, index, checksum, utbl_checksum);
ret = -EINVAL;
error:
if (tmp_bh)
brelse(tmp_bh);
free_upcase_table(sb);
return ret;
}
static s32 __load_default_upcase_table(struct super_block *sb)
{
s32 i, ret = -EIO;
u32 j;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
u8 skip = false;
u32 index = 0;
u16 uni = 0;
u16 **upcase_table;
upcase_table = kmalloc((UTBL_COL_COUNT * sizeof(u16 *)), GFP_KERNEL);
if (!upcase_table)
return -ENOMEM;
fsi->vol_utbl = upcase_table;
memset(upcase_table, 0, UTBL_COL_COUNT * sizeof(u16 *));
for (i = 0; index <= 0xFFFF && i < SDFAT_NUM_UPCASE*2; i += 2) {
/* FIXME : is __le16 ok? */
//uni = le16_to_cpu(((__le16*)uni_def_upcase)[i>>1]);
uni = get_unaligned_le16((u8 *)uni_def_upcase+i);
if (skip) {
MMSG("skip from 0x%x ", index);
index += uni;
MMSG("to 0x%x (amount of 0x%x)\n", index, uni);
skip = false;
} else if (uni == index) {
index++;
} else if (uni == 0xFFFF) {
skip = true;
} else { /* uni != index , uni != 0xFFFF */
u16 col_index = get_col_index(index);
if (!upcase_table[col_index]) {
upcase_table[col_index] = kmalloc((UTBL_ROW_COUNT * sizeof(u16)), GFP_KERNEL);
if (!upcase_table[col_index]) {
EMSG("failed to allocate memory for "
"new column 0x%x\n", col_index);
ret = -ENOMEM;
goto error;
}
for (j = 0; j < UTBL_ROW_COUNT; j++)
upcase_table[col_index][j] = (col_index << LOW_INDEX_BIT) | j;
}
upcase_table[col_index][get_row_index(index)] = uni;
index++;
}
}
if (index >= 0xFFFF)
return 0;
error:
/* FATAL error: default upcase table has error */
free_upcase_table(sb);
return ret;
}
static s32 load_upcase_table(struct super_block *sb)
{
s32 i, ret;
u32 tbl_clu, type;
u64 sector, tbl_size, num_sectors;
u8 blksize_bits = sb->s_blocksize_bits;
CHAIN_T clu;
CASE_DENTRY_T *ep;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
clu.dir = fsi->root_dir;
clu.flags = 0x01;
if (fsi->vol_type != EXFAT)
goto load_default;
while (!IS_CLUS_EOF(clu.dir)) {
for (i = 0; i < fsi->dentries_per_clu; i++) {
ep = (CASE_DENTRY_T *) get_dentry_in_dir(sb, &clu, i, NULL);
if (!ep)
return -EIO;
type = fsi->fs_func->get_entry_type((DENTRY_T *) ep);
if (type == TYPE_UNUSED)
break;
if (type != TYPE_UPCASE)
continue;
tbl_clu = le32_to_cpu(ep->start_clu);
tbl_size = le64_to_cpu(ep->size);
sector = CLUS_TO_SECT(fsi, tbl_clu);
num_sectors = ((tbl_size - 1) >> blksize_bits) + 1;
ret = __load_upcase_table(sb, sector, num_sectors,
le32_to_cpu(ep->checksum));
if (ret && (ret != -EIO))
goto load_default;
/* load successfully */
return ret;
}
if (get_next_clus_safe(sb, &(clu.dir)))
return -EIO;
}
load_default:
sdfat_log_msg(sb, KERN_INFO, "trying to load default upcase table");
/* load default upcase table */
return __load_default_upcase_table(sb);
} /* end of load_upcase_table */
/*
* Directory Entry Management Functions
*/
s32 walk_fat_chain(struct super_block *sb, CHAIN_T *p_dir, u32 byte_offset, u32 *clu)
{
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
u32 clu_offset;
u32 cur_clu;
clu_offset = byte_offset >> fsi->cluster_size_bits;
cur_clu = p_dir->dir;
if (p_dir->flags == 0x03) {
cur_clu += clu_offset;
} else {
while (clu_offset > 0) {
if (get_next_clus_safe(sb, &cur_clu))
return -EIO;
if (IS_CLUS_EOF(cur_clu)) {
sdfat_fs_error(sb, "invalid dentry access "
"beyond EOF (clu : %u, eidx : %d)",
p_dir->dir,
byte_offset >> DENTRY_SIZE_BITS);
return -EIO;
}
clu_offset--;
}
}
if (clu)
*clu = cur_clu;
return 0;
}
static s32 find_location(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u64 *sector, s32 *offset)
{
s32 ret;
u32 off, clu = 0;
u32 blksize_mask = (u32)(sb->s_blocksize-1);
u8 blksize_bits = sb->s_blocksize_bits;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
off = entry << DENTRY_SIZE_BITS;
/* FAT16 root_dir */
if (IS_CLUS_FREE(p_dir->dir)) {
*offset = off & blksize_mask;
*sector = off >> blksize_bits;
*sector += fsi->root_start_sector;
return 0;
}
ret = walk_fat_chain(sb, p_dir, off, &clu);
if (ret)
return ret;
/* byte offset in cluster */
off &= (fsi->cluster_size - 1);
/* byte offset in sector */
*offset = off & blksize_mask;
/* sector offset in cluster */
*sector = off >> blksize_bits;
*sector += CLUS_TO_SECT(fsi, clu);
return 0;
} /* end of find_location */
DENTRY_T *get_dentry_in_dir(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u64 *sector)
{
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
u32 dentries_per_page = PAGE_SIZE >> DENTRY_SIZE_BITS;
s32 off;
u64 sec;
u8 *buf;
if (p_dir->dir == DIR_DELETED) {
EMSG("%s : abnormal access to deleted dentry\n", __func__);
BUG_ON(!fsi->prev_eio);
return NULL;
}
if (find_location(sb, p_dir, entry, &sec, &off))
return NULL;
/* DIRECTORY READAHEAD :
* Try to read ahead per a page except root directory of fat12/16
*/
if ((!IS_CLUS_FREE(p_dir->dir)) &&
!(entry & (dentries_per_page - 1)))
dcache_readahead(sb, sec);
buf = dcache_getblk(sb, sec);
if (!buf)
return NULL;
if (sector)
*sector = sec;
return (DENTRY_T *)(buf + off);
} /* end of get_dentry_in_dir */
/* used only in search empty_slot() */
#define CNT_UNUSED_NOHIT (-1)
#define CNT_UNUSED_HIT (-2)
/* search EMPTY CONTINUOUS "num_entries" entries */
static s32 search_empty_slot(struct super_block *sb, HINT_FEMP_T *hint_femp, CHAIN_T *p_dir, s32 num_entries)
{
s32 i, dentry, num_empty = 0;
s32 dentries_per_clu;
u32 type;
CHAIN_T clu;
DENTRY_T *ep;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
if (IS_CLUS_FREE(p_dir->dir)) /* FAT16 root_dir */
dentries_per_clu = fsi->dentries_in_root;
else
dentries_per_clu = fsi->dentries_per_clu;
ASSERT(-1 <= hint_femp->eidx);
if (hint_femp->eidx != -1) {
clu.dir = hint_femp->cur.dir;
clu.size = hint_femp->cur.size;
clu.flags = hint_femp->cur.flags;
dentry = hint_femp->eidx;
if (num_entries <= hint_femp->count) {
MMSG("%s: empty slot(HIT) - found "
"(clu : 0x%08x eidx : %d)\n",
__func__, hint_femp->cur.dir, hint_femp->eidx);
hint_femp->eidx = -1;
if (fsi->vol_type == EXFAT)
return dentry;
return dentry + (num_entries - 1);
}
MMSG("%s: empty slot(HIT) - search from "
"(clu : 0x%08x eidx : %d)\n",
__func__, hint_femp->cur.dir, hint_femp->eidx);
} else {
MMSG("%s: empty slot(MISS) - search from "
"(clu:0x%08x eidx : 0)\n",
__func__, p_dir->dir);
clu.dir = p_dir->dir;
clu.size = p_dir->size;
clu.flags = p_dir->flags;
dentry = 0;
}
while (!IS_CLUS_EOF(clu.dir)) {
/* FAT16 root_dir */
if (IS_CLUS_FREE(p_dir->dir))
i = dentry % dentries_per_clu;
else
i = dentry & (dentries_per_clu-1);
for ( ; i < dentries_per_clu; i++, dentry++) {
ep = get_dentry_in_dir(sb, &clu, i, NULL);
if (!ep)
return -EIO;
type = fsi->fs_func->get_entry_type(ep);
if ((type == TYPE_UNUSED) || (type == TYPE_DELETED)) {
num_empty++;
if (hint_femp->eidx == -1) {
hint_femp->eidx = dentry;
hint_femp->count = CNT_UNUSED_NOHIT;
hint_femp->cur.dir = clu.dir;
hint_femp->cur.size = clu.size;
hint_femp->cur.flags = clu.flags;
}
if ((type == TYPE_UNUSED) &&
(hint_femp->count != CNT_UNUSED_HIT)) {
hint_femp->count = CNT_UNUSED_HIT;
}
} else {
if ((hint_femp->eidx != -1) &&
(hint_femp->count == CNT_UNUSED_HIT)) {
/* unused empty group means
* an empty group which includes
* unused dentry
*/
sdfat_fs_error(sb,
"found bogus dentry(%d) "
"beyond unused empty group(%d) "
"(start_clu : %u, cur_clu : %u)",
dentry, hint_femp->eidx, p_dir->dir,
clu.dir);
return -EIO;
}
num_empty = 0;
hint_femp->eidx = -1;
}
if (num_empty >= num_entries) {
/* found and invalidate hint_femp */
hint_femp->eidx = -1;
if (fsi->vol_type == EXFAT)
return (dentry - (num_entries-1));
return dentry;
}
}
if (IS_CLUS_FREE(p_dir->dir))
break; /* FAT16 root_dir */
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;
}
}
return -ENOSPC;
} /* end of search_empty_slot */
/* find empty directory entry.
* if there isn't any empty slot, expand cluster chain.
*/
static s32 find_empty_entry(struct inode *inode, CHAIN_T *p_dir, s32 num_entries)
{
s32 dentry;
u32 ret, last_clu;
u64 sector;
u64 size = 0;
CHAIN_T clu;
DENTRY_T *ep = NULL;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
FILE_ID_T *fid = &(SDFAT_I(inode)->fid);
HINT_FEMP_T hint_femp;
hint_femp.eidx = -1;
ASSERT(-1 <= fid->hint_femp.eidx);
if (fid->hint_femp.eidx != -1) {
memcpy(&hint_femp, &fid->hint_femp, sizeof(HINT_FEMP_T));
fid->hint_femp.eidx = -1;
}
/* FAT16 root_dir */
if (IS_CLUS_FREE(p_dir->dir))
return search_empty_slot(sb, &hint_femp, p_dir, num_entries);
while ((dentry = search_empty_slot(sb, &hint_femp, p_dir, num_entries)) < 0) {
if (dentry == -EIO)
break;
if (fsi->fs_func->check_max_dentries(fid))
return -ENOSPC;
/* we trust p_dir->size regardless of FAT type */
if (__find_last_cluster(sb, p_dir, &last_clu))
return -EIO;
/*
* Allocate new cluster to this directory
*/
clu.dir = last_clu + 1;
clu.size = 0; /* UNUSED */
clu.flags = p_dir->flags;
/* (0) check if there are reserved clusters
* (create_dir 의 주석 참고)
*/
if (!IS_CLUS_EOF(fsi->used_clusters) &&
((fsi->used_clusters + fsi->reserved_clusters) >= (fsi->num_clusters - 2)))
return -ENOSPC;
/* (1) allocate a cluster */
ret = fsi->fs_func->alloc_cluster(sb, 1, &clu, ALLOC_HOT);
if (ret)
return ret;
if (__clear_cluster(inode, clu.dir))
return -EIO;
/* (2) append to the FAT chain */
if (clu.flags != p_dir->flags) {
/* no-fat-chain bit is disabled,
* so fat-chain should be synced with alloc-bmp
*/
chain_cont_cluster(sb, p_dir->dir, p_dir->size);
p_dir->flags = 0x01;
hint_femp.cur.flags = 0x01;
}
if (clu.flags == 0x01)
if (fat_ent_set(sb, last_clu, clu.dir))
return -EIO;
if (hint_femp.eidx == -1) {
/* the special case that new dentry
* should be allocated from the start of new cluster
*/
hint_femp.eidx = (s32)(p_dir->size <<
(fsi->cluster_size_bits - DENTRY_SIZE_BITS));
hint_femp.count = fsi->dentries_per_clu;
hint_femp.cur.dir = clu.dir;
hint_femp.cur.size = 0;
hint_femp.cur.flags = clu.flags;
}
hint_femp.cur.size++;
p_dir->size++;
size = (p_dir->size << fsi->cluster_size_bits);
/* (3) update the directory entry */
if ((fsi->vol_type == EXFAT) && (p_dir->dir != fsi->root_dir)) {
ep = get_dentry_in_dir(sb,
&(fid->dir), fid->entry+1, &sector);
if (!ep)
return -EIO;
fsi->fs_func->set_entry_size(ep, size);
fsi->fs_func->set_entry_flag(ep, p_dir->flags);
if (dcache_modify(sb, sector))
return -EIO;
if (update_dir_chksum(sb, &(fid->dir), fid->entry))
return -EIO;
}
/* directory inode should be updated in here */
i_size_write(inode, (loff_t)size);
SDFAT_I(inode)->i_size_ondisk += fsi->cluster_size;
SDFAT_I(inode)->i_size_aligned += fsi->cluster_size;
SDFAT_I(inode)->fid.size = size;
SDFAT_I(inode)->fid.flags = p_dir->flags;
inode->i_blocks += 1 << (fsi->cluster_size_bits - sb->s_blocksize_bits);
}
return dentry;
} /* end of find_empty_entry */
#define SDFAT_MIN_SUBDIR (2)
static const char *dot_name[SDFAT_MIN_SUBDIR] = { DOS_CUR_DIR_NAME, DOS_PAR_DIR_NAME };
static s32 __count_dos_name_entries(struct super_block *sb, CHAIN_T *p_dir, u32 type, u32 *dotcnt)
{
s32 i, count = 0, check_dot = 0;
s32 dentries_per_clu;
u32 entry_type;
CHAIN_T clu;
DENTRY_T *ep;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
if (IS_CLUS_FREE(p_dir->dir)) /* FAT16 root_dir */
dentries_per_clu = fsi->dentries_in_root;
else
dentries_per_clu = fsi->dentries_per_clu;
clu.dir = p_dir->dir;
clu.size = p_dir->size;
clu.flags = p_dir->flags;
if (dotcnt) {
*dotcnt = 0;
if (fsi->vol_type != EXFAT)
check_dot = 1;
}
while (!IS_CLUS_EOF(clu.dir)) {
for (i = 0; i < dentries_per_clu; i++) {
ep = get_dentry_in_dir(sb, &clu, i, NULL);
if (!ep)
return -EIO;
entry_type = fsi->fs_func->get_entry_type(ep);
if (entry_type == TYPE_UNUSED)
return count;
if (!(type & TYPE_CRITICAL_PRI) && !(type & TYPE_BENIGN_PRI))
continue;
if ((type != TYPE_ALL) && (type != entry_type))
continue;
count++;
if (check_dot && (i < SDFAT_MIN_SUBDIR)) {
BUG_ON(fsi->vol_type == EXFAT);
/* 11 is DOS_NAME_LENGTH */
if (!strncmp(ep->dummy, dot_name[i], 11))
(*dotcnt)++;
}
}
/* FAT16 root_dir */
if (IS_CLUS_FREE(p_dir->dir))
break;
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;
}
check_dot = 0;
}
return count;
}
s32 check_dir_empty(struct super_block *sb, CHAIN_T *p_dir)
{
s32 i, count = 0;
s32 dentries_per_clu;
u32 type;
CHAIN_T clu;
DENTRY_T *ep;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
if (IS_CLUS_FREE(p_dir->dir)) /* FAT16 root_dir */
dentries_per_clu = fsi->dentries_in_root;
else
dentries_per_clu = fsi->dentries_per_clu;
clu.dir = p_dir->dir;
clu.size = p_dir->size;
clu.flags = p_dir->flags;
while (!IS_CLUS_EOF(clu.dir)) {
for (i = 0; i < dentries_per_clu; i++) {
ep = get_dentry_in_dir(sb, &clu, i, NULL);
if (!ep)
return -EIO;
type = fsi->fs_func->get_entry_type(ep);
if (type == TYPE_UNUSED)
return 0;
if ((type != TYPE_FILE) && (type != TYPE_DIR))
continue;
/* FAT16 root_dir */
if (IS_CLUS_FREE(p_dir->dir))
return -ENOTEMPTY;
if (fsi->vol_type == EXFAT)
return -ENOTEMPTY;
if ((p_dir->dir == fsi->root_dir) || (++count > 2))
return -ENOTEMPTY;
}
/* FAT16 root_dir */
if (IS_CLUS_FREE(p_dir->dir))
return -ENOTEMPTY;
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;
}
}
return 0;
}
/*
* Name Conversion Functions
*/
#ifdef CONFIG_SDFAT_ALLOW_LOOKUP_LOSSY_SFN
/* over name length only */
#define NEED_INVALIDATE_SFN(x) ((x) & NLS_NAME_OVERLEN)
#else
/* all lossy case */
#define NEED_INVALIDATE_SFN(x) (x)
#endif
/* NOTE :
* We should keep shortname code compatible with v1.0.15 or lower
* So, we try to check ext-only-name at create-mode only.
*
* i.e. '.mtp' ->
* v1.0.15 : ' MTP' with name_case, 0x10
* v1.1.0 : 'MT????~?' with name_case, 0x00 and longname.
*/
static inline void preprocess_ext_only_sfn(s32 lookup, u16 first_char, DOS_NAME_T *p_dosname, s32 *lossy)
{
#ifdef CONFIG_SDFAT_RESTRICT_EXT_ONLY_SFN
int i;
/* check ext-only-name at create-mode */
if (*lossy || lookup || (first_char != (u16)'.'))
return;
p_dosname->name_case = 0xFF;
/* move ext-name to base-name */
for (i = 0; i < 3; i++) {
p_dosname->name[i] = p_dosname->name[8+i];
if (p_dosname->name[i] == ' ')
p_dosname->name[i] = '_';
}
/* fill remained space with '_' */
for (i = 3; i < 8; i++)
p_dosname->name[i] = '_';
/* eliminate ext-name */
for (i = 8; i < 11; i++)
p_dosname->name[i] = ' ';
*lossy = NLS_NAME_LOSSY;
#endif /* CONFIG_SDFAT_CAN_CREATE_EXT_ONLY_SFN */
}
/* input : dir, uni_name
* output : num_of_entry, dos_name(format : aaaaaa~1.bbb)
*/
static s32 get_num_entries_and_dos_name(struct super_block *sb, CHAIN_T *p_dir,
UNI_NAME_T *p_uniname, s32 *entries,
DOS_NAME_T *p_dosname, s32 lookup)
{
s32 ret, num_entries, lossy = NLS_NAME_NO_LOSSY;
s8 **r;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
/* Init null char. */
p_dosname->name[0] = '\0';
num_entries = fsi->fs_func->calc_num_entries(p_uniname);
if (num_entries == 0)
return -EINVAL;
if (fsi->vol_type == EXFAT)
goto out;
nls_uni16s_to_sfn(sb, p_uniname, p_dosname, &lossy);
preprocess_ext_only_sfn(lookup, p_uniname->name[0], p_dosname, &lossy);
if (!lossy) {
for (r = reserved_names; *r; r++) {
if (!strncmp((void *) p_dosname->name, *r, 8))
return -EINVAL;
}
if (p_dosname->name_case != 0xFF)
num_entries = 1;
} else if (!lookup) {
/* create new dos name */
ret = fat_generate_dos_name_new(sb, p_dir, p_dosname,
num_entries);
if (ret)
return ret;
} else if (NEED_INVALIDATE_SFN(lossy)) {
/* FIXME : We should check num_entries */
p_dosname->name[0] = '\0';
}
if (num_entries > 1)
p_dosname->name_case = 0x0;
out:
*entries = num_entries;
return 0;
} /* end of get_num_entries_and_dos_name */
void get_uniname_from_dos_entry(struct super_block *sb, DOS_DENTRY_T *ep, UNI_NAME_T *p_uniname, u8 mode)
{
DOS_NAME_T dos_name;
if (mode == 0x0)
dos_name.name_case = 0x0;
else
dos_name.name_case = ep->lcase;
memcpy(dos_name.name, ep->name, DOS_NAME_LENGTH);
nls_sfn_to_uni16s(sb, &dos_name, p_uniname);
} /* end of get_uniname_from_dos_entry */
/* returns the length of a struct qstr, ignoring trailing dots */
static inline unsigned int __striptail_len(unsigned int len, const char *name)
{
while (len && name[len - 1] == '.')
len--;
return len;
}
/*
* Name Resolution Functions :
* Zero if it was successful; otherwise nonzero.
*/
static s32 __resolve_path(struct inode *inode, const u8 *path, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, int lookup)
{
s32 namelen;
s32 lossy = NLS_NAME_NO_LOSSY;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
FILE_ID_T *fid = &(SDFAT_I(inode)->fid);
/* DOT and DOTDOT are handled by VFS layer */
/* strip all trailing spaces */
/* DO NOTHING : Is needed? */
/* strip all trailing periods */
namelen = __striptail_len(strlen(path), path);
if (!namelen)
return -ENOENT;
/* the limitation of linux? */
if (strlen(path) > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE))
return -ENAMETOOLONG;
/*
* strip all leading spaces :
* "MS windows 7" supports leading spaces.
* So we should skip this preprocessing for compatibility.
*/
/* file name conversion :
* If lookup case, we allow bad-name for compatibility.
*/
namelen = nls_vfsname_to_uni16s(sb, path, namelen, p_uniname, &lossy);
if (namelen < 0)
return namelen; /* return error value */
if ((lossy && !lookup) || !namelen)
return -EINVAL;
sdfat_debug_bug_on(fid->size != i_size_read(inode));
// fid->size = i_size_read(inode);
p_dir->dir = fid->start_clu;
p_dir->size = (u32)(fid->size >> fsi->cluster_size_bits);
p_dir->flags = fid->flags;
return 0;
}
static inline s32 resolve_path(struct inode *inode, const u8 *path, CHAIN_T *dir, UNI_NAME_T *uni)
{
return __resolve_path(inode, path, dir, uni, 0);
}
static inline s32 resolve_path_for_lookup(struct inode *inode, const u8 *path, CHAIN_T *dir, UNI_NAME_T *uni)
{
return __resolve_path(inode, path, dir, uni, 1);
}
static s32 create_dir(struct inode *inode, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, FILE_ID_T *fid)
{
s32 dentry, num_entries;
u64 ret;
u64 size;
CHAIN_T clu;
DOS_NAME_T dos_name, dot_name;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
ret = get_num_entries_and_dos_name(sb, p_dir, p_uniname, &num_entries, &dos_name, 0);
if (ret)
return ret;
/* find_empty_entry must be called before alloc_cluster */
dentry = find_empty_entry(inode, p_dir, num_entries);
if (dentry < 0)
return dentry; /* -EIO or -ENOSPC */
clu.dir = CLUS_EOF;
clu.size = 0;
clu.flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01;
/* (0) Check if there are reserved clusters up to max. */
if ((fsi->used_clusters + fsi->reserved_clusters) >= (fsi->num_clusters - CLUS_BASE))
return -ENOSPC;
/* (1) allocate a cluster */
ret = fsi->fs_func->alloc_cluster(sb, 1, &clu, ALLOC_HOT);
if (ret)
return ret;
ret = __clear_cluster(inode, clu.dir);
if (ret)
return ret;
size = fsi->cluster_size;
if (fsi->vol_type != EXFAT) {
/* initialize the . and .. entry
* Information for . points to itself
* Information for .. points to parent dir
*/
dot_name.name_case = 0x0;
memcpy(dot_name.name, DOS_CUR_DIR_NAME, DOS_NAME_LENGTH);
ret = fsi->fs_func->init_dir_entry(sb, &clu, 0, TYPE_DIR, clu.dir, 0);
if (ret)
return ret;
ret = fsi->fs_func->init_ext_entry(sb, &clu, 0, 1, NULL, &dot_name);
if (ret)
return ret;
memcpy(dot_name.name, DOS_PAR_DIR_NAME, DOS_NAME_LENGTH);
if (p_dir->dir == fsi->root_dir)
ret = fsi->fs_func->init_dir_entry(sb, &clu, 1, TYPE_DIR, CLUS_FREE, 0);
else
ret = fsi->fs_func->init_dir_entry(sb, &clu, 1, TYPE_DIR, p_dir->dir, 0);
if (ret)
return ret;
ret = fsi->fs_func->init_ext_entry(sb, &clu, 1, 1, NULL, &dot_name);
if (ret)
return ret;
}
/* (2) update the directory entry */
/* make sub-dir entry in parent directory */
ret = fsi->fs_func->init_dir_entry(sb, p_dir, dentry, TYPE_DIR, clu.dir, size);
if (ret)
return ret;
ret = fsi->fs_func->init_ext_entry(sb, p_dir, dentry, num_entries, p_uniname, &dos_name);
if (ret)
return ret;
fid->dir.dir = p_dir->dir;
fid->dir.size = p_dir->size;
fid->dir.flags = p_dir->flags;
fid->entry = dentry;
fid->attr = ATTR_SUBDIR;
fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01;
fid->size = size;
fid->start_clu = clu.dir;
fid->type = TYPE_DIR;
fid->rwoffset = 0;
fid->hint_bmap.off = CLUS_EOF;
/* hint_stat will be used if this is directory. */
fid->version = 0;
fid->hint_stat.eidx = 0;
fid->hint_stat.clu = fid->start_clu;
fid->hint_femp.eidx = -1;
return 0;
} /* end of create_dir */
static s32 create_file(struct inode *inode, CHAIN_T *p_dir, UNI_NAME_T *p_uniname, u8 mode, FILE_ID_T *fid)
{
s32 ret, dentry, num_entries;
DOS_NAME_T dos_name;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
ret = get_num_entries_and_dos_name(sb, p_dir, p_uniname, &num_entries, &dos_name, 0);
if (ret)
return ret;
/* find_empty_entry must be called before alloc_cluster() */
dentry = find_empty_entry(inode, p_dir, num_entries);
if (dentry < 0)
return dentry; /* -EIO or -ENOSPC */
/* (1) update the directory entry */
/* fill the dos name directory entry information of the created file.
* the first cluster is not determined yet. (0)
*/
ret = fsi->fs_func->init_dir_entry(sb, p_dir, dentry, TYPE_FILE | mode, CLUS_FREE, 0);
if (ret)
return ret;
ret = fsi->fs_func->init_ext_entry(sb, p_dir, dentry, num_entries, p_uniname, &dos_name);
if (ret)
return ret;
fid->dir.dir = p_dir->dir;
fid->dir.size = p_dir->size;
fid->dir.flags = p_dir->flags;
fid->entry = dentry;
fid->attr = ATTR_ARCHIVE | mode;
fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01;
fid->size = 0;
fid->start_clu = CLUS_EOF;
fid->type = TYPE_FILE;
fid->rwoffset = 0;
fid->hint_bmap.off = CLUS_EOF;
/* hint_stat will be used if this is directory. */
fid->version = 0;
fid->hint_stat.eidx = 0;
fid->hint_stat.clu = fid->start_clu;
fid->hint_femp.eidx = -1;
return 0;
} /* end of create_file */
static s32 remove_file(struct inode *inode, CHAIN_T *p_dir, s32 entry)
{
s32 num_entries;
u64 sector;
DENTRY_T *ep;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
ep = get_dentry_in_dir(sb, p_dir, entry, &sector);
if (!ep)
return -EIO;
dcache_lock(sb, sector);
/* dcache_lock() before call count_ext_entries() */
num_entries = fsi->fs_func->count_ext_entries(sb, p_dir, entry, ep);
if (num_entries < 0) {
dcache_unlock(sb, sector);
return -EIO;
}
num_entries++;
dcache_unlock(sb, sector);
/* (1) update the directory entry */
return fsi->fs_func->delete_dir_entry(sb, p_dir, entry, 0, num_entries);
} /* end of remove_file */
static s32 rename_file(struct inode *inode, CHAIN_T *p_dir, s32 oldentry, UNI_NAME_T *p_uniname, FILE_ID_T *fid)
{
s32 ret, newentry = -1, num_old_entries, num_new_entries;
u64 sector_old, sector_new;
DOS_NAME_T dos_name;
DENTRY_T *epold, *epnew;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
epold = get_dentry_in_dir(sb, p_dir, oldentry, &sector_old);
if (!epold)
return -EIO;
dcache_lock(sb, sector_old);
/* dcache_lock() before call count_ext_entries() */
num_old_entries = fsi->fs_func->count_ext_entries(sb, p_dir, oldentry, epold);
if (num_old_entries < 0) {
dcache_unlock(sb, sector_old);
return -EIO;
}
num_old_entries++;
ret = get_num_entries_and_dos_name(sb, p_dir, p_uniname, &num_new_entries, &dos_name, 0);
if (ret) {
dcache_unlock(sb, sector_old);
return ret;
}
if (num_old_entries < num_new_entries) {
newentry = find_empty_entry(inode, p_dir, num_new_entries);
if (newentry < 0) {
dcache_unlock(sb, sector_old);
return newentry; /* -EIO or -ENOSPC */
}
epnew = get_dentry_in_dir(sb, p_dir, newentry, &sector_new);
if (!epnew) {
dcache_unlock(sb, sector_old);
return -EIO;
}
memcpy((void *) epnew, (void *) epold, DENTRY_SIZE);
if (fsi->fs_func->get_entry_type(epnew) == TYPE_FILE) {
fsi->fs_func->set_entry_attr(epnew, fsi->fs_func->get_entry_attr(epnew) | ATTR_ARCHIVE);
fid->attr |= ATTR_ARCHIVE;
}
dcache_modify(sb, sector_new);
dcache_unlock(sb, sector_old);
if (fsi->vol_type == EXFAT) {
epold = get_dentry_in_dir(sb, p_dir, oldentry+1, &sector_old);
dcache_lock(sb, sector_old);
epnew = get_dentry_in_dir(sb, p_dir, newentry+1, &sector_new);
if (!epold || !epnew) {
dcache_unlock(sb, sector_old);
return -EIO;
}
memcpy((void *) epnew, (void *) epold, DENTRY_SIZE);
dcache_modify(sb, sector_new);
dcache_unlock(sb, sector_old);
}
ret = fsi->fs_func->init_ext_entry(sb, p_dir, newentry, num_new_entries, p_uniname, &dos_name);
if (ret)
return ret;
fsi->fs_func->delete_dir_entry(sb, p_dir, oldentry, 0, num_old_entries);
fid->entry = newentry;
} else {
if (fsi->fs_func->get_entry_type(epold) == TYPE_FILE) {
fsi->fs_func->set_entry_attr(epold, fsi->fs_func->get_entry_attr(epold) | ATTR_ARCHIVE);
fid->attr |= ATTR_ARCHIVE;
}
dcache_modify(sb, sector_old);
dcache_unlock(sb, sector_old);
ret = fsi->fs_func->init_ext_entry(sb, p_dir, oldentry, num_new_entries, p_uniname, &dos_name);
if (ret)
return ret;
fsi->fs_func->delete_dir_entry(sb, p_dir, oldentry, num_new_entries, num_old_entries);
}
return 0;
} /* end of rename_file */
static s32 move_file(struct inode *inode, CHAIN_T *p_olddir, s32 oldentry,
CHAIN_T *p_newdir, UNI_NAME_T *p_uniname, FILE_ID_T *fid)
{
s32 ret, newentry, num_new_entries, num_old_entries;
u64 sector_mov, sector_new;
CHAIN_T clu;
DOS_NAME_T dos_name;
DENTRY_T *epmov, *epnew;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
epmov = get_dentry_in_dir(sb, p_olddir, oldentry, &sector_mov);
if (!epmov)
return -EIO;
/* check if the source and target directory is the same */
if (fsi->fs_func->get_entry_type(epmov) == TYPE_DIR &&
fsi->fs_func->get_entry_clu0(epmov) == p_newdir->dir)
return -EINVAL;
dcache_lock(sb, sector_mov);
/* dcache_lock() before call count_ext_entries() */
num_old_entries = fsi->fs_func->count_ext_entries(sb, p_olddir, oldentry, epmov);
if (num_old_entries < 0) {
dcache_unlock(sb, sector_mov);
return -EIO;
}
num_old_entries++;
ret = get_num_entries_and_dos_name(sb, p_newdir, p_uniname, &num_new_entries, &dos_name, 0);
if (ret) {
dcache_unlock(sb, sector_mov);
return ret;
}
newentry = find_empty_entry(inode, p_newdir, num_new_entries);
if (newentry < 0) {
dcache_unlock(sb, sector_mov);
return newentry; /* -EIO or -ENOSPC */
}
epnew = get_dentry_in_dir(sb, p_newdir, newentry, &sector_new);
if (!epnew) {
dcache_unlock(sb, sector_mov);
return -EIO;
}
memcpy((void *) epnew, (void *) epmov, DENTRY_SIZE);
if (fsi->fs_func->get_entry_type(epnew) == TYPE_FILE) {
fsi->fs_func->set_entry_attr(epnew, fsi->fs_func->get_entry_attr(epnew) | ATTR_ARCHIVE);
fid->attr |= ATTR_ARCHIVE;
}
dcache_modify(sb, sector_new);
dcache_unlock(sb, sector_mov);
if (fsi->vol_type == EXFAT) {
epmov = get_dentry_in_dir(sb, p_olddir, oldentry+1, &sector_mov);
dcache_lock(sb, sector_mov);
epnew = get_dentry_in_dir(sb, p_newdir, newentry+1, &sector_new);
if (!epmov || !epnew) {
dcache_unlock(sb, sector_mov);
return -EIO;
}
memcpy((void *) epnew, (void *) epmov, DENTRY_SIZE);
dcache_modify(sb, sector_new);
dcache_unlock(sb, sector_mov);
} else if (fsi->fs_func->get_entry_type(epnew) == TYPE_DIR) {
/* change ".." pointer to new parent dir */
clu.dir = fsi->fs_func->get_entry_clu0(epnew);
clu.flags = 0x01;
epnew = get_dentry_in_dir(sb, &clu, 1, &sector_new);
if (!epnew)
return -EIO;
if (p_newdir->dir == fsi->root_dir)
fsi->fs_func->set_entry_clu0(epnew, CLUS_FREE);
else
fsi->fs_func->set_entry_clu0(epnew, p_newdir->dir);
dcache_modify(sb, sector_new);
}
ret = fsi->fs_func->init_ext_entry(sb, p_newdir, newentry, num_new_entries, p_uniname, &dos_name);
if (ret)
return ret;
fsi->fs_func->delete_dir_entry(sb, p_olddir, oldentry, 0, num_old_entries);
fid->dir.dir = p_newdir->dir;
fid->dir.size = p_newdir->size;
fid->dir.flags = p_newdir->flags;
fid->entry = newentry;
return 0;
} /* end of move_file */
/*======================================================================*/
/* Global Function Definitions */
/*======================================================================*/
/* roll back to the initial state of the file system */
s32 fscore_init(void)
{
s32 ret;
ret = check_type_size();
if (ret)
return ret;
return extent_cache_init();
}
/* make free all memory-alloced global buffers */
s32 fscore_shutdown(void)
{
extent_cache_shutdown();
return 0;
}
/* check device is ejected */
s32 fscore_check_bdi_valid(struct super_block *sb)
{
return bdev_check_bdi_valid(sb);
}
static bool is_exfat(pbr_t *pbr)
{
int i = 53;
do {
if (pbr->bpb.f64.res_zero[i-1])
break;
} while (--i);
return i ? false : true;
}
static bool is_fat32(pbr_t *pbr)
{
if (le16_to_cpu(pbr->bpb.fat.num_fat_sectors))
return false;
return true;
}
inline pbr_t *read_pbr_with_logical_sector(struct super_block *sb, struct buffer_head **prev_bh)
{
pbr_t *p_pbr = (pbr_t *) (*prev_bh)->b_data;
u16 logical_sect = 0;
if (is_exfat(p_pbr))
logical_sect = 1 << p_pbr->bsx.f64.sect_size_bits;
else
logical_sect = get_unaligned_le16(&p_pbr->bpb.fat.sect_size);
/* is x a power of 2?
* (x) != 0 && (((x) & ((x) - 1)) == 0)
*/
if (!is_power_of_2(logical_sect)
|| (logical_sect < 512)
|| (logical_sect > 4096)) {
sdfat_log_msg(sb, KERN_ERR, "bogus logical sector size %u",
logical_sect);
return NULL;
}
if (logical_sect < sb->s_blocksize) {
sdfat_log_msg(sb, KERN_ERR,
"logical sector size too small for device"
" (logical sector size = %u)", logical_sect);
return NULL;
}
if (logical_sect > sb->s_blocksize) {
struct buffer_head *bh = NULL;
__brelse(*prev_bh);
*prev_bh = NULL;
if (!sb_set_blocksize(sb, logical_sect)) {
sdfat_log_msg(sb, KERN_ERR,
"unable to set blocksize %u", logical_sect);
return NULL;
}
bh = sb_bread(sb, 0);
if (!bh) {
sdfat_log_msg(sb, KERN_ERR,
"unable to read boot sector "
"(logical sector size = %lu)", sb->s_blocksize);
return NULL;
}
*prev_bh = bh;
p_pbr = (pbr_t *) bh->b_data;
}
sdfat_log_msg(sb, KERN_INFO,
"set logical sector size : %lu", sb->s_blocksize);
return p_pbr;
}
/* mount the file system volume */
s32 fscore_mount(struct super_block *sb)
{
s32 ret;
pbr_t *p_pbr;
struct buffer_head *tmp_bh = NULL;
struct gendisk *disk = sb->s_bdev->bd_disk;
struct sdfat_mount_options *opts = &(SDFAT_SB(sb)->options);
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
/* initialize previous I/O error */
fsi->prev_eio = 0;
/* open the block device */
if (bdev_open_dev(sb))
return -EIO;
/* set block size to read super block */
sb_min_blocksize(sb, 512);
/* read boot sector */
ret = read_sect(sb, 0, &tmp_bh, 1);
if (ret) {
sdfat_log_msg(sb, KERN_ERR, "unable to read boot sector");
ret = -EIO;
goto bd_close;
}
/* PRB is read */
p_pbr = (pbr_t *) tmp_bh->b_data;
/* check the validity of PBR */
if (le16_to_cpu((p_pbr->signature)) != PBR_SIGNATURE) {
sdfat_log_msg(sb, KERN_ERR, "invalid boot record signature");
brelse(tmp_bh);
ret = -EINVAL;
goto bd_close;
}
/* check logical sector size */
p_pbr = read_pbr_with_logical_sector(sb, &tmp_bh);
if (!p_pbr) {
brelse(tmp_bh);
ret = -EIO;
goto bd_close;
}
/* fill fs_struct */
if (is_exfat(p_pbr)) {
if (opts->fs_type && opts->fs_type != FS_TYPE_EXFAT) {
sdfat_log_msg(sb, KERN_ERR,
"not specified filesystem type "
"(media:exfat, opts:%s)",
FS_TYPE_STR[opts->fs_type]);
ret = -EINVAL;
goto free_bh;
}
/* set maximum file size for exFAT */
sb->s_maxbytes = 0x7fffffffffffffffLL;
opts->improved_allocation = 0;
opts->defrag = 0;
ret = mount_exfat(sb, p_pbr);
} else {
if (opts->fs_type && opts->fs_type != FS_TYPE_VFAT) {
sdfat_log_msg(sb, KERN_ERR,
"not specified filesystem type "
"(media:vfat, opts:%s)",
FS_TYPE_STR[opts->fs_type]);
ret = -EINVAL;
goto free_bh;
}
/* set maximum file size for FAT */
sb->s_maxbytes = 0xffffffff;
if (is_fat32(p_pbr)) {
ret = mount_fat32(sb, p_pbr);
} else {
opts->improved_allocation = 0;
opts->defrag = 0;
ret = mount_fat16(sb, p_pbr);
}
}
free_bh:
brelse(tmp_bh);
if (ret) {
sdfat_log_msg(sb, KERN_ERR, "failed to mount fs-core");
goto bd_close;
}
/* warn misaligned data data start sector must be a multiple of clu_size */
sdfat_log_msg(sb, KERN_INFO,
"detected volume info : %s "
"(%04hX-%04hX, bps : %lu, spc : %u, data start : %llu, %s)",
sdfat_get_vol_type_str(fsi->vol_type),
(fsi->vol_id >> 16) & 0xffff, fsi->vol_id & 0xffff,
sb->s_blocksize, fsi->sect_per_clus, fsi->data_start_sector,
(fsi->data_start_sector & (fsi->sect_per_clus - 1)) ?
"misaligned" : "aligned");
sdfat_log_msg(sb, KERN_INFO,
"detected volume size : %llu KB (disk : %llu KB, "
"part : %llu KB)",
(fsi->num_sectors * (sb->s_blocksize >> SECTOR_SIZE_BITS)) >> 1,
disk ? (u64)(get_capacity(disk) >> 1) : 0,
(u64)bdev_nr_sectors(sb->s_bdev) >> 1);
ret = load_upcase_table(sb);
if (ret) {
sdfat_log_msg(sb, KERN_ERR, "failed to load upcase table");
goto bd_close;
}
if (fsi->vol_type != EXFAT)
goto update_used_clus;
/* allocate-bitmap is only for exFAT */
ret = load_alloc_bmp(sb);
if (ret) {
sdfat_log_msg(sb, KERN_ERR, "failed to load alloc-bitmap");
goto free_upcase;
}
update_used_clus:
if (fsi->used_clusters == (u32) ~0) {
ret = fsi->fs_func->count_used_clusters(sb, &fsi->used_clusters);
if (ret) {
sdfat_log_msg(sb, KERN_ERR, "failed to scan clusters");
goto free_alloc_bmp;
}
}
return 0;
free_alloc_bmp:
if (fsi->vol_type == EXFAT)
free_alloc_bmp(sb);
free_upcase:
free_upcase_table(sb);
bd_close:
bdev_close_dev(sb);
return ret;
} /* end of fscore_mount */
/* umount the file system volume */
s32 fscore_umount(struct super_block *sb)
{
s32 ret = 0;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
if (fs_sync(sb, 0))
ret = -EIO;
if (fs_set_vol_flags(sb, VOL_CLEAN))
ret = -EIO;
free_upcase_table(sb);
if (fsi->vol_type == EXFAT)
free_alloc_bmp(sb);
if (fcache_release_all(sb))
ret = -EIO;
if (dcache_release_all(sb))
ret = -EIO;
amap_destroy(sb);
if (fsi->prev_eio)
ret = -EIO;
/* close the block device */
bdev_close_dev(sb);
return ret;
}
/* get the information of a file system volume */
s32 fscore_statfs(struct super_block *sb, VOL_INFO_T *info)
{
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
if (fsi->used_clusters == (u32) ~0) {
if (fsi->fs_func->count_used_clusters(sb, &fsi->used_clusters))
return -EIO;
}
info->FatType = fsi->vol_type;
info->ClusterSize = fsi->cluster_size;
info->NumClusters = fsi->num_clusters - 2; /* clu 0 & 1 */
info->UsedClusters = fsi->used_clusters + fsi->reserved_clusters;
info->FreeClusters = info->NumClusters - info->UsedClusters;
return 0;
}
/* synchronize all file system volumes */
s32 fscore_sync_fs(struct super_block *sb, s32 do_sync)
{
/* synchronize the file system */
if (fs_sync(sb, do_sync))
return -EIO;
if (fs_set_vol_flags(sb, VOL_CLEAN))
return -EIO;
return 0;
}
/* stat allocation unit of a file system volume */
u32 fscore_get_au_stat(struct super_block *sb, s32 mode)
{
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
if (fsi->fs_func->get_au_stat)
return fsi->fs_func->get_au_stat(sb, mode);
/* No error, just returns 0 */
return 0;
}
/*----------------------------------------------------------------------*/
/* File Operation Functions */
/*----------------------------------------------------------------------*/
/* lookup a file */
s32 fscore_lookup(struct inode *inode, u8 *path, FILE_ID_T *fid)
{
s32 ret, dentry, num_entries;
CHAIN_T dir;
UNI_NAME_T uni_name;
DOS_NAME_T dos_name;
DENTRY_T *ep, *ep2;
ENTRY_SET_CACHE_T *es = NULL;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
FILE_ID_T *dir_fid = &(SDFAT_I(inode)->fid);
TMSG("%s entered\n", __func__);
/* check the validity of directory name in the given pathname */
ret = resolve_path_for_lookup(inode, path, &dir, &uni_name);
if (ret)
return ret;
ret = get_num_entries_and_dos_name(sb, &dir, &uni_name, &num_entries, &dos_name, 1);
if (ret)
return ret;
/* check the validation of hint_stat and initialize it if required */
if (dir_fid->version != (u32)inode_peek_iversion(inode)) {
dir_fid->hint_stat.clu = dir.dir;
dir_fid->hint_stat.eidx = 0;
dir_fid->version = (u32)inode_peek_iversion(inode);
dir_fid->hint_femp.eidx = -1;
}
/* search the file name for directories */
dentry = fsi->fs_func->find_dir_entry(sb, dir_fid, &dir, &uni_name,
num_entries, &dos_name, TYPE_ALL);
if ((dentry < 0) && (dentry != -EEXIST))
return dentry; /* -error value */
fid->dir.dir = dir.dir;
fid->dir.size = dir.size;
fid->dir.flags = dir.flags;
fid->entry = dentry;
/* root directory itself */
if (unlikely(dentry == -EEXIST)) {
fid->type = TYPE_DIR;
fid->rwoffset = 0;
fid->hint_bmap.off = CLUS_EOF;
fid->attr = ATTR_SUBDIR;
fid->flags = 0x01;
fid->size = 0;
fid->start_clu = fsi->root_dir;
} else {
if (fsi->vol_type == EXFAT) {
es = get_dentry_set_in_dir(sb, &dir, dentry, ES_2_ENTRIES, &ep);
if (!es)
return -EIO;
ep2 = ep+1;
} else {
ep = get_dentry_in_dir(sb, &dir, dentry, NULL);
if (!ep)
return -EIO;
ep2 = ep;
}
fid->type = fsi->fs_func->get_entry_type(ep);
fid->rwoffset = 0;
fid->hint_bmap.off = CLUS_EOF;
fid->attr = fsi->fs_func->get_entry_attr(ep);
fid->size = fsi->fs_func->get_entry_size(ep2);
if ((fid->type == TYPE_FILE) && (fid->size == 0)) {
fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01;
fid->start_clu = CLUS_EOF;
} else {
fid->flags = fsi->fs_func->get_entry_flag(ep2);
fid->start_clu = fsi->fs_func->get_entry_clu0(ep2);
}
if ((fid->type == TYPE_DIR) && (fsi->vol_type != EXFAT)) {
u32 num_clu = 0;
CHAIN_T tmp_dir;
tmp_dir.dir = fid->start_clu;
tmp_dir.flags = fid->flags;
tmp_dir.size = 0; /* UNUSED */
if (__count_num_clusters(sb, &tmp_dir, &num_clu))
return -EIO;
fid->size = (u64)num_clu << fsi->cluster_size_bits;
}
/* FOR GRACEFUL ERROR HANDLING */
if (IS_CLUS_FREE(fid->start_clu)) {
sdfat_fs_error(sb,
"non-zero size file starts with zero cluster "
"(size : %llu, p_dir : %u, entry : 0x%08x)",
fid->size, fid->dir.dir, fid->entry);
sdfat_debug_bug_on(1);
return -EIO;
}
if (fsi->vol_type == EXFAT)
release_dentry_set(es);
}
/* hint_stat will be used if this is directory. */
fid->version = 0;
fid->hint_stat.eidx = 0;
fid->hint_stat.clu = fid->start_clu;
fid->hint_femp.eidx = -1;
TMSG("%s exited successfully\n", __func__);
return 0;
} /* end of fscore_lookup */
/* create a file */
s32 fscore_create(struct inode *inode, u8 *path, u8 mode, FILE_ID_T *fid)
{
s32 ret/*, dentry*/;
CHAIN_T dir;
UNI_NAME_T uni_name;
struct super_block *sb = inode->i_sb;
/* check the validity of directory name in the given pathname */
ret = resolve_path(inode, path, &dir, &uni_name);
if (ret)
return ret;
fs_set_vol_flags(sb, VOL_DIRTY);
/* create a new file */
ret = create_file(inode, &dir, &uni_name, mode, fid);
fs_sync(sb, 0);
fs_set_vol_flags(sb, VOL_CLEAN);
return ret;
}
/* read data from a opened file */
s32 fscore_read_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *rcount)
{
s32 ret = 0;
s32 offset, sec_offset;
u32 clu_offset;
u32 clu;
u64 logsector, oneblkread, read_bytes;
struct buffer_head *tmp_bh = NULL;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
/* check if the given file ID is opened */
if (fid->type != TYPE_FILE)
return -EPERM;
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)
*rcount = 0;
return 0;
}
read_bytes = 0;
while (count > 0) {
clu_offset = fid->rwoffset >> fsi->cluster_size_bits;
clu = fid->start_clu;
if (fid->flags == 0x03) {
clu += clu_offset;
} else {
/* hint information */
if ((clu_offset > 0) &&
((fid->hint_bmap.off != CLUS_EOF) && (fid->hint_bmap.off > 0)) &&
(clu_offset >= fid->hint_bmap.off)) {
clu_offset -= fid->hint_bmap.off;
clu = fid->hint_bmap.clu;
}
while (clu_offset > 0) {
ret = get_next_clus_safe(sb, &clu);
if (ret)
goto err_out;
clu_offset--;
}
}
/* hint information */
fid->hint_bmap.off = fid->rwoffset >> fsi->cluster_size_bits;
fid->hint_bmap.clu = clu;
offset = (s32)(fid->rwoffset & (fsi->cluster_size - 1)); /* byte offset in cluster */
sec_offset = offset >> sb->s_blocksize_bits; /* sector offset in cluster */
offset &= (sb->s_blocksize - 1); /* byte offset in sector */
logsector = CLUS_TO_SECT(fsi, clu) + sec_offset;
oneblkread = (u64)(sb->s_blocksize - offset);
if (oneblkread > count)
oneblkread = count;
if ((offset == 0) && (oneblkread == sb->s_blocksize)) {
ret = read_sect(sb, logsector, &tmp_bh, 1);
if (ret)
goto err_out;
memcpy(((s8 *) buffer)+read_bytes, ((s8 *) tmp_bh->b_data), (s32) oneblkread);
} else {
ret = read_sect(sb, logsector, &tmp_bh, 1);
if (ret)
goto err_out;
memcpy(((s8 *) buffer)+read_bytes, ((s8 *) tmp_bh->b_data)+offset, (s32) oneblkread);
}
count -= oneblkread;
read_bytes += oneblkread;
fid->rwoffset += oneblkread;
}
err_out:
brelse(tmp_bh);
/* set the size of read bytes */
if (rcount != NULL)
*rcount = read_bytes;
return ret;
} /* end of fscore_read_link */
/* write data into a opened file */
s32 fscore_write_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *wcount)
{
s32 ret = 0;
s32 modified = false, offset, sec_offset;
u32 clu_offset, num_clusters, num_alloc;
u32 clu, last_clu;
u64 logsector, sector, 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;
u32 blksize = (u32)sb->s_blocksize;
u32 blksize_mask = (u32)(sb->s_blocksize-1);
u8 blksize_bits = sb->s_blocksize_bits;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
/* check if the given file ID is opened */
if (fid->type != TYPE_FILE)
return -EPERM;
if (fid->rwoffset > fid->size)
fid->rwoffset = fid->size;
if (count == 0) {
if (wcount)
*wcount = 0;
return 0;
}
fs_set_vol_flags(sb, VOL_DIRTY);
if (fid->size == 0)
num_clusters = 0;
else
num_clusters = ((fid->size-1) >> fsi->cluster_size_bits) + 1;
write_bytes = 0;
while (count > 0) {
clu_offset = (fid->rwoffset >> fsi->cluster_size_bits);
clu = last_clu = fid->start_clu;
if (fid->flags == 0x03) {
if ((clu_offset > 0) && (!IS_CLUS_EOF(clu))) {
last_clu += clu_offset - 1;
if (clu_offset == num_clusters)
clu = CLUS_EOF;
else
clu += clu_offset;
}
} else {
/* hint information */
if ((clu_offset > 0) &&
((fid->hint_bmap.off != CLUS_EOF) && (fid->hint_bmap.off > 0)) &&
(clu_offset >= fid->hint_bmap.off)) {
clu_offset -= fid->hint_bmap.off;
clu = fid->hint_bmap.clu;
}
while ((clu_offset > 0) && (!IS_CLUS_EOF(clu))) {
last_clu = clu;
ret = get_next_clus_safe(sb, &clu);
if (ret)
goto err_out;
clu_offset--;
}
}
if (IS_CLUS_EOF(clu)) {
num_alloc = ((count-1) >> fsi->cluster_size_bits) + 1;
new_clu.dir = IS_CLUS_EOF(last_clu) ? CLUS_EOF : last_clu+1;
new_clu.size = 0;
new_clu.flags = fid->flags;
/* (1) allocate a chain of clusters */
ret = fsi->fs_func->alloc_cluster(sb, num_alloc, &new_clu, ALLOC_COLD);
if (ret)
goto err_out;
/* (2) append to the FAT chain */
if (IS_CLUS_EOF(last_clu)) {
if (new_clu.flags == 0x01)
fid->flags = 0x01;
fid->start_clu = new_clu.dir;
modified = true;
} else {
if (new_clu.flags != fid->flags) {
/* no-fat-chain bit is disabled,
* so fat-chain should be synced with
* alloc-bmp
*/
chain_cont_cluster(sb, fid->start_clu, num_clusters);
fid->flags = 0x01;
modified = true;
}
if (new_clu.flags == 0x01) {
ret = fat_ent_set(sb, last_clu, new_clu.dir);
if (ret)
goto err_out;
}
}
num_clusters += num_alloc;
clu = new_clu.dir;
}
/* hint information */
fid->hint_bmap.off = fid->rwoffset >> fsi->cluster_size_bits;
fid->hint_bmap.clu = clu;
/* byte offset in cluster */
offset = (s32)(fid->rwoffset & (fsi->cluster_size-1));
/* sector offset in cluster */
sec_offset = offset >> blksize_bits;
/* byte offset in sector */
offset &= blksize_mask;
logsector = CLUS_TO_SECT(fsi, clu) + sec_offset;
oneblkwrite = (u64)(blksize - offset);
if (oneblkwrite > count)
oneblkwrite = count;
if ((offset == 0) && (oneblkwrite == blksize)) {
ret = read_sect(sb, logsector, &tmp_bh, 0);
if (ret)
goto err_out;
memcpy(((s8 *)tmp_bh->b_data),
((s8 *)buffer)+write_bytes,
(s32)oneblkwrite);
ret = write_sect(sb, logsector, tmp_bh, 0);
if (ret) {
brelse(tmp_bh);
goto err_out;
}
} else {
if ((offset > 0) || ((fid->rwoffset+oneblkwrite) < fid->size)) {
ret = read_sect(sb, logsector, &tmp_bh, 1);
if (ret)
goto err_out;
} else {
ret = read_sect(sb, logsector, &tmp_bh, 0);
if (ret)
goto err_out;
}
memcpy(((s8 *) tmp_bh->b_data)+offset, ((s8 *) buffer)+write_bytes, (s32) oneblkwrite);
ret = write_sect(sb, logsector, tmp_bh, 0);
if (ret) {
brelse(tmp_bh);
goto err_out;
}
}
count -= oneblkwrite;
write_bytes += oneblkwrite;
fid->rwoffset += oneblkwrite;
fid->attr |= ATTR_ARCHIVE;
if (fid->size < fid->rwoffset) {
fid->size = fid->rwoffset;
modified = true;
}
}
brelse(tmp_bh);
/* (3) update the direcoty entry */
/* get_entry_(set_)in_dir shoulb be check DIR_DELETED flag. */
if (fsi->vol_type == EXFAT) {
es = get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep);
if (!es) {
ret = -EIO;
goto err_out;
}
ep2 = ep+1;
} else {
ep = get_dentry_in_dir(sb, &(fid->dir), fid->entry, &sector);
if (!ep) {
ret = -EIO;
goto err_out;
}
ep2 = ep;
}
fsi->fs_func->set_entry_time(ep, tm_now(inode, &tm), TM_MODIFY);
fsi->fs_func->set_entry_attr(ep, fid->attr);
if (modified) {
if (fsi->fs_func->get_entry_flag(ep2) != fid->flags)
fsi->fs_func->set_entry_flag(ep2, fid->flags);
if (fsi->fs_func->get_entry_size(ep2) != fid->size)
fsi->fs_func->set_entry_size(ep2, fid->size);
if (fsi->fs_func->get_entry_clu0(ep2) != fid->start_clu)
fsi->fs_func->set_entry_clu0(ep2, fid->start_clu);
}
if (fsi->vol_type == EXFAT) {
if (update_dir_chksum_with_entry_set(sb, es)) {
ret = -EIO;
goto err_out;
}
release_dentry_set(es);
} else {
if (dcache_modify(sb, sector)) {
ret = -EIO;
goto err_out;
}
}
fs_sync(sb, 0);
fs_set_vol_flags(sb, VOL_CLEAN);
err_out:
/* set the size of written bytes */
if (wcount)
*wcount = write_bytes;
return ret;
} /* end of fscore_write_link */
/* resize the file length */
s32 fscore_truncate(struct inode *inode, u64 old_size, u64 new_size)
{
u32 num_clusters_new, num_clusters_da, num_clusters_phys;
u32 last_clu = CLUS_FREE;
u64 sector;
CHAIN_T clu;
TIMESTAMP_T tm;
DENTRY_T *ep, *ep2;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
FILE_ID_T *fid = &(SDFAT_I(inode)->fid);
ENTRY_SET_CACHE_T *es = NULL;
s32 evict = (fid->dir.dir == DIR_DELETED) ? 1 : 0;
/* check if the given file ID is opened */
if ((fid->type != TYPE_FILE) && (fid->type != TYPE_DIR))
return -EPERM;
/* TO CHECK inode type and size */
MMSG("%s: inode(%p) type(%s) size:%lld->%lld\n", __func__, inode,
(fid->type == TYPE_FILE) ? "file" : "dir", old_size, new_size);
/* XXX : This is for debugging. */
/* It can be when write failed */
#if 0
if (fid->size != old_size) {
DMSG("%s: inode(%p) size-mismatch(old:%lld != fid:%lld)\n",
__func__, inode, old_size, fid->size);
WARN_ON(1);
}
#endif
/*
* There is no lock to protect fid->size.
* So, we should get old_size and use it.
*/
if (old_size <= new_size)
return 0;
fs_set_vol_flags(sb, VOL_DIRTY);
/* Reserved count update */
#define num_clusters(v) ((v) ? (u32)(((v) - 1) >> fsi->cluster_size_bits) + 1 : 0)
num_clusters_da = num_clusters(SDFAT_I(inode)->i_size_aligned);
num_clusters_new = num_clusters(i_size_read(inode));
num_clusters_phys = num_clusters(SDFAT_I(inode)->i_size_ondisk);
/* num_clusters(i_size_old) should be equal to num_clusters_da */
BUG_ON((num_clusters(old_size)) != (num_clusters(SDFAT_I(inode)->i_size_aligned)));
/* for debugging (FIXME: is okay on no-da case?) */
BUG_ON(num_clusters_da < num_clusters_phys);
if ((num_clusters_da != num_clusters_phys) &&
(num_clusters_new < num_clusters_da)) {
/* Decrement reserved clusters
* n_reserved = num_clusters_da - max(new,phys)
*/
int n_reserved = (num_clusters_new > num_clusters_phys) ?
(num_clusters_da - num_clusters_new) :
(num_clusters_da - num_clusters_phys);
fsi->reserved_clusters -= n_reserved;
BUG_ON(fsi->reserved_clusters < 0);
}
clu.dir = fid->start_clu;
/* In no-da case, num_clusters_phys is equal to below value
* clu.size = (u32)((old_size-1) >> fsi->cluster_size_bits) + 1;
*/
clu.size = num_clusters_phys;
clu.flags = fid->flags;
/* For bigdata */
sdfat_statistics_set_trunc(clu.flags, &clu);
if (new_size > 0) {
/* Truncate FAT chain num_clusters after the first cluster
* num_clusters = min(new, phys);
*/
u32 num_clusters = (num_clusters_new < num_clusters_phys) ?
num_clusters_new : num_clusters_phys;
/* Follow FAT chain
* (defensive coding - works fine even with corrupted FAT table
*/
if (clu.flags == 0x03) {
clu.dir += num_clusters;
clu.size -= num_clusters;
#if 0
/* extent_get_clus can`t know last_cluster
* when find target cluster in cache.
*/
} else if (fid->type == TYPE_FILE) {
u32 fclus = 0;
s32 err = extent_get_clus(inode, num_clusters,
&fclus, &(clu.dir), &last_clu, 0);
if (err)
return -EIO;
ASSERT(fclus == num_clusters);
if ((num_clusters > 1) && (last_clu == fid->start_clu)) {
u32 fclus_tmp = 0;
u32 temp = 0;
err = extent_get_clus(inode, num_clusters - 1,
&fclus_tmp, &last_clu, &temp, 0);
if (err)
return -EIO;
ASSERT(fclus_tmp == (num_clusters - 1));
}
num_clusters -= fclus;
clu.size -= fclus;
#endif
} else {
while (num_clusters > 0) {
last_clu = clu.dir;
if (get_next_clus_safe(sb, &(clu.dir)))
return -EIO;
num_clusters--;
clu.size--;
}
}
/* Optimization avialable: */
#if 0
if (num_clusters_new < num_clusters) {
< loop >
} else {
// num_clusters_new >= num_clusters_phys
// FAT truncation is not necessary
clu.dir = CLUS_EOF;
clu.size = 0;
}
#endif
} else if (new_size == 0) {
fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01;
fid->start_clu = CLUS_EOF;
}
fid->size = new_size;
if (fid->type == TYPE_FILE)
fid->attr |= ATTR_ARCHIVE;
/*
* clu.dir: free from
* clu.size: # of clusters to free (exFAT, 0x03 only), no fat_free if 0
* clu.flags: fid->flags (exFAT only)
*/
/* (1) update the directory entry */
if (!evict) {
if (fsi->vol_type == EXFAT) {
es = get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep);
if (!es)
return -EIO;
ep2 = ep+1;
} else {
ep = get_dentry_in_dir(sb, &(fid->dir), fid->entry, &sector);
if (!ep)
return -EIO;
ep2 = ep;
}
fsi->fs_func->set_entry_time(ep, tm_now(inode, &tm), TM_MODIFY);
fsi->fs_func->set_entry_attr(ep, fid->attr);
/*
* if (fsi->vol_type != EXFAT)
* dcache_modify(sb, sector);
*/
/* File size should be zero if there is no cluster allocated */
if (IS_CLUS_EOF(fid->start_clu))
fsi->fs_func->set_entry_size(ep2, 0);
else
fsi->fs_func->set_entry_size(ep2, new_size);
if (new_size == 0) {
/* Any directory can not be truncated to zero */
BUG_ON(fid->type != TYPE_FILE);
fsi->fs_func->set_entry_flag(ep2, 0x01);
fsi->fs_func->set_entry_clu0(ep2, CLUS_FREE);
}
if (fsi->vol_type == EXFAT) {
if (update_dir_chksum_with_entry_set(sb, es))
return -EIO;
release_dentry_set(es);
} else {
if (dcache_modify(sb, sector))
return -EIO;
}
} /* end of if(fid->dir.dir != DIR_DELETED) */
/* (2) cut off from the FAT chain */
if ((fid->flags == 0x01) &&
(!IS_CLUS_FREE(last_clu)) && (!IS_CLUS_EOF(last_clu))) {
if (fat_ent_set(sb, last_clu, CLUS_EOF))
return -EIO;
}
/* (3) invalidate cache and free the clusters */
/* clear extent cache */
extent_cache_inval_inode(inode);
/* hint information */
fid->hint_bmap.off = CLUS_EOF;
fid->hint_bmap.clu = CLUS_EOF;
if (fid->rwoffset > fid->size)
fid->rwoffset = fid->size;
/* hint_stat will be used if this is directory. */
fid->hint_stat.eidx = 0;
fid->hint_stat.clu = fid->start_clu;
fid->hint_femp.eidx = -1;
/* free the clusters */
if (fsi->fs_func->free_cluster(sb, &clu, evict))
return -EIO;
fs_sync(sb, 0);
fs_set_vol_flags(sb, VOL_CLEAN);
return 0;
} /* end of fscore_truncate */
static void update_parent_info(FILE_ID_T *fid, struct inode *parent_inode)
{
FS_INFO_T *fsi = &(SDFAT_SB(parent_inode->i_sb)->fsi);
FILE_ID_T *parent_fid = &(SDFAT_I(parent_inode)->fid);
/*
* the problem that FILE_ID_T caches wrong parent info.
*
* because of flag-mismatch of fid->dir,
* there is abnormal traversing cluster chain.
*/
if (unlikely((parent_fid->flags != fid->dir.flags)
|| (parent_fid->size != (fid->dir.size<<fsi->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 + (fsi->cluster_size-1))
>> fsi->cluster_size_bits);
}
}
/* rename or move a old file into a new file */
s32 fscore_rename(struct inode *old_parent_inode, FILE_ID_T *fid,
struct inode *new_parent_inode, struct dentry *new_dentry)
{
s32 ret;
s32 dentry;
CHAIN_T olddir, newdir;
CHAIN_T *p_dir = NULL;
UNI_NAME_T uni_name;
DENTRY_T *ep;
struct super_block *sb = old_parent_inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
u8 *new_path = (u8 *) new_dentry->d_name.name;
struct inode *new_inode = new_dentry->d_inode;
int num_entries;
FILE_ID_T *new_fid = NULL;
u32 new_entry_type = TYPE_UNUSED;
s32 new_entry = 0;
/* check the validity of pointer parameters */
if ((new_path == NULL) || (strlen(new_path) == 0))
return -EINVAL;
if (fid->dir.dir == DIR_DELETED) {
EMSG("%s : abnormal access to deleted source dentry\n", __func__);
return -ENOENT;
}
/* patch 1.2.4 : the problem that FILE_ID_T caches wrong parent info. */
update_parent_info(fid, old_parent_inode);
olddir.dir = fid->dir.dir;
olddir.size = fid->dir.size;
olddir.flags = fid->dir.flags;
dentry = fid->entry;
/* check if the old file is "." or ".." */
if (fsi->vol_type != EXFAT) {
if ((olddir.dir != fsi->root_dir) && (dentry < 2))
return -EPERM;
}
ep = get_dentry_in_dir(sb, &olddir, dentry, NULL);
if (!ep)
return -EIO;
#ifdef CONFIG_SDFAT_CHECK_RO_ATTR
if (fsi->fs_func->get_entry_attr(ep) & ATTR_READONLY)
return -EPERM;
#endif
/* check whether new dir is existing directory and empty */
if (new_inode) {
ret = -EIO;
new_fid = &SDFAT_I(new_inode)->fid;
if (new_fid->dir.dir == DIR_DELETED) {
EMSG("%s : abnormal access to deleted target dentry\n", __func__);
goto out;
}
/* patch 1.2.4 :
* the problem that FILE_ID_T caches wrong parent info.
*
* FIXME : is needed?
*/
update_parent_info(new_fid, new_parent_inode);
p_dir = &(new_fid->dir);
new_entry = new_fid->entry;
ep = get_dentry_in_dir(sb, p_dir, new_entry, NULL);
if (!ep)
goto out;
new_entry_type = fsi->fs_func->get_entry_type(ep);
/* if new_inode exists, update fid */
new_fid->size = i_size_read(new_inode);
if (new_entry_type == TYPE_DIR) {
CHAIN_T new_clu;
new_clu.dir = new_fid->start_clu;
new_clu.size = ((new_fid->size-1) >> fsi->cluster_size_bits) + 1;
new_clu.flags = new_fid->flags;
ret = check_dir_empty(sb, &new_clu);
if (ret)
return ret;
}
}
/* check the validity of directory name in the given new pathname */
ret = resolve_path(new_parent_inode, new_path, &newdir, &uni_name);
if (ret)
return ret;
fs_set_vol_flags(sb, VOL_DIRTY);
if (olddir.dir == newdir.dir)
ret = rename_file(new_parent_inode, &olddir, dentry, &uni_name, fid);
else
ret = move_file(new_parent_inode, &olddir, dentry, &newdir, &uni_name, fid);
if ((!ret) && new_inode) {
/* delete entries of new_dir */
ep = get_dentry_in_dir(sb, p_dir, new_entry, NULL);
if (!ep) {
ret = -EIO;
goto del_out;
}
num_entries = fsi->fs_func->count_ext_entries(sb, p_dir, new_entry, ep);
if (num_entries < 0) {
ret = -EIO;
goto del_out;
}
if (fsi->fs_func->delete_dir_entry(sb, p_dir, new_entry, 0, num_entries+1)) {
ret = -EIO;
goto del_out;
}
/* Free the clusters if new_inode is a dir(as if fscore_rmdir) */
if (new_entry_type == TYPE_DIR) {
/* new_fid, new_clu_to_free */
CHAIN_T new_clu_to_free;
new_clu_to_free.dir = new_fid->start_clu;
new_clu_to_free.size = ((new_fid->size-1) >> fsi->cluster_size_bits) + 1;
new_clu_to_free.flags = new_fid->flags;
if (fsi->fs_func->free_cluster(sb, &new_clu_to_free, 1)) {
/* just set I/O error only */
ret = -EIO;
}
new_fid->size = 0;
new_fid->start_clu = CLUS_EOF;
new_fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01;
}
del_out:
/* Update new_inode fid
* Prevent syncing removed new_inode
* (new_fid is already initialized above code ("if (new_inode)")
*/
new_fid->dir.dir = DIR_DELETED;
}
out:
fs_sync(sb, 0);
fs_set_vol_flags(sb, VOL_CLEAN);
return ret;
} /* end of fscore_rename */
/* remove a file */
s32 fscore_remove(struct inode *inode, FILE_ID_T *fid)
{
s32 ret;
s32 dentry;
CHAIN_T dir, clu_to_free;
DENTRY_T *ep;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
dir.dir = fid->dir.dir;
dir.size = fid->dir.size;
dir.flags = fid->dir.flags;
dentry = fid->entry;
if (fid->dir.dir == DIR_DELETED) {
EMSG("%s : abnormal access to deleted dentry\n", __func__);
return -ENOENT;
}
ep = get_dentry_in_dir(sb, &dir, dentry, NULL);
if (!ep)
return -EIO;
#ifdef CONFIG_SDFAT_CHECK_RO_ATTR
if (fsi->fs_func->get_entry_attr(ep) & ATTR_READONLY)
return -EPERM;
#endif
fs_set_vol_flags(sb, VOL_DIRTY);
/* (1) update the directory entry */
ret = remove_file(inode, &dir, dentry);
if (ret)
goto out;
clu_to_free.dir = fid->start_clu;
clu_to_free.size = ((fid->size-1) >> fsi->cluster_size_bits) + 1;
clu_to_free.flags = fid->flags;
/* (2) invalidate extent cache and free the clusters
*/
/* clear extent cache */
extent_cache_inval_inode(inode);
ret = fsi->fs_func->free_cluster(sb, &clu_to_free, 0);
/* WARN : DO NOT RETURN ERROR IN HERE */
/* (3) update FILE_ID_T */
fid->size = 0;
fid->start_clu = CLUS_EOF;
fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01;
fid->dir.dir = DIR_DELETED;
fs_sync(sb, 0);
fs_set_vol_flags(sb, VOL_CLEAN);
out:
return ret;
} /* end of fscore_remove */
/*
* Get the information of a given file
* REMARK : This function does not need any file name on linux
*
* info.Size means the value saved on disk.
* But root directory doesn`t have real dentry,
* so the size of root directory returns calculated one exceptively.
*/
s32 fscore_read_inode(struct inode *inode, DIR_ENTRY_T *info)
{
u64 sector;
s32 count;
CHAIN_T dir;
TIMESTAMP_T tm;
DENTRY_T *ep, *ep2;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
FILE_ID_T *fid = &(SDFAT_I(inode)->fid);
ENTRY_SET_CACHE_T *es = NULL;
u8 is_dir = (fid->type == TYPE_DIR) ? 1 : 0;
TMSG("%s entered\n", __func__);
extent_cache_init_inode(inode);
/* if root directory */
if (is_dir && (fid->dir.dir == fsi->root_dir) && (fid->entry == -1)) {
info->Attr = ATTR_SUBDIR;
memset((s8 *) &info->CreateTimestamp, 0, sizeof(DATE_TIME_T));
memset((s8 *) &info->ModifyTimestamp, 0, sizeof(DATE_TIME_T));
memset((s8 *) &info->AccessTimestamp, 0, sizeof(DATE_TIME_T));
//strcpy(info->NameBuf.sfn, ".");
//strcpy(info->NameBuf.lfn, ".");
dir.dir = fsi->root_dir;
dir.flags = 0x01;
dir.size = 0; /* UNUSED */
/* FAT16 root_dir */
if (IS_CLUS_FREE(fsi->root_dir)) {
info->Size = fsi->dentries_in_root << DENTRY_SIZE_BITS;
} else {
u32 num_clu;
if (__count_num_clusters(sb, &dir, &num_clu))
return -EIO;
info->Size = (u64)num_clu << fsi->cluster_size_bits;
}
count = __count_dos_name_entries(sb, &dir, TYPE_DIR, NULL);
if (count < 0)
return -EIO;
info->NumSubdirs = count;
return 0;
}
/* get the directory entry of given file or directory */
if (fsi->vol_type == EXFAT) {
/* es should be released */
es = get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_2_ENTRIES, &ep);
if (!es)
return -EIO;
ep2 = ep+1;
} else {
ep = get_dentry_in_dir(sb, &(fid->dir), fid->entry, &sector);
if (!ep)
return -EIO;
ep2 = ep;
/* dcache should be unlocked */
dcache_lock(sb, sector);
}
/* set FILE_INFO structure using the acquired DENTRY_T */
info->Attr = fsi->fs_func->get_entry_attr(ep);
fsi->fs_func->get_entry_time(ep, &tm, TM_CREATE);
info->CreateTimestamp.Year = tm.year;
info->CreateTimestamp.Month = tm.mon;
info->CreateTimestamp.Day = tm.day;
info->CreateTimestamp.Hour = tm.hour;
info->CreateTimestamp.Minute = tm.min;
info->CreateTimestamp.Second = tm.sec;
info->CreateTimestamp.MilliSecond = 0;
info->CreateTimestamp.Timezone.value = tm.tz.value;
fsi->fs_func->get_entry_time(ep, &tm, TM_MODIFY);
info->ModifyTimestamp.Year = tm.year;
info->ModifyTimestamp.Month = tm.mon;
info->ModifyTimestamp.Day = tm.day;
info->ModifyTimestamp.Hour = tm.hour;
info->ModifyTimestamp.Minute = tm.min;
info->ModifyTimestamp.Second = tm.sec;
info->ModifyTimestamp.MilliSecond = 0;
info->ModifyTimestamp.Timezone.value = tm.tz.value;
memset((s8 *) &info->AccessTimestamp, 0, sizeof(DATE_TIME_T));
info->NumSubdirs = 0;
info->Size = fsi->fs_func->get_entry_size(ep2);
if (fsi->vol_type == EXFAT)
release_dentry_set(es);
else
dcache_unlock(sb, sector);
if (is_dir) {
u32 dotcnt = 0;
dir.dir = fid->start_clu;
dir.flags = fid->flags;
dir.size = fid->size >> fsi->cluster_size_bits;
/*
* NOTE :
* If "dir.flags" has 0x01, "dir.size" is meaningless.
*/
#if 0
if (info->Size == 0) {
s32 num_clu;
if (__count_num_clusters(sb, &dir, &num_clu))
return -EIO;
info->Size = (u64)num_clu << fsi->cluster_size_bits;
}
#endif
count = __count_dos_name_entries(sb, &dir, TYPE_DIR, &dotcnt);
if (count < 0)
return -EIO;
if (fsi->vol_type == EXFAT) {
count += SDFAT_MIN_SUBDIR;
} else {
/*
* if directory has been corrupted,
* we have to adjust subdir count.
*/
BUG_ON(dotcnt > SDFAT_MIN_SUBDIR);
if (dotcnt < SDFAT_MIN_SUBDIR) {
EMSG("%s: contents of the directory has been "
"corrupted (parent clus : %08x, idx : %d)",
__func__, fid->dir.dir, fid->entry);
}
count += (SDFAT_MIN_SUBDIR - dotcnt);
}
info->NumSubdirs = count;
}
TMSG("%s exited successfully\n", __func__);
return 0;
} /* end of fscore_read_inode */
/* set the information of a given file
* REMARK : This function does not need any file name on linux
*/
s32 fscore_write_inode(struct inode *inode, DIR_ENTRY_T *info, s32 sync)
{
s32 ret = -EIO;
u64 sector;
TIMESTAMP_T tm;
DENTRY_T *ep, *ep2;
ENTRY_SET_CACHE_T *es = NULL;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
FILE_ID_T *fid = &(SDFAT_I(inode)->fid);
u8 is_dir = (fid->type == TYPE_DIR) ? 1 : 0;
/* SKIP WRITING INODE :
* if the indoe is already unlinked,
* there is no need for updating inode
*/
if (fid->dir.dir == DIR_DELETED)
return 0;
if (is_dir && (fid->dir.dir == fsi->root_dir) && (fid->entry == -1))
return 0;
fs_set_vol_flags(sb, VOL_DIRTY);
/* get the directory entry of given file or directory */
if (fsi->vol_type == EXFAT) {
es = get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep);
if (!es)
return -EIO;
ep2 = ep+1;
} else {
/* for other than exfat */
ep = get_dentry_in_dir(sb, &(fid->dir), fid->entry, &sector);
if (!ep)
return -EIO;
ep2 = ep;
}
fsi->fs_func->set_entry_attr(ep, info->Attr);
/* set FILE_INFO structure using the acquired DENTRY_T */
tm.tz = info->CreateTimestamp.Timezone;
tm.sec = info->CreateTimestamp.Second;
tm.min = info->CreateTimestamp.Minute;
tm.hour = info->CreateTimestamp.Hour;
tm.day = info->CreateTimestamp.Day;
tm.mon = info->CreateTimestamp.Month;
tm.year = info->CreateTimestamp.Year;
fsi->fs_func->set_entry_time(ep, &tm, TM_CREATE);
tm.tz = info->ModifyTimestamp.Timezone;
tm.sec = info->ModifyTimestamp.Second;
tm.min = info->ModifyTimestamp.Minute;
tm.hour = info->ModifyTimestamp.Hour;
tm.day = info->ModifyTimestamp.Day;
tm.mon = info->ModifyTimestamp.Month;
tm.year = info->ModifyTimestamp.Year;
fsi->fs_func->set_entry_time(ep, &tm, TM_MODIFY);
if (is_dir && fsi->vol_type != EXFAT) {
/* overwirte dirsize if FAT32 and dir size != 0 */
if (fsi->fs_func->get_entry_size(ep2))
fsi->fs_func->set_entry_size(ep2, 0);
} else {
/* File size should be zero if there is no cluster allocated */
u64 on_disk_size = info->Size;
if (IS_CLUS_EOF(fid->start_clu))
on_disk_size = 0;
fsi->fs_func->set_entry_size(ep2, on_disk_size);
}
if (fsi->vol_type == EXFAT) {
ret = update_dir_chksum_with_entry_set(sb, es);
release_dentry_set(es);
} else {
ret = dcache_modify(sb, sector);
}
fs_sync(sb, sync);
/* Comment below code to prevent super block update frequently */
//fs_set_vol_flags(sb, VOL_CLEAN);
return ret;
} /* end of fscore_write_inode */
/*
* Input: inode, (logical) clu_offset, target allocation area
* Output: errcode, cluster number
* *clu = (~0), if it's unable to allocate a new cluster
*/
s32 fscore_map_clus(struct inode *inode, u32 clu_offset, u32 *clu, int dest)
{
s32 ret, modified = false;
u32 last_clu;
u64 sector;
CHAIN_T new_clu;
DENTRY_T *ep;
ENTRY_SET_CACHE_T *es = NULL;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
FILE_ID_T *fid = &(SDFAT_I(inode)->fid);
u32 local_clu_offset = clu_offset;
s32 reserved_clusters = fsi->reserved_clusters;
u32 num_to_be_allocated = 0, num_clusters = 0;
fid->rwoffset = (s64)(clu_offset) << fsi->cluster_size_bits;
if (SDFAT_I(inode)->i_size_ondisk > 0)
num_clusters = (u32)((SDFAT_I(inode)->i_size_ondisk-1) >> fsi->cluster_size_bits) + 1;
if (clu_offset >= num_clusters)
num_to_be_allocated = clu_offset - num_clusters + 1;
if ((dest == ALLOC_NOWHERE) && (num_to_be_allocated > 0)) {
*clu = CLUS_EOF;
return 0;
}
/* check always request cluster is 1 */
//ASSERT(num_to_be_allocated == 1);
sdfat_debug_check_clusters(inode);
*clu = last_clu = fid->start_clu;
/* XXX: Defensive code needed.
* what if i_size_ondisk != # of allocated clusters
*/
if (fid->flags == 0x03) {
if ((clu_offset > 0) && (!IS_CLUS_EOF(*clu))) {
last_clu += clu_offset - 1;
if (clu_offset == num_clusters)
*clu = CLUS_EOF;
else
*clu += clu_offset;
}
} else if (fid->type == TYPE_FILE) {
u32 fclus = 0;
s32 err = extent_get_clus(inode, clu_offset,
&fclus, clu, &last_clu, 1);
if (err)
return -EIO;
clu_offset -= fclus;
} else {
/* hint information */
if ((clu_offset > 0) &&
((fid->hint_bmap.off != CLUS_EOF) && (fid->hint_bmap.off > 0)) &&
(clu_offset >= fid->hint_bmap.off)) {
clu_offset -= fid->hint_bmap.off;
/* hint_bmap.clu should be valid */
ASSERT(fid->hint_bmap.clu >= 2);
*clu = fid->hint_bmap.clu;
}
while ((clu_offset > 0) && (!IS_CLUS_EOF(*clu))) {
last_clu = *clu;
if (get_next_clus_safe(sb, clu))
return -EIO;
clu_offset--;
}
}
if (IS_CLUS_EOF(*clu)) {
fs_set_vol_flags(sb, VOL_DIRTY);
new_clu.dir = (IS_CLUS_EOF(last_clu)) ? CLUS_EOF : last_clu + 1;
new_clu.size = 0;
new_clu.flags = fid->flags;
/* (1) allocate a cluster */
if (num_to_be_allocated < 1) {
/* Broken FAT (i_sze > allocated FAT) */
EMSG("%s: invalid fat chain : inode(%p) "
"num_to_be_allocated(%d) "
"i_size_ondisk(%lld) fid->flags(%02x) "
"fid->start(%08x) fid->hint_off(%u) "
"fid->hint_clu(%u) fid->rwoffset(%llu) "
"modified_clu_off(%d) last_clu(%08x) "
"new_clu(%08x)", __func__, inode,
num_to_be_allocated,
(SDFAT_I(inode)->i_size_ondisk),
fid->flags, fid->start_clu,
fid->hint_bmap.off, fid->hint_bmap.clu,
fid->rwoffset, clu_offset,
last_clu, new_clu.dir);
sdfat_fs_error(sb, "broken FAT chain.");
return -EIO;
}
ret = fsi->fs_func->alloc_cluster(sb, num_to_be_allocated, &new_clu, ALLOC_COLD);
if (ret)
return ret;
if (IS_CLUS_EOF(new_clu.dir) || IS_CLUS_FREE(new_clu.dir)) {
sdfat_fs_error(sb, "bogus cluster new allocated"
"(last_clu : %u, new_clu : %u)",
last_clu, new_clu.dir);
ASSERT(0);
return -EIO;
}
/* Reserved cluster dec. */
// XXX: Inode DA flag needed
if (SDFAT_SB(sb)->options.improved_allocation & SDFAT_ALLOC_DELAY) {
BUG_ON(reserved_clusters < num_to_be_allocated);
reserved_clusters -= num_to_be_allocated;
}
/* (2) append to the FAT chain */
if (IS_CLUS_EOF(last_clu)) {
if (new_clu.flags == 0x01)
fid->flags = 0x01;
fid->start_clu = new_clu.dir;
modified = true;
} else {
if (new_clu.flags != fid->flags) {
/* no-fat-chain bit is disabled,
* so fat-chain should be synced with alloc-bmp
*/
chain_cont_cluster(sb, fid->start_clu, num_clusters);
fid->flags = 0x01;
modified = true;
}
if (new_clu.flags == 0x01)
if (fat_ent_set(sb, last_clu, new_clu.dir))
return -EIO;
}
num_clusters += num_to_be_allocated;
*clu = new_clu.dir;
if (fid->dir.dir != DIR_DELETED) {
if (fsi->vol_type == EXFAT) {
es = get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep);
if (!es)
return -EIO;
/* get stream entry */
ep++;
}
/* (3) update directory entry */
if (modified) {
if (fsi->vol_type != EXFAT) {
ep = get_dentry_in_dir(sb, &(fid->dir), fid->entry, &sector);
if (!ep)
return -EIO;
}
if (fsi->fs_func->get_entry_flag(ep) != fid->flags)
fsi->fs_func->set_entry_flag(ep, fid->flags);
if (fsi->fs_func->get_entry_clu0(ep) != fid->start_clu)
fsi->fs_func->set_entry_clu0(ep, fid->start_clu);
fsi->fs_func->set_entry_size(ep, fid->size);
if (fsi->vol_type != EXFAT) {
if (dcache_modify(sb, sector))
return -EIO;
}
}
if (fsi->vol_type == EXFAT) {
if (update_dir_chksum_with_entry_set(sb, es))
return -EIO;
release_dentry_set(es);
}
} /* end of if != DIR_DELETED */
/* add number of new blocks to inode (non-DA only) */
if (!(SDFAT_SB(sb)->options.improved_allocation & SDFAT_ALLOC_DELAY)) {
inode->i_blocks += num_to_be_allocated << (fsi->cluster_size_bits - sb->s_blocksize_bits);
} else {
// DA의 경우, i_blocks가 이미 증가해있어야 함.
BUG_ON(clu_offset >= (inode->i_blocks >> (fsi->cluster_size_bits - sb->s_blocksize_bits)));
}
#if 0
fs_sync(sb, 0);
fs_set_vol_flags(sb, VOL_CLEAN);
#endif
/* (4) Move *clu pointer along FAT chains (hole care)
* because the caller of this function expect *clu to be the last cluster.
* This only works when num_to_be_allocated >= 2,
* *clu = (the first cluster of the allocated chain) => (the last cluster of ...)
*/
if (fid->flags == 0x03) {
*clu += num_to_be_allocated - 1;
} else {
while (num_to_be_allocated > 1) {
if (get_next_clus_safe(sb, clu))
return -EIO;
num_to_be_allocated--;
}
}
}
/* update reserved_clusters */
fsi->reserved_clusters = reserved_clusters;
/* hint information */
fid->hint_bmap.off = local_clu_offset;
fid->hint_bmap.clu = *clu;
return 0;
} /* end of fscore_map_clus */
/* allocate reserved cluster */
s32 fscore_reserve_clus(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
if ((fsi->used_clusters + fsi->reserved_clusters) >= (fsi->num_clusters - 2))
return -ENOSPC;
if (bdev_check_bdi_valid(sb))
return -EIO;
fsi->reserved_clusters++;
/* inode->i_blocks update */
inode->i_blocks += 1 << (fsi->cluster_size_bits - sb->s_blocksize_bits);
sdfat_debug_check_clusters(inode);
return 0;
}
/* remove an entry, BUT don't truncate */
s32 fscore_unlink(struct inode *inode, FILE_ID_T *fid)
{
s32 dentry;
CHAIN_T dir;
DENTRY_T *ep;
struct super_block *sb = inode->i_sb;
dir.dir = fid->dir.dir;
dir.size = fid->dir.size;
dir.flags = fid->dir.flags;
dentry = fid->entry;
if (fid->dir.dir == DIR_DELETED) {
EMSG("%s : abnormal access to deleted dentry\n", __func__);
return -ENOENT;
}
ep = get_dentry_in_dir(sb, &dir, dentry, NULL);
if (!ep)
return -EIO;
#ifdef CONFIG_SDFAT_CHECK_RO_ATTR
if (SDFAT_SB(sb)->fsi.fs_func->get_entry_attr(ep) & ATTR_READONLY)
return -EPERM;
#endif
fs_set_vol_flags(sb, VOL_DIRTY);
/* (1) update the directory entry */
if (remove_file(inode, &dir, dentry))
return -EIO;
/* This doesn't modify fid */
fid->dir.dir = DIR_DELETED;
fs_sync(sb, 0);
fs_set_vol_flags(sb, VOL_CLEAN);
return 0;
}
/*----------------------------------------------------------------------*/
/* Directory Operation Functions */
/*----------------------------------------------------------------------*/
/* create a directory */
s32 fscore_mkdir(struct inode *inode, u8 *path, FILE_ID_T *fid)
{
s32 ret/*, dentry*/;
CHAIN_T dir;
UNI_NAME_T uni_name;
struct super_block *sb = inode->i_sb;
TMSG("%s entered\n", __func__);
/* check the validity of directory name in the given old pathname */
ret = resolve_path(inode, path, &dir, &uni_name);
if (ret)
goto out;
fs_set_vol_flags(sb, VOL_DIRTY);
ret = create_dir(inode, &dir, &uni_name, fid);
fs_sync(sb, 0);
fs_set_vol_flags(sb, VOL_CLEAN);
out:
TMSG("%s exited with err(%d)\n", __func__, ret);
return ret;
}
/* read a directory entry from the opened directory */
s32 fscore_readdir(struct inode *inode, DIR_ENTRY_T *dir_entry)
{
s32 i;
s32 dentries_per_clu, dentries_per_clu_bits = 0;
u32 type, clu_offset;
u64 sector;
CHAIN_T dir, clu;
UNI_NAME_T uni_name;
TIMESTAMP_T tm;
DENTRY_T *ep;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
FILE_ID_T *fid = &(SDFAT_I(inode)->fid);
u32 dentry = (u32)(fid->rwoffset & 0xFFFFFFFF); /* u32 is enough for directory */
/* check if the given file ID is opened */
if (fid->type != TYPE_DIR)
return -EPERM;
if (fid->entry == -1) {
dir.dir = fsi->root_dir;
dir.size = 0; /* just initialize, but will not use */
dir.flags = 0x01;
} else {
dir.dir = fid->start_clu;
dir.size = fid->size >> fsi->cluster_size_bits;
dir.flags = fid->flags;
sdfat_debug_bug_on(dentry >= (dir.size * fsi->dentries_per_clu));
}
if (IS_CLUS_FREE(dir.dir)) { /* FAT16 root_dir */
dentries_per_clu = fsi->dentries_in_root;
/* Prevent readdir over directory size */
if (dentry >= dentries_per_clu) {
clu.dir = CLUS_EOF;
} else {
clu.dir = dir.dir;
clu.size = dir.size;
clu.flags = dir.flags;
}
} else {
dentries_per_clu = fsi->dentries_per_clu;
dentries_per_clu_bits = ilog2(dentries_per_clu);
clu_offset = dentry >> dentries_per_clu_bits;
clu.dir = dir.dir;
clu.size = dir.size;
clu.flags = dir.flags;
if (clu.flags == 0x03) {
clu.dir += clu_offset;
clu.size -= clu_offset;
} else {
/* hint_information */
if ((clu_offset > 0) &&
((fid->hint_bmap.off != CLUS_EOF) && (fid->hint_bmap.off > 0)) &&
(clu_offset >= fid->hint_bmap.off)) {
clu_offset -= fid->hint_bmap.off;
clu.dir = fid->hint_bmap.clu;
}
while (clu_offset > 0) {
if (get_next_clus_safe(sb, &(clu.dir)))
return -EIO;
clu_offset--;
}
}
}
while (!IS_CLUS_EOF(clu.dir)) {
if (IS_CLUS_FREE(dir.dir)) /* FAT16 root_dir */
i = dentry % dentries_per_clu;
else
i = dentry & (dentries_per_clu-1);
for ( ; i < dentries_per_clu; i++, dentry++) {
ep = get_dentry_in_dir(sb, &clu, i, &sector);
if (!ep)
return -EIO;
type = fsi->fs_func->get_entry_type(ep);
if (type == TYPE_UNUSED)
break;
if ((type != TYPE_FILE) && (type != TYPE_DIR))
continue;
dcache_lock(sb, sector);
dir_entry->Attr = fsi->fs_func->get_entry_attr(ep);
fsi->fs_func->get_entry_time(ep, &tm, TM_CREATE);
dir_entry->CreateTimestamp.Year = tm.year;
dir_entry->CreateTimestamp.Month = tm.mon;
dir_entry->CreateTimestamp.Day = tm.day;
dir_entry->CreateTimestamp.Hour = tm.hour;
dir_entry->CreateTimestamp.Minute = tm.min;
dir_entry->CreateTimestamp.Second = tm.sec;
dir_entry->CreateTimestamp.MilliSecond = 0;
fsi->fs_func->get_entry_time(ep, &tm, TM_MODIFY);
dir_entry->ModifyTimestamp.Year = tm.year;
dir_entry->ModifyTimestamp.Month = tm.mon;
dir_entry->ModifyTimestamp.Day = tm.day;
dir_entry->ModifyTimestamp.Hour = tm.hour;
dir_entry->ModifyTimestamp.Minute = tm.min;
dir_entry->ModifyTimestamp.Second = tm.sec;
dir_entry->ModifyTimestamp.MilliSecond = 0;
memset((s8 *) &dir_entry->AccessTimestamp, 0, sizeof(DATE_TIME_T));
*(uni_name.name) = 0x0;
fsi->fs_func->get_uniname_from_ext_entry(sb, &dir, dentry, uni_name.name);
if (*(uni_name.name) == 0x0)
get_uniname_from_dos_entry(sb, (DOS_DENTRY_T *) ep, &uni_name, 0x1);
nls_uni16s_to_vfsname(sb, &uni_name,
dir_entry->NameBuf.lfn,
dir_entry->NameBuf.lfnbuf_len);
dcache_unlock(sb, sector);
if (fsi->vol_type == EXFAT) {
ep = get_dentry_in_dir(sb, &clu, i+1, NULL);
if (!ep)
return -EIO;
} else {
get_uniname_from_dos_entry(sb, (DOS_DENTRY_T *) ep, &uni_name, 0x0);
nls_uni16s_to_vfsname(sb, &uni_name,
dir_entry->NameBuf.sfn,
dir_entry->NameBuf.sfnbuf_len);
}
dir_entry->Size = fsi->fs_func->get_entry_size(ep);
/*
* Update hint information :
* fat16 root directory does not need it.
*/
if (!IS_CLUS_FREE(dir.dir)) {
fid->hint_bmap.off = dentry >> dentries_per_clu_bits;
fid->hint_bmap.clu = clu.dir;
}
fid->rwoffset = (s64) ++dentry;
return 0;
}
/* fat16 root directory */
if (IS_CLUS_FREE(dir.dir))
break;
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;
}
}
dir_entry->NameBuf.lfn[0] = '\0';
fid->rwoffset = (s64)dentry;
return 0;
} /* end of fscore_readdir */
/* remove a directory */
s32 fscore_rmdir(struct inode *inode, FILE_ID_T *fid)
{
s32 ret;
s32 dentry;
DENTRY_T *ep;
CHAIN_T dir, clu_to_free;
struct super_block *sb = inode->i_sb;
FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
dir.dir = fid->dir.dir;
dir.size = fid->dir.size;
dir.flags = fid->dir.flags;
dentry = fid->entry;
if (fid->dir.dir == DIR_DELETED) {
EMSG("%s : abnormal access to deleted dentry\n", __func__);
return -ENOENT;
}
/* check if the file is "." or ".." */
if (fsi->vol_type != EXFAT) {
if ((dir.dir != fsi->root_dir) && (dentry < 2))
return -EPERM;
}
ep = get_dentry_in_dir(sb, &dir, dentry, NULL);
if (!ep)
return -EIO;
#ifdef CONFIG_SDFAT_CHECK_RO_ATTR
if (SDFAT_SB(sb)->fsi.fs_func->get_entry_attr(ep) & ATTR_READONLY)
return -EPERM;
#endif
clu_to_free.dir = fid->start_clu;
clu_to_free.size = ((fid->size-1) >> fsi->cluster_size_bits) + 1;
clu_to_free.flags = fid->flags;
ret = check_dir_empty(sb, &clu_to_free);
if (ret) {
if (ret == -EIO)
EMSG("%s : failed to check_dir_empty : err(%d)\n",
__func__, ret);
return ret;
}
fs_set_vol_flags(sb, VOL_DIRTY);
/* (1) update the directory entry */
ret = remove_file(inode, &dir, dentry);
if (ret) {
EMSG("%s : failed to remove_file : err(%d)\n", __func__, ret);
return ret;
}
fid->dir.dir = DIR_DELETED;
fs_sync(sb, 0);
fs_set_vol_flags(sb, VOL_CLEAN);
return ret;
} /* end of fscore_rmdir */
/* end of core.c */