/* * bmove.c --- Move blocks around to make way for a particular filesystem structure. * Adjusted to work correctly with bigalloc. * * Copyright (C) 1997 Theodore Ts'o, (C) 2014 Vitaliy Filippov. * * %Begin-Header% * This file may be redistributed under the terms of the GNU Library * General Public License, version 2. * %End-Header% */ #include #include #include "bmove.h" struct process_block_struct { ext2_ino_t ino; struct ext2_inode *inode; ext2fs_block_bitmap reserve; ext2fs_block_bitmap alloc_map; errcode_t error; char *buf; int add_dir; int flags; int changes; blk64_t last, last_new; }; static int process_block(ext2_filsys fs, blk64_t *block_nr, e2_blkcnt_t blockcnt, blk64_t ref_block, int ref_offset, void *priv_data) { struct process_block_struct *pb; errcode_t retval; int ret; blk64_t block, orig; pb = (struct process_block_struct *) priv_data; block = orig = *block_nr; ret = 0; /* * Let's see if this is one which we need to relocate */ if (ext2fs_test_block_bitmap2(pb->reserve, block)) { if (block & EXT2FS_CLUSTER_MASK(fs)) { // This means we've already moved the cluster and just need // to rewrite numbers of a non-first block in a cluster if (pb->last == block-1) { *block_nr = pb->last_new+1; pb->last++; pb->changes++; return BLOCK_CHANGED; } return 0; } if (blockcnt >= 0 && (pb->ino == EXT2_BAD_INO || pb->ino == EXT2_RESIZE_INO)) { // We obviously can't move bad blocks; and also the resize inode, because it must be in a predefined location // But we allow to move extent blocks (blockcnt == -1) and directory index blocks (blockcnt == -2) pb->error = EXT2_ET_BAD_BLOCK_IN_INODE_TABLE; return BLOCK_ABORT; } do { if (++block >= ext2fs_blocks_count(fs->super)) block = fs->super->s_first_data_block; if (block == orig) { pb->error = EXT2_ET_BLOCK_ALLOC_FAIL; return BLOCK_ABORT; } } while (ext2fs_test_block_bitmap2(pb->reserve, block) || ext2fs_test_block_bitmap2(pb->alloc_map, block)); retval = io_channel_read_blk64(fs->io, orig, EXT2FS_CLUSTER_RATIO(fs), pb->buf); if (retval) { pb->error = retval; return BLOCK_ABORT; } retval = io_channel_write_blk64(fs->io, block, EXT2FS_CLUSTER_RATIO(fs), pb->buf); if (retval) { pb->error = retval; return BLOCK_ABORT; } *block_nr = block; pb->last = orig; pb->last_new = block; ext2fs_block_alloc_stats2(fs, orig, -1); ext2fs_block_alloc_stats2(fs, block, +1); ret = BLOCK_CHANGED; pb->changes++; if (pb->flags & EXT2_BMOVE_DEBUG) printf("ino=%u, blockcnt=%lld, %llu->%llu\n", (unsigned) pb->ino, blockcnt, (unsigned long long) orig, (unsigned long long) block); } if (pb->add_dir) { retval = ext2fs_add_dir_block2(fs->dblist, pb->ino, block, blockcnt); if (retval) { pb->error = retval; ret |= BLOCK_ABORT; } } return ret; } errcode_t ext2fs_move_blocks(ext2_filsys fs, ext2fs_block_bitmap reserve, ext2fs_block_bitmap alloc_map, int flags) { ext2_ino_t ino; struct ext2_inode inode; errcode_t retval; struct process_block_struct pb; ext2_inode_scan scan; char *block_buf; retval = ext2fs_open_inode_scan(fs, 0, &scan); if (retval) return retval; pb.reserve = reserve; pb.error = 0; pb.alloc_map = alloc_map ? alloc_map : fs->block_map; pb.flags = flags; retval = ext2fs_get_array(3 + EXT2FS_CLUSTER_RATIO(fs), fs->blocksize, &block_buf); if (retval) return retval; pb.buf = block_buf + fs->blocksize * 3; pb.last = -1; pb.changes = 0; /* * If GET_DBLIST is set in the flags field, then we should * gather directory block information while we're doing the * block move. */ if (flags & EXT2_BMOVE_GET_DBLIST) { if (fs->dblist) { ext2fs_free_dblist(fs->dblist); fs->dblist = NULL; } retval = ext2fs_init_dblist(fs, 0); if (retval) return retval; } retval = ext2fs_get_next_inode(scan, &ino, &inode); if (retval) return retval; while (ino) { if ((inode.i_links_count == 0) || !ext2fs_inode_has_valid_blocks2(fs, &inode)) goto next; pb.ino = ino; pb.inode = &inode; pb.add_dir = (LINUX_S_ISDIR(inode.i_mode) && flags & EXT2_BMOVE_GET_DBLIST); if ((inode.i_flags & EXT4_INLINE_DATA_FL) && pb.add_dir) { /* inline data dir; update it too */ retval = ext2fs_add_dir_block2(fs->dblist, ino, 0, 0); if (retval) return retval; } retval = ext2fs_block_iterate3(fs, ino, 0, block_buf, process_block, &pb); if (retval) return retval; if (pb.error) return pb.error; // Adjust journal backup after changing the journal inode if (ino == EXT2_JOURNAL_INO && pb.changes) { retval = ext2fs_read_inode(fs, ino, &inode); if (retval) return retval; memcpy(fs->super->s_jnl_blocks, inode.i_block, EXT2_N_BLOCKS*4); fs->super->s_jnl_blocks[15] = inode.i_size_high; fs->super->s_jnl_blocks[16] = inode.i_size; fs->super->s_jnl_backup_type = EXT3_JNL_BACKUP_BLOCKS; ext2fs_mark_super_dirty(fs); } next: retval = ext2fs_get_next_inode(scan, &ino, &inode); if (retval == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE) goto next; } return 0; }