* FAILED: patch "[PATCH] exfat: preserve benign secondary entries during rename and" failed to apply to 6.12-stable tree
@ 2026-07-15 14:41 gregkh
2026-07-21 1:28 ` [PATCH 6.12.y 1/6] exfat: remove unnecessary read entry in __exfat_rename() Sasha Levin
0 siblings, 1 reply; 7+ messages in thread
From: gregkh @ 2026-07-15 14:41 UTC (permalink / raw)
To: rochan.avlur, Yuezhang.Mo, linkinjeon; +Cc: stable
The patch below does not apply to the 6.12-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.12.y
git checkout FETCH_HEAD
git cherry-pick -x 942296784b2a9439651750c42f540bf2579b330f
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '2026071544-able-urology-c058@gregkh' --subject-prefix 'PATCH 6.12.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 942296784b2a9439651750c42f540bf2579b330f Mon Sep 17 00:00:00 2001
From: Rochan Avlur <rochan.avlur@gmail.com>
Date: Thu, 28 May 2026 07:21:37 -0700
Subject: [PATCH] exfat: preserve benign secondary entries during rename and
move
Commit 8258ef28001a ("exfat: handle unreconized benign secondary
entries") added cluster freeing for benign secondary entries inside
exfat_remove_entries(). However, exfat_remove_entries() is also called
from the rename and move paths (exfat_rename_file and exfat_move_file),
where the old entry set is being relocated rather than deleted. This
causes benign secondary entries such as vendor extension entries to be
silently destroyed on rename or cross-directory move, violating the
exFAT spec requirement (section 8.2) that implementations preserve
unrecognized benign secondary entries.
Fix this by adding a free_benign parameter to exfat_remove_entries()
so callers can suppress cluster freeing during relocation, and
extending exfat_init_ext_entry() to copy trailing benign secondary
entries from the old entry set into the new one internally. Also
clean up the error paths to delete newly allocated entries on failure.
Fixes: 8258ef28001a ("exfat: handle unreconized benign secondary entries")
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/linux-fsdevel/CAG7tbBV--waov7XVu2FHQEc6paR92dufS=em9DW5Kzsrpu3iQg@mail.gmail.com/
Signed-off-by: Rochan Avlur <rochan.avlur@gmail.com>
Reviewed-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c
index 8b8f6bc0c233..5a85a8ec0cc0 100644
--- a/fs/exfat/dir.c
+++ b/fs/exfat/dir.c
@@ -470,32 +470,70 @@ static void exfat_free_benign_secondary_clusters(struct inode *inode,
exfat_free_cluster(inode, &dir);
}
+/*
+ * exfat_init_ext_entry - initialize extension entries in a directory entry set
+ * @es: target entry set
+ * @num_entries: number of entries excluding benign secondary entries
+ * @p_uniname: filename to store
+ * @old_es: optional source entry set with benign secondary entries, or NULL
+ * @num_extra: number of benign secondary entries to copy from @old_es
+ *
+ * Set up the file, stream extension, and filename entries in @es, optionally
+ * preserving @num_extra benign secondary entries from @old_es. @es and @old_es
+ * may refer to the same entry set; excess entries are marked as deleted.
+ */
void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries,
- struct exfat_uni_name *p_uniname)
+ struct exfat_uni_name *p_uniname,
+ struct exfat_entry_set_cache *old_es, int num_extra)
{
- int i;
+ int i, src_start = 0, old_num;
unsigned short *uniname = p_uniname->name;
struct exfat_dentry *ep;
- es->num_entries = num_entries;
+ if (WARN_ON(num_extra < 0 || (num_extra && (!old_es ||
+ old_es->num_entries < ES_IDX_FIRST_FILENAME + num_extra))))
+ num_extra = 0;
+
+ /*
+ * Save old entry count and source position before modifying
+ * es->num_entries, since old_es and es may point to the same
+ * entry set.
+ */
+ old_num = es->num_entries;
+ if (old_es && num_extra > 0)
+ src_start = old_es->num_entries - num_extra;
+
+ es->num_entries = num_entries + num_extra;
ep = exfat_get_dentry_cached(es, ES_IDX_FILE);
- ep->dentry.file.num_ext = (unsigned char)(num_entries - 1);
+ ep->dentry.file.num_ext = (unsigned char)(num_entries - 1 + num_extra);
ep = exfat_get_dentry_cached(es, ES_IDX_STREAM);
ep->dentry.stream.name_len = p_uniname->name_len;
ep->dentry.stream.name_hash = cpu_to_le16(p_uniname->name_hash);
+ if (old_es && num_extra > 0) {
+ for (i = 0; i < num_extra; i++)
+ *exfat_get_dentry_cached(es, num_entries + i) =
+ *exfat_get_dentry_cached(old_es, src_start + i);
+ }
+
for (i = ES_IDX_FIRST_FILENAME; i < num_entries; i++) {
ep = exfat_get_dentry_cached(es, i);
exfat_init_name_entry(ep, uniname);
uniname += EXFAT_FILE_NAME_LEN;
}
+ /* Mark excess old entries as deleted (in-place shrink) */
+ for (i = num_entries + num_extra; i < old_num; i++) {
+ ep = exfat_get_dentry_cached(es, i);
+ exfat_set_entry_type(ep, TYPE_DELETED);
+ }
+
exfat_update_dir_chksum(es);
}
void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es,
- int order)
+ int order, bool free_benign)
{
int i;
struct exfat_dentry *ep;
@@ -503,7 +541,7 @@ void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es,
for (i = order; i < es->num_entries; i++) {
ep = exfat_get_dentry_cached(es, i);
- if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC)
+ if (free_benign && (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC))
exfat_free_benign_secondary_clusters(inode, ep);
exfat_set_entry_type(ep, TYPE_DELETED);
diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
index 2607e51804b2..9be50949ce34 100644
--- a/fs/exfat/exfat_fs.h
+++ b/fs/exfat/exfat_fs.h
@@ -592,9 +592,10 @@ void exfat_init_dir_entry(struct exfat_entry_set_cache *es,
unsigned int type, unsigned int start_clu,
unsigned long long size, struct timespec64 *ts);
void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries,
- struct exfat_uni_name *p_uniname);
+ struct exfat_uni_name *p_uniname,
+ struct exfat_entry_set_cache *old_es, int num_extra);
void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es,
- int order);
+ int order, bool free_benign);
void exfat_update_dir_chksum(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,
diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index 78f861f23246..b7d5e44ad38e 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -504,7 +504,7 @@ static int exfat_add_entry(struct inode *inode, const char *path,
* the first cluster is not determined yet. (0)
*/
exfat_init_dir_entry(&es, type, start_clu, clu_size, &ts);
- exfat_init_ext_entry(&es, num_entries, &uniname);
+ exfat_init_ext_entry(&es, num_entries, &uniname, NULL, 0);
ret = exfat_put_dentry_set(&es, IS_DIRSYNC(inode));
if (ret)
@@ -786,7 +786,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry)
exfat_set_volume_dirty(sb);
/* update the directory entry */
- exfat_remove_entries(inode, &es, ES_IDX_FILE);
+ exfat_remove_entries(inode, &es, ES_IDX_FILE, true);
err = exfat_put_dentry_set(&es, IS_DIRSYNC(inode));
if (err)
@@ -941,7 +941,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
exfat_set_volume_dirty(sb);
- exfat_remove_entries(inode, &es, ES_IDX_FILE);
+ exfat_remove_entries(inode, &es, ES_IDX_FILE, true);
err = exfat_put_dentry_set(&es, IS_DIRSYNC(dir));
if (err)
@@ -968,6 +968,23 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
return err;
}
+/*
+ * Count benign secondary entries beyond the filename entries.
+ * Returns the count, or -EIO if the entry set is inconsistent.
+ */
+static int exfat_count_extra_entries(struct exfat_entry_set_cache *es)
+{
+ struct exfat_dentry *stream;
+ unsigned int name_entries;
+ int extra;
+
+ stream = exfat_get_dentry_cached(es, ES_IDX_STREAM);
+ name_entries = EXFAT_FILENAME_ENTRY_NUM(stream->dentry.stream.name_len);
+ extra = es->num_entries - (ES_IDX_FIRST_FILENAME + name_entries);
+
+ return extra >= 0 ? extra : -EIO;
+}
+
static int exfat_rename_file(struct inode *parent_inode,
struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
{
@@ -976,6 +993,7 @@ static int exfat_rename_file(struct inode *parent_inode,
struct super_block *sb = parent_inode->i_sb;
struct exfat_entry_set_cache old_es, new_es;
int sync = IS_DIRSYNC(parent_inode);
+ unsigned int num_extra_entries, num_total_entries;
if (unlikely(exfat_forced_shutdown(sb)))
return -EIO;
@@ -985,19 +1003,23 @@ static int exfat_rename_file(struct inode *parent_inode,
return num_new_entries;
ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei);
- if (ret) {
- ret = -EIO;
- return ret;
- }
+ if (ret)
+ return -EIO;
epold = exfat_get_dentry_cached(&old_es, ES_IDX_FILE);
- if (old_es.num_entries < num_new_entries) {
+ ret = exfat_count_extra_entries(&old_es);
+ if (ret < 0)
+ goto put_old_es;
+ num_extra_entries = ret;
+ num_total_entries = num_new_entries + num_extra_entries;
+
+ if (old_es.num_entries < num_total_entries) {
int newentry;
struct exfat_chain dir;
newentry = exfat_find_empty_entry(parent_inode, &dir,
- num_new_entries, &new_es);
+ num_total_entries, &new_es);
if (newentry < 0) {
ret = newentry; /* -EIO or -ENOSPC */
goto put_old_es;
@@ -1014,13 +1036,23 @@ static int exfat_rename_file(struct inode *parent_inode,
epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM);
*epnew = *epold;
- exfat_init_ext_entry(&new_es, num_new_entries, p_uniname);
+ exfat_init_ext_entry(&new_es, num_new_entries, p_uniname,
+ &old_es, num_extra_entries);
ret = exfat_put_dentry_set(&new_es, sync);
- if (ret)
+ if (ret) {
+ /* Best-effort delete to avoid duplicate entries */
+ if (!exfat_get_dentry_set(&new_es, sb,
+ &dir, newentry,
+ ES_ALL_ENTRIES)) {
+ exfat_remove_entries(parent_inode, &new_es,
+ ES_IDX_FILE, false);
+ exfat_put_dentry_set(&new_es, false);
+ }
goto put_old_es;
+ }
- exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE);
+ exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE, false);
ei->dir = dir;
ei->entry = newentry;
} else {
@@ -1029,8 +1061,8 @@ static int exfat_rename_file(struct inode *parent_inode,
ei->attr |= EXFAT_ATTR_ARCHIVE;
}
- exfat_remove_entries(parent_inode, &old_es, ES_IDX_FIRST_FILENAME + 1);
- exfat_init_ext_entry(&old_es, num_new_entries, p_uniname);
+ exfat_init_ext_entry(&old_es, num_new_entries, p_uniname,
+ &old_es, num_extra_entries);
}
return exfat_put_dentry_set(&old_es, sync);
@@ -1046,6 +1078,7 @@ static int exfat_move_file(struct inode *parent_inode,
struct exfat_dentry *epmov, *epnew;
struct exfat_entry_set_cache mov_es, new_es;
struct exfat_chain newdir;
+ unsigned int num_extra_entries, num_total_entries;
num_new_entries = exfat_calc_num_entries(p_uniname);
if (num_new_entries < 0)
@@ -1055,8 +1088,14 @@ static int exfat_move_file(struct inode *parent_inode,
if (ret)
return -EIO;
+ ret = exfat_count_extra_entries(&mov_es);
+ if (ret < 0)
+ goto put_mov_es;
+ num_extra_entries = ret;
+ num_total_entries = num_new_entries + num_extra_entries;
+
newentry = exfat_find_empty_entry(parent_inode, &newdir,
- num_new_entries, &new_es);
+ num_total_entries, &new_es);
if (newentry < 0) {
ret = newentry; /* -EIO or -ENOSPC */
goto put_mov_es;
@@ -1074,21 +1113,31 @@ static int exfat_move_file(struct inode *parent_inode,
epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM);
*epnew = *epmov;
- exfat_init_ext_entry(&new_es, num_new_entries, p_uniname);
- exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE);
+ exfat_init_ext_entry(&new_es, num_new_entries, p_uniname,
+ &mov_es, num_extra_entries);
+
+ exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE, false);
ei->dir = newdir;
ei->entry = newentry;
ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode));
- if (ret)
+ if (ret) {
+ /* Best-effort delete to avoid duplicate entries */
+ if (!exfat_get_dentry_set(&new_es, parent_inode->i_sb,
+ &newdir, newentry,
+ ES_ALL_ENTRIES)) {
+ exfat_remove_entries(parent_inode, &new_es,
+ ES_IDX_FILE, false);
+ exfat_put_dentry_set(&new_es, false);
+ }
goto put_mov_es;
+ }
return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(parent_inode));
put_mov_es:
exfat_put_dentry_set(&mov_es, false);
-
return ret;
}
@@ -1162,7 +1211,7 @@ static int __exfat_rename(struct inode *old_parent_inode,
goto del_out;
}
- exfat_remove_entries(new_inode, &es, ES_IDX_FILE);
+ exfat_remove_entries(new_inode, &es, ES_IDX_FILE, true);
ret = exfat_put_dentry_set(&es, IS_DIRSYNC(new_inode));
if (ret)
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 6.12.y 1/6] exfat: remove unnecessary read entry in __exfat_rename()
2026-07-15 14:41 FAILED: patch "[PATCH] exfat: preserve benign secondary entries during rename and" failed to apply to 6.12-stable tree gregkh
@ 2026-07-21 1:28 ` Sasha Levin
2026-07-21 1:28 ` [PATCH 6.12.y 2/6] exfat: rename argument name for exfat_move_file and exfat_rename_file Sasha Levin
` (4 more replies)
0 siblings, 5 replies; 7+ messages in thread
From: Sasha Levin @ 2026-07-21 1:28 UTC (permalink / raw)
To: stable
Cc: Yuezhang Mo, Aoyama Wataru, Daniel Palmer, Sungjong Seo,
Namjae Jeon, Sasha Levin
From: Yuezhang Mo <Yuezhang.Mo@sony.com>
[ Upstream commit 30ef0e0d7ff5b6dceda19d18a85d9d72a4909784 ]
To determine whether it is a directory, there is no need to read its
directory entry, just use S_ISDIR(inode->i_mode).
Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
Reviewed-by: Aoyama Wataru <wataru.aoyama@sony.com>
Reviewed-by: Daniel Palmer <daniel.palmer@sony.com>
Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/exfat/namei.c | 20 ++++----------------
1 file changed, 4 insertions(+), 16 deletions(-)
diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index 44cce2544a7467..94ce344b5e855e 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -1145,17 +1145,12 @@ static int __exfat_rename(struct inode *old_parent_inode,
int ret;
int dentry;
struct exfat_chain olddir, newdir;
- struct exfat_chain *p_dir = NULL;
struct exfat_uni_name uni_name;
- struct exfat_dentry *ep;
struct super_block *sb = old_parent_inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
const unsigned char *new_path = new_dentry->d_name.name;
struct inode *new_inode = new_dentry->d_inode;
struct exfat_inode_info *new_ei = NULL;
- unsigned int new_entry_type = TYPE_UNUSED;
- int new_entry = 0;
- struct buffer_head *new_bh = NULL;
/* check the validity of pointer parameters */
if (new_path == NULL || strlen(new_path) == 0)
@@ -1181,17 +1176,8 @@ static int __exfat_rename(struct inode *old_parent_inode,
goto out;
}
- p_dir = &(new_ei->dir);
- new_entry = new_ei->entry;
- ep = exfat_get_dentry(sb, p_dir, new_entry, &new_bh);
- if (!ep)
- goto out;
-
- new_entry_type = exfat_get_entry_type(ep);
- brelse(new_bh);
-
/* if new_inode exists, update ei */
- if (new_entry_type == TYPE_DIR) {
+ if (S_ISDIR(new_inode->i_mode)) {
struct exfat_chain new_clu;
new_clu.dir = new_ei->start_clu;
@@ -1223,6 +1209,8 @@ static int __exfat_rename(struct inode *old_parent_inode,
if (!ret && new_inode) {
struct exfat_entry_set_cache es;
+ struct exfat_chain *p_dir = &(new_ei->dir);
+ int new_entry = new_ei->entry;
/* delete entries of new_dir */
ret = exfat_get_dentry_set(&es, sb, p_dir, new_entry,
@@ -1239,7 +1227,7 @@ static int __exfat_rename(struct inode *old_parent_inode,
goto del_out;
/* Free the clusters if new_inode is a dir(as if exfat_rmdir) */
- if (new_entry_type == TYPE_DIR &&
+ if (S_ISDIR(new_inode->i_mode) &&
new_ei->start_clu != EXFAT_EOF_CLUSTER) {
/* new_ei, new_clu_to_free */
struct exfat_chain new_clu_to_free;
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 6.12.y 2/6] exfat: rename argument name for exfat_move_file and exfat_rename_file
2026-07-21 1:28 ` [PATCH 6.12.y 1/6] exfat: remove unnecessary read entry in __exfat_rename() Sasha Levin
@ 2026-07-21 1:28 ` Sasha Levin
2026-07-21 1:28 ` [PATCH 6.12.y 3/6] exfat: add exfat_get_dentry_set_by_ei() helper Sasha Levin
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Sasha Levin @ 2026-07-21 1:28 UTC (permalink / raw)
To: stable; +Cc: Yuezhang Mo, Sungjong Seo, Namjae Jeon, Sasha Levin
From: Yuezhang Mo <Yuezhang.Mo@sony.com>
[ Upstream commit 06a2b0b3b490a6103376652c01c3ac6e8e22e654 ]
In this exfat implementation, the relationship between inode and ei
is ei=EXFAT_I(inode). However, in the arguments of exfat_move_file()
and exfat_rename_file(), argument 'inode' indicates the parent
directory, but argument 'ei' indicates the target file to be renamed.
They do not have the above relationship, which is not friendly to code
readers.
So this commit renames 'inode' to 'parent_inode', making the argument
name match its role.
Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/exfat/namei.c | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index 94ce344b5e855e..5f740e37825986 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -1009,15 +1009,15 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
return err;
}
-static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir,
+static int exfat_rename_file(struct inode *parent_inode, struct exfat_chain *p_dir,
int oldentry, struct exfat_uni_name *p_uniname,
struct exfat_inode_info *ei)
{
int ret, num_new_entries;
struct exfat_dentry *epold, *epnew;
- struct super_block *sb = inode->i_sb;
+ struct super_block *sb = parent_inode->i_sb;
struct exfat_entry_set_cache old_es, new_es;
- int sync = IS_DIRSYNC(inode);
+ int sync = IS_DIRSYNC(parent_inode);
if (unlikely(exfat_forced_shutdown(sb)))
return -EIO;
@@ -1037,7 +1037,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir,
if (old_es.num_entries < num_new_entries) {
int newentry;
- newentry = exfat_find_empty_entry(inode, p_dir, num_new_entries,
+ newentry = exfat_find_empty_entry(parent_inode, p_dir, num_new_entries,
&new_es);
if (newentry < 0) {
ret = newentry; /* -EIO or -ENOSPC */
@@ -1061,7 +1061,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir,
if (ret)
goto put_old_es;
- exfat_remove_entries(inode, &old_es, ES_IDX_FILE);
+ exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE);
ei->dir = *p_dir;
ei->entry = newentry;
} else {
@@ -1070,7 +1070,7 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir,
ei->attr |= EXFAT_ATTR_ARCHIVE;
}
- exfat_remove_entries(inode, &old_es, ES_IDX_FIRST_FILENAME + 1);
+ exfat_remove_entries(parent_inode, &old_es, ES_IDX_FIRST_FILENAME + 1);
exfat_init_ext_entry(&old_es, num_new_entries, p_uniname);
}
return exfat_put_dentry_set(&old_es, sync);
@@ -1080,13 +1080,13 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir,
return ret;
}
-static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir,
+static int exfat_move_file(struct inode *parent_inode, struct exfat_chain *p_olddir,
int oldentry, struct exfat_chain *p_newdir,
struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
{
int ret, newentry, num_new_entries;
struct exfat_dentry *epmov, *epnew;
- struct super_block *sb = inode->i_sb;
+ struct super_block *sb = parent_inode->i_sb;
struct exfat_entry_set_cache mov_es, new_es;
num_new_entries = exfat_calc_num_entries(p_uniname);
@@ -1098,7 +1098,7 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir,
if (ret)
return -EIO;
- newentry = exfat_find_empty_entry(inode, p_newdir, num_new_entries,
+ newentry = exfat_find_empty_entry(parent_inode, p_newdir, num_new_entries,
&new_es);
if (newentry < 0) {
ret = newentry; /* -EIO or -ENOSPC */
@@ -1118,18 +1118,18 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir,
*epnew = *epmov;
exfat_init_ext_entry(&new_es, num_new_entries, p_uniname);
- exfat_remove_entries(inode, &mov_es, ES_IDX_FILE);
+ exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE);
exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size,
p_newdir->flags);
ei->entry = newentry;
- ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(inode));
+ ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode));
if (ret)
goto put_mov_es;
- return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(inode));
+ return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(parent_inode));
put_mov_es:
exfat_put_dentry_set(&mov_es, false);
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 6.12.y 3/6] exfat: add exfat_get_dentry_set_by_ei() helper
2026-07-21 1:28 ` [PATCH 6.12.y 1/6] exfat: remove unnecessary read entry in __exfat_rename() Sasha Levin
2026-07-21 1:28 ` [PATCH 6.12.y 2/6] exfat: rename argument name for exfat_move_file and exfat_rename_file Sasha Levin
@ 2026-07-21 1:28 ` Sasha Levin
2026-07-21 1:28 ` [PATCH 6.12.y 4/6] exfat: move exfat_chain_set() out of __exfat_resolve_path() Sasha Levin
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Sasha Levin @ 2026-07-21 1:28 UTC (permalink / raw)
To: stable
Cc: Yuezhang Mo, Aoyama Wataru, Daniel Palmer, Sungjong Seo,
Namjae Jeon, Sasha Levin
From: Yuezhang Mo <Yuezhang.Mo@sony.com>
[ Upstream commit ac844e91364a03c35838fd488437605fbe56f8c3 ]
This helper gets the directory entry set of the file for the exfat
inode which has been created.
It's used to remove all the instances of the pattern it replaces
making the code cleaner, it's also a preparation for changing ->dir
to record the cluster where the directory entry set is located and
changing ->entry to record the index of the directory entry within
the cluster.
Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
Reviewed-by: Aoyama Wataru <wataru.aoyama@sony.com>
Reviewed-by: Daniel Palmer <daniel.palmer@sony.com>
Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/exfat/exfat_fs.h | 2 ++
fs/exfat/inode.c | 2 +-
fs/exfat/namei.c | 53 +++++++++++++++------------------------------
3 files changed, 21 insertions(+), 36 deletions(-)
diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
index d2ba8e2d0c398a..c7aee1a14406ca 100644
--- a/fs/exfat/exfat_fs.h
+++ b/fs/exfat/exfat_fs.h
@@ -508,6 +508,8 @@ struct exfat_dentry *exfat_get_dentry_cached(struct exfat_entry_set_cache *es,
int exfat_get_dentry_set(struct exfat_entry_set_cache *es,
struct super_block *sb, struct exfat_chain *p_dir, int entry,
unsigned int num_entries);
+#define exfat_get_dentry_set_by_ei(es, sb, ei) \
+ exfat_get_dentry_set(es, sb, &(ei)->dir, (ei)->entry, ES_ALL_ENTRIES)
int exfat_get_empty_dentry_set(struct exfat_entry_set_cache *es,
struct super_block *sb, struct exfat_chain *p_dir, int entry,
unsigned int num_entries);
diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c
index cc4dcb7a32b57d..b22c02d6000f79 100644
--- a/fs/exfat/inode.c
+++ b/fs/exfat/inode.c
@@ -43,7 +43,7 @@ int __exfat_write_inode(struct inode *inode, int sync)
exfat_set_volume_dirty(sb);
/* get the directory entry of given file or directory */
- if (exfat_get_dentry_set(&es, sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES))
+ if (exfat_get_dentry_set_by_ei(&es, sb, ei))
return -EIO;
ep = exfat_get_dentry_cached(&es, ES_IDX_FILE);
ep2 = exfat_get_dentry_cached(&es, ES_IDX_STREAM);
diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index 5f740e37825986..2508894514e844 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -788,26 +788,23 @@ static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry,
/* remove an entry, BUT don't truncate */
static int exfat_unlink(struct inode *dir, struct dentry *dentry)
{
- struct exfat_chain cdir;
struct super_block *sb = dir->i_sb;
struct inode *inode = dentry->d_inode;
struct exfat_inode_info *ei = EXFAT_I(inode);
struct exfat_entry_set_cache es;
- int entry, err = 0;
+ int err = 0;
if (unlikely(exfat_forced_shutdown(sb)))
return -EIO;
mutex_lock(&EXFAT_SB(sb)->s_lock);
- exfat_chain_dup(&cdir, &ei->dir);
- entry = ei->entry;
if (ei->dir.dir == DIR_DELETED) {
exfat_err(sb, "abnormal access to deleted dentry");
err = -ENOENT;
goto unlock;
}
- err = exfat_get_dentry_set(&es, sb, &cdir, entry, ES_ALL_ENTRIES);
+ err = exfat_get_dentry_set_by_ei(&es, sb, ei);
if (err) {
err = -EIO;
goto unlock;
@@ -942,21 +939,18 @@ static int exfat_check_dir_empty(struct super_block *sb,
static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
{
struct inode *inode = dentry->d_inode;
- struct exfat_chain cdir, clu_to_free;
+ struct exfat_chain clu_to_free;
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode);
struct exfat_entry_set_cache es;
- int entry, err;
+ int err;
if (unlikely(exfat_forced_shutdown(sb)))
return -EIO;
mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock);
- exfat_chain_dup(&cdir, &ei->dir);
- entry = ei->entry;
-
if (ei->dir.dir == DIR_DELETED) {
exfat_err(sb, "abnormal access to deleted dentry");
err = -ENOENT;
@@ -974,7 +968,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
goto unlock;
}
- err = exfat_get_dentry_set(&es, sb, &cdir, entry, ES_ALL_ENTRIES);
+ err = exfat_get_dentry_set_by_ei(&es, sb, ei);
if (err) {
err = -EIO;
goto unlock;
@@ -1009,8 +1003,8 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
return err;
}
-static int exfat_rename_file(struct inode *parent_inode, struct exfat_chain *p_dir,
- int oldentry, struct exfat_uni_name *p_uniname,
+static int exfat_rename_file(struct inode *parent_inode,
+ struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname,
struct exfat_inode_info *ei)
{
int ret, num_new_entries;
@@ -1026,7 +1020,7 @@ static int exfat_rename_file(struct inode *parent_inode, struct exfat_chain *p_d
if (num_new_entries < 0)
return num_new_entries;
- ret = exfat_get_dentry_set(&old_es, sb, p_dir, oldentry, ES_ALL_ENTRIES);
+ ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei);
if (ret) {
ret = -EIO;
return ret;
@@ -1080,21 +1074,19 @@ static int exfat_rename_file(struct inode *parent_inode, struct exfat_chain *p_d
return ret;
}
-static int exfat_move_file(struct inode *parent_inode, struct exfat_chain *p_olddir,
- int oldentry, struct exfat_chain *p_newdir,
- struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
+static int exfat_move_file(struct inode *parent_inode,
+ struct exfat_chain *p_newdir, struct exfat_uni_name *p_uniname,
+ struct exfat_inode_info *ei)
{
int ret, newentry, num_new_entries;
struct exfat_dentry *epmov, *epnew;
- struct super_block *sb = parent_inode->i_sb;
struct exfat_entry_set_cache mov_es, new_es;
num_new_entries = exfat_calc_num_entries(p_uniname);
if (num_new_entries < 0)
return num_new_entries;
- ret = exfat_get_dentry_set(&mov_es, sb, p_olddir, oldentry,
- ES_ALL_ENTRIES);
+ ret = exfat_get_dentry_set_by_ei(&mov_es, parent_inode->i_sb, ei);
if (ret)
return -EIO;
@@ -1143,8 +1135,7 @@ static int __exfat_rename(struct inode *old_parent_inode,
struct dentry *new_dentry)
{
int ret;
- int dentry;
- struct exfat_chain olddir, newdir;
+ struct exfat_chain newdir;
struct exfat_uni_name uni_name;
struct super_block *sb = old_parent_inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
@@ -1161,11 +1152,6 @@ static int __exfat_rename(struct inode *old_parent_inode,
return -ENOENT;
}
- exfat_chain_set(&olddir, EXFAT_I(old_parent_inode)->start_clu,
- EXFAT_B_TO_CLU_ROUND_UP(i_size_read(old_parent_inode), sbi),
- EXFAT_I(old_parent_inode)->flags);
- dentry = ei->entry;
-
/* check whether new dir is existing directory and empty */
if (new_inode) {
ret = -EIO;
@@ -1200,21 +1186,18 @@ static int __exfat_rename(struct inode *old_parent_inode,
exfat_set_volume_dirty(sb);
- if (olddir.dir == newdir.dir)
- ret = exfat_rename_file(new_parent_inode, &olddir, dentry,
+ if (new_parent_inode == old_parent_inode)
+ ret = exfat_rename_file(new_parent_inode, &newdir,
&uni_name, ei);
else
- ret = exfat_move_file(new_parent_inode, &olddir, dentry,
- &newdir, &uni_name, ei);
+ ret = exfat_move_file(new_parent_inode, &newdir,
+ &uni_name, ei);
if (!ret && new_inode) {
struct exfat_entry_set_cache es;
- struct exfat_chain *p_dir = &(new_ei->dir);
- int new_entry = new_ei->entry;
/* delete entries of new_dir */
- ret = exfat_get_dentry_set(&es, sb, p_dir, new_entry,
- ES_ALL_ENTRIES);
+ ret = exfat_get_dentry_set_by_ei(&es, sb, new_ei);
if (ret) {
ret = -EIO;
goto del_out;
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 6.12.y 4/6] exfat: move exfat_chain_set() out of __exfat_resolve_path()
2026-07-21 1:28 ` [PATCH 6.12.y 1/6] exfat: remove unnecessary read entry in __exfat_rename() Sasha Levin
2026-07-21 1:28 ` [PATCH 6.12.y 2/6] exfat: rename argument name for exfat_move_file and exfat_rename_file Sasha Levin
2026-07-21 1:28 ` [PATCH 6.12.y 3/6] exfat: add exfat_get_dentry_set_by_ei() helper Sasha Levin
@ 2026-07-21 1:28 ` Sasha Levin
2026-07-21 1:28 ` [PATCH 6.12.y 5/6] exfat: fix incorrect directory checksum after rename to shorter name Sasha Levin
2026-07-21 1:28 ` [PATCH 6.12.y 6/6] exfat: preserve benign secondary entries during rename and move Sasha Levin
4 siblings, 0 replies; 7+ messages in thread
From: Sasha Levin @ 2026-07-21 1:28 UTC (permalink / raw)
To: stable
Cc: Yuezhang Mo, Aoyama Wataru, Daniel Palmer, Sungjong Seo,
Namjae Jeon, Sasha Levin
From: Yuezhang Mo <Yuezhang.Mo@sony.com>
[ Upstream commit 0891c7313d87a1b6baf7162bc2f0d755ce70383f ]
__exfat_resolve_path() mixes two functions. The first one is to
resolve and check if the path is valid. The second one is to output
the cluster assigned to the directory.
The second one is only needed when need to traverse the directory
entries, and calling exfat_chain_set() so early causes p_dir to be
passed as an argument multiple times, increasing the complexity of
the code.
This commit moves the call to exfat_chain_set() before traversing
directory entries.
Signed-off-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
Reviewed-by: Aoyama Wataru <wataru.aoyama@sony.com>
Reviewed-by: Daniel Palmer <daniel.palmer@sony.com>
Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/exfat/namei.c | 60 +++++++++++++++++++++---------------------------
1 file changed, 26 insertions(+), 34 deletions(-)
diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index 2508894514e844..d4eaf422c36c68 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -311,6 +311,9 @@ static int exfat_find_empty_entry(struct inode *inode,
ei->hint_femp.eidx = EXFAT_HINT_NONE;
}
+ exfat_chain_set(p_dir, ei->start_clu,
+ EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags);
+
while ((dentry = exfat_search_empty_slot(sb, &hint_femp, p_dir,
num_entries, es)) < 0) {
if (dentry == -EIO)
@@ -386,14 +389,11 @@ static int exfat_find_empty_entry(struct inode *inode,
* Zero if it was successful; otherwise nonzero.
*/
static int __exfat_resolve_path(struct inode *inode, const unsigned char *path,
- struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname,
- int lookup)
+ struct exfat_uni_name *p_uniname, int lookup)
{
int namelen;
int lossy = NLS_NAME_NO_LOSSY;
struct super_block *sb = inode->i_sb;
- struct exfat_sb_info *sbi = EXFAT_SB(sb);
- struct exfat_inode_info *ei = EXFAT_I(inode);
int pathlen = strlen(path);
/*
@@ -432,24 +432,19 @@ static int __exfat_resolve_path(struct inode *inode, const unsigned char *path,
if ((lossy && !lookup) || !namelen)
return (lossy & NLS_NAME_OVERLEN) ? -ENAMETOOLONG : -EINVAL;
- exfat_chain_set(p_dir, ei->start_clu,
- EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags);
-
return 0;
}
static inline int exfat_resolve_path(struct inode *inode,
- const unsigned char *path, struct exfat_chain *dir,
- struct exfat_uni_name *uni)
+ const unsigned char *path, struct exfat_uni_name *uni)
{
- return __exfat_resolve_path(inode, path, dir, uni, 0);
+ return __exfat_resolve_path(inode, path, uni, 0);
}
static inline int exfat_resolve_path_for_lookup(struct inode *inode,
- const unsigned char *path, struct exfat_chain *dir,
- struct exfat_uni_name *uni)
+ const unsigned char *path, struct exfat_uni_name *uni)
{
- return __exfat_resolve_path(inode, path, dir, uni, 1);
+ return __exfat_resolve_path(inode, path, uni, 1);
}
static inline loff_t exfat_make_i_pos(struct exfat_dir_entry *info)
@@ -471,7 +466,7 @@ static int exfat_add_entry(struct inode *inode, const char *path,
int clu_size = 0;
unsigned int start_clu = EXFAT_FREE_CLUSTER;
- ret = exfat_resolve_path(inode, path, p_dir, &uniname);
+ ret = exfat_resolve_path(inode, path, &uniname);
if (ret)
goto out;
@@ -602,10 +597,13 @@ static int exfat_find(struct inode *dir, struct qstr *qname,
return -ENOENT;
/* check the validity of directory name in the given pathname */
- ret = exfat_resolve_path_for_lookup(dir, qname->name, &cdir, &uni_name);
+ ret = exfat_resolve_path_for_lookup(dir, qname->name, &uni_name);
if (ret)
return ret;
+ exfat_chain_set(&cdir, ei->start_clu,
+ EXFAT_B_TO_CLU(i_size_read(dir), sbi), ei->flags);
+
/* check the validation of hint_stat and initialize it if required */
if (ei->version != (inode_peek_iversion_raw(dir) & 0xffffffff)) {
ei->hint_stat.clu = cdir.dir;
@@ -1004,8 +1002,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
}
static int exfat_rename_file(struct inode *parent_inode,
- struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname,
- struct exfat_inode_info *ei)
+ struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
{
int ret, num_new_entries;
struct exfat_dentry *epold, *epnew;
@@ -1030,9 +1027,10 @@ static int exfat_rename_file(struct inode *parent_inode,
if (old_es.num_entries < num_new_entries) {
int newentry;
+ struct exfat_chain dir;
- newentry = exfat_find_empty_entry(parent_inode, p_dir, num_new_entries,
- &new_es);
+ newentry = exfat_find_empty_entry(parent_inode, &dir,
+ num_new_entries, &new_es);
if (newentry < 0) {
ret = newentry; /* -EIO or -ENOSPC */
goto put_old_es;
@@ -1056,7 +1054,7 @@ static int exfat_rename_file(struct inode *parent_inode,
goto put_old_es;
exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE);
- ei->dir = *p_dir;
+ ei->dir = dir;
ei->entry = newentry;
} else {
if (exfat_get_entry_type(epold) == TYPE_FILE) {
@@ -1075,12 +1073,12 @@ static int exfat_rename_file(struct inode *parent_inode,
}
static int exfat_move_file(struct inode *parent_inode,
- struct exfat_chain *p_newdir, struct exfat_uni_name *p_uniname,
- struct exfat_inode_info *ei)
+ struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
{
int ret, newentry, num_new_entries;
struct exfat_dentry *epmov, *epnew;
struct exfat_entry_set_cache mov_es, new_es;
+ struct exfat_chain newdir;
num_new_entries = exfat_calc_num_entries(p_uniname);
if (num_new_entries < 0)
@@ -1090,8 +1088,8 @@ static int exfat_move_file(struct inode *parent_inode,
if (ret)
return -EIO;
- newentry = exfat_find_empty_entry(parent_inode, p_newdir, num_new_entries,
- &new_es);
+ newentry = exfat_find_empty_entry(parent_inode, &newdir,
+ num_new_entries, &new_es);
if (newentry < 0) {
ret = newentry; /* -EIO or -ENOSPC */
goto put_mov_es;
@@ -1112,9 +1110,7 @@ static int exfat_move_file(struct inode *parent_inode,
exfat_init_ext_entry(&new_es, num_new_entries, p_uniname);
exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE);
- exfat_chain_set(&ei->dir, p_newdir->dir, p_newdir->size,
- p_newdir->flags);
-
+ ei->dir = newdir;
ei->entry = newentry;
ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode));
@@ -1135,7 +1131,6 @@ static int __exfat_rename(struct inode *old_parent_inode,
struct dentry *new_dentry)
{
int ret;
- struct exfat_chain newdir;
struct exfat_uni_name uni_name;
struct super_block *sb = old_parent_inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
@@ -1179,19 +1174,16 @@ static int __exfat_rename(struct inode *old_parent_inode,
}
/* check the validity of directory name in the given new pathname */
- ret = exfat_resolve_path(new_parent_inode, new_path, &newdir,
- &uni_name);
+ ret = exfat_resolve_path(new_parent_inode, new_path, &uni_name);
if (ret)
goto out;
exfat_set_volume_dirty(sb);
if (new_parent_inode == old_parent_inode)
- ret = exfat_rename_file(new_parent_inode, &newdir,
- &uni_name, ei);
+ ret = exfat_rename_file(new_parent_inode, &uni_name, ei);
else
- ret = exfat_move_file(new_parent_inode, &newdir,
- &uni_name, ei);
+ ret = exfat_move_file(new_parent_inode, &uni_name, ei);
if (!ret && new_inode) {
struct exfat_entry_set_cache es;
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 6.12.y 5/6] exfat: fix incorrect directory checksum after rename to shorter name
2026-07-21 1:28 ` [PATCH 6.12.y 1/6] exfat: remove unnecessary read entry in __exfat_rename() Sasha Levin
` (2 preceding siblings ...)
2026-07-21 1:28 ` [PATCH 6.12.y 4/6] exfat: move exfat_chain_set() out of __exfat_resolve_path() Sasha Levin
@ 2026-07-21 1:28 ` Sasha Levin
2026-07-21 1:28 ` [PATCH 6.12.y 6/6] exfat: preserve benign secondary entries during rename and move Sasha Levin
4 siblings, 0 replies; 7+ messages in thread
From: Sasha Levin @ 2026-07-21 1:28 UTC (permalink / raw)
To: stable; +Cc: Chi Zhiling, Sungjong Seo, Yuezhang Mo, Namjae Jeon, Sasha Levin
From: Chi Zhiling <chizhiling@kylinos.cn>
[ Upstream commit ff37797badd831797b8a27830fe5046d7e23fdc3 ]
When renaming a file in-place to a shorter name, exfat_remove_entries
marks excess entries as DELETED, but es->num_entries is not updated
accordingly. As a result, exfat_update_dir_chksum iterates over the
deleted entries and computes an incorrect checksum.
This does not lead to persistent corruption because mark_inode_dirty()
is called afterward, and __exfat_write_inode later recomputes the
checksum using the correct num_entries value.
Fix by setting es->num_entries = num_entries in exfat_init_ext_entry.
Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
Reviewed-by: Sungjong Seo <sj1557.seo@samsung.com>
Reviewed-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/exfat/dir.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c
index 39310bb9eccc57..b6e700c8a9b043 100644
--- a/fs/exfat/dir.c
+++ b/fs/exfat/dir.c
@@ -507,6 +507,7 @@ void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries,
unsigned short *uniname = p_uniname->name;
struct exfat_dentry *ep;
+ es->num_entries = num_entries;
ep = exfat_get_dentry_cached(es, ES_IDX_FILE);
ep->dentry.file.num_ext = (unsigned char)(num_entries - 1);
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 6.12.y 6/6] exfat: preserve benign secondary entries during rename and move
2026-07-21 1:28 ` [PATCH 6.12.y 1/6] exfat: remove unnecessary read entry in __exfat_rename() Sasha Levin
` (3 preceding siblings ...)
2026-07-21 1:28 ` [PATCH 6.12.y 5/6] exfat: fix incorrect directory checksum after rename to shorter name Sasha Levin
@ 2026-07-21 1:28 ` Sasha Levin
4 siblings, 0 replies; 7+ messages in thread
From: Sasha Levin @ 2026-07-21 1:28 UTC (permalink / raw)
To: stable; +Cc: Rochan Avlur, Yuezhang Mo, Namjae Jeon, Sasha Levin
From: Rochan Avlur <rochan.avlur@gmail.com>
[ Upstream commit 942296784b2a9439651750c42f540bf2579b330f ]
Commit 8258ef28001a ("exfat: handle unreconized benign secondary
entries") added cluster freeing for benign secondary entries inside
exfat_remove_entries(). However, exfat_remove_entries() is also called
from the rename and move paths (exfat_rename_file and exfat_move_file),
where the old entry set is being relocated rather than deleted. This
causes benign secondary entries such as vendor extension entries to be
silently destroyed on rename or cross-directory move, violating the
exFAT spec requirement (section 8.2) that implementations preserve
unrecognized benign secondary entries.
Fix this by adding a free_benign parameter to exfat_remove_entries()
so callers can suppress cluster freeing during relocation, and
extending exfat_init_ext_entry() to copy trailing benign secondary
entries from the old entry set into the new one internally. Also
clean up the error paths to delete newly allocated entries on failure.
Fixes: 8258ef28001a ("exfat: handle unreconized benign secondary entries")
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/linux-fsdevel/CAG7tbBV--waov7XVu2FHQEc6paR92dufS=em9DW5Kzsrpu3iQg@mail.gmail.com/
Signed-off-by: Rochan Avlur <rochan.avlur@gmail.com>
Reviewed-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/exfat/dir.c | 50 ++++++++++++++++++++++---
fs/exfat/exfat_fs.h | 5 ++-
fs/exfat/namei.c | 89 +++++++++++++++++++++++++++++++++++----------
3 files changed, 116 insertions(+), 28 deletions(-)
diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c
index b6e700c8a9b043..e624fc7c2788b8 100644
--- a/fs/exfat/dir.c
+++ b/fs/exfat/dir.c
@@ -500,32 +500,70 @@ static void exfat_free_benign_secondary_clusters(struct inode *inode,
exfat_free_cluster(inode, &dir);
}
+/*
+ * exfat_init_ext_entry - initialize extension entries in a directory entry set
+ * @es: target entry set
+ * @num_entries: number of entries excluding benign secondary entries
+ * @p_uniname: filename to store
+ * @old_es: optional source entry set with benign secondary entries, or NULL
+ * @num_extra: number of benign secondary entries to copy from @old_es
+ *
+ * Set up the file, stream extension, and filename entries in @es, optionally
+ * preserving @num_extra benign secondary entries from @old_es. @es and @old_es
+ * may refer to the same entry set; excess entries are marked as deleted.
+ */
void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries,
- struct exfat_uni_name *p_uniname)
+ struct exfat_uni_name *p_uniname,
+ struct exfat_entry_set_cache *old_es, int num_extra)
{
- int i;
+ int i, src_start = 0, old_num;
unsigned short *uniname = p_uniname->name;
struct exfat_dentry *ep;
- es->num_entries = num_entries;
+ if (WARN_ON(num_extra < 0 || (num_extra && (!old_es ||
+ old_es->num_entries < ES_IDX_FIRST_FILENAME + num_extra))))
+ num_extra = 0;
+
+ /*
+ * Save old entry count and source position before modifying
+ * es->num_entries, since old_es and es may point to the same
+ * entry set.
+ */
+ old_num = es->num_entries;
+ if (old_es && num_extra > 0)
+ src_start = old_es->num_entries - num_extra;
+
+ es->num_entries = num_entries + num_extra;
ep = exfat_get_dentry_cached(es, ES_IDX_FILE);
- ep->dentry.file.num_ext = (unsigned char)(num_entries - 1);
+ ep->dentry.file.num_ext = (unsigned char)(num_entries - 1 + num_extra);
ep = exfat_get_dentry_cached(es, ES_IDX_STREAM);
ep->dentry.stream.name_len = p_uniname->name_len;
ep->dentry.stream.name_hash = cpu_to_le16(p_uniname->name_hash);
+ if (old_es && num_extra > 0) {
+ for (i = 0; i < num_extra; i++)
+ *exfat_get_dentry_cached(es, num_entries + i) =
+ *exfat_get_dentry_cached(old_es, src_start + i);
+ }
+
for (i = ES_IDX_FIRST_FILENAME; i < num_entries; i++) {
ep = exfat_get_dentry_cached(es, i);
exfat_init_name_entry(ep, uniname);
uniname += EXFAT_FILE_NAME_LEN;
}
+ /* Mark excess old entries as deleted (in-place shrink) */
+ for (i = num_entries + num_extra; i < old_num; i++) {
+ ep = exfat_get_dentry_cached(es, i);
+ exfat_set_entry_type(ep, TYPE_DELETED);
+ }
+
exfat_update_dir_chksum(es);
}
void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es,
- int order)
+ int order, bool free_benign)
{
int i;
struct exfat_dentry *ep;
@@ -533,7 +571,7 @@ void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es,
for (i = order; i < es->num_entries; i++) {
ep = exfat_get_dentry_cached(es, i);
- if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC)
+ if (free_benign && (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC))
exfat_free_benign_secondary_clusters(inode, ep);
exfat_set_entry_type(ep, TYPE_DELETED);
diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
index c7aee1a14406ca..13cd686e1dbc21 100644
--- a/fs/exfat/exfat_fs.h
+++ b/fs/exfat/exfat_fs.h
@@ -492,9 +492,10 @@ void exfat_init_dir_entry(struct exfat_entry_set_cache *es,
unsigned int type, unsigned int start_clu,
unsigned long long size, struct timespec64 *ts);
void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries,
- struct exfat_uni_name *p_uniname);
+ struct exfat_uni_name *p_uniname,
+ struct exfat_entry_set_cache *old_es, int num_extra);
void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es,
- int order);
+ int order, bool free_benign);
void exfat_update_dir_chksum(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,
diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index d4eaf422c36c68..22eca587cb9531 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -498,7 +498,7 @@ static int exfat_add_entry(struct inode *inode, const char *path,
* the first cluster is not determined yet. (0)
*/
exfat_init_dir_entry(&es, type, start_clu, clu_size, &ts);
- exfat_init_ext_entry(&es, num_entries, &uniname);
+ exfat_init_ext_entry(&es, num_entries, &uniname, NULL, 0);
ret = exfat_put_dentry_set(&es, IS_DIRSYNC(inode));
if (ret)
@@ -811,7 +811,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry)
exfat_set_volume_dirty(sb);
/* update the directory entry */
- exfat_remove_entries(inode, &es, ES_IDX_FILE);
+ exfat_remove_entries(inode, &es, ES_IDX_FILE, true);
err = exfat_put_dentry_set(&es, IS_DIRSYNC(inode));
if (err)
@@ -974,7 +974,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
exfat_set_volume_dirty(sb);
- exfat_remove_entries(inode, &es, ES_IDX_FILE);
+ exfat_remove_entries(inode, &es, ES_IDX_FILE, true);
err = exfat_put_dentry_set(&es, IS_DIRSYNC(dir));
if (err)
@@ -1001,6 +1001,23 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
return err;
}
+/*
+ * Count benign secondary entries beyond the filename entries.
+ * Returns the count, or -EIO if the entry set is inconsistent.
+ */
+static int exfat_count_extra_entries(struct exfat_entry_set_cache *es)
+{
+ struct exfat_dentry *stream;
+ unsigned int name_entries;
+ int extra;
+
+ stream = exfat_get_dentry_cached(es, ES_IDX_STREAM);
+ name_entries = EXFAT_FILENAME_ENTRY_NUM(stream->dentry.stream.name_len);
+ extra = es->num_entries - (ES_IDX_FIRST_FILENAME + name_entries);
+
+ return extra >= 0 ? extra : -EIO;
+}
+
static int exfat_rename_file(struct inode *parent_inode,
struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
{
@@ -1009,6 +1026,7 @@ static int exfat_rename_file(struct inode *parent_inode,
struct super_block *sb = parent_inode->i_sb;
struct exfat_entry_set_cache old_es, new_es;
int sync = IS_DIRSYNC(parent_inode);
+ unsigned int num_extra_entries, num_total_entries;
if (unlikely(exfat_forced_shutdown(sb)))
return -EIO;
@@ -1018,19 +1036,23 @@ static int exfat_rename_file(struct inode *parent_inode,
return num_new_entries;
ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei);
- if (ret) {
- ret = -EIO;
- return ret;
- }
+ if (ret)
+ return -EIO;
epold = exfat_get_dentry_cached(&old_es, ES_IDX_FILE);
- if (old_es.num_entries < num_new_entries) {
+ ret = exfat_count_extra_entries(&old_es);
+ if (ret < 0)
+ goto put_old_es;
+ num_extra_entries = ret;
+ num_total_entries = num_new_entries + num_extra_entries;
+
+ if (old_es.num_entries < num_total_entries) {
int newentry;
struct exfat_chain dir;
newentry = exfat_find_empty_entry(parent_inode, &dir,
- num_new_entries, &new_es);
+ num_total_entries, &new_es);
if (newentry < 0) {
ret = newentry; /* -EIO or -ENOSPC */
goto put_old_es;
@@ -1047,13 +1069,23 @@ static int exfat_rename_file(struct inode *parent_inode,
epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM);
*epnew = *epold;
- exfat_init_ext_entry(&new_es, num_new_entries, p_uniname);
+ exfat_init_ext_entry(&new_es, num_new_entries, p_uniname,
+ &old_es, num_extra_entries);
ret = exfat_put_dentry_set(&new_es, sync);
- if (ret)
+ if (ret) {
+ /* Best-effort delete to avoid duplicate entries */
+ if (!exfat_get_dentry_set(&new_es, sb,
+ &dir, newentry,
+ ES_ALL_ENTRIES)) {
+ exfat_remove_entries(parent_inode, &new_es,
+ ES_IDX_FILE, false);
+ exfat_put_dentry_set(&new_es, false);
+ }
goto put_old_es;
+ }
- exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE);
+ exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE, false);
ei->dir = dir;
ei->entry = newentry;
} else {
@@ -1062,8 +1094,8 @@ static int exfat_rename_file(struct inode *parent_inode,
ei->attr |= EXFAT_ATTR_ARCHIVE;
}
- exfat_remove_entries(parent_inode, &old_es, ES_IDX_FIRST_FILENAME + 1);
- exfat_init_ext_entry(&old_es, num_new_entries, p_uniname);
+ exfat_init_ext_entry(&old_es, num_new_entries, p_uniname,
+ &old_es, num_extra_entries);
}
return exfat_put_dentry_set(&old_es, sync);
@@ -1079,6 +1111,7 @@ static int exfat_move_file(struct inode *parent_inode,
struct exfat_dentry *epmov, *epnew;
struct exfat_entry_set_cache mov_es, new_es;
struct exfat_chain newdir;
+ unsigned int num_extra_entries, num_total_entries;
num_new_entries = exfat_calc_num_entries(p_uniname);
if (num_new_entries < 0)
@@ -1088,8 +1121,14 @@ static int exfat_move_file(struct inode *parent_inode,
if (ret)
return -EIO;
+ ret = exfat_count_extra_entries(&mov_es);
+ if (ret < 0)
+ goto put_mov_es;
+ num_extra_entries = ret;
+ num_total_entries = num_new_entries + num_extra_entries;
+
newentry = exfat_find_empty_entry(parent_inode, &newdir,
- num_new_entries, &new_es);
+ num_total_entries, &new_es);
if (newentry < 0) {
ret = newentry; /* -EIO or -ENOSPC */
goto put_mov_es;
@@ -1107,21 +1146,31 @@ static int exfat_move_file(struct inode *parent_inode,
epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM);
*epnew = *epmov;
- exfat_init_ext_entry(&new_es, num_new_entries, p_uniname);
- exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE);
+ exfat_init_ext_entry(&new_es, num_new_entries, p_uniname,
+ &mov_es, num_extra_entries);
+
+ exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE, false);
ei->dir = newdir;
ei->entry = newentry;
ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode));
- if (ret)
+ if (ret) {
+ /* Best-effort delete to avoid duplicate entries */
+ if (!exfat_get_dentry_set(&new_es, parent_inode->i_sb,
+ &newdir, newentry,
+ ES_ALL_ENTRIES)) {
+ exfat_remove_entries(parent_inode, &new_es,
+ ES_IDX_FILE, false);
+ exfat_put_dentry_set(&new_es, false);
+ }
goto put_mov_es;
+ }
return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(parent_inode));
put_mov_es:
exfat_put_dentry_set(&mov_es, false);
-
return ret;
}
@@ -1195,7 +1244,7 @@ static int __exfat_rename(struct inode *old_parent_inode,
goto del_out;
}
- exfat_remove_entries(new_inode, &es, ES_IDX_FILE);
+ exfat_remove_entries(new_inode, &es, ES_IDX_FILE, true);
ret = exfat_put_dentry_set(&es, IS_DIRSYNC(new_inode));
if (ret)
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-07-21 1:28 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-15 14:41 FAILED: patch "[PATCH] exfat: preserve benign secondary entries during rename and" failed to apply to 6.12-stable tree gregkh
2026-07-21 1:28 ` [PATCH 6.12.y 1/6] exfat: remove unnecessary read entry in __exfat_rename() Sasha Levin
2026-07-21 1:28 ` [PATCH 6.12.y 2/6] exfat: rename argument name for exfat_move_file and exfat_rename_file Sasha Levin
2026-07-21 1:28 ` [PATCH 6.12.y 3/6] exfat: add exfat_get_dentry_set_by_ei() helper Sasha Levin
2026-07-21 1:28 ` [PATCH 6.12.y 4/6] exfat: move exfat_chain_set() out of __exfat_resolve_path() Sasha Levin
2026-07-21 1:28 ` [PATCH 6.12.y 5/6] exfat: fix incorrect directory checksum after rename to shorter name Sasha Levin
2026-07-21 1:28 ` [PATCH 6.12.y 6/6] exfat: preserve benign secondary entries during rename and move Sasha Levin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox