Subvolume Listing feature for ioctl.
diff mbox

Message ID 4B00AE6C.7080507@jp.fujitsu.com
State Accepted
Headers show

Commit Message

TARUISI Hiroaki Nov. 16, 2009, 1:44 a.m. UTC
None

Patch
diff mbox

Index: b/fs/btrfs/ioctl.c
===================================================================
--- a/fs/btrfs/ioctl.c	2009-11-12 23:47:05.000000000 +0900
+++ b/fs/btrfs/ioctl.c	2009-11-12 23:54:21.000000000 +0900
@@ -48,6 +48,7 @@ 
 #include "print-tree.h"
 #include "volumes.h"
 #include "locking.h"
+#include "ctree.h"

 /* Mask out flags that are inappropriate for the given type of inode. */
 static inline __u32 btrfs_mask_flags(umode_t mode, __u32 flags)
@@ -738,6 +739,323 @@  out:
 	return ret;
 }

+/*
+  Search INODE_REFs to identify path name of 'dirid' directory
+  in a 'tree_id' tree. and sets path name to 'name'.
+*/
+static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info,
+				u64 tree_id, u64 dirid, char *name)
+{
+	struct btrfs_root *root;
+	struct btrfs_key key;
+	char *name_stack, *ptr;
+	int ret = -1;
+	int slot;
+	int len;
+	int total_len = 0;
+	struct btrfs_inode_ref *iref;
+	struct extent_buffer *l;
+	struct btrfs_path *path;
+
+	if (dirid == BTRFS_FIRST_FREE_OBJECTID) {
+		name[0]='\0';
+		ret = 0;
+		goto out_direct;
+	}
+
+	path = btrfs_alloc_path();
+	name_stack = kzalloc(BTRFS_PATH_NAME_MAX+1, GFP_NOFS);
+	ptr = &name_stack[BTRFS_PATH_NAME_MAX];
+
+	key.objectid = tree_id;
+	key.type = BTRFS_ROOT_ITEM_KEY;
+	key.offset = (u64)-1;
+	root = btrfs_read_fs_root_no_name(info, &key);
+
+	key.objectid = dirid;
+	key.type = BTRFS_INODE_REF_KEY;
+	key.offset = 0;
+
+	while(1) {
+		ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
+		if (ret<0)
+			goto out;
+
+		l = path->nodes[0];
+		slot = path->slots[0];
+		btrfs_item_key_to_cpu(l, &key, slot);
+
+		if (ret>0 && (key.objectid != dirid ||
+					key.type != BTRFS_INODE_REF_KEY))
+			goto out;
+
+		iref = btrfs_item_ptr(l, slot, struct btrfs_inode_ref);
+		len = btrfs_inode_ref_name_len(l, iref);
+		ptr -= len+1;
+		total_len += len+1;
+		if (ptr < name_stack)
+			goto out;
+
+		*(ptr + len) = '/';
+		read_extent_buffer(l, ptr,(unsigned long)(iref + 1), len);
+
+		if (key.offset == BTRFS_FIRST_FREE_OBJECTID)
+			break;
+
+		btrfs_release_path(root, path);
+		key.objectid = key.offset;
+		key.offset = 0;
+		dirid = key.objectid;
+
+	}
+	if (ptr < name_stack)
+		goto out;
+	strncpy(name, ptr, total_len);
+	name[total_len]='\0';
+	ret = 0;
+out:
+	btrfs_release_path(root, path);
+	kfree(path);
+	kfree(name_stack);
+
+out_direct:
+	return ret;
+}
+
+/*
+  Helper function to search tree root directory which contains
+  specified dentry.
+  This function is used in btrfs_ioctl_snap_listing function,
+  to notify root directory(different from the directory what
+  user specified) to user.
+*/
+static noinline struct dentry *btrfs_walkup_dentry_to_root(struct dentry *d)
+{
+	u64 ino;
+	struct dentry *dent=d;
+
+	ino = dent->d_inode->i_ino;
+	while (ino!=BTRFS_FIRST_FREE_OBJECTID) {
+		dent = dent->d_parent;
+		ino = dent->d_inode->i_ino;
+	}
+	return dent;
+}
+
+/*
+  Create a list of Snapshot/Subvolume in specified tree.
+  Target tree is specified by struct file.
+*/
+static noinline int btrfs_ioctl_snap_listing(struct file *file,
+					     void __user *arg)
+{
+	struct btrfs_ioctl_subvol_name *svol_iname;
+	struct btrfs_ioctl_subvol_cache *subvol, *tmp;
+	struct btrfs_ioctl_subvol_args *svol;
+	struct btrfs_root *tree_root, *root;
+	struct btrfs_root_ref *ref;
+	struct extent_buffer *l;
+	struct btrfs_path *path=NULL;
+	struct btrfs_key key;
+	u64 tree_id;
+	char *work_path, *f_path, *name;
+	int err, ret = 0, slot = 0, seq = 0;
+	LIST_HEAD(pending_subvols);
+	struct list_head *cur;
+	struct path vfs_path;
+	struct inode *d_inode;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
+	svol_iname = kzalloc(sizeof(*svol_iname), GFP_NOFS);
+	name = kzalloc(BTRFS_PATH_NAME_MAX+1, GFP_NOFS);
+	svol = memdup_user(arg, sizeof(*svol));
+	if (!svol_iname || IS_ERR(svol) || !name)
+		return -ENOMEM;
+
+	/* identify tree base and set it to user parameter. */
+	svol->nr = 0;
+	if (svol->seq == 0) {
+		work_path = kzalloc(BTRFS_PATH_NAME_MAX+1, GFP_NOFS);
+		if (!work_path) {
+			ret = -ENOMEM;
+			goto error_unrelease;
+		}
+		vfs_path.mnt = file->f_path.mnt;
+		vfs_path.dentry = btrfs_walkup_dentry_to_root(file->f_path.dentry);
+		f_path = d_path(&vfs_path, work_path, BTRFS_PATH_NAME_MAX);
+		if (!IS_ERR(f_path)) {
+			strcpy(svol_iname->name, f_path);
+			strcat(svol_iname->name, "/");
+			svol_iname->objectid = 0;
+			if (copy_to_user((svol->subvols), svol_iname,
+						sizeof(*svol_iname))) {
+				ret = -EFAULT;
+				kfree(work_path);
+				goto error_unrelease;
+			};
+			svol->nr++;
+		}
+		kfree(work_path);
+	}
+
+	/* identify tree id and tree root */
+	d_inode = file->f_path.dentry->d_inode;
+	root = BTRFS_I(d_inode)->root;
+	tree_root = root->fs_info->tree_root;
+	tree_id = root->root_key.objectid;
+
+	/* create first tree info to search subvolume and add it to inner list */
+	subvol = kzalloc(sizeof(struct btrfs_ioctl_subvol_cache), GFP_NOFS);
+	subvol->tree_id = tree_id;
+	subvol->name_len = 0;
+	subvol->path_len = 0;
+	list_add_tail(&subvol->list, &pending_subvols);
+	path = btrfs_alloc_path();
+
+	/* finalize path name info and search subvolumes until list get empty */
+	while (!list_empty(&pending_subvols)) {
+
+		/* pick up tree info */
+		subvol = list_entry(pending_subvols.next,
+					struct btrfs_ioctl_subvol_cache, list);
+		tree_id = subvol->tree_id;
+
+		/* if not first tree, identfy its path in the parent tree.
+		   subvol->path_name holds path_name from the specified root
+		   to the parent tree root. so, we concatenate subvol->path_name,
+		   and path_name in the parent tree, and directry name itself. */
+		if (subvol->name_len > 0) {
+			btrfs_search_path_in_tree(root->fs_info,
+						subvol->parent, subvol->dirid, name);
+
+			if (strlen(name)+subvol->name_len>BTRFS_PATH_NAME_MAX) {
+				ret = -1;
+				goto error_unrelease;
+			}
+			strcpy(svol_iname->name, subvol->path_name);
+			strcpy(svol_iname->name+strlen(subvol->path_name), name);
+			strcpy(svol_iname->name+strlen(subvol->path_name)+strlen(name),
+						subvol->name);
+			/* this name string is used to set subvolume info as path name
+			   of parent tree. */
+			strcpy(name, svol_iname->name);
+
+			/* if sequence is bigger than required, return subvol info to
+				user parameter */
+			if(seq > svol->seq) {
+				strcat(svol_iname->name, "/");
+				svol_iname->objectid = tree_id;
+				if (copy_to_user((svol->subvols+svol->nr),
+							svol_iname, sizeof(*svol_iname))) {
+					ret = -EFAULT;
+					goto error;
+				}
+				svol->nr++;
+				if (svol->nr >= svol->max) {
+					list_for_each_entry_safe(subvol, tmp,
+								&pending_subvols, list) {
+						list_del(&subvol->list);
+						if (subvol->name_len != 0)
+							kfree(subvol->name);
+						if (subvol->path_name)
+							kfree(subvol->path_name);
+						kfree(subvol);
+					}
+					ret = 1;
+					goto reach_limit;
+				}
+			}
+		}
+
+		/* search root tree to find subvolumes */
+		key.objectid = tree_id;
+		key.type = BTRFS_ROOT_REF_KEY;
+		key.offset = 0;
+		list_del(&subvol->list);
+		if (subvol->name_len != 0)
+			kfree(subvol->name);
+		if (subvol->path_name)
+			kfree(subvol->path_name);
+		kfree(subvol);
+		seq++;
+
+		err = btrfs_search_slot(NULL, tree_root, &key, path, 0, 0);
+		if (err < 0) {
+			printk("search slot failed: code=%d\n", err);
+			ret = -1;
+			goto error_unrelease;
+		}
+		cur = &pending_subvols;
+
+		/* traverse leafs to search subvolumes under the subvolume */
+		while (1) {
+			l = path->nodes[0];
+			slot = path->slots[0];
+			btrfs_item_key_to_cpu( l, &key, path->slots[0]);
+			if (slot >= btrfs_header_nritems(l)) {
+				err = btrfs_next_leaf(tree_root, path);
+				if (err == 0)
+					continue;
+				if (err < 0) {
+					printk("next_leaf failed: code=%d\n", err);
+					ret = -1;
+					goto error;
+				}
+			}
+			if (key.type != BTRFS_ROOT_REF_KEY || key.objectid != tree_id)
+				break;
+
+			subvol = kzalloc(sizeof(struct btrfs_ioctl_subvol_cache),
+						GFP_NOFS);
+			subvol->tree_id = key.offset;
+			subvol->parent = key.objectid;
+
+			/* set to subvolume info its name and info about parent tree */
+			ref = btrfs_item_ptr(l, slot, struct btrfs_root_ref);
+			subvol->name_len = btrfs_root_ref_name_len( l, ref);
+			subvol->name = kzalloc(subvol->name_len+1, GFP_NOFS);
+			read_extent_buffer(l, subvol->name,
+					(unsigned long)(ref + 1), subvol->name_len);
+			subvol->path_name = kzalloc(strlen(name)+2, GFP_NOFS);
+			if (strlen(name)!=0) {
+				strcpy(subvol->path_name, name);
+				strcat(subvol->path_name, "/");
+			}
+			subvol->dirid = btrfs_root_ref_dirid(l, ref);
+
+			list_add(&subvol->list, cur);
+			cur = &subvol->list;
+
+			path->slots[0]++;
+			cond_resched();
+		}
+		btrfs_release_path(tree_root, path);
+	}
+
+reach_limit:
+	svol->seq = seq;
+	if (copy_to_user(arg, svol,
+				sizeof(struct btrfs_ioctl_subvol_args))) {
+		ret = -EFAULT;
+	}
+	btrfs_release_path(tree_root, path);
+	kfree(svol);
+	kfree(svol_iname);
+	kfree(path);
+	kfree(name);
+	return ret;
+error:
+	btrfs_release_path(tree_root, path);
+error_unrelease:
+	kfree(svol);
+	kfree(svol_iname);
+	kfree(path);
+	kfree(name);
+	return ret;
+}
+
 static noinline int btrfs_ioctl_snap_destroy(struct file *file,
 					     void __user *arg)
 {
@@ -1334,10 +1652,13 @@  long btrfs_ioctl(struct file *file, unsi
 		return btrfs_ioctl_trans_start(file);
 	case BTRFS_IOC_TRANS_END:
 		return btrfs_ioctl_trans_end(file);
+	case BTRFS_IOC_SNAP_LISTING:
+		return btrfs_ioctl_snap_listing(file, argp);
 	case BTRFS_IOC_SYNC:
 		btrfs_sync_fs(file->f_dentry->d_sb, 1);
 		return 0;
 	}

+	printk("cmd : %u \n", cmd);
 	return -ENOTTY;
 }
Index: b/fs/btrfs/ioctl.h
===================================================================
--- a/fs/btrfs/ioctl.h	2009-11-12 23:47:08.000000000 +0900
+++ b/fs/btrfs/ioctl.h	2009-11-12 23:47:27.000000000 +0900
@@ -23,6 +23,7 @@ 
 #define BTRFS_IOCTL_MAGIC 0x94
 #define BTRFS_VOL_NAME_MAX 255
 #define BTRFS_PATH_NAME_MAX 4087
+#define BTRFS_SUBVOL_LIST_MAX 3

 /* this should be 4k */
 struct btrfs_ioctl_vol_args {
@@ -30,6 +31,30 @@  struct btrfs_ioctl_vol_args {
 	char name[BTRFS_PATH_NAME_MAX + 1];
 };

+struct btrfs_ioctl_subvol_name {
+	u64 objectid;
+	char name[BTRFS_PATH_NAME_MAX + 1];
+};
+
+/* these members are same as first three members of btrfs_ioctl_subvol_args_inner */
+struct btrfs_ioctl_subvol_args {
+	int max;
+	int nr;
+	u64 seq;
+	struct btrfs_ioctl_subvol_name *subvols;
+};
+
+struct btrfs_ioctl_subvol_cache {
+	struct list_head list;
+	u64 tree_id;
+	u64 parent;
+	u64 dirid;
+	int path_len;
+	char *path_name;
+	int name_len;
+	char *name;
+};
+
 struct btrfs_ioctl_clone_range_args {
   __s64 src_fd;
   __u64 src_offset, src_length;
@@ -67,4 +92,6 @@  struct btrfs_ioctl_clone_range_args {
 				   struct btrfs_ioctl_vol_args)
 #define BTRFS_IOC_SNAP_DESTROY _IOW(BTRFS_IOCTL_MAGIC, 15, \
 				struct btrfs_ioctl_vol_args)
+#define BTRFS_IOC_SNAP_LISTING _IOWR(BTRFS_IOCTL_MAGIC, 16, \
+				   struct btrfs_ioctl_subvol_args)
 #endif