From 95c24b6f28c92e1a44af5e6940c98800d90d4da9 Mon Sep 17 00:00:00 2001
From: Martin Krizek <martin.krizek@gmail.com>
Date: Wed, 27 Mar 2013 14:50:24 -0400
Subject: [PATCH] Add online deduplication

---
 fs/btrfs/Makefile          |   2 +-
 fs/btrfs/ctree.h           |  43 +++++++-
 fs/btrfs/dedup.c           | 269 +++++++++++++++++++++++++++++++++++++++++++++
 fs/btrfs/dedup.h           |  42 +++++++
 fs/btrfs/disk-io.c         |  53 +++++++--
 fs/btrfs/inode.c           | 260 ++++++++++++++++++++++++++++++++++++++++---
 fs/btrfs/ioctl.c           |  20 ++++
 fs/btrfs/ordered-data.c    |  36 +++++-
 fs/btrfs/ordered-data.h    |  26 +++++
 fs/btrfs/print-tree.c      |  15 +++
 fs/btrfs/super.c           |  14 +++
 include/uapi/linux/btrfs.h |   1 +
 12 files changed, 750 insertions(+), 31 deletions(-)
 create mode 100644 fs/btrfs/dedup.c
 create mode 100644 fs/btrfs/dedup.h

diff --git a/fs/btrfs/Makefile b/fs/btrfs/Makefile
index 3932224..d1e7ce3 100644
--- a/fs/btrfs/Makefile
+++ b/fs/btrfs/Makefile
@@ -8,7 +8,7 @@ btrfs-y += super.o ctree.o extent-tree.o print-tree.o root-tree.o dir-item.o \
 	   extent_io.o volumes.o async-thread.o ioctl.o locking.o orphan.o \
 	   export.o tree-log.o free-space-cache.o zlib.o lzo.o \
 	   compression.o delayed-ref.o relocation.o delayed-inode.o scrub.o \
-	   reada.o backref.o ulist.o qgroup.o send.o dev-replace.o raid56.o
+	   reada.o backref.o ulist.o qgroup.o send.o dev-replace.o raid56.o dedup.o
 
 btrfs-$(CONFIG_BTRFS_FS_POSIX_ACL) += acl.o
 btrfs-$(CONFIG_BTRFS_FS_CHECK_INTEGRITY) += check-integrity.o
diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index 0d82922..f4435eb 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -94,6 +94,9 @@ struct btrfs_ordered_sum;
 /* holds quota configuration and tracking */
 #define BTRFS_QUOTA_TREE_OBJECTID 8ULL
 
+/* holds hashes of all the data extents for dedup */
+#define BTRFS_DEDUP_TREE_OBJECTID 9ULL
+
 /* orhpan objectid for tracking unlinked/truncated files */
 #define BTRFS_ORPHAN_OBJECTID -5ULL
 
@@ -419,11 +422,14 @@ struct btrfs_root_backup {
 	__le64 csum_root;
 	__le64 csum_root_gen;
 
+	__le64 dedup_root;
+	__le64 dedup_root_gen;
+
 	__le64 total_bytes;
 	__le64 bytes_used;
 	__le64 num_devices;
 	/* future */
-	__le64 unused_64[4];
+	__le64 unused_64[2];
 
 	u8 tree_root_level;
 	u8 chunk_root_level;
@@ -431,8 +437,9 @@ struct btrfs_root_backup {
 	u8 fs_root_level;
 	u8 dev_root_level;
 	u8 csum_root_level;
+	u8 dedup_root_level;
 	/* future and to align */
-	u8 unused_8[10];
+	u8 unused_8[9];
 } __attribute__ ((__packed__));
 
 /*
@@ -463,6 +470,7 @@ struct btrfs_super_block {
 	__le32 nodesize;
 	__le32 leafsize;
 	__le32 stripesize;
+	__le32 dedupsize;
 	__le32 sys_chunk_array_size;
 	__le64 chunk_root_generation;
 	__le64 compat_flags;
@@ -890,6 +898,12 @@ struct btrfs_csum_item {
 	u8 csum;
 } __attribute__ ((__packed__));
 
+struct btrfs_dedup_item {
+	__le64 disk_bytenr;
+	__le64 disk_num_bytes;
+	__le64 extent_offset;
+} __attribute__ ((__packed__));
+
 struct btrfs_dev_stats_item {
 	/*
 	 * grow this item struct at the end for future enhancements and keep
@@ -1288,6 +1302,7 @@ struct btrfs_fs_info {
 	struct btrfs_root *fs_root;
 	struct btrfs_root *csum_root;
 	struct btrfs_root *quota_root;
+	struct btrfs_root *dedup_root;
 
 	/* the log root tree is a directory of all the other log roots */
 	struct btrfs_root *log_root_tree;
@@ -1665,6 +1680,8 @@ struct btrfs_root {
 
 	u32 stripesize;
 
+	u32 dedupsize;
+
 	u32 type;
 
 	u64 highest_objectid;
@@ -1790,6 +1807,12 @@ struct btrfs_ioctl_defrag_range_args {
 #define BTRFS_ROOT_ITEM_KEY	132
 
 /*
+ * extents hashes are stored in a separate tree and hold hashes of
+ * dedup-sized block for an entire extent on disk.
+ */
+#define BTRFS_DEDUP_ITEM_KEY	136
+
+/*
  * root backrefs tie subvols and snapshots to the directory entries that
  * reference them
  */
@@ -1900,6 +1923,7 @@ struct btrfs_ioctl_defrag_range_args {
 #define BTRFS_MOUNT_CHECK_INTEGRITY	(1 << 20)
 #define BTRFS_MOUNT_CHECK_INTEGRITY_INCLUDING_EXTENT_DATA (1 << 21)
 #define BTRFS_MOUNT_PANIC_ON_FATAL_ERROR	(1 << 22)
+#define BTRFS_MOUNT_DEDUP		(1 << 23)
 
 #define btrfs_clear_opt(o, opt)		((o) &= ~BTRFS_MOUNT_##opt)
 #define btrfs_set_opt(o, opt)		((o) |= BTRFS_MOUNT_##opt)
@@ -2629,6 +2653,12 @@ BTRFS_SETGET_STACK_FUNCS(backup_csum_root_gen, struct btrfs_root_backup,
 		   csum_root_gen, 64);
 BTRFS_SETGET_STACK_FUNCS(backup_csum_root_level, struct btrfs_root_backup,
 		   csum_root_level, 8);
+BTRFS_SETGET_STACK_FUNCS(backup_dedup_root, struct btrfs_root_backup,
+		   dedup_root, 64);
+BTRFS_SETGET_STACK_FUNCS(backup_dedup_root_gen, struct btrfs_root_backup,
+		   dedup_root_gen, 64);
+BTRFS_SETGET_STACK_FUNCS(backup_dedup_root_level, struct btrfs_root_backup,
+		   dedup_root_level, 8);
 BTRFS_SETGET_STACK_FUNCS(backup_total_bytes, struct btrfs_root_backup,
 		   total_bytes, 64);
 BTRFS_SETGET_STACK_FUNCS(backup_bytes_used, struct btrfs_root_backup,
@@ -2749,6 +2779,8 @@ BTRFS_SETGET_STACK_FUNCS(super_leafsize, struct btrfs_super_block,
 			 leafsize, 32);
 BTRFS_SETGET_STACK_FUNCS(super_stripesize, struct btrfs_super_block,
 			 stripesize, 32);
+BTRFS_SETGET_STACK_FUNCS(super_dedupsize, struct btrfs_super_block,
+			 dedupsize, 32);
 BTRFS_SETGET_STACK_FUNCS(super_root_dir, struct btrfs_super_block,
 			 root_dir_objectid, 64);
 BTRFS_SETGET_STACK_FUNCS(super_num_devices, struct btrfs_super_block,
@@ -2867,6 +2899,12 @@ BTRFS_SETGET_FUNCS(qgroup_status_flags, struct btrfs_qgroup_status_item,
 BTRFS_SETGET_FUNCS(qgroup_status_scan, struct btrfs_qgroup_status_item,
 		   scan, 64);
 
+/* btrfs_dedup_item */
+BTRFS_SETGET_FUNCS(dedup_disk_bytenr, struct btrfs_dedup_item, disk_bytenr, 64);
+BTRFS_SETGET_FUNCS(dedup_disk_num_bytes, struct btrfs_dedup_item, disk_num_bytes, 64);
+BTRFS_SETGET_FUNCS(dedup_extent_offset, struct btrfs_dedup_item, extent_offset, 64);
+
+
 /* btrfs_qgroup_info_item */
 BTRFS_SETGET_FUNCS(qgroup_info_generation, struct btrfs_qgroup_info_item,
 		   generation, 64);
@@ -3308,6 +3346,7 @@ static inline void free_fs_info(struct btrfs_fs_info *fs_info)
 	kfree(fs_info->dev_root);
 	kfree(fs_info->csum_root);
 	kfree(fs_info->quota_root);
+	kfree(fs_info->dedup_root);
 	kfree(fs_info->super_copy);
 	kfree(fs_info->super_for_commit);
 	kfree(fs_info);
diff --git a/fs/btrfs/dedup.c b/fs/btrfs/dedup.c
new file mode 100644
index 0000000..0baedd2
--- /dev/null
+++ b/fs/btrfs/dedup.c
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2013 XXX.  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 v2 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ */
+
+#include "ctree.h"
+#include "disk-io.h"
+#include "dedup.h"
+#include "btrfs_inode.h"
+
+#include <linux/crc32c.h> // TODO change this
+
+static int hash_data(struct btrfs_trans_handle *trans,
+		     struct btrfs_root *root,
+		     struct inode *inode)
+{
+	int ret = 0;
+	struct page *page;
+	void *addr;
+	char *buffer;
+	pgoff_t index;
+	pgoff_t last_index;
+	int bytes_read = 0;
+	struct btrfs_root *dedup_root = root->fs_info->dedup_root;
+	struct btrfs_file_extent_item *fi;
+	u64 disk_bytenr = 0;
+	u64 num_bytes = 0;
+	u64 extent_offset = 0;
+	u64 hash;
+	int extent_type;
+	u64 ino = btrfs_ino(inode);
+	u64 offset = 0;
+	struct btrfs_path *path;
+	struct extent_buffer *leaf;
+
+	buffer = kmalloc(PAGE_CACHE_SIZE, GFP_NOFS);
+	if (!buffer)
+		return -ENOMEM;
+
+	path = btrfs_alloc_path();
+	if (!path)
+		return -ENOMEM;
+
+	index = 0;
+	last_index = (inode->i_size - 1) >> PAGE_CACHE_SHIFT;
+
+	while (index <= last_index) {
+		if (bytes_read == offset)
+		{
+			ret = btrfs_lookup_file_extent(trans, root,
+							path, ino,
+							offset, 0);
+			// check ret
+			leaf = path->nodes[0];
+			fi = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_file_extent_item);
+			extent_type = btrfs_file_extent_type(leaf, fi);
+			if (extent_type == BTRFS_FILE_EXTENT_REG ||
+			    extent_type == BTRFS_FILE_EXTENT_PREALLOC) {
+				disk_bytenr = btrfs_file_extent_disk_bytenr(leaf, fi);
+				num_bytes = btrfs_file_extent_disk_num_bytes(leaf, fi);
+				extent_offset = btrfs_file_extent_offset(leaf, fi);
+			}
+			// XXX else
+			//
+
+			offset = num_bytes;
+		}
+		bytes_read += PAGE_CACHE_SIZE;
+
+		page = grab_cache_page(inode->i_mapping, index);
+		if (!page) {
+			ret = -EINVAL;
+			goto out;
+		}
+		if (!PageUptodate(page)) {
+			btrfs_readpage(NULL, page);
+			lock_page(page);
+			if (!PageUptodate(page)) {
+				unlock_page(page);
+				page_cache_release(page);
+				ret = -EINVAL;
+				goto out;
+			}
+		}
+
+		addr = kmap(page);
+		memcpy(buffer, addr, PAGE_CACHE_SIZE);
+		hash = crc32c(0, (unsigned char *)buffer, PAGE_CACHE_SIZE);
+		ret = btrfs_insert_dedup_item(trans,
+					      dedup_root,
+					      root->objectid,
+					      hash,
+					      disk_bytenr,
+					      num_bytes,
+					      extent_offset+bytes_read);
+		// TODO chech ret
+		kunmap(page);
+		unlock_page(page);
+		page_cache_release(page);
+		index++;
+	}
+
+out:
+	kfree(buffer);
+	btrfs_free_path(path);
+	return ret;
+}
+
+int btrfs_insert_dedup_items(struct btrfs_trans_handle *trans,
+			     struct btrfs_root *root)
+{
+	int ret;
+	struct btrfs_key key;
+	struct btrfs_key found_key;
+	struct btrfs_path *path;
+	struct inode *inode = NULL;
+	struct extent_buffer *leaf;
+
+	path = btrfs_alloc_path();
+	if (!path)
+		return -ENOMEM;
+
+	key.objectid = (u64)-1;
+	key.offset = 0;
+	btrfs_set_key_type(&key, BTRFS_INODE_ITEM_KEY);
+
+	ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
+	if (ret < 0)
+		goto err;
+
+	BUG_ON(ret == 0);
+	while (1) {
+		ret = btrfs_previous_item(root, path, 0,
+					  BTRFS_INODE_ITEM_KEY);
+		if (ret < 0)
+			goto err;
+		if (ret)
+			break;
+
+		leaf = path->nodes[0];
+		btrfs_item_key_to_cpu(path->nodes[0], &found_key,
+				      path->slots[0]);
+		inode = btrfs_iget(root->fs_info->sb, &found_key,
+				   root, NULL);
+
+		if (IS_ERR(inode)) {
+			ret = PTR_ERR(inode);
+			goto err;
+		}
+
+		if (!S_ISREG(inode->i_mode) ||
+		    BTRFS_I(inode)->flags & BTRFS_INODE_NODATACOW ||
+		    BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM ||
+		    BTRFS_I(inode)->flags & BTRFS_INODE_COMPRESS ||
+		    BTRFS_I(inode)->flags & BTRFS_INODE_PREALLOC ||
+		    BTRFS_I(inode)->force_compress)
+		{
+			iput(inode);
+			continue;
+		}
+
+		ret = hash_data(trans, root, inode);
+		iput(inode);
+
+		if (ret < 0)
+			goto err;
+	}
+
+	ret = 0;
+err:
+	btrfs_free_path(path);
+	return ret;
+}
+
+int btrfs_insert_dedup_item(struct btrfs_trans_handle *trans,
+			    struct btrfs_root *root,
+			    u64 rootid,
+			    u64 hash, u64 disk_bytenr, u64 disk_num_bytes,
+			    u64 extent_offset)
+{
+	int ret = 0;
+	struct btrfs_dedup_item *item;
+	struct btrfs_key key;
+	struct btrfs_path *path;
+	struct extent_buffer *leaf;
+
+	path = btrfs_alloc_path();
+	if (!path)
+		return -ENOMEM;
+
+	key.objectid = rootid;
+	key.offset = hash; // XXX
+	btrfs_set_key_type(&key, BTRFS_DEDUP_ITEM_KEY);
+
+	ret = btrfs_insert_empty_item(trans, root, path, &key,
+				      sizeof(*item));
+	if (ret == -EEXIST) {
+		ret = 0;
+		goto out;
+	}
+	if (ret < 0)
+		goto out;
+	BUG_ON(ret);
+	leaf = path->nodes[0];
+	item = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_dedup_item);
+
+	btrfs_set_dedup_disk_bytenr(leaf, item, disk_bytenr);
+	btrfs_set_dedup_disk_num_bytes(leaf, item, disk_num_bytes);
+	btrfs_set_dedup_extent_offset(leaf, item, extent_offset);
+
+	btrfs_mark_buffer_dirty(leaf);
+out:
+	btrfs_free_path(path);
+	return ret;
+}
+
+int btrfs_lookup_dedup_item(struct btrfs_trans_handle *trans,
+			    struct btrfs_root *root,
+			    struct btrfs_path *path,
+			    u64 rootid, u64 hash, int mod)
+{
+	int ret;
+	struct btrfs_key key;
+	int ins_len = mod < 0 ? -1 : 0;
+	int cow = mod != 0;
+
+	printk(KERN_INFO "rootid = '%llu', hash = '%llu'\n", rootid, hash);
+
+	key.objectid = rootid;
+	key.offset = hash; // XXX
+	btrfs_set_key_type(&key, BTRFS_DEDUP_ITEM_KEY);
+	ret = btrfs_search_slot(trans, root, &key, path, ins_len, cow);
+	return ret;
+}
+
+int btrfs_del_dedup_item(struct btrfs_trans_handle *trans,
+			 struct btrfs_root *root,
+			 u64 rootid, u64 hash)
+{
+	int ret;
+	struct btrfs_path *path;
+	path = btrfs_alloc_path();
+	if (!path) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	ret = btrfs_lookup_dedup_item(trans, root, path, rootid, hash, -1);
+	if (ret) {
+		ret = -ENOENT;
+		goto out;
+	}
+	ret = btrfs_del_item(trans, root, path);
+out:
+	btrfs_free_path(path);
+	return ret;
+}
diff --git a/fs/btrfs/dedup.h b/fs/btrfs/dedup.h
new file mode 100644
index 0000000..6078c26
--- /dev/null
+++ b/fs/btrfs/dedup.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2013 XXX.  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 v2 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.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ */
+
+#ifndef __BTRFS_DEDUP_
+#define __BTRFS_DEDUP_
+
+
+int btrfs_insert_dedup_items(struct btrfs_trans_handle *trans,
+			     struct btrfs_root *root);
+
+int btrfs_insert_dedup_item(struct btrfs_trans_handle *trans,
+			    struct btrfs_root *root,
+			    u64 rootid,
+			    u64 hash, u64 disk_bytenr, u64 disk_num_bytes,
+			    u64 offset);
+
+
+int btrfs_lookup_dedup_item(struct btrfs_trans_handle *trans,
+			    struct btrfs_root *root,
+			    struct btrfs_path *path,
+			    u64 rootid, u64 hash, int mod);
+
+int btrfs_del_dedup_item(struct btrfs_trans_handle *trans,
+			 struct btrfs_root *root,
+			 u64 rootid, u64 hash);
+
+#endif
diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index 7d84651..58ec49c 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -1162,7 +1162,8 @@ void clean_tree_block(struct btrfs_trans_handle *trans, struct btrfs_root *root,
 }
 
 static void __setup_root(u32 nodesize, u32 leafsize, u32 sectorsize,
-			 u32 stripesize, struct btrfs_root *root,
+			 u32 stripesize, u32 dedupsize,
+			 struct btrfs_root *root,
 			 struct btrfs_fs_info *fs_info,
 			 u64 objectid)
 {
@@ -1172,6 +1173,7 @@ static void __setup_root(u32 nodesize, u32 leafsize, u32 sectorsize,
 	root->nodesize = nodesize;
 	root->leafsize = leafsize;
 	root->stripesize = stripesize;
+	root->dedupsize = dedupsize;
 	root->ref_cows = 0;
 	root->track_dirty = 0;
 	root->in_radix = 0;
@@ -1235,7 +1237,7 @@ static int __must_check find_and_setup_root(struct btrfs_root *tree_root,
 
 	__setup_root(tree_root->nodesize, tree_root->leafsize,
 		     tree_root->sectorsize, tree_root->stripesize,
-		     root, fs_info, objectid);
+		     tree_root->dedupsize, root, fs_info, objectid);
 	ret = btrfs_find_last_root(tree_root, objectid,
 				   &root->root_item, &root->root_key);
 	if (ret > 0)
@@ -1282,7 +1284,7 @@ struct btrfs_root *btrfs_create_tree(struct btrfs_trans_handle *trans,
 
 	__setup_root(tree_root->nodesize, tree_root->leafsize,
 		     tree_root->sectorsize, tree_root->stripesize,
-		     root, fs_info, objectid);
+		     tree_root->dedupsize, root, fs_info, objectid);
 	root->root_key.objectid = objectid;
 	root->root_key.type = BTRFS_ROOT_ITEM_KEY;
 	root->root_key.offset = 0;
@@ -1353,8 +1355,9 @@ static struct btrfs_root *alloc_log_tree(struct btrfs_trans_handle *trans,
 		return ERR_PTR(-ENOMEM);
 
 	__setup_root(tree_root->nodesize, tree_root->leafsize,
-		     tree_root->sectorsize, tree_root->stripesize,
-		     root, fs_info, BTRFS_TREE_LOG_OBJECTID);
+		     tree_root->dedupsize, tree_root->sectorsize,
+		     tree_root->stripesize, root, fs_info,
+		     BTRFS_TREE_LOG_OBJECTID);
 
 	root->root_key.objectid = BTRFS_TREE_LOG_OBJECTID;
 	root->root_key.type = BTRFS_ROOT_ITEM_KEY;
@@ -1458,8 +1461,9 @@ struct btrfs_root *btrfs_read_fs_root_no_radix(struct btrfs_root *tree_root,
 	}
 
 	__setup_root(tree_root->nodesize, tree_root->leafsize,
-		     tree_root->sectorsize, tree_root->stripesize,
-		     root, fs_info, location->objectid);
+		     tree_root->dedupsize, tree_root->sectorsize,
+		     tree_root->stripesize, root, fs_info,
+		     location->objectid);
 
 	path = btrfs_alloc_path();
 	if (!path) {
@@ -1512,6 +1516,8 @@ struct btrfs_root *btrfs_read_fs_root_no_name(struct btrfs_fs_info *fs_info,
 		return fs_info->dev_root;
 	if (location->objectid == BTRFS_CSUM_TREE_OBJECTID)
 		return fs_info->csum_root;
+	if (location->objectid == BTRFS_DEDUP_TREE_OBJECTID)
+		return fs_info->dedup_root;
 	if (location->objectid == BTRFS_QUOTA_TREE_OBJECTID)
 		return fs_info->quota_root ? fs_info->quota_root :
 					     ERR_PTR(-ENOENT);
@@ -1861,6 +1867,12 @@ static void backup_super_roots(struct btrfs_fs_info *info)
 	btrfs_set_backup_csum_root_level(root_backup,
 			       btrfs_header_level(info->csum_root->node));
 
+	btrfs_set_backup_dedup_root(root_backup, info->dedup_root->node->start);
+	btrfs_set_backup_dedup_root_gen(root_backup,
+			       btrfs_header_generation(info->dedup_root->node));
+	btrfs_set_backup_dedup_root_level(root_backup,
+			       btrfs_header_level(info->dedup_root->node));
+
 	btrfs_set_backup_total_bytes(root_backup,
 			     btrfs_super_total_bytes(info->super_copy));
 	btrfs_set_backup_bytes_used(root_backup,
@@ -1940,6 +1952,8 @@ static void free_root_pointers(struct btrfs_fs_info *info, int chunk_root)
 	free_extent_buffer(info->extent_root->commit_root);
 	free_extent_buffer(info->csum_root->node);
 	free_extent_buffer(info->csum_root->commit_root);
+	free_extent_buffer(info->dedup_root->node);
+	free_extent_buffer(info->dedup_root->commit_root);
 	if (info->quota_root) {
 		free_extent_buffer(info->quota_root->node);
 		free_extent_buffer(info->quota_root->commit_root);
@@ -1953,6 +1967,8 @@ static void free_root_pointers(struct btrfs_fs_info *info, int chunk_root)
 	info->extent_root->commit_root = NULL;
 	info->csum_root->node = NULL;
 	info->csum_root->commit_root = NULL;
+	info->dedup_root->node = NULL;
+	info->dedup_root->commit_root = NULL;
 	if (info->quota_root) {
 		info->quota_root->node = NULL;
 		info->quota_root->commit_root = NULL;
@@ -1976,6 +1992,7 @@ int open_ctree(struct super_block *sb,
 	u32 leafsize;
 	u32 blocksize;
 	u32 stripesize;
+	u32 dedupsize;
 	u64 generation;
 	u64 features;
 	struct btrfs_key location;
@@ -1985,6 +2002,7 @@ int open_ctree(struct super_block *sb,
 	struct btrfs_root *tree_root;
 	struct btrfs_root *extent_root;
 	struct btrfs_root *csum_root;
+	struct btrfs_root *dedup_root;
 	struct btrfs_root *chunk_root;
 	struct btrfs_root *dev_root;
 	struct btrfs_root *quota_root;
@@ -1997,12 +2015,14 @@ int open_ctree(struct super_block *sb,
 	tree_root = fs_info->tree_root = btrfs_alloc_root(fs_info);
 	extent_root = fs_info->extent_root = btrfs_alloc_root(fs_info);
 	csum_root = fs_info->csum_root = btrfs_alloc_root(fs_info);
+	dedup_root = fs_info->dedup_root = btrfs_alloc_root(fs_info);
 	chunk_root = fs_info->chunk_root = btrfs_alloc_root(fs_info);
 	dev_root = fs_info->dev_root = btrfs_alloc_root(fs_info);
 	quota_root = fs_info->quota_root = btrfs_alloc_root(fs_info);
 
 	if (!tree_root || !extent_root || !csum_root ||
-	    !chunk_root || !dev_root || !quota_root) {
+	    !dedup_root || !chunk_root || !dev_root ||
+	    !quota_root) {
 		err = -ENOMEM;
 		goto fail;
 	}
@@ -2201,7 +2221,7 @@ int open_ctree(struct super_block *sb,
 		goto fail_alloc;
 	}
 
-	__setup_root(4096, 4096, 4096, 4096, tree_root,
+	__setup_root(4096, 4096, 4096, 4096, 4096, tree_root,
 		     fs_info, BTRFS_ROOT_TREE_OBJECTID);
 
 	invalidate_bdev(fs_devices->latest_bdev);
@@ -2298,6 +2318,7 @@ int open_ctree(struct super_block *sb,
 	leafsize = btrfs_super_leafsize(disk_super);
 	sectorsize = btrfs_super_sectorsize(disk_super);
 	stripesize = btrfs_super_stripesize(disk_super);
+	dedupsize = btrfs_super_dedupsize(disk_super);
 	fs_info->dirty_metadata_batch = leafsize * (1 + ilog2(nr_cpu_ids));
 	fs_info->delalloc_batch = sectorsize * 512 * (1 + ilog2(nr_cpu_ids));
 
@@ -2435,6 +2456,7 @@ int open_ctree(struct super_block *sb,
 	tree_root->leafsize = leafsize;
 	tree_root->sectorsize = sectorsize;
 	tree_root->stripesize = stripesize;
+	tree_root->dedupsize = dedupsize;
 
 	sb->s_blocksize = sectorsize;
 	sb->s_blocksize_bits = blksize_bits(sectorsize);
@@ -2463,7 +2485,7 @@ int open_ctree(struct super_block *sb,
 				     btrfs_super_chunk_root_level(disk_super));
 	generation = btrfs_super_chunk_root_generation(disk_super);
 
-	__setup_root(nodesize, leafsize, sectorsize, stripesize,
+	__setup_root(nodesize, leafsize, sectorsize, stripesize, dedupsize,
 		     chunk_root, fs_info, BTRFS_CHUNK_TREE_OBJECTID);
 
 	chunk_root->node = read_tree_block(chunk_root,
@@ -2539,6 +2561,13 @@ retry_root_backup:
 	csum_root->track_dirty = 1;
 
 	ret = find_and_setup_root(tree_root, fs_info,
+				  BTRFS_DEDUP_TREE_OBJECTID, dedup_root);
+	if (ret)
+		goto recovery_tree_root;
+	dedup_root->track_dirty = 1;
+
+
+	ret = find_and_setup_root(tree_root, fs_info,
 				  BTRFS_QUOTA_TREE_OBJECTID, quota_root);
 	if (ret) {
 		kfree(quota_root);
@@ -2649,7 +2678,7 @@ retry_root_backup:
 			goto fail_qgroup;
 		}
 
-		__setup_root(nodesize, leafsize, sectorsize, stripesize,
+		__setup_root(nodesize, leafsize, sectorsize, stripesize, dedupsize,
 			     log_tree_root, fs_info, BTRFS_TREE_LOG_OBJECTID);
 
 		log_tree_root->node = read_tree_block(tree_root, bytenr,
@@ -3430,6 +3459,8 @@ int close_ctree(struct btrfs_root *root)
 	free_extent_buffer(fs_info->dev_root->commit_root);
 	free_extent_buffer(fs_info->csum_root->node);
 	free_extent_buffer(fs_info->csum_root->commit_root);
+	free_extent_buffer(fs_info->dedup_root->node);
+	free_extent_buffer(fs_info->dedup_root->commit_root);
 	if (fs_info->quota_root) {
 		free_extent_buffer(fs_info->quota_root->node);
 		free_extent_buffer(fs_info->quota_root->commit_root);
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 1f268888..2d06f9b 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -39,6 +39,7 @@
 #include <linux/slab.h>
 #include <linux/ratelimit.h>
 #include <linux/mount.h>
+#include <linux/crc32c.h>
 #include <linux/btrfs.h>
 #include <linux/blkdev.h>
 #include "compat.h"
@@ -56,6 +57,7 @@
 #include "free-space-cache.h"
 #include "inode-map.h"
 #include "backref.h"
+#include "dedup.h"
 
 struct btrfs_iget_args {
 	u64 ino;
@@ -96,7 +98,8 @@ static int btrfs_finish_ordered_io(struct btrfs_ordered_extent *ordered_extent);
 static noinline int cow_file_range(struct inode *inode,
 				   struct page *locked_page,
 				   u64 start, u64 end, int *page_started,
-				   unsigned long *nr_written, int unlock);
+				   unsigned long *nr_written, int unlock,
+				   struct btrfs_dedup_metadata *dedup_metadata);
 static struct extent_map *create_pinned_em(struct inode *inode, u64 start,
 					   u64 len, u64 orig_start,
 					   u64 block_start, u64 block_len,
@@ -631,7 +634,7 @@ retry:
 					     async_extent->start,
 					     async_extent->start +
 					     async_extent->ram_size - 1,
-					     &page_started, &nr_written, 0);
+					     &page_started, &nr_written, 0, NULL);
 
 			/* JDM XXX */
 
@@ -838,8 +841,8 @@ static noinline int __cow_file_range(struct btrfs_trans_handle *trans,
 				     struct btrfs_root *root,
 				     struct page *locked_page,
 				     u64 start, u64 end, int *page_started,
-				     unsigned long *nr_written,
-				     int unlock)
+				     unsigned long *nr_written, int unlock,
+				     struct btrfs_dedup_metadata *dedup_metadata)
 {
 	u64 alloc_hint = 0;
 	u64 num_bytes;
@@ -938,8 +941,13 @@ static noinline int __cow_file_range(struct btrfs_trans_handle *trans,
 		}
 
 		cur_alloc_size = ins.offset;
-		ret = btrfs_add_ordered_extent(inode, start, ins.objectid,
-					       ram_size, cur_alloc_size, 0);
+		if (dedup_metadata)
+			ret = btrfs_add_ordered_extent_dedup(inode, start, ins.objectid,
+						       ram_size, cur_alloc_size, 0,
+						       dedup_metadata);
+		else
+			ret = btrfs_add_ordered_extent(inode, start, ins.objectid,
+						       ram_size, cur_alloc_size, 0);
 		BUG_ON(ret); /* -ENOMEM */
 
 		if (root->root_key.objectid ==
@@ -994,8 +1002,8 @@ out_unlock:
 static noinline int cow_file_range(struct inode *inode,
 				   struct page *locked_page,
 				   u64 start, u64 end, int *page_started,
-				   unsigned long *nr_written,
-				   int unlock)
+				   unsigned long *nr_written, int unlock,
+				   struct btrfs_dedup_metadata *dedup_metadata)
 {
 	struct btrfs_trans_handle *trans;
 	struct btrfs_root *root = BTRFS_I(inode)->root;
@@ -1017,7 +1025,7 @@ static noinline int cow_file_range(struct inode *inode,
 	trans->block_rsv = &root->fs_info->delalloc_block_rsv;
 
 	ret = __cow_file_range(trans, inode, root, locked_page, start, end,
-			       page_started, nr_written, unlock);
+			       page_started, nr_written, unlock, dedup_metadata);
 
 	btrfs_end_transaction(trans, root);
 
@@ -1339,7 +1347,7 @@ out_check:
 		if (cow_start != (u64)-1) {
 			ret = __cow_file_range(trans, inode, root, locked_page,
 					       cow_start, found_key.offset - 1,
-					       page_started, nr_written, 1);
+					       page_started, nr_written, 1, NULL);
 			if (ret) {
 				btrfs_abort_transaction(trans, root, ret);
 				goto error;
@@ -1417,7 +1425,7 @@ out_check:
 	if (cow_start != (u64)-1) {
 		ret = __cow_file_range(trans, inode, root, locked_page,
 				       cow_start, end,
-				       page_started, nr_written, 1);
+				       page_started, nr_written, 1, NULL);
 		if (ret) {
 			btrfs_abort_transaction(trans, root, ret);
 			goto error;
@@ -1444,6 +1452,156 @@ error:
 	return ret;
 }
 
+static noinline int cow_file_range_dedup(struct inode *inode,
+					struct page *locked_page,
+					u64 start, u64 end, int *page_started,
+					unsigned long *nr_written,
+					int unlock)
+{
+	struct btrfs_root *root = BTRFS_I(inode)->root;
+	struct btrfs_trans_handle *trans;
+	struct btrfs_path *path;
+	struct btrfs_dedup_item *item;
+	struct btrfs_dedup_metadata *dedup_metadata;
+	struct btrfs_duplicate *duplicate;
+	struct btrfs_dedup_hash *hash_entry;
+	struct extent_buffer *leaf;
+	int ret = 0;
+	char *data;
+	struct page *page = NULL;
+	unsigned long written;
+	u64 bytes_left = end - start + 1;
+	u64 start_write = start;
+	u64 len_write = 0;
+	u64 start_new = start;
+	u64 hash;
+	int start_dup = 1;
+
+	dedup_metadata = kzalloc(sizeof(struct btrfs_dedup_metadata), GFP_NOFS);
+	BUG_ON(!dedup_metadata);
+	INIT_LIST_HEAD(&dedup_metadata->hashes.list);
+	INIT_LIST_HEAD(&dedup_metadata->duplicates.list);
+
+	trans = btrfs_join_transaction(root);
+	BUG_ON(IS_ERR(trans));
+
+/*
+ * Currently, the code below does not handle:
+ * 1. extents consisting of duplicate blocks only
+ *  - reason: linked list of duplicates cannot be passed to finish_ordered_io
+ *            since cow_file_range is not called
+ *  - solution: insert file_extent_items here
+ *
+ * 2. the following case: an extent consist of blocks x|y|x, block x is not on
+ *    disk, therefore the last block x is not considered as duplicate
+ *  - solution: change linked list of dedup_items to rbtree and look for
+ *              duplicates there as well as in the dedup tree
+ *
+ * Also, for now, this assumes that:
+ * - incoming range is block-alligned
+ * - start == 0
+ * - incoming range is written in one extent
+ * - dedupsize == PAGE_CACHE_SHIFT
+ */
+	while (bytes_left > 0) {
+		page = find_get_page(inode->i_mapping,
+					start_new >> PAGE_CACHE_SHIFT);
+		data = kmap(page);
+
+		hash = ~(u64)0;
+		hash = crc32c(hash, data, PAGE_CACHE_SIZE);
+		path = btrfs_alloc_path();
+		BUG_ON(!path);
+
+		ret = btrfs_lookup_dedup_item(trans, root->fs_info->dedup_root, path,
+						root->objectid, hash, 0);
+
+		if (!ret) {
+			printk(KERN_INFO "duplicate\n");
+			duplicate = kzalloc(
+				sizeof(struct btrfs_duplicate),
+				GFP_NOFS);
+			BUG_ON(!duplicate);
+
+			leaf = path->nodes[0];
+			item = btrfs_item_ptr(leaf, path->slots[0],
+						struct btrfs_dedup_item);
+
+			duplicate->disk_bytenr = btrfs_dedup_disk_bytenr(leaf, item);
+			duplicate->num_bytes = btrfs_dedup_disk_num_bytes(leaf, item);
+			duplicate->file_offset = start_new; // XXX start != 0 ?
+			duplicate->extent_offset = btrfs_dedup_extent_offset(leaf, item);
+			INIT_LIST_HEAD(&duplicate->list);
+			list_add_tail(&duplicate->list,
+					&dedup_metadata->duplicates.list);
+
+			extent_clear_unlock_delalloc(inode,
+				     &BTRFS_I(inode)->io_tree,
+				     start_new, start_new + PAGE_CACHE_SIZE - 1,
+				     locked_page,
+				     EXTENT_CLEAR_UNLOCK_PAGE |
+				     EXTENT_CLEAR_UNLOCK |
+				     EXTENT_CLEAR_DELALLOC |
+				     EXTENT_CLEAR_DIRTY |
+				     EXTENT_SET_WRITEBACK |
+				     EXTENT_END_WRITEBACK);
+			if (start_dup)
+				start_write = start_new + PAGE_CACHE_SIZE;
+		}
+		else {
+			start_dup = 0;
+			printk(KERN_INFO "unique\n");
+			if (!list_empty(&dedup_metadata->duplicates.list) && len_write) {
+				printk(KERN_INFO "write %llu %llu\n", start_write, start_write + len_write - 1);
+				ret = cow_file_range(inode, locked_page,
+						start_write, start_write + len_write - 1,
+						page_started, &written,
+						unlock, dedup_metadata);
+				*nr_written += written;
+				start_write = start_new;
+				len_write = 0;
+
+				dedup_metadata = kzalloc(
+					sizeof(struct btrfs_dedup_metadata),
+					GFP_NOFS);
+				BUG_ON(!dedup_metadata);
+				INIT_LIST_HEAD(&dedup_metadata->hashes.list);
+				INIT_LIST_HEAD(&dedup_metadata->duplicates.list);
+			}
+
+			hash_entry = kzalloc(
+				sizeof(struct btrfs_dedup_hash),
+				GFP_NOFS);
+			BUG_ON(!hash_entry);
+			hash_entry->extent_offset = start_new; // XXX start != 0 ?
+			hash_entry->hash = hash;
+			INIT_LIST_HEAD(&hash_entry->list);
+			list_add_tail(&hash_entry->list,
+					&dedup_metadata->hashes.list);
+			len_write += PAGE_CACHE_SIZE; // XXX dedup_block
+		}
+
+		bytes_left -= PAGE_CACHE_SIZE;
+		start_new += PAGE_CACHE_SIZE;
+
+		kunmap(page);
+		page_cache_release(page);
+		btrfs_free_path(path);
+
+		if (bytes_left <= 0) {
+			printk(KERN_INFO "final write %llu %llu\n", start_write, start_write + len_write - 1);
+			ret = cow_file_range(inode, locked_page,
+					start_write, start_write + len_write - 1,
+					page_started, &written,
+					unlock, dedup_metadata);
+			*nr_written += written;
+		}
+	}
+
+	btrfs_end_transaction(trans, root);
+	return ret;
+}
+
 /*
  * extent_io.c call back to do delayed allocation processing
  */
@@ -1464,12 +1622,15 @@ static int run_delalloc_range(struct inode *inode, struct page *locked_page,
 		   !(BTRFS_I(inode)->force_compress) &&
 		   !(BTRFS_I(inode)->flags & BTRFS_INODE_COMPRESS)) {
 		ret = cow_file_range(inode, locked_page, start, end,
-				      page_started, nr_written, 1);
-	} else {
+				      page_started, nr_written, 1, NULL);
+	} else if (!btrfs_test_opt(root, DEDUP)) {
 		set_bit(BTRFS_INODE_HAS_ASYNC_EXTENT,
 			&BTRFS_I(inode)->runtime_flags);
 		ret = cow_file_range_async(inode, locked_page, start, end,
 					   page_started, nr_written);
+	} else {
+		ret = cow_file_range_dedup(inode, locked_page, start, end,
+					   page_started, nr_written, 1);
 	}
 	return ret;
 }
@@ -2568,6 +2729,70 @@ out_kfree:
 	return NULL;
 }
 
+static int link_duplicates(struct inode *inode, struct btrfs_root *root,
+			struct btrfs_trans_handle *trans,
+			struct btrfs_ordered_extent *ordered_extent)
+{
+	int ret = 0;
+	u64 disk_bytenr;
+	u32 dedup_block = root->dedupsize;
+	struct btrfs_duplicate *duplicate, *next;
+
+	list_for_each_entry_safe(duplicate, next,
+				&ordered_extent->dedup_metadata->duplicates.list, list)
+	{
+		disk_bytenr = duplicate->disk_bytenr ?
+				duplicate->disk_bytenr : ordered_extent->start;
+
+		printk(KERN_INFO "duplicate: %llu %llu %llu %llu\n", duplicate->disk_bytenr,
+			duplicate->file_offset,
+			duplicate->extent_offset, disk_bytenr);
+
+		ret = btrfs_inc_extent_ref(trans, root, disk_bytenr,
+				duplicate->num_bytes, 0,
+				root->objectid,
+				inode->i_ino,
+				duplicate->extent_offset,
+				0);
+
+		ret = btrfs_drop_extents(trans, root, inode,
+				duplicate->file_offset,
+				duplicate->file_offset + dedup_block,
+				0);
+		ret = btrfs_insert_file_extent(trans, root,
+				inode->i_ino, duplicate->file_offset,
+				disk_bytenr, dedup_block,
+				dedup_block, duplicate->extent_offset,
+				dedup_block, 0, 0, 0);
+
+		inode_add_bytes(inode, dedup_block);
+
+		list_del(&duplicate->list);
+		kfree(duplicate);
+	}
+	return ret;
+}
+
+static int insert_dedup_metadata(struct inode *inode, struct btrfs_root *root,
+			struct btrfs_trans_handle *trans,
+			struct btrfs_ordered_extent *ordered_extent)
+{
+	int ret = 0;
+	struct btrfs_dedup_hash *hash_entry, *next;
+
+	list_for_each_entry_safe(hash_entry, next,
+				&ordered_extent->dedup_metadata->hashes.list, list)
+	{
+		btrfs_insert_dedup_item(trans, root->fs_info->dedup_root,
+					root->objectid, hash_entry->hash, ordered_extent->start,
+					ordered_extent->len, hash_entry->extent_offset);
+
+		list_del(&hash_entry->list);
+		kfree(hash_entry);
+	}
+	return ret;
+}
+
 /*
  * helper function for btrfs_finish_ordered_io, this
  * just reads in some of the csum leaves to prime them into ram
@@ -2663,6 +2888,10 @@ static int btrfs_finish_ordered_io(struct btrfs_ordered_extent *ordered_extent)
 						ordered_extent->len,
 						compress_type, 0, 0,
 						BTRFS_FILE_EXTENT_REG);
+		if (ordered_extent->dedup_metadata) {
+			ret = link_duplicates(inode, root, trans, ordered_extent);
+			ret = insert_dedup_metadata(inode, root, trans, ordered_extent);
+		}
 	}
 	unpin_extent_cache(&BTRFS_I(inode)->extent_tree,
 			   ordered_extent->file_offset, ordered_extent->len,
@@ -2677,6 +2906,11 @@ static int btrfs_finish_ordered_io(struct btrfs_ordered_extent *ordered_extent)
 
 	btrfs_ordered_update_i_size(inode, 0, ordered_extent);
 	ret = btrfs_update_inode_fallback(trans, root, inode);
+
+	if (ordered_extent->dedup_metadata) {
+		btrfs_i_size_write(inode, inode->i_size); // XXX investigate
+	}
+
 	if (ret) { /* -ENOMEM or corruption */
 		btrfs_abort_transaction(trans, root, ret);
 		goto out_unlock;
diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index 898c572..b34de46 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -57,6 +57,8 @@
 #include "send.h"
 #include "dev-replace.h"
 
+#include "dedup.h"
+
 /* Mask out flags that are inappropriate for the given type of inode. */
 static inline __u32 btrfs_mask_flags(umode_t mode, __u32 flags)
 {
@@ -4017,6 +4019,22 @@ out_unlock:
 	return ret;
 }
 
+static long btrfs_ioctl_dedup_test(struct file *file,
+				   void __user *arg)
+{
+	struct inode *inode = fdentry(file)->d_inode;
+	printk(KERN_INFO "fs tree -------------------\n");
+	btrfs_print_tree(BTRFS_I(inode)->root->fs_info->fs_root,
+			BTRFS_I(inode)->root->fs_info->fs_root->node);
+	printk(KERN_INFO "extent tree ---------------\n");
+	btrfs_print_tree(BTRFS_I(inode)->root->fs_info->extent_root,
+			BTRFS_I(inode)->root->fs_info->extent_root->node);
+	printk(KERN_INFO "dedup tree ----------------\n");
+	btrfs_print_tree(BTRFS_I(inode)->root->fs_info->dedup_root,
+			BTRFS_I(inode)->root->fs_info->dedup_root->node);
+	return 0;
+}
+
 long btrfs_ioctl(struct file *file, unsigned int
 		cmd, unsigned long arg)
 {
@@ -4121,6 +4139,8 @@ long btrfs_ioctl(struct file *file, unsigned int
 		return btrfs_ioctl_get_fslabel(file, argp);
 	case BTRFS_IOC_SET_FSLABEL:
 		return btrfs_ioctl_set_fslabel(file, argp);
+	case BTRFS_IOC_DEDUP_TEST:
+		return btrfs_ioctl_dedup_test(file, argp);
 	}
 
 	return -ENOTTY;
diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
index dc08d77..2c07b1a 100644
--- a/fs/btrfs/ordered-data.c
+++ b/fs/btrfs/ordered-data.c
@@ -182,7 +182,8 @@ static inline struct rb_node *tree_search(struct btrfs_ordered_inode_tree *tree,
  */
 static int __btrfs_add_ordered_extent(struct inode *inode, u64 file_offset,
 				      u64 start, u64 len, u64 disk_len,
-				      int type, int dio, int compress_type)
+				      int type, int dio, int compress_type,
+				      struct btrfs_dedup_metadata *dedup_metadata)
 {
 	struct btrfs_ordered_inode_tree *tree;
 	struct rb_node *node;
@@ -203,6 +204,7 @@ static int __btrfs_add_ordered_extent(struct inode *inode, u64 file_offset,
 	entry->bytes_left = len;
 	entry->inode = igrab(inode);
 	entry->compress_type = compress_type;
+	entry->dedup_metadata = dedup_metadata;
 	if (type != BTRFS_ORDERED_IO_DONE && type != BTRFS_ORDERED_COMPLETE)
 		set_bit(type, &entry->flags);
 
@@ -240,7 +242,16 @@ int btrfs_add_ordered_extent(struct inode *inode, u64 file_offset,
 {
 	return __btrfs_add_ordered_extent(inode, file_offset, start, len,
 					  disk_len, type, 0,
-					  BTRFS_COMPRESS_NONE);
+					  BTRFS_COMPRESS_NONE, NULL);
+}
+
+int btrfs_add_ordered_extent_dedup(struct inode *inode, u64 file_offset,
+			     u64 start, u64 len, u64 disk_len, int type,
+			     struct btrfs_dedup_metadata *dedup_metadata)
+{
+	return __btrfs_add_ordered_extent(inode, file_offset, start, len,
+					  disk_len, type, 0,
+					  BTRFS_COMPRESS_NONE, dedup_metadata);
 }
 
 int btrfs_add_ordered_extent_dio(struct inode *inode, u64 file_offset,
@@ -248,7 +259,7 @@ int btrfs_add_ordered_extent_dio(struct inode *inode, u64 file_offset,
 {
 	return __btrfs_add_ordered_extent(inode, file_offset, start, len,
 					  disk_len, type, 1,
-					  BTRFS_COMPRESS_NONE);
+					  BTRFS_COMPRESS_NONE, NULL);
 }
 
 int btrfs_add_ordered_extent_compress(struct inode *inode, u64 file_offset,
@@ -257,7 +268,7 @@ int btrfs_add_ordered_extent_compress(struct inode *inode, u64 file_offset,
 {
 	return __btrfs_add_ordered_extent(inode, file_offset, start, len,
 					  disk_len, type, 0,
-					  compress_type);
+					  compress_type, NULL);
 }
 
 /*
@@ -481,6 +492,8 @@ void btrfs_put_ordered_extent(struct btrfs_ordered_extent *entry)
 {
 	struct list_head *cur;
 	struct btrfs_ordered_sum *sum;
+	struct btrfs_dedup_hash *hash;
+	struct btrfs_duplicate *duplicate;
 
 	trace_btrfs_ordered_extent_put(entry->inode, entry);
 
@@ -493,6 +506,21 @@ void btrfs_put_ordered_extent(struct btrfs_ordered_extent *entry)
 			list_del(&sum->list);
 			kfree(sum);
 		}
+		if (entry->dedup_metadata) {
+			while (!list_empty(&entry->dedup_metadata->hashes.list)) {
+				cur = entry->dedup_metadata->hashes.list.next;
+				hash = list_entry(cur, struct btrfs_dedup_hash, list);
+				list_del(&hash->list);
+				kfree(hash);
+			}
+			while (!list_empty(&entry->dedup_metadata->duplicates.list)) {
+				cur = entry->dedup_metadata->duplicates.list.next;
+				duplicate = list_entry(cur, struct btrfs_duplicate, list);
+				list_del(&duplicate->list);
+				kfree(duplicate);
+			}
+			kfree(entry->dedup_metadata);
+		}
 		kmem_cache_free(btrfs_ordered_extent_cache, entry);
 	}
 }
diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
index 8eadfe4..f29e7c5 100644
--- a/fs/btrfs/ordered-data.h
+++ b/fs/btrfs/ordered-data.h
@@ -51,6 +51,26 @@ struct btrfs_ordered_sum {
 	struct btrfs_sector_sum sums[];
 };
 
+struct btrfs_dedup_hash {
+	u64 extent_offset;
+	u64 hash;
+	struct list_head list;
+};
+
+struct btrfs_duplicate {
+	u64 disk_bytenr;
+	u64 num_bytes;
+	u64 extent_offset;
+	u64 file_offset;
+	struct list_head list;
+};
+
+// XXX too complicated?
+struct btrfs_dedup_metadata {
+	struct btrfs_dedup_hash hashes;
+	struct btrfs_duplicate duplicates;
+};
+
 /*
  * bits for the flags field:
  *
@@ -114,6 +134,9 @@ struct btrfs_ordered_extent {
 	/* compression algorithm */
 	int compress_type;
 
+	/* dedup metadata */
+	struct btrfs_dedup_metadata *dedup_metadata;
+
 	/* reference count */
 	atomic_t refs;
 
@@ -176,6 +199,9 @@ int btrfs_dec_test_first_ordered_pending(struct inode *inode,
 				   int uptodate);
 int btrfs_add_ordered_extent(struct inode *inode, u64 file_offset,
 			     u64 start, u64 len, u64 disk_len, int type);
+int btrfs_add_ordered_extent_dedup(struct inode *inode, u64 file_offset,
+			     u64 start, u64 len, u64 disk_len, int type,
+			     struct btrfs_dedup_metadata *dedup_metadata);
 int btrfs_add_ordered_extent_dio(struct inode *inode, u64 file_offset,
 				 u64 start, u64 len, u64 disk_len, int type);
 int btrfs_add_ordered_extent_compress(struct inode *inode, u64 file_offset,
diff --git a/fs/btrfs/print-tree.c b/fs/btrfs/print-tree.c
index 920957e..fef4fdc 100644
--- a/fs/btrfs/print-tree.c
+++ b/fs/btrfs/print-tree.c
@@ -168,6 +168,7 @@ void btrfs_print_leaf(struct btrfs_root *root, struct extent_buffer *l)
 	struct btrfs_extent_data_ref *dref;
 	struct btrfs_shared_data_ref *sref;
 	struct btrfs_dev_extent *dev_extent;
+	struct btrfs_dedup_item *dedup_item;
 	struct btrfs_key key;
 	struct btrfs_key found_key;
 
@@ -301,6 +302,20 @@ void btrfs_print_leaf(struct btrfs_root *root, struct extent_buffer *l)
 		case BTRFS_DEV_REPLACE_KEY:
 			printk(KERN_INFO "\t\tdev replace\n");
 			break;
+		case BTRFS_DEDUP_ITEM_KEY:
+			dedup_item = btrfs_item_ptr(l, i,
+					struct btrfs_dedup_item);
+			printk(KERN_INFO "\t\tdedup disk bytenr %llu "
+			       "nr %llu "
+			       "offset %llu\n",
+			       (unsigned long long)
+			       btrfs_dedup_disk_bytenr(l, dedup_item),
+			       (unsigned long long)
+			       btrfs_dedup_disk_num_bytes(l, dedup_item),
+			       (unsigned long long)
+			       btrfs_dedup_extent_offset(l, dedup_item));
+			break;
+
 		};
 	}
 }
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 68a29a1..e770185 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -315,6 +315,7 @@ enum {
 	Opt_nodatacow, Opt_max_inline, Opt_alloc_start, Opt_nobarrier, Opt_ssd,
 	Opt_nossd, Opt_ssd_spread, Opt_thread_pool, Opt_noacl, Opt_compress,
 	Opt_compress_type, Opt_compress_force, Opt_compress_force_type,
+	Opt_dedup,
 	Opt_notreelog, Opt_ratio, Opt_flushoncommit, Opt_discard,
 	Opt_space_cache, Opt_clear_cache, Opt_user_subvol_rm_allowed,
 	Opt_enospc_debug, Opt_subvolrootid, Opt_defrag, Opt_inode_cache,
@@ -339,6 +340,7 @@ static match_table_t tokens = {
 	{Opt_compress_type, "compress=%s"},
 	{Opt_compress_force, "compress-force"},
 	{Opt_compress_force_type, "compress-force=%s"},
+	{Opt_dedup, "dedup"},
 	{Opt_ssd, "ssd"},
 	{Opt_ssd_spread, "ssd_spread"},
 	{Opt_nossd, "nossd"},
@@ -474,6 +476,16 @@ int btrfs_parse_options(struct btrfs_root *root, char *options)
 				pr_info("btrfs: use %s compression\n",
 					compress_type);
 			break;
+		case Opt_dedup:
+			printk(KERN_INFO "btrfs: use online deduplication,"
+				"dedup block size = %d\n", root->dedupsize);
+			btrfs_set_opt(info->mount_opt, DEDUP);
+			btrfs_clear_opt(info->mount_opt, NODATACOW);
+			btrfs_clear_opt(info->mount_opt, NODATASUM);
+			info->compress_type = BTRFS_COMPRESS_NONE;
+			btrfs_clear_opt(info->mount_opt, COMPRESS);
+			btrfs_clear_opt(info->mount_opt, FORCE_COMPRESS);
+			break;
 		case Opt_ssd:
 			printk(KERN_INFO "btrfs: use ssd allocation scheme\n");
 			btrfs_set_opt(info->mount_opt, SSD);
@@ -921,6 +933,8 @@ static int btrfs_show_options(struct seq_file *seq, struct dentry *dentry)
 		else
 			seq_printf(seq, ",compress=%s", compress_type);
 	}
+	if (btrfs_test_opt(root, DEDUP))
+		seq_puts(seq, ",dedup");
 	if (btrfs_test_opt(root, NOSSD))
 		seq_puts(seq, ",nossd");
 	if (btrfs_test_opt(root, SSD_SPREAD))
diff --git a/include/uapi/linux/btrfs.h b/include/uapi/linux/btrfs.h
index fa3a5f9..1232143 100644
--- a/include/uapi/linux/btrfs.h
+++ b/include/uapi/linux/btrfs.h
@@ -510,5 +510,6 @@ struct btrfs_ioctl_send_args {
 				      struct btrfs_ioctl_get_dev_stats)
 #define BTRFS_IOC_DEV_REPLACE _IOWR(BTRFS_IOCTL_MAGIC, 53, \
 				    struct btrfs_ioctl_dev_replace_args)
+#define BTRFS_IOC_DEDUP_TEST _IO(BTRFS_IOCTL_MAGIC, 54)
 
 #endif /* _UAPI_LINUX_BTRFS_H */
-- 
1.8.1.4

