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;
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);
* 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)
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)
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)
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)
{
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;
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;
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 {
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);
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)
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;
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;
}
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)