/* -*- mode: c; c-basic-offset: 8; -*- * vim: noexpandtab sw=8 ts=8 sts=0: * * feature_refcount.c * * ocfs2 tune utility for enabling and disabling the refcount feature. * * Copyright (C) 2009 Oracle. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. */ #include #include #include #include #include #include #include #include "ocfs2/kernel-rbtree.h" #include "ocfs2-kernel/kernel-list.h" #include "ocfs2/ocfs2.h" #include "libocfs2ne.h" struct refcount_file { struct list_head list; uint64_t blkno; }; struct refcount_block { struct rb_node ref_node; uint64_t blkno; struct list_head files_list; }; struct disable_refcount_ctxt { errcode_t ret; struct tools_progress *prog; uint32_t more_clusters; uint32_t more_ebs; struct rb_root ref_blknos; int files_count; }; /* See if the recount_file rbtree has the given ref_blkno. */ static struct refcount_block* refcount_block_lookup(struct disable_refcount_ctxt *ctxt, uint64_t ref_blkno) { struct rb_node *p = ctxt->ref_blknos.rb_node; struct refcount_block *ref_blk; while (p) { ref_blk = rb_entry(p, struct refcount_block, ref_node); if (ref_blkno < ref_blk->blkno) p = p->rb_left; else if (ref_blkno > ref_blk->blkno) p = p->rb_right; else return ref_blk; } return NULL; } static void refcount_block_insert(struct disable_refcount_ctxt *ctxt, struct refcount_block *insert_rb) { struct rb_node **p = &ctxt->ref_blknos.rb_node; struct rb_node *parent = NULL; struct refcount_block *ref_blk = NULL; while (*p) { parent = *p; ref_blk = rb_entry(parent, struct refcount_block, ref_node); if (insert_rb->blkno < ref_blk->blkno) p = &(*p)->rb_left; else if (insert_rb->blkno > ref_blk->blkno) p = &(*p)->rb_right; else assert(0); /* Caller checked */ } rb_link_node(&insert_rb->ref_node, parent, p); rb_insert_color(&insert_rb->ref_node, &ctxt->ref_blknos); } static void empty_refcount_file_context(struct disable_refcount_ctxt *ctxt) { struct refcount_block *ref_blk; struct refcount_file *file; struct rb_node *node; struct list_head *p, *next; while ((node = rb_first(&ctxt->ref_blknos)) != NULL) { ref_blk = rb_entry(node, struct refcount_block, ref_node); list_for_each_safe(p, next, &ref_blk->files_list) { file = list_entry(p, struct refcount_file, list); list_del(&file->list); ocfs2_free(&file); } rb_erase(&ref_blk->ref_node, &ctxt->ref_blknos); ocfs2_free(&ref_blk); } } static int ocfs2_xattr_get_refcount_clusters(ocfs2_cached_inode *ci, char *xe_buf, uint64_t xe_blkno, struct ocfs2_xattr_entry *xe, char *value_buf, uint64_t value_blkno, void *value, int in_bucket, void *priv_data) { errcode_t ret = 0; uint32_t *clusters = priv_data; uint32_t cpos = 0, len, p_cluster, num_clusters; uint16_t ext_flags; struct ocfs2_xattr_value_root *xv; if (ocfs2_xattr_is_local(xe)) return 0; xv = (struct ocfs2_xattr_value_root *)value; len = xv->xr_clusters; while (len) { ret = ocfs2_xattr_get_clusters(ci->ci_fs, &xv->xr_list, value_blkno, value_buf, cpos, &p_cluster, &num_clusters, &ext_flags); if (ret) break; if (ext_flags & OCFS2_EXT_REFCOUNTED) *clusters += num_clusters; len -= num_clusters; cpos += num_clusters; } if (ret) return OCFS2_XATTR_ERROR; return 0; } static errcode_t ocfs2_find_refcounted_clusters(ocfs2_filesys *fs, uint64_t blkno, uint32_t *clusters) { errcode_t ret; ocfs2_cached_inode *ci = NULL; uint32_t cpos, len, p_cluster, num_clusters; uint16_t ext_flags; ret = ocfs2_read_cached_inode(fs, blkno, &ci); if (ret) goto out; *clusters = 0; cpos = 0; if (!(ci->ci_inode->i_dyn_features & OCFS2_INLINE_DATA_FL)) { len = ocfs2_clusters_in_bytes(fs, ci->ci_inode->i_size); while (len) { ret = ocfs2_get_clusters(ci, cpos, &p_cluster, &num_clusters, &ext_flags); if (ret) break; if (ext_flags & OCFS2_EXT_REFCOUNTED) *clusters += num_clusters; len -= num_clusters; cpos += num_clusters; } } if (ci->ci_inode->i_dyn_features & OCFS2_HAS_XATTR_FL) ret = ocfs2_xattr_iterate(ci, ocfs2_xattr_get_refcount_clusters, clusters); out: if (ci) ocfs2_free_cached_inode(fs, ci); return ret; } static errcode_t refcount_iterate(ocfs2_filesys *fs, struct ocfs2_dinode *di, void *user_data) { errcode_t ret = 0; uint32_t clusters; uint64_t blk_num; struct refcount_file *file = NULL; struct refcount_block *ref_blk = NULL; struct disable_refcount_ctxt *ctxt = user_data; uint32_t recs_per_eb = ocfs2_extent_recs_per_eb(fs->fs_blocksize); if (!S_ISREG(di->i_mode)) goto bail; if (di->i_flags & OCFS2_SYSTEM_FL) goto bail; if (!(di->i_dyn_features & OCFS2_HAS_REFCOUNT_FL)) goto bail; ret = ocfs2_find_refcounted_clusters(fs, di->i_blkno, &clusters); if (ret) goto bail; ret = ocfs2_malloc0(sizeof(struct refcount_file), &file); if (ret) goto bail; file->blkno = di->i_blkno; INIT_LIST_HEAD(&file->list); ref_blk = refcount_block_lookup(ctxt, di->i_refcount_loc); if (!ref_blk) { ret = ocfs2_malloc0(sizeof(struct refcount_block), &ref_blk); if (ret) goto bail; ref_blk->blkno = di->i_refcount_loc; INIT_LIST_HEAD(&ref_blk->files_list); refcount_block_insert(ctxt, ref_blk); } list_add_tail(&file->list, &ref_blk->files_list); ctxt->more_clusters += clusters; blk_num = (clusters + recs_per_eb - 1) / recs_per_eb; ctxt->more_ebs += ocfs2_clusters_in_blocks(fs, blk_num); ctxt->files_count++; tools_progress_step(ctxt->prog, 1); return 0; bail: if (file) ocfs2_free(&file); return ret; } static errcode_t find_refcounted_files(ocfs2_filesys *fs, struct disable_refcount_ctxt *ctxt) { errcode_t ret; uint32_t free_clusters = 0; ctxt->prog = tools_progress_start("Scanning filesystem", "scanning", 0); if (!ctxt->prog) { ret = TUNEFS_ET_NO_MEMORY; goto bail; } ret = tunefs_foreach_inode(fs, refcount_iterate, ctxt); if (ret) goto bail; ret = tunefs_get_free_clusters(fs, &free_clusters); if (ret) goto bail; verbosef(VL_APP, "We have %u clusters free, and need %u clusters to fill " "every sparse file and %u clusters for more extent " "blocks\n", free_clusters, ctxt->more_clusters, ctxt->more_ebs); if (free_clusters < (ctxt->more_clusters + ctxt->more_ebs)) ret = OCFS2_ET_NO_SPACE; bail: if (ctxt->prog) { tools_progress_stop(ctxt->prog); ctxt->prog = NULL; } return ret; } static int ocfs2_xattr_cow_refcount_clusters(ocfs2_cached_inode *ci, char *xe_buf, uint64_t xe_blkno, struct ocfs2_xattr_entry *xe, char *value_buf, uint64_t value_blkno, void *value, int in_bucket, void *priv_data) { errcode_t ret = 0; uint32_t cpos = 0, len, p_cluster, num_clusters; uint16_t ext_flags; struct ocfs2_xattr_value_root *xv; if (ocfs2_xattr_is_local(xe)) return 0; xv = (struct ocfs2_xattr_value_root *)value; len = xv->xr_clusters; while (len) { ret = ocfs2_xattr_get_clusters(ci->ci_fs, &xv->xr_list, value_blkno, value_buf, cpos, &p_cluster, &num_clusters, &ext_flags); if (ret) break; if (ext_flags & OCFS2_EXT_REFCOUNTED) { ret = ocfs2_refcount_cow_xattr(ci, xe_buf, xe_blkno, value_buf, value_blkno, xv, 0, xv->xr_clusters); break; } len -= num_clusters; cpos += num_clusters; } if (ret) return OCFS2_XATTR_ERROR; return 0; } static errcode_t refcount_one_file(ocfs2_filesys *fs, struct refcount_file *file) { errcode_t ret; ocfs2_cached_inode *ci = NULL; uint32_t len; ret = ocfs2_read_cached_inode(fs, file->blkno, &ci); if (ret) goto out; if (!(ci->ci_inode->i_dyn_features & OCFS2_INLINE_DATA_FL)) { len = ocfs2_clusters_in_bytes(fs, ci->ci_inode->i_size); ret = ocfs2_refcount_cow(ci, 0, len, UINT_MAX); if (ret) goto out; } if (ci->ci_inode->i_dyn_features & OCFS2_HAS_XATTR_FL) { ret = ocfs2_xattr_iterate(ci, ocfs2_xattr_cow_refcount_clusters, NULL); if (ret) goto out; } ci->ci_inode->i_dyn_features &= ~OCFS2_HAS_REFCOUNT_FL; ci->ci_inode->i_refcount_loc = 0; ret = ocfs2_write_cached_inode(fs, ci); out: if (ci) ocfs2_free_cached_inode(fs, ci); return ret; } static errcode_t free_refcount_tree(ocfs2_filesys *fs, struct refcount_block *ref_blk) { errcode_t ret; char *buf = NULL; struct ocfs2_refcount_block *rb = NULL; ret = ocfs2_malloc_block(fs->fs_io, &buf); if (ret) goto out; ret = ocfs2_read_refcount_block(fs, ref_blk->blkno, buf); if (ret) goto out; rb = (struct ocfs2_refcount_block *)buf; /* Now the refcount tree should be an empty leaf one. */ assert(!(rb->rf_flags & OCFS2_REFCOUNT_TREE_FL)); assert(!rb->rf_records.rl_used); ret = ocfs2_delete_refcount_block(fs, ref_blk->blkno); out: if (buf) ocfs2_free(&buf); return ret; } static errcode_t replace_refcounted_files(ocfs2_filesys *fs, struct disable_refcount_ctxt *ctxt) { errcode_t ret = 0; struct tools_progress *prog; struct refcount_block *ref_blk; struct refcount_file *file; struct rb_node *node; struct list_head *p, *next; prog = tools_progress_start("Replacing files", "replacing", ctxt->files_count); if (!prog) { ret = TUNEFS_ET_NO_MEMORY; goto out; } while ((node = rb_first(&ctxt->ref_blknos)) != NULL) { ref_blk = rb_entry(node, struct refcount_block, ref_node); list_for_each_safe(p, next, &ref_blk->files_list) { file = list_entry(p, struct refcount_file, list); ret = refcount_one_file(fs, file); if (ret) goto out; tools_progress_step(prog, 1); } ret = free_refcount_tree(fs, ref_blk); if (ret) goto out; rb_erase(&ref_blk->ref_node, &ctxt->ref_blknos); ocfs2_free(&ref_blk); } out: if (prog) tools_progress_stop(prog); return ret; } static int disable_refcount(ocfs2_filesys *fs, int flags) { errcode_t ret = 0; struct ocfs2_super_block *super = OCFS2_RAW_SB(fs->fs_super); struct disable_refcount_ctxt ctxt; struct tools_progress *prog = NULL; if (!ocfs2_refcount_tree(super)) { verbosef(VL_APP, "Refcount feature is not enabled; " "nothing to disable\n"); goto out; } if (!tools_interact("Disable the refcount feature on device " "\"%s\"? ", fs->fs_devname)) goto out; prog = tools_progress_start("Disabling refcount", "norefcount", 3); if (!prog) { ret = TUNEFS_ET_NO_MEMORY; tcom_err(ret, "while initializing the progress display"); goto out; } memset(&ctxt, 0, sizeof(ctxt)); ctxt.ref_blknos = RB_ROOT; ret = find_refcounted_files(fs, &ctxt); if (ret) { if (ret == OCFS2_ET_NO_SPACE) errorf("There is not enough space to fill all of " "the refcounted files on device \"%s\"\n", fs->fs_devname); else tcom_err(ret, "while trying to find refcounted files"); goto out_cleanup; } tools_progress_step(prog, 1); ret = replace_refcounted_files(fs, &ctxt); if (ret) { tcom_err(ret, "while trying to replace refcounted files on device " "\"%s\"", fs->fs_devname); goto out_cleanup; } tools_progress_step(prog, 1); OCFS2_CLEAR_INCOMPAT_FEATURE(super, OCFS2_FEATURE_INCOMPAT_REFCOUNT_TREE); tunefs_block_signals(); ret = ocfs2_write_super(fs); tunefs_unblock_signals(); if (ret) tcom_err(ret, "while writing out the superblock"); tools_progress_step(prog, 1); out_cleanup: empty_refcount_file_context(&ctxt); out: if (prog) tools_progress_stop(prog); return ret; } static int enable_refcount(ocfs2_filesys *fs, int flags) { errcode_t ret = 0; struct ocfs2_super_block *super = OCFS2_RAW_SB(fs->fs_super); struct tools_progress *prog; if (ocfs2_refcount_tree(super)) { verbosef(VL_APP, "Refcount feature is already enabled; " "nothing to enable\n"); goto out; } if (!tools_interact("Enable the refcount feature on " "device \"%s\"? ", fs->fs_devname)) goto out; prog = tools_progress_start("Enable refcount", "refcount", 1); if (!prog) { ret = TUNEFS_ET_NO_MEMORY; tcom_err(ret, "while initializing the progress display"); goto out; } OCFS2_SET_INCOMPAT_FEATURE(super, OCFS2_FEATURE_INCOMPAT_REFCOUNT_TREE); tunefs_block_signals(); ret = ocfs2_write_super(fs); tunefs_unblock_signals(); if (ret) tcom_err(ret, "while writing out the superblock"); tools_progress_step(prog, 1); tools_progress_stop(prog); out: return ret; } DEFINE_TUNEFS_FEATURE_INCOMPAT(refcount, OCFS2_FEATURE_INCOMPAT_REFCOUNT_TREE, TUNEFS_FLAG_RW | TUNEFS_FLAG_ALLOCATION | TUNEFS_FLAG_LARGECACHE, enable_refcount, disable_refcount); #ifdef DEBUG_EXE int main(int argc, char *argv[]) { return tunefs_feature_main(argc, argv, &refcount_feature); } #endif