btrfs-progs/btrfs-list.c
Wang Shilong 8e8e019e91 Btrfs-progs: introduces '-a' option into subvolume list command
We list the subvolumes under current directory according to the input
subvolume.

However, if we still want to list all the subvolumes in the tree, we
can use '-a' option to help us.

There may be two kinds of path: absolute path , relative path .

The absolute path is beginning with "<FS_TREE>"
The relative path is under current path that you input.

Signed-off-by: Wang Shilong <wangsl-fnst@cn.fujitsu.com>
2012-10-04 16:26:33 -04:00

1801 lines
39 KiB
C

/*
* Copyright (C) 2010 Oracle. 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.
*/
#define _GNU_SOURCE
#ifndef __CHECKER__
#include <sys/ioctl.h>
#include <sys/mount.h>
#include "ioctl.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <libgen.h>
#include "ctree.h"
#include "transaction.h"
#include "utils.h"
#include <uuid/uuid.h>
#include "btrfs-list.h"
#define BTRFS_LIST_NFILTERS_INCREASE (2 * BTRFS_LIST_FILTER_MAX)
#define BTRFS_LIST_NCOMPS_INCREASE (2 * BTRFS_LIST_COMP_MAX)
/* we store all the roots we find in an rbtree so that we can
* search for them later.
*/
struct root_lookup {
struct rb_root root;
};
/*
* one of these for each root we find.
*/
struct root_info {
struct rb_node rb_node;
struct rb_node sort_node;
/* this root's id */
u64 root_id;
/* equal the offset of the root's key */
u64 root_offset;
/* flags of the root */
u64 flags;
/* the id of the root that references this one */
u64 ref_tree;
/* the dir id we're in from ref_tree */
u64 dir_id;
u64 top_id;
/* generation when the root is created or last updated */
u64 gen;
/* creation generation of this root in sec*/
u64 ogen;
/* creation time of this root in sec*/
time_t otime;
u8 uuid[BTRFS_UUID_SIZE];
/* path from the subvol we live in to this root, including the
* root's name. This is null until we do the extra lookup ioctl.
*/
char *path;
/* the name of this root in the directory it lives in */
char *name;
char *full_path;
};
struct {
char *name;
char *column_name;
int need_print;
} btrfs_list_columns[] = {
{
.name = "ID",
.column_name = "ID",
.need_print = 1,
},
{
.name = "gen",
.column_name = "Gen",
.need_print = 1,
},
{
.name = "cgen",
.column_name = "CGen",
.need_print = 0,
},
{
.name = "parent",
.column_name = "Parent",
.need_print = 0,
},
{
.name = "top level",
.column_name = "Top Level",
.need_print = 1,
},
{
.name = "otime",
.column_name = "OTime",
.need_print = 0,
},
{
.name = "uuid",
.column_name = "UUID",
.need_print = 0,
},
{
.name = "path",
.column_name = "Path",
.need_print = 1,
},
{
.name = NULL,
.column_name = NULL,
.need_print = 0,
},
};
static btrfs_list_filter_func all_filter_funcs[];
static btrfs_list_comp_func all_comp_funcs[];
void btrfs_list_setup_print_column(enum btrfs_list_column_enum column)
{
int i;
BUG_ON(column < 0 || column > BTRFS_LIST_ALL);
if (column < BTRFS_LIST_ALL) {
btrfs_list_columns[column].need_print = 1;
return;
}
for (i = 0; i < BTRFS_LIST_ALL; i++)
btrfs_list_columns[i].need_print = 1;
}
static void root_lookup_init(struct root_lookup *tree)
{
tree->root.rb_node = NULL;
}
static int comp_entry_with_rootid(struct root_info *entry1,
struct root_info *entry2,
int is_descending)
{
int ret;
if (entry1->root_id > entry2->root_id)
ret = 1;
else if (entry1->root_id < entry2->root_id)
ret = -1;
else
ret = 0;
return is_descending ? -ret : ret;
}
static int comp_entry_with_gen(struct root_info *entry1,
struct root_info *entry2,
int is_descending)
{
int ret;
if (entry1->gen > entry2->gen)
ret = 1;
else if (entry1->gen < entry2->gen)
ret = -1;
else
ret = 0;
return is_descending ? -ret : ret;
}
static int comp_entry_with_ogen(struct root_info *entry1,
struct root_info *entry2,
int is_descending)
{
int ret;
if (entry1->ogen > entry2->ogen)
ret = 1;
else if (entry1->ogen < entry2->ogen)
ret = -1;
else
ret = 0;
return is_descending ? -ret : ret;
}
static int comp_entry_with_path(struct root_info *entry1,
struct root_info *entry2,
int is_descending)
{
int ret;
if (strcmp(entry1->full_path, entry2->full_path) > 0)
ret = 1;
else if (strcmp(entry1->full_path, entry2->full_path) < 0)
ret = -1;
else
ret = 0;
return is_descending ? -ret : ret;
}
static btrfs_list_comp_func all_comp_funcs[] = {
[BTRFS_LIST_COMP_ROOTID] = comp_entry_with_rootid,
[BTRFS_LIST_COMP_OGEN] = comp_entry_with_ogen,
[BTRFS_LIST_COMP_GEN] = comp_entry_with_gen,
[BTRFS_LIST_COMP_PATH] = comp_entry_with_path,
};
static char *all_sort_items[] = {
[BTRFS_LIST_COMP_ROOTID] = "rootid",
[BTRFS_LIST_COMP_OGEN] = "ogen",
[BTRFS_LIST_COMP_GEN] = "gen",
[BTRFS_LIST_COMP_PATH] = "path",
[BTRFS_LIST_COMP_MAX] = NULL,
};
static int btrfs_list_get_sort_item(char *sort_name)
{
int i;
for (i = 0; i < BTRFS_LIST_COMP_MAX; i++) {
if (strcmp(sort_name, all_sort_items[i]) == 0)
return i;
}
return -1;
}
struct btrfs_list_comparer_set *btrfs_list_alloc_comparer_set(void)
{
struct btrfs_list_comparer_set *set;
int size;
size = sizeof(struct btrfs_list_comparer_set) +
BTRFS_LIST_NCOMPS_INCREASE * sizeof(struct btrfs_list_comparer);
set = malloc(size);
if (!set) {
fprintf(stderr, "memory allocation failed\n");
exit(1);
}
memset(set, 0, size);
set->total = BTRFS_LIST_NCOMPS_INCREASE;
return set;
}
void btrfs_list_free_comparer_set(struct btrfs_list_comparer_set *comp_set)
{
free(comp_set);
}
int btrfs_list_setup_comparer(struct btrfs_list_comparer_set **comp_set,
enum btrfs_list_comp_enum comparer,
int is_descending)
{
struct btrfs_list_comparer_set *set = *comp_set;
int size;
BUG_ON(!set);
BUG_ON(comparer >= BTRFS_LIST_COMP_MAX);
BUG_ON(set->ncomps > set->total);
if (set->ncomps == set->total) {
size = set->total + BTRFS_LIST_NCOMPS_INCREASE;
size = sizeof(*set) + size * sizeof(struct btrfs_list_comparer);
set = realloc(set, size);
if (!set) {
fprintf(stderr, "memory allocation failed\n");
exit(1);
}
memset(&set->comps[set->total], 0,
BTRFS_LIST_NCOMPS_INCREASE *
sizeof(struct btrfs_list_comparer));
set->total += BTRFS_LIST_NCOMPS_INCREASE;
*comp_set = set;
}
BUG_ON(set->comps[set->ncomps].comp_func);
set->comps[set->ncomps].comp_func = all_comp_funcs[comparer];
set->comps[set->ncomps].is_descending = is_descending;
set->ncomps++;
return 0;
}
static int sort_comp(struct root_info *entry1, struct root_info *entry2,
struct btrfs_list_comparer_set *set)
{
int rootid_compared = 0;
int i, ret = 0;
if (!set || !set->ncomps)
goto comp_rootid;
for (i = 0; i < set->ncomps; i++) {
if (!set->comps[i].comp_func)
break;
ret = set->comps[i].comp_func(entry1, entry2,
set->comps[i].is_descending);
if (ret)
return ret;
if (set->comps[i].comp_func == comp_entry_with_rootid)
rootid_compared = 1;
}
if (!rootid_compared) {
comp_rootid:
ret = comp_entry_with_rootid(entry1, entry2, 0);
}
return ret;
}
static int sort_tree_insert(struct root_lookup *sort_tree,
struct root_info *ins,
struct btrfs_list_comparer_set *comp_set)
{
struct rb_node **p = &sort_tree->root.rb_node;
struct rb_node *parent = NULL;
struct root_info *curr;
int ret;
while (*p) {
parent = *p;
curr = rb_entry(parent, struct root_info, sort_node);
ret = sort_comp(ins, curr, comp_set);
if (ret < 0)
p = &(*p)->rb_left;
else if (ret > 0)
p = &(*p)->rb_right;
else
return -EEXIST;
}
rb_link_node(&ins->sort_node, parent, p);
rb_insert_color(&ins->sort_node, &sort_tree->root);
return 0;
}
/*
* insert a new root into the tree. returns the existing root entry
* if one is already there. Both root_id and ref_tree are used
* as the key
*/
static int root_tree_insert(struct root_lookup *root_tree,
struct root_info *ins)
{
struct rb_node **p = &root_tree->root.rb_node;
struct rb_node * parent = NULL;
struct root_info *curr;
int ret;
while(*p) {
parent = *p;
curr = rb_entry(parent, struct root_info, rb_node);
ret = comp_entry_with_rootid(ins, curr, 0);
if (ret < 0)
p = &(*p)->rb_left;
else if (ret > 0)
p = &(*p)->rb_right;
else
return -EEXIST;
}
rb_link_node(&ins->rb_node, parent, p);
rb_insert_color(&ins->rb_node, &root_tree->root);
return 0;
}
/*
* find a given root id in the tree. We return the smallest one,
* rb_next can be used to move forward looking for more if required
*/
static struct root_info *root_tree_search(struct root_lookup *root_tree,
u64 root_id)
{
struct rb_node *n = root_tree->root.rb_node;
struct root_info *entry;
struct root_info tmp;
int ret;
tmp.root_id = root_id;
while(n) {
entry = rb_entry(n, struct root_info, rb_node);
ret = comp_entry_with_rootid(&tmp, entry, 0);
if (ret < 0)
n = n->rb_left;
else if (ret > 0)
n = n->rb_right;
else
return entry;
}
return NULL;
}
static int update_root(struct root_lookup *root_lookup,
u64 root_id, u64 ref_tree, u64 root_offset, u64 flags,
u64 dir_id, char *name, int name_len, u64 ogen, u64 gen,
time_t ot, void *uuid)
{
struct root_info *ri;
ri = root_tree_search(root_lookup, root_id);
if (!ri || ri->root_id != root_id)
return -ENOENT;
if (name && name_len > 0) {
if (ri->name)
free(ri->name);
ri->name = malloc(name_len + 1);
if (!ri->name) {
fprintf(stderr, "memory allocation failed\n");
exit(1);
}
strncpy(ri->name, name, name_len);
ri->name[name_len] = 0;
}
if (ref_tree)
ri->ref_tree = ref_tree;
if (root_offset)
ri->root_offset = root_offset;
if (flags)
ri->flags = flags;
if (dir_id)
ri->dir_id = dir_id;
if (gen)
ri->gen = gen;
if (ogen)
ri->ogen = ogen;
if (!ri->ogen && root_offset)
ri->ogen = root_offset;
if (ot)
ri->otime = ot;
if (uuid)
memcpy(&ri->uuid, uuid, BTRFS_UUID_SIZE);
return 0;
}
/*
* add_root - update the existed root, or allocate a new root and insert it
* into the lookup tree.
* root_id: object id of the root
* ref_tree: object id of the referring root.
* root_offset: offset value of the root'key
* dir_id: inode id of the directory in ref_tree where this root can be found.
* name: the name of root_id in that directory
* name_len: the length of name
* ogen: the original generation of the root
* gen: the current generation of the root
* ot: the original time(create time) of the root
* uuid: uuid of the root
*/
static int add_root(struct root_lookup *root_lookup,
u64 root_id, u64 ref_tree, u64 root_offset, u64 flags,
u64 dir_id, char *name, int name_len, u64 ogen, u64 gen,
time_t ot, void *uuid)
{
struct root_info *ri;
int ret;
ret = update_root(root_lookup, root_id, ref_tree, root_offset, flags,
dir_id, name, name_len, ogen, gen, ot, uuid);
if (!ret)
return 0;
ri = malloc(sizeof(*ri));
if (!ri) {
printf("memory allocation failed\n");
exit(1);
}
memset(ri, 0, sizeof(*ri));
ri->root_id = root_id;
if (name && name_len > 0) {
ri->name = malloc(name_len + 1);
if (!ri->name) {
fprintf(stderr, "memory allocation failed\n");
exit(1);
}
strncpy(ri->name, name, name_len);
ri->name[name_len] = 0;
}
if (ref_tree)
ri->ref_tree = ref_tree;
if (dir_id)
ri->dir_id = dir_id;
if (root_offset)
ri->root_offset = root_offset;
if (flags)
ri->flags = flags;
if (gen)
ri->gen = gen;
if (ogen)
ri->ogen = ogen;
if (!ri->ogen && root_offset)
ri->ogen = root_offset;
if (ot)
ri->otime = ot;
if (uuid)
memcpy(&ri->uuid, uuid, BTRFS_UUID_SIZE);
ret = root_tree_insert(root_lookup, ri);
if (ret) {
printf("failed to insert tree %llu\n", (unsigned long long)root_id);
exit(1);
}
return 0;
}
void __free_root_info(struct root_info *ri)
{
if (ri->name)
free(ri->name);
if (ri->path)
free(ri->path);
if (ri->full_path)
free(ri->full_path);
free(ri);
}
void __free_all_subvolumn(struct root_lookup *root_tree)
{
struct root_info *entry;
struct rb_node *n;
n = rb_first(&root_tree->root);
while (n) {
entry = rb_entry(n, struct root_info, rb_node);
rb_erase(n, &root_tree->root);
__free_root_info(entry);
n = rb_first(&root_tree->root);
}
}
/*
* for a given root_info, search through the root_lookup tree to construct
* the full path name to it.
*
* This can't be called until all the root_info->path fields are filled
* in by lookup_ino_path
*/
static int resolve_root(struct root_lookup *rl, struct root_info *ri,
u64 top_id)
{
char *full_path = NULL;
int len = 0;
struct root_info *found;
/*
* we go backwards from the root_info object and add pathnames
* from parent directories as we go.
*/
found = ri;
while (1) {
char *tmp;
u64 next;
int add_len = strlen(found->path);
/* room for / and for null */
tmp = malloc(add_len + 2 + len);
if (!tmp) {
perror("malloc failed");
exit(1);
}
if (full_path) {
memcpy(tmp + add_len + 1, full_path, len);
tmp[add_len] = '/';
memcpy(tmp, found->path, add_len);
tmp [add_len + len + 1] = '\0';
free(full_path);
full_path = tmp;
len += add_len + 1;
} else {
full_path = strdup(found->path);
len = add_len;
}
next = found->ref_tree;
if (next == top_id) {
ri->top_id = top_id;
break;
}
if (next == BTRFS_FS_TREE_OBJECTID) {
char p[] = "<FS_TREE>";
add_len = strlen(p);
len = strlen(full_path);
tmp = malloc(len + add_len + 2);
memcpy(tmp + add_len + 1, full_path, len);
tmp[add_len] = '/';
memcpy(tmp, p, add_len);
free(full_path);
full_path = tmp;
ri->top_id = next;
break;
}
/*
* if the ref_tree wasn't in our tree of roots, we're
* at the top
*/
found = root_tree_search(rl, next);
if (!found) {
ri->top_id = next;
break;
}
}
ri->full_path = full_path;
return 0;
}
/*
* for a single root_info, ask the kernel to give us a path name
* inside it's ref_root for the dir_id where it lives.
*
* This fills in root_info->path with the path to the directory and and
* appends this root's name.
*/
static int lookup_ino_path(int fd, struct root_info *ri)
{
struct btrfs_ioctl_ino_lookup_args args;
int ret, e;
if (ri->path)
return 0;
memset(&args, 0, sizeof(args));
args.treeid = ri->ref_tree;
args.objectid = ri->dir_id;
ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args);
e = errno;
if (ret) {
fprintf(stderr, "ERROR: Failed to lookup path for root %llu - %s\n",
(unsigned long long)ri->ref_tree,
strerror(e));
return ret;
}
if (args.name[0]) {
/*
* we're in a subdirectory of ref_tree, the kernel ioctl
* puts a / in there for us
*/
ri->path = malloc(strlen(ri->name) + strlen(args.name) + 1);
if (!ri->path) {
perror("malloc failed");
exit(1);
}
strcpy(ri->path, args.name);
strcat(ri->path, ri->name);
} else {
/* we're at the root of ref_tree */
ri->path = strdup(ri->name);
if (!ri->path) {
perror("strdup failed");
exit(1);
}
}
return 0;
}
/* finding the generation for a given path is a two step process.
* First we use the inode loookup routine to find out the root id
*
* Then we use the tree search ioctl to scan all the root items for a
* given root id and spit out the latest generation we can find
*/
static u64 find_root_gen(int fd)
{
struct btrfs_ioctl_ino_lookup_args ino_args;
int ret;
struct btrfs_ioctl_search_args args;
struct btrfs_ioctl_search_key *sk = &args.key;
struct btrfs_ioctl_search_header *sh;
unsigned long off = 0;
u64 max_found = 0;
int i;
int e;
memset(&ino_args, 0, sizeof(ino_args));
ino_args.objectid = BTRFS_FIRST_FREE_OBJECTID;
/* this ioctl fills in ino_args->treeid */
ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &ino_args);
e = errno;
if (ret) {
fprintf(stderr, "ERROR: Failed to lookup path for dirid %llu - %s\n",
(unsigned long long)BTRFS_FIRST_FREE_OBJECTID,
strerror(e));
return 0;
}
memset(&args, 0, sizeof(args));
sk->tree_id = 1;
/*
* there may be more than one ROOT_ITEM key if there are
* snapshots pending deletion, we have to loop through
* them.
*/
sk->min_objectid = ino_args.treeid;
sk->max_objectid = ino_args.treeid;
sk->max_type = BTRFS_ROOT_ITEM_KEY;
sk->min_type = BTRFS_ROOT_ITEM_KEY;
sk->max_offset = (u64)-1;
sk->max_transid = (u64)-1;
sk->nr_items = 4096;
while (1) {
ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
e = errno;
if (ret < 0) {
fprintf(stderr, "ERROR: can't perform the search - %s\n",
strerror(e));
return 0;
}
/* the ioctl returns the number of item it found in nr_items */
if (sk->nr_items == 0)
break;
off = 0;
for (i = 0; i < sk->nr_items; i++) {
struct btrfs_root_item *item;
sh = (struct btrfs_ioctl_search_header *)(args.buf +
off);
off += sizeof(*sh);
item = (struct btrfs_root_item *)(args.buf + off);
off += sh->len;
sk->min_objectid = sh->objectid;
sk->min_type = sh->type;
sk->min_offset = sh->offset;
if (sh->objectid > ino_args.treeid)
break;
if (sh->objectid == ino_args.treeid &&
sh->type == BTRFS_ROOT_ITEM_KEY) {
max_found = max(max_found,
btrfs_root_generation(item));
}
}
if (sk->min_offset < (u64)-1)
sk->min_offset++;
else
break;
if (sk->min_type != BTRFS_ROOT_ITEM_KEY)
break;
if (sk->min_objectid != BTRFS_ROOT_ITEM_KEY)
break;
}
return max_found;
}
/* pass in a directory id and this will return
* the full path of the parent directory inside its
* subvolume root.
*
* It may return NULL if it is in the root, or an ERR_PTR if things
* go badly.
*/
static char *__ino_resolve(int fd, u64 dirid)
{
struct btrfs_ioctl_ino_lookup_args args;
int ret;
char *full;
int e;
memset(&args, 0, sizeof(args));
args.objectid = dirid;
ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args);
e = errno;
if (ret) {
fprintf(stderr, "ERROR: Failed to lookup path for dirid %llu - %s\n",
(unsigned long long)dirid, strerror(e) );
return ERR_PTR(ret);
}
if (args.name[0]) {
/*
* we're in a subdirectory of ref_tree, the kernel ioctl
* puts a / in there for us
*/
full = strdup(args.name);
if (!full) {
perror("malloc failed");
return ERR_PTR(-ENOMEM);
}
} else {
/* we're at the root of ref_tree */
full = NULL;
}
return full;
}
/*
* simple string builder, returning a new string with both
* dirid and name
*/
char *build_name(char *dirid, char *name)
{
char *full;
if (!dirid)
return strdup(name);
full = malloc(strlen(dirid) + strlen(name) + 1);
if (!full)
return NULL;
strcpy(full, dirid);
strcat(full, name);
return full;
}
/*
* given an inode number, this returns the full path name inside the subvolume
* to that file/directory. cache_dirid and cache_name are used to
* cache the results so we can avoid tree searches if a later call goes
* to the same directory or file name
*/
static char *ino_resolve(int fd, u64 ino, u64 *cache_dirid, char **cache_name)
{
u64 dirid;
char *dirname;
char *name;
char *full;
int ret;
struct btrfs_ioctl_search_args args;
struct btrfs_ioctl_search_key *sk = &args.key;
struct btrfs_ioctl_search_header *sh;
unsigned long off = 0;
int namelen;
int e;
memset(&args, 0, sizeof(args));
sk->tree_id = 0;
/*
* step one, we search for the inode back ref. We just use the first
* one
*/
sk->min_objectid = ino;
sk->max_objectid = ino;
sk->max_type = BTRFS_INODE_REF_KEY;
sk->max_offset = (u64)-1;
sk->min_type = BTRFS_INODE_REF_KEY;
sk->max_transid = (u64)-1;
sk->nr_items = 1;
ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
e = errno;
if (ret < 0) {
fprintf(stderr, "ERROR: can't perform the search - %s\n",
strerror(e));
return NULL;
}
/* the ioctl returns the number of item it found in nr_items */
if (sk->nr_items == 0)
return NULL;
off = 0;
sh = (struct btrfs_ioctl_search_header *)(args.buf + off);
if (sh->type == BTRFS_INODE_REF_KEY) {
struct btrfs_inode_ref *ref;
dirid = sh->offset;
ref = (struct btrfs_inode_ref *)(sh + 1);
namelen = btrfs_stack_inode_ref_name_len(ref);
name = (char *)(ref + 1);
name = strndup(name, namelen);
/* use our cached value */
if (dirid == *cache_dirid && *cache_name) {
dirname = *cache_name;
goto build;
}
} else {
return NULL;
}
/*
* the inode backref gives us the file name and the parent directory id.
* From here we use __ino_resolve to get the path to the parent
*/
dirname = __ino_resolve(fd, dirid);
build:
full = build_name(dirname, name);
if (*cache_name && dirname != *cache_name)
free(*cache_name);
*cache_name = dirname;
*cache_dirid = dirid;
free(name);
return full;
}
int btrfs_list_get_default_subvolume(int fd, u64 *default_id)
{
struct btrfs_ioctl_search_args args;
struct btrfs_ioctl_search_key *sk = &args.key;
struct btrfs_ioctl_search_header *sh;
u64 found = 0;
int ret;
memset(&args, 0, sizeof(args));
/*
* search for a dir item with a name 'default' in the tree of
* tree roots, it should point us to a default root
*/
sk->tree_id = 1;
/* don't worry about ancient format and request only one item */
sk->nr_items = 1;
sk->max_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
sk->min_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
sk->max_type = BTRFS_DIR_ITEM_KEY;
sk->min_type = BTRFS_DIR_ITEM_KEY;
sk->max_offset = (u64)-1;
sk->max_transid = (u64)-1;
ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
if (ret < 0)
return ret;
/* the ioctl returns the number of items it found in nr_items */
if (sk->nr_items == 0)
goto out;
sh = (struct btrfs_ioctl_search_header *)args.buf;
if (sh->type == BTRFS_DIR_ITEM_KEY) {
struct btrfs_dir_item *di;
int name_len;
char *name;
di = (struct btrfs_dir_item *)(sh + 1);
name_len = btrfs_stack_dir_name_len(di);
name = (char *)(di + 1);
if (!strncmp("default", name, name_len))
found = btrfs_disk_key_objectid(&di->location);
}
out:
*default_id = found;
return 0;
}
static int __list_subvol_search(int fd, struct root_lookup *root_lookup)
{
int ret;
struct btrfs_ioctl_search_args args;
struct btrfs_ioctl_search_key *sk = &args.key;
struct btrfs_ioctl_search_header *sh;
struct btrfs_root_ref *ref;
struct btrfs_root_item *ri;
unsigned long off = 0;
int name_len;
char *name;
u64 dir_id;
u64 gen = 0;
u64 ogen;
u64 flags;
int i;
time_t t;
u8 uuid[BTRFS_UUID_SIZE];
root_lookup_init(root_lookup);
memset(&args, 0, sizeof(args));
/* search in the tree of tree roots */
sk->tree_id = 1;
/*
* set the min and max to backref keys. The search will
* only send back this type of key now.
*/
sk->max_type = BTRFS_ROOT_BACKREF_KEY;
sk->min_type = BTRFS_ROOT_ITEM_KEY;
sk->min_objectid = BTRFS_FIRST_FREE_OBJECTID;
/*
* set all the other params to the max, we'll take any objectid
* and any trans
*/
sk->max_objectid = BTRFS_LAST_FREE_OBJECTID;
sk->max_offset = (u64)-1;
sk->max_transid = (u64)-1;
/* just a big number, doesn't matter much */
sk->nr_items = 4096;
while(1) {
ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
if (ret < 0)
return ret;
/* the ioctl returns the number of item it found in nr_items */
if (sk->nr_items == 0)
break;
off = 0;
/*
* for each item, pull the key out of the header and then
* read the root_ref item it contains
*/
for (i = 0; i < sk->nr_items; i++) {
sh = (struct btrfs_ioctl_search_header *)(args.buf +
off);
off += sizeof(*sh);
if (sh->type == BTRFS_ROOT_BACKREF_KEY) {
ref = (struct btrfs_root_ref *)(args.buf + off);
name_len = btrfs_stack_root_ref_name_len(ref);
name = (char *)(ref + 1);
dir_id = btrfs_stack_root_ref_dirid(ref);
add_root(root_lookup, sh->objectid, sh->offset,
0, 0, dir_id, name, name_len, 0, 0, 0,
NULL);
} else if (sh->type == BTRFS_ROOT_ITEM_KEY) {
ri = (struct btrfs_root_item *)(args.buf + off);
gen = btrfs_root_generation(ri);
flags = btrfs_root_flags(ri);
if(sh->len >
sizeof(struct btrfs_root_item_v0)) {
t = ri->otime.sec;
ogen = btrfs_root_otransid(ri);
memcpy(uuid, ri->uuid, BTRFS_UUID_SIZE);
} else {
t = 0;
ogen = 0;
memset(uuid, 0, BTRFS_UUID_SIZE);
}
add_root(root_lookup, sh->objectid, 0,
sh->offset, flags, 0, NULL, 0, ogen,
gen, t, uuid);
}
off += sh->len;
/*
* record the mins in sk so we can make sure the
* next search doesn't repeat this root
*/
sk->min_objectid = sh->objectid;
sk->min_type = sh->type;
sk->min_offset = sh->offset;
}
sk->nr_items = 4096;
sk->min_offset++;
if (!sk->min_offset) /* overflow */
sk->min_type++;
else
continue;
if (sk->min_type > BTRFS_ROOT_BACKREF_KEY) {
sk->min_type = BTRFS_ROOT_ITEM_KEY;
sk->min_objectid++;
} else
continue;
if (sk->min_objectid > sk->max_objectid)
break;
}
return 0;
}
static int filter_by_rootid(struct root_info *ri, u64 data)
{
return ri->root_id == data;
}
static int filter_snapshot(struct root_info *ri, u64 data)
{
return !!ri->root_offset;
}
static int filter_flags(struct root_info *ri, u64 flags)
{
return ri->flags & flags;
}
static int filter_gen_more(struct root_info *ri, u64 data)
{
return ri->gen >= data;
}
static int filter_gen_less(struct root_info *ri, u64 data)
{
return ri->gen <= data;
}
static int filter_gen_equal(struct root_info *ri, u64 data)
{
return ri->gen == data;
}
static int filter_cgen_more(struct root_info *ri, u64 data)
{
return ri->ogen >= data;
}
static int filter_cgen_less(struct root_info *ri, u64 data)
{
return ri->ogen <= data;
}
static int filter_cgen_equal(struct root_info *ri, u64 data)
{
return ri->ogen == data;
}
static int filter_topid_equal(struct root_info *ri, u64 data)
{
return ri->top_id == data;
}
static btrfs_list_filter_func all_filter_funcs[] = {
[BTRFS_LIST_FILTER_ROOTID] = filter_by_rootid,
[BTRFS_LIST_FILTER_SNAPSHOT_ONLY] = filter_snapshot,
[BTRFS_LIST_FILTER_FLAGS] = filter_flags,
[BTRFS_LIST_FILTER_GEN_MORE] = filter_gen_more,
[BTRFS_LIST_FILTER_GEN_LESS] = filter_gen_less,
[BTRFS_LIST_FILTER_GEN_EQUAL] = filter_gen_equal,
[BTRFS_LIST_FILTER_CGEN_MORE] = filter_cgen_more,
[BTRFS_LIST_FILTER_CGEN_LESS] = filter_cgen_less,
[BTRFS_LIST_FILTER_CGEN_EQUAL] = filter_cgen_equal,
[BTRFS_LIST_FILTER_TOPID_EQUAL] = filter_topid_equal,
};
struct btrfs_list_filter_set *btrfs_list_alloc_filter_set(void)
{
struct btrfs_list_filter_set *set;
int size;
size = sizeof(struct btrfs_list_filter_set) +
BTRFS_LIST_NFILTERS_INCREASE * sizeof(struct btrfs_list_filter);
set = malloc(size);
if (!set) {
fprintf(stderr, "memory allocation failed\n");
exit(1);
}
memset(set, 0, size);
set->total = BTRFS_LIST_NFILTERS_INCREASE;
return set;
}
void btrfs_list_free_filter_set(struct btrfs_list_filter_set *filter_set)
{
free(filter_set);
}
int btrfs_list_setup_filter(struct btrfs_list_filter_set **filter_set,
enum btrfs_list_filter_enum filter, u64 data)
{
struct btrfs_list_filter_set *set = *filter_set;
int size;
BUG_ON(!set);
BUG_ON(filter >= BTRFS_LIST_FILTER_MAX);
BUG_ON(set->nfilters > set->total);
if (set->nfilters == set->total) {
size = set->total + BTRFS_LIST_NFILTERS_INCREASE;
size = sizeof(*set) + size * sizeof(struct btrfs_list_filter);
set = realloc(set, size);
if (!set) {
fprintf(stderr, "memory allocation failed\n");
exit(1);
}
memset(&set->filters[set->total], 0,
BTRFS_LIST_NFILTERS_INCREASE *
sizeof(struct btrfs_list_filter));
set->total += BTRFS_LIST_NFILTERS_INCREASE;
*filter_set = set;
}
BUG_ON(set->filters[set->nfilters].filter_func);
set->filters[set->nfilters].filter_func = all_filter_funcs[filter];
set->filters[set->nfilters].data = data;
set->nfilters++;
return 0;
}
static int filter_root(struct root_info *ri,
struct btrfs_list_filter_set *set)
{
int i, ret;
if (!set || !set->nfilters)
return 1;
for (i = 0; i < set->nfilters; i++) {
if (!set->filters[i].filter_func)
break;
ret = set->filters[i].filter_func(ri, set->filters[i].data);
if (!ret)
return 0;
}
return 1;
}
static void __filter_and_sort_subvol(struct root_lookup *all_subvols,
struct root_lookup *sort_tree,
struct btrfs_list_filter_set *filter_set,
struct btrfs_list_comparer_set *comp_set,
int fd)
{
struct rb_node *n;
struct root_info *entry;
int ret;
u64 top_id = btrfs_list_get_path_rootid(fd);
root_lookup_init(sort_tree);
n = rb_last(&all_subvols->root);
while (n) {
entry = rb_entry(n, struct root_info, rb_node);
resolve_root(all_subvols, entry, top_id);
ret = filter_root(entry, filter_set);
if (ret)
sort_tree_insert(sort_tree, entry, comp_set);
n = rb_prev(n);
}
}
static int __list_subvol_fill_paths(int fd, struct root_lookup *root_lookup)
{
struct rb_node *n;
n = rb_first(&root_lookup->root);
while (n) {
struct root_info *entry;
int ret;
entry = rb_entry(n, struct root_info, rb_node);
ret = lookup_ino_path(fd, entry);
if(ret < 0)
return ret;
n = rb_next(n);
}
return 0;
}
static void print_subvolume_column(struct root_info *subv,
enum btrfs_list_column_enum column)
{
char tstr[256];
char uuidparse[37];
BUG_ON(column >= BTRFS_LIST_ALL || column < 0);
switch (column) {
case BTRFS_LIST_OBJECTID:
printf("%llu", subv->root_id);
break;
case BTRFS_LIST_GENERATION:
printf("%llu", subv->gen);
break;
case BTRFS_LIST_OGENERATION:
printf("%llu", subv->ogen);
break;
case BTRFS_LIST_PARENT:
printf("%llu", subv->ref_tree);
break;
case BTRFS_LIST_TOP_LEVEL:
printf("%llu", subv->top_id);
break;
case BTRFS_LIST_OTIME:
if (subv->otime)
strftime(tstr, 256, "%Y-%m-%d %X",
localtime(&subv->otime));
else
strcpy(tstr, "-");
printf("%s", tstr);
break;
case BTRFS_LIST_UUID:
if (uuid_is_null(subv->uuid))
strcpy(uuidparse, "-");
else
uuid_unparse(subv->uuid, uuidparse);
printf("%s", uuidparse);
break;
case BTRFS_LIST_PATH:
BUG_ON(!subv->full_path);
printf("%s", subv->full_path);
break;
default:
break;
}
}
static void print_single_volume_info_table(struct root_info *subv)
{
int i;
for (i = 0; i < BTRFS_LIST_ALL; i++) {
if (!btrfs_list_columns[i].need_print)
continue;
print_subvolume_column(subv, i);
if (i != BTRFS_LIST_PATH)
printf("\t");
if (i == BTRFS_LIST_TOP_LEVEL)
printf("\t");
}
printf("\n");
}
static void print_single_volume_info_default(struct root_info *subv)
{
int i;
for (i = 0; i < BTRFS_LIST_ALL; i++) {
if (!btrfs_list_columns[i].need_print)
continue;
printf("%s ", btrfs_list_columns[i].name);
print_subvolume_column(subv, i);
if (i != BTRFS_LIST_PATH)
printf(" ");
}
printf("\n");
}
static void print_all_volume_info_tab_head()
{
int i;
int len;
char barrier[20];
for (i = 0; i < BTRFS_LIST_ALL; i++) {
if (btrfs_list_columns[i].need_print)
printf("%s\t", btrfs_list_columns[i].name);
if (i == BTRFS_LIST_ALL-1)
printf("\n");
}
for (i = 0; i < BTRFS_LIST_ALL; i++) {
memset(barrier, 0, sizeof(barrier));
if (btrfs_list_columns[i].need_print) {
len = strlen(btrfs_list_columns[i].name);
while (len--)
strcat(barrier, "-");
printf("%s\t", barrier);
}
if (i == BTRFS_LIST_ALL-1)
printf("\n");
}
}
static void print_all_volume_info(struct root_lookup *sorted_tree,
int is_tab_result)
{
struct rb_node *n;
struct root_info *entry;
if (is_tab_result)
print_all_volume_info_tab_head();
n = rb_first(&sorted_tree->root);
while (n) {
entry = rb_entry(n, struct root_info, sort_node);
if (is_tab_result)
print_single_volume_info_table(entry);
else
print_single_volume_info_default(entry);
n = rb_next(n);
}
}
int btrfs_list_subvols(int fd, struct btrfs_list_filter_set *filter_set,
struct btrfs_list_comparer_set *comp_set,
int is_tab_result)
{
struct root_lookup root_lookup;
struct root_lookup root_sort;
int ret;
ret = __list_subvol_search(fd, &root_lookup);
if (ret) {
fprintf(stderr, "ERROR: can't perform the search - %s\n",
strerror(errno));
return ret;
}
/*
* 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 < 0)
return ret;
__filter_and_sort_subvol(&root_lookup, &root_sort, filter_set,
comp_set, fd);
print_all_volume_info(&root_sort, is_tab_result);
__free_all_subvolumn(&root_lookup);
return ret;
}
static int print_one_extent(int fd, struct btrfs_ioctl_search_header *sh,
struct btrfs_file_extent_item *item,
u64 found_gen, u64 *cache_dirid,
char **cache_dir_name, u64 *cache_ino,
char **cache_full_name)
{
u64 len = 0;
u64 disk_start = 0;
u64 disk_offset = 0;
u8 type;
int compressed = 0;
int flags = 0;
char *name = NULL;
if (sh->objectid == *cache_ino) {
name = *cache_full_name;
} else if (*cache_full_name) {
free(*cache_full_name);
*cache_full_name = NULL;
}
if (!name) {
name = ino_resolve(fd, sh->objectid, cache_dirid,
cache_dir_name);
*cache_full_name = name;
*cache_ino = sh->objectid;
}
if (!name)
return -EIO;
type = btrfs_stack_file_extent_type(item);
compressed = btrfs_stack_file_extent_compression(item);
if (type == BTRFS_FILE_EXTENT_REG ||
type == BTRFS_FILE_EXTENT_PREALLOC) {
disk_start = btrfs_stack_file_extent_disk_bytenr(item);
disk_offset = btrfs_stack_file_extent_offset(item);
len = btrfs_stack_file_extent_num_bytes(item);
} else if (type == BTRFS_FILE_EXTENT_INLINE) {
disk_start = 0;
disk_offset = 0;
len = btrfs_stack_file_extent_ram_bytes(item);
} else {
printf("unhandled extent type %d for inode %llu "
"file offset %llu gen %llu\n",
type,
(unsigned long long)sh->objectid,
(unsigned long long)sh->offset,
(unsigned long long)found_gen);
return -EIO;
}
printf("inode %llu file offset %llu len %llu disk start %llu "
"offset %llu gen %llu flags ",
(unsigned long long)sh->objectid,
(unsigned long long)sh->offset,
(unsigned long long)len,
(unsigned long long)disk_start,
(unsigned long long)disk_offset,
(unsigned long long)found_gen);
if (compressed) {
printf("COMPRESS");
flags++;
}
if (type == BTRFS_FILE_EXTENT_PREALLOC) {
printf("%sPREALLOC", flags ? "|" : "");
flags++;
}
if (type == BTRFS_FILE_EXTENT_INLINE) {
printf("%sINLINE", flags ? "|" : "");
flags++;
}
if (!flags)
printf("NONE");
printf(" %s\n", name);
return 0;
}
int btrfs_list_find_updated_files(int fd, u64 root_id, u64 oldest_gen)
{
int ret;
struct btrfs_ioctl_search_args args;
struct btrfs_ioctl_search_key *sk = &args.key;
struct btrfs_ioctl_search_header *sh;
struct btrfs_file_extent_item *item;
unsigned long off = 0;
u64 found_gen;
u64 max_found = 0;
int i;
int e;
u64 cache_dirid = 0;
u64 cache_ino = 0;
char *cache_dir_name = NULL;
char *cache_full_name = NULL;
struct btrfs_file_extent_item backup;
memset(&backup, 0, sizeof(backup));
memset(&args, 0, sizeof(args));
sk->tree_id = root_id;
/*
* set all the other params to the max, we'll take any objectid
* and any trans
*/
sk->max_objectid = (u64)-1;
sk->max_offset = (u64)-1;
sk->max_transid = (u64)-1;
sk->max_type = BTRFS_EXTENT_DATA_KEY;
sk->min_transid = oldest_gen;
/* just a big number, doesn't matter much */
sk->nr_items = 4096;
max_found = find_root_gen(fd);
while(1) {
ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
e = errno;
if (ret < 0) {
fprintf(stderr, "ERROR: can't perform the search- %s\n",
strerror(e));
return ret;
}
/* the ioctl returns the number of item it found in nr_items */
if (sk->nr_items == 0)
break;
off = 0;
/*
* for each item, pull the key out of the header and then
* read the root_ref item it contains
*/
for (i = 0; i < sk->nr_items; i++) {
sh = (struct btrfs_ioctl_search_header *)(args.buf +
off);
off += sizeof(*sh);
/*
* just in case the item was too big, pass something other
* than garbage
*/
if (sh->len == 0)
item = &backup;
else
item = (struct btrfs_file_extent_item *)(args.buf +
off);
found_gen = btrfs_stack_file_extent_generation(item);
if (sh->type == BTRFS_EXTENT_DATA_KEY &&
found_gen >= oldest_gen) {
print_one_extent(fd, sh, item, found_gen,
&cache_dirid, &cache_dir_name,
&cache_ino, &cache_full_name);
}
off += sh->len;
/*
* record the mins in sk so we can make sure the
* next search doesn't repeat this root
*/
sk->min_objectid = sh->objectid;
sk->min_offset = sh->offset;
sk->min_type = sh->type;
}
sk->nr_items = 4096;
if (sk->min_offset < (u64)-1)
sk->min_offset++;
else if (sk->min_objectid < (u64)-1) {
sk->min_objectid++;
sk->min_offset = 0;
sk->min_type = 0;
} else
break;
}
free(cache_dir_name);
free(cache_full_name);
printf("transid marker was %llu\n", (unsigned long long)max_found);
return ret;
}
char *btrfs_list_path_for_root(int fd, u64 root)
{
struct root_lookup root_lookup;
struct rb_node *n;
char *ret_path = NULL;
int ret;
u64 top_id = btrfs_list_get_path_rootid(fd);
ret = __list_subvol_search(fd, &root_lookup);
if (ret < 0)
return ERR_PTR(ret);
ret = __list_subvol_fill_paths(fd, &root_lookup);
if (ret < 0)
return ERR_PTR(ret);
n = rb_last(&root_lookup.root);
while (n) {
struct root_info *entry;
entry = rb_entry(n, struct root_info, rb_node);
resolve_root(&root_lookup, entry, top_id);
if (entry->root_id == root) {
ret_path = entry->full_path;
entry->full_path = NULL;
}
n = rb_prev(n);
}
__free_all_subvolumn(&root_lookup);
return ret_path;
}
int btrfs_list_parse_sort_string(char *optarg,
struct btrfs_list_comparer_set **comps)
{
int order;
int flag;
char *p;
char **ptr_argv;
int what_to_sort;
while ((p = strtok(optarg, ",")) != NULL) {
flag = 0;
ptr_argv = all_sort_items;
while (*ptr_argv) {
if (strcmp(*ptr_argv, p) == 0) {
flag = 1;
break;
} else {
p++;
if (strcmp(*ptr_argv, p) == 0) {
flag = 1;
p--;
break;
}
p--;
}
ptr_argv++;
}
if (flag == 0)
return -1;
else {
if (*p == '+') {
order = 0;
p++;
} else if (*p == '-') {
order = 1;
p++;
} else
order = 0;
what_to_sort = btrfs_list_get_sort_item(p);
btrfs_list_setup_comparer(comps, what_to_sort, order);
}
optarg = NULL;
}
return 0;
}
/*
* This function is used to parse the argument of filter condition.
*
* type is the filter object.
*/
int btrfs_list_parse_filter_string(char *optarg,
struct btrfs_list_filter_set **filters,
enum btrfs_list_filter_enum type)
{
u64 arg;
char *ptr_parse_end = NULL;
char *ptr_optarg_end = optarg + strlen(optarg);
switch (*(optarg++)) {
case '+':
arg = (u64)strtol(optarg, &ptr_parse_end, 10);
type += 2;
if (ptr_parse_end != ptr_optarg_end)
return -1;
btrfs_list_setup_filter(filters, type, arg);
break;
case '-':
arg = (u64)strtoll(optarg, &ptr_parse_end, 10);
type += 1;
if (ptr_parse_end != ptr_optarg_end)
return -1;
btrfs_list_setup_filter(filters, type, arg);
break;
default:
optarg--;
arg = (u64)strtoll(optarg, &ptr_parse_end, 10);
if (ptr_parse_end != ptr_optarg_end)
return -1;
btrfs_list_setup_filter(filters, type, arg);
break;
}
return 0;
}
u64 btrfs_list_get_path_rootid(int fd)
{
int ret;
struct btrfs_ioctl_ino_lookup_args args;
memset(&args, 0, sizeof(args));
args.objectid = BTRFS_FIRST_FREE_OBJECTID;
ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args);
if (ret < 0) {
fprintf(stderr,
"ERROR: can't perform the search -%s\n",
strerror(errno));
return ret;
}
return args.treeid;
}