Whamcloud - gitweb
Add SIGINT and SIGTERM handling to fsck and e2fsck. For e2fsck,
[tools/e2fsprogs.git] / e2fsck / super.c
index 7875dc1..49e9750 100644 (file)
@@ -55,10 +55,15 @@ errcode_t e2fsck_get_device_size(e2fsck_t ctx)
  * helper function to release an inode
  */
 struct process_block_struct {
-       ino_t   ino;
-       e2fsck_t ctx;
+       e2fsck_t        ctx;
+       char            *buf;
        struct problem_context *pctx;
-       int     abort;
+       int             truncating;
+       int             truncate_offset;
+       blk_t           truncate_block;
+       int             truncated_blocks;
+       int             abort;
+       errcode_t       errcode;
 };
 
 static int release_inode_block(ext2_filsys fs,
@@ -67,9 +72,10 @@ static int release_inode_block(ext2_filsys fs,
                               void *priv_data)
 {
        struct process_block_struct *pb;
-       e2fsck_t ctx;
-       struct problem_context *pctx;
-       blk_t   blk = *block_nr;
+       e2fsck_t                ctx;
+       struct problem_context  *pctx;
+       blk_t                   blk = *block_nr;
+       int                     retval = 0;
 
        pb = (struct process_block_struct *) priv_data;
        ctx = pb->ctx;
@@ -84,42 +90,113 @@ static int release_inode_block(ext2_filsys fs,
        if ((blk < fs->super->s_first_data_block) ||
            (blk >= fs->super->s_blocks_count)) {
                fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_BLOCK_NUM, pctx);
+       return_abort:
                pb->abort = 1;
                return BLOCK_ABORT;
        }
 
        if (!ext2fs_test_block_bitmap(fs->block_map, blk)) {
                fix_problem(ctx, PR_0_ORPHAN_ALREADY_CLEARED_BLOCK, pctx);
-               pb->abort = 1;
-               return BLOCK_ABORT;
+               goto return_abort;
+       }
+
+       /*
+        * If we are deleting an orphan, then we leave the fields alone.
+        * If we are truncating an orphan, then update the inode fields
+        * and clean up any partial block data.
+        */
+       if (pb->truncating) {
+               /*
+                * We only remove indirect blocks if they are
+                * completely empty.
+                */
+               if (blockcnt < 0) {
+                       int     i, limit;
+                       blk_t   *bp;
+                       
+                       pb->errcode = io_channel_read_blk(fs->io, blk, 1,
+                                                       pb->buf);
+                       if (pb->errcode)
+                               goto return_abort;
+
+                       limit = fs->blocksize >> 2;
+                       for (i = 0, bp = (blk_t *) pb->buf;
+                            i < limit;  i++, bp++)
+                               if (*bp)
+                                       return 0;
+               }
+               /*
+                * We don't remove direct blocks until we've reached
+                * the truncation block.
+                */
+               if (blockcnt >= 0 && blockcnt < pb->truncate_block)
+                       return 0;
+               /*
+                * If part of the last block needs truncating, we do
+                * it here.
+                */
+               if ((blockcnt == pb->truncate_block) && pb->truncate_offset) {
+                       pb->errcode = io_channel_read_blk(fs->io, blk, 1,
+                                                       pb->buf);
+                       if (pb->errcode)
+                               goto return_abort;
+                       memset(pb->buf + pb->truncate_offset, 0,
+                              fs->blocksize - pb->truncate_offset);
+                       pb->errcode = io_channel_write_blk(fs->io, blk, 1,
+                                                        pb->buf);
+                       if (pb->errcode)
+                               goto return_abort;
+               }
+               pb->truncated_blocks++;
+               *block_nr = 0;
+               retval |= BLOCK_CHANGED;
        }
        
        ext2fs_unmark_block_bitmap(fs->block_map, blk);
        fs->group_desc[ext2fs_group_of_blk(fs, blk)].bg_free_blocks_count++;
        fs->super->s_free_blocks_count++;
        
-       return 0;
+       return retval;
 }
                
 /*
  * This function releases an inode.  Returns 1 if an inconsistency was
- * found.
+ * found.  If the inode has a link count, then it is being truncated and
+ * not deleted.
  */
-static int release_inode_blocks(e2fsck_t ctx, ino_t ino, char* block_buf,
+static int release_inode_blocks(e2fsck_t ctx, ext2_ino_t ino,
+                               struct ext2_inode *inode, char *block_buf,
                                struct problem_context *pctx)
 {
-       ext2_filsys fs = ctx->fs;
+       ext2_filsys                     fs = ctx->fs;
        errcode_t                       retval;
        struct process_block_struct     pb;
 
-       pb.ino = ino;
+       if (!ext2fs_inode_has_valid_blocks(inode))
+               return 0;
+
+       pb.buf = block_buf + 3 * ctx->fs->blocksize;
        pb.ctx = ctx;
        pb.abort = 0;
+       pb.errcode = 0;
        pb.pctx = pctx;
-       retval = ext2fs_block_iterate(fs, ino, 0, block_buf,
-                                     release_inode_block, &pb);
+       if (inode->i_links_count) {
+               pb.truncating = 1;
+               pb.truncate_block = (blk_t)
+                       ((((long long)inode->i_size_high << 32) +
+                         inode->i_size + fs->blocksize - 1) /
+                        fs->blocksize);
+               pb.truncate_offset = inode->i_size % fs->blocksize;
+       } else {
+               pb.truncating = 0;
+               pb.truncate_block = 0;
+               pb.truncate_offset = 0;
+       }
+       pb.truncated_blocks = 0;
+       retval = ext2fs_block_iterate(fs, ino, BLOCK_FLAG_DEPTH_TRAVERSE, 
+                                     block_buf, release_inode_block, &pb);
        if (retval) {
-               com_err("delete_file", retval,
+               com_err("release_inode_blocks", retval,
                        _("while calling ext2fs_block_iterate for inode %d"),
                        ino);
                return 1;
@@ -127,6 +204,13 @@ static int release_inode_blocks(e2fsck_t ctx, ino_t ino, char* block_buf,
        if (pb.abort)
                return 1;
 
+       /* Refresh the inode since ext2fs_block_iterate may have changed it */
+       e2fsck_read_inode(ctx, ino, inode, "release_inode_blocks");
+
+       if (pb.truncated_blocks)
+               inode->i_blocks -= pb.truncated_blocks *
+                       (fs->blocksize / 512);
+
        ext2fs_mark_bb_dirty(fs);
        return 0;
 }
@@ -139,7 +223,7 @@ static int release_orphan_inodes(e2fsck_t ctx)
 {
        ext2_filsys fs = ctx->fs;
        int group;
-       ino_t   ino, next_ino;
+       ext2_ino_t      ino, next_ino;
        struct ext2_inode inode;
        struct problem_context pctx;
        char *block_buf;
@@ -153,58 +237,66 @@ static int release_orphan_inodes(e2fsck_t ctx)
         */
        fs->super->s_last_orphan = 0;
        ext2fs_mark_super_dirty(fs);
+
+       /*
+        * If the filesystem contains errors, don't run the orphan
+        * list, since the orphan list can't be trusted; and we're
+        * going to be running a full e2fsck run anyway...
+        */
+       if (fs->super->s_state & EXT2_ERROR_FS)
+               return 0;
        
        if ((ino < EXT2_FIRST_INODE(fs->super)) ||
            (ino > fs->super->s_inodes_count)) {
                clear_problem_context(&pctx);
-               pctx.ino;
+               pctx.ino = ino;
                fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_HEAD_INODE, &pctx);
                return 1;
        }
 
-       block_buf = (char *) e2fsck_allocate_memory(ctx, fs->blocksize * 3,
-                                                   "block interate buffer");
+       block_buf = (char *) e2fsck_allocate_memory(ctx, fs->blocksize * 4,
+                                                   "block iterate buffer");
        e2fsck_read_bitmaps(ctx);
        
        while (ino) {
-               e2fsck_read_inode(ctx, ino, &inode, "delete_file");
+               e2fsck_read_inode(ctx, ino, &inode, "release_orphan_inodes");
                clear_problem_context(&pctx);
                pctx.ino = ino;
                pctx.inode = &inode;
+               pctx.str = inode.i_links_count ? _("Truncating") :
+                       _("Clearing");
 
-               fix_problem(ctx, PR_0_CLEAR_ORPHAN_INODE, &pctx);
+               fix_problem(ctx, PR_0_ORPHAN_CLEAR_INODE, &pctx);
 
-               if (inode.i_links_count) {
-                       fix_problem(ctx, PR_0_ORPHAN_INODE_INUSE, &pctx);
-                       goto abort;
-               }
                next_ino = inode.i_dtime;
                if (next_ino &&
                    ((next_ino < EXT2_FIRST_INODE(fs->super)) ||
                     (next_ino > fs->super->s_inodes_count))) {
                        pctx.ino = next_ino;
                        fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_INODE, &pctx);
-                       goto abort;
+                       goto return_abort;
                }
 
-               if (release_inode_blocks(ctx, ino, block_buf, &pctx))
-                       goto abort;
-               
-               inode.i_dtime = time(0);
+               if (release_inode_blocks(ctx, ino, &inode, block_buf, &pctx))
+                       goto return_abort;
+
+               if (!inode.i_links_count) {
+                       ext2fs_unmark_inode_bitmap(fs->inode_map, ino);
+                       ext2fs_mark_ib_dirty(fs);
+                       group = ext2fs_group_of_ino(fs, ino);
+                       fs->group_desc[group].bg_free_inodes_count++;
+                       fs->super->s_free_inodes_count++;
+                       if (LINUX_S_ISDIR(inode.i_mode))
+                               fs->group_desc[group].bg_used_dirs_count--;
+                       
+                       inode.i_dtime = time(0);
+               }
                e2fsck_write_inode(ctx, ino, &inode, "delete_file");
-
-               ext2fs_unmark_inode_bitmap(fs->inode_map, ino);
-               ext2fs_mark_ib_dirty(fs);
-               group = ext2fs_group_of_ino(fs, ino);
-               fs->group_desc[group].bg_free_inodes_count++;
-               fs->super->s_free_inodes_count++;
-               if (LINUX_S_ISDIR(inode.i_mode))
-                       fs->group_desc[group].bg_used_dirs_count--;
-               
                ino = next_ino;
        }
+       ext2fs_free_mem((void **) &block_buf);
        return 0;
-abort:
+return_abort:
        ext2fs_free_mem((void **) &block_buf);
        return 1;
 }
@@ -214,16 +306,22 @@ void check_super_block(e2fsck_t ctx)
 {
        ext2_filsys fs = ctx->fs;
        blk_t   first_block, last_block;
-       struct ext2fs_sb *s = (struct ext2fs_sb *) fs->super;
+       struct ext2_super_block *sb = fs->super;
        blk_t   blocks_per_group = fs->super->s_blocks_per_group;
+       blk_t   bpg_max;
        int     inodes_per_block;
+       int     ipg_max;
        dgrp_t  i;
        blk_t   should_be;
        struct problem_context  pctx;
-       
-       inodes_per_block = (EXT2_INODE_SIZE(fs->super) + 
-                           EXT2_BLOCK_SIZE(fs->super) - 1) /
-                                   EXT2_BLOCK_SIZE(fs->super);
+
+       inodes_per_block = EXT2_INODES_PER_BLOCK(fs->super);
+       ipg_max = inodes_per_block * (blocks_per_group - 4);
+       if (ipg_max > EXT2_MAX_INODES_PER_GROUP(sb))
+               ipg_max = EXT2_MAX_INODES_PER_GROUP(sb);
+       bpg_max = 8 * EXT2_BLOCK_SIZE(sb);
+       if (bpg_max > EXT2_MAX_BLOCKS_PER_GROUP(sb))
+               bpg_max = EXT2_MAX_BLOCKS_PER_GROUP(sb);
 
        ctx->invalid_inode_bitmap_flag = (int *) e2fsck_allocate_memory(ctx,
                 sizeof(int) * fs->group_desc_count, "invalid_inode_bitmap");
@@ -231,32 +329,32 @@ void check_super_block(e2fsck_t ctx)
                 sizeof(int) * fs->group_desc_count, "invalid_block_bitmap");
        ctx->invalid_inode_table_flag = (int *) e2fsck_allocate_memory(ctx,
                sizeof(int) * fs->group_desc_count, "invalid_inode_table");
-               
+
        clear_problem_context(&pctx);
 
        /*
         * Verify the super block constants...
         */
-       check_super_value(ctx, "inodes_count", s->s_inodes_count,
+       check_super_value(ctx, "inodes_count", sb->s_inodes_count,
                          MIN_CHECK, 1, 0);
-       check_super_value(ctx, "blocks_count", s->s_blocks_count,
+       check_super_value(ctx, "blocks_count", sb->s_blocks_count,
                          MIN_CHECK, 1, 0);
-       check_super_value(ctx, "first_data_block", s->s_first_data_block,
-                         MAX_CHECK, 0, s->s_blocks_count);
-       check_super_value(ctx, "log_frag_size", s->s_log_frag_size,
-                         MAX_CHECK, 0, 2);
-       check_super_value(ctx, "log_block_size", s->s_log_block_size,
-                         MIN_CHECK | MAX_CHECK, s->s_log_frag_size,
-                         2);
-       check_super_value(ctx, "frags_per_group", s->s_frags_per_group,
-                         MIN_CHECK | MAX_CHECK, 1, 8 * EXT2_BLOCK_SIZE(s));
-       check_super_value(ctx, "blocks_per_group", s->s_blocks_per_group,
-                         MIN_CHECK | MAX_CHECK, 1, 8 * EXT2_BLOCK_SIZE(s));
-       check_super_value(ctx, "inodes_per_group", s->s_inodes_per_group,
-                         MIN_CHECK | MAX_CHECK, 1,
-                         inodes_per_block * blocks_per_group);
-       check_super_value(ctx, "r_blocks_count", s->s_r_blocks_count,
-                         MAX_CHECK, 0, s->s_blocks_count);
+       check_super_value(ctx, "first_data_block", sb->s_first_data_block,
+                         MAX_CHECK, 0, sb->s_blocks_count);
+       check_super_value(ctx, "log_block_size", sb->s_log_block_size,
+                         MIN_CHECK | MAX_CHECK, 0,
+                         EXT2_MAX_BLOCK_LOG_SIZE - EXT2_MIN_BLOCK_LOG_SIZE);
+       check_super_value(ctx, "log_frag_size", sb->s_log_frag_size,
+                         MIN_CHECK | MAX_CHECK, 0, sb->s_log_block_size);
+       check_super_value(ctx, "frags_per_group", sb->s_frags_per_group,
+                         MIN_CHECK | MAX_CHECK, sb->s_blocks_per_group,
+                         bpg_max);
+       check_super_value(ctx, "blocks_per_group", sb->s_blocks_per_group,
+                         MIN_CHECK | MAX_CHECK, 8, bpg_max);
+       check_super_value(ctx, "inodes_per_group", sb->s_inodes_per_group,
+                         MIN_CHECK | MAX_CHECK, inodes_per_block, ipg_max);
+       check_super_value(ctx, "r_blocks_count", sb->s_r_blocks_count,
+                         MAX_CHECK, 0, sb->s_blocks_count / 4);
 
        if (!ctx->num_blocks) {
                pctx.errcode = e2fsck_get_device_size(ctx);
@@ -266,8 +364,8 @@ void check_super_block(e2fsck_t ctx)
                        return;
                }
                if ((pctx.errcode != EXT2_ET_UNIMPLEMENTED) &&
-                   (ctx->num_blocks < s->s_blocks_count)) {
-                       pctx.blk = s->s_blocks_count;
+                   (ctx->num_blocks < sb->s_blocks_count)) {
+                       pctx.blk = sb->s_blocks_count;
                        pctx.blk2 = ctx->num_blocks;
                        if (fix_problem(ctx, PR_0_FS_SIZE_WRONG, &pctx)) {
                                ctx->flags |= E2F_FLAG_ABORT;
@@ -276,39 +374,39 @@ void check_super_block(e2fsck_t ctx)
                }
        }
 
-       if (s->s_log_block_size != s->s_log_frag_size) {
-               pctx.blk = EXT2_BLOCK_SIZE(s);
-               pctx.blk2 = EXT2_FRAG_SIZE(s);
+       if (sb->s_log_block_size != sb->s_log_frag_size) {
+               pctx.blk = EXT2_BLOCK_SIZE(sb);
+               pctx.blk2 = EXT2_FRAG_SIZE(sb);
                fix_problem(ctx, PR_0_NO_FRAGMENTS, &pctx);
                ctx->flags |= E2F_FLAG_ABORT;
                return;
        }
 
-       should_be = s->s_frags_per_group >>
-               (s->s_log_block_size - s->s_log_frag_size);             
-       if (s->s_blocks_per_group != should_be) {
-               pctx.blk = s->s_blocks_per_group;
+       should_be = sb->s_frags_per_group >>
+               (sb->s_log_block_size - sb->s_log_frag_size);           
+       if (sb->s_blocks_per_group != should_be) {
+               pctx.blk = sb->s_blocks_per_group;
                pctx.blk2 = should_be;
                fix_problem(ctx, PR_0_BLOCKS_PER_GROUP, &pctx);
                ctx->flags |= E2F_FLAG_ABORT;
                return;
        }
 
-       should_be = (s->s_log_block_size == 0) ? 1 : 0;
-       if (s->s_first_data_block != should_be) {
-               pctx.blk = s->s_first_data_block;
+       should_be = (sb->s_log_block_size == 0) ? 1 : 0;
+       if (sb->s_first_data_block != should_be) {
+               pctx.blk = sb->s_first_data_block;
                pctx.blk2 = should_be;
                fix_problem(ctx, PR_0_FIRST_DATA_BLOCK, &pctx);
                ctx->flags |= E2F_FLAG_ABORT;
                return;
        }
 
-       should_be = s->s_inodes_per_group * fs->group_desc_count;
-       if (s->s_inodes_count != should_be) {
-               pctx.ino = s->s_inodes_count;
+       should_be = sb->s_inodes_per_group * fs->group_desc_count;
+       if (sb->s_inodes_count != should_be) {
+               pctx.ino = sb->s_inodes_count;
                pctx.ino2 = should_be;
                if (fix_problem(ctx, PR_0_INODE_COUNT_WRONG, &pctx)) {
-                       s->s_inodes_count = should_be;
+                       sb->s_inodes_count = should_be;
                        ext2fs_mark_super_dirty(fs);
                }
        }
@@ -327,30 +425,33 @@ void check_super_block(e2fsck_t ctx)
                if ((fs->group_desc[i].bg_block_bitmap < first_block) ||
                    (fs->group_desc[i].bg_block_bitmap >= last_block)) {
                        pctx.blk = fs->group_desc[i].bg_block_bitmap;
-                       if (fix_problem(ctx, PR_0_BB_NOT_GROUP, &pctx)) {
+                       if (fix_problem(ctx, PR_0_BB_NOT_GROUP, &pctx))
                                fs->group_desc[i].bg_block_bitmap = 0;
-                               ctx->invalid_block_bitmap_flag[i]++;
-                               ctx->invalid_bitmaps++;
-                       }
+               }
+               if (fs->group_desc[i].bg_block_bitmap == 0) {
+                       ctx->invalid_block_bitmap_flag[i]++;
+                       ctx->invalid_bitmaps++;
                }
                if ((fs->group_desc[i].bg_inode_bitmap < first_block) ||
                    (fs->group_desc[i].bg_inode_bitmap >= last_block)) {
                        pctx.blk = fs->group_desc[i].bg_inode_bitmap;
-                       if (fix_problem(ctx, PR_0_IB_NOT_GROUP, &pctx)) {
+                       if (fix_problem(ctx, PR_0_IB_NOT_GROUP, &pctx))
                                fs->group_desc[i].bg_inode_bitmap = 0;
-                               ctx->invalid_inode_bitmap_flag[i]++;
-                               ctx->invalid_bitmaps++;
-                       }
+               }
+               if (fs->group_desc[i].bg_inode_bitmap == 0) {
+                       ctx->invalid_inode_bitmap_flag[i]++;
+                       ctx->invalid_bitmaps++;
                }
                if ((fs->group_desc[i].bg_inode_table < first_block) ||
                    ((fs->group_desc[i].bg_inode_table +
                      fs->inode_blocks_per_group - 1) >= last_block)) {
                        pctx.blk = fs->group_desc[i].bg_inode_table;
-                       if (fix_problem(ctx, PR_0_ITABLE_NOT_GROUP, &pctx)) {
+                       if (fix_problem(ctx, PR_0_ITABLE_NOT_GROUP, &pctx))
                                fs->group_desc[i].bg_inode_table = 0;
-                               ctx->invalid_inode_table_flag[i]++;
-                               ctx->invalid_bitmaps++;
-                       }
+               }
+               if (fs->group_desc[i].bg_inode_table == 0) {
+                       ctx->invalid_inode_table_flag[i]++;
+                       ctx->invalid_bitmaps++;
                }
                first_block += fs->super->s_blocks_per_group;
                last_block += fs->super->s_blocks_per_group;
@@ -370,9 +471,9 @@ void check_super_block(e2fsck_t ctx)
        /*
         * If the UUID field isn't assigned, assign it.
         */
-       if (!(ctx->options & E2F_OPT_READONLY) && uuid_is_null(s->s_uuid)) {
+       if (!(ctx->options & E2F_OPT_READONLY) && uuid_is_null(sb->s_uuid)) {
                if (fix_problem(ctx, PR_0_ADD_UUID, &pctx)) {
-                       uuid_generate(s->s_uuid);
+                       uuid_generate(sb->s_uuid);
                        ext2fs_mark_super_dirty(fs);
                }
        }
@@ -395,13 +496,33 @@ void check_super_block(e2fsck_t ctx)
        }
 
        /*
+        * If we have any of the compatibility flags set, we need to have a
+        * revision 1 filesystem.  Most kernels will not check the flags on
+        * a rev 0 filesystem and we may have corruption issues because of
+        * the incompatible changes to the filesystem.
+        */
+       if (!(ctx->options & E2F_OPT_READONLY) &&
+           fs->super->s_rev_level == EXT2_GOOD_OLD_REV &&
+           (fs->super->s_feature_compat ||
+            fs->super->s_feature_ro_compat ||
+            fs->super->s_feature_incompat) &&
+           fix_problem(ctx, PR_0_FS_REV_LEVEL, &pctx)) {
+               ext2fs_update_dynamic_rev(fs);
+               ext2fs_mark_super_dirty(fs);
+       }
+
+       /*
         * Clean up any orphan inodes, if present.
         */
        if (!(ctx->options & E2F_OPT_READONLY) && release_orphan_inodes(ctx)) {
                fs->super->s_state &= ~EXT2_VALID_FS;
-               ext2fs_mark_super_dirty(ctx->fs);
+               ext2fs_mark_super_dirty(fs);
        }
 
+       /*
+        * Move the ext3 journal file, if necessary.
+        */
+       e2fsck_move_ext3_journal(ctx);
        return;
 }