From e87a9e97d9e99a3d47254aa58de6b4471e421816 Mon Sep 17 00:00:00 2001 From: Hyeongseok Kim Date: Thu, 4 Mar 2021 08:50:59 +0900 Subject: [PATCH 1/5] 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 | 1 + 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 9d582e7..139ee3d 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 dd54570..74f3092 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 @@ int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) 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 c140e35..24ce233 100644 --- a/super.c +++ b/super.c @@ -1046,6 +1046,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 89965a97eb6c1335bbe44095c35f3124b0e84861 Mon Sep 17 00:00:00 2001 From: Hyeongseok Kim Date: Thu, 4 Mar 2021 08:59:45 +0900 Subject: [PATCH 2/5] 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 411fb0a..ad99021 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 308e5e7..e671094 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 139ee3d..0260b9f 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 44c41c9..37e013c 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 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) 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 1e8bd2078c7a04faad7014f2ba1b546125e99940 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 4 Mar 2021 10:47:50 +0900 Subject: [PATCH 3/5] 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 32ef139..cb25bd0 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 f92ec6d735a671775ac2d55a1f4718312565f075 Mon Sep 17 00:00:00 2001 From: Matthieu CASTET Date: Tue, 16 Mar 2021 10:31:46 +0100 Subject: [PATCH 4/5] Check validity of FAT in exfat_ent_set Change-Id: I4c630201d043b7a763d9faea2a87ca42aafdb8ec Signed-off-by: Matthieu Castet --- fatent.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/fatent.c b/fatent.c index 74f3092..8204ee2 100644 --- a/fatent.c +++ b/fatent.c @@ -62,6 +62,14 @@ static int __exfat_ent_get(struct super_block *sb, unsigned int loc, return 0; } +static inline bool is_valid_cluster(struct exfat_sb_info *sbi, + unsigned int clus) +{ + if (clus < EXFAT_FIRST_CLUSTER || sbi->num_clusters <= clus) + return false; + return true; +} + int exfat_ent_set(struct super_block *sb, unsigned int loc, unsigned int content) { @@ -69,6 +77,13 @@ int exfat_ent_set(struct super_block *sb, unsigned int loc, sector_t sec; __le32 *fat_entry; struct buffer_head *bh; + struct exfat_sb_info *sbi = EXFAT_SB(sb); + + if (!is_valid_cluster(sbi, loc)) { + exfat_fs_error(sb, "invalid write to FAT (entry 0x%08x)", + loc); + return -EIO; + } sec = FAT_ENT_OFFSET_SECTOR(sb, loc); off = FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc); @@ -89,14 +104,6 @@ int exfat_ent_set(struct super_block *sb, unsigned int loc, return 0; } -static inline bool is_valid_cluster(struct exfat_sb_info *sbi, - unsigned int clus) -{ - if (clus < EXFAT_FIRST_CLUSTER || sbi->num_clusters <= clus) - return false; - return true; -} - int exfat_ent_get(struct super_block *sb, unsigned int loc, unsigned int *content) { From 8576ad79b44afcae381d935c3ad2bd8768645a12 Mon Sep 17 00:00:00 2001 From: Matthieu CASTET Date: Tue, 16 Mar 2021 10:56:11 +0100 Subject: [PATCH 5/5] Add missing () in FAT_ENT_OFFSET_x macro Change-Id: I8db1e9660dcb29f206a4dca396d945504965126f Signed-off-by: Matthieu Castet --- exfat_fs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 0260b9f..7c65495 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -110,9 +110,9 @@ enum { #define FAT_ENT_SIZE (4) #define FAT_ENT_SIZE_BITS (2) #define FAT_ENT_OFFSET_SECTOR(sb, loc) (EXFAT_SB(sb)->FAT1_start_sector + \ - (((u64)loc << FAT_ENT_SIZE_BITS) >> sb->s_blocksize_bits)) + (((u64)(loc) << FAT_ENT_SIZE_BITS) >> sb->s_blocksize_bits)) #define FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc) \ - ((loc << FAT_ENT_SIZE_BITS) & (sb->s_blocksize - 1)) + (((loc) << FAT_ENT_SIZE_BITS) & (sb->s_blocksize - 1)) /* * helpers for bitmap.