[PATCH AUTOSEL 5.15 22/22] exfat: support handle zero-size directory

Sasha Levin
2023-11-07T16:17:47Z

From: Yuezhang Mo <Yuezhang.Mo@sony.com>

[ Upstream commit dab48b8f2fe7264d51ec9eed0adea0fe3c78830a ]

After repairing a corrupted file system with exfatprogs' fsck.exfat,
zero-size directories may result. It is also possible to create
zero-size directories in other exFAT implementation, such as Paragon
ufsd dirver.

As described in the specification, the lower directory size limits
is 0 bytes.

Without this commit, sub-directories and files cannot be created
under a zero-size directory, and it cannot be removed.

Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
Reviewed-by: Andy Wu <Andy.Wu@sony.com>
Reviewed-by: Aoyama Wataru <wataru.aoyama@sony.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/exfat/namei.c | 29 ++++++++++++++++++++++-------
1 file changed, 22 insertions(+), 7 deletions(-)

diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index b22d6c984f8c7..cfa46d8cf5b39 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -330,14 +330,20 @@ static int exfat_find_empty_entry(struct inode *inode,
if (exfat_check_max_dentries(inode))
return -ENOSPC;

- /* we trust p_dir->size regardless of FAT type */
- if (exfat_find_last_cluster(sb, p_dir, &last_clu))
- return -EIO;
-
/*
* Allocate new cluster to this directory
*/
- exfat_chain_set(&clu, last_clu + 1, 0, p_dir->flags);
+ if (ei->start_clu != EXFAT_EOF_CLUSTER) {
+ /* we trust p_dir->size regardless of FAT type */
+ if (exfat_find_last_cluster(sb, p_dir, &last_clu))
+ return -EIO;
+
+ exfat_chain_set(&clu, last_clu + 1, 0, p_dir->flags);
+ } else {
+ /* This directory is empty */
+ exfat_chain_set(&clu, EXFAT_EOF_CLUSTER, 0,
+ ALLOC_NO_FAT_CHAIN);
+ }

/* allocate a cluster */
ret = exfat_alloc_cluster(inode, 1, &clu, IS_DIRSYNC(inode));
@@ -347,6 +353,11 @@ static int exfat_find_empty_entry(struct inode *inode,
if (exfat_zeroed_cluster(inode, clu.dir))
return -EIO;

+ if (ei->start_clu == EXFAT_EOF_CLUSTER) {
+ ei->start_clu = clu.dir;
+ p_dir->dir = clu.dir;
+ }
+
/* append to the FAT chain */
if (clu.flags != p_dir->flags) {
/* no-fat-chain bit is disabled,
@@ -644,7 +655,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname,
info->type = exfat_get_entry_type(ep);
info->attr = le16_to_cpu(ep->dentry.file.attr);
info->size = le64_to_cpu(ep2->dentry.stream.valid_size);
- if ((info->type == TYPE_FILE) && (info->size == 0)) {
+ if (info->size == 0) {
info->flags = ALLOC_NO_FAT_CHAIN;
info->start_clu = EXFAT_EOF_CLUSTER;
} else {
@@ -891,6 +902,9 @@ static int exfat_check_dir_empty(struct super_block *sb,

dentries_per_clu = sbi->dentries_per_clu;

+ if (p_dir->dir == EXFAT_EOF_CLUSTER)
+ return 0;
+
exfat_chain_dup(&clu, p_dir);

while (clu.dir != EXFAT_EOF_CLUSTER) {
@@ -1274,7 +1288,8 @@ static int __exfat_rename(struct inode *old_parent_inode,
}

/* Free the clusters if new_inode is a dir(as if exfat_rmdir) */
- if (new_entry_type == TYPE_DIR) {
+ if (new_entry_type == TYPE_DIR &&
+ new_ei->start_clu != EXFAT_EOF_CLUSTER) {
/* new_ei, new_clu_to_free */
struct exfat_chain new_clu_to_free;

--
2.42.0