Btrfs: snapshot progress

Signed-off-by: Chris Mason <chris.mason@oracle.com>
diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index 5460030..2cbcaae 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -227,7 +227,7 @@
 } __attribute__ ((__packed__));
 
 struct btrfs_inode_map_item {
-	struct btrfs_disk_key key;
+	u32 refs;
 } __attribute__ ((__packed__));
 
 struct crypto_hash;
@@ -883,6 +883,17 @@
 	e->num_blocks = cpu_to_le64(val);
 }
 
+static inline u32 btrfs_inode_map_refs(struct btrfs_inode_map_item *m)
+{
+	return le32_to_cpu(m->refs);
+}
+
+static inline void btrfs_set_inode_map_refs(struct btrfs_inode_map_item *m,
+					    u32 val)
+{
+	m->refs = cpu_to_le32(val);
+}
+
 static inline struct btrfs_root *btrfs_sb(struct super_block *sb)
 {
 	return sb->s_fs_info;
@@ -925,6 +936,8 @@
 	btrfs_item_offset((leaf)->items + (slot))))
 
 /* extent-item.c */
+int btrfs_inc_root_ref(struct btrfs_trans_handle *trans,
+		       struct btrfs_root *root);
 struct buffer_head *btrfs_alloc_free_block(struct btrfs_trans_handle *trans,
 					    struct btrfs_root *root);
 int btrfs_alloc_extent(struct btrfs_trans_handle *trans, struct btrfs_root
diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c
index 7aed9f0..0ba46bc 100644
--- a/fs/btrfs/dir-item.c
+++ b/fs/btrfs/dir-item.c
@@ -55,12 +55,14 @@
 	btrfs_set_dir_flags(dir_item, 0);
 	btrfs_set_dir_name_len(dir_item, name_len);
 	name_ptr = (char *)(dir_item + 1);
+
+	btrfs_memcpy(root, path->nodes[0]->b_data, name_ptr, name, name_len);
+	btrfs_mark_buffer_dirty(path->nodes[0]);
+
 	/* FIXME, use some real flag for selecting the extra index */
 	if (root == root->fs_info->tree_root)
 		goto out;
 
-	btrfs_memcpy(root, path->nodes[0]->b_data, name_ptr, name, name_len);
-	btrfs_mark_buffer_dirty(path->nodes[0]);
 	btrfs_release_path(root, path);
 
 	btrfs_set_key_type(&key, BTRFS_DIR_INDEX_KEY);
diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index b557bdd..6b097ed 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -16,10 +16,6 @@
 	if (buf->b_blocknr != btrfs_header_blocknr(&node->header)) {
 		BUG();
 	}
-	if (root->node && btrfs_header_parentid(&node->header) !=
-	    btrfs_header_parentid(btrfs_buffer_header(root->node))) {
-		BUG();
-	}
 	return 0;
 }
 
diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c
index 7c21f63..efc604e 100644
--- a/fs/btrfs/extent-tree.c
+++ b/fs/btrfs/extent-tree.c
@@ -77,6 +77,12 @@
 	return 0;
 }
 
+int btrfs_inc_root_ref(struct btrfs_trans_handle *trans,
+		       struct btrfs_root *root)
+{
+	return inc_block_ref(trans, root, root->node->b_blocknr, 1);
+}
+
 int btrfs_inc_ref(struct btrfs_trans_handle *trans, struct btrfs_root *root,
 		  struct buffer_head *buf)
 {
diff --git a/fs/btrfs/ioctl.h b/fs/btrfs/ioctl.h
new file mode 100644
index 0000000..201fb32
--- /dev/null
+++ b/fs/btrfs/ioctl.h
@@ -0,0 +1,13 @@
+#ifndef __IOCTL_
+#define __IOCTL_
+#include <linux/ioctl.h>
+
+#define BTRFS_IOCTL_MAGIC 0x94
+#define BTRFS_VOL_NAME_MAX 255
+struct btrfs_ioctl_vol_args {
+	char name[BTRFS_VOL_NAME_MAX + 1];
+};
+
+#define BTRFS_IOC_SNAP_CREATE _IOW(BTRFS_IOCTL_MAGIC, 1, \
+				   struct btrfs_ioctl_vol_args)
+#endif
diff --git a/fs/btrfs/root-tree.c b/fs/btrfs/root-tree.c
index ddc1c13..72be983 100644
--- a/fs/btrfs/root-tree.c
+++ b/fs/btrfs/root-tree.c
@@ -83,6 +83,8 @@
 {
 	struct btrfs_path *path;
 	int ret;
+	u32 refs;
+	struct btrfs_root_item *ri;
 
 	path = btrfs_alloc_path();
 	BUG_ON(!path);
@@ -91,7 +93,19 @@
 	if (ret < 0)
 		goto out;
 	BUG_ON(ret != 0);
-	ret = btrfs_del_item(trans, root, path);
+	ri = btrfs_item_ptr(btrfs_buffer_leaf(path->nodes[0]),
+			    path->slots[0], struct btrfs_root_item);
+
+	refs = btrfs_root_refs(ri);
+	BUG_ON(refs == 0);
+	if (refs == 1) {
+		ret = btrfs_del_item(trans, root, path);
+printk("deleting root %Lu %Lu %u\n", key->objectid, key->offset, key->flags);
+	} else {
+		btrfs_set_root_refs(ri, refs - 1);
+printk("ref now %u root %Lu %Lu %u\n", refs -1, key->objectid, key->offset, key->flags);
+		mark_buffer_dirty(path->nodes[0]);
+	}
 out:
 	btrfs_release_path(root, path);
 	btrfs_free_path(path);
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 3c9236c..bbe5cab 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -15,6 +15,7 @@
 #include "disk-io.h"
 #include "transaction.h"
 #include "btrfs_inode.h"
+#include "ioctl.h"
 
 void btrfs_fsinfo_release(struct kobject *obj)
 {
@@ -27,6 +28,11 @@
 	.release = btrfs_fsinfo_release,
 };
 
+struct btrfs_iget_args {
+	u64 ino;
+	struct btrfs_root *root;
+};
+
 decl_subsys(btrfs, &btrfs_fsinfo_ktype, NULL);
 
 #define BTRFS_SUPER_MAGIC 0x9123682E
@@ -461,6 +467,34 @@
 	return 0;
 }
 
+int btrfs_init_locked_inode(struct inode *inode, void *p)
+{
+	struct btrfs_iget_args *args = p;
+	inode->i_ino = args->ino;
+	BTRFS_I(inode)->root = args->root;
+	return 0;
+}
+
+int btrfs_find_actor(struct inode *inode, void *opaque)
+{
+	struct btrfs_iget_args *args = opaque;
+	return (args->ino == inode->i_ino &&
+		args->root == BTRFS_I(inode)->root);
+}
+
+struct inode *btrfs_iget_locked(struct super_block *s, u64 objectid,
+				struct btrfs_root *root)
+{
+	struct inode *inode;
+	struct btrfs_iget_args args;
+	args.ino = objectid;
+	args.root = root;
+
+	inode = iget5_locked(s, objectid, btrfs_find_actor,
+			     btrfs_init_locked_inode,
+			     (void *)&args);
+	return inode;
+}
 
 static struct dentry *btrfs_lookup(struct inode *dir, struct dentry *dentry,
 				   struct nameidata *nd)
@@ -486,7 +520,8 @@
 			return ERR_PTR(ret);
 		if (ret > 0)
 			return ERR_PTR(-ENOENT);
-		inode = iget_locked(dir->i_sb, location.objectid);
+		inode = btrfs_iget_locked(dir->i_sb, location.objectid,
+					  sub_root);
 		if (!inode)
 			return ERR_PTR(-EACCES);
 		if (inode->i_state & I_NEW) {
@@ -495,7 +530,7 @@
 						&root->fs_info->fs_roots_radix,
 						(unsigned long)sub_root,
 						sub_root);
-printk("adding new root for inode %lu\n", inode->i_ino);
+printk("adding new root for inode %lu root %p (found %p)\n", inode->i_ino, sub_root, BTRFS_I(inode)->root);
 				igrab(inode);
 				sub_root->inode = inode;
 			}
@@ -630,7 +665,8 @@
 	       btrfs_super_total_blocks(disk_super),
 	       btrfs_super_root_dir(disk_super));
 
-	inode = iget_locked(sb, btrfs_super_root_dir(disk_super));
+	inode = btrfs_iget_locked(sb, btrfs_super_root_dir(disk_super),
+				  tree_root);
 	bi = BTRFS_I(inode);
 	bi->location.objectid = inode->i_ino;
 	bi->location.offset = 0;
@@ -750,7 +786,7 @@
 	inode->i_mode = mode;
 	inode->i_ino = objectid;
 	inode->i_blocks = 0;
-	inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME_SEC;
+	inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
 	fill_inode_item(&inode_item, inode);
 
 	key->objectid = objectid;
@@ -1650,6 +1686,95 @@
 	return retval;
 }
 
+static int create_snapshot(struct btrfs_root *root, char *name, int namelen)
+{
+	struct btrfs_trans_handle *trans;
+	struct btrfs_key key;
+	struct btrfs_root_item new_root_item;
+	int ret;
+	u64 objectid;
+
+	mutex_lock(&root->fs_info->fs_mutex);
+	trans = btrfs_start_transaction(root, 1);
+	BUG_ON(!trans);
+
+	ret = btrfs_update_inode(trans, root, root->inode);
+	BUG_ON(ret);
+
+	ret = btrfs_find_free_objectid(trans, root, 0, &objectid);
+	BUG_ON(ret);
+
+	memset(&new_root_item, 0, sizeof(new_root_item));
+	memcpy(&new_root_item, &root->root_item,
+	       sizeof(new_root_item));
+
+	key.objectid = objectid;
+	key.flags = 0;
+	key.offset = 0;
+	btrfs_set_key_type(&key, BTRFS_INODE_ITEM_KEY);
+	ret = btrfs_insert_inode_map(trans, root, objectid, &key);
+	BUG_ON(ret);
+
+	key.objectid = objectid;
+	key.offset = 1;
+	key.flags = 0;
+	btrfs_set_key_type(&key, BTRFS_ROOT_ITEM_KEY);
+	btrfs_set_root_blocknr(&new_root_item, root->node->b_blocknr);
+
+	ret = btrfs_insert_root(trans, root->fs_info->tree_root, &key,
+				&new_root_item);
+	BUG_ON(ret);
+
+printk("adding snapshot name %.*s root %Lu %Lu %u\n", namelen, name, key.objectid, key.offset, key.flags);
+
+	/*
+	 * insert the directory item
+	 */
+	key.offset = (u64)-1;
+	ret = btrfs_insert_dir_item(trans, root->fs_info->tree_root,
+				    name, namelen,
+				    root->fs_info->sb->s_root->d_inode->i_ino,
+				    &key, 0);
+
+	BUG_ON(ret);
+
+	ret = btrfs_inc_root_ref(trans, root);
+	BUG_ON(ret);
+
+	ret = btrfs_commit_transaction(trans, root);
+	BUG_ON(ret);
+	mutex_unlock(&root->fs_info->fs_mutex);
+	return 0;
+}
+
+static int btrfs_ioctl(struct inode *inode, struct file *filp, unsigned int
+		       cmd, unsigned long arg)
+{
+	struct btrfs_root *root = BTRFS_I(inode)->root;
+	struct btrfs_ioctl_vol_args vol_args;
+	int ret;
+	int namelen;
+
+	if (!root->ref_cows)
+		return -EINVAL;
+	switch (cmd) {
+	case BTRFS_IOC_SNAP_CREATE:
+		if (copy_from_user(&vol_args,
+				   (struct btrfs_ioctl_vol_args __user *)arg,
+				   sizeof(vol_args)))
+			return -EFAULT;
+		namelen = strlen(vol_args.name);
+		if (namelen > BTRFS_VOL_NAME_MAX)
+			return -EINVAL;
+		ret = create_snapshot(root, vol_args.name, namelen);
+		WARN_ON(ret);
+		break;
+	default:
+		return -ENOTTY;
+	}
+	return 0;
+}
+
 static struct kmem_cache *btrfs_inode_cachep;
 struct kmem_cache *btrfs_trans_handle_cachep;
 struct kmem_cache *btrfs_transaction_cachep;
@@ -1781,6 +1906,7 @@
 	.llseek		= generic_file_llseek,
 	.read		= generic_read_dir,
 	.readdir	= btrfs_readdir,
+	.ioctl		= btrfs_ioctl,
 };
 
 static struct address_space_operations btrfs_aops = {
@@ -1803,6 +1929,7 @@
 	.write		= btrfs_file_write,
 	.mmap		= generic_file_mmap,
 	.open		= generic_file_open,
+	.ioctl		= btrfs_ioctl,
 };
 
 static int __init init_btrfs_fs(void)