From b8b50699804fdceb89e72fb02a6207a6ad56b2da Mon Sep 17 00:00:00 2001 From: OBS User hlohr Date: Wed, 8 Apr 2020 11:34:04 +0000 Subject: [PATCH] Set link to e2fsprogs.14587 via maintenance_release request Rev SUSE:SLE-15:Update/6 Md5 d8f0e1e9351003e8fa23e76a764813bf 2020-04-08 11:34:04 hlohr None --- ...fy-overflow-link-count-error-message.patch | 96 +++ e2fsprogs.changes | 14 + e2fsprogs.spec | 10 + ...-dir-entry-creation-in-htree-directo.patch | 616 ++++++++++++++++++ ...location-info-earlier-in-ext2fs_mkdi.patch | 128 ++++ ...o-excercise-indexed-directories-with.patch | 158 +++++ ...ir-checksums-when-clearing-dir_index.patch | 391 +++++++++++ 7 files changed, 1413 insertions(+) create mode 100644 e2fsck-clarify-overflow-link-count-error-message.patch create mode 100644 ext2fs-implement-dir-entry-creation-in-htree-directo.patch create mode 100644 ext2fs-update-allocation-info-earlier-in-ext2fs_mkdi.patch create mode 100644 tests-add-test-to-excercise-indexed-directories-with.patch create mode 100644 tune2fs-update-dir-checksums-when-clearing-dir_index.patch diff --git a/e2fsck-clarify-overflow-link-count-error-message.patch b/e2fsck-clarify-overflow-link-count-error-message.patch new file mode 100644 index 0000000..c95fcb2 --- /dev/null +++ b/e2fsck-clarify-overflow-link-count-error-message.patch @@ -0,0 +1,96 @@ +From 125850eb92f042c76b6f001bf63833ffc15e7916 Mon Sep 17 00:00:00 2001 +From: Jan Kara +Date: Thu, 13 Feb 2020 11:15:56 +0100 +Subject: [PATCH] e2fsck: clarify overflow link count error message +References: bsc#1160979 + +When directory link count is set to overflow value (1) but during pass 4 +we find out the exact link count would fit, we either silently fix this +(which is not great because e2fsck then reports the fs was modified but +output doesn't indicate why in any way), or we report that link count is +wrong and ask whether we should fix it (in case -n option was +specified). The second case is even more misleading because it suggests +non-trivial fs corruption which then gets silently fixed on the next +run. Similarly to how we fix up other non-problems, just create a new +error message for the case directory link count is not overflown anymore +and always report it to clarify what is going on. + +Reviewed-by: Andreas Dilger +Signed-off-by: Jan Kara +Signed-off-by: Theodore Ts'o +(cherry picked from commit 4ebce13292f54c96f43dcb1bd1d5b8df5dc8749d) +Acked-by: Jan Kara +--- + e2fsck/pass4.c | 20 ++++++++++++++++---- + e2fsck/problem.c | 5 +++++ + e2fsck/problem.h | 3 +++ + 3 files changed, 24 insertions(+), 4 deletions(-) + +Index: e2fsprogs-1.43.8/e2fsck/pass4.c +=================================================================== +--- e2fsprogs-1.43.8.orig/e2fsck/pass4.c ++++ e2fsprogs-1.43.8/e2fsck/pass4.c +@@ -169,6 +169,8 @@ void e2fsck_pass4(e2fsck_t ctx) + if (isdir && (link_counted > EXT2_LINK_MAX)) + link_counted = 1; + if (link_counted != link_count) { ++ int fix_nlink = 0; ++ + e2fsck_read_inode_full(ctx, i, EXT2_INODE(inode), + inode_size, "pass4"); + pctx.ino = i; +@@ -182,10 +184,20 @@ void e2fsck_pass4(e2fsck_t ctx) + pctx.num = link_counted; + /* i_link_count was previously exceeded, but no longer + * is, fix this but don't consider it an error */ +- if ((isdir && link_counted > 1 && +- (inode->i_flags & EXT2_INDEX_FL) && +- link_count == 1 && !(ctx->options & E2F_OPT_NO)) || +- fix_problem(ctx, PR_4_BAD_REF_COUNT, &pctx)) { ++ if (isdir && link_counted > 1 && ++ (inode->i_flags & EXT2_INDEX_FL) && ++ link_count == 1) { ++ if ((ctx->options & E2F_OPT_READONLY) == 0) { ++ fix_nlink = ++ fix_problem(ctx, ++ PR_4_DIR_OVERFLOW_REF_COUNT, ++ &pctx); ++ } ++ } else { ++ fix_nlink = fix_problem(ctx, PR_4_BAD_REF_COUNT, ++ &pctx); ++ } ++ if (fix_nlink) { + inode->i_links_count = link_counted; + e2fsck_write_inode_full(ctx, i, + EXT2_INODE(inode), +Index: e2fsprogs-1.43.8/e2fsck/problem.c +=================================================================== +--- e2fsprogs-1.43.8.orig/e2fsck/problem.c ++++ e2fsprogs-1.43.8/e2fsck/problem.c +@@ -1857,6 +1857,11 @@ static struct e2fsck_problem problem_tab + "They @s the same!\n"), + PROMPT_NONE, 0 }, + ++ /* Directory inode ref count set to overflow but could be exact value */ ++ { PR_4_DIR_OVERFLOW_REF_COUNT, ++ N_("@d @i %i ref count set to overflow but could be exact value %N. "), ++ PROMPT_FIX, PR_PREEN_OK, 0, 0, 0 }, ++ + /* Pass 5 errors */ + + /* Pass 5: Checking group summary information */ +Index: e2fsprogs-1.43.8/e2fsck/problem.h +=================================================================== +--- e2fsprogs-1.43.8.orig/e2fsck/problem.h ++++ e2fsprogs-1.43.8/e2fsck/problem.h +@@ -1123,6 +1123,9 @@ struct problem_context { + /* Inconsistent inode count information cached */ + #define PR_4_INCONSISTENT_COUNT 0x040004 + ++/* Directory ref count set to overflow but it doesn't have to be */ ++#define PR_4_DIR_OVERFLOW_REF_COUNT 0x040007 ++ + /* + * Pass 5 errors + */ diff --git a/e2fsprogs.changes b/e2fsprogs.changes index 6853176..cbaef56 100644 --- a/e2fsprogs.changes +++ b/e2fsprogs.changes @@ -1,3 +1,17 @@ +------------------------------------------------------------------- +Fri Mar 27 16:00:29 UTC 2020 - Jan Kara + +- e2fsck-clarify-overflow-link-count-error-message.patch: e2fsck: clarify + overflow link count error message (bsc#1160979) +- ext2fs-update-allocation-info-earlier-in-ext2fs_mkdi.patch: ext2fs: update + allocation info earlier in ext2fs_mkdir() (bsc#1160979) +- ext2fs-implement-dir-entry-creation-in-htree-directo.patch: ext2fs: implement + dir entry creation in htree directories (bsc#1160979) +- tests-add-test-to-excercise-indexed-directories-with.patch: tests: add test + to excercise indexed directories with metadata_csum (bsc#1160979) +- tune2fs-update-dir-checksums-when-clearing-dir_index.patch: tune2fs: update + dir checksums when clearing dir_index feature (bsc#1160979) + ------------------------------------------------------------------- Thu Jan 9 14:25:34 UTC 2020 - Jan Kara diff --git a/e2fsprogs.spec b/e2fsprogs.spec index 72e43bd..33a51ca 100644 --- a/e2fsprogs.spec +++ b/e2fsprogs.spec @@ -88,6 +88,11 @@ Patch9: libsupport-add-checks-to-prevent-buffer-overrun-bugs.patch Patch10: resize2fs-Make-minimum-size-estimates-more-reliable.patch Patch11: e2fsck-abort-if-there-is-a-corrupted-directory-block.patch Patch12: e2fsck-don-t-try-to-rehash-a-deleted-directory.patch +Patch13: e2fsck-clarify-overflow-link-count-error-message.patch +Patch14: ext2fs-update-allocation-info-earlier-in-ext2fs_mkdi.patch +Patch15: ext2fs-implement-dir-entry-creation-in-htree-directo.patch +Patch16: tests-add-test-to-excercise-indexed-directories-with.patch +Patch17: tune2fs-update-dir-checksums-when-clearing-dir_index.patch # Do not suppress make commands BuildRoot: %{_tmppath}/%{name}-%{version}-build @@ -239,6 +244,11 @@ Development files for the com_err error message display library. Static librarie %patch10 -p1 %patch11 -p1 %patch12 -p1 +%patch13 -p1 +%patch14 -p1 +%patch15 -p1 +%patch16 -p1 +%patch17 -p1 cp %{SOURCE2} . %build diff --git a/ext2fs-implement-dir-entry-creation-in-htree-directo.patch b/ext2fs-implement-dir-entry-creation-in-htree-directo.patch new file mode 100644 index 0000000..5b53c83 --- /dev/null +++ b/ext2fs-implement-dir-entry-creation-in-htree-directo.patch @@ -0,0 +1,616 @@ +From 9a4d2dcc8deaa1c28b3a713c2f610be503855946 Mon Sep 17 00:00:00 2001 +From: Jan Kara +Date: Thu, 13 Feb 2020 11:15:59 +0100 +Subject: [PATCH 2/5] ext2fs: implement dir entry creation in htree directories +References: bsc#1160979 + +Implement proper creation of new directory entries in htree directories +in ext2fs_link(). So far we just cleared EXT2_INDEX_FL and treated +directory as unindexed however this results in mismatched checksums if +metadata checksums are in use because checksums are placed in different +places depending on htree node type. + +Signed-off-by: Jan Kara +Signed-off-by: Theodore Ts'o +Acked-by: Jan Kara +--- + lib/ext2fs/link.c | 549 ++++++++++++++++++++++++++++++++++++++++++++++++------ + 1 file changed, 497 insertions(+), 52 deletions(-) + +Index: e2fsprogs-1.43.8/lib/ext2fs/link.c +=================================================================== +--- e2fsprogs-1.43.8.orig/lib/ext2fs/link.c ++++ e2fsprogs-1.43.8/lib/ext2fs/link.c +@@ -18,6 +18,153 @@ + + #include "ext2_fs.h" + #include "ext2fs.h" ++#include "ext2fsP.h" ++ ++#define EXT2_DX_ROOT_OFF 24 ++ ++struct dx_frame { ++ void *buf; ++ blk64_t pblock; ++ struct ext2_dx_countlimit *head; ++ struct ext2_dx_entry *entries; ++ struct ext2_dx_entry *at; ++}; ++ ++struct dx_lookup_info { ++ const char *name; ++ int namelen; ++ int hash_alg; ++ __u32 hash; ++ int levels; ++ struct dx_frame frames[2]; ++}; ++ ++static errcode_t alloc_dx_frame(ext2_filsys fs, struct dx_frame *frame) ++{ ++ return ext2fs_get_mem(fs->blocksize, &frame->buf); ++} ++ ++static void dx_release(struct dx_lookup_info *info) ++{ ++ struct ext2_dx_root_info *root; ++ int level; ++ ++ for (level = 0; level < info->levels; level++) { ++ if (info->frames[level].buf == NULL) ++ break; ++ ext2fs_free_mem(&(info->frames[level].buf)); ++ } ++ info->levels = 0; ++} ++ ++static void dx_search_entry(struct dx_frame *frame, int count, __u32 hash) ++{ ++ struct ext2_dx_entry *p, *q, *m; ++ ++ p = frame->entries + 1; ++ q = frame->entries + count - 1; ++ while (p <= q) { ++ m = p + (q - p) / 2; ++ if (ext2fs_le32_to_cpu(m->hash) > hash) ++ q = m - 1; ++ else ++ p = m + 1; ++ } ++ frame->at = p - 1; ++} ++ ++static errcode_t load_logical_dir_block(ext2_filsys fs, ext2_ino_t dir, ++ struct ext2_inode *diri, blk64_t block, ++ blk64_t *pblk, void *buf) ++{ ++ errcode_t errcode; ++ int ret_flags; ++ ++ errcode = ext2fs_bmap2(fs, dir, diri, NULL, 0, block, &ret_flags, ++ pblk); ++ if (errcode) ++ return errcode; ++ if (ret_flags & BMAP_RET_UNINIT) ++ return EXT2_ET_DIR_CORRUPTED; ++ return ext2fs_read_dir_block4(fs, *pblk, buf, 0, dir); ++} ++ ++static errcode_t dx_lookup(ext2_filsys fs, ext2_ino_t dir, ++ struct ext2_inode *diri, struct dx_lookup_info *info) ++{ ++ struct ext2_dx_root_info *root; ++ errcode_t errcode; ++ int level = 0; ++ int count, limit; ++ int hash_alg; ++ __u32 hash, minor_hash; ++ struct dx_frame *frame; ++ ++ errcode = alloc_dx_frame(fs, &(info->frames[0])); ++ if (errcode) ++ return errcode; ++ info->levels = 1; ++ ++ errcode = load_logical_dir_block(fs, dir, diri, 0, ++ &(info->frames[0].pblock), ++ info->frames[0].buf); ++ if (errcode) ++ goto out_err; ++ root = info->frames[0].buf + EXT2_DX_ROOT_OFF; ++ hash_alg = root->hash_version; ++ if (hash_alg != EXT2_HASH_TEA && hash_alg != EXT2_HASH_HALF_MD4 && ++ hash_alg != EXT2_HASH_LEGACY) { ++ errcode = EXT2_ET_DIRHASH_UNSUPP; ++ goto out_err; ++ } ++ if (hash_alg <= EXT2_HASH_TEA && ++ fs->super->s_flags & EXT2_FLAGS_UNSIGNED_HASH) ++ hash_alg += 3; ++ if (root->indirect_levels >= 2) { ++ errcode = EXT2_ET_DIR_CORRUPTED; ++ goto out_err; ++ } ++ info->hash_alg = hash_alg; ++ ++ errcode = ext2fs_dirhash(hash_alg, info->name, info->namelen, ++ fs->super->s_hash_seed, &info->hash, ++ &minor_hash); ++ if (errcode) ++ goto out_err; ++ ++ for (level = 0; level <= root->indirect_levels; level++) { ++ frame = &(info->frames[level]); ++ if (level > 0) { ++ errcode = alloc_dx_frame(fs, frame); ++ if (errcode) ++ goto out_err; ++ info->levels++; ++ ++ errcode = load_logical_dir_block(fs, dir, diri, ++ ext2fs_le32_to_cpu(info->frames[level-1].at->block) & 0x0fffffff, ++ &(frame->pblock), frame->buf); ++ if (errcode) ++ goto out_err; ++ } ++ errcode = ext2fs_get_dx_countlimit(fs, frame->buf, ++ &(frame->head), NULL); ++ if (errcode) ++ goto out_err; ++ count = ext2fs_le16_to_cpu(frame->head->count); ++ limit = ext2fs_le16_to_cpu(frame->head->limit); ++ frame->entries = (struct ext2_dx_entry *)(frame->head); ++ if (!count || count > limit) { ++ errcode = EXT2_ET_DIR_CORRUPTED; ++ goto out_err; ++ } ++ ++ dx_search_entry(frame, count, info->hash); ++ } ++ return 0; ++out_err: ++ dx_release(info); ++ return errcode; ++} + + struct link_struct { + ext2_filsys fs; +@@ -31,7 +178,9 @@ struct link_struct { + struct ext2_super_block *sb; + }; + +-static int link_proc(struct ext2_dir_entry *dirent, ++static int link_proc(ext2_ino_t dir EXT2FS_ATTR((unused)), ++ int entru EXT2FS_ATTR((unused)), ++ struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, +@@ -71,40 +220,6 @@ static int link_proc(struct ext2_dir_ent + } + + /* +- * Since ext2fs_link blows away htree data, we need to be +- * careful -- if metadata_csum is enabled and we're passed in +- * a dirent that contains htree data, we need to create the +- * fake entry at the end of the block that hides the checksum. +- */ +- +- /* De-convert a dx_node block */ +- if (csum_size && +- curr_rec_len == ls->fs->blocksize && +- !dirent->inode) { +- curr_rec_len -= csum_size; +- ls->err = ext2fs_set_rec_len(ls->fs, curr_rec_len, dirent); +- if (ls->err) +- return DIRENT_ABORT; +- t = EXT2_DIRENT_TAIL(buf, ls->fs->blocksize); +- ext2fs_initialize_dirent_tail(ls->fs, t); +- ret = DIRENT_CHANGED; +- } +- +- /* De-convert a dx_root block */ +- if (csum_size && +- curr_rec_len == ls->fs->blocksize - EXT2_DIR_REC_LEN(1) && +- offset == EXT2_DIR_REC_LEN(1) && +- dirent->name[0] == '.' && dirent->name[1] == '.') { +- curr_rec_len -= csum_size; +- ls->err = ext2fs_set_rec_len(ls->fs, curr_rec_len, dirent); +- if (ls->err) +- return DIRENT_ABORT; +- t = EXT2_DIRENT_TAIL(buf, ls->fs->blocksize); +- ext2fs_initialize_dirent_tail(ls->fs, t); +- ret = DIRENT_CHANGED; +- } +- +- /* + * If the directory entry is used, see if we can split the + * directory entry to make room for the new name. If so, + * truncate it and return. +@@ -144,6 +259,341 @@ static int link_proc(struct ext2_dir_ent + return DIRENT_ABORT|DIRENT_CHANGED; + } + ++static errcode_t add_dirent_to_buf(ext2_filsys fs, e2_blkcnt_t blockcnt, ++ char *buf, ext2_ino_t dir, ++ struct ext2_inode *diri, const char *name, ++ ext2_ino_t ino, int flags, blk64_t *pblkp) ++{ ++ struct dir_context ctx; ++ struct link_struct ls; ++ errcode_t retval; ++ ++ retval = load_logical_dir_block(fs, dir, diri, blockcnt, pblkp, buf); ++ if (retval) ++ return retval; ++ ctx.errcode = 0; ++ ctx.func = link_proc; ++ ctx.dir = dir; ++ ctx.flags = DIRENT_FLAG_INCLUDE_EMPTY; ++ ctx.buf = buf; ++ ctx.priv_data = &ls; ++ ++ ls.fs = fs; ++ ls.name = name; ++ ls.namelen = strlen(name); ++ ls.inode = ino; ++ ls.flags = flags; ++ ls.done = 0; ++ ls.sb = fs->super; ++ ls.blocksize = fs->blocksize; ++ ls.err = 0; ++ ++ ext2fs_process_dir_block(fs, pblkp, blockcnt, 0, 0, &ctx); ++ if (ctx.errcode) ++ return ctx.errcode; ++ if (ls.err) ++ return ls.err; ++ if (!ls.done) ++ return EXT2_ET_DIR_NO_SPACE; ++ return 0; ++} ++ ++struct dx_hash_map { ++ __u32 hash; ++ int size; ++ int off; ++}; ++ ++static EXT2_QSORT_TYPE dx_hash_map_cmp(const void *ap, const void *bp) ++{ ++ const struct dx_hash_map *a = ap, *b = bp; ++ ++ if (a->hash < b->hash) ++ return -1; ++ if (a->hash > b->hash) ++ return 1; ++ return 0; ++} ++ ++static errcode_t dx_move_dirents(ext2_filsys fs, struct dx_hash_map *map, ++ int count, void *from, void *to) ++{ ++ struct ext2_dir_entry *de; ++ int i; ++ int rec_len; ++ errcode_t retval; ++ int csum_size = 0; ++ void *base = to; ++ ++ if (ext2fs_has_feature_metadata_csum(fs->super)) ++ csum_size = sizeof(struct ext2_dir_entry_tail); ++ ++ for (i = 0; i < count; i++) { ++ de = from + map[i].off; ++ rec_len = EXT2_DIR_REC_LEN(ext2fs_dirent_name_len(de)); ++ memcpy(to, de, rec_len); ++ retval = ext2fs_set_rec_len(fs, rec_len, to); ++ if (retval) ++ return retval; ++ to += rec_len; ++ } ++ /* ++ * Update rec_len of the last dir entry to stretch to the end of block ++ */ ++ to -= rec_len; ++ rec_len = fs->blocksize - (to - base) - csum_size; ++ retval = ext2fs_set_rec_len(fs, rec_len, to); ++ if (retval) ++ return retval; ++ if (csum_size) ++ ext2fs_initialize_dirent_tail(fs, ++ EXT2_DIRENT_TAIL(base, fs->blocksize)); ++ return 0; ++} ++ ++static errcode_t dx_insert_entry(ext2_filsys fs, ext2_ino_t dir, ++ struct dx_lookup_info *info, int level, ++ __u32 hash, blk64_t lblk) ++{ ++ int pcount; ++ struct ext2_dx_entry *top, *new; ++ ++ pcount = ext2fs_le16_to_cpu(info->frames[level].head->count); ++ top = info->frames[level].entries + pcount; ++ new = info->frames[level].at + 1; ++ memmove(new + 1, new, (char *)top - (char *)new); ++ new->hash = ext2fs_cpu_to_le32(hash); ++ new->block = ext2fs_cpu_to_le32(lblk); ++ info->frames[level].head->count = ext2fs_cpu_to_le16(pcount + 1); ++ return ext2fs_write_dir_block4(fs, info->frames[level].pblock, ++ info->frames[level].buf, 0, dir); ++} ++ ++static errcode_t dx_split_leaf(ext2_filsys fs, ext2_ino_t dir, ++ struct ext2_inode *diri, ++ struct dx_lookup_info *info, void *buf, ++ blk64_t leaf_pblk, blk64_t new_lblk, ++ blk64_t new_pblk) ++{ ++ struct ext2_dir_entry *de; ++ void *buf2; ++ errcode_t retval = 0; ++ int rec_len; ++ int offset, move_size; ++ int i, count = 0; ++ struct dx_hash_map *map; ++ int continued; ++ __u32 minor_hash; ++ ++ retval = ext2fs_get_mem(fs->blocksize, &buf2); ++ if (retval) ++ return retval; ++ retval = ext2fs_get_array(fs->blocksize / 12, ++ sizeof(struct dx_hash_map), &map); ++ if (retval) { ++ ext2fs_free_mem(&buf2); ++ return retval; ++ } ++ for (offset = 0; offset < fs->blocksize; offset += rec_len) { ++ de = buf + offset; ++ retval = ext2fs_get_rec_len(fs, de, &rec_len); ++ if (retval) ++ goto out; ++ if (ext2fs_dirent_name_len(de) > 0 && de->inode) { ++ map[count].off = offset; ++ map[count].size = rec_len; ++ retval = ext2fs_dirhash(info->hash_alg, de->name, ++ ext2fs_dirent_name_len(de), ++ fs->super->s_hash_seed, ++ &(map[count].hash), ++ &minor_hash); ++ if (retval) ++ goto out; ++ count++; ++ } ++ } ++ qsort(map, count, sizeof(struct dx_hash_map), dx_hash_map_cmp); ++ move_size = 0; ++ /* Find place to split block */ ++ for (i = count - 1; i >= 0; i--) { ++ if (move_size + map[i].size / 2 > fs->blocksize / 2) ++ break; ++ move_size += map[i].size; ++ } ++ /* Let i be the first entry to move */ ++ i++; ++ /* Move selected directory entries to new block */ ++ retval = dx_move_dirents(fs, map + i, count - i, buf, buf2); ++ if (retval) ++ goto out; ++ retval = ext2fs_write_dir_block4(fs, new_pblk, buf2, 0, dir); ++ if (retval) ++ goto out; ++ /* Repack remaining entries in the old block */ ++ retval = dx_move_dirents(fs, map, i, buf, buf2); ++ if (retval) ++ goto out; ++ retval = ext2fs_write_dir_block4(fs, leaf_pblk, buf2, 0, dir); ++ if (retval) ++ goto out; ++ /* Update parent node */ ++ continued = map[i].hash == map[i-1].hash; ++ retval = dx_insert_entry(fs, dir, info, info->levels - 1, ++ map[i].hash + continued, new_lblk); ++out: ++ ext2fs_free_mem(&buf2); ++ ext2fs_free_mem(&map); ++ return retval; ++} ++ ++static errcode_t dx_grow_tree(ext2_filsys fs, ext2_ino_t dir, ++ struct ext2_inode *diri, ++ struct dx_lookup_info *info, void *buf, ++ blk64_t leaf_pblk) ++{ ++ int i; ++ errcode_t retval; ++ ext2_off64_t size = EXT2_I_SIZE(diri); ++ blk64_t lblk, pblk; ++ struct ext2_dir_entry *de; ++ struct ext2_dx_countlimit *head; ++ int csum_size = 0; ++ int count; ++ ++ if (ext2fs_has_feature_metadata_csum(fs->super)) ++ csum_size = sizeof(struct ext2_dx_tail); ++ ++ /* Find level which can accommodate new child */ ++ for (i = info->levels - 1; i >= 0; i--) ++ if (ext2fs_le16_to_cpu(info->frames[i].head->count) < ++ ext2fs_le16_to_cpu(info->frames[i].head->limit)) ++ break; ++ /* Need to grow tree depth? */ ++ if (i < 0 && info->levels >= 2) ++ return EXT2_ET_DIR_NO_SPACE; ++ lblk = size / fs->blocksize; ++ size += fs->blocksize; ++ retval = ext2fs_inode_size_set(fs, diri, size); ++ if (retval) ++ return retval; ++ retval = ext2fs_fallocate(fs, ++ EXT2_FALLOCATE_FORCE_INIT | EXT2_FALLOCATE_ZERO_BLOCKS, ++ dir, diri, 0, lblk, 1); ++ if (retval) ++ return retval; ++ retval = ext2fs_write_inode(fs, dir, diri); ++ if (retval) ++ return retval; ++ retval = ext2fs_bmap2(fs, dir, diri, NULL, 0, lblk, NULL, &pblk); ++ if (retval) ++ return retval; ++ /* Only leaf addition needed? */ ++ if (i == info->levels - 1) ++ return dx_split_leaf(fs, dir, diri, info, buf, leaf_pblk, ++ lblk, pblk); ++ ++ de = buf; ++ de->inode = 0; ++ ext2fs_dirent_set_name_len(de, 0); ++ ext2fs_dirent_set_file_type(de, 0); ++ retval = ext2fs_set_rec_len(fs, fs->blocksize, de); ++ if (retval) ++ return retval; ++ head = buf + 8; ++ count = ext2fs_le16_to_cpu(info->frames[i+1].head->count); ++ /* Growing tree depth? */ ++ if (i < 0) { ++ struct ext2_dx_root_info *root; ++ ++ memcpy(head, info->frames[0].entries, ++ count * sizeof(struct ext2_dx_entry)); ++ head->limit = ext2fs_cpu_to_le16( ++ (fs->blocksize - (8 + csum_size)) / ++ sizeof(struct ext2_dx_entry)); ++ /* head->count gets set by memcpy above to correct value */ ++ ++ /* Now update tree root */ ++ info->frames[0].head->count = ext2fs_cpu_to_le16(1); ++ info->frames[0].entries[0].block = ext2fs_cpu_to_le32(lblk); ++ root = info->frames[0].buf + EXT2_DX_ROOT_OFF; ++ root->indirect_levels++; ++ } else { ++ /* Splitting internal node in two */ ++ int count1 = count / 2; ++ int count2 = count - count1; ++ __u32 split_hash = ext2fs_le32_to_cpu(info->frames[i+1].entries[count1].hash); ++ ++ memcpy(head, info->frames[i+1].entries + count1, ++ count2 * sizeof(struct ext2_dx_entry)); ++ head->count = ext2fs_cpu_to_le16(count2); ++ head->limit = ext2fs_cpu_to_le16( ++ (fs->blocksize - (8 + csum_size)) / ++ sizeof(struct ext2_dx_entry)); ++ info->frames[i+1].head->count = ext2fs_cpu_to_le16(count1); ++ ++ /* Update parent node */ ++ retval = dx_insert_entry(fs, dir, info, i, split_hash, lblk); ++ if (retval) ++ return retval; ++ ++ } ++ /* Writeout split block / updated root */ ++ retval = ext2fs_write_dir_block4(fs, info->frames[i+1].pblock, ++ info->frames[i+1].buf, 0, dir); ++ if (retval) ++ return retval; ++ /* Writeout new tree block */ ++ retval = ext2fs_write_dir_block4(fs, pblk, buf, 0, dir); ++ if (retval) ++ return retval; ++ return 0; ++} ++ ++static errcode_t dx_link(ext2_filsys fs, ext2_ino_t dir, ++ struct ext2_inode *diri, const char *name, ++ ext2_ino_t ino, int flags) ++{ ++ struct dx_lookup_info dx_info; ++ errcode_t retval; ++ void *blockbuf; ++ int restart = 0; ++ blk64_t leaf_pblk; ++ ++ retval = ext2fs_get_mem(fs->blocksize, &blockbuf); ++ if (retval) ++ return retval; ++ ++ dx_info.name = name; ++ dx_info.namelen = strlen(name); ++again: ++ retval = dx_lookup(fs, dir, diri, &dx_info); ++ if (retval) ++ goto free_buf; ++ ++ retval = add_dirent_to_buf(fs, ++ ext2fs_le32_to_cpu(dx_info.frames[dx_info.levels-1].at->block) & 0x0fffffff, ++ blockbuf, dir, diri, name, ino, flags, &leaf_pblk); ++ /* ++ * Success or error other than ENOSPC...? We are done. We may need upto ++ * two tries to add entry. One to split htree node and another to add ++ * new leaf block. ++ */ ++ if (restart >= dx_info.levels || retval != EXT2_ET_DIR_NO_SPACE) ++ goto free_frames; ++ retval = dx_grow_tree(fs, dir, diri, &dx_info, blockbuf, leaf_pblk); ++ if (retval) ++ goto free_frames; ++ /* Restart everything now that the tree is larger */ ++ restart++; ++ dx_release(&dx_info); ++ goto again; ++free_frames: ++ dx_release(&dx_info); ++free_buf: ++ ext2fs_free_mem(&blockbuf); ++ return retval; ++} ++ + /* + * Note: the low 3 bits of the flags field are used as the directory + * entry filetype. +@@ -163,6 +613,12 @@ errcode_t ext2fs_link(ext2_filsys fs, ex + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + ++ if ((retval = ext2fs_read_inode(fs, dir, &inode)) != 0) ++ return retval; ++ ++ if (inode.i_flags & EXT2_INDEX_FL) ++ return dx_link(fs, dir, &inode, name, ino, flags); ++ + ls.fs = fs; + ls.name = name; + ls.namelen = name ? strlen(name) : 0; +@@ -173,8 +629,8 @@ errcode_t ext2fs_link(ext2_filsys fs, ex + ls.blocksize = fs->blocksize; + ls.err = 0; + +- retval = ext2fs_dir_iterate(fs, dir, DIRENT_FLAG_INCLUDE_EMPTY, +- 0, link_proc, &ls); ++ retval = ext2fs_dir_iterate2(fs, dir, DIRENT_FLAG_INCLUDE_EMPTY, ++ NULL, link_proc, &ls); + if (retval) + return retval; + if (ls.err) +@@ -182,20 +638,5 @@ errcode_t ext2fs_link(ext2_filsys fs, ex + + if (!ls.done) + return EXT2_ET_DIR_NO_SPACE; +- +- if ((retval = ext2fs_read_inode(fs, dir, &inode)) != 0) +- return retval; +- +- /* +- * If this function changes to preserve the htree, remove the +- * two hunks in link_proc that shove checksum tails into the +- * former dx_root/dx_node blocks. +- */ +- if (inode.i_flags & EXT2_INDEX_FL) { +- inode.i_flags &= ~EXT2_INDEX_FL; +- if ((retval = ext2fs_write_inode(fs, dir, &inode)) != 0) +- return retval; +- } +- + return 0; + } diff --git a/ext2fs-update-allocation-info-earlier-in-ext2fs_mkdi.patch b/ext2fs-update-allocation-info-earlier-in-ext2fs_mkdi.patch new file mode 100644 index 0000000..a6667a9 --- /dev/null +++ b/ext2fs-update-allocation-info-earlier-in-ext2fs_mkdi.patch @@ -0,0 +1,128 @@ +From a25a84c6aa2a19987b0aeced363c4bf1dcc89ec8 Mon Sep 17 00:00:00 2001 +From: Jan Kara +Date: Thu, 13 Feb 2020 11:15:58 +0100 +Subject: [PATCH 1/5] ext2fs: update allocation info earlier in ext2fs_mkdir() + and ext2fs_symlink() +References: bsc#1160979 + +Currently, ext2fs_mkdir() and ext2fs_symlink() update allocation bitmaps +and other information only close to the end of the function, in +particular after calling to ext2fs_link(). When ext2fs_link() will +support indexed directories, it will also need to allocate blocks and +that would cause filesystem corruption in case allocation info isn't +properly updated. So make sure ext2fs_mkdir() and ext2fs_symlink() +update allocation info before calling into ext2fs_link(). + +[ Added error handling so the calls to ext2fs_{block,inode}_alloc_stats() + can be undone if the newly created directory or symlink can not be linked + into the directory. -- TYT ] + +Signed-off-by: Jan Kara +Signed-off-by: Theodore Ts'o +Acked-by: Jan Kara +--- + lib/ext2fs/mkdir.c | 22 +++++++++++++++------- + lib/ext2fs/symlink.c | 22 +++++++++++++++------- + 2 files changed, 30 insertions(+), 14 deletions(-) + +Index: e2fsprogs-1.43.8/lib/ext2fs/mkdir.c +=================================================================== +--- e2fsprogs-1.43.8.orig/lib/ext2fs/mkdir.c ++++ e2fsprogs-1.43.8/lib/ext2fs/mkdir.c +@@ -43,6 +43,7 @@ errcode_t ext2fs_mkdir(ext2_filsys fs, e + blk64_t blk; + char *block = 0; + int inline_data = 0; ++ int drop_refcount = 0; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + +@@ -144,6 +145,14 @@ errcode_t ext2fs_mkdir(ext2_filsys fs, e + } + + /* ++ * Update accounting.... ++ */ ++ if (!inline_data) ++ ext2fs_block_alloc_stats2(fs, blk, +1); ++ ext2fs_inode_alloc_stats2(fs, ino, +1, 1); ++ drop_refcount = 1; ++ ++ /* + * Link the directory into the filesystem hierarchy + */ + if (name) { +@@ -174,17 +183,16 @@ errcode_t ext2fs_mkdir(ext2_filsys fs, e + if (retval) + goto cleanup; + } +- +- /* +- * Update accounting.... +- */ +- if (!inline_data) +- ext2fs_block_alloc_stats2(fs, blk, +1); +- ext2fs_inode_alloc_stats2(fs, ino, +1, 1); ++ drop_refcount = 0; + + cleanup: + if (block) + ext2fs_free_mem(&block); ++ if (drop_refcount) { ++ if (!inline_data) ++ ext2fs_block_alloc_stats2(fs, blk, -1); ++ ext2fs_inode_alloc_stats2(fs, ino, -1, 1); ++ } + return retval; + + } +Index: e2fsprogs-1.43.8/lib/ext2fs/symlink.c +=================================================================== +--- e2fsprogs-1.43.8.orig/lib/ext2fs/symlink.c ++++ e2fsprogs-1.43.8/lib/ext2fs/symlink.c +@@ -38,6 +38,7 @@ errcode_t ext2fs_symlink(ext2_filsys fs, + int fastlink, inlinelink; + unsigned int target_len; + char *block_buf = 0; ++ int drop_refcount = 0; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + +@@ -146,6 +147,14 @@ need_block: + } + + /* ++ * Update accounting.... ++ */ ++ if (!fastlink && !inlinelink) ++ ext2fs_block_alloc_stats2(fs, blk, +1); ++ ext2fs_inode_alloc_stats2(fs, ino, +1, 0); ++ drop_refcount = 1; ++ ++ /* + * Link the symlink into the filesystem hierarchy + */ + if (name) { +@@ -161,16 +170,15 @@ need_block: + if (retval) + goto cleanup; + } +- +- /* +- * Update accounting.... +- */ +- if (!fastlink && !inlinelink) +- ext2fs_block_alloc_stats2(fs, blk, +1); +- ext2fs_inode_alloc_stats2(fs, ino, +1, 0); ++ drop_refcount = 0; + + cleanup: + if (block_buf) + ext2fs_free_mem(&block_buf); ++ if (drop_refcount) { ++ if (!fastlink && !inlinelink) ++ ext2fs_block_alloc_stats2(fs, blk, -1); ++ ext2fs_inode_alloc_stats2(fs, ino, -1, 0); ++ } + return retval; + } diff --git a/tests-add-test-to-excercise-indexed-directories-with.patch b/tests-add-test-to-excercise-indexed-directories-with.patch new file mode 100644 index 0000000..d3e5bd2 --- /dev/null +++ b/tests-add-test-to-excercise-indexed-directories-with.patch @@ -0,0 +1,158 @@ +From 4ed27a5bb9bfabaf437b45aaad315f028da5a6f1 Mon Sep 17 00:00:00 2001 +From: Jan Kara +Date: Thu, 13 Feb 2020 11:16:01 +0100 +Subject: [PATCH 4/5] tests: add test to excercise indexed directories with + metadata_csum +References: bsc#1160979 + +Indexed directories have somewhat different format when metadata_csum is +enabled. Add test to excercise linking in indexed directories and e2fsck +rehash code in this case. + +Reviewed-by: Andreas Dilger +Signed-off-by: Jan Kara +Signed-off-by: Theodore Ts'o +Acked-by: Jan Kara +--- + tests/f_large_dir_csum/expect | 32 ++++++++++++++ + tests/f_large_dir_csum/is_slow_test | 0 + tests/f_large_dir_csum/name | 1 + + tests/f_large_dir_csum/script | 84 +++++++++++++++++++++++++++++++++++++ + 4 files changed, 117 insertions(+) + create mode 100644 tests/f_large_dir_csum/expect + create mode 100644 tests/f_large_dir_csum/is_slow_test + create mode 100644 tests/f_large_dir_csum/name + create mode 100644 tests/f_large_dir_csum/script + +Index: e2fsprogs-1.43.8/tests/f_large_dir_csum/expect +=================================================================== +--- /dev/null ++++ e2fsprogs-1.43.8/tests/f_large_dir_csum/expect +@@ -0,0 +1,32 @@ ++Creating filesystem with 30996 1k blocks and 64 inodes ++Superblock backups stored on blocks: ++ 8193, 24577 ++ ++Allocating group tables: done ++Writing inode tables: done ++Writing superblocks and filesystem accounting information: done ++ ++Pass 1: Checking inodes, blocks, and sizes ++Pass 2: Checking directory structure ++Pass 3: Checking directory connectivity ++Pass 3A: Optimizing directories ++Pass 4: Checking reference counts ++Inode 13 ref count is 1, should be 5. Fix? yes ++ ++Pass 5: Checking group summary information ++ ++test.img: ***** FILE SYSTEM WAS MODIFIED ***** ++test.img: 13/64 files (0.0% non-contiguous), 766/30996 blocks ++Exit status is 1 ++Pass 1: Checking inodes, blocks, and sizes ++Pass 2: Checking directory structure ++Pass 3: Checking directory connectivity ++Pass 3A: Optimizing directories ++Pass 4: Checking reference counts ++Inode 13 ref count is 5, should be 15499. Fix? yes ++ ++Pass 5: Checking group summary information ++ ++test.img: ***** FILE SYSTEM WAS MODIFIED ***** ++test.img: 13/64 files (0.0% non-contiguous), 5971/30996 blocks ++Exit status is 1 +Index: e2fsprogs-1.43.8/tests/f_large_dir_csum/name +=================================================================== +--- /dev/null ++++ e2fsprogs-1.43.8/tests/f_large_dir_csum/name +@@ -0,0 +1 @@ ++optimize 3 level htree directories with metadata checksums +Index: e2fsprogs-1.43.8/tests/f_large_dir_csum/script +=================================================================== +--- /dev/null ++++ e2fsprogs-1.43.8/tests/f_large_dir_csum/script +@@ -0,0 +1,84 @@ ++OUT=$test_name.log ++EXP=$test_dir/expect ++E2FSCK=../e2fsck/e2fsck ++ ++NAMELEN=255 ++DIRENT_SZ=8 ++BLOCKSZ=1024 ++INODESZ=128 ++CSUM_SZ=8 ++CSUM_TAIL_SZ=12 ++DIRENT_PER_LEAF=$(((BLOCKSZ - CSUM_TAIL_SZ) / (NAMELEN + DIRENT_SZ))) ++HEADER=32 ++INDEX_SZ=8 ++INDEX_L1=$(((BLOCKSZ - HEADER - CSUM_SZ) / INDEX_SZ)) ++INDEX_L2=$(((BLOCKSZ - DIRENT_SZ - CSUM_SZ) / INDEX_SZ)) ++DIRBLK=$((INDEX_L1 * INDEX_L2)) ++ENTRIES=$((DIRBLK * DIRENT_PER_LEAF / 3)) ++# directory leaf blocks - get twice as much because the leaves won't be full ++# and there are also other filesystem blocks. ++FSIZE=$((DIRBLK * 2)) ++ ++$MKE2FS -b 1024 -O extents,64bit,uninit_bg,metadata_csum -N 50 \ ++ -I $INODESZ -F $TMPFILE $FSIZE > $OUT.new 2>&1 ++RC=$? ++if [ $RC -eq 0 ]; then ++{ ++ # First some initial fs setup to create indexed dir ++ echo "mkdir /foo" ++ echo "cd /foo" ++ touch $TMPFILE.tmp ++ echo "write $TMPFILE.tmp foofile" ++ i=0 ++ while test $i -lt $DIRENT_PER_LEAF ; do ++ printf "ln foofile f%0254u\n" $i ++ i=$((i + 1)); ++ done ++ echo "expand ./" ++ printf "ln foofile f%0254u\n" $i ++} | $DEBUGFS -w $TMPFILE > /dev/null 2>> $OUT.new ++ RC=$? ++ # e2fsck should optimize the dir to become indexed ++ $E2FSCK -yfD $TMPFILE >> $OUT.new 2>&1 ++ status=$? ++ echo Exit status is $status >> $OUT.new ++fi ++ ++if [ $RC -eq 0 ]; then ++{ ++ START=$SECONDS ++ i=$(($DIRENT_PER_LEAF+1)) ++ last=$i ++ echo "cd /foo" ++ while test $i -lt $ENTRIES ; do ++ ELAPSED=$((SECONDS - START)) ++ if test $((i % 5000)) -eq 0 -a $ELAPSED -gt 10; then ++ RATE=$(((i - last) / ELAPSED)) ++ echo "$test_name: $i/$ENTRIES links, ${ELAPSED}s @ $RATE/s" >&2 ++ START=$SECONDS ++ last=$i ++ fi ++ printf "ln foofile f%0254u\n" $i ++ i=$((i + 1)) ++ done ++} | $DEBUGFS -w $TMPFILE > /dev/null 2>> $OUT.new ++ RC=$? ++fi ++ ++if [ $RC -eq 0 ]; then ++ $E2FSCK -yfD $TMPFILE >> $OUT.new 2>&1 ++ status=$? ++ echo Exit status is $status >> $OUT.new ++ sed -f $cmd_dir/filter.sed -e "s;$TMPFILE;test.img;" $OUT.new > $OUT ++ rm -f $OUT.new ++ ++ cmp -s $OUT $EXP ++ RC=$? ++fi ++if [ $RC -eq 0 ]; then ++ echo "$test_name: $test_description: ok" ++ touch $test_name.ok ++else ++ echo "$test_name: $test_description: failed" ++ diff -u $EXP $OUT > $test_name.failed ++fi diff --git a/tune2fs-update-dir-checksums-when-clearing-dir_index.patch b/tune2fs-update-dir-checksums-when-clearing-dir_index.patch new file mode 100644 index 0000000..6cb3e21 --- /dev/null +++ b/tune2fs-update-dir-checksums-when-clearing-dir_index.patch @@ -0,0 +1,391 @@ +From 514fde0296c2085fb11685820f3d396d5c41026e Mon Sep 17 00:00:00 2001 +From: Jan Kara +Date: Thu, 13 Feb 2020 11:16:02 +0100 +Subject: [PATCH 5/5] tune2fs: update dir checksums when clearing dir_index + feature +References: bsc#1160979 + +When clearing dir_index feature while metadata_csum is enabled, we have +to rewrite checksums of all indexed directories to update checksums of +internal tree nodes. + +Signed-off-by: Jan Kara +Signed-off-by: Theodore Ts'o +Acked-by: Jan Kara +--- + misc/tune2fs.c | 143 ++++++++++++++++++++++++++++++++++++++------------------- + 1 file changed, 95 insertions(+), 48 deletions(-) + +Index: e2fsprogs-1.43.8/misc/tune2fs.c +=================================================================== +--- e2fsprogs-1.43.8.orig/misc/tune2fs.c ++++ e2fsprogs-1.43.8/misc/tune2fs.c +@@ -553,7 +553,8 @@ struct rewrite_dir_context { + char *buf; + errcode_t errcode; + ext2_ino_t dir; +- int is_htree; ++ int is_htree:1; ++ int clear_htree:1; + }; + + static int rewrite_dir_block(ext2_filsys fs, +@@ -572,8 +573,13 @@ static int rewrite_dir_block(ext2_filsys + if (ctx->errcode) + return BLOCK_ABORT; + +- /* if htree node... */ +- if (ctx->is_htree) ++ /* ++ * if htree node... Note that if we are clearing htree structures from ++ * the directory, we treat the htree internal block as an ordinary leaf. ++ * The code below will do the right thing and make space for checksum ++ * there. ++ */ ++ if (ctx->is_htree && !ctx->clear_htree) + ext2fs_get_dx_countlimit(fs, (struct ext2_dir_entry *)ctx->buf, + &dcl, &dcl_offset); + if (dcl) { +@@ -702,7 +708,8 @@ static errcode_t rewrite_directory(ext2_ + if (retval) + return retval; + +- ctx.is_htree = (inode->i_flags & EXT2_INDEX_FL); ++ ctx.is_htree = !!(inode->i_flags & EXT2_INDEX_FL); ++ ctx.clear_htree = !ext2fs_has_feature_dir_index(fs->super); + ctx.dir = dir; + ctx.errcode = 0; + retval = ext2fs_block_iterate3(fs, dir, BLOCK_FLAG_READ_ONLY | +@@ -713,121 +720,169 @@ static errcode_t rewrite_directory(ext2_ + if (retval) + return retval; + ++ if (ctx.is_htree && ctx.clear_htree) { ++ inode->i_flags &= ~EXT2_INDEX_FL; ++ retval = ext2fs_write_inode(fs, dir, inode); ++ if (retval) ++ return retval; ++ } ++ + return ctx.errcode; + } + +-/* +- * Forcibly set checksums in all inodes. +- */ +-static void rewrite_inodes(ext2_filsys fs) ++struct rewrite_context { ++ ext2_filsys fs; ++ struct ext2_inode *zero_inode; ++ char *ea_buf; ++ int inode_size; ++}; ++ ++static void rewrite_one_inode(struct rewrite_context *ctx, ext2_ino_t ino, ++ struct ext2_inode *inode) + { +- int length = EXT2_INODE_SIZE(fs->super); +- struct ext2_inode *inode, *zero; +- char *ea_buf; +- ext2_inode_scan scan; +- errcode_t retval; +- ext2_ino_t ino; +- blk64_t file_acl_block; +- int inode_dirty; ++ ext2_filsys fs = ctx->fs; ++ int inode_dirty; ++ errcode_t retval; ++ blk64_t file_acl_block; + +- if (fs->super->s_creator_os == EXT2_OS_HURD) +- return; ++ if (ext2fs_test_inode_bitmap2(fs->inode_map, ino)) { ++ inode_dirty = 1; ++ } else { ++ if (memcmp(inode, ctx->zero_inode, ctx->inode_size) != 0) { ++ memset(inode, 0, ctx->inode_size); ++ inode_dirty = 1; ++ } else { ++ inode_dirty = 0; ++ } ++ } + +- retval = ext2fs_open_inode_scan(fs, 0, &scan); ++ if (inode_dirty) { ++ retval = ext2fs_write_inode_full(fs, ino, inode, ++ ctx->inode_size); ++ if (retval) { ++ com_err("set_csum", retval, "while writing " ++ "inode"); ++ exit(1); ++ } ++ } ++ ++ retval = rewrite_extents(fs, ino, inode); + if (retval) { +- com_err("set_csum", retval, "while opening inode scan"); ++ com_err("rewrite_extents", retval, ++ "while rewriting extents"); + exit(1); + } + +- retval = ext2fs_get_mem(length, &inode); ++ if (LINUX_S_ISDIR(inode->i_mode) && ++ ext2fs_inode_has_valid_blocks2(fs, inode)) { ++ retval = rewrite_directory(fs, ino, inode); ++ if (retval) { ++ com_err("rewrite_directory", retval, ++ "while rewriting directories"); ++ exit(1); ++ } ++ } ++ ++ file_acl_block = ext2fs_file_acl_block(fs, inode); ++ if (!file_acl_block) ++ return; ++ retval = ext2fs_read_ext_attr3(fs, file_acl_block, ctx->ea_buf, ino); + if (retval) { +- com_err("set_csum", retval, "while allocating memory"); ++ com_err("rewrite_eablock", retval, ++ "while rewriting extended attribute"); ++ exit(1); ++ } ++ retval = ext2fs_write_ext_attr3(fs, file_acl_block, ctx->ea_buf, ++ ino); ++ if (retval) { ++ com_err("rewrite_eablock", retval, ++ "while rewriting extended attribute"); + exit(1); + } ++} ++ ++#define REWRITE_DIR_FL 0x02 /* Rewrite directories */ ++#define REWRITE_NONDIR_FL 0x04 /* Rewrite other inodes */ ++#define REWRITE_ALL (REWRITE_DIR_FL | REWRITE_NONDIR_FL) ++ ++static void rewrite_inodes_pass(struct rewrite_context *ctx, unsigned int flags) ++{ ++ ext2_filsys fs = ctx->fs; ++ struct ext2_inode *inode; ++ ext2_inode_scan scan; ++ errcode_t retval; ++ ext2_ino_t ino; ++ int rewrite; + +- retval = ext2fs_get_memzero(length, &zero); ++ if (fs->super->s_creator_os == EXT2_OS_HURD) ++ return; ++ ++ retval = ext2fs_open_inode_scan(fs, 0, &scan); + if (retval) { +- com_err("set_csum", retval, "while allocating memory"); ++ com_err("set_csum", retval, "while opening inode scan"); + exit(1); + } + +- retval = ext2fs_get_mem(fs->blocksize, &ea_buf); ++ retval = ext2fs_get_mem(ctx->inode_size, &inode); + if (retval) { + com_err("set_csum", retval, "while allocating memory"); + exit(1); + } + + do { +- retval = ext2fs_get_next_inode_full(scan, &ino, inode, length); ++ retval = ext2fs_get_next_inode_full(scan, &ino, inode, ctx->inode_size); + if (retval) { + com_err("set_csum", retval, "while getting next inode"); + exit(1); + } + if (!ino) + break; +- if (ext2fs_test_inode_bitmap2(fs->inode_map, ino)) { +- inode_dirty = 1; ++ rewrite = 0; ++ if (LINUX_S_ISDIR(inode->i_mode)) { ++ if (flags & REWRITE_DIR_FL) ++ rewrite = 1; + } else { +- if (memcmp(inode, zero, length) != 0) { +- memset(inode, 0, length); +- inode_dirty = 1; +- } else { +- inode_dirty = 0; +- } ++ if (flags & REWRITE_NONDIR_FL) ++ rewrite = 1; + } ++ if (rewrite) ++ rewrite_one_inode(ctx, ino, inode); ++ } while (ino); + +- if (inode_dirty) { +- retval = ext2fs_write_inode_full(fs, ino, inode, +- length); +- if (retval) { +- com_err("set_csum", retval, "while writing " +- "inode"); +- exit(1); +- } +- } ++ ext2fs_free_mem(&inode); ++ ext2fs_close_inode_scan(scan); ++} + +- retval = rewrite_extents(fs, ino, inode); +- if (retval) { +- com_err("rewrite_extents", retval, +- "while rewriting extents"); +- exit(1); +- } ++/* ++ * Forcibly rewrite checksums in inodes specified by 'flags' ++ */ ++static void rewrite_inodes(ext2_filsys fs, unsigned int flags) ++{ ++ struct rewrite_context ctx = { ++ .fs = fs, ++ .inode_size = EXT2_INODE_SIZE(fs->super), ++ }; ++ errcode_t retval; + +- if (LINUX_S_ISDIR(inode->i_mode) && +- ext2fs_inode_has_valid_blocks2(fs, inode)) { +- retval = rewrite_directory(fs, ino, inode); +- if (retval) { +- com_err("rewrite_directory", retval, +- "while rewriting directories"); +- exit(1); +- } +- } ++ retval = ext2fs_get_memzero(ctx.inode_size, &ctx.zero_inode); ++ if (retval) { ++ com_err("set_csum", retval, "while allocating memory"); ++ exit(1); ++ } + +- file_acl_block = ext2fs_file_acl_block(fs, inode); +- if (!file_acl_block) +- continue; +- retval = ext2fs_read_ext_attr3(fs, file_acl_block, ea_buf, ino); +- if (retval) { +- com_err("rewrite_eablock", retval, +- "while rewriting extended attribute"); +- exit(1); +- } +- retval = ext2fs_write_ext_attr3(fs, file_acl_block, ea_buf, +- ino); +- if (retval) { +- com_err("rewrite_eablock", retval, +- "while rewriting extended attribute"); +- exit(1); +- } +- } while (ino); ++ retval = ext2fs_get_mem(fs->blocksize, &ctx.ea_buf); ++ if (retval) { ++ com_err("set_csum", retval, "while allocating memory"); ++ exit(1); ++ } + +- ext2fs_free_mem(&zero); +- ext2fs_free_mem(&inode); +- ext2fs_free_mem(&ea_buf); +- ext2fs_close_inode_scan(scan); ++ rewrite_inodes_pass(&ctx, flags); ++ ++ ext2fs_free_mem(&ctx.zero_inode); ++ ext2fs_free_mem(&ctx.ea_buf); + } + +-static void rewrite_metadata_checksums(ext2_filsys fs) ++static void rewrite_metadata_checksums(ext2_filsys fs, unsigned int flags) + { + errcode_t retval; + dgrp_t i; +@@ -842,7 +897,7 @@ static void rewrite_metadata_checksums(e + "while reading bitmaps"); + exit(1); + } +- rewrite_inodes(fs); ++ rewrite_inodes(fs, flags); + ext2fs_mark_ib_dirty(fs); + ext2fs_mark_bb_dirty(fs); + ext2fs_mmp_update2(fs, 1); +@@ -1141,6 +1196,23 @@ mmp_error: + uuid_generate((unsigned char *) sb->s_hash_seed); + } + ++ if (FEATURE_OFF(E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_DIR_INDEX) && ++ ext2fs_has_feature_metadata_csum(sb)) { ++ check_fsck_needed(fs, ++ _("Disabling directory index on filesystem with " ++ "checksums could take some time.")); ++ if (mount_flags & EXT2_MF_MOUNTED) { ++ fputs(_("Cannot disable dir_index on a mounted " ++ "filesystem!\n"), stderr); ++ exit(1); ++ } ++ /* ++ * Clearing dir_index on checksummed filesystem requires ++ * rewriting all directories to update checksums. ++ */ ++ rewrite_checksums |= REWRITE_DIR_FL; ++ } ++ + if (FEATURE_OFF(E2P_FEATURE_INCOMPAT, EXT4_FEATURE_INCOMPAT_FLEX_BG)) { + if (ext2fs_check_desc(fs)) { + fputs(_("Clearing the flex_bg flag would " +@@ -1184,7 +1256,7 @@ mmp_error: + "The larger fields afforded by this feature " + "enable full-strength checksumming. " + "Run resize2fs -b to rectify.\n")); +- rewrite_checksums = 1; ++ rewrite_checksums = REWRITE_ALL; + /* metadata_csum supersedes uninit_bg */ + ext2fs_clear_feature_gdt_csum(fs->super); + +@@ -1212,7 +1284,7 @@ mmp_error: + "filesystem!\n"), stderr); + exit(1); + } +- rewrite_checksums = 1; ++ rewrite_checksums = REWRITE_ALL; + + /* Enable uninit_bg unless the user expressly turned it off */ + memcpy(test_features, old_features, sizeof(test_features)); +@@ -1375,7 +1447,7 @@ mmp_error: + return 1; + } + +- rewrite_checksums = 1; ++ rewrite_checksums = REWRITE_ALL; + } + + if (sb->s_rev_level == EXT2_GOOD_OLD_REV && +@@ -3148,7 +3220,7 @@ retry_open: + ext2fs_mark_super_dirty(fs); + if (ext2fs_has_feature_metadata_csum(fs->super) && + !ext2fs_has_feature_csum_seed(fs->super)) +- rewrite_checksums = 1; ++ rewrite_checksums = REWRITE_ALL; + } + + if (I_flag) { +@@ -3180,7 +3252,7 @@ retry_open: + if (retval == 0) { + printf(_("Setting inode size %lu\n"), + new_inode_size); +- rewrite_checksums = 1; ++ rewrite_checksums = REWRITE_ALL; + } else { + printf("%s", _("Failed to change inode size\n")); + rc = 1; +@@ -3189,7 +3261,7 @@ retry_open: + } + + if (rewrite_checksums) +- rewrite_metadata_checksums(fs); ++ rewrite_metadata_checksums(fs, rewrite_checksums); + + if (l_flag) + list_super(sb);