Btrfs: rename

Signed-off-by: Chris Mason <chris.mason@oracle.com>
diff --git a/fs/btrfs/TODO b/fs/btrfs/TODO
index f6df246..861e34a 100644
--- a/fs/btrfs/TODO
+++ b/fs/btrfs/TODO
@@ -8,6 +8,7 @@
 * Add generation number to key pointer in nodes
 * Add generation number to inode
 * Add ability to switch a block group from data to metadata or vice versa
+* forbid cross subvolume renames and hardlinks
 * Release
 * Do real tree locking
 * Add extent mirroring (backup copies of blocks)
diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index da12d82..3330004 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -32,6 +32,7 @@
 #define BTRFS_CSUM_SIZE 32
 /* four bytes for CRC32 */
 #define BTRFS_CRC32_SIZE 4
+#define BTRFS_EMPTY_DIR_SIZE 6
 
 /*
  * the key defines the order in the tree, and so it also defines (optimal)
diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c
index 00a28d9..b408a3d 100644
--- a/fs/btrfs/dir-item.c
+++ b/fs/btrfs/dir-item.c
@@ -9,7 +9,9 @@
 						   struct btrfs_root *root,
 						   struct btrfs_path *path,
 						   struct btrfs_key *cpu_key,
-						   u32 data_size)
+						   u32 data_size,
+						   const char *name,
+						   int name_len)
 {
 	int ret;
 	char *ptr;
@@ -18,6 +20,10 @@
 
 	ret = btrfs_insert_empty_item(trans, root, path, cpu_key, data_size);
 	if (ret == -EEXIST) {
+		struct btrfs_dir_item *di;
+		di = btrfs_match_dir_item_name(root, path, name, name_len);
+		if (di)
+			return ERR_PTR(-EEXIST);
 		ret = btrfs_extend_item(trans, root, path, data_size);
 		WARN_ON(ret > 0);
 		if (ret)
@@ -37,6 +43,7 @@
 			  struct btrfs_key *location, u8 type)
 {
 	int ret = 0;
+	int ret2 = 0;
 	struct btrfs_path *path;
 	struct btrfs_dir_item *dir_item;
 	char *name_ptr;
@@ -51,9 +58,12 @@
 	path = btrfs_alloc_path();
 	btrfs_init_path(path);
 	data_size = sizeof(*dir_item) + name_len;
-	dir_item = insert_with_overflow(trans, root, path, &key, data_size);
+	dir_item = insert_with_overflow(trans, root, path, &key, data_size,
+					name, name_len);
 	if (IS_ERR(dir_item)) {
 		ret = PTR_ERR(dir_item);
+		if (ret == -EEXIST)
+			goto second_insert;
 		goto out;
 	}
 
@@ -66,19 +76,20 @@
 	btrfs_memcpy(root, path->nodes[0]->b_data, name_ptr, name, name_len);
 	btrfs_mark_buffer_dirty(path->nodes[0]);
 
+second_insert:
 	/* FIXME, use some real flag for selecting the extra index */
 	if (root == root->fs_info->tree_root) {
 		ret = 0;
 		goto out;
 	}
-
 	btrfs_release_path(root, path);
 
 	btrfs_set_key_type(&key, BTRFS_DIR_INDEX_KEY);
 	key.offset = location->objectid;
-	dir_item = insert_with_overflow(trans, root, path, &key, data_size);
+	dir_item = insert_with_overflow(trans, root, path, &key, data_size,
+					name, name_len);
 	if (IS_ERR(dir_item)) {
-		ret = PTR_ERR(dir_item);
+		ret2 = PTR_ERR(dir_item);
 		goto out;
 	}
 	btrfs_cpu_key_to_disk(&dir_item->location, location);
@@ -90,7 +101,11 @@
 	btrfs_mark_buffer_dirty(path->nodes[0]);
 out:
 	btrfs_free_path(path);
-	return ret;
+	if (ret)
+		return ret;
+	if (ret2)
+		return ret2;
+	return 0;
 }
 
 struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans,
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 0220c82..f49cad6 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -375,6 +375,7 @@
 	struct btrfs_path *path;
 	struct btrfs_key key;
 	struct btrfs_disk_key *found_key;
+	u32 found_type;
 	struct btrfs_leaf *leaf;
 	struct btrfs_file_extent_item *fi = NULL;
 	u64 extent_start = 0;
@@ -386,12 +387,7 @@
 	/* FIXME, add redo link to tree so we don't leak on crash */
 	key.objectid = inode->i_ino;
 	key.offset = (u64)-1;
-	key.flags = 0;
-	/*
-	 * use BTRFS_CSUM_ITEM_KEY because it is larger than inline keys
-	 * or extent data
-	 */
-	btrfs_set_key_type(&key, BTRFS_CSUM_ITEM_KEY);
+	key.flags = (u32)-1;
 	while(1) {
 		btrfs_init_path(path);
 		ret = btrfs_search_slot(trans, root, &key, path, -1, 1);
@@ -405,10 +401,13 @@
 		reada_truncate(root, path, inode->i_ino);
 		leaf = btrfs_buffer_leaf(path->nodes[0]);
 		found_key = &leaf->items[path->slots[0]].key;
+		found_type = btrfs_disk_key_type(found_key);
 		if (btrfs_disk_key_objectid(found_key) != inode->i_ino)
 			break;
-		if (btrfs_disk_key_type(found_key) != BTRFS_CSUM_ITEM_KEY &&
-		    btrfs_disk_key_type(found_key) != BTRFS_EXTENT_DATA_KEY)
+		if (found_type != BTRFS_CSUM_ITEM_KEY &&
+		    found_type != BTRFS_DIR_ITEM_KEY &&
+		    found_type != BTRFS_DIR_INDEX_KEY &&
+		    found_type != BTRFS_EXTENT_DATA_KEY)
 			break;
 		if (btrfs_disk_key_offset(found_key) < inode->i_size)
 			break;
@@ -460,10 +459,8 @@
 	mutex_lock(&root->fs_info->fs_mutex);
 	trans = btrfs_start_transaction(root, 1);
 	btrfs_set_trans_block_group(trans, inode);
-	if (S_ISREG(inode->i_mode)) {
-		ret = btrfs_truncate_in_trans(trans, root, inode);
-		BUG_ON(ret);
-	}
+	ret = btrfs_truncate_in_trans(trans, root, inode);
+	BUG_ON(ret);
 	btrfs_free_inode(trans, root, inode);
 	btrfs_end_transaction(trans, root);
 	mutex_unlock(&root->fs_info->fs_mutex);
@@ -2504,6 +2501,116 @@
 	return 0;
 }
 
+static int btrfs_rename(struct inode * old_dir, struct dentry *old_dentry,
+			   struct inode * new_dir,struct dentry *new_dentry)
+{
+	struct btrfs_trans_handle *trans;
+	struct btrfs_root *root = BTRFS_I(old_dir)->root;
+	struct inode *new_inode = new_dentry->d_inode;
+	struct inode *old_inode = old_dentry->d_inode;
+	struct timespec ctime = CURRENT_TIME;
+	struct btrfs_path *path;
+	struct btrfs_dir_item *di;
+	int ret;
+
+	if (S_ISDIR(old_inode->i_mode) && new_inode &&
+	    new_inode->i_size > BTRFS_EMPTY_DIR_SIZE) {
+		return -ENOTEMPTY;
+	}
+	mutex_lock(&root->fs_info->fs_mutex);
+	trans = btrfs_start_transaction(root, 1);
+	btrfs_set_trans_block_group(trans, new_dir);
+	path = btrfs_alloc_path();
+	if (!path) {
+		ret = -ENOMEM;
+		goto out_fail;
+	}
+
+	old_dentry->d_inode->i_nlink++;
+	old_dir->i_ctime = old_dir->i_mtime = ctime;
+	new_dir->i_ctime = new_dir->i_mtime = ctime;
+	old_inode->i_ctime = ctime;
+	if (S_ISDIR(old_inode->i_mode) && old_dir != new_dir) {
+		struct btrfs_key *location = &BTRFS_I(new_dir)->location;
+		u64 old_parent_oid;
+		di = btrfs_lookup_dir_item(trans, root, path, old_inode->i_ino,
+					   "..", 2, -1);
+		if (IS_ERR(di)) {
+			ret = PTR_ERR(di);
+			goto out_fail;
+		}
+		if (!di) {
+			ret = -ENOENT;
+			goto out_fail;
+		}
+		old_parent_oid = btrfs_disk_key_objectid(&di->location);
+		ret = btrfs_del_item(trans, root, path);
+		if (ret) {
+			ret = -EIO;
+			goto out_fail;
+		}
+		btrfs_release_path(root, path);
+
+		di = btrfs_lookup_dir_index_item(trans, root, path,
+						 old_inode->i_ino,
+						 old_parent_oid,
+						 "..", 2, -1);
+		if (IS_ERR(di)) {
+			ret = PTR_ERR(di);
+			goto out_fail;
+		}
+		if (!di) {
+			ret = -ENOENT;
+			goto out_fail;
+		}
+		ret = btrfs_del_item(trans, root, path);
+		if (ret) {
+			ret = -EIO;
+			goto out_fail;
+		}
+		btrfs_release_path(root, path);
+
+		ret = btrfs_insert_dir_item(trans, root, "..", 2,
+					    old_inode->i_ino, location, 0);
+		if (ret)
+			goto out_fail;
+	}
+
+
+	ret = btrfs_add_link(trans, new_dentry, old_inode);
+	if (ret == -EEXIST && new_inode)
+		ret = 0;
+	else if (ret)
+		goto out_fail;
+
+	ret = btrfs_unlink_trans(trans, root, old_dir, old_dentry);
+	if (ret)
+		goto out_fail;
+
+	if (new_inode) {
+		new_inode->i_ctime = CURRENT_TIME;
+		di = btrfs_lookup_dir_index_item(trans, root, path,
+						 new_dir->i_ino,
+						 new_inode->i_ino,
+						 new_dentry->d_name.name,
+						 new_dentry->d_name.len, -1);
+		if (di && !IS_ERR(di)) {
+			btrfs_del_item(trans, root, path);
+			btrfs_release_path(root, path);
+		}
+		if (S_ISDIR(new_inode->i_mode))
+			clear_nlink(new_inode);
+		else
+			drop_nlink(new_inode);
+		btrfs_update_inode(trans, root, new_inode);
+	}
+out_fail:
+	btrfs_free_path(path);
+	btrfs_end_transaction(trans, root);
+	mutex_unlock(&root->fs_info->fs_mutex);
+	return ret;
+}
+
 static struct file_system_type btrfs_fs_type = {
 	.owner		= THIS_MODULE,
 	.name		= "btrfs",
@@ -2531,6 +2638,7 @@
 	.unlink		= btrfs_unlink,
 	.mkdir		= btrfs_mkdir,
 	.rmdir		= btrfs_rmdir,
+	.rename		= btrfs_rename,
 };
 
 static struct inode_operations btrfs_dir_ro_inode_operations = {