diff options
Diffstat (limited to 'fs/sdfat/core.c')
| -rw-r--r-- | fs/sdfat/core.c | 3694 |
1 files changed, 3694 insertions, 0 deletions
diff --git a/fs/sdfat/core.c b/fs/sdfat/core.c new file mode 100644 index 000000000000..3a5af0b83d59 --- /dev/null +++ b/fs/sdfat/core.c @@ -0,0 +1,3694 @@ +/* + * 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 + + +/*----------------------------------------------------------------------*/ +/* 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->s_flags & MS_RDONLY) + 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.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 *)); + */ + + 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, §or); + 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, §or); + 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, §or_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, §or_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, §or_old); + dcache_lock(sb, sector_old); + epnew = get_dentry_in_dir(sb, p_dir, newentry+1, §or_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, §or_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, §or_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, §or_mov); + dcache_lock(sb, sector_mov); + epnew = get_dentry_in_dir(sb, p_newdir, newentry+1, §or_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, §or_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.f16.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.f16.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 hd_struct *part = sb->s_bdev->bd_part; + 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 (is_fat32(p_pbr)) { + 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; + ret = mount_fat32(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; + 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 " + "(bps : %lu, spc : %u, data start : %llu, %s)", + sdfat_get_vol_type_str(fsi->vol_type), + 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)((disk->part0.nr_sects) >> 1) : 0, + part ? (u64)((part->nr_sects) >> 1) : 0); + + 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, §or); + if (!ep) { + ret = -EIO; + goto err_out; + } + ep2 = ep; + } + + fsi->fs_func->set_entry_time(ep, tm_now(SDFAT_SB(sb), &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, §or); + if (!ep) + return -EIO; + ep2 = ep; + } + + fsi->fs_func->set_entry_time(ep, tm_now(SDFAT_SB(sb), &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, §or); + 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, §or); + 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, §or); + 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, §or); + 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 */ |
