e2fsprogs/e2fsck/pass1.c
Theodore Ts'o c4e3d3f374 ext2fs_getmem(), ext2fs_free_mem(), and ext2fs_resize_mem()
all now take a 'void *' instead of a 'void **' in order to 
avoid pointer aliasing problems with GCC 3.x.
2003-08-01 09:41:07 -04:00

1961 lines
53 KiB
C

/*
* pass1.c -- pass #1 of e2fsck: sequential scan of the inode table
*
* Copyright (C) 1993, 1994, 1995, 1996, 1997 Theodore Ts'o.
*
* %Begin-Header%
* This file may be redistributed under the terms of the GNU Public
* License.
* %End-Header%
*
* Pass 1 of e2fsck iterates over all the inodes in the filesystems,
* and applies the following tests to each inode:
*
* - The mode field of the inode must be legal.
* - The size and block count fields of the inode are correct.
* - A data block must not be used by another inode
*
* Pass 1 also gathers the collects the following information:
*
* - 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)
* - 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 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)
*
* Pass 1 is designed to stash away enough information so that the
* other passes should not need to read in the inode information
* during the normal course of a filesystem check. (Althogh if an
* inconsistency is detected, other passes may need to read in an
* inode to fix it.)
*
* Note that pass 1B will be invoked if there are any duplicate blocks
* found.
*/
#include <string.h>
#include <time.h>
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#include "e2fsck.h"
#include <ext2fs/ext2_ext_attr.h>
#include "problem.h"
#ifdef NO_INLINE_FUNCS
#define _INLINE_
#else
#define _INLINE_ inline
#endif
static int process_block(ext2_filsys fs, blk_t *blocknr,
e2_blkcnt_t blockcnt, blk_t ref_blk,
int ref_offset, void *priv_data);
static int process_bad_block(ext2_filsys fs, blk_t *block_nr,
e2_blkcnt_t blockcnt, blk_t ref_blk,
int ref_offset, void *priv_data);
static void check_blocks(e2fsck_t ctx, struct problem_context *pctx,
char *block_buf);
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 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);
static void adjust_extattr_refcount(e2fsck_t ctx, ext2_refcount_t refcount,
char *block_buf, int adjust_sign);
/* static char *describe_illegal_block(ext2_filsys fs, blk_t block); */
struct process_block_struct {
ext2_ino_t ino;
int is_dir:1, is_reg:1, clear:1, suppress:1,
fragmented:1, compressed:1;
blk_t num_blocks;
blk_t max_blocks;
e2_blkcnt_t last_block;
int num_illegal_blocks;
blk_t previous_block;
struct ext2_inode *inode;
struct problem_context *pctx;
e2fsck_t ctx;
};
struct process_inode_block {
ext2_ino_t ino;
struct ext2_inode inode;
};
struct scan_callback_struct {
e2fsck_t ctx;
char *block_buf;
};
/*
* For the inodes to process list.
*/
static struct process_inode_block *inodes_to_process;
static 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_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.
*
* Note: this routine is now also used to check FIFO's and Sockets,
* 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 i;
/*
* If i_blocks is non-zero, or 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))
return 0;
/*
* We should be able to do the test below all the time, but
* because the kernel doesn't forcibly clear the device
* inode's additional i_block fields, there are some rare
* occasions when a legitimate device inode will have non-zero
* additional i_block fields. So for now, we only complain
* when the immutable flag is set, which should never happen
* for devices. (And that's when the problem is caused, since
* you can't set or clear immutable flags for devices.) Once
* the kernel has been fixed we can change this...
*/
if (inode->i_flags & (EXT2_IMMUTABLE_FL | EXT2_APPEND_FL)) {
for (i=4; i < EXT2_N_BLOCKS; i++)
if (inode->i_block[i])
return 0;
}
return 1;
}
/*
* 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 len;
int i;
blk_t blocks;
if ((inode->i_size_high || inode->i_size == 0) ||
(inode->i_flags & EXT2_INDEX_FL))
return 0;
blocks = ext2fs_inode_data_blocks(fs, inode);
if (blocks) {
if ((inode->i_size >= fs->blocksize) ||
(blocks != fs->blocksize >> 9) ||
(inode->i_block[0] < fs->super->s_first_data_block) ||
(inode->i_block[0] >= fs->super->s_blocks_count))
return 0;
for (i = 1; i < EXT2_N_BLOCKS; i++)
if (inode->i_block[i])
return 0;
if (io_channel_read_blk(fs->io, inode->i_block[0], 1, buf))
return 0;
len = strnlen(buf, fs->blocksize);
if (len == fs->blocksize)
return 0;
} else {
if (inode->i_size >= sizeof(inode->i_block))
return 0;
len = strnlen((char *)inode->i_block, sizeof(inode->i_block));
if (len == sizeof(inode->i_block))
return 0;
}
if (len != inode->i_size)
return 0;
return 1;
}
/*
* If the immutable (or append-only) flag is set on the inode, offer
* to clear it.
*/
#define BAD_SPECIAL_FLAGS (EXT2_IMMUTABLE_FL | EXT2_APPEND_FL)
static void check_immutable(e2fsck_t ctx, struct problem_context *pctx)
{
if (!(pctx->inode->i_flags & BAD_SPECIAL_FLAGS))
return;
if (!fix_problem(ctx, PR_1_SET_IMMUTABLE, pctx))
return;
pctx->inode->i_flags &= ~BAD_SPECIAL_FLAGS;
e2fsck_write_inode(ctx, pctx->ino, pctx->inode, "pass1");
}
/*
* If device, fifo or socket, check size is zero -- if not offer to
* clear it
*/
static void check_size(e2fsck_t ctx, struct problem_context *pctx)
{
struct ext2_inode *inode = pctx->inode;
if ((inode->i_size == 0) && (inode->i_size_high == 0))
return;
if (!fix_problem(ctx, PR_1_SET_NONZSIZE, pctx))
return;
inode->i_size = 0;
inode->i_size_high = 0;
e2fsck_write_inode(ctx, pctx->ino, pctx->inode, "pass1");
}
void e2fsck_pass1(e2fsck_t ctx)
{
int i;
__u64 max_sizes;
ext2_filsys fs = ctx->fs;
ext2_ino_t ino;
struct ext2_inode inode;
ext2_inode_scan scan;
char *block_buf;
#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;
int imagic_fs;
int busted_fs_time = 0;
#ifdef RESOURCE_TRACK
init_resource_track(&rtrack);
#endif
clear_problem_context(&pctx);
if (!(ctx->options & E2F_OPT_PREEN))
fix_problem(ctx, PR_1_PASS_HEADER, &pctx);
if ((fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) &&
!(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
#define EXT2_BPP(bits) (1ULL << ((bits) - 2))
for (i = EXT2_MIN_BLOCK_LOG_SIZE; i <= EXT2_MAX_BLOCK_LOG_SIZE; i++) {
max_sizes = EXT2_NDIR_BLOCKS + EXT2_BPP(i);
max_sizes = max_sizes + EXT2_BPP(i) * EXT2_BPP(i);
max_sizes = max_sizes + EXT2_BPP(i) * EXT2_BPP(i) * EXT2_BPP(i);
max_sizes = (max_sizes * (1UL << i)) - 1;
ext2_max_sizes[i - EXT2_MIN_BLOCK_LOG_SIZE] = max_sizes;
}
#undef EXT2_BPP
imagic_fs = (sb->s_feature_compat & EXT2_FEATURE_COMPAT_IMAGIC_INODES);
/*
* Allocate bitmaps structures
*/
pctx.errcode = ext2fs_allocate_inode_bitmap(fs, _("in-use inode 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 = ext2fs_allocate_inode_bitmap(fs,
_("directory inode 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 = ext2fs_allocate_inode_bitmap(fs,
_("regular file inode 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;
}
pctx.errcode = ext2fs_allocate_block_bitmap(fs, _("in-use block 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;
}
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;
return;
}
inodes_to_process = (struct process_inode_block *)
e2fsck_allocate_memory(ctx,
(ctx->process_inode_size *
sizeof(struct process_inode_block)),
"array of inodes to process");
process_inode_count = 0;
pctx.errcode = ext2fs_init_dblist(fs, 0);
if (pctx.errcode) {
fix_problem(ctx, PR_1_ALLOCATE_DBCOUNT, &pctx);
ctx->flags |= E2F_FLAG_ABORT;
return;
}
/*
* If the last orphan field is set, clear it, since the pass1
* processing will automatically find and clear the orphans.
* In the future, we may want to try using the last_orphan
* linked list ourselves, but for now, we clear it so that the
* ext3 mount code won't get confused.
*/
if (!(ctx->options & E2F_OPT_READONLY)) {
if (fs->super->s_last_orphan) {
fs->super->s_last_orphan = 0;
ext2fs_mark_super_dirty(fs);
}
}
mark_table_blocks(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"));
pctx.errcode = ext2fs_open_inode_scan(fs, ctx->inode_buffer_blocks,
&scan);
if (pctx.errcode) {
fix_problem(ctx, PR_1_ISCAN_ERROR, &pctx);
ctx->flags |= E2F_FLAG_ABORT;
return;
}
ext2fs_inode_scan_flags(scan, EXT2_SF_SKIP_MISSING_ITABLE, 0);
ctx->stashed_inode = &inode;
scan_struct.ctx = ctx;
scan_struct.block_buf = block_buf;
ext2fs_set_inode_callback(scan, scan_callback, &scan_struct);
if (ctx->progress)
if ((ctx->progress)(ctx, 1, 0, ctx->fs->group_desc_count))
return;
if (fs->super->s_wtime < fs->super->s_inodes_count)
busted_fs_time = 1;
while (1) {
pctx.errcode = ext2fs_get_next_inode(scan, &ino, &inode);
if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
return;
if (pctx.errcode == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE) {
if (!ctx->inode_bb_map)
alloc_bb_map(ctx);
ext2fs_mark_inode_bitmap(ctx->inode_bb_map, ino);
ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino);
continue;
}
if (pctx.errcode) {
fix_problem(ctx, PR_1_ISCAN_ERROR, &pctx);
ctx->flags |= E2F_FLAG_ABORT;
return;
}
if (!ino)
break;
pctx.ino = ino;
pctx.inode = &inode;
ctx->stashed_ino = ino;
if (inode.i_links_count) {
pctx.errcode = ext2fs_icount_store(ctx->inode_link_info,
ino, inode.i_links_count);
if (pctx.errcode) {
pctx.num = inode.i_links_count;
fix_problem(ctx, PR_1_ICOUNT_STORE, &pctx);
ctx->flags |= E2F_FLAG_ABORT;
return;
}
}
if (ino == EXT2_BAD_INO) {
struct process_block_struct pb;
pb.ino = EXT2_BAD_INO;
pb.num_blocks = pb.last_block = 0;
pb.num_illegal_blocks = 0;
pb.suppress = 0; pb.clear = 0; pb.is_dir = 0;
pb.is_reg = 0; pb.fragmented = 0;
pb.inode = &inode;
pb.pctx = &pctx;
pb.ctx = ctx;
pctx.errcode = ext2fs_block_iterate2(fs, ino, 0,
block_buf, process_bad_block, &pb);
if (pctx.errcode) {
fix_problem(ctx, PR_1_BLOCK_ITERATE, &pctx);
ctx->flags |= E2F_FLAG_ABORT;
return;
}
ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino);
clear_problem_context(&pctx);
continue;
} else if (ino == EXT2_ROOT_INO) {
/*
* Make sure the root inode is a directory; if
* not, offer to clear it. It will be
* regnerated in pass #3.
*/
if (!LINUX_S_ISDIR(inode.i_mode)) {
if (fix_problem(ctx, PR_1_ROOT_NO_DIR, &pctx)) {
inode.i_dtime = time(0);
inode.i_links_count = 0;
ext2fs_icount_store(ctx->inode_link_info,
ino, 0);
e2fsck_write_inode(ctx, ino, &inode,
"pass1");
}
}
/*
* If dtime is set, offer to clear it. mke2fs
* version 0.2b created filesystems with the
* dtime field set for the root and lost+found
* directories. We won't worry about
* /lost+found, since that can be regenerated
* easily. But we will fix the root directory
* as a special case.
*/
if (inode.i_dtime && inode.i_links_count) {
if (fix_problem(ctx, PR_1_ROOT_DTIME, &pctx)) {
inode.i_dtime = 0;
e2fsck_write_inode(ctx, ino, &inode,
"pass1");
}
}
} else if (ino == EXT2_JOURNAL_INO) {
ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino);
if (fs->super->s_journal_inum == EXT2_JOURNAL_INO) {
if (!LINUX_S_ISREG(inode.i_mode) &&
fix_problem(ctx, PR_1_JOURNAL_BAD_MODE,
&pctx)) {
inode.i_mode = LINUX_S_IFREG;
e2fsck_write_inode(ctx, ino, &inode,
"pass1");
}
check_blocks(ctx, &pctx, block_buf);
continue;
}
if ((inode.i_links_count || inode.i_blocks ||
inode.i_blocks || inode.i_block[0]) &&
fix_problem(ctx, PR_1_JOURNAL_INODE_NOT_CLEAR,
&pctx)) {
memset(&inode, 0, sizeof(inode));
ext2fs_icount_store(ctx->inode_link_info,
ino, 0);
e2fsck_write_inode(ctx, ino, &inode, "pass1");
}
} else if (ino < EXT2_FIRST_INODE(fs->super)) {
int problem = 0;
ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino);
if (ino == EXT2_BOOT_LOADER_INO) {
if (LINUX_S_ISDIR(inode.i_mode))
problem = PR_1_RESERVED_BAD_MODE;
} else if (ino == EXT2_RESIZE_INO) {
if (inode.i_mode &&
!LINUX_S_ISREG(inode.i_mode))
problem = PR_1_RESERVED_BAD_MODE;
} else {
if (inode.i_mode != 0)
problem = PR_1_RESERVED_BAD_MODE;
}
if (problem) {
if (fix_problem(ctx, problem, &pctx)) {
inode.i_mode = 0;
e2fsck_write_inode(ctx, ino, &inode,
"pass1");
}
}
check_blocks(ctx, &pctx, block_buf);
continue;
}
/*
* Check for inodes who might have been part of the
* orphaned list linked list. They should have gotten
* dealt with by now, unless the list had somehow been
* corrupted.
*
* FIXME: In the future, inodes which are still in use
* (and which are therefore) pending truncation should
* be handled specially. Right now we just clear the
* dtime field, and the normal e2fsck handling of
* inodes where i_size and the inode blocks are
* inconsistent is to fix i_size, instead of releasing
* the extra blocks. This won't catch the inodes that
* was at the end of the orphan list, but it's better
* than nothing. The right answer is that there
* shouldn't be any bugs in the orphan list handling. :-)
*/
if (inode.i_dtime && !busted_fs_time &&
inode.i_dtime < ctx->fs->super->s_inodes_count) {
if (fix_problem(ctx, PR_1_LOW_DTIME, &pctx)) {
inode.i_dtime = inode.i_links_count ?
0 : time(0);
e2fsck_write_inode(ctx, ino, &inode,
"pass1");
}
}
/*
* This code assumes that deleted inodes have
* i_links_count set to 0.
*/
if (!inode.i_links_count) {
if (!inode.i_dtime && inode.i_mode) {
if (fix_problem(ctx,
PR_1_ZERO_DTIME, &pctx)) {
inode.i_dtime = time(0);
e2fsck_write_inode(ctx, ino, &inode,
"pass1");
}
}
continue;
}
/*
* n.b. 0.3c ext2fs code didn't clear i_links_count for
* deleted files. Oops.
*
* Since all new ext2 implementations get this right,
* we now assume that the case of non-zero
* i_links_count and non-zero dtime means that we
* should keep the file, not delete it.
*
*/
if (inode.i_dtime) {
if (fix_problem(ctx, PR_1_SET_DTIME, &pctx)) {
inode.i_dtime = 0;
e2fsck_write_inode(ctx, ino, &inode, "pass1");
}
}
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;
}
if (inode.i_faddr || frag || fsize ||
(LINUX_S_ISDIR(inode.i_mode) && inode.i_dir_acl))
mark_inode_bad(ctx, ino);
if (inode.i_flags & EXT2_IMAGIC_FL) {
if (imagic_fs) {
if (!ctx->inode_imagic_map)
alloc_imagic_map(ctx);
ext2fs_mark_inode_bitmap(ctx->inode_imagic_map,
ino);
} else {
if (fix_problem(ctx, PR_1_SET_IMAGIC, &pctx)) {
inode.i_flags &= ~EXT2_IMAGIC_FL;
e2fsck_write_inode(ctx, ino,
&inode, "pass1");
}
}
}
if (LINUX_S_ISDIR(inode.i_mode)) {
ext2fs_mark_inode_bitmap(ctx->inode_dir_map, ino);
e2fsck_add_dir_info(ctx, ino, 0);
ctx->fs_directory_count++;
} else if (LINUX_S_ISREG (inode.i_mode)) {
ext2fs_mark_inode_bitmap(ctx->inode_reg_map, ino);
ctx->fs_regular_count++;
} else if (LINUX_S_ISCHR (inode.i_mode) &&
e2fsck_pass1_check_device_inode(fs, &inode)) {
check_immutable(ctx, &pctx);
check_size(ctx, &pctx);
ctx->fs_chardev_count++;
} else if (LINUX_S_ISBLK (inode.i_mode) &&
e2fsck_pass1_check_device_inode(fs, &inode)) {
check_immutable(ctx, &pctx);
check_size(ctx, &pctx);
ctx->fs_blockdev_count++;
} else if (LINUX_S_ISLNK (inode.i_mode) &&
e2fsck_pass1_check_symlink(fs, &inode, block_buf)) {
check_immutable(ctx, &pctx);
ctx->fs_symlinks_count++;
if (ext2fs_inode_data_blocks(fs, &inode) == 0) {
ctx->fs_fast_symlinks_count++;
check_blocks(ctx, &pctx, block_buf);
continue;
}
}
else if (LINUX_S_ISFIFO (inode.i_mode) &&
e2fsck_pass1_check_device_inode(fs, &inode)) {
check_immutable(ctx, &pctx);
check_size(ctx, &pctx);
ctx->fs_fifo_count++;
} else if ((LINUX_S_ISSOCK (inode.i_mode)) &&
e2fsck_pass1_check_device_inode(fs, &inode)) {
check_immutable(ctx, &pctx);
check_size(ctx, &pctx);
ctx->fs_sockets_count++;
} else
mark_inode_bad(ctx, ino);
if (inode.i_block[EXT2_IND_BLOCK])
ctx->fs_ind_count++;
if (inode.i_block[EXT2_DIND_BLOCK])
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) {
inodes_to_process[process_inode_count].ino = ino;
inodes_to_process[process_inode_count].inode = inode;
process_inode_count++;
} else
check_blocks(ctx, &pctx, block_buf);
if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
return;
if (process_inode_count >= ctx->process_inode_size) {
process_inodes(ctx, block_buf);
if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
return;
}
}
process_inodes(ctx, block_buf);
ext2fs_close_inode_scan(scan);
ehandler_operation(0);
/*
* 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_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;
}
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);
}
ext2fs_free_mem(&inodes_to_process);
endit:
e2fsck_use_inode_shortcuts(ctx, 0);
ext2fs_free_mem(&block_buf);
#ifdef RESOURCE_TRACK
if (ctx->options & E2F_OPT_TIME2) {
e2fsck_clear_progbar(ctx);
print_resource_track(_("Pass 1"), &rtrack);
}
#endif
}
/*
* When the inode_scan routines call this callback at the end of the
* glock group, call process_inodes.
*/
static errcode_t scan_callback(ext2_filsys fs, ext2_inode_scan scan,
dgrp_t group, void * priv_data)
{
struct scan_callback_struct *scan_struct;
e2fsck_t ctx;
scan_struct = (struct scan_callback_struct *) priv_data;
ctx = scan_struct->ctx;
process_inodes((e2fsck_t) fs->priv_data, scan_struct->block_buf);
if (ctx->progress)
if ((ctx->progress)(ctx, 1, group+1,
ctx->fs->group_desc_count))
return EXT2_ET_CANCEL_REQUESTED;
return 0;
}
/*
* Process the inodes in the "inodes to process" list.
*/
static void process_inodes(e2fsck_t ctx, char *block_buf)
{
int i;
struct ext2_inode *old_stashed_inode;
ext2_ino_t old_stashed_ino;
const char *old_operation;
char buf[80];
struct problem_context pctx;
#if 0
printf("begin process_inodes: ");
#endif
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,
sizeof(struct process_inode_block), process_inode_cmp);
clear_problem_context(&pctx);
for (i=0; i < process_inode_count; i++) {
pctx.inode = ctx->stashed_inode = &inodes_to_process[i].inode;
pctx.ino = ctx->stashed_ino = inodes_to_process[i].ino;
#if 0
printf("%u ", pctx.ino);
#endif
sprintf(buf, _("reading indirect blocks of inode %u"),
pctx.ino);
ehandler_operation(buf);
check_blocks(ctx, &pctx, block_buf);
if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
break;
}
ctx->stashed_inode = old_stashed_inode;
ctx->stashed_ino = old_stashed_ino;
process_inode_count = 0;
#if 0
printf("end process inodes\n");
#endif
ehandler_operation(old_operation);
}
static EXT2_QSORT_TYPE process_inode_cmp(const void *a, const void *b)
{
const struct process_inode_block *ib_a =
(const struct process_inode_block *) a;
const struct process_inode_block *ib_b =
(const struct process_inode_block *) b;
int ret;
ret = (ib_a->inode.i_block[EXT2_IND_BLOCK] -
ib_b->inode.i_block[EXT2_IND_BLOCK]);
if (ret == 0)
ret = ib_a->inode.i_file_acl - ib_b->inode.i_file_acl;
return ret;
}
/*
* Mark an inode as being bad in some what
*/
static void mark_inode_bad(e2fsck_t ctx, ino_t ino)
{
struct problem_context pctx;
if (!ctx->inode_bad_map) {
clear_problem_context(&pctx);
pctx.errcode = ext2fs_allocate_inode_bitmap(ctx->fs,
_("bad inode map"), &ctx->inode_bad_map);
if (pctx.errcode) {
pctx.num = 3;
fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx);
/* Should never get here */
ctx->flags |= E2F_FLAG_ABORT;
return;
}
}
ext2fs_mark_inode_bitmap(ctx->inode_bad_map, ino);
}
/*
* This procedure will allocate the inode "bb" (badblock) map table
*/
static void alloc_bb_map(e2fsck_t ctx)
{
struct problem_context pctx;
clear_problem_context(&pctx);
pctx.errcode = ext2fs_allocate_inode_bitmap(ctx->fs,
_("inode in bad block map"),
&ctx->inode_bb_map);
if (pctx.errcode) {
pctx.num = 4;
fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx);
/* Should never get here */
ctx->flags |= E2F_FLAG_ABORT;
return;
}
}
/*
* This procedure will allocate the inode imagic table
*/
static void alloc_imagic_map(e2fsck_t ctx)
{
struct problem_context pctx;
clear_problem_context(&pctx);
pctx.errcode = ext2fs_allocate_inode_bitmap(ctx->fs,
_("imagic inode map"),
&ctx->inode_imagic_map);
if (pctx.errcode) {
pctx.num = 5;
fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx);
/* Should never get here */
ctx->flags |= E2F_FLAG_ABORT;
return;
}
}
/*
* Marks a block as in use, setting the dup_map if it's been set
* already. Called by process_block and process_bad_block.
*
* WARNING: Assumes checks have already been done to make sure block
* is valid. This is true in both process_block and process_bad_block.
*/
static _INLINE_ void mark_block_used(e2fsck_t ctx, blk_t block)
{
struct problem_context pctx;
clear_problem_context(&pctx);
if (ext2fs_fast_test_block_bitmap(ctx->block_found_map, block)) {
if (!ctx->block_dup_map) {
pctx.errcode = ext2fs_allocate_block_bitmap(ctx->fs,
_("multiply claimed block 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_bitmap(ctx->block_dup_map, block);
} else {
ext2fs_fast_mark_block_bitmap(ctx->block_found_map, block);
}
}
/*
* Adjust the extended attribute block's reference counts at the end
* of pass 1, either by subtracting out references for EA blocks that
* are still referenced in ctx->refcount, or by adding references for
* EA blocks that had extra references as accounted for in
* ctx->refcount_extra.
*/
static void adjust_extattr_refcount(e2fsck_t ctx, ext2_refcount_t refcount,
char *block_buf, int adjust_sign)
{
struct ext2_ext_attr_header *header;
struct problem_context pctx;
ext2_filsys fs = ctx->fs;
blk_t blk;
__u32 should_be;
int count;
clear_problem_context(&pctx);
ea_refcount_intr_begin(refcount);
while (1) {
if ((blk = ea_refcount_intr_next(refcount, &count)) == 0)
break;
pctx.blk = blk;
pctx.errcode = ext2fs_read_ext_attr(fs, blk, block_buf);
if (pctx.errcode) {
fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, &pctx);
return;
}
header = (struct ext2_ext_attr_header *) block_buf;
pctx.blkcount = header->h_refcount;
should_be = header->h_refcount + adjust_sign * count;
pctx.num = should_be;
if (fix_problem(ctx, PR_1_EXTATTR_REFCOUNT, &pctx)) {
header->h_refcount = should_be;
pctx.errcode = ext2fs_write_ext_attr(fs, blk,
block_buf);
if (pctx.errcode) {
fix_problem(ctx, PR_1_EXTATTR_WRITE, &pctx);
continue;
}
}
}
}
/*
* Handle processing the extended attribute blocks
*/
static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx,
char *block_buf)
{
ext2_filsys fs = ctx->fs;
ext2_ino_t ino = pctx->ino;
struct ext2_inode *inode = pctx->inode;
blk_t blk;
char * end;
struct ext2_ext_attr_header *header;
struct ext2_ext_attr_entry *entry;
int count;
region_t region;
blk = inode->i_file_acl;
if (blk == 0)
return 0;
/*
* If the Extended attribute flag isn't set, then a non-zero
* file acl means that the inode is corrupted.
*
* Or if the extended attribute block is an invalid block,
* then the inode is also corrupted.
*/
if (!(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR) ||
(blk < fs->super->s_first_data_block) ||
(blk >= fs->super->s_blocks_count)) {
mark_inode_bad(ctx, ino);
return 0;
}
/* If ea bitmap hasn't been allocated, create it */
if (!ctx->block_ea_map) {
pctx->errcode = ext2fs_allocate_block_bitmap(fs,
_("ext attr block map"),
&ctx->block_ea_map);
if (pctx->errcode) {
pctx->num = 2;
fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, pctx);
ctx->flags |= E2F_FLAG_ABORT;
return 0;
}
}
/* Create the EA refcount structure if necessary */
if (!ctx->refcount) {
pctx->errcode = ea_refcount_create(0, &ctx->refcount);
if (pctx->errcode) {
pctx->num = 1;
fix_problem(ctx, PR_1_ALLOCATE_REFCOUNT, pctx);
ctx->flags |= E2F_FLAG_ABORT;
return 0;
}
}
#if 0
/* Debugging text */
printf("Inode %u has EA block %u\n", ino, blk);
#endif
/* Have we seen this EA block before? */
if (ext2fs_fast_test_block_bitmap(ctx->block_ea_map, blk)) {
if (ea_refcount_decrement(ctx->refcount, blk, 0) == 0)
return 1;
/* Ooops, this EA was referenced more than it stated */
if (!ctx->refcount_extra) {
pctx->errcode = ea_refcount_create(0,
&ctx->refcount_extra);
if (pctx->errcode) {
pctx->num = 2;
fix_problem(ctx, PR_1_ALLOCATE_REFCOUNT, pctx);
ctx->flags |= E2F_FLAG_ABORT;
return 0;
}
}
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
*/
pctx->blk = blk;
pctx->errcode = ext2fs_read_ext_attr(fs, blk, block_buf);
if (pctx->errcode && fix_problem(ctx, PR_1_READ_EA_BLOCK, pctx))
goto clear_extattr;
header = (struct ext2_ext_attr_header *) block_buf;
pctx->blk = inode->i_file_acl;
if (((ctx->ext_attr_ver == 1) &&
(header->h_magic != EXT2_EXT_ATTR_MAGIC_v1)) ||
((ctx->ext_attr_ver == 2) &&
(header->h_magic != EXT2_EXT_ATTR_MAGIC))) {
if (fix_problem(ctx, PR_1_BAD_EA_BLOCK, pctx))
goto clear_extattr;
}
if (header->h_blocks != 1) {
if (fix_problem(ctx, PR_1_EA_MULTI_BLOCK, pctx))
goto clear_extattr;
}
region = region_create(0, fs->blocksize);
if (!region) {
fix_problem(ctx, PR_1_EA_ALLOC_REGION, pctx);
ctx->flags |= E2F_FLAG_ABORT;
return 0;
}
if (region_allocate(region, 0, sizeof(struct ext2_ext_attr_header))) {
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) {
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;
}
if ((ctx->ext_attr_ver == 1 &&
(entry->e_name_len == 0 || entry->e_name_index != 0)) ||
(ctx->ext_attr_ver == 2 &&
entry->e_name_index == 0)) {
if (fix_problem(ctx, PR_1_EA_BAD_NAME, pctx))
goto clear_extattr;
}
if (entry->e_value_block != 0) {
if (fix_problem(ctx, PR_1_EA_BAD_VALUE, pctx))
goto clear_extattr;
}
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;
}
entry = EXT2_EXT_ATTR_NEXT(entry);
}
if (region_allocate(region, (char *)entry - (char *)header, 4)) {
if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx))
goto clear_extattr;
}
region_free(region);
count = header->h_refcount - 1;
if (count)
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:
inode->i_file_acl = 0;
e2fsck_write_inode(ctx, ino, inode, "check_ext_attr");
return 0;
}
/* Returns 1 if bad htree, 0 if OK */
static int handle_htree(e2fsck_t ctx, struct problem_context *pctx,
ext2_ino_t ino, struct ext2_inode *inode,
char *block_buf)
{
struct ext2_dx_root_info *root;
ext2_filsys fs = ctx->fs;
errcode_t retval;
blk_t blk;
if ((!LINUX_S_ISDIR(inode->i_mode) &&
fix_problem(ctx, PR_1_HTREE_NODIR, pctx)) ||
(!(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) &&
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;
retval = io_channel_read_blk(fs->io, blk, 1, block_buf);
if (retval && 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);
if ((root->reserved_zero || root->info_length < 8) &&
fix_problem(ctx, PR_1_HTREE_BADROOT, pctx))
return 1;
pctx->num = root->hash_version;
if ((root->hash_version != EXT2_HASH_LEGACY) &&
(root->hash_version != EXT2_HASH_HALF_MD4) &&
(root->hash_version != EXT2_HASH_TEA) &&
fix_problem(ctx, PR_1_HTREE_HASHV, 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 > 1) &&
fix_problem(ctx, PR_1_HTREE_DEPTH, pctx))
return 1;
return 0;
}
/*
* This subroutine is called on each inode to account for all of the
* blocks used by that inode.
*/
static void check_blocks(e2fsck_t ctx, struct problem_context *pctx,
char *block_buf)
{
ext2_filsys fs = ctx->fs;
struct process_block_struct pb;
ext2_ino_t ino = pctx->ino;
struct ext2_inode *inode = pctx->inode;
int bad_size = 0;
int dirty_inode = 0;
__u64 size;
pb.ino = ino;
pb.num_blocks = 0;
pb.last_block = -1;
pb.num_illegal_blocks = 0;
pb.suppress = 0; pb.clear = 0;
pb.fragmented = 0;
pb.compressed = 0;
pb.previous_block = 0;
pb.is_dir = LINUX_S_ISDIR(inode->i_mode);
pb.is_reg = LINUX_S_ISREG(inode->i_mode);
pb.max_blocks = 1 << (31 - fs->super->s_log_block_size);
pb.inode = inode;
pb.pctx = pctx;
pb.ctx = ctx;
pctx->ino = ino;
pctx->errcode = 0;
if (inode->i_flags & EXT2_COMPRBLK_FL) {
if (fs->super->s_feature_incompat &
EXT2_FEATURE_INCOMPAT_COMPRESSION)
pb.compressed = 1;
else {
if (fix_problem(ctx, PR_1_COMPR_SET, pctx)) {
inode->i_flags &= ~EXT2_COMPRBLK_FL;
dirty_inode++;
}
}
}
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);
end_problem_latch(ctx, PR_LATCH_BLOCK);
end_problem_latch(ctx, PR_LATCH_TOOBIG);
if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
goto out;
if (pctx->errcode)
fix_problem(ctx, PR_1_BLOCK_ITERATE, pctx);
if (pb.fragmented && pb.num_blocks < fs->super->s_blocks_per_group)
ctx->fs_fragmented++;
if (pb.clear) {
inode->i_links_count = 0;
ext2fs_icount_store(ctx->inode_link_info, ino, 0);
inode->i_dtime = time(0);
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;
}
if (inode->i_flags & EXT2_INDEX_FL) {
if (handle_htree(ctx, pctx, ino, inode, block_buf)) {
inode->i_flags &= ~EXT2_INDEX_FL;
dirty_inode++;
} else {
#ifdef ENABLE_HTREE
e2fsck_add_dx_dir(ctx, ino, pb.last_block+1);
#endif
}
}
if (ctx->dirs_to_hash && pb.is_dir &&
!(inode->i_flags & EXT2_INDEX_FL) &&
((inode->i_size / fs->blocksize) >= 3))
ext2fs_u32_list_add(ctx->dirs_to_hash, ino);
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 = time(0);
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);
ctx->fs_directory_count--;
goto out;
}
}
if (inode->i_file_acl && check_ext_attr(ctx, pctx, block_buf))
pb.num_blocks++;
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,
pb.num_blocks);
#endif
if (pb.is_dir) {
int nblock = inode->i_size >> EXT2_BLOCK_SIZE_BITS(fs->super);
if (nblock > (pb.last_block + 1))
bad_size = 1;
else if (nblock < (pb.last_block + 1)) {
if (((pb.last_block + 1) - nblock) >
fs->super->s_prealloc_dir_blocks)
bad_size = 2;
}
} else {
size = EXT2_I_SIZE(inode);
if ((pb.last_block >= 0) &&
(size < pb.last_block * fs->blocksize))
bad_size = 3;
else if (size > ext2_max_sizes[fs->super->s_log_block_size])
bad_size = 4;
}
/* i_size for symlinks is checked elsewhere */
if (bad_size && !LINUX_S_ISLNK(inode->i_mode)) {
pctx->num = (pb.last_block+1) * fs->blocksize;
if (fix_problem(ctx, PR_1_BAD_I_SIZE, pctx)) {
inode->i_size = pctx->num;
if (!LINUX_S_ISDIR(inode->i_mode))
inode->i_size_high = pctx->num >> 32;
dirty_inode++;
}
pctx->num = 0;
}
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) {
pctx->num = pb.num_blocks;
if (fix_problem(ctx, PR_1_BAD_I_BLOCKS, pctx)) {
inode->i_blocks = pb.num_blocks;
dirty_inode++;
}
pctx->num = 0;
}
out:
if (dirty_inode)
e2fsck_write_inode(ctx, ino, inode, "check_blocks");
}
#if 0
/*
* Helper function called by process block when an illegal block is
* found. It returns a description about why the block is illegal
*/
static char *describe_illegal_block(ext2_filsys fs, blk_t block)
{
blk_t super;
int i;
static char problem[80];
super = fs->super->s_first_data_block;
strcpy(problem, "PROGRAMMING ERROR: Unknown reason for illegal block");
if (block < super) {
sprintf(problem, "< FIRSTBLOCK (%u)", super);
return(problem);
} else if (block >= fs->super->s_blocks_count) {
sprintf(problem, "> BLOCKS (%u)", fs->super->s_blocks_count);
return(problem);
}
for (i = 0; i < fs->group_desc_count; i++) {
if (block == super) {
sprintf(problem, "is the superblock in group %d", i);
break;
}
if (block > super &&
block <= (super + fs->desc_blocks)) {
sprintf(problem, "is in the group descriptors "
"of group %d", i);
break;
}
if (block == fs->group_desc[i].bg_block_bitmap) {
sprintf(problem, "is the block bitmap of group %d", i);
break;
}
if (block == fs->group_desc[i].bg_inode_bitmap) {
sprintf(problem, "is the inode bitmap of group %d", i);
break;
}
if (block >= fs->group_desc[i].bg_inode_table &&
(block < fs->group_desc[i].bg_inode_table
+ fs->inode_blocks_per_group)) {
sprintf(problem, "is in the inode table of group %d",
i);
break;
}
super += fs->super->s_blocks_per_group;
}
return(problem);
}
#endif
/*
* This is a helper function for check_blocks().
*/
static int process_block(ext2_filsys fs,
blk_t *block_nr,
e2_blkcnt_t blockcnt,
blk_t ref_block,
int ref_offset,
void *priv_data)
{
struct process_block_struct *p;
struct problem_context *pctx;
blk_t blk = *block_nr;
int ret_code = 0;
int problem = 0;
e2fsck_t ctx;
p = (struct process_block_struct *) priv_data;
pctx = p->pctx;
ctx = p->ctx;
if (p->compressed && (blk == EXT2FS_COMPRESSED_BLKADDR)) {
/* todo: Check that the comprblk_fl is high, that the
blkaddr pattern looks right (all non-holes up to
first EXT2FS_COMPRESSED_BLKADDR, then all
EXT2FS_COMPRESSED_BLKADDR up to end of cluster),
that the feature_incompat bit is high, and that the
inode is a regular file. If we're doing a "full
check" (a concept introduced to e2fsck by e2compr,
meaning that we look at data blocks as well as
metadata) then call some library routine that
checks the compressed data. I'll have to think
about this, because one particularly important
problem to be able to fix is to recalculate the
cluster size if necessary. I think that perhaps
we'd better do most/all e2compr-specific checks
separately, after the non-e2compr checks. If not
doing a full check, it may be useful to test that
the personality is linux; e.g. if it isn't then
perhaps this really is just an illegal block. */
return 0;
}
if (blk == 0) {
if (p->is_dir == 0) {
/*
* Should never happen, since only directories
* get called with BLOCK_FLAG_HOLE
*/
#if DEBUG_E2FSCK
printf("process_block() called with blk == 0, "
"blockcnt=%d, inode %lu???\n",
blockcnt, p->ino);
#endif
return 0;
}
if (blockcnt < 0)
return 0;
if (blockcnt * fs->blocksize < p->inode->i_size) {
#if 0
printf("Missing block (#%d) in directory inode %lu!\n",
blockcnt, p->ino);
#endif
goto mark_dir;
}
return 0;
}
#if 0
printf("Process_block, inode %lu, block %u, #%d\n", p->ino, blk,
blockcnt);
#endif
/*
* Simplistic fragmentation check. We merely require that the
* file be contiguous. (Which can never be true for really
* big files that are greater than a block group.)
*/
if (!HOLE_BLKADDR(p->previous_block)) {
if (p->previous_block+1 != blk)
p->fragmented = 1;
}
p->previous_block = blk;
if (p->is_dir && blockcnt > (1 << (15 - fs->super->s_log_block_size)))
problem = PR_1_TOOBIG_DIR;
if (p->is_reg && p->num_blocks+1 >= p->max_blocks)
problem = PR_1_TOOBIG_REG;
if (!p->is_dir && !p->is_reg && blockcnt > 0)
problem = PR_1_TOOBIG_SYMLINK;
if (blk < fs->super->s_first_data_block ||
blk >= fs->super->s_blocks_count)
problem = PR_1_ILLEGAL_BLOCK_NUM;
if (problem) {
p->num_illegal_blocks++;
if (!p->suppress && (p->num_illegal_blocks % 12) == 0) {
if (fix_problem(ctx, PR_1_TOO_MANY_BAD_BLOCKS, pctx)) {
p->clear = 1;
return BLOCK_ABORT;
}
if (fix_problem(ctx, PR_1_SUPPRESS_MESSAGES, pctx)) {
p->suppress = 1;
set_latch_flags(PR_LATCH_BLOCK,
PRL_SUPPRESS, 0);
}
}
pctx->blk = blk;
pctx->blkcount = blockcnt;
if (fix_problem(ctx, problem, pctx)) {
blk = *block_nr = 0;
ret_code = BLOCK_CHANGED;
goto mark_dir;
} else
return 0;
}
mark_block_used(ctx, blk);
p->num_blocks++;
if (blockcnt >= 0)
p->last_block = blockcnt;
mark_dir:
if (p->is_dir && (blockcnt >= 0)) {
pctx->errcode = ext2fs_add_dir_block(fs->dblist, p->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 BLOCK_ABORT;
}
}
return ret_code;
}
static void bad_block_indirect(e2fsck_t ctx, blk_t blk)
{
struct problem_context pctx;
clear_problem_context(&pctx);
/*
* Prompt to see if we should continue or not.
*/
if (!fix_problem(ctx, PR_1_BBINODE_BAD_METABLOCK, &pctx))
ctx->flags |= E2F_FLAG_ABORT;
}
static int process_bad_block(ext2_filsys fs,
blk_t *block_nr,
e2_blkcnt_t blockcnt,
blk_t ref_block,
int ref_offset,
void *priv_data)
{
struct process_block_struct *p;
blk_t blk = *block_nr;
int first_block;
int i;
struct problem_context *pctx;
e2fsck_t ctx;
/*
* Note: This function processes blocks for the bad blocks
* inode, which is never compressed. So we don't use HOLE_BLKADDR().
*/
if (!blk)
return 0;
p = (struct process_block_struct *) priv_data;
ctx = p->ctx;
pctx = p->pctx;
pctx->ino = EXT2_BAD_INO;
pctx->blk = blk;
pctx->blkcount = blockcnt;
if ((blk < fs->super->s_first_data_block) ||
(blk >= fs->super->s_blocks_count)) {
if (fix_problem(ctx, PR_1_BB_ILLEGAL_BLOCK_NUM, pctx)) {
*block_nr = 0;
return BLOCK_CHANGED;
} else
return 0;
}
if (blockcnt < 0) {
if (ext2fs_test_block_bitmap(ctx->block_found_map, blk)) {
bad_block_indirect(ctx, blk);
if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
return BLOCK_ABORT;
} else
mark_block_used(ctx, blk);
return 0;
}
#if 0
printf ("DEBUG: Marking %u as bad.\n", blk);
#endif
ctx->fs_badblocks_count++;
/*
* If the block is not used, then mark it as used and return.
* If it is already marked as found, this must mean that
* there's an overlap between the filesystem table blocks
* (bitmaps and inode table) and the bad block list.
*/
if (!ext2fs_test_block_bitmap(ctx->block_found_map, blk)) {
ext2fs_mark_block_bitmap(ctx->block_found_map, blk);
return 0;
}
/*
* Try to find the where the filesystem block was used...
*/
first_block = fs->super->s_first_data_block;
for (i = 0; i < fs->group_desc_count; i++ ) {
pctx->group = i;
pctx->blk = blk;
if (!ext2fs_bg_has_super(fs, i))
goto skip_super;
if (blk == first_block) {
if (i == 0) {
if (fix_problem(ctx,
PR_1_BAD_PRIMARY_SUPERBLOCK,
pctx)) {
*block_nr = 0;
return BLOCK_CHANGED;
}
return 0;
}
fix_problem(ctx, PR_1_BAD_SUPERBLOCK, pctx);
return 0;
}
if ((blk > first_block) &&
(blk <= first_block + fs->desc_blocks)) {
if (i == 0) {
pctx->blk = *block_nr;
if (fix_problem(ctx,
PR_1_BAD_PRIMARY_GROUP_DESCRIPTOR, pctx)) {
*block_nr = 0;
return BLOCK_CHANGED;
}
return 0;
}
fix_problem(ctx, PR_1_BAD_GROUP_DESCRIPTORS, pctx);
return 0;
}
skip_super:
if (blk == fs->group_desc[i].bg_block_bitmap) {
if (fix_problem(ctx, PR_1_BB_BAD_BLOCK, pctx)) {
ctx->invalid_block_bitmap_flag[i]++;
ctx->invalid_bitmaps++;
}
return 0;
}
if (blk == fs->group_desc[i].bg_inode_bitmap) {
if (fix_problem(ctx, PR_1_IB_BAD_BLOCK, pctx)) {
ctx->invalid_inode_bitmap_flag[i]++;
ctx->invalid_bitmaps++;
}
return 0;
}
if ((blk >= fs->group_desc[i].bg_inode_table) &&
(blk < (fs->group_desc[i].bg_inode_table +
fs->inode_blocks_per_group))) {
/*
* If there are bad blocks in the inode table,
* the inode scan code will try to do
* something reasonable automatically.
*/
return 0;
}
first_block += fs->super->s_blocks_per_group;
}
/*
* If we've gotten to this point, then the only
* possibility is that the bad block inode meta data
* is using a bad block.
*/
if ((blk == p->inode->i_block[EXT2_IND_BLOCK]) ||
p->inode->i_block[EXT2_DIND_BLOCK]) {
bad_block_indirect(ctx, blk);
if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
return BLOCK_ABORT;
return 0;
}
pctx->group = -1;
/* Warn user that the block wasn't claimed */
fix_problem(ctx, PR_1_PROGERR_CLAIMED_BLOCK, pctx);
return 0;
}
static void new_table_block(e2fsck_t ctx, blk_t first_block, int group,
const char *name, int num, blk_t *new_block)
{
ext2_filsys fs = ctx->fs;
blk_t old_block = *new_block;
int i;
char *buf;
struct problem_context pctx;
clear_problem_context(&pctx);
pctx.group = group;
pctx.blk = old_block;
pctx.str = name;
pctx.errcode = ext2fs_get_free_blocks(fs, first_block,
first_block + fs->super->s_blocks_per_group,
num, ctx->block_found_map, new_block);
if (pctx.errcode) {
pctx.num = num;
fix_problem(ctx, PR_1_RELOC_BLOCK_ALLOCATE, &pctx);
ext2fs_unmark_valid(fs);
return;
}
pctx.errcode = ext2fs_get_mem(fs->blocksize, &buf);
if (pctx.errcode) {
fix_problem(ctx, PR_1_RELOC_MEMORY_ALLOCATE, &pctx);
ext2fs_unmark_valid(fs);
return;
}
ext2fs_mark_super_dirty(fs);
fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY;
pctx.blk2 = *new_block;
fix_problem(ctx, (old_block ? PR_1_RELOC_FROM_TO :
PR_1_RELOC_TO), &pctx);
pctx.blk2 = 0;
for (i = 0; i < num; i++) {
pctx.blk = i;
ext2fs_mark_block_bitmap(ctx->block_found_map, (*new_block)+i);
if (old_block) {
pctx.errcode = io_channel_read_blk(fs->io,
old_block + i, 1, buf);
if (pctx.errcode)
fix_problem(ctx, PR_1_RELOC_READ_ERR, &pctx);
} else
memset(buf, 0, fs->blocksize);
pctx.blk = (*new_block) + i;
pctx.errcode = io_channel_write_blk(fs->io, pctx.blk,
1, buf);
if (pctx.errcode)
fix_problem(ctx, PR_1_RELOC_WRITE_ERR, &pctx);
}
ext2fs_free_mem(&buf);
}
/*
* This routine gets called at the end of pass 1 if bad blocks are
* detected in the superblock, group descriptors, inode_bitmaps, or
* block bitmaps. At this point, all of the blocks have been mapped
* out, so we can try to allocate new block(s) to replace the bad
* blocks.
*/
static void handle_fs_bad_blocks(e2fsck_t ctx)
{
ext2_filsys fs = ctx->fs;
int i;
int first_block = fs->super->s_first_data_block;
for (i = 0; i < fs->group_desc_count; i++) {
if (ctx->invalid_block_bitmap_flag[i]) {
new_table_block(ctx, first_block, i, _("block bitmap"),
1, &fs->group_desc[i].bg_block_bitmap);
}
if (ctx->invalid_inode_bitmap_flag[i]) {
new_table_block(ctx, first_block, i, _("inode bitmap"),
1, &fs->group_desc[i].bg_inode_bitmap);
}
if (ctx->invalid_inode_table_flag[i]) {
new_table_block(ctx, first_block, i, _("inode table"),
fs->inode_blocks_per_group,
&fs->group_desc[i].bg_inode_table);
ctx->flags |= E2F_FLAG_RESTART;
}
first_block += fs->super->s_blocks_per_group;
}
ctx->invalid_bitmaps = 0;
}
/*
* This routine marks all blocks which are used by the superblock,
* group descriptors, inode bitmaps, and block bitmaps.
*/
static void mark_table_blocks(e2fsck_t ctx)
{
ext2_filsys fs = ctx->fs;
blk_t block, b;
int i, j, has_super, meta_bg, meta_bg_size, old_desc_blocks;
struct problem_context pctx;
clear_problem_context(&pctx);
block = fs->super->s_first_data_block;
if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG)
old_desc_blocks = fs->super->s_first_meta_bg;
else
old_desc_blocks = fs->desc_blocks;
for (i = 0; i < fs->group_desc_count; i++) {
pctx.group = i;
has_super = ext2fs_bg_has_super(fs, i);
if (has_super)
/*
* Mark this group's copy of the superblock
*/
ext2fs_mark_block_bitmap(ctx->block_found_map, block);
meta_bg_size = (fs->blocksize /
sizeof (struct ext2_group_desc));
meta_bg = i / meta_bg_size;
if (!(fs->super->s_feature_incompat &
EXT2_FEATURE_INCOMPAT_META_BG) ||
(meta_bg < fs->super->s_first_meta_bg)) {
if (has_super) {
/*
* Mark this group's copy of the descriptors
*/
for (j = 0; j < old_desc_blocks; j++) {
ext2fs_mark_block_bitmap(ctx->block_found_map,
block + j + 1);
}
}
} else {
if (has_super)
has_super = 1;
if (((i % meta_bg_size) == 0) ||
((i % meta_bg_size) == 1) ||
((i % meta_bg_size) == (meta_bg_size-1)))
ext2fs_mark_block_bitmap(ctx->block_found_map,
block + has_super);
}
/*
* Mark the blocks used for the inode table
*/
if (fs->group_desc[i].bg_inode_table) {
for (j = 0, b = fs->group_desc[i].bg_inode_table;
j < fs->inode_blocks_per_group;
j++, b++) {
if (ext2fs_test_block_bitmap(ctx->block_found_map,
b)) {
pctx.blk = b;
if (fix_problem(ctx,
PR_1_ITABLE_CONFLICT, &pctx)) {
ctx->invalid_inode_table_flag[i]++;
ctx->invalid_bitmaps++;
}
} else {
ext2fs_mark_block_bitmap(ctx->block_found_map,
b);
}
}
}
/*
* Mark block used for the block bitmap
*/
if (fs->group_desc[i].bg_block_bitmap) {
if (ext2fs_test_block_bitmap(ctx->block_found_map,
fs->group_desc[i].bg_block_bitmap)) {
pctx.blk = fs->group_desc[i].bg_block_bitmap;
if (fix_problem(ctx, PR_1_BB_CONFLICT, &pctx)) {
ctx->invalid_block_bitmap_flag[i]++;
ctx->invalid_bitmaps++;
}
} else {
ext2fs_mark_block_bitmap(ctx->block_found_map,
fs->group_desc[i].bg_block_bitmap);
}
}
/*
* Mark block used for the inode bitmap
*/
if (fs->group_desc[i].bg_inode_bitmap) {
if (ext2fs_test_block_bitmap(ctx->block_found_map,
fs->group_desc[i].bg_inode_bitmap)) {
pctx.blk = fs->group_desc[i].bg_inode_bitmap;
if (fix_problem(ctx, PR_1_IB_CONFLICT, &pctx)) {
ctx->invalid_inode_bitmap_flag[i]++;
ctx->invalid_bitmaps++;
}
} else {
ext2fs_mark_block_bitmap(ctx->block_found_map,
fs->group_desc[i].bg_inode_bitmap);
}
}
block += fs->super->s_blocks_per_group;
}
}
/*
* Thes subroutines short circuits ext2fs_get_blocks and
* ext2fs_check_directory; we use them since we already have the inode
* structure, so there's no point in letting the ext2fs library read
* the inode again.
*/
static errcode_t pass1_get_blocks(ext2_filsys fs, ext2_ino_t ino,
blk_t *blocks)
{
e2fsck_t ctx = (e2fsck_t) fs->priv_data;
int i;
if ((ino != ctx->stashed_ino) || !ctx->stashed_inode)
return EXT2_ET_CALLBACK_NOTHANDLED;
for (i=0; i < EXT2_N_BLOCKS; i++)
blocks[i] = ctx->stashed_inode->i_block[i];
return 0;
}
static errcode_t pass1_read_inode(ext2_filsys fs, ext2_ino_t ino,
struct ext2_inode *inode)
{
e2fsck_t ctx = (e2fsck_t) fs->priv_data;
if ((ino != ctx->stashed_ino) || !ctx->stashed_inode)
return EXT2_ET_CALLBACK_NOTHANDLED;
*inode = *ctx->stashed_inode;
return 0;
}
static errcode_t pass1_write_inode(ext2_filsys fs, ext2_ino_t ino,
struct ext2_inode *inode)
{
e2fsck_t ctx = (e2fsck_t) fs->priv_data;
if ((ino == ctx->stashed_ino) && ctx->stashed_inode)
*ctx->stashed_inode = *inode;
return EXT2_ET_CALLBACK_NOTHANDLED;
}
static errcode_t pass1_check_directory(ext2_filsys fs, ext2_ino_t ino)
{
e2fsck_t ctx = (e2fsck_t) fs->priv_data;
if ((ino != ctx->stashed_ino) || !ctx->stashed_inode)
return EXT2_ET_CALLBACK_NOTHANDLED;
if (!LINUX_S_ISDIR(ctx->stashed_inode->i_mode))
return EXT2_ET_NO_DIRECTORY;
return 0;
}
void e2fsck_use_inode_shortcuts(e2fsck_t ctx, int bool)
{
ext2_filsys fs = ctx->fs;
if (bool) {
fs->get_blocks = pass1_get_blocks;
fs->check_directory = pass1_check_directory;
fs->read_inode = pass1_read_inode;
fs->write_inode = pass1_write_inode;
ctx->stashed_ino = 0;
} else {
fs->get_blocks = 0;
fs->check_directory = 0;
fs->read_inode = 0;
fs->write_inode = 0;
}
}