[Ocfs2-tools-devel] [PATCH 11/12] tunefs.ocfs2: Enable and disable the metaecc feature.

Joel Becker joel.becker at oracle.com
Mon Dec 29 19:23:47 PST 2008


The metaecc feature requires a bit of work.

To disable it is easy; just clear the feature bit.  All structures left
behind are fully compatible with filesystems that don't know about
metaecc.

Enabling it is hard work.  We must run though every block in the
filesystem and compute its checksum and error correction values.  But
even that's not all.  Directory blocks need trailers to store the ECC
values, and the directories might not have them yet.  More on the
trailers below.

The preparation step runs through every inode in the filesystem.  If the
inode does not need directory trailers - that is, it is not a directory,
is an inline directory, or already has trailers - we add all of its
metadata blocks to an rbtree.  This caches the blocks for later
re-write.  We don't want to have to re-read them in the writeout step.
If the inode is a directory requiring trailers, we call
tunefs_prepare_dir_trailer() to determine how much space it needs.  At
the end of this step, we have a list of all directories requiring
trailers, and we know whether we have enough space to do the work.

Next, we install all the trailers.  When this completes, we have a valid
filesystem with trailers.  For each directory, we add its blocks to the
rbtree after we've added the trailers.

Finally, we run through the rbtree and compute the ECC values for each
block.  When this is finished, we can write the META_ECC feature bit to
the superblock.

We add two functions to handle the trailers.

tunefs_prepare_dir_trailer() will walk a directory and check all of its
dirblocks.  If the directory needs trailers, a tunefs_trailer_context is
returned.  This contains all the information needed to add trailers to
this directory.  The caller can use this structure to determine how much
allocation is needed.  The caller passes this structure to
tunefs_install_dir_trailer() to actually write out the trailers for this
directory.

We add a tunefs-in-progress flag for adding dirblock trailers.

When tunefs.ocfs2 is adding directory block trailers, it cannot do it
atomically.  For some directory blocks, dirents have to be moved out of
the way to make room for the trailer.  These dirents are shifted to new
directory blocks.  When tunefs writes out the changes, it does so in the
following order:

1) Write out the new blocks
2) Update the directory's i_size to include the new blocks
3) Write the old blocks with trailers in place.

If tunefs crashes after (1), the new blocks are just unused allocation.
We're safe.  If tunefs crashes after (3), we have the directory with
trailers correctly installed.  However, if tunefs crashes after (2), the
old dirents are in the old blocks, but the shifted copies are in the new
blocks.  This leaves duplicate dirents.  fsck.ocfs2 can fix them, but we
need to set OCFS2_TUNEFS_INPROG_DIR_TRAILER to make sure fsck is run.

The alternative order, writing out the old blocks before the new ones,
leaves missing dirents in the case of a crash.  That's not a good plan.

Signed-off-by: Joel Becker <joel.becker at oracle.com>
---
 include/ocfs2-kernel/ocfs2_fs.h |    3 +
 libocfs2/feature_string.c       |    4 +
 tunefs.ocfs2/Makefile           |    1 +
 tunefs.ocfs2/feature_metaecc.c  | 1272 +++++++++++++++++++++++++++++++++++++++
 tunefs.ocfs2/op_features.c      |    2 +
 5 files changed, 1282 insertions(+), 0 deletions(-)
 create mode 100644 tunefs.ocfs2/feature_metaecc.c

diff --git a/include/ocfs2-kernel/ocfs2_fs.h b/include/ocfs2-kernel/ocfs2_fs.h
index bd6c941..cbea655 100644
--- a/include/ocfs2-kernel/ocfs2_fs.h
+++ b/include/ocfs2-kernel/ocfs2_fs.h
@@ -185,6 +185,9 @@
  */
 #define OCFS2_TUNEFS_INPROG_REMOVE_SLOT		0x0001	/* Removing slots */
 
+/* Adding directory block trailers */
+#define OCFS2_TUNEFS_INPROG_DIR_TRAILER		0x0002
+
 /*
  * Flags on ocfs2_dinode.i_flags
  */
diff --git a/libocfs2/feature_string.c b/libocfs2/feature_string.c
index 56618db..129402d 100644
--- a/libocfs2/feature_string.c
+++ b/libocfs2/feature_string.c
@@ -195,6 +195,10 @@ static struct tunefs_flag_name ocfs2_tunefs_flag_names[] = {
 		.tfn_flag = OCFS2_TUNEFS_INPROG_REMOVE_SLOT,
 	},
 	{
+		.tfn_name = "DirTrailer",
+		.tfn_flag = OCFS2_TUNEFS_INPROG_DIR_TRAILER,
+	},
+	{
 		.tfn_name = NULL,
 	},
 };
diff --git a/tunefs.ocfs2/Makefile b/tunefs.ocfs2/Makefile
index 6ba3d3f..b693e9c 100644
--- a/tunefs.ocfs2/Makefile
+++ b/tunefs.ocfs2/Makefile
@@ -21,6 +21,7 @@ OCFS2NE_FEATURES =			\
 	feature_extended_slotmap	\
 	feature_inline_data		\
 	feature_local			\
+	feature_metaecc			\
 	feature_sparse_files		\
 	feature_unwritten_extents
 
diff --git a/tunefs.ocfs2/feature_metaecc.c b/tunefs.ocfs2/feature_metaecc.c
new file mode 100644
index 0000000..da9620e
--- /dev/null
+++ b/tunefs.ocfs2/feature_metaecc.c
@@ -0,0 +1,1272 @@
+/* -*- mode: c; c-basic-offset: 8; -*-
+ * vim: noexpandtab sw=8 ts=8 sts=0:
+ *
+ * feature_metaecc.c
+ *
+ * ocfs2 tune utility for enabling and disabling the metaecc feature.
+ *
+ * Copyright (C) 2004, 2008 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <assert.h>
+
+#include "ocfs2-kernel/kernel-list.h"
+#include "ocfs2/kernel-rbtree.h"
+#include "ocfs2/ocfs2.h"
+
+#include "libocfs2ne.h"
+
+
+
+
+/* A dirblock we have to add a trailer to */
+struct tunefs_trailer_dirblock {
+	struct list_head db_list;
+	uint64_t db_blkno;
+	char *db_buf;
+
+	/*
+	 * These require a little explanation.  They point to
+	 * ocfs2_dir_entry structures inside db_buf.
+	 *
+	 * db_last entry we're going to *keep*.  If the last entry in the
+	 * dirblock has enough extra rec_len to allow the trailer, db_last
+	 * points to it.  We will shorten its rec_len and insert the
+	 * trailer.
+	 *
+	 * However, if the last entry in the dirblock cannot be truncated,
+	 * db_move points to the entry we have to move out, and db_last
+	 * points to the entry before that - the last entry we're keeping
+	 * in this dirblock.
+	 *
+	 * Examples:
+	 *
+	 * - The last entry in the dirblock has a name_len of 1 and a
+	 *   rec_len of 128.  We can easily change the rec_len to 64 and
+	 *   insert the trailer.  db_last points to this entry.
+	 *
+	 * - The last entry in the dirblock has a name_len of 1 and a
+	 *   rec_len of 48.  The previous entry has a name_len of 1 and a
+	 *   rec_len of 32.  We have to move the last entry out.  The
+	 *   second-to-last entry can have its rec_len truncated to 16, so
+	 *   we put it in db_last.
+	 */
+	struct ocfs2_dir_entry *db_last;
+};
+
+/* A directory inode we're adding trailers to */
+struct tunefs_trailer_context {
+	struct list_head d_list;
+	uint64_t d_blkno;		/* block number of the dir */
+	struct ocfs2_dinode *d_di;	/* The directory's inode */
+	struct list_head d_dirblocks;	/* List of its dirblocks */
+	uint64_t d_bytes_needed;	/* How many new bytes will
+					   cover the dirents we are moving
+					   to make way for trailers */
+	uint64_t d_blocks_needed;	/* How many blocks covers
+					   d_bytes_needed */
+	char *d_new_blocks;		/* Buffer of new blocks to fill */
+	char *d_cur_block;		/* Which block we're filling in
+					   d_new_blocks */
+	struct ocfs2_dir_entry *d_next_dirent;	/* Next dentry to use */
+	errcode_t d_err;		/* Any processing error during
+					   iteration of the directory */
+};
+
+static void tunefs_trailer_context_free(struct tunefs_trailer_context *tc)
+{
+	struct tunefs_trailer_dirblock *db;
+	struct list_head *n, *pos;
+
+	if (!list_empty(&tc->d_list))
+		list_del(&tc->d_list);
+
+	list_for_each_safe(pos, n, &tc->d_dirblocks) {
+		db = list_entry(pos, struct tunefs_trailer_dirblock, db_list);
+		list_del(&db->db_list);
+		ocfs2_free(&db->db_buf);
+		ocfs2_free(&db);
+	}
+
+	ocfs2_free(&tc);
+}
+
+/*
+ * We're calculating how many bytes we need to add to make space for
+ * the dir trailers.  But we need to make sure that the added directory
+ * blocks also have room for a trailer.
+ */
+static void add_bytes_needed(ocfs2_filesys *fs,
+			     struct tunefs_trailer_context *tc,
+			     unsigned int rec_len)
+{
+	unsigned int toff = ocfs2_dir_trailer_blk_off(fs);
+	unsigned int block_offset = tc->d_bytes_needed % fs->fs_blocksize;
+
+	/*
+	 * If the current byte offset would put us into a trailer, push
+	 * it out to the start of the next block.  Remember, dirents have
+	 * to be at least 16 bytes, which is why we check against the
+	 * next smallest size of 12.
+	 */
+	if ((block_offset + rec_len) > (toff - 12))
+		tc->d_bytes_needed += fs->fs_blocksize - block_offset;
+
+	tc->d_bytes_needed += rec_len;
+	tc->d_blocks_needed =
+		ocfs2_blocks_in_bytes(fs, tc->d_bytes_needed);
+}
+
+static errcode_t walk_dirblock(ocfs2_filesys *fs,
+			       struct tunefs_trailer_context *tc,
+			       struct tunefs_trailer_dirblock *db)
+{
+	errcode_t ret;
+	struct ocfs2_dir_entry *dirent, *prev = NULL;
+	unsigned int real_rec_len;
+	unsigned int offset = 0;
+	unsigned int toff = ocfs2_dir_trailer_blk_off(fs);
+
+	while (offset < fs->fs_blocksize) {
+		dirent = (struct ocfs2_dir_entry *) (db->db_buf + offset);
+		if (((offset + dirent->rec_len) > fs->fs_blocksize) ||
+		    (dirent->rec_len < 8) ||
+		    ((dirent->rec_len % 4) != 0) ||
+		    (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) {
+			ret = OCFS2_ET_DIR_CORRUPTED;
+			break;
+		}
+
+		real_rec_len = dirent->inode ?
+			OCFS2_DIR_REC_LEN(dirent->name_len) :
+			OCFS2_DIR_REC_LEN(1);
+		if ((offset + real_rec_len) <= toff)
+			goto next;
+
+		/*
+		 * The first time through, we store off the last dirent
+		 * before the trailer.
+		 */
+		if (!db->db_last)
+			db->db_last = prev;
+
+		/* Only live dirents need to be moved */
+		if (dirent->inode) {
+			verbosef(VL_DEBUG,
+				 "Will move dirent %.*s out of "
+				 "directory block %"PRIu64" to make way "
+				 "for the trailer\n",
+				 dirent->name_len, dirent->name,
+				 db->db_blkno);
+			add_bytes_needed(fs, tc, real_rec_len);
+		}
+
+next:
+		prev = dirent;
+		offset += dirent->rec_len;
+	}
+
+	/* There were no dirents across the boundary */
+	if (!db->db_last)
+		db->db_last = prev;
+
+	return ret;
+}
+
+static int dirblock_scan_iterate(ocfs2_filesys *fs, uint64_t blkno,
+				 uint64_t bcount, uint16_t ext_flags,
+				 void *priv_data)
+{
+	errcode_t ret = 0;
+	struct tunefs_trailer_dirblock *db = NULL;
+	struct tunefs_trailer_context *tc = priv_data;
+
+	ret = ocfs2_malloc0(sizeof(struct tunefs_trailer_dirblock), &db);
+	if (ret)
+		goto out;
+
+	ret = ocfs2_malloc_block(fs->fs_io, &db->db_buf);
+	if (ret)
+		goto out;
+
+	db->db_blkno = blkno;
+
+	verbosef(VL_DEBUG,
+		 "Reading dinode %"PRIu64" dirblock %"PRIu64" at block "
+		 "%"PRIu64"\n",
+		 tc->d_di->i_blkno, bcount, blkno);
+	ret = ocfs2_read_dir_block(fs, tc->d_di, blkno, db->db_buf);
+	if (ret)
+		goto out;
+
+	ret = walk_dirblock(fs, tc, db);
+	if (ret)
+		goto out;
+
+	list_add_tail(&db->db_list, &tc->d_dirblocks);
+	db = NULL;
+
+out:
+	if (db) {
+		if (db->db_buf)
+			ocfs2_free(&db->db_buf);
+		ocfs2_free(&db);
+	}
+
+	if (ret) {
+		tc->d_err = ret;
+		return OCFS2_BLOCK_ABORT;
+	}
+
+	return 0;
+}
+
+static errcode_t tunefs_prepare_dir_trailer(ocfs2_filesys *fs,
+					    struct ocfs2_dinode *di,
+					    struct tunefs_trailer_context **tc_ret)
+{
+	errcode_t ret = 0;
+	struct tunefs_trailer_context *tc = NULL;
+
+	if (ocfs2_dir_has_trailer(fs, di))
+		goto out;
+
+	ret = ocfs2_malloc0(sizeof(struct tunefs_trailer_context), &tc);
+	if (ret)
+		goto out;
+
+	tc->d_blkno = di->i_blkno;
+	tc->d_di = di;
+	INIT_LIST_HEAD(&tc->d_list);
+	INIT_LIST_HEAD(&tc->d_dirblocks);
+
+	ret = ocfs2_block_iterate_inode(fs, tc->d_di, 0,
+					dirblock_scan_iterate, tc);
+	if (!ret)
+		ret = tc->d_err;
+	if (ret)
+		goto out;
+
+	*tc_ret = tc;
+	tc = NULL;
+
+out:
+	if (tc)
+		tunefs_trailer_context_free(tc);
+
+	return ret;
+}
+
+/*
+ * We are hand-coding the directory expansion because we're going to
+ * build the new directory blocks ourselves.  We can't just use
+ * ocfs2_expand_dir() and ocfs2_link(), because we're moving around
+ * entries.
+ */
+static errcode_t expand_dir_if_needed(ocfs2_filesys *fs,
+				      struct ocfs2_dinode *di,
+				      uint64_t blocks_needed)
+{
+	errcode_t ret = 0;
+	uint64_t used_blocks, total_blocks;
+	uint32_t clusters_needed;
+
+	/* This relies on the fact that i_size of a directory is a
+	 * multiple of blocksize */
+	used_blocks = ocfs2_blocks_in_bytes(fs, di->i_size);
+	total_blocks = ocfs2_clusters_to_blocks(fs, di->i_clusters);
+	if ((used_blocks + blocks_needed) <= total_blocks)
+		goto out;
+
+	clusters_needed =
+		ocfs2_clusters_in_blocks(fs,
+					 (used_blocks + blocks_needed) -
+					 total_blocks);
+	ret = ocfs2_extend_allocation(fs, di->i_blkno, clusters_needed);
+	if (ret)
+		goto out;
+
+	/* Pick up changes to the inode */
+	ret = ocfs2_read_inode(fs, di->i_blkno, (char *)di);
+
+out:
+	return ret;
+}
+
+static void shift_dirent(ocfs2_filesys *fs,
+			 struct tunefs_trailer_context *tc,
+			 struct ocfs2_dir_entry *dirent)
+{
+	/* Using the real rec_len */
+	unsigned int rec_len = OCFS2_DIR_REC_LEN(dirent->name_len);
+	unsigned int offset, remain;
+
+	/*
+	 * If the current byte offset would put us into a trailer, push
+	 * it out to the start of the next block.  Remember, dirents have
+	 * to be at least 16 bytes, which is why we check against the
+	 * next smallest size of 12.
+	 */
+	if (rec_len > (tc->d_next_dirent->rec_len - 12)) {
+		tc->d_cur_block += fs->fs_blocksize;
+		tc->d_next_dirent = (struct ocfs2_dir_entry *)tc->d_cur_block;
+	}
+
+	assert(ocfs2_blocks_in_bytes(fs,
+				     tc->d_cur_block - tc->d_new_blocks) <
+	       tc->d_blocks_needed);
+
+	offset = (char *)(tc->d_next_dirent) - tc->d_cur_block;
+	remain = tc->d_next_dirent->rec_len - rec_len;
+
+	memcpy(tc->d_cur_block + offset, dirent, rec_len);
+	tc->d_next_dirent->rec_len = rec_len;
+
+	verbosef(VL_DEBUG,
+		 "Installed dirent %.*s at offset %u of new block "
+		 "%"PRIu64", rec_len %u\n",
+		 tc->d_next_dirent->name_len, tc->d_next_dirent->name,
+		 offset,
+		 ocfs2_blocks_in_bytes(fs, tc->d_cur_block - tc->d_new_blocks),
+		 rec_len);
+
+
+	offset += rec_len;
+	tc->d_next_dirent =
+		(struct ocfs2_dir_entry *)(tc->d_cur_block + offset);
+	tc->d_next_dirent->rec_len = remain;
+
+	verbosef(VL_DEBUG,
+		 "New block %"PRIu64" has its last dirent at %u, with %u "
+		 "bytes left\n",
+		 ocfs2_blocks_in_bytes(fs, tc->d_cur_block - tc->d_new_blocks),
+		 offset, remain);
+}
+
+static errcode_t fixup_dirblock(ocfs2_filesys *fs,
+				struct tunefs_trailer_context *tc,
+				struct tunefs_trailer_dirblock *db)
+{
+	errcode_t ret = 0;
+	struct ocfs2_dir_entry *dirent;
+	unsigned int real_rec_len;
+	unsigned int offset;
+	unsigned int toff = ocfs2_dir_trailer_blk_off(fs);
+
+	/*
+	 * db_last is the last dirent we're *keeping*.  So we need to 
+	 * move out every valid dirent *after* db_last.
+	 *
+	 * tunefs_prepare_dir_trailer() should have calculated this
+	 * correctly.
+	 */
+	offset = ((char *)db->db_last) - db->db_buf;
+	offset += db->db_last->rec_len;
+	while (offset < fs->fs_blocksize) {
+		dirent = (struct ocfs2_dir_entry *) (db->db_buf + offset);
+		if (((offset + dirent->rec_len) > fs->fs_blocksize) ||
+		    (dirent->rec_len < 8) ||
+		    ((dirent->rec_len % 4) != 0) ||
+		    (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) {
+			ret = OCFS2_ET_DIR_CORRUPTED;
+			break;
+		}
+
+		real_rec_len = dirent->inode ?
+			OCFS2_DIR_REC_LEN(dirent->name_len) :
+			OCFS2_DIR_REC_LEN(1);
+
+		assert((offset + real_rec_len) > toff);
+
+		/* Only live dirents need to be moved */
+		if (dirent->inode) {
+			verbosef(VL_DEBUG,
+				 "Moving dirent %.*s out of directory "
+				 "block %"PRIu64" to make way for the "
+				 "trailer\n",
+				 dirent->name_len, dirent->name,
+				 db->db_blkno);
+			shift_dirent(fs, tc, dirent);
+		}
+
+		offset += dirent->rec_len;
+	}
+
+	/*
+	 * Now that we've moved any dirents out of the way, we need to
+	 * fix up db_last and install the trailer.
+	 */
+	offset = ((char *)db->db_last) - db->db_buf;
+	verbosef(VL_DEBUG,
+		 "Last valid dirent of directory block %"PRIu64" "
+		 "(\"%.*s\") is %u bytes in.  Setting rec_len to %u and "
+		 "installing the trailer\n",
+		 db->db_blkno, db->db_last->name_len, db->db_last->name,
+		 offset, toff - offset);
+	db->db_last->rec_len = toff - offset;
+	ocfs2_init_dir_trailer(fs, tc->d_di, db->db_blkno, db->db_buf);
+
+	return ret;
+}
+
+static errcode_t run_dirblocks(ocfs2_filesys *fs,
+			       struct tunefs_trailer_context *tc)
+{
+	errcode_t ret = 0;
+	struct list_head *pos;
+	struct tunefs_trailer_dirblock *db;
+
+	list_for_each(pos, &tc->d_dirblocks) {
+		db = list_entry(pos, struct tunefs_trailer_dirblock, db_list);
+		ret = fixup_dirblock(fs, tc, db);
+		if (ret)
+			break;
+	}
+
+	return ret;
+}
+
+static errcode_t write_dirblocks(ocfs2_filesys *fs,
+				 struct tunefs_trailer_context *tc)
+{
+	errcode_t ret = 0;
+	struct list_head *pos;
+	struct tunefs_trailer_dirblock *db;
+
+	list_for_each(pos, &tc->d_dirblocks) {
+		db = list_entry(pos, struct tunefs_trailer_dirblock, db_list);
+		ret = ocfs2_write_dir_block(fs, tc->d_di, db->db_blkno,
+					    db->db_buf);
+		if (ret) {
+			verbosef(VL_DEBUG,
+				 "Error writing dirblock %"PRIu64"\n",
+				 db->db_blkno);
+			break;
+		}
+	}
+
+	return ret;
+}
+
+static errcode_t init_new_dirblocks(ocfs2_filesys *fs,
+				    struct tunefs_trailer_context *tc)
+{
+	int i;
+	errcode_t ret;
+	uint64_t blkno;
+	uint64_t orig_block = ocfs2_blocks_in_bytes(fs, tc->d_di->i_size);
+	ocfs2_cached_inode *cinode;
+	char *blockptr;
+	struct ocfs2_dir_entry *first;
+
+	ret = ocfs2_read_cached_inode(fs, tc->d_blkno, &cinode);
+	if (ret)
+		goto out;
+	assert(!memcmp(tc->d_di, cinode->ci_inode, fs->fs_blocksize));
+
+	for (i = 0; i < tc->d_blocks_needed; i++) {
+		ret = ocfs2_extent_map_get_blocks(cinode, orig_block + i,
+						  1, &blkno, NULL, NULL);
+		if (ret)
+			goto out;
+		blockptr = tc->d_new_blocks + (i * fs->fs_blocksize);
+		memset(blockptr, 0, fs->fs_blocksize);
+		first = (struct ocfs2_dir_entry *)blockptr;
+		first->rec_len = ocfs2_dir_trailer_blk_off(fs);
+		ocfs2_init_dir_trailer(fs, tc->d_di, blkno, blockptr);
+	}
+
+out:
+	return ret;
+}
+
+static errcode_t write_new_dirblocks(ocfs2_filesys *fs,
+				     struct tunefs_trailer_context *tc)
+{
+	int i;
+	errcode_t ret;
+	uint64_t blkno;
+	uint64_t orig_block = ocfs2_blocks_in_bytes(fs, tc->d_di->i_size);
+	ocfs2_cached_inode *cinode;
+	char *blockptr;
+
+	ret = ocfs2_read_cached_inode(fs, tc->d_blkno, &cinode);
+	if (ret)
+		goto out;
+	assert(!memcmp(tc->d_di, cinode->ci_inode, fs->fs_blocksize));
+
+	for (i = 0; i < tc->d_blocks_needed; i++) {
+		ret = ocfs2_extent_map_get_blocks(cinode, orig_block + i,
+						  1, &blkno, NULL, NULL);
+		if (ret)
+			goto out;
+		blockptr = tc->d_new_blocks + (i * fs->fs_blocksize);
+		ret = ocfs2_write_dir_block(fs, tc->d_di, blkno, blockptr);
+		if (ret) {
+			verbosef(VL_DEBUG,
+				 "Error writing dirblock %"PRIu64"\n",
+				 blkno);
+			goto out;
+		}
+	}
+
+out:
+	return ret;
+}
+
+
+static errcode_t tunefs_install_dir_trailer(ocfs2_filesys *fs,
+					    struct ocfs2_dinode *di,
+					    struct tunefs_trailer_context *tc)
+{
+	errcode_t ret = 0;
+	struct tunefs_trailer_context *our_tc = NULL;
+
+	if (!tc) {
+		ret = tunefs_prepare_dir_trailer(fs, di, &our_tc);
+		if (ret)
+			goto out;
+		tc = our_tc;
+	}
+
+	if (tc->d_di != di) {
+		ret = OCFS2_ET_INVALID_ARGUMENT;
+		goto out;
+	}
+
+	if (tc->d_blocks_needed) {
+		ret = ocfs2_malloc_blocks(fs->fs_io, tc->d_blocks_needed,
+					  &tc->d_new_blocks);
+		if (ret)
+			goto out;
+
+		tc->d_cur_block = tc->d_new_blocks;
+
+		ret = expand_dir_if_needed(fs, di, tc->d_blocks_needed);
+		if (ret)
+			goto out;
+
+		ret = init_new_dirblocks(fs, tc);
+		if (ret)
+			goto out;
+		tc->d_next_dirent = (struct ocfs2_dir_entry *)tc->d_cur_block;
+		verbosef(VL_DEBUG, "t_next_dirent has rec_len of %u\n",
+			 tc->d_next_dirent->rec_len);
+	}
+
+	ret = run_dirblocks(fs, tc);
+	if (ret)
+		goto out;
+
+	/*
+	 * We write in a specific order.  We write any new dirblocks first
+	 * so that they are on disk.  Then we write the new i_size in the
+	 * inode.  If we crash at this point, the directory has duplicate
+	 * entries but no lost entries.  fsck can clean it up.  Finally, we
+	 * write the modified dirblocks with trailers.
+	 */
+	if (tc->d_blocks_needed) {
+		ret = write_new_dirblocks(fs, tc);
+		if (ret)
+			goto out;
+
+		di->i_size += ocfs2_blocks_to_bytes(fs, tc->d_blocks_needed);
+		ret = ocfs2_write_inode(fs, di->i_blkno, (char *)di);
+		if (ret)
+			goto out;
+	}
+
+	ret = write_dirblocks(fs, tc);
+
+out:
+	if (our_tc)
+		tunefs_trailer_context_free(our_tc);
+	return ret;
+}
+
+
+/*
+ * Since we have to scan the inodes in our first pass to find directories
+ * that need trailers, we might as well store them off and avoid reading
+ * them again when its time to write ECC data.  In fact, we'll do all the
+ * scanning up-front, including extent blocks and group descriptors.  The
+ * only metadata block we don't store is the superblock, because we'll
+ * write that last from fs->fs_super.
+ *
+ * We store all of this in an rb-tree of block_to_ecc structures.  We can
+ * look blocks back up if needed, and we have writeback functions attached.
+ *
+ * For directory inodes, we pass e_buf into tunefs_prepare_dir_trailer(),
+ * which does not copy off the inode.  Thus, when
+ * tunefs_install_dir_trailer() modifies the inode, this is the one that
+ * gets updated.
+ *
+ * For directory blocks, tunefs_prepare_dir_trailer() makes its own copies.
+ * After we run tunefs_install_dir_trailer(), we'll have to copy the
+ * changes back to our copy.
+ */
+struct block_to_ecc {
+	struct rb_node e_node;
+	uint64_t e_blkno;
+	struct ocfs2_dinode *e_di;
+	char *e_buf;
+	errcode_t (*e_write)(ocfs2_filesys *fs, struct block_to_ecc *block);
+};
+
+struct add_ecc_context {
+	errcode_t ae_ret;
+	uint32_t ae_clusters;
+	struct list_head ae_dirs;
+	struct rb_root ae_blocks;
+};
+
+static void block_free(struct block_to_ecc *block)
+{
+	if (block->e_buf)
+		ocfs2_free(&block->e_buf);
+	ocfs2_free(&block);
+}
+
+static struct block_to_ecc *block_lookup(struct add_ecc_context *ctxt,
+					 uint64_t blkno)
+{
+	struct rb_node *p = ctxt->ae_blocks.rb_node;
+	struct block_to_ecc *block;
+
+	while (p) {
+		block = rb_entry(p, struct block_to_ecc, e_node);
+		if (blkno < block->e_blkno) {
+			p = p->rb_left;
+		} else if (blkno > block->e_blkno) {
+			p = p->rb_right;
+		} else
+			return block;
+	}
+
+	return NULL;
+}
+
+static void dump_ecc_tree(struct add_ecc_context *ctxt)
+{
+	struct rb_node *n;
+	struct block_to_ecc *tmp;
+
+	verbosef(VL_DEBUG, "Dumping ecc block tree\n");
+	n = rb_first(&ctxt->ae_blocks);
+	while (n) {
+		tmp = rb_entry(n, struct block_to_ecc, e_node);
+		verbosef(VL_DEBUG, "Block %"PRIu64", struct %p, buf %p\n",
+			 tmp->e_blkno, tmp, tmp->e_buf);
+		n = rb_next(n);
+	}
+}
+
+static errcode_t block_insert(struct add_ecc_context *ctxt,
+			      struct block_to_ecc *block)
+{
+	errcode_t ret;
+	struct block_to_ecc *tmp;
+	struct rb_node **p = &ctxt->ae_blocks.rb_node;
+	struct rb_node *parent = NULL;
+
+	while (*p) {
+		parent = *p;
+		tmp = rb_entry(parent, struct block_to_ecc, e_node);
+		if (block->e_blkno < tmp->e_blkno) {
+			p = &(*p)->rb_left;
+			tmp = NULL;
+		} else if (block->e_blkno > tmp->e_blkno) {
+			p = &(*p)->rb_right;
+			tmp = NULL;
+		} else {
+			dump_ecc_tree(ctxt);
+			assert(0);  /* We shouldn't find it. */
+		}
+	}
+
+	rb_link_node(&block->e_node, parent, p);
+	rb_insert_color(&block->e_node, &ctxt->ae_blocks);
+	ret = 0;
+
+	return ret;
+}
+
+static errcode_t dinode_write_func(ocfs2_filesys *fs,
+				   struct block_to_ecc *block)
+{
+	return ocfs2_write_inode(fs, block->e_blkno, block->e_buf);
+}
+
+static errcode_t block_insert_dinode(ocfs2_filesys *fs,
+				     struct add_ecc_context *ctxt,
+				     struct ocfs2_dinode *di)
+{
+	errcode_t ret;
+	struct block_to_ecc *block = NULL;
+
+	ret = ocfs2_malloc0(sizeof(struct block_to_ecc), &block);
+	if (ret)
+		goto out;
+
+	ret = ocfs2_malloc_block(fs->fs_io, &block->e_buf);
+	if (ret)
+		goto out;
+
+	memcpy(block->e_buf, di, fs->fs_blocksize);
+	block->e_di = (struct ocfs2_dinode *)block->e_buf;
+	block->e_blkno = di->i_blkno;
+	block->e_write = dinode_write_func;
+	block_insert(ctxt, block);
+
+out:
+	if (ret && block)
+		block_free(block);
+	return ret;
+}
+
+static errcode_t eb_write_func(ocfs2_filesys *fs,
+			       struct block_to_ecc *block)
+{
+	return ocfs2_write_extent_block(fs, block->e_blkno, block->e_buf);
+}
+
+static errcode_t block_insert_eb(ocfs2_filesys *fs,
+				 struct add_ecc_context *ctxt,
+				 struct ocfs2_dinode *di,
+				 struct ocfs2_extent_block *eb)
+{
+	errcode_t ret;
+	struct block_to_ecc *block = NULL;
+
+	ret = ocfs2_malloc0(sizeof(struct block_to_ecc), &block);
+	if (ret)
+		goto out;
+
+	ret = ocfs2_malloc_block(fs->fs_io, &block->e_buf);
+	if (ret)
+		goto out;
+
+	memcpy(block->e_buf, eb, fs->fs_blocksize);
+	block->e_blkno = eb->h_blkno;
+	block->e_write = eb_write_func;
+	block_insert(ctxt, block);
+
+out:
+	if (ret && block)
+		block_free(block);
+	return ret;
+}
+
+static errcode_t gd_write_func(ocfs2_filesys *fs,
+			       struct block_to_ecc *block)
+{
+	return ocfs2_write_group_desc(fs, block->e_blkno, block->e_buf);
+}
+
+static errcode_t block_insert_gd(ocfs2_filesys *fs,
+				 struct add_ecc_context *ctxt,
+				 struct ocfs2_dinode *di,
+				 struct ocfs2_group_desc *gd)
+{
+	errcode_t ret;
+	struct block_to_ecc *block = NULL;
+
+	ret = ocfs2_malloc0(sizeof(struct block_to_ecc), &block);
+	if (ret)
+		goto out;
+
+	ret = ocfs2_malloc_block(fs->fs_io, &block->e_buf);
+	if (ret)
+		goto out;
+
+	memcpy(block->e_buf, gd, fs->fs_blocksize);
+	block->e_blkno = gd->bg_blkno;
+	block->e_write = gd_write_func;
+	block_insert(ctxt, block);
+
+out:
+	if (ret && block)
+		block_free(block);
+	return ret;
+}
+
+static errcode_t dirblock_write_func(ocfs2_filesys *fs,
+				     struct block_to_ecc *block)
+{
+	return ocfs2_write_dir_block(fs, block->e_di, block->e_blkno,
+				     block->e_buf);
+}
+
+static errcode_t block_insert_dirblock(ocfs2_filesys *fs,
+				       struct add_ecc_context *ctxt,
+				       struct ocfs2_dinode *di,
+				       uint64_t blkno, char *buf)
+{
+	errcode_t ret;
+	struct block_to_ecc *block = NULL;
+
+	ret = ocfs2_malloc0(sizeof(struct block_to_ecc), &block);
+	if (ret)
+		goto out;
+
+	ret = ocfs2_malloc_block(fs->fs_io, &block->e_buf);
+	if (ret)
+		goto out;
+
+	memcpy(block->e_buf, buf, fs->fs_blocksize);
+	block->e_di = di;
+	block->e_blkno = blkno;
+	block->e_write = dirblock_write_func;
+	block_insert(ctxt, block);
+
+out:
+	if (ret && block)
+		block_free(block);
+	return ret;
+
+}
+
+static void empty_ecc_blocks(struct add_ecc_context *ctxt)
+{
+	struct block_to_ecc *block;
+	struct rb_node *node;
+
+	while ((node = rb_first(&ctxt->ae_blocks)) != NULL) {
+		block = rb_entry(node, struct block_to_ecc, e_node);
+
+		rb_erase(&block->e_node, &ctxt->ae_blocks);
+		ocfs2_free(&block->e_buf);
+		ocfs2_free(&block);
+	}
+}
+
+static void empty_add_ecc_context(struct add_ecc_context *ctxt)
+{
+	struct tunefs_trailer_context *tc;
+	struct list_head *n, *pos;
+
+	list_for_each_safe(pos, n, &ctxt->ae_dirs) {
+		tc = list_entry(pos, struct tunefs_trailer_context, d_list);
+		tunefs_trailer_context_free(tc);
+	}
+
+	empty_ecc_blocks(ctxt);
+}
+
+
+struct add_ecc_iterate {
+	struct add_ecc_context *ic_ctxt;
+	struct ocfs2_dinode *ic_di;
+};
+
+static int chain_iterate(ocfs2_filesys *fs, uint64_t gd_blkno,
+			 int chain_num, void *priv_data)
+{
+	struct add_ecc_iterate *iter = priv_data;
+	struct ocfs2_group_desc *gd = NULL;
+	errcode_t ret;
+	int iret = 0;
+
+	ret = ocfs2_malloc_block(fs->fs_io, &gd);
+	if (ret)
+		goto out;
+
+	verbosef(VL_DEBUG, "Reading group descriptor at %"PRIu64"\n",
+		 gd_blkno);
+	ret = ocfs2_read_group_desc(fs, gd_blkno, (char *)gd);
+	if (ret)
+		goto out;
+
+	ret = block_insert_gd(fs, iter->ic_ctxt, iter->ic_di, gd);
+
+out:
+	if (gd)
+		ocfs2_free(&gd);
+	if (ret) {
+		iter->ic_ctxt->ae_ret = ret;
+		iret = OCFS2_CHAIN_ABORT;
+	}
+
+	return iret;
+}
+
+/*
+ * Right now, this only handles directory data.  Quota stuff will want
+ * to genericize this or copy it.
+ */
+static int dirdata_iterate(ocfs2_filesys *fs, struct ocfs2_extent_rec *rec,
+			   int tree_depth, uint32_t ccount,
+			   uint64_t ref_blkno, int ref_recno,
+			   void *priv_data)
+{
+	errcode_t ret = 0;
+	struct add_ecc_iterate *iter = priv_data;
+	char *buf = NULL;
+	struct ocfs2_extent_block *eb;
+	int iret = 0;
+	uint64_t blocks, i;
+
+	ret = ocfs2_malloc_block(fs->fs_io, &buf);
+	if (ret)
+		goto out;
+
+	if (tree_depth) {
+		verbosef(VL_DEBUG, "Reading extent block at %"PRIu64"\n",
+			 rec->e_blkno);
+		ret = ocfs2_read_extent_block(fs, rec->e_blkno, (char *)eb);
+		if (ret)
+			goto out;
+
+		ret = block_insert_eb(fs, iter->ic_ctxt, iter->ic_di, eb);
+	} else {
+		blocks = ocfs2_clusters_to_blocks(fs, rec->e_leaf_clusters);
+		for (i = 0; i < blocks; i++) {
+			ret = ocfs2_read_dir_block(fs, iter->ic_di,
+						   rec->e_blkno + i, buf);
+			if (ret)
+				break;
+
+			ret = block_insert_dirblock(fs, iter->ic_ctxt,
+						    iter->ic_di,
+						    rec->e_blkno + i, buf);
+			if (ret)
+				break;
+		}
+	}
+
+out:
+	if (buf)
+		ocfs2_free(&buf);
+	if (ret) {
+		iter->ic_ctxt->ae_ret = ret;
+		iret = OCFS2_EXTENT_ABORT;
+	}
+
+	return iret;
+}
+
+static int metadata_iterate(ocfs2_filesys *fs, struct ocfs2_extent_rec *rec,
+			    int tree_depth, uint32_t ccount,
+			    uint64_t ref_blkno, int ref_recno,
+			    void *priv_data)
+{
+	errcode_t ret = 0;
+	struct add_ecc_iterate *iter = priv_data;
+	struct ocfs2_extent_block *eb = NULL;
+	int iret = 0;
+
+	if (!tree_depth)
+		goto out;
+
+	ret = ocfs2_malloc_block(fs->fs_io, &eb);
+	if (ret)
+		goto out;
+
+	verbosef(VL_DEBUG, "Reading extent block at %"PRIu64"\n",
+		 rec->e_blkno);
+	ret = ocfs2_read_extent_block(fs, rec->e_blkno, (char *)eb);
+	if (ret)
+		goto out;
+
+	ret = block_insert_eb(fs, iter->ic_ctxt, iter->ic_di, eb);
+
+out:
+	if (eb)
+		ocfs2_free(&eb);
+	if (ret) {
+		iter->ic_ctxt->ae_ret = ret;
+		iret = OCFS2_EXTENT_ABORT;
+	}
+
+	return iret;
+}
+
+static errcode_t inode_iterate(ocfs2_filesys *fs, struct ocfs2_dinode *di,
+			       void *user_data)
+{
+	errcode_t ret;
+	struct block_to_ecc *block = NULL;
+	struct tunefs_trailer_context *tc;
+	struct add_ecc_context *ctxt = user_data;
+	struct add_ecc_iterate iter = {
+		.ic_ctxt = ctxt,
+		.ic_di = di,
+	};
+
+	ret = block_insert_dinode(fs, ctxt, di);
+	if (ret)
+		goto out;
+
+	/* These inodes have no other metadata on them */
+	if ((di->i_flags & (OCFS2_SUPER_BLOCK_FL | OCFS2_LOCAL_ALLOC_FL |
+			    OCFS2_DEALLOC_FL)) ||
+	    (S_ISLNK(di->i_mode) && di->i_clusters == 0) ||
+	    (di->i_dyn_features & OCFS2_INLINE_DATA_FL))
+		goto out;
+
+	/* We need the inode, look it back up */
+	block = block_lookup(ctxt, di->i_blkno);
+	if (!block) {
+		ret = TUNEFS_ET_INTERNAL_FAILURE;
+		goto out;
+	}
+
+	/* Now using our copy of the inode */
+	di = (struct ocfs2_dinode *)block->e_buf;
+
+	if (di->i_flags & OCFS2_CHAIN_FL) {
+		ret = ocfs2_chain_iterate(fs, di->i_blkno,
+					  chain_iterate,
+					  &iter);
+		goto out;
+	}
+
+	/*
+	 * Ok, it's a regular file or directory.
+	 * If it's a regular file, gather extent blocks for this inode.
+	 * If it's a directory that has trailers, we need to gather all
+	 * of its blocks, data and metadata.
+	 *
+	 * We don't gather extent info for directories that need trailers
+	 * yet, because they might get modified as they gain trailers.
+	 * We'll add them after we insert their trailers.
+	 */
+	if (!S_ISDIR(di->i_mode))
+		ret = ocfs2_extent_iterate_inode(fs, di, 0, NULL,
+						 metadata_iterate, &iter);
+	else if (ocfs2_dir_has_trailer(fs, di))
+		ret = ocfs2_extent_iterate_inode(fs, di, 0, NULL,
+						 dirdata_iterate, &iter);
+	else {
+		ret = tunefs_prepare_dir_trailer(fs, di, &tc);
+		if (!ret) {
+			verbosef(VL_DEBUG,
+				 "Directory %"PRIu64" needs %"PRIu64" "
+				 "more blocks\n",
+				 tc->d_blkno, tc->d_blocks_needed);
+			list_add(&tc->d_list, &ctxt->ae_dirs);
+			ctxt->ae_clusters +=
+				ocfs2_clusters_in_blocks(fs,
+							 tc->d_blocks_needed);
+		}
+	}
+
+out:
+	return ret;
+}
+
+static errcode_t find_blocks(ocfs2_filesys *fs, struct add_ecc_context *ctxt)
+{
+	errcode_t ret;
+	uint32_t free_clusters = 0;
+
+	ret = tunefs_foreach_inode(fs, inode_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 add "
+		 "trailers to every directory\n",
+		 free_clusters, ctxt->ae_clusters);
+
+	if (free_clusters < ctxt->ae_clusters)
+		ret = OCFS2_ET_NO_SPACE;
+
+bail:
+	return ret;
+}
+
+static errcode_t install_trailers(ocfs2_filesys *fs,
+				  struct add_ecc_context *ctxt)
+{
+	errcode_t ret = 0;
+	struct tunefs_trailer_context *tc;
+	struct list_head *n, *pos;
+	struct add_ecc_iterate iter = {
+		.ic_ctxt = ctxt,
+	};
+
+	list_for_each_safe(pos, n, &ctxt->ae_dirs) {
+		tc = list_entry(pos, struct tunefs_trailer_context, d_list);
+		verbosef(VL_DEBUG,
+			 "Writing trailer for dinode %"PRIu64"\n",
+			 tc->d_di->i_blkno);
+		tunefs_block_signals();
+		ret = tunefs_install_dir_trailer(fs, tc->d_di, tc);
+		tunefs_unblock_signals();
+		if (ret)
+			break;
+
+		iter.ic_di = tc->d_di;
+		tunefs_trailer_context_free(tc);
+
+		/*
+		 * Now that we've put trailers on the directory, we know
+		 * its allocation won't change.  Add its blocks to the
+		 * block list.  These will be cached, as installing the
+		 * trailer will have just touched them.
+		 */
+		ret = ocfs2_extent_iterate_inode(fs, tc->d_di, 0, NULL,
+						 dirdata_iterate, &iter);
+		if (ret)
+			break;
+	}
+
+	return ret;
+}
+
+static errcode_t write_ecc_blocks(ocfs2_filesys *fs,
+				  struct add_ecc_context *ctxt)
+{
+	errcode_t ret = 0;
+	struct rb_node *n;
+	struct block_to_ecc *block;
+
+	verbosef(VL_DEBUG, "Dumping ecc block tree\n");
+	n = rb_first(&ctxt->ae_blocks);
+	while (n) {
+		block = rb_entry(n, struct block_to_ecc, e_node);
+		verbosef(VL_DEBUG, "Writing block %"PRIu64"\n",
+			 block->e_blkno);
+
+		ret = block->e_write(fs, block);
+		if (ret)
+			break;
+
+		n = rb_next(n);
+	}
+
+	return ret;
+}
+
+static int enable_metaecc(ocfs2_filesys *fs, int flags)
+{
+	errcode_t ret = 0;
+	struct ocfs2_super_block *super = OCFS2_RAW_SB(fs->fs_super);
+	struct add_ecc_context ctxt;
+
+	if (ocfs2_meta_ecc(super)) {
+		verbosef(VL_APP,
+			 "The metadata ECC feature is already enabled; "
+			 "nothing to enable\n");
+		goto out;
+	}
+
+	if (!tools_interact("Enable the metadata ECC feature on device "
+			    "\"%s\"? ",
+			    fs->fs_devname))
+		goto out;
+
+	memset(&ctxt, 0, sizeof(ctxt));
+	INIT_LIST_HEAD(&ctxt.ae_dirs);
+	ret = find_blocks(fs, &ctxt);
+	if (ret) {
+		if (ret == OCFS2_ET_NO_SPACE)
+			errorf("There is not enough space to add directory "
+			       "trailers to the directories on device "
+			       "\"%s\"\n",
+			       fs->fs_devname);
+		else
+			tcom_err(ret,
+				 "while trying to find directory blocks");
+		goto out_cleanup;
+	}
+
+	ret = tunefs_set_in_progress(fs, OCFS2_TUNEFS_INPROG_DIR_TRAILER);
+	if (ret)
+		goto out_cleanup;
+
+	ret = install_trailers(fs, &ctxt);
+	if (ret) {
+		tcom_err(ret,
+			 "while trying to install directory trailers on "
+			 "device \"%s\"",
+			 fs->fs_devname);
+		goto out_cleanup;
+	}
+
+	ret = tunefs_clear_in_progress(fs, OCFS2_TUNEFS_INPROG_DIR_TRAILER);
+	if (ret)
+		goto out_cleanup;
+
+	/* Set the feature bit in-memory and rewrite all our blocks */
+	OCFS2_SET_INCOMPAT_FEATURE(super, OCFS2_FEATURE_INCOMPAT_META_ECC);
+
+	ret = write_ecc_blocks(fs, &ctxt);
+	if (ret)
+		goto out_cleanup;
+
+	tunefs_block_signals();
+	ret = ocfs2_write_super(fs);
+	tunefs_unblock_signals();
+	if (ret)
+		tcom_err(ret, "while writing out the superblock");
+
+out_cleanup:
+	empty_add_ecc_context(&ctxt);
+
+out:
+	return ret;
+}
+
+static int disable_metaecc(ocfs2_filesys *fs, int flags)
+{
+	errcode_t ret = 0;
+	struct ocfs2_super_block *super = OCFS2_RAW_SB(fs->fs_super);
+
+	if (!ocfs2_meta_ecc(super)) {
+		verbosef(VL_APP,
+			 "The metadata ECC feature is not enabled; "
+			 "nothing to disable\n");
+		goto out;
+	}
+
+	if (!tools_interact("Enable the metadata ECC feature on device "
+			    "\"%s\"? ",
+			    fs->fs_devname))
+		goto out;
+
+	OCFS2_CLEAR_INCOMPAT_FEATURE(super,
+				     OCFS2_FEATURE_INCOMPAT_META_ECC);
+	tunefs_block_signals();
+	ret = ocfs2_write_super(fs);
+	tunefs_unblock_signals();
+	if (ret)
+		tcom_err(ret, "while writing out the superblock");
+
+out:
+	return ret;
+}
+
+
+DEFINE_TUNEFS_FEATURE_INCOMPAT(metaecc,
+			       OCFS2_FEATURE_INCOMPAT_META_ECC,
+			       TUNEFS_FLAG_RW | TUNEFS_FLAG_ALLOCATION,
+			       enable_metaecc,
+			       disable_metaecc);
+
+#ifdef DEBUG_EXE
+int main(int argc, char *argv[])
+{
+	return tunefs_feature_main(argc, argv, &metaecc_feature);
+}
+#endif
diff --git a/tunefs.ocfs2/op_features.c b/tunefs.ocfs2/op_features.c
index 07addaf..b1424ee 100644
--- a/tunefs.ocfs2/op_features.c
+++ b/tunefs.ocfs2/op_features.c
@@ -37,6 +37,7 @@ extern struct tunefs_feature backup_super_feature;
 extern struct tunefs_feature extended_slotmap_feature;
 extern struct tunefs_feature inline_data_feature;
 extern struct tunefs_feature local_feature;
+extern struct tunefs_feature metaecc_feature;
 extern struct tunefs_feature sparse_files_feature;
 extern struct tunefs_feature unwritten_extents_feature;
 
@@ -46,6 +47,7 @@ static struct tunefs_feature *features[] = {
 	&extended_slotmap_feature,
 	&inline_data_feature,
 	&local_feature,
+	&metaecc_feature,
 	&sparse_files_feature,
 	&unwritten_extents_feature,
 	NULL,
-- 
1.5.6.5




More information about the Ocfs2-tools-devel mailing list