From 50fb5077e8884ad6d5c634266b3c05a0d7dc3628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Mon, 6 Apr 2020 12:23:37 +0900 Subject: [PATCH 01/36] exfat: Simplify exfat_utf8_d_cmp() for code points above U+FFFF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If two Unicode code points represented in UTF-16 are different then also their UTF-32 representation must be different. Therefore conversion from UTF-32 to UTF-16 is not needed. Signed-off-by: Pali Rohár Signed-off-by: Namjae Jeon --- namei.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/namei.c b/namei.c index 21f5e872ae51..f46c078f392c 100644 --- a/namei.c +++ b/namei.c @@ -212,14 +212,9 @@ static int exfat_utf8_d_cmp(const struct dentry *parent, if (u_a <= 0xFFFF && u_b <= 0xFFFF) { if (exfat_toupper(sb, u_a) != exfat_toupper(sb, u_b)) return 1; - } else if (u_a > 0xFFFF && u_b > 0xFFFF) { - if (exfat_low_surrogate(u_a) != - exfat_low_surrogate(u_b) || - exfat_high_surrogate(u_a) != - exfat_high_surrogate(u_b)) - return 1; } else { - return 1; + if (u_a != u_b) + return 1; } } From ff09ff7549c104b5eedff711cc8a994760f69a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Mon, 6 Apr 2020 12:24:32 +0900 Subject: [PATCH 02/36] exfat: Fix discard support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Discard support was always unconditionally disabled. Now it is disabled only in the case when blk_queue_discard() returns false. Signed-off-by: Pali Rohár Signed-off-by: Namjae Jeon --- super.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/super.c b/super.c index 769dcdf4ac10..e89c56f5fb90 100644 --- a/super.c +++ b/super.c @@ -741,10 +741,11 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) if (opts->discard) { struct request_queue *q = bdev_get_queue(sb->s_bdev); - if (!blk_queue_discard(q)) + if (!blk_queue_discard(q)) { exfat_msg(sb, KERN_WARNING, "mounting with \"discard\" option, but the device does not support discard"); - opts->discard = 0; + opts->discard = 0; + } } #else struct exfat_sb_info *sbi; From fb5c212fe063337e9579475a58b42947ca7545fb Mon Sep 17 00:00:00 2001 From: Thomas Backlund Date: Mon, 6 Apr 2020 12:25:38 +0900 Subject: [PATCH 03/36] exfat: add missing MODULE_ALIAS_FS() This adds the necessary MODULE_ALIAS_FS() to exfat so the module gets automatically loaded when an exfat filesystem is mounted. Signed-off-by: Thomas Backlund Signed-off-by: Namjae Jeon --- super.c | 1 + 1 file changed, 1 insertion(+) diff --git a/super.c b/super.c index e89c56f5fb90..d5917f2e882b 100644 --- a/super.c +++ b/super.c @@ -982,6 +982,7 @@ static void __exit exit_exfat_fs(void) module_init(init_exfat_fs); module_exit(exit_exfat_fs); +MODULE_ALIAS_FS("exfat"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("exFAT filesystem support"); MODULE_AUTHOR("Samsung Electronics Co., Ltd."); From e08ed901472a3fc2aa5f564b524d346a67c819e1 Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Mon, 6 Apr 2020 12:49:36 +0900 Subject: [PATCH 04/36] exfat: Use a more common logging style Remove the direct use of KERN_ in functions by creating separate exfat_ macros. Miscellanea: o Remove several unnecessary terminating newlines in formats o Realign arguments and fit to 80 columns where appropriate Signed-off-by: Joe Perches Signed-off-by: Namjae Jeon --- balloc.c | 8 +++----- dir.c | 9 ++++----- exfat_fs.h | 7 +++++++ fatent.c | 13 +++++-------- misc.c | 4 ++-- namei.c | 26 ++++++++++---------------- nls.c | 20 ++++++++------------ super.c | 49 ++++++++++++++++++++++--------------------------- 8 files changed, 61 insertions(+), 75 deletions(-) diff --git a/balloc.c b/balloc.c index 6a04cc02565a..7238a3875198 100644 --- a/balloc.c +++ b/balloc.c @@ -58,9 +58,8 @@ static int exfat_allocate_bitmap(struct super_block *sb, need_map_size = ((EXFAT_DATA_CLUSTER_COUNT(sbi) - 1) / BITS_PER_BYTE) + 1; if (need_map_size != map_size) { - exfat_msg(sb, KERN_ERR, - "bogus allocation bitmap size(need : %u, cur : %lld)", - need_map_size, map_size); + exfat_err(sb, "bogus allocation bitmap size(need : %u, cur : %lld)", + need_map_size, map_size); /* * Only allowed when bogus allocation * bitmap size is large @@ -195,8 +194,7 @@ void exfat_clear_bitmap(struct inode *inode, unsigned int clu) (1 << sbi->sect_per_clus_bits), GFP_NOFS, 0); if (ret_discard == -EOPNOTSUPP) { - exfat_msg(sb, KERN_ERR, - "discard not supported by device, disabling"); + exfat_err(sb, "discard not supported by device, disabling"); opts->discard = 0; } } diff --git a/dir.c b/dir.c index a8e3e36de072..89acd787645e 100644 --- a/dir.c +++ b/dir.c @@ -725,9 +725,8 @@ static int exfat_dir_readahead(struct super_block *sb, sector_t sec) return 0; if (sec < sbi->data_start_sector) { - exfat_msg(sb, KERN_ERR, - "requested sector is invalid(sect:%llu, root:%llu)", - (unsigned long long)sec, sbi->data_start_sector); + exfat_err(sb, "requested sector is invalid(sect:%llu, root:%llu)", + (unsigned long long)sec, sbi->data_start_sector); return -EIO; } @@ -755,7 +754,7 @@ struct exfat_dentry *exfat_get_dentry(struct super_block *sb, sector_t sec; if (p_dir->dir == DIR_DELETED) { - exfat_msg(sb, KERN_ERR, "abnormal access to deleted dentry\n"); + exfat_err(sb, "abnormal access to deleted dentry"); return NULL; } @@ -858,7 +857,7 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, struct buffer_head *bh; if (p_dir->dir == DIR_DELETED) { - exfat_msg(sb, KERN_ERR, "access to deleted dentry\n"); + exfat_err(sb, "access to deleted dentry"); return NULL; } diff --git a/exfat_fs.h b/exfat_fs.h index 5d81b89688ad..c40182885bd9 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -512,6 +512,13 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) fmt, ## args) void exfat_msg(struct super_block *sb, const char *lv, const char *fmt, ...) __printf(3, 4) __cold; +#define exfat_err(sb, fmt, ...) \ + exfat_msg(sb, KERN_ERR, fmt, ##__VA_ARGS__) +#define exfat_warn(sb, fmt, ...) \ + exfat_msg(sb, KERN_WARNING, fmt, ##__VA_ARGS__) +#define exfat_info(sb, fmt, ...) \ + exfat_msg(sb, KERN_INFO, fmt, ##__VA_ARGS__) + void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 tz, __le16 time, __le16 date, u8 time_ms); void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, diff --git a/fatent.c b/fatent.c index 7e6eb3b7b7c5..12b177035f74 100644 --- a/fatent.c +++ b/fatent.c @@ -178,8 +178,7 @@ int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) /* check cluster validation */ if (p_chain->dir < 2 && p_chain->dir >= sbi->num_clusters) { - exfat_msg(sb, KERN_ERR, "invalid start cluster (%u)", - p_chain->dir); + exfat_err(sb, "invalid start cluster (%u)", p_chain->dir); return -EIO; } @@ -313,8 +312,7 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) return 0; release_bhs: - exfat_msg(sb, KERN_ERR, "failed zeroed sect %llu\n", - (unsigned long long)blknr); + exfat_err(sb, "failed zeroed sect %llu\n", (unsigned long long)blknr); for (i = 0; i < n; i++) bforget(bhs[i]); return err; @@ -345,9 +343,8 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, /* find new cluster */ if (hint_clu == EXFAT_EOF_CLUSTER) { if (sbi->clu_srch_ptr < EXFAT_FIRST_CLUSTER) { - exfat_msg(sb, KERN_ERR, - "sbi->clu_srch_ptr is invalid (%u)\n", - sbi->clu_srch_ptr); + exfat_err(sb, "sbi->clu_srch_ptr is invalid (%u)\n", + sbi->clu_srch_ptr); sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER; } @@ -358,7 +355,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, /* check cluster validation */ if (hint_clu < EXFAT_FIRST_CLUSTER && hint_clu >= sbi->num_clusters) { - exfat_msg(sb, KERN_ERR, "hint_cluster is invalid (%u)\n", + exfat_err(sb, "hint_cluster is invalid (%u)", hint_clu); hint_clu = EXFAT_FIRST_CLUSTER; if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { diff --git a/misc.c b/misc.c index f363c5c917d0..234a2d8ec515 100644 --- a/misc.c +++ b/misc.c @@ -32,7 +32,7 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) va_start(args, fmt); vaf.fmt = fmt; vaf.va = &args; - exfat_msg(sb, KERN_ERR, "error, %pV\n", &vaf); + exfat_err(sb, "error, %pV", &vaf); va_end(args); } @@ -47,7 +47,7 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) !(sb->s_flags & MS_RDONLY)) { sb->s_flags |= MS_RDONLY; #endif - exfat_msg(sb, KERN_ERR, "Filesystem has been set read-only"); + exfat_err(sb, "Filesystem has been set read-only"); } } diff --git a/namei.c b/namei.c index f46c078f392c..02e2491ac300 100644 --- a/namei.c +++ b/namei.c @@ -824,8 +824,8 @@ static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry, if (d_unhashed(alias)) { WARN_ON(alias->d_name.hash_len != dentry->d_name.hash_len); - exfat_msg(sb, KERN_INFO, - "rehashed a dentry(%p) in read lookup", alias); + exfat_info(sb, "rehashed a dentry(%p) in read lookup", + alias); d_drop(dentry); d_rehash(alias); } else if (!S_ISDIR(i_mode)) { @@ -874,7 +874,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) exfat_chain_dup(&cdir, &ei->dir); entry = ei->entry; if (ei->dir.dir == DIR_DELETED) { - exfat_msg(sb, KERN_ERR, "abnormal access to deleted dentry"); + exfat_err(sb, "abnormal access to deleted dentry"); err = -ENOENT; goto unlock; } @@ -1060,7 +1060,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) entry = ei->entry; if (ei->dir.dir == DIR_DELETED) { - exfat_msg(sb, KERN_ERR, "abnormal access to deleted dentry"); + exfat_err(sb, "abnormal access to deleted dentry"); err = -ENOENT; goto unlock; } @@ -1072,9 +1072,8 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) err = exfat_check_dir_empty(sb, &clu_to_free); if (err) { if (err == -EIO) - exfat_msg(sb, KERN_ERR, - "failed to exfat_check_dir_empty : err(%d)", - err); + exfat_err(sb, "failed to exfat_check_dir_empty : err(%d)", + err); goto unlock; } @@ -1095,9 +1094,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) err = exfat_remove_entries(dir, &cdir, entry, 0, num_entries); if (err) { - exfat_msg(sb, KERN_ERR, - "failed to exfat_remove_entries : err(%d)", - err); + exfat_err(sb, "failed to exfat_remove_entries : err(%d)", err); goto unlock; } ei->dir.dir = DIR_DELETED; @@ -1340,8 +1337,7 @@ static int __exfat_rename(struct inode *old_parent_inode, return -EINVAL; if (ei->dir.dir == DIR_DELETED) { - exfat_msg(sb, KERN_ERR, - "abnormal access to deleted source dentry"); + exfat_err(sb, "abnormal access to deleted source dentry"); return -ENOENT; } @@ -1363,8 +1359,7 @@ static int __exfat_rename(struct inode *old_parent_inode, new_ei = EXFAT_I(new_inode); if (new_ei->dir.dir == DIR_DELETED) { - exfat_msg(sb, KERN_ERR, - "abnormal access to deleted target dentry"); + exfat_err(sb, "abnormal access to deleted target dentry"); goto out; } @@ -1549,8 +1544,7 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, if (S_ISDIR(new_inode->i_mode)) drop_nlink(new_inode); } else { - exfat_msg(sb, KERN_WARNING, - "abnormal access to an inode dropped"); + exfat_warn(sb, "abnormal access to an inode dropped"); WARN_ON(new_inode->i_nlink == 0); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) diff --git a/nls.c b/nls.c index 6d1c3ae130ff..2178786b708b 100644 --- a/nls.c +++ b/nls.c @@ -503,16 +503,14 @@ static int exfat_utf8_to_utf16(struct super_block *sb, unilen = utf8s_to_utf16s(p_cstring, len, UTF16_HOST_ENDIAN, (wchar_t *)uniname, MAX_NAME_LENGTH + 2); if (unilen < 0) { - exfat_msg(sb, KERN_ERR, - "failed to %s (err : %d) nls len : %d", - __func__, unilen, len); + exfat_err(sb, "failed to %s (err : %d) nls len : %d", + __func__, unilen, len); return unilen; } if (unilen > MAX_NAME_LENGTH) { - exfat_msg(sb, KERN_ERR, - "failed to %s (estr:ENAMETOOLONG) nls len : %d, unilen : %d > %d", - __func__, len, unilen, MAX_NAME_LENGTH); + exfat_err(sb, "failed to %s (estr:ENAMETOOLONG) nls len : %d, unilen : %d > %d", + __func__, len, unilen, MAX_NAME_LENGTH); return -ENAMETOOLONG; } @@ -687,9 +685,8 @@ static int exfat_load_upcase_table(struct super_block *sb, bh = sb_bread(sb, sector); if (!bh) { - exfat_msg(sb, KERN_ERR, - "failed to read sector(0x%llx)\n", - (unsigned long long)sector); + exfat_err(sb, "failed to read sector(0x%llx)\n", + (unsigned long long)sector); ret = -EIO; goto free_table; } @@ -722,9 +719,8 @@ static int exfat_load_upcase_table(struct super_block *sb, if (index >= 0xFFFF && utbl_checksum == checksum) return 0; - exfat_msg(sb, KERN_ERR, - "failed to load upcase table (idx : 0x%08x, chksum : 0x%08x, utbl_chksum : 0x%08x)\n", - index, checksum, utbl_checksum); + exfat_err(sb, "failed to load upcase table (idx : 0x%08x, chksum : 0x%08x, utbl_chksum : 0x%08x)", + index, checksum, utbl_checksum); ret = -EINVAL; free_table: exfat_free_upcase_table(sbi); diff --git a/super.c b/super.c index d5917f2e882b..8c1e6a86cb23 100644 --- a/super.c +++ b/super.c @@ -135,7 +135,7 @@ int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) if (!sbi->pbr_bh) { sbi->pbr_bh = sb_bread(sb, 0); if (!sbi->pbr_bh) { - exfat_msg(sb, KERN_ERR, "failed to read boot sector"); + exfat_err(sb, "failed to read boot sector"); return -ENOMEM; } } @@ -570,15 +570,13 @@ static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb, if (!is_power_of_2(logical_sect) || logical_sect < 512 || logical_sect > 4096) { - exfat_msg(sb, KERN_ERR, "bogus logical sector size %u", - logical_sect); + exfat_err(sb, "bogus logical sector size %u", logical_sect); return NULL; } if (logical_sect < sb->s_blocksize) { - exfat_msg(sb, KERN_ERR, - "logical sector size too small for device (logical sector size = %u)", - logical_sect); + exfat_err(sb, "logical sector size too small for device (logical sector size = %u)", + logical_sect); return NULL; } @@ -589,15 +587,14 @@ static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb, *prev_bh = NULL; if (!sb_set_blocksize(sb, logical_sect)) { - exfat_msg(sb, KERN_ERR, - "unable to set blocksize %u", logical_sect); + exfat_err(sb, "unable to set blocksize %u", + logical_sect); return NULL; } bh = sb_bread(sb, 0); if (!bh) { - exfat_msg(sb, KERN_ERR, - "unable to read boot sector (logical sector size = %lu)", - sb->s_blocksize); + exfat_err(sb, "unable to read boot sector (logical sector size = %lu)", + sb->s_blocksize); return NULL; } @@ -622,7 +619,7 @@ static int __exfat_fill_super(struct super_block *sb) /* read boot sector */ bh = sb_bread(sb, 0); if (!bh) { - exfat_msg(sb, KERN_ERR, "unable to read boot sector"); + exfat_err(sb, "unable to read boot sector"); return -EIO; } @@ -631,7 +628,7 @@ static int __exfat_fill_super(struct super_block *sb) /* check the validity of PBR */ if (le16_to_cpu((p_pbr->signature)) != PBR_SIGNATURE) { - exfat_msg(sb, KERN_ERR, "invalid boot record signature"); + exfat_err(sb, "invalid boot record signature"); ret = -EINVAL; goto free_bh; } @@ -656,7 +653,7 @@ static int __exfat_fill_super(struct super_block *sb) p_bpb = (struct pbr64 *)p_pbr; if (!p_bpb->bsx.num_fats) { - exfat_msg(sb, KERN_ERR, "bogus number of FAT structure"); + exfat_err(sb, "bogus number of FAT structure"); ret = -EINVAL; goto free_bh; } @@ -686,8 +683,7 @@ static int __exfat_fill_super(struct super_block *sb) if (le16_to_cpu(p_bpb->bsx.vol_flags) & VOL_DIRTY) { sbi->vol_flag |= VOL_DIRTY; - exfat_msg(sb, KERN_WARNING, - "Volume was not properly unmounted. Some data may be corrupt. Please run fsck."); + exfat_warn(sb, "Volume was not properly unmounted. Some data may be corrupt. Please run fsck."); } /* exFAT file size is limited by a disk volume size */ @@ -696,19 +692,19 @@ static int __exfat_fill_super(struct super_block *sb) ret = exfat_create_upcase_table(sb); if (ret) { - exfat_msg(sb, KERN_ERR, "failed to load upcase table"); + exfat_err(sb, "failed to load upcase table"); goto free_bh; } ret = exfat_load_bitmap(sb); if (ret) { - exfat_msg(sb, KERN_ERR, "failed to load alloc-bitmap"); + exfat_err(sb, "failed to load alloc-bitmap"); goto free_upcase_table; } ret = exfat_count_used_clusters(sb, &sbi->used_clusters); if (ret) { - exfat_msg(sb, KERN_ERR, "failed to scan clusters"); + exfat_err(sb, "failed to scan clusters"); goto free_alloc_bitmap; } @@ -742,8 +738,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) struct request_queue *q = bdev_get_queue(sb->s_bdev); if (!blk_queue_discard(q)) { - exfat_msg(sb, KERN_WARNING, - "mounting with \"discard\" option, but the device does not support discard"); + exfat_warn(sb, "mounting with \"discard\" option, but the device does not support discard"); opts->discard = 0; } } @@ -787,7 +782,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) err = __exfat_fill_super(sb); if (err) { - exfat_msg(sb, KERN_ERR, "failed to recognize exfat type"); + exfat_err(sb, "failed to recognize exfat type"); goto check_nls_io; } @@ -803,8 +798,8 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) else { sbi->nls_io = load_nls(sbi->options.iocharset); if (!sbi->nls_io) { - exfat_msg(sb, KERN_ERR, "IO charset %s not found", - sbi->options.iocharset); + exfat_err(sb, "IO charset %s not found", + sbi->options.iocharset); err = -EINVAL; goto free_table; } @@ -817,7 +812,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) root_inode = new_inode(sb); if (!root_inode) { - exfat_msg(sb, KERN_ERR, "failed to allocate root inode."); + exfat_err(sb, "failed to allocate root inode"); err = -ENOMEM; goto free_table; } @@ -831,7 +826,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) #endif err = exfat_read_root(root_inode); if (err) { - exfat_msg(sb, KERN_ERR, "failed to initialize root inode."); + exfat_err(sb, "failed to initialize root inode"); goto put_inode; } @@ -840,7 +835,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) sb->s_root = d_make_root(root_inode); if (!sb->s_root) { - exfat_msg(sb, KERN_ERR, "failed to get the root dentry"); + exfat_err(sb, "failed to get the root dentry"); err = -ENOMEM; goto put_inode; } From 0790539add0494392e08020444d2a56431fa767b Mon Sep 17 00:00:00 2001 From: Tetsuhiro Kohada Date: Mon, 6 Apr 2020 13:02:28 +0900 Subject: [PATCH 05/36] exfat: Unify access to the boot sector Unify access to boot sector via 'sbi->pbr_bh'. This fixes vol_flags inconsistency at read failed in fs_set_vol_flags(), and buffer_head leak in __exfat_fill_super(). Signed-off-by: Tetsuhiro Kohada Signed-off-by: Namjae Jeon --- balloc.c | 3 --- super.c | 43 ++++++++++++++++--------------------------- 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/balloc.c b/balloc.c index 7238a3875198..4055eb00ea9b 100644 --- a/balloc.c +++ b/balloc.c @@ -90,7 +90,6 @@ static int exfat_allocate_bitmap(struct super_block *sb, } } - sbi->pbr_bh = NULL; return 0; } @@ -136,8 +135,6 @@ void exfat_free_bitmap(struct exfat_sb_info *sbi) { int i; - brelse(sbi->pbr_bh); - for (i = 0; i < sbi->map_sectors; i++) __brelse(sbi->vol_amap[i]); diff --git a/super.c b/super.c index 8c1e6a86cb23..6605d4513f64 100644 --- a/super.c +++ b/super.c @@ -62,6 +62,7 @@ static void exfat_put_super(struct super_block *sb) sync_blockdev(sb->s_bdev); exfat_set_vol_flags(sb, VOL_CLEAN); exfat_free_bitmap(sbi); + brelse(sbi->pbr_bh); mutex_unlock(&sbi->s_lock); call_rcu(&sbi->rcu, exfat_delayed_free); @@ -113,7 +114,7 @@ static int exfat_statfs(struct dentry *dentry, struct kstatfs *buf) int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) { struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct pbr64 *bpb; + struct pbr64 *bpb = (struct pbr64 *)sbi->pbr_bh->b_data; bool sync = 0; /* flags are not changed */ @@ -132,15 +133,6 @@ int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) #endif return 0; - if (!sbi->pbr_bh) { - sbi->pbr_bh = sb_bread(sb, 0); - if (!sbi->pbr_bh) { - exfat_err(sb, "failed to read boot sector"); - return -ENOMEM; - } - } - - bpb = (struct pbr64 *)sbi->pbr_bh->b_data; bpb->bsx.vol_flags = cpu_to_le16(new_flag); if (new_flag == VOL_DIRTY && !buffer_dirty(sbi->pbr_bh)) @@ -560,10 +552,10 @@ static int exfat_read_root(struct inode *inode) return 0; } -static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb, - struct buffer_head **prev_bh) +static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb) { - struct pbr *p_pbr = (struct pbr *) (*prev_bh)->b_data; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct pbr *p_pbr = (struct pbr *) (sbi->pbr_bh)->b_data; unsigned short logical_sect = 0; logical_sect = 1 << p_pbr->bsx.f64.sect_size_bits; @@ -581,25 +573,22 @@ static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb, } if (logical_sect > sb->s_blocksize) { - struct buffer_head *bh = NULL; - - __brelse(*prev_bh); - *prev_bh = NULL; + brelse(sbi->pbr_bh); + sbi->pbr_bh = NULL; if (!sb_set_blocksize(sb, logical_sect)) { exfat_err(sb, "unable to set blocksize %u", logical_sect); return NULL; } - bh = sb_bread(sb, 0); - if (!bh) { + sbi->pbr_bh = sb_bread(sb, 0); + if (!sbi->pbr_bh) { exfat_err(sb, "unable to read boot sector (logical sector size = %lu)", sb->s_blocksize); return NULL; } - *prev_bh = bh; - p_pbr = (struct pbr *) bh->b_data; + p_pbr = (struct pbr *)sbi->pbr_bh->b_data; } return p_pbr; } @@ -610,21 +599,20 @@ static int __exfat_fill_super(struct super_block *sb) int ret; struct pbr *p_pbr; struct pbr64 *p_bpb; - struct buffer_head *bh; struct exfat_sb_info *sbi = EXFAT_SB(sb); /* set block size to read super block */ sb_min_blocksize(sb, 512); /* read boot sector */ - bh = sb_bread(sb, 0); - if (!bh) { + sbi->pbr_bh = sb_bread(sb, 0); + if (!sbi->pbr_bh) { exfat_err(sb, "unable to read boot sector"); return -EIO; } /* PRB is read */ - p_pbr = (struct pbr *)bh->b_data; + p_pbr = (struct pbr *)sbi->pbr_bh->b_data; /* check the validity of PBR */ if (le16_to_cpu((p_pbr->signature)) != PBR_SIGNATURE) { @@ -635,7 +623,7 @@ static int __exfat_fill_super(struct super_block *sb) /* check logical sector size */ - p_pbr = exfat_read_pbr_with_logical_sector(sb, &bh); + p_pbr = exfat_read_pbr_with_logical_sector(sb); if (!p_pbr) { ret = -EIO; goto free_bh; @@ -715,7 +703,7 @@ static int __exfat_fill_super(struct super_block *sb) free_upcase_table: exfat_free_upcase_table(sbi); free_bh: - brelse(bh); + brelse(sbi->pbr_bh); return ret; } @@ -849,6 +837,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) free_table: exfat_free_upcase_table(sbi); exfat_free_bitmap(sbi); + brelse(sbi->pbr_bh); check_nls_io: unload_nls(sbi->nls_io); From 91a2de7e4d9596447991a472e05c73d799696fce Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 6 Apr 2020 14:24:30 +0900 Subject: [PATCH 06/36] exfat: fix wrong format option Signed-off-by: Namjae Jeon --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c4a1e79ad976..6c8935a376f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,9 +67,9 @@ script: - time dd if=/dev/zero bs=1G count=10 of=test.img - time dd if=/dev/zero bs=1G count=10 of=scratch.img - sudo losetup /dev/loop20 test.img - - sudo losetup /dev/loop21 test.img - - sudo mkfs.exfat -c 4 /dev/loop20 - - sudo mkfs.exfat -c 4 /dev/loop21 + - sudo losetup /dev/loop21 scratch.img + - sudo mkfs.exfat -c 4k /dev/loop20 + - sudo mkfs.exfat -c 4k /dev/loop21 - cd .. - cd exfat-testsuites/ - tar xzvf xfstests-exfat.tgz > /dev/null From 1f5b9538b0af322a88ac6fa057ad894ceddfff9c Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 6 Apr 2020 14:29:20 +0900 Subject: [PATCH 07/36] exfat: fix wrong exfat_oot repo directory name Signed-off-by: Namjae Jeon --- .travis.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6c8935a376f5..89a0f746c7ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,16 +43,16 @@ script: - make -j$((`nproc`+1)) fs/exfat/exfat.ko # Compile with latest Torvalds' kernel - # - cd ../linux - # - yes "" | make oldconfig > /dev/null - # - echo 'obj-$(CONFIG_EXFAT) += exfat/' >> fs/Makefile - #- echo 'source "fs/exfat/Kconfig"' >> fs/Kconfig - #- echo 'CONFIG_EXFAT_FS=m' >> .config - #- echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config - #- make -j$((`nproc`+1)) fs/exfat/exfat.ko + - cd ../linux + - yes "" | make oldconfig > /dev/null + - echo 'obj-$(CONFIG_EXFAT) += exfat/' >> fs/Makefile + - echo 'source "fs/exfat/Kconfig"' >> fs/Kconfig + - echo 'CONFIG_EXFAT_FS=m' >> .config + - echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config + - make -j$((`nproc`+1)) fs/exfat/exfat.ko # Run xfstests testsuite - - cd ../exfat + - cd ../exfat_oot - make > /dev/null - sudo make install > /dev/null - sudo modprobe exfat From 2ac0fc42e4a0e852cd738dc756348c16e9f1fe7c Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 6 Apr 2020 14:58:30 +0900 Subject: [PATCH 08/36] exfat: disable build exfat with linus tree Signed-off-by: Namjae Jeon --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 89a0f746c7ef..e6f40aca55ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,13 +43,13 @@ script: - make -j$((`nproc`+1)) fs/exfat/exfat.ko # Compile with latest Torvalds' kernel - - cd ../linux - - yes "" | make oldconfig > /dev/null - - echo 'obj-$(CONFIG_EXFAT) += exfat/' >> fs/Makefile - - echo 'source "fs/exfat/Kconfig"' >> fs/Kconfig - - echo 'CONFIG_EXFAT_FS=m' >> .config - - echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config - - make -j$((`nproc`+1)) fs/exfat/exfat.ko +# - cd ../linux +# - yes "" | make oldconfig > /dev/null +# - echo 'obj-$(CONFIG_EXFAT) += exfat/' >> fs/Makefile +# - echo 'source "fs/exfat/Kconfig"' >> fs/Kconfig +# - echo 'CONFIG_EXFAT_FS=m' >> .config +# - echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config +# - make -j$((`nproc`+1)) fs/exfat/exfat.ko # Run xfstests testsuite - cd ../exfat_oot From 39018f1bca7b4d7a9ed79b56f27ed16d78c196b4 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 6 Apr 2020 21:59:28 +0900 Subject: [PATCH 09/36] exfat: use truncate instead of dd command Signed-off-by: Namjae Jeon --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index e6f40aca55ee..63dd4817ad21 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,12 +64,12 @@ script: - sudo cp lib/.libs/libexfat.so* /lib/ - sudo mkdir -p /mnt/scratch - sudo mkdir -p /mnt/test - - time dd if=/dev/zero bs=1G count=10 of=test.img - - time dd if=/dev/zero bs=1G count=10 of=scratch.img + - truncate -s 100G test.img + - truncate -s 100G scratch.img - sudo losetup /dev/loop20 test.img - sudo losetup /dev/loop21 scratch.img - - sudo mkfs.exfat -c 4k /dev/loop20 - - sudo mkfs.exfat -c 4k /dev/loop21 + - sudo mkfs.exfat /dev/loop20 + - sudo mkfs.exfat /dev/loop21 - cd .. - cd exfat-testsuites/ - tar xzvf xfstests-exfat.tgz > /dev/null From c3dd1a225a73b0ef67644e5fbb1aa5ddd16242d8 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 7 Apr 2020 14:32:24 +0900 Subject: [PATCH 10/36] exfat: add more xfstests test in travis-CI Signed-off-by: Namjae Jeon --- .travis.yml | 60 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 63dd4817ad21..41592dd9a02b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -79,21 +79,25 @@ script: - sudo ./check generic/006 - sudo ./check generic/007 - sudo ./check generic/011 + - sudo ./check generic/013 - sudo ./check generic/014 - sudo ./check generic/028 - sudo ./check generic/029 - sudo ./check generic/030 + - sudo ./check generic/034 - sudo ./check generic/035 - sudo ./check generic/036 - sudo ./check generic/069 - - sudo ./check generic/071 + - sudo ./check generic/073 - sudo ./check generic/074 - sudo ./check generic/075 + - sudo ./check generic/076 - sudo ./check generic/080 - sudo ./check generic/084 - - sudo ./check generic/086 - sudo ./check generic/095 - sudo ./check generic/098 + - sudo ./check generic/100 + - sudo ./check generic/101 - sudo ./check generic/112 - sudo ./check generic/113 - sudo ./check generic/114 @@ -101,8 +105,11 @@ script: - sudo ./check generic/123 - sudo ./check generic/124 - sudo ./check generic/127 + - sudo ./check generic/129 + - sudo ./check generic/130 - sudo ./check generic/131 - sudo ./check generic/132 + - sudo ./check generic/133 - sudo ./check generic/135 - sudo ./check generic/141 - sudo ./check generic/169 @@ -111,8 +118,53 @@ script: - sudo ./check generic/208 - sudo ./check generic/209 - sudo ./check generic/210 + - sudo ./check generic/211 - sudo ./check generic/212 - - sudo ./check generic/214 - sudo ./check generic/215 - sudo ./check generic/221 - - sudo ./check generic/228 + - sudo ./check generic/239 + - sudo ./check generic/240 + - sudo ./check generic/241 + - sudo ./check generic/245 + - sudo ./check generic/246 + - sudo ./check generic/247 + - sudo ./check generic/248 + - sudo ./check generic/249 + - sudo ./check generic/257 + - sudo ./check generic/285 + - sudo ./check generic/286 + - sudo ./check generic/308 + - sudo ./check generic/309 + - sudo ./check generic/310 + - sudo ./check generic/313 + - sudo ./check generic/322 + - sudo ./check generic/323 + - sudo ./check generic/325 + - sudo ./check generic/338 + - sudo ./check generic/339 + - sudo ./check generic/340 + - sudo ./check generic/342 + - sudo ./check generic/344 + - sudo ./check generic/345 + - sudo ./check generic/346 + - sudo ./check generic/347 + - sudo ./check generic/354 + - sudo ./check generic/376 + - sudo ./check generic/393 + - sudo ./check generic/394 + - sudo ./check generic/405 + - sudo ./check generic/406 + - sudo ./check generic/409 + - sudo ./check generic/410 + - sudo ./check generic/411 + - sudo ./check generic/412 + - sudo ./check generic/418 + - sudo ./check generic/428 + - sudo ./check generic/437 + - sudo ./check generic/438 + - sudo ./check generic/441 + - sudo ./check generic/443 + - sudo ./check generic/448 + - sudo ./check generic/450 + - sudo ./check generic/451 + - sudo ./check generic/452 From a4aad16ccfac3db39030411e828001cca8e948b1 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 7 Apr 2020 15:09:18 +0900 Subject: [PATCH 11/36] exfat: add create file and directory test Signed-off-by: Namjae Jeon --- .travis.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.travis.yml b/.travis.yml index 41592dd9a02b..7cb4d82fec75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,6 +64,23 @@ script: - sudo cp lib/.libs/libexfat.so* /lib/ - sudo mkdir -p /mnt/scratch - sudo mkdir -p /mnt/test + - sudo mkdir -p /mnt/full_test + # create file/director test + - truncate -s 10G full_test.img + - sudo losetup /dev/loop22 full_test.img + - sudo mkfs.exfat /dev/loop22 + - sudo mount -t exfat /dev/loop22 /mnt/full_test/ + - cd /mnt/full_test/ + - i=1;while [ $i -le 10000 ];do sudo touch file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done + - sync + - sudo rm -rf * + - i=1;while [ $i -le 10000 ];do sudo mkdir file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done + - sync + - sudo rm -rf * + - sudo fsck.exfat /dev/loop22 + - cd - + - sudo umount /mnt/full_test/ + # run xfstests test - truncate -s 100G test.img - truncate -s 100G scratch.img - sudo losetup /dev/loop20 test.img From f65abe88d5b2a9fc747abd9d2f68c190dcc34cc4 Mon Sep 17 00:00:00 2001 From: Tetsuhiro Kohada Date: Tue, 7 Apr 2020 17:34:10 +0900 Subject: [PATCH 12/36] exfat: remove 'bps' mount-option remount fails because exfat_show_options() returns unsupported option 'bps'. > # mount -o ro,remount > exfat: Unknown parameter 'bps' To fix the problem, just remove 'bps' option from exfat_show_options(). Signed-off-by: Tetsuhiro Kohada Signed-off-by: Namjae Jeon --- super.c | 1 - 1 file changed, 1 deletion(-) diff --git a/super.c b/super.c index 6605d4513f64..128d2da33de3 100644 --- a/super.c +++ b/super.c @@ -168,7 +168,6 @@ static int exfat_show_options(struct seq_file *m, struct dentry *root) seq_puts(m, ",iocharset=utf8"); else if (sbi->nls_io) seq_printf(m, ",iocharset=%s", sbi->nls_io->charset); - seq_printf(m, ",bps=%ld", sb->s_blocksize); if (opts->errors == EXFAT_ERRORS_CONT) seq_puts(m, ",errors=continue"); else if (opts->errors == EXFAT_ERRORS_PANIC) From 233b4ba5abb4b92f78b9aeb880b31d70bccfdca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Thu, 16 Apr 2020 09:40:37 +0900 Subject: [PATCH 13/36] exfat: Simplify exfat_utf8_d_hash() for code points above U+FFFF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Function partial_name_hash() takes long type value into which can be stored one Unicode code point. Therefore conversion from UTF-32 to UTF-16 is not needed. Signed-off-by: Pali Rohár Signed-off-by: Namjae Jeon --- namei.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/namei.c b/namei.c index 02e2491ac300..9920203271c1 100644 --- a/namei.c +++ b/namei.c @@ -168,16 +168,10 @@ static int exfat_utf8_d_hash(const struct dentry *dentry, struct qstr *qstr) return charlen; /* - * Convert to UTF-16: code points above U+FFFF are encoded as - * surrogate pairs. * exfat_toupper() works only for code points up to the U+FFFF. */ - if (u > 0xFFFF) { - hash = partial_name_hash(exfat_high_surrogate(u), hash); - hash = partial_name_hash(exfat_low_surrogate(u), hash); - } else { - hash = partial_name_hash(exfat_toupper(sb, u), hash); - } + hash = partial_name_hash(u <= 0xFFFF ? exfat_toupper(sb, u) : u, + hash); } qstr->hash = end_name_hash(hash); From d2424acd1dfccaf8628b3f9aeeb21f3601983df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Thu, 16 Apr 2020 09:41:16 +0900 Subject: [PATCH 14/36] exfat: Remove unused functions exfat_high_surrogate() and exfat_low_surrogate() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After applying previous two patches, these functions are not used anymore. Signed-off-by: Pali Rohár Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 -- nls.c | 13 ------------- 2 files changed, 15 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index c40182885bd9..ca683ab1de47 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -499,8 +499,6 @@ int exfat_nls_to_utf16(struct super_block *sb, struct exfat_uni_name *uniname, int *p_lossy); int exfat_create_upcase_table(struct super_block *sb); void exfat_free_upcase_table(struct exfat_sb_info *sbi); -unsigned short exfat_high_surrogate(unicode_t u); -unsigned short exfat_low_surrogate(unicode_t u); /* exfat/misc.c */ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) diff --git a/nls.c b/nls.c index 2178786b708b..1ebda90cbdd7 100644 --- a/nls.c +++ b/nls.c @@ -535,22 +535,9 @@ static int exfat_utf8_to_utf16(struct super_block *sb, return unilen; } -#define PLANE_SIZE 0x00010000 #define SURROGATE_MASK 0xfffff800 #define SURROGATE_PAIR 0x0000d800 #define SURROGATE_LOW 0x00000400 -#define SURROGATE_BITS 0x000003ff - -unsigned short exfat_high_surrogate(unicode_t u) -{ - return ((u - PLANE_SIZE) >> 10) + SURROGATE_PAIR; -} - -unsigned short exfat_low_surrogate(unicode_t u) -{ - return ((u - PLANE_SIZE) & SURROGATE_BITS) | SURROGATE_PAIR | - SURROGATE_LOW; -} static int __exfat_utf16_to_nls(struct super_block *sb, struct exfat_uni_name *p_uniname, unsigned char *p_cstring, From db8bd78e867c8085d6f4785cbaa4c6bd2b75a970 Mon Sep 17 00:00:00 2001 From: Jason Yan Date: Thu, 16 Apr 2020 09:51:09 +0900 Subject: [PATCH 15/36] exfat: remove the assignment of 0 to bool variable There is no need to init 'sync' in exfat_set_vol_flags(). This also fixes the following coccicheck warning: fs/exfat/super.c:104:6-10: WARNING: Assignment of 0/1 to bool variable Signed-off-by: Jason Yan Signed-off-by: Namjae Jeon --- super.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/super.c b/super.c index 128d2da33de3..71ae2bb339a4 100644 --- a/super.c +++ b/super.c @@ -115,7 +115,7 @@ int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) { struct exfat_sb_info *sbi = EXFAT_SB(sb); struct pbr64 *bpb = (struct pbr64 *)sbi->pbr_bh->b_data; - bool sync = 0; + bool sync; /* flags are not changed */ if (sbi->vol_flag == new_flag) From b0333de3fffb9b0365d51b7a9950a55bfcb4e313 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Fri, 17 Apr 2020 15:40:01 +0900 Subject: [PATCH 16/36] exfat: properly set s_time_gran The s_time_gran superblock field indicates the on-disk nanosecond granularity of timestamps, and for exfat that seems to be 10ms, so set s_time_gran to 10000000ns. Without this, in-memory timestamps change when they get re-read from disk. Signed-off-by: Eric Sandeen Signed-off-by: Namjae Jeon --- super.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/super.c b/super.c index 71ae2bb339a4..bcc362dcb52a 100644 --- a/super.c +++ b/super.c @@ -762,7 +762,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) sb->s_op = &exfat_sops; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) - sb->s_time_gran = 1; + sb->s_time_gran = 10 * NSEC_PER_MSEC; sb->s_time_min = EXFAT_MIN_TIMESTAMP_SECS; sb->s_time_max = EXFAT_MAX_TIMESTAMP_SECS; #endif From 09241c144babdcd404427211a0a9c66e85a7734e Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Fri, 17 Apr 2020 15:47:40 +0900 Subject: [PATCH 17/36] exfat: truncate atimes to 2s granularity exfat atimes are restricted to only 2s granularity so after we set an atime, round it down to the nearest 2s and set the sub-second component of the timestamp to 0. Signed-off-by: Eric Sandeen Signed-off-by: Namjae Jeon --- exfat_fs.h | 1 + file.c | 2 ++ misc.c | 10 +++++++++- namei.c | 7 +++++++ super.c | 1 + 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/exfat_fs.h b/exfat_fs.h index ca683ab1de47..828262c03eb9 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -519,6 +519,7 @@ void exfat_msg(struct super_block *sb, const char *lv, const char *fmt, ...) void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 tz, __le16 time, __le16 date, u8 time_ms); +void exfat_truncate_atime(struct timespec64 *ts); void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 *tz, __le16 *time, __le16 *date, u8 *time_ms); unsigned short exfat_calc_chksum_2byte(void *data, int len, diff --git a/file.c b/file.c index 415a8d48849e..93d044959c49 100644 --- a/file.c +++ b/file.c @@ -300,6 +300,7 @@ int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, #endif generic_fillattr(inode, stat); + exfat_truncate_atime(&stat->atime); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) stat->result_mask |= STATX_BTIME; stat->btime.tv_sec = ei->i_crtime.tv_sec; @@ -374,6 +375,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) } setattr_copy(inode, attr); + exfat_truncate_atime(&inode->i_atime); mark_inode_dirty(inode); out: diff --git a/misc.c b/misc.c index 234a2d8ec515..bf6e8cdd5d36 100644 --- a/misc.c +++ b/misc.c @@ -94,7 +94,8 @@ void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, if (time_ms) { ts->tv_sec += time_ms / 100; ts->tv_nsec = (time_ms % 100) * 10 * NSEC_PER_MSEC; - } + } else + ts->tv_nsec = 0; if (tz & EXFAT_TZ_VALID) /* Adjust timezone to UTC0. */ @@ -134,6 +135,13 @@ void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, *tz = EXFAT_TZ_VALID; } +/* atime has only a 2-second resolution */ +void exfat_truncate_atime(struct timespec64 *ts) +{ + ts->tv_sec = round_down(ts->tv_sec, 2); + ts->tv_nsec = 0; +} + unsigned short exfat_calc_chksum_2byte(void *data, int len, unsigned short chksum, int type) { diff --git a/namei.c b/namei.c index 9920203271c1..1987864e66e9 100644 --- a/namei.c +++ b/namei.c @@ -629,6 +629,7 @@ static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, inode->i_mtime = inode->i_atime = inode->i_ctime = EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; #endif + exfat_truncate_atime(&inode->i_atime); /* timestamp is already written, so mark_inode_dirty() is unneeded. */ d_instantiate(dentry, inode); @@ -908,6 +909,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) #else dir->i_mtime = dir->i_atime = CURRENT_TIME_SEC; #endif + exfat_truncate_atime(&dir->i_atime); if (IS_DIRSYNC(dir)) exfat_sync_inode(dir); else @@ -919,6 +921,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) #else inode->i_mtime = inode->i_atime = CURRENT_TIME_SEC; #endif + exfat_truncate_atime(&inode->i_atime); exfat_unhash_inode(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) exfat_d_version_set(dentry, inode_query_iversion(dir)); @@ -983,6 +986,7 @@ static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) inode->i_mtime = inode->i_atime = inode->i_ctime = EXFAT_I(inode)->i_crtime = CURRENT_TIME_SEC; #endif + exfat_truncate_atime(&inode->i_atime); /* timestamp is already written, so mark_inode_dirty() is unneeded. */ d_instantiate(dentry, inode); @@ -1104,6 +1108,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) #else dir->i_mtime = dir->i_atime = CURRENT_TIME_SEC; #endif + exfat_truncate_atime(&dir->i_atime); if (IS_DIRSYNC(dir)) exfat_sync_inode(dir); else @@ -1116,6 +1121,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry) #else inode->i_mtime = inode->i_atime = CURRENT_TIME_SEC; #endif + exfat_truncate_atime(&inode->i_atime); exfat_unhash_inode(inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) exfat_d_version_set(dentry, inode_query_iversion(dir)); @@ -1494,6 +1500,7 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, new_dir->i_ctime = new_dir->i_mtime = new_dir->i_atime = EXFAT_I(new_dir)->i_crtime = CURRENT_TIME_SEC; #endif + exfat_truncate_atime(&new_dir->i_atime); if (IS_DIRSYNC(new_dir)) exfat_sync_inode(new_dir); else diff --git a/super.c b/super.c index bcc362dcb52a..391cc171f04e 100644 --- a/super.c +++ b/super.c @@ -547,6 +547,7 @@ static int exfat_read_root(struct inode *inode) inode->i_mtime = inode->i_atime = inode->i_ctime = ei->i_crtime = CURRENT_TIME_SEC; #endif + exfat_truncate_atime(&inode->i_atime); exfat_cache_init_inode(inode); return 0; } From 9772742131e5064d69ed77452348bef89d9c8460 Mon Sep 17 00:00:00 2001 From: Tetsuhiro Kohada Date: Sun, 3 May 2020 23:10:31 +0900 Subject: [PATCH 18/36] exfat: replace 'time_ms' with 'time_cs' Replace time_ms with time_cs in the file directory entry structure and related functions. The unit of create_time_ms/modify_time_ms in File Directory Entry are not 'milli-second', but 'centi-second'. The exfat specification uses the term '10ms', but instead use 'cs' as in msdos_fs.h. Signed-off-by: Tetsuhiro Kohada Signed-off-by: Namjae Jeon --- dir.c | 8 ++++---- exfat_fs.h | 4 ++-- exfat_raw.h | 4 ++-- file.c | 2 +- inode.c | 4 ++-- misc.c | 18 +++++++++--------- namei.c | 4 ++-- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/dir.c b/dir.c index 89acd787645e..bbade7f3b3ae 100644 --- a/dir.c +++ b/dir.c @@ -138,12 +138,12 @@ static int exfat_readdir(struct inode *inode, struct exfat_dir_entry *dir_entry) ep->dentry.file.create_tz, ep->dentry.file.create_time, ep->dentry.file.create_date, - ep->dentry.file.create_time_ms); + ep->dentry.file.create_time_cs); exfat_get_entry_time(sbi, &dir_entry->mtime, ep->dentry.file.modify_tz, ep->dentry.file.modify_time, ep->dentry.file.modify_date, - ep->dentry.file.modify_time_ms); + ep->dentry.file.modify_time_cs); exfat_get_entry_time(sbi, &dir_entry->atime, ep->dentry.file.access_tz, ep->dentry.file.access_time, @@ -466,12 +466,12 @@ int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir, &ep->dentry.file.create_tz, &ep->dentry.file.create_time, &ep->dentry.file.create_date, - &ep->dentry.file.create_time_ms); + &ep->dentry.file.create_time_cs); exfat_set_entry_time(sbi, &ts, &ep->dentry.file.modify_tz, &ep->dentry.file.modify_time, &ep->dentry.file.modify_date, - &ep->dentry.file.modify_time_ms); + &ep->dentry.file.modify_time_cs); exfat_set_entry_time(sbi, &ts, &ep->dentry.file.access_tz, &ep->dentry.file.access_time, diff --git a/exfat_fs.h b/exfat_fs.h index 828262c03eb9..688d50d5325b 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -518,10 +518,10 @@ void exfat_msg(struct super_block *sb, const char *lv, const char *fmt, ...) exfat_msg(sb, KERN_INFO, fmt, ##__VA_ARGS__) void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, - u8 tz, __le16 time, __le16 date, u8 time_ms); + u8 tz, __le16 time, __le16 date, u8 time_cs); void exfat_truncate_atime(struct timespec64 *ts); void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, - u8 *tz, __le16 *time, __le16 *date, u8 *time_ms); + u8 *tz, __le16 *time, __le16 *date, u8 *time_cs); unsigned short exfat_calc_chksum_2byte(void *data, int len, unsigned short chksum, int type); void exfat_update_bh(struct super_block *sb, struct buffer_head *bh, int sync); diff --git a/exfat_raw.h b/exfat_raw.h index 2a841010e649..8d6c64a7546d 100644 --- a/exfat_raw.h +++ b/exfat_raw.h @@ -136,8 +136,8 @@ struct exfat_dentry { __le16 modify_date; __le16 access_time; __le16 access_date; - __u8 create_time_ms; - __u8 modify_time_ms; + __u8 create_time_cs; + __u8 modify_time_cs; __u8 create_tz; __u8 modify_tz; __u8 access_tz; diff --git a/file.c b/file.c index 93d044959c49..4a74ef7c947b 100644 --- a/file.c +++ b/file.c @@ -178,7 +178,7 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) &ep->dentry.file.modify_tz, &ep->dentry.file.modify_time, &ep->dentry.file.modify_date, - &ep->dentry.file.modify_time_ms); + &ep->dentry.file.modify_time_cs); ep->dentry.file.attr = cpu_to_le16(ei->attr); /* File size should be zero if there is no cluster allocated */ diff --git a/inode.c b/inode.c index 0a535bd86422..dde5d02daf5f 100644 --- a/inode.c +++ b/inode.c @@ -58,12 +58,12 @@ static int __exfat_write_inode(struct inode *inode, int sync) &ep->dentry.file.create_tz, &ep->dentry.file.create_time, &ep->dentry.file.create_date, - &ep->dentry.file.create_time_ms); + &ep->dentry.file.create_time_cs); exfat_set_entry_time(sbi, &inode->i_mtime, &ep->dentry.file.modify_tz, &ep->dentry.file.modify_time, &ep->dentry.file.modify_date, - &ep->dentry.file.modify_time_ms); + &ep->dentry.file.modify_time_cs); exfat_set_entry_time(sbi, &inode->i_atime, &ep->dentry.file.access_tz, &ep->dentry.file.access_time, diff --git a/misc.c b/misc.c index bf6e8cdd5d36..3971cf14150d 100644 --- a/misc.c +++ b/misc.c @@ -81,7 +81,7 @@ static void exfat_adjust_tz(struct timespec64 *ts, u8 tz_off) /* Convert a EXFAT time/date pair to a UNIX date (seconds since 1 1 70). */ void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, - u8 tz, __le16 time, __le16 date, u8 time_ms) + u8 tz, __le16 time, __le16 date, u8 time_cs) { u16 t = le16_to_cpu(time); u16 d = le16_to_cpu(date); @@ -90,10 +90,10 @@ void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, t >> 11, (t >> 5) & 0x003F, (t & 0x001F) << 1); - /* time_ms field represent 0 ~ 199(1990 ms) */ - if (time_ms) { - ts->tv_sec += time_ms / 100; - ts->tv_nsec = (time_ms % 100) * 10 * NSEC_PER_MSEC; + /* time_cs field represent 0 ~ 199cs(1990 ms) */ + if (time_cs) { + ts->tv_sec += time_cs / 100; + ts->tv_nsec = (time_cs % 100) * 10 * NSEC_PER_MSEC; } else ts->tv_nsec = 0; @@ -107,7 +107,7 @@ void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, /* Convert linear UNIX date to a EXFAT time/date pair. */ void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, - u8 *tz, __le16 *time, __le16 *date, u8 *time_ms) + u8 *tz, __le16 *time, __le16 *date, u8 *time_cs) { struct tm tm; u16 t, d; @@ -123,9 +123,9 @@ void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, *time = cpu_to_le16(t); *date = cpu_to_le16(d); - /* time_ms field represent 0 ~ 199(1990 ms) */ - if (time_ms) - *time_ms = (tm.tm_sec & 1) * 100 + + /* time_cs field represent 0 ~ 199cs(1990 ms) */ + if (time_cs) + *time_cs = (tm.tm_sec & 1) * 100 + ts->tv_nsec / (10 * NSEC_PER_MSEC); /* diff --git a/namei.c b/namei.c index 1987864e66e9..2c35bf5ecc9b 100644 --- a/namei.c +++ b/namei.c @@ -741,12 +741,12 @@ static int exfat_find(struct inode *dir, struct qstr *qname, ep->dentry.file.create_tz, ep->dentry.file.create_time, ep->dentry.file.create_date, - ep->dentry.file.create_time_ms); + ep->dentry.file.create_time_cs); exfat_get_entry_time(sbi, &info->mtime, ep->dentry.file.modify_tz, ep->dentry.file.modify_time, ep->dentry.file.modify_date, - ep->dentry.file.modify_time_ms); + ep->dentry.file.modify_time_cs); exfat_get_entry_time(sbi, &info->atime, ep->dentry.file.access_tz, ep->dentry.file.access_time, From 3f985aa6d54f613e553f62a0e9a2ff2e725364b0 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Sun, 3 May 2020 23:24:55 +0900 Subject: [PATCH 19/36] exfat: use iter_file_splice_write Doing copy_file_range() on exfat with a file opened for direct IO leads to an -EFAULT: # xfs_io -f -d -c "truncate 32768" \ -c "copy_range -d 16384 -l 16384 -f 0" /mnt/test/junk copy_range: Bad address and the reason seems to be that we go through: default_file_splice_write splice_from_pipe __splice_from_pipe write_pipe_buf __kernel_write new_sync_write generic_file_write_iter generic_file_direct_write exfat_direct_IO do_blockdev_direct_IO iov_iter_get_pages and land in iterate_all_kinds(), which does "return -EFAULT" for our kvec iter. Setting exfat's splice_write to iter_file_splice_write fixes this and lets fsx (which originally detected the problem) run to success from the xfstests harness. Signed-off-by: Eric Sandeen Signed-off-by: Namjae Jeon --- file.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/file.c b/file.c index 4a74ef7c947b..7151206bdbbc 100644 --- a/file.c +++ b/file.c @@ -383,12 +383,13 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) } const struct file_operations exfat_file_operations = { - .llseek = generic_file_llseek, - .read_iter = generic_file_read_iter, - .write_iter = generic_file_write_iter, - .mmap = generic_file_mmap, - .fsync = generic_file_fsync, - .splice_read = generic_file_splice_read, + .llseek = generic_file_llseek, + .read_iter = generic_file_read_iter, + .write_iter = generic_file_write_iter, + .mmap = generic_file_mmap, + .fsync = generic_file_fsync, + .splice_read = generic_file_splice_read, + .splice_write = iter_file_splice_write, }; const struct inode_operations exfat_file_inode_operations = { From 5b0b76fb88940b3bead57159584a68eb3eb7c162 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sun, 3 May 2020 23:58:06 +0900 Subject: [PATCH 20/36] exfat: add fsx xfstests Signed-off-by: Namjae Jeon --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7cb4d82fec75..8692a8e5a1f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -111,6 +111,7 @@ script: - sudo ./check generic/076 - sudo ./check generic/080 - sudo ./check generic/084 + - sudo ./check generic/091 - sudo ./check generic/095 - sudo ./check generic/098 - sudo ./check generic/100 @@ -148,6 +149,7 @@ script: - sudo ./check generic/248 - sudo ./check generic/249 - sudo ./check generic/257 + - sudo ./check generic/263 - sudo ./check generic/285 - sudo ./check generic/286 - sudo ./check generic/308 From 16897b770cc428bf72fc5c7a3da435ec2205a194 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 4 May 2020 00:15:23 +0900 Subject: [PATCH 21/36] exfat: remove so library copy Signed-off-by: Namjae Jeon --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8692a8e5a1f3..ad16a819d553 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,7 +61,6 @@ script: - ./configure > /dev/null - make -j$((`nproc`+1)) > /dev/null - sudo make install > /dev/null - - sudo cp lib/.libs/libexfat.so* /lib/ - sudo mkdir -p /mnt/scratch - sudo mkdir -p /mnt/test - sudo mkdir -p /mnt/full_test From acb6ab805217fd69f3f70fa7a3d65739d2dfe14d Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 26 May 2020 21:32:18 +0900 Subject: [PATCH 22/36] exfat: add missing time_offset mount option for kernel version that is lower than 5.4 version Signed-off-by: Namjae Jeon --- super.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/super.c b/super.c index 391cc171f04e..5ed7165a1808 100644 --- a/super.c +++ b/super.c @@ -362,6 +362,7 @@ enum { Opt_err_ro, Opt_err, Opt_discard, + Opt_time_offset, Opt_fs, }; @@ -452,6 +453,17 @@ static int parse_options(struct super_block *sb, char *options, int silent, case Opt_discard: opts->discard = 1; break; + case Opt_time_offset: + if (match_int(&args[0], &option)) + return -EINVAL; + /* + * Make the limit 24 just in case someone + * invents something unusual. + */ + if (option < -24 * 60 || option > 24 * 60) + return -EINVAL; + opts->time_offset = option; + break; default: if (!silent) { exfat_msg(sb, KERN_ERR, From 377751a6849a6f0a96044311cbd71871d0e4abcc Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Wed, 6 May 2020 14:25:54 +0000 Subject: [PATCH 23/36] exfat: fix possible memory leak in exfat_find() 'es' is malloced from exfat_get_dentry_set() in exfat_find() and should be freed before leaving from the error handling cases, otherwise it will cause memory leak. Fixes: 5f2aa075070c ("exfat: add inode operations") Signed-off-by: Wei Yongjun Signed-off-by: Namjae Jeon --- namei.c | 1 + 1 file changed, 1 insertion(+) diff --git a/namei.c b/namei.c index 2c35bf5ecc9b..4aa27fd98b3d 100644 --- a/namei.c +++ b/namei.c @@ -734,6 +734,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, exfat_fs_error(sb, "non-zero size file starts with zero cluster (size : %llu, p_dir : %u, entry : 0x%08x)", i_size_read(dir), ei->dir.dir, ei->entry); + kfree(es); return -EIO; } From c486d443f8f5ca16e80172bbf75efd41f1af2abd Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 26 May 2020 21:59:31 +0900 Subject: [PATCH 24/36] exfat: use renamed repo in travis script Signed-off-by: Namjae Jeon --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ad16a819d553..ebee78e40683 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,7 +52,7 @@ script: # - make -j$((`nproc`+1)) fs/exfat/exfat.ko # Run xfstests testsuite - - cd ../exfat_oot + - cd ../linux-exfat-oot - make > /dev/null - sudo make install > /dev/null - sudo modprobe exfat From 9f140e2c22b309af99a2cde3fa3307fb0bd16a83 Mon Sep 17 00:00:00 2001 From: Tetsuhiro Kohada Date: Sun, 31 May 2020 21:00:20 +0900 Subject: [PATCH 25/36] exfat: optimize dir-cache Optimize directory access based on exfat_entry_set_cache. - Hold bh instead of copied d-entry. - Modify bh->data directly instead of the copied d-entry. - Write back the retained bh instead of rescanning the d-entry-set. And - Remove unused cache related definitions. Signed-off-by: Tetsuhiro Kohada Signed-off-by: Namjae Jeon --- dir.c | 203 ++++++++++++++++++++--------------------------------- exfat_fs.h | 27 ++++--- file.c | 15 ++-- inode.c | 53 ++++++-------- namei.c | 14 ++-- 5 files changed, 124 insertions(+), 188 deletions(-) diff --git a/dir.c b/dir.c index bbade7f3b3ae..a6f19f01323a 100644 --- a/dir.c +++ b/dir.c @@ -33,35 +33,30 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, struct exfat_chain *p_dir, int entry, unsigned short *uniname) { int i; - struct exfat_dentry *ep; struct exfat_entry_set_cache *es; - es = exfat_get_dentry_set(sb, p_dir, entry, ES_ALL_ENTRIES, &ep); + es = exfat_get_dentry_set(sb, p_dir, entry, ES_ALL_ENTRIES); if (!es) return; - if (es->num_entries < 3) - goto free_es; - - ep += 2; - /* * First entry : file entry * Second entry : stream-extension entry * Third entry : first file-name entry * So, the index of first file-name dentry should start from 2. */ - for (i = 2; i < es->num_entries; i++, ep++) { + for (i = 2; i < es->num_entries; i++) { + struct exfat_dentry *ep = exfat_get_dentry_cached(es, i); + /* end of name entry */ if (exfat_get_entry_type(ep) != TYPE_EXTEND) - goto free_es; + break; exfat_extract_uni_name(ep, uniname); uniname += EXFAT_FILE_NAME_LEN; } -free_es: - kfree(es); + exfat_free_dentry_set(es, false); } /* read a directory entry from the opened directory */ @@ -595,62 +590,33 @@ int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, return 0; } -int exfat_update_dir_chksum_with_entry_set(struct super_block *sb, - struct exfat_entry_set_cache *es, int sync) +void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es) { - struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct buffer_head *bh; - sector_t sec = es->sector; - unsigned int off = es->offset; - int chksum_type = CS_DIR_ENTRY, i, num_entries = es->num_entries; - unsigned int buf_off = (off - es->offset); - unsigned int remaining_byte_in_sector, copy_entries, clu; + int chksum_type = CS_DIR_ENTRY, i; unsigned short chksum = 0; + struct exfat_dentry *ep; - for (i = 0; i < num_entries; i++) { - chksum = exfat_calc_chksum_2byte(&es->entries[i], DENTRY_SIZE, - chksum, chksum_type); + for (i = 0; i < es->num_entries; i++) { + ep = exfat_get_dentry_cached(es, i); + chksum = exfat_calc_chksum_2byte(ep, DENTRY_SIZE, chksum, + chksum_type); chksum_type = CS_DEFAULT; } + ep = exfat_get_dentry_cached(es, 0); + ep->dentry.file.checksum = cpu_to_le16(chksum); + es->modified = true; +} - es->entries[0].dentry.file.checksum = cpu_to_le16(chksum); +void exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync) +{ + int i; - while (num_entries) { - /* write per sector base */ - remaining_byte_in_sector = (1 << sb->s_blocksize_bits) - off; - copy_entries = min_t(int, - EXFAT_B_TO_DEN(remaining_byte_in_sector), - num_entries); - bh = sb_bread(sb, sec); - if (!bh) - goto err_out; - memcpy(bh->b_data + off, - (unsigned char *)&es->entries[0] + buf_off, - EXFAT_DEN_TO_B(copy_entries)); - exfat_update_bh(sb, bh, sync); - brelse(bh); - num_entries -= copy_entries; - - if (num_entries) { - /* get next sector */ - if (exfat_is_last_sector_in_cluster(sbi, sec)) { - clu = exfat_sector_to_cluster(sbi, sec); - if (es->alloc_flag == ALLOC_NO_FAT_CHAIN) - clu++; - else if (exfat_get_next_cluster(sb, &clu)) - goto err_out; - sec = exfat_cluster_to_sector(sbi, clu); - } else { - sec++; - } - off = 0; - buf_off += EXFAT_DEN_TO_B(copy_entries); - } + for (i = 0; i < es->num_bh; i++) { + if (es->modified) + exfat_update_bh(es->sb, es->bh[i], sync); + brelse(es->bh[i]); } - - return 0; -err_out: - return -EIO; + kfree(es); } static int exfat_walk_fat_chain(struct super_block *sb, @@ -825,34 +791,40 @@ static bool exfat_validate_entry(unsigned int type, } } +struct exfat_dentry *exfat_get_dentry_cached( + struct exfat_entry_set_cache *es, int num) +{ + int off = es->start_off + num * DENTRY_SIZE; + struct buffer_head *bh = es->bh[EXFAT_B_TO_BLK(off, es->sb)]; + char *p = bh->b_data + EXFAT_BLK_OFFSET(off, es->sb); + + return (struct exfat_dentry *)p; +} + /* * Returns a set of dentries for a file or dir. * - * Note that this is a copy (dump) of dentries so that user should - * call write_entry_set() to apply changes made in this entry set - * to the real device. + * Note It provides a direct pointer to bh->data via exfat_get_dentry_cached(). + * User should call exfat_get_dentry_set() after setting 'modified' to apply + * changes made in this entry set to the real device. * * in: * sb+p_dir+entry: indicates a file/dir * type: specifies how many dentries should be included. - * out: - * file_ep: will point the first dentry(= file dentry) on success * return: * pointer of entry set on success, * NULL on failure. */ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, - struct exfat_chain *p_dir, int entry, unsigned int type, - struct exfat_dentry **file_ep) + struct exfat_chain *p_dir, int entry, unsigned int type) { - int ret; + int ret, i, num_bh; unsigned int off, byte_offset, clu = 0; - unsigned int entry_type; sector_t sec; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_entry_set_cache *es; - struct exfat_dentry *ep, *pos; - unsigned char num_entries; + struct exfat_dentry *ep; + int num_entries; enum exfat_validate_dentry_mode mode = ES_MODE_STARTED; struct buffer_head *bh; @@ -866,11 +838,18 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, if (ret) return NULL; + es = kzalloc(sizeof(*es), GFP_KERNEL); + if (!es) + return NULL; + es->sb = sb; + es->modified = false; + /* byte offset in cluster */ byte_offset = EXFAT_CLU_OFFSET(byte_offset, sbi); /* byte offset in sector */ off = EXFAT_BLK_OFFSET(byte_offset, sb); + es->start_off = off; /* sector offset in cluster */ sec = EXFAT_B_TO_BLK(byte_offset, sb); @@ -878,78 +857,46 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, bh = sb_bread(sb, sec); if (!bh) - return NULL; - - ep = (struct exfat_dentry *)(bh->b_data + off); - entry_type = exfat_get_entry_type(ep); + goto free_es; + es->bh[es->num_bh++] = bh; - if (entry_type != TYPE_FILE && entry_type != TYPE_DIR) - goto release_bh; + ep = exfat_get_dentry_cached(es, 0); + if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) + goto free_es; num_entries = type == ES_ALL_ENTRIES ? ep->dentry.file.num_ext + 1 : type; - -#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) - es = kmalloc(struct_size(es, entries, num_entries), GFP_KERNEL); -#else - es = kmalloc((offsetof(struct exfat_entry_set_cache, entries) + - (num_entries) * sizeof(struct exfat_dentry)), GFP_KERNEL); -#endif - if (!es) - goto release_bh; - es->num_entries = num_entries; - es->sector = sec; - es->offset = off; - es->alloc_flag = p_dir->flags; - - pos = &es->entries[0]; - - while (num_entries) { - if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) - goto free_es; - - /* copy dentry */ - memcpy(pos, ep, sizeof(struct exfat_dentry)); - if (--num_entries == 0) - break; - - if (((off + DENTRY_SIZE) & (sb->s_blocksize - 1)) < - (off & (sb->s_blocksize - 1))) { - /* get the next sector */ - if (exfat_is_last_sector_in_cluster(sbi, sec)) { - if (es->alloc_flag == ALLOC_NO_FAT_CHAIN) - clu++; - else if (exfat_get_next_cluster(sb, &clu)) - goto free_es; - sec = exfat_cluster_to_sector(sbi, clu); - } else { - sec++; - } - - brelse(bh); - bh = sb_bread(sb, sec); - if (!bh) + num_bh = EXFAT_B_TO_BLK_ROUND_UP(off + num_entries * DENTRY_SIZE, sb); + for (i = 1; i < num_bh; i++) { + /* get the next sector */ + if (exfat_is_last_sector_in_cluster(sbi, sec)) { + if (p_dir->flags == ALLOC_NO_FAT_CHAIN) + clu++; + else if (exfat_get_next_cluster(sb, &clu)) goto free_es; - off = 0; - ep = (struct exfat_dentry *)bh->b_data; + sec = exfat_cluster_to_sector(sbi, clu); } else { - ep++; - off += DENTRY_SIZE; + sec++; } - pos++; + + bh = sb_bread(sb, sec); + if (!bh) + goto free_es; + es->bh[es->num_bh++] = bh; } - if (file_ep) - *file_ep = &es->entries[0]; - brelse(bh); + /* validiate cached dentries */ + for (i = 1; i < num_entries; i++) { + ep = exfat_get_dentry_cached(es, i); + if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) + goto free_es; + } return es; free_es: - kfree(es); -release_bh: - brelse(bh); + exfat_free_dentry_set(es, false); return NULL; } diff --git a/exfat_fs.h b/exfat_fs.h index 688d50d5325b..06588d861427 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -72,10 +72,8 @@ enum { #define MAX_NAME_LENGTH 255 /* max len of file name excluding NULL */ #define MAX_VFSNAME_BUF_SIZE ((MAX_NAME_LENGTH + 1) * MAX_CHARSET_SIZE) -#define FAT_CACHE_SIZE 128 -#define FAT_CACHE_HASH_SIZE 64 -#define BUF_CACHE_SIZE 256 -#define BUF_CACHE_HASH_SIZE 64 +/* Enough size to hold 256 dentry (even 512 Byte sector) */ +#define DIR_CACHE_SIZE (256*sizeof(struct exfat_dentry)/512+1) #define EXFAT_HINT_NONE -1 #define EXFAT_MIN_SUBDIR 2 @@ -171,14 +169,12 @@ struct exfat_hint { }; struct exfat_entry_set_cache { - /* sector number that contains file_entry */ - sector_t sector; - /* byte offset in the sector */ - unsigned int offset; - /* flag in stream entry. 01 for cluster chain, 03 for contig. */ - int alloc_flag; + struct super_block *sb; + bool modified; + unsigned int start_off; + int num_bh; + struct buffer_head *bh[DIR_CACHE_SIZE]; unsigned int num_entries; - struct exfat_dentry entries[]; }; struct exfat_dir_entry { @@ -458,8 +454,7 @@ int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, int entry, int order, int num_entries); int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, int entry); -int exfat_update_dir_chksum_with_entry_set(struct super_block *sb, - struct exfat_entry_set_cache *es, int sync); +void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es); int exfat_calc_num_entries(struct exfat_uni_name *p_uniname); int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname, @@ -470,9 +465,11 @@ int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir, struct exfat_dentry *exfat_get_dentry(struct super_block *sb, struct exfat_chain *p_dir, int entry, struct buffer_head **bh, sector_t *sector); +struct exfat_dentry *exfat_get_dentry_cached(struct exfat_entry_set_cache *es, + int num); struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, - struct exfat_chain *p_dir, int entry, unsigned int type, - struct exfat_dentry **file_ep); + struct exfat_chain *p_dir, int entry, unsigned int type); +void exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync); int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir); /* inode.c */ diff --git a/file.c b/file.c index 7151206bdbbc..e529b66b93ab 100644 --- a/file.c +++ b/file.c @@ -105,11 +105,9 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) unsigned int num_clusters_new, num_clusters_phys; unsigned int last_clu = EXFAT_FREE_CLUSTER; struct exfat_chain clu; - struct exfat_dentry *ep, *ep2; struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); - struct exfat_entry_set_cache *es = NULL; int evict = (ei->dir.dir == DIR_DELETED) ? 1 : 0; /* check if the given file ID is opened */ @@ -162,12 +160,15 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) /* update the directory entry */ if (!evict) { struct timespec64 ts; + struct exfat_dentry *ep, *ep2; + struct exfat_entry_set_cache *es; es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, - ES_ALL_ENTRIES, &ep); + ES_ALL_ENTRIES); if (!es) return -EIO; - ep2 = ep + 1; + ep = exfat_get_dentry_cached(es, 0); + ep2 = exfat_get_dentry_cached(es, 1); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) ts = current_time(inode); @@ -198,10 +199,8 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) ep2->dentry.stream.start_clu = EXFAT_FREE_CLUSTER; } - if (exfat_update_dir_chksum_with_entry_set(sb, es, - inode_needs_sync(inode))) - return -EIO; - kfree(es); + exfat_update_dir_chksum_with_entry_set(es); + exfat_free_dentry_set(es, inode_needs_sync(inode)); } /* cut off from the FAT chain */ diff --git a/inode.c b/inode.c index dde5d02daf5f..9bc472d894ca 100644 --- a/inode.c +++ b/inode.c @@ -21,7 +21,6 @@ static int __exfat_write_inode(struct inode *inode, int sync) { - int ret = -EIO; unsigned long long on_disk_size; struct exfat_dentry *ep, *ep2; struct exfat_entry_set_cache *es = NULL; @@ -45,11 +44,11 @@ static int __exfat_write_inode(struct inode *inode, int sync) exfat_set_vol_flags(sb, VOL_DIRTY); /* get the directory entry of given file or directory */ - es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES, - &ep); + es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES); if (!es) return -EIO; - ep2 = ep + 1; + ep = exfat_get_dentry_cached(es, 0); + ep2 = exfat_get_dentry_cached(es, 1); ep->dentry.file.attr = cpu_to_le16(exfat_make_attr(inode)); @@ -79,9 +78,9 @@ static int __exfat_write_inode(struct inode *inode, int sync) ep2->dentry.stream.valid_size = cpu_to_le64(on_disk_size); ep2->dentry.stream.size = ep2->dentry.stream.valid_size; - ret = exfat_update_dir_chksum_with_entry_set(sb, es, sync); - kfree(es); - return ret; + exfat_update_dir_chksum_with_entry_set(es); + exfat_free_dentry_set(es, sync); + return 0; } int exfat_write_inode(struct inode *inode, struct writeback_control *wbc) @@ -112,8 +111,6 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, int ret, modified = false; unsigned int last_clu; struct exfat_chain new_clu; - struct exfat_dentry *ep; - struct exfat_entry_set_cache *es = NULL; struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); @@ -224,34 +221,28 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, num_clusters += num_to_be_allocated; *clu = new_clu.dir; - if (ei->dir.dir != DIR_DELETED) { + if (ei->dir.dir != DIR_DELETED && modified) { + struct exfat_dentry *ep; + struct exfat_entry_set_cache *es; + es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, - ES_ALL_ENTRIES, &ep); + ES_ALL_ENTRIES); if (!es) return -EIO; /* get stream entry */ - ep++; + ep = exfat_get_dentry_cached(es, 1); /* update directory entry */ - if (modified) { - if (ep->dentry.stream.flags != ei->flags) - ep->dentry.stream.flags = ei->flags; - - if (le32_to_cpu(ep->dentry.stream.start_clu) != - ei->start_clu) - ep->dentry.stream.start_clu = - cpu_to_le32(ei->start_clu); - - ep->dentry.stream.valid_size = - cpu_to_le64(i_size_read(inode)); - ep->dentry.stream.size = - ep->dentry.stream.valid_size; - } - - if (exfat_update_dir_chksum_with_entry_set(sb, es, - inode_needs_sync(inode))) - return -EIO; - kfree(es); + ep->dentry.stream.flags = ei->flags; + ep->dentry.stream.start_clu = + cpu_to_le32(ei->start_clu); + ep->dentry.stream.valid_size = + cpu_to_le64(i_size_read(inode)); + ep->dentry.stream.size = + ep->dentry.stream.valid_size; + + exfat_update_dir_chksum_with_entry_set(es); + exfat_free_dentry_set(es, inode_needs_sync(inode)); } /* end of if != DIR_DELETED */ diff --git a/namei.c b/namei.c index 4aa27fd98b3d..917d4e6d9e1a 100644 --- a/namei.c +++ b/namei.c @@ -645,8 +645,6 @@ static int exfat_find(struct inode *dir, struct qstr *qname, int ret, dentry, num_entries, count; struct exfat_chain cdir; struct exfat_uni_name uni_name; - struct exfat_dentry *ep, *ep2; - struct exfat_entry_set_cache *es = NULL; struct super_block *sb = dir->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(dir); @@ -713,10 +711,14 @@ static int exfat_find(struct inode *dir, struct qstr *qname, info->num_subdirs = count; } else { - es = exfat_get_dentry_set(sb, &cdir, dentry, ES_2_ENTRIES, &ep); + struct exfat_dentry *ep, *ep2; + struct exfat_entry_set_cache *es; + + es = exfat_get_dentry_set(sb, &cdir, dentry, ES_2_ENTRIES); if (!es) return -EIO; - ep2 = ep + 1; + ep = exfat_get_dentry_cached(es, 0); + ep2 = exfat_get_dentry_cached(es, 1); info->type = exfat_get_entry_type(ep); info->attr = le16_to_cpu(ep->dentry.file.attr); @@ -734,7 +736,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, exfat_fs_error(sb, "non-zero size file starts with zero cluster (size : %llu, p_dir : %u, entry : 0x%08x)", i_size_read(dir), ei->dir.dir, ei->entry); - kfree(es); + exfat_free_dentry_set(es, false); return -EIO; } @@ -753,7 +755,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, ep->dentry.file.access_time, ep->dentry.file.access_date, 0); - kfree(es); + exfat_free_dentry_set(es, false); if (info->type == TYPE_DIR) { exfat_chain_set(&cdir, info->start_clu, From 461d45b45c18a4686cb446639b325fb4054f7efc Mon Sep 17 00:00:00 2001 From: Tetsuhiro Kohada Date: Sun, 31 May 2020 21:01:35 +0900 Subject: [PATCH 26/36] exfat: redefine PBR as boot_sector Aggregate PBR related definitions and redefine as boot_sector to comply with the exFAT specification. And, rename variable names including 'pbr'. Signed-off-by: Tetsuhiro Kohada Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 +- exfat_raw.h | 79 ++++++++++++++++++------------------------------- super.c | 84 ++++++++++++++++++++++++++--------------------------- 3 files changed, 72 insertions(+), 93 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 06588d861427..838b3e055c10 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -228,7 +228,7 @@ struct exfat_sb_info { unsigned int root_dir; /* root dir cluster */ unsigned int dentries_per_clu; /* num of dentries per cluster */ unsigned int vol_flag; /* volume dirty flag */ - struct buffer_head *pbr_bh; /* buffer_head of PBR sector */ + struct buffer_head *boot_bh; /* buffer_head of BOOT sector */ unsigned int map_clu; /* allocation bitmap start cluster */ unsigned int map_sectors; /* num of allocation bitmap sectors */ diff --git a/exfat_raw.h b/exfat_raw.h index 8d6c64a7546d..07f74190df44 100644 --- a/exfat_raw.h +++ b/exfat_raw.h @@ -8,7 +8,8 @@ #include -#define PBR_SIGNATURE 0xAA55 +#define BOOT_SIGNATURE 0xAA55 +#define EXBOOT_SIGNATURE 0xAA550000 #define EXFAT_MAX_FILE_LEN 255 @@ -55,7 +56,7 @@ /* checksum types */ #define CS_DIR_ENTRY 0 -#define CS_PBR_SECTOR 1 +#define CS_BOOT_SECTOR 1 #define CS_DEFAULT 2 /* file attributes */ @@ -69,57 +70,35 @@ #define ATTR_RWMASK (ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME | \ ATTR_SUBDIR | ATTR_ARCHIVE) -#define PBR64_JUMP_BOOT_LEN 3 -#define PBR64_OEM_NAME_LEN 8 -#define PBR64_RESERVED_LEN 53 +#define BOOTSEC_JUMP_BOOT_LEN 3 +#define BOOTSEC_FS_NAME_LEN 8 +#define BOOTSEC_OLDBPB_LEN 53 #define EXFAT_FILE_NAME_LEN 15 -/* EXFAT BIOS parameter block (64 bytes) */ -struct bpb64 { - __u8 jmp_boot[PBR64_JUMP_BOOT_LEN]; - __u8 oem_name[PBR64_OEM_NAME_LEN]; - __u8 res_zero[PBR64_RESERVED_LEN]; -} __packed; - -/* EXFAT EXTEND BIOS parameter block (56 bytes) */ -struct bsx64 { - __le64 vol_offset; - __le64 vol_length; - __le32 fat_offset; - __le32 fat_length; - __le32 clu_offset; - __le32 clu_count; - __le32 root_cluster; - __le32 vol_serial; - __u8 fs_version[2]; - __le16 vol_flags; - __u8 sect_size_bits; - __u8 sect_per_clus_bits; - __u8 num_fats; - __u8 phy_drv_no; - __u8 perc_in_use; - __u8 reserved2[7]; -} __packed; - -/* EXFAT PBR[BPB+BSX] (120 bytes) */ -struct pbr64 { - struct bpb64 bpb; - struct bsx64 bsx; -} __packed; - -/* Common PBR[Partition Boot Record] (512 bytes) */ -struct pbr { - union { - __u8 raw[64]; - struct bpb64 f64; - } bpb; - union { - __u8 raw[56]; - struct bsx64 f64; - } bsx; - __u8 boot_code[390]; - __le16 signature; +/* EXFAT: Main and Backup Boot Sector (512 bytes) */ +struct boot_sector { + __u8 jmp_boot[BOOTSEC_JUMP_BOOT_LEN]; + __u8 fs_name[BOOTSEC_FS_NAME_LEN]; + __u8 must_be_zero[BOOTSEC_OLDBPB_LEN]; + __le64 partition_offset; + __le64 vol_length; + __le32 fat_offset; + __le32 fat_length; + __le32 clu_offset; + __le32 clu_count; + __le32 root_cluster; + __le32 vol_serial; + __u8 fs_revision[2]; + __le16 vol_flags; + __u8 sect_size_bits; + __u8 sect_per_clus_bits; + __u8 num_fats; + __u8 drv_sel; + __u8 percent_in_use; + __u8 reserved[7]; + __u8 boot_code[390]; + __le16 signature; } __packed; struct exfat_dentry { diff --git a/super.c b/super.c index 5ed7165a1808..5ecd977c60ef 100644 --- a/super.c +++ b/super.c @@ -62,7 +62,7 @@ static void exfat_put_super(struct super_block *sb) sync_blockdev(sb->s_bdev); exfat_set_vol_flags(sb, VOL_CLEAN); exfat_free_bitmap(sbi); - brelse(sbi->pbr_bh); + brelse(sbi->boot_bh); mutex_unlock(&sbi->s_lock); call_rcu(&sbi->rcu, exfat_delayed_free); @@ -114,7 +114,7 @@ static int exfat_statfs(struct dentry *dentry, struct kstatfs *buf) int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) { struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct pbr64 *bpb = (struct pbr64 *)sbi->pbr_bh->b_data; + struct boot_sector *p_boot = (struct boot_sector *)sbi->boot_bh->b_data; bool sync; /* flags are not changed */ @@ -133,18 +133,18 @@ int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag) #endif return 0; - bpb->bsx.vol_flags = cpu_to_le16(new_flag); + p_boot->vol_flags = cpu_to_le16(new_flag); - if (new_flag == VOL_DIRTY && !buffer_dirty(sbi->pbr_bh)) + if (new_flag == VOL_DIRTY && !buffer_dirty(sbi->boot_bh)) sync = true; else sync = false; - set_buffer_uptodate(sbi->pbr_bh); - mark_buffer_dirty(sbi->pbr_bh); + set_buffer_uptodate(sbi->boot_bh); + mark_buffer_dirty(sbi->boot_bh); if (sync) - sync_dirty_buffer(sbi->pbr_bh); + sync_dirty_buffer(sbi->boot_bh); return 0; } @@ -564,13 +564,14 @@ static int exfat_read_root(struct inode *inode) return 0; } -static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb) +static struct boot_sector *exfat_read_boot_with_logical_sector( + struct super_block *sb) { struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct pbr *p_pbr = (struct pbr *) (sbi->pbr_bh)->b_data; + struct boot_sector *p_boot = (struct boot_sector *)sbi->boot_bh->b_data; unsigned short logical_sect = 0; - logical_sect = 1 << p_pbr->bsx.f64.sect_size_bits; + logical_sect = 1 << p_boot->sect_size_bits; if (!is_power_of_2(logical_sect) || logical_sect < 512 || logical_sect > 4096) { @@ -585,49 +586,48 @@ static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb) } if (logical_sect > sb->s_blocksize) { - brelse(sbi->pbr_bh); - sbi->pbr_bh = NULL; + brelse(sbi->boot_bh); + sbi->boot_bh = NULL; if (!sb_set_blocksize(sb, logical_sect)) { exfat_err(sb, "unable to set blocksize %u", logical_sect); return NULL; } - sbi->pbr_bh = sb_bread(sb, 0); - if (!sbi->pbr_bh) { + sbi->boot_bh = sb_bread(sb, 0); + if (!sbi->boot_bh) { exfat_err(sb, "unable to read boot sector (logical sector size = %lu)", sb->s_blocksize); return NULL; } - p_pbr = (struct pbr *)sbi->pbr_bh->b_data; + p_boot = (struct boot_sector *)sbi->boot_bh->b_data; } - return p_pbr; + return p_boot; } /* mount the file system volume */ static int __exfat_fill_super(struct super_block *sb) { int ret; - struct pbr *p_pbr; - struct pbr64 *p_bpb; + struct boot_sector *p_boot; struct exfat_sb_info *sbi = EXFAT_SB(sb); /* set block size to read super block */ sb_min_blocksize(sb, 512); /* read boot sector */ - sbi->pbr_bh = sb_bread(sb, 0); - if (!sbi->pbr_bh) { + sbi->boot_bh = sb_bread(sb, 0); + if (!sbi->boot_bh) { exfat_err(sb, "unable to read boot sector"); return -EIO; } /* PRB is read */ - p_pbr = (struct pbr *)sbi->pbr_bh->b_data; + p_boot = (struct boot_sector *)sbi->boot_bh->b_data; - /* check the validity of PBR */ - if (le16_to_cpu((p_pbr->signature)) != PBR_SIGNATURE) { + /* check the validity of BOOT */ + if (le16_to_cpu((p_boot->signature)) != BOOT_SIGNATURE) { exfat_err(sb, "invalid boot record signature"); ret = -EINVAL; goto free_bh; @@ -635,8 +635,8 @@ static int __exfat_fill_super(struct super_block *sb) /* check logical sector size */ - p_pbr = exfat_read_pbr_with_logical_sector(sb); - if (!p_pbr) { + p_boot = exfat_read_boot_with_logical_sector(sb); + if (!p_boot) { ret = -EIO; goto free_bh; } @@ -645,43 +645,43 @@ static int __exfat_fill_super(struct super_block *sb) * res_zero field must be filled with zero to prevent mounting * from FAT volume. */ - if (memchr_inv(p_pbr->bpb.f64.res_zero, 0, - sizeof(p_pbr->bpb.f64.res_zero))) { + if (memchr_inv(p_boot->must_be_zero, 0, + sizeof(p_boot->must_be_zero))) { ret = -EINVAL; goto free_bh; } - p_bpb = (struct pbr64 *)p_pbr; - if (!p_bpb->bsx.num_fats) { + p_boot = (struct boot_sector *)p_boot; + if (!p_boot->num_fats) { exfat_err(sb, "bogus number of FAT structure"); ret = -EINVAL; goto free_bh; } - sbi->sect_per_clus = 1 << p_bpb->bsx.sect_per_clus_bits; - sbi->sect_per_clus_bits = p_bpb->bsx.sect_per_clus_bits; + sbi->sect_per_clus = 1 << p_boot->sect_per_clus_bits; + sbi->sect_per_clus_bits = p_boot->sect_per_clus_bits; sbi->cluster_size_bits = sbi->sect_per_clus_bits + sb->s_blocksize_bits; sbi->cluster_size = 1 << sbi->cluster_size_bits; - sbi->num_FAT_sectors = le32_to_cpu(p_bpb->bsx.fat_length); - sbi->FAT1_start_sector = le32_to_cpu(p_bpb->bsx.fat_offset); - sbi->FAT2_start_sector = p_bpb->bsx.num_fats == 1 ? + sbi->num_FAT_sectors = le32_to_cpu(p_boot->fat_length); + sbi->FAT1_start_sector = le32_to_cpu(p_boot->fat_offset); + sbi->FAT2_start_sector = p_boot->num_fats == 1 ? sbi->FAT1_start_sector : sbi->FAT1_start_sector + sbi->num_FAT_sectors; - sbi->data_start_sector = le32_to_cpu(p_bpb->bsx.clu_offset); - sbi->num_sectors = le64_to_cpu(p_bpb->bsx.vol_length); + sbi->data_start_sector = le32_to_cpu(p_boot->clu_offset); + sbi->num_sectors = le64_to_cpu(p_boot->vol_length); /* because the cluster index starts with 2 */ - sbi->num_clusters = le32_to_cpu(p_bpb->bsx.clu_count) + + sbi->num_clusters = le32_to_cpu(p_boot->clu_count) + EXFAT_RESERVED_CLUSTERS; - sbi->root_dir = le32_to_cpu(p_bpb->bsx.root_cluster); + sbi->root_dir = le32_to_cpu(p_boot->root_cluster); sbi->dentries_per_clu = 1 << (sbi->cluster_size_bits - DENTRY_SIZE_BITS); - sbi->vol_flag = le16_to_cpu(p_bpb->bsx.vol_flags); + sbi->vol_flag = le16_to_cpu(p_boot->vol_flags); sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER; sbi->used_clusters = EXFAT_CLUSTERS_UNTRACKED; - if (le16_to_cpu(p_bpb->bsx.vol_flags) & VOL_DIRTY) { + if (le16_to_cpu(p_boot->vol_flags) & VOL_DIRTY) { sbi->vol_flag |= VOL_DIRTY; exfat_warn(sb, "Volume was not properly unmounted. Some data may be corrupt. Please run fsck."); } @@ -715,7 +715,7 @@ static int __exfat_fill_super(struct super_block *sb) free_upcase_table: exfat_free_upcase_table(sbi); free_bh: - brelse(sbi->pbr_bh); + brelse(sbi->boot_bh); return ret; } @@ -849,7 +849,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) free_table: exfat_free_upcase_table(sbi); exfat_free_bitmap(sbi); - brelse(sbi->pbr_bh); + brelse(sbi->boot_bh); check_nls_io: unload_nls(sbi->nls_io); From c099996e894512573e8b5da6650ad6c6c0cd5124 Mon Sep 17 00:00:00 2001 From: Tetsuhiro Kohada Date: Sun, 31 May 2020 21:02:31 +0900 Subject: [PATCH 27/36] exfat: separate the boot sector analysis Separate the boot sector analysis to read_boot_sector(). And add a check for the fs_name field. Furthermore, add a strict consistency check, because overlapping areas can cause serious corruption. Signed-off-by: Tetsuhiro Kohada Signed-off-by: Namjae Jeon --- exfat_raw.h | 2 ++ super.c | 97 +++++++++++++++++++++++++++++------------------------ 2 files changed, 56 insertions(+), 43 deletions(-) diff --git a/exfat_raw.h b/exfat_raw.h index 07f74190df44..350ce59cc324 100644 --- a/exfat_raw.h +++ b/exfat_raw.h @@ -10,11 +10,13 @@ #define BOOT_SIGNATURE 0xAA55 #define EXBOOT_SIGNATURE 0xAA550000 +#define STR_EXFAT "EXFAT " /* size should be 8 */ #define EXFAT_MAX_FILE_LEN 255 #define VOL_CLEAN 0x0000 #define VOL_DIRTY 0x0002 +#define ERR_MEDIUM 0x0004 #define EXFAT_EOF_CLUSTER 0xFFFFFFFFu #define EXFAT_BAD_CLUSTER 0xFFFFFFF7u diff --git a/super.c b/super.c index 5ecd977c60ef..13c7658c3018 100644 --- a/super.c +++ b/super.c @@ -564,25 +564,20 @@ static int exfat_read_root(struct inode *inode) return 0; } -static struct boot_sector *exfat_read_boot_with_logical_sector( - struct super_block *sb) +static int exfat_calibrate_blocksize(struct super_block *sb, int logical_sect) { struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct boot_sector *p_boot = (struct boot_sector *)sbi->boot_bh->b_data; - unsigned short logical_sect = 0; - - logical_sect = 1 << p_boot->sect_size_bits; if (!is_power_of_2(logical_sect) || logical_sect < 512 || logical_sect > 4096) { exfat_err(sb, "bogus logical sector size %u", logical_sect); - return NULL; + return -EIO; } if (logical_sect < sb->s_blocksize) { exfat_err(sb, "logical sector size too small for device (logical sector size = %u)", logical_sect); - return NULL; + return -EIO; } if (logical_sect > sb->s_blocksize) { @@ -592,24 +587,20 @@ static struct boot_sector *exfat_read_boot_with_logical_sector( if (!sb_set_blocksize(sb, logical_sect)) { exfat_err(sb, "unable to set blocksize %u", logical_sect); - return NULL; + return -EIO; } sbi->boot_bh = sb_bread(sb, 0); if (!sbi->boot_bh) { exfat_err(sb, "unable to read boot sector (logical sector size = %lu)", sb->s_blocksize); - return NULL; + return -EIO; } - - p_boot = (struct boot_sector *)sbi->boot_bh->b_data; } - return p_boot; + return 0; } -/* mount the file system volume */ -static int __exfat_fill_super(struct super_block *sb) +static int exfat_read_boot_sector(struct super_block *sb) { - int ret; struct boot_sector *p_boot; struct exfat_sb_info *sbi = EXFAT_SB(sb); @@ -622,51 +613,41 @@ static int __exfat_fill_super(struct super_block *sb) exfat_err(sb, "unable to read boot sector"); return -EIO; } - - /* PRB is read */ p_boot = (struct boot_sector *)sbi->boot_bh->b_data; /* check the validity of BOOT */ if (le16_to_cpu((p_boot->signature)) != BOOT_SIGNATURE) { exfat_err(sb, "invalid boot record signature"); - ret = -EINVAL; - goto free_bh; + return -EINVAL; } - - /* check logical sector size */ - p_boot = exfat_read_boot_with_logical_sector(sb); - if (!p_boot) { - ret = -EIO; - goto free_bh; + if (memcmp(p_boot->fs_name, STR_EXFAT, BOOTSEC_FS_NAME_LEN)) { + exfat_err(sb, "invalid fs_name"); /* fs_name may unprintable */ + return -EINVAL; } /* - * res_zero field must be filled with zero to prevent mounting + * must_be_zero field must be filled with zero to prevent mounting * from FAT volume. */ - if (memchr_inv(p_boot->must_be_zero, 0, - sizeof(p_boot->must_be_zero))) { - ret = -EINVAL; - goto free_bh; - } + if (memchr_inv(p_boot->must_be_zero, 0, sizeof(p_boot->must_be_zero))) + return -EINVAL; - p_boot = (struct boot_sector *)p_boot; - if (!p_boot->num_fats) { + if (p_boot->num_fats != 1 && p_boot->num_fats != 2) { exfat_err(sb, "bogus number of FAT structure"); - ret = -EINVAL; - goto free_bh; + return -EINVAL; } sbi->sect_per_clus = 1 << p_boot->sect_per_clus_bits; sbi->sect_per_clus_bits = p_boot->sect_per_clus_bits; - sbi->cluster_size_bits = sbi->sect_per_clus_bits + sb->s_blocksize_bits; + sbi->cluster_size_bits = p_boot->sect_per_clus_bits + + p_boot->sect_size_bits; sbi->cluster_size = 1 << sbi->cluster_size_bits; sbi->num_FAT_sectors = le32_to_cpu(p_boot->fat_length); sbi->FAT1_start_sector = le32_to_cpu(p_boot->fat_offset); - sbi->FAT2_start_sector = p_boot->num_fats == 1 ? - sbi->FAT1_start_sector : - sbi->FAT1_start_sector + sbi->num_FAT_sectors; + sbi->FAT2_start_sector = le32_to_cpu(p_boot->fat_offset); + if (p_boot->num_fats == 2) + sbi->FAT2_start_sector += sbi->num_FAT_sectors; sbi->data_start_sector = le32_to_cpu(p_boot->clu_offset); sbi->num_sectors = le64_to_cpu(p_boot->vol_length); /* because the cluster index starts with 2 */ @@ -681,15 +662,45 @@ static int __exfat_fill_super(struct super_block *sb) sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER; sbi->used_clusters = EXFAT_CLUSTERS_UNTRACKED; - if (le16_to_cpu(p_boot->vol_flags) & VOL_DIRTY) { - sbi->vol_flag |= VOL_DIRTY; - exfat_warn(sb, "Volume was not properly unmounted. Some data may be corrupt. Please run fsck."); + /* check consistencies */ + if (sbi->num_FAT_sectors << p_boot->sect_size_bits < + sbi->num_clusters * 4) { + exfat_err(sb, "bogus fat length"); + return -EINVAL; + } + if (sbi->data_start_sector < + sbi->FAT1_start_sector + sbi->num_FAT_sectors * p_boot->num_fats) { + exfat_err(sb, "bogus data start sector"); + return -EINVAL; } + if (sbi->vol_flag & VOL_DIRTY) + exfat_warn(sb, "Volume was not properly unmounted. Some data may be corrupt. Please run fsck."); + if (sbi->vol_flag & ERR_MEDIUM) + exfat_warn(sb, "Medium has reported failures. Some data may be lost."); /* exFAT file size is limited by a disk volume size */ sb->s_maxbytes = (u64)(sbi->num_clusters - EXFAT_RESERVED_CLUSTERS) << sbi->cluster_size_bits; + /* check logical sector size */ + if (exfat_calibrate_blocksize(sb, 1 << p_boot->sect_size_bits)) + return -EIO; + + return 0; +} + +/* mount the file system volume */ +static int __exfat_fill_super(struct super_block *sb) +{ + int ret; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + + ret = exfat_read_boot_sector(sb); + if (ret) { + exfat_err(sb, "failed to read boot sector"); + goto free_bh; + } + ret = exfat_create_upcase_table(sb); if (ret) { exfat_err(sb, "failed to load upcase table"); From af08f9af6f683f34939750170fd92322b11b0ca4 Mon Sep 17 00:00:00 2001 From: Tetsuhiro Kohada Date: Sun, 31 May 2020 21:03:19 +0900 Subject: [PATCH 28/36] exfat: add boot region verification Add Boot-Regions verification specified in exFAT specification. Note that the checksum type is strongly related to the raw structure, so the'u32 'type is used to clarify the number of bits. Signed-off-by: Tetsuhiro Kohada Signed-off-by: Namjae Jeon --- exfat_fs.h | 1 + misc.c | 14 ++++++++++++++ super.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/exfat_fs.h b/exfat_fs.h index 838b3e055c10..29a289221470 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -521,6 +521,7 @@ void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 *tz, __le16 *time, __le16 *date, u8 *time_cs); unsigned short exfat_calc_chksum_2byte(void *data, int len, unsigned short chksum, int type); +u32 exfat_calc_chksum32(void *data, int len, u32 chksum, int type); void exfat_update_bh(struct super_block *sb, struct buffer_head *bh, int sync); void exfat_chain_set(struct exfat_chain *ec, unsigned int dir, unsigned int size, unsigned char flags); diff --git a/misc.c b/misc.c index 3971cf14150d..f8e192285d62 100644 --- a/misc.c +++ b/misc.c @@ -157,6 +157,20 @@ unsigned short exfat_calc_chksum_2byte(void *data, int len, return chksum; } +u32 exfat_calc_chksum32(void *data, int len, u32 chksum, int type) +{ + int i; + u8 *c = (u8 *)data; + + for (i = 0; i < len; i++, c++) { + if (unlikely(type == CS_BOOT_SECTOR && + (i == 106 || i == 107 || i == 112))) + continue; + chksum = ((chksum << 31) | (chksum >> 1)) + *c; + } + return chksum; +} + void exfat_update_bh(struct super_block *sb, struct buffer_head *bh, int sync) { set_bit(EXFAT_SB_DIRTY, &EXFAT_SB(sb)->s_state); diff --git a/super.c b/super.c index 13c7658c3018..e48a92e940ac 100644 --- a/super.c +++ b/super.c @@ -689,6 +689,50 @@ static int exfat_read_boot_sector(struct super_block *sb) return 0; } +static int exfat_verify_boot_region(struct super_block *sb) +{ + struct buffer_head *bh = NULL; + u32 chksum = 0; + __le32 *p_sig, *p_chksum; + int sn, i; + + /* read boot sector sub-regions */ + for (sn = 0; sn < 11; sn++) { + bh = sb_bread(sb, sn); + if (!bh) + return -EIO; + + if (sn != 0 && sn <= 8) { + /* extended boot sector sub-regions */ + p_sig = (__le32 *)&bh->b_data[sb->s_blocksize - 4]; + if (le32_to_cpu(*p_sig) != EXBOOT_SIGNATURE) + exfat_warn(sb, "Invalid exboot-signature(sector = %d): 0x%08x", + sn, le32_to_cpu(*p_sig)); + } + + chksum = exfat_calc_chksum32(bh->b_data, sb->s_blocksize, + chksum, sn ? CS_DEFAULT : CS_BOOT_SECTOR); + brelse(bh); + } + + /* boot checksum sub-regions */ + bh = sb_bread(sb, sn); + if (!bh) + return -EIO; + + for (i = 0; i < sb->s_blocksize; i += sizeof(u32)) { + p_chksum = (__le32 *)&bh->b_data[i]; + if (le32_to_cpu(*p_chksum) != chksum) { + exfat_err(sb, "Invalid boot checksum (boot checksum : 0x%08x, checksum : 0x%08x)", + le32_to_cpu(*p_chksum), chksum); + brelse(bh); + return -EINVAL; + } + } + brelse(bh); + return 0; +} + /* mount the file system volume */ static int __exfat_fill_super(struct super_block *sb) { @@ -701,6 +745,12 @@ static int __exfat_fill_super(struct super_block *sb) goto free_bh; } + ret = exfat_verify_boot_region(sb); + if (ret) { + exfat_err(sb, "invalid boot region"); + goto free_bh; + } + ret = exfat_create_upcase_table(sb); if (ret) { exfat_err(sb, "failed to load upcase table"); From 149ef0460efc792eb60ac8fdfb12210b23af2ade Mon Sep 17 00:00:00 2001 From: Tetsuhiro Kohada Date: Sun, 31 May 2020 21:04:00 +0900 Subject: [PATCH 29/36] exfat: standardize checksum calculation To clarify that it is a 16-bit checksum, the parts related to the 16-bit checksum are renamed and change type to u16. Furthermore, replace checksum calculation in exfat_load_upcase_table() with exfat_calc_checksum32(). Signed-off-by: Tetsuhiro Kohada Signed-off-by: Namjae Jeon --- dir.c | 12 ++++++------ exfat_fs.h | 5 ++--- misc.c | 10 ++++------ nls.c | 19 +++++++------------ 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/dir.c b/dir.c index a6f19f01323a..039e3b60cf3a 100644 --- a/dir.c +++ b/dir.c @@ -496,7 +496,7 @@ int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, int ret = 0; int i, num_entries; sector_t sector; - unsigned short chksum; + u16 chksum; struct exfat_dentry *ep, *fep; struct buffer_head *fbh, *bh; @@ -505,7 +505,7 @@ int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, return -EIO; num_entries = fep->dentry.file.num_ext + 1; - chksum = exfat_calc_chksum_2byte(fep, DENTRY_SIZE, 0, CS_DIR_ENTRY); + chksum = exfat_calc_chksum16(fep, DENTRY_SIZE, 0, CS_DIR_ENTRY); for (i = 1; i < num_entries; i++) { ep = exfat_get_dentry(sb, p_dir, entry + i, &bh, NULL); @@ -513,7 +513,7 @@ int exfat_update_dir_chksum(struct inode *inode, struct exfat_chain *p_dir, ret = -EIO; goto release_fbh; } - chksum = exfat_calc_chksum_2byte(ep, DENTRY_SIZE, chksum, + chksum = exfat_calc_chksum16(ep, DENTRY_SIZE, chksum, CS_DEFAULT); brelse(bh); } @@ -598,8 +598,8 @@ void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es) for (i = 0; i < es->num_entries; i++) { ep = exfat_get_dentry_cached(es, i); - chksum = exfat_calc_chksum_2byte(ep, DENTRY_SIZE, chksum, - chksum_type); + chksum = exfat_calc_chksum16(ep, DENTRY_SIZE, chksum, + chksum_type); chksum_type = CS_DEFAULT; } ep = exfat_get_dentry_cached(es, 0); @@ -1005,7 +1005,7 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, } if (entry_type == TYPE_STREAM) { - unsigned short name_hash; + u16 name_hash; if (step != DIRENT_STEP_STRM) { step = DIRENT_STEP_FILE; diff --git a/exfat_fs.h b/exfat_fs.h index 29a289221470..59fbfa3d737b 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -138,7 +138,7 @@ struct exfat_dentry_namebuf { struct exfat_uni_name { /* +3 for null and for converting */ unsigned short name[MAX_NAME_LENGTH + 3]; - unsigned short name_hash; + u16 name_hash; unsigned char name_len; }; @@ -519,8 +519,7 @@ void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, void exfat_truncate_atime(struct timespec64 *ts); void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 *tz, __le16 *time, __le16 *date, u8 *time_cs); -unsigned short exfat_calc_chksum_2byte(void *data, int len, - unsigned short chksum, int type); +u16 exfat_calc_chksum16(void *data, int len, u16 chksum, int type); u32 exfat_calc_chksum32(void *data, int len, u32 chksum, int type); void exfat_update_bh(struct super_block *sb, struct buffer_head *bh, int sync); void exfat_chain_set(struct exfat_chain *ec, unsigned int dir, diff --git a/misc.c b/misc.c index f8e192285d62..c61ead46b428 100644 --- a/misc.c +++ b/misc.c @@ -142,17 +142,15 @@ void exfat_truncate_atime(struct timespec64 *ts) ts->tv_nsec = 0; } -unsigned short exfat_calc_chksum_2byte(void *data, int len, - unsigned short chksum, int type) +u16 exfat_calc_chksum16(void *data, int len, u16 chksum, int type) { int i; - unsigned char *c = (unsigned char *)data; + u8 *c = (u8 *)data; for (i = 0; i < len; i++, c++) { - if (((i == 2) || (i == 3)) && (type == CS_DIR_ENTRY)) + if (unlikely(type == CS_DIR_ENTRY && (i == 2 || i == 3))) continue; - chksum = (((chksum & 1) << 15) | ((chksum & 0xFFFE) >> 1)) + - (unsigned short)*c; + chksum = ((chksum << 15) | (chksum >> 1)) + *c; } return chksum; } diff --git a/nls.c b/nls.c index 1ebda90cbdd7..19321773dd07 100644 --- a/nls.c +++ b/nls.c @@ -527,7 +527,7 @@ static int exfat_utf8_to_utf16(struct super_block *sb, *uniname = '\0'; p_uniname->name_len = unilen; - p_uniname->name_hash = exfat_calc_chksum_2byte(upname, unilen << 1, 0, + p_uniname->name_hash = exfat_calc_chksum16(upname, unilen << 1, 0, CS_DEFAULT); if (p_lossy) @@ -623,7 +623,7 @@ static int exfat_nls_to_ucs2(struct super_block *sb, *uniname = '\0'; p_uniname->name_len = unilen; - p_uniname->name_hash = exfat_calc_chksum_2byte(upname, unilen << 1, 0, + p_uniname->name_hash = exfat_calc_chksum16(upname, unilen << 1, 0, CS_DEFAULT); if (p_lossy) @@ -655,7 +655,8 @@ static int exfat_load_upcase_table(struct super_block *sb, { struct exfat_sb_info *sbi = EXFAT_SB(sb); unsigned int sect_size = sb->s_blocksize; - unsigned int i, index = 0, checksum = 0; + unsigned int i, index = 0; + u32 chksum = 0; int ret; unsigned char skip = false; unsigned short *upcase_table; @@ -681,13 +682,6 @@ static int exfat_load_upcase_table(struct super_block *sb, for (i = 0; i < sect_size && index <= 0xFFFF; i += 2) { unsigned short uni = get_unaligned_le16(bh->b_data + i); - checksum = ((checksum & 1) ? 0x80000000 : 0) + - (checksum >> 1) + - *(((unsigned char *)bh->b_data) + i); - checksum = ((checksum & 1) ? 0x80000000 : 0) + - (checksum >> 1) + - *(((unsigned char *)bh->b_data) + (i + 1)); - if (skip) { index += uni; skip = false; @@ -701,13 +695,14 @@ static int exfat_load_upcase_table(struct super_block *sb, } } brelse(bh); + chksum = exfat_calc_chksum32(bh->b_data, i, chksum, CS_DEFAULT); } - if (index >= 0xFFFF && utbl_checksum == checksum) + if (index >= 0xFFFF && utbl_checksum == chksum) return 0; exfat_err(sb, "failed to load upcase table (idx : 0x%08x, chksum : 0x%08x, utbl_chksum : 0x%08x)", - index, checksum, utbl_checksum); + index, chksum, utbl_checksum); ret = -EINVAL; free_table: exfat_free_upcase_table(sbi); From 6a3cf32b8b4054b86ad12a126dfca871bc5d31d4 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sun, 31 May 2020 21:30:59 +0900 Subject: [PATCH 30/36] exfat: add the dummy mount options to be backward compatible with staging/exfat As Ubuntu and Fedora release new version used kernel version equal to or higher than v5.4, They started to support kernel exfat filesystem. Linus reported a mount error with new version of exfat on Fedora: exfat: Unknown parameter 'namecase' This is because there is a difference in mount option between old staging/exfat and new exfat. And utf8, debug, and codepage options as well as namecase have been removed from new exfat. This patch add the dummy mount options as deprecated option to be backward compatible with old one. Reported-by: Linus Torvalds Signed-off-by: Namjae Jeon Cc: Matthew Wilcox Cc: Al Viro Cc: Eric Sandeen Signed-off-by: Linus Torvalds --- super.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/super.c b/super.c index e48a92e940ac..860b40c8f6b0 100644 --- a/super.c +++ b/super.c @@ -238,6 +238,12 @@ enum { Opt_errors, Opt_discard, Opt_time_offset, + + /* Deprecated options */ + Opt_utf8, + Opt_debug, + Opt_namecase, + Opt_codepage, }; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) @@ -275,6 +281,14 @@ static const struct fs_parameter_spec exfat_param_specs[] = { #endif fsparam_flag("discard", Opt_discard), fsparam_s32("time_offset", Opt_time_offset), + __fsparam(NULL, "utf8", Opt_utf8, fs_param_deprecated, + NULL), + __fsparam(NULL, "debug", Opt_debug, fs_param_deprecated, + NULL), + __fsparam(fs_param_is_u32, "namecase", Opt_namecase, + fs_param_deprecated, NULL), + __fsparam(fs_param_is_u32, "codepage", Opt_codepage, + fs_param_deprecated, NULL), {} }; @@ -342,6 +356,11 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param) return -EINVAL; opts->time_offset = result.int_32; break; + case Opt_utf8: + case Opt_debug: + case Opt_namecase: + case Opt_codepage: + break; default: return -EINVAL; } @@ -363,6 +382,12 @@ enum { Opt_err, Opt_discard, Opt_time_offset, + + /* Deprecated options */ + Opt_utf8, + Opt_debug, + Opt_namecase, + Opt_codepage, Opt_fs, }; @@ -378,6 +403,10 @@ static const match_table_t exfat_tokens = { {Opt_err_panic, "errors=panic"}, {Opt_err_ro, "errors=remount-ro"}, {Opt_discard, "discard"}, + {Opt_codepage, "codepage=%u"}, + {Opt_namecase, "namecase=%u"}, + {Opt_debug, "debug"}, + {Opt_utf8, "utf8"}, {Opt_err, NULL} }; @@ -464,6 +493,11 @@ static int parse_options(struct super_block *sb, char *options, int silent, return -EINVAL; opts->time_offset = option; break; + case Opt_utf8: + case Opt_debug: + case Opt_namecase: + case Opt_codepage: + break; default: if (!silent) { exfat_msg(sb, KERN_ERR, From 726d9e53274ab5247fc502f114835e8a7ce83335 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 1 Jun 2020 11:28:34 +0900 Subject: [PATCH 31/36] exfat: remove unnecessary reassignment for p_uniname->name_len kbuild test robot reported : fs/exfat/nls.c:531:22: warning: Variable 'p_uniname->name_len' is reassigned a value before the old one has been used. The reassignment for p_uniname->name_len is not needed and remove it. Reported-by: kbuild test robot Signed-off-by: Namjae Jeon --- nls.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/nls.c b/nls.c index 19321773dd07..c1ec05695497 100644 --- a/nls.c +++ b/nls.c @@ -514,8 +514,6 @@ static int exfat_utf8_to_utf16(struct super_block *sb, return -ENAMETOOLONG; } - p_uniname->name_len = unilen & 0xFF; - for (i = 0; i < unilen; i++) { if (*uniname < 0x0020 || exfat_wstrchr(bad_uni_chars, *uniname)) From a9ac0a3eb4d5cefaa84fd0a79cc2114b39aa38d7 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 1 Jun 2020 11:29:34 +0900 Subject: [PATCH 32/36] exfat: fix incorrect update of stream entry in __exfat_truncate() At truncate, there is a problem of incorrect updating in the file entry pointer instead of stream entry. This will cause the problem of overwriting the time field of the file entry to new_size. This patch change entry pointer with stream entry. Signed-off-by: Namjae Jeon --- file.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/file.c b/file.c index e529b66b93ab..a55e0b6e99e1 100644 --- a/file.c +++ b/file.c @@ -184,11 +184,11 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) /* File size should be zero if there is no cluster allocated */ if (ei->start_clu == EXFAT_EOF_CLUSTER) { - ep->dentry.stream.valid_size = 0; - ep->dentry.stream.size = 0; + ep2->dentry.stream.valid_size = 0; + ep2->dentry.stream.size = 0; } else { - ep->dentry.stream.valid_size = cpu_to_le64(new_size); - ep->dentry.stream.size = ep->dentry.stream.valid_size; + ep2->dentry.stream.valid_size = cpu_to_le64(new_size); + ep2->dentry.stream.size = ep->dentry.stream.valid_size; } if (new_size == 0) { From cd342c404e9b0f6eed12beb1de2fd9f17563aceb Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 4 Jun 2020 16:15:10 +0900 Subject: [PATCH 33/36] exfat: don't create exfat directory in the latest kernel Signed-off-by: Namjae Jeon --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ebee78e40683..395960808002 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,6 @@ script: - mv linux ../ - mkdir ../linux-stable/fs/exfat - cp -ar * ../linux-stable/fs/exfat/ - - mkdir ../linux/fs/exfat - cp -ar * ../linux/fs/exfat/ # Compile with 4.1 kernel From 029c820f74f88cdb533e05bb47cbd6f1c6c2c3a2 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 9 Jun 2020 08:43:07 +0900 Subject: [PATCH 34/36] exfat: remove metadata journing tests Signed-off-by: Namjae Jeon --- .travis.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 395960808002..d1d35e77b6f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -71,6 +71,7 @@ script: - cd /mnt/full_test/ - i=1;while [ $i -le 10000 ];do sudo touch file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done - sync + - sudo fsck.exfat /dev/loop22 - sudo rm -rf * - i=1;while [ $i -le 10000 ];do sudo mkdir file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done - sync @@ -78,6 +79,7 @@ script: - sudo fsck.exfat /dev/loop22 - cd - - sudo umount /mnt/full_test/ + - sudo fsck.exfat /dev/loop22 # run xfstests test - truncate -s 100G test.img - truncate -s 100G scratch.img @@ -113,7 +115,6 @@ script: - sudo ./check generic/095 - sudo ./check generic/098 - sudo ./check generic/100 - - sudo ./check generic/101 - sudo ./check generic/112 - sudo ./check generic/113 - sudo ./check generic/114 @@ -154,13 +155,11 @@ script: - sudo ./check generic/309 - sudo ./check generic/310 - sudo ./check generic/313 - - sudo ./check generic/322 - sudo ./check generic/323 - sudo ./check generic/325 - sudo ./check generic/338 - sudo ./check generic/339 - sudo ./check generic/340 - - sudo ./check generic/342 - sudo ./check generic/344 - sudo ./check generic/345 - sudo ./check generic/346 @@ -184,4 +183,4 @@ script: - sudo ./check generic/448 - sudo ./check generic/450 - sudo ./check generic/451 - - sudo ./check generic/452 + # - sudo ./check generic/452 From eef5c756ed0bd58c36e52d19b7cd6edf0d7ca7e9 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 9 Jun 2020 08:44:16 +0900 Subject: [PATCH 35/36] exfat: Fix pontential use after free in exfat_load_upcase_table() This code calls brelse(bh) and then dereferences "bh" on the next line resulting in a possible use after free. The brelse() should just be moved down a line. Fixes: b676fdbcf4c8 ("exfat: standardize checksum calculation") Signed-off-by: Dan Carpenter Signed-off-by: Namjae Jeon --- nls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nls.c b/nls.c index c1ec05695497..57b5a7a4d1f7 100644 --- a/nls.c +++ b/nls.c @@ -692,8 +692,8 @@ static int exfat_load_upcase_table(struct super_block *sb, index++; } } - brelse(bh); chksum = exfat_calc_chksum32(bh->b_data, i, chksum, CS_DEFAULT); + brelse(bh); } if (index >= 0xFFFF && utbl_checksum == chksum) From d599ce4b37d811aa90e14d1b3b5d0c1743d6d299 Mon Sep 17 00:00:00 2001 From: "hyeongseok.kim" Date: Thu, 4 Jun 2020 15:45:12 +0900 Subject: [PATCH 36/36] exfat: fix range validation error in alloc and free cluster There is check error in range condition that can never be entered even with invalid input. Replace incorrent checking code with already existing valid checker. Signed-off-by: hyeongseok.kim Signed-off-by: Namjae Jeon --- fatent.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fatent.c b/fatent.c index 12b177035f74..c264eda3c27b 100644 --- a/fatent.c +++ b/fatent.c @@ -177,7 +177,7 @@ int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) return 0; /* check cluster validation */ - if (p_chain->dir < 2 && p_chain->dir >= sbi->num_clusters) { + if (!is_valid_cluster(sbi, p_chain->dir)) { exfat_err(sb, "invalid start cluster (%u)", p_chain->dir); return -EIO; } @@ -354,7 +354,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, } /* check cluster validation */ - if (hint_clu < EXFAT_FIRST_CLUSTER && hint_clu >= sbi->num_clusters) { + if (!is_valid_cluster(sbi, hint_clu)) { exfat_err(sb, "hint_cluster is invalid (%u)", hint_clu); hint_clu = EXFAT_FIRST_CLUSTER;