e2fsprogs/e2fsck/pass1.c

1276 lines
33 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 have bad fields. (inode_bad_map)
* - A bitmap of which inodes are in bad blocks. (inode_bb_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 <time.h>
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#include "e2fsck.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,
int blockcnt, blk_t ref_blk,
int ref_offset, void *private);
static int process_bad_block(ext2_filsys fs, blk_t *block_nr,
int blockcnt, blk_t ref_blk,
int ref_offset, void *private);
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_bad_map(e2fsck_t ctx);
static void alloc_bb_map(e2fsck_t ctx);
static void handle_fs_bad_blocks(e2fsck_t ctx);
static void process_inodes(e2fsck_t ctx, char *block_buf);
static int 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 * private);
/* static char *describe_illegal_block(ext2_filsys fs, blk_t block); */
struct process_block_struct {
ino_t ino;
int is_dir:1, clear:1, suppress:1, fragmented:1;
int num_blocks;
int 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 {
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;
/*
* Free all memory allocated by pass1 in preparation for restarting
* things.
*/
static void unwind_pass1(ext2_filsys fs)
{
ext2fs_free_mem((void **) &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.
*/
int e2fsck_pass1_check_device_inode(struct ext2_inode *inode)
{
int i;
for (i=4; i < EXT2_N_BLOCKS; i++)
if (inode->i_block[i])
return 0;
return 1;
}
void e2fsck_pass1(e2fsck_t ctx)
{
ext2_filsys fs = ctx->fs;
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;
#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);
#ifdef MTRACE
mtrace_print("Pass 1");
#endif
/*
* 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_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_allocate_block_bitmap(fs, "illegal block map",
&ctx->block_illegal_map);
if (pctx.errcode) {
pctx.num = 2;
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 = 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;
}
mark_table_blocks(ctx);
block_buf = e2fsck_allocate_memory(ctx, fs->blocksize * 3,
"block interate buffer");
fs->get_blocks = pass1_get_blocks;
fs->check_directory = pass1_check_directory;
fs->read_inode = pass1_read_inode;
fs->write_inode = pass1_write_inode;
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);
pctx.errcode = ext2fs_get_next_inode(scan, &ino, &inode);
if (pctx.errcode) {
fix_problem(ctx, PR_1_ISCAN_ERROR, &pctx);
ctx->flags |= E2F_FLAG_ABORT;
return;
}
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)
(ctx->progress)(ctx, 1, 0, ctx->fs->group_desc_count);
while (ino) {
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.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);
goto next;
}
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");
}
}
}
if (ino == EXT2_BOOT_LOADER_INO) {
ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino);
check_blocks(ctx, &pctx, block_buf);
goto next;
}
if ((ino != EXT2_ROOT_INO) &&
(ino < EXT2_FIRST_INODE(fs->super))) {
ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino);
if (inode.i_mode != 0) {
if (fix_problem(ctx,
PR_1_RESERVED_BAD_MODE, &pctx)) {
inode.i_mode = 0;
e2fsck_write_inode(ctx, ino, &inode,
"pass1");
}
}
check_blocks(ctx, &pctx, block_buf);
goto next;
}
/*
* 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");
}
}
goto next;
}
/*
* 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
|| inode.i_file_acl || inode.i_dir_acl) {
if (!ctx->inode_bad_map)
alloc_bad_map(ctx);
ext2fs_mark_inode_bitmap(ctx->inode_bad_map, ino);
}
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))
ctx->fs_regular_count++;
else if (LINUX_S_ISCHR (inode.i_mode) &&
e2fsck_pass1_check_device_inode(&inode))
ctx->fs_chardev_count++;
else if (LINUX_S_ISBLK (inode.i_mode) &&
e2fsck_pass1_check_device_inode(&inode))
ctx->fs_blockdev_count++;
else if (LINUX_S_ISLNK (inode.i_mode)) {
ctx->fs_symlinks_count++;
if (!inode.i_blocks) {
ctx->fs_fast_symlinks_count++;
goto next;
}
}
else if (LINUX_S_ISFIFO (inode.i_mode))
ctx->fs_fifo_count++;
else if (LINUX_S_ISSOCK (inode.i_mode))
ctx->fs_sockets_count++;
else {
if (!ctx->inode_bad_map)
alloc_bad_map(ctx);
ext2fs_mark_inode_bitmap(ctx->inode_bad_map, 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]) {
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_ABORT)
return;
if (process_inode_count >= ctx->process_inode_size) {
process_inodes(ctx, block_buf);
if (ctx->flags & E2F_FLAG_ABORT)
return;
}
next:
pctx.errcode = ext2fs_get_next_inode(scan, &ino, &inode);
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);
goto next;
}
if (pctx.errcode) {
fix_problem(ctx, PR_1_ISCAN_ERROR, &pctx);
ctx->flags |= E2F_FLAG_ABORT;
return;
}
}
process_inodes(ctx, block_buf);
ext2fs_close_inode_scan(scan);
ehandler_operation(0);
if (ctx->invalid_bitmaps)
handle_fs_bad_blocks(ctx);
if (ctx->flags & E2F_FLAG_RESTART) {
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((void **) &inodes_to_process);
endit:
fs->get_blocks = 0;
fs->check_directory = 0;
fs->read_inode = 0;
fs->write_inode = 0;
ext2fs_free_mem((void **) &block_buf);
ext2fs_free_block_bitmap(ctx->block_illegal_map);
ctx->block_illegal_map = 0;
#ifdef RESOURCE_TRACK
if (ctx->options & E2F_OPT_TIME2)
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 * private)
{
struct scan_callback_struct *scan_struct = private;
e2fsck_t ctx;
ctx = scan_struct->ctx;
process_inodes((e2fsck_t) fs->private, scan_struct->block_buf);
if (ctx->progress)
(ctx->progress)(ctx, 1, group+1, ctx->fs->group_desc_count);
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;
ino_t old_stashed_ino;
const char *old_operation;
char buf[80];
struct problem_context pctx;
#if 0
printf("begin process_inodes: ");
#endif
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 %lu", pctx.ino);
ehandler_operation(buf);
check_blocks(ctx, &pctx, block_buf);
if (ctx->flags & E2F_FLAG_ABORT)
return;
}
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 int 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;
return (ib_a->inode.i_block[EXT2_IND_BLOCK] -
ib_b->inode.i_block[EXT2_IND_BLOCK]);
}
/*
* This procedure will allocate the inode bad map table
*/
static void alloc_bad_map(e2fsck_t ctx)
{
struct problem_context pctx;
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;
}
}
/*
* 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;
}
}
/*
* 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);
}
}
/*
* 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;
ino_t ino = pctx->ino;
struct ext2_inode *inode = pctx->inode;
if (!ext2fs_inode_has_valid_blocks(pctx->inode))
return;
pb.ino = ino;
pb.num_blocks = pb.last_block = 0;
pb.num_illegal_blocks = 0;
pb.suppress = 0; pb.clear = 0;
pb.fragmented = 0;
pb.previous_block = 0;
pb.is_dir = LINUX_S_ISDIR(pctx->inode->i_mode);
pb.inode = inode;
pb.pctx = pctx;
pb.ctx = ctx;
pctx->ino = ino;
pctx->errcode = ext2fs_block_iterate2(fs, ino,
pb.is_dir ? BLOCK_FLAG_HOLE : 0,
block_buf, process_block, &pb);
if (ctx->flags & E2F_FLAG_ABORT)
return;
end_problem_latch(ctx, PR_LATCH_BLOCK);
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) {
e2fsck_read_inode(ctx, ino, inode, "check_blocks");
inode->i_links_count = 0;
ext2fs_icount_store(ctx->inode_link_info, ino, 0);
inode->i_dtime = time(0);
e2fsck_write_inode(ctx, ino, inode, "check_blocks");
ext2fs_unmark_inode_bitmap(ctx->inode_dir_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;
return;
}
pb.num_blocks *= (fs->blocksize / 512);
#if 0
printf("inode %u, i_size = %lu, last_block = %lu, i_blocks=%lu, num_blocks = %lu\n",
ino, inode->i_size, pb.last_block, inode->i_blocks,
pb.num_blocks);
#endif
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);
e2fsck_write_inode(ctx, ino, inode, "check_blocks");
ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino);
ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino);
ctx->fs_directory_count--;
pb.is_dir = 0;
}
}
if ((pb.is_dir && (inode->i_size !=
(pb.last_block + 1) * fs->blocksize)) ||
(inode->i_size < pb.last_block * fs->blocksize)) {
pctx->num = (pb.last_block+1) * fs->blocksize;
if (fix_problem(ctx, PR_1_BAD_I_SIZE, pctx)) {
inode->i_size = pctx->num;
e2fsck_write_inode(ctx, ino, inode, "check_blocks");
}
pctx->num = 0;
}
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;
e2fsck_write_inode(ctx, ino, inode, "check_blocks");
}
pctx->num = 0;
}
}
#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().
*/
int process_block(ext2_filsys fs,
blk_t *block_nr,
int blockcnt,
blk_t ref_block,
int ref_offset,
void *private)
{
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 *) private;
pctx = p->pctx;
ctx = p->ctx;
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 (p->previous_block) {
if (p->previous_block+1 != blk)
p->fragmented = 1;
}
p->previous_block = blk;
if (blk < fs->super->s_first_data_block ||
blk >= fs->super->s_blocks_count)
problem = PR_1_ILLEGAL_BLOCK_NUM;
#if 0
else
if (ext2fs_test_block_bitmap(block_illegal_map, blk))
problem = PR_1_BLOCK_OVERLAPS_METADATA;
#endif
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;
pctx->blk = 0;
pctx->blkcount = -1;
}
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;
}
int process_bad_block(ext2_filsys fs,
blk_t *block_nr,
int blockcnt,
blk_t ref_block,
int ref_offset,
void *private)
{
struct process_block_struct *p;
blk_t blk = *block_nr;
int first_block;
int i;
struct problem_context *pctx;
e2fsck_t ctx;
if (!blk)
return 0;
p = (struct process_block_struct *) private;
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_ABORT)
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 (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;
}
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;
}
}
/*
* 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_ABORT)
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, (void **) &buf);
if (pctx.errcode) {
fix_problem(ctx, PR_1_RELOC_MEMORY_ALLOCATE, &pctx);
ext2fs_unmark_valid(fs);
return;
}
ext2fs_mark_super_dirty(fs);
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((void **) &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;
struct problem_context pctx;
clear_problem_context(&pctx);
block = fs->super->s_first_data_block;
for (i = 0; i < fs->group_desc_count; i++) {
pctx.group = i;
if (ext2fs_bg_has_super(fs, i)) {
/*
* Mark this group's copy of the superblock
*/
ext2fs_mark_block_bitmap(ctx->block_found_map, block);
ext2fs_mark_block_bitmap(ctx->block_illegal_map,
block);
/*
* Mark this group's copy of the descriptors
*/
for (j = 0; j < fs->desc_blocks; j++) {
ext2fs_mark_block_bitmap(ctx->block_found_map,
block + j + 1);
ext2fs_mark_block_bitmap(ctx->block_illegal_map,
block + j + 1);
}
}
/*
* 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);
ext2fs_mark_block_bitmap(ctx->block_illegal_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);
ext2fs_mark_block_bitmap(ctx->block_illegal_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);
ext2fs_mark_block_bitmap(ctx->block_illegal_map,
fs->group_desc[i].bg_inode_bitmap);
}
}
block += fs->super->s_blocks_per_group;
}
}
/*
* This 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.
*/
errcode_t pass1_get_blocks(ext2_filsys fs, ino_t ino, blk_t *blocks)
{
e2fsck_t ctx = fs->private;
int i;
if (ino != ctx->stashed_ino)
return EXT2_ET_CALLBACK_NOTHANDLED;
for (i=0; i < EXT2_N_BLOCKS; i++)
blocks[i] = ctx->stashed_inode->i_block[i];
return 0;
}
errcode_t pass1_read_inode(ext2_filsys fs, ino_t ino, struct ext2_inode *inode)
{
e2fsck_t ctx = fs->private;
if (ino != ctx->stashed_ino)
return EXT2_ET_CALLBACK_NOTHANDLED;
*inode = *ctx->stashed_inode;
return 0;
}
errcode_t pass1_write_inode(ext2_filsys fs, ino_t ino,
struct ext2_inode *inode)
{
e2fsck_t ctx = fs->private;
if (ino == ctx->stashed_ino)
*ctx->stashed_inode = *inode;
return EXT2_ET_CALLBACK_NOTHANDLED;
}
errcode_t pass1_check_directory(ext2_filsys fs, ino_t ino)
{
e2fsck_t ctx = fs->private;
if (ino != ctx->stashed_ino)
return EXT2_ET_CALLBACK_NOTHANDLED;
if (!LINUX_S_ISDIR(ctx->stashed_inode->i_mode))
return EXT2_ET_NO_DIRECTORY;
return 0;
}