diff --git a/btrfs-list.c b/btrfs-list.c
index 2e82dc5c..ef97d2db 100644
--- a/btrfs-list.c
+++ b/btrfs-list.c
@@ -33,6 +33,7 @@
 #include <uuid/uuid.h>
 #include "btrfs-list.h"
 #include "rbtree-utils.h"
+#include <sys/queue.h>
 
 #define BTRFS_LIST_NFILTERS_INCREASE	(2 * BTRFS_LIST_FILTER_MAX)
 #define BTRFS_LIST_NCOMPS_INCREASE	(2 * BTRFS_LIST_COMP_MAX)
@@ -549,6 +550,9 @@ static int resolve_root(struct root_lookup *rl, struct root_info *ri,
 	int len = 0;
 	struct root_info *found;
 
+	if (ri->full_path != NULL)
+		return 0;
+
 	/*
 	 * we go backwards from the root_info object and add pathnames
 	 * from parent directories as we go.
@@ -672,6 +676,50 @@ static int lookup_ino_path(int fd, struct root_info *ri)
 	return 0;
 }
 
+/* user version of lookup_ino_path which also cheks the access right */
+static int lookup_ino_path_user(int fd, struct root_info *ri)
+{
+	struct btrfs_ioctl_ino_lookup_user_args args;
+	int ret = 0;
+
+	if (ri->path)
+		return 0;
+	if (!ri->ref_tree)
+		return -ENOENT;
+
+	memset(&args, 0, sizeof(args));
+	args.dirid = ri->dir_id;
+	args.subvolid = ri->root_id;
+
+	ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP_USER, &args);
+	if (ret < 0) {
+		if (errno == ENOENT) {
+			ri->ref_tree = 0;
+			return -ENOENT;
+		}
+		if (errno != EACCES) {
+			error("failed to lookup path for root %llu: %m",
+			(unsigned long long)ri->ref_tree);
+			return ret;
+		} else {
+			return -EACCES;
+		}
+	}
+
+	ri->path = malloc(strlen(args.path) + strlen(args.name) + 1);
+	if (!ri->path)
+		return -ENOMEM;
+	strcpy(ri->path, args.path);
+
+	ri->name = malloc(strlen(args.name) + 1);
+	if (!ri->name)
+		return -ENOMEM;
+	strcpy(ri->name, args.name);
+
+	strcat(ri->path, ri->name);
+	return ret;
+}
+
 /* finding the generation for a given path is a two step process.
  * First we use the inode lookup routine to find out the root id
  *
@@ -1303,6 +1351,20 @@ static void filter_and_sort_subvol(struct root_lookup *all_subvols,
 	while (n) {
 		entry = rb_entry(n, struct root_info, rb_node);
 
+		/*
+		 * If list_subvol_user() is used, there may be
+		 * entries which have been skipped for search.
+		 * Just remove these entries here.
+		 */
+		if (!entry->path) {
+			struct rb_node *prev = rb_prev(n);
+
+			rb_erase(n, &all_subvols->root);
+			free_root_info(n);
+			n = prev;
+			continue;
+		}
+
 		ret = resolve_root(all_subvols, entry, top_id);
 		if (ret == -ENOENT) {
 			if (entry->root_id != BTRFS_FS_TREE_OBJECTID) {
@@ -1343,6 +1405,243 @@ static int list_subvol_fill_paths(int fd, struct root_lookup *root_lookup)
 	return 0;
 }
 
+static int fill_subvol_info(struct root_lookup *root_lookup, int fd)
+{
+	struct btrfs_ioctl_get_subvol_info_args subvol_info;
+	u64 root_offset;
+	int ret;
+
+	ret = ioctl(fd, BTRFS_IOC_GET_SUBVOL_INFO, &subvol_info);
+	if (ret < 0)
+		return -errno;
+
+	if (!uuid_is_null(subvol_info.parent_uuid))
+		root_offset = subvol_info.otransid;
+	else
+		root_offset = 0;
+
+	add_root(root_lookup, subvol_info.id, 0,
+		 root_offset, subvol_info.flags, subvol_info.dirid,
+		 NULL, 0,
+		 subvol_info.otransid, subvol_info.generation,
+		 subvol_info.otime.sec, subvol_info.uuid,
+		 subvol_info.parent_uuid, subvol_info.received_uuid);
+
+	return 0;
+}
+
+static int fill_subvol_info_top(struct root_lookup *root_lookup, int fd)
+{
+	struct btrfs_ioctl_get_subvol_info_args subvol_info;
+	struct root_info *ri;
+	u64 root_offset;
+	int ret;
+
+	ret = ioctl(fd, BTRFS_IOC_GET_SUBVOL_INFO, &subvol_info);
+	if (ret < 0)
+		return -errno;
+
+	if (!uuid_is_null(subvol_info.parent_uuid))
+		root_offset = subvol_info.otransid;
+	else
+		root_offset = 0;
+
+	add_root(root_lookup, subvol_info.id, subvol_info.parent_id,
+		 root_offset, subvol_info.flags, subvol_info.dirid,
+		 subvol_info.name, strlen(subvol_info.name),
+		 subvol_info.otransid, subvol_info.generation,
+		 subvol_info.otime.sec, subvol_info.uuid,
+		 subvol_info.parent_uuid, subvol_info.received_uuid);
+
+	/*
+	 * fill the path since we won't lookup directory/subvolume
+	 * above this subvolume and cannot use root_lookup()
+	 */
+	ri = root_tree_search(root_lookup, subvol_info.id);
+	ri->top_id = subvol_info.parent_id;
+	if (subvol_info.id == BTRFS_FS_TREE_OBJECTID) {
+		ri->path = strdup("/");
+		ri->name = strdup("<FS_TREE>");
+		ri->full_path = strdup("/");
+		if (!ri->path || !ri->name || !ri->full_path)
+			return -ENOMEM;
+	} else {
+		ri->path = malloc(strlen(ri->name + 1));
+		ri->full_path = malloc(strlen(ri->name + 1));
+		if (!ri->path || !ri->full_path)
+			return -ENOMEM;
+
+		strcpy(ri->path, ri->name);
+		strcpy(ri->full_path, ri->name);
+	}
+
+	return 0;
+}
+
+static int fill_rootref(struct root_lookup *root_lookup, int fd, int parent_id)
+{
+	struct btrfs_ioctl_get_subvol_rootref_args rootrefs;
+	bool continue_search = true;
+	int i, ret;
+
+	memset(&rootrefs, 0, sizeof(rootrefs));
+	while (continue_search) {
+		ret = ioctl(fd, BTRFS_IOC_GET_SUBVOL_ROOTREF, &rootrefs);
+		if (ret < 0) {
+			if (errno != EOVERFLOW)
+				return -errno;
+			continue_search = true;
+		} else {
+			continue_search = false;
+		}
+
+		for (i = 0; i < rootrefs.num_items; i++) {
+			add_root(root_lookup, rootrefs.rootref[i].subvolid,
+			    parent_id, 0, 0, rootrefs.rootref[i].dirid,
+			    NULL, 0, 0, 0, 0, NULL, NULL, NULL);
+		}
+	}
+
+	return 0;
+}
+
+static int list_subvol_user(int top_fd, struct root_lookup *root_lookup,
+				const char *path)
+{
+	struct root_info *ri, *parent;
+	struct rb_node *n;
+	char *fullpath;
+	u64 top_id;
+	/* fifo queue entry which holds subvolume's id */
+	struct queue_entry {
+		u64 id;
+
+		STAILQ_ENTRY(queue_entry) entries;
+	} *e, *etemp;
+	int fd;
+	int ret = 0;
+
+	root_lookup->root.rb_node = NULL;
+
+	ret = btrfs_list_get_path_rootid(top_fd, &top_id);
+	if (ret)
+		return ret;
+
+	/* Add top_id to the queue */
+	STAILQ_HEAD(slistead, queue_entry) head = STAILQ_HEAD_INITIALIZER(head);
+	STAILQ_INIT(&head);
+	e = malloc(sizeof(struct queue_entry));
+	if (!e)
+		return -ENOMEM;
+	e->id = top_id;
+	STAILQ_INSERT_TAIL(&head, e, entries);
+
+	/*
+	 * Iterate until queue is empty:
+	 * 1. Pop the first entry
+	 * 2. Open the entry's path
+	 * 3  Get the subvolume information by fill_subvol_info/fill_rootref
+	 * 4. Iterate over rb_tree:
+	 * 4-1. Searth the rb_tree whose ref_tree is e->id
+	 *    (this means the subvolume exists under e->id's subvolume)
+	 * 4-2. Call ino_lookup_user ioctl
+	 * 4-3. If the call succeeds, add the subvolume id to the queue
+	 */
+	while (!STAILQ_EMPTY(&head)) {
+		e = STAILQ_FIRST(&head);
+		STAILQ_REMOVE_HEAD(&head, entries);
+
+		if (e->id == top_id) {
+			fd = top_fd;
+			fill_subvol_info_top(root_lookup, fd);
+			fill_rootref(root_lookup, fd, e->id);
+		} else {
+			parent = root_tree_search(root_lookup, e->id);
+			resolve_root(root_lookup, parent, top_id);
+			fd = openat(top_fd, parent->full_path, O_RDONLY);
+			if (fd == -1) {
+				if (errno == EACCES) {
+					/* skip this subvolume */
+					continue;
+				} else {
+					error("error at open %s: %m",
+							parent->full_path);
+					goto err;
+				}
+			}
+
+			fill_subvol_info(root_lookup, fd);
+			fill_rootref(root_lookup, fd, e->id);
+		}
+
+		n = rb_first(&root_lookup->root);
+		while (n) {
+			ri = rb_entry(n, struct root_info, rb_node);
+			if (ri->ref_tree == 0) {
+				/* BTRFS_FS_TREE_OBJECTID or deleted */
+				n = rb_next(n);
+				continue;
+			}
+
+			if (ri->ref_tree == e->id) {
+				ret = lookup_ino_path_user(fd, ri);
+				if (ret < 0 && ret != -ENOENT && ret != -EACCES)
+					goto err;
+
+				/* add ths subvol id to queue */
+				if (!ret) {
+					etemp = malloc(sizeof(struct queue_entry));
+
+					if (!etemp) {
+						ret = -ENOMEM;
+						goto err;
+					}
+					etemp->id = ri->root_id;
+					STAILQ_INSERT_TAIL(&head, etemp,
+					    entries);
+				}
+			}
+			n = rb_next(n);
+		}
+
+		if (fd != top_fd)
+			close(fd);
+		free(e);
+	}
+
+	/* If the specified path itself is not a subvolume, remove the entry */
+	fullpath = realpath(path, NULL);
+	if (!fullpath) {
+		ret = -ENOMEM;
+		goto err;
+	}
+	ret = test_issubvolume(fullpath);
+	free(fullpath);
+	if (ret < 0)
+		goto err;
+
+	if (!ret) {
+		ri = root_tree_search(root_lookup, top_id);
+		rb_erase(&ri->rb_node, &root_lookup->root);
+		free_root_info(&ri->rb_node);
+	}
+
+	return 0;
+
+err:
+	if (fd != -1 && fd != top_fd)
+		close(fd);
+
+	/* free remaining queue entries */
+	while (!STAILQ_EMPTY(&head)) {
+		e = STAILQ_FIRST(&head);
+		STAILQ_REMOVE_HEAD(&head, entries);
+		free(e);
+	}
+
+	return ret;
+}
+
 static void print_subvolume_column(struct root_info *subv,
 				   enum btrfs_list_column_enum column)
 {
@@ -1527,17 +1826,28 @@ static int btrfs_list_subvols(int fd, struct root_lookup *root_lookup,
 {
 	int ret;
 
-	ret = list_subvol_search(fd, root_lookup);
-	if (ret) {
-		error("can't perform the search: %m");
-		return ret;
+	ret = check_perm_for_tree_search(fd);
+	if (ret < 0) {
+		error("can't check the permission for tree search: %s",
+				strerror(-ret));
+		return -1;
 	}
 
 	/*
 	 * now we have an rbtree full of root_info objects, but we need to fill
 	 * in their path names within the subvol that is referencing each one.
 	 */
-	ret = list_subvol_fill_paths(fd, root_lookup);
+	if (ret) {
+		ret = list_subvol_search(fd, root_lookup);
+		if (ret) {
+			error("can't perform the search: %s", strerror(-ret));
+			return ret;
+		}
+		ret = list_subvol_fill_paths(fd, root_lookup);
+	} else {
+		ret = list_subvol_user(fd, root_lookup, path);
+	}
+
 	return ret;
 }
 
@@ -1631,12 +1941,14 @@ int btrfs_get_subvol(int fd, struct root_info *the_ri, const char *path)
 		return ret;
 	}
 
+	ret = -ENOENT;
 	rbn = rb_first(&rl.root);
 	while(rbn) {
 		ri = rb_entry(rbn, struct root_info, rb_node);
 		rr = resolve_root(&rl, ri, root_id);
-		if (rr == -ENOENT) {
-			ret = -ENOENT;
+		if (rr == -ENOENT ||
+		    ri->root_id == BTRFS_FS_TREE_OBJECTID ||
+		    uuid_is_null(ri->uuid)) {
 			rbn = rb_next(rbn);
 			continue;
 		}
diff --git a/cmds-subvolume.c b/cmds-subvolume.c
index faa10c5a..7a7c6f3b 100644
--- a/cmds-subvolume.c
+++ b/cmds-subvolume.c
@@ -596,6 +596,19 @@ static int cmd_subvol_list(int argc, char **argv)
 		goto out;
 	}
 
+	ret = check_perm_for_tree_search(fd);
+	if (ret < 0) {
+		ret = -1;
+		error("can't check the permission for tree search: %s",
+				strerror(-ret));
+		goto out;
+	}
+	if (!ret && is_list_all) {
+		ret = -1;
+		error("only root can use -a option");
+		goto out;
+	}
+
 	if (flags)
 		btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_FLAGS,
 					flags);
