From: Namjae Jeon Date: Thu, 7 May 2026 00:05:08 +0000 (+0900) Subject: exfat: add support for multi-cluster allocation X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=286ae368871d2a93b68148c7684b8cff63c9ba80;p=thirdparty%2Flinux.git exfat: add support for multi-cluster allocation Currently exfat_map_cluster() allocates and returns only one cluster at a time even when more clusters are needed. This causes multiple FAT walks and repeated allocation calls during large sequential writes or when using iomap for writes. This change exfat_map_cluster() and exfat_alloc_cluster() to be able to allocate multiple contiguous clusters. Acked-by: Christoph Hellwig Acked-by: "Darrick J. Wong" Signed-off-by: Namjae Jeon --- diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c index f7f0b6a5bbcf..8b8f6bc0c233 100644 --- a/fs/exfat/dir.c +++ b/fs/exfat/dir.c @@ -295,7 +295,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, IS_DIRSYNC(inode)); + ret = exfat_alloc_cluster(inode, 1, clu, IS_DIRSYNC(inode), false); if (ret) return ret; diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index ad8d6a48fe1f..e47fbfa1d854 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -497,7 +497,7 @@ int exfat_clear_volume_dirty(struct super_block *sb); exfat_cluster_walk(sb, (pclu), 1, ALLOC_FAT_CHAIN) int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, - struct exfat_chain *p_chain, bool sync_bmap); + struct exfat_chain *p_chain, bool sync_bmap, bool contig); 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, struct buffer_head **last); diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c index 45b0b754a2e4..a8b11e2ce43f 100644 --- a/fs/exfat/fatent.c +++ b/fs/exfat/fatent.c @@ -419,7 +419,7 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) } int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, - struct exfat_chain *p_chain, bool sync_bmap) + struct exfat_chain *p_chain, bool sync_bmap, bool contig) { int ret = -ENOSPC; unsigned int total_cnt; @@ -470,14 +470,20 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, while ((new_clu = exfat_find_free_bitmap(sb, hint_clu)) != EXFAT_EOF_CLUSTER) { - if (new_clu != hint_clu && - p_chain->flags == ALLOC_NO_FAT_CHAIN) { - if (exfat_chain_cont_cluster(sb, p_chain->dir, - p_chain->size)) { - ret = -EIO; - goto free_cluster; + if (new_clu != hint_clu) { + if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { + if (exfat_chain_cont_cluster(sb, p_chain->dir, + p_chain->size)) { + ret = -EIO; + goto free_cluster; + } + p_chain->flags = ALLOC_FAT_CHAIN; + } + + if (contig && p_chain->size > 0) { + hint_clu = last_clu; + goto done; } - p_chain->flags = ALLOC_FAT_CHAIN; } /* update allocation bitmap */ @@ -507,9 +513,9 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, last_clu = new_clu; if (p_chain->size == num_alloc) { +done: sbi->clu_srch_ptr = hint_clu; - sbi->used_clusters += num_alloc; - + sbi->used_clusters += p_chain->size; mutex_unlock(&sbi->bitmap_lock); return 0; } diff --git a/fs/exfat/file.c b/fs/exfat/file.c index 857cd3030cae..89cc3d046f0f 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -57,7 +57,7 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) clu.flags = ei->flags; ret = exfat_alloc_cluster(inode, new_num_clusters - num_clusters, - &clu, inode_needs_sync(inode)); + &clu, inode_needs_sync(inode), false); if (ret) return ret; diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index a10d4f3c66a1..7b09d94ac464 100644 --- a/fs/exfat/inode.c +++ b/fs/exfat/inode.c @@ -137,9 +137,9 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, unsigned int num_to_be_allocated = 0, num_clusters; num_clusters = exfat_bytes_to_cluster(sbi, exfat_ondisk_size(inode)); - - if (clu_offset >= num_clusters) - num_to_be_allocated = clu_offset - num_clusters + 1; + if (clu_offset > num_clusters || + *count > num_clusters - clu_offset) + num_to_be_allocated = clu_offset + *count - num_clusters; if (!create && (num_to_be_allocated > 0)) { *clu = EXFAT_EOF_CLUSTER; @@ -182,7 +182,7 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, } ret = exfat_alloc_cluster(inode, num_to_be_allocated, &new_clu, - inode_needs_sync(inode)); + inode_needs_sync(inode), true); if (ret) return ret; @@ -216,20 +216,9 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, } *clu = new_clu.dir; + *count = new_clu.size; - inode->i_blocks += - exfat_cluster_to_sectors(sbi, num_to_be_allocated); - - /* - * Move *clu pointer along FAT chains (hole care) because the - * caller of this function expect *clu to be the last cluster. - * This only works when num_to_be_allocated >= 2, - * *clu = (the first cluster of the allocated chain) => - * (the last cluster of ...) - */ - if (exfat_cluster_walk(sb, clu, num_to_be_allocated - 1, ei->flags)) - return -EIO; - *count = 1; + inode->i_blocks += exfat_cluster_to_sectors(sbi, new_clu.size); if (balloc) *balloc = true; } diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index 269993881dba..78f861f23246 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -341,7 +341,7 @@ int exfat_find_empty_entry(struct inode *inode, } /* allocate a cluster */ - ret = exfat_alloc_cluster(inode, 1, &clu, IS_DIRSYNC(inode)); + ret = exfat_alloc_cluster(inode, 1, &clu, IS_DIRSYNC(inode), false); if (ret) return ret;