diff options
Diffstat (limited to 'lib/ext2fs/orphan.c')
-rw-r--r-- | lib/ext2fs/orphan.c | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/lib/ext2fs/orphan.c b/lib/ext2fs/orphan.c new file mode 100644 index 0000000..e25f20c --- /dev/null +++ b/lib/ext2fs/orphan.c @@ -0,0 +1,273 @@ +/* + * orphan.c --- utility function to handle orphan file + * + * Copyright (C) 2015 Jan Kara. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Library + * General Public License, version 2. + * %End-Header% + */ + +#include "config.h" +#include <string.h> + +#include "ext2_fs.h" +#include "ext2fsP.h" + +errcode_t ext2fs_truncate_orphan_file(ext2_filsys fs) +{ + struct ext2_inode inode; + errcode_t err; + ext2_ino_t ino = fs->super->s_orphan_file_inum; + + err = ext2fs_read_inode(fs, ino, &inode); + if (err) + return err; + + err = ext2fs_punch(fs, ino, &inode, NULL, 0, ~0ULL); + if (err) + return err; + + fs->flags &= ~EXT2_FLAG_SUPER_ONLY; + memset(&inode, 0, sizeof(struct ext2_inode)); + err = ext2fs_write_inode(fs, ino, &inode); + + ext2fs_clear_feature_orphan_file(fs->super); + ext2fs_clear_feature_orphan_present(fs->super); + ext2fs_mark_super_dirty(fs); + /* Need to update group descriptors as well */ + fs->flags &= ~EXT2_FLAG_SUPER_ONLY; + + return err; +} + +__u32 ext2fs_do_orphan_file_block_csum(ext2_filsys fs, ext2_ino_t ino, + __u32 gen, blk64_t blk, char *buf) +{ + int inodes_per_ob = ext2fs_inodes_per_orphan_block(fs); + __u32 crc; + + ino = ext2fs_cpu_to_le32(ino); + gen = ext2fs_cpu_to_le32(gen); + blk = ext2fs_cpu_to_le64(blk); + crc = ext2fs_crc32c_le(fs->csum_seed, (unsigned char *)&ino, + sizeof(ino)); + crc = ext2fs_crc32c_le(crc, (unsigned char *)&gen, sizeof(gen)); + crc = ext2fs_crc32c_le(crc, (unsigned char *)&blk, sizeof(blk)); + crc = ext2fs_crc32c_le(crc, (unsigned char *)buf, + inodes_per_ob * sizeof(__u32)); + + return ext2fs_cpu_to_le32(crc); +} + +struct mkorphan_info { + char *buf; + char *zerobuf; + blk_t num_blocks; + blk_t alloc_blocks; + blk64_t last_blk; + errcode_t err; + ext2_ino_t ino; + __u32 generation; +}; + +static int mkorphan_proc(ext2_filsys fs, + blk64_t *blocknr, + e2_blkcnt_t blockcnt, + blk64_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct mkorphan_info *oi = (struct mkorphan_info *)priv_data; + blk64_t new_blk; + errcode_t err; + + /* Can we just continue in currently allocated cluster? */ + if (blockcnt && + EXT2FS_B2C(fs, oi->last_blk) == EXT2FS_B2C(fs, oi->last_blk + 1)) { + new_blk = oi->last_blk + 1; + } else { + err = ext2fs_new_block2(fs, oi->last_blk, 0, &new_blk); + if (err) { + oi->err = err; + return BLOCK_ABORT; + } + ext2fs_block_alloc_stats2(fs, new_blk, +1); + oi->alloc_blocks++; + } + if (blockcnt >= 0) { + if (ext2fs_has_feature_metadata_csum(fs->super)) { + struct ext4_orphan_block_tail *tail; + + tail = ext2fs_orphan_block_tail(fs, oi->buf); + tail->ob_checksum = ext2fs_do_orphan_file_block_csum(fs, + oi->ino, oi->generation, new_blk, oi->buf); + } + err = io_channel_write_blk64(fs->io, new_blk, 1, oi->buf); + } else /* zerobuf is used to initialize new indirect blocks... */ + err = io_channel_write_blk64(fs->io, new_blk, 1, oi->zerobuf); + if (err) { + oi->err = err; + return BLOCK_ABORT; + } + oi->last_blk = new_blk; + *blocknr = new_blk; + if (blockcnt >= 0 && --oi->num_blocks == 0) + return BLOCK_CHANGED | BLOCK_ABORT; + return BLOCK_CHANGED; +} + +errcode_t ext2fs_create_orphan_file(ext2_filsys fs, blk_t num_blocks) +{ + struct ext2_inode inode; + ext2_ino_t ino = fs->super->s_orphan_file_inum; + errcode_t err; + char *buf = NULL, *zerobuf = NULL; + struct mkorphan_info oi; + struct ext4_orphan_block_tail *ob_tail; + + if (!ino) { + err = ext2fs_new_inode(fs, EXT2_ROOT_INO, LINUX_S_IFREG | 0600, + 0, &ino); + if (err) + return err; + ext2fs_inode_alloc_stats2(fs, ino, +1, 0); + ext2fs_mark_ib_dirty(fs); + } + + err = ext2fs_read_inode(fs, ino, &inode); + if (err) + return err; + if (EXT2_I_SIZE(&inode)) { + err = ext2fs_truncate_orphan_file(fs); + if (err) + return err; + } + + memset(&inode, 0, sizeof(struct ext2_inode)); + if (ext2fs_has_feature_extents(fs->super)) { + inode.i_flags |= EXT4_EXTENTS_FL; + err = ext2fs_write_inode(fs, ino, &inode); + if (err) + return err; + } + + err = ext2fs_get_mem(fs->blocksize, &buf); + if (err) + return err; + err = ext2fs_get_mem(fs->blocksize, &zerobuf); + if (err) + goto out; + memset(buf, 0, fs->blocksize); + memset(zerobuf, 0, fs->blocksize); + ob_tail = ext2fs_orphan_block_tail(fs, buf); + ob_tail->ob_magic = ext2fs_cpu_to_le32(EXT4_ORPHAN_BLOCK_MAGIC); + oi.num_blocks = num_blocks; + oi.alloc_blocks = 0; + oi.last_blk = 0; + oi.generation = inode.i_generation; + oi.ino = ino; + oi.buf = buf; + oi.zerobuf = zerobuf; + oi.err = 0; + err = ext2fs_block_iterate3(fs, ino, BLOCK_FLAG_APPEND, + 0, mkorphan_proc, &oi); + if (err) + goto out; + if (oi.err) { + err = oi.err; + goto out; + } + + /* Reread inode after blocks were allocated */ + err = ext2fs_read_inode(fs, ino, &inode); + if (err) + goto out; + ext2fs_iblk_set(fs, &inode, 0); + inode.i_atime = inode.i_mtime = + inode.i_ctime = fs->now ? fs->now : time(0); + inode.i_links_count = 1; + inode.i_mode = LINUX_S_IFREG | 0600; + ext2fs_iblk_add_blocks(fs, &inode, oi.alloc_blocks); + err = ext2fs_inode_size_set(fs, &inode, + (unsigned long long)fs->blocksize * num_blocks); + if (err) + goto out; + err = ext2fs_write_new_inode(fs, ino, &inode); + if (err) + goto out; + + fs->super->s_orphan_file_inum = ino; + ext2fs_set_feature_orphan_file(fs->super); + ext2fs_mark_super_dirty(fs); + /* Need to update group descriptors as well */ + fs->flags &= ~EXT2_FLAG_SUPER_ONLY; +out: + if (buf) + ext2fs_free_mem(&buf); + if (zerobuf) + ext2fs_free_mem(&zerobuf); + return err; +} + +/* + * Find reasonable size for orphan file. We choose orphan file size to be + * between 32 and 512 filesystem blocks and not more than 1/4096 of the + * filesystem unless it is really small. + */ +e2_blkcnt_t ext2fs_default_orphan_file_blocks(ext2_filsys fs) +{ + __u64 num_blocks = ext2fs_blocks_count(fs->super); + e2_blkcnt_t blks = 512; + + if (num_blocks < 128 * 1024) + blks = 32; + else if (num_blocks < 2 * 1024 * 1024) + blks = num_blocks / 4096; + return (blks + EXT2FS_CLUSTER_MASK(fs)) & ~EXT2FS_CLUSTER_MASK(fs); +} + +static errcode_t ext2fs_orphan_file_block_csum(ext2_filsys fs, ext2_ino_t ino, + blk64_t blk, char *buf, + __u32 *crcp) +{ + struct ext2_inode inode; + errcode_t retval; + + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) + return retval; + *crcp = ext2fs_do_orphan_file_block_csum(fs, ino, inode.i_generation, + blk, buf); + return 0; +} + +errcode_t ext2fs_orphan_file_block_csum_set(ext2_filsys fs, ext2_ino_t ino, + blk64_t blk, char *buf) +{ + struct ext4_orphan_block_tail *tail; + + if (!ext2fs_has_feature_metadata_csum(fs->super)) + return 0; + + tail = ext2fs_orphan_block_tail(fs, buf); + return ext2fs_orphan_file_block_csum(fs, ino, blk, buf, + &tail->ob_checksum); +} + +int ext2fs_orphan_file_block_csum_verify(ext2_filsys fs, ext2_ino_t ino, + blk64_t blk, char *buf) +{ + struct ext4_orphan_block_tail *tail; + __u32 crc; + errcode_t retval; + + if (!ext2fs_has_feature_metadata_csum(fs->super)) + return 1; + retval = ext2fs_orphan_file_block_csum(fs, ino, blk, buf, &crc); + if (retval) + return 0; + tail = ext2fs_orphan_block_tail(fs, buf); + return ext2fs_le32_to_cpu(tail->ob_checksum) == crc; +} |