diff --git a/e2fsck/ChangeLog b/e2fsck/ChangeLog index bb0f6b5f..1eb1064d 100644 --- a/e2fsck/ChangeLog +++ b/e2fsck/ChangeLog @@ -1,5 +1,15 @@ 2007-03-31 Theodore Tso + * pass1.c (e2fsck_pass1, check_is_really_dir): Check for an edge + condition where the mode of a directory is incorrect, and + looks like a special device, but it is really a directory. + We can't do this for regular files because of the + performance hit, but this will catch directories which + have their i_mode bits mutated so they looks like a + special device. + + * problem.c, problem.h (PR_1_TREAT_AS_DIRECTORY): New problem code + * message.c (expand_percent_expression): Add support for %It, which will print the type of the inode. diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c index 9162b668..6f816dff 100644 --- a/e2fsck/pass1.c +++ b/e2fsck/pass1.c @@ -372,6 +372,74 @@ 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; + int i, not_device = 0; + blk_t blk; + struct ext2_dir_entry *dirent; + + if (LINUX_S_ISDIR(inode->i_mode) || LINUX_S_ISREG(inode->i_mode) || + inode->i_block[0] == 0) + return; + + for (i=1; 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; + + if (LINUX_S_ISLNK(inode->i_mode) && inode->i_links_count == 1) + return; + + if (ext2fs_read_dir_block(ctx->fs, inode->i_block[0], buf)) + 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"); + } +} + void e2fsck_pass1(e2fsck_t ctx) { int i; @@ -769,6 +837,7 @@ void e2fsck_pass1(e2fsck_t ctx) } check_inode_extra_space(ctx, &pctx); + check_is_really_dir(ctx, &pctx, block_buf); if (LINUX_S_ISDIR(inode->i_mode)) { ext2fs_mark_inode_bitmap(ctx->inode_dir_map, ino); diff --git a/e2fsck/problem.c b/e2fsck/problem.c index cb535ad0..9f4af509 100644 --- a/e2fsck/problem.c +++ b/e2fsck/problem.c @@ -779,6 +779,11 @@ static struct e2fsck_problem problem_table[] = { N_("@a in @i %i has a hash (%N) which is @n (must be 0)\n"), PROMPT_CLEAR, PR_PREEN_OK }, + /* inode appears to be a directory */ + { PR_1_TREAT_AS_DIRECTORY, + N_("@i %i is a %It but it looks like it is really a directory.\n"), + PROMPT_FIX, 0 }, + /* Pass 1b errors */ /* Pass 1B: Rescan for duplicate/bad blocks */ diff --git a/e2fsck/problem.h b/e2fsck/problem.h index e5070b05..560907ac 100644 --- a/e2fsck/problem.h +++ b/e2fsck/problem.h @@ -452,6 +452,9 @@ struct problem_context { /* wrong EA hash value */ #define PR_1_ATTR_HASH 0x010054 +/* inode appears to be a directory */ +#define PR_1_TREAT_AS_DIRECTORY 0x010055 + /* * Pass 1b errors */ diff --git a/tests/ChangeLog b/tests/ChangeLog index d7711384..98e1b753 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,7 @@ +2007-04-01 Theodore Tso + + * f_dir_bad_mode: New test case. + 2007-03-31 Theodore Tso * f_orphan_dotdot_ft: New test case which checks to see what diff --git a/tests/f_dir_bad_mode/expect.1 b/tests/f_dir_bad_mode/expect.1 new file mode 100644 index 00000000..10b4a997 --- /dev/null +++ b/tests/f_dir_bad_mode/expect.1 @@ -0,0 +1,12 @@ +Pass 1: Checking inodes, blocks, and sizes +Inode 12 is a socket but it looks like it is really a directory. +Fix? yes + +Pass 2: Checking directory structure +Pass 3: Checking directory connectivity +Pass 4: Checking reference counts +Pass 5: Checking group summary information + +test_filesys: ***** FILE SYSTEM WAS MODIFIED ***** +test_filesys: 15/16 files (0.0% non-contiguous), 25/100 blocks +Exit status is 1 diff --git a/tests/f_dir_bad_mode/expect.2 b/tests/f_dir_bad_mode/expect.2 new file mode 100644 index 00000000..7d2352de --- /dev/null +++ b/tests/f_dir_bad_mode/expect.2 @@ -0,0 +1,7 @@ +Pass 1: Checking inodes, blocks, and sizes +Pass 2: Checking directory structure +Pass 3: Checking directory connectivity +Pass 4: Checking reference counts +Pass 5: Checking group summary information +test_filesys: 15/16 files (0.0% non-contiguous), 25/100 blocks +Exit status is 0 diff --git a/tests/f_dir_bad_mode/image.gz b/tests/f_dir_bad_mode/image.gz new file mode 100644 index 00000000..2f647a13 Binary files /dev/null and b/tests/f_dir_bad_mode/image.gz differ diff --git a/tests/f_dir_bad_mode/name b/tests/f_dir_bad_mode/name new file mode 100644 index 00000000..7cbacc2e --- /dev/null +++ b/tests/f_dir_bad_mode/name @@ -0,0 +1 @@ +directory with corrupted i_mode