Skip to content

Commit

Permalink
Merge tag 'exfat-for-6.12-rc1' of git://git.kernel.org/pub/scm/linux/…
Browse files Browse the repository at this point in the history
…kernel/git/linkinjeon/exfat

Pull exfat updates from Namjae Jeon:

 - Clean-up unnecessary codes as ->valid_size is supported

 - buffered-IO fallback is no longer needed when using direct-IO

 - Move ->valid_size extension from mmap to ->page_mkwrite. This
   improves the overhead caused by unnecessary zero-out during mmap.

 - Fix memleaks from exfat_load_bitmap() and exfat_create_upcase_table()

 - Add sops->shutdown and ioctl

 - Add Yuezhang Mo as a reviwer

* tag 'exfat-for-6.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/exfat:
  MAINTAINERS: exfat: add myself as reviewer
  exfat: resolve memory leak from exfat_create_upcase_table()
  exfat: move extend valid_size into ->page_mkwrite()
  exfat: fix memory leak in exfat_load_bitmap()
  exfat: Implement sops->shutdown and ioctl
  exfat: do not fallback to buffered write
  exfat: drop ->i_size_ondisk
  • Loading branch information
torvalds committed Sep 24, 2024
2 parents 79952bd + cb7d850 commit 4165cee
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 127 deletions.
1 change: 1 addition & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -8466,6 +8466,7 @@ N: binfmt
EXFAT FILE SYSTEM
M: Namjae Jeon <linkinjeon@kernel.org>
M: Sungjong Seo <sj1557.seo@samsung.com>
R: Yuezhang Mo <yuezhang.mo@sony.com>
L: linux-fsdevel@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/exfat.git
Expand Down
10 changes: 5 additions & 5 deletions fs/exfat/balloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,18 @@ int exfat_load_bitmap(struct super_block *sb)
return -EIO;

type = exfat_get_entry_type(ep);
if (type == TYPE_UNUSED)
break;
if (type != TYPE_BITMAP)
continue;
if (ep->dentry.bitmap.flags == 0x0) {
if (type == TYPE_BITMAP &&
ep->dentry.bitmap.flags == 0x0) {
int err;

err = exfat_allocate_bitmap(sb, ep);
brelse(bh);
return err;
}
brelse(bh);

if (type == TYPE_UNUSED)
return -EINVAL;
}

if (exfat_get_next_cluster(sb, &clu.dir))
Expand Down
24 changes: 17 additions & 7 deletions fs/exfat/exfat_fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <linux/ratelimit.h>
#include <linux/nls.h>
#include <linux/blkdev.h>
#include <uapi/linux/exfat.h>

#define EXFAT_ROOT_INO 1

Expand Down Expand Up @@ -148,6 +149,9 @@ enum {
#define DIR_CACHE_SIZE \
(DIV_ROUND_UP(EXFAT_DEN_TO_B(ES_MAX_ENTRY_NUM), SECTOR_SIZE) + 1)

/* Superblock flags */
#define EXFAT_FLAGS_SHUTDOWN 1

struct exfat_dentry_namebuf {
char *lfn;
int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */
Expand Down Expand Up @@ -267,6 +271,8 @@ struct exfat_sb_info {
unsigned int clu_srch_ptr; /* cluster search pointer */
unsigned int used_clusters; /* number of used clusters */

unsigned long s_exfat_flags; /* Exfat superblock flags */

struct mutex s_lock; /* superblock lock */
struct mutex bitmap_lock; /* bitmap lock */
struct exfat_mount_options options;
Expand Down Expand Up @@ -309,13 +315,6 @@ struct exfat_inode_info {
/* for avoiding the race between alloc and free */
unsigned int cache_valid_id;

/*
* NOTE: i_size_ondisk is 64bits, so must hold ->inode_lock to access.
* physically allocated size.
*/
loff_t i_size_ondisk;
/* block-aligned i_size (used in cont_write_begin) */
loff_t i_size_aligned;
/* on-disk position of directory entry or 0 */
loff_t i_pos;
loff_t valid_size;
Expand All @@ -338,6 +337,11 @@ static inline struct exfat_inode_info *EXFAT_I(struct inode *inode)
return container_of(inode, struct exfat_inode_info, vfs_inode);
}

static inline int exfat_forced_shutdown(struct super_block *sb)
{
return test_bit(EXFAT_FLAGS_SHUTDOWN, &EXFAT_SB(sb)->s_exfat_flags);
}

/*
* If ->i_mode can't hold 0222 (i.e. ATTR_RO), we use ->i_attrs to
* save ATTR_RO instead of ->i_mode.
Expand Down Expand Up @@ -417,6 +421,11 @@ static inline bool is_valid_cluster(struct exfat_sb_info *sbi,
return clus >= EXFAT_FIRST_CLUSTER && clus < sbi->num_clusters;
}

static inline loff_t exfat_ondisk_size(const struct inode *inode)
{
return ((loff_t)inode->i_blocks) << 9;
}

/* super.c */
int exfat_set_volume_dirty(struct super_block *sb);
int exfat_clear_volume_dirty(struct super_block *sb);
Expand Down Expand Up @@ -461,6 +470,7 @@ 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);
int exfat_force_shutdown(struct super_block *sb, u32 flags);

/* namei.c */
extern const struct dentry_operations exfat_dentry_ops;
Expand Down
110 changes: 68 additions & 42 deletions fs/exfat/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ static int exfat_cont_expand(struct inode *inode, loff_t size)
if (ret)
return ret;

num_clusters = EXFAT_B_TO_CLU_ROUND_UP(ei->i_size_ondisk, sbi);
num_clusters = EXFAT_B_TO_CLU(exfat_ondisk_size(inode), sbi);
new_num_clusters = EXFAT_B_TO_CLU_ROUND_UP(size, sbi);

if (new_num_clusters == num_clusters)
Expand Down Expand Up @@ -74,8 +74,6 @@ static int exfat_cont_expand(struct inode *inode, loff_t size)
/* Expanded range not zeroed, do not update valid_size */
i_size_write(inode, size);

ei->i_size_aligned = round_up(size, sb->s_blocksize);
ei->i_size_ondisk = ei->i_size_aligned;
inode->i_blocks = round_up(size, sbi->cluster_size) >> 9;
mark_inode_dirty(inode);

Expand Down Expand Up @@ -159,7 +157,7 @@ int __exfat_truncate(struct inode *inode)
exfat_set_volume_dirty(sb);

num_clusters_new = EXFAT_B_TO_CLU_ROUND_UP(i_size_read(inode), sbi);
num_clusters_phys = EXFAT_B_TO_CLU_ROUND_UP(ei->i_size_ondisk, sbi);
num_clusters_phys = EXFAT_B_TO_CLU(exfat_ondisk_size(inode), sbi);

exfat_chain_set(&clu, ei->start_clu, num_clusters_phys, ei->flags);

Expand Down Expand Up @@ -245,8 +243,6 @@ void exfat_truncate(struct inode *inode)
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode);
unsigned int blocksize = i_blocksize(inode);
loff_t aligned_size;
int err;

mutex_lock(&sbi->s_lock);
Expand All @@ -264,17 +260,6 @@ void exfat_truncate(struct inode *inode)

inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9;
write_size:
aligned_size = i_size_read(inode);
if (aligned_size & (blocksize - 1)) {
aligned_size |= (blocksize - 1);
aligned_size++;
}

if (ei->i_size_ondisk > i_size_read(inode))
ei->i_size_ondisk = aligned_size;

if (ei->i_size_aligned > i_size_read(inode))
ei->i_size_aligned = aligned_size;
mutex_unlock(&sbi->s_lock);
}

Expand Down Expand Up @@ -302,6 +287,9 @@ int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
unsigned int ia_valid;
int error;

if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;

if ((attr->ia_valid & ATTR_SIZE) &&
attr->ia_size > i_size_read(inode)) {
error = exfat_cont_expand(inode, attr->ia_size);
Expand Down Expand Up @@ -485,6 +473,19 @@ static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg)
return 0;
}

static int exfat_ioctl_shutdown(struct super_block *sb, unsigned long arg)
{
u32 flags;

if (!capable(CAP_SYS_ADMIN))
return -EPERM;

if (get_user(flags, (__u32 __user *)arg))
return -EFAULT;

return exfat_force_shutdown(sb, flags);
}

long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(filp);
Expand All @@ -495,6 +496,8 @@ long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
return exfat_ioctl_get_attributes(inode, user_attr);
case FAT_IOCTL_SET_ATTRIBUTES:
return exfat_ioctl_set_attributes(filp, user_attr);
case EXFAT_IOC_SHUTDOWN:
return exfat_ioctl_shutdown(inode->i_sb, arg);
case FITRIM:
return exfat_ioctl_fitrim(inode, arg);
default:
Expand All @@ -515,6 +518,9 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
struct inode *inode = filp->f_mapping->host;
int err;

if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;

err = __generic_file_fsync(filp, start, end, datasync);
if (err)
return err;
Expand All @@ -526,32 +532,32 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
return blkdev_issue_flush(inode->i_sb->s_bdev);
}

static int exfat_file_zeroed_range(struct file *file, loff_t start, loff_t end)
static int exfat_extend_valid_size(struct file *file, loff_t new_valid_size)
{
int err;
loff_t pos;
struct inode *inode = file_inode(file);
struct exfat_inode_info *ei = EXFAT_I(inode);
struct address_space *mapping = inode->i_mapping;
const struct address_space_operations *ops = mapping->a_ops;

while (start < end) {
u32 zerofrom, len;
pos = ei->valid_size;
while (pos < new_valid_size) {
u32 len;
struct folio *folio;

zerofrom = start & (PAGE_SIZE - 1);
len = PAGE_SIZE - zerofrom;
if (start + len > end)
len = end - start;
len = PAGE_SIZE - (pos & (PAGE_SIZE - 1));
if (pos + len > new_valid_size)
len = new_valid_size - pos;

err = ops->write_begin(file, mapping, start, len, &folio, NULL);
err = ops->write_begin(file, mapping, pos, len, &folio, NULL);
if (err)
goto out;

folio_zero_range(folio, offset_in_folio(folio, start), len);

err = ops->write_end(file, mapping, start, len, len, folio, NULL);
err = ops->write_end(file, mapping, pos, len, len, folio, NULL);
if (err < 0)
goto out;
start += len;
pos += len;

balance_dirty_pages_ratelimited(mapping);
cond_resched();
Expand Down Expand Up @@ -579,7 +585,7 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
goto unlock;

if (pos > valid_size) {
ret = exfat_file_zeroed_range(file, valid_size, pos);
ret = exfat_extend_valid_size(file, pos);
if (ret < 0 && ret != -ENOSPC) {
exfat_err(inode->i_sb,
"write: fail to zero from %llu to %llu(%zd)",
Expand Down Expand Up @@ -613,26 +619,46 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
return ret;
}

static int exfat_file_mmap(struct file *file, struct vm_area_struct *vma)
static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
{
int ret;
int err;
struct vm_area_struct *vma = vmf->vma;
struct file *file = vma->vm_file;
struct inode *inode = file_inode(file);
struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t start = ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
loff_t end = min_t(loff_t, i_size_read(inode),
loff_t start, end;

if (!inode_trylock(inode))
return VM_FAULT_RETRY;

start = ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
end = min_t(loff_t, i_size_read(inode),
start + vma->vm_end - vma->vm_start);

if ((vma->vm_flags & VM_WRITE) && ei->valid_size < end) {
ret = exfat_file_zeroed_range(file, ei->valid_size, end);
if (ret < 0) {
exfat_err(inode->i_sb,
"mmap: fail to zero from %llu to %llu(%d)",
start, end, ret);
return ret;
if (ei->valid_size < end) {
err = exfat_extend_valid_size(file, end);
if (err < 0) {
inode_unlock(inode);
return vmf_fs_error(err);
}
}

return generic_file_mmap(file, vma);
inode_unlock(inode);

return filemap_page_mkwrite(vmf);
}

static const struct vm_operations_struct exfat_file_vm_ops = {
.fault = filemap_fault,
.map_pages = filemap_map_pages,
.page_mkwrite = exfat_page_mkwrite,
};

static int exfat_file_mmap(struct file *file, struct vm_area_struct *vma)
{
file_accessed(file);
vma->vm_ops = &exfat_file_vm_ops;
return 0;
}

const struct file_operations exfat_file_operations = {
Expand Down
Loading

0 comments on commit 4165cee

Please sign in to comment.