X-Git-Url: https://git.whamcloud.com/?a=blobdiff_plain;f=e2fsck%2Fpass1.c;h=037b99adfa8ea5762a3ec12bee0abcd6ca131fc7;hb=165abd0095613b60fbf64d139b899ba4896a2d92;hp=41eac0888bf27d8d2e9ade75bfafbae618062da0;hpb=b9e66a187f8ff1df4a35244b1d1e87a35aae46f3;p=tools%2Fe2fsprogs.git diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c index 41eac08..037b99a 100644 --- a/e2fsck/pass1.c +++ b/e2fsck/pass1.c @@ -20,13 +20,17 @@ * - A bitmap of which inodes are in use. (inode_used_map) * - A bitmap of which inodes are directories. (inode_dir_map) * - A bitmap of which inodes are regular files. (inode_reg_map) - * - A bitmap of which inodes have bad fields. (inode_bad_map) + * - An icount mechanism is used to keep track of + * inodes with bad fields and its badness (ctx->inode_badness) * - A bitmap of which inodes are in bad blocks. (inode_bb_map) * - A bitmap of which inodes are imagic inodes. (inode_imagic_map) + * - A bitmap of which inodes are casefolded. (inode_casefold_map) + * - A bitmap of which inodes need to be expanded (expand_eisize_map) * - A bitmap of which blocks are in use. (block_found_map) * - A bitmap of which blocks are in use by two inodes (block_dup_map) * - The data blocks of the directory inodes. (dir_map) * - Ref counts for ea_inodes. (ea_inode_refs) + * - The encryption policy ID of each encrypted inode. (encrypted_files) * * Pass 1 is designed to stash away enough information so that the * other passes should not need to read in the inode information @@ -45,9 +49,15 @@ #ifdef HAVE_ERRNO_H #include #endif +#include +#ifdef HAVE_PTHREAD +#include +#endif #include "e2fsck.h" #include +/* todo remove this finally */ +#include #include #include "problem.h" @@ -77,10 +87,8 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, static void mark_table_blocks(e2fsck_t ctx); static void alloc_bb_map(e2fsck_t ctx); static void alloc_imagic_map(e2fsck_t ctx); -static void mark_inode_bad(e2fsck_t ctx, ino_t ino); -static void add_encrypted_dir(e2fsck_t ctx, ino_t ino); +static void add_casefolded_dir(e2fsck_t ctx, ext2_ino_t ino); static void handle_fs_bad_blocks(e2fsck_t ctx); -static void process_inodes(e2fsck_t ctx, char *block_buf); static EXT2_QSORT_TYPE process_inode_cmp(const void *a, const void *b); static errcode_t scan_callback(ext2_filsys fs, ext2_inode_scan scan, dgrp_t group, void * priv_data); @@ -115,30 +123,20 @@ struct process_inode_block { }; struct scan_callback_struct { - e2fsck_t ctx; - char *block_buf; + e2fsck_t ctx; + char *block_buf; + struct process_inode_block *inodes_to_process; + int *process_inode_count; }; -/* - * For the inodes to process list. - */ -static struct process_inode_block *inodes_to_process; -static int process_inode_count; +static void process_inodes(e2fsck_t ctx, char *block_buf, + struct process_inode_block *inodes_to_process, + int *process_inode_count); static __u64 ext2_max_sizes[EXT2_MAX_BLOCK_LOG_SIZE - EXT2_MIN_BLOCK_LOG_SIZE + 1]; /* - * Free all memory allocated by pass1 in preparation for restarting - * things. - */ -static void unwind_pass1(ext2_filsys fs EXT2FS_ATTR((unused))) -{ - ext2fs_free_mem(&inodes_to_process); - inodes_to_process = 0; -} - -/* * Check to make sure a device inode is real. Returns 1 if the device * checks out, 0 if not. * @@ -181,11 +179,12 @@ int e2fsck_pass1_check_device_inode(ext2_filsys fs EXT2FS_ATTR((unused)), * Check to make sure a symlink inode is real. Returns 1 if the symlink * checks out, 0 if not. */ -int e2fsck_pass1_check_symlink(ext2_filsys fs, ext2_ino_t ino, - struct ext2_inode *inode, char *buf) +static int check_symlink(e2fsck_t ctx, struct problem_context *pctx, + ext2_ino_t ino, struct ext2_inode *inode, char *buf) { unsigned int buflen; unsigned int len; + blk64_t blk; if ((inode->i_size_high || inode->i_size == 0) || (inode->i_flags & EXT2_INDEX_FL)) @@ -196,7 +195,7 @@ int e2fsck_pass1_check_symlink(ext2_filsys fs, ext2_ino_t ino, if (inode->i_flags & EXT4_EXTENTS_FL) return 0; - if (ext2fs_inline_data_size(fs, ino, &inline_size)) + if (ext2fs_inline_data_size(ctx->fs, ino, &inline_size)) return 0; if (inode->i_size != inline_size) return 0; @@ -213,11 +212,10 @@ int e2fsck_pass1_check_symlink(ext2_filsys fs, ext2_ino_t ino, ext2_extent_handle_t handle; struct ext2_extent_info info; struct ext2fs_extent extent; - blk64_t blk; int i; if (inode->i_flags & EXT4_EXTENTS_FL) { - if (ext2fs_extent_open2(fs, ino, inode, &handle)) + if (ext2fs_extent_open2(ctx->fs, ino, inode, &handle)) return 0; if (ext2fs_extent_get_info(handle, &info) || (info.num_entries != 1) || @@ -242,29 +240,53 @@ int e2fsck_pass1_check_symlink(ext2_filsys fs, ext2_ino_t ino, return 0; } - if (blk < fs->super->s_first_data_block || - blk >= ext2fs_blocks_count(fs->super)) + if (blk < ctx->fs->super->s_first_data_block || + blk >= ext2fs_blocks_count(ctx->fs->super)) return 0; - if (io_channel_read_blk64(fs->io, blk, 1, buf)) + if (io_channel_read_blk64(ctx->fs->io, blk, 1, buf)) return 0; - buflen = fs->blocksize; + buflen = ctx->fs->blocksize; } if (inode->i_flags & EXT4_ENCRYPT_FL) len = ext2fs_le16_to_cpu(*(__u16 *)buf) + 2; - else + else { len = strnlen(buf, buflen); + /* Add missing NUL terminator at end of symlink (LU-1540), + * but only offer to fix this in pass1, not from pass2. */ + if (len > inode->i_size && pctx != NULL && + fix_problem(ctx, PR_1_SYMLINK_NUL, pctx)) { + buf[inode->i_size] = '\0'; + if (ext2fs_is_fast_symlink(inode)) { + e2fsck_write_inode(ctx, ino, + inode, "check_ext_attr"); + } else { + if (io_channel_write_blk64(ctx->fs->io, + blk, 1, buf)) + return 0; + } + len = inode->i_size; + } + } + if (len >= buflen) return 0; if (len != inode->i_size) return 0; + return 1; } +int e2fsck_pass1_check_symlink(e2fsck_t ctx, ext2_ino_t ino, + struct ext2_inode *inode, char *buf) +{ + return check_symlink(ctx, NULL, ino, inode, buf); +} + /* * If the extents or inlinedata flags are set on the inode, offer to clear 'em. */ @@ -339,7 +361,7 @@ static problem_t check_large_ea_inode(e2fsck_t ctx, blk64_t *quota_blocks) { struct ext2_inode inode; - __u32 hash; + __u32 hash, signed_hash; errcode_t retval; /* Check if inode is within valid range */ @@ -351,7 +373,8 @@ static problem_t check_large_ea_inode(e2fsck_t ctx, e2fsck_read_inode(ctx, entry->e_value_inum, &inode, "pass1"); - retval = ext2fs_ext_attr_hash_entry2(ctx->fs, entry, NULL, &hash); + retval = ext2fs_ext_attr_hash_entry3(ctx->fs, entry, NULL, &hash, + &signed_hash); if (retval) { com_err("check_large_ea_inode", retval, _("while hashing entry with e_value_inum = %u"), @@ -359,7 +382,7 @@ static problem_t check_large_ea_inode(e2fsck_t ctx, fatal_error(ctx, 0); } - if (hash == entry->e_hash) { + if ((hash == entry->e_hash) || (signed_hash == entry->e_hash)) { *quota_blocks = size_to_quota_blocks(ctx->fs, entry->e_value_size); } else { @@ -385,8 +408,10 @@ static problem_t check_large_ea_inode(e2fsck_t ctx, pctx->num = entry->e_value_inum; if (fix_problem(ctx, PR_1_ATTR_SET_EA_INODE_FL, pctx)) { inode.i_flags |= EXT4_EA_INODE_FL; + e2fsck_pass1_fix_lock(ctx); ext2fs_write_inode(ctx->fs, entry->e_value_inum, &inode); + e2fsck_pass1_fix_unlock(ctx); } else { return PR_1_ATTR_NO_EA_INODE_FL; } @@ -397,16 +422,15 @@ static problem_t check_large_ea_inode(e2fsck_t ctx, static void inc_ea_inode_refs(e2fsck_t ctx, struct problem_context *pctx, struct ext2_ext_attr_entry *first, void *end) { - struct ext2_ext_attr_entry *entry; + struct ext2_ext_attr_entry *entry = first; + struct ext2_ext_attr_entry *np = EXT2_EXT_ATTR_NEXT(entry); - for (entry = first; - (void *)entry < end && !EXT2_EXT_IS_LAST_ENTRY(entry); - entry = EXT2_EXT_ATTR_NEXT(entry)) { + while ((void *) entry < end && (void *) np < end && + !EXT2_EXT_IS_LAST_ENTRY(entry)) { if (!entry->e_value_inum) - continue; + goto next; if (!ctx->ea_inode_refs) { - pctx->errcode = ea_refcount_create(0, - &ctx->ea_inode_refs); + pctx->errcode = ea_refcount_create(&ctx->ea_inode_refs); if (pctx->errcode) { pctx->num = 4; fix_problem(ctx, PR_1_ALLOCATE_REFCOUNT, pctx); @@ -416,6 +440,9 @@ static void inc_ea_inode_refs(e2fsck_t ctx, struct problem_context *pctx, } ea_refcount_increment(ctx->ea_inode_refs, entry->e_value_inum, 0); + next: + entry = np; + np = EXT2_EXT_ATTR_NEXT(entry); } } @@ -434,13 +461,13 @@ static void check_ea_in_inode(e2fsck_t ctx, struct problem_context *pctx, ea_ibody_quota->inodes = 0; inode = (struct ext2_inode_large *) pctx->inode; - storage_size = EXT2_INODE_SIZE(ctx->fs->super) - EXT2_GOOD_OLD_INODE_SIZE - - inode->i_extra_isize; + storage_size = EXT2_INODE_SIZE(ctx->fs->super) - + EXT2_GOOD_OLD_INODE_SIZE - inode->i_extra_isize; header = ((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE + inode->i_extra_isize; end = header + storage_size; - start = header + sizeof(__u32); - entry = (struct ext2_ext_attr_entry *) start; + entry = &IHDR(inode)->h_first_entry[0]; + start = (char *)entry; /* scan all entry's headers first */ @@ -500,7 +527,10 @@ static void check_ea_in_inode(e2fsck_t ctx, struct problem_context *pctx, } hash = ext2fs_ext_attr_hash_entry(entry, - start + entry->e_value_offs); + start + entry->e_value_offs); + if (entry->e_hash != 0 && entry->e_hash != hash) + hash = ext2fs_ext_attr_hash_entry_signed(entry, + start + entry->e_value_offs); /* e_hash may be 0 in older inode's ea */ if (entry->e_hash != 0 && entry->e_hash != hash) { @@ -575,7 +605,7 @@ static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx, struct ext2_super_block *sb = ctx->fs->super; struct ext2_inode_large *inode; __u32 *eamagic; - int min, max; + int min, max, dirty = 0; ea_ibody_quota->blocks = 0; ea_ibody_quota->inodes = 0; @@ -603,24 +633,40 @@ static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx, if (!fix_problem(ctx, PR_1_EXTRA_ISIZE, pctx)) return; if (inode->i_extra_isize < min || inode->i_extra_isize > max) - inode->i_extra_isize = sb->s_want_extra_isize; + inode->i_extra_isize = ctx->want_extra_isize; else inode->i_extra_isize = (inode->i_extra_isize + 3) & ~3; - e2fsck_write_inode_full(ctx, pctx->ino, pctx->inode, - EXT2_INODE_SIZE(sb), "pass1"); + dirty = 1; + + goto out; } /* check if there is no place for an EA header */ if (inode->i_extra_isize >= max - sizeof(__u32)) return; - eamagic = (__u32 *) (((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE + - inode->i_extra_isize); - if (*eamagic == EXT2_EXT_ATTR_MAGIC) { - /* it seems inode has an extended attribute(s) in body */ - check_ea_in_inode(ctx, pctx, ea_ibody_quota); + eamagic = &IHDR(inode)->h_magic; + if (*eamagic != EXT2_EXT_ATTR_MAGIC && + (ctx->flags & E2F_FLAG_EXPAND_EISIZE) && + (inode->i_extra_isize < ctx->want_extra_isize)) { + fix_problem(ctx, PR_1_EXPAND_EISIZE, pctx); + memset((char *)inode + EXT2_GOOD_OLD_INODE_SIZE, 0, + EXT2_INODE_SIZE(sb) - EXT2_GOOD_OLD_INODE_SIZE); + inode->i_extra_isize = ctx->want_extra_isize; + dirty = 1; + if (inode->i_extra_isize < ctx->min_extra_isize) + ctx->min_extra_isize = inode->i_extra_isize; } + if (*eamagic == EXT2_EXT_ATTR_MAGIC) + check_ea_in_inode(ctx, pctx, ea_ibody_quota); + + /* Since crtime cannot be set directly from userspace, consider + * very old/future values worse than a bad atime/mtime. */ + if (EXT4_XTIME_FUTURE(ctx, sb, inode->i_crtime, ctx->time_fudge)) + e2fsck_mark_inode_badder(ctx, pctx, PR_1_CRTIME_BAD); + else if (EXT4_XTIME_ANCIENT(ctx, sb, inode->i_crtime, ctx->time_fudge)) + e2fsck_mark_inode_badder(ctx, pctx, PR_1_CRTIME_BAD); /* * If the inode's extended atime (ctime, crtime, mtime) is stored in * the old, invalid format, repair it. @@ -633,7 +679,7 @@ static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx, CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, crtime) || CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, mtime))) { - if (!fix_problem(ctx, PR_1_EA_TIME_OUT_OF_RANGE, pctx)) + if (!fix_problem_bad(ctx, PR_1_EA_TIME_OUT_OF_RANGE, pctx, 2)) return; if (CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, atime)) @@ -644,10 +690,38 @@ static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx, inode->i_crtime_extra &= ~EXT4_EPOCH_MASK; if (CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, mtime)) inode->i_mtime_extra &= ~EXT4_EPOCH_MASK; + dirty = 1; + } + +out: + if (dirty) e2fsck_write_inode_full(ctx, pctx->ino, pctx->inode, EXT2_INODE_SIZE(sb), "pass1"); +} + +static _INLINE_ int is_blocks_used(e2fsck_t ctx, blk64_t block, + unsigned int num) +{ + int retval; + + /* used to avoid duplicate output from below */ + retval = ext2fs_test_block_bitmap_range2_valid(ctx->block_found_map, + block, num); + if (!retval) + return 0; + + retval = ext2fs_test_block_bitmap_range2(ctx->block_found_map, block, num); + if (retval) { + e2fsck_pass1_block_map_r_lock(ctx); + if (ctx->global_ctx) + retval = ext2fs_test_block_bitmap_range2( + ctx->global_ctx->block_found_map, block, num); + e2fsck_pass1_block_map_r_unlock(ctx); + if (retval) + return 0; } + return 1; } /* @@ -680,14 +754,14 @@ static void check_is_really_dir(e2fsck_t ctx, struct problem_context *pctx, LINUX_S_ISLNK(inode->i_mode) || inode->i_block[0] == 0) return; - /* + /* * Check the block numbers in the i_block array for validity: * zero blocks are skipped (but the first one cannot be zero - * see above), other blocks are checked against the first and * max data blocks (from the the superblock) and against the * block bitmap. Any invalid block found means this cannot be * a directory. - * + * * If there are non-zero blocks past the fourth entry, then * this cannot be a device file: we remember that for the next * check. @@ -722,7 +796,7 @@ static void check_is_really_dir(e2fsck_t ctx, struct problem_context *pctx, */ memcpy(&dotdot, inode->i_block, sizeof(dotdot)); memcpy(&de, ((char *)inode->i_block) + EXT4_INLINE_DATA_DOTDOT_SIZE, - EXT2_DIR_REC_LEN(0)); + EXT2_DIR_NAME_LEN(0)); dotdot = ext2fs_le32_to_cpu(dotdot); de.inode = ext2fs_le32_to_cpu(de.inode); de.rec_len = ext2fs_le16_to_cpu(de.rec_len); @@ -755,8 +829,7 @@ static void check_is_really_dir(e2fsck_t ctx, struct problem_context *pctx, if (blk < ctx->fs->super->s_first_data_block || blk >= ext2fs_blocks_count(ctx->fs->super) || - ext2fs_fast_test_block_bitmap2(ctx->block_found_map, - blk)) + is_blocks_used(ctx, blk, 1)) return; /* Invalid block, can't be dir */ } blk = inode->i_block[0]; @@ -877,11 +950,158 @@ static errcode_t recheck_bad_inode_checksum(ext2_filsys fs, ext2_ino_t ino, if (!fix_problem(ctx, PR_1_INODE_ONLY_CSUM_INVALID, pctx)) return 0; + + e2fsck_pass1_fix_lock(ctx); retval = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode, sizeof(inode)); + e2fsck_pass1_fix_unlock(ctx); return retval; } +int e2fsck_pass1_delete_attr(e2fsck_t ctx, struct ext2_inode_large *inode, + struct problem_context *pctx, int needed_size) +{ + struct ext2_ext_attr_header *header; + struct ext2_ext_attr_entry *entry_ino, *entry_blk = NULL, *entry; + char *start, name[4096], block_buf[4096]; + int len, index = EXT2_ATTR_INDEX_USER, entry_size, ea_size; + int in_inode = 1, error; + unsigned int freed_bytes = inode->i_extra_isize; + + entry_ino = &IHDR(inode)->h_first_entry[0]; + start = (char *)entry_ino; + + if (inode->i_file_acl) { + error = ext2fs_read_ext_attr(ctx->fs, inode->i_file_acl, + block_buf); + /* We have already checked this block, shouldn't happen */ + if (error) { + fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, pctx); + return 0; + } + header = BHDR(block_buf); + if (header->h_magic != EXT2_EXT_ATTR_MAGIC) { + fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, pctx); + return 0; + } + + entry_blk = (struct ext2_ext_attr_entry *)(header+1); + } + entry = entry_ino; + len = sizeof(entry->e_name); + entry_size = ext2fs_attr_get_next_attr(entry, index, name, len, 1); + + while (freed_bytes < needed_size) { + if (entry_size && name[0] != '\0') { + pctx->str = name; + if (fix_problem(ctx, PR_1_EISIZE_DELETE_EA, pctx)) { + ea_size = EXT2_EXT_ATTR_LEN(entry->e_name_len) + + EXT2_EXT_ATTR_SIZE(entry->e_value_size); + error = ext2fs_attr_set(ctx->fs, pctx->ino, + (struct ext2_inode *)inode, + index, name, 0, 0, 0); + if (!error) + freed_bytes += ea_size; + } + } + len = sizeof(entry->e_name); + entry_size = ext2fs_attr_get_next_attr(entry, index,name,len,0); + entry = EXT2_EXT_ATTR_NEXT(entry); + if (EXT2_EXT_IS_LAST_ENTRY(entry)) { + if (in_inode) { + entry = entry_blk; + len = sizeof(entry->e_name); + entry_size = ext2fs_attr_get_next_attr(entry, + index, name, len, 1); + in_inode = 0; + } else { + index += 1; + in_inode = 1; + if (!entry && index < EXT2_ATTR_INDEX_MAX) + entry = (struct ext2_ext_attr_entry *)start; + else + return freed_bytes; + } + } + } + + return freed_bytes; +} + +int e2fsck_pass1_expand_eisize(e2fsck_t ctx, struct ext2_inode_large *inode, + struct problem_context *pctx) +{ + int needed_size = 0, retval, ret = EXT2_EXPAND_EISIZE_UNSAFE; + static int message; + +retry: + retval = ext2fs_expand_extra_isize(ctx->fs, pctx->ino, inode, + ctx->want_extra_isize, &ret, + &needed_size); + if (ret & EXT2_EXPAND_EISIZE_NEW_BLOCK) + goto mark_expand_eisize_map; + if (!retval) { + e2fsck_write_inode_full(ctx, pctx->ino, + (struct ext2_inode *)inode, + EXT2_INODE_SIZE(ctx->fs->super), + "pass1"); + return 0; + } + + if (ret & EXT2_EXPAND_EISIZE_NOSPC) { + if (ctx->options & (E2F_OPT_PREEN | E2F_OPT_YES)) { + fix_problem(ctx, PR_1_EA_BLK_NOSPC, pctx); + ctx->flags |= E2F_FLAG_ABORT; + return -1; + } + + if (!message) { + pctx->num = ctx->fs->super->s_min_extra_isize; + fix_problem(ctx, PR_1_EXPAND_EISIZE_WARNING, pctx); + message = 1; + } +delete_EA: + retval = e2fsck_pass1_delete_attr(ctx, inode, pctx, + needed_size); + if (retval >= ctx->want_extra_isize) + goto retry; + + needed_size -= retval; + + /* + * We loop here until either the user deletes EA(s) or + * EXTRA_ISIZE feature is disabled. + */ + if (fix_problem(ctx, PR_1_CLEAR_EXTRA_ISIZE, pctx)) { + ctx->fs->super->s_feature_ro_compat &= + ~EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE; + ext2fs_mark_super_dirty(ctx->fs); + } else { + goto delete_EA; + } + ctx->fs_unexpanded_inodes++; + + /* No EA was deleted, inode cannot be expanded */ + return -1; + } + +mark_expand_eisize_map: + if (!ctx->expand_eisize_map) { + pctx->errcode = ext2fs_allocate_inode_bitmap(ctx->fs, + _("expand extrz isize map"), + &ctx->expand_eisize_map); + if (pctx->errcode) { + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, + pctx); + exit(1); + } + } + + /* Add this inode to the expand_eisize_map */ + ext2fs_mark_inode_bitmap2(ctx->expand_eisize_map, pctx->ino); + return 0; +} + static void reserve_block_for_root_repair(e2fsck_t ctx) { blk64_t blk = 0; @@ -916,9 +1136,11 @@ static void reserve_block_for_lnf_repair(e2fsck_t ctx) return; ext2fs_mark_block_bitmap2(ctx->block_found_map, blk); ctx->lnf_repair_block = blk; + return; } static errcode_t get_inline_data_ea_size(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, size_t *sz) { void *p; @@ -929,7 +1151,8 @@ static errcode_t get_inline_data_ea_size(ext2_filsys fs, ext2_ino_t ino, if (retval) return retval; - retval = ext2fs_xattrs_read(handle); + retval = ext2fs_xattrs_read_inode(handle, + (struct ext2_inode_large *)inode); if (retval) goto err; @@ -942,6 +1165,28 @@ err: return retval; } +int e2fsck_fix_bad_inode(e2fsck_t ctx, struct problem_context *pctx) +{ + __u16 badness; + int rc = 0; + + if (!ctx->inode_badness) + return 0; + + if (ext2fs_icount_fetch(ctx->inode_badness, pctx->ino, &badness)) + return 0; + + if (badness > ctx->inode_badness_threshold) { + __u64 pctx_num_sav = pctx->num; + + pctx->num = badness; + rc = fix_problem_notbad(ctx, PR_1B_INODE_TOOBAD, pctx); + pctx->num = pctx_num_sav; + } + + return rc; +} + static void finish_processing_inode(e2fsck_t ctx, ext2_ino_t ino, struct problem_context *pctx, int failed_csum) @@ -961,8 +1206,10 @@ static void finish_processing_inode(e2fsck_t ctx, ext2_ino_t ino, #define FINISH_INODE_LOOP(ctx, ino, pctx, failed_csum) \ do { \ finish_processing_inode((ctx), (ino), (pctx), (failed_csum)); \ - if ((ctx)->flags & E2F_FLAG_ABORT) \ + if (e2fsck_should_abort(ctx)) { \ + e2fsck_pass1_check_unlock(ctx); \ return; \ + } \ } while (0) static int could_be_block_map(ext2_filsys fs, struct ext2_inode *inode) @@ -1068,16 +1315,20 @@ out: static void pass1_readahead(e2fsck_t ctx, dgrp_t *group, ext2_ino_t *next_ino) { ext2_ino_t inodes_in_group = 0, inodes_per_block, inodes_per_buffer; - dgrp_t start = *group, grp; + dgrp_t start = *group, grp, grp_end = ctx->fs->group_desc_count; blk64_t blocks_to_read = 0; errcode_t err = EXT2_ET_INVALID_ARGUMENT; +#ifdef HAVE_PTHREAD + if (ctx->fs->fs_num_threads > 1) + grp_end = ctx->thread_info.et_group_end; +#endif if (ctx->readahead_kb == 0) goto out; /* Keep iterating groups until we have enough to readahead */ inodes_per_block = EXT2_INODES_PER_BLOCK(ctx->fs->super); - for (grp = start; grp < ctx->fs->group_desc_count; grp++) { + for (grp = start; grp < grp_end; grp++) { if (ext2fs_bg_flags_test(ctx->fs, grp, EXT2_BG_INODE_UNINIT)) continue; inodes_in_group = ctx->fs->super->s_inodes_per_group - @@ -1154,55 +1405,30 @@ static int quota_inum_is_reserved(ext2_filsys fs, ext2_ino_t ino) return 0; } -void e2fsck_pass1(e2fsck_t ctx) +static int e2fsck_should_abort(e2fsck_t ctx) { - int i; - __u64 max_sizes; - ext2_filsys fs = ctx->fs; - ext2_ino_t ino = 0; - struct ext2_inode *inode = NULL; - ext2_inode_scan scan = NULL; - char *block_buf = NULL; -#ifdef RESOURCE_TRACK - struct resource_track rtrack; -#endif - unsigned char frag, fsize; - struct problem_context pctx; - struct scan_callback_struct scan_struct; - struct ext2_super_block *sb = ctx->fs->super; - const char *old_op; - int imagic_fs, extent_fs, inlinedata_fs; - int low_dtime_check = 1; - unsigned int inode_size = EXT2_INODE_SIZE(fs->super); - unsigned int bufsize; - int failed_csum = 0; - ext2_ino_t ino_threshold = 0; - dgrp_t ra_group = 0; - struct ea_quota ea_ibody_quota; - - init_resource_track(&rtrack, ctx->fs->io); - clear_problem_context(&pctx); - - /* If we can do readahead, figure out how many groups to pull in. */ - if (!e2fsck_can_readahead(ctx->fs)) - ctx->readahead_kb = 0; - else if (ctx->readahead_kb == ~0ULL) - ctx->readahead_kb = e2fsck_guess_readahead(ctx->fs); - pass1_readahead(ctx, &ra_group, &ino_threshold); + e2fsck_t global_ctx; - if (!(ctx->options & E2F_OPT_PREEN)) - fix_problem(ctx, PR_1_PASS_HEADER, &pctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return 1; - if (ext2fs_has_feature_dir_index(fs->super) && - !(ctx->options & E2F_OPT_NO)) { - if (ext2fs_u32_list_create(&ctx->dirs_to_hash, 50)) - ctx->dirs_to_hash = 0; + if (ctx->global_ctx) { + global_ctx = ctx->global_ctx; + if (global_ctx->flags & E2F_FLAG_SIGNAL_MASK) + return 1; } + return 0; +} -#ifdef MTRACE - mtrace_print("Pass 1"); -#endif +static void init_ext2_max_sizes() +{ + int i; + __u64 max_sizes; + /* + * Init ext2_max_sizes which will be immutable and shared between + * threads + */ #define EXT2_BPP(bits) (1ULL << ((bits) - 2)) for (i = EXT2_MIN_BLOCK_LOG_SIZE; i <= EXT2_MAX_BLOCK_LOG_SIZE; i++) { @@ -1213,61 +1439,373 @@ void e2fsck_pass1(e2fsck_t ctx) ext2_max_sizes[i - EXT2_MIN_BLOCK_LOG_SIZE] = max_sizes; } #undef EXT2_BPP +} - imagic_fs = ext2fs_has_feature_imagic_inodes(sb); - extent_fs = ext2fs_has_feature_extents(sb); - inlinedata_fs = ext2fs_has_feature_inline_data(sb); +#ifdef HAVE_PTHREAD +/* TODO: tdb needs to be handled properly for multiple threads*/ +static int multiple_threads_supported(e2fsck_t ctx) +{ +#ifdef CONFIG_TDB + unsigned int threshold; + ext2_ino_t num_dirs; + errcode_t retval; + char *tdb_dir; + int enable; - /* - * Allocate bitmaps structures - */ - pctx.errcode = e2fsck_allocate_inode_bitmap(fs, _("in-use inode map"), - EXT2FS_BMAP64_RBTREE, - "inode_used_map", - &ctx->inode_used_map); - if (pctx.errcode) { - pctx.num = 1; - fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); - ctx->flags |= E2F_FLAG_ABORT; - return; + profile_get_string(ctx->profile, "scratch_files", "directory", 0, 0, + &tdb_dir); + profile_get_uint(ctx->profile, "scratch_files", + "numdirs_threshold", 0, 0, &threshold); + profile_get_boolean(ctx->profile, "scratch_files", + "icount", 0, 1, &enable); + + retval = ext2fs_get_num_dirs(ctx->fs, &num_dirs); + if (retval) + num_dirs = 1024; /* Guess */ + + /* tdb is unsupported now */ + if (enable && tdb_dir && !access(tdb_dir, W_OK) && + (!threshold || num_dirs > threshold)) + return 0; +#endif + return 1; +} + +/** + * Even though we could specify number of threads, + * but it might be more than the whole filesystem + * block groups, correct it here. + */ +static void e2fsck_pass1_set_thread_num(e2fsck_t ctx) +{ + unsigned flexbg_size = 1; + ext2_filsys fs = ctx->fs; + int num_threads = ctx->pfs_num_threads; + int max_threads; + + if (num_threads < 1) { + num_threads = 1; + goto out; } - pctx.errcode = e2fsck_allocate_inode_bitmap(fs, - _("directory inode map"), - EXT2FS_BMAP64_AUTODIR, - "inode_dir_map", &ctx->inode_dir_map); - if (pctx.errcode) { - pctx.num = 2; - fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); - ctx->flags |= E2F_FLAG_ABORT; - return; + + if (!multiple_threads_supported(ctx)) { + num_threads = 1; + fprintf(stderr, "Fall through single thread for pass1 " + "because tdb could not handle properly\n"); + goto out; } - pctx.errcode = e2fsck_allocate_inode_bitmap(fs, - _("regular file inode map"), EXT2FS_BMAP64_RBTREE, - "inode_reg_map", &ctx->inode_reg_map); - if (pctx.errcode) { - pctx.num = 6; - fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); - ctx->flags |= E2F_FLAG_ABORT; - return; + + if (ext2fs_has_feature_flex_bg(fs->super)) + flexbg_size = 1 << fs->super->s_log_groups_per_flex; + max_threads = fs->group_desc_count / flexbg_size; + if (max_threads == 0) + max_threads = 1; + if (max_threads > E2FSCK_MAX_THREADS) + max_threads = E2FSCK_MAX_THREADS; + + if (num_threads > max_threads) { + fprintf(stderr, "Use max possible thread num: %d instead\n", + max_threads); + num_threads = max_threads; } - pctx.errcode = e2fsck_allocate_subcluster_bitmap(fs, +out: + ctx->pfs_num_threads = num_threads; + ctx->fs->fs_num_threads = num_threads; +} +#endif + +/* + * We need call mark_table_blocks() before multiple + * thread start, since all known system blocks should be + * marked and checked later. + */ +static errcode_t e2fsck_pass1_prepare(e2fsck_t ctx) +{ + struct problem_context pctx; + ext2_filsys fs = ctx->fs; + unsigned long long readahead_kb; + + init_ext2_max_sizes(); +#ifdef HAVE_PTHREAD + e2fsck_pass1_set_thread_num(ctx); +#endif + /* If we can do readahead, figure out how many groups to pull in. */ + if (!e2fsck_can_readahead(ctx->fs)) + ctx->readahead_kb = 0; + else if (ctx->readahead_kb == ~0ULL) + ctx->readahead_kb = e2fsck_guess_readahead(ctx->fs); + +#ifdef HAVE_PTHREAD + /* don't use more than 1/10 of memory for threads checking */ + readahead_kb = get_memory_size() / (10 * ctx->pfs_num_threads); + /* maybe better disable RA if this is too small? */ + if (ctx->readahead_kb > readahead_kb) + ctx->readahead_kb = readahead_kb; +#endif + clear_problem_context(&pctx); + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_1_PASS_HEADER, &pctx); + + pctx.errcode = e2fsck_allocate_subcluster_bitmap(ctx->fs, _("in-use block map"), EXT2FS_BMAP64_RBTREE, "block_found_map", &ctx->block_found_map); if (pctx.errcode) { pctx.num = 1; fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, &pctx); ctx->flags |= E2F_FLAG_ABORT; - return; + return pctx.errcode; } - pctx.errcode = e2fsck_allocate_block_bitmap(fs, + pctx.errcode = e2fsck_allocate_block_bitmap(ctx->fs, _("metadata block map"), EXT2FS_BMAP64_RBTREE, "block_metadata_map", &ctx->block_metadata_map); if (pctx.errcode) { pctx.num = 1; fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, &pctx); ctx->flags |= E2F_FLAG_ABORT; + return pctx.errcode; + } + + mark_table_blocks(ctx); + pctx.errcode = ext2fs_convert_subcluster_bitmap(ctx->fs, + &ctx->block_found_map); + if (pctx.errcode) { + fix_problem(ctx, PR_1_CONVERT_SUBCLUSTER, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return pctx.errcode; + } + + pctx.errcode = e2fsck_allocate_block_bitmap(ctx->fs, + _("multiply claimed block map"), + EXT2FS_BMAP64_RBTREE, "block_dup_map", + &ctx->block_dup_map); + if (pctx.errcode) { + pctx.num = 3; + fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, + &pctx); + /* Should never get here */ + ctx->flags |= E2F_FLAG_ABORT; + return pctx.errcode; + } + + if (ext2fs_has_feature_mmp(fs->super) && + fs->super->s_mmp_block > fs->super->s_first_data_block && + fs->super->s_mmp_block < ext2fs_blocks_count(fs->super)) + ext2fs_mark_block_bitmap2(ctx->block_found_map, + fs->super->s_mmp_block); +#ifdef HAVE_PTHREAD + pthread_rwlock_init(&ctx->fs_fix_rwlock, NULL); + pthread_rwlock_init(&ctx->fs_block_map_rwlock, NULL); + if (ctx->pfs_num_threads > 1) + ctx->fs_need_locking = 1; +#endif + + return 0; +} + +static void e2fsck_pass1_post(e2fsck_t ctx) +{ + struct problem_context pctx; + ext2_filsys fs = ctx->fs; + char *block_buf; + + if (e2fsck_should_abort(ctx)) + return; + + block_buf = (char *)e2fsck_allocate_memory(ctx, ctx->fs->blocksize * 3, + "block interate buffer"); + reserve_block_for_root_repair(ctx); + reserve_block_for_lnf_repair(ctx); + + /* + * If any extended attribute blocks' reference counts need to + * be adjusted, either up (ctx->refcount_extra), or down + * (ctx->refcount), then fix them. + */ + if (ctx->refcount) { + adjust_extattr_refcount(ctx, ctx->refcount, block_buf, -1); + ea_refcount_free(ctx->refcount); + ctx->refcount = 0; + } + if (ctx->refcount_extra) { + adjust_extattr_refcount(ctx, ctx->refcount_extra, + block_buf, +1); + ea_refcount_free(ctx->refcount_extra); + ctx->refcount_extra = 0; + } + + if (ctx->invalid_bitmaps) + handle_fs_bad_blocks(ctx); + + /* We don't need the block_ea_map any more */ + if (ctx->block_ea_map) { + ext2fs_free_block_bitmap(ctx->block_ea_map); + ctx->block_ea_map = 0; + } + + if (ctx->flags & E2F_FLAG_RESIZE_INODE) { + struct ext2_inode *inode; + int inode_size = EXT2_INODE_SIZE(fs->super); + inode = e2fsck_allocate_memory(ctx, inode_size, + "scratch inode"); + + clear_problem_context(&pctx); + pctx.errcode = ext2fs_create_resize_inode(fs); + if (pctx.errcode) { + if (!fix_problem(ctx, PR_1_RESIZE_INODE_CREATE, + &pctx)) { + ctx->flags |= E2F_FLAG_ABORT; + ext2fs_free_mem(&inode); + ext2fs_free_mem(&block_buf); + return; + } + pctx.errcode = 0; + } + if (!pctx.errcode) { + e2fsck_read_inode(ctx, EXT2_RESIZE_INO, inode, + "recreate inode"); + inode->i_mtime = ctx->now; + e2fsck_write_inode(ctx, EXT2_RESIZE_INO, inode, + "recreate inode"); + } + ctx->flags &= ~E2F_FLAG_RESIZE_INODE; + ext2fs_free_mem(&inode); + } + + if (ctx->flags & E2F_FLAG_RESTART) { + ext2fs_free_mem(&block_buf); + return; + } + + if (ctx->block_dup_map) { + if (!(ctx->flags & E2F_FLAG_DUP_BLOCK)) { + ext2fs_free_mem(&block_buf); + return; + } + if (ctx->options & E2F_OPT_PREEN) { + clear_problem_context(&pctx); + fix_problem(ctx, PR_1_DUP_BLOCKS_PREENSTOP, &pctx); + } + e2fsck_pass1_dupblocks(ctx, block_buf); + ext2fs_free_mem(&block_buf); + ctx->flags &= ~E2F_FLAG_DUP_BLOCK; + } + + ctx->flags |= E2F_FLAG_ALLOC_OK; +} + + +/* + * Lustre FS creates special inodes - precreated objects. + * They are zero-sized and have special attributes: + * mode |= S_ISUID | S_ISGID; + * valid |= LA_ATIME | LA_MTIME | LA_CTIME; + * atime = 0; + * mtime = 0; + * ctime = 0; + */ +static int precreated_object(struct ext2_inode *inode) +{ + if (((inode->i_mode & (S_ISUID | S_ISGID)) == (S_ISUID | S_ISGID)) && + inode->i_ctime == 0) + return 1; + return 0; +} + +void e2fsck_pass1_run(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t ino = 0; + struct ext2_inode *inode = NULL; + ext2_inode_scan scan = NULL; + char *block_buf = NULL; +#ifdef RESOURCE_TRACK + struct resource_track rtrack; +#endif + unsigned char frag, fsize; + struct problem_context pctx; + struct scan_callback_struct scan_struct; + struct ext2_super_block *sb = ctx->fs->super; + const char *old_op; + const char *eop_next_inode = _("getting next inode from scan"); + int imagic_fs, extent_fs, inlinedata_fs, casefold_fs; + int low_dtime_check = 1; + unsigned int inode_size = EXT2_INODE_SIZE(fs->super); + unsigned int bufsize; + int failed_csum = 0; + ext2_ino_t ino_threshold = 0; + dgrp_t ra_group = 0; + struct ea_quota ea_ibody_quota; + struct process_inode_block *inodes_to_process; + int process_inode_count, check_mmp = 0; + e2fsck_t global_ctx = ctx->global_ctx ? ctx->global_ctx : ctx; + int inode_exp = 0; + + init_resource_track(&rtrack, ctx->fs->io); + clear_problem_context(&pctx); + + pass1_readahead(ctx, &ra_group, &ino_threshold); + if (ext2fs_has_feature_dir_index(fs->super) && + !(ctx->options & E2F_OPT_NO)) { + if (ext2fs_u32_list_create(&ctx->dirs_to_hash, 50)) + ctx->dirs_to_hash = 0; + } + +#ifdef MTRACE + mtrace_print("Pass 1"); +#endif + + imagic_fs = ext2fs_has_feature_imagic_inodes(sb); + extent_fs = ext2fs_has_feature_extents(sb); + inlinedata_fs = ext2fs_has_feature_inline_data(sb); + casefold_fs = ext2fs_has_feature_casefold(sb); + + /* + * Allocate bitmaps structures + */ + pctx.errcode = e2fsck_allocate_inode_bitmap(fs, _("in-use inode map"), + EXT2FS_BMAP64_RBTREE, + "inode_used_map", + &ctx->inode_used_map); + if (pctx.errcode) { + pctx.num = 1; + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; return; } + pctx.errcode = e2fsck_allocate_inode_bitmap(fs, + _("directory inode map"), + ctx->global_ctx ? EXT2FS_BMAP64_RBTREE : + EXT2FS_BMAP64_AUTODIR, + "inode_dir_map", &ctx->inode_dir_map); + if (pctx.errcode) { + pctx.num = 2; + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + pctx.errcode = e2fsck_allocate_inode_bitmap(fs, + _("regular file inode map"), EXT2FS_BMAP64_RBTREE, + "inode_reg_map", &ctx->inode_reg_map); + if (pctx.errcode) { + pctx.num = 6; + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if (casefold_fs) { + pctx.errcode = + e2fsck_allocate_inode_bitmap(fs, + _("inode casefold map"), + EXT2FS_BMAP64_RBTREE, + "inode_casefold_map", + &ctx->inode_casefold_map); + if (pctx.errcode) { + pctx.num = 1; + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + } pctx.errcode = e2fsck_setup_icount(ctx, "inode_link_info", 0, NULL, &ctx->inode_link_info); if (pctx.errcode) { @@ -1309,16 +1847,8 @@ void e2fsck_pass1(e2fsck_t ctx) } } - mark_table_blocks(ctx); - pctx.errcode = ext2fs_convert_subcluster_bitmap(fs, - &ctx->block_found_map); - if (pctx.errcode) { - fix_problem(ctx, PR_1_CONVERT_SUBCLUSTER, &pctx); - ctx->flags |= E2F_FLAG_ABORT; - goto endit; - } block_buf = (char *) e2fsck_allocate_memory(ctx, fs->blocksize * 3, - "block interate buffer"); + "block iterate buffer"); if (EXT2_INODE_SIZE(fs->super) == EXT2_GOOD_OLD_INODE_SIZE) e2fsck_use_inode_shortcuts(ctx, 1); e2fsck_intercept_block_allocations(ctx); @@ -1336,38 +1866,77 @@ void e2fsck_pass1(e2fsck_t ctx) ctx->stashed_inode = inode; scan_struct.ctx = ctx; scan_struct.block_buf = block_buf; + scan_struct.inodes_to_process = inodes_to_process; + scan_struct.process_inode_count = &process_inode_count; ext2fs_set_inode_callback(scan, scan_callback, &scan_struct); if (ctx->progress && ((ctx->progress)(ctx, 1, 0, ctx->fs->group_desc_count))) goto endit; - if ((fs->super->s_wtime < fs->super->s_inodes_count) || - (fs->super->s_mtime < fs->super->s_inodes_count) || + if ((fs->super->s_wtime && + fs->super->s_wtime < fs->super->s_inodes_count) || + (fs->super->s_mtime && + fs->super->s_mtime < fs->super->s_inodes_count) || (fs->super->s_mkfs_time && fs->super->s_mkfs_time < fs->super->s_inodes_count)) low_dtime_check = 0; - if (ext2fs_has_feature_mmp(fs->super) && - fs->super->s_mmp_block > fs->super->s_first_data_block && - fs->super->s_mmp_block < ext2fs_blocks_count(fs->super)) - ext2fs_mark_block_bitmap2(ctx->block_found_map, - fs->super->s_mmp_block); - /* Set up ctx->lost_and_found if possible */ (void) e2fsck_get_lost_and_found(ctx, 0); +#ifdef HAVE_PTHREAD + if (ctx->global_ctx) { + if (ctx->options & E2F_OPT_DEBUG && + ctx->options & E2F_OPT_MULTITHREAD) + log_out(ctx, "jumping to group %u\n", + ctx->thread_info.et_group_start); + pctx.errcode = ext2fs_inode_scan_goto_blockgroup(scan, + ctx->thread_info.et_group_start); + if (pctx.errcode) { + fix_problem(ctx, PR_1_PASS_HEADER, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + goto endit; + } + } +#endif + while (1) { - if (ino % (fs->super->s_inodes_per_group * 4) == 1) { + check_mmp = 0; + e2fsck_pass1_check_lock(ctx); +#ifdef HAVE_PTHREAD + if (!global_ctx->mmp_update_thread) { + e2fsck_pass1_block_map_w_lock(ctx); + if (!global_ctx->mmp_update_thread) { + global_ctx->mmp_update_thread = + ctx->thread_info.et_thread_index + 1; + check_mmp = 1; + } + e2fsck_pass1_block_map_w_unlock(ctx); + } + + /* only one active thread could update mmp block. */ + e2fsck_pass1_block_map_r_lock(ctx); + if (global_ctx->mmp_update_thread == + ctx->thread_info.et_thread_index + 1) + check_mmp = 1; + e2fsck_pass1_block_map_r_unlock(ctx); +#else + check_mmp = 1; +#endif + + if (check_mmp && (ino % (fs->super->s_inodes_per_group * 4) == 1)) { if (e2fsck_mmp_update(fs)) fatal_error(ctx, 0); } - old_op = ehandler_operation(_("getting next inode from scan")); + old_op = ehandler_operation(eop_next_inode); pctx.errcode = ext2fs_get_next_inode_full(scan, &ino, inode, inode_size); if (ino > ino_threshold) pass1_readahead(ctx, &ra_group, &ino_threshold); ehandler_operation(old_op); - if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + if (e2fsck_should_abort(ctx)) { + e2fsck_pass1_check_unlock(ctx); goto endit; + } if (pctx.errcode == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE) { /* * If badblocks says badblocks is bad, offer to clear @@ -1388,33 +1957,49 @@ void e2fsck_pass1(e2fsck_t ctx) fix_problem(ctx, PR_1_ISCAN_ERROR, &pctx); ctx->flags |= E2F_FLAG_ABORT; + e2fsck_pass1_check_unlock(ctx); goto endit; - } + } else + ctx->flags |= E2F_FLAG_RESTART; err = ext2fs_inode_scan_goto_blockgroup(scan, 0); if (err) { fix_problem(ctx, PR_1_ISCAN_ERROR, &pctx); ctx->flags |= E2F_FLAG_ABORT; + e2fsck_pass1_check_unlock(ctx); goto endit; } + e2fsck_pass1_check_unlock(ctx); continue; } if (!ctx->inode_bb_map) alloc_bb_map(ctx); ext2fs_mark_inode_bitmap2(ctx->inode_bb_map, ino); ext2fs_mark_inode_bitmap2(ctx->inode_used_map, ino); + e2fsck_pass1_check_unlock(ctx); continue; } + if (pctx.errcode == EXT2_ET_SCAN_FINISHED) { + e2fsck_pass1_check_unlock(ctx); + break; + } if (pctx.errcode && pctx.errcode != EXT2_ET_INODE_CSUM_INVALID && pctx.errcode != EXT2_ET_INODE_IS_GARBAGE) { fix_problem(ctx, PR_1_ISCAN_ERROR, &pctx); ctx->flags |= E2F_FLAG_ABORT; + e2fsck_pass1_check_unlock(ctx); goto endit; } - if (!ino) + if (!ino) { + e2fsck_pass1_check_unlock(ctx); break; + } +#ifdef HAVE_PTHREAD + if (ctx->global_ctx) + ctx->thread_info.et_inode_number++; +#endif pctx.ino = ino; pctx.inode = inode; ctx->stashed_ino = ino; @@ -1463,6 +2048,7 @@ void e2fsck_pass1(e2fsck_t ctx) pctx.num = inode->i_links_count; fix_problem(ctx, PR_1_ICOUNT_STORE, &pctx); ctx->flags |= E2F_FLAG_ABORT; + e2fsck_pass1_check_unlock(ctx); goto endit; } } else if ((ino >= EXT2_FIRST_INODE(fs->super)) && @@ -1477,9 +2063,19 @@ void e2fsck_pass1(e2fsck_t ctx) } } FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum); + e2fsck_pass1_check_unlock(ctx); continue; } + if ((inode->i_flags & EXT4_CASEFOLD_FL) && + ((!LINUX_S_ISDIR(inode->i_mode) && + fix_problem(ctx, PR_1_CASEFOLD_NONDIR, &pctx)) || + (!casefold_fs && + fix_problem(ctx, PR_1_CASEFOLD_FEATURE, &pctx)))) { + inode->i_flags &= ~EXT4_CASEFOLD_FL; + e2fsck_write_inode(ctx, ino, inode, "pass1"); + } + /* Conflicting inlinedata/extents inode flags? */ if ((inode->i_flags & EXT4_INLINE_DATA_FL) && (inode->i_flags & EXT4_EXTENTS_FL)) { @@ -1488,6 +2084,7 @@ void e2fsck_pass1(e2fsck_t ctx) &pctx); if (res < 0) { /* skip FINISH_INODE_LOOP */ + e2fsck_pass1_check_unlock(ctx); continue; } } @@ -1497,15 +2094,19 @@ void e2fsck_pass1(e2fsck_t ctx) (ino >= EXT2_FIRST_INODE(fs->super))) { size_t size = 0; - pctx.errcode = get_inline_data_ea_size(fs, ino, &size); + pctx.errcode = get_inline_data_ea_size(fs, ino, inode, + &size); if (!pctx.errcode && fix_problem(ctx, PR_1_INLINE_DATA_FEATURE, &pctx)) { + e2fsck_pass1_fix_lock(ctx); ext2fs_set_feature_inline_data(sb); ext2fs_mark_super_dirty(fs); + e2fsck_pass1_fix_unlock(ctx); inlinedata_fs = 1; } else if (fix_problem(ctx, PR_1_INLINE_DATA_SET, &pctx)) { e2fsck_clear_inode(ctx, ino, inode, 0, "pass1"); /* skip FINISH_INODE_LOOP */ + e2fsck_pass1_check_unlock(ctx); continue; } } @@ -1520,7 +2121,7 @@ void e2fsck_pass1(e2fsck_t ctx) flags = fs->flags; if (failed_csum) fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS; - err = get_inline_data_ea_size(fs, ino, &size); + err = get_inline_data_ea_size(fs, ino, inode, &size); fs->flags = (flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) | (fs->flags & ~EXT2_FLAG_IGNORE_CSUM_ERRORS); @@ -1550,6 +2151,7 @@ void e2fsck_pass1(e2fsck_t ctx) if (err) { pctx.errcode = err; ctx->flags |= E2F_FLAG_ABORT; + e2fsck_pass1_check_unlock(ctx); goto endit; } inode->i_flags &= ~EXT4_INLINE_DATA_FL; @@ -1564,6 +2166,7 @@ void e2fsck_pass1(e2fsck_t ctx) /* Some other kind of non-xattr error? */ pctx.errcode = err; ctx->flags |= E2F_FLAG_ABORT; + e2fsck_pass1_check_unlock(ctx); goto endit; } } @@ -1589,9 +2192,11 @@ void e2fsck_pass1(e2fsck_t ctx) if ((ext2fs_extent_header_verify(inode->i_block, sizeof(inode->i_block)) == 0) && fix_problem(ctx, PR_1_EXTENT_FEATURE, &pctx)) { + e2fsck_pass1_fix_lock(ctx); ext2fs_set_feature_extents(sb); ext2fs_mark_super_dirty(fs); extent_fs = 1; + e2fsck_pass1_fix_unlock(ctx); } else if (fix_problem(ctx, PR_1_EXTENTS_SET, &pctx)) { clear_inode: e2fsck_clear_inode(ctx, ino, inode, 0, "pass1"); @@ -1599,6 +2204,7 @@ void e2fsck_pass1(e2fsck_t ctx) ext2fs_mark_inode_bitmap2(ctx->inode_used_map, ino); /* skip FINISH_INODE_LOOP */ + e2fsck_pass1_check_unlock(ctx); continue; } } @@ -1619,6 +2225,7 @@ void e2fsck_pass1(e2fsck_t ctx) void *ehp; #ifdef WORDS_BIGENDIAN __u32 tmp_block[EXT2_N_BLOCKS]; + int i; for (i = 0; i < EXT2_N_BLOCKS; i++) tmp_block[i] = ext2fs_swab32(inode->i_block[i]); @@ -1636,6 +2243,12 @@ void e2fsck_pass1(e2fsck_t ctx) #endif e2fsck_write_inode(ctx, ino, inode, "pass1"); failed_csum = 0; + } else { + /* Consider an inode in extent fs w/o extents + * at least a bit suspect. It only matters if + * the inode has several other problems. */ + e2fsck_mark_inode_bad(ctx, &pctx, + PR_1_UNSET_EXTENT_FL); } } @@ -1653,12 +2266,16 @@ void e2fsck_pass1(e2fsck_t ctx) failed_csum = 0; } - pctx.errcode = ext2fs_copy_bitmap(ctx->block_found_map, - &pb.fs_meta_blocks); + e2fsck_pass1_block_map_r_lock(ctx); + pctx.errcode = ext2fs_copy_bitmap(ctx->global_ctx ? + ctx->global_ctx->block_found_map : + ctx->block_found_map, &pb.fs_meta_blocks); + e2fsck_pass1_block_map_r_unlock(ctx); if (pctx.errcode) { pctx.num = 4; fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, &pctx); ctx->flags |= E2F_FLAG_ABORT; + e2fsck_pass1_check_unlock(ctx); goto endit; } pb.ino = EXT2_BAD_INO; @@ -1676,16 +2293,19 @@ void e2fsck_pass1(e2fsck_t ctx) if (pctx.errcode) { fix_problem(ctx, PR_1_BLOCK_ITERATE, &pctx); ctx->flags |= E2F_FLAG_ABORT; + e2fsck_pass1_check_unlock(ctx); goto endit; } if (pb.bbcheck) if (!fix_problem(ctx, PR_1_BBINODE_BAD_METABLOCK_PROMPT, &pctx)) { ctx->flags |= E2F_FLAG_ABORT; + e2fsck_pass1_check_unlock(ctx); goto endit; } ext2fs_mark_inode_bitmap2(ctx->inode_used_map, ino); clear_problem_context(&pctx); FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum); + e2fsck_pass1_check_unlock(ctx); continue; } else if (ino == EXT2_ROOT_INO) { /* @@ -1727,6 +2347,7 @@ void e2fsck_pass1(e2fsck_t ctx) } check_blocks(ctx, &pctx, block_buf, NULL); FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum); + e2fsck_pass1_check_unlock(ctx); continue; } if ((inode->i_links_count || @@ -1754,6 +2375,7 @@ void e2fsck_pass1(e2fsck_t ctx) } check_blocks(ctx, &pctx, block_buf, NULL); FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum); + e2fsck_pass1_check_unlock(ctx); continue; } if ((inode->i_links_count || @@ -1767,6 +2389,32 @@ void e2fsck_pass1(e2fsck_t ctx) inode_size, "pass1"); failed_csum = 0; } + } else if (ino == fs->super->s_orphan_file_inum) { + ext2fs_mark_inode_bitmap2(ctx->inode_used_map, ino); + if (ext2fs_has_feature_orphan_file(fs->super)) { + if (!LINUX_S_ISREG(inode->i_mode) && + fix_problem(ctx, PR_1_ORPHAN_FILE_BAD_MODE, + &pctx)) { + inode->i_mode = LINUX_S_IFREG; + e2fsck_write_inode(ctx, ino, inode, + "pass1"); + failed_csum = 0; + } + check_blocks(ctx, &pctx, block_buf, NULL); + FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum); + continue; + } + if ((inode->i_links_count || + inode->i_blocks || inode->i_block[0]) && + fix_problem(ctx, PR_1_ORPHAN_FILE_NOT_CLEAR, + &pctx)) { + memset(inode, 0, inode_size); + ext2fs_icount_store(ctx->inode_link_info, ino, + 0); + e2fsck_write_inode_full(ctx, ino, inode, + inode_size, "pass1"); + failed_csum = 0; + } } else if (ino < EXT2_FIRST_INODE(fs->super)) { problem_t problem = 0; @@ -1792,11 +2440,13 @@ void e2fsck_pass1(e2fsck_t ctx) } check_blocks(ctx, &pctx, block_buf, NULL); FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum); + e2fsck_pass1_check_unlock(ctx); continue; } if (!inode->i_links_count) { FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum); + e2fsck_pass1_check_unlock(ctx); continue; } /* @@ -1827,18 +2477,21 @@ void e2fsck_pass1(e2fsck_t ctx) frag = fsize = 0; } + /* Fixed in pass2, e2fsck_process_bad_inode(). */ if (inode->i_faddr || frag || fsize || (!ext2fs_has_feature_largedir(fs->super) && - (LINUX_S_ISDIR(inode->i_mode) && inode->i_size_high))) - mark_inode_bad(ctx, ino); + LINUX_S_ISDIR(inode->i_mode) && inode->i_size_high)) + e2fsck_mark_inode_bad(ctx, &pctx, + PR_2_DIR_SIZE_HIGH_ZERO); if ((fs->super->s_creator_os != EXT2_OS_HURD) && !ext2fs_has_feature_64bit(fs->super) && inode->osd2.linux2.l_i_file_acl_high != 0) - mark_inode_bad(ctx, ino); + e2fsck_mark_inode_bad(ctx, &pctx, + PR_2_I_FILE_ACL_HI_ZERO); if ((fs->super->s_creator_os != EXT2_OS_HURD) && !ext2fs_has_feature_huge_file(fs->super) && (inode->osd2.linux2.l_i_blocks_hi != 0)) - mark_inode_bad(ctx, ino); + e2fsck_mark_inode_bad(ctx, &pctx, PR_2_BLOCKS_HI_ZERO); if (inode->i_flags & EXT2_IMAGIC_FL) { if (imagic_fs) { if (!ctx->inode_imagic_map) @@ -1871,12 +2524,19 @@ void e2fsck_pass1(e2fsck_t ctx) failed_csum = 0; } + if ((inode->i_flags & EXT4_ENCRYPT_FL) && + add_encrypted_file(ctx, &pctx) < 0) + goto clear_inode; + + if (casefold_fs && inode->i_flags & EXT4_CASEFOLD_FL) + ext2fs_mark_inode_bitmap2(ctx->inode_casefold_map, ino); + if (LINUX_S_ISDIR(inode->i_mode)) { ext2fs_mark_inode_bitmap2(ctx->inode_dir_map, ino); e2fsck_add_dir_info(ctx, ino, 0); ctx->fs_directory_count++; - if (inode->i_flags & EXT4_ENCRYPT_FL) - add_encrypted_dir(ctx, ino); + if (inode->i_flags & EXT4_CASEFOLD_FL) + add_casefolded_dir(ctx, ino); } else if (LINUX_S_ISREG (inode->i_mode)) { ext2fs_mark_inode_bitmap2(ctx->inode_reg_map, ino); ctx->fs_regular_count++; @@ -1893,18 +2553,19 @@ void e2fsck_pass1(e2fsck_t ctx) check_size(ctx, &pctx); ctx->fs_blockdev_count++; } else if (LINUX_S_ISLNK (inode->i_mode) && - e2fsck_pass1_check_symlink(fs, ino, inode, - block_buf)) { + check_symlink(ctx, &pctx, ino, inode, block_buf)) { check_immutable(ctx, &pctx); ctx->fs_symlinks_count++; if (inode->i_flags & EXT4_INLINE_DATA_FL) { FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum); + e2fsck_pass1_check_unlock(ctx); continue; } else if (ext2fs_is_fast_symlink(inode)) { ctx->fs_fast_symlinks_count++; check_blocks(ctx, &pctx, block_buf, &ea_ibody_quota); FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum); + e2fsck_pass1_check_unlock(ctx); continue; } } @@ -1920,8 +2581,37 @@ void e2fsck_pass1(e2fsck_t ctx) check_immutable(ctx, &pctx); check_size(ctx, &pctx); ctx->fs_sockets_count++; - } else - mark_inode_bad(ctx, ino); + } else { + e2fsck_mark_inode_bad(ctx, &pctx, PR_2_BAD_MODE); + } + + /* Future atime/mtime may be valid in rare cases, but are more + * likely to indicate corruption. Don't try to fix timestamps, + * but take into consideration whether inode is corrupted. If + * no other problems with the inode, probably it is OK. */ + if (EXT4_XTIME_FUTURE(ctx, sb, inode->i_atime, ctx->time_fudge)) + e2fsck_mark_inode_bad(ctx, &pctx, PR_1_INODE_BAD_TIME); + if (EXT4_XTIME_FUTURE(ctx, sb, inode->i_mtime, ctx->time_fudge)) + e2fsck_mark_inode_bad(ctx, &pctx, PR_1_INODE_BAD_TIME); + + /* Since ctime cannot be set directly from userspace, consider + * very old/future values worse than a bad atime/mtime. Same for + * crtime, but it is checked in check_inode_extra_space(). */ + if (EXT4_XTIME_FUTURE(ctx, sb, inode->i_ctime, ctx->time_fudge)) + e2fsck_mark_inode_badder(ctx, &pctx, + PR_1_INODE_BAD_TIME); + else if (!precreated_object(inode) && + EXT4_XTIME_ANCIENT(ctx, sb, inode->i_ctime, + ctx->time_fudge)) + e2fsck_mark_inode_badder(ctx, &pctx, + PR_1_INODE_BAD_TIME); + + /* no restart if clearing bad inode before block processing */ + if (e2fsck_fix_bad_inode(ctx, &pctx)) { + e2fsck_clear_inode(ctx, ino, inode, 0, "pass1"); + goto next_unlock; + } + if (!(inode->i_flags & EXT4_EXTENTS_FL) && !(inode->i_flags & EXT4_INLINE_DATA_FL)) { if (inode->i_block[EXT2_IND_BLOCK]) @@ -1939,135 +2629,1148 @@ void e2fsck_pass1(e2fsck_t ctx) ext2fs_file_acl_block(fs, inode))) { struct process_inode_block *itp; - itp = &inodes_to_process[process_inode_count]; - itp->ino = ino; - itp->ea_ibody_quota = ea_ibody_quota; - if (inode_size < sizeof(struct ext2_inode_large)) - memcpy(&itp->inode, inode, inode_size); - else - memcpy(&itp->inode, inode, sizeof(itp->inode)); - process_inode_count++; - } else - check_blocks(ctx, &pctx, block_buf, &ea_ibody_quota); + itp = &inodes_to_process[process_inode_count]; + itp->ino = ino; + itp->ea_ibody_quota = ea_ibody_quota; + if (inode_size < sizeof(struct ext2_inode_large)) + memcpy(&itp->inode, inode, inode_size); + else + memcpy(&itp->inode, inode, sizeof(itp->inode)); + process_inode_count++; + } else + check_blocks(ctx, &pctx, block_buf, &ea_ibody_quota); + + FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum); + + if (ctx->flags & E2F_FLAG_EXPAND_EISIZE) { + struct ext2_inode_large *inode_l; + + inode_l = (struct ext2_inode_large *)inode; + + if (inode_l->i_extra_isize < ctx->want_extra_isize) { + fix_problem(ctx, PR_1_EXPAND_EISIZE, &pctx); + inode_exp = e2fsck_pass1_expand_eisize(ctx, + inode_l, + &pctx); + } + if ((inode_l->i_extra_isize < ctx->min_extra_isize) && + inode_exp == 0) + ctx->min_extra_isize = inode_l->i_extra_isize; + } + + if (e2fsck_should_abort(ctx)) { + e2fsck_pass1_check_unlock(ctx); + goto endit; + } + + if (process_inode_count >= ctx->process_inode_size) { + process_inodes(ctx, block_buf, inodes_to_process, + &process_inode_count); + + if (e2fsck_should_abort(ctx)) { + e2fsck_pass1_check_unlock(ctx); + goto endit; + } + } + next_unlock: + e2fsck_pass1_check_unlock(ctx); + } + process_inodes(ctx, block_buf, inodes_to_process, + &process_inode_count); + ext2fs_close_inode_scan(scan); + scan = NULL; + + if (ctx->ea_block_quota_blocks) { + ea_refcount_free(ctx->ea_block_quota_blocks); + ctx->ea_block_quota_blocks = 0; + } + + if (ctx->ea_block_quota_inodes) { + ea_refcount_free(ctx->ea_block_quota_inodes); + ctx->ea_block_quota_inodes = 0; + } + + if (ctx->flags & E2F_FLAG_RESTART) { + /* + * Only the master copy of the superblock and block + * group descriptors are going to be written during a + * restart, so set the superblock to be used to be the + * master superblock. + */ + ctx->use_superblock = 0; + goto endit; + } + + if (ctx->large_dirs && !ext2fs_has_feature_largedir(fs->super)) { + if (fix_problem(ctx, PR_2_FEATURE_LARGE_DIRS, &pctx)) { + ext2fs_set_feature_largedir(fs->super); + fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY; + ext2fs_mark_super_dirty(fs); + } + if (fs->super->s_rev_level == EXT2_GOOD_OLD_REV && + fix_problem(ctx, PR_1_FS_REV_LEVEL, &pctx)) { + ext2fs_update_dynamic_rev(fs); + ext2fs_mark_super_dirty(fs); + } + } + + ctx->flags |= E2F_FLAG_ALLOC_OK; + ext2fs_free_mem(&inodes_to_process); +endit: + e2fsck_use_inode_shortcuts(ctx, 0); + ext2fs_free_mem(&inodes_to_process); + inodes_to_process = 0; + + if (scan) + ext2fs_close_inode_scan(scan); + if (block_buf) + ext2fs_free_mem(&block_buf); + if (inode) + ext2fs_free_mem(&inode); + + /* + * The l+f inode may have been cleared, so zap it now and + * later passes will recalculate it if necessary + */ + ctx->lost_and_found = 0; + + if ((ctx->flags & E2F_FLAG_SIGNAL_MASK) == 0) + print_resource_track(ctx, _("Pass 1"), &rtrack, ctx->fs->io); + else + ctx->invalid_bitmaps++; +#ifdef HAVE_PTHREAD + /* reset update_thread after this thread exit */ + e2fsck_pass1_block_map_w_lock(ctx); + if (check_mmp) + global_ctx->mmp_update_thread = 0; + e2fsck_pass1_block_map_w_unlock(ctx); +#endif +} + +#ifdef HAVE_PTHREAD +static errcode_t e2fsck_pass1_copy_bitmap(ext2_filsys fs, ext2fs_generic_bitmap *src, + ext2fs_generic_bitmap *dest) +{ + errcode_t ret; + + ret = ext2fs_copy_bitmap(*src, dest); + if (ret) + return ret; + + (*dest)->fs = fs; + + return 0; +} + +static void e2fsck_pass1_free_bitmap(ext2fs_generic_bitmap *bitmap) +{ + if (*bitmap) { + ext2fs_free_generic_bmap(*bitmap); + *bitmap = NULL; + } + +} + +static errcode_t e2fsck_pass1_merge_bitmap(ext2_filsys fs, ext2fs_generic_bitmap *src, + ext2fs_generic_bitmap *dest) +{ + errcode_t ret = 0; + + if (*src) { + if (*dest == NULL) { + *dest = *src; + *src = NULL; + } else { + ret = ext2fs_merge_bitmap(*src, *dest, NULL, NULL); + if (ret) + return ret; + } + (*dest)->fs = fs; + } + + return 0; +} + +static errcode_t e2fsck_pass1_copy_fs(ext2_filsys dest, e2fsck_t src_context, + ext2_filsys src) +{ + errcode_t retval; + + memcpy(dest, src, sizeof(struct struct_ext2_filsys)); + dest->inode_map = NULL; + dest->block_map = NULL; + dest->badblocks = NULL; + if (dest->dblist) + dest->dblist->fs = dest; + if (src->block_map) { + retval = e2fsck_pass1_copy_bitmap(dest, &src->block_map, + &dest->block_map); + if (retval) + return retval; + } + if (src->inode_map) { + retval = e2fsck_pass1_copy_bitmap(dest, &src->inode_map, + &dest->inode_map); + if (retval) + return retval; + } + + if (src->badblocks) { + retval = ext2fs_badblocks_copy(src->badblocks, + &dest->badblocks); + if (retval) + return retval; + } + + /* disable it for now */ + src_context->openfs_flags &= ~EXT2_FLAG_EXCLUSIVE; + retval = ext2fs_open_channel(dest, src_context->io_options, + src_context->io_manager, + src_context->openfs_flags, + src->io->block_size); + if (retval) + return retval; + + /* Block size might not be default */ + io_channel_set_blksize(dest->io, src->io->block_size); + ehandler_init(dest->io); + + assert(dest->io->magic == src->io->magic); + assert(dest->io->manager == src->io->manager); + assert(strcmp(dest->io->name, src->io->name) == 0); + assert(dest->io->block_size == src->io->block_size); + assert(dest->io->read_error == src->io->read_error); + assert(dest->io->write_error == src->io->write_error); + assert(dest->io->refcount == src->io->refcount); + assert(dest->io->flags == src->io->flags); + assert(dest->io->app_data == dest); + assert(src->io->app_data == src); + assert(dest->io->align == src->io->align); + + /* The data should be written to disk immediately */ + dest->io->flags |= CHANNEL_FLAGS_WRITETHROUGH; + /* icache will be rebuilt if needed, so do not copy from @src */ + src->icache = NULL; + return 0; +} + +static int e2fsck_pass1_merge_fs(ext2_filsys dest, ext2_filsys src) +{ + struct ext2_inode_cache *icache = dest->icache; + errcode_t retval = 0; + io_channel dest_io; + io_channel dest_image_io; + ext2fs_inode_bitmap inode_map; + ext2fs_block_bitmap block_map; + ext2_badblocks_list badblocks; + ext2_dblist dblist; + int flags; + e2fsck_t dest_ctx = dest->priv_data; + + dest_io = dest->io; + dest_image_io = dest->image_io; + inode_map = dest->inode_map; + block_map = dest->block_map; + badblocks = dest->badblocks; + dblist = dest->dblist; + flags = dest->flags; + + memcpy(dest, src, sizeof(struct struct_ext2_filsys)); + dest->io = dest_io; + dest->image_io = dest_image_io; + dest->icache = icache; + dest->inode_map = inode_map; + dest->block_map = block_map; + dest->badblocks = badblocks; + dest->dblist = dblist; + dest->priv_data = dest_ctx; + if (dest->dblist) + dest->dblist->fs = dest; + dest->flags = src->flags | flags; + if (!(src->flags & EXT2_FLAG_VALID) || !(flags & EXT2_FLAG_VALID)) + ext2fs_unmark_valid(dest); + + if (src->icache) { + ext2fs_free_inode_cache(src->icache); + src->icache = NULL; + } + + retval = e2fsck_pass1_merge_bitmap(dest, &src->inode_map, + &dest->inode_map); + if (retval) + goto out; + + retval = e2fsck_pass1_merge_bitmap(dest, &src->block_map, + &dest->block_map); + if (retval) + goto out; + + if (src->dblist) { + if (dest->dblist) { + retval = ext2fs_merge_dblist(src->dblist, + dest->dblist); + if (retval) + goto out; + } else { + dest->dblist = src->dblist; + dest->dblist->fs = dest; + src->dblist = NULL; + } + } + + if (src->badblocks) { + if (dest->badblocks == NULL) + retval = ext2fs_badblocks_copy(src->badblocks, + &dest->badblocks); + else + retval = ext2fs_badblocks_merge(src->badblocks, + dest->badblocks); + } +out: + io_channel_close(src->io); + if (src->inode_map) + ext2fs_free_generic_bmap(src->inode_map); + if (src->block_map) + ext2fs_free_generic_bmap(src->block_map); + if (src->badblocks) + ext2fs_badblocks_list_free(src->badblocks); + if (src->dblist) + ext2fs_free_dblist(src->dblist); + + return retval; +} + +static void e2fsck_pass1_copy_invalid_bitmaps(e2fsck_t global_ctx, + e2fsck_t thread_ctx) +{ + dgrp_t i, j; + dgrp_t grp_start = thread_ctx->thread_info.et_group_start; + dgrp_t grp_end = thread_ctx->thread_info.et_group_end; + dgrp_t total = grp_end - grp_start; + + thread_ctx->invalid_inode_bitmap_flag = + e2fsck_allocate_memory(global_ctx, sizeof(int) * total, + "invalid_inode_bitmap"); + thread_ctx->invalid_block_bitmap_flag = + e2fsck_allocate_memory(global_ctx, sizeof(int) * total, + "invalid_block_bitmap"); + thread_ctx->invalid_inode_table_flag = + e2fsck_allocate_memory(global_ctx, sizeof(int) * total, + "invalid_inode_table"); + + memcpy(thread_ctx->invalid_block_bitmap_flag, + &global_ctx->invalid_block_bitmap_flag[grp_start], + total * sizeof(int)); + memcpy(thread_ctx->invalid_inode_bitmap_flag, + &global_ctx->invalid_inode_bitmap_flag[grp_start], + total * sizeof(int)); + memcpy(thread_ctx->invalid_inode_table_flag, + &global_ctx->invalid_inode_table_flag[grp_start], + total * sizeof(int)); + + thread_ctx->invalid_bitmaps = 0; + for (i = grp_start, j = 0; i < grp_end; i++, j++) { + if (thread_ctx->invalid_block_bitmap_flag[j]) + thread_ctx->invalid_bitmaps++; + if (thread_ctx->invalid_inode_bitmap_flag[j]) + thread_ctx->invalid_bitmaps++; + if (thread_ctx->invalid_inode_table_flag[j]) + thread_ctx->invalid_bitmaps++; + } +} + +static void e2fsck_pass1_merge_invalid_bitmaps(e2fsck_t global_ctx, + e2fsck_t thread_ctx) +{ + dgrp_t grp_start = thread_ctx->thread_info.et_group_start; + dgrp_t grp_end = thread_ctx->thread_info.et_group_end; + dgrp_t total = grp_end - grp_start; + + memcpy(&global_ctx->invalid_block_bitmap_flag[grp_start], + thread_ctx->invalid_block_bitmap_flag, total * sizeof(int)); + memcpy(&global_ctx->invalid_inode_bitmap_flag[grp_start], + thread_ctx->invalid_inode_bitmap_flag, total * sizeof(int)); + memcpy(&global_ctx->invalid_inode_table_flag[grp_start], + thread_ctx->invalid_inode_table_flag, total * sizeof(int)); + global_ctx->invalid_bitmaps += thread_ctx->invalid_bitmaps; +} + +static errcode_t e2fsck_pass1_thread_prepare(e2fsck_t global_ctx, e2fsck_t *thread_ctx, + int thread_index, int num_threads, + dgrp_t average_group) +{ + errcode_t retval; + e2fsck_t thread_context; + ext2_filsys thread_fs; + ext2_filsys global_fs = global_ctx->fs; + struct e2fsck_thread *tinfo; + + assert(global_ctx->inode_used_map == NULL); + assert(global_ctx->inode_dir_map == NULL); + assert(global_ctx->inode_bb_map == NULL); + assert(global_ctx->inode_imagic_map == NULL); + assert(global_ctx->inode_reg_map == NULL); + assert(global_ctx->inodes_to_rebuild == NULL); + + assert(global_ctx->block_found_map != NULL); + assert(global_ctx->block_metadata_map != NULL); + assert(global_ctx->block_dup_map != NULL); + assert(global_ctx->block_ea_map == NULL); + assert(global_ctx->fs->dblist == NULL); + + retval = ext2fs_get_mem(sizeof(struct e2fsck_struct), &thread_context); + if (retval) { + com_err(global_ctx->program_name, retval, "while allocating memory"); + return retval; + } + memcpy(thread_context, global_ctx, sizeof(struct e2fsck_struct)); + thread_context->block_dup_map = NULL; + thread_context->casefolded_dirs = NULL; + thread_context->expand_eisize_map = NULL; + thread_context->inode_badness = NULL; + + retval = e2fsck_allocate_block_bitmap(global_ctx->fs, + _("in-use block map"), EXT2FS_BMAP64_RBTREE, + "block_found_map", &thread_context->block_found_map); + if (retval) + goto out_context; + + thread_context->global_ctx = global_ctx; + retval = ext2fs_get_mem(sizeof(struct struct_ext2_filsys), &thread_fs); + if (retval) { + com_err(global_ctx->program_name, retval, "while allocating memory"); + goto out_context; + } + + io_channel_flush_cleanup(global_fs->io); + retval = e2fsck_pass1_copy_fs(thread_fs, global_ctx, global_fs); + if (retval) { + com_err(global_ctx->program_name, retval, "while copying fs"); + goto out_fs; + } + thread_fs->priv_data = thread_context; + + thread_context->thread_info.et_thread_index = thread_index; + set_up_logging(thread_context); + + tinfo = &thread_context->thread_info; + tinfo->et_group_start = average_group * thread_index; + if (thread_index == global_fs->fs_num_threads - 1) + tinfo->et_group_end = thread_fs->group_desc_count; + else + tinfo->et_group_end = average_group * (thread_index + 1); + tinfo->et_group_next = tinfo->et_group_start; + tinfo->et_inode_number = 0; + tinfo->et_log_buf[0] = '\0'; + tinfo->et_log_length = 0; + if (thread_context->options & E2F_OPT_MULTITHREAD) + log_out(thread_context, _("Scan group range [%d, %d)\n"), + tinfo->et_group_start, tinfo->et_group_end); + thread_context->fs = thread_fs; + retval = quota_init_context(&thread_context->qctx, thread_fs, 0); + if (retval) { + com_err(global_ctx->program_name, retval, + "while init quota context"); + goto out_fs; + } + *thread_ctx = thread_context; + e2fsck_pass1_copy_invalid_bitmaps(global_ctx, thread_context); + return 0; +out_fs: + ext2fs_free_mem(&thread_fs); +out_context: + if (thread_context->block_found_map) + ext2fs_free_mem(&thread_context->block_found_map); + ext2fs_free_mem(&thread_context); + return retval; +} + +static void e2fsck_pass1_merge_dir_info(e2fsck_t global_ctx, e2fsck_t thread_ctx) +{ + if (thread_ctx->dir_info == NULL) + return; + + if (global_ctx->dir_info == NULL) { + global_ctx->dir_info = thread_ctx->dir_info; + thread_ctx->dir_info = NULL; + return; + } + + e2fsck_merge_dir_info(global_ctx, thread_ctx->dir_info, + global_ctx->dir_info); +} + +static void e2fsck_pass1_merge_dx_dir(e2fsck_t global_ctx, e2fsck_t thread_ctx) +{ + if (thread_ctx->dx_dir_info == NULL) + return; + + if (global_ctx->dx_dir_info == NULL) { + global_ctx->dx_dir_info = thread_ctx->dx_dir_info; + global_ctx->dx_dir_info_size = thread_ctx->dx_dir_info_size; + global_ctx->dx_dir_info_count = thread_ctx->dx_dir_info_count; + thread_ctx->dx_dir_info = NULL; + return; + } + + e2fsck_merge_dx_dir(global_ctx, thread_ctx); +} + +static int e2fsck_pass1_merge_encrypted_info(e2fsck_t global_ctx, + e2fsck_t thread_ctx) +{ + if (thread_ctx->encrypted_files == NULL) + return 0; + + if (global_ctx->encrypted_files == NULL) { + global_ctx->encrypted_files = thread_ctx->encrypted_files; + thread_ctx->encrypted_files = NULL; + return 0; + } + + return e2fsck_merge_encrypted_info(global_ctx, + thread_ctx->encrypted_files, + global_ctx->encrypted_files); +} + +static inline errcode_t +e2fsck_pass1_merge_icount(ext2_icount_t *dest_icount, + ext2_icount_t *src_icount) +{ + if (*src_icount) { + if (*dest_icount == NULL) { + *dest_icount = *src_icount; + *src_icount = NULL; + } else { + errcode_t ret; + + ret = ext2fs_icount_merge(*src_icount, + *dest_icount); + if (ret) + return ret; + } + } + + return 0; +} + +static errcode_t e2fsck_pass1_merge_icounts(e2fsck_t global_ctx, e2fsck_t thread_ctx) +{ + errcode_t ret; + + ret = e2fsck_pass1_merge_icount(&global_ctx->inode_count, + &thread_ctx->inode_count); + if (ret) + return ret; + ret = e2fsck_pass1_merge_icount(&global_ctx->inode_link_info, + &thread_ctx->inode_link_info); + if (ret) + return ret; + + ret = e2fsck_pass1_merge_icount(&global_ctx->inode_badness, + &thread_ctx->inode_badness); + + return ret; +} + +static errcode_t e2fsck_pass1_merge_dirs_to_hash(e2fsck_t global_ctx, + e2fsck_t thread_ctx) +{ + errcode_t retval = 0; + + if (!thread_ctx->dirs_to_hash) + return 0; + + if (!global_ctx->dirs_to_hash) + retval = ext2fs_badblocks_copy(thread_ctx->dirs_to_hash, + &global_ctx->dirs_to_hash); + else + retval = ext2fs_badblocks_merge(thread_ctx->dirs_to_hash, + global_ctx->dirs_to_hash); + + return retval; +} + +static errcode_t e2fsck_pass1_merge_ea_inode_refs(e2fsck_t global_ctx, + e2fsck_t thread_ctx) +{ + ea_value_t thread_count, global_count; + ea_key_t ino; + errcode_t retval; + + if (!thread_ctx->ea_inode_refs) + return 0; + + if (!global_ctx->ea_inode_refs) { + global_ctx->ea_inode_refs = thread_ctx->ea_inode_refs; + thread_ctx->ea_inode_refs = NULL; + return 0; + } + + ea_refcount_intr_begin(thread_ctx->ea_inode_refs); + while (1) { + if ((ino = ea_refcount_intr_next(thread_ctx->ea_inode_refs, + &thread_count)) == 0) + break; + ea_refcount_fetch(global_ctx->ea_inode_refs, + ino, &global_count); + retval = ea_refcount_store(global_ctx->ea_inode_refs, + ino, thread_count + global_count); + if (retval) + return retval; + } + + return retval; +} + +static ea_value_t ea_refcount_usage(e2fsck_t ctx, blk64_t blk, + ea_value_t *orig) +{ + ea_value_t count_cur; + ea_value_t count_extra = 0; + ea_value_t count_orig; + + ea_refcount_fetch(ctx->refcount_orig, blk, &count_orig); + ea_refcount_fetch(ctx->refcount, blk, &count_cur); + /* most of time this is not needed */ + if (ctx->refcount_extra && count_cur == 0) + ea_refcount_fetch(ctx->refcount_extra, blk, &count_extra); + + if (!count_orig) + count_orig = *orig; + else if (orig) + *orig = count_orig; + + return count_orig + count_extra - count_cur; +} + +static errcode_t e2fsck_pass1_merge_ea_refcount(e2fsck_t global_ctx, + e2fsck_t thread_ctx) +{ + ea_value_t count; + blk64_t blk; + errcode_t retval = 0; + + if (!thread_ctx->refcount) + return 0; + + if (!global_ctx->refcount) { + global_ctx->refcount = thread_ctx->refcount; + thread_ctx->refcount = NULL; + global_ctx->refcount_extra = thread_ctx->refcount; + thread_ctx->refcount_extra = NULL; + return 0; + } + + ea_refcount_intr_begin(thread_ctx->refcount); + while (1) { + if ((blk = ea_refcount_intr_next(thread_ctx->refcount, + &count)) == 0) + break; + /** + * this EA has never seen before, so just store its + * refcount and refcount_extra into global_ctx if needed. + */ + if (!global_ctx->block_ea_map || + !ext2fs_fast_test_block_bitmap2(global_ctx->block_ea_map, + blk)) { + ea_value_t extra; + + retval = ea_refcount_store(global_ctx->refcount, + blk, count); + if (retval) + return retval; + + if (count > 0 || !thread_ctx->refcount_extra) + continue; + ea_refcount_fetch(thread_ctx->refcount_extra, blk, + &extra); + if (extra == 0) + continue; + + if (!global_ctx->refcount_extra) { + retval = ea_refcount_create(&global_ctx->refcount_extra); + if (retval) + return retval; + } + retval = ea_refcount_store(global_ctx->refcount_extra, + blk, extra); + if (retval) + return retval; + } else { + ea_value_t orig; + ea_value_t thread_usage; + ea_value_t global_usage; + ea_value_t new; + + thread_usage = ea_refcount_usage(thread_ctx, + blk, &orig); + global_usage = ea_refcount_usage(global_ctx, + blk, &orig); + if (thread_usage + global_usage <= orig) { + new = orig - thread_usage - global_usage; + retval = ea_refcount_store(global_ctx->refcount, + blk, new); + if (retval) + return retval; + continue; + } + /* update it is as zero */ + retval = ea_refcount_store(global_ctx->refcount, + blk, 0); + if (retval) + return retval; + /* Ooops, this EA was referenced more than it stated */ + if (!global_ctx->refcount_extra) { + retval = ea_refcount_create(&global_ctx->refcount_extra); + if (retval) + return retval; + } + new = global_usage + thread_usage - orig; + retval = ea_refcount_store(global_ctx->refcount_extra, + blk, new); + if (retval) + return retval; + } + } + + return retval; +} + +static errcode_t e2fsck_pass1_merge_casefolded_dirs(e2fsck_t global_ctx, + e2fsck_t thread_ctx) +{ + errcode_t retval = 0; + + if (!thread_ctx->casefolded_dirs) + return 0; + + if (!global_ctx->casefolded_dirs) + retval = ext2fs_badblocks_copy(thread_ctx->casefolded_dirs, + &global_ctx->casefolded_dirs); + else + retval = ext2fs_badblocks_merge(thread_ctx->casefolded_dirs, + global_ctx->casefolded_dirs); + + return retval; +} + +static errcode_t e2fsck_pass1_merge_context(e2fsck_t global_ctx, + e2fsck_t thread_ctx) +{ + ext2_filsys global_fs = global_ctx->fs; + errcode_t retval; + int i; + + global_ctx->fs_directory_count += thread_ctx->fs_directory_count; + global_ctx->fs_regular_count += thread_ctx->fs_regular_count; + global_ctx->fs_blockdev_count += thread_ctx->fs_blockdev_count; + global_ctx->fs_chardev_count += thread_ctx->fs_chardev_count; + global_ctx->fs_links_count += thread_ctx->fs_links_count; + global_ctx->fs_symlinks_count += thread_ctx->fs_symlinks_count; + global_ctx->fs_fast_symlinks_count += thread_ctx->fs_fast_symlinks_count; + global_ctx->fs_fifo_count += thread_ctx->fs_fifo_count; + global_ctx->fs_total_count += thread_ctx->fs_total_count; + global_ctx->fs_badblocks_count += thread_ctx->fs_badblocks_count; + global_ctx->fs_sockets_count += thread_ctx->fs_sockets_count; + global_ctx->fs_ind_count += thread_ctx->fs_ind_count; + global_ctx->fs_dind_count += thread_ctx->fs_dind_count; + global_ctx->fs_tind_count += thread_ctx->fs_tind_count; + global_ctx->fs_fragmented += thread_ctx->fs_fragmented; + global_ctx->fs_fragmented_dir += thread_ctx->fs_fragmented_dir; + global_ctx->large_files += thread_ctx->large_files; + /* threads might enable E2F_OPT_YES */ + global_ctx->options |= thread_ctx->options; + global_ctx->flags |= thread_ctx->flags; + /* + * The l+f inode may have been cleared, so zap it now and + * later passes will recalculate it if necessary + */ + global_ctx->lost_and_found = 0; + /* merge extent depth count */ + for (i = 0; i < MAX_EXTENT_DEPTH_COUNT; i++) + global_ctx->extent_depth_count[i] += + thread_ctx->extent_depth_count[i]; + + e2fsck_pass1_merge_dir_info(global_ctx, thread_ctx); + e2fsck_pass1_merge_dx_dir(global_ctx, thread_ctx); + retval = e2fsck_pass1_merge_encrypted_info(global_ctx, thread_ctx); + if (retval) { + com_err(global_ctx->program_name, 0, + _("while merging encrypted info\n")); + return retval; + } + + retval = e2fsck_pass1_merge_fs(global_ctx->fs, thread_ctx->fs); + if (retval) { + com_err(global_ctx->program_name, 0, _("while merging fs\n")); + return retval; + } + retval = e2fsck_pass1_merge_icounts(global_ctx, thread_ctx); + if (retval) { + com_err(global_ctx->program_name, 0, + _("while merging icounts\n")); + return retval; + } + + retval = e2fsck_pass1_merge_dirs_to_hash(global_ctx, thread_ctx); + if (retval) { + com_err(global_ctx->program_name, 0, + _("while merging dirs to hash\n")); + return retval; + } + + e2fsck_pass1_merge_ea_inode_refs(global_ctx, thread_ctx); + e2fsck_pass1_merge_ea_refcount(global_ctx, thread_ctx); + retval = quota_merge_and_update_usage(global_ctx->qctx, + thread_ctx->qctx); + if (retval) + return retval; + + retval = e2fsck_pass1_merge_casefolded_dirs(global_ctx, thread_ctx); + if (retval) { + com_err(global_ctx->program_name, 0, + _("while merging casefolded dirs\n")); + return retval; + } + + e2fsck_pass1_merge_invalid_bitmaps(global_ctx, thread_ctx); + + if (thread_ctx->min_extra_isize < global_ctx->min_extra_isize) + global_ctx->min_extra_isize = thread_ctx->min_extra_isize; + + retval = e2fsck_pass1_merge_bitmap(global_fs, + &thread_ctx->inode_used_map, + &global_ctx->inode_used_map); + if (retval) + return retval; + + retval = e2fsck_pass1_merge_bitmap(global_fs, + &thread_ctx->inode_dir_map, + &global_ctx->inode_dir_map); + if (retval) + return retval; + retval = e2fsck_pass1_merge_bitmap(global_fs, + &thread_ctx->inode_bb_map, + &global_ctx->inode_bb_map); + if (retval) + return retval; + retval = e2fsck_pass1_merge_bitmap(global_fs, + &thread_ctx->inode_imagic_map, + &global_ctx->inode_imagic_map); + if (retval) + return retval; + retval = e2fsck_pass1_merge_bitmap(global_fs, + &thread_ctx->inode_reg_map, + &global_ctx->inode_reg_map); + if (retval) + return retval; + retval = e2fsck_pass1_merge_bitmap(global_fs, + &thread_ctx->inodes_to_rebuild, + &global_ctx->inodes_to_rebuild); + if (retval) + return retval; + retval = e2fsck_pass1_merge_bitmap(global_fs, + &thread_ctx->block_ea_map, + &global_ctx->block_ea_map); + if (retval) + return retval; + + retval = e2fsck_pass1_merge_bitmap(global_fs, + &thread_ctx->expand_eisize_map, + &global_ctx->expand_eisize_map); + if (retval) + return retval; + + if (ext2fs_has_feature_shared_blocks(global_fs->super) && + !(global_ctx->options & E2F_OPT_UNSHARE_BLOCKS)) + return 0; + /* + * This need be done after merging block_ea_map + * because ea block might be shared, we need exclude + * them from dup blocks. + */ + e2fsck_pass1_block_map_w_lock(thread_ctx); + retval = ext2fs_merge_bitmap(thread_ctx->block_found_map, + global_ctx->block_found_map, + global_ctx->block_dup_map, + global_ctx->block_ea_map); + e2fsck_pass1_block_map_w_unlock(thread_ctx); + if (retval == EEXIST) + global_ctx->flags |= E2F_FLAG_DUP_BLOCK; - FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum); + return 0; +} - if (ctx->flags & E2F_FLAG_SIGNAL_MASK) - goto endit; +static int e2fsck_pass1_thread_join(e2fsck_t global_ctx, e2fsck_t thread_ctx) +{ + errcode_t retval; - if (process_inode_count >= ctx->process_inode_size) { - process_inodes(ctx, block_buf); + retval = e2fsck_pass1_merge_context(global_ctx, thread_ctx); + ext2fs_free_mem(&thread_ctx->fs); + if (thread_ctx->logf) + fclose(thread_ctx->logf); + if (thread_ctx->problem_logf) { + fputs("\n", thread_ctx->problem_logf); + fclose(thread_ctx->problem_logf); + } - if (ctx->flags & E2F_FLAG_SIGNAL_MASK) - goto endit; + quota_release_context(&thread_ctx->qctx); + /* + * @block_metadata_map and @block_dup_map are + * shared, so we don't free them. + */ + thread_ctx->block_metadata_map = NULL; + thread_ctx->block_dup_map = NULL; + e2fsck_reset_context(thread_ctx); + ext2fs_free_mem(&thread_ctx); + + return retval; +} + +static int e2fsck_pass1_threads_join(e2fsck_t global_ctx) +{ + errcode_t rc; + errcode_t ret = 0; + struct e2fsck_thread_info *infos = global_ctx->infos; + struct e2fsck_thread_info *pinfo; + int num_threads = global_ctx->pfs_num_threads; + int i; + + /* merge invalid bitmaps will recalculate it */ + global_ctx->invalid_bitmaps = 0; + for (i = 0; i < num_threads; i++) { + pinfo = &infos[i]; + + if (!pinfo->eti_started) + continue; + + rc = pthread_join(pinfo->eti_thread_id, NULL); + if (rc) { + com_err(global_ctx->program_name, rc, + _("while joining thread\n")); + if (ret == 0) + ret = rc; + } + rc = e2fsck_pass1_thread_join(global_ctx, infos[i].eti_thread_ctx); + if (rc) { + com_err(global_ctx->program_name, rc, + _("while joining pass1 thread\n")); + if (ret == 0) + ret = rc; } } - process_inodes(ctx, block_buf); - ext2fs_close_inode_scan(scan); - scan = NULL; + free(infos); + global_ctx->infos = NULL; - reserve_block_for_root_repair(ctx); - reserve_block_for_lnf_repair(ctx); + return ret; +} + +static void *e2fsck_pass1_thread(void *arg) +{ + struct e2fsck_thread_info *info = arg; + e2fsck_t thread_ctx = info->eti_thread_ctx; +#ifdef DEBUG_THREADS + struct e2fsck_thread_debug *thread_debug = info->eti_debug; +#endif + +#ifdef DEBUG_THREADS + pthread_mutex_lock(&thread_debug->etd_mutex); + while (info->eti_thread_index > thread_debug->etd_finished_threads) { + pthread_cond_wait(&thread_debug->etd_cond, + &thread_debug->etd_mutex); + } + pthread_mutex_unlock(&thread_debug->etd_mutex); +#endif +#ifdef HAVE_SETJMP_H /* - * If any extended attribute blocks' reference counts need to - * be adjusted, either up (ctx->refcount_extra), or down - * (ctx->refcount), then fix them. + * When fatal_error() happens, jump to here. The thread + * context's flags will be saved, but its abort_loc will + * be overwritten by original jump buffer for the later + * tests. */ - if (ctx->refcount) { - adjust_extattr_refcount(ctx, ctx->refcount, block_buf, -1); - ea_refcount_free(ctx->refcount); - ctx->refcount = 0; - } - if (ctx->refcount_extra) { - adjust_extattr_refcount(ctx, ctx->refcount_extra, - block_buf, +1); - ea_refcount_free(ctx->refcount_extra); - ctx->refcount_extra = 0; + if (setjmp(thread_ctx->abort_loc)) { + thread_ctx->flags &= ~E2F_FLAG_SETJMP_OK; + goto out; } + thread_ctx->flags |= E2F_FLAG_SETJMP_OK; +#endif - if (ctx->ea_block_quota_blocks) { - ea_refcount_free(ctx->ea_block_quota_blocks); - ctx->ea_block_quota_blocks = 0; - } + e2fsck_pass1_run(thread_ctx); - if (ctx->ea_block_quota_inodes) { - ea_refcount_free(ctx->ea_block_quota_inodes); - ctx->ea_block_quota_inodes = 0; +out: + if (thread_ctx->options & E2F_OPT_MULTITHREAD) + log_out(thread_ctx, + _("Scanned group range [%u, %u), inodes %u\n"), + thread_ctx->thread_info.et_group_start, + thread_ctx->thread_info.et_group_end, + thread_ctx->thread_info.et_inode_number); + +#ifdef DEBUG_THREADS + pthread_mutex_lock(&thread_debug->etd_mutex); + thread_debug->etd_finished_threads++; + pthread_cond_broadcast(&thread_debug->etd_cond); + pthread_mutex_unlock(&thread_debug->etd_mutex); +#endif + + return NULL; +} + +static dgrp_t ext2fs_get_avg_group(ext2_filsys fs) +{ +#ifdef HAVE_PTHREAD + dgrp_t average_group; + unsigned flexbg_size; + + if (fs->fs_num_threads <= 1) + return fs->group_desc_count; + + average_group = fs->group_desc_count / fs->fs_num_threads; + if (average_group <= 1) + return 1; + + if (ext2fs_has_feature_flex_bg(fs->super)) { + int times = 1; + + flexbg_size = 1 << fs->super->s_log_groups_per_flex; + if (average_group % flexbg_size) { + times = average_group / flexbg_size; + average_group = times * flexbg_size; + } } - if (ctx->invalid_bitmaps) - handle_fs_bad_blocks(ctx); + return average_group; +#else + return fs->group_desc_count; +#endif +} - /* We don't need the block_ea_map any more */ - if (ctx->block_ea_map) { - ext2fs_free_block_bitmap(ctx->block_ea_map); - ctx->block_ea_map = 0; +static int e2fsck_pass1_threads_start(e2fsck_t global_ctx) +{ + struct e2fsck_thread_info *infos; + pthread_attr_t attr; + errcode_t retval; + errcode_t ret; + struct e2fsck_thread_info *tmp_pinfo; + int i; + e2fsck_t thread_ctx; + dgrp_t average_group; + int num_threads = global_ctx->pfs_num_threads; +#ifdef DEBUG_THREADS + struct e2fsck_thread_debug thread_debug = + {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, 0}; + + thread_debug.etd_finished_threads = 0; +#endif + + retval = pthread_attr_init(&attr); + if (retval) { + com_err(global_ctx->program_name, retval, + _("while setting pthread attribute\n")); + return retval; } - if (ctx->flags & E2F_FLAG_RESIZE_INODE) { - clear_problem_context(&pctx); - pctx.errcode = ext2fs_create_resize_inode(fs); - if (pctx.errcode) { - if (!fix_problem(ctx, PR_1_RESIZE_INODE_CREATE, - &pctx)) { - ctx->flags |= E2F_FLAG_ABORT; - goto endit; - } - pctx.errcode = 0; + infos = calloc(num_threads, sizeof(struct e2fsck_thread_info)); + if (infos == NULL) { + retval = -ENOMEM; + com_err(global_ctx->program_name, retval, + _("while allocating memory for threads\n")); + pthread_attr_destroy(&attr); + return retval; + } + global_ctx->infos = infos; + + average_group = ext2fs_get_avg_group(global_ctx->fs); + for (i = 0; i < num_threads; i++) { + tmp_pinfo = &infos[i]; + tmp_pinfo->eti_thread_index = i; +#ifdef DEBUG_THREADS + tmp_pinfo->eti_debug = &thread_debug; +#endif + retval = e2fsck_pass1_thread_prepare(global_ctx, &thread_ctx, + i, num_threads, + average_group); + if (retval) { + com_err(global_ctx->program_name, retval, + _("while preparing pass1 thread\n")); + break; } - if (!pctx.errcode) { - e2fsck_read_inode(ctx, EXT2_RESIZE_INO, inode, - "recreate inode"); - inode->i_mtime = ctx->now; - e2fsck_write_inode(ctx, EXT2_RESIZE_INO, inode, - "recreate inode"); + tmp_pinfo->eti_thread_ctx = thread_ctx; + + retval = pthread_create(&tmp_pinfo->eti_thread_id, &attr, + &e2fsck_pass1_thread, tmp_pinfo); + if (retval) { + com_err(global_ctx->program_name, retval, + _("while creating thread\n")); + e2fsck_pass1_thread_join(global_ctx, thread_ctx); + break; } - ctx->flags &= ~E2F_FLAG_RESIZE_INODE; + + tmp_pinfo->eti_started = 1; } - if (ctx->flags & E2F_FLAG_RESTART) { - /* - * Only the master copy of the superblock and block - * group descriptors are going to be written during a - * restart, so set the superblock to be used to be the - * master superblock. - */ - ctx->use_superblock = 0; - unwind_pass1(fs); - goto endit; + /* destroy the thread attribute object, since it is no longer needed */ + ret = pthread_attr_destroy(&attr); + if (ret) { + com_err(global_ctx->program_name, ret, + _("while destroying thread attribute\n")); + if (retval == 0) + retval = ret; } - if (ctx->block_dup_map) { - if (ctx->options & E2F_OPT_PREEN) { - clear_problem_context(&pctx); - fix_problem(ctx, PR_1_DUP_BLOCKS_PREENSTOP, &pctx); - } - e2fsck_pass1_dupblocks(ctx, block_buf); + if (retval) { + e2fsck_pass1_threads_join(global_ctx); + return retval; } - ctx->flags |= E2F_FLAG_ALLOC_OK; - ext2fs_free_mem(&inodes_to_process); -endit: - e2fsck_use_inode_shortcuts(ctx, 0); + return 0; +} - if (scan) - ext2fs_close_inode_scan(scan); - if (block_buf) - ext2fs_free_mem(&block_buf); - if (inode) - ext2fs_free_mem(&inode); +static void e2fsck_pass1_multithread(e2fsck_t global_ctx) +{ + errcode_t retval; - /* - * The l+f inode may have been cleared, so zap it now and - * later passes will recalculate it if necessary - */ - ctx->lost_and_found = 0; + retval = e2fsck_pass1_threads_start(global_ctx); + if (retval) { + com_err(global_ctx->program_name, retval, + _("while starting pass1 threads\n")); + goto out_abort; + } - if ((ctx->flags & E2F_FLAG_SIGNAL_MASK) == 0) - print_resource_track(ctx, _("Pass 1"), &rtrack, ctx->fs->io); - else - ctx->invalid_bitmaps++; + retval = e2fsck_pass1_threads_join(global_ctx); + if (retval) { + com_err(global_ctx->program_name, retval, + _("while joining pass1 threads\n")); + goto out_abort; + } + return; +out_abort: + global_ctx->flags |= E2F_FLAG_ABORT; + return; +} +#endif + +void e2fsck_pass1(e2fsck_t ctx) +{ + errcode_t retval; + int need_single = 1; + + retval = e2fsck_pass1_prepare(ctx); + if (retval) + return; +#ifdef HAVE_PTHREAD + if (ctx->pfs_num_threads > 1 || ctx->options & E2F_OPT_MULTITHREAD) { + need_single = 0; + e2fsck_pass1_multithread(ctx); + } + /* No lock is needed at this time */ + ctx->fs_need_locking = 0; +#endif + if (need_single) + e2fsck_pass1_run(ctx); + e2fsck_pass1_post(ctx); } + #undef FINISH_INODE_LOOP /* @@ -2080,24 +3783,65 @@ static errcode_t scan_callback(ext2_filsys fs, { struct scan_callback_struct *scan_struct; e2fsck_t ctx; + dgrp_t cur = group + 1; + struct e2fsck_thread *tinfo; + struct e2fsck_thread_info *pinfo, *infos; + int i; scan_struct = (struct scan_callback_struct *) priv_data; ctx = scan_struct->ctx; - process_inodes((e2fsck_t) fs->priv_data, scan_struct->block_buf); + process_inodes((e2fsck_t) fs->priv_data, scan_struct->block_buf, + scan_struct->inodes_to_process, + scan_struct->process_inode_count); + +#ifdef HAVE_PTHREAD + if (ctx->global_ctx) { + cur = 0; + infos = ctx->global_ctx->infos; + for (i = 0; i < ctx->global_ctx->pfs_num_threads; i++) { + pinfo = &infos[i]; + + if (!pinfo->eti_started) + continue; + + tinfo = &pinfo->eti_thread_ctx->thread_info; + if (ctx == pinfo->eti_thread_ctx) + cur += group + 1 - tinfo->et_group_start; + else + cur += tinfo->et_group_next - + tinfo->et_group_start; + } + } +#endif if (ctx->progress) - if ((ctx->progress)(ctx, 1, group+1, + if ((ctx->progress)(ctx, 1, cur, ctx->fs->group_desc_count)) return EXT2_ET_CANCEL_REQUESTED; +#ifdef HAVE_PTHREAD + if (ctx->global_ctx) { + tinfo = &ctx->thread_info; + tinfo->et_group_next++; + if (ctx->options & E2F_OPT_DEBUG && + ctx->options & E2F_OPT_MULTITHREAD) + log_out(ctx, _("group %d finished\n"), + tinfo->et_group_next); + if (tinfo->et_group_next >= tinfo->et_group_end) + return EXT2_ET_SCAN_FINISHED; + } +#endif + return 0; } /* * Process the inodes in the "inodes to process" list. */ -static void process_inodes(e2fsck_t ctx, char *block_buf) +static void process_inodes(e2fsck_t ctx, char *block_buf, + struct process_inode_block *inodes_to_process, + int *process_inode_count) { int i; struct ext2_inode *old_stashed_inode; @@ -2109,15 +3853,15 @@ static void process_inodes(e2fsck_t ctx, char *block_buf) #if 0 printf("begin process_inodes: "); #endif - if (process_inode_count == 0) + if (*process_inode_count == 0) return; old_operation = ehandler_operation(0); old_stashed_inode = ctx->stashed_inode; old_stashed_ino = ctx->stashed_ino; - qsort(inodes_to_process, process_inode_count, + qsort(inodes_to_process, *process_inode_count, sizeof(struct process_inode_block), process_inode_cmp); clear_problem_context(&pctx); - for (i=0; i < process_inode_count; i++) { + for (i=0; i < *process_inode_count; i++) { pctx.inode = ctx->stashed_inode = (struct ext2_inode *) &inodes_to_process[i].inode; pctx.ino = ctx->stashed_ino = inodes_to_process[i].ino; @@ -2130,12 +3874,12 @@ static void process_inodes(e2fsck_t ctx, char *block_buf) ehandler_operation(buf); check_blocks(ctx, &pctx, block_buf, &inodes_to_process[i].ea_ibody_quota); - if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + if (e2fsck_should_abort(ctx)) break; } ctx->stashed_inode = old_stashed_inode; ctx->stashed_ino = old_stashed_ino; - process_inode_count = 0; + *process_inode_count = 0; #if 0 printf("end process inodes\n"); #endif @@ -2166,43 +3910,59 @@ static EXT2_QSORT_TYPE process_inode_cmp(const void *a, const void *b) } /* - * Mark an inode as being bad in some what + * Mark an inode as being bad and increment its badness counter. */ -static void mark_inode_bad(e2fsck_t ctx, ino_t ino) +void e2fsck_mark_inode_bad_loc(e2fsck_t ctx, struct problem_context *pctx, + __u32 code, int badness, const char *func, + const int line) { - struct problem_context pctx; + __u16 badness_before, badness_after; - if (!ctx->inode_bad_map) { - clear_problem_context(&pctx); + if (!ctx->inode_badness_threshold) /* badness is disabled */ + return; - pctx.errcode = e2fsck_allocate_inode_bitmap(ctx->fs, - _("bad inode map"), EXT2FS_BMAP64_RBTREE, - "inode_bad_map", &ctx->inode_bad_map); - if (pctx.errcode) { - pctx.num = 3; - fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); - /* Should never get here */ + if (!ctx->inode_badness) { + errcode_t retval; + + retval = ext2fs_create_icount2(ctx->fs, 0, 0, NULL, + &ctx->inode_badness); + if (retval) { + pctx->errcode = retval; + fix_problem(ctx, PR_1_ALLOCATE_ICOUNT, pctx); ctx->flags |= E2F_FLAG_ABORT; return; } } - ext2fs_mark_inode_bitmap2(ctx->inode_bad_map, ino); + ext2fs_icount_fetch(ctx->inode_badness, pctx->ino, &badness_before); + if (badness + badness_before > BADNESS_MAX) + badness_after = BADNESS_MAX; + else if (badness < 0 && badness_before < -badness) + badness_after = 0; + else + badness_after = badness_before + badness; + ext2fs_icount_store(ctx->inode_badness, pctx->ino, badness_after); + + if (ctx->options & E2F_OPT_DEBUG) + log_out(ctx, + "%s:%d: increase inode %lu badness %u to %u for %x\n", + func, line, (unsigned long)pctx->ino, badness_before, + badness_after, code); } -static void add_encrypted_dir(e2fsck_t ctx, ino_t ino) +static void add_casefolded_dir(e2fsck_t ctx, ext2_ino_t ino) { struct problem_context pctx; - if (!ctx->encrypted_dirs) { - pctx.errcode = ext2fs_u32_list_create(&ctx->encrypted_dirs, 0); + if (!ctx->casefolded_dirs) { + pctx.errcode = ext2fs_u32_list_create(&ctx->casefolded_dirs, 0); if (pctx.errcode) goto error; } - pctx.errcode = ext2fs_u32_list_add(ctx->encrypted_dirs, ino); + pctx.errcode = ext2fs_u32_list_add(ctx->casefolded_dirs, ino); if (pctx.errcode == 0) return; error: - fix_problem(ctx, PR_1_ALLOCATE_ENCRYPTED_DIRLIST, &pctx); + fix_problem(ctx, PR_1_ALLOCATE_CASEFOLDED_DIRLIST, &pctx); /* Should never get here */ ctx->flags |= E2F_FLAG_ABORT; } @@ -2256,30 +4016,20 @@ static void alloc_imagic_map(e2fsck_t ctx) */ static _INLINE_ void mark_block_used(e2fsck_t ctx, blk64_t block) { - struct problem_context pctx; + struct problem_context pctx; + e2fsck_t global_ctx = ctx->global_ctx ? ctx->global_ctx : ctx; clear_problem_context(&pctx); - if (ext2fs_fast_test_block_bitmap2(ctx->block_found_map, block)) { + if (is_blocks_used(ctx, block, 1)) { if (ext2fs_has_feature_shared_blocks(ctx->fs->super) && !(ctx->options & E2F_OPT_UNSHARE_BLOCKS)) { return; } - if (!ctx->block_dup_map) { - pctx.errcode = e2fsck_allocate_block_bitmap(ctx->fs, - _("multiply claimed block map"), - EXT2FS_BMAP64_RBTREE, "block_dup_map", - &ctx->block_dup_map); - if (pctx.errcode) { - pctx.num = 3; - fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, - &pctx); - /* Should never get here */ - ctx->flags |= E2F_FLAG_ABORT; - return; - } - } - ext2fs_fast_mark_block_bitmap2(ctx->block_dup_map, block); + ctx->flags |= E2F_FLAG_DUP_BLOCK; + e2fsck_pass1_block_map_w_lock(ctx); + ext2fs_fast_mark_block_bitmap2(global_ctx->block_dup_map, block); + e2fsck_pass1_block_map_w_unlock(ctx); } else { ext2fs_fast_mark_block_bitmap2(ctx->block_found_map, block); } @@ -2292,9 +4042,9 @@ static _INLINE_ void mark_block_used(e2fsck_t ctx, blk64_t block) static _INLINE_ void mark_blocks_used(e2fsck_t ctx, blk64_t block, unsigned int num) { - if (ext2fs_test_block_bitmap_range2(ctx->block_found_map, block, num)) + if (!is_blocks_used(ctx, block, num)) { ext2fs_mark_block_bitmap_range2(ctx->block_found_map, block, num); - else { + } else { unsigned int i; for (i = 0; i < num; i += EXT2FS_CLUSTER_RATIO(ctx->fs)) @@ -2302,6 +4052,18 @@ static _INLINE_ void mark_blocks_used(e2fsck_t ctx, blk64_t block, } } +static errcode_t _INLINE_ e2fsck_write_ext_attr3(e2fsck_t ctx, blk64_t block, + void *inbuf, ext2_ino_t inum) +{ + errcode_t retval; + ext2_filsys fs = ctx->fs; + + e2fsck_pass1_fix_lock(ctx); + retval = ext2fs_write_ext_attr3(fs, block, inbuf, inum); + e2fsck_pass1_fix_unlock(ctx); + + return retval; +} /* * Adjust the extended attribute block's reference counts at the end * of pass 1, either by subtracting out references for EA blocks that @@ -2328,17 +4090,23 @@ static void adjust_extattr_refcount(e2fsck_t ctx, ext2_refcount_t refcount, pctx.blk = blk; pctx.errcode = ext2fs_read_ext_attr3(fs, blk, block_buf, pctx.ino); + /* We already checked this block, shouldn't happen */ if (pctx.errcode) { fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, &pctx); return; } - header = (struct ext2_ext_attr_header *) block_buf; + header = BHDR(block_buf); + if (header->h_magic != EXT2_EXT_ATTR_MAGIC) { + fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, &pctx); + return; + } + pctx.blkcount = header->h_refcount; should_be = header->h_refcount + adjust_sign * (int)count; pctx.num = should_be; if (fix_problem(ctx, PR_1_EXTATTR_REFCOUNT, &pctx)) { header->h_refcount = should_be; - pctx.errcode = ext2fs_write_ext_attr3(fs, blk, + pctx.errcode = e2fsck_write_ext_attr3(ctx, blk, block_buf, pctx.ino); if (pctx.errcode) { @@ -2385,7 +4153,8 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, if (!ext2fs_has_feature_xattr(fs->super) || (blk < fs->super->s_first_data_block) || (blk >= ext2fs_blocks_count(fs->super))) { - mark_inode_bad(ctx, ino); + /* Fixed in pass2, e2fsck_process_bad_inode(). */ + e2fsck_mark_inode_bad(ctx, pctx, PR_2_FILE_ACL_ZERO); return 0; } @@ -2405,7 +4174,15 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, /* Create the EA refcount structure if necessary */ if (!ctx->refcount) { - pctx->errcode = ea_refcount_create(0, &ctx->refcount); + pctx->errcode = ea_refcount_create(&ctx->refcount_orig); + if (pctx->errcode) { + pctx->num = 1; + fix_problem(ctx, PR_1_ALLOCATE_REFCOUNT, pctx); + ctx->flags |= E2F_FLAG_ABORT; + return 0; + } + + pctx->errcode = ea_refcount_create(&ctx->refcount); if (pctx->errcode) { pctx->num = 1; fix_problem(ctx, PR_1_ALLOCATE_REFCOUNT, pctx); @@ -2439,8 +4216,7 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, return 1; /* Ooops, this EA was referenced more than it stated */ if (!ctx->refcount_extra) { - pctx->errcode = ea_refcount_create(0, - &ctx->refcount_extra); + pctx->errcode = ea_refcount_create(&ctx->refcount_extra); if (pctx->errcode) { pctx->num = 2; fix_problem(ctx, PR_1_ALLOCATE_REFCOUNT, pctx); @@ -2469,7 +4245,7 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, pctx->errcode = 0; goto clear_extattr; } - header = (struct ext2_ext_attr_header *) block_buf; + header = BHDR(block_buf); pctx->blk = ext2fs_file_acl_block(fs, inode); if (((ctx->ext_attr_ver == 1) && (header->h_magic != EXT2_EXT_ATTR_MAGIC_v1)) || @@ -2519,8 +4295,9 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, break; } if (entry->e_value_inum == 0) { - if (entry->e_value_offs + entry->e_value_size > - fs->blocksize) { + if (entry->e_value_size > EXT2_XATTR_SIZE_MAX || + (entry->e_value_offs + entry->e_value_size > + fs->blocksize)) { if (fix_problem(ctx, PR_1_EA_BAD_VALUE, pctx)) goto clear_extattr; break; @@ -2535,6 +4312,9 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, hash = ext2fs_ext_attr_hash_entry(entry, block_buf + entry->e_value_offs); + if (entry->e_hash != hash) + hash = ext2fs_ext_attr_hash_entry_signed(entry, + block_buf + entry->e_value_offs); if (entry->e_hash != hash) { pctx->num = entry->e_hash; @@ -2569,7 +4349,7 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, */ if (failed_csum && fix_problem(ctx, PR_1_EA_BLOCK_ONLY_CSUM_INVALID, pctx)) { - pctx->errcode = ext2fs_write_ext_attr3(fs, blk, block_buf, + pctx->errcode = e2fsck_write_ext_attr3(ctx, blk, block_buf, pctx->ino); if (pctx->errcode) return 0; @@ -2577,8 +4357,7 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, if (quota_blocks != EXT2FS_C2B(fs, 1U)) { if (!ctx->ea_block_quota_blocks) { - pctx->errcode = ea_refcount_create(0, - &ctx->ea_block_quota_blocks); + pctx->errcode = ea_refcount_create(&ctx->ea_block_quota_blocks); if (pctx->errcode) { pctx->num = 3; goto refcount_fail; @@ -2590,8 +4369,7 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, if (quota_inodes) { if (!ctx->ea_block_quota_inodes) { - pctx->errcode = ea_refcount_create(0, - &ctx->ea_block_quota_inodes); + pctx->errcode = ea_refcount_create(&ctx->ea_block_quota_inodes); if (pctx->errcode) { pctx->num = 4; refcount_fail: @@ -2609,7 +4387,13 @@ refcount_fail: inc_ea_inode_refs(ctx, pctx, first, end); ea_refcount_store(ctx->refcount, blk, header->h_refcount - 1); - mark_block_used(ctx, blk); + ea_refcount_store(ctx->refcount_orig, blk, header->h_refcount); + /** + * It might be racy that this block has been merged in the + * global found map. + */ + if (!is_blocks_used(ctx, blk, 1)) + ext2fs_fast_mark_block_bitmap2(ctx->block_found_map, blk); ext2fs_fast_mark_block_bitmap2(ctx->block_ea_map, blk); return 1; @@ -2650,11 +4434,13 @@ static int handle_htree(e2fsck_t ctx, struct problem_context *pctx, } retval = io_channel_read_blk64(fs->io, blk, 1, block_buf); - if (retval && fix_problem(ctx, PR_1_HTREE_BADROOT, pctx)) - return 1; + if (retval) { + if (fix_problem(ctx, PR_1_HTREE_BADROOT, pctx)) + return 1; + } /* XXX should check that beginning matches a directory */ - root = (struct ext2_dx_root_info *) (block_buf + 24); + root = get_ext2_dx_root_info(fs, block_buf); if ((root->reserved_zero || root->info_length < 8) && fix_problem(ctx, PR_1_HTREE_BADROOT, pctx)) @@ -2664,18 +4450,49 @@ static int handle_htree(e2fsck_t ctx, struct problem_context *pctx, if ((root->hash_version != EXT2_HASH_LEGACY) && (root->hash_version != EXT2_HASH_HALF_MD4) && (root->hash_version != EXT2_HASH_TEA) && + (root->hash_version != EXT2_HASH_SIPHASH) && fix_problem(ctx, PR_1_HTREE_HASHV, pctx)) return 1; + if (ext4_hash_in_dirent(inode)) { + if (root->hash_version != EXT2_HASH_SIPHASH && + fix_problem(ctx, PR_1_HTREE_NEEDS_SIPHASH, pctx)) + return 1; + } else { + if (root->hash_version == EXT2_HASH_SIPHASH && + fix_problem(ctx, PR_1_HTREE_CANNOT_SIPHASH, pctx)) + return 1; + } + if ((root->unused_flags & EXT2_HASH_FLAG_INCOMPAT) && fix_problem(ctx, PR_1_HTREE_INCOMPAT, pctx)) return 1; pctx->num = root->indirect_levels; - if ((root->indirect_levels > ext2_dir_htree_level(fs)) && + /* if htree level is clearly too high, consider it to be broken */ + if (root->indirect_levels > EXT4_HTREE_LEVEL && fix_problem(ctx, PR_1_HTREE_DEPTH, pctx)) return 1; + /* if level is only maybe too high, LARGE_DIR feature could be unset */ + if (root->indirect_levels > ext2_dir_htree_level(fs) && + !ext2fs_has_feature_largedir(fs->super)) { + int blockbits = EXT2_BLOCK_SIZE_BITS(fs->super) + 10; + unsigned idx_pb = 1 << (blockbits - 3); + + /* compare inode size/blocks vs. max-sized 2-level htree */ + if (EXT2_I_SIZE(pctx->inode) < + (idx_pb - 1) * (idx_pb - 2) << blockbits && + pctx->inode->i_blocks < + (idx_pb - 1) * (idx_pb - 2) << (blockbits - 9) && + fix_problem(ctx, PR_1_HTREE_DEPTH, pctx)) + return 1; + } + + if (root->indirect_levels > EXT4_HTREE_LEVEL_COMPAT || + ext2fs_needs_large_file_feature(EXT2_I_SIZE(inode))) + ctx->large_dirs++; + return 0; } @@ -2702,8 +4519,8 @@ void e2fsck_clear_inode(e2fsck_t ctx, ext2_ino_t ino, ext2fs_unmark_inode_bitmap2(ctx->inode_used_map, ino); if (ctx->inode_reg_map) ext2fs_unmark_inode_bitmap2(ctx->inode_reg_map, ino); - if (ctx->inode_bad_map) - ext2fs_unmark_inode_bitmap2(ctx->inode_bad_map, ino); + if (ctx->inode_badness) + ext2fs_icount_store(ctx->inode_badness, ino, 0); /* * If the inode was partially accounted for before processing @@ -2773,7 +4590,8 @@ static void scan_extent_node(e2fsck_t ctx, struct problem_context *pctx, if (pctx->errcode) return; if (!(ctx->options & E2F_OPT_FIXES_ONLY) && - !pb->eti.force_rebuild) { + !pb->eti.force_rebuild && + info.curr_level < MAX_EXTENT_DEPTH_COUNT) { struct extent_tree_level *etl; etl = pb->eti.ext_info + info.curr_level; @@ -2822,7 +4640,8 @@ static void scan_extent_node(e2fsck_t ctx, struct problem_context *pctx, (extent.e_pblk + extent.e_len) > ext2fs_blocks_count(ctx->fs->super)) problem = PR_1_EXTENT_ENDS_BEYOND; - else if (is_leaf && is_dir && + else if (is_leaf && is_dir && !pctx->inode->i_size_high && + !ext2fs_has_feature_largedir(ctx->fs->super) && ((extent.e_lblk + extent.e_len) > (1U << (21 - ctx->fs->super->s_log_block_size)))) problem = PR_1_TOOBIG_DIR; @@ -2830,9 +4649,10 @@ static void scan_extent_node(e2fsck_t ctx, struct problem_context *pctx, if (is_leaf && problem == 0 && extent.e_len > 0) { #if 0 printf("extent_region(ino=%u, expect=%llu, " - "lblk=%llu, len=%u)\n", - pb->ino, pb->next_lblock, - extent.e_lblk, extent.e_len); + "lblk=%llu, len=%u)\n", pb->ino, + (unsigned long long) pb->next_lblock, + (unsigned long long) extent.e_lblk, + extent.e_len); #endif if (extent.e_lblk < pb->next_lblock) problem = PR_1_EXTENT_COLLISION; @@ -2847,10 +4667,12 @@ static void scan_extent_node(e2fsck_t ctx, struct problem_context *pctx, if (try_repairs && is_dir && problem == 0 && (extent.e_flags & EXT2_EXTENT_FLAGS_UNINIT) && fix_problem(ctx, PR_1_UNINIT_DBLOCK, pctx)) { + e2fsck_pass1_fix_lock(ctx); extent.e_flags &= ~EXT2_EXTENT_FLAGS_UNINIT; pb->inode_modified = 1; pctx->errcode = ext2fs_extent_replace(ehandle, 0, &extent); + e2fsck_pass1_fix_unlock(ctx); if (pctx->errcode) return; failed_csum = 0; @@ -2871,7 +4693,9 @@ static void scan_extent_node(e2fsck_t ctx, struct problem_context *pctx, #endif if (try_repairs && problem) { report_problem: - if (fix_problem(ctx, problem, pctx)) { + /* Record badness only if extent is within inode */ + if (fix_problem_bad(ctx, problem, pctx, + info.curr_level == 0)) { if (ctx->invalid_bitmaps) { /* * If fsck knows the bitmaps are bad, @@ -2892,15 +4716,19 @@ report_problem: } continue; } + e2fsck_pass1_fix_lock(ctx); e2fsck_read_bitmaps(ctx); pb->inode_modified = 1; pctx->errcode = ext2fs_extent_delete(ehandle, 0); + e2fsck_pass1_fix_unlock(ctx); if (pctx->errcode) { pctx->str = "ext2fs_extent_delete"; return; } + e2fsck_pass1_fix_lock(ctx); pctx->errcode = ext2fs_extent_fix_parents(ehandle); + e2fsck_pass1_fix_unlock(ctx); if (pctx->errcode && pctx->errcode != EXT2_ET_NO_CURRENT_NODE) { pctx->str = "ext2fs_extent_fix_parents"; @@ -2936,9 +4764,9 @@ report_problem: extent.e_pblk)) { next_try_repairs = 0; pctx->blk = blk; - fix_problem(ctx, - PR_1_CRITICAL_METADATA_COLLISION, - pctx); + fix_problem_bad(ctx, + PR_1_CRITICAL_METADATA_COLLISION, + pctx, 2); if ((ctx->options & E2F_OPT_NO) == 0) ctx->flags |= E2F_FLAG_RESTART_LATER; } @@ -2958,15 +4786,22 @@ report_problem: if (extent.e_lblk != lblk) { struct ext2_extent_info e_info; - ext2fs_extent_get_info(ehandle, &e_info); + pctx->errcode = ext2fs_extent_get_info(ehandle, + &e_info); + if (pctx->errcode) { + pctx->str = "ext2fs_extent_get_info"; + return; + } pctx->blk = lblk; pctx->blk2 = extent.e_lblk; pctx->num = e_info.curr_level - 1; problem = PR_1_EXTENT_INDEX_START_INVALID; if (fix_problem(ctx, problem, pctx)) { + e2fsck_pass1_fix_lock(ctx); pb->inode_modified = 1; pctx->errcode = ext2fs_extent_fix_parents(ehandle); + e2fsck_pass1_fix_unlock(ctx); if (pctx->errcode) { pctx->str = "ext2fs_extent_fix_parents"; return; @@ -3030,15 +4865,19 @@ report_problem: pctx->blk = extent.e_lblk; pctx->blk2 = new_lblk; if (fix_problem(ctx, PR_1_COLLAPSE_DBLOCK, pctx)) { + e2fsck_pass1_fix_lock(ctx); extent.e_lblk = new_lblk; pb->inode_modified = 1; pctx->errcode = ext2fs_extent_replace(ehandle, 0, &extent); + e2fsck_pass1_fix_unlock(ctx); if (pctx->errcode) { pctx->errcode = 0; goto alloc_later; } + e2fsck_pass1_fix_lock(ctx); pctx->errcode = ext2fs_extent_fix_parents(ehandle); + e2fsck_pass1_fix_unlock(ctx); if (pctx->errcode) goto failed_add_dir_block; pctx->errcode = ext2fs_extent_goto(ehandle, @@ -3134,8 +4973,10 @@ alloc_later: /* Failed csum but passes checks? Ask to fix checksum. */ if (failed_csum && fix_problem(ctx, PR_1_EXTENT_ONLY_CSUM_INVALID, pctx)) { + e2fsck_pass1_fix_lock(ctx); pb->inode_modified = 1; pctx->errcode = ext2fs_extent_replace(ehandle, 0, &extent); + e2fsck_pass1_fix_unlock(ctx); if (pctx->errcode) return; } @@ -3318,7 +5159,7 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, inlinedata_fs = ext2fs_has_feature_inline_data(ctx->fs->super); if (check_ext_attr(ctx, pctx, block_buf, &ea_block_quota)) { - if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + if (e2fsck_should_abort(ctx)) goto out; pb.num_blocks += EXT2FS_B2C(ctx->fs, ea_block_quota.blocks); } @@ -3373,7 +5214,7 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, } end_problem_latch(ctx, PR_LATCH_BLOCK); end_problem_latch(ctx, PR_LATCH_TOOBIG); - if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + if (e2fsck_should_abort(ctx)) goto out; if (pctx->errcode) fix_problem(ctx, PR_1_BLOCK_ITERATE, pctx); @@ -3402,6 +5243,10 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, if (!pb.num_blocks && pb.is_dir && !(inode->i_flags & EXT4_INLINE_DATA_FL)) { + /* + * The mode might be in-correct. Increasing the badness by + * small amount won't hurt much. + */ if (fix_problem(ctx, PR_1_ZERO_LENGTH_DIR, pctx)) { e2fsck_clear_inode(ctx, ino, inode, 0, "check_blocks"); ctx->fs_directory_count--; @@ -3410,6 +5255,7 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, } if (ino != quota_type2inum(PRJQUOTA, fs->super) && + ino != fs->super->s_orphan_file_inum && (ino == EXT2_ROOT_INO || ino >= EXT2_FIRST_INODE(ctx->fs->super)) && !(inode->i_flags & EXT4_EA_INODE_FL)) { quota_data_add(ctx->qctx, (struct ext2_inode_large *) inode, @@ -3427,11 +5273,13 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, pb.num_blocks *= EXT2FS_CLUSTER_RATIO(fs); #if 0 printf("inode %u, i_size = %u, last_block = %llu, i_blocks=%llu, num_blocks = %llu\n", - ino, inode->i_size, pb.last_block, ext2fs_inode_i_blocks(fs, inode), - pb.num_blocks); + ino, inode->i_size, (unsigned long long) pb.last_block, + (unsigned long long) ext2fs_inode_i_blocks(fs, inode), + (unsigned long long) pb.num_blocks); #endif + size = EXT2_I_SIZE(inode); if (pb.is_dir) { - unsigned nblock = inode->i_size >> EXT2_BLOCK_SIZE_BITS(fs->super); + unsigned nblock = size >> EXT2_BLOCK_SIZE_BITS(fs->super); if (inode->i_flags & EXT4_INLINE_DATA_FL) { int flags; size_t sz = 0; @@ -3445,11 +5293,11 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, EXT2_FLAG_IGNORE_CSUM_ERRORS) | (ctx->fs->flags & ~EXT2_FLAG_IGNORE_CSUM_ERRORS); - if (err || sz != inode->i_size) { + if (err || sz != size) { bad_size = 7; pctx->num = sz; } - } else if (inode->i_size & (fs->blocksize - 1)) + } else if (size & (fs->blocksize - 1)) bad_size = 5; else if (nblock > (pb.last_block + 1)) bad_size = 1; @@ -3459,7 +5307,6 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, bad_size = 2; } } else { - size = EXT2_I_SIZE(inode); if ((pb.last_init_lblock >= 0) && /* Do not allow initialized allocated blocks past i_size*/ (size < (__u64)pb.last_init_lblock * fs->blocksize) && @@ -3482,8 +5329,6 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, pctx->num = (pb.last_block + 1) * fs->blocksize; pctx->group = bad_size; if (fix_problem(ctx, PR_1_BAD_I_SIZE, pctx)) { - if (LINUX_S_ISDIR(inode->i_mode)) - pctx->num &= 0xFFFFFFFFULL; ext2fs_inode_size_set(fs, inode, pctx->num); if (EXT2_I_SIZE(inode) == 0 && (inode->i_flags & EXT4_INLINE_DATA_FL)) { @@ -3535,7 +5380,11 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, e2fsck_rehash_dir_later(ctx, ino); out: - if (dirty_inode) + /* need restart if clearing bad inode after block processing */ + if (e2fsck_fix_bad_inode(ctx, pctx)) + e2fsck_clear_inode(ctx, ino, inode, E2F_FLAG_RESTART, + "check_blocks_bad"); + else if (dirty_inode) e2fsck_write_inode(ctx, ino, inode, "check_blocks"); } @@ -3655,13 +5504,14 @@ static int process_block(ext2_filsys fs, (unsigned long) pctx->ino, type, (unsigned long) p->previous_block+1, (unsigned long) blk, - blockcnt); + (long long) blockcnt); } p->fragmented = 1; } } if (p->is_dir && !ext2fs_has_feature_largedir(fs->super) && + !pctx->inode->i_size_high && blockcnt > (1 << (21 - fs->super->s_log_block_size))) problem = PR_1_TOOBIG_DIR; if (p->is_dir && p->num_blocks + 1 >= p->max_blocks) @@ -3686,7 +5536,7 @@ static int process_block(ext2_filsys fs, blk < ctx->fs->super->s_blocks_count && ext2fs_test_block_bitmap2(ctx->block_metadata_map, blk)) { pctx->blk = blk; - fix_problem(ctx, PR_1_CRITICAL_METADATA_COLLISION, pctx); + fix_problem_bad(ctx, PR_1_CRITICAL_METADATA_COLLISION, pctx, 2); if ((ctx->options & E2F_OPT_NO) == 0) ctx->flags |= E2F_FLAG_RESTART_LATER; } @@ -3846,18 +5696,18 @@ static int process_bad_block(ext2_filsys fs, *block_nr = 0; return BLOCK_CHANGED; } - } else if (ext2fs_test_block_bitmap2(ctx->block_found_map, - blk)) { + } else if (is_blocks_used(ctx, blk, 1)) { p->bbcheck = 1; if (fix_problem(ctx, PR_1_BBINODE_BAD_METABLOCK, pctx)) { *block_nr = 0; return BLOCK_CHANGED; } - if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + if (e2fsck_should_abort(ctx)) return BLOCK_ABORT; - } else + } else { mark_block_used(ctx, blk); + } return 0; } #if 0 @@ -3870,7 +5720,7 @@ static int process_bad_block(ext2_filsys fs, * there's an overlap between the filesystem table blocks * (bitmaps and inode table) and the bad block list. */ - if (!ext2fs_test_block_bitmap2(ctx->block_found_map, blk)) { + if (!is_blocks_used(ctx, blk, 1)) { ext2fs_mark_block_bitmap2(ctx->block_found_map, blk); return 0; } @@ -3951,7 +5801,7 @@ static int process_bad_block(ext2_filsys fs, *block_nr = 0; return BLOCK_CHANGED; } - if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + if (e2fsck_should_abort(ctx)) return BLOCK_ABORT; return 0; } @@ -3990,7 +5840,7 @@ static void new_table_block(e2fsck_t ctx, blk64_t first_block, dgrp_t group, */ is_flexbg = ext2fs_has_feature_flex_bg(fs->super); if (is_flexbg) { - flexbg_size = 1 << fs->super->s_log_groups_per_flex; + flexbg_size = 1U << fs->super->s_log_groups_per_flex; flexbg = group / flexbg_size; first_block = ext2fs_group_first_block2(fs, flexbg_size * flexbg);