From 898b0ec688540473bb9ef7abbc343eb9dfc9698f Mon Sep 17 00:00:00 2001 From: Hyeongseok Kim Date: Mon, 1 Feb 2021 10:09:48 +0900 Subject: [PATCH 01/15] exfat: improve performance of exfat_free_cluster when using dirsync mount option There are stressful update of cluster allocation bitmap when using dirsync mount option which is doing sync buffer on every cluster bit clearing. This could result in performance degradation when deleting big size file. Fix to update only when the bitmap buffer index is changed would make less disk access, improving performance especially for truncate operation. Testing with Samsung 256GB sdcard, mounted with dirsync option (mount -t exfat /dev/block/mmcblk0p1 /temp/mount -o dirsync) Remove 4GB file, blktrace result. [Before] : 39 secs. Total (blktrace): Reads Queued: 0, 0KiB Writes Queued: 32775, 16387KiB Read Dispatches: 0, 0KiB Write Dispatches: 32775, 16387KiB Reads Requeued: 0 Writes Requeued: 0 Reads Completed: 0, 0KiB Writes Completed: 32775, 16387KiB Read Merges: 0, 0KiB Write Merges: 0, 0KiB IO unplugs: 2 Timer unplugs: 0 [After] : 1 sec. Total (blktrace): Reads Queued: 0, 0KiB Writes Queued: 13, 6KiB Read Dispatches: 0, 0KiB Write Dispatches: 13, 6KiB Reads Requeued: 0 Writes Requeued: 0 Reads Completed: 0, 0KiB Writes Completed: 13, 6KiB Read Merges: 0, 0KiB Write Merges: 0, 0KiB IO unplugs: 1 Timer unplugs: 0 Signed-off-by: Hyeongseok Kim Acked-by: Sungjong Seo Signed-off-by: Namjae Jeon --- balloc.c | 4 ++-- exfat_fs.h | 2 +- fatent.c | 45 ++++++++++++++++++++++++++++++++++++++------- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/balloc.c b/balloc.c index a987919686c0..761c79c3a4ba 100644 --- a/balloc.c +++ b/balloc.c @@ -166,7 +166,7 @@ int exfat_set_bitmap(struct inode *inode, unsigned int clu) * If the value of "clu" is 0, it means cluster 2 which is the first cluster of * the cluster heap. */ -void exfat_clear_bitmap(struct inode *inode, unsigned int clu) +void exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync) { int i, b; unsigned int ent_idx; @@ -180,7 +180,7 @@ void exfat_clear_bitmap(struct inode *inode, unsigned int clu) b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx); clear_bit_le(b, sbi->vol_amap[i]->b_data); - exfat_update_bh(sbi->vol_amap[i], IS_DIRSYNC(inode)); + exfat_update_bh(sbi->vol_amap[i], sync); if (opts->discard) { int ret_discard; diff --git a/exfat_fs.h b/exfat_fs.h index 89ea4942f88f..9d582e7ac3ad 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -421,7 +421,7 @@ int exfat_count_num_clusters(struct super_block *sb, int exfat_load_bitmap(struct super_block *sb); void exfat_free_bitmap(struct exfat_sb_info *sbi); int exfat_set_bitmap(struct inode *inode, unsigned int clu); -void exfat_clear_bitmap(struct inode *inode, unsigned int clu); +void exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync); unsigned int exfat_find_free_bitmap(struct super_block *sb, unsigned int clu); int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count); diff --git a/fatent.c b/fatent.c index f2afda61dbb5..dd54570410a3 100644 --- a/fatent.c +++ b/fatent.c @@ -165,6 +165,7 @@ int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) unsigned int clu; struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); + int cur_cmap_i, next_cmap_i; /* invalid cluster number */ if (p_chain->dir == EXFAT_FREE_CLUSTER || @@ -184,21 +185,51 @@ int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) clu = p_chain->dir; - if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { - do { - exfat_clear_bitmap(inode, clu); - clu++; + cur_cmap_i = next_cmap_i = + BITMAP_OFFSET_SECTOR_INDEX(sb, CLUSTER_TO_BITMAP_ENT(clu)); + if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { + unsigned int last_cluster = p_chain->dir + p_chain->size - 1; + do { + bool sync = false; + + if (clu < last_cluster) + next_cmap_i = + BITMAP_OFFSET_SECTOR_INDEX(sb, CLUSTER_TO_BITMAP_ENT(clu+1)); + + /* flush bitmap only if index would be changed or for last cluster */ + if (clu == last_cluster || cur_cmap_i != next_cmap_i) { + sync = true; + cur_cmap_i = next_cmap_i; + } + + exfat_clear_bitmap(inode, clu, (sync && IS_DIRSYNC(inode))); + clu++; num_clusters++; } while (num_clusters < p_chain->size); } else { do { - exfat_clear_bitmap(inode, clu); + bool sync = false; + unsigned int n_clu = clu; + int err = exfat_get_next_cluster(sb, &n_clu); - if (exfat_get_next_cluster(sb, &clu)) - goto dec_used_clus; + if (err || n_clu == EXFAT_EOF_CLUSTER) + sync = true; + else + next_cmap_i = + BITMAP_OFFSET_SECTOR_INDEX(sb, CLUSTER_TO_BITMAP_ENT(n_clu)); + if (cur_cmap_i != next_cmap_i) { + sync = true; + cur_cmap_i = next_cmap_i; + } + + exfat_clear_bitmap(inode, clu, (sync && IS_DIRSYNC(inode))); + clu = n_clu; num_clusters++; + + if (err) + goto dec_used_clus; } while (clu != EXFAT_EOF_CLUSTER); } From 1b618f4cbe386da47512a28b4fec14a41400b32c Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Tue, 12 Jan 2021 10:49:36 +0900 Subject: [PATCH 02/15] exfat: add remount_fs operation for 5.4 kernel The remount_fs operations should be defined for kernel 5.4, because fs_context is not provided for the 5.4 kernel and the operation will be called when exfat is remounted. This fixes xfstests generic/452 failure. Signed-off-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- super.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/super.c b/super.c index 96b31b1ea59e..f4d19efeea41 100644 --- a/super.c +++ b/super.c @@ -224,7 +224,9 @@ static void exfat_destroy_inode(struct inode *inode) { call_rcu(&inode->i_rcu, exfat_i_callback); } +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) static int exfat_remount(struct super_block *sb, int *flags, char *data) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0) @@ -244,6 +246,8 @@ static const struct super_operations exfat_sops = { .free_inode = exfat_free_inode, #else .destroy_inode = exfat_destroy_inode, +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0) .remount_fs = exfat_remount, #endif .write_inode = exfat_write_inode, From 5d39deced19012f2a377d85abc2887d1b1e43d5e Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 1 Feb 2021 10:34:37 +0900 Subject: [PATCH 03/15] exfat: fix shift-out-of-bounds in exfat_fill_super() syzbot reported a warning which could cause shift-out-of-bounds issue. Call Trace: __dump_stack lib/dump_stack.c:79 [inline] dump_stack+0x183/0x22e lib/dump_stack.c:120 ubsan_epilogue lib/ubsan.c:148 [inline] __ubsan_handle_shift_out_of_bounds+0x432/0x4d0 lib/ubsan.c:395 exfat_read_boot_sector fs/exfat/super.c:471 [inline] __exfat_fill_super fs/exfat/super.c:556 [inline] exfat_fill_super+0x2acb/0x2d00 fs/exfat/super.c:624 get_tree_bdev+0x406/0x630 fs/super.c:1291 vfs_get_tree+0x86/0x270 fs/super.c:1496 do_new_mount fs/namespace.c:2881 [inline] path_mount+0x1937/0x2c50 fs/namespace.c:3211 do_mount fs/namespace.c:3224 [inline] __do_sys_mount fs/namespace.c:3432 [inline] __se_sys_mount+0x2f9/0x3b0 fs/namespace.c:3409 do_syscall_64+0x2d/0x70 arch/x86/entry/common.c:46 entry_SYSCALL_64_after_hwframe+0x44/0xa9 exfat specification describe sect_per_clus_bits field of boot sector could be at most 25 - sect_size_bits and at least 0. And sect_size_bits can also affect this calculation, It also needs validation. This patch add validation for sect_per_clus_bits and sect_size_bits field of boot sector. Fixes: 719c1e182916 ("exfat: add super block operations") Cc: stable@vger.kernel.org # v5.9+ Reported-by: syzbot+da4fe66aaadd3c2e2d1c@syzkaller.appspotmail.com Reviewed-by: Sungjong Seo Tested-by: Randy Dunlap Signed-off-by: Namjae Jeon --- exfat_raw.h | 4 ++++ super.c | 31 ++++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/exfat_raw.h b/exfat_raw.h index 6aec6288e1f2..7f39b1c6469c 100644 --- a/exfat_raw.h +++ b/exfat_raw.h @@ -77,6 +77,10 @@ #define EXFAT_FILE_NAME_LEN 15 +#define EXFAT_MIN_SECT_SIZE_BITS 9 +#define EXFAT_MAX_SECT_SIZE_BITS 12 +#define EXFAT_MAX_SECT_PER_CLUS_BITS(x) (25 - (x)->sect_size_bits) + /* EXFAT: Main and Backup Boot Sector (512 bytes) */ struct boot_sector { __u8 jmp_boot[BOOTSEC_JUMP_BOOT_LEN]; diff --git a/super.c b/super.c index f4d19efeea41..c140e358f482 100644 --- a/super.c +++ b/super.c @@ -631,8 +631,7 @@ static int exfat_calibrate_blocksize(struct super_block *sb, int logical_sect) { struct exfat_sb_info *sbi = EXFAT_SB(sb); - if (!is_power_of_2(logical_sect) || - logical_sect < 512 || logical_sect > 4096) { + if (!is_power_of_2(logical_sect)) { exfat_err(sb, "bogus logical sector size %u", logical_sect); return -EIO; } @@ -701,6 +700,25 @@ static int exfat_read_boot_sector(struct super_block *sb) return -EINVAL; } + /* + * sect_size_bits could be at least 9 and at most 12. + */ + if (p_boot->sect_size_bits < EXFAT_MIN_SECT_SIZE_BITS || + p_boot->sect_size_bits > EXFAT_MAX_SECT_SIZE_BITS) { + exfat_err(sb, "bogus sector size bits : %u\n", + p_boot->sect_size_bits); + return -EINVAL; + } + + /* + * sect_per_clus_bits could be at least 0 and at most 25 - sect_size_bits. + */ + if (p_boot->sect_per_clus_bits > EXFAT_MAX_SECT_PER_CLUS_BITS(p_boot)) { + exfat_err(sb, "bogus sectors bits per cluster : %u\n", + p_boot->sect_per_clus_bits); + 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 = p_boot->sect_per_clus_bits + @@ -727,16 +745,19 @@ static int exfat_read_boot_sector(struct super_block *sb) sbi->used_clusters = EXFAT_CLUSTERS_UNTRACKED; /* check consistencies */ - if (sbi->num_FAT_sectors << p_boot->sect_size_bits < - sbi->num_clusters * 4) { + if ((u64)sbi->num_FAT_sectors << p_boot->sect_size_bits < + (u64)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) { + (u64)sbi->FAT1_start_sector + + (u64)sbi->num_FAT_sectors * p_boot->num_fats) { exfat_err(sb, "bogus data start sector"); return -EINVAL; } + if (sbi->vol_flags & VOLUME_DIRTY) exfat_warn(sb, "Volume was not properly unmounted. Some data may be corrupt. Please run fsck."); if (sbi->vol_flags & MEDIA_FAILURE) From 73138cb2dca9af189e4d21ac5c9f98355714b44a Mon Sep 17 00:00:00 2001 From: Hyeongseok Kim Date: Thu, 4 Mar 2021 08:42:45 +0900 Subject: [PATCH 04/15] exfat: fix erroneous discard when clear cluster bit If mounted with discard option, exFAT issues discard command when clear cluster bit to remove file. But the input parameter of cluster-to-sector calculation is abnormally added by reserved cluster size which is 2, leading to discard unrelated sectors included in target+2 cluster. With fixing this, remove the wrong comments in set/clear/find bitmap functions. Fixes: 1e49a94cf707 ("exfat: add bitmap operations") Cc: stable@vger.kernel.org # v5.7+ Signed-off-by: Hyeongseok Kim Acked-by: Sungjong Seo Signed-off-by: Namjae Jeon --- balloc.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/balloc.c b/balloc.c index 761c79c3a4ba..411fb0a8da10 100644 --- a/balloc.c +++ b/balloc.c @@ -141,10 +141,6 @@ void exfat_free_bitmap(struct exfat_sb_info *sbi) kfree(sbi->vol_amap); } -/* - * If the value of "clu" is 0, it means cluster 2 which is the first cluster of - * the cluster heap. - */ int exfat_set_bitmap(struct inode *inode, unsigned int clu) { int i, b; @@ -162,10 +158,6 @@ int exfat_set_bitmap(struct inode *inode, unsigned int clu) return 0; } -/* - * If the value of "clu" is 0, it means cluster 2 which is the first cluster of - * the cluster heap. - */ void exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync) { int i, b; @@ -186,8 +178,7 @@ void exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync) int ret_discard; ret_discard = sb_issue_discard(sb, - exfat_cluster_to_sector(sbi, clu + - EXFAT_RESERVED_CLUSTERS), + exfat_cluster_to_sector(sbi, clu), (1 << sbi->sect_per_clus_bits), GFP_NOFS, 0); if (ret_discard == -EOPNOTSUPP) { From 7646a7516df2b6eb243f386d4a8e2df5be85be5f Mon Sep 17 00:00:00 2001 From: Hyeongseok Kim Date: Thu, 25 Mar 2021 08:09:50 +0900 Subject: [PATCH 05/15] exfat: introduce bitmap_lock for cluster bitmap access s_lock which is for protecting concurrent access of file operations is too huge for cluster bitmap protection, so introduce a new bitmap_lock to narrow the lock range if only need to access cluster bitmap. Signed-off-by: Hyeongseok Kim Acked-by: Sungjong Seo Signed-off-by: Namjae Jeon --- exfat_fs.h | 1 + fatent.c | 37 +++++++++++++++++++++++++++++-------- super.c | 2 ++ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 9d582e7ac3ad..139ee3d52d2d 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -247,6 +247,7 @@ struct exfat_sb_info { unsigned int used_clusters; /* number of used clusters */ struct mutex s_lock; /* superblock lock */ + struct mutex bitmap_lock; /* bitmap lock */ struct exfat_mount_options options; struct nls_table *nls_io; /* Charset used for input and display */ struct ratelimit_state ratelimit; diff --git a/fatent.c b/fatent.c index dd54570410a3..74f3092a277d 100644 --- a/fatent.c +++ b/fatent.c @@ -159,13 +159,14 @@ int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain, return 0; } -int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) +/* This function must be called with bitmap_lock held */ +static int __exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) { - unsigned int num_clusters = 0; - unsigned int clu; struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); int cur_cmap_i, next_cmap_i; + unsigned int num_clusters = 0; + unsigned int clu; /* invalid cluster number */ if (p_chain->dir == EXFAT_FREE_CLUSTER || @@ -238,6 +239,17 @@ dec_used_clus: return 0; } +int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) +{ + int ret = 0; + + mutex_lock(&EXFAT_SB(inode->i_sb)->bitmap_lock); + ret = __exfat_free_cluster(inode, p_chain); + mutex_unlock(&EXFAT_SB(inode->i_sb)->bitmap_lock); + + return ret; +} + int exfat_find_last_cluster(struct super_block *sb, struct exfat_chain *p_chain, unsigned int *ret_clu) { @@ -336,6 +348,8 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, if (num_alloc > total_cnt - sbi->used_clusters) return -ENOSPC; + mutex_lock(&sbi->bitmap_lock); + hint_clu = p_chain->dir; /* find new cluster */ if (hint_clu == EXFAT_EOF_CLUSTER) { @@ -346,8 +360,10 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, } hint_clu = exfat_find_free_bitmap(sb, sbi->clu_srch_ptr); - if (hint_clu == EXFAT_EOF_CLUSTER) - return -ENOSPC; + if (hint_clu == EXFAT_EOF_CLUSTER) { + ret = -ENOSPC; + goto unlock; + } } /* check cluster validation */ @@ -357,8 +373,10 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, hint_clu = EXFAT_FIRST_CLUSTER; if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { if (exfat_chain_cont_cluster(sb, p_chain->dir, - num_clusters)) - return -EIO; + num_clusters)) { + ret = -EIO; + goto unlock; + } p_chain->flags = ALLOC_FAT_CHAIN; } } @@ -408,6 +426,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, sbi->used_clusters += num_clusters; p_chain->size += num_clusters; + mutex_unlock(&sbi->bitmap_lock); return 0; } @@ -427,7 +446,9 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, } free_cluster: if (num_clusters) - exfat_free_cluster(inode, p_chain); + __exfat_free_cluster(inode, p_chain); +unlock: + mutex_unlock(&sbi->bitmap_lock); return ret; } diff --git a/super.c b/super.c index c140e358f482..7d5651decef2 100644 --- a/super.c +++ b/super.c @@ -902,6 +902,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) return -ENOMEM; mutex_init(&sbi->s_lock); + mutex_init(&sbi->bitmap_lock); sb->s_fs_info = sbi; ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL, DEFAULT_RATELIMIT_BURST); @@ -1046,6 +1047,7 @@ static int exfat_init_fs_context(struct fs_context *fc) return -ENOMEM; mutex_init(&sbi->s_lock); + mutex_init(&sbi->bitmap_lock); ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL, DEFAULT_RATELIMIT_BURST); From 42d15a856c131a770493dd5ad116860c3c7afd5f Mon Sep 17 00:00:00 2001 From: Hyeongseok Kim Date: Thu, 4 Mar 2021 08:59:45 +0900 Subject: [PATCH 06/15] exfat: add support ioctl and FITRIM function Add FITRIM ioctl to enable discarding unused blocks while mounted. As current exFAT doesn't have generic ioctl handler, add empty ioctl function first, and add FITRIM handler. Signed-off-by: Hyeongseok Kim Reviewed-by: Chaitanya Kulkarni Acked-by: Sungjong Seo Signed-off-by: Namjae Jeon --- balloc.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ dir.c | 5 ++++ exfat_fs.h | 4 +++ file.c | 55 +++++++++++++++++++++++++++++++++-- 4 files changed, 146 insertions(+), 2 deletions(-) diff --git a/balloc.c b/balloc.c index 411fb0a8da10..ad990217d3d0 100644 --- a/balloc.c +++ b/balloc.c @@ -3,9 +3,13 @@ * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. */ +#include #include #include #include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) +#include +#endif #include "exfat_raw.h" #include "exfat_fs.h" @@ -264,3 +268,83 @@ int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count) *ret_count = count; return 0; } + +int exfat_trim_fs(struct inode *inode, struct fstrim_range *range) +{ + unsigned int trim_begin, trim_end, count, next_free_clu; + u64 clu_start, clu_end, trim_minlen, trimmed_total = 0; + struct super_block *sb = inode->i_sb; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + int err = 0; + + clu_start = max_t(u64, range->start >> sbi->cluster_size_bits, + EXFAT_FIRST_CLUSTER); + clu_end = clu_start + (range->len >> sbi->cluster_size_bits) - 1; + trim_minlen = range->minlen >> sbi->cluster_size_bits; + + if (clu_start >= sbi->num_clusters || range->len < sbi->cluster_size) + return -EINVAL; + + if (clu_end >= sbi->num_clusters) + clu_end = sbi->num_clusters - 1; + + mutex_lock(&sbi->bitmap_lock); + + trim_begin = trim_end = exfat_find_free_bitmap(sb, clu_start); + if (trim_begin == EXFAT_EOF_CLUSTER) + goto unlock; + + next_free_clu = exfat_find_free_bitmap(sb, trim_end + 1); + if (next_free_clu == EXFAT_EOF_CLUSTER) + goto unlock; + + do { + if (next_free_clu == trim_end + 1) { + /* extend trim range for continuous free cluster */ + trim_end++; + } else { + /* trim current range if it's larger than trim_minlen */ + count = trim_end - trim_begin + 1; + if (count >= trim_minlen) { + err = sb_issue_discard(sb, + exfat_cluster_to_sector(sbi, trim_begin), + count * sbi->sect_per_clus, GFP_NOFS, 0); + if (err) + goto unlock; + + trimmed_total += count; + } + + /* set next start point of the free hole */ + trim_begin = trim_end = next_free_clu; + } + + if (next_free_clu >= clu_end) + break; + + if (fatal_signal_pending(current)) { + err = -ERESTARTSYS; + goto unlock; + } + + next_free_clu = exfat_find_free_bitmap(sb, next_free_clu + 1); + } while (next_free_clu != EXFAT_EOF_CLUSTER && + next_free_clu > trim_end); + + /* try to trim remainder */ + count = trim_end - trim_begin + 1; + if (count >= trim_minlen) { + err = sb_issue_discard(sb, exfat_cluster_to_sector(sbi, trim_begin), + count * sbi->sect_per_clus, GFP_NOFS, 0); + if (err) + goto unlock; + + trimmed_total += count; + } + +unlock: + mutex_unlock(&sbi->bitmap_lock); + range->len = trimmed_total << sbi->cluster_size_bits; + + return err; +} diff --git a/dir.c b/dir.c index 308e5e7579cf..e671094f57ae 100644 --- a/dir.c +++ b/dir.c @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -307,6 +308,10 @@ const struct file_operations exfat_dir_operations = { .llseek = generic_file_llseek, .read = generic_read_dir, .iterate = exfat_iterate, + .unlocked_ioctl = exfat_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = exfat_compat_ioctl, +#endif .fsync = exfat_file_fsync, }; diff --git a/exfat_fs.h b/exfat_fs.h index 139ee3d52d2d..0260b9f20790 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -425,6 +425,7 @@ int exfat_set_bitmap(struct inode *inode, unsigned int clu); void exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync); unsigned int exfat_find_free_bitmap(struct super_block *sb, unsigned int clu); int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count); +int exfat_trim_fs(struct inode *inode, struct fstrim_range *range); /* file.c */ extern const struct file_operations exfat_file_operations; @@ -439,6 +440,9 @@ int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat); #endif int exfat_file_fsync(struct file *file, loff_t start, loff_t end, int datasync); +long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); +long exfat_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg); /* namei.c */ diff --git a/file.c b/file.c index 44c41c9b5308..37e013cff2e2 100644 --- a/file.c +++ b/file.c @@ -8,9 +8,8 @@ #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) #include -#else -#include #endif +#include #include #include "exfat_raw.h" @@ -391,6 +390,54 @@ out: return error; } +static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) +{ + struct request_queue *q = bdev_get_queue(inode->i_sb->s_bdev); + struct fstrim_range range; + int ret = 0; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!blk_queue_discard(q)) + return -EOPNOTSUPP; + + if (copy_from_user(&range, (struct fstrim_range __user *)arg, sizeof(range))) + return -EFAULT; + + range.minlen = max_t(unsigned int, range.minlen, + q->limits.discard_granularity); + + ret = exfat_trim_fs(inode, &range); + if (ret < 0) + return ret; + + if (copy_to_user((struct fstrim_range __user *)arg, &range, sizeof(range))) + return -EFAULT; + + return 0; +} + +long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct inode *inode = file_inode(filp); + + switch (cmd) { + case FITRIM: + return exfat_ioctl_fitrim(inode, arg); + default: + return -ENOTTY; + } +} + +#ifdef CONFIG_COMPAT +long exfat_compat_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + return exfat_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) { struct inode *inode = filp->f_mapping->host; @@ -415,6 +462,10 @@ const struct file_operations exfat_file_operations = { .llseek = generic_file_llseek, .read_iter = generic_file_read_iter, .write_iter = generic_file_write_iter, + .unlocked_ioctl = exfat_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = exfat_compat_ioctl, +#endif .mmap = generic_file_mmap, .fsync = exfat_file_fsync, .splice_read = generic_file_splice_read, From c5775e6d24e904b8d55ae3a6ace4a7ad2463cc51 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 4 Mar 2021 10:47:50 +0900 Subject: [PATCH 07/15] exfat: add FITRIM ioctl tests Signed-off-by: Namjae Jeon --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 32ef139d6e2d..cb25bd0f4c0b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -148,9 +148,11 @@ script: - sudo ./check generic/248 - sudo ./check generic/249 - sudo ./check generic/257 + - sudo ./check generic/260 - sudo ./check generic/263 - sudo ./check generic/285 - sudo ./check generic/286 + - sudo ./check generic/288 - sudo ./check generic/308 - sudo ./check generic/309 - sudo ./check generic/310 From 10e5987db764f5e6d79a6a8c19966c44039f2d4c Mon Sep 17 00:00:00 2001 From: Matthieu CASTET Date: Tue, 16 Mar 2021 18:48:07 +0100 Subject: [PATCH 08/15] exfat: Fix date for year < 1980 and > 2108 Change-Id: Ifcc1be945bda1cd0e0dc7ae71a26c939135e51f0 Signed-off-by: Matthieu Castet Signed-off-by: Namjae Jeon --- misc.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/misc.c b/misc.c index 62b5957cab47..b9fed54901d1 100644 --- a/misc.c +++ b/misc.c @@ -126,6 +126,17 @@ void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec *ts, struct tm tm; u16 t, d; +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0) + if (ts->tv_sec < EXFAT_MIN_TIMESTAMP_SECS) { + ts->tv_sec = EXFAT_MIN_TIMESTAMP_SECS; + ts->tv_nsec = 0; + } + else if (ts->tv_sec > EXFAT_MAX_TIMESTAMP_SECS) { + ts->tv_sec = EXFAT_MAX_TIMESTAMP_SECS; + ts->tv_nsec = 0; + } +#endif + #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) time64_to_tm(ts->tv_sec, 0, &tm); #else From 4728c613cf274107cc88cf5228ef8e2caf1d27ca Mon Sep 17 00:00:00 2001 From: Hyeongseok Kim Date: Thu, 18 Mar 2021 15:23:56 +0900 Subject: [PATCH 09/15] exfat: improve write performance when dirsync enabled Degradation of write speed caused by frequent disk access for cluster bitmap update on every cluster allocation could be improved by selective syncing bitmap buffer. Change to flush bitmap buffer only for the directory related operations. Signed-off-by: Hyeongseok Kim Acked-by: Sungjong Seo Signed-off-by: Namjae Jeon --- balloc.c | 4 ++-- dir.c | 2 +- exfat_fs.h | 4 ++-- fatent.c | 4 ++-- inode.c | 3 ++- namei.c | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/balloc.c b/balloc.c index ad990217d3d0..c6807392183e 100644 --- a/balloc.c +++ b/balloc.c @@ -145,7 +145,7 @@ void exfat_free_bitmap(struct exfat_sb_info *sbi) kfree(sbi->vol_amap); } -int exfat_set_bitmap(struct inode *inode, unsigned int clu) +int exfat_set_bitmap(struct inode *inode, unsigned int clu, bool sync) { int i, b; unsigned int ent_idx; @@ -158,7 +158,7 @@ int exfat_set_bitmap(struct inode *inode, unsigned int clu) b = BITMAP_OFFSET_BIT_IN_SECTOR(sb, ent_idx); set_bit_le(b, sbi->vol_amap[i]->b_data); - exfat_update_bh(sbi->vol_amap[i], IS_DIRSYNC(inode)); + exfat_update_bh(sbi->vol_amap[i], sync); return 0; } diff --git a/dir.c b/dir.c index e671094f57ae..ba91062d8004 100644 --- a/dir.c +++ b/dir.c @@ -321,7 +321,7 @@ int exfat_alloc_new_dir(struct inode *inode, struct exfat_chain *clu) exfat_chain_set(clu, EXFAT_EOF_CLUSTER, 0, ALLOC_NO_FAT_CHAIN); - ret = exfat_alloc_cluster(inode, 1, clu); + ret = exfat_alloc_cluster(inode, 1, clu, IS_DIRSYNC(inode)); if (ret) return ret; diff --git a/exfat_fs.h b/exfat_fs.h index 0260b9f20790..f38a061e082b 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -402,7 +402,7 @@ int exfat_clear_volume_dirty(struct super_block *sb); #define exfat_get_next_cluster(sb, pclu) exfat_ent_get(sb, *(pclu), pclu) int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, - struct exfat_chain *p_chain); + struct exfat_chain *p_chain, bool sync_bmap); int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain); int exfat_ent_get(struct super_block *sb, unsigned int loc, unsigned int *content); @@ -421,7 +421,7 @@ int exfat_count_num_clusters(struct super_block *sb, /* balloc.c */ int exfat_load_bitmap(struct super_block *sb); void exfat_free_bitmap(struct exfat_sb_info *sbi); -int exfat_set_bitmap(struct inode *inode, unsigned int clu); +int exfat_set_bitmap(struct inode *inode, unsigned int clu, bool sync); void exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync); unsigned int exfat_find_free_bitmap(struct super_block *sb, unsigned int clu); int exfat_count_used_clusters(struct super_block *sb, unsigned int *ret_count); diff --git a/fatent.c b/fatent.c index 74f3092a277d..c0a9cc9ac6e2 100644 --- a/fatent.c +++ b/fatent.c @@ -328,7 +328,7 @@ release_bhs: } int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, - struct exfat_chain *p_chain) + struct exfat_chain *p_chain, bool sync_bmap) { int ret = -ENOSPC; unsigned int num_clusters = 0, total_cnt; @@ -396,7 +396,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, } /* update allocation bitmap */ - if (exfat_set_bitmap(inode, new_clu)) { + if (exfat_set_bitmap(inode, new_clu, sync_bmap)) { ret = -EIO; goto free_cluster; } diff --git a/inode.c b/inode.c index 02ddbd3df7bb..2d9a01a6fa94 100644 --- a/inode.c +++ b/inode.c @@ -181,7 +181,8 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, return -EIO; } - ret = exfat_alloc_cluster(inode, num_to_be_allocated, &new_clu); + ret = exfat_alloc_cluster(inode, num_to_be_allocated, &new_clu, + inode_needs_sync(inode)); if (ret) return ret; diff --git a/namei.c b/namei.c index 3b45cc6e0113..a68e56ecd644 100644 --- a/namei.c +++ b/namei.c @@ -367,7 +367,7 @@ static int exfat_find_empty_entry(struct inode *inode, exfat_chain_set(&clu, last_clu + 1, 0, p_dir->flags); /* allocate a cluster */ - ret = exfat_alloc_cluster(inode, 1, &clu); + ret = exfat_alloc_cluster(inode, 1, &clu, IS_DIRSYNC(inode)); if (ret) return ret; From df7fdcfcbf394da72548f74147b3442a01914185 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 24 Mar 2021 11:57:43 +0900 Subject: [PATCH 10/15] exfat: attr: handle idmapped mounts When file attributes are changed most filesystems rely on the setattr_prepare(), setattr_copy(), and notify_change() helpers for initialization and permission checking. Let them handle idmapped mounts. If the inode is accessed through an idmapped mount map it into the mount's user namespace. Afterwards the checks are identical to non-idmapped mounts. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Helpers that perform checks on the ia_uid and ia_gid fields in struct iattr assume that ia_uid and ia_gid are intended values and have already been mapped correctly at the userspace-kernelspace boundary as we already do today. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Link: https://lore.kernel.org/r/20210121131959.646623-8-christian.brauner@ubuntu.com Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- file.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/file.c b/file.c index 37e013cff2e2..858a454d5827 100644 --- a/file.c +++ b/file.c @@ -344,7 +344,11 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) #if ((LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0)) && \ (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 37))) || \ (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + error = setattr_prepare(&init_user_ns, dentry, attr); +#else error = setattr_prepare(dentry, attr); +#endif #else error = inode_change_ok(inode, attr); #endif @@ -382,7 +386,11 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) up_write(&EXFAT_I(inode)->truncate_lock); } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + setattr_copy(&init_user_ns, inode, attr); +#else setattr_copy(inode, attr); +#endif exfat_truncate_atime(&inode->i_atime); mark_inode_dirty(inode); From fb1b903695f01693675b160029e6830295451168 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 24 Mar 2021 11:59:41 +0900 Subject: [PATCH 11/15] exfat: stat: handle idmapped mounts The generic_fillattr() helper fills in the basic attributes associated with an inode. Enable it to handle idmapped mounts. If the inode is accessed through an idmapped mount map it into the mount's user namespace before we store the uid and gid. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Link: https://lore.kernel.org/r/20210121131959.646623-12-christian.brauner@ubuntu.com Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Reviewed-by: Christoph Hellwig Reviewed-by: James Morris Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- file.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/file.c b/file.c index 858a454d5827..4ddd911f3c9e 100644 --- a/file.c +++ b/file.c @@ -307,7 +307,11 @@ int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, struct inode *inode = d_inode(dentry); #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + generic_fillattr(&init_user_ns, inode, stat); +#else generic_fillattr(inode, stat); +#endif exfat_truncate_atime(&stat->atime); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) stat->result_mask |= STATX_BTIME; From ddc275ccb4e32078f2a2e267ee6d5df9b740de63 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 24 Mar 2021 12:07:41 +0900 Subject: [PATCH 12/15] exfat: fs: make helpers idmap mount aware Extend some inode methods with an additional user namespace argument. A filesystem that is aware of idmapped mounts will receive the user namespace the mount has been marked with. This can be used for additional permission checking and also to enable filesystems to translate between uids and gids if they need to. We have implemented all relevant helpers in earlier patches. As requested we simply extend the exisiting inode method instead of introducing new ones. This is a little more code churn but it's mostly mechanical and doesnt't leave us with additional inode methods. Link: https://lore.kernel.org/r/20210121131959.646623-25-christian.brauner@ubuntu.com Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner Signed-off-by: Namjae Jeon --- exfat_fs.h | 9 +++++++++ file.c | 12 +++++++++++- namei.c | 17 +++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/exfat_fs.h b/exfat_fs.h index f38a061e082b..e4b3a933c79b 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -431,6 +431,14 @@ int exfat_trim_fs(struct inode *inode, struct fstrim_range *range); extern const struct file_operations exfat_file_operations; int __exfat_truncate(struct inode *inode, loff_t new_size); void exfat_truncate(struct inode *inode, loff_t size); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, + struct iattr *attr); +int exfat_getattr(struct user_namespace *mnt_userns, const struct path *path, + struct kstat *stat, unsigned int request_mask, + unsigned int query_flags); +#else int exfat_setattr(struct dentry *dentry, struct iattr *attr); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) int exfat_getattr(const struct path *path, struct kstat *stat, @@ -439,6 +447,7 @@ int exfat_getattr(const struct path *path, struct kstat *stat, int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat); #endif +#endif int exfat_file_fsync(struct file *file, loff_t start, loff_t end, int datasync); long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); long exfat_compat_ioctl(struct file *filp, unsigned int cmd, diff --git a/file.c b/file.c index 4ddd911f3c9e..1a4ffb0358b3 100644 --- a/file.c +++ b/file.c @@ -291,6 +291,11 @@ write_size: mutex_unlock(&sbi->s_lock); } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +int exfat_getattr(struct user_namespace *mnt_uerns, const struct path *path, + struct kstat *stat, unsigned int request_mask, + unsigned int query_flags) +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) int exfat_getattr(const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags) @@ -298,7 +303,7 @@ int exfat_getattr(const struct path *path, struct kstat *stat, int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) #endif - +#endif { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) struct inode *inode = d_backing_inode(path->dentry); @@ -322,7 +327,12 @@ int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, return 0; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, + struct iattr *attr) +#else int exfat_setattr(struct dentry *dentry, struct iattr *attr) +#endif { struct exfat_sb_info *sbi = EXFAT_SB(dentry->d_sb); struct inode *inode = dentry->d_inode; diff --git a/namei.c b/namei.c index a68e56ecd644..ecbfd52194f2 100644 --- a/namei.c +++ b/namei.c @@ -568,8 +568,13 @@ out: return ret; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +static int exfat_create(struct user_namespace *mnt_userns, struct inode *dir, + struct dentry *dentry, umode_t mode, bool excl) +#else static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool excl) +#endif { struct super_block *sb = dir->i_sb; struct inode *inode; @@ -900,7 +905,12 @@ unlock: return err; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +static int exfat_mkdir(struct user_namespace *mnt_userns, struct inode *dir, + struct dentry *dentry, umode_t mode) +#else static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) +#endif { struct super_block *sb = dir->i_sb; struct inode *inode; @@ -1425,6 +1435,12 @@ out: return ret; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +static int exfat_rename(struct user_namespace *mnt_userns, + struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, @@ -1433,6 +1449,7 @@ static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) #endif +#endif { struct inode *old_inode, *new_inode; struct super_block *sb = old_dir->i_sb; From c024378d20dfc1409bd702abc0a7917aee4d3f04 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 24 Mar 2021 12:10:48 +0900 Subject: [PATCH 13/15] exfat: block: use an on-stack bio in blkdev_issue_flush There is no point in allocating memory for a synchronous flush. Signed-off-by: Christoph Hellwig Reviewed-by: Johannes Thumshirn Reviewed-by: Chaitanya Kulkarni Acked-by: Damien Le Moal Signed-off-by: Jens Axboe Signed-off-by: Namjae Jeon --- file.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/file.c b/file.c index 1a4ffb0358b3..c12d55f58aa5 100644 --- a/file.c +++ b/file.c @@ -473,11 +473,15 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) if (err) return err; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) + return blkdev_issue_flush(inode->i_sb->s_bdev); +#else #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) return blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL); #else return blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL, NULL); #endif +#endif } const struct file_operations exfat_file_operations = { From f693dd9e55af5224c8c5f5758144206a9989d735 Mon Sep 17 00:00:00 2001 From: Hyeongseok Kim Date: Wed, 24 Mar 2021 12:14:06 +0900 Subject: [PATCH 14/15] exfat: speed up iterate/lookup by fixing start point of traversing cluster chain When directory iterate and lookup is called, there's a buggy rewinding of start point for traversing cluster chain to the parent directory entry's first cluster. This caused repeated cluster chain traversing from the first entry of the parent directory that would show worse performance if huge amounts of files exist under the parent directory. Fix not to rewind, make continue from currently referenced cluster and dir entry. Tested with 50,000 files under single directory / 256GB sdcard, with command "time ls -l > /dev/null", Before : 0m08.69s real 0m00.27s user 0m05.91s system After : 0m07.01s real 0m00.25s user 0m04.34s system Signed-off-by: Hyeongseok Kim Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 19 +++++++++++++------ exfat_fs.h | 2 +- namei.c | 9 ++++++++- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/dir.c b/dir.c index ba91062d8004..565cc34829f3 100644 --- a/dir.c +++ b/dir.c @@ -148,7 +148,7 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent 0); *uni_name.name = 0x0; - exfat_get_uniname_from_ext_entry(sb, &dir, dentry, + exfat_get_uniname_from_ext_entry(sb, &clu, i, uni_name.name); exfat_utf16_to_nls(sb, &uni_name, dir_entry->namebuf.lfn, @@ -922,14 +922,19 @@ enum { }; /* - * return values: - * >= 0 : return dir entiry position with the name in dir - * -ENOENT : entry with the name does not exist - * -EIO : I/O error + * @ei: inode info of parent directory + * @p_dir: directory structure of parent directory + * @num_entries:entry size of p_uniname + * @hint_opt: If p_uniname is found, filled with optimized dir/entry + * for traversing cluster chain. + * @return: + * >= 0: file directory entry position where the name exists + * -ENOENT: entry with the name does not exist + * -EIO: I/O error */ 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, - int num_entries, unsigned int type) + int num_entries, unsigned int type, struct exfat_hint *hint_opt) { int i, rewind = 0, dentry = 0, end_eidx = 0, num_ext = 0, len; int order, step, name_len = 0; @@ -1006,6 +1011,8 @@ rewind: if (entry_type == TYPE_FILE || entry_type == TYPE_DIR) { step = DIRENT_STEP_FILE; + hint_opt->clu = clu.dir; + hint_opt->eidx = i; if (type == TYPE_ALL || type == entry_type) { num_ext = ep->dentry.file.num_ext; step = DIRENT_STEP_STRM; diff --git a/exfat_fs.h b/exfat_fs.h index e4b3a933c79b..5bb78062f062 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -483,7 +483,7 @@ 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, - int num_entries, unsigned int type); + int num_entries, unsigned int type, struct exfat_hint *hint_opt); int exfat_alloc_new_dir(struct inode *inode, struct exfat_chain *clu); int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir, int entry, sector_t *sector, int *offset); diff --git a/namei.c b/namei.c index ecbfd52194f2..f7182d327899 100644 --- a/namei.c +++ b/namei.c @@ -646,6 +646,8 @@ static int exfat_find(struct inode *dir, struct qstr *qname, struct exfat_inode_info *ei = EXFAT_I(dir); struct exfat_dentry *ep, *ep2; struct exfat_entry_set_cache *es; + /* for optimized dir & entry to prevent long traverse of cluster chain */ + struct exfat_hint hint_opt; if (qname->len == 0) return -ENOENT; @@ -677,7 +679,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, /* search the file name for directories */ dentry = exfat_find_dir_entry(sb, ei, &cdir, &uni_name, - num_entries, TYPE_ALL); + num_entries, TYPE_ALL, &hint_opt); if (dentry < 0) return dentry; /* -error value */ @@ -686,6 +688,11 @@ static int exfat_find(struct inode *dir, struct qstr *qname, info->entry = dentry; info->num_subdirs = 0; + /* adjust cdir to the optimized value */ + cdir.dir = hint_opt.clu; + if (cdir.flags & ALLOC_NO_FAT_CHAIN) + cdir.size -= dentry / sbi->dentries_per_clu; + dentry = hint_opt.eidx; es = exfat_get_dentry_set(sb, &cdir, dentry, ES_2_ENTRIES); if (!es) return -EIO; From a52427b5c6cbcbc4ef53beea4aca447f06838155 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 24 Mar 2021 12:17:31 +0900 Subject: [PATCH 15/15] exfat: release 5.12.3 version Major changes are: - Add support for FITRIM. - Improve write perofmrance on dirsync mount. - Improve lookup perofmrance. - Fix a bug on discard mount. Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exfat_fs.h b/exfat_fs.h index 5bb78062f062..b3db5a8217c8 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -11,7 +11,7 @@ #include #include -#define EXFAT_VERSION "5.11.1" +#define EXFAT_VERSION "5.12.3" #define EXFAT_SUPER_MAGIC 0x2011BAB0UL #define EXFAT_ROOT_INO 1