Whamcloud - gitweb
LU-16350 ldiskfs: update for kernel 6.12 26/57126/14
authorShaun Tancheff <shaun.tancheff@hpe.com>
Fri, 11 Apr 2025 01:40:54 +0000 (08:40 +0700)
committerOleg Drokin <green@whamcloud.com>
Fri, 2 May 2025 02:17:53 +0000 (02:17 +0000)
Update patches
   ext4-data-in-dirent.patch
   ext4-ext-merge.patch
   ext4-filename-encode.patch
   ext4-prealloc.patch
for kernel 6.12.

Linux commit v6.11-rc4-18-g4f05ee2f82b4
  ext4: store cookie in private data

If file private_data is not set and an open() handler
is defined then the file must be open()'d before proceeding
to iterate() over the directory.

This corresponds with the dropping of file->f_version member
which is replaced by the cookie in file->private_data.

Note that the file->open() handler will load the expected
cookie into file->private_data.

For older kernels the open() handler is not provided.

HPE-bug-id: LUS-11376
Test-Parameters: trivial
Signed-off-by: Shaun Tancheff <shaun.tancheff@hpe.com>
Change-Id: Ie64bb701f45a024dce4caefe0e3ee673e6cfe25a
Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/57126
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Jian Yu <yujian@whamcloud.com>
Reviewed-by: Timothy Day <timday@amazon.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
config/lustre-build-ldiskfs.m4
ldiskfs/kernel_patches/patches/linux-6.12/ext4-data-in-dirent.patch [new file with mode: 0644]
ldiskfs/kernel_patches/patches/linux-6.12/ext4-ext-merge.patch [new file with mode: 0644]
ldiskfs/kernel_patches/patches/linux-6.12/ext4-filename-encode.patch [new file with mode: 0644]
ldiskfs/kernel_patches/patches/linux-6.12/ext4-prealloc.patch [new file with mode: 0644]
ldiskfs/kernel_patches/series/ldiskfs-6.12-ml.series [new file with mode: 0644]
lustre/osd-ldiskfs/osd_handler.c

index 855ba48..d53c6d3 100644 (file)
@@ -215,7 +215,11 @@ AS_IF([test -z "$LDISKFS_SERIES"],
        AS_VERSION_COMPARE([$LINUXRELEASE],[6.10.5], [
                LDISKFS_SERIES="6.10-ml.series"], [
                LDISKFS_SERIES="6.11-ml.series"], [
-               LDISKFS_SERIES="6.11-ml.series"]
+       AS_VERSION_COMPARE([$LINUXRELEASE],[6.12.0], [
+               LDISKFS_SERIES="6.11-ml.series"], [
+               LDISKFS_SERIES="6.12-ml.series"], [
+               LDISKFS_SERIES="6.12-ml.series"]
+       )] # 6.12
        )] # 6.11
        )] # 6.10
        )] # 6.7
diff --git a/ldiskfs/kernel_patches/patches/linux-6.12/ext4-data-in-dirent.patch b/ldiskfs/kernel_patches/patches/linux-6.12/ext4-data-in-dirent.patch
new file mode 100644 (file)
index 0000000..4b4720f
--- /dev/null
@@ -0,0 +1,853 @@
+commit 2db3b2b33ee796f4ea61316773452d936303ad27
+Author:     Pravin Shelar <Pravin.Shelar@sun.com>
+AuthorDate: Sun Oct 4 18:13:14 2009 +0000
+Subject: ext4: add ext4-data-in-dirent patch
+
+Allows ext4 to store extra data records inside the ext4_dirent
+along with the regular directory entry (type, length, filename).
+Data is stored in ext4 dirent after filename, with a bit flag in
+de->file_type to indicate if any record after de->name is used.
+Each in-use record is variable length and must store a 1-byte
+length (including the length byte itself) at the start so that it
+can be skipped if the record type is unknown/uneeded.  The record
+starts after a NUL byte terminator for the filename.  This extra
+space is accounted in de->rec_len but not de->name_len.
+
+Flag EXT4_DIRENT_LUFID is used for a 128-bit file identifier.
+Make use of dentry->d_fsdata to pass LUFID to ext4, so no changes
+in ext4_add_entry() interface are required.
+
+Bugzilla-ID: b=17670
+Signed-off-by: Pravin Shelar <Pravin.Shelar@sun.com>
+Reviewed-by: Huang Hua <h.huang@sun.com>
+Signed-off-by: Andreas Dilger <andreas.dilger@sun.com> 
+---
+ fs/ext4/dir.c         |   9 +-
+ fs/ext4/ext4.h        | 107 ++++++++++++++-
+ fs/ext4/fast_commit.c |   2 +-
+ fs/ext4/inline.c      |   8 +-
+ fs/ext4/namei.c       | 297 ++++++++++++++++++++++++++++++++++++------
+ fs/ext4/super.c       |   4 +-
+ 6 files changed, 370 insertions(+), 57 deletions(-)
+
+diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c
+index ef6a3c8f..5012aa63 100644
+--- a/fs/ext4/dir.c
++++ b/fs/ext4/dir.c
+@@ -472,12 +472,17 @@ int ext4_htree_store_dirent(struct file *dir_file, __u32 hash,
+       struct fname *fname, *new_fn;
+       struct dir_private_info *info;
+       int len;
++      int extra_data = 0;
+       info = dir_file->private_data;
+       p = &info->root.rb_node;
+       /* Create and allocate the fname structure */
+-      len = sizeof(struct fname) + ent_name->len + 1;
++      if (dirent->file_type & EXT4_DIRENT_LUFID)
++              extra_data = ext4_get_dirent_data_len(dirent);
++
++      len = sizeof(struct fname) + ent_name->len + extra_data + 1;
++
+       new_fn = kzalloc(len, GFP_KERNEL);
+       if (!new_fn)
+               return -ENOMEM;
+@@ -486,7 +491,7 @@ int ext4_htree_store_dirent(struct file *dir_file, __u32 hash,
+       new_fn->inode = le32_to_cpu(dirent->inode);
+       new_fn->name_len = ent_name->len;
+       new_fn->file_type = dirent->file_type;
+-      memcpy(new_fn->name, ent_name->name, ent_name->len);
++      memcpy(new_fn->name, ent_name->name, ent_name->len + extra_data);
+       while (*p) {
+               parent = *p;
+diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
+index cf1fac7c..c672126f 100644
+--- a/fs/ext4/ext4.h
++++ b/fs/ext4/ext4.h
+@@ -1170,6 +1170,7 @@ struct ext4_inode_info {
+       __u32 i_csum_seed;
+       kprojid_t i_projid;
++      void *i_dirdata;
+ };
+ /*
+@@ -1191,6 +1192,7 @@ struct ext4_inode_info {
+  * Mount flags set via mount options or defaults
+  */
+ #define EXT4_MOUNT_NO_MBCACHE         0x00001 /* Do not use mbcache */
++#define EXT4_MOUNT_DIRDATA            0x00002 /* Data in directory entries */
+ #define EXT4_MOUNT_GRPID              0x00004 /* Create files with directory's group */
+ #define EXT4_MOUNT_DEBUG              0x00008 /* Some debugging messages */
+ #define EXT4_MOUNT_ERRORS_CONT                0x00010 /* Continue on errors */
+@@ -2187,6 +2189,7 @@ EXT4_FEATURE_INCOMPAT_FUNCS(casefold,            CASEFOLD)
+                                        EXT4_FEATURE_INCOMPAT_FLEX_BG| \
+                                        EXT4_FEATURE_INCOMPAT_EA_INODE| \
+                                        EXT4_FEATURE_INCOMPAT_MMP | \
++                                       EXT4_FEATURE_INCOMPAT_DIRDATA| \
+                                        EXT4_FEATURE_INCOMPAT_INLINE_DATA | \
+                                        EXT4_FEATURE_INCOMPAT_ENCRYPT | \
+                                        EXT4_FEATURE_INCOMPAT_CASEFOLD | \
+@@ -2398,6 +2401,42 @@ struct ext4_dir_entry_tail {
+ #define EXT4_FT_SYMLINK               7
+ #define EXT4_FT_MAX           8
++#define EXT4_FT_MASK          0xf
++
++#if EXT4_FT_MAX > EXT4_FT_MASK
++#error "conflicting EXT4_FT_MAX and EXT4_FT_MASK"
++#endif
++
++/*
++ * d_type has 4 unused bits, so it can hold four types data. these different
++ * type of data (e.g. lustre data, high 32 bits of 64-bit inode number) can be
++ * stored, in flag order, after file-name in ext4 dirent.
++*/
++/*
++ * this flag is added to d_type if ext4 dirent has extra data after
++ * filename. this data length is variable and length is stored in first byte
++ * of data. data start after filename NUL byte.
++ * This is used by Lustre FS.
++  */
++#define EXT4_DIRENT_LUFID             0x10
++
++#define EXT4_LUFID_MAGIC    0xAD200907UL
++struct ext4_dentry_param {
++      __u32  edp_magic;       /* EXT4_LUFID_MAGIC */
++      char   edp_len;         /* size of edp_data in bytes */
++      char   edp_data[0];     /* packed array of data */
++} __packed;
++
++static inline unsigned char *ext4_dentry_get_data(struct super_block *sb,
++                                                struct ext4_dentry_param *p)
++{
++      if (!ext4_has_feature_dirdata(sb))
++              return NULL;
++      if (p && p->edp_magic == EXT4_LUFID_MAGIC)
++              return &p->edp_len;
++      else
++              return NULL;
++}
+ #define EXT4_FT_DIR_CSUM      0xDE
+@@ -2409,6 +2448,17 @@ struct ext4_dir_entry_tail {
+ #define EXT4_DIR_PAD                  4
+ #define EXT4_DIR_ROUND                        (EXT4_DIR_PAD - 1)
+ #define EXT4_MAX_REC_LEN              ((1<<16)-1)
++#define EXT4_DIR_REC_LEN_(name_len, i_dir) \
++      ext4_dir_rec_len((name_len), (i_dir))
++#define EXT4_DIR_ENTRY_LEN_(de, i_dir) \
++      (EXT4_DIR_REC_LEN_((de)->name_len + ext4_get_dirent_data_len(de), \
++              (i_dir)))
++/* ldiskfs */
++#define EXT4_DIR_REC_LEN(name_len, i_dir)     EXT4_DIR_REC_LEN_((name_len), (i_dir))
++#define EXT4_DIR_ENTRY_LEN(de, i_dir)         EXT4_DIR_ENTRY_LEN_((de), (i_dir))
++/* lustre osd_handler compat -- ifdef LDISKFS_DIR_REC_LEN_WITH_DIR */
++#define EXT4_DIR_REC_LEN_WITH_DIR             1
++#define __EXT4_DIR_REC_LEN(name_len)          EXT4_DIR_REC_LEN_((name_len), NULL)
+ /*
+  * The rec_len is dependent on the type of directory. Directories that are
+@@ -2416,10 +2466,10 @@ struct ext4_dir_entry_tail {
+  * ext4_extended_dir_entry_2. For all entries related to '.' or '..' you should
+  * pass NULL for dir, as those entries do not use the extra fields.
+  */
+-static inline unsigned int ext4_dir_rec_len(__u8 name_len,
++static inline unsigned int ext4_dir_rec_len(__u32 name_len,
+                                               const struct inode *dir)
+ {
+-      int rec_len = (name_len + 8 + EXT4_DIR_ROUND);
++      __u32 rec_len = (name_len + 8 + EXT4_DIR_ROUND);
+       if (dir && ext4_hash_in_dirent(dir))
+               rec_len += sizeof(struct ext4_dir_entry_hash);
+@@ -2853,11 +2903,13 @@ extern int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+                            struct buffer_head *bh,
+                            void *buf, int buf_size,
+                            struct ext4_filename *fname,
+-                           struct ext4_dir_entry_2 **dest_de);
++                           struct ext4_dir_entry_2 **dest_de,
++                           int dlen);
+ void ext4_insert_dentry(struct inode *dir, struct inode *inode,
+                       struct ext4_dir_entry_2 *de,
+                       int buf_size,
+-                      struct ext4_filename *fname);
++                      struct ext4_filename *fname,
++                      void *data);
+ static inline void ext4_update_dx_flag(struct inode *inode)
+ {
+       if (!ext4_has_feature_dir_index(inode->i_sb) &&
+@@ -2873,10 +2925,17 @@ static const unsigned char ext4_filetype_table[] = {
+ static inline  unsigned char get_dtype(struct super_block *sb, int filetype)
+ {
+-      if (!ext4_has_feature_filetype(sb) || filetype >= EXT4_FT_MAX)
++      int fl_index = filetype & EXT4_FT_MASK;
++
++      if (!ext4_has_feature_filetype(sb) || fl_index >= EXT4_FT_MAX)
+               return DT_UNKNOWN;
+-      return ext4_filetype_table[filetype];
++      if (!test_opt(sb, DIRDATA))
++              return ext4_filetype_table[fl_index];
++
++      return (ext4_filetype_table[fl_index]) |
++              (filetype & EXT4_DIRENT_LUFID);
++
+ }
+ extern int ext4_check_all_de(struct inode *dir, struct buffer_head *bh,
+                            void *buf, int buf_size);
+@@ -3083,9 +3142,13 @@ extern int ext4_ind_migrate(struct inode *inode);
+ /* namei.c */
+ extern int ext4_init_new_dir(handle_t *handle, struct inode *dir,
+-                           struct inode *inode);
++                           struct inode *inode,
++                           const void *data1, const void *data2);
+ extern int ext4_dirblock_csum_verify(struct inode *inode,
+                                    struct buffer_head *bh);
++extern int ext4_add_dot_dotdot(handle_t *handle, struct inode *dir,
++                             struct inode *inode,
++                             const void *data1, const void *data2);
+ extern int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,
+                               __u32 start_minor_hash, __u32 *next_hash);
+ extern struct inode *ext4_create_inode(handle_t *handle,
+@@ -3873,6 +3936,36 @@ static inline void ext4_clear_io_unwritten_flag(ext4_io_end_t *io_end)
+       }
+ }
++/*
++ * Compute the total directory entry data length.
++ * This includes the filename and an implicit NUL terminator (always present),
++ * and optional extensions.  Each extension has a bit set in the high 4 bits of
++ * de->file_type, and the extension length is the first byte in each entry.
++ */
++static inline int ext4_get_dirent_data_len(struct ext4_dir_entry_2 *de)
++{
++      char *len = de->name + de->name_len + 1 /* NUL terminator */;
++      int dlen = 0;
++      __u8 extra_data_flags = (de->file_type & ~EXT4_FT_MASK) >> 4;
++      struct ext4_dir_entry_tail *t = (struct ext4_dir_entry_tail *)de;
++
++      if (!t->det_reserved_zero1 &&
++          le16_to_cpu(t->det_rec_len) ==
++              sizeof(struct ext4_dir_entry_tail) &&
++          !t->det_reserved_zero2 &&
++          t->det_reserved_ft == EXT4_FT_DIR_CSUM)
++              return 0;
++
++      while (extra_data_flags) {
++              if (extra_data_flags & 1) {
++                      dlen += *len + (dlen == 0);
++                      len += *len;
++              }
++              extra_data_flags >>= 1;
++      }
++      return dlen;
++}
++
+ extern const struct iomap_ops ext4_iomap_ops;
+ extern const struct iomap_ops ext4_iomap_overwrite_ops;
+ extern const struct iomap_ops ext4_iomap_report_ops;
+diff --git a/fs/ext4/fast_commit.c b/fs/ext4/fast_commit.c
+index b33664f6..9333c1c3 100644
+--- a/fs/ext4/fast_commit.c
++++ b/fs/ext4/fast_commit.c
+@@ -1682,7 +1682,7 @@ static int ext4_fc_replay_create(struct super_block *sb,
+                       ext4_debug("Dir %d not found.", darg.ino);
+                       goto out;
+               }
+-              ret = ext4_init_new_dir(NULL, dir, inode);
++              ret = ext4_init_new_dir(NULL, dir, inode, NULL, NULL);
+               iput(dir);
+               if (ret) {
+                       ret = 0;
+diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c
+index 3536ca7e..db367773 100644
+--- a/fs/ext4/inline.c
++++ b/fs/ext4/inline.c
+@@ -1013,7 +1013,7 @@ static int ext4_add_dirent_to_inline(handle_t *handle,
+       struct ext4_dir_entry_2 *de;
+       err = ext4_find_dest_de(dir, inode, iloc->bh, inline_start,
+-                              inline_size, fname, &de);
++                              inline_size, fname, &de, 0);
+       if (err)
+               return err;
+@@ -1022,7 +1022,7 @@ static int ext4_add_dirent_to_inline(handle_t *handle,
+                                           EXT4_JTR_NONE);
+       if (err)
+               return err;
+-      ext4_insert_dentry(dir, inode, de, inline_size, fname);
++      ext4_insert_dentry(dir, inode, de, inline_size, fname, NULL);
+       ext4_show_inline_dir(dir, iloc->bh, inline_start, inline_size);
+@@ -1381,7 +1381,7 @@ int ext4_inlinedir_to_tree(struct file *dir_file,
+                       fake.name_len = 1;
+                       strcpy(fake.name, ".");
+                       fake.rec_len = ext4_rec_len_to_disk(
+-                                        ext4_dir_rec_len(fake.name_len, NULL),
++                                        EXT4_DIR_ENTRY_LEN(&fake, NULL),
+                                         inline_size);
+                       ext4_set_de_type(inode->i_sb, &fake, S_IFDIR);
+                       de = &fake;
+@@ -1391,7 +1391,7 @@ int ext4_inlinedir_to_tree(struct file *dir_file,
+                       fake.name_len = 2;
+                       strcpy(fake.name, "..");
+                       fake.rec_len = ext4_rec_len_to_disk(
+-                                        ext4_dir_rec_len(fake.name_len, NULL),
++                                        EXT4_DIR_ENTRY_LEN(&fake, NULL),
+                                         inline_size);
+                       ext4_set_de_type(inode->i_sb, &fake, S_IFDIR);
+                       de = &fake;
+diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
+index 637386bb..07d8d6b0 100644
+--- a/fs/ext4/namei.c
++++ b/fs/ext4/namei.c
+@@ -291,7 +291,8 @@ static unsigned dx_get_count(struct dx_entry *entries);
+ static unsigned dx_get_limit(struct dx_entry *entries);
+ static void dx_set_count(struct dx_entry *entries, unsigned value);
+ static void dx_set_limit(struct dx_entry *entries, unsigned value);
+-static unsigned dx_root_limit(struct inode *dir, unsigned infosize);
++static inline unsigned dx_root_limit(struct inode *dir,
++              struct ext4_dir_entry_2 *dot_de, unsigned infosize);
+ static unsigned dx_node_limit(struct inode *dir);
+ static struct dx_frame *dx_probe(struct ext4_filename *fname,
+                                struct inode *dir,
+@@ -438,23 +439,24 @@ static struct dx_countlimit *get_dx_countlimit(struct inode *inode,
+ {
+       struct ext4_dir_entry *dp;
+       struct dx_root_info *root;
+-      int count_offset;
++      int count_offset, dot_rec_len, dotdot_rec_len;
+       int blocksize = EXT4_BLOCK_SIZE(inode->i_sb);
+       unsigned int rlen = ext4_rec_len_from_disk(dirent->rec_len, blocksize);
+       if (rlen == blocksize)
+               count_offset = 8;
+-      else if (rlen == 12) {
+-              dp = (struct ext4_dir_entry *)(((void *)dirent) + 12);
+-              if (ext4_rec_len_from_disk(dp->rec_len, blocksize) != blocksize - 12)
++      else {
++              dot_rec_len = le16_to_cpu(dirent->rec_len);
++              dp = (struct ext4_dir_entry *)(((void *)dirent) + dot_rec_len);
++              if (le16_to_cpu(dp->rec_len) != (blocksize - dot_rec_len))
+                       return NULL;
+-              root = (struct dx_root_info *)(((void *)dp + 12));
++              dotdot_rec_len = EXT4_DIR_ENTRY_LEN((struct ext4_dir_entry_2 *)dp, NULL);
++              root = (struct dx_root_info *)(((void *)dp + dotdot_rec_len));
+               if (root->reserved_zero ||
+                   root->info_length != sizeof(struct dx_root_info))
+                       return NULL;
+-              count_offset = 32;
+-      } else
+-              return NULL;
++              count_offset = 8 + dot_rec_len + dotdot_rec_len;
++      }
+       if (offset)
+               *offset = count_offset;
+@@ -557,13 +559,14 @@ ext4_next_entry(struct ext4_dir_entry_2 *p, unsigned long blocksize)
+  * Future: use high four bits of block for coalesce-on-delete flags
+  * Mask them off for now.
+  */
+-struct dx_root_info *dx_get_dx_info(struct ext4_dir_entry_2 *de)
++static struct dx_root_info *dx_get_dx_info(struct ext4_dir_entry_2 *de)
+ {
++      BUG_ON(de->name_len != 1);
+       /* get dotdot first */
+-      de = (struct ext4_dir_entry_2 *)((char *)de + EXT4_DIR_REC_LEN(1));
++      de = (struct ext4_dir_entry_2 *)((char *)de + EXT4_DIR_ENTRY_LEN(de, NULL));
+       /* dx root info is after dotdot entry */
+-      de = (struct ext4_dir_entry_2 *)((char *)de + EXT4_DIR_REC_LEN(2));
++      de = (struct ext4_dir_entry_2 *)((char *)de + EXT4_DIR_ENTRY_LEN(de, NULL));
+       return (struct dx_root_info *)de;
+ }
+@@ -608,11 +611,16 @@ static inline void dx_set_limit(struct dx_entry *entries, unsigned value)
+       ((struct dx_countlimit *) entries)->limit = cpu_to_le16(value);
+ }
+-static inline unsigned dx_root_limit(struct inode *dir, unsigned infosize)
++static inline unsigned dx_root_limit(struct inode *dir,
++              struct ext4_dir_entry_2 *dot_de, unsigned infosize)
+ {
+-      unsigned int entry_space = dir->i_sb->s_blocksize -
+-                      ext4_dir_rec_len(1, NULL) -
+-                      ext4_dir_rec_len(2, NULL) - infosize;
++      struct ext4_dir_entry_2 *dotdot_de;
++      unsigned entry_space;
++
++      BUG_ON(dot_de->name_len != 1);
++      dotdot_de = ext4_next_entry(dot_de, dir->i_sb->s_blocksize);
++      entry_space = dir->i_sb->s_blocksize - EXT4_DIR_ENTRY_LEN(dot_de, NULL) -
++                       EXT4_DIR_ENTRY_LEN(dotdot_de, NULL) - infosize;
+       if (ext4_has_metadata_csum(dir->i_sb))
+               entry_space -= sizeof(struct dx_tail);
+@@ -732,7 +740,7 @@ static struct stats dx_show_leaf(struct inode *dir,
+                                      (unsigned) ((char *) de - base));
+ #endif
+                       }
+-                      space += ext4_dir_rec_len(de->name_len, dir);
++                      space += EXT4_DIR_ENTRY_LEN(de, dir);
+                       names++;
+               }
+               de = ext4_next_entry(de, size);
+@@ -888,11 +896,14 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
+       entries = (struct dx_entry *)(((char *)info) + info->info_length);
+-      if (dx_get_limit(entries) != dx_root_limit(dir,
+-                                                 info->info_length)) {
++      if (dx_get_limit(entries) !=
++          dx_root_limit(dir, (struct ext4_dir_entry_2 *)frame->bh->b_data,
++                        info->info_length)) {
+               ext4_warning_inode(dir, "dx entry: limit %u != root limit %u",
+                                  dx_get_limit(entries),
+-                                 dx_root_limit(dir, info->info_length));
++                                 dx_root_limit(dir,
++                                        (struct ext4_dir_entry_2 *)frame->bh->b_data,
++                                        info->info_length));
+               goto fail;
+       }
+@@ -1906,7 +1917,7 @@ dx_move_dirents(struct inode *dir, char *from, char *to,
+       while (count--) {
+               struct ext4_dir_entry_2 *de = (struct ext4_dir_entry_2 *)
+                                               (from + (map->offs<<2));
+-              rec_len = ext4_dir_rec_len(de->name_len, dir);
++              rec_len = EXT4_DIR_ENTRY_LEN(de, dir);
+               memcpy (to, de, rec_len);
+               ((struct ext4_dir_entry_2 *) to)->rec_len =
+@@ -1939,7 +1950,7 @@ static struct ext4_dir_entry_2 *dx_pack_dirents(struct inode *dir, char *base,
+       while ((char*)de < base + blocksize) {
+               next = ext4_next_entry(de, blocksize);
+               if (de->inode && de->name_len) {
+-                      rec_len = ext4_dir_rec_len(de->name_len, dir);
++                      rec_len = EXT4_DIR_ENTRY_LEN(de, dir);
+                       if (de > to)
+                               memmove(to, de, rec_len);
+                       to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
+@@ -2082,10 +2093,11 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+                     struct buffer_head *bh,
+                     void *buf, int buf_size,
+                     struct ext4_filename *fname,
+-                    struct ext4_dir_entry_2 **dest_de)
++                    struct ext4_dir_entry_2 **dest_de,
++                    int dlen)
+ {
+       struct ext4_dir_entry_2 *de;
+-      unsigned short reclen = ext4_dir_rec_len(fname_len(fname), dir);
++      unsigned short reclen = ext4_dir_rec_len(fname_len(fname) + dlen, dir);
+       int nlen, rlen;
+       unsigned int offset = 0;
+       char *top;
+@@ -2098,7 +2110,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+                       return -EFSCORRUPTED;
+               if (ext4_match(dir, fname, de))
+                       return -EEXIST;
+-              nlen = ext4_dir_rec_len(de->name_len, dir);
++              nlen = EXT4_DIR_ENTRY_LEN(de, dir);
+               rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
+               if ((de->inode ? rlen - nlen : rlen) >= reclen)
+                       break;
+@@ -2116,12 +2128,13 @@ void ext4_insert_dentry(struct inode *dir,
+                       struct inode *inode,
+                       struct ext4_dir_entry_2 *de,
+                       int buf_size,
+-                      struct ext4_filename *fname)
++                      struct ext4_filename *fname,
++                      void *data)
+ {
+       int nlen, rlen;
+-      nlen = ext4_dir_rec_len(de->name_len, dir);
++      nlen = EXT4_DIR_ENTRY_LEN(de, dir);
+       rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
+       if (de->inode) {
+               struct ext4_dir_entry_2 *de1 =
+@@ -2142,6 +2155,12 @@ void ext4_insert_dentry(struct inode *dir,
+               EXT4_DIRENT_HASHES(de)->minor_hash =
+                                               cpu_to_le32(hinfo->minor_hash);
+       }
++      if (data) {
++              de->name[fname_len(fname)] = 0;
++              memcpy(&de->name[fname_len(fname) + 1], data, *(char *)data);
++              de->file_type |= EXT4_DIRENT_LUFID;
++      }
++
+ }
+ /*
+@@ -2159,14 +2178,19 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
+ {
+       unsigned int    blocksize = dir->i_sb->s_blocksize;
+       int             csum_size = 0;
+-      int             err, err2;
++      int             err, err2, dlen = 0;
++      unsigned char   *data;
++      data = ext4_dentry_get_data(inode->i_sb, (struct ext4_dentry_param *)
++                                              EXT4_I(inode)->i_dirdata);
+       if (ext4_has_metadata_csum(inode->i_sb))
+               csum_size = sizeof(struct ext4_dir_entry_tail);
+       if (!de) {
++              if (data)
++                      dlen = (*data) + 1;
+               err = ext4_find_dest_de(dir, inode, bh, bh->b_data,
+-                                      blocksize - csum_size, fname, &de);
++                                      blocksize - csum_size, fname, &de, dlen);
+               if (err)
+                       return err;
+       }
+@@ -2179,7 +2203,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
+       }
+       /* By now the buffer is marked for journaling */
+-      ext4_insert_dentry(dir, inode, de, blocksize, fname);
++      ext4_insert_dentry(dir, inode, de, blocksize, fname, data);
+       /*
+        * XXX shouldn't update any times until successful
+@@ -2333,7 +2357,8 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
+       entries = (void *)dx_info + sizeof(*dx_info);
+       dx_set_block(entries, 1);
+       dx_set_count(entries, 1);
+-      dx_set_limit(entries, dx_root_limit(dir, sizeof(*dx_info)));
++      dx_set_limit(entries, dx_root_limit(dir,
++                                       dot_de, sizeof(*dx_info)));
+       /* Initialize as for dx_probe */
+       fname->hinfo.hash_version = dx_info->hash_version;
+@@ -2384,7 +2409,106 @@ out_frames:
+       return retval;
+ }
+-/* update ".." entry */
++static int ext4_expand_dotdot(struct inode *dir,
++                            struct buffer_head *bh,
++                            int dlen)
++{
++      struct ext4_dir_entry_2 *dot_de;
++      struct ext4_dir_entry_2 *dotdot_de;
++      int len;
++      unsigned blocksize = dir->i_sb->s_blocksize;
++
++      dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
++      dotdot_de = ext4_next_entry(dot_de, blocksize);
++
++      if (is_dx(dir)) {
++              struct dx_entry *entries;
++              struct dx_root_info *dx_info;
++              int limit, count;
++              int entry_space;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      EXT4_DIR_ENTRY_LEN(dotdot_de, NULL);
++
++              dx_info = dx_get_dx_info(dot_de);
++              entries = (struct dx_entry *)((char *)dx_info +
++                                                      sizeof(*dx_info));
++              count = dx_get_count(entries);
++
++              /*
++               * figure out new limit with dlen,
++               * check if we have enough space
++               */
++              entry_space = blocksize;
++              entry_space -= (char *)dotdot_de - (char *)dot_de +
++                             EXT4_DIR_REC_LEN(2 + dlen, NULL) +
++                             sizeof(*dx_info);
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      entry_space -= sizeof(struct dx_tail);
++              limit = entry_space / sizeof(struct dx_entry);
++              if (count > limit)
++                      return -ENOSPC;
++
++              /* set the new limit, move dx_info and the entries */
++              dx_set_limit(entries, limit);
++              memmove((char *)dx_info + len, dx_info,
++                      sizeof(*dx_info) + count * sizeof(struct dx_entry));
++      } else {
++              struct ext4_dir_entry_2 *next, *to, *prev, *de;
++              char *top = (char *)bh->b_data + blocksize;
++              int space = 0;
++              unsigned rec_len = 0;
++
++              len = EXT4_DIR_REC_LEN(2 + dlen, NULL) -
++                      ext4_rec_len_from_disk(dotdot_de->rec_len, blocksize);
++
++              if (ext4_has_metadata_csum(dir->i_sb))
++                      top -= sizeof(struct ext4_dir_entry_tail);
++
++              de = ext4_next_entry(dotdot_de, blocksize);
++              while ((char *)de < top) {
++                      space += ext4_rec_len_from_disk(de->rec_len, blocksize) -
++                                      EXT4_DIR_ENTRY_LEN(de, dir);
++                      de = ext4_next_entry(de, blocksize);
++              }
++
++              if (space < len)
++                      return -ENOSPC;
++
++              /* pack all the entries after dotdot */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              prev = to = de;
++              while ((char *)de < top) {
++                      next = ext4_next_entry(de, blocksize);
++                      if (de->inode && de->name_len) {
++                              rec_len = EXT4_DIR_ENTRY_LEN(de, dir);
++                              if (de > to)
++                                      memmove(to, de, rec_len);
++                              to->rec_len = ext4_rec_len_to_disk(rec_len,
++                                                                 blocksize);
++                              prev = to;
++                              to = (struct ext4_dir_entry_2 *)
++                                              (((char *)to) + rec_len);
++                      }
++                      de = next;
++              }
++              /* fix up rec_len for the last entry */
++              prev->rec_len = ext4_rec_len_to_disk(top - (char *)prev - len,
++                                                   blocksize);
++              /* move all the entries after dotdot to make space */
++              de = ext4_next_entry(dotdot_de, blocksize);
++              memmove((char *)de + len, de, (char *)prev - (char *)de +
++                      EXT4_DIR_ENTRY_LEN(prev, dir));
++              /* fix the rec_len for dotdot */
++              dotdot_de->rec_len = ext4_rec_len_to_disk(
++                                      EXT4_DIR_REC_LEN(2 + dlen, NULL),
++                                      blocksize);
++      }
++
++      return 0;
++}
++
++/* update ".." entry, try to expand the entry if necessary */
+ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+                             struct inode *inode)
+ {
+@@ -2393,6 +2517,8 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+       struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+       unsigned int offset;
+       int retval = 0;
++      int dlen = 0;
++      char *data;
+       if (IS_ERR(handle))
+               return PTR_ERR(handle);
+@@ -2433,6 +2559,30 @@ static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+       dotdot_de->inode = cpu_to_le32(inode->i_ino);
++      data = ext4_dentry_get_data(dir->i_sb,
++                      (struct ext4_dentry_param *)dentry->d_fsdata);
++      if (data != NULL) {
++              dlen = *data + 1;
++              if (is_dx(dir)) {
++                      if (ext4_get_dirent_data_len(dotdot_de) < dlen) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              } else {
++                      if (ext4_rec_len_from_disk(dotdot_de->rec_len,
++                                                 dir->i_sb->s_blocksize) <
++                          EXT4_DIR_REC_LEN(2 + dlen, NULL)) {
++                              if (ext4_expand_dotdot(dir, bh, dlen) < 0)
++                                      dlen = 0;
++                      }
++              }
++      }
++      if (dlen) {
++              dotdot_de->name[2] = 0;
++              memcpy(&dotdot_de->name[2 + 1], data, *data);
++              dotdot_de->file_type |= LDISKFS_DIRENT_LUFID;
++      }
++
+       ext4_mark_inode_dirty(handle, dir);
+       BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+       if (is_dx(dir)) {
+@@ -2470,6 +2620,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
+       ext4_lblk_t block, blocks;
+       int     csum_size = 0;
++      EXT4_I(inode)->i_dirdata = dentry->d_fsdata;
+       if (ext4_has_metadata_csum(inode->i_sb))
+               csum_size = sizeof(struct ext4_dir_entry_tail);
+@@ -3041,38 +3192,73 @@ err_unlock_inode:
+       return err;
+ }
++struct tp_block {
++      struct inode *inode;
++      void *data1;
++      void *data2;
++};
++
+ struct ext4_dir_entry_2 *ext4_init_dot_dotdot(struct inode *inode,
+                         struct ext4_dir_entry_2 *de,
+                         int blocksize, int csum_size,
+                         unsigned int parent_ino, int dotdot_real_len)
+ {
++      void *data1 = NULL, *data2 = NULL;
++      int dot_reclen = 0;
++
++      if (dotdot_real_len == 10) {
++              struct tp_block *tpb = (struct tp_block *)inode;
++              data1 = tpb->data1;
++              data2 = tpb->data2;
++              inode = tpb->inode;
++              dotdot_real_len = 0;
++      }
+       de->inode = cpu_to_le32(inode->i_ino);
+       de->name_len = 1;
+-      de->rec_len = ext4_rec_len_to_disk(ext4_dir_rec_len(de->name_len, NULL),
+-                                         blocksize);
+       strcpy(de->name, ".");
+       ext4_set_de_type(inode->i_sb, de, S_IFDIR);
++      /* get packed fid data*/
++      data1 = ext4_dentry_get_data(inode->i_sb,
++                              (struct ext4_dentry_param *) data1);
++      if (data1) {
++              de->name[1] = 0;
++              memcpy(&de->name[2], data1, *(char *) data1);
++              de->file_type |= EXT4_DIRENT_LUFID;
++      }
++      de->rec_len = cpu_to_le16(EXT4_DIR_ENTRY_LEN(de, NULL));
++
++      dot_reclen = cpu_to_le16(de->rec_len);
+       de = ext4_next_entry(de, blocksize);
+       de->inode = cpu_to_le32(parent_ino);
+       de->name_len = 2;
++
++      strcpy(de->name, "..");
++      ext4_set_de_type(inode->i_sb, de, S_IFDIR);
++      data2 = ext4_dentry_get_data(inode->i_sb,
++                      (struct ext4_dentry_param *) data2);
++      if (data2) {
++              de->name[2] = 0;
++              memcpy(&de->name[3], data2, *(char *) data2);
++              de->file_type |= EXT4_DIRENT_LUFID;
++      }
++
+       if (!dotdot_real_len)
+               de->rec_len = ext4_rec_len_to_disk(blocksize -
+-                                      (csum_size + ext4_dir_rec_len(1, NULL)),
+-                                      blocksize);
++                                      (csum_size + dot_reclen), blocksize);
+       else
+               de->rec_len = ext4_rec_len_to_disk(
+-                                      ext4_dir_rec_len(de->name_len, NULL),
++                                      EXT4_DIR_ENTRY_LEN(de, NULL),
+                                       blocksize);
+-      strcpy(de->name, "..");
+-      ext4_set_de_type(inode->i_sb, de, S_IFDIR);
+       return ext4_next_entry(de, blocksize);
+ }
+ int ext4_init_new_dir(handle_t *handle, struct inode *dir,
+-                           struct inode *inode)
++                           struct inode *inode,
++                           const void *data1, const void *data2)
+ {
++      struct tp_block param;
+       struct buffer_head *dir_block = NULL;
+       struct ext4_dir_entry_2 *de;
+       ext4_lblk_t block = 0;
+@@ -3096,7 +3282,11 @@ int ext4_init_new_dir(handle_t *handle, struct inode *dir,
+       if (IS_ERR(dir_block))
+               return PTR_ERR(dir_block);
+       de = (struct ext4_dir_entry_2 *)dir_block->b_data;
+-      ext4_init_dot_dotdot(inode, de, blocksize, csum_size, dir->i_ino, 0);
++      param.inode = inode;
++      param.data1 = (void *)data1;
++      param.data2 = (void *)data2;
++      ext4_init_dot_dotdot((struct inode *)(&param), de, blocksize,
++                           csum_size, dir->i_ino, 10);
+       set_nlink(inode, 2);
+       if (csum_size)
+               ext4_initialize_dirent_tail(dir_block, blocksize);
+@@ -3138,7 +3328,7 @@ retry:
+       inode->i_op = &ext4_dir_inode_operations;
+       inode->i_fop = &ext4_dir_operations;
+-      err = ext4_init_new_dir(handle, dir, inode);
++      err = ext4_init_new_dir(handle, dir, inode, NULL, NULL);
+       if (err)
+               goto out_clear_inode;
+       err = ext4_mark_inode_dirty(handle, inode);
+@@ -3176,6 +3366,29 @@ out_retry:
+       return err;
+ }
++/* Initialize @inode as a subdirectory of @dir, and add the
++ * "." and ".." entries into the first directory block. */
++int ext4_add_dot_dotdot(handle_t *handle, struct inode *dir,
++                      struct inode *inode,
++                      const void *data1, const void *data2)
++{
++      int rc;
++
++      if (IS_ERR(handle))
++              return PTR_ERR(handle);
++
++      if (IS_DIRSYNC(dir))
++              ext4_handle_sync(handle);
++
++      inode->i_op = &ext4_dir_inode_operations;
++      inode->i_fop = &ext4_dir_operations;
++      rc = ext4_init_new_dir(handle, dir, inode, data1, data2);
++      if (!rc)
++              rc = ext4_mark_inode_dirty(handle, inode);
++      return rc;
++}
++EXPORT_SYMBOL(ext4_add_dot_dotdot);
++
+ /*
+  * routine to check that the specified directory is empty (for rmdir)
+  */
+diff --git a/fs/ext4/super.c b/fs/ext4/super.c
+index cc4f8072..7274bb7d 100644
+--- a/fs/ext4/super.c
++++ b/fs/ext4/super.c
+@@ -1661,7 +1661,7 @@ enum {
+       Opt_data_err_abort, Opt_data_err_ignore, Opt_test_dummy_encryption,
+       Opt_inlinecrypt,
+       Opt_usrjquota, Opt_grpjquota, Opt_quota,
+-      Opt_noquota, Opt_barrier, Opt_nobarrier, Opt_err,
++      Opt_noquota, Opt_barrier, Opt_nobarrier, Opt_err, Opt_dirdata,
+       Opt_usrquota, Opt_grpquota, Opt_prjquota,
+       Opt_dax, Opt_dax_always, Opt_dax_inode, Opt_dax_never,
+       Opt_stripe, Opt_delalloc, Opt_nodelalloc, Opt_warn_on_error,
+@@ -1771,6 +1771,7 @@ static const struct fs_parameter_spec ext4_param_specs[] = {
+       fsparam_u32     ("stripe",              Opt_stripe),
+       fsparam_flag    ("delalloc",            Opt_delalloc),
+       fsparam_flag    ("nodelalloc",          Opt_nodelalloc),
++      fsparam_flag    ("dirdata",             Opt_dirdata),
+       fsparam_flag    ("warn_on_error",       Opt_warn_on_error),
+       fsparam_flag    ("nowarn_on_error",     Opt_nowarn_on_error),
+       fsparam_u32     ("debug_want_extra_isize",
+@@ -1900,6 +1901,7 @@ static const struct mount_opts {
+                                                       MOPT_CLEAR | MOPT_Q},
+       {Opt_usrjquota, 0, MOPT_Q},
+       {Opt_grpjquota, 0, MOPT_Q},
++      {Opt_dirdata, EXT4_MOUNT_DIRDATA, MOPT_SET},
+       {Opt_jqfmt, 0, MOPT_QFMT},
+       {Opt_nombcache, EXT4_MOUNT_NO_MBCACHE, MOPT_SET},
+       {Opt_no_prefetch_block_bitmaps, EXT4_MOUNT_NO_PREFETCH_BLOCK_BITMAPS,
+-- 
+2.34.1
+
diff --git a/ldiskfs/kernel_patches/patches/linux-6.12/ext4-ext-merge.patch b/ldiskfs/kernel_patches/patches/linux-6.12/ext4-ext-merge.patch
new file mode 100644 (file)
index 0000000..8c8bdbf
--- /dev/null
@@ -0,0 +1,251 @@
+From 0f7e6c02a9ea5b85d8d97f724bed318268cea60f Mon Sep 17 00:00:00 2001
+From: Alex Zhuravlev <bzzz@whamcloud.com>
+Date: Tue, 23 May 2023 16:30:58 +0300
+Subject: [PATCH] LU-16843 ldiskfs: merge extent blocks
+
+There are cases (e.g. file written synchronously with discontiguous
+blocks that are later filled in) when a lot of extents are created
+initially, then the extents get merged over time, but there is no
+way to merge the index blocks.  This can cause a very deep extent
+index tree (above 5 levels) and cause problems like:
+
+inode has invalid extent depth: 6
+
+Merge leave/index blocks (one at each level at most) to right/left
+when extents are removed from the index.
+
+submitted to ext4@ maillist:
+https://lore.kernel.org/linux-ext4/7A2B8861-96AA-4815-BB58-180F63F62436@whamcloud.com/
+
+Signed-off-by: Alex Zhuravlev <bzzz@whamcloud.com>
+Change-Id: I746c0917e746eb442d3c69a23f591d9cdade76fa
+Reviewed-on: https://review.whamcloud.com/51096
+Reviewed-by: Artem Blagodarenko <ablagodarenko@ddn.com>
+Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
+Reviewed-by: Oleg Drokin <green@whamcloud.com>
+---
+ fs/ext4/extents.c | 178 +++++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 175 insertions(+), 3 deletions(-)
+
+diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
+index 34e25eee..a50f3d22 100644
+--- a/fs/ext4/extents.c
++++ b/fs/ext4/extents.c
+@@ -1892,7 +1892,7 @@ static void ext4_ext_try_to_merge_up(handle_t *handle,
+  * This function tries to merge the @ex extent to neighbours in the tree, then
+  * tries to collapse the extent tree into the inode.
+  */
+-static void ext4_ext_try_to_merge(handle_t *handle,
++static int ext4_ext_try_to_merge(handle_t *handle,
+                                 struct inode *inode,
+                                 struct ext4_ext_path *path,
+                                 struct ext4_extent *ex)
+@@ -1909,9 +1909,178 @@ static void ext4_ext_try_to_merge(handle_t *handle,
+               merge_done = ext4_ext_try_to_merge_right(inode, path, ex - 1);
+       if (!merge_done)
+-              (void) ext4_ext_try_to_merge_right(inode, path, ex);
++              merge_done = ext4_ext_try_to_merge_right(inode, path, ex);
+       ext4_ext_try_to_merge_up(handle, inode, path);
++
++      return merge_done;
++}
++
++/*
++ * This function tries to merge blocks from @path into @npath
++ */
++static int ext4_ext_merge_blocks(handle_t *handle,
++                              struct inode *inode,
++                              struct ext4_ext_path *path,
++                              struct ext4_ext_path *npath)
++{
++      unsigned int depth = ext_depth(inode);
++      int used, nused, free, i, k, err;
++      ext4_fsblk_t next;
++
++      if (path[depth].p_hdr == npath[depth].p_hdr)
++              return 0;
++
++      used = le16_to_cpu(path[depth].p_hdr->eh_entries);
++      free = le16_to_cpu(npath[depth].p_hdr->eh_max) -
++              le16_to_cpu(npath[depth].p_hdr->eh_entries);
++      if (free < used)
++              return 0;
++
++      err = ext4_ext_get_access(handle, inode, path + depth);
++      if (err)
++              return err;
++      err = ext4_ext_get_access(handle, inode, npath + depth);
++      if (err)
++              return err;
++
++      /* move entries from the current leave to the next one */
++      nused = le16_to_cpu(npath[depth].p_hdr->eh_entries);
++      memmove(EXT_FIRST_EXTENT(npath[depth].p_hdr) + used,
++              EXT_FIRST_EXTENT(npath[depth].p_hdr),
++              nused * sizeof(struct ext4_extent));
++      memcpy(EXT_FIRST_EXTENT(npath[depth].p_hdr),
++              EXT_FIRST_EXTENT(path[depth].p_hdr),
++              used * sizeof(struct ext4_extent));
++      le16_add_cpu(&npath[depth].p_hdr->eh_entries, used);
++      le16_add_cpu(&path[depth].p_hdr->eh_entries, -used);
++      ext4_ext_try_to_merge_right(inode, npath,
++                                      EXT_FIRST_EXTENT(npath[depth].p_hdr));
++
++      err = ext4_ext_dirty(handle, inode, path + depth);
++      if (err)
++              return err;
++      err = ext4_ext_dirty(handle, inode, npath + depth);
++      if (err)
++              return err;
++
++      /* otherwise the index won't get corrected */
++      npath[depth].p_ext = EXT_FIRST_EXTENT(npath[depth].p_hdr);
++      err = ext4_ext_correct_indexes(handle, inode, npath);
++      if (err)
++              return err;
++
++      for (i = depth - 1; i >= 0; i--) {
++
++              next = ext4_idx_pblock(path[i].p_idx);
++              ext4_free_blocks(handle, inode, NULL, next, 1,
++                              EXT4_FREE_BLOCKS_METADATA |
++                              EXT4_FREE_BLOCKS_FORGET);
++              err = ext4_ext_get_access(handle, inode, path + i);
++              if (err)
++                      return err;
++              le16_add_cpu(&path[i].p_hdr->eh_entries, -1);
++              if (le16_to_cpu(path[i].p_hdr->eh_entries) == 0) {
++                      /* whole index block collapsed, go up */
++                      continue;
++              }
++              /* remove index pointer */
++              used = EXT_LAST_INDEX(path[i].p_hdr) - path[i].p_idx + 1;
++              memmove(path[i].p_idx, path[i].p_idx + 1,
++                      used * sizeof(struct ext4_extent_idx));
++
++              err = ext4_ext_dirty(handle, inode, path + i);
++              if (err)
++                      return err;
++
++              if (path[i].p_hdr == npath[i].p_hdr)
++                      break;
++
++              /* try to move index pointers */
++              used = le16_to_cpu(path[i].p_hdr->eh_entries);
++              free = le16_to_cpu(npath[i].p_hdr->eh_max) -
++                      le16_to_cpu(npath[i].p_hdr->eh_entries);
++              if (used > free)
++                      break;
++              err = ext4_ext_get_access(handle, inode, npath + i);
++              if (err)
++                      return err;
++              memmove(EXT_FIRST_INDEX(npath[i].p_hdr) + used,
++                      EXT_FIRST_INDEX(npath[i].p_hdr),
++                      npath[i].p_hdr->eh_entries * sizeof(struct ext4_extent_idx));
++              memcpy(EXT_FIRST_INDEX(npath[i].p_hdr), EXT_FIRST_INDEX(path[i].p_hdr),
++                      used * sizeof(struct ext4_extent_idx));
++              le16_add_cpu(&path[i].p_hdr->eh_entries, -used);
++              le16_add_cpu(&npath[i].p_hdr->eh_entries, used);
++              err = ext4_ext_dirty(handle, inode, path + i);
++              if (err)
++                      return err;
++              err = ext4_ext_dirty(handle, inode, npath + i);
++              if (err)
++                      return err;
++
++              /* correct index above */
++              for (k = i; k > 0; k--) {
++                      err = ext4_ext_get_access(handle, inode, npath + k - 1);
++                      if (err)
++                              return err;
++                      npath[k-1].p_idx->ei_block =
++                              EXT_FIRST_INDEX(npath[k].p_hdr)->ei_block;
++                      err = ext4_ext_dirty(handle, inode, npath + k - 1);
++                      if (err)
++                              return err;
++              }
++      }
++
++      /*
++       * TODO: given we've got two paths, it should be possible to
++       * collapse those two blocks into the root one in some cases
++       */
++      return 1;
++}
++
++static int ext4_ext_try_to_merge_blocks(handle_t *handle,
++              struct inode *inode,
++              struct ext4_ext_path *path)
++{
++      struct ext4_ext_path *npath = NULL;
++      unsigned int depth = ext_depth(inode);
++      ext4_lblk_t next;
++      int used, rc = 0;
++
++      if (depth == 0)
++              return 0;
++
++      used = le16_to_cpu(path[depth].p_hdr->eh_entries);
++      /* don't be too agressive as checking space in
++       * the next block is not free */
++      if (used > ext4_ext_space_block(inode, 0) / 4)
++              return 0;
++
++      /* try to merge to the next block */
++      next = ext4_ext_next_leaf_block(path);
++      if (next == EXT_MAX_BLOCKS)
++              return 0;
++      npath = ext4_find_extent(inode, next, NULL, 0);
++      if (IS_ERR(npath))
++              return 0;
++      rc = ext4_ext_merge_blocks(handle, inode, path, npath);
++      ext4_ext_drop_refs(npath);
++      kfree(npath);
++      if (rc)
++              return rc > 0 ? 0 : rc;
++
++      /* try to merge with the previous block */
++      if (EXT_FIRST_EXTENT(path[depth].p_hdr)->ee_block == 0)
++              return 0;
++      next = EXT_FIRST_EXTENT(path[depth].p_hdr)->ee_block - 1;
++      npath = ext4_find_extent(inode, next, NULL, 0);
++      if (IS_ERR(npath))
++              return 0;
++      rc = ext4_ext_merge_blocks(handle, inode, npath, path);
++      ext4_ext_drop_refs(npath);
++      kfree(npath);
++      return rc > 0 ? 0 : rc;
+ }
+ /*
+@@ -1982,6 +2151,7 @@ ext4_ext_insert_extent(handle_t *handle, struct inode *inode,
+       int depth, len, err = 0;
+       ext4_lblk_t next;
+       int mb_flags = 0, unwritten;
++      int merged = 0;
+       if (gb_flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE)
+               mb_flags |= EXT4_MB_DELALLOC_RESERVED;
+@@ -2180,7 +2350,7 @@ has_space:
+ merge:
+       /* try to merge extents */
+       if (!(gb_flags & EXT4_GET_BLOCKS_PRE_IO))
+-              ext4_ext_try_to_merge(handle, inode, path, nearex);
++              merged = ext4_ext_try_to_merge(handle, inode, path, nearex);
+       /* time to correct all indexes above */
+       err = ext4_ext_correct_indexes(handle, inode, path);
+@@ -2188,6 +2358,8 @@ merge:
+               goto errout;
+       err = ext4_ext_dirty(handle, inode, path + path->p_depth);
++      if (!err && merged)
++              err = ext4_ext_try_to_merge_blocks(handle, inode, path);
+       if (err)
+               goto errout;
+-- 
+2.34.1
+
diff --git a/ldiskfs/kernel_patches/patches/linux-6.12/ext4-filename-encode.patch b/ldiskfs/kernel_patches/patches/linux-6.12/ext4-filename-encode.patch
new file mode 100644 (file)
index 0000000..0b0a75f
--- /dev/null
@@ -0,0 +1,545 @@
+From d0a722cb8fb886380e24e8261e8efca09a3262d6 Mon Sep 17 00:00:00 2001
+From: Sebastien Buisson <sbuisson@ddn.com>
+Date: Tue, 20 Dec 2022 15:40:52 +0100
+Subject: [PATCH] LU-16374 ldiskfs: implement security.encdata xattr
+
+security.encdata is a virtual xattr containing information related
+to encrypted files. It is expressed as ASCII text with a "key: value"
+format, and space as field separator. For instance:
+
+   { encoding: base64url, size: 3012, enc_ctx: YWJjZGVmZ2hpamtsbW
+   5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbg, enc_name: ZmlsZXdpdGh2ZX
+   J5bG9uZ25hbWVmaWxld2l0aHZlcnlsb25nbmFtZWZpbGV3aXRodmVyeWxvbmdu
+   YW1lZmlsZXdpdGg }
+
+'encoding' is the encoding method used for binary data, assume name
+can be up to 255 chars.
+'size' is the clear text file data length in bytes.
+'enc_ctx' is encoded encryption context, 40 bytes for v2.
+'enc_name' is encoded encrypted name, 256 bytes max.
+So on overall, this xattr is at most 727 chars plus terminating '0'.
+
+On get, the value of the security.encdata xattr is computed from
+encrypted file's information.
+On set, encrypted file's information is restored from xattr value.
+The encrypted name is stored temporarily in a dedicated xattr
+LDISKFS_XATTR_NAME_RAWENCNAME, that will be used to set correct name
+at linkat.
+
+Signed-off-by: Sebastien Buisson <sbuisson@ddn.com>
+Change-Id: Ia318c39d403b1c448e71bcd5b29862d022d05d0a
+Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/49456
+Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
+Reviewed-by: Li Dongyang <dongyangli@ddn.com>
+Reviewed-by: Oleg Drokin <green@whamcloud.com>
+---
+ fs/ext4/critical_encode.h | 170 ++++++++++++++++++++++++++++++++++++++
+ fs/ext4/dir.c             |  81 +++++++++++-------
+ fs/ext4/ialloc.c          |   1 +
+ fs/ext4/namei.c           |  56 +++++++++----
+ 4 files changed, 264 insertions(+), 44 deletions(-)
+ create mode 100644 fs/ext4/critical_encode.h
+
+diff --git a/fs/ext4/critical_encode.h b/fs/ext4/critical_encode.h
+new file mode 100644
+index 00000000..f75aedab
+--- /dev/null
++++ b/fs/ext4/critical_encode.h
+@@ -0,0 +1,170 @@
++/*
++ *  critical_encode.h
++ *
++ *  Copyright (c) 2022 Whamcloud
++ */
++
++#ifndef _CRITICAL_ENCODE_H
++#define _CRITICAL_ENCODE_H
++
++#include <linux/ctype.h>
++
++/* Encoding/decoding routines inspired from yEnc principles.
++ * We just take care of a few critical characters:
++ * NULL, LF, CR, /, DEL and =.
++ * If such a char is found, it is replaced with '=' followed by
++ * the char value + 64.
++ * All other chars are left untouched.
++ * Efficiency of this encoding depends on the occurences of the
++ * critical chars, but statistically on binary data it can be much higher
++ * than base64 for instance.
++ */
++static inline int critical_encode(const u8 *src, int len, char *dst)
++{
++      u8 *p = (u8 *)src, *q = dst;
++
++      while (p - src < len) {
++              /* escape NULL, LF, CR, /, DEL and = */
++              if (unlikely(*p == 0x0 || *p == 0xA || *p == 0xD ||
++                           *p == '/' || *p == 0x7F || *p == '=')) {
++                      *(q++) = '=';
++                      *(q++) = *(p++) + 64;
++              } else {
++                      *(q++) = *(p++);
++              }
++      }
++
++      return (char *)q - dst;
++}
++
++/* returns the number of chars encoding would produce */
++static inline int critical_chars(const u8 *src, int len)
++{
++      u8 *p = (u8 *)src;
++      int newlen = len;
++
++      while (p - src < len) {
++              /* NULL, LF, CR, /, DEL and = cost an additional '=' */
++              if (unlikely(*p == 0x0 || *p == 0xA || *p == 0xD ||
++                           *p == '/' || *p == 0x7F || *p == '='))
++                      newlen++;
++              p++;
++      }
++
++      return newlen;
++}
++
++/* decoding routine - returns the number of chars in output */
++static inline int critical_decode(const u8 *src, int len, char *dst)
++{
++      u8 *p = (u8 *)src, *q = dst;
++
++      while (p - src < len) {
++              if (unlikely(*p == '=')) {
++                      *(q++) = *(++p) - 64;
++                      p++;
++              } else {
++                      *(q++) = *(p++);
++              }
++      }
++
++      return (char *)q - dst;
++}
++
++#define fscrypt_get_encryption_info(inode) \
++      (unlikely(!IS_LUSTRE_MOUNT(inode->i_sb)) ? 0 : -EOPNOTSUPP)
++
++static inline int ext4_has_permitted_context(struct inode *parent,
++                                      struct inode *child)
++{
++      if (unlikely(!IS_LUSTRE_MOUNT(parent->i_sb)))
++              return 1;
++      return fscrypt_has_permitted_context(parent, child);
++}
++
++struct ext4_filename;
++
++static inline int ext4_prepare_readdir(struct inode *dir)
++{
++      if (unlikely(!IS_LUSTRE_MOUNT(dir->i_sb)))
++              return 0;
++      return fscrypt_prepare_readdir(dir);
++}
++
++static inline int ext4_fname_alloc_buffer(const struct inode *inode,
++                                           u32 max_encrypted_len,
++                                           struct fscrypt_str *crypto_str)
++{
++      crypto_str->name = kmalloc(max_encrypted_len + 1, GFP_NOFS);
++      if (!crypto_str->name)
++              return -ENOMEM;
++      crypto_str->len = max_encrypted_len;
++      return 0;
++}
++
++static inline void ext4_fname_free_buffer(struct fscrypt_str *crypto_str)
++{
++      if (!crypto_str)
++              return;
++      kfree(crypto_str->name);
++      crypto_str->name = NULL;
++}
++
++static inline int ext4_fname_disk_to_usr(struct inode *inode,
++                                          u32 hash, u32 minor_hash,
++                                          const struct fscrypt_str *iname,
++                                          struct fscrypt_str *oname)
++{
++      int presented_len;
++
++      presented_len = critical_encode(iname->name, iname->len, oname->name);
++      if (presented_len > NAME_MAX) {
++              /* truncate at NAME_MAX,
++               * or NAME_MAX-1 if name ends with '=' to avoid decoding issue
++               */
++              presented_len = NAME_MAX;
++              if (oname->name[presented_len - 1] == '=')
++                      presented_len--;
++              oname->len = presented_len;
++      }
++      oname->name[presented_len] = '\0';
++
++      return 0;
++}
++
++static inline int ext4_setup_filename(struct inode *dir,
++                                       const struct qstr *iname,
++                                       int lookup,
++                                       struct ext4_filename *fname)
++{
++      fname->usr_fname = iname;
++
++      if (lookup && IS_ENCRYPTED(dir) &&
++          unlikely(!IS_LUSTRE_MOUNT(dir->i_sb) &&
++                   strnchr(iname->name, iname->len, '='))) {
++              /* Only proceed to critical decode if
++               * iname contains escape char '='.
++               */
++              int len = iname->len;
++              char *buf;
++
++              buf = kmalloc(len, GFP_NOFS);
++              if (!buf)
++                      return -ENOMEM;
++
++              len = critical_decode(iname->name, len, buf);
++              fname->disk_name.name = (unsigned char *)buf;
++              fname->disk_name.len = len;
++              return 0;
++      }
++
++      fname->disk_name.name = (unsigned char *) iname->name;
++      fname->disk_name.len = iname->len;
++
++#ifdef CONFIG_UNICODE
++      ext4_fname_setup_ci_filename(dir, iname, fname);
++#endif
++      return 0;
++}
++
++#endif /* _CRITICAL_ENCODE_H */
+diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c
+index 5012aa63..ceeffb61 100644
+--- a/fs/ext4/dir.c
++++ b/fs/ext4/dir.c
+@@ -29,6 +29,7 @@
+ #include <linux/unicode.h>
+ #include "ext4.h"
+ #include "xattr.h"
++#include "critical_encode.h"
+ static int ext4_dx_readdir(struct file *, struct dir_context *);
+@@ -135,7 +136,7 @@ static int ext4_readdir(struct file *file, struct dir_context *ctx)
+       struct fscrypt_str fstr = FSTR_INIT(NULL, 0);
+       struct dir_private_info *info = file->private_data;
+-      err = fscrypt_prepare_readdir(inode);
++      err = ext4_prepare_readdir(inode);
+       if (err)
+               return err;
+@@ -162,7 +163,8 @@ static int ext4_readdir(struct file *file, struct dir_context *ctx)
+                       return err;
+       }
+-      if (IS_ENCRYPTED(inode)) {
++      /* disable decryption of filename, present only escaped name */
++      if (0 && IS_ENCRYPTED(inode)) {
+               err = fscrypt_fname_alloc_buffer(EXT4_NAME_LEN, &fstr);
+               if (err < 0)
+                       return err;
+@@ -269,39 +271,43 @@ static int ext4_readdir(struct file *file, struct dir_context *ctx)
+                       offset += ext4_rec_len_from_disk(de->rec_len,
+                                       sb->s_blocksize);
+                       if (le32_to_cpu(de->inode)) {
+-                              if (!IS_ENCRYPTED(inode)) {
++                              /* disable decryption of filename... */
++                              if (1 || !IS_ENCRYPTED(inode)) {
+                                       if (!dir_emit(ctx, de->name,
+                                           de->name_len,
+                                           le32_to_cpu(de->inode),
+                                           get_dtype(sb, de->file_type)))
+                                               goto done;
+                               } else {
+-                                      int save_len = fstr.len;
+                                       struct fscrypt_str de_name =
+                                                       FSTR_INIT(de->name,
+                                                               de->name_len);
+-                                      u32 hash;
+-                                      u32 minor_hash;
+-
+-                                      if (IS_CASEFOLDED(inode)) {
+-                                              hash = EXT4_DIRENT_HASH(de);
+-                                              minor_hash = EXT4_DIRENT_MINOR_HASH(de);
+-                                      } else {
+-                                              hash = 0;
+-                                              minor_hash = 0;
+-                                      }
++                                      int presented_len;
+                                       /* Directory is encrypted */
+-                                      err = fscrypt_fname_disk_to_usr(inode,
+-                                              hash, minor_hash, &de_name, &fstr);
+-                                      de_name = fstr;
+-                                      fstr.len = save_len;
++                                      presented_len = critical_chars(de->name,
++                                                                de->name_len);
++                                      err = ext4_fname_alloc_buffer(inode,
++                                                                presented_len,
++                                                                &fstr);
+                                       if (err)
+                                               goto errout;
+-                                      if (!dir_emit(ctx,
++
++                                      err = ext4_fname_disk_to_usr(inode,
++                                              EXT4_DIRENT_HASH(de),
++                                              EXT4_DIRENT_MINOR_HASH(de),
++                                              &de_name, &fstr);
++                                      de_name = fstr;
++                                      if (err) {
++                                              ext4_fname_free_buffer(&fstr);
++                                              goto errout;
++                                      }
++                                      err = dir_emit(ctx,
+                                           de_name.name, de_name.len,
+                                           le32_to_cpu(de->inode),
+-                                          get_dtype(sb, de->file_type)))
++                                          get_dtype(sb, de->file_type));
++                                      ext4_fname_free_buffer(&fstr);
++                                      if (!err)
+                                               goto done;
+                               }
+                       }
+@@ -439,15 +445,26 @@ static void free_rb_tree_fname(struct rb_root *root)
+       *root = RB_ROOT;
+ }
+-static void ext4_htree_init_dir_info(struct file *filp, loff_t pos)
++static int ext4_htree_init_dir_info(struct file *filp, loff_t pos)
+ {
+       struct dir_private_info *p = filp->private_data;
+-
+-      if (is_dx_dir(file_inode(filp)) && !p->initialized) {
+-              p->curr_hash = pos2maj_hash(filp, pos);
+-              p->curr_minor_hash = pos2min_hash(filp, pos);
+-              p->initialized = true;
++      int err = 0;
++
++      if (is_dx_dir(file_inode(filp))) {
++              if (!p && filp->f_op && filp->f_op->open) {
++                      err = filp->f_op->open(file_inode(filp), filp);
++                      if (err)
++                              goto out;
++                      p = filp->private_data;
++              }
++              if (p && !p->initialized) {
++                      p->curr_hash = pos2maj_hash(filp, pos);
++                      p->curr_minor_hash = pos2min_hash(filp, pos);
++                      p->initialized = true;
++              }
+       }
++out:
++      return err;
+ }
+ void ext4_htree_free_dir_info(struct dir_private_info *p)
+@@ -559,12 +576,16 @@ static int call_filldir(struct file *file, struct dir_context *ctx,
+ static int ext4_dx_readdir(struct file *file, struct dir_context *ctx)
+ {
+-      struct dir_private_info *info = file->private_data;
+-      struct inode *inode = file_inode(file);
++      struct dir_private_info *info;
++      struct inode *inode;
+       struct fname *fname;
+-      int ret = 0;
++      int ret;
+-      ext4_htree_init_dir_info(file, ctx->pos);
++      ret = ext4_htree_init_dir_info(file, ctx->pos);
++      if (ret)
++              return ret;
++      info = file->private_data;
++      inode = file_inode(file);
+       if (ctx->pos == ext4_get_htree_eof(file))
+               return 0;       /* EOF */
+diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
+index 113754f0..3b1671dd 100644
+--- a/fs/ext4/ialloc.c
++++ b/fs/ext4/ialloc.c
+@@ -30,6 +30,7 @@
+ #include "ext4_jbd2.h"
+ #include "xattr.h"
+ #include "acl.h"
++#include "critical_encode.h"
+ #include <trace/events/ext4.h>
+diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
+index fdc815ca..f2c0af5e 100644
+--- a/fs/ext4/namei.c
++++ b/fs/ext4/namei.c
+@@ -41,6 +41,7 @@
+ #include "xattr.h"
+ #include "acl.h"
++#include "critical_encode.h"
+ #include <trace/events/ext4.h>
+ /*
+@@ -1442,7 +1443,7 @@ static int htree_dirblock_to_tree(struct file *dir_file,
+                                          ext4_dir_rec_len(0,
+                                                          csum ? NULL : dir));
+       /* Check if the directory is encrypted */
+-      if (IS_ENCRYPTED(dir)) {
++      if (0 && IS_ENCRYPTED(dir)) {
+               err = fscrypt_prepare_readdir(dir);
+               if (err < 0) {
+                       brelse(bh);
+@@ -1493,22 +1494,31 @@ static int htree_dirblock_to_tree(struct file *dir_file,
+                                  hinfo->hash, hinfo->minor_hash, de,
+                                  &tmp_str);
+               } else {
+-                      int save_len = fname_crypto_str.len;
+                       struct fscrypt_str de_name = FSTR_INIT(de->name,
+                                                               de->name_len);
++                      int presented_len;
+                       /* Directory is encrypted */
+-                      err = fscrypt_fname_disk_to_usr(dir, hinfo->hash,
++                      presented_len = critical_chars(de->name, de->name_len);
++                      err = ext4_fname_alloc_buffer(dir, presented_len,
++                                                       &fname_crypto_str);
++                      if (err) {
++                              count = err;
++                              goto errout;
++                      }
++
++                      err = ext4_fname_disk_to_usr(dir, hinfo->hash,
+                                       hinfo->minor_hash, &de_name,
+                                       &fname_crypto_str);
+                       if (err) {
++                              ext4_fname_free_buffer(&fname_crypto_str);
+                               count = err;
+                               goto errout;
+                       }
+                       err = ext4_htree_store_dirent(dir_file,
+                                  hinfo->hash, hinfo->minor_hash, de,
+                                       &fname_crypto_str);
+-                      fname_crypto_str.len = save_len;
++                      ext4_fname_free_buffer(&fname_crypto_str);
+               }
+               if (err != 0) {
+                       count = err;
+@@ -1787,7 +1797,7 @@ int ext4_fname_setup_ci_filename(struct inode *dir, const struct qstr *iname,
+  */
+ static bool ext4_match(struct inode *parent,
+                             const struct ext4_filename *fname,
+-                            struct ext4_dir_entry_2 *de)
++                            struct ext4_dir_entry_2 *de, int denamelen)
+ {
+       struct fscrypt_name f;
+@@ -1829,7 +1839,7 @@ static bool ext4_match(struct inode *parent,
+       }
+ #endif
+-      return fscrypt_match_name(&f, de->name, de->name_len);
++      return fscrypt_match_name(&f, de->name, denamelen);
+ }
+ /*
+@@ -1840,16 +1850,30 @@ int ext4_search_dir(struct buffer_head *bh, char *search_buf, int buf_size,
+                   unsigned int offset, struct ext4_dir_entry_2 **res_dir)
+ {
+       struct ext4_dir_entry_2 * de;
++      bool probablytrunc;
+       char * dlimit;
+-      int de_len;
++      int de_len, denamelen;
+       de = (struct ext4_dir_entry_2 *)search_buf;
+       dlimit = search_buf + buf_size;
++      /* fname is probably truncated if it is the decoded representation of
++       * an encrypted filename not aligned on a 32-byte boundary
++       */
++      probablytrunc = !IS_LUSTRE_MOUNT(dir->i_sb) && IS_ENCRYPTED(dir) &&
++              fname->disk_name.len & 31;
+       while ((char *) de < dlimit - EXT4_BASE_DIR_LEN) {
+               /* this code is executed quadratically often */
+               /* do minimal checking `by hand' */
++              denamelen = de->name_len;
++              if (unlikely(probablytrunc) &&
++                  de->name_len > fname->disk_name.len)
++                      /* Adjust name len to look for a partial match.
++                       * Since it is binary encrypted names, there
++                       * should not be any collision between names.
++                       */
++                      denamelen = fname->disk_name.len;
+               if (de->name + de->name_len <= dlimit &&
+-                  ext4_match(dir, fname, de)) {
++                  ext4_match(dir, fname, de, denamelen)) {
+                       /* found a match - just to be sure, do
+                        * a full check */
+                       if (ext4_check_dir_entry(dir, NULL, de, bh, search_buf,
+@@ -2052,7 +2076,7 @@ struct buffer_head *ext4_find_entry_locked(struct inode *dir,
+       struct ext4_filename fname;
+       struct buffer_head *bh;
+-      err = ext4_fname_setup_filename(dir, d_name, 1, &fname);
++      err = ext4_setup_filename(dir, d_name, 1, &fname);
+       if (err == -ENOENT)
+               return NULL;
+       if (err)
+@@ -2060,7 +2084,9 @@ struct buffer_head *ext4_find_entry_locked(struct inode *dir,
+       bh = __ext4_find_entry(dir, &fname, res_dir, inlined, lck);
+-      ext4_fname_free_filename(&fname);
++      if (fname.disk_name.name != d_name->name)
++              kfree(fname.disk_name.name);
++
+       return bh;
+ }
+@@ -2074,7 +2100,7 @@ static struct buffer_head *ext4_lookup_entry(struct inode *dir,
+       struct ext4_filename fname;
+       struct buffer_head *bh;
+-      err = ext4_fname_prepare_lookup(dir, dentry, &fname);
++      err = ext4_setup_filename(dir, &dentry->d_name, 1, &fname);
+       if (err == -ENOENT)
+               return NULL;
+       if (err)
+@@ -2082,7 +2108,9 @@ static struct buffer_head *ext4_lookup_entry(struct inode *dir,
+       bh = __ext4_find_entry(dir, &fname, res_dir, NULL, NULL);
+-      ext4_fname_free_filename(&fname);
++      if (fname.disk_name.name != dentry->d_name.name)
++              kfree(fname.disk_name.name);
++
+       return bh;
+ }
+@@ -2174,7 +2202,7 @@ static struct dentry *ext4_lookup(struct inode *dir, struct dentry *dentry, unsi
+               }
+               if (!IS_ERR(inode) && IS_ENCRYPTED(dir) &&
+                   (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode)) &&
+-                  !fscrypt_has_permitted_context(dir, inode)) {
++                  !ext4_has_permitted_context(dir, inode)) {
+                       ext4_warning(inode->i_sb,
+                                    "Inconsistent encryption contexts: %lu/%lu",
+                                    dir->i_ino, inode->i_ino);
+@@ -2466,7 +2494,7 @@ int ext4_find_dest_de(struct inode *dir, struct inode *inode,
+               if (ext4_check_dir_entry(dir, NULL, de, bh,
+                                        buf, buf_size, offset))
+                       return -EFSCORRUPTED;
+-              if (ext4_match(dir, fname, de))
++              if (ext4_match(dir, fname, de, de->name_len))
+                       return -EEXIST;
+               nlen = EXT4_DIR_ENTRY_LEN(de, dir);
+               rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
+-- 
+2.34.1
+
diff --git a/ldiskfs/kernel_patches/patches/linux-6.12/ext4-prealloc.patch b/ldiskfs/kernel_patches/patches/linux-6.12/ext4-prealloc.patch
new file mode 100644 (file)
index 0000000..fafaf38
--- /dev/null
@@ -0,0 +1,411 @@
+commit d8d8fd9192a54c7b8caef8cca9b7a1eb5e5e3298
+Author: Alex Zhuravlev <alex.zhuravlev@sun.com>
+AuthorDate: Thu Oct 23 10:02:19 2008 +0000
+Subject: ext4: support for tunable preallocation window
+
+Add support for tunable preallocation window and new tunables
+for large/small requests.
+Bugzilla-ID: b=12800
+
+Signed-off-by: Alex Zhuravlev <alex.zhuravlev@sun.com>
+Reviewed-by: Kalpak Shah <kalpak@clusterfs.com>
+Reviewed-by: Andreas Dilger <andreas.dilger@sun.com>
+---
+ fs/ext4/ext4.h    |   7 +-
+ fs/ext4/inode.c   |   3 +
+ fs/ext4/mballoc.c | 220 +++++++++++++++++++++++++++++++++++-----------
+ fs/ext4/sysfs.c   |   8 +-
+ 4 files changed, 182 insertions(+), 56 deletions(-)
+
+diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
+index 7b9b0aa2..f8cc644c 100644
+--- a/fs/ext4/ext4.h
++++ b/fs/ext4/ext4.h
+@@ -1287,6 +1287,8 @@ extern void mb_set_bits(void *bm, int cur, int len);
+ #define EXT4_DFL_MAX_MNT_COUNT                20      /* Allow 20 mounts */
+ #define EXT4_DFL_CHECKINTERVAL                0       /* Don't use interval check */
++#define EXT4_MAX_PREALLOC_TABLE       64
++
+ /*
+  * Behaviour when detecting errors
+  */
+@@ -1595,11 +1597,13 @@ struct ext4_sb_info {
+       /* tunables */
+       unsigned long s_stripe;
+       unsigned int s_mb_max_linear_groups;
+-      unsigned int s_mb_stream_request;
++      unsigned long s_mb_small_req;
++      unsigned long s_mb_large_req;
+       unsigned int s_mb_max_to_scan;
+       unsigned int s_mb_min_to_scan;
+       unsigned int s_mb_stats;
+       unsigned int s_mb_order2_reqs;
++      unsigned long *s_mb_prealloc_table;
+       unsigned int s_mb_group_prealloc;
+       unsigned int s_max_dir_size_kb;
+       /* where last allocation was done - for stream allocation */
+@@ -2927,6 +2931,7 @@ int ext4_fc_record_regions(struct super_block *sb, int ino,
+                          int len, int replay);
+ /* mballoc.c */
++extern const struct proc_ops ext4_seq_prealloc_table_fops;
+ extern const struct seq_operations ext4_mb_seq_groups_ops;
+ extern const struct seq_operations ext4_mb_seq_structs_summary_ops;
+ extern int ext4_seq_mb_stats_show(struct seq_file *seq, void *offset);
+diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
+index 6232dac5..ddd90018 100644
+--- a/fs/ext4/inode.c
++++ b/fs/ext4/inode.c
+@@ -2640,6 +2640,9 @@ static int ext4_do_writepages(struct mpage_da_data *mpd)
+                                               PAGE_SIZE >> inode->i_blkbits);
+       }
++      if (wbc->nr_to_write < sbi->s_mb_small_req)
++              wbc->nr_to_write = sbi->s_mb_small_req;
++
+       if (wbc->range_start == 0 && wbc->range_end == LLONG_MAX)
+               range_whole = 1;
+diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
+index d73e3832..01b5cc04 100644
+--- a/fs/ext4/mballoc.c
++++ b/fs/ext4/mballoc.c
+@@ -3275,6 +3275,99 @@ const struct seq_operations ext4_mb_seq_structs_summary_ops = {
+       .show   = ext4_mb_seq_structs_summary_show,
+ };
++static int ext4_mb_check_and_update_prealloc(struct ext4_sb_info *sbi,
++                                               char *str, size_t cnt,
++                                               int update)
++{
++      unsigned long value;
++      unsigned long prev = 0;
++      char *cur;
++      char *next;
++      char *end;
++      int num = 0;
++
++      cur = str;
++      end = str + cnt;
++      while (cur < end) {
++              while ((cur < end) && (*cur == ' ')) cur++;
++              value = simple_strtol(cur, &next, 0);
++              if (value == 0)
++                      break;
++              if (cur == next)
++                      return -EINVAL;
++
++              cur = next;
++
++              if (value > (sbi->s_blocks_per_group - 1 - 1 - sbi->s_itb_per_group))
++                      return -EINVAL;
++
++              /* they should add values in order */
++              if (value <= prev)
++                      return -EINVAL;
++
++              if (update)
++                      sbi->s_mb_prealloc_table[num] = value;
++
++              prev = value;
++              num++;
++      }
++
++      if (num > EXT4_MAX_PREALLOC_TABLE - 1)
++              return -EOVERFLOW;
++
++      if (update)
++              sbi->s_mb_prealloc_table[num] = 0;
++
++      return 0;
++}
++
++static ssize_t ext4_mb_prealloc_table_proc_write(struct file *file,
++                                           const char __user *buf,
++                                           size_t cnt, loff_t *pos)
++{
++      struct ext4_sb_info *sbi = EXT4_SB(pde_data(file_inode(file)));
++      char str[128];
++      int rc;
++
++      if (cnt >= sizeof(str))
++              return -EINVAL;
++      if (copy_from_user(str, buf, cnt))
++              return -EFAULT;
++
++      rc = ext4_mb_check_and_update_prealloc(sbi, str, cnt, 0);
++      if (rc)
++              return rc;
++
++      rc = ext4_mb_check_and_update_prealloc(sbi, str, cnt, 1);
++      return rc ? rc : cnt;
++}
++
++static int mb_prealloc_table_seq_show(struct seq_file *m, void *v)
++{
++      struct ext4_sb_info *sbi = EXT4_SB(m->private);
++      int i;
++
++      for (i = 0; i < EXT4_MAX_PREALLOC_TABLE &&
++                      sbi->s_mb_prealloc_table[i] != 0; i++)
++              seq_printf(m, "%ld ", sbi->s_mb_prealloc_table[i]);
++      seq_printf(m, "\n");
++
++      return 0;
++}
++
++static int mb_prealloc_table_seq_open(struct inode *inode, struct file *file)
++{
++      return single_open(file, mb_prealloc_table_seq_show, pde_data(inode));
++}
++
++const struct proc_ops ext4_seq_prealloc_table_fops = {
++      .proc_open      = mb_prealloc_table_seq_open,
++      .proc_read      = seq_read,
++      .proc_lseek     = seq_lseek,
++      .proc_release   = single_release,
++      .proc_write     = ext4_mb_prealloc_table_proc_write,
++};
++
+ static struct kmem_cache *get_groupinfo_cache(int blocksize_bits)
+ {
+       int cache_index = blocksize_bits - EXT4_MIN_BLOCK_LOG_SIZE;
+@@ -3591,7 +3684,7 @@ static void ext4_discard_work(struct work_struct *work)
+ int ext4_mb_init(struct super_block *sb)
+ {
+       struct ext4_sb_info *sbi = EXT4_SB(sb);
+-      unsigned i, j;
++      unsigned i, j, k, l;
+       unsigned offset, offset_incr;
+       unsigned max;
+       int ret;
+@@ -3680,7 +3773,6 @@ int ext4_mb_init(struct super_block *sb)
+       sbi->s_mb_max_to_scan = MB_DEFAULT_MAX_TO_SCAN;
+       sbi->s_mb_min_to_scan = MB_DEFAULT_MIN_TO_SCAN;
+       sbi->s_mb_stats = MB_DEFAULT_STATS;
+-      sbi->s_mb_stream_request = MB_DEFAULT_STREAM_THRESHOLD;
+       sbi->s_mb_order2_reqs = MB_DEFAULT_ORDER2_REQS;
+       sbi->s_mb_best_avail_max_trim_order = MB_DEFAULT_BEST_AVAIL_TRIM_ORDER;
+@@ -3706,9 +3798,29 @@ int ext4_mb_init(struct super_block *sb)
+        * RAID stripe size so that preallocations don't fragment
+        * the stripes.
+        */
+-      if (sbi->s_stripe > 1) {
+-              sbi->s_mb_group_prealloc = roundup(
+-                      sbi->s_mb_group_prealloc, EXT4_NUM_B2C(sbi, sbi->s_stripe));
++
++      /* Allocate table once */
++      sbi->s_mb_prealloc_table = kzalloc(
++              EXT4_MAX_PREALLOC_TABLE * sizeof(unsigned long), GFP_NOFS);
++      if (sbi->s_mb_prealloc_table == NULL) {
++              ret = -ENOMEM;
++              goto out;
++      }
++
++      if (sbi->s_stripe == 0) {
++              for (k = 0, l = 4; k <= 9; ++k, l *= 2)
++                      sbi->s_mb_prealloc_table[k] = l;
++
++              sbi->s_mb_small_req = 256;
++              sbi->s_mb_large_req = 1024;
++              sbi->s_mb_group_prealloc = 512;
++      } else {
++              for (k = 0, l = sbi->s_stripe; k <= 2; ++k, l *= 2)
++                      sbi->s_mb_prealloc_table[k] = l;
++
++              sbi->s_mb_small_req = sbi->s_stripe;
++              sbi->s_mb_large_req = sbi->s_stripe * 8;
++              sbi->s_mb_group_prealloc = sbi->s_stripe * 4;
+       }
+       sbi->s_locality_groups = alloc_percpu(struct ext4_locality_group);
+@@ -3744,6 +3856,7 @@ out:
+       kfree(sbi->s_mb_avg_fragment_size_locks);
+       kfree(sbi->s_mb_largest_free_orders);
+       kfree(sbi->s_mb_largest_free_orders_locks);
++      kfree(sbi->s_mb_prealloc_table);
+       kfree(sbi->s_mb_offsets);
+       sbi->s_mb_offsets = NULL;
+       kfree(sbi->s_mb_maxs);
+@@ -4097,7 +4210,6 @@ ext4_mb_mark_diskspace_used(struct ext4_allocation_context *ac,
+       ext4_grpblk_t changed;
+       BUG_ON(ac->ac_status != AC_STATUS_FOUND);
+-      BUG_ON(ac->ac_b_ex.fe_len <= 0);
+       sb = ac->ac_sb;
+       sbi = EXT4_SB(sb);
+@@ -4421,10 +4533,11 @@ ext4_mb_normalize_request(struct ext4_allocation_context *ac,
+ {
+       struct ext4_sb_info *sbi = EXT4_SB(ac->ac_sb);
+       struct ext4_super_block *es = sbi->s_es;
+-      int bsbits, max;
+-      loff_t size, start_off, end;
++      int bsbits, i, wind;
++      loff_t size, end;
+       loff_t orig_size __maybe_unused;
+       ext4_lblk_t start;
++      unsigned long value, last_non_zero;
+       /* do normalize only data requests, metadata requests
+          do not need preallocation */
+@@ -4453,51 +4566,46 @@ ext4_mb_normalize_request(struct ext4_allocation_context *ac,
+       size = size << bsbits;
+       if (size < i_size_read(ac->ac_inode))
+               size = i_size_read(ac->ac_inode);
+-      orig_size = size;
++      size = (size + ac->ac_sb->s_blocksize - 1) >> bsbits;
++
++      start = wind = 0;
++      value = last_non_zero = 0;
+-      /* max size of free chunks */
+-      max = 2 << bsbits;
+-
+-#define NRL_CHECK_SIZE(req, size, max, chunk_size)    \
+-              (req <= (size) || max <= (chunk_size))
+-
+-      /* first, try to predict filesize */
+-      /* XXX: should this table be tunable? */
+-      start_off = 0;
+-      if (size <= 16 * 1024) {
+-              size = 16 * 1024;
+-      } else if (size <= 32 * 1024) {
+-              size = 32 * 1024;
+-      } else if (size <= 64 * 1024) {
+-              size = 64 * 1024;
+-      } else if (size <= 128 * 1024) {
+-              size = 128 * 1024;
+-      } else if (size <= 256 * 1024) {
+-              size = 256 * 1024;
+-      } else if (size <= 512 * 1024) {
+-              size = 512 * 1024;
+-      } else if (size <= 1024 * 1024) {
+-              size = 1024 * 1024;
+-      } else if (NRL_CHECK_SIZE(size, 4 * 1024 * 1024, max, 2 * 1024)) {
+-              start_off = ((loff_t)ac->ac_o_ex.fe_logical >>
+-                                              (21 - bsbits)) << 21;
+-              size = 2 * 1024 * 1024;
+-      } else if (NRL_CHECK_SIZE(size, 8 * 1024 * 1024, max, 4 * 1024)) {
+-              start_off = ((loff_t)ac->ac_o_ex.fe_logical >>
+-                                                      (22 - bsbits)) << 22;
+-              size = 4 * 1024 * 1024;
+-      } else if (NRL_CHECK_SIZE(EXT4_C2B(sbi, ac->ac_o_ex.fe_len),
+-                                      (8<<20)>>bsbits, max, 8 * 1024)) {
+-              start_off = ((loff_t)ac->ac_o_ex.fe_logical >>
+-                                                      (23 - bsbits)) << 23;
+-              size = 8 * 1024 * 1024;
++      /* let's choose preallocation window depending on file size */
++      for (i = 0; i < EXT4_MAX_PREALLOC_TABLE; i++) {
++              value = sbi->s_mb_prealloc_table[i];
++              if (value == 0)
++                      break;
++              else
++                      last_non_zero = value;
++
++              if (size <= value) {
++                      wind = value;
++                      break;
++              }
++      }
++
++      if (wind == 0) {
++              if (last_non_zero != 0) {
++                      __u64 tstart, tend;
++                      /* file is quite large, we now preallocate with
++                      * the biggest configured window with regart to
++                      * logical offset */
++                      wind = last_non_zero;
++                      tstart = ac->ac_o_ex.fe_logical;
++                      do_div(tstart, wind);
++                      start = tstart * wind;
++                      tend = ac->ac_o_ex.fe_logical + ac->ac_o_ex.fe_len - 1;
++                      do_div(tend, wind);
++                      tend = tend * wind + wind;
++                      size = tend - start;
++              }
+       } else {
+-              start_off = (loff_t) ac->ac_o_ex.fe_logical << bsbits;
+-              size      = (loff_t) EXT4_C2B(sbi,
+-                                            ac->ac_o_ex.fe_len) << bsbits;
++              size = wind;
+       }
+-      size = size >> bsbits;
+-      start = start_off >> bsbits;
++
++
++      orig_size = size;
+       /*
+        * For tiny groups (smaller than 8MB) the chosen allocation
+@@ -4556,7 +4664,6 @@ ext4_mb_normalize_request(struct ext4_allocation_context *ac,
+                        (unsigned long) ac->ac_o_ex.fe_logical);
+               BUG();
+       }
+-      BUG_ON(size <= 0 || size > EXT4_BLOCKS_PER_GROUP(ac->ac_sb));
+       /* now prepare goal request */
+@@ -5759,8 +5866,8 @@ static void ext4_mb_group_or_file(struct ext4_allocation_context *ac)
+               inode_pa_eligible = false;
+       size = max(size, isize);
+-      /* Don't use group allocation for large files */
+-      if (size > sbi->s_mb_stream_request)
++      if ((ac->ac_o_ex.fe_len >= sbi->s_mb_small_req) ||
++          (size >= sbi->s_mb_large_req))
+               group_pa_eligible = false;
+       if (!group_pa_eligible) {
+@@ -5771,6 +5878,13 @@ static void ext4_mb_group_or_file(struct ext4_allocation_context *ac)
+               return;
+       }
++      /*
++       * request is so large that we don't care about
++       * streaming - it overweights any possible seek
++       */
++      if (ac->ac_o_ex.fe_len >= sbi->s_mb_large_req)
++              return;
++
+       BUG_ON(ac->ac_lg != NULL);
+       /*
+        * locality group prealloc space are per cpu. The reason for having
+diff --git a/fs/ext4/sysfs.c b/fs/ext4/sysfs.c
+index ddb54608..2aeff069 100644
+--- a/fs/ext4/sysfs.c
++++ b/fs/ext4/sysfs.c
+@@ -222,7 +222,8 @@ EXT4_RW_ATTR_SBI_UI(mb_stats, s_mb_stats);
+ EXT4_RW_ATTR_SBI_UI(mb_max_to_scan, s_mb_max_to_scan);
+ EXT4_RW_ATTR_SBI_UI(mb_min_to_scan, s_mb_min_to_scan);
+ EXT4_RW_ATTR_SBI_UI(mb_order2_req, s_mb_order2_reqs);
+-EXT4_RW_ATTR_SBI_UI(mb_stream_req, s_mb_stream_request);
++EXT4_RW_ATTR_SBI_UI(mb_small_req, s_mb_small_req);
++EXT4_RW_ATTR_SBI_UI(mb_large_req, s_mb_large_req);
+ EXT4_RW_ATTR_SBI_UI(mb_max_linear_groups, s_mb_max_linear_groups);
+ EXT4_RW_ATTR_SBI_UI(extent_max_zeroout_kb, s_extent_max_zeroout_kb);
+ EXT4_ATTR(trigger_fs_error, 0200, trigger_test_error);
+@@ -270,7 +271,8 @@ static struct attribute *ext4_attrs[] = {
+       ATTR_LIST(mb_max_to_scan),
+       ATTR_LIST(mb_min_to_scan),
+       ATTR_LIST(mb_order2_req),
+-      ATTR_LIST(mb_stream_req),
++      ATTR_LIST(mb_small_req),
++      ATTR_LIST(mb_large_req),
+       ATTR_LIST(mb_group_prealloc),
+       ATTR_LIST(mb_max_linear_groups),
+       ATTR_LIST(max_writeback_mb_bump),
+@@ -584,6 +586,8 @@ int ext4_register_sysfs(struct super_block *sb)
+                                       ext4_fc_info_show, sb);
+               proc_create_seq_data("mb_groups", S_IRUGO, sbi->s_proc,
+                               &ext4_mb_seq_groups_ops, sb);
++              proc_create_data("prealloc_table", S_IRUGO, sbi->s_proc,
++                              &ext4_seq_prealloc_table_fops, sb);
+               proc_create_single_data("mb_stats", 0444, sbi->s_proc,
+                               ext4_seq_mb_stats_show, sb);
+               proc_create_seq_data("mb_structs_summary", 0444, sbi->s_proc,
+-- 
+2.34.1
+
diff --git a/ldiskfs/kernel_patches/series/ldiskfs-6.12-ml.series b/ldiskfs/kernel_patches/series/ldiskfs-6.12-ml.series
new file mode 100644 (file)
index 0000000..d310505
--- /dev/null
@@ -0,0 +1,38 @@
+linux-5.16/ext4-inode-version.patch
+linux-6.11/ext4-lookup-dotdot.patch
+linux-5.14/ext4-print-inum-in-htree-warning.patch
+linux-6.12/ext4-prealloc.patch
+linux-5.16/ext4-osd-iop-common.patch
+linux-6.10/ext4-misc.patch
+linux-6.10/ext4-mballoc-extra-checks.patch
+sles15sp4/ext4-hash-indexed-dir-dotdot-update.patch
+linux-6.11/ext4-kill-dx-root.patch
+linux-6.5/ext4-mballoc-pa-free-mismatch.patch
+linux-6.12/ext4-data-in-dirent.patch
+linux-6.6/ext4-nocmtime.patch
+base/ext4-htree-lock.patch
+linux-6.5/ext4-pdirop.patch
+linux-6.10/ext4-max-dir-size.patch
+linux-6.10/ext4-corrupted-inode-block-bitmaps-handling-patches.patch
+rhel9/ext4-give-warning-with-dir-htree-growing.patch
+ubuntu18/ext4-jcb-optimization.patch
+linux-6.2/ext4-attach-jinode-in-writepages.patch
+linux-6.5/ext4-dont-check-before-replay.patch
+base/ext4-fix-ext4_inode_attach_jinode.patch
+rhel7.6/ext4-export-orphan-add.patch
+linux-5.18/ext4-export-mb-stream-allocator-variables.patch
+ubuntu19/ext4-iget-with-flags.patch
+linux-5.14/export-ext4fs-dirhash-helper.patch
+linux-5.8/ext4-no-max-dir-size-limit-for-iam-objects.patch
+rhel9/ext4-dquot-commit-speedup.patch
+linux-6.7/ext4-ialloc-uid-gid-and-pass-owner-down.patch
+linux-5.14/ext4-projid-xattrs.patch
+linux-6.10/ext4-delayed-iput.patch
+linux-6.12/ext4-ext-merge.patch
+linux-5.14/ext4-xattr-disable-credits-check.patch
+rhel9.2/ext4-fiemap-kernel-data.patch
+rhel8/ext4-old_ea_inodes_handling_fix.patch
+linux-6.12/ext4-filename-encode.patch
+rhel9.1/ext4-enc-flag.patch
+linux-6.6/ext4-encdata.patch
+rhel9.4/ext4-add-IGET_NO_CHECKS-flag.patch
index ca163dc..933181d 100644 (file)
@@ -6927,6 +6927,7 @@ struct osd_it_ea *osd_it_dir_init(const struct lu_env *env,
        struct osd_it_ea *oie;
        struct file *file;
        struct dentry *obj_dentry;
+       int rc = -ENOMEM;
 
        ENTRY;
        OBD_SLAB_ALLOC_PTR_GFP(oie, osd_itea_cachep, GFP_NOFS);
@@ -6946,7 +6947,7 @@ struct osd_it_ea *osd_it_dir_init(const struct lu_env *env,
        } else {
                OBD_ALLOC(oie->oie_buf, OSD_IT_EA_BUFSIZE);
                if (!oie->oie_buf)
-                       goto out_free;
+                       GOTO(out_free, rc);
        }
        oie->oie_obj = NULL;
        file = &oie->oie_file;
@@ -6961,12 +6962,21 @@ struct osd_it_ea *osd_it_dir_init(const struct lu_env *env,
        file->f_op = inode->i_fop;
        file->f_inode = inode;
 
+       /* Linux v6.11-rc4-18-g4f05ee2f82b4: if open handler is defined
+        * and private_data is not yet provided the file open needs
+        * to fill private_data before iterate_shared() is called.
+        */
+       if (file->f_op->open && !file->private_data) {
+               rc = file->f_op->open(inode, file);
+               if (rc)
+                       GOTO(out_free, rc);
+       }
        RETURN(oie);
 
 out_free:
        OBD_SLAB_FREE_PTR(oie, osd_itea_cachep);
 
-       return ERR_PTR(-ENOMEM);
+       return ERR_PTR(rc);
 }
 
 /**