| /* |
| * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| /************************************************************************/ |
| /* */ |
| /* PROJECT : exFAT & FAT12/16/32 File System */ |
| /* FILE : core.c */ |
| /* PURPOSE : FAT & exFAT common core code for sdFAT */ |
| /* */ |
| /*----------------------------------------------------------------------*/ |
| /* NOTES */ |
| /* */ |
| /* */ |
| /************************************************************************/ |
| |
| #include <linux/version.h> |
| #include <linux/blkdev.h> |
| #include <linux/workqueue.h> |
| #include <linux/writeback.h> |
| #include <linux/kernel.h> |
| #include <linux/log2.h> |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) |
| #include <linux/iversion.h> |
| #endif |
| |
| #include "sdfat.h" |
| #include "core.h" |
| #include <asm/byteorder.h> |
| #include <asm/unaligned.h> |
| |
| |
| /************************************************************************* |
| * FUNCTIONS WHICH HAS KERNEL VERSION DEPENDENCY |
| *************************************************************************/ |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0) |
| static inline u64 inode_peek_iversion(struct inode *inode) |
| { |
| return inode->i_version; |
| } |
| #endif |
| |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 11, 0) |
| static u64 bdev_nr_sectors(struct block_device *bdev) |
| { |
| struct hd_struct *part = bdev->bd_part; |
| |
| if (!part) |
| return 0; |
| |
| return (u64)(part->nr_sects); |
| } |
| #endif |
| |
| |
| /*----------------------------------------------------------------------*/ |
| /* Constant & Macro Definitions */ |
| /*----------------------------------------------------------------------*/ |
| static inline void __set_sb_dirty(struct super_block *sb) |
| { |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0) |
| sb->s_dirt = 1; |
| #else /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) */ |
| struct sdfat_sb_info *sbi = SDFAT_SB(sb); |
| |
| sbi->s_dirt = 1; |
| /* Insert work */ |
| spin_lock(&sbi->work_lock); |
| if (!sbi->write_super_queued) { |
| unsigned long delay; |
| |
| delay = msecs_to_jiffies(CONFIG_SDFAT_WRITE_SB_INTERVAL_CSECS * 10); |
| queue_delayed_work(system_long_wq, &sbi->write_super_work, delay); |
| sbi->write_super_queued = 1; |
| } |
| spin_unlock(&sbi->work_lock); |
| #endif |
| } |
| |
| void set_sb_dirty(struct super_block *sb) |
| { |
| __set_sb_dirty(sb); |
| // XXX: to be removed later, prints too much output |
| //TMSG("%s finished.\n", __func__); |
| } |
| |
| /*----------------------------------------------------------------------*/ |
| /* Global Variable Definitions */ |
| /*----------------------------------------------------------------------*/ |
| |
| /*----------------------------------------------------------------------*/ |
| /* Local Variable Definitions */ |
| /*----------------------------------------------------------------------*/ |
| |
| static s8 *reserved_names[] = { |
| "AUX ", "CON ", "NUL ", "PRN ", |
| "COM1 ", "COM2 ", "COM3 ", "COM4 ", |
| "COM5 ", "COM6 ", "COM7 ", "COM8 ", "COM9 ", |
| "LPT1 ", "LPT2 ", "LPT3 ", "LPT4 ", |
| "LPT5 ", "LPT6 ", "LPT7 ", "LPT8 ", "LPT9 ", |
| NULL |
| }; |
| |
| /*======================================================================*/ |
| /* Local Function Definitions */ |
| /*======================================================================*/ |
| |
| /* |
| * File System Management Functions |
| */ |
| |
| static s32 check_type_size(void) |
| { |
| /* critical check for system requirement on size of DENTRY_T structure */ |
| if (sizeof(DENTRY_T) != DENTRY_SIZE) |
| return -EINVAL; |
| |
| if (sizeof(DOS_DENTRY_T) != DENTRY_SIZE) |
| return -EINVAL; |
| |
| if (sizeof(EXT_DENTRY_T) != DENTRY_SIZE) |
| return -EINVAL; |
| |
| if (sizeof(FILE_DENTRY_T) != DENTRY_SIZE) |
| return -EINVAL; |
| |
| if (sizeof(STRM_DENTRY_T) != DENTRY_SIZE) |
| return -EINVAL; |
| |
| if (sizeof(NAME_DENTRY_T) != DENTRY_SIZE) |
| return -EINVAL; |
| |
| if (sizeof(BMAP_DENTRY_T) != DENTRY_SIZE) |
| return -EINVAL; |
| |
| if (sizeof(CASE_DENTRY_T) != DENTRY_SIZE) |
| return -EINVAL; |
| |
| if (sizeof(VOLM_DENTRY_T) != DENTRY_SIZE) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static s32 __fs_set_vol_flags(struct super_block *sb, u16 new_flag, s32 always_sync) |
| { |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| s32 err; |
| s32 sync = 0; |
| |
| /* flags are not changed */ |
| if (fsi->vol_flag == new_flag) |
| return 0; |
| |
| fsi->vol_flag = new_flag; |
| |
| /* skip updating volume dirty flag, |
| * if this volume has been mounted with read-only |
| */ |
| if (sb_rdonly(sb)) |
| return 0; |
| |
| if (!fsi->pbr_bh) { |
| err = read_sect(sb, 0, &(fsi->pbr_bh), 1); |
| if (err) { |
| EMSG("%s : failed to read boot sector\n", __func__); |
| return err; |
| } |
| } |
| |
| if (fsi->vol_type == EXFAT) { |
| pbr64_t *bpb = (pbr64_t *)fsi->pbr_bh->b_data; |
| bpb->bsx.vol_flags = cpu_to_le16(new_flag); |
| } else if (fsi->vol_type == FAT32) { |
| pbr32_t *bpb = (pbr32_t *)fsi->pbr_bh->b_data; |
| bpb->bsx.state = new_flag & VOL_DIRTY ? FAT_VOL_DIRTY : 0x00; |
| } else { /* FAT16/12 */ |
| pbr16_t *bpb = (pbr16_t *) fsi->pbr_bh->b_data; |
| bpb->bpb.f16.state = new_flag & VOL_DIRTY ? |
| FAT_VOL_DIRTY : 0x00; |
| } |
| |
| if (always_sync) |
| sync = 1; |
| else if ((new_flag == VOL_DIRTY) && (!buffer_dirty(fsi->pbr_bh))) |
| sync = 1; |
| else |
| sync = 0; |
| |
| err = write_sect(sb, 0, fsi->pbr_bh, sync); |
| if (err) |
| EMSG("%s : failed to modify volume flag\n", __func__); |
| |
| return err; |
| } |
| |
| static s32 fs_set_vol_flags(struct super_block *sb, u16 new_flag) |
| { |
| return __fs_set_vol_flags(sb, new_flag, 0); |
| } |
| |
| s32 fscore_set_vol_flags(struct super_block *sb, u16 new_flag, s32 always_sync) |
| { |
| return __fs_set_vol_flags(sb, new_flag, always_sync); |
| } |
| |
| static inline s32 __fs_meta_sync(struct super_block *sb, s32 do_sync) |
| { |
| #ifdef CONFIG_SDFAT_DELAYED_META_DIRTY |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| |
| if (fsi->vol_type != EXFAT) { |
| MMSG("meta flush in fs_sync(sync=%d)\n", do_sync); |
| fcache_flush(sb, 0); |
| dcache_flush(sb, 0); |
| } |
| #else |
| /* DO NOTHING */ |
| #endif |
| return 0; |
| } |
| |
| static s32 fs_sync(struct super_block *sb, s32 do_sync) |
| { |
| s32 err; |
| |
| if (!do_sync) |
| return 0; |
| |
| err = __fs_meta_sync(sb, do_sync); |
| |
| if (!err) |
| err = bdev_sync_all(sb); |
| |
| if (err) |
| EMSG("%s : failed to sync. (err:%d)\n", __func__, err); |
| |
| return err; |
| } |
| |
| /* |
| * Cluster Management Functions |
| */ |
| |
| static s32 __clear_cluster(struct inode *inode, u32 clu) |
| { |
| u64 s, n; |
| struct super_block *sb = inode->i_sb; |
| u32 sect_size = (u32)sb->s_blocksize; |
| s32 ret = 0; |
| struct buffer_head *tmp_bh = NULL; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| |
| if (IS_CLUS_FREE(clu)) { /* FAT16 root_dir */ |
| s = fsi->root_start_sector; |
| n = fsi->data_start_sector; |
| } else { |
| s = CLUS_TO_SECT(fsi, clu); |
| n = s + fsi->sect_per_clus; |
| } |
| |
| if (IS_DIRSYNC(inode)) { |
| ret = write_msect_zero(sb, s, (u64)fsi->sect_per_clus); |
| if (ret != -EAGAIN) |
| return ret; |
| } |
| |
| /* Trying buffered zero writes |
| * if it doesn't have DIRSYNC or write_msect_zero() returned -EAGAIN |
| */ |
| for ( ; s < n; s++) { |
| #if 0 |
| dcache_release(sb, s); |
| #endif |
| ret = read_sect(sb, s, &tmp_bh, 0); |
| if (ret) |
| goto out; |
| |
| memset((u8 *)tmp_bh->b_data, 0x0, sect_size); |
| ret = write_sect(sb, s, tmp_bh, 0); |
| if (ret) |
| goto out; |
| } |
| out: |
| brelse(tmp_bh); |
| return ret; |
| } /* end of __clear_cluster */ |
| |
| static s32 __find_last_cluster(struct super_block *sb, CHAIN_T *p_chain, u32 *ret_clu) |
| { |
| u32 clu, next; |
| u32 count = 0; |
| |
| next = p_chain->dir; |
| if (p_chain->flags == 0x03) { |
| *ret_clu = next + p_chain->size - 1; |
| return 0; |
| } |
| |
| do { |
| count++; |
| clu = next; |
| if (fat_ent_get_safe(sb, clu, &next)) |
| return -EIO; |
| } while (!IS_CLUS_EOF(next)); |
| |
| if (p_chain->size != count) { |
| sdfat_fs_error(sb, "bogus directory size " |
| "(clus : ondisk(%d) != counted(%d))", |
| p_chain->size, count); |
| sdfat_debug_bug_on(1); |
| return -EIO; |
| } |
| |
| *ret_clu = clu; |
| return 0; |
| } |
| |
| |
| static s32 __count_num_clusters(struct super_block *sb, CHAIN_T *p_chain, u32 *ret_count) |
| { |
| u32 i, count; |
| u32 clu; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| |
| if (!p_chain->dir || IS_CLUS_EOF(p_chain->dir)) { |
| *ret_count = 0; |
| return 0; |
| } |
| |
| if (p_chain->flags == 0x03) { |
| *ret_count = p_chain->size; |
| return 0; |
| } |
| |
| clu = p_chain->dir; |
| count = 0; |
| for (i = CLUS_BASE; i < fsi->num_clusters; i++) { |
| count++; |
| if (fat_ent_get_safe(sb, clu, &clu)) |
| return -EIO; |
| if (IS_CLUS_EOF(clu)) |
| break; |
| } |
| |
| *ret_count = count; |
| return 0; |
| } |
| |
| /* |
| * Upcase table Management Functions |
| */ |
| static void free_upcase_table(struct super_block *sb) |
| { |
| u32 i; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| u16 **upcase_table; |
| |
| upcase_table = fsi->vol_utbl; |
| for (i = 0 ; i < UTBL_COL_COUNT ; i++) { |
| /* kfree(NULL) is safe */ |
| kfree(upcase_table[i]); |
| upcase_table[i] = NULL; |
| } |
| |
| /* kfree(NULL) is safe */ |
| kfree(fsi->vol_utbl); |
| fsi->vol_utbl = NULL; |
| } |
| |
| static s32 __load_upcase_table(struct super_block *sb, u64 sector, u64 num_sectors, u32 utbl_checksum) |
| { |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| struct buffer_head *tmp_bh = NULL; |
| u32 sect_size = (u32)sb->s_blocksize; |
| s32 ret = -EIO; |
| u32 i, j; |
| |
| u8 skip = false; |
| u32 index = 0; |
| u32 checksum = 0; |
| u16 **upcase_table = kzalloc((UTBL_COL_COUNT * sizeof(u16 *)), GFP_KERNEL); |
| |
| if (!upcase_table) |
| return -ENOMEM; |
| /* thanks for kzalloc |
| * memset(upcase_table, 0, UTBL_COL_COUNT * sizeof(u16 *)); |
| */ |
| |
| /* trigger read upcase_table ahead */ |
| bdev_readahead(sb, sector, num_sectors); |
| |
| fsi->vol_utbl = upcase_table; |
| num_sectors += sector; |
| |
| while (sector < num_sectors) { |
| ret = read_sect(sb, sector, &tmp_bh, 1); |
| if (ret) { |
| EMSG("%s: failed to read sector(0x%llx)\n", |
| __func__, sector); |
| goto error; |
| } |
| sector++; |
| |
| for (i = 0; i < sect_size && index <= 0xFFFF; i += 2) { |
| /* FIXME : is __le16 ok? */ |
| //u16 uni = le16_to_cpu(((__le16*)(tmp_bh->b_data))[i]); |
| u16 uni = get_unaligned_le16((u8 *)tmp_bh->b_data+i); |
| |
| checksum = ((checksum & 1) ? 0x80000000 : 0) + |
| (checksum >> 1) + *(((u8 *)tmp_bh->b_data)+i); |
| checksum = ((checksum & 1) ? 0x80000000 : 0) + |
| (checksum >> 1) + *(((u8 *)tmp_bh->b_data)+(i+1)); |
| |
| if (skip) { |
| MMSG("skip from 0x%X to 0x%X(amount of 0x%X)\n", |
| index, index+uni, uni); |
| index += uni; |
| skip = false; |
| } else if (uni == index) { |
| index++; |
| } else if (uni == 0xFFFF) { |
| skip = true; |
| } else { /* uni != index , uni != 0xFFFF */ |
| u16 col_index = get_col_index(index); |
| |
| if (!upcase_table[col_index]) { |
| upcase_table[col_index] = |
| kmalloc((UTBL_ROW_COUNT * sizeof(u16)), GFP_KERNEL); |
| if (!upcase_table[col_index]) { |
| EMSG("failed to allocate memory" |
| " for column 0x%X\n", |
| col_index); |
| ret = -ENOMEM; |
| goto error; |
| } |
| |
| for (j = 0; j < UTBL_ROW_COUNT; j++) |
| upcase_table[col_index][j] = (col_index << LOW_INDEX_BIT) | j; |
| } |
| |
| upcase_table[col_index][get_row_index(index)] = uni; |
| index++; |
| } |
| } |
| } |
| if (index >= 0xFFFF && utbl_checksum == checksum) { |
| DMSG("%s: load upcase table successfully" |
| "(idx:0x%08x, utbl_chksum:0x%08x)\n", |
| __func__, index, utbl_checksum); |
| if (tmp_bh) |
| brelse(tmp_bh); |
| return 0; |
| } |
| |
| EMSG("%s: failed to load upcase table" |
| "(idx:0x%08x, chksum:0x%08x, utbl_chksum:0x%08x)\n", |
| __func__, index, checksum, utbl_checksum); |
| |
| ret = -EINVAL; |
| error: |
| if (tmp_bh) |
| brelse(tmp_bh); |
| free_upcase_table(sb); |
| return ret; |
| } |
| |
| static s32 __load_default_upcase_table(struct super_block *sb) |
| { |
| s32 i, ret = -EIO; |
| u32 j; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| |
| u8 skip = false; |
| u32 index = 0; |
| u16 uni = 0; |
| u16 **upcase_table; |
| |
| upcase_table = kmalloc((UTBL_COL_COUNT * sizeof(u16 *)), GFP_KERNEL); |
| if (!upcase_table) |
| return -ENOMEM; |
| |
| fsi->vol_utbl = upcase_table; |
| memset(upcase_table, 0, UTBL_COL_COUNT * sizeof(u16 *)); |
| |
| for (i = 0; index <= 0xFFFF && i < SDFAT_NUM_UPCASE*2; i += 2) { |
| /* FIXME : is __le16 ok? */ |
| //uni = le16_to_cpu(((__le16*)uni_def_upcase)[i>>1]); |
| uni = get_unaligned_le16((u8 *)uni_def_upcase+i); |
| if (skip) { |
| MMSG("skip from 0x%x ", index); |
| index += uni; |
| MMSG("to 0x%x (amount of 0x%x)\n", index, uni); |
| skip = false; |
| } else if (uni == index) { |
| index++; |
| } else if (uni == 0xFFFF) { |
| skip = true; |
| } else { /* uni != index , uni != 0xFFFF */ |
| u16 col_index = get_col_index(index); |
| |
| if (!upcase_table[col_index]) { |
| upcase_table[col_index] = kmalloc((UTBL_ROW_COUNT * sizeof(u16)), GFP_KERNEL); |
| if (!upcase_table[col_index]) { |
| EMSG("failed to allocate memory for " |
| "new column 0x%x\n", col_index); |
| ret = -ENOMEM; |
| goto error; |
| } |
| |
| for (j = 0; j < UTBL_ROW_COUNT; j++) |
| upcase_table[col_index][j] = (col_index << LOW_INDEX_BIT) | j; |
| } |
| |
| upcase_table[col_index][get_row_index(index)] = uni; |
| index++; |
| } |
| } |
| |
| if (index >= 0xFFFF) |
| return 0; |
| |
| error: |
| /* FATAL error: default upcase table has error */ |
| free_upcase_table(sb); |
| return ret; |
| } |
| |
| static s32 load_upcase_table(struct super_block *sb) |
| { |
| s32 i, ret; |
| u32 tbl_clu, type; |
| u64 sector, tbl_size, num_sectors; |
| u8 blksize_bits = sb->s_blocksize_bits; |
| CHAIN_T clu; |
| CASE_DENTRY_T *ep; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| |
| clu.dir = fsi->root_dir; |
| clu.flags = 0x01; |
| |
| if (fsi->vol_type != EXFAT) |
| goto load_default; |
| |
| while (!IS_CLUS_EOF(clu.dir)) { |
| for (i = 0; i < fsi->dentries_per_clu; i++) { |
| ep = (CASE_DENTRY_T *) get_dentry_in_dir(sb, &clu, i, NULL); |
| if (!ep) |
| return -EIO; |
| |
| type = fsi->fs_func->get_entry_type((DENTRY_T *) ep); |
| |
| if (type == TYPE_UNUSED) |
| break; |
| if (type != TYPE_UPCASE) |
| continue; |
| |
| tbl_clu = le32_to_cpu(ep->start_clu); |
| tbl_size = le64_to_cpu(ep->size); |
| |
| sector = CLUS_TO_SECT(fsi, tbl_clu); |
| num_sectors = ((tbl_size - 1) >> blksize_bits) + 1; |
| ret = __load_upcase_table(sb, sector, num_sectors, |
| le32_to_cpu(ep->checksum)); |
| |
| if (ret && (ret != -EIO)) |
| goto load_default; |
| |
| /* load successfully */ |
| return ret; |
| } |
| |
| if (get_next_clus_safe(sb, &(clu.dir))) |
| return -EIO; |
| } |
| |
| load_default: |
| sdfat_log_msg(sb, KERN_INFO, "trying to load default upcase table"); |
| /* load default upcase table */ |
| return __load_default_upcase_table(sb); |
| } /* end of load_upcase_table */ |
| |
| |
| /* |
| * Directory Entry Management Functions |
| */ |
| s32 walk_fat_chain(struct super_block *sb, CHAIN_T *p_dir, u32 byte_offset, u32 *clu) |
| { |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| u32 clu_offset; |
| u32 cur_clu; |
| |
| clu_offset = byte_offset >> fsi->cluster_size_bits; |
| cur_clu = p_dir->dir; |
| |
| if (p_dir->flags == 0x03) { |
| cur_clu += clu_offset; |
| } else { |
| while (clu_offset > 0) { |
| if (get_next_clus_safe(sb, &cur_clu)) |
| return -EIO; |
| if (IS_CLUS_EOF(cur_clu)) { |
| sdfat_fs_error(sb, "invalid dentry access " |
| "beyond EOF (clu : %u, eidx : %d)", |
| p_dir->dir, |
| byte_offset >> DENTRY_SIZE_BITS); |
| return -EIO; |
| } |
| clu_offset--; |
| } |
| } |
| |
| if (clu) |
| *clu = cur_clu; |
| return 0; |
| } |
| |
| static s32 find_location(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u64 *sector, s32 *offset) |
| { |
| s32 ret; |
| u32 off, clu = 0; |
| u32 blksize_mask = (u32)(sb->s_blocksize-1); |
| u8 blksize_bits = sb->s_blocksize_bits; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| |
| off = entry << DENTRY_SIZE_BITS; |
| |
| /* FAT16 root_dir */ |
| if (IS_CLUS_FREE(p_dir->dir)) { |
| *offset = off & blksize_mask; |
| *sector = off >> blksize_bits; |
| *sector += fsi->root_start_sector; |
| return 0; |
| } |
| |
| ret = walk_fat_chain(sb, p_dir, off, &clu); |
| if (ret) |
| return ret; |
| |
| /* byte offset in cluster */ |
| off &= (fsi->cluster_size - 1); |
| |
| /* byte offset in sector */ |
| *offset = off & blksize_mask; |
| |
| /* sector offset in cluster */ |
| *sector = off >> blksize_bits; |
| *sector += CLUS_TO_SECT(fsi, clu); |
| return 0; |
| } /* end of find_location */ |
| |
| DENTRY_T *get_dentry_in_dir(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u64 *sector) |
| { |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| u32 dentries_per_page = PAGE_SIZE >> DENTRY_SIZE_BITS; |
| s32 off; |
| u64 sec; |
| u8 *buf; |
| |
| if (p_dir->dir == DIR_DELETED) { |
| EMSG("%s : abnormal access to deleted dentry\n", __func__); |
| BUG_ON(!fsi->prev_eio); |
| return NULL; |
| } |
| |
| if (find_location(sb, p_dir, entry, &sec, &off)) |
| return NULL; |
| |
| /* DIRECTORY READAHEAD : |
| * Try to read ahead per a page except root directory of fat12/16 |
| */ |
| if ((!IS_CLUS_FREE(p_dir->dir)) && |
| !(entry & (dentries_per_page - 1))) |
| dcache_readahead(sb, sec); |
| |
| buf = dcache_getblk(sb, sec); |
| if (!buf) |
| return NULL; |
| |
| if (sector) |
| *sector = sec; |
| return (DENTRY_T *)(buf + off); |
| } /* end of get_dentry_in_dir */ |
| |
| /* used only in search empty_slot() */ |
| #define CNT_UNUSED_NOHIT (-1) |
| #define CNT_UNUSED_HIT (-2) |
| /* search EMPTY CONTINUOUS "num_entries" entries */ |
| static s32 search_empty_slot(struct super_block *sb, HINT_FEMP_T *hint_femp, CHAIN_T *p_dir, s32 num_entries) |
| { |
| s32 i, dentry, num_empty = 0; |
| s32 dentries_per_clu; |
| u32 type; |
| CHAIN_T clu; |
| DENTRY_T *ep; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| |
| if (IS_CLUS_FREE(p_dir->dir)) /* FAT16 root_dir */ |
| dentries_per_clu = fsi->dentries_in_root; |
| else |
| dentries_per_clu = fsi->dentries_per_clu; |
| |
| ASSERT(-1 <= hint_femp->eidx); |
| |
| if (hint_femp->eidx != -1) { |
| clu.dir = hint_femp->cur.dir; |
| clu.size = hint_femp->cur.size; |
| clu.flags = hint_femp->cur.flags; |
| |
| dentry = hint_femp->eidx; |
| |
| if (num_entries <= hint_femp->count) { |
| MMSG("%s: empty slot(HIT) - found " |
| "(clu : 0x%08x eidx : %d)\n", |
| __func__, hint_femp->cur.dir, hint_femp->eidx); |
| hint_femp->eidx = -1; |
| |
| if (fsi->vol_type == EXFAT) |
| return dentry; |
| |
| return dentry + (num_entries - 1); |
| } |
| MMSG("%s: empty slot(HIT) - search from " |
| "(clu : 0x%08x eidx : %d)\n", |
| __func__, hint_femp->cur.dir, hint_femp->eidx); |
| } else { |
| MMSG("%s: empty slot(MISS) - search from " |
| "(clu:0x%08x eidx : 0)\n", |
| __func__, p_dir->dir); |
| |
| clu.dir = p_dir->dir; |
| clu.size = p_dir->size; |
| clu.flags = p_dir->flags; |
| |
| dentry = 0; |
| } |
| |
| while (!IS_CLUS_EOF(clu.dir)) { |
| /* FAT16 root_dir */ |
| if (IS_CLUS_FREE(p_dir->dir)) |
| i = dentry % dentries_per_clu; |
| else |
| i = dentry & (dentries_per_clu-1); |
| |
| for ( ; i < dentries_per_clu; i++, dentry++) { |
| ep = get_dentry_in_dir(sb, &clu, i, NULL); |
| if (!ep) |
| return -EIO; |
| |
| type = fsi->fs_func->get_entry_type(ep); |
| |
| if ((type == TYPE_UNUSED) || (type == TYPE_DELETED)) { |
| num_empty++; |
| if (hint_femp->eidx == -1) { |
| hint_femp->eidx = dentry; |
| hint_femp->count = CNT_UNUSED_NOHIT; |
| |
| hint_femp->cur.dir = clu.dir; |
| hint_femp->cur.size = clu.size; |
| hint_femp->cur.flags = clu.flags; |
| } |
| |
| if ((type == TYPE_UNUSED) && |
| (hint_femp->count != CNT_UNUSED_HIT)) { |
| hint_femp->count = CNT_UNUSED_HIT; |
| } |
| } else { |
| if ((hint_femp->eidx != -1) && |
| (hint_femp->count == CNT_UNUSED_HIT)) { |
| /* unused empty group means |
| * an empty group which includes |
| * unused dentry |
| */ |
| sdfat_fs_error(sb, |
| "found bogus dentry(%d) " |
| "beyond unused empty group(%d) " |
| "(start_clu : %u, cur_clu : %u)", |
| dentry, hint_femp->eidx, p_dir->dir, |
| clu.dir); |
| return -EIO; |
| } |
| |
| num_empty = 0; |
| hint_femp->eidx = -1; |
| } |
| |
| if (num_empty >= num_entries) { |
| /* found and invalidate hint_femp */ |
| hint_femp->eidx = -1; |
| |
| if (fsi->vol_type == EXFAT) |
| return (dentry - (num_entries-1)); |
| |
| return dentry; |
| } |
| } |
| |
| if (IS_CLUS_FREE(p_dir->dir)) |
| break; /* FAT16 root_dir */ |
| |
| if (clu.flags == 0x03) { |
| if ((--clu.size) > 0) |
| clu.dir++; |
| else |
| clu.dir = CLUS_EOF; |
| } else { |
| if (get_next_clus_safe(sb, &(clu.dir))) |
| return -EIO; |
| } |
| } |
| |
| return -ENOSPC; |
| } /* end of search_empty_slot */ |
| |
| /* find empty directory entry. |
| * if there isn't any empty slot, expand cluster chain. |
| */ |
| static s32 find_empty_entry(struct inode *inode, CHAIN_T *p_dir, s32 num_entries) |
| { |
| s32 dentry; |
| u32 ret, last_clu; |
| u64 sector; |
| u64 size = 0; |
| CHAIN_T clu; |
| DENTRY_T *ep = NULL; |
| struct super_block *sb = inode->i_sb; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| FILE_ID_T *fid = &(SDFAT_I(inode)->fid); |
| HINT_FEMP_T hint_femp; |
| |
| hint_femp.eidx = -1; |
| |
| ASSERT(-1 <= fid->hint_femp.eidx); |
| |
| if (fid->hint_femp.eidx != -1) { |
| memcpy(&hint_femp, &fid->hint_femp, sizeof(HINT_FEMP_T)); |
| fid->hint_femp.eidx = -1; |
| } |
| |
| /* FAT16 root_dir */ |
| if (IS_CLUS_FREE(p_dir->dir)) |
| return search_empty_slot(sb, &hint_femp, p_dir, num_entries); |
| |
| while ((dentry = search_empty_slot(sb, &hint_femp, p_dir, num_entries)) < 0) { |
| if (dentry == -EIO) |
| break; |
| |
| if (fsi->fs_func->check_max_dentries(fid)) |
| return -ENOSPC; |
| |
| /* we trust p_dir->size regardless of FAT type */ |
| if (__find_last_cluster(sb, p_dir, &last_clu)) |
| return -EIO; |
| |
| /* |
| * Allocate new cluster to this directory |
| */ |
| clu.dir = last_clu + 1; |
| clu.size = 0; /* UNUSED */ |
| clu.flags = p_dir->flags; |
| |
| /* (0) check if there are reserved clusters |
| * (create_dir 의 주석 참고) |
| */ |
| if (!IS_CLUS_EOF(fsi->used_clusters) && |
| ((fsi->used_clusters + fsi->reserved_clusters) >= (fsi->num_clusters - 2))) |
| return -ENOSPC; |
| |
| /* (1) allocate a cluster */ |
| ret = fsi->fs_func->alloc_cluster(sb, 1, &clu, ALLOC_HOT); |
| if (ret) |
| return ret; |
| |
| if (__clear_cluster(inode, clu.dir)) |
| return -EIO; |
| |
| /* (2) append to the FAT chain */ |
| if (clu.flags != p_dir->flags) { |
| /* no-fat-chain bit is disabled, |
| * so fat-chain should be synced with alloc-bmp |
| */ |
| chain_cont_cluster(sb, p_dir->dir, p_dir->size); |
| p_dir->flags = 0x01; |
| hint_femp.cur.flags = 0x01; |
| } |
| |
| if (clu.flags == 0x01) |
| if (fat_ent_set(sb, last_clu, clu.dir)) |
| return -EIO; |
| |
| if (hint_femp.eidx == -1) { |
| /* the special case that new dentry |
| * should be allocated from the start of new cluster |
| */ |
| hint_femp.eidx = (s32)(p_dir->size << |
| (fsi->cluster_size_bits - DENTRY_SIZE_BITS)); |
| hint_femp.count = fsi->dentries_per_clu; |
| |
| hint_femp.cur.dir = clu.dir; |
| hint_femp.cur.size = 0; |
| hint_femp.cur.flags = clu.flags; |
| } |
| hint_femp.cur.size++; |
| p_dir->size++; |
| size = (p_dir->size << fsi->cluster_size_bits); |
| |
| /* (3) update the directory entry */ |
| if ((fsi->vol_type == EXFAT) && (p_dir->dir != fsi->root_dir)) { |
| ep = get_dentry_in_dir(sb, |
| &(fid->dir), fid->entry+1, §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.fat.num_fat_sectors)) |
| return false; |
| return true; |
| } |
| |
| inline pbr_t *read_pbr_with_logical_sector(struct super_block *sb, struct buffer_head **prev_bh) |
| { |
| pbr_t *p_pbr = (pbr_t *) (*prev_bh)->b_data; |
| u16 logical_sect = 0; |
| |
| if (is_exfat(p_pbr)) |
| logical_sect = 1 << p_pbr->bsx.f64.sect_size_bits; |
| else |
| logical_sect = get_unaligned_le16(&p_pbr->bpb.fat.sect_size); |
| |
| /* is x a power of 2? |
| * (x) != 0 && (((x) & ((x) - 1)) == 0) |
| */ |
| if (!is_power_of_2(logical_sect) |
| || (logical_sect < 512) |
| || (logical_sect > 4096)) { |
| sdfat_log_msg(sb, KERN_ERR, "bogus logical sector size %u", |
| logical_sect); |
| return NULL; |
| } |
| |
| if (logical_sect < sb->s_blocksize) { |
| sdfat_log_msg(sb, KERN_ERR, |
| "logical sector size too small for device" |
| " (logical sector size = %u)", logical_sect); |
| return NULL; |
| } |
| |
| if (logical_sect > sb->s_blocksize) { |
| struct buffer_head *bh = NULL; |
| |
| __brelse(*prev_bh); |
| *prev_bh = NULL; |
| |
| if (!sb_set_blocksize(sb, logical_sect)) { |
| sdfat_log_msg(sb, KERN_ERR, |
| "unable to set blocksize %u", logical_sect); |
| return NULL; |
| } |
| bh = sb_bread(sb, 0); |
| if (!bh) { |
| sdfat_log_msg(sb, KERN_ERR, |
| "unable to read boot sector " |
| "(logical sector size = %lu)", sb->s_blocksize); |
| return NULL; |
| } |
| |
| *prev_bh = bh; |
| p_pbr = (pbr_t *) bh->b_data; |
| } |
| |
| sdfat_log_msg(sb, KERN_INFO, |
| "set logical sector size : %lu", sb->s_blocksize); |
| |
| return p_pbr; |
| } |
| |
| /* mount the file system volume */ |
| s32 fscore_mount(struct super_block *sb) |
| { |
| s32 ret; |
| pbr_t *p_pbr; |
| struct buffer_head *tmp_bh = NULL; |
| struct gendisk *disk = sb->s_bdev->bd_disk; |
| struct sdfat_mount_options *opts = &(SDFAT_SB(sb)->options); |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| |
| /* initialize previous I/O error */ |
| fsi->prev_eio = 0; |
| |
| /* open the block device */ |
| if (bdev_open_dev(sb)) |
| return -EIO; |
| |
| /* set block size to read super block */ |
| sb_min_blocksize(sb, 512); |
| |
| /* read boot sector */ |
| ret = read_sect(sb, 0, &tmp_bh, 1); |
| if (ret) { |
| sdfat_log_msg(sb, KERN_ERR, "unable to read boot sector"); |
| ret = -EIO; |
| goto bd_close; |
| } |
| |
| /* PRB is read */ |
| p_pbr = (pbr_t *) tmp_bh->b_data; |
| |
| /* check the validity of PBR */ |
| if (le16_to_cpu((p_pbr->signature)) != PBR_SIGNATURE) { |
| sdfat_log_msg(sb, KERN_ERR, "invalid boot record signature"); |
| brelse(tmp_bh); |
| ret = -EINVAL; |
| goto bd_close; |
| } |
| |
| /* check logical sector size */ |
| p_pbr = read_pbr_with_logical_sector(sb, &tmp_bh); |
| if (!p_pbr) { |
| brelse(tmp_bh); |
| ret = -EIO; |
| goto bd_close; |
| } |
| |
| /* fill fs_struct */ |
| if (is_exfat(p_pbr)) { |
| if (opts->fs_type && opts->fs_type != FS_TYPE_EXFAT) { |
| sdfat_log_msg(sb, KERN_ERR, |
| "not specified filesystem type " |
| "(media:exfat, opts:%s)", |
| FS_TYPE_STR[opts->fs_type]); |
| ret = -EINVAL; |
| goto free_bh; |
| } |
| /* set maximum file size for exFAT */ |
| sb->s_maxbytes = 0x7fffffffffffffffLL; |
| opts->improved_allocation = 0; |
| opts->defrag = 0; |
| ret = mount_exfat(sb, p_pbr); |
| } else { |
| if (opts->fs_type && opts->fs_type != FS_TYPE_VFAT) { |
| sdfat_log_msg(sb, KERN_ERR, |
| "not specified filesystem type " |
| "(media:vfat, opts:%s)", |
| FS_TYPE_STR[opts->fs_type]); |
| ret = -EINVAL; |
| goto free_bh; |
| } |
| /* set maximum file size for FAT */ |
| sb->s_maxbytes = 0xffffffff; |
| |
| if (is_fat32(p_pbr)) { |
| ret = mount_fat32(sb, p_pbr); |
| } else { |
| opts->improved_allocation = 0; |
| opts->defrag = 0; |
| ret = mount_fat16(sb, p_pbr); |
| } |
| } |
| free_bh: |
| brelse(tmp_bh); |
| if (ret) { |
| sdfat_log_msg(sb, KERN_ERR, "failed to mount fs-core"); |
| goto bd_close; |
| } |
| |
| /* warn misaligned data data start sector must be a multiple of clu_size */ |
| sdfat_log_msg(sb, KERN_INFO, |
| "detected volume info : %s " |
| "(%04hX-%04hX, bps : %lu, spc : %u, data start : %llu, %s)", |
| sdfat_get_vol_type_str(fsi->vol_type), |
| (fsi->vol_id >> 16) & 0xffff, fsi->vol_id & 0xffff, |
| sb->s_blocksize, fsi->sect_per_clus, fsi->data_start_sector, |
| (fsi->data_start_sector & (fsi->sect_per_clus - 1)) ? |
| "misaligned" : "aligned"); |
| |
| sdfat_log_msg(sb, KERN_INFO, |
| "detected volume size : %llu KB (disk : %llu KB, " |
| "part : %llu KB)", |
| (fsi->num_sectors * (sb->s_blocksize >> SECTOR_SIZE_BITS)) >> 1, |
| disk ? (u64)(get_capacity(disk) >> 1) : 0, |
| (u64)bdev_nr_sectors(sb->s_bdev) >> 1); |
| ret = load_upcase_table(sb); |
| if (ret) { |
| sdfat_log_msg(sb, KERN_ERR, "failed to load upcase table"); |
| goto bd_close; |
| } |
| |
| if (fsi->vol_type != EXFAT) |
| goto update_used_clus; |
| |
| /* allocate-bitmap is only for exFAT */ |
| ret = load_alloc_bmp(sb); |
| if (ret) { |
| sdfat_log_msg(sb, KERN_ERR, "failed to load alloc-bitmap"); |
| goto free_upcase; |
| } |
| |
| update_used_clus: |
| if (fsi->used_clusters == (u32) ~0) { |
| ret = fsi->fs_func->count_used_clusters(sb, &fsi->used_clusters); |
| if (ret) { |
| sdfat_log_msg(sb, KERN_ERR, "failed to scan clusters"); |
| goto free_alloc_bmp; |
| } |
| } |
| |
| return 0; |
| free_alloc_bmp: |
| if (fsi->vol_type == EXFAT) |
| free_alloc_bmp(sb); |
| free_upcase: |
| free_upcase_table(sb); |
| bd_close: |
| bdev_close_dev(sb); |
| return ret; |
| } /* end of fscore_mount */ |
| |
| /* umount the file system volume */ |
| s32 fscore_umount(struct super_block *sb) |
| { |
| s32 ret = 0; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| |
| if (fs_sync(sb, 0)) |
| ret = -EIO; |
| |
| if (fs_set_vol_flags(sb, VOL_CLEAN)) |
| ret = -EIO; |
| |
| free_upcase_table(sb); |
| |
| if (fsi->vol_type == EXFAT) |
| free_alloc_bmp(sb); |
| |
| if (fcache_release_all(sb)) |
| ret = -EIO; |
| |
| if (dcache_release_all(sb)) |
| ret = -EIO; |
| |
| amap_destroy(sb); |
| |
| if (fsi->prev_eio) |
| ret = -EIO; |
| /* close the block device */ |
| bdev_close_dev(sb); |
| return ret; |
| } |
| |
| /* get the information of a file system volume */ |
| s32 fscore_statfs(struct super_block *sb, VOL_INFO_T *info) |
| { |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| |
| if (fsi->used_clusters == (u32) ~0) { |
| if (fsi->fs_func->count_used_clusters(sb, &fsi->used_clusters)) |
| return -EIO; |
| } |
| |
| info->FatType = fsi->vol_type; |
| info->ClusterSize = fsi->cluster_size; |
| info->NumClusters = fsi->num_clusters - 2; /* clu 0 & 1 */ |
| info->UsedClusters = fsi->used_clusters + fsi->reserved_clusters; |
| info->FreeClusters = info->NumClusters - info->UsedClusters; |
| |
| return 0; |
| } |
| |
| /* synchronize all file system volumes */ |
| s32 fscore_sync_fs(struct super_block *sb, s32 do_sync) |
| { |
| /* synchronize the file system */ |
| if (fs_sync(sb, do_sync)) |
| return -EIO; |
| |
| if (fs_set_vol_flags(sb, VOL_CLEAN)) |
| return -EIO; |
| |
| return 0; |
| } |
| |
| /* stat allocation unit of a file system volume */ |
| u32 fscore_get_au_stat(struct super_block *sb, s32 mode) |
| { |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| |
| if (fsi->fs_func->get_au_stat) |
| return fsi->fs_func->get_au_stat(sb, mode); |
| |
| /* No error, just returns 0 */ |
| return 0; |
| } |
| |
| |
| /*----------------------------------------------------------------------*/ |
| /* File Operation Functions */ |
| /*----------------------------------------------------------------------*/ |
| /* lookup a file */ |
| s32 fscore_lookup(struct inode *inode, u8 *path, FILE_ID_T *fid) |
| { |
| s32 ret, dentry, num_entries; |
| CHAIN_T dir; |
| UNI_NAME_T uni_name; |
| DOS_NAME_T dos_name; |
| DENTRY_T *ep, *ep2; |
| ENTRY_SET_CACHE_T *es = NULL; |
| struct super_block *sb = inode->i_sb; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| FILE_ID_T *dir_fid = &(SDFAT_I(inode)->fid); |
| |
| TMSG("%s entered\n", __func__); |
| |
| /* check the validity of directory name in the given pathname */ |
| ret = resolve_path_for_lookup(inode, path, &dir, &uni_name); |
| if (ret) |
| return ret; |
| |
| ret = get_num_entries_and_dos_name(sb, &dir, &uni_name, &num_entries, &dos_name, 1); |
| if (ret) |
| return ret; |
| |
| /* check the validation of hint_stat and initialize it if required */ |
| if (dir_fid->version != (u32)inode_peek_iversion(inode)) { |
| dir_fid->hint_stat.clu = dir.dir; |
| dir_fid->hint_stat.eidx = 0; |
| dir_fid->version = (u32)inode_peek_iversion(inode); |
| dir_fid->hint_femp.eidx = -1; |
| } |
| |
| /* search the file name for directories */ |
| dentry = fsi->fs_func->find_dir_entry(sb, dir_fid, &dir, &uni_name, |
| num_entries, &dos_name, TYPE_ALL); |
| |
| if ((dentry < 0) && (dentry != -EEXIST)) |
| return dentry; /* -error value */ |
| |
| fid->dir.dir = dir.dir; |
| fid->dir.size = dir.size; |
| fid->dir.flags = dir.flags; |
| fid->entry = dentry; |
| |
| /* root directory itself */ |
| if (unlikely(dentry == -EEXIST)) { |
| fid->type = TYPE_DIR; |
| fid->rwoffset = 0; |
| fid->hint_bmap.off = CLUS_EOF; |
| |
| fid->attr = ATTR_SUBDIR; |
| fid->flags = 0x01; |
| fid->size = 0; |
| fid->start_clu = fsi->root_dir; |
| } else { |
| if (fsi->vol_type == EXFAT) { |
| es = get_dentry_set_in_dir(sb, &dir, dentry, ES_2_ENTRIES, &ep); |
| if (!es) |
| return -EIO; |
| ep2 = ep+1; |
| } else { |
| ep = get_dentry_in_dir(sb, &dir, dentry, NULL); |
| if (!ep) |
| return -EIO; |
| ep2 = ep; |
| } |
| |
| fid->type = fsi->fs_func->get_entry_type(ep); |
| fid->rwoffset = 0; |
| fid->hint_bmap.off = CLUS_EOF; |
| fid->attr = fsi->fs_func->get_entry_attr(ep); |
| |
| fid->size = fsi->fs_func->get_entry_size(ep2); |
| if ((fid->type == TYPE_FILE) && (fid->size == 0)) { |
| fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01; |
| fid->start_clu = CLUS_EOF; |
| } else { |
| fid->flags = fsi->fs_func->get_entry_flag(ep2); |
| fid->start_clu = fsi->fs_func->get_entry_clu0(ep2); |
| } |
| |
| if ((fid->type == TYPE_DIR) && (fsi->vol_type != EXFAT)) { |
| u32 num_clu = 0; |
| CHAIN_T tmp_dir; |
| |
| tmp_dir.dir = fid->start_clu; |
| tmp_dir.flags = fid->flags; |
| tmp_dir.size = 0; /* UNUSED */ |
| |
| if (__count_num_clusters(sb, &tmp_dir, &num_clu)) |
| return -EIO; |
| fid->size = (u64)num_clu << fsi->cluster_size_bits; |
| } |
| |
| /* FOR GRACEFUL ERROR HANDLING */ |
| if (IS_CLUS_FREE(fid->start_clu)) { |
| sdfat_fs_error(sb, |
| "non-zero size file starts with zero cluster " |
| "(size : %llu, p_dir : %u, entry : 0x%08x)", |
| fid->size, fid->dir.dir, fid->entry); |
| sdfat_debug_bug_on(1); |
| return -EIO; |
| } |
| |
| if (fsi->vol_type == EXFAT) |
| release_dentry_set(es); |
| } |
| |
| /* hint_stat will be used if this is directory. */ |
| fid->version = 0; |
| fid->hint_stat.eidx = 0; |
| fid->hint_stat.clu = fid->start_clu; |
| fid->hint_femp.eidx = -1; |
| |
| TMSG("%s exited successfully\n", __func__); |
| return 0; |
| } /* end of fscore_lookup */ |
| |
| /* create a file */ |
| s32 fscore_create(struct inode *inode, u8 *path, u8 mode, FILE_ID_T *fid) |
| { |
| s32 ret/*, dentry*/; |
| CHAIN_T dir; |
| UNI_NAME_T uni_name; |
| struct super_block *sb = inode->i_sb; |
| |
| /* check the validity of directory name in the given pathname */ |
| ret = resolve_path(inode, path, &dir, &uni_name); |
| if (ret) |
| return ret; |
| |
| fs_set_vol_flags(sb, VOL_DIRTY); |
| |
| /* create a new file */ |
| ret = create_file(inode, &dir, &uni_name, mode, fid); |
| |
| fs_sync(sb, 0); |
| fs_set_vol_flags(sb, VOL_CLEAN); |
| |
| return ret; |
| } |
| |
| /* read data from a opened file */ |
| s32 fscore_read_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *rcount) |
| { |
| s32 ret = 0; |
| s32 offset, sec_offset; |
| u32 clu_offset; |
| u32 clu; |
| u64 logsector, oneblkread, read_bytes; |
| struct buffer_head *tmp_bh = NULL; |
| struct super_block *sb = inode->i_sb; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| |
| /* check if the given file ID is opened */ |
| if (fid->type != TYPE_FILE) |
| return -EPERM; |
| |
| if (fid->rwoffset > fid->size) |
| fid->rwoffset = fid->size; |
| |
| if (count > (fid->size - fid->rwoffset)) |
| count = fid->size - fid->rwoffset; |
| |
| if (count == 0) { |
| if (rcount) |
| *rcount = 0; |
| return 0; |
| } |
| |
| read_bytes = 0; |
| |
| while (count > 0) { |
| clu_offset = fid->rwoffset >> fsi->cluster_size_bits; |
| clu = fid->start_clu; |
| |
| if (fid->flags == 0x03) { |
| clu += clu_offset; |
| } else { |
| /* hint information */ |
| if ((clu_offset > 0) && |
| ((fid->hint_bmap.off != CLUS_EOF) && (fid->hint_bmap.off > 0)) && |
| (clu_offset >= fid->hint_bmap.off)) { |
| clu_offset -= fid->hint_bmap.off; |
| clu = fid->hint_bmap.clu; |
| } |
| |
| while (clu_offset > 0) { |
| ret = get_next_clus_safe(sb, &clu); |
| if (ret) |
| goto err_out; |
| |
| clu_offset--; |
| } |
| } |
| |
| /* hint information */ |
| fid->hint_bmap.off = fid->rwoffset >> fsi->cluster_size_bits; |
| fid->hint_bmap.clu = clu; |
| |
| offset = (s32)(fid->rwoffset & (fsi->cluster_size - 1)); /* byte offset in cluster */ |
| sec_offset = offset >> sb->s_blocksize_bits; /* sector offset in cluster */ |
| offset &= (sb->s_blocksize - 1); /* byte offset in sector */ |
| |
| logsector = CLUS_TO_SECT(fsi, clu) + sec_offset; |
| |
| oneblkread = (u64)(sb->s_blocksize - offset); |
| if (oneblkread > count) |
| oneblkread = count; |
| |
| if ((offset == 0) && (oneblkread == sb->s_blocksize)) { |
| ret = read_sect(sb, logsector, &tmp_bh, 1); |
| if (ret) |
| goto err_out; |
| memcpy(((s8 *) buffer)+read_bytes, ((s8 *) tmp_bh->b_data), (s32) oneblkread); |
| } else { |
| ret = read_sect(sb, logsector, &tmp_bh, 1); |
| if (ret) |
| goto err_out; |
| memcpy(((s8 *) buffer)+read_bytes, ((s8 *) tmp_bh->b_data)+offset, (s32) oneblkread); |
| } |
| count -= oneblkread; |
| read_bytes += oneblkread; |
| fid->rwoffset += oneblkread; |
| } |
| |
| err_out: |
| brelse(tmp_bh); |
| |
| /* set the size of read bytes */ |
| if (rcount != NULL) |
| *rcount = read_bytes; |
| |
| return ret; |
| } /* end of fscore_read_link */ |
| |
| /* write data into a opened file */ |
| s32 fscore_write_link(struct inode *inode, FILE_ID_T *fid, void *buffer, u64 count, u64 *wcount) |
| { |
| s32 ret = 0; |
| s32 modified = false, offset, sec_offset; |
| u32 clu_offset, num_clusters, num_alloc; |
| u32 clu, last_clu; |
| u64 logsector, sector, oneblkwrite, write_bytes; |
| CHAIN_T new_clu; |
| TIMESTAMP_T tm; |
| DENTRY_T *ep, *ep2; |
| ENTRY_SET_CACHE_T *es = NULL; |
| struct buffer_head *tmp_bh = NULL; |
| struct super_block *sb = inode->i_sb; |
| u32 blksize = (u32)sb->s_blocksize; |
| u32 blksize_mask = (u32)(sb->s_blocksize-1); |
| u8 blksize_bits = sb->s_blocksize_bits; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| |
| /* check if the given file ID is opened */ |
| if (fid->type != TYPE_FILE) |
| return -EPERM; |
| |
| if (fid->rwoffset > fid->size) |
| fid->rwoffset = fid->size; |
| |
| if (count == 0) { |
| if (wcount) |
| *wcount = 0; |
| return 0; |
| } |
| |
| fs_set_vol_flags(sb, VOL_DIRTY); |
| |
| if (fid->size == 0) |
| num_clusters = 0; |
| else |
| num_clusters = ((fid->size-1) >> fsi->cluster_size_bits) + 1; |
| |
| write_bytes = 0; |
| |
| while (count > 0) { |
| clu_offset = (fid->rwoffset >> fsi->cluster_size_bits); |
| clu = last_clu = fid->start_clu; |
| |
| if (fid->flags == 0x03) { |
| if ((clu_offset > 0) && (!IS_CLUS_EOF(clu))) { |
| last_clu += clu_offset - 1; |
| |
| if (clu_offset == num_clusters) |
| clu = CLUS_EOF; |
| else |
| clu += clu_offset; |
| } |
| } else { |
| /* hint information */ |
| if ((clu_offset > 0) && |
| ((fid->hint_bmap.off != CLUS_EOF) && (fid->hint_bmap.off > 0)) && |
| (clu_offset >= fid->hint_bmap.off)) { |
| clu_offset -= fid->hint_bmap.off; |
| clu = fid->hint_bmap.clu; |
| } |
| |
| while ((clu_offset > 0) && (!IS_CLUS_EOF(clu))) { |
| last_clu = clu; |
| ret = get_next_clus_safe(sb, &clu); |
| if (ret) |
| goto err_out; |
| |
| clu_offset--; |
| } |
| } |
| |
| if (IS_CLUS_EOF(clu)) { |
| num_alloc = ((count-1) >> fsi->cluster_size_bits) + 1; |
| new_clu.dir = IS_CLUS_EOF(last_clu) ? CLUS_EOF : last_clu+1; |
| new_clu.size = 0; |
| new_clu.flags = fid->flags; |
| |
| /* (1) allocate a chain of clusters */ |
| ret = fsi->fs_func->alloc_cluster(sb, num_alloc, &new_clu, ALLOC_COLD); |
| if (ret) |
| goto err_out; |
| |
| /* (2) append to the FAT chain */ |
| if (IS_CLUS_EOF(last_clu)) { |
| if (new_clu.flags == 0x01) |
| fid->flags = 0x01; |
| fid->start_clu = new_clu.dir; |
| modified = true; |
| } else { |
| if (new_clu.flags != fid->flags) { |
| /* no-fat-chain bit is disabled, |
| * so fat-chain should be synced with |
| * alloc-bmp |
| */ |
| chain_cont_cluster(sb, fid->start_clu, num_clusters); |
| fid->flags = 0x01; |
| modified = true; |
| } |
| if (new_clu.flags == 0x01) { |
| ret = fat_ent_set(sb, last_clu, new_clu.dir); |
| if (ret) |
| goto err_out; |
| } |
| } |
| |
| num_clusters += num_alloc; |
| clu = new_clu.dir; |
| } |
| |
| /* hint information */ |
| fid->hint_bmap.off = fid->rwoffset >> fsi->cluster_size_bits; |
| fid->hint_bmap.clu = clu; |
| |
| /* byte offset in cluster */ |
| offset = (s32)(fid->rwoffset & (fsi->cluster_size-1)); |
| /* sector offset in cluster */ |
| sec_offset = offset >> blksize_bits; |
| /* byte offset in sector */ |
| offset &= blksize_mask; |
| logsector = CLUS_TO_SECT(fsi, clu) + sec_offset; |
| |
| oneblkwrite = (u64)(blksize - offset); |
| if (oneblkwrite > count) |
| oneblkwrite = count; |
| |
| if ((offset == 0) && (oneblkwrite == blksize)) { |
| ret = read_sect(sb, logsector, &tmp_bh, 0); |
| if (ret) |
| goto err_out; |
| |
| memcpy(((s8 *)tmp_bh->b_data), |
| ((s8 *)buffer)+write_bytes, |
| (s32)oneblkwrite); |
| |
| ret = write_sect(sb, logsector, tmp_bh, 0); |
| if (ret) { |
| brelse(tmp_bh); |
| goto err_out; |
| } |
| } else { |
| if ((offset > 0) || ((fid->rwoffset+oneblkwrite) < fid->size)) { |
| ret = read_sect(sb, logsector, &tmp_bh, 1); |
| if (ret) |
| goto err_out; |
| } else { |
| ret = read_sect(sb, logsector, &tmp_bh, 0); |
| if (ret) |
| goto err_out; |
| } |
| |
| memcpy(((s8 *) tmp_bh->b_data)+offset, ((s8 *) buffer)+write_bytes, (s32) oneblkwrite); |
| ret = write_sect(sb, logsector, tmp_bh, 0); |
| if (ret) { |
| brelse(tmp_bh); |
| goto err_out; |
| } |
| } |
| |
| count -= oneblkwrite; |
| write_bytes += oneblkwrite; |
| fid->rwoffset += oneblkwrite; |
| |
| fid->attr |= ATTR_ARCHIVE; |
| |
| if (fid->size < fid->rwoffset) { |
| fid->size = fid->rwoffset; |
| modified = true; |
| } |
| } |
| |
| brelse(tmp_bh); |
| |
| /* (3) update the direcoty entry */ |
| /* get_entry_(set_)in_dir shoulb be check DIR_DELETED flag. */ |
| if (fsi->vol_type == EXFAT) { |
| es = get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep); |
| if (!es) { |
| ret = -EIO; |
| goto err_out; |
| } |
| ep2 = ep+1; |
| } else { |
| ep = get_dentry_in_dir(sb, &(fid->dir), fid->entry, §or); |
| if (!ep) { |
| ret = -EIO; |
| goto err_out; |
| } |
| ep2 = ep; |
| } |
| |
| fsi->fs_func->set_entry_time(ep, tm_now(inode, &tm), TM_MODIFY); |
| fsi->fs_func->set_entry_attr(ep, fid->attr); |
| |
| if (modified) { |
| if (fsi->fs_func->get_entry_flag(ep2) != fid->flags) |
| fsi->fs_func->set_entry_flag(ep2, fid->flags); |
| |
| if (fsi->fs_func->get_entry_size(ep2) != fid->size) |
| fsi->fs_func->set_entry_size(ep2, fid->size); |
| |
| if (fsi->fs_func->get_entry_clu0(ep2) != fid->start_clu) |
| fsi->fs_func->set_entry_clu0(ep2, fid->start_clu); |
| } |
| |
| if (fsi->vol_type == EXFAT) { |
| if (update_dir_chksum_with_entry_set(sb, es)) { |
| ret = -EIO; |
| goto err_out; |
| } |
| release_dentry_set(es); |
| } else { |
| if (dcache_modify(sb, sector)) { |
| ret = -EIO; |
| goto err_out; |
| } |
| } |
| |
| fs_sync(sb, 0); |
| fs_set_vol_flags(sb, VOL_CLEAN); |
| |
| err_out: |
| /* set the size of written bytes */ |
| if (wcount) |
| *wcount = write_bytes; |
| |
| return ret; |
| } /* end of fscore_write_link */ |
| |
| /* resize the file length */ |
| s32 fscore_truncate(struct inode *inode, u64 old_size, u64 new_size) |
| { |
| u32 num_clusters_new, num_clusters_da, num_clusters_phys; |
| u32 last_clu = CLUS_FREE; |
| u64 sector; |
| CHAIN_T clu; |
| TIMESTAMP_T tm; |
| DENTRY_T *ep, *ep2; |
| struct super_block *sb = inode->i_sb; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| FILE_ID_T *fid = &(SDFAT_I(inode)->fid); |
| ENTRY_SET_CACHE_T *es = NULL; |
| s32 evict = (fid->dir.dir == DIR_DELETED) ? 1 : 0; |
| |
| /* check if the given file ID is opened */ |
| if ((fid->type != TYPE_FILE) && (fid->type != TYPE_DIR)) |
| return -EPERM; |
| |
| /* TO CHECK inode type and size */ |
| MMSG("%s: inode(%p) type(%s) size:%lld->%lld\n", __func__, inode, |
| (fid->type == TYPE_FILE) ? "file" : "dir", old_size, new_size); |
| |
| /* XXX : This is for debugging. */ |
| |
| /* It can be when write failed */ |
| #if 0 |
| if (fid->size != old_size) { |
| DMSG("%s: inode(%p) size-mismatch(old:%lld != fid:%lld)\n", |
| __func__, inode, old_size, fid->size); |
| WARN_ON(1); |
| } |
| #endif |
| /* |
| * There is no lock to protect fid->size. |
| * So, we should get old_size and use it. |
| */ |
| if (old_size <= new_size) |
| return 0; |
| |
| fs_set_vol_flags(sb, VOL_DIRTY); |
| |
| /* Reserved count update */ |
| #define num_clusters(v) ((v) ? (u32)(((v) - 1) >> fsi->cluster_size_bits) + 1 : 0) |
| num_clusters_da = num_clusters(SDFAT_I(inode)->i_size_aligned); |
| num_clusters_new = num_clusters(i_size_read(inode)); |
| num_clusters_phys = num_clusters(SDFAT_I(inode)->i_size_ondisk); |
| |
| /* num_clusters(i_size_old) should be equal to num_clusters_da */ |
| BUG_ON((num_clusters(old_size)) != (num_clusters(SDFAT_I(inode)->i_size_aligned))); |
| |
| /* for debugging (FIXME: is okay on no-da case?) */ |
| BUG_ON(num_clusters_da < num_clusters_phys); |
| |
| if ((num_clusters_da != num_clusters_phys) && |
| (num_clusters_new < num_clusters_da)) { |
| /* Decrement reserved clusters |
| * n_reserved = num_clusters_da - max(new,phys) |
| */ |
| int n_reserved = (num_clusters_new > num_clusters_phys) ? |
| (num_clusters_da - num_clusters_new) : |
| (num_clusters_da - num_clusters_phys); |
| |
| fsi->reserved_clusters -= n_reserved; |
| BUG_ON(fsi->reserved_clusters < 0); |
| } |
| |
| clu.dir = fid->start_clu; |
| /* In no-da case, num_clusters_phys is equal to below value |
| * clu.size = (u32)((old_size-1) >> fsi->cluster_size_bits) + 1; |
| */ |
| clu.size = num_clusters_phys; |
| clu.flags = fid->flags; |
| |
| /* For bigdata */ |
| sdfat_statistics_set_trunc(clu.flags, &clu); |
| |
| if (new_size > 0) { |
| /* Truncate FAT chain num_clusters after the first cluster |
| * num_clusters = min(new, phys); |
| */ |
| u32 num_clusters = (num_clusters_new < num_clusters_phys) ? |
| num_clusters_new : num_clusters_phys; |
| |
| /* Follow FAT chain |
| * (defensive coding - works fine even with corrupted FAT table |
| */ |
| if (clu.flags == 0x03) { |
| clu.dir += num_clusters; |
| clu.size -= num_clusters; |
| #if 0 |
| /* extent_get_clus can`t know last_cluster |
| * when find target cluster in cache. |
| */ |
| } else if (fid->type == TYPE_FILE) { |
| u32 fclus = 0; |
| s32 err = extent_get_clus(inode, num_clusters, |
| &fclus, &(clu.dir), &last_clu, 0); |
| if (err) |
| return -EIO; |
| ASSERT(fclus == num_clusters); |
| |
| if ((num_clusters > 1) && (last_clu == fid->start_clu)) { |
| u32 fclus_tmp = 0; |
| u32 temp = 0; |
| |
| err = extent_get_clus(inode, num_clusters - 1, |
| &fclus_tmp, &last_clu, &temp, 0); |
| if (err) |
| return -EIO; |
| ASSERT(fclus_tmp == (num_clusters - 1)); |
| } |
| |
| num_clusters -= fclus; |
| clu.size -= fclus; |
| #endif |
| } else { |
| while (num_clusters > 0) { |
| last_clu = clu.dir; |
| if (get_next_clus_safe(sb, &(clu.dir))) |
| return -EIO; |
| |
| num_clusters--; |
| clu.size--; |
| } |
| } |
| |
| /* Optimization avialable: */ |
| #if 0 |
| if (num_clusters_new < num_clusters) { |
| < loop > |
| } else { |
| // num_clusters_new >= num_clusters_phys |
| // FAT truncation is not necessary |
| |
| clu.dir = CLUS_EOF; |
| clu.size = 0; |
| } |
| #endif |
| } else if (new_size == 0) { |
| fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01; |
| fid->start_clu = CLUS_EOF; |
| } |
| fid->size = new_size; |
| |
| if (fid->type == TYPE_FILE) |
| fid->attr |= ATTR_ARCHIVE; |
| |
| /* |
| * clu.dir: free from |
| * clu.size: # of clusters to free (exFAT, 0x03 only), no fat_free if 0 |
| * clu.flags: fid->flags (exFAT only) |
| */ |
| |
| /* (1) update the directory entry */ |
| if (!evict) { |
| |
| if (fsi->vol_type == EXFAT) { |
| es = get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_ALL_ENTRIES, &ep); |
| if (!es) |
| return -EIO; |
| ep2 = ep+1; |
| } else { |
| ep = get_dentry_in_dir(sb, &(fid->dir), fid->entry, §or); |
| if (!ep) |
| return -EIO; |
| ep2 = ep; |
| } |
| |
| fsi->fs_func->set_entry_time(ep, tm_now(inode, &tm), TM_MODIFY); |
| fsi->fs_func->set_entry_attr(ep, fid->attr); |
| |
| /* |
| * if (fsi->vol_type != EXFAT) |
| * dcache_modify(sb, sector); |
| */ |
| |
| /* File size should be zero if there is no cluster allocated */ |
| if (IS_CLUS_EOF(fid->start_clu)) |
| fsi->fs_func->set_entry_size(ep2, 0); |
| else |
| fsi->fs_func->set_entry_size(ep2, new_size); |
| |
| if (new_size == 0) { |
| /* Any directory can not be truncated to zero */ |
| BUG_ON(fid->type != TYPE_FILE); |
| |
| fsi->fs_func->set_entry_flag(ep2, 0x01); |
| fsi->fs_func->set_entry_clu0(ep2, CLUS_FREE); |
| } |
| |
| if (fsi->vol_type == EXFAT) { |
| if (update_dir_chksum_with_entry_set(sb, es)) |
| return -EIO; |
| release_dentry_set(es); |
| } else { |
| if (dcache_modify(sb, sector)) |
| return -EIO; |
| } |
| |
| } /* end of if(fid->dir.dir != DIR_DELETED) */ |
| |
| /* (2) cut off from the FAT chain */ |
| if ((fid->flags == 0x01) && |
| (!IS_CLUS_FREE(last_clu)) && (!IS_CLUS_EOF(last_clu))) { |
| if (fat_ent_set(sb, last_clu, CLUS_EOF)) |
| return -EIO; |
| } |
| |
| /* (3) invalidate cache and free the clusters */ |
| /* clear extent cache */ |
| extent_cache_inval_inode(inode); |
| |
| /* hint information */ |
| fid->hint_bmap.off = CLUS_EOF; |
| fid->hint_bmap.clu = CLUS_EOF; |
| if (fid->rwoffset > fid->size) |
| fid->rwoffset = fid->size; |
| |
| /* hint_stat will be used if this is directory. */ |
| fid->hint_stat.eidx = 0; |
| fid->hint_stat.clu = fid->start_clu; |
| fid->hint_femp.eidx = -1; |
| |
| /* free the clusters */ |
| if (fsi->fs_func->free_cluster(sb, &clu, evict)) |
| return -EIO; |
| |
| fs_sync(sb, 0); |
| fs_set_vol_flags(sb, VOL_CLEAN); |
| |
| return 0; |
| } /* end of fscore_truncate */ |
| |
| static void update_parent_info(FILE_ID_T *fid, struct inode *parent_inode) |
| { |
| FS_INFO_T *fsi = &(SDFAT_SB(parent_inode->i_sb)->fsi); |
| FILE_ID_T *parent_fid = &(SDFAT_I(parent_inode)->fid); |
| |
| /* |
| * the problem that FILE_ID_T caches wrong parent info. |
| * |
| * because of flag-mismatch of fid->dir, |
| * there is abnormal traversing cluster chain. |
| */ |
| if (unlikely((parent_fid->flags != fid->dir.flags) |
| || (parent_fid->size != (fid->dir.size<<fsi->cluster_size_bits)) |
| || (parent_fid->start_clu != fid->dir.dir))) { |
| |
| fid->dir.dir = parent_fid->start_clu; |
| fid->dir.flags = parent_fid->flags; |
| fid->dir.size = ((parent_fid->size + (fsi->cluster_size-1)) |
| >> fsi->cluster_size_bits); |
| } |
| } |
| |
| /* rename or move a old file into a new file */ |
| s32 fscore_rename(struct inode *old_parent_inode, FILE_ID_T *fid, |
| struct inode *new_parent_inode, struct dentry *new_dentry) |
| { |
| s32 ret; |
| s32 dentry; |
| CHAIN_T olddir, newdir; |
| CHAIN_T *p_dir = NULL; |
| UNI_NAME_T uni_name; |
| DENTRY_T *ep; |
| struct super_block *sb = old_parent_inode->i_sb; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| u8 *new_path = (u8 *) new_dentry->d_name.name; |
| struct inode *new_inode = new_dentry->d_inode; |
| int num_entries; |
| FILE_ID_T *new_fid = NULL; |
| u32 new_entry_type = TYPE_UNUSED; |
| s32 new_entry = 0; |
| |
| /* check the validity of pointer parameters */ |
| if ((new_path == NULL) || (strlen(new_path) == 0)) |
| return -EINVAL; |
| |
| if (fid->dir.dir == DIR_DELETED) { |
| EMSG("%s : abnormal access to deleted source dentry\n", __func__); |
| return -ENOENT; |
| } |
| |
| /* patch 1.2.4 : the problem that FILE_ID_T caches wrong parent info. */ |
| update_parent_info(fid, old_parent_inode); |
| |
| olddir.dir = fid->dir.dir; |
| olddir.size = fid->dir.size; |
| olddir.flags = fid->dir.flags; |
| |
| dentry = fid->entry; |
| |
| /* check if the old file is "." or ".." */ |
| if (fsi->vol_type != EXFAT) { |
| if ((olddir.dir != fsi->root_dir) && (dentry < 2)) |
| return -EPERM; |
| } |
| |
| ep = get_dentry_in_dir(sb, &olddir, dentry, NULL); |
| if (!ep) |
| return -EIO; |
| |
| #ifdef CONFIG_SDFAT_CHECK_RO_ATTR |
| if (fsi->fs_func->get_entry_attr(ep) & ATTR_READONLY) |
| return -EPERM; |
| #endif |
| |
| /* check whether new dir is existing directory and empty */ |
| if (new_inode) { |
| ret = -EIO; |
| new_fid = &SDFAT_I(new_inode)->fid; |
| |
| if (new_fid->dir.dir == DIR_DELETED) { |
| EMSG("%s : abnormal access to deleted target dentry\n", __func__); |
| goto out; |
| } |
| |
| /* patch 1.2.4 : |
| * the problem that FILE_ID_T caches wrong parent info. |
| * |
| * FIXME : is needed? |
| */ |
| update_parent_info(new_fid, new_parent_inode); |
| |
| p_dir = &(new_fid->dir); |
| new_entry = new_fid->entry; |
| ep = get_dentry_in_dir(sb, p_dir, new_entry, NULL); |
| if (!ep) |
| goto out; |
| |
| new_entry_type = fsi->fs_func->get_entry_type(ep); |
| |
| /* if new_inode exists, update fid */ |
| new_fid->size = i_size_read(new_inode); |
| |
| if (new_entry_type == TYPE_DIR) { |
| CHAIN_T new_clu; |
| |
| new_clu.dir = new_fid->start_clu; |
| new_clu.size = ((new_fid->size-1) >> fsi->cluster_size_bits) + 1; |
| new_clu.flags = new_fid->flags; |
| |
| ret = check_dir_empty(sb, &new_clu); |
| if (ret) |
| return ret; |
| } |
| } |
| |
| /* check the validity of directory name in the given new pathname */ |
| ret = resolve_path(new_parent_inode, new_path, &newdir, &uni_name); |
| if (ret) |
| return ret; |
| |
| fs_set_vol_flags(sb, VOL_DIRTY); |
| |
| if (olddir.dir == newdir.dir) |
| ret = rename_file(new_parent_inode, &olddir, dentry, &uni_name, fid); |
| else |
| ret = move_file(new_parent_inode, &olddir, dentry, &newdir, &uni_name, fid); |
| |
| if ((!ret) && new_inode) { |
| /* delete entries of new_dir */ |
| ep = get_dentry_in_dir(sb, p_dir, new_entry, NULL); |
| if (!ep) { |
| ret = -EIO; |
| goto del_out; |
| } |
| |
| num_entries = fsi->fs_func->count_ext_entries(sb, p_dir, new_entry, ep); |
| if (num_entries < 0) { |
| ret = -EIO; |
| goto del_out; |
| } |
| |
| |
| if (fsi->fs_func->delete_dir_entry(sb, p_dir, new_entry, 0, num_entries+1)) { |
| ret = -EIO; |
| goto del_out; |
| } |
| |
| /* Free the clusters if new_inode is a dir(as if fscore_rmdir) */ |
| if (new_entry_type == TYPE_DIR) { |
| /* new_fid, new_clu_to_free */ |
| CHAIN_T new_clu_to_free; |
| |
| new_clu_to_free.dir = new_fid->start_clu; |
| new_clu_to_free.size = ((new_fid->size-1) >> fsi->cluster_size_bits) + 1; |
| new_clu_to_free.flags = new_fid->flags; |
| |
| if (fsi->fs_func->free_cluster(sb, &new_clu_to_free, 1)) { |
| /* just set I/O error only */ |
| ret = -EIO; |
| } |
| |
| new_fid->size = 0; |
| new_fid->start_clu = CLUS_EOF; |
| new_fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01; |
| } |
| del_out: |
| /* Update new_inode fid |
| * Prevent syncing removed new_inode |
| * (new_fid is already initialized above code ("if (new_inode)") |
| */ |
| new_fid->dir.dir = DIR_DELETED; |
| } |
| out: |
| fs_sync(sb, 0); |
| fs_set_vol_flags(sb, VOL_CLEAN); |
| |
| return ret; |
| } /* end of fscore_rename */ |
| |
| /* remove a file */ |
| s32 fscore_remove(struct inode *inode, FILE_ID_T *fid) |
| { |
| s32 ret; |
| s32 dentry; |
| CHAIN_T dir, clu_to_free; |
| DENTRY_T *ep; |
| struct super_block *sb = inode->i_sb; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| |
| dir.dir = fid->dir.dir; |
| dir.size = fid->dir.size; |
| dir.flags = fid->dir.flags; |
| |
| dentry = fid->entry; |
| |
| if (fid->dir.dir == DIR_DELETED) { |
| EMSG("%s : abnormal access to deleted dentry\n", __func__); |
| return -ENOENT; |
| } |
| |
| ep = get_dentry_in_dir(sb, &dir, dentry, NULL); |
| if (!ep) |
| return -EIO; |
| |
| |
| #ifdef CONFIG_SDFAT_CHECK_RO_ATTR |
| if (fsi->fs_func->get_entry_attr(ep) & ATTR_READONLY) |
| return -EPERM; |
| #endif |
| |
| fs_set_vol_flags(sb, VOL_DIRTY); |
| |
| /* (1) update the directory entry */ |
| ret = remove_file(inode, &dir, dentry); |
| if (ret) |
| goto out; |
| |
| clu_to_free.dir = fid->start_clu; |
| clu_to_free.size = ((fid->size-1) >> fsi->cluster_size_bits) + 1; |
| clu_to_free.flags = fid->flags; |
| |
| /* (2) invalidate extent cache and free the clusters |
| */ |
| /* clear extent cache */ |
| extent_cache_inval_inode(inode); |
| ret = fsi->fs_func->free_cluster(sb, &clu_to_free, 0); |
| /* WARN : DO NOT RETURN ERROR IN HERE */ |
| |
| /* (3) update FILE_ID_T */ |
| fid->size = 0; |
| fid->start_clu = CLUS_EOF; |
| fid->flags = (fsi->vol_type == EXFAT) ? 0x03 : 0x01; |
| fid->dir.dir = DIR_DELETED; |
| |
| fs_sync(sb, 0); |
| fs_set_vol_flags(sb, VOL_CLEAN); |
| out: |
| return ret; |
| } /* end of fscore_remove */ |
| |
| |
| /* |
| * Get the information of a given file |
| * REMARK : This function does not need any file name on linux |
| * |
| * info.Size means the value saved on disk. |
| * But root directory doesn`t have real dentry, |
| * so the size of root directory returns calculated one exceptively. |
| */ |
| s32 fscore_read_inode(struct inode *inode, DIR_ENTRY_T *info) |
| { |
| u64 sector; |
| s32 count; |
| CHAIN_T dir; |
| TIMESTAMP_T tm; |
| DENTRY_T *ep, *ep2; |
| struct super_block *sb = inode->i_sb; |
| FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); |
| FILE_ID_T *fid = &(SDFAT_I(inode)->fid); |
| ENTRY_SET_CACHE_T *es = NULL; |
| u8 is_dir = (fid->type == TYPE_DIR) ? 1 : 0; |
| |
| TMSG("%s entered\n", __func__); |
| |
| extent_cache_init_inode(inode); |
| |
| /* if root directory */ |
| if (is_dir && (fid->dir.dir == fsi->root_dir) && (fid->entry == -1)) { |
| info->Attr = ATTR_SUBDIR; |
| memset((s8 *) &info->CreateTimestamp, 0, sizeof(DATE_TIME_T)); |
| memset((s8 *) &info->ModifyTimestamp, 0, sizeof(DATE_TIME_T)); |
| memset((s8 *) &info->AccessTimestamp, 0, sizeof(DATE_TIME_T)); |
| //strcpy(info->NameBuf.sfn, "."); |
| //strcpy(info->NameBuf.lfn, "."); |
| |
| dir.dir = fsi->root_dir; |
| dir.flags = 0x01; |
| dir.size = 0; /* UNUSED */ |
| |
| /* FAT16 root_dir */ |
| if (IS_CLUS_FREE(fsi->root_dir)) { |
| info->Size = fsi->dentries_in_root << DENTRY_SIZE_BITS; |
| } else { |
| u32 num_clu; |
| |
| if (__count_num_clusters(sb, &dir, &num_clu)) |
| return -EIO; |
| info->Size = (u64)num_clu << fsi->cluster_size_bits; |
| } |
| |
| count = __count_dos_name_entries(sb, &dir, TYPE_DIR, NULL); |
| if (count < 0) |
| return -EIO; |
| info->NumSubdirs = count; |
| |
| return 0; |
| } |
| |
| /* get the directory entry of given file or directory */ |
| if (fsi->vol_type == EXFAT) { |
| /* es should be released */ |
| es = get_dentry_set_in_dir(sb, &(fid->dir), fid->entry, ES_2_ENTRIES, &ep); |
| if (!es) |
| return -EIO; |
| ep2 = ep+1; |
| } else { |
| ep = get_dentry_in_dir(sb, &(fid->dir), fid->entry, §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 */ |