From 9fe3951c4439c17f946212ad5f2f37ed83f0097d Mon Sep 17 00:00:00 2001 From: Shaun Tancheff Date: Fri, 11 Apr 2025 08:40:54 +0700 Subject: [PATCH] LU-16350 ldiskfs: update for kernel 6.12 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 Change-Id: Ie64bb701f45a024dce4caefe0e3ee673e6cfe25a Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/57126 Tested-by: jenkins Tested-by: Maloo Reviewed-by: Jian Yu Reviewed-by: Timothy Day Reviewed-by: Oleg Drokin --- config/lustre-build-ldiskfs.m4 | 6 +- .../patches/linux-6.12/ext4-data-in-dirent.patch | 853 +++++++++++++++++++++ .../patches/linux-6.12/ext4-ext-merge.patch | 251 ++++++ .../patches/linux-6.12/ext4-filename-encode.patch | 545 +++++++++++++ .../patches/linux-6.12/ext4-prealloc.patch | 411 ++++++++++ .../kernel_patches/series/ldiskfs-6.12-ml.series | 38 + lustre/osd-ldiskfs/osd_handler.c | 14 +- 7 files changed, 2115 insertions(+), 3 deletions(-) create mode 100644 ldiskfs/kernel_patches/patches/linux-6.12/ext4-data-in-dirent.patch create mode 100644 ldiskfs/kernel_patches/patches/linux-6.12/ext4-ext-merge.patch create mode 100644 ldiskfs/kernel_patches/patches/linux-6.12/ext4-filename-encode.patch create mode 100644 ldiskfs/kernel_patches/patches/linux-6.12/ext4-prealloc.patch create mode 100644 ldiskfs/kernel_patches/series/ldiskfs-6.12-ml.series diff --git a/config/lustre-build-ldiskfs.m4 b/config/lustre-build-ldiskfs.m4 index 855ba48..d53c6d3 100644 --- a/config/lustre-build-ldiskfs.m4 +++ b/config/lustre-build-ldiskfs.m4 @@ -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 index 0000000..4b4720f --- /dev/null +++ b/ldiskfs/kernel_patches/patches/linux-6.12/ext4-data-in-dirent.patch @@ -0,0 +1,853 @@ +commit 2db3b2b33ee796f4ea61316773452d936303ad27 +Author: Pravin Shelar +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 +Reviewed-by: Huang Hua +Signed-off-by: Andreas Dilger +--- + 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 *)(¶m), 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 index 0000000..8c8bdbf --- /dev/null +++ b/ldiskfs/kernel_patches/patches/linux-6.12/ext4-ext-merge.patch @@ -0,0 +1,251 @@ +From 0f7e6c02a9ea5b85d8d97f724bed318268cea60f Mon Sep 17 00:00:00 2001 +From: Alex Zhuravlev +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 +Change-Id: I746c0917e746eb442d3c69a23f591d9cdade76fa +Reviewed-on: https://review.whamcloud.com/51096 +Reviewed-by: Artem Blagodarenko +Reviewed-by: Andreas Dilger +Reviewed-by: Oleg Drokin +--- + 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 index 0000000..0b0a75f --- /dev/null +++ b/ldiskfs/kernel_patches/patches/linux-6.12/ext4-filename-encode.patch @@ -0,0 +1,545 @@ +From d0a722cb8fb886380e24e8261e8efca09a3262d6 Mon Sep 17 00:00:00 2001 +From: Sebastien Buisson +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 +Change-Id: Ia318c39d403b1c448e71bcd5b29862d022d05d0a +Reviewed-on: https://review.whamcloud.com/c/fs/lustre-release/+/49456 +Reviewed-by: Andreas Dilger +Reviewed-by: Li Dongyang +Reviewed-by: Oleg Drokin +--- + 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 ++ ++/* 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 + #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 + +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 + /* +@@ -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 index 0000000..fafaf38 --- /dev/null +++ b/ldiskfs/kernel_patches/patches/linux-6.12/ext4-prealloc.patch @@ -0,0 +1,411 @@ +commit d8d8fd9192a54c7b8caef8cca9b7a1eb5e5e3298 +Author: Alex Zhuravlev +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 +Reviewed-by: Kalpak Shah +Reviewed-by: Andreas Dilger +--- + 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 index 0000000..d310505 --- /dev/null +++ b/ldiskfs/kernel_patches/series/ldiskfs-6.12-ml.series @@ -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 diff --git a/lustre/osd-ldiskfs/osd_handler.c b/lustre/osd-ldiskfs/osd_handler.c index ca163dc..933181d 100644 --- a/lustre/osd-ldiskfs/osd_handler.c +++ b/lustre/osd-ldiskfs/osd_handler.c @@ -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); } /** -- 1.8.3.1