diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..7564fcc --- /dev/null +++ b/Kconfig @@ -0,0 +1,95 @@ +config SNAPSHOT_ON_DISK + bool "Snapshots on-disk format changes" + help + Snapshots uses a few reserved fields and flags in the Ext2 super block, + and inode structs. + Snapshots require the compatible feature 'exclude_bitmap', meaning that + the exclude bitmap was allocated. + snapshots require the read-only compatible features 'has_snapshot', + meaning that the file system may contain snapshots. + +config SNAPSHOT_BIG_JOURNAL + bool "Create a big journal for COW operations" + help + COW transactions reserve up to 24 times more credits than traditional + transactions for the same operation. On mke2fs and tune2fs, if the + '-J big' option is used to create a journal, increase the default + journal size by a factor of 24. + +config SNAPSHOT_HAS_SNAPSHOT + bool "Avoid offline modifications to a file system with snapshots" + help + Snapshots require the read-only compatible feature 'has_snapshot', + so the file system will be mounted by old kernels in read-only mode + to protect the snapshots. + Fsck displays a warning about possible corruption of the snapshots + in interactive mode and avoids freeing blocks in preen mode. + +config SNAPSHOT_EXCLUDE_BITMAP + bool "Create/check the exclude bitmap" + help + Excluding snapshot blocks from COW is done by setting their bit in + the exclude bitmap. There is one exclude bitmap block per block group. + Mke2fs allocates the exclude bitmap and it cannot be removed. + Fsck checks that all (and only) snapshot file blocks are excluded. + +config SNAPSHOT_CTL + bool "Snapshot control with chsnap/lssnap" + help + Set/clear snapshots parent directory with chattr +/-x. + Take/delete snapshot with chsnap +/-S. + Enable/disable snapshot with chsnap +/-n. + View snapshot status with lssnap. + +config SNAPSHOT_CHECK_LIST + bool "Check snapshot list on fsck" + help + Check that all inodes on snapshot list are valid snapshots and + prompt to terminate list when bad inode is found. + +config SNAPSHOT_FIX_SNAPSHOT + bool "Delete all snapshots on fsck -x" + help + On fsck -x or if the 'fix_snapshot' flag is set, prompt to delete + all snapshot inodes and reset exclude bitmap. + +config SNAPSHOT_HUGE_SNAPSHOT + bool "Map maximum filesystem size with a single snapshot file" + help + To map 2^32 logical blocks, 4 triple indirect blocks are used instead + of just one. The extra 3 triple indirect blocks are stored in-place + of direct blocks, which are not in use by snapshot files. + Snapshots cannot be enabled on filesytem with block size < 4K. + +config SNAPSHOT_MESSAGE_BUFFER + bool "Dump message buffer on fsck" + help + Error messages are recorded in a message buffer after the + journal super block. On journal recovery, the journal message buffer + is copied to the file system message buffer. On fsck, if the message + buffer is not empty, the recorded messages are printed to stdout and + the buffer is cleared. + With a default block size of 4K, there is always 2K of free + space for the message buffer after the 1K super block. + +config SNAPSHOT_CLEANUP + bool "Cleanup snapshot list when removing has_snapshot feature" + help + Discard all snapshots by 'tune2fs -O ^has_snapshot'. + Snapshot inodes are chained on a list starting at the super block. + Delete all snapshot inodes on the list and reset exclude bitmap. + +config SNAPSHOT_CTL_OLD + bool "Old next3 snapshot control with chattr/lsattr -X" + help + Take/delete snapshot with chattr -X +/-S. + Enable/disable snapshot with chattr -X +/-n. + View snapshot status with lsattr -X. + +config SNAPSHOT_EXCLUDE_INODE + bool "Migrate old exclude_inode feature to exclude_bitmap feature" + help + Read exclude bitmap block locations from exclude inode, store them + in the group descriptors, remove the exclude inode, clear the + exclude_inode feature and set the exclude_bitmap feature. + diff --git a/e2fsck/e2fsck.h b/e2fsck/e2fsck.h index 4e7fc72..c3cae8f 100644 --- a/e2fsck/e2fsck.h +++ b/e2fsck/e2fsck.h @@ -156,6 +156,9 @@ struct resource_track { #define E2F_OPT_COMPRESS_DIRS 0x0400 #define E2F_OPT_FRAGCHECK 0x0800 #define E2F_OPT_JOURNAL_ONLY 0x1000 /* only replay the journal */ +#ifdef EXT2FS_SNAPSHOT_FIX_SNAPSHOT +#define E2F_OPT_FIX_SNAPSHOT 0x2000 +#endif /* * E2fsck flags @@ -178,6 +181,12 @@ struct resource_track { #define E2F_FLAG_GOT_DEVSIZE 0x0800 /* Device size has been fetched */ #define E2F_FLAG_EXITING 0x1000 /* E2fsck exiting due to errors */ #define E2F_FLAG_TIME_INSANE 0x2000 /* Time is insane */ +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE +#define E2F_FLAG_EXCLUDE_INODE 0x4000 /* Request to recreate exclude inode */ +#endif +#ifdef EXT2FS_SNAPSHOT_FIX_SNAPSHOT +#define E2F_FLAG_CLEAR_SNAPSHOTS 0x8000 /* Clear all snapshot inodes */ +#endif #define E2F_RESET_FLAGS (E2F_FLAG_TIME_INSANE) @@ -234,6 +243,9 @@ struct e2fsck_struct { ext2fs_inode_bitmap inode_reg_map; /* Inodes which are regular files*/ ext2fs_block_bitmap block_found_map; /* Blocks which are in use */ +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + ext2fs_block_bitmap block_excluded_map; /* Blocks which are excluded */ +#endif ext2fs_block_bitmap block_dup_map; /* Blks referenced more than once */ ext2fs_block_bitmap block_ea_map; /* Blocks which are used by EA's */ @@ -476,6 +488,15 @@ void e2fsck_rehash_directories(e2fsck_t ctx); void check_super_block(e2fsck_t ctx); int check_backup_super_block(e2fsck_t ctx); void check_resize_inode(e2fsck_t ctx); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE +void check_exclude_inode(e2fsck_t ctx); +#endif +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT +void check_snapshots(e2fsck_t ctx); +#endif +#ifdef EXT2FS_SNAPSHOT_MESSAGE_BUFFER +void e2fsck_clear_message_buffer(e2fsck_t ctx); +#endif /* util.c */ extern void *e2fsck_allocate_memory(e2fsck_t ctx, unsigned int size, diff --git a/e2fsck/journal.c b/e2fsck/journal.c index b741eb9..a1eefb4 100644 --- a/e2fsck/journal.c +++ b/e2fsck/journal.c @@ -837,6 +837,24 @@ static errcode_t recover_ext3_journal(e2fsck_t ctx) if (journal->j_superblock->s_errno) { +#ifdef EXT2FS_SNAPSHOT_MESSAGE_BUFFER + /* journal message buffer at journal super block + 1K */ + char *buf = ((char *) journal->j_superblock) + + SUPERBLOCK_OFFSET; + int n, len = ctx->fs->blocksize - MSGBUF_OFFSET; + + if (len >= MSGBUF_OFFSET && *buf) { + /* keep it simple - write in MSGBUF_OFFSET blocksize */ + io_channel_set_blksize(ctx->fs->io, MSGBUF_OFFSET); + n = len / MSGBUF_OFFSET; + /* write journal message buffer to super block + 2K */ + retval = io_channel_write_blk(ctx->fs->io, 1, n, buf); + io_channel_set_blksize(ctx->fs->io, ctx->fs->blocksize); + /* clear journal message buffer */ + memset(buf, 0, len); + } + +#endif ctx->fs->super->s_state |= EXT2_ERROR_FS; ext2fs_mark_super_dirty(ctx->fs); journal->j_superblock->s_errno = 0; diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c index 4bf80d2..f01f579 100644 --- a/e2fsck/pass1.c +++ b/e2fsck/pass1.c @@ -55,6 +55,11 @@ #define _INLINE_ inline #endif +#define ext2fs_file_acl_block(inode) \ + (inode)->i_file_acl +#define ext2fs_fast_mark_block_bitmap2(map, blk) \ + ext2fs_fast_mark_block_bitmap(map, blk) + static int process_block(ext2_filsys fs, blk_t *blocknr, e2_blkcnt_t blockcnt, blk_t ref_blk, int ref_offset, void *priv_data); @@ -79,13 +84,17 @@ static void adjust_extattr_refcount(e2fsck_t ctx, ext2_refcount_t refcount, struct process_block_struct { ext2_ino_t ino; unsigned is_dir:1, is_reg:1, clear:1, suppress:1, +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + fragmented:1, compressed:1, bbcheck:1, snapfile:1; +#else fragmented:1, compressed:1, bbcheck:1; +#endif blk64_t num_blocks; - blk_t max_blocks; + blk64_t max_blocks; e2_blkcnt_t last_block; e2_blkcnt_t last_db_block; int num_illegal_blocks; - blk_t previous_block; + blk64_t previous_block; struct ext2_inode *inode; struct problem_context *pctx; ext2fs_block_bitmap fs_meta_blocks; @@ -626,6 +635,18 @@ void e2fsck_pass1(e2fsck_t ctx) ctx->flags |= E2F_FLAG_ABORT; return; } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (sb->s_feature_compat & EXT2_FEATURE_COMPAT_EXCLUDE_BITMAP) + pctx.errcode = ext2fs_allocate_block_bitmap(fs, + _("excluded block map"), + &ctx->block_excluded_map); + if (pctx.errcode) { + pctx.num = 1; + fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } +#endif e2fsck_setup_tdb_icount(ctx, 0, &ctx->inode_link_info); if (!ctx->inode_link_info) pctx.errcode = ext2fs_create_icount2(fs, 0, 0, 0, @@ -891,7 +912,11 @@ void e2fsck_pass1(e2fsck_t ctx) if (ino == EXT2_BOOT_LOADER_INO) { if (LINUX_S_ISDIR(inode->i_mode)) problem = PR_1_RESERVED_BAD_MODE; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + } else if (ino == EXT2_RESIZE_INO || ino == EXT2_EXCLUDE_INO) { +#else } else if (ino == EXT2_RESIZE_INO) { +#endif if (inode->i_mode && !LINUX_S_ISREG(inode->i_mode)) problem = PR_1_RESERVED_BAD_MODE; @@ -1072,7 +1097,10 @@ void e2fsck_pass1(e2fsck_t ctx) (inode->i_block[EXT2_IND_BLOCK] || inode->i_block[EXT2_DIND_BLOCK] || inode->i_block[EXT2_TIND_BLOCK] || - inode->i_file_acl)) { +#ifdef EXT2FS_SNAPSHOT_HUGE_SNAPSHOT + (inode->i_flags & EXT4_SNAPFILE_FL) || +#endif + ext2fs_file_acl_block(inode))) { inodes_to_process[process_inode_count].ino = ino; inodes_to_process[process_inode_count].inode = *inode; process_inode_count++; @@ -1144,6 +1172,27 @@ void e2fsck_pass1(e2fsck_t ctx) ctx->flags &= ~E2F_FLAG_RESIZE_INODE; } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + if (ctx->flags & E2F_FLAG_EXCLUDE_INODE) { + ext2fs_block_bitmap save_bmap; + + save_bmap = fs->block_map; + fs->block_map = ctx->block_found_map; + clear_problem_context(&pctx); + pctx.errcode = ext2fs_create_exclude_inode(fs, EXCLUDE_CREATE); + if (pctx.errcode && + fix_problem(ctx, PR_1_EXCLUDE_INODE_CREATE, &pctx)) { + memset(&inode, 0, sizeof(inode)); + e2fsck_write_inode(ctx, EXT2_EXCLUDE_INO, inode, + "clear_exclude"); + fs->super->s_feature_compat &= ~EXT2_FEATURE_COMPAT_EXCLUDE_INODE; + ctx->flags |= E2F_FLAG_RESTART; + } + fs->block_map = save_bmap; + ctx->flags &= ~E2F_FLAG_EXCLUDE_INODE; + } + +#endif if (ctx->flags & E2F_FLAG_RESTART) { /* * Only the master copy of the superblock and block @@ -1649,6 +1698,19 @@ void e2fsck_clear_inode(e2fsck_t ctx, ext2_ino_t ino, struct ext2_inode *inode, int restart_flag, const char *source) { +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT + /* don't clear inode with blocks when preening volume with active snapshot */ + if ((ctx->fs->super->s_feature_ro_compat & + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT) && + ctx->fs->super->s_snapshot_inum) { + int i; + for (i = 0; i < EXT2_N_BLOCKS; i++) + if (inode->i_block[i]) + /* if we don't halt, inode blocks will be freed */ + preenhalt(ctx); + } + +#endif inode->i_flags = 0; inode->i_links_count = 0; ext2fs_icount_store(ctx->inode_link_info, ino, 0); @@ -1892,6 +1954,9 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, pb.previous_block = 0; pb.is_dir = LINUX_S_ISDIR(inode->i_mode); pb.is_reg = LINUX_S_ISREG(inode->i_mode); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + pb.snapfile = (pb.is_reg && (inode->i_flags & EXT4_SNAPFILE_FL)); +#endif pb.max_blocks = 1 << (31 - fs->super->s_log_block_size); pb.inode = inode; pb.pctx = pctx; @@ -1899,6 +1964,15 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, pctx->ino = ino; pctx->errcode = 0; +#ifdef EXT2FS_SNAPSHOT_FIX_SNAPSHOT + if (pb.snapfile && (ctx->flags & E2F_FLAG_CLEAR_SNAPSHOTS)) { + /* discarding all snapshot files */ + e2fsck_clear_inode(ctx, ino, inode, E2F_FLAG_RESTART, + "check_blocks"); + return; + } + +#endif extent_fs = (ctx->fs->super->s_feature_incompat & EXT3_FEATURE_INCOMPAT_EXTENTS); @@ -1967,8 +2041,15 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, } } +#ifdef EXT2FS_SNAPSHOT_HUGE_SNAPSHOT + if ((!(fs->super->s_feature_ro_compat & + EXT4_FEATURE_RO_COMPAT_HUGE_FILE) && + /* snapshot file always supports the 'huge_file' flag */ + !(inode->i_flags & EXT4_SNAPFILE_FL)) || +#else if (!(fs->super->s_feature_ro_compat & EXT4_FEATURE_RO_COMPAT_HUGE_FILE) || +#endif !(inode->i_flags & EXT4_HUGE_FILE_FL)) pb.num_blocks *= (fs->blocksize / 512); #if 0 @@ -1999,6 +2080,9 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, !(inode->i_flags & EXT4_EOFBLOCKS_FL)) bad_size = 3; else if (!(extent_fs && (inode->i_flags & EXT4_EXTENTS_FL)) && +#ifdef EXT2FS_SNAPSHOT_HUGE_SNAPSHOT + !(pb.is_reg && (inode->i_flags & EXT4_SNAPFILE_FL)) && +#endif size > ext2_max_sizes[fs->super->s_log_block_size]) /* too big for a direct/indirect-mapped file */ bad_size = 4; @@ -2037,8 +2121,15 @@ static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, (inode->i_size_high || inode->i_size & 0x80000000UL)) ctx->large_files++; if ((pb.num_blocks != ext2fs_inode_i_blocks(fs, inode)) || +#ifdef EXT2FS_SNAPSHOT_HUGE_SNAPSHOT + (((fs->super->s_feature_ro_compat & + EXT4_FEATURE_RO_COMPAT_HUGE_FILE) || + /* snapshot file always supports the 'huge_file' flag */ + (inode->i_flags & EXT4_SNAPFILE_FL)) && +#else ((fs->super->s_feature_ro_compat & EXT4_FEATURE_RO_COMPAT_HUGE_FILE) && +#endif (inode->i_flags & EXT4_HUGE_FILE_FL) && (inode->osd2.linux2.l_i_blocks_hi != 0))) { pctx->num = pb.num_blocks; @@ -2236,6 +2327,11 @@ static int process_block(ext2_filsys fs, mark_block_used(ctx, blk); } else mark_block_used(ctx, blk); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + /* mark snapshot file blocks excluded */ + if (p->snapfile && ctx->block_excluded_map) + ext2fs_fast_mark_block_bitmap2(ctx->block_excluded_map, blk); +#endif p->num_blocks++; if (blockcnt >= 0) p->last_block = blockcnt; diff --git a/e2fsck/pass5.c b/e2fsck/pass5.c index bc3bf02..b744f6a 100644 --- a/e2fsck/pass5.c +++ b/e2fsck/pass5.c @@ -14,6 +14,9 @@ #include "problem.h" static void check_block_bitmaps(e2fsck_t ctx); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP +static void check_exclude_bitmaps(e2fsck_t ctx); +#endif static void check_inode_bitmaps(e2fsck_t ctx); static void check_inode_end(e2fsck_t ctx); static void check_block_end(e2fsck_t ctx); @@ -44,6 +47,11 @@ void e2fsck_pass5(e2fsck_t ctx) check_block_bitmaps(ctx); if (ctx->flags & E2F_FLAG_SIGNAL_MASK) return; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + check_exclude_bitmaps(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; +#endif check_inode_bitmaps(ctx); if (ctx->flags & E2F_FLAG_SIGNAL_MASK) return; @@ -60,6 +68,11 @@ void e2fsck_pass5(e2fsck_t ctx) ctx->inode_dir_map = 0; ext2fs_free_block_bitmap(ctx->block_found_map); ctx->block_found_map = 0; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (ctx->block_excluded_map) + ext2fs_free_block_bitmap(ctx->block_excluded_map); + ctx->block_excluded_map = 0; +#endif print_resource_track(ctx, _("Pass 5"), &rtrack, ctx->fs->io); } @@ -82,6 +95,20 @@ static void print_bitmap_problem(e2fsck_t ctx, int problem, else problem = PR_5_BLOCK_RANGE_USED; break; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + case PR_5_BLOCK_NOTEXCLUDED: + if (pctx->blk == pctx->blk2) + pctx->blk2 = 0; + else + problem = PR_5_BLOCK_RANGE_NOTEXCLUDED; + break; + case PR_5_BLOCK_EXCLUDED: + if (pctx->blk == pctx->blk2) + pctx->blk2 = 0; + else + problem = PR_5_BLOCK_RANGE_EXCLUDED; + break; +#endif case PR_5_INODE_UNUSED: if (pctx->ino == pctx->ino2) pctx->ino2 = 0; @@ -294,6 +321,11 @@ redo_counts: ext2fs_unmark_valid(fs); for (i = 0; i < fs->group_desc_count; i++) { +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT + if (fs->super->s_flags & EXT2_FLAGS_IS_SNAPSHOT) + /* ignore group block counts in next3 snapshot image */ + break; +#endif if (free_array[i] != fs->group_desc[i].bg_free_blocks_count) { pctx.group = i; pctx.blk = fs->group_desc[i].bg_free_blocks_count; @@ -323,6 +355,142 @@ errout: ext2fs_free_mem(&free_array); } +#define blk64_t blk_t +#define ext2fs_bg_flags_test(fs, group, flag) \ + (fs->group_desc[group].bg_flags & flag) +#define ext2fs_bg_flags_clear(fs, group, flag) \ + fs->group_desc[group].bg_flags &= ~flag +#define ext2fs_blocks_count(sb) \ + (sb)->s_blocks_count +#define ext2fs_fast_test_block_bitmap2 ext2fs_fast_test_block_bitmap + +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP +static void check_exclude_bitmaps(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + blk64_t i; + int group = 0; + int blocks = 0; + int actual, bitmap; + struct problem_context pctx; + int problem, save_problem, fixit, had_problem; + errcode_t retval; + int csum_flag; + int skip_group = 0; + + clear_problem_context(&pctx); + + if (!(fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_EXCLUDE_BITMAP)) + return; + + csum_flag = EXT2_HAS_RO_COMPAT_FEATURE(fs->super, + EXT4_FEATURE_RO_COMPAT_GDT_CSUM); +redo_counts: + had_problem = 0; + save_problem = 0; + pctx.blk = pctx.blk2 = NO_BLK; + if (csum_flag && + (ext2fs_bg_flags_test(fs, group, EXT2_BG_BLOCK_UNINIT))) + skip_group++; + for (i = fs->super->s_first_data_block; + i < ext2fs_blocks_count(fs->super); + i++) { + actual = ext2fs_fast_test_block_bitmap2(ctx->block_excluded_map, i); + + if (skip_group) { + bitmap = 0; + actual = (actual != 0); + } else + bitmap = ext2fs_fast_test_block_bitmap2(fs->exclude_map, i); + + if (actual == bitmap) + goto do_counts; + + if (!actual && bitmap) { + /* + * Block not excluded, but marked in exclude bitmap. + */ + problem = PR_5_BLOCK_NOTEXCLUDED; + } else { + /* + * Block excluded, but not marked in exclude bitmap. + */ + problem = PR_5_BLOCK_EXCLUDED; + + if (skip_group) { + struct problem_context pctx2; + pctx2.blk = i; + pctx2.group = group; + if (fix_problem(ctx, PR_5_BLOCK_UNINIT,&pctx2)){ + ext2fs_bg_flags_clear(fs, group, EXT2_BG_BLOCK_UNINIT); + skip_group = 0; + } + } + } + if (pctx.blk == NO_BLK) { + pctx.blk = pctx.blk2 = i; + save_problem = problem; + } else { + if ((problem == save_problem) && + (pctx.blk2 == i-1)) + pctx.blk2++; + else { + print_bitmap_problem(ctx, save_problem, &pctx); + pctx.blk = pctx.blk2 = i; + save_problem = problem; + } + } + ctx->flags |= E2F_FLAG_PROG_SUPPRESS; + had_problem++; + + do_counts: + blocks ++; + if ((blocks == fs->super->s_blocks_per_group) || + (i == fs->super->s_blocks_count-1)) { + group ++; + blocks = 0; + skip_group = 0; + if (ctx->progress) + if ((ctx->progress)(ctx, 5, group, + fs->group_desc_count*2)) + return; + if (csum_flag && + (i != ext2fs_blocks_count(fs->super)-1) && + ext2fs_bg_flags_test(fs, group, + EXT2_BG_BLOCK_UNINIT)) + skip_group++; + } + } + if (pctx.blk != NO_BLK) + print_bitmap_problem(ctx, save_problem, &pctx); + if (had_problem) + fixit = end_problem_latch(ctx, PR_LATCH_XBITMAP); + else + fixit = -1; + ctx->flags &= ~E2F_FLAG_PROG_SUPPRESS; + + if (fixit == 1) { + ext2fs_free_block_bitmap(fs->exclude_map); + retval = ext2fs_copy_bitmap(ctx->block_excluded_map, + &fs->exclude_map); + if (retval) { + clear_problem_context(&pctx); + fix_problem(ctx, PR_5_COPY_BBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ext2fs_mark_exclude_dirty(fs); + /* clear fix_exclude flag */ + if (fs->super->s_flags & EXT2_FLAGS_FIX_EXCLUDE) { + fs->super->s_flags &= ~EXT2_FLAGS_FIX_EXCLUDE; + ext2fs_mark_super_dirty(fs); + } + } else if (fixit == 0) + ext2fs_unmark_valid(fs); +} + +#endif static void check_inode_bitmaps(e2fsck_t ctx) { ext2_filsys fs = ctx->fs; diff --git a/e2fsck/problem.c b/e2fsck/problem.c index 8f0b211..420516e 100644 --- a/e2fsck/problem.c +++ b/e2fsck/problem.c @@ -40,7 +40,15 @@ #define PROMPT_UNLINK 17 #define PROMPT_CLEAR_HTREE 18 #define PROMPT_RECREATE 19 +#ifdef EXT2FS_SNAPSHOT_CHECK_LIST +#define PROMPT_TERMINATE_LIST 20 +#ifdef EXT2FS_SNAPSHOT_FIX_SNAPSHOT +#define PROMPT_DISCARD_SNAPSHOTS 21 +#endif +#define PROMPT_NULL 22 +#else #define PROMPT_NULL 20 +#endif /* * These are the prompts which are used to ask the user if they want @@ -67,7 +75,15 @@ static const char *prompt[] = { N_("Unlink"), /* 17 */ N_("Clear HTree index"),/* 18 */ N_("Recreate"), /* 19 */ +#ifdef EXT2FS_SNAPSHOT_CHECK_LIST + N_("Terminate list"), /* 20 */ +#ifdef EXT2FS_SNAPSHOT_FIX_SNAPSHOT + N_("Discard snapshots"),/* 21 */ +#endif + "", /* 22 */ +#else "", /* 20 */ +#endif }; /* @@ -332,6 +348,33 @@ static struct e2fsck_problem problem_table[] = { N_("Resize @i not valid. "), PROMPT_RECREATE, 0 }, +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + /* Exclude not enabled, but exclude inode is non-zero */ + { PR_0_CLEAR_EXCLUDE_INODE, + N_("Exclude_@i not enabled, but the exclude @i is non-zero. "), + PROMPT_CLEAR, 0 }, + + /* Exclude inode invalid */ + { PR_0_EXCLUDE_INODE_INVALID, + N_("Exclude @i not valid. "), + PROMPT_RECREATE, 0 }, + +#endif +#ifdef EXT2FS_SNAPSHOT_CHECK_LIST + /* Bad snapshot on list */ + { PR_0_BAD_SNAPSHOT, + N_("Bad @i found on snapshot list. "), + PROMPT_TERMINATE_LIST, PR_PREEN_OK }, + +#endif +#ifdef EXT2FS_SNAPSHOT_FIX_SNAPSHOT + /* Corrupted snapshot */ + { PR_0_FIX_SNAPSHOT, + N_("@f may contain corrupted snapshots.\n" + "This version of e2fsck does not support fixing snapshots.\n"), + PROMPT_DISCARD_SNAPSHOTS, 0 }, + +#endif /* Last mount time is in the future */ { PR_0_FUTURE_SB_LAST_MOUNT, N_("@S last mount time (%t,\n\tnow = %T) is in the future.\n"), @@ -800,6 +843,13 @@ static struct e2fsck_problem problem_table[] = { N_("Resize @i (re)creation failed: %m."), PROMPT_CONTINUE, 0 }, +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + /* Exclude inode failed */ + { PR_1_EXCLUDE_INODE_CREATE, + N_("Exclude @i (re)creation failed: %m."), + PROMPT_CLEAR, 0 }, + +#endif /* invalid inode->i_extra_isize */ { PR_1_EXTRA_ISIZE, N_("@i %i has a extra size (%IS) which is @n\n"), @@ -1533,6 +1583,28 @@ static struct e2fsck_problem problem_table[] = { "\n", PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + /* Exclude bitmap differences header */ + { PR_5_EXCLUDE_BITMAP_HEADER, + N_("Exclude @B differences: "), + PROMPT_NONE, PR_PREEN_OK | PR_PREEN_NOMSG}, + + /* Block not excluded, but marked in exclude bitmap */ + { PR_5_BLOCK_NOTEXCLUDED, + " -%b", + PROMPT_NONE, PR_LATCH_XBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Block excluded, but not marked in exclude bitmap */ + { PR_5_BLOCK_EXCLUDED, + " +%b", + PROMPT_NONE, PR_LATCH_XBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Exclude bitmap differences end */ + { PR_5_EXCLUDE_BITMAP_END, + "\n", + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + +#endif /* Inode bitmap differences header */ { PR_5_INODE_BITMAP_HEADER, N_("@i @B differences: "), @@ -1609,6 +1681,18 @@ static struct e2fsck_problem problem_table[] = { " +(%b--%c)", PROMPT_NONE, PR_LATCH_BBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + /* Block range not excluded, but marked in exclude bitmap */ + { PR_5_BLOCK_RANGE_NOTEXCLUDED, + " -(%b--%c)", + PROMPT_NONE, PR_LATCH_XBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Block range excluded, but not marked in exclude bitmap */ + { PR_5_BLOCK_RANGE_EXCLUDED, + " +(%b--%c)", + PROMPT_NONE, PR_LATCH_XBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + +#endif /* Inode range not used, but marked in bitmap */ { PR_5_INODE_RANGE_UNUSED, " -(%i--%j)", @@ -1650,6 +1734,9 @@ static struct latch_descr pr_latch_info[] = { { PR_LATCH_BBLOCK, PR_1_INODE_BBLOCK_LATCH, 0 }, { PR_LATCH_IBITMAP, PR_5_INODE_BITMAP_HEADER, PR_5_INODE_BITMAP_END }, { PR_LATCH_BBITMAP, PR_5_BLOCK_BITMAP_HEADER, PR_5_BLOCK_BITMAP_END }, +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + { PR_LATCH_XBITMAP, PR_5_EXCLUDE_BITMAP_HEADER, PR_5_EXCLUDE_BITMAP_END }, +#endif { PR_LATCH_RELOC, PR_0_RELOCATE_HINT, 0 }, { PR_LATCH_DBLOCK, PR_1B_DUP_BLOCK_HEADER, PR_1B_DUP_BLOCK_END }, { PR_LATCH_LOW_DTIME, PR_1_ORPHAN_LIST_REFUGEES, 0 }, diff --git a/e2fsck/problem.h b/e2fsck/problem.h index 7c4c156..8c58e4d 100644 --- a/e2fsck/problem.h +++ b/e2fsck/problem.h @@ -39,6 +39,9 @@ struct problem_context { #define PR_LATCH_TOOBIG 0x0080 /* Latch for file to big errors */ #define PR_LATCH_OPTIMIZE_DIR 0x0090 /* Latch for optimize directories */ #define PR_LATCH_BG_CHECKSUM 0x00A0 /* Latch for block group checksums */ +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP +#define PR_LATCH_XBITMAP 0x00B0 /* Latch for pass 5 exclude bitmap proc. */ +#endif #define PR_LATCH(x) ((((x) & PR_LATCH_MASK) >> 4) - 1) @@ -227,6 +230,24 @@ struct problem_context { /* Block group checksum (latch question) */ #define PR_0_GDT_CSUM_LATCH 0x00003E +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE +/* Exclude_inode not enabled, but exclude inode is non-zero */ +#define PR_0_CLEAR_EXCLUDE_INODE 0x000100 + +/* Exclude inode invalid */ +#define PR_0_EXCLUDE_INODE_INVALID 0x000101 + +#endif +#ifdef EXT2FS_SNAPSHOT_CHECK_LIST +/* Bas snapshot on list */ +#define PR_0_BAD_SNAPSHOT 0x000102 + +#endif +#ifdef EXT2FS_SNAPSHOT_FIX_SNAPSHOT +/* Corrupted snapshot */ +#define PR_0_FIX_SNAPSHOT 0x000103 + +#endif /* * Pass 1 errors @@ -520,6 +541,11 @@ struct problem_context { /* EOFBLOCKS flag set when not necessary */ #define PR_1_EOFBLOCKS_FL_SET 0x010060 +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE +/* Exclude inode failed */ +#define PR_1_EXCLUDE_INODE_CREATE 0x010100 + +#endif /* * Pass 1b errors */ @@ -983,6 +1009,26 @@ struct problem_context { /* Inode in use but group is marked INODE_UNINIT */ #define PR_5_INODE_UNINIT 0x050019 +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP +/* Exclude bitmap differences header */ +#define PR_5_EXCLUDE_BITMAP_HEADER 0x050100 + +/* Block not excluded, but marked in exclude bitmap */ +#define PR_5_BLOCK_NOTEXCLUDED 0x050101 + +/* Block excluded, but not marked in exclude bitmap */ +#define PR_5_BLOCK_EXCLUDED 0x050102 + +/* Block range not excluded, but marked in exclude bitmap */ +#define PR_5_BLOCK_RANGE_NOTEXCLUDED 0x050103 + +/* Block range excluded, but not marked in exclude bitmap */ +#define PR_5_BLOCK_RANGE_EXCLUDED 0x050104 + +/* Exclude bitmap differences end */ +#define PR_5_EXCLUDE_BITMAP_END 0x050105 + +#endif /* * Post-Pass 5 errors */ diff --git a/e2fsck/rehash.c b/e2fsck/rehash.c index ceb8543..6db3f90 100644 --- a/e2fsck/rehash.c +++ b/e2fsck/rehash.c @@ -829,6 +829,15 @@ void e2fsck_rehash_directories(e2fsck_t ctx) int cur, max, all_dirs, dir_index, first = 1; init_resource_track(&rtrack, ctx->fs->io); +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT + + /* never rehash directories when scanning volume with active snapshot */ + if ((ctx->fs->super->s_feature_ro_compat & + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT) && + ctx->fs->super->s_snapshot_inum) + return; + +#endif all_dirs = ctx->options & E2F_OPT_COMPRESS_DIRS; if (!ctx->dirs_to_hash && !all_dirs) diff --git a/e2fsck/super.c b/e2fsck/super.c index accc2f1..1f1f416 100644 --- a/e2fsck/super.c +++ b/e2fsck/super.c @@ -229,6 +229,14 @@ static int release_orphan_inodes(e2fsck_t ctx) struct problem_context pctx; char *block_buf; +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT + /* never release orphans when scanning volume with active snapshot */ + if ((fs->super->s_feature_ro_compat & + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT) && + fs->super->s_snapshot_inum) + return 0; + +#endif if ((ino = fs->super->s_last_orphan) == 0) return 0; @@ -423,6 +431,234 @@ cleanup: } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE +/* + * Check the exclude inode to make sure it is sane. We check both for + * the case where exclude bitmap is not enabled (in which case the + * exclude inode should be cleared) as well as the case where exclude + * bitmap is enabled. + */ +void check_exclude_inode(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + struct ext2_inode inode; + struct problem_context pctx; + int i, flags = 0; + blk_t blk; + errcode_t retval; + + clear_problem_context(&pctx); + + /* Read the exclude inode */ + pctx.ino = EXT2_EXCLUDE_INO; + retval = ext2fs_read_inode(fs, EXT2_EXCLUDE_INO, &inode); + if (retval) { + if (fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_EXCLUDE_INODE) + ctx->flags |= E2F_FLAG_EXCLUDE_INODE; + return; + } + + /* + * If the exclude inode feature isn't set, check to make sure + * the exclude inode is cleared; then we're done. + */ + if (!(fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_EXCLUDE_INODE)) { + for (i=0; i < EXT2_N_BLOCKS; i++) { + if (inode.i_block[i]) + break; + } + if ((i < EXT2_N_BLOCKS) && + fix_problem(ctx, PR_0_CLEAR_EXCLUDE_INODE, &pctx)) { + memset(&inode, 0, sizeof(inode)); + e2fsck_write_inode(ctx, EXT2_EXCLUDE_INO, &inode, + "clear_exclude"); + } + return; + } + + /* + * The exclude inode feature is enabled; check to make sure the + * only block in use is the double indirect block + */ + blk = inode.i_block[EXT2_DIND_BLOCK]; + for (i=0; i < EXT2_N_BLOCKS; i++) { + if (i != EXT2_DIND_BLOCK && inode.i_block[i]) + break; + } + if ((i < EXT2_N_BLOCKS) || !blk || !inode.i_links_count || + !(inode.i_mode & LINUX_S_IFREG) || + (blk < fs->super->s_first_data_block || + blk >= fs->super->s_blocks_count)) { + if (fix_problem(ctx, PR_0_EXCLUDE_INODE_INVALID, &pctx)) { + memset(&inode, 0, sizeof(inode)); + e2fsck_write_inode(ctx, EXT2_EXCLUDE_INO, &inode, + "clear_exclude"); + ctx->flags |= E2F_FLAG_EXCLUDE_INODE; + } + return; + } + + if (!(ctx->options & E2F_OPT_READONLY)) + flags = EXCLUDE_ALLOC; + /* + * create exclude inode and/or allocate missing exclude bitmap blocks. + */ + clear_problem_context(&pctx); + pctx.errcode = ext2fs_create_exclude_inode(fs, flags); + if (pctx.errcode && + fix_problem(ctx, PR_1_EXCLUDE_INODE_CREATE, &pctx)) { + memset(&inode, 0, sizeof(inode)); + e2fsck_write_inode(ctx, EXT2_EXCLUDE_INO, &inode, + "clear_exclude"); + ctx->flags |= E2F_FLAG_EXCLUDE_INODE; + } +} + +#endif +#ifdef EXT2FS_SNAPSHOT_CHECK_LIST +/* + * Check that snapshot list contains valid snapshot files. + * Returns the number of valid snapshots on list. + * + * TODO: cleanup orphan snapshot files (not found on list) + */ +static int check_snapshot_list(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + struct ext2_super_block *sb = fs->super; + struct ext2_inode inode; + struct ext2_inode inode_prev; + ext2_ino_t ino = sb->s_snapshot_list; + ext2_ino_t ino_prev = 0; + errcode_t retval = 0; + struct problem_context pctx; + int i = 0; + + if (!ino && sb->s_snapshot_inum) { + /* reset snapshot list head to active snapshot */ + ino = sb->s_snapshot_list = sb->s_snapshot_inum; + ext2fs_mark_super_dirty(fs); + } + if (ino) + fputs(_("Checking snapshots: "), stderr); + + while (ino) { + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval || !(inode.i_flags & EXT4_SNAPFILE_FL) || + !LINUX_S_ISREG(inode.i_mode) || + inode.i_dtime) { + if (ino == sb->s_snapshot_list) { + /* reset list head */ + sb->s_snapshot_list = 0; + ext2fs_mark_super_dirty(fs); + ino = sb->s_snapshot_inum; + continue; + } + clear_problem_context(&pctx); + if (!fix_problem(ctx, PR_0_BAD_SNAPSHOT, &pctx)) + break; + + /* disconnect inode from snapshot list */ + if (ino == sb->s_snapshot_inum) { + /* reset active snapshot */ + sb->s_snapshot_inum = 0; + ext2fs_mark_super_dirty(fs); + } + if (ino_prev) { + /* terminate list at prev inode */ + inode_prev.i_next_snapshot = 0; + e2fsck_write_inode(ctx, ino_prev, &inode_prev, + "terminate snapshot list"); + } + break; + } + + fprintf(stderr, _("%u,"), inode.i_generation); + inode_prev = inode; + ino_prev = ino; + ino = inode.i_next_snapshot; + i++; + } + + if (ino_prev && !ino) + fputs(_("done\n"), stderr); + return i; +} + +#endif +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT +/* + * This function checks if the file system has snapshots + */ +void check_snapshots(e2fsck_t ctx) +{ + struct ext2_super_block *sb = ctx->fs->super; + struct problem_context pctx; + int cont; + + if (!(sb->s_feature_ro_compat & EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT)) + /* no snapshots */ + return; + +#ifdef EXT2FS_SNAPSHOT_FIX_SNAPSHOT + if ((ctx->options & E2F_OPT_FIX_SNAPSHOT) || + (sb->s_flags & EXT2_FLAGS_FIX_SNAPSHOT)) { + /* corrupted snapshot need to be fixed */ + clear_problem_context(&pctx); + if (fix_problem(ctx, PR_0_FIX_SNAPSHOT, &pctx)) { + if (sb->s_snapshot_list || sb->s_snapshot_inum || + (sb->s_flags & (EXT2_FLAGS_FIX_SNAPSHOT | + EXT2_FLAGS_IS_SNAPSHOT))) { + /* reset snapshot list head */ + sb->s_snapshot_list = sb->s_snapshot_inum = 0; + sb->s_flags &= ~(EXT2_FLAGS_FIX_SNAPSHOT | + EXT2_FLAGS_IS_SNAPSHOT); + ext2fs_mark_super_dirty(ctx->fs); + } + /* don't ask again after pass1 */ + ctx->options &= ~E2F_OPT_FIX_SNAPSHOT; + /* clear all snapshot inodes (in pass1) */ + ctx->flags |= E2F_FLAG_CLEAR_SNAPSHOTS; + if (sb->s_feature_compat & + EXT2_FEATURE_COMPAT_EXCLUDE_INODE) + /* and reset exclude bitmap */ + ctx->flags |= E2F_FLAG_EXCLUDE_INODE; + return; + } + } + +#endif +#ifdef EXT2FS_SNAPSHOT_CHECK_LIST + if (!check_snapshot_list(ctx)) + /* no valid snapshots on list */ + return; + +#endif + if (!sb->s_snapshot_inum) + /* no active snapshot */ + return; + + if ((ctx->options & E2F_OPT_PREEN) || + (ctx->options & E2F_OPT_NO)) + /* preen and readonly modes are snapshot friendly */ + return; + + printf(_("%s has snapshots. "), ctx->filesystem_name); + if (!ctx->interactive) + fatal_error(ctx, _("Cannot continue, aborting.\n\n")); + printf(_("\n\n\007\007\007\007WARNING!!! " + "Running e2fsck on filesystem with snapshots may\n" + "damage the snapshots.\007\007\007\n\n")); + cont = ask_yn(_("Do you really want to continue"), -1); + if (!cont) { + printf (_("check aborted.\n")); + exit (0); + } +} + +#endif /* * This function checks the dirhash signed/unsigned hint if necessary. */ @@ -450,6 +686,72 @@ static void e2fsck_fix_dirhash_hint(e2fsck_t ctx) } } +#ifdef EXT2FS_SNAPSHOT_MESSAGE_BUFFER +/* + * This function prints the message buffer at the end of super block. + */ +static void e2fsck_print_message_buffer(e2fsck_t ctx) +{ + char *buf; + int n, len = ctx->fs->blocksize - MSGBUF_OFFSET; + unsigned offset = 0; + int retval; + + if (len < MSGBUF_OFFSET) + /* 1K or 2K fs->blocksize */ + return; + + buf = (char *) e2fsck_allocate_memory(ctx, len, "message buffer"); + + /* keep it simple - write in MSGBUF_OFFSET blocksize */ + io_channel_set_blksize(ctx->fs->io, MSGBUF_OFFSET); + n = len / MSGBUF_OFFSET; + /* read message buffer from super block + 2K */ + retval = io_channel_read_blk(ctx->fs->io, 1, n, buf); + if (retval || !*buf) + goto out; + + /* print messages in buffer */ + puts("Error messages recorded in message buffer:"); + while (offset < len && buf[offset]) { + puts(buf+offset); + offset += MSGBUF_RECLEN; + } + puts("End of message buffer."); +out: + io_channel_set_blksize(ctx->fs->io, ctx->fs->blocksize); + ext2fs_free_mem(&buf); +} + +/* + * This function clears the message buffer at the end of super block. + * It is called before clearing the EXT2_ERROR_FS flag from super block. + */ +void e2fsck_clear_message_buffer(e2fsck_t ctx) +{ + char *buf; + int n, len = ctx->fs->blocksize - MSGBUF_OFFSET; + unsigned offset = 0; + int retval; + + if (len < MSGBUF_OFFSET) + /* 1K or 2K fs->blocksize */ + return; + + buf = (char *) e2fsck_allocate_memory(ctx, len, "message buffer"); + + /* keep it simple - write in MSGBUF_OFFSET blocksize */ + io_channel_set_blksize(ctx->fs->io, MSGBUF_OFFSET); + n = len / MSGBUF_OFFSET; + + /* clear message buffer at super block + 2K */ + memset(buf, 0, len); + io_channel_write_blk(ctx->fs->io, 1, n, buf); + io_channel_set_blksize(ctx->fs->io, ctx->fs->blocksize); + ext2fs_free_mem(&buf); +} + +#endif void check_super_block(e2fsck_t ctx) { @@ -869,6 +1171,13 @@ void check_super_block(e2fsck_t ctx) */ e2fsck_fix_dirhash_hint(ctx); +#ifdef EXT2FS_SNAPSHOT_MESSAGE_BUFFER + /* + * Print message buffer if necessary + */ + e2fsck_print_message_buffer(ctx); + +#endif return; } diff --git a/e2fsck/unix.c b/e2fsck/unix.c index 624e11b..9b9ffd9 100644 --- a/e2fsck/unix.c +++ b/e2fsck/unix.c @@ -73,7 +73,11 @@ int journal_enable_debug = -1; static void usage(e2fsck_t ctx) { fprintf(stderr, +#ifdef EXT2FS_SNAPSHOT_FIX_SNAPSHOT + _("Usage: %s [-panyrcdfvtxDFV] [-b superblock] [-B blocksize]\n" +#else _("Usage: %s [-panyrcdfvtDFV] [-b superblock] [-B blocksize]\n" +#endif "\t\t[-I inode_buffer_blocks] [-P process_inode_size]\n" "\t\t[-l|-L bad_blocks_file] [-C fd] [-j external_journal]\n" "\t\t[-E extended-options] device\n"), @@ -92,6 +96,9 @@ static void usage(e2fsck_t ctx) " -j external_journal Set location of the external journal\n" " -l bad_blocks_file Add to badblocks list\n" " -L bad_blocks_file Set badblocks list\n" +#ifdef EXT2FS_SNAPSHOT_FIX_SNAPSHOT + " -x Fix or discard snapshots\n" +#endif )); exit(FSCK_USAGE); @@ -676,7 +683,11 @@ static errcode_t PRS(int argc, char *argv[], e2fsck_t *ret_ctx) ctx->program_name = *argv; else ctx->program_name = "e2fsck"; +#ifdef EXT2FS_SNAPSHOT_FIX_SNAPSHOT + while ((c = getopt (argc, argv, "panyrcC:B:dE:fvtFVM:b:I:j:P:l:L:N:SsDkx")) != EOF) +#else while ((c = getopt (argc, argv, "panyrcC:B:dE:fvtFVM:b:I:j:P:l:L:N:SsDk")) != EOF) +#endif switch (c) { case 'C': ctx->progress = e2fsck_update_progress; @@ -806,6 +817,11 @@ static errcode_t PRS(int argc, char *argv[], e2fsck_t *ret_ctx) case 'k': keep_bad_blocks++; break; +#ifdef EXT2FS_SNAPSHOT_FIX_SNAPSHOT + case 'x': + ctx->options |= E2F_OPT_FIX_SNAPSHOT; + break; +#endif default: usage(ctx); } @@ -1332,6 +1348,12 @@ print_unsupp_features: fatal_error(ctx, 0); check_if_skip(ctx); check_resize_inode(ctx); +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT + check_snapshots(ctx); +#endif +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + check_exclude_inode(ctx); +#endif if (bad_blocks_file) read_bad_blocks_file(ctx, bad_blocks_file, replace_bad_blocks); else if (cflag) @@ -1465,6 +1487,9 @@ no_journal: sb->s_lastcheck = ctx->now; memset(((char *) sb) + EXT4_S_ERR_START, 0, EXT4_S_ERR_LEN); +#ifdef EXT2FS_SNAPSHOT_MESSAGE_BUFFER + e2fsck_clear_message_buffer(ctx); +#endif ext2fs_mark_super_dirty(fs); } } diff --git a/install_next3.sh b/install_next3.sh new file mode 100755 index 0000000..4955488 --- /dev/null +++ b/install_next3.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# Install next3 patched e2fsprogs + +# build e2fsprogs from e2fsprogs-snapshots git +#./configure +make +# install progs with .next3 suffix +install -T e2fsck/e2fsck /sbin/fsck.next3 +install -T misc/mke2fs /sbin/mkfs.next3 +install -T misc/tune2fs /sbin/tunefs.next3 +install -T misc/dumpe2fs /sbin/dumpfs.next3 +install -T misc/lsattr /sbin/lsattr.next3 +install -T misc/chattr /sbin/chattr.next3 +install -T resize/resize2fs /sbin/resize.next3 +install -T next3 /sbin/next3 + diff --git a/install_next4.sh b/install_next4.sh new file mode 100755 index 0000000..bbdae85 --- /dev/null +++ b/install_next4.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# Install next4 patched e2fsprogs + +# build e2fsprogs from e2fsprogs-snapshots git +#./configure +make +# install progs with .next4 suffix +install -T e2fsck/e2fsck /sbin/fsck.next4 +install -T misc/mke2fs /sbin/mkfs.next4 +install -T misc/tune2fs /sbin/tunefs.next4 +install -T misc/dumpe2fs /sbin/dumpfs.next4 +install -T misc/lsattr /sbin/lsattr.next4 +install -T misc/chattr /sbin/chattr.next4 +install -T misc/lsattr /sbin/lssnap +install -T misc/chattr /sbin/chsnap +install -T resize/resize2fs /sbin/resize.next4 +# next3 script is generic and chooses the FS type by it's own name +install -T next3 /sbin/next4 diff --git a/lib/e2p/e2p.h b/lib/e2p/e2p.h index 7a90c37..f3c9ab0 100644 --- a/lib/e2p/e2p.h +++ b/lib/e2p/e2p.h @@ -26,8 +26,18 @@ /* `options' for print_flags() */ #define PFOPT_LONG 1 /* Must be 1 for compatibility with `int long_format'. */ +#ifdef EXT2FS_SNAPSHOT_CTL +#define PFOPT_SNAPSHOT 2 /* list/control snapshot flags */ +#define PFOPT_SNAPSHOT_X 4 /* for backward compatibility with next3 */ +#endif + + +#ifdef EXT2FS_SNAPSHOT_CTL +int fgetsnapflags(const char * name, unsigned long * flags); +int fsetsnapflags(const char * name, unsigned long flags); +#endif int fgetflags (const char * name, unsigned long * flags); int fgetversion (const char * name, unsigned long * version); int fsetflags (const char * name, unsigned long flags); diff --git a/lib/e2p/feature.c b/lib/e2p/feature.c index c7f8a35..d1ef001 100644 --- a/lib/e2p/feature.c +++ b/lib/e2p/feature.c @@ -40,6 +40,14 @@ static struct feature feature_list[] = { "resize_inode" }, { E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_LAZY_BG, "lazy_bg" }, +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + { E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_EXCLUDE_INODE, + "exclude_inode" }, +#endif +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + { E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_EXCLUDE_BITMAP, + "exclude_bitmap" }, +#endif { E2P_FEATURE_RO_INCOMPAT, EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER, "sparse_super" }, @@ -55,6 +63,12 @@ static struct feature feature_list[] = { "dir_nlink" }, { E2P_FEATURE_RO_INCOMPAT, EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE, "extra_isize" }, +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT + { E2P_FEATURE_RO_INCOMPAT, EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT, + "snapshots" }, + { E2P_FEATURE_RO_INCOMPAT, EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT, + "has_snapshot" }, +#endif { E2P_FEATURE_INCOMPAT, EXT2_FEATURE_INCOMPAT_COMPRESSION, "compression" }, diff --git a/lib/e2p/fgetflags.c b/lib/e2p/fgetflags.c index d66f8e1..33037cf 100644 --- a/lib/e2p/fgetflags.c +++ b/lib/e2p/fgetflags.c @@ -40,7 +40,7 @@ #define OPEN_FLAGS (O_RDONLY|O_NONBLOCK) #endif -int fgetflags (const char * name, unsigned long * flags) +static int fgetflags_ioctl(const char * name, unsigned long * flags, int ioc) { struct stat buf; #if HAVE_STAT_FLAGS && !(APPLE_DARWIN && HAVE_EXT2_IOCTLS) @@ -75,7 +75,7 @@ int fgetflags (const char * name, unsigned long * flags) fd = open (name, OPEN_FLAGS); if (fd == -1) return -1; - r = ioctl (fd, EXT2_IOC_GETFLAGS, &f); + r = ioctl (fd, ioc, &f); if (r == -1) save_errno = errno; *flags = f; @@ -84,10 +84,10 @@ int fgetflags (const char * name, unsigned long * flags) errno = save_errno; return r; #else - f = -1; - save_errno = syscall(SYS_fsctl, name, EXT2_IOC_GETFLAGS, &f, 0); - *flags = f; - return (save_errno); + f = -1; + save_errno = syscall(SYS_fsctl, name, ioc, &f, 0); + *flags = f; + return (save_errno); #endif #endif /* HAVE_EXT2_IOCTLS */ #endif @@ -95,3 +95,16 @@ notsupp: errno = EOPNOTSUPP; return -1; } + +int fgetflags(const char * name, unsigned long * flags) +{ + return fgetflags_ioctl(name, flags, EXT2_IOC_GETFLAGS); +} + +#ifdef EXT2FS_SNAPSHOT_CTL +int fgetsnapflags(const char * name, unsigned long * flags) +{ + return fgetflags_ioctl(name, flags, EXT2_IOC_GETSNAPFLAGS); +} + +#endif diff --git a/lib/e2p/fsetflags.c b/lib/e2p/fsetflags.c index 30437a2..e67f2e1 100644 --- a/lib/e2p/fsetflags.c +++ b/lib/e2p/fsetflags.c @@ -49,7 +49,7 @@ #define OPEN_FLAGS (O_RDONLY|O_NONBLOCK) #endif -int fsetflags (const char * name, unsigned long flags) +int fsetflags_ioctl(const char * name, unsigned long flags, int ioc) { struct stat buf; #if HAVE_CHFLAGS && !(APPLE_DARWIN && HAVE_EXT2_IOCTLS) @@ -82,15 +82,15 @@ int fsetflags (const char * name, unsigned long flags) if (fd == -1) return -1; f = (int) flags; - r = ioctl (fd, EXT2_IOC_SETFLAGS, &f); + r = ioctl (fd, ioc, &f); if (r == -1) save_errno = errno; close (fd); if (save_errno) errno = save_errno; #else - f = (int) flags; - return syscall(SYS_fsctl, name, EXT2_IOC_SETFLAGS, &f, 0); + f = (int) flags; + return syscall(SYS_fsctl, name, ioc, &f, 0); #endif return r; #endif /* HAVE_EXT2_IOCTLS */ @@ -99,3 +99,16 @@ notsupp: errno = EOPNOTSUPP; return -1; } + +int fsetflags(const char * name, unsigned long flags) +{ + return fsetflags_ioctl(name, flags, EXT2_IOC_SETFLAGS); +} + +#ifdef EXT2FS_SNAPSHOT_CTL +int fsetsnapflags(const char * name, unsigned long flags) +{ + return fsetflags_ioctl(name, flags, EXT2_IOC_SETSNAPFLAGS); +} + +#endif diff --git a/lib/e2p/ls.c b/lib/e2p/ls.c index 5ef8098..3ebc07f 100644 --- a/lib/e2p/ls.c +++ b/lib/e2p/ls.c @@ -160,6 +160,20 @@ static void print_super_flags(struct ext2_super_block * s, FILE *f) fputs("test_filesystem ", f); flags_found++; } +#ifdef EXT2FS_SNAPSHOT_ON_DISK + if (s->s_flags & EXT2_FLAGS_IS_SNAPSHOT) { + fputs("is_snapshot ", f); + flags_found++; + } + if (s->s_flags & EXT2_FLAGS_FIX_SNAPSHOT) { + fputs("fix_snapshot ", f); + flags_found++; + } + if (s->s_flags & EXT2_FLAGS_FIX_EXCLUDE) { + fputs("fix_exclude ", f); + flags_found++; + } +#endif if (flags_found) fputs("\n", f); else @@ -315,6 +329,17 @@ void list_super2(struct ext2_super_block * sb, FILE *f) if (sb->s_last_orphan) fprintf(f, "First orphan inode: %u\n", sb->s_last_orphan); + if (sb->s_snapshot_inum) { + fprintf(f, "Snapshot inode: %u\n", + sb->s_snapshot_inum); + fprintf(f, "Snapshot ID: %u\n", + sb->s_snapshot_id); + fprintf(f, "Snapshot reserved blocks: %llu\n", + sb->s_snapshot_r_blocks_count); + } + if (sb->s_snapshot_list) + fprintf(f, "Snapshot list first inode: %u\n", + sb->s_snapshot_list); if ((sb->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) || sb->s_def_hash_version) fprintf(f, "Default directory hash: %s\n", diff --git a/lib/e2p/pf.c b/lib/e2p/pf.c index f34a5cc..747edde 100644 --- a/lib/e2p/pf.c +++ b/lib/e2p/pf.c @@ -48,16 +48,59 @@ static struct flags_name flags_array[] = { { EXT2_TOPDIR_FL, "T", "Top_of_Directory_Hierarchies" }, { EXT4_EXTENTS_FL, "e", "Extents" }, { EXT4_HUGE_FILE_FL, "h", "Huge_file" }, +#ifdef EXT2FS_SNAPSHOT_CTL + { EXT4_SNAPFILE_FL, "x", "Snapshot_File" }, +#endif + { 0, NULL, NULL } +}; + +#ifdef EXT2FS_SNAPSHOT_CTL +/* Snapshot dynamic state flags */ +static struct flags_name snapshot_flags_array[] = { + { 1UL<<EXT4_SNAPSHOT_LIST, "S", "on_liSt" }, + { 1UL<<EXT4_SNAPSHOT_ENABLED, "n", "eNabled" }, + { 1UL<<EXT4_SNAPSHOT_ACTIVE, "a", "Active" }, + { 1UL<<EXT4_SNAPSHOT_INUSE, "p", "inuse_by_Previous" }, + { 1UL<<EXT4_SNAPSHOT_DELETED, "s", "Deleted" }, + { 1UL<<EXT4_SNAPSHOT_SHRUNK, "h", "sHrunk" }, + { 1UL<<EXT4_SNAPSHOT_OPEN, "o", "mOunted" }, + { 1UL<<EXT4_SNAPSHOT_TAGGED, "t", "Tagged" }, { 0, NULL, NULL } }; +#ifdef EXT2FS_SNAPSHOT_CTL_OLD +/* Old snapshot flags for backward compatibility with next3 */ +static struct flags_name snapshot_X_flags_array[] = { + { NEXT3_SNAPFILE_LIST_FL, "S", "on_liSt" }, + { NEXT3_SNAPFILE_ENABLED_FL, "n", "eNabled" }, + { NEXT3_SNAPFILE_ACTIVE_FL, "a", "Active" }, + { NEXT3_SNAPFILE_INUSE_FL, "p", "inuse_by_Previous" }, + { NEXT3_SNAPFILE_DELETED_FL, "s", "Deleted" }, + { NEXT3_SNAPFILE_SHRUNK_FL, "h", "sHrunk" }, + { NEXT3_SNAPFILE_OPEN_FL, "o", "mOunted" }, + { NEXT3_SNAPFILE_TAGGED_FL, "t", "Tagged" }, + { 0, NULL, NULL } +}; + +#endif +#endif void print_flags (FILE * f, unsigned long flags, unsigned options) { +#ifdef EXT2FS_SNAPSHOT_CTL + struct flags_name *array = ((options & PFOPT_SNAPSHOT_X) ? + snapshot_X_flags_array : + ((options & PFOPT_SNAPSHOT) ? + snapshot_flags_array : flags_array)); +#endif int long_opt = (options & PFOPT_LONG); struct flags_name *fp; int first = 1; +#ifdef EXT2FS_SNAPSHOT_CTL + for (fp = array; fp->flag != 0; fp++) { +#else for (fp = flags_array; fp->flag != 0; fp++) { +#endif if (flags & fp->flag) { if (long_opt) { if (first) diff --git a/lib/ext2fs/block.c b/lib/ext2fs/block.c index 4054a07..89a1e2b 100644 --- a/lib/ext2fs/block.c +++ b/lib/ext2fs/block.c @@ -310,6 +310,7 @@ errcode_t ext2fs_block_iterate2(ext2_filsys fs, errcode_t retval; struct block_context ctx; int limit; + blk_t blk64; EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); @@ -459,9 +460,19 @@ errcode_t ext2fs_block_iterate2(ext2_filsys fs, * Iterate over normal data blocks */ for (i = 0; i < EXT2_NDIR_BLOCKS ; i++, ctx.bcount++) { +#ifdef EXT2FS_SNAPSHOT_HUGE_SNAPSHOT + if ((inode.i_flags & EXT4_SNAPFILE_FL) && + LINUX_S_ISREG(inode.i_mode) && + i < NEXT3_EXTRA_TIND_BLOCKS) + /* snapshot file extra triple indirect blocks */ + continue; + +#endif if (inode.i_block[i] || (flags & BLOCK_FLAG_APPEND)) { - ret |= (*ctx.func)(fs, &inode.i_block[i], - ctx.bcount, 0, i, priv_data); + blk64 = inode.i_block[i]; + ret |= (*ctx.func)(fs, &blk64, ctx.bcount, 0, i, + priv_data); + inode.i_block[i] = (blk_t) blk64; if (ret & BLOCK_ABORT) goto abort_exit; } @@ -487,6 +498,19 @@ errcode_t ext2fs_block_iterate2(ext2_filsys fs, if (ret & BLOCK_ABORT) goto abort_exit; } +#ifdef EXT2FS_SNAPSHOT_HUGE_SNAPSHOT + if ((inode.i_flags & EXT4_SNAPFILE_FL) && LINUX_S_ISREG(inode.i_mode)) { + /* iterate snapshot file extra triple indirect blocks */ + for (i = 0; i < NEXT3_EXTRA_TIND_BLOCKS; i++) { + if (!inode.i_block[i] && !(flags & BLOCK_FLAG_APPEND)) + continue; + ret |= block_iterate_tind(&inode.i_block[i], + 0, EXT2_N_BLOCKS+i, &ctx); + if (ret & BLOCK_ABORT) + goto abort_exit; + } + } +#endif abort_exit: if (ret & BLOCK_CHANGED) { diff --git a/lib/ext2fs/bmap.c b/lib/ext2fs/bmap.c index fbcb375..1385770 100644 --- a/lib/ext2fs/bmap.c +++ b/lib/ext2fs/bmap.c @@ -136,6 +136,10 @@ errcode_t ext2fs_bmap2(ext2_filsys fs, ext2_ino_t ino, struct ext2_inode *inode, struct ext2_inode inode_buf; ext2_extent_handle_t handle = 0; blk_t addr_per_block; +#ifdef EXT2FS_SNAPSHOT_HUGE_SNAPSHOT + blk64_t addr_per_tind_block; + int tind; +#endif blk_t b, blk32; char *buf = 0; errcode_t retval = 0; @@ -286,7 +290,24 @@ errcode_t ext2fs_bmap2(ext2_filsys fs, ext2_ino_t ino, struct ext2_inode *inode, /* Triply indirect block */ block -= addr_per_block * addr_per_block; +#ifdef EXT2FS_SNAPSHOT_HUGE_SNAPSHOT + tind = EXT2_TIND_BLOCK; + addr_per_tind_block = addr_per_block * addr_per_block * addr_per_block; + if (block > addr_per_tind_block) { + /* use direct blocks as extra triple indirect blocks? */ + tind = block / addr_per_tind_block; + block -= tind * addr_per_tind_block; + if (!(inode->i_flags & EXT4_SNAPFILE_FL) || + !LINUX_S_ISREG(inode->i_mode) || + tind >= NEXT3_EXTRA_TIND_BLOCKS) { + retval = EXT2_ET_BAD_BLOCK_NUM; + goto done; + } + } + b = inode_bmap(inode, tind); +#else b = inode_bmap(inode, EXT2_TIND_BLOCK); +#endif if (!b) { if (!(bmap_flags & BMAP_ALLOC)) { if (bmap_flags & BMAP_SET) @@ -298,7 +319,11 @@ errcode_t ext2fs_bmap2(ext2_filsys fs, ext2_ino_t ino, struct ext2_inode *inode, retval = ext2fs_alloc_block(fs, b, block_buf, &b); if (retval) goto done; +#ifdef EXT2FS_SNAPSHOT_HUGE_SNAPSHOT + inode_bmap(inode, tind) = b; +#else inode_bmap(inode, EXT2_TIND_BLOCK) = b; +#endif blocks_alloc++; } retval = block_tind_bmap(fs, bmap_flags, b, block_buf, diff --git a/lib/ext2fs/dupfs.c b/lib/ext2fs/dupfs.c index a9e2a97..984fb5e 100644 --- a/lib/ext2fs/dupfs.c +++ b/lib/ext2fs/dupfs.c @@ -76,6 +76,13 @@ errcode_t ext2fs_dup_handle(ext2_filsys src, ext2_filsys *dest) if (retval) goto errout; } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (src->exclude_map) { + retval = ext2fs_copy_bitmap(src->exclude_map, &fs->exclude_map); + if (retval) + goto errout; + } +#endif if (src->badblocks) { retval = ext2fs_badblocks_copy(src->badblocks, &fs->badblocks); if (retval) diff --git a/lib/ext2fs/ext2_fs.h b/lib/ext2fs/ext2_fs.h index afd33a9..d7a4187 100644 --- a/lib/ext2fs/ext2_fs.h +++ b/lib/ext2fs/ext2_fs.h @@ -26,6 +26,18 @@ * Define EXT2FS_DEBUG to produce debug messages */ #undef EXT2FS_DEBUG +#define EXT2FS_SNAPSHOT_ON_DISK +#define EXT2FS_SNAPSHOT_BIG_JOURNAL +#define EXT2FS_SNAPSHOT_EXCLUDE_INODE +#define EXT2FS_SNAPSHOT_EXCLUDE_BITMAP +#define EXT2FS_SNAPSHOT_CTL +#define EXT2FS_SNAPSHOT_HAS_SNAPSHOT +#define EXT2FS_SNAPSHOT_CHECK_LIST +#define EXT2FS_SNAPSHOT_FIX_SNAPSHOT +#define EXT2FS_SNAPSHOT_HUGE_SNAPSHOT +#define EXT2FS_SNAPSHOT_MESSAGE_BUFFER +#define EXT2FS_SNAPSHOT_CTL_OLD +#define EXT2FS_SNAPSHOT_CLEANUP /* * Define EXT2_PREALLOCATE to preallocate data blocks for expanding files @@ -145,11 +157,19 @@ struct ext2_group_desc __u16 bg_free_inodes_count; /* Free inodes count */ __u16 bg_used_dirs_count; /* Directories count */ __u16 bg_flags; +#ifdef EXT2FS_SNAPSHOT_ON_DISK + __u32 bg_exclude_bitmap; /* Exclude bitmap block */ + __u32 bg_reserved[1]; +#else __u32 bg_reserved[2]; +#endif __u16 bg_itable_unused; /* Unused inodes count */ __u16 bg_checksum; /* crc16(s_uuid+grouo_num+group_desc)*/ }; +/* + * Structure of a blocks group descriptor + */ struct ext4_group_desc { __u32 bg_block_bitmap; /* Blocks bitmap block */ @@ -159,7 +179,12 @@ struct ext4_group_desc __u16 bg_free_inodes_count; /* Free inodes count */ __u16 bg_used_dirs_count; /* Directories count */ __u16 bg_flags; +#ifdef EXT2FS_SNAPSHOT_ON_DISK + __u32 bg_exclude_bitmap; /* Exclude bitmap block */ + __u32 bg_reserved[1]; +#else __u32 bg_reserved[2]; +#endif __u16 bg_itable_unused; /* Unused inodes count */ __u16 bg_checksum; /* crc16(s_uuid+grouo_num+group_desc)*/ __u32 bg_block_bitmap_hi; /* Blocks bitmap block MSB */ @@ -169,12 +194,20 @@ struct ext4_group_desc __u16 bg_free_inodes_count_hi;/* Free inodes count MSB */ __u16 bg_used_dirs_count_hi; /* Directories count MSB */ __u16 bg_pad; +#ifdef EXT2FS_SNAPSHOT_ON_DISK + __u32 bg_exclude_bitmap_hi; /* Exclude bitmap block MSB */ + __u32 bg_reserved2[2]; +#else __u32 bg_reserved2[3]; +#endif }; #define EXT2_BG_INODE_UNINIT 0x0001 /* Inode table/bitmap not initialized */ #define EXT2_BG_BLOCK_UNINIT 0x0002 /* Block bitmap not initialized */ #define EXT2_BG_INODE_ZEROED 0x0004 /* On-disk itable initialized to zero */ +#ifdef EXT2FS_SNAPSHOT_ON_DISK +#define EXT2_BG_EXCLUDE_UNINIT 0x0008 /* Exclude bitmap not initialized */ +#endif /* * Data structures used by the directory indexing feature @@ -247,6 +280,19 @@ struct ext2_dx_countlimit { #define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1) #define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1) #define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1) +#ifdef EXT2FS_SNAPSHOT_HUGE_SNAPSHOT +/* + * Snapshot files have different indirection mapping that can map up to 2^32 + * logical blocks, so they can cover the mapped filesystem block address space. + * Next3 must use either 4K or 8K blocks (depending on PAGE_SIZE). + * With 8K blocks, 1 triple indirect block maps 2^33 logical blocks. + * With 4K blocks (the system default), each triple indirect block maps 2^30 + * logical blocks, so 4 triple indirect blocks map 2^32 logical blocks. + * Snapshot files in small filesystems (<= 4G), use only 1 double indirect + * block to map the entire filesystem. + */ +#define NEXT3_EXTRA_TIND_BLOCKS 3 +#endif /* * Inode flags @@ -322,6 +368,40 @@ struct ext4_new_group_input { #define EXT2_IOC_GROUP_EXTEND _IOW('f', 7, unsigned long) #define EXT2_IOC_GROUP_ADD _IOW('f', 8,struct ext2_new_group_input) #define EXT4_IOC_GROUP_ADD _IOW('f', 8,struct ext4_new_group_input) +#ifdef EXT2FS_SNAPSHOT_CTL +#define EXT2_IOC_GETSNAPFLAGS _IOR('f', 13, long) +#define EXT2_IOC_SETSNAPFLAGS _IOW('f', 14, long) + +/* + * Snapshot status/control flags for lssnap/chsnap. + * The flags below must appear in the same order as they do in ext4 dynamic + * inode state flags enum. GETSNAPFLAGS ioctl shifts them down to lower 8 bits. + */ +enum { + EXT4_SNAPSHOT_LIST, /* snapshot is on list (S) */ + EXT4_SNAPSHOT_ENABLED, /* snapshot is enabled (n) */ + EXT4_SNAPSHOT_ACTIVE, /* snapshot is active (a) */ + EXT4_SNAPSHOT_INUSE, /* snapshot is in-use (p) */ + EXT4_SNAPSHOT_DELETED, /* snapshot is deleted (s) */ + EXT4_SNAPSHOT_SHRUNK, /* snapshot was shrunk (h) */ + EXT4_SNAPSHOT_OPEN, /* snapshot is mounted (o) */ + EXT4_SNAPSHOT_TAGGED, /* snapshot is tagged (t) */ +}; + + +#ifdef EXT2FS_SNAPSHOT_CTL_OLD +/* Old snapshot flags for backward compatibility with next3 */ +#define NEXT3_SNAPFILE_LIST_FL 0x00000100 /* snapshot is on list */ +#define NEXT3_SNAPFILE_ENABLED_FL 0x00000200 /* snapshot is enabled */ +#define NEXT3_SNAPFILE_ACTIVE_FL 0x00000400 /* snapshot is active */ +#define NEXT3_SNAPFILE_INUSE_FL 0x00000800 /* snapshot is in-use */ +#define NEXT3_SNAPFILE_DELETED_FL 0x04000000 /* snapshot is deleted */ +#define NEXT3_SNAPFILE_SHRUNK_FL 0x08000000 /* snapshot is shrunk */ +#define NEXT3_SNAPFILE_OPEN_FL 0x10000000 /* snapshot is mounted */ +#define NEXT3_SNAPFILE_TAGGED_FL 0x20000000 /* snapshot is tagged */ + +#endif +#endif /* * Structure of an inode on the disk @@ -428,7 +508,11 @@ struct ext2_inode_large { #define i_size_high i_dir_acl #if defined(__KERNEL__) || defined(__linux__) +#ifdef EXT2FS_SNAPSHOT_ON_DISK +#define i_next_snapshot osd1.linux1.l_i_version +#else #define i_reserved1 osd1.linux1.l_i_reserved1 +#endif #define i_frag osd2.linux2.l_i_frag #define i_fsize osd2.linux2.l_i_fsize #define i_uid_low i_uid @@ -644,6 +728,19 @@ struct ext2_super_block { */ #define EXT3_JNL_BACKUP_BLOCKS 1 +#ifdef EXT2FS_SNAPSHOT_BIG_JOURNAL +/* + * A Next3 'big' journal needs to accomodate extra snapshot COW credits. + * Default journal size accomodates maximum possible COW credits. + * Minimum required journal size accomodates the avarage COW credits. + */ +#define EXT3_DEF_JOURNAL_BLOCKS 32768 +#define NEXT3_AVG_COW_CREDITS 16 +#define NEXT3_MAX_COW_CREDITS 24 +#define NEXT3_MIN_JOURNAL_BLOCKS (EXT3_DEF_JOURNAL_BLOCKS*NEXT3_AVG_COW_CREDITS) +#define NEXT3_DEF_JOURNAL_BLOCKS (EXT3_DEF_JOURNAL_BLOCKS*NEXT3_MAX_COW_CREDITS) + +#endif /* * Feature set definitions */ @@ -663,6 +760,9 @@ struct ext2_super_block { #define EXT2_FEATURE_COMPAT_DIR_INDEX 0x0020 #define EXT2_FEATURE_COMPAT_LAZY_BG 0x0040 #define EXT2_FEATURE_COMPAT_EXCLUDE_INODE 0x0080 +#ifdef EXT2FS_SNAPSHOT_ON_DISK +#define EXT2_FEATURE_COMPAT_EXCLUDE_BITMAP 0x0100 +#endif #define EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001 #define EXT2_FEATURE_RO_COMPAT_LARGE_FILE 0x0002 @@ -688,10 +788,18 @@ struct ext2_super_block { #define EXT2_FEATURE_COMPAT_SUPP 0 #define EXT2_FEATURE_INCOMPAT_SUPP (EXT2_FEATURE_INCOMPAT_FILETYPE) +#ifdef EXT2FS_SNAPSHOT_ON_DISK #define EXT2_FEATURE_RO_COMPAT_SUPP (EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER| \ EXT2_FEATURE_RO_COMPAT_LARGE_FILE| \ + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT|\ EXT4_FEATURE_RO_COMPAT_DIR_NLINK| \ EXT2_FEATURE_RO_COMPAT_BTREE_DIR) +#else +#define EXT2_FEATURE_RO_COMPAT_SUPP (EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER| \ + EXT2_FEATURE_RO_COMPAT_LARGE_FILE| \ + EXT4_FEATURE_RO_COMPAT_DIR_NLINK| \ + EXT2_FEATURE_RO_COMPAT_BTREE_DIR) +#endif /* * Default values for user and/or group using reserved blocks diff --git a/lib/ext2fs/ext2fs.h b/lib/ext2fs/ext2fs.h index cf76562..f3a1119 100644 --- a/lib/ext2fs/ext2fs.h +++ b/lib/ext2fs/ext2fs.h @@ -38,6 +38,12 @@ extern "C" { */ #define SUPERBLOCK_OFFSET 1024 #define SUPERBLOCK_SIZE 1024 +/* + * When blocksize > 2K, the space after the superblock is used as + * a buffer to record error messages (in 256 bytes records). + */ +#define MSGBUF_OFFSET (SUPERBLOCK_OFFSET+SUPERBLOCK_SIZE) +#define MSGBUF_RECLEN 256 /* * The last ext2fs revision level that this version of the library is @@ -173,7 +179,12 @@ typedef struct ext2_file *ext2_file_t; #define EXT2_FLAG_EXCLUSIVE 0x4000 #define EXT2_FLAG_SOFTSUPP_FEATURES 0x8000 #define EXT2_FLAG_NOFREE_ON_ERROR 0x10000 +#define EXT2_FLAG_64BITS 0x20000 +#define EXT2_FLAG_PRINT_PROGRESS 0x40000 #define EXT2_FLAG_DIRECT_IO 0x80000 +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP +#define EXT2_FLAG_EXCLUDE_DIRTY 0x100000 +#endif /* * Special flag in the ext2 inode i_flag field that means that this is @@ -188,6 +199,8 @@ typedef struct ext2_file *ext2_file_t; */ #define EXT2_MKJOURNAL_V1_SUPER 0x0000001 +#define opaque_ext2_group_desc ext2_group_desc + struct struct_ext2_filsys { errcode_t magic; io_channel io; @@ -198,10 +211,14 @@ struct struct_ext2_filsys { int fragsize; dgrp_t group_desc_count; unsigned long desc_blocks; - struct ext2_group_desc * group_desc; + struct opaque_ext2_group_desc * group_desc; int inode_blocks_per_group; ext2fs_inode_bitmap inode_map; ext2fs_block_bitmap block_map; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + ext2fs_block_bitmap exclude_map; +#endif + /* XXX FIXME-64: not 64-bit safe, but not used? */ errcode_t (*get_blocks)(ext2_filsys fs, ext2_ino_t ino, blk_t *blocks); errcode_t (*check_directory)(ext2_filsys fs, ext2_ino_t ino); errcode_t (*write_bitmaps)(ext2_filsys fs); @@ -503,12 +520,23 @@ typedef struct ext2_icount *ext2_icount_t; /* * Features supported by this version of the library */ +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP #define EXT2_LIB_FEATURE_COMPAT_SUPP (EXT2_FEATURE_COMPAT_DIR_PREALLOC|\ EXT2_FEATURE_COMPAT_IMAGIC_INODES|\ EXT3_FEATURE_COMPAT_HAS_JOURNAL|\ + EXT2_FEATURE_COMPAT_EXCLUDE_INODE|\ + EXT2_FEATURE_COMPAT_EXCLUDE_BITMAP|\ EXT2_FEATURE_COMPAT_RESIZE_INODE|\ EXT2_FEATURE_COMPAT_DIR_INDEX|\ EXT2_FEATURE_COMPAT_EXT_ATTR) +#else +#define EXT2_LIB_FEATURE_COMPAT_SUPP (EXT2_FEATURE_COMPAT_DIR_PREALLOC|\ + EXT2_FEATURE_COMPAT_IMAGIC_INODES|\ + EXT3_FEATURE_COMPAT_HAS_JOURNAL|\ + EXT2_FEATURE_COMPAT_RESIZE_INODE|\ + EXT2_FEATURE_COMPAT_DIR_INDEX|\ + EXT2_FEATURE_COMPAT_EXT_ATTR) +#endif /* This #ifdef is temporary until compression is fully supported */ #ifdef ENABLE_COMPRESSION @@ -533,12 +561,22 @@ typedef struct ext2_icount *ext2_icount_t; EXT3_FEATURE_INCOMPAT_EXTENTS|\ EXT4_FEATURE_INCOMPAT_FLEX_BG) #endif +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT #define EXT2_LIB_FEATURE_RO_COMPAT_SUPP (EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER|\ EXT4_FEATURE_RO_COMPAT_HUGE_FILE|\ EXT2_FEATURE_RO_COMPAT_LARGE_FILE|\ + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT|\ EXT4_FEATURE_RO_COMPAT_DIR_NLINK|\ EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE|\ EXT4_FEATURE_RO_COMPAT_GDT_CSUM) +#else +#define EXT2_LIB_FEATURE_RO_COMPAT_SUPP (EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER|\ + EXT4_FEATURE_RO_COMPAT_HUGE_FILE|\ + EXT2_FEATURE_RO_COMPAT_LARGE_FILE|\ + EXT4_FEATURE_RO_COMPAT_DIR_NLINK|\ + EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE|\ + EXT4_FEATURE_RO_COMPAT_GDT_CSUM) +#endif /* * These features are only allowed if EXT2_FLAG_SOFTSUPP_FEATURES is passed @@ -646,8 +684,14 @@ extern errcode_t ext2fs_copy_bitmap(ext2fs_generic_bitmap src, ext2fs_generic_bitmap *dest); extern errcode_t ext2fs_write_inode_bitmap(ext2_filsys fs); extern errcode_t ext2fs_write_block_bitmap (ext2_filsys fs); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP +extern errcode_t ext2fs_write_exclude_bitmap (ext2_filsys fs); +#endif extern errcode_t ext2fs_read_inode_bitmap (ext2_filsys fs); extern errcode_t ext2fs_read_block_bitmap(ext2_filsys fs); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP +extern errcode_t ext2fs_read_exclude_bitmap (ext2_filsys fs); +#endif extern errcode_t ext2fs_allocate_block_bitmap(ext2_filsys fs, const char *descr, ext2fs_block_bitmap *ret); @@ -1073,6 +1117,10 @@ extern errcode_t ext2fs_add_journal_device(ext2_filsys fs, extern errcode_t ext2fs_add_journal_inode(ext2_filsys fs, blk_t size, int flags); extern int ext2fs_default_journal_size(__u64 blocks); +#ifdef EXT2FS_SNAPSHOT_BIG_JOURNAL +extern int ext2fs_big_journal_size(int factor, __u64 blocks); +extern int ext2fs_check_journal_size(ext2_filsys fs); +#endif /* openfs.c */ extern errcode_t ext2fs_open(const char *name, int flags, int superblock, @@ -1117,6 +1165,15 @@ extern errcode_t ext2fs_read_bb_FILE(ext2_filsys fs, FILE *f, /* res_gdt.c */ extern errcode_t ext2fs_create_resize_inode(ext2_filsys fs); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE +extern errcode_t ext2fs_create_exclude_inode(ext2_filsys fs, int flags); + +/* exclude inode creation flags */ +#define EXCLUDE_READONLY 0 /* only read exclude bitmap blocks */ +#define EXCLUDE_ALLOC 1 /* allocate missing exclude bitmap blocks */ +#define EXCLUDE_RESET 2 /* reset exclude bitmap blocks to zero */ +#define EXCLUDE_CREATE 3 /* alloc and/or reset exclude bitmap blocks */ +#endif /* swapfs.c */ extern void ext2fs_swap_ext_attr(char *to, char *from, int bufsize, @@ -1212,7 +1269,8 @@ _INLINE_ errcode_t ext2fs_get_memalign(unsigned long size, if (align == 0) align = 8; - if (retval = posix_memalign((void **) ptr, align, size)) { + retval = posix_memalign((void **) ptr, align, size); + if (retval) { if (retval == ENOMEM) return EXT2_ET_NO_MEMORY; return retval; @@ -1324,6 +1382,16 @@ _INLINE_ void ext2fs_mark_bb_dirty(ext2_filsys fs) fs->flags |= EXT2_FLAG_BB_DIRTY | EXT2_FLAG_CHANGED; } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP +/* + * Mark the exclude bitmap as dirty + */ +_INLINE_ void ext2fs_mark_exclude_dirty(ext2_filsys fs) +{ + fs->flags |= EXT2_FLAG_EXCLUDE_DIRTY | EXT2_FLAG_CHANGED; +} + +#endif /* * Check to see if a filesystem's inode bitmap is dirty */ @@ -1340,6 +1408,16 @@ _INLINE_ int ext2fs_test_bb_dirty(ext2_filsys fs) return (fs->flags & EXT2_FLAG_BB_DIRTY); } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP +/* + * Check to see if a filesystem's exclude bitmap is dirty + */ +_INLINE_ int ext2fs_test_exclude_dirty(ext2_filsys fs) +{ + return (fs->flags & EXT2_FLAG_EXCLUDE_DIRTY); +} + +#endif /* * Return the group # of a block */ diff --git a/lib/ext2fs/freefs.c b/lib/ext2fs/freefs.c index 5c35bb6..9055b09 100644 --- a/lib/ext2fs/freefs.c +++ b/lib/ext2fs/freefs.c @@ -42,6 +42,10 @@ void ext2fs_free(ext2_filsys fs) ext2fs_free_block_bitmap(fs->block_map); if (fs->inode_map) ext2fs_free_inode_bitmap(fs->inode_map); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (fs->exclude_map) + ext2fs_free_inode_bitmap(fs->exclude_map); +#endif if (fs->badblocks) ext2fs_badblocks_list_free(fs->badblocks); diff --git a/lib/ext2fs/i_block.c b/lib/ext2fs/i_block.c index 822776d..f96f200 100644 --- a/lib/ext2fs/i_block.c +++ b/lib/ext2fs/i_block.c @@ -31,8 +31,15 @@ errcode_t ext2fs_iblk_add_blocks(ext2_filsys fs, struct ext2_inode *inode, { unsigned long long b = inode->i_blocks; +#ifdef EXT2FS_SNAPSHOT_HUGE_SNAPSHOT + if (!((fs->super->s_feature_ro_compat & + EXT4_FEATURE_RO_COMPAT_HUGE_FILE) || + /* snapshot file always supports the 'huge_file' flag */ + (inode->i_flags & EXT4_SNAPFILE_FL)) || +#else if (!(fs->super->s_feature_ro_compat & EXT4_FEATURE_RO_COMPAT_HUGE_FILE) || +#endif !(inode->i_flags & EXT4_HUGE_FILE_FL)) num_blocks *= fs->blocksize / 512; @@ -53,8 +60,15 @@ errcode_t ext2fs_iblk_sub_blocks(ext2_filsys fs, struct ext2_inode *inode, { unsigned long long b = inode->i_blocks; +#ifdef EXT2FS_SNAPSHOT_HUGE_SNAPSHOT + if (!((fs->super->s_feature_ro_compat & + EXT4_FEATURE_RO_COMPAT_HUGE_FILE) || + /* snapshot file always supports the 'huge_file' flag */ + (inode->i_flags & EXT4_SNAPFILE_FL)) || +#else if (!(fs->super->s_feature_ro_compat & EXT4_FEATURE_RO_COMPAT_HUGE_FILE) || +#endif !(inode->i_flags & EXT4_HUGE_FILE_FL)) num_blocks *= fs->blocksize / 512; @@ -74,8 +88,15 @@ errcode_t ext2fs_iblk_sub_blocks(ext2_filsys fs, struct ext2_inode *inode, errcode_t ext2fs_iblk_set(ext2_filsys fs, struct ext2_inode *inode, blk64_t b) { +#ifdef EXT2FS_SNAPSHOT_HUGE_SNAPSHOT + if (!((fs->super->s_feature_ro_compat & + EXT4_FEATURE_RO_COMPAT_HUGE_FILE) || + /* snapshot file always supports the 'huge_file' flag */ + (inode->i_flags & EXT4_SNAPFILE_FL)) || +#else if (!(fs->super->s_feature_ro_compat & EXT4_FEATURE_RO_COMPAT_HUGE_FILE) || +#endif !(inode->i_flags & EXT4_HUGE_FILE_FL)) b *= fs->blocksize / 512; diff --git a/lib/ext2fs/mkjournal.c b/lib/ext2fs/mkjournal.c index 4a81f4b..c0c86c0 100644 --- a/lib/ext2fs/mkjournal.c +++ b/lib/ext2fs/mkjournal.c @@ -388,6 +388,49 @@ int ext2fs_default_journal_size(__u64 blocks) return 32768; } +#ifdef EXT2FS_SNAPSHOT_BIG_JOURNAL +/* + * Big journal is up to X times bigger than the default journal + * to accomodate snapshot COW credits in transactions. + * journal size is restricted to 1/32 of the filesystem size + */ +int ext2fs_big_journal_size(int factor, __u64 blocks) +{ + int mega_blocks = blocks >> 20; + if (!mega_blocks) + return ext2fs_default_journal_size(blocks); + + if (mega_blocks < factor) + /* 32K/1M = 1/32 of filesystem size */ + return EXT3_DEF_JOURNAL_BLOCKS*mega_blocks; + + /* X times bigger than the default journal */ + return EXT3_DEF_JOURNAL_BLOCKS*factor; +} + +/* + * Return the number of blocks in the journal inode + */ +int ext2fs_check_journal_size(ext2_filsys fs) +{ + struct ext2_inode j_inode; + int j_blocks; + + if (!(fs->super->s_feature_compat & + EXT3_FEATURE_COMPAT_HAS_JOURNAL) || + !fs->super->s_journal_inum) + return 0; + + if (ext2fs_read_inode(fs, fs->super->s_journal_inum, &j_inode)) + return -1; + + /* read journal inode size */ + j_blocks = j_inode.i_size >> EXT2_BLOCK_SIZE_BITS(fs->super); + + return j_blocks; +} + +#endif /* * This function adds a journal device to a filesystem */ diff --git a/lib/ext2fs/res_gdt.c b/lib/ext2fs/res_gdt.c index 424d867..787bd5a 100644 --- a/lib/ext2fs/res_gdt.c +++ b/lib/ext2fs/res_gdt.c @@ -217,3 +217,220 @@ out_free: return retval; } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE +#define ext2fs_group_desc(fs, gdp, grp) (gdp)+(grp) + +/* + * ext2fs_create_exclude_inode(): + * the exclude inode owns all the exclude bitmap blocks (one per block group) + * the exclude bitmap blocks are double indirectly linked to the exclude inode + * the exclude bitmap allocation goal is the first block of the block group + * exclude inode creation @flags: + * EXCLUDE_ALLOC (1) - allocate missing exclude bitmap blocks + * EXCLUDE_RESET (2) - reset exclude bitmap to zero + */ +errcode_t ext2fs_create_exclude_inode(ext2_filsys fs, int flags) +{ + errcode_t retval, retval2; + struct ext2_super_block *sb; + struct ext2_inode inode; + __u32 *dindir_buf, *indir_buf, *data_buf; + unsigned long long apb, inode_size; + blk_t dindir_blk, indir_blk, data_blk; + int gdt_dirty = 0, dindir_dirty = 0, inode_dirty = 0; + int indir_dirty = 0, data_dirty = 0; + int dindir_off, indir_off, grp, i, max_groups; + int create = flags & EXCLUDE_ALLOC; + int reset = flags & EXCLUDE_RESET; + + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + sb = fs->super; + + retval = ext2fs_get_array(3, fs->blocksize, &dindir_buf); + if (retval) + goto out_free; + indir_buf = (__u32 *)((char *)dindir_buf + 1*fs->blocksize); + data_buf = (__u32 *)((char *)dindir_buf + 2*fs->blocksize); + + retval = ext2fs_read_inode(fs, EXT2_EXCLUDE_INO, &inode); + if (retval) + goto out_free; + +#ifdef EXCLUDE_INO_PROGRESS + printf("Reserving exclude bitmap blocks: "); +#endif + + apb = EXT2_ADDR_PER_BLOCK(sb); + if ((dindir_blk = inode.i_block[EXT2_DIND_BLOCK])) { +#ifdef EXCLUDE_INO_DEBUG + printf("reading exclude inode dindir %u\n", dindir_blk); +#endif + retval = ext2fs_read_ind_block(fs, dindir_blk, dindir_buf); + if (retval) + goto out_free; + } else if (create) { + blk_t goal = sb->s_first_data_block + fs->desc_blocks + + sb->s_reserved_gdt_blocks + 2 + + fs->inode_blocks_per_group; + + retval = ext2fs_alloc_block(fs, goal, (char *)dindir_buf, &dindir_blk); + if (retval) + goto out_free; + inode.i_mode = LINUX_S_IFREG | 0600; + inode.i_links_count = 1; + inode.i_block[EXT2_DIND_BLOCK] = dindir_blk; + ext2fs_iblk_set(fs, &inode, 1); +#ifdef EXCLUDE_INO_DEBUG + printf("allocated exclude inode dindir %u\n", dindir_blk); +#endif + dindir_dirty = inode_dirty = 1; + inode.i_ctime = fs->now ? fs->now : time(0); + } + + /* + * allocate exclude bitmaps for all existing block groups + * and allocate indirect blocks for all reserved block groups + */ + max_groups = fs->desc_blocks + sb->s_reserved_gdt_blocks; + max_groups *= EXT2_DESC_PER_BLOCK(sb); + for (grp = 0; grp < max_groups; grp++) { + struct ext2_group_desc *gd = + ext2fs_group_desc(fs, fs->group_desc, grp); + + dindir_off = grp/apb; + indir_off = grp%apb; + if (indir_off == 0) { + /* flush current indirect block */ + if (indir_dirty) { + retval = ext2fs_write_ind_block(fs, indir_blk, indir_buf); + if (retval) + goto out_dindir; + indir_dirty = 0; + } + /* read/alloc next indirect block */ + if ((indir_blk = dindir_buf[dindir_off])) { +#ifdef EXCLUDE_INO_DEBUG + printf("reading exclude inode indir %u\n", indir_blk); +#endif + retval = ext2fs_read_ind_block(fs, indir_blk, indir_buf); + if (retval) + goto out_dindir; + } else if (create) { + retval = ext2fs_alloc_block(fs, dindir_blk, (char *)indir_buf, &indir_blk); + if (retval) + goto out_dindir; + dindir_buf[dindir_off] = indir_blk; + ext2fs_iblk_add_blocks(fs, &inode, 1); +#ifdef EXCLUDE_INO_DEBUG + printf("allocated exclude inode indir %u\n", indir_blk); +#endif + dindir_dirty = inode_dirty = 1; + } + } + + if (grp >= fs->group_desc_count) + continue; + /* read/alloc exclude bitmap block */ + data_blk = indir_buf[indir_off]; + if (!data_blk && create) { + /* allocate exclude bitmap block */ + retval = ext2fs_alloc_block(fs, gd->bg_block_bitmap, + (char *)data_buf, &data_blk); + if (retval) + goto out_dindir; + indir_buf[indir_off] = data_blk; + ext2fs_iblk_add_blocks(fs, &inode, 1); +#ifdef EXCLUDE_INO_DEBUG + printf("allocated exclude bitmap block %u\n", data_blk); +#endif + indir_dirty = inode_dirty = 1; + } else if (data_blk && reset) { + /* reset exclude bitmap block */ +#ifdef EXCLUDE_INO_DEBUG + printf("reading exclude bitmap block %u\n", data_blk); +#endif + retval = io_channel_read_blk(fs->io, data_blk, 1, + data_buf); + if (retval) + goto out_dindir; + /* zero data block */ + for (i = 0; i < apb; i++) { + if (!data_buf[i]) + continue; + data_buf[i] = 0; + data_dirty = 1; + } + if (data_dirty) { + retval = io_channel_write_blk(fs->io, data_blk, + 1, data_buf); + if (retval) + goto out_dindir; + data_dirty = 0; + } + } + if (!gd->bg_exclude_bitmap) { + gd->bg_exclude_bitmap = data_blk; + ext2fs_group_desc_csum_set(fs, grp); + gdt_dirty = 1; + } +#ifdef EXCLUDE_INO_PROGRESS + printf("\b\b\b\b\b\b\b\b\b\b\b%5d/%5d", grp, + fs->group_desc_count); +#endif + } +#ifdef EXCLUDE_INO_PROGRESS + printf("\b\b\b\b\b\b\b\b\b\b\bdone \n"); +#endif + + /* exclude bitmap was reset to zero - clear fix_exclude flag */ + if (sb->s_flags & EXT2_FLAGS_FIX_EXCLUDE) { + sb->s_flags &= ~EXT2_FLAGS_FIX_EXCLUDE; + ext2fs_mark_super_dirty(fs); + } + +out_dindir: + if (indir_dirty) { + retval2 = ext2fs_write_ind_block(fs, indir_blk, indir_buf); + if (!retval) + retval = retval2; + } + if (dindir_dirty) { + retval2 = ext2fs_write_ind_block(fs, dindir_blk, dindir_buf); + if (!retval) + retval = retval2; + } +out_inode: + if (inode_dirty) { + inode_size = fs->group_desc_count + apb + EXT2_NDIR_BLOCKS; + inode_size *= fs->blocksize; + inode.i_size = inode_size & 0xFFFFFFFF; + inode.i_atime = inode.i_mtime = fs->now ? fs->now : time(0); + retval2 = ext2fs_write_new_inode(fs, EXT2_EXCLUDE_INO, &inode); + if (!retval) + retval = retval2; + /* need to write out block bitmaps and group descriptors */ + fs->flags &= ~EXT2_FLAG_SUPER_ONLY; + } + if (gdt_dirty) { + fs->flags &= ~EXT2_FLAG_SUPER_ONLY; + ext2fs_mark_super_dirty(fs); + } + if (!(fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_EXCLUDE_BITMAP)) { + /* set exclude_bitmap along with exclude_inode */ + fs->super->s_feature_compat |= + EXT2_FEATURE_COMPAT_EXCLUDE_BITMAP; + ext2fs_mark_super_dirty(fs); + } +#ifdef EXCLUDE_INO_DEBUG + printf("inode.i_blocks = %u, i_size = %u\n", + inode.i_blocks, inode.i_size); +#endif +out_free: + ext2fs_free_mem(&dindir_buf); + return retval; +} + +#endif diff --git a/lib/ext2fs/rw_bitmaps.c b/lib/ext2fs/rw_bitmaps.c index 4e77a8f..c681660 100644 --- a/lib/ext2fs/rw_bitmaps.c +++ b/lib/ext2fs/rw_bitmaps.c @@ -25,19 +25,47 @@ #include "ext2_fs.h" #include "ext2fs.h" + +#define blk64_t blk_t +#define __u64 __u32 +#define io_channel_read_blk64 io_channel_read_blk +#define io_channel_write_blk64 io_channel_write_blk +#define ext2fs_get_block_bitmap_range2 ext2fs_get_block_bitmap_range +#define ext2fs_set_block_bitmap_range2 ext2fs_set_block_bitmap_range +#define ext2fs_block_bitmap_loc(fs, group) \ + fs->group_desc[group].bg_block_bitmap +#define ext2fs_exclude_bitmap_loc(fs, group) \ + fs->group_desc[group].bg_exclude_bitmap +#define ext2fs_inode_bitmap_loc(fs, group) \ + fs->group_desc[group].bg_inode_bitmap +#define ext2fs_bg_flags_test(fs, group, bg_flag) \ + fs->group_desc[group].bg_flags & bg_flag +#define ext2fs_blocks_count(sb) sb->s_blocks_count + +#include "ext2_fs.h" +#include "ext2fs.h" #include "e2image.h" +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP +static errcode_t write_bitmaps(ext2_filsys fs, int do_inode, int do_block, + int do_exclude) +#else static errcode_t write_bitmaps(ext2_filsys fs, int do_inode, int do_block) +#endif { dgrp_t i; unsigned int j; int block_nbytes, inode_nbytes; unsigned int nbits; errcode_t retval; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + char *block_buf, *inode_buf, *exclude_buf; +#else char *block_buf, *inode_buf; +#endif int csum_flag = 0; - blk_t blk; - blk_t blk_itr = fs->super->s_first_data_block; + blk64_t blk; + blk64_t blk_itr = fs->super->s_first_data_block; ext2_ino_t ino_itr = 1; EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); @@ -45,6 +73,12 @@ static errcode_t write_bitmaps(ext2_filsys fs, int do_inode, int do_block) if (!(fs->flags & EXT2_FLAG_RW)) return EXT2_ET_RO_FILSYS; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (!EXT2_HAS_COMPAT_FEATURE(fs->super, + EXT2_FEATURE_COMPAT_EXCLUDE_BITMAP)) + do_exclude = 0; + +#endif if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super, EXT4_FEATURE_RO_COMPAT_GDT_CSUM)) csum_flag = 1; @@ -58,6 +92,16 @@ static errcode_t write_bitmaps(ext2_filsys fs, int do_inode, int do_block) return retval; memset(block_buf, 0xff, fs->blocksize); } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (do_exclude) { + block_nbytes = EXT2_BLOCKS_PER_GROUP(fs->super) / 8; + retval = ext2fs_get_memalign(fs->blocksize, fs->blocksize, + &exclude_buf); + if (retval) + return retval; + memset(exclude_buf, 0xff, fs->blocksize); + } +#endif if (do_inode) { inode_nbytes = (size_t) ((EXT2_INODES_PER_GROUP(fs->super)+7) / 8); @@ -69,34 +113,67 @@ static errcode_t write_bitmaps(ext2_filsys fs, int do_inode, int do_block) } for (i = 0; i < fs->group_desc_count; i++) { +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (!do_block && !do_exclude) +#else if (!do_block) +#endif goto skip_block_bitmap; - if (csum_flag && fs->group_desc[i].bg_flags & - EXT2_BG_BLOCK_UNINIT) + if (csum_flag && ext2fs_bg_flags_test(fs, i, EXT2_BG_BLOCK_UNINIT) + ) goto skip_this_block_bitmap; - retval = ext2fs_get_block_bitmap_range(fs->block_map, +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (do_block) + retval = ext2fs_get_block_bitmap_range2(fs->block_map, + blk_itr, block_nbytes << 3, block_buf); + if (retval) + return retval; + + if (do_exclude) + retval = ext2fs_get_block_bitmap_range2(fs->exclude_map, + blk_itr, block_nbytes << 3, exclude_buf); +#else + retval = ext2fs_get_block_bitmap_range2(fs->block_map, blk_itr, block_nbytes << 3, block_buf); +#endif if (retval) return retval; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (do_block && i == fs->group_desc_count - 1) { +#else if (i == fs->group_desc_count - 1) { +#endif /* Force bitmap padding for the last group */ - nbits = ((fs->super->s_blocks_count - - fs->super->s_first_data_block) - % EXT2_BLOCKS_PER_GROUP(fs->super)); + nbits = ((ext2fs_blocks_count(fs->super) + - (__u64) fs->super->s_first_data_block) + % (__u64) EXT2_BLOCKS_PER_GROUP(fs->super)); if (nbits) for (j = nbits; j < fs->blocksize * 8; j++) ext2fs_set_bit(j, block_buf); } - blk = fs->group_desc[i].bg_block_bitmap; + blk = ext2fs_block_bitmap_loc(fs, i); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (do_block && blk) { +#else if (blk) { - retval = io_channel_write_blk(fs->io, blk, 1, - block_buf); +#endif + retval = io_channel_write_blk64(fs->io, blk, 1, + block_buf); if (retval) return EXT2_ET_BLOCK_BITMAP_WRITE; } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + blk = ext2fs_exclude_bitmap_loc(fs, i); + if (do_exclude && blk) { + retval = io_channel_write_blk64(fs->io, blk, 1, + exclude_buf); + if (retval) + return EXT2_ET_BLOCK_BITMAP_WRITE; + } +#endif skip_this_block_bitmap: blk_itr += block_nbytes << 3; skip_block_bitmap: @@ -104,8 +181,8 @@ static errcode_t write_bitmaps(ext2_filsys fs, int do_inode, int do_block) if (!do_inode) continue; - if (csum_flag && fs->group_desc[i].bg_flags & - EXT2_BG_INODE_UNINIT) + if (csum_flag && ext2fs_bg_flags_test(fs, i, EXT2_BG_BLOCK_UNINIT) + ) goto skip_this_inode_bitmap; retval = ext2fs_get_inode_bitmap_range(fs->inode_map, @@ -113,9 +190,9 @@ static errcode_t write_bitmaps(ext2_filsys fs, int do_inode, int do_block) if (retval) return retval; - blk = fs->group_desc[i].bg_inode_bitmap; + blk = ext2fs_inode_bitmap_loc(fs, i); if (blk) { - retval = io_channel_write_blk(fs->io, blk, 1, + retval = io_channel_write_blk64(fs->io, blk, 1, inode_buf); if (retval) return EXT2_ET_INODE_BITMAP_WRITE; @@ -135,10 +212,19 @@ static errcode_t write_bitmaps(ext2_filsys fs, int do_inode, int do_block) return 0; } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP +static errcode_t read_bitmaps(ext2_filsys fs, int do_inode, int do_block, + int do_exclude) +#else static errcode_t read_bitmaps(ext2_filsys fs, int do_inode, int do_block) +#endif { dgrp_t i; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + char *block_bitmap = 0, *inode_bitmap = 0, *exclude_bitmap = 0; +#else char *block_bitmap = 0, *inode_bitmap = 0; +#endif char *buf; errcode_t retval; int block_nbytes = EXT2_BLOCKS_PER_GROUP(fs->super) / 8; @@ -146,9 +232,9 @@ static errcode_t read_bitmaps(ext2_filsys fs, int do_inode, int do_block) int csum_flag = 0; int do_image = fs->flags & EXT2_FLAG_IMAGE_FILE; unsigned int cnt; - blk_t blk; - blk_t blk_itr = fs->super->s_first_data_block; - blk_t blk_cnt; + blk64_t blk; + blk64_t blk_itr = fs->super->s_first_data_block; + blk64_t blk_cnt; ext2_ino_t ino_itr = 1; ext2_ino_t ino_cnt; @@ -156,6 +242,12 @@ static errcode_t read_bitmaps(ext2_filsys fs, int do_inode, int do_block) fs->write_bitmaps = ext2fs_write_bitmaps; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (!EXT2_HAS_COMPAT_FEATURE(fs->super, + EXT2_FEATURE_COMPAT_EXCLUDE_BITMAP)) + do_exclude = 0; + +#endif if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super, EXT4_FEATURE_RO_COMPAT_GDT_CSUM)) csum_flag = 1; @@ -180,7 +272,29 @@ static errcode_t read_bitmaps(ext2_filsys fs, int do_inode, int do_block) if (retval) goto cleanup; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + } + if (do_exclude) { + if (fs->exclude_map) + ext2fs_free_block_bitmap(fs->exclude_map); + strcpy(buf, "exclude bitmap for "); + strcat(buf, fs->device_name); + retval = ext2fs_allocate_block_bitmap(fs, buf, &fs->exclude_map); + if (retval) + goto cleanup; + if (do_image) + retval = ext2fs_get_mem(fs->blocksize, &exclude_bitmap); + else + retval = ext2fs_get_memalign((unsigned) block_nbytes, + fs->blocksize, + &exclude_bitmap); + if (retval) + goto cleanup; + } + if (!do_block && !do_exclude) +#else } else +#endif block_nbytes = 0; if (do_inode) { if (fs->inode_map) @@ -202,7 +316,7 @@ static errcode_t read_bitmaps(ext2_filsys fs, int do_inode, int do_block) blk = (fs->image_header->offset_inodemap / fs->blocksize); ino_cnt = fs->super->s_inodes_count; while (inode_nbytes > 0) { - retval = io_channel_read_blk(fs->image_io, blk++, + retval = io_channel_read_blk64(fs->image_io, blk++, 1, inode_bitmap); if (retval) goto cleanup; @@ -219,17 +333,24 @@ static errcode_t read_bitmaps(ext2_filsys fs, int do_inode, int do_block) } blk = (fs->image_header->offset_blockmap / fs->blocksize); - blk_cnt = EXT2_BLOCKS_PER_GROUP(fs->super) * + blk_cnt = (blk64_t)EXT2_BLOCKS_PER_GROUP(fs->super) * fs->group_desc_count; while (block_nbytes > 0) { - retval = io_channel_read_blk(fs->image_io, blk++, +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (do_exclude) { + retval = EXT2_ET_BLOCK_BITMAP_READ; + goto cleanup; + } + +#endif + retval = io_channel_read_blk64(fs->image_io, blk++, 1, block_bitmap); if (retval) goto cleanup; cnt = fs->blocksize << 3; if (cnt > blk_cnt) cnt = blk_cnt; - retval = ext2fs_set_block_bitmap_range(fs->block_map, + retval = ext2fs_set_block_bitmap_range2(fs->block_map, blk_itr, cnt, block_bitmap); if (retval) goto cleanup; @@ -242,13 +363,13 @@ static errcode_t read_bitmaps(ext2_filsys fs, int do_inode, int do_block) for (i = 0; i < fs->group_desc_count; i++) { if (block_bitmap) { - blk = fs->group_desc[i].bg_block_bitmap; - if (csum_flag && fs->group_desc[i].bg_flags & - EXT2_BG_BLOCK_UNINIT && + blk = ext2fs_block_bitmap_loc(fs, i); + if (csum_flag && + ext2fs_bg_flags_test(fs, i, EXT2_BG_BLOCK_UNINIT) && ext2fs_group_desc_csum_verify(fs, i)) blk = 0; if (blk) { - retval = io_channel_read_blk(fs->io, blk, + retval = io_channel_read_blk64(fs->io, blk, -block_nbytes, block_bitmap); if (retval) { retval = EXT2_ET_BLOCK_BITMAP_READ; @@ -257,20 +378,47 @@ static errcode_t read_bitmaps(ext2_filsys fs, int do_inode, int do_block) } else memset(block_bitmap, 0, block_nbytes); cnt = block_nbytes << 3; - retval = ext2fs_set_block_bitmap_range(fs->block_map, + retval = ext2fs_set_block_bitmap_range2(fs->block_map, blk_itr, cnt, block_bitmap); if (retval) goto cleanup; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + } + if (exclude_bitmap) { + blk = ext2fs_exclude_bitmap_loc(fs, i); + if (csum_flag && + ext2fs_bg_flags_test(fs, i, EXT2_BG_BLOCK_UNINIT) && + ext2fs_group_desc_csum_verify(fs, i)) + blk = 0; + if (blk) { + retval = io_channel_read_blk64(fs->io, blk, + -block_nbytes, exclude_bitmap); + if (retval) { + retval = EXT2_ET_BLOCK_BITMAP_READ; + goto cleanup; + } + } else + memset(exclude_bitmap, 0, block_nbytes); + cnt = block_nbytes << 3; + retval = ext2fs_set_block_bitmap_range2(fs->exclude_map, + blk_itr, cnt, exclude_bitmap); + if (retval) + goto cleanup; + } + if (block_nbytes) + blk_itr += block_nbytes << 3; +#else blk_itr += block_nbytes << 3; } +#endif if (inode_bitmap) { - blk = fs->group_desc[i].bg_inode_bitmap; - if (csum_flag && fs->group_desc[i].bg_flags & - EXT2_BG_INODE_UNINIT && + blk = ext2fs_inode_bitmap_loc(fs, i); + if (csum_flag && + ext2fs_bg_flags_test(fs, i, EXT2_BG_INODE_UNINIT) && ext2fs_group_desc_csum_verify(fs, i)) blk = 0; if (blk) { - retval = io_channel_read_blk(fs->io, blk, + retval = io_channel_read_blk64(fs->io, blk, -inode_nbytes, inode_bitmap); if (retval) { retval = EXT2_ET_INODE_BITMAP_READ; @@ -291,6 +439,10 @@ success_cleanup: ext2fs_free_mem(&inode_bitmap); if (block_bitmap) ext2fs_free_mem(&block_bitmap); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (exclude_bitmap) + ext2fs_free_mem(&exclude_bitmap); +#endif return 0; cleanup: @@ -306,6 +458,10 @@ cleanup: ext2fs_free_mem(&inode_bitmap); if (block_bitmap) ext2fs_free_mem(&block_bitmap); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (exclude_bitmap) + ext2fs_free_mem(&exclude_bitmap); +#endif if (buf) ext2fs_free_mem(&buf); return retval; @@ -313,39 +469,88 @@ cleanup: errcode_t ext2fs_read_inode_bitmap(ext2_filsys fs) { +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + return read_bitmaps(fs, 1, 0, 0); +#else return read_bitmaps(fs, 1, 0); +#endif } errcode_t ext2fs_read_block_bitmap(ext2_filsys fs) { +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + return read_bitmaps(fs, 0, 1, 0); +#else return read_bitmaps(fs, 0, 1); +#endif +} + +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP +errcode_t ext2fs_read_exclude_bitmap (ext2_filsys fs) +{ + return read_bitmaps(fs, 0, 0, 1); } +#endif errcode_t ext2fs_write_inode_bitmap(ext2_filsys fs) { +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + return write_bitmaps(fs, 1, 0, 0); +#else return write_bitmaps(fs, 1, 0); +#endif } errcode_t ext2fs_write_block_bitmap (ext2_filsys fs) { +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + return write_bitmaps(fs, 0, 1, 0); +#else return write_bitmaps(fs, 0, 1); +#endif } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP +errcode_t ext2fs_write_exclude_bitmap (ext2_filsys fs) +{ + return write_bitmaps(fs, 0, 0, 1); +} + +#endif errcode_t ext2fs_read_bitmaps(ext2_filsys fs) { +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (fs->inode_map && fs->block_map && fs->exclude_map) +#else if (fs->inode_map && fs->block_map) +#endif return 0; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + return read_bitmaps(fs, !fs->inode_map, !fs->block_map, !fs->exclude_map); +#else return read_bitmaps(fs, !fs->inode_map, !fs->block_map); +#endif } errcode_t ext2fs_write_bitmaps(ext2_filsys fs) { int do_inode = fs->inode_map && ext2fs_test_ib_dirty(fs); int do_block = fs->block_map && ext2fs_test_bb_dirty(fs); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + int do_exclude = fs->exclude_map && ext2fs_test_exclude_dirty(fs); +#endif +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (!do_inode && !do_block && !do_exclude) +#else if (!do_inode && !do_block) +#endif return 0; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + return write_bitmaps(fs, do_inode, do_block, do_exclude); +#else return write_bitmaps(fs, do_inode, do_block); +#endif } diff --git a/misc/chattr.c b/misc/chattr.c index de33b08..a28ed39 100644 --- a/misc/chattr.c +++ b/misc/chattr.c @@ -55,6 +55,10 @@ #include "nls-enable.h" static const char * program_name = "chattr"; +#ifdef EXT2FS_SNAPSHOT_CTL + +static int chsnap; +#endif static int add; static int rem; @@ -81,6 +85,18 @@ static unsigned long sf; static void usage(void) { +#ifdef EXT2FS_SNAPSHOT_CTL + if (chsnap) { + fprintf(stderr, +#ifdef EXT2FS_SNAPSHOT_CTL_OLD + _("Usage: %s [-X] [-+=Sn] snapshot files...\n"), +#else + _("Usage: %s [-+=Sn] snapshot files...\n"), +#endif + program_name); + exit(1); + } +#endif fprintf(stderr, _("Usage: %s [-RVf] [-+=AacDdeijsSu] [-v version] files...\n"), program_name); @@ -92,7 +108,11 @@ struct flags_char { char optchar; }; +#ifdef EXT2FS_SNAPSHOT_CTL +static const struct flags_char ext2_flags_array[] = { +#else static const struct flags_char flags_array[] = { +#endif { EXT2_NOATIME_FL, 'A' }, { EXT2_SYNC_FL, 'S' }, { EXT2_DIRSYNC_FL, 'D' }, @@ -106,9 +126,32 @@ static const struct flags_char flags_array[] = { { EXT2_UNRM_FL, 'u' }, { EXT2_NOTAIL_FL, 't' }, { EXT2_TOPDIR_FL, 'T' }, +#ifdef EXT2FS_SNAPSHOT_CTL + { EXT4_SNAPFILE_FL, 'x' }, +#endif + { 0, 0 } +}; + +#ifdef EXT2FS_SNAPSHOT_CTL +static const struct flags_char *flags_array = ext2_flags_array; + +/* Snapshot dynamic state flags */ +static struct flags_char snapshot_flags_array[] = { + { 1UL<<EXT4_SNAPSHOT_LIST, 'S' }, + { 1UL<<EXT4_SNAPSHOT_ENABLED, 'n' }, + { 0, 0 } +}; + +#ifdef EXT2FS_SNAPSHOT_CTL_OLD +/* Old snapshot flags for backward compatibility with next3 */ +static struct flags_char snapshot_X_flags_array[] = { + { NEXT3_SNAPFILE_LIST_FL, 'S' }, + { NEXT3_SNAPFILE_ENABLED_FL, 'n' }, { 0, 0 } }; +#endif +#endif static unsigned long get_flag(char c) { const struct flags_char *fp; @@ -131,6 +174,12 @@ static int decode_arg (int * i, int argc, char ** argv) { case '-': for (p = &argv[*i][1]; *p; p++) { +#ifdef EXT2FS_SNAPSHOT_CTL_OLD + if (*p == 'X') { + flags_array = snapshot_X_flags_array; + continue; + } +#endif if (*p == 'R') { recursive = 1; continue; @@ -193,6 +242,9 @@ static int change_attributes(const char * name) unsigned long flags; STRUCT_STAT st; int extent_file = 0; +#ifdef EXT2FS_SNAPSHOT_CTL + int ret; +#endif if (LSTAT (name, &st) == -1) { if (!silent) @@ -201,7 +253,15 @@ static int change_attributes(const char * name) return -1; } +#ifdef EXT2FS_SNAPSHOT_CTL + if (chsnap) + ret = fgetsnapflags (name, &flags); + else + ret = fgetflags (name, &flags); + if (ret == -1) { +#else if (fgetflags(name, &flags) == -1) { +#endif if (!silent) com_err(program_name, errno, _("while reading flags on %s"), name); @@ -222,7 +282,15 @@ static int change_attributes(const char * name) print_flags (stdout, sf, 0); printf ("\n"); } +#ifdef EXT2FS_SNAPSHOT_CTL + if (chsnap) + ret = fsetsnapflags (name, sf); + else + ret = fsetflags (name, sf); + if (ret == -1) +#else if (fsetflags (name, sf) == -1) +#endif perror (name); } else { if (rem) @@ -243,7 +311,15 @@ static int change_attributes(const char * name) } if (!S_ISDIR(st.st_mode)) flags &= ~EXT2_DIRSYNC_FL; +#ifdef EXT2FS_SNAPSHOT_CTL + if (chsnap) + ret = fsetsnapflags (name, flags); + else + ret = fsetflags (name, flags); + if (ret == -1) { +#else if (fsetflags(name, flags) == -1) { +#endif if (!silent) { com_err(program_name, errno, _("while setting flags on %s"), @@ -303,6 +379,13 @@ int main (int argc, char ** argv) #endif if (argc && *argv) program_name = *argv; +#ifdef EXT2FS_SNAPSHOT_CTL + i = strlen(program_name); + if (i >= 6 && !strcmp(program_name + i - 6, "chsnap")) { + flags_array = snapshot_flags_array; + chsnap = 1; + } +#endif i = 1; while (i < argc && !end_arg) { /* '--' arg should end option processing */ diff --git a/misc/dumpe2fs.c b/misc/dumpe2fs.c index 5e0a8a2..65b8ce6 100644 --- a/misc/dumpe2fs.c +++ b/misc/dumpe2fs.c @@ -216,6 +216,17 @@ static void list_desc (ext2_filsys fs) print_bg_rel_offset(fs, fs->group_desc[i].bg_block_bitmap, 0, first_block, last_block); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_BITMAP + if (fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_EXCLUDE_BITMAP) { + fputs(_(", Exclude bitmap at "), stdout); + print_number(fs->group_desc[i].bg_exclude_bitmap); + print_bg_rel_offset(fs, + fs->group_desc[i].bg_exclude_bitmap, 0, + first_block, last_block); + } + +#endif fputs(_(", Inode bitmap at "), stdout); print_number(fs->group_desc[i].bg_inode_bitmap); print_bg_rel_offset(fs, fs->group_desc[i].bg_inode_bitmap, 0, diff --git a/misc/e2image.c b/misc/e2image.c index 83c1cca..1db8069 100644 --- a/misc/e2image.c +++ b/misc/e2image.c @@ -536,6 +536,9 @@ static void write_raw_image_file(ext2_filsys fs, int fd, int scramble_flag) } } else { if ((inode.i_flags & EXT4_EXTENTS_FL) || +#ifdef EXT2FS_SNAPSHOT_HUGE_SNAPSHOT + (inode.i_flags & EXT4_SNAPFILE_FL) || +#endif inode.i_block[EXT2_IND_BLOCK] || inode.i_block[EXT2_DIND_BLOCK] || inode.i_block[EXT2_TIND_BLOCK]) { diff --git a/misc/lsattr.c b/misc/lsattr.c index 15b17ad..143aa78 100644 --- a/misc/lsattr.c +++ b/misc/lsattr.c @@ -70,7 +70,11 @@ static int generation_opt; static void usage(void) { +#ifdef EXT2FS_SNAPSHOT_CTL_OLD + fprintf(stderr, _("Usage: %s [-XRVadlv] [files...]\n"), program_name); +#else fprintf(stderr, _("Usage: %s [-RVadlv] [files...]\n"), program_name); +#endif exit(1); } @@ -78,8 +82,18 @@ static int list_attributes (const char * name) { unsigned long flags; unsigned long generation; +#ifdef EXT2FS_SNAPSHOT_CTL + int ret; + + if (pf_options & PFOPT_SNAPSHOT) + ret = fgetsnapflags (name, &flags); + else + ret = fgetflags (name, &flags); + if (ret == -1) { +#else if (fgetflags (name, &flags) == -1) { +#endif com_err (program_name, errno, _("While reading flags on %s"), name); return -1; @@ -169,9 +183,26 @@ int main (int argc, char ** argv) #endif if (argc && *argv) program_name = *argv; +#ifdef EXT2FS_SNAPSHOT_CTL + i = strlen(program_name); + if (i >= 6 && !strcmp(program_name + i - 6, "lssnap")) + pf_options |= PFOPT_SNAPSHOT; + +#endif +#ifdef EXT2FS_SNAPSHOT_CTL_OLD + while ((c = getopt (argc, argv, "XRVadlv")) != EOF) +#else while ((c = getopt (argc, argv, "RVadlv")) != EOF) +#endif switch (c) { +#ifdef EXT2FS_SNAPSHOT_CTL_OLD + case 'X': + /* for backward compatibility with next3 */ + pf_options &= ~PFOPT_SNAPSHOT; + pf_options |= PFOPT_SNAPSHOT_X; + break; +#endif case 'R': recursive = 1; break; @@ -185,7 +216,11 @@ int main (int argc, char ** argv) dirs_opt = 1; break; case 'l': +#ifdef EXT2FS_SNAPSHOT_CTL + pf_options |= PFOPT_LONG; +#else pf_options = PFOPT_LONG; +#endif break; case 'v': generation_opt = 1; diff --git a/misc/mke2fs.c b/misc/mke2fs.c index b994e1e..1ecc313 100644 --- a/misc/mke2fs.c +++ b/misc/mke2fs.c @@ -382,6 +382,12 @@ static void write_inode_tables(ext2_filsys fs, int lazy_flag, int itable_zeroed) /* The kernel doesn't need to zero the itable blocks */ fs->group_desc[i].bg_flags |= EXT2_BG_INODE_ZEROED; ext2fs_group_desc_csum_set(fs, i); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + if (fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_EXCLUDE_INODE) + /* zero the designated exclude bitmap block */ + num++; +#endif } retval = ext2fs_zero_blocks(fs, blk, num, &blk, &num); if (retval) { @@ -844,6 +850,9 @@ static void parse_extended_opts(struct ext2_super_block *param, static __u32 ok_features[3] = { /* Compat */ EXT3_FEATURE_COMPAT_HAS_JOURNAL | +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + EXT2_FEATURE_COMPAT_EXCLUDE_INODE | +#endif EXT2_FEATURE_COMPAT_RESIZE_INODE | EXT2_FEATURE_COMPAT_DIR_INDEX | EXT2_FEATURE_COMPAT_EXT_ATTR, @@ -855,6 +864,9 @@ static __u32 ok_features[3] = { EXT4_FEATURE_INCOMPAT_FLEX_BG, /* R/O compat */ EXT2_FEATURE_RO_COMPAT_LARGE_FILE| +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT| +#endif EXT4_FEATURE_RO_COMPAT_HUGE_FILE| EXT4_FEATURE_RO_COMPAT_DIR_NLINK| EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE| @@ -990,6 +1002,12 @@ static char **parse_fs_type(const char *fs_type, ext_type = "ext2"; else if (!strcmp(program_name, "mke3fs")) ext_type = "ext3"; +#ifdef EXT2FS_SNAPSHOT_BIG_JOURNAL + else if (!strcmp(program_name, "mkfs.next3")) + ext_type = "ext3"; + else if (!strcmp(program_name, "mkfs.next4")) + ext_type = "ext4"; +#endif else if (progname) { ext_type = strrchr(progname, '/'); if (ext_type) @@ -1262,6 +1280,27 @@ static void PRS(int argc, char *argv[]) if (!strcmp(program_name, "mkfs.ext3") || !strcmp(program_name, "mke3fs")) journal_size = -1; +#ifdef EXT2FS_SNAPSHOT_BIG_JOURNAL + + /* If called as mkfs.next3/next4: */ + if (!strcmp(program_name, "mkfs.next3") || + !strcmp(program_name, "mkfs.next4")) { + /* 1. create a big journal */ + journal_size = -NEXT3_MAX_COW_CREDITS; + /* 2. use system page size as block size */ + blocksize = sys_page_size; + fs_param.s_log_block_size = + int_log2(blocksize >> EXT2_MIN_BLOCK_LOG_SIZE); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + /* 3. create exclude inode */ + edit_feature("exclude_inode", &fs_param.s_feature_compat); +#endif +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT + /* 4. enable snapshot support */ + edit_feature("has_snapshot", &fs_param.s_feature_compat); +#endif + } +#endif } while ((c = getopt (argc, argv, @@ -2282,6 +2321,17 @@ int main (int argc, char *argv[]) exit(1); } } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + if (fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_EXCLUDE_INODE) { + retval = ext2fs_create_exclude_inode(fs, EXCLUDE_CREATE); + if (retval) { + com_err("ext2fs_create_exclude_inode", retval, + _("while reserving blocks for exclude bitmap")); + exit(1); + } + } +#endif } if (journal_device) { diff --git a/misc/tune2fs.c b/misc/tune2fs.c index e5722a5..9064402 100644 --- a/misc/tune2fs.c +++ b/misc/tune2fs.c @@ -118,6 +118,9 @@ static void usage(void) static __u32 ok_features[3] = { /* Compat */ EXT3_FEATURE_COMPAT_HAS_JOURNAL | +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + EXT2_FEATURE_COMPAT_EXCLUDE_INODE | +#endif EXT2_FEATURE_COMPAT_DIR_INDEX, /* Incompat */ EXT2_FEATURE_INCOMPAT_FILETYPE | @@ -125,6 +128,9 @@ static __u32 ok_features[3] = { EXT4_FEATURE_INCOMPAT_FLEX_BG, /* R/O compat */ EXT2_FEATURE_RO_COMPAT_LARGE_FILE | +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT| +#endif EXT4_FEATURE_RO_COMPAT_HUGE_FILE| EXT4_FEATURE_RO_COMPAT_DIR_NLINK| EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE| @@ -135,6 +141,9 @@ static __u32 ok_features[3] = { static __u32 clear_ok_features[3] = { /* Compat */ EXT3_FEATURE_COMPAT_HAS_JOURNAL | +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + EXT2_FEATURE_COMPAT_EXCLUDE_INODE | +#endif EXT2_FEATURE_COMPAT_RESIZE_INODE | EXT2_FEATURE_COMPAT_DIR_INDEX, /* Incompat */ @@ -142,6 +151,9 @@ static __u32 clear_ok_features[3] = { EXT4_FEATURE_INCOMPAT_FLEX_BG, /* R/O compat */ EXT2_FEATURE_RO_COMPAT_LARGE_FILE | +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT| +#endif EXT4_FEATURE_RO_COMPAT_HUGE_FILE| EXT4_FEATURE_RO_COMPAT_DIR_NLINK| EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE| @@ -249,23 +261,170 @@ no_valid_journal: free(journal_path); } +#define blk64_t blk_t +#define ext2fs_block_iterate3 \ + ext2fs_block_iterate2 +#define ext2fs_unmark_block_bitmap2 \ + ext2fs_unmark_block_bitmap +#define ext2fs_group_of_blk2 \ + ext2fs_group_of_blk +#define ext2fs_bg_free_blocks_count_set(fs, group, count) \ + fs->group_desc[group].bg_free_blocks_count = (count) +#define ext2fs_bg_free_blocks_count(fs, group) \ + fs->group_desc[group].bg_free_blocks_count +#define ext2fs_free_blocks_count_add(sb, count) \ + sb->s_free_blocks_count += (count) +#define ext2fs_group_desc(fs, gdp, grp) \ + (gdp)+(grp) + /* Helper function for remove_journal_inode */ -static int release_blocks_proc(ext2_filsys fs, blk_t *blocknr, - int blockcnt EXT2FS_ATTR((unused)), +static int release_blocks_proc(ext2_filsys fs, blk64_t *blocknr, + e2_blkcnt_t blockcnt EXT2FS_ATTR((unused)), + blk64_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), void *private EXT2FS_ATTR((unused))) { - blk_t block; + blk64_t block; int group; block = *blocknr; - ext2fs_unmark_block_bitmap(fs->block_map, block); - group = ext2fs_group_of_blk(fs, block); - fs->group_desc[group].bg_free_blocks_count++; + ext2fs_unmark_block_bitmap2(fs->block_map, block); + group = ext2fs_group_of_blk2(fs, block); + ext2fs_bg_free_blocks_count_set(fs, group, ext2fs_bg_free_blocks_count(fs, group) + 1); ext2fs_group_desc_csum_set(fs, group); - fs->super->s_free_blocks_count++; + ext2fs_free_blocks_count_add(fs->super, 1); return 0; } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE +/* + * Remove a special inode from the filesystem: + * - resize inode, @nlink = 0 + * - exclude inode, @nlink = 0 + * - snapshot inodes, @nlink = 1 (snapshots directory) + */ +static void remove_special_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, int nlink) +{ + int retval = ext2fs_read_bitmaps(fs); + if (retval) { + com_err(program_name, retval, + _("while reading bitmaps")); + exit(1); + } + retval = ext2fs_block_iterate3(fs, ino, + BLOCK_FLAG_READ_ONLY, NULL, + release_blocks_proc, NULL); + if (retval) { + com_err(program_name, retval, + _("while clearing inode")); + exit(1); + } + if (nlink) { + /* reset truncated inode */ + inode->i_size = 0; + inode->i_size_high = 0; + inode->i_blocks = 0; + memset(inode->i_block, 0, sizeof(inode->i_block)); + } else { + /* clear unlinked inode */ + memset(inode, 0, sizeof(*inode)); + } + ext2fs_mark_bb_dirty(fs); + fs->flags &= ~EXT2_FLAG_SUPER_ONLY; +} + +#endif +#ifdef EXT2FS_SNAPSHOT_CLEANUP +/* + * Discard snapshots list (free all snapshot blocks) + */ +static void discard_snapshot_list(ext2_filsys fs) +{ + struct ext2_super_block *sb = fs->super; + struct ext2_inode inode; + ext2_ino_t ino = sb->s_snapshot_list; + errcode_t retval; + int i = 0; + + if (!ino) + /* no snapshot list, but maybe active snapshot exists? */ + ino = sb->s_snapshot_inum; + if (ino) + fputs(_("Discarding snapshots: "), stderr); + + while (ino) { + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) { + com_err(program_name, retval, + _("while reading snapshot inode %u"), + ino); + exit(1); + } + + remove_special_inode(fs, ino, &inode, 1); + + retval = ext2fs_write_inode(fs, ino, &inode); + if (retval) { + com_err(program_name, retval, + _("while writing snapshot inode %u"), + ino); + exit(1); + } + + fprintf(stderr, _("%u,"), inode.i_generation); + ino = inode.i_next_snapshot; + i++; + } + + if (i > 0) { + sb->s_snapshot_inum = 0; + sb->s_snapshot_id = 0; + sb->s_snapshot_r_blocks_count = 0; + sb->s_snapshot_list = 0; + fputs(_("done\n"), stderr); + } + + /* no snapshots, so no snapshot problems to fix */ + sb->s_flags &= ~EXT2_FLAGS_FIX_SNAPSHOT; + fs->flags &= ~EXT2_FLAG_SUPER_ONLY; + ext2fs_mark_super_dirty(fs); +} + +#endif +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE +/* + * Remove the exclude inode from the filesystem + */ +static void remove_exclude_inode(ext2_filsys fs) +{ + struct ext2_inode inode; + ino_t ino = EXT2_EXCLUDE_INO; + errcode_t retval; + + /* clear fix_exclude flag */ + fs->super->s_flags &= ~EXT2_FLAGS_FIX_EXCLUDE; + fs->flags &= ~EXT2_FLAG_SUPER_ONLY; + ext2fs_mark_super_dirty(fs); + + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) { + com_err(program_name, retval, + _("while reading exclude inode")); + exit(1); + } + + remove_special_inode(fs, ino, &inode, 0); + + retval = ext2fs_write_inode(fs, ino, &inode); + if (retval) { + com_err(program_name, retval, + _("while writing exclude inode")); + exit(1); + } +} + +#endif /* * Remove the journal inode from the filesystem */ @@ -281,6 +440,11 @@ static void remove_journal_inode(ext2_filsys fs) _("while reading journal inode")); exit(1); } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + if (ino == EXT2_JOURNAL_INO) + remove_special_inode(fs, ino, &inode, 0); + else +#else if (ino == EXT2_JOURNAL_INO) { retval = ext2fs_read_bitmaps(fs); if (retval) { @@ -288,9 +452,9 @@ static void remove_journal_inode(ext2_filsys fs) _("while reading bitmaps")); exit(1); } - retval = ext2fs_block_iterate(fs, ino, - BLOCK_FLAG_READ_ONLY, NULL, - release_blocks_proc, NULL); + retval = ext2fs_block_iterate3(fs, ino, + BLOCK_FLAG_READ_ONLY, NULL, + release_blocks_proc, NULL); if (retval) { com_err(program_name, retval, _("while clearing journal inode")); @@ -300,6 +464,7 @@ static void remove_journal_inode(ext2_filsys fs) ext2fs_mark_bb_dirty(fs); fs->flags &= ~EXT2_FLAG_SUPER_ONLY; } else +#endif inode.i_flags &= ~EXT2_IMMUTABLE_FL; retval = ext2fs_write_inode(fs, ino, &inode); if (retval) { @@ -338,6 +503,34 @@ static void request_fsck_afterwards(ext2_filsys fs) printf(_("(and reboot afterwards!)\n")); } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE +static int verify_clean_fs(ext2_filsys fs, int compat, unsigned int mask, + int on) +{ + struct ext2_super_block *sb= fs->super; + + if ((mount_flags & EXT2_MF_MOUNTED) && + !(mount_flags & EXT2_MF_READONLY)) { + fprintf(stderr, _("The '%s' feature may only be " + "%s when the filesystem is\n" + "unmounted or mounted read-only.\n"), + e2p_feature2string(compat, mask), + on ? "set" : "cleared"); + exit(1); + } + if (sb->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_RECOVER) { + fprintf(stderr, _("The needs_recovery flag is set. " + "Please run e2fsck before %s\n" + "the '%s' flag.\n"), + on ? "setting" : "clearing", + e2p_feature2string(compat, mask)); + exit(1); + } + return 1; +} + +#endif /* * Update the feature set as provided by the user. */ @@ -356,11 +549,32 @@ static void update_feature_set(ext2_filsys fs, char *features) !((&sb->s_feature_compat)[(type)] & (mask))) #define FEATURE_CHANGED(type, mask) ((mask) & \ (old_features[(type)] ^ (&sb->s_feature_compat)[(type)])) +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE +#define FEATURE_ON_SAFE(compat, mask) \ + (FEATURE_ON(compat, mask) && verify_clean_fs(fs, compat, mask, 1)) +#define FEATURE_OFF_SAFE(compat, mask) \ + (FEATURE_OFF(compat, mask) && verify_clean_fs(fs, compat, mask, 0)) +#endif old_features[E2P_FEATURE_COMPAT] = sb->s_feature_compat; old_features[E2P_FEATURE_INCOMPAT] = sb->s_feature_incompat; old_features[E2P_FEATURE_RO_INCOMPAT] = sb->s_feature_ro_compat; +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT + /* disallow changing features when filesystem has snapshots */ + if (sb->s_feature_ro_compat & + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT) { + ok_features[E2P_FEATURE_COMPAT] = 0; + ok_features[E2P_FEATURE_INCOMPAT] = 0; + ok_features[E2P_FEATURE_RO_INCOMPAT] = + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT; + clear_ok_features[E2P_FEATURE_COMPAT] = 0; + clear_ok_features[E2P_FEATURE_INCOMPAT] = 0; + clear_ok_features[E2P_FEATURE_RO_INCOMPAT] = + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT; + } + +#endif if (e2p_edit_feature2(features, &sb->s_feature_compat, ok_features, clear_ok_features, &type_err, &mask_err)) { @@ -368,6 +582,14 @@ static void update_feature_set(ext2_filsys fs, char *features) fprintf(stderr, _("Invalid filesystem option set: %s\n"), features); +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT + else if (old_features[E2P_FEATURE_RO_INCOMPAT] & + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT) + fputs(_("The filesystem has snapshots. " + "Please clear the has_snapshot flag\n" + "before clearing/setting other filesystem flags.\n"), + stderr); +#endif else if (type_err & E2P_FEATURE_NEGATE_FLAG) fprintf(stderr, _("Clearing filesystem feature '%s' " "not supported.\n"), @@ -416,6 +638,78 @@ static void update_feature_set(ext2_filsys fs, char *features) sb->s_feature_compat &= ~EXT3_FEATURE_COMPAT_HAS_JOURNAL; } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + if (FEATURE_OFF_SAFE(E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_EXCLUDE_INODE)) { + remove_exclude_inode(fs); + /* clear exclude_bitmap along with exclude_inode */ + sb->s_feature_compat &= + ~EXT2_FEATURE_COMPAT_EXCLUDE_BITMAP; + } + + if (FEATURE_ON_SAFE(E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_EXCLUDE_INODE)) { + retval = ext2fs_create_exclude_inode(fs, EXCLUDE_CREATE); + if (retval) { + com_err(program_name, retval, + _("while creating exclude inode")); + exit(1); + } + } + +#endif +#ifdef EXT2FS_SNAPSHOT_CLEANUP + if (FEATURE_OFF_SAFE(E2P_FEATURE_RO_INCOMPAT, + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT)) { + discard_snapshot_list(fs); + if (sb->s_feature_compat & + EXT2_FEATURE_COMPAT_EXCLUDE_INODE) { + /* reset exclude bitmap blocks */ + retval = ext2fs_create_exclude_inode(fs, EXCLUDE_RESET); + if (retval) + /* cleanup bad exclude inode/bitmap */ + sb->s_feature_compat &= + ~(EXT2_FEATURE_COMPAT_EXCLUDE_INODE| + EXT2_FEATURE_COMPAT_EXCLUDE_BITMAP); + } + } + +#endif +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT + if (FEATURE_ON_SAFE(E2P_FEATURE_RO_INCOMPAT, + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT)) { + int big_journal = 0; + + if ((sb->s_feature_compat & + EXT3_FEATURE_COMPAT_HAS_JOURNAL)) { + /* update 'big_journal' flag */ + big_journal = (ext2fs_check_journal_size(fs) >= + NEXT3_MIN_JOURNAL_BLOCKS); + } else if (!journal_size || journal_size == -1) { + /* Create a big journal for Next3 */ + journal_size = -NEXT3_MAX_COW_CREDITS; + big_journal = 1; + } + + if (!big_journal) + fprintf(stderr, + _("Warning: journal size is not big enough.\n" + "For best operation of Next3, try re-creating " + "the journal with '-J big' before setting the " + "'has_snapshot' flag.\n")); + + /* allocate/reset exclude bitmap blocks */ + retval = ext2fs_create_exclude_inode(fs, EXCLUDE_CREATE); + if (!retval) + sb->s_feature_compat |= + EXT2_FEATURE_COMPAT_EXCLUDE_INODE; + else + fprintf(stderr, + _("Warning: failed to create exclude inode.\n" + "For best operation of Next3, try re-creating " + "the exclude inode before setting the " + "'has_snapshot' flag.\n")); + } + +#endif if (FEATURE_ON(E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_DIR_INDEX)) { if (!sb->s_def_hash_version) sb->s_def_hash_version = EXT2_HASH_HALF_MD4; diff --git a/misc/util.c b/misc/util.c index 2897937..9502082 100644 --- a/misc/util.c +++ b/misc/util.c @@ -222,6 +222,22 @@ void parse_journal_opts(const char *opts) journal_size = strtoul(arg, &p, 0); if (*p) journal_usage++; +#ifdef EXT2FS_SNAPSHOT_BIG_JOURNAL + } else if (strcmp(token, "big") == 0) { + /* Create a big journal for Next3 */ + journal_size = -NEXT3_MAX_COW_CREDITS; + continue; + } else if (strcmp(token, "bigger") == 0) { + /* Create a journal bigger than default */ + if (!arg) { + journal_usage++; + continue; + } + journal_size = -strtoul(arg, &p, 0); + if (*p) + journal_usage++; + continue; +#endif } else if (strcmp(token, "v1_superblock") == 0) { journal_flags |= EXT2_MKJOURNAL_V1_SUPER; continue; @@ -235,6 +251,10 @@ void parse_journal_opts(const char *opts) "\tis set off by an equals ('=') sign.\n\n" "Valid journal options are:\n" "\tsize=<journal size in megabytes>\n" +#ifdef EXT2FS_SNAPSHOT_BIG_JOURNAL + "\tbig (Next3 big journal size)\n" + "\tbigger=<X times bigger than default size>\n" +#endif "\tdevice=<journal device>\n\n" "The journal size must be between " "1024 and 10240000 filesystem blocks.\n\n"), stderr); @@ -263,6 +283,22 @@ unsigned int figure_journal_size(int size, ext2_filsys fs) return 0; } +#ifdef EXT2FS_SNAPSHOT_BIG_JOURNAL + if (size < -1) { + /* bigger journal requested */ + j_blocks = ext2fs_big_journal_size(-size, fs->super->s_blocks_count); + if (j_blocks < EXT3_DEF_JOURNAL_BLOCKS*(-size)) { + fputs(_("\nFilesystem too small for requested " + "journal size. "), stderr); + if (j_blocks < 0) { + fputs(_("Aborting.\n"), stderr); + exit(1); + } + fputs(_("Creating a smaller journal.\n"), stderr); + } + } + +#endif if (size > 0) { j_blocks = size * 1024 / (fs->blocksize / 1024); if (j_blocks < 1024 || j_blocks > 10240000) { diff --git a/next3 b/next3 new file mode 100755 index 0000000..fdddbd3 --- /dev/null +++ b/next3 @@ -0,0 +1,764 @@ +#!/bin/sh +# +# NEXT3(R) snapshot management script +# +# Written by Amir Goldstein <amir73il@users.sf.net>, 2008 +# Copyright (C) 2008-2010 CTERA Networks +# + +SCRIPT_NAME=$(basename $0) +SCRIPT_VER=1.0.13-5 +SCRIPT_DESC="$FSTYPE snapshot management script" +# fs/module name = script name +FSTYPE=$SCRIPT_NAME + +# generic e2fs progs +FSCK=e2fsck +MKFS=mke2fs +TUNEFS=tune2fs +LSSNAP="lssnap" +CHSNAP="chsnap" +CHATTR=chattr + +# override generic e2fs progs with installed e2fs.$FSTYPE progs in /sbin fodler +# and override those with private built e2fs.$FSTYPE progs in ./bin folder +for dir in /sbin ./bin ; do +if [ -f $dir/fsck.$FSTYPE ] ; then + FSCK=$dir/fsck.$FSTYPE +fi +if [ -f $dir/mkfs.$FSTYPE ] ; then + MKFS=$dir/mkfs.$FSTYPE +fi +if [ -f $dir/tunefs.$FSTYPE ] ; then + TUNEFS=$dir/tunefs.$FSTYPE +fi +if [ -f $dir/lssnap ] ; then + LSSNAP=$dir/lssnap +elif [ -f $dir/lsattr.$FSTYPE ] ; then + LSSNAP="$dir/lsattr.$FSTYPE -X" +fi +if [ -f $dir/chsnap ] ; then + CHSNAP=$dir/chsnap +elif [ -f $dir/chattr.$FSTYPE ] ; then + CHSNAP="$dir/chattr.$FSTYPE -X" +fi +if [ -f $dir/chattr.$FSTYPE ] ; then + CHATTR=$dir/chattr.$FSTYPE +fi +done + +if [ $# -ge 2 ] && [ $1 != help ] && [ $1_$2 != $1_help ] && [ $1 != config ] && \ + [ $1 != version ] && [ $1 != debug ] && [ $1 != delay ] && [ _$1 != _test ] && [ $1 != tests ] ; then + # parse [[next-mount@]snapshot-name] or $FSTYPE-mount or $FSTYPE-device argument + s=$( echo $2 | ( IFS=$IFS@ ; read a b ; echo $b ) ) + if [ -z $s ] ; then + # check if argument is a $FSTYPE mount point + dev=$( cat /proc/mounts | while read a b c d ; do ( [ _$b = _$2 ] && [ _$c = _$FSTYPE ] && echo $a ) ; done ) + if [ ! -z $dev ] ; then + # found $FSTYPE mount point + ROOTMNT=$2 + ROOTDEV=$dev + elif [ -b $2 ] || [ $1 = mkfs ] || [ $1 = on ] || [ $1 = off ] ; then + # argument is a $FSTYPE block device + ROOTDEV=$2 + ROOTMNT=$3 + else + # snapshot name was given without $FSTYPE mount point + s=$2 + fi + else + # snapshot name is prepended with $FSTYPE mount point + ROOTMNT=$( echo $2 | ( IFS=$IFS@ ; read a b ; echo $a ) ) + # find $FSTYPE block device from $FSTYPE mount point + ROOTDEV=$( cat /proc/mounts | while read a b c d ; do ( [ _$b = _$ROOTMNT ] && [ _$c = _$FSTYPE ] && echo $a ) ; done ) + if [ -z $ROOTDEV ] ; then + echo "$0 $1 $2: $ROOTMNT is not a mounted $FSTYPE filesystem!" + exit 1 + fi + # strip / from snapshot name + s=$( echo $s | ( IFS=$IFS/ ; read a b ; echo $a ) ) + fi +fi + +SYSDEBUGDIR=/sys/kernel/debug +SNAPDEBUGDIR=$SYSDEBUGDIR/$FSTYPE + +# default debug-level is old-debug-level +if [ ! -d $SNAPDEBUGDIR ] ; then + mount -t debugfs debugfs $SYSDEBUGDIR 2> /dev/null +fi +if [ -e $SNAPDEBUGDIR/snapshot-debug ] ; then + L=$(cat $SNAPDEBUGDIR/snapshot-debug 2> /dev/null) + if [ -z $2 ] ; then + l=$L + else + l=$2 + fi +else + L=0 + l=0 +fi +# check if test read is enabled +if [ -e $SNAPDEBUGDIR/test-read-delay-msec ] ; then + test_read=$(cat $SNAPDEBUGDIR/test-read-delay-msec) +else + test_read=0 +fi + +# read $FSTYPE block device and mount point from conf file +CONFFILE=.$FSTYPE.conf + +# look for snapshot config file in current then home directory +if [ -z $ROOTDEV ] && [ -z $ROOTMNT ] ; then + if [ -f ./$CONFFILE ] ; then + . ./$CONFFILE + elif [ -f ~/$CONFFILE ] ; then + . ~/$CONFFILE + elif [ $( mount -t $FSTYPE | grep -v noload | wc -l ) = 1 ] ; then + # exactly one mounted $FSTYPE filesystem found (mounted snapshots excluded) + ROOTDEV=$( mount -t $FSTYPE | grep -v noload | ( read a b c d ; echo $a ) ) + ROOTMNT=$( mount -t $FSTYPE | grep -v noload | ( read a b c d ; echo $c ) ) + fi +fi + +if [ ! -z $1 ] && [ $1 != help ] && [ $1_$2 != $1_help ] && [ $1 != config ] && \ + [ $1 != version ] && [ $1 != debug ] && [ $1 != delay ] && [ _$1 != _test ] && [ $1 != tests ] ; then + if [ $1 != mkfs ] && [ $1 != fsck ] && [ $1 != on ] && [ $1 != off ] ; then + if [ -z $ROOTMNT ] ; then + echo "$0: ambigous or missing $FSTYPE mount point" + echo "run '$0 config' to set default value" + exit 1 + fi + if [ ! -d $ROOTMNT ] ; then + echo "$0: bad $FSTYPE mount point $ROOTMNT" + exit 1 + fi + else + if [ -z $ROOTDEV ] ; then + echo "$0: ambigous or missing $FSTYPE block device" + echo "run '$0 config' to set default value" + exit 1 + fi + if [ ! -b $ROOTDEV ] && [ ! -f $ROOTDEV ] ; then + echo "$0: bad $FSTYPE block device $ROOTDEV" + exit 1 + fi + fi + export ROOTMNT + export ROOTDEV + S=$ROOTMNT@$s +fi + +if [ -z $SNAPDIR ] ; then + # directory inside $FSTYPE filesystem to store snapshot files + SNAPDIR=$ROOTMNT/.snapshots +fi +if [ -z $SNAPMNT ] ; then + # directory prefix for snapshot mount points + # snaphot mount points will be created as $SNAPMNT<snapshot-name> + # default to ZFS snapshot naming convention <filesystem>@<snapshot> + SNAPMNT=$ROOTMNT@ +fi +TESTDIR=test + +# See how we were called. +case "$1" in +# Snapshot global commands + help) + if [ -z $2 ] ; then + $0 + else + $0 $2 help + fi + ;; + version) + if [ $1_$2 = $1_help ] ; then + echo "$1: display $FSTYPE snapshot version." + exit 0 + fi + ver=$(cat $SNAPDEBUGDIR/snapshot-version) + echo snapshot-version = $ver + ;; + debug) + if [ $1_$2 = $1_help ] ; then + echo "$1: set snapshot debug level." + echo "debug levels: 0=none, 1=error, 2=warning, 3=info, 4=debug." + echo "usage: $0 $1 [debug-level]" + exit 0 + fi + if [ -e $SNAPDEBUGDIR/snapshot-debug ] ; then + echo $l > $SNAPDEBUGDIR/snapshot-debug + l=$(cat $SNAPDEBUGDIR/snapshot-debug) + fi + echo snapshot-debug = $l + ;; + delay) + if [ $1_$2 = $1_help ] ; then + echo "$1: set snapshot test delay." + echo "adds delay to specific snapshot operation." + echo "usage: $0 $1 <take|delete|cow|read|bitmap> [delay-sec] [delay-msec]" + exit 0 + fi + if [ -z $2 ] ; then + echo missing test name + $0 usage + fi + if [ ! -e $SNAPDEBUGDIR/test-$2-delay-msec ] ; then + echo invalid test name $2 + $0 usage + fi + if [ ! -z $3 ] ; then + if [ $3 -gt 60 ] || [ $3 -lt 0 ] ; then + echo valid range for test delay is 0-60 sec + else + if [ ! -z $4 ] ; then + ms=$3$4 + else + ms=${3}000 + fi + echo $ms > $SNAPDEBUGDIR/test-$2-delay-msec + fi + fi + ms=$(cat $SNAPDEBUGDIR/test-$2-delay-msec) + echo test-$2-delay = $ms msec + ;; + +# $FSTYPE filesystem commands + config) + if [ $1_$2 = $1_help ] ; then + echo "$1: configure or print default next filesystem parameters." + echo "creates configuration file $CONFFILE in current directory." + echo "usage: $0 config [$FSTYPE-device $FSTYPE-mount]" + exit 0 + fi + if [ ! -z $2 ] && [ ! -b $2 ] && [ ! -f $2 ] ; then + echo "$0 config: bad block device: $2" + exit 1 + fi + if [ ! -z $3 ] && [ ! -d $3 ] ; then + echo "$0 config: bad mount point: $3" + exit 1 + fi + if [ ! -z $2 ] && [ ! -z $3 ] ; then + echo ROOTDEV=$2 > $CONFFILE + echo ROOTMNT=$3 >> $CONFFILE + fi + test -f $CONFFILE && cat $CONFFILE + ;; + on) + if [ $1_$2 = $1_help ] ; then + echo "on: add the snapshot feature to an existing ext3 filesystem." + echo "after adding the snapshot feature to an ext3 filesystem, the filesystem" + echo "can no longer be mounted as ext3 read-write (only as $FSTYPE or ext3 read-only)." + echo "usage: $0 on [$FSTYPE-device]" + exit 0 + fi + $TUNEFS -e remount-ro -O has_snapshot $ROOTDEV || exit 1 + echo snapshot feature added to $ROOTDEV + echo . + ;; + off) + if [ $1_$2 = $1_help ] ; then + echo "off: remove the snapshot feature from a $FSTYPE filesystem." + echo "after removing the snapshot feature, all existing snapshots will be discarded" + echo "and the filesystem could be mounted as ext3 read-write again." + echo "usage: $0 off [$FSTYPE-device]" + exit 0 + fi + $TUNEFS -O ^has_snapshot $ROOTDEV || exit 1 + echo snapshot feature removed from $ROOTDEV + echo . + ;; + mkfs) + if [ $1_$2 = $1_help ] ; then + echo "$1: create a $FSTYPE filesystem." + echo "usage: $0 mkfs [$FSTYPE-device]" + exit 0 + fi + $MKFS -b 4096 -j -J big -O has_snapshot,exclude_inode $ROOTDEV || exit 1 + $TUNEFS -e remount-ro $ROOTDEV + echo $FSTYPE filesystem created on $ROOTDEV + echo . + ;; + + stat) + if [ $1_$2 = $1_help ] ; then + echo "stat: display status of $FSTYPE filesystem and all its snapshots." + echo "snapshot attributes are displayed in short format. to display" + echo "attributes in long format use the '$0 lsattr' command." + echo "usage: $0 stat [next-mount]" + exit 0 + fi + echo "Mounted $FSTYPE filesystem and snapshots:" + df -h $ROOTMNT $SNAPMNT* 2> /dev/null + echo . + echo "Snapshots list:" + echo "id inode attributes disk-usage mtime filename" + echo "---------------------------------------------" + for s in $( ls -vr $SNAPDIR/* ) ; do + ( echo "$( $LSSNAP -v $s ) " ; \ + echo "$( du -h $s ) " ; \ + echo "$( ls -lih --color=never $s ) " ) | \ + ( read id attr a ; read du b ; read ino c d e f g mm dd tt fname h ; \ + echo $id $ino $attr $du $mm $dd $tt $fname ) + done 2> /dev/null + echo . + ;; + lsattr) + if [ $1_$2 = $1_help ] ; then + echo "$1: display attributes of $FSTYPE snapshots in long format." + echo "usage: $0 $1" + exit 0 + fi + $LSSNAP -l $SNAPDIR + ;; + +# Snapshot control commands (single snapshot) + take) + if [ $1_$2 = $1_help ] ; then + echo "take: take a snapshot." + echo "usage: $0 take [[next-mount@]snapshot-name] (default=$FSTYPE-mount@GMT-date)" + exit 0 + fi + mkdir -p $SNAPDIR || exit 1 + $CHATTR +dx $SNAPDIR || exit 1 + if [ -z $s ] ; then + # default snapshot-name is the <date-time> in shadow copy format + s=$( date -u +GMT-%Y.%m.%d-%H.%M.%S ) + S=$ROOTMNT@$s + fi + if [ -f $SNAPDIR/$s ] ; then + echo "$0 take: snapshot $S already exists!" + exit 1 + fi + touch $SNAPDIR/$s 2> /dev/null + sync + $CHSNAP +S $SNAPDIR/$s || exit 1 + echo snapshot $S was created + echo . + ;; + delete) + if [ $1_$2 = $1_help ] ; then + echo "delete: umount a snapshot and mark it for deletion." + echo "any non-mounted snapshot can be marked for deletion" + echo "but some snapshot deletion is deferred to later time." + echo "usage: $0 delete [next-mount@]<snapshot-name>" + exit 0 + fi + if [ -z $s ] ; then + $0 + exit 1 + fi + if [ -f $SNAPDIR/$s ] ; then + $0 umount $s + $CHSNAP -S $SNAPDIR/$s || exit 1 + fi + echo snapshot $S is deleted + echo . + ;; + remove) + if [ $1_$2 = $1_help ] ; then + echo "remove: delete a snapshot permanently." + echo "this command will fail for active snapshot and snapshots" + echo "in use by older snapshots. try using the delete command." + echo "usage: $0 remove [next-mount@]<snapshot-name>" + exit 0 + fi + if [ -z $2 ] ; then + $0 + exit 1 + fi + if [ -f $SNAPDIR/$s ] ; then + $0 delete $s + rm -f $SNAPDIR/$s 2> /dev/null || exit 1 + fi + echo snapshot $S was removed + echo . + ;; + enable) + if [ $1_$2 = $1_help ] ; then + echo "enable: enable access to snapshot file." + echo "usage: $0 enable [next-mount@]<snapshot-name>" + exit 0 + fi + if [ -z $2 ] ; then + echo "$0 enable: snapshot name is expected!" + exit 1 + fi + if [ ! -f $SNAPDIR/$s ] ; then + echo "$0 enable: snapshot $S not found!" + exit 1 + fi + $CHSNAP +n $SNAPDIR/$s || exit 1 + ;; + disable) + if [ $1_$2 = $1_help ] ; then + echo "disable: disable access to snapshot file." + echo "usage: $0 disable [next-mount@]<snapshot-name>" + exit 0 + fi + if [ -z $2 ] ; then + echo "$0 disable: snapshot name is expected!" + exit 1 + fi + if [ ! -f $SNAPDIR/$s ] ; then + echo "$0 disable: snapshot $S not found!" + exit 1 + fi + if grep $SNAPMNT$s /proc/mounts ; then + echo "$0 disable: snapshot $S is mounted!" + exit 1 + fi + $CHSNAP -n $SNAPDIR/$s || exit 1 + ;; + +# Snapshot control commands (single or all snapshots) + mount) + if [ $1_$2 = $1_help ] ; then + echo "mount: mount a snapshot." + echo "usage: $0 mount [$FSTYPE-device $FSTYPE-mount]" + echo "usage: $0 mount [next-mount@]<snapshot-name>" + exit 0 + fi + if [ -z $s ] ; then + # mount $FSTYPE filesystem + MNTOPT="-o errors=remount-ro" + # uncomment to enable delayed allocation + #MNTOPT="$MNTOPT,nodelalloc" + if [ ! -z $ROOTDEV ] && [ -f $ROOTDEV ] ; then + MNTOPT="$MNTOPT,loop" + fi + # we may be called from mount.$FSTYPE so add -i + mount -t $FSTYPE -i $MNTOPT $ROOTDEV $ROOTMNT || exit 1 + echo $FSTYPE filesystem is mounted on $ROOTMNT + else + $0 enable $s || exit 1 + if grep $SNAPMNT$s /proc/mounts ; then + echo "$0 mount: snapshot $S is already mounted!" + exit 1 + fi + mkdir -p $SNAPMNT$s + mount -t $FSTYPE -r -o loop,noload $SNAPDIR/$s $SNAPMNT$s || exit 1 + echo snapshot $S is mounted + fi + echo . + ;; + umount) + if [ $1_$2 = $1_help ] ; then + echo "umount: umount a snapshot." + echo "usage: $0 umount [$FSTYPE-mount]" + echo "usage: $0 umount [next-mount@]<snapshot-name>" + exit 0 + fi + if [ -z $s ] ; then + # umount all snapshots + for s in $( ls $SNAPDIR/ 2> /dev/null ) ; do + $0 umount $s 2> /dev/null + done + # umount $FSTYPE filesystem + # we may be called from umount.$FSTYPE so add -i + umount -i -d $MNTOPT $ROOTMNT || exit 1 + echo $FSTYPE filesystem was unmounted + else + if [ -d $SNAPMNT$s ] ; then + umount -d $SNAPMNT$s || exit 1 + rmdir $SNAPMNT$s + fi + $0 disable $s + echo snapshot $S was unmounted + fi + echo . + ;; + +# Snapshot sanity tests + dump) + if [ $1_$2 = $1_help ] ; then + echo "dump: print a map of a snapshot file to kernel ring buffer." + echo "usage: $0 dump [next-mount@]<snapshot-name>" + exit 0 + fi + if [ -z $2 ] ; then + echo "$0 dump: snapshot name is expected!" + exit 1 + fi + if [ ! -f $SNAPDIR/$s ] ; then + echo "$0 dump: snapshot $S not found!" + exit 1 + fi + $CHATTR -d $SNAPDIR/$s + echo "map of snapshot $S was printed to kernel ring buffer." + echo "use dmesg to display kernel ring buffer." + echo . + ;; + fsck) + if [ $1_$2 = $1_help ] ; then + echo "fsck: run fsck on a snapshot or $FSTYPE filesystem." + echo "usage: $0 fsck [next-device]" + echo "usage: $0 fsck [next-mount@]<snapshot-name>" + exit 0 + fi + if [ -z $s ] ; then + echo "Fscking $FSTYPE filesystem on $ROOTDEV..." + $FSCK -pf -C 0 $ROOTDEV >&2 && \ + echo $FSTYPE filesystem on $ROOTDEV 'is healthy' || \ + echo $FSTYPE filesystem on $ROOTDEV 'has errors' + else + $0 enable $s || exit 1 + echo "Fscking snapshot $S..." + $FSCK -nf -C 0 $SNAPDIR/$s >&2 && \ + echo snapshot $S 'is healthy' || \ + echo snaphsot $S 'has errors' + if ! grep $SNAPMNT$s /proc/mounts ; then + $0 disable $s || exit 1 + fi + fi + echo . + ;; + + mktest) + if [ -d $ROOTMNT/A ] ; then + exit 0 + fi + mkdir $ROOTMNT/A + mkdir $ROOTMNT/B + mkdir $ROOTMNT/C + mkdir $ROOTMNT/D + mkdir $ROOTMNT/E + echo aligator > $ROOTMNT/A/a.txt + echo bizon > $ROOTMNT/B/b.txt + echo camel > $ROOTMNT/C/c.txt + # create non-snapshot dir in snapshots dir block group + mkdir -p $ROOTMNT/$TESTDIR + $CHATTR -x $ROOTMNT/$TESTDIR || exit 1 + ;; + rmtest) + rm -rf $ROOTMNT/? 2> /dev/null + rm -rf $ROOTMNT/$TESTDIR 2> /dev/null + ;; + lstest) + if [ $1_$2 = $1_help ] ; then + echo "lstest: list the content of test files in a snapshot or $FSTYPE filesystem." + echo "usage: $0 lstest [[next-mount@]snapshot-name] (default=$FSTYPE-mount)" + exit 0 + fi + if [ -z $s ] ; then + echo Files in file system: + d=$ROOTMNT + else + $0 mount $s || exit 1 + echo Files in snapshot $s: + d=$SNAPMNT$s + fi + if [ -d $d ] ; then + cd $d > /dev/null + grep -v xxx ?/* + test -e $TESTDIR/md5list && (cd $TESTDIR ; md5sum -c md5list || exit 1) + #find $d/ -maxdepth 1 -type f -print + cd - > /dev/null + echo . + fi + if [ ! -z $s ] ; then + $0 umount $s || exit 1 + fi + ;; + tests) + if [ $1_$2 = $1_help ] ; then + echo "tests: run snapshot sanity tests 1..N." + echo "usage: $0 tests [test-number] [delay-sec] [file-size-mb]" + echo "delay-sec: sleep between tests" + echo "file-size-mb: test file size in mega bytes (default = 1)" + exit 0 + fi + if [ -z $2 ] ; then + N=4 + else + N=$2 + fi + # disable read-ahead if test read is enabled + test $test_read = 0 || blockdev --setra 0 $ROOTDEV + for s in $( ls $SNAPDIR/ 2> /dev/null ) ; do + $0 delete $s 2> /dev/null + done + for s in $( ls $SNAPDIR/ 2> /dev/null ) ; do + $0 remove $s 2> /dev/null + done + for n in $( seq 0 $N ) ; do + $0 test $n $3 $4 || exit 1 + done + $0 lstest + for n in $( seq 1 $N ) ; do + $0 lstest $n + done + # skip fsck if non zero read delay or zero delay between tests + ( [ ${test_read}_ms = 0_ms ] && [ ${3}_ms != 0_ms ] ) || exit 0 + sleep 1 + if [ $N = 0 ] ; then + $0 umount $ROOTMNT || exit 1 + $0 fsck $ROOTDEV + $0 mount $ROOTDEV $ROOTMNT || exit 1 + else + for n in $( seq 1 $N ) ; do + $0 fsck $n 2> /dev/null + done + fi + ;; + test) + if [ $1_$2 = $1_help ] ; then + echo "test: run snapshot sanity test N." + echo "usage: $0 test [test-number] [delay-sec] [file-size-mb]" + echo "delay-sec: sleep before test" + echo "file-size-mb: test file size in mega bytes (default = 1)" + exit 0 + fi + if [ -z $2 ] ; then + n=1 + else + n=$2 + fi + if [ $n = 0 ] ; then + $0 rmtest + exit 0 + fi + $0 mktest + echo + echo Running snapshot test $n: + echo ------------------------ + if [ ! -z $3 ] ; then + sleep $3 # delay between tests + fi + if [ ! -z $4 ] ; then + M=$4 + else + M=1 + fi + F=${M}M + cd $ROOTMNT/$TESTDIR > /dev/null + NOTRUNC="conv=notrunc" + # uncomment the following line to run in-place write tests + INPLACE=$NOTRUNC + # uncomment the following line to run direct I/O write tests + DIRECT="oflag=direct" + TRUNCSIZE=4 + echo Appending $F zeros to $F.1 $DIRECT + # append writes to new allocated blocks + dd if=/dev/zero bs=1M count=$M of=$F.1 $NOTRUNC oflag=append $DIRECT status=noxfer || exit 1 + echo Writing $F random data to $n files + for i in $( seq 1 $n ) ; do + # 1st rewrite moves existing blocks to snapshot and allocates new blocks + fallocate -mrs -l 1M $F.$i 2>/dev/null || \ + dd if=/dev/urandom bs=1M count=$M of=$F.$i $INPLACE status=noxfer || exit 1 + # subsequent rewrites doesn't move blocks to snapshot + fallocate -mrs -l 1M $F.1 2>/dev/null || \ + dd if=/dev/urandom bs=1M count=$M of=$F.1 $INPLACE $DIRECT status=noxfer || exit 1 + done + for i in $( seq 1 $n ) ; do + md5sum $F.$i || exit 1 + done > md5list + cd - > /dev/null + $0 lstest || exit 1 + s=$n + $0 take $s || exit 1 + case "$n" in + 1) + echo Create test: + echo ------------ + echo 'Creating d.txt' + echo dodo > $ROOTMNT/D/d.txt + echo 'Creating e.txt' + echo emu > $ROOTMNT/E/e.txt + ;; + 2) + echo Write test: + echo ----------- + echo 'Writing b.txt (append)' + echo 'barracuda' >> $ROOTMNT/B/b.txt + echo 'Writing c.txt (truncate)' + echo 'crocodile' > $ROOTMNT/C/c.txt + ;; + 3) + echo Remove test: + echo ------------ + echo "Truncating c.txt (to size $TRUNCSIZE)" + truncate -s $TRUNCSIZE $ROOTMNT/C/c.txt + echo 'Removing d.txt' + rm $ROOTMNT/D/d.txt + ;; + 4) + echo Restore test: + echo ------------- + f=$( ls -v $SNAPDIR/ | head -n 1 ) + echo 'Restoring from snapshot' $f + if ! grep $SNAPMNT$f /proc/mounts ; then + $0 mount $f || exit 1 + fi + rm -rf $ROOTMNT/? + cp -R $SNAPMNT$f/? $ROOTMNT/ + $0 umount $f || exit 1 + ;; + 5) + echo Delete excluded test: + echo --------------------- + #echo Removing excluded files + #rm $ROOTMNT/*M + ;; + 6) + echo Delete reallocated test: + echo ------------------------ + #echo Removing /$F + #rm $ROOTMNT/$F + ;; + 7) + echo Shrink snapshots test: + echo --------------------- + $0 mount 1 + for f in 5 6 4 3 2 ; do + echo 'Deleting snapshot' $f + $0 delete $f 2> /dev/null + $0 stat + done + ;; + 8) + echo Merge snapshots test: + echo --------------------- + $0 umount 1 + for f in 7 ; do + echo 'Deleting snapshot' $f + $0 delete $f 2> /dev/null + $0 stat + done + ;; + esac || exit 1 + echo . + $0 lstest || exit 1 + $0 lstest $s || exit 1 + $0 stat + ;; + *) + echo "$SCRIPT_NAME v$SCRIPT_VER ($SCRIPT_DESC)" + echo "usage: $0 help [cmd]" + echo "usage: $0 version" + echo + echo "$FSTYPE filesystem maintenance commands:" + echo "usage: $0 {config|mount} [$FSTYPE-device $FSTYPE-mount]" + echo "usage: $0 {mkfs|fsck|on|off} [$FSTYPE-device]" + echo "usage: $0 {stat|umount} [$FSTYPE-mount]" + echo + echo "$FSTYPE snapshot commands:" + echo "usage: $0 take [[next-mount@]snapshot-name] (default=next-mount@GMT-date)" + echo "usage: $0 {mount|umount|delete|remove|fsck|dump} [next-mount@]<snapshot-name>" + echo + echo "<snapshot-name> parameter may be just the name of the snapshot or may be" + echo "given in ZFS style of <$FSTYPE-mount>@<snapshot-name>." + echo "in the former case, the $FSTYPE mount point is either read from /proc/mounts" + echo "(if only one $FSTYPE filesystem is mounted) or from $CONFFILE config file." + echo "in the latter case, the $FSTYPE mount point is parsed from the snapshot name prefix." + echo + echo "$FSTYPE debug and test commands:" + echo "usage: $0 debug [debug-level]" + echo "usage: $0 delay <take|delete|cow|read|bitmap> [delay-sec] [delay-msec]" + echo "usage: $0 {test|tests} [test-number] [delay-sec] [file-size-mb]" + echo + exit 1 +esac + +exit 0 diff --git a/resize/main.c b/resize/main.c index bb04d0b..f386ae3 100644 --- a/resize/main.c +++ b/resize/main.c @@ -452,6 +452,19 @@ int main (int argc, char ** argv) if (mount_flags & EXT2_MF_MOUNTED) { retval = online_resize_fs(fs, mtpt, &new_size, flags); } else { +#ifdef EXT2FS_SNAPSHOT_HAS_SNAPSHOT + /* do not offline resize a volume with active snapshot */ + if (!force && (fs->super->s_feature_ro_compat & + EXT4_FEATURE_RO_COMPAT_HAS_SNAPSHOT) && + fs->super->s_snapshot_inum) { + fprintf(stderr, + _("offline resize will damage next3 snapshots " + "on %s - Please mount the filesystem " + "for online resize.\n\n"), + device_name); + exit(1); + } +#endif if (!force && ((fs->super->s_lastcheck < fs->super->s_mtime) || (fs->super->s_state & EXT2_ERROR_FS) || ((fs->super->s_state & EXT2_VALID_FS) == 0))) { diff --git a/resize/resize2fs.c b/resize/resize2fs.c index 024f1cc..333d656 100644 --- a/resize/resize2fs.c +++ b/resize/resize2fs.c @@ -48,6 +48,9 @@ static errcode_t inode_scan_and_fix(ext2_resize_t rfs); static errcode_t inode_ref_fix(ext2_resize_t rfs); static errcode_t move_itables(ext2_resize_t rfs); static errcode_t fix_resize_inode(ext2_filsys fs); +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE +static errcode_t fix_exclude_inode(ext2_filsys fs); +#endif static errcode_t ext2fs_calculate_summary_stats(ext2_filsys fs); static errcode_t fix_sb_journal_backup(ext2_filsys fs); @@ -153,6 +156,12 @@ errcode_t resize_fs(ext2_filsys fs, blk_t *new_size, int flags, if (retval) goto errout; +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE + retval = fix_exclude_inode(rfs->new_fs); + if (retval) + goto errout; + +#endif retval = fix_sb_journal_backup(rfs->new_fs); if (retval) goto errout; @@ -1754,6 +1763,27 @@ errout: return retval; } +#ifdef EXT2FS_SNAPSHOT_EXCLUDE_INODE +/* + * Fix the exclude inode + */ +static errcode_t fix_exclude_inode(ext2_filsys fs) +{ + if (!(fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_EXCLUDE_INODE)) + return 0; + /* + * create_exclude_inode(): + * - updates exclude_blks for existing block groups + * - allocates exclude bitmap blocks for new block groups + * - doesn't free exclude bitmap blocks of deleted block group, + * so when resizing from large to small filesystem, + * it would be wise to remove the exclude inode beforehand. + */ + return ext2fs_create_exclude_inode(fs, EXCLUDE_CREATE); +} + +#endif /* * Finally, recalculate the summary information */ diff --git a/version.h b/version.h index 7005be1..a24a9e9 100644 --- a/version.h +++ b/version.h @@ -7,5 +7,5 @@ * file may be redistributed under the GNU Public License v2. */ -#define E2FSPROGS_VERSION "1.41.14" -#define E2FSPROGS_DATE "22-Dec-2010" +#define E2FSPROGS_VERSION "1.41.14-next3-1.0.13-7" +#define E2FSPROGS_DATE "24-May-2011"