FAILED: patch "[PATCH] exfat: preserve benign secondary entries during rename and" failed to apply to 6.6-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.6-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 <[email protected]>.
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.6.y
git checkout FETCH_HEAD
git cherry-pick -x 942296784b2a9439651750c42f540bf2579b330f
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026071545-counting-slander-c4c4@gregkh' --subject-prefix 'PATCH 6.6.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 <[email protected]>
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.
fs/exfat/dir.c +44 -6
@@ -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 );
fs/exfat/exfat_fs.h +3 -2
@@ -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,
fs/exfat/namei.c +69 -20
184 lines not on this page · raw message
Sasha Levin
maintainerPATCH 1/6 exfat: move free cluster out of exfat_init_ext_entry()
From: Yuezhang Mo <[email protected]>
[ Upstream commit 4e1aa22fea106014397455506d1383d519c4d3d1 ]
exfat_init_ext_entry() is an init function, it's a bit strange to free cluster in it. And the argument 'inode' will be removed from exfat_init_ext_entry(). So this commit changes to free the cluster in exfat_remove_entries().
Code refinement, no functional changes.
Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
--- fs/exfat/dir.c | 3 --- fs/exfat/namei.c | 7 ++++--- 2 files changed, 4 insertions(+), 6 deletions(-)
fs/exfat/dir.c +0 -3
@@ -584,9 +584,6 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir,if (!ep)return -EIO ; -if (exfat_get_entry_type (ep) &TYPE_BENIGN_SEC ) -exfat_free_benign_secondary_clusters (inode, ep); -exfat_init_name_entry (ep, uniname);exfat_update_bh (bh, sync);brelse (bh);
fs/exfat/namei.c +4 -3
@@ -1089,13 +1089,14 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir, }exfat_update_bh (old_bh, sync);brelse (old_bh); + +exfat_remove_entries (inode, p_dir, oldentry, +ES_IDX_FIRST_FILENAME +1 , num_old_entries); + ret =exfat_init_ext_entry (inode, p_dir, oldentry, num_new_entries, p_uniname);if (ret)return ret; - -exfat_remove_entries (inode, p_dir, oldentry, num_new_entries, - num_old_entries); }return 0 ; }
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/6 exfat: remove unnecessary read entry in __exfat_rename()
From: Yuezhang Mo <[email protected]>
[ 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).
Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
--- fs/exfat/namei.c | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-)
fs/exfat/namei.c +7 -16
@@ -1177,18 +1177,13 @@ 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;int num_entries;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 )
@@ -1214,17 +1209,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;
@@ -1255,6 +1241,11 @@ static int __exfat_rename(struct inode *old_parent_inode, &newdir, &uni_name, ei);if (!ret && new_inode) { +struct exfat_chain *p_dir = &(new_ei->dir); +int new_entry = new_ei->entry; +struct exfat_dentry *ep; +struct buffer_head *new_bh =NULL ; +/* delete entries of new_dir */ ep =exfat_get_dentry (sb, p_dir, new_entry, &new_bh);if (!ep) {
@@ -1276,7 +1267,7 @@ 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 (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;
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/6 exfat: rename argument name for exfat_move_file and exfat_rename_file
In reply to Sasha LevinFrom: Yuezhang Mo <[email protected]>
[ 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.
Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
--- fs/exfat/namei.c | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-)
fs/exfat/namei.c +19 -17
@@ -1015,15 +1015,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_old_entries, num_new_entries;struct exfat_dentry *epold, *epnew; -struct super_block *sb = inode->i_sb; +struct super_block *sb = parent_inode->i_sb;struct buffer_head *new_bh, *old_bh; -int sync =IS_DIRSYNC (inode); +int sync =IS_DIRSYNC (parent_inode); epold =exfat_get_dentry (sb, p_dir, oldentry, &old_bh);if (!epold)
@@ -1041,8 +1041,8 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir,if (num_old_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);if (newentry <0 )return newentry;/* -EIO or -ENOSPC */
@@ -1073,12 +1073,12 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir,brelse (old_bh);brelse (new_bh); - ret =exfat_init_ext_entry (inode, p_dir, newentry, + ret =exfat_init_ext_entry (parent_inode, p_dir, newentry, num_new_entries, p_uniname);if (ret)return ret; -exfat_remove_entries (inode, p_dir, oldentry,0 , +exfat_remove_entries (parent_inode, p_dir, oldentry,0 , num_old_entries); ei->dir = *p_dir; ei->entry = newentry;
@@ -1090,10 +1090,10 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir,exfat_update_bh (old_bh, sync);brelse (old_bh); -exfat_remove_entries (inode, p_dir, oldentry, +exfat_remove_entries (parent_inode, p_dir, oldentry,ES_IDX_FIRST_FILENAME +1 , num_old_entries); - ret =exfat_init_ext_entry (inode, p_dir, oldentry, + ret =exfat_init_ext_entry (parent_inode, p_dir, oldentry, num_new_entries, p_uniname);if (ret)return ret;
@@ -1101,13 +1101,13 @@ static int exfat_rename_file(struct inode *inode, struct exfat_chain *p_dir,return 0 ; } -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, num_old_entries;struct exfat_dentry *epmov, *epnew; -struct super_block *sb = inode->i_sb; +struct super_block *sb = parent_inode->i_sb;struct buffer_head *mov_bh, *new_bh; epmov =exfat_get_dentry (sb, p_olddir, oldentry, &mov_bh);
@@ -1124,7 +1124,8 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir,if (num_new_entries <0 )return num_new_entries; - newentry =exfat_find_empty_entry (inode, p_newdir, num_new_entries); + newentry =exfat_find_empty_entry (parent_inode, p_newdir, + num_new_entries);if (newentry <0 )return newentry;/* -EIO or -ENOSPC */
@@ -1137,7 +1138,7 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, epnew->dentry.file.attr |=cpu_to_le16 (ATTR_ARCHIVE ); ei->attr |=ATTR_ARCHIVE ; } -exfat_update_bh (new_bh,IS_DIRSYNC (inode)); +exfat_update_bh (new_bh,IS_DIRSYNC (parent_inode));brelse (mov_bh);brelse (new_bh);
@@ -1151,16 +1152,17 @@ static int exfat_move_file(struct inode *inode, struct exfat_chain *p_olddir, } *epnew = *epmov; -exfat_update_bh (new_bh,IS_DIRSYNC (inode)); +exfat_update_bh (new_bh,IS_DIRSYNC (parent_inode));brelse (mov_bh);brelse (new_bh); - ret =exfat_init_ext_entry (inode, p_newdir, newentry, num_new_entries, - p_uniname); + ret =exfat_init_ext_entry (parent_inode, p_newdir, newentry, + num_new_entries, p_uniname);if (ret)return ret; -exfat_remove_entries (inode, p_olddir, oldentry,0 , num_old_entries); +exfat_remove_entries (parent_inode, p_olddir, oldentry,0 , + num_old_entries);exfat_chain_set (&ei->dir, p_newdir->dir, p_newdir->size, p_newdir->flags);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 4/6 exfat: add exfat_get_dentry_set_by_ei() helper
In reply to Sasha LevinFrom: Yuezhang Mo <[email protected]>
[ 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.
Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
--- fs/exfat/exfat_fs.h | 2 ++ fs/exfat/inode.c | 2 +- fs/exfat/namei.c | 82 +++++++++++++++++++-------------------------- 3 files changed, 38 insertions(+), 48 deletions(-)
fs/exfat/exfat_fs.h +2 -0
@@ -498,6 +498,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 type); +#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_put_dentry_set (struct exfat_entry_set_cache *es,int sync);int exfat_count_dir_entries (struct super_block *sb,struct exfat_chain *p_dir);
fs/exfat/inode.c +1 -1
@@ -42,7 +42,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 );
fs/exfat/namei.c +35 -47
240 lines not on this page · raw message
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 5/6 exfat: move exfat_chain_set() out of __exfat_resolve_path()
In reply to Sasha LevinFrom: Yuezhang Mo <[email protected]>
[ 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.
Stable-dep-of: 942296784b2a ("exfat: preserve benign secondary entries during rename and move")
--- fs/exfat/namei.c | 68 +++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 38 deletions(-)
fs/exfat/namei.c +30 -38
@@ -343,6 +343,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)) <0 ) {if (dentry == -EIO )
@@ -419,14 +422,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);/*
@@ -465,24 +465,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)
@@ -502,7 +497,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;
@@ -623,10 +618,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;
@@ -1010,8 +1008,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_old_entries, num_new_entries;struct exfat_dentry *epold, *epnew;
@@ -1035,13 +1032,14 @@ static int exfat_rename_file(struct inode *parent_inode,if (num_old_entries < num_new_entries) {int newentry; +struct exfat_chain dir; - newentry =exfat_find_empty_entry (parent_inode, p_dir, + newentry =exfat_find_empty_entry (parent_inode, &dir, num_new_entries);if (newentry <0 )return newentry;/* -EIO or -ENOSPC */ - epnew =exfat_get_dentry (sb, p_dir, newentry, &new_bh); + epnew =exfat_get_dentry (sb, &dir, newentry, &new_bh);if (!epnew)return -EIO ;
@@ -1057,7 +1055,7 @@ static int exfat_rename_file(struct inode *parent_inode, epold =exfat_get_dentry (sb, &ei->dir, ei->entry +1 , &old_bh);if (!epold)return -EIO ; - epnew =exfat_get_dentry (sb, p_dir, newentry +1 , &new_bh); + epnew =exfat_get_dentry (sb, &dir, newentry +1 , &new_bh);if (!epnew) {brelse (old_bh);return -EIO ;
@@ -1068,14 +1066,14 @@ static int exfat_rename_file(struct inode *parent_inode,brelse (old_bh);brelse (new_bh); - ret =exfat_init_ext_entry (parent_inode, p_dir, newentry, + ret =exfat_init_ext_entry (parent_inode, &dir, newentry, num_new_entries, p_uniname);if (ret)return ret;exfat_remove_entries (parent_inode, &ei->dir, ei->entry,0 , num_old_entries); - ei->dir = *p_dir; + ei->dir = dir; ei->entry = newentry; }else {if (exfat_get_entry_type (epold) ==TYPE_FILE ) {
@@ -1097,13 +1095,13 @@ 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, num_old_entries;struct exfat_dentry *epmov, *epnew;struct super_block *sb = parent_inode->i_sb;struct buffer_head *mov_bh, *new_bh; +struct exfat_chain newdir; epmov =exfat_get_dentry (sb, &ei->dir, ei->entry, &mov_bh);if (!epmov)
@@ -1119,12 +1117,12 @@ static int exfat_move_file(struct inode *parent_inode,if (num_new_entries <0 )return num_new_entries; - newentry =exfat_find_empty_entry (parent_inode, p_newdir, + newentry =exfat_find_empty_entry (parent_inode, &newdir, num_new_entries);if (newentry <0 )return newentry;/* -EIO or -ENOSPC */ - epnew =exfat_get_dentry (sb, p_newdir, newentry, &new_bh); + epnew =exfat_get_dentry (sb, &newdir, newentry, &new_bh);if (!epnew)return -EIO ;
@@ -1140,7 +1138,7 @@ static int exfat_move_file(struct inode *parent_inode, epmov =exfat_get_dentry (sb, &ei->dir, ei->entry +1 , &mov_bh);if (!epmov)return -EIO ; - epnew =exfat_get_dentry (sb, p_newdir, newentry +1 , &new_bh); + epnew =exfat_get_dentry (sb, &newdir, newentry +1 , &new_bh);if (!epnew) {brelse (mov_bh);return -EIO ;
@@ -1151,7 +1149,7 @@ static int exfat_move_file(struct inode *parent_inode,brelse (mov_bh);brelse (new_bh); - ret =exfat_init_ext_entry (parent_inode, p_newdir, newentry, + ret =exfat_init_ext_entry (parent_inode, &newdir, newentry, num_new_entries, p_uniname);if (ret)return ret;
@@ -1159,9 +1157,7 @@ static int exfat_move_file(struct inode *parent_inode,exfat_remove_entries (parent_inode, &ei->dir, ei->entry,0 , num_old_entries); -exfat_chain_set (&ei->dir, p_newdir->dir, p_newdir->size, - p_newdir->flags); - + ei->dir = newdir; ei->entry = newentry;return 0 ; }
@@ -1172,7 +1168,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);
@@ -1217,19 +1212,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_dentry *ep;
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 6/6 exfat: preserve benign secondary entries during rename and move
In reply to Sasha LevinFrom: Rochan Avlur <[email protected]>
[ 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.
--- fs/exfat/dir.c | 45 +++++++++-- fs/exfat/exfat_fs.h | 5 +- fs/exfat/namei.c | 191 +++++++++++++++++++++++++++++--------------- 3 files changed, 169 insertions(+), 72 deletions(-)
fs/exfat/dir.c +40 -5
@@ -552,21 +552,43 @@ static void exfat_free_benign_secondary_clusters(struct inode *inode,exfat_free_cluster (inode, &dir); } +/* +* exfat_init_ext_entry - initialize extension entries of a dentry set +* @inode: parent directory inode +* @p_dir: directory the dentry set is in +* @entry: index of the first entry of the dentry 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, optionally +* preserving @num_extra benign secondary entries from @old_es. @old_es +* may refer to the same dentry set that is being initialized. +*/ int exfat_init_ext_entry (struct inode *inode,struct exfat_chain *p_dir, -int entry,int num_entries,struct exfat_uni_name *p_uniname) +int entry,int num_entries,struct exfat_uni_name *p_uniname, +struct exfat_entry_set_cache *old_es,int num_extra) {struct super_block *sb = inode->i_sb; -int i; +int i, src_start =0 ;unsigned short *uniname = p_uniname->name;struct exfat_dentry *ep;struct buffer_head *bh;int sync =IS_DIRSYNC (inode); +if (WARN_ON (num_extra <0 || (num_extra && (!old_es || + old_es->num_entries <ES_IDX_FIRST_FILENAME + num_extra)))) + num_extra =0 ; + +if (old_es && num_extra >0 ) + src_start = old_es->num_entries - num_extra; + ep =exfat_get_dentry (sb, p_dir, entry, &bh);if (!ep)return -EIO ; - ep->dentry.file.num_ext = (unsigned char )(num_entries -1 ); + ep->dentry.file.num_ext = (unsigned char )(num_entries -1 + num_extra);exfat_update_bh (bh, sync);brelse (bh);
@@ -579,6 +601,19 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir,exfat_update_bh (bh, sync);brelse (bh); +if (old_es && num_extra >0 ) { +for (i =0 ; i < num_extra; i++) { + ep =exfat_get_dentry (sb, p_dir, + entry + num_entries + i, &bh); +if (!ep) +return -EIO ; + + *ep = *exfat_get_dentry_cached (old_es, src_start + i); +exfat_update_bh (bh, sync); +brelse (bh); + } + } +for (i =EXFAT_FIRST_CLUSTER ; i < num_entries; i++) { ep =exfat_get_dentry (sb, p_dir, entry + i, &bh);if (!ep)
@@ -595,7 +630,7 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, }int exfat_remove_entries (struct inode *inode,struct exfat_chain *p_dir, -int entry,int order,int num_entries) +int entry,int order,int num_entries,bool free_benign) {struct super_block *sb = inode->i_sb;int i;
@@ -607,7 +642,7 @@ int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir,if (!ep)return -EIO ; -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 );
fs/exfat/exfat_fs.h +3 -2
@@ -480,9 +480,10 @@ int exfat_init_dir_entry(struct inode *inode, struct exfat_chain *p_dir, int entry, unsigned int type, unsigned int start_clu, unsigned long long size);int exfat_init_ext_entry (struct inode *inode,struct exfat_chain *p_dir, -int entry,int num_entries,struct exfat_uni_name *p_uniname); +int entry,int num_entries,struct exfat_uni_name *p_uniname, +struct exfat_entry_set_cache *old_es,int num_extra);int exfat_remove_entries (struct inode *inode,struct exfat_chain *p_dir, -int entry,int order,int num_entries); +int entry,int order,int num_entries,bool free_benign);int exfat_update_dir_chksum (struct inode *inode,struct exfat_chain *p_dir,int entry);void exfat_update_dir_chksum_with_entry_set (struct exfat_entry_set_cache *es);
fs/exfat/namei.c +126 -65
308 lines not on this page · raw message
Signature · 1 line
2.53.0