X-Git-Url: https://git.whamcloud.com/?a=blobdiff_plain;f=e2fsck%2Fpass1.c;h=c240195f9aa121010cb97652d5f45baf361f5e5a;hb=3d05802f925239f516f05b048809281e9e13b3a2;hp=e6778af1f176dad98eb0c8c2225d3823a9cab61a;hpb=abf23439d51a3ddbca475b931abebd381ff7ceea;p=tools%2Fe2fsprogs.git diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c index e6778af..c240195 100644 --- a/e2fsck/pass1.c +++ b/e2fsck/pass1.c @@ -128,16 +128,16 @@ static void unwind_pass1(ext2_filsys fs EXT2FS_ATTR((unused))) * since they have the same requirement; the i_block fields should be * zero. */ -int e2fsck_pass1_check_device_inode(ext2_filsys fs, struct ext2_inode *inode) +int e2fsck_pass1_check_device_inode(ext2_filsys fs EXT2FS_ATTR((unused)), + struct ext2_inode *inode) { int i; /* - * If i_blocks is non-zero, or the index flag is set, then - * this is a bogus device/fifo/socket + * If the index flag is set, then this is a bogus + * device/fifo/socket */ - if ((ext2fs_inode_data_blocks(fs, inode) != 0) || - (inode->i_flags & EXT2_INDEX_FL)) + if (inode->i_flags & EXT2_INDEX_FL) return 0; /* @@ -163,17 +163,42 @@ int e2fsck_pass1_check_device_inode(ext2_filsys fs, struct ext2_inode *inode) * 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, struct ext2_inode *inode, - char *buf) +int e2fsck_pass1_check_symlink(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, char *buf) { unsigned int len; int i; blk_t blocks; + ext2_extent_handle_t handle; + struct ext2_extent_info info; + struct ext2fs_extent extent; if ((inode->i_size_high || inode->i_size == 0) || (inode->i_flags & EXT2_INDEX_FL)) return 0; + if (inode->i_flags & EXT4_EXTENTS_FL) { + if (inode->i_size > fs->blocksize) + return 0; + if (ext2fs_extent_open(fs, ino, &handle)) + return 0; + i = 0; + if (ext2fs_extent_get_info(handle, &info) || + (info.num_entries != 1) || + (info.max_depth != 0)) + goto exit_extent; + if (ext2fs_extent_get(handle, EXT2_EXTENT_ROOT, &extent) || + (extent.e_lblk != 0) || + (extent.e_len != 1) || + (extent.e_pblk < fs->super->s_first_data_block) || + (extent.e_pblk >= fs->super->s_blocks_count)) + goto exit_extent; + i = 1; + exit_extent: + ext2fs_extent_free(handle); + return i; + } + blocks = ext2fs_inode_data_blocks(fs, inode); if (blocks) { if ((inode->i_size >= fs->blocksize) || @@ -246,8 +271,8 @@ static void check_ea_in_inode(e2fsck_t ctx, struct problem_context *pctx) struct ext2_super_block *sb = ctx->fs->super; struct ext2_inode_large *inode; struct ext2_ext_attr_entry *entry; - char *start, *end, *name; - int storage_size, remain, offs; + char *start, *end; + unsigned int storage_size, remain; int problem = 0; inode = (struct ext2_inode_large *) pctx->inode; @@ -262,9 +287,9 @@ static void check_ea_in_inode(e2fsck_t ctx, struct problem_context *pctx) /* take finish entry 0UL into account */ remain = storage_size - sizeof(__u32); - offs = end - start; while (!EXT2_EXT_IS_LAST_ENTRY(entry)) { + __u32 hash; /* header eats this space */ remain -= sizeof(struct ext2_ext_attr_entry); @@ -286,31 +311,24 @@ static void check_ea_in_inode(e2fsck_t ctx, struct problem_context *pctx) goto fix; } - /* check value placement */ - if (entry->e_value_offs + - EXT2_XATTR_SIZE(entry->e_value_size) != offs) { - printf("(entry->e_value_offs + entry->e_value_size: %d, offs: %d)\n", entry->e_value_offs + entry->e_value_size, offs); - pctx->num = entry->e_value_offs; - problem = PR_1_ATTR_VALUE_OFFSET; - goto fix; - } - /* e_value_block must be 0 in inode's ea */ if (entry->e_value_block != 0) { pctx->num = entry->e_value_block; problem = PR_1_ATTR_VALUE_BLOCK; goto fix; } - - /* e_hash must be 0 in inode's ea */ - if (entry->e_hash != 0) { + + hash = ext2fs_ext_attr_hash_entry(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) { pctx->num = entry->e_hash; problem = PR_1_ATTR_HASH; goto fix; } remain -= entry->e_value_size; - offs -= EXT2_XATTR_SIZE(entry->e_value_size); entry = EXT2_EXT_ATTR_NEXT(entry); } @@ -319,15 +337,12 @@ fix: * it seems like a corruption. it's very unlikely we could repair * EA(s) in automatic fashion -bzzz */ -#if 0 - problem = PR_1_ATTR_HASH; -#endif if (problem == 0 || !fix_problem(ctx, problem, pctx)) return; - /* simple remove all possible EA(s) */ + /* simply remove all possible EA(s) */ *((__u32 *)start) = 0UL; - e2fsck_write_inode_full(ctx, pctx->ino, inode, + e2fsck_write_inode_full(ctx, pctx->ino, pctx->inode, EXT2_INODE_SIZE(sb), "pass1"); } @@ -373,6 +388,107 @@ static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx) } } +/* + * Check to see if the inode might really be a directory, despite i_mode + * + * This is a lot of complexity for something for which I'm not really + * convinced happens frequently in the wild. If for any reason this + * causes any problems, take this code out. + * [tytso:20070331.0827EDT] + */ +static void check_is_really_dir(e2fsck_t ctx, struct problem_context *pctx, + char *buf) +{ + struct ext2_inode *inode = pctx->inode; + struct ext2_dir_entry *dirent; + const char *old_op; + errcode_t retval; + blk_t blk; + int i, not_device = 0; + + if (LINUX_S_ISDIR(inode->i_mode) || LINUX_S_ISREG(inode->i_mode) || + LINUX_S_ISLNK(inode->i_mode) || inode->i_block[0] == 0) + return; + + for (i=0; i < EXT2_N_BLOCKS; i++) { + blk = inode->i_block[i]; + if (!blk) + continue; + if (i >= 4) + not_device++; + + if (blk < ctx->fs->super->s_first_data_block || + blk >= ctx->fs->super->s_blocks_count || + ext2fs_fast_test_block_bitmap(ctx->block_found_map, blk)) + return; /* Invalid block, can't be dir */ + } + + if ((LINUX_S_ISCHR(inode->i_mode) || LINUX_S_ISBLK(inode->i_mode)) && + (inode->i_links_count == 1) && !not_device) + return; + + old_op = ehandler_operation(_("reading directory block")); + retval = ext2fs_read_dir_block(ctx->fs, inode->i_block[0], buf); + ehandler_operation(0); + if (retval) + return; + + dirent = (struct ext2_dir_entry *) buf; + if (((dirent->name_len & 0xFF) != 1) || + (dirent->name[0] != '.') || + (dirent->inode != pctx->ino) || + (dirent->rec_len < 12) || + (dirent->rec_len % 4) || + (dirent->rec_len >= ctx->fs->blocksize - 12)) + return; + + dirent = (struct ext2_dir_entry *) (buf + dirent->rec_len); + if (((dirent->name_len & 0xFF) != 2) || + (dirent->name[0] != '.') || + (dirent->name[1] != '.') || + (dirent->rec_len < 12) || + (dirent->rec_len % 4)) + return; + + if (fix_problem(ctx, PR_1_TREAT_AS_DIRECTORY, pctx)) { + inode->i_mode = (inode->i_mode & 07777) | LINUX_S_IFDIR; + e2fsck_write_inode_full(ctx, pctx->ino, inode, + EXT2_INODE_SIZE(ctx->fs->super), + "check_is_really_dir"); + } +} + +extern void e2fsck_setup_tdb_icount(e2fsck_t ctx, int flags, + ext2_icount_t *ret) +{ + unsigned int threshold; + ext2_ino_t num_dirs; + errcode_t retval; + char *tdb_dir; + int enable; + + *ret = 0; + + 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 */ + + if (!enable || !tdb_dir || access(tdb_dir, W_OK) || + (threshold && num_dirs <= threshold)) + return; + + retval = ext2fs_create_icount_tdb(ctx->fs, tdb_dir, flags, ret); + if (retval) + *ret = 0; +} + void e2fsck_pass1(e2fsck_t ctx) { int i; @@ -389,12 +505,13 @@ void e2fsck_pass1(e2fsck_t ctx) struct problem_context pctx; struct scan_callback_struct scan_struct; struct ext2_super_block *sb = ctx->fs->super; - int imagic_fs; + const char *old_op; + int imagic_fs, extent_fs; int busted_fs_time = 0; int inode_size; #ifdef RESOURCE_TRACK - init_resource_track(&rtrack); + init_resource_track(&rtrack, ctx->fs->io); #endif clear_problem_context(&pctx); @@ -423,6 +540,7 @@ void e2fsck_pass1(e2fsck_t ctx) #undef EXT2_BPP imagic_fs = (sb->s_feature_compat & EXT2_FEATURE_COMPAT_IMAGIC_INODES); + extent_fs = (sb->s_feature_incompat & EXT3_FEATURE_INCOMPAT_EXTENTS); /* * Allocate bitmaps structures @@ -459,8 +577,10 @@ void e2fsck_pass1(e2fsck_t ctx) ctx->flags |= E2F_FLAG_ABORT; return; } - pctx.errcode = ext2fs_create_icount2(fs, 0, 0, 0, - &ctx->inode_link_info); + e2fsck_setup_tdb_icount(ctx, 0, &ctx->inode_link_info); + if (!ctx->inode_link_info) + pctx.errcode = ext2fs_create_icount2(fs, 0, 0, 0, + &ctx->inode_link_info); if (pctx.errcode) { fix_problem(ctx, PR_1_ALLOCATE_ICOUNT, &pctx); ctx->flags |= E2F_FLAG_ABORT; @@ -481,6 +601,7 @@ void e2fsck_pass1(e2fsck_t ctx) if (pctx.errcode) { fix_problem(ctx, PR_1_ALLOCATE_DBCOUNT, &pctx); ctx->flags |= E2F_FLAG_ABORT; + ext2fs_free_mem(&inode); return; } @@ -502,12 +623,15 @@ void e2fsck_pass1(e2fsck_t ctx) block_buf = (char *) e2fsck_allocate_memory(ctx, fs->blocksize * 3, "block interate buffer"); e2fsck_use_inode_shortcuts(ctx, 1); - ehandler_operation(_("doing inode scan")); + old_op = ehandler_operation(_("opening inode scan")); pctx.errcode = ext2fs_open_inode_scan(fs, ctx->inode_buffer_blocks, &scan); + ehandler_operation(old_op); if (pctx.errcode) { fix_problem(ctx, PR_1_ISCAN_ERROR, &pctx); ctx->flags |= E2F_FLAG_ABORT; + ext2fs_free_mem(&block_buf); + ext2fs_free_mem(&inode); return; } ext2fs_inode_scan_flags(scan, EXT2_SF_SKIP_MISSING_ITABLE, 0); @@ -523,8 +647,10 @@ void e2fsck_pass1(e2fsck_t ctx) busted_fs_time = 1; while (1) { + old_op = ehandler_operation(_("getting next inode from scan")); pctx.errcode = ext2fs_get_next_inode_full(scan, &ino, inode, inode_size); + ehandler_operation(old_op); if (ctx->flags & E2F_FLAG_SIGNAL_MASK) return; if (pctx.errcode == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE) { @@ -554,6 +680,76 @@ void e2fsck_pass1(e2fsck_t ctx) return; } } + + /* + * Test for incorrect extent flag settings. + * + * On big-endian machines we must be careful: + * When the inode is read, the i_block array is not swapped + * if the extent flag is set. Therefore if we are testing + * for or fixing a wrongly-set flag, we must potentially + * (un)swap before testing, or after fixing. + */ + + /* + * In this case the extents flag was set when read, so + * extent_header_verify is ok. If the inode is cleared, + * no need to swap... so no extra swapping here. + */ + if ((inode->i_flags & EXT4_EXTENTS_FL) && !extent_fs && + (inode->i_links_count || (ino == EXT2_BAD_INO) || + (ino == EXT2_ROOT_INO) || (ino == EXT2_JOURNAL_INO))) { + if ((ext2fs_extent_header_verify(inode->i_block, + sizeof(inode->i_block)) == 0) && + fix_problem(ctx, PR_1_EXTENT_FEATURE, &pctx)) { + sb->s_feature_incompat |= EXT3_FEATURE_INCOMPAT_EXTENTS; + ext2fs_mark_super_dirty(fs); + extent_fs = 1; + } else if (fix_problem(ctx, PR_1_EXTENTS_SET, &pctx)) { + clear_inode: + e2fsck_clear_inode(ctx, ino, inode, 0, "pass1"); + if (ino == EXT2_BAD_INO) + ext2fs_mark_inode_bitmap(ctx->inode_used_map, + ino); + continue; + } + } + + /* + * For big-endian machines: + * If the inode didn't have the extents flag set when it + * was read, then the i_blocks array was swapped. To test + * as an extents header, we must swap it back first. + * IF we then set the extents flag, the entire i_block + * array must be un/re-swapped to make it proper extents data. + */ + if (extent_fs && !(inode->i_flags & EXT4_EXTENTS_FL) && + (inode->i_links_count || (ino == EXT2_BAD_INO) || + (ino == EXT2_ROOT_INO) || (ino == EXT2_JOURNAL_INO)) && + (LINUX_S_ISREG(inode->i_mode) || + LINUX_S_ISDIR(inode->i_mode))) { + void *ehp; +#ifdef WORDS_BIGENDIAN + __u32 tmp_block[EXT2_N_BLOCKS]; + + for (i = 0; i < EXT2_N_BLOCKS; i++) + tmp_block[i] = ext2fs_swab32(inode->i_block[i]); + ehp = tmp_block; +#else + ehp = inode->i_block; +#endif + if ((ext2fs_extent_header_verify(ehp, + sizeof(inode->i_block)) == 0) && + (fix_problem(ctx, PR_1_UNSET_EXTENT_FL, &pctx))) { + inode->i_flags |= EXT4_EXTENTS_FL; +#ifdef WORDS_BIGENDIAN + memcpy(inode->i_block, tmp_block, + sizeof(inode->i_block)); +#endif + e2fsck_write_inode(ctx, ino, inode, "pass1"); + } + } + if (ino == EXT2_BAD_INO) { struct process_block_struct pb; @@ -596,15 +792,8 @@ void e2fsck_pass1(e2fsck_t ctx) * regnerated in pass #3. */ if (!LINUX_S_ISDIR(inode->i_mode)) { - if (fix_problem(ctx, PR_1_ROOT_NO_DIR, &pctx)) { - inode->i_dtime = ctx->now; - inode->i_links_count = 0; - ext2fs_icount_store(ctx->inode_link_info, - ino, 0); - e2fsck_write_inode(ctx, ino, inode, - "pass1"); - } - + if (fix_problem(ctx, PR_1_ROOT_NO_DIR, &pctx)) + goto clear_inode; } /* * If dtime is set, offer to clear it. mke2fs @@ -731,18 +920,10 @@ void e2fsck_pass1(e2fsck_t ctx) ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino); switch (fs->super->s_creator_os) { - case EXT2_OS_LINUX: - frag = inode->osd2.linux2.l_i_frag; - fsize = inode->osd2.linux2.l_i_fsize; - break; case EXT2_OS_HURD: frag = inode->osd2.hurd2.h_i_frag; fsize = inode->osd2.hurd2.h_i_fsize; break; - case EXT2_OS_MASIX: - frag = inode->osd2.masix2.m_i_frag; - fsize = inode->osd2.masix2.m_i_fsize; - break; default: frag = fsize = 0; } @@ -750,6 +931,11 @@ void e2fsck_pass1(e2fsck_t ctx) if (inode->i_faddr || frag || fsize || (LINUX_S_ISDIR(inode->i_mode) && inode->i_dir_acl)) mark_inode_bad(ctx, ino); + if ((fs->super->s_creator_os == EXT2_OS_LINUX) && + !(fs->super->s_feature_ro_compat & + EXT4_FEATURE_RO_COMPAT_HUGE_FILE) && + (inode->osd2.linux2.l_i_blocks_hi != 0)) + mark_inode_bad(ctx, ino); if (inode->i_flags & EXT2_IMAGIC_FL) { if (imagic_fs) { if (!ctx->inode_imagic_map) @@ -766,6 +952,19 @@ void e2fsck_pass1(e2fsck_t ctx) } check_inode_extra_space(ctx, &pctx); + check_is_really_dir(ctx, &pctx, block_buf); + + /* + * ext2fs_inode_has_valid_blocks does not actually look + * at i_block[] values, so not endian-sensitive here. + */ + if (extent_fs && (inode->i_flags & EXT4_EXTENTS_FL) && + LINUX_S_ISLNK(inode->i_mode) && + !ext2fs_inode_has_valid_blocks(inode) && + fix_problem(ctx, PR_1_FAST_SYMLINK_EXTENT_FL, &pctx)) { + inode->i_flags &= ~EXT4_EXTENTS_FL; + e2fsck_write_inode(ctx, ino, inode, "pass1"); + } if (LINUX_S_ISDIR(inode->i_mode)) { ext2fs_mark_inode_bitmap(ctx->inode_dir_map, ino); @@ -785,7 +984,8 @@ 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, inode, block_buf)) { + e2fsck_pass1_check_symlink(fs, ino, inode, + block_buf)) { check_immutable(ctx, &pctx); ctx->fs_symlinks_count++; if (ext2fs_inode_data_blocks(fs, inode) == 0) { @@ -812,10 +1012,11 @@ void e2fsck_pass1(e2fsck_t ctx) ctx->fs_dind_count++; if (inode->i_block[EXT2_TIND_BLOCK]) ctx->fs_tind_count++; - if (inode->i_block[EXT2_IND_BLOCK] || - inode->i_block[EXT2_DIND_BLOCK] || - inode->i_block[EXT2_TIND_BLOCK] || - inode->i_file_acl) { + if (!(inode->i_flags & EXT4_EXTENTS_FL) && + (inode->i_block[EXT2_IND_BLOCK] || + inode->i_block[EXT2_DIND_BLOCK] || + inode->i_block[EXT2_TIND_BLOCK] || + inode->i_file_acl)) { inodes_to_process[process_inode_count].ino = ino; inodes_to_process[process_inode_count].inode = *inode; process_inode_count++; @@ -834,7 +1035,6 @@ void e2fsck_pass1(e2fsck_t ctx) } process_inodes(ctx, block_buf); ext2fs_close_inode_scan(scan); - ehandler_operation(0); /* * If any extended attribute blocks' reference counts need to @@ -864,7 +1064,6 @@ void e2fsck_pass1(e2fsck_t ctx) if (ctx->flags & E2F_FLAG_RESIZE_INODE) { ext2fs_block_bitmap save_bmap; - errcode_t retval; save_bmap = fs->block_map; fs->block_map = ctx->block_found_map; @@ -914,7 +1113,7 @@ endit: #ifdef RESOURCE_TRACK if (ctx->options & E2F_OPT_TIME2) { e2fsck_clear_progbar(ctx); - print_resource_track(_("Pass 1"), &rtrack); + print_resource_track(_("Pass 1"), &rtrack, ctx->fs->io); } #endif } @@ -1160,8 +1359,8 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, struct ext2_ext_attr_header *header; struct ext2_ext_attr_entry *entry; int count; - region_t region; - + region_t region = 0; + blk = inode->i_file_acl; if (blk == 0) return 0; @@ -1227,7 +1426,7 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, ea_refcount_increment(ctx->refcount_extra, blk, 0); return 1; } - + /* * OK, we haven't seen this EA block yet. So we need to * validate it @@ -1261,14 +1460,17 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx)) goto clear_extattr; } - + entry = (struct ext2_ext_attr_entry *)(header+1); end = block_buf + fs->blocksize; while ((char *)entry < end && *(__u32 *)entry) { + __u32 hash; + if (region_allocate(region, (char *)entry - (char *)header, EXT2_EXT_ATTR_LEN(entry->e_name_len))) { if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx)) goto clear_extattr; + break; } if ((ctx->ext_attr_ver == 1 && (entry->e_name_len == 0 || entry->e_name_index != 0)) || @@ -1276,17 +1478,34 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, entry->e_name_index == 0)) { if (fix_problem(ctx, PR_1_EA_BAD_NAME, pctx)) goto clear_extattr; + break; } if (entry->e_value_block != 0) { if (fix_problem(ctx, PR_1_EA_BAD_VALUE, pctx)) goto clear_extattr; } + if (entry->e_value_offs + entry->e_value_size > fs->blocksize) { + if (fix_problem(ctx, PR_1_EA_BAD_VALUE, pctx)) + goto clear_extattr; + break; + } if (entry->e_value_size && region_allocate(region, entry->e_value_offs, EXT2_EXT_ATTR_SIZE(entry->e_value_size))) { if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx)) goto clear_extattr; } + + hash = ext2fs_ext_attr_hash_entry(entry, block_buf + + entry->e_value_offs); + + if (entry->e_hash != hash) { + pctx->num = entry->e_hash; + if (fix_problem(ctx, PR_1_ATTR_HASH, pctx)) + goto clear_extattr; + entry->e_hash = hash; + } + entry = EXT2_EXT_ATTR_NEXT(entry); } if (region_allocate(region, (char *)entry - (char *)header, 4)) { @@ -1300,10 +1519,11 @@ static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, ea_refcount_store(ctx->refcount, blk, count); mark_block_used(ctx, blk); ext2fs_fast_mark_block_bitmap(ctx->block_ea_map, blk); - return 1; clear_extattr: + if (region) + region_free(region); inode->i_file_acl = 0; e2fsck_write_inode(ctx, ino, inode, "check_ext_attr"); return 0; @@ -1311,8 +1531,7 @@ clear_extattr: /* Returns 1 if bad htree, 0 if OK */ static int handle_htree(e2fsck_t ctx, struct problem_context *pctx, - ext2_ino_t ino EXT2FS_ATTR((unused)), - struct ext2_inode *inode, + ext2_ino_t ino, struct ext2_inode *inode, char *block_buf) { struct ext2_dx_root_info *root; @@ -1326,12 +1545,17 @@ static int handle_htree(e2fsck_t ctx, struct problem_context *pctx, fix_problem(ctx, PR_1_HTREE_SET, pctx))) return 1; - blk = inode->i_block[0]; - if (((blk == 0) || - (blk < fs->super->s_first_data_block) || - (blk >= fs->super->s_blocks_count)) && - fix_problem(ctx, PR_1_HTREE_BADROOT, pctx)) - return 1; + pctx->errcode = ext2fs_bmap(fs, ino, inode, 0, 0, 0, &blk); + + if ((pctx->errcode) || + (blk == 0) || + (blk < fs->super->s_first_data_block) || + (blk >= fs->super->s_blocks_count)) { + if (fix_problem(ctx, PR_1_HTREE_BADROOT, pctx)) + return 1; + else + return 0; + } retval = io_channel_read_blk(fs->io, blk, 1, block_buf); if (retval && fix_problem(ctx, PR_1_HTREE_BADROOT, pctx)) @@ -1363,6 +1587,162 @@ static int handle_htree(e2fsck_t ctx, struct problem_context *pctx, return 0; } +void e2fsck_clear_inode(e2fsck_t ctx, ext2_ino_t ino, + struct ext2_inode *inode, int restart_flag, + const char *source) +{ + inode->i_flags = 0; + inode->i_links_count = 0; + ext2fs_icount_store(ctx->inode_link_info, ino, 0); + inode->i_dtime = ctx->now; + + ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino); + ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino); + if (ctx->inode_reg_map) + ext2fs_unmark_inode_bitmap(ctx->inode_reg_map, ino); + if (ctx->inode_bad_map) + ext2fs_unmark_inode_bitmap(ctx->inode_bad_map, ino); + + /* + * If the inode was partially accounted for before processing + * was aborted, we need to restart the pass 1 scan. + */ + ctx->flags |= restart_flag; + + e2fsck_write_inode(ctx, ino, inode, source); +} + +static void scan_extent_node(e2fsck_t ctx, struct problem_context *pctx, + struct process_block_struct *pb, + blk64_t start_block, + ext2_extent_handle_t ehandle) +{ + struct ext2fs_extent extent; + blk_t blk; + e2_blkcnt_t blockcnt; + unsigned int i; + int is_dir, is_leaf; + errcode_t problem; + struct ext2_extent_info info; + + pctx->errcode = ext2fs_extent_get_info(ehandle, &info); + if (pctx->errcode) + return; + + pctx->errcode = ext2fs_extent_get(ehandle, EXT2_EXTENT_FIRST_SIB, + &extent); + while (!pctx->errcode && info.num_entries-- > 0) { + is_leaf = extent.e_flags & EXT2_EXTENT_FLAGS_LEAF; + is_dir = LINUX_S_ISDIR(pctx->inode->i_mode); + + problem = 0; + if (extent.e_pblk < ctx->fs->super->s_first_data_block || + extent.e_pblk >= ctx->fs->super->s_blocks_count) + problem = PR_1_EXTENT_BAD_START_BLK; + else if (extent.e_lblk < start_block) + problem = PR_1_OUT_OF_ORDER_EXTENTS; + else if (is_leaf && + (extent.e_pblk + extent.e_len) > + ctx->fs->super->s_blocks_count) + problem = PR_1_EXTENT_ENDS_BEYOND; + + if (problem) { + pctx->blk = extent.e_pblk; + pctx->blk2 = extent.e_lblk; + pctx->num = extent.e_len; + if (fix_problem(ctx, problem, pctx)) { + pctx->errcode = + ext2fs_extent_delete(ehandle, 0); + if (pctx->errcode) { + fix_problem(ctx, + PR_1_EXTENT_DELETE_FAIL, + pctx); + /* Should never get here */ + ctx->flags |= E2F_FLAG_ABORT; + return; + } + pctx->errcode = ext2fs_extent_get(ehandle, + EXT2_EXTENT_CURRENT, + &extent); + if (pctx->errcode == EXT2_ET_NO_CURRENT_NODE) { + pctx->errcode = 0; + break; + } + continue; + } + goto next; + } + + if (!is_leaf) { + mark_block_used(ctx, extent.e_pblk); + pb->num_blocks++; + pctx->errcode = ext2fs_extent_get(ehandle, + EXT2_EXTENT_DOWN, &extent); + if (pctx->errcode) { + printf("Error1: %s on inode %u\n", + error_message(pctx->errcode), pctx->ino); + abort(); + } + scan_extent_node(ctx, pctx, pb, extent.e_lblk, ehandle); + pctx->errcode = ext2fs_extent_get(ehandle, + EXT2_EXTENT_UP, &extent); + if (pctx->errcode) { + printf("Error1: %s on inode %u\n", + error_message(pctx->errcode), pctx->ino); + abort(); + } + goto next; + } + + for (blk = extent.e_pblk, blockcnt = extent.e_lblk, i = 0; + i < extent.e_len; + blk++, blockcnt++, i++) { + mark_block_used(ctx, blk); + + if (is_dir) { + pctx->errcode = ext2fs_add_dir_block(ctx->fs->dblist, pctx->ino, blk, blockcnt); + if (pctx->errcode) { + pctx->blk = blk; + pctx->num = blockcnt; + fix_problem(ctx, PR_1_ADD_DBLOCK, pctx); + /* Should never get here */ + ctx->flags |= E2F_FLAG_ABORT; + return; + } + } + } + pb->num_blocks += extent.e_len; + start_block = pb->last_block = extent.e_lblk + extent.e_len - 1; + next: + pctx->errcode = ext2fs_extent_get(ehandle, + EXT2_EXTENT_NEXT_SIB, + &extent); + } + if (pctx->errcode == EXT2_ET_EXTENT_NO_NEXT) + pctx->errcode = 0; +} + +static void check_blocks_extents(e2fsck_t ctx, struct problem_context *pctx, + struct process_block_struct *pb) +{ + struct ext2_inode *inode = pctx->inode; + ext2_extent_handle_t ehandle; + ext2_filsys fs = ctx->fs; + ext2_ino_t ino = pctx->ino; + + pctx->errcode = ext2fs_extent_open(fs, ino, &ehandle); + if (pctx->errcode && + fix_problem(ctx, PR_1_READ_EXTENT, pctx)) { + e2fsck_clear_inode(ctx, ino, inode, 0, "check_blocks_extents"); + pctx->errcode = 0; + return; + } + + scan_extent_node(ctx, pctx, pb, 0, ehandle); + + ext2fs_extent_free(ehandle); +} + /* * This subroutine is called on each inode to account for all of the * blocks used by that inode. @@ -1376,6 +1756,7 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, struct ext2_inode *inode = pctx->inode; int bad_size = 0; int dirty_inode = 0; + int extent_fs; __u64 size; pb.ino = ino; @@ -1395,6 +1776,9 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, pctx->ino = ino; pctx->errcode = 0; + extent_fs = (ctx->fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_EXTENTS); + if (inode->i_flags & EXT2_COMPRBLK_FL) { if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_COMPRESSION) @@ -1407,13 +1791,20 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, } } - if (inode->i_file_acl && check_ext_attr(ctx, pctx, block_buf)) + if (inode->i_file_acl && check_ext_attr(ctx, pctx, block_buf)) { + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + goto out; pb.num_blocks++; + } - if (ext2fs_inode_has_valid_blocks(inode)) - pctx->errcode = ext2fs_block_iterate2(fs, ino, - pb.is_dir ? BLOCK_FLAG_HOLE : 0, - block_buf, process_block, &pb); + if (ext2fs_inode_has_valid_blocks(inode)) { + if (extent_fs && (inode->i_flags & EXT4_EXTENTS_FL)) + check_blocks_extents(ctx, pctx, &pb); + else + pctx->errcode = ext2fs_block_iterate2(fs, ino, + pb.is_dir ? BLOCK_FLAG_HOLE : 0, + block_buf, process_block, &pb); + } end_problem_latch(ctx, PR_LATCH_BLOCK); end_problem_latch(ctx, PR_LATCH_TOOBIG); if (ctx->flags & E2F_FLAG_SIGNAL_MASK) @@ -1425,22 +1816,31 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, ctx->fs_fragmented++; if (pb.clear) { - inode->i_links_count = 0; - ext2fs_icount_store(ctx->inode_link_info, ino, 0); - inode->i_dtime = ctx->now; - dirty_inode++; - ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino); - ext2fs_unmark_inode_bitmap(ctx->inode_reg_map, ino); - ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino); - /* - * The inode was probably partially accounted for - * before processing was aborted, so we need to - * restart the pass 1 scan. - */ - ctx->flags |= E2F_FLAG_RESTART; - goto out; + e2fsck_clear_inode(ctx, ino, inode, E2F_FLAG_RESTART, + "check_blocks"); + return; } + if (pb.is_dir) { + while (1) { + struct ext2_db_entry *entry; + + if (ext2fs_dblist_get_last(fs->dblist, &entry) || + (entry->ino != ino) || + (entry->blk != 0) || + (entry->blockcnt == 0)) + break; + /* printf("Dropping ino %lu blk %lu blockcnt %d\n", + entry->ino, entry->blk, entry->blockcnt); */ + ext2fs_dblist_drop_last(fs->dblist); + if (ext2fs_dblist_get_last(fs->dblist, &entry) || + (entry->ino != ino)) + pb.last_block--; + else + pb.last_block = entry->blockcnt; + } + } + if (inode->i_flags & EXT2_INDEX_FL) { if (handle_htree(ctx, pctx, ino, inode, block_buf)) { inode->i_flags &= ~EXT2_INDEX_FL; @@ -1458,19 +1858,16 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, if (!pb.num_blocks && pb.is_dir) { if (fix_problem(ctx, PR_1_ZERO_LENGTH_DIR, pctx)) { - inode->i_links_count = 0; - ext2fs_icount_store(ctx->inode_link_info, ino, 0); - inode->i_dtime = ctx->now; - dirty_inode++; - ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino); - ext2fs_unmark_inode_bitmap(ctx->inode_reg_map, ino); - ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino); + e2fsck_clear_inode(ctx, ino, inode, 0, "check_blocks"); ctx->fs_directory_count--; - goto out; + return; } } - pb.num_blocks *= (fs->blocksize / 512); + if (!(fs->super->s_feature_ro_compat & + EXT4_FEATURE_RO_COMPAT_HUGE_FILE) || + !(inode->i_flags & EXT4_HUGE_FILE_FL)) + pb.num_blocks *= (fs->blocksize / 512); #if 0 printf("inode %u, i_size = %lu, last_block = %lld, i_blocks=%lu, num_blocks = %lu\n", ino, inode->i_size, pb.last_block, inode->i_blocks, @@ -1478,7 +1875,9 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, #endif if (pb.is_dir) { int nblock = inode->i_size >> EXT2_BLOCK_SIZE_BITS(fs->super); - if (nblock > (pb.last_block + 1)) + if (inode->i_size & (fs->blocksize - 1)) + bad_size = 5; + else if (nblock > (pb.last_block + 1)) bad_size = 1; else if (nblock < (pb.last_block + 1)) { if (((pb.last_block + 1) - nblock) > @@ -1486,12 +1885,23 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, bad_size = 2; } } else { + e2_blkcnt_t blkpg = ctx->blocks_per_page; + size = EXT2_I_SIZE(inode); if ((pb.last_block >= 0) && - (size < (__u64) pb.last_block * fs->blocksize)) + /* allow allocated blocks to end of PAGE_SIZE */ + (size < (__u64)pb.last_block * fs->blocksize) && + (pb.last_block / blkpg * blkpg != pb.last_block || + size < (__u64)(pb.last_block & ~(blkpg-1)) *fs->blocksize)) bad_size = 3; - else if (size > ext2_max_sizes[fs->super->s_log_block_size]) + else if (!(extent_fs && (inode->i_flags & EXT4_EXTENTS_FL)) && + size > ext2_max_sizes[fs->super->s_log_block_size]) + /* too big for a direct/indirect-mapped file */ bad_size = 4; + else if ((extent_fs && (inode->i_flags & EXT4_EXTENTS_FL)) && + size > (1LL << (32 + fs->super->s_log_block_size) - 1)) + /* too big for an extent-based file - 32bit ee_block */ + bad_size = 6; } /* i_size for symlinks is checked elsewhere */ if (bad_size && !LINUX_S_ISLNK(inode->i_mode)) { @@ -1507,10 +1917,15 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, if (LINUX_S_ISREG(inode->i_mode) && (inode->i_size_high || inode->i_size & 0x80000000UL)) ctx->large_files++; - if (pb.num_blocks != inode->i_blocks) { + if ((pb.num_blocks != inode->i_blocks) || + ((fs->super->s_feature_ro_compat & + EXT4_FEATURE_RO_COMPAT_HUGE_FILE) && + (inode->i_flags & EXT4_HUGE_FILE_FL) && + (inode->osd2.linux2.l_i_blocks_hi != 0))) { pctx->num = pb.num_blocks; if (fix_problem(ctx, PR_1_BAD_I_BLOCKS, pctx)) { inode->i_blocks = pb.num_blocks; + inode->osd2.linux2.l_i_blocks_hi = 0; dirty_inode++; } pctx->num = 0; @@ -1635,6 +2050,7 @@ static int process_block(ext2_filsys fs, printf("Missing block (#%d) in directory inode %lu!\n", blockcnt, p->ino); #endif + p->last_block = blockcnt; goto mark_dir; } return 0; @@ -1953,7 +2369,7 @@ static void handle_fs_bad_blocks(e2fsck_t ctx) { ext2_filsys fs = ctx->fs; dgrp_t i; - int first_block; + blk_t first_block; for (i = 0; i < fs->group_desc_count; i++) { first_block = ext2fs_group_first_block(fs, i); @@ -2107,6 +2523,48 @@ static errcode_t pass1_check_directory(ext2_filsys fs, ext2_ino_t ino) return 0; } +static errcode_t e2fsck_get_alloc_block(ext2_filsys fs, blk64_t goal, + blk64_t *ret) +{ + e2fsck_t ctx = (e2fsck_t) fs->priv_data; + errcode_t retval; + blk_t new_block; + + if (ctx->block_found_map) { + retval = ext2fs_new_block(fs, (blk_t) goal, + ctx->block_found_map, &new_block); + if (retval) + return retval; + } else { + if (!fs->block_map) { + retval = ext2fs_read_block_bitmap(fs); + if (retval) + return retval; + } + + retval = ext2fs_new_block(fs, (blk_t) goal, 0, &new_block); + if (retval) + return retval; + } + + *ret = new_block; + return (0); +} + +static void e2fsck_block_alloc_stats(ext2_filsys fs, blk64_t blk, int inuse) +{ + e2fsck_t ctx = (e2fsck_t) fs->priv_data; + + if (ctx->block_found_map) { + if (inuse > 0) + ext2fs_mark_block_bitmap(ctx->block_found_map, + (blk_t) blk); + else + ext2fs_unmark_block_bitmap(ctx->block_found_map, + (blk_t) blk); + } +} + void e2fsck_use_inode_shortcuts(e2fsck_t ctx, int bool) { ext2_filsys fs = ctx->fs; @@ -2117,6 +2575,11 @@ void e2fsck_use_inode_shortcuts(e2fsck_t ctx, int bool) fs->read_inode = pass1_read_inode; fs->write_inode = pass1_write_inode; ctx->stashed_ino = 0; + ext2fs_set_alloc_block_callback(fs, e2fsck_get_alloc_block, + 0); + ext2fs_set_block_alloc_stats_callback(fs, + e2fsck_block_alloc_stats, + 0); } else { fs->get_blocks = 0; fs->check_directory = 0;