Add btrfs subvol find-new command
authorChris Mason <chris.mason@oracle.com>
Thu, 18 Mar 2010 16:32:32 +0000 (12:32 -0400)
committerChris Mason <chris.mason@oracle.com>
Thu, 18 Mar 2010 16:32:32 +0000 (12:32 -0400)
btrfs-subvol find-new <subvol> <id> will search through a given subvol
and print out all the files with extents newer than a given id.

Care must be taken to make sure any pending delalloc is on disk before
running this because that won't show up in the output.

btrfs-list.c
btrfs.c
btrfs_cmds.c
btrfs_cmds.h
ctree.h
ioctl.h

index f2f119b..7741705 100644 (file)
@@ -303,6 +303,238 @@ static int lookup_ino_path(int fd, struct root_info *ri)
        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;
+
+       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);
+       if (ret) {
+               fprintf(stderr, "ERROR: Failed to lookup path for dirid %llu\n",
+                       (unsigned long long)BTRFS_FIRST_FREE_OBJECTID);
+               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);
+               if (ret < 0) {
+                       fprintf(stderr, "ERROR: can't perform the search\n");
+                       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;
+
+       memset(&args, 0, sizeof(args));
+       args.objectid = dirid;
+
+       ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args);
+       if (ret) {
+               fprintf(stderr, "ERROR: Failed to lookup path for dirid %llu\n",
+                       (unsigned long long)dirid);
+               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;
+
+       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);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: can't perform the search\n");
+               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 list_subvols(int fd)
 {
        struct root_lookup root_lookup;
@@ -363,14 +595,15 @@ int list_subvols(int fd)
                        sh = (struct btrfs_ioctl_search_header *)(args.buf +
                                                                  off);
                        off += sizeof(*sh);
-
-                       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,
-                                dir_id, name, name_len);
+                       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,
+                                        dir_id, name, name_len);
+                       }
 
                        off += sh->len;
 
@@ -386,9 +619,11 @@ int list_subvols(int fd)
                /* this iteration is done, step forward one root for the next
                 * ioctl
                 */
-               if (sk->min_objectid < (u64)-1)
+               if (sk->min_objectid < (u64)-1) {
                        sk->min_objectid++;
-               else
+                       sk->min_type = BTRFS_ROOT_BACKREF_KEY;
+                       sk->min_offset = 0;
+               } else
                        break;
        }
        /*
@@ -420,3 +655,171 @@ int list_subvols(int fd)
 
        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;
+       u64 disk_start;
+       u64 disk_offset;
+       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);
+       }
+       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 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;
+       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);
+               if (ret < 0) {
+                       fprintf(stderr, "ERROR: can't perform the search\n");
+                       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;
+}
diff --git a/btrfs.c b/btrfs.c
index f1a8806..ab5e57f 100644 (file)
--- a/btrfs.c
+++ b/btrfs.c
@@ -19,6 +19,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "kerncompat.h"
 #include "btrfs_cmds.h"
 #include "version.h"
 
@@ -60,6 +61,9 @@ static struct Command commands[] = {
        { do_subvol_list, 1, "subvolume list", "<path>\n"
                "List the snapshot/subvolume of a filesystem."
        },
+       { do_find_newer, 2, "subvolume find-new", "<path> <last_gen>\n"
+               "List the recently modified files in a filesystem."
+       },
        { do_defrag, -1,
          "filesystem defragment", "[-vcf] [-s start] [-l len] [-t size] <file>|<dir> [<file>|<dir>...]\n"
                "Defragment a file or a directory."
index 05134fd..8031c58 100644 (file)
@@ -247,6 +247,37 @@ int do_defrag(int ac, char **av)
        return errors + 20;
 }
 
+int do_find_newer(int argc, char **argv)
+{
+       int fd;
+       int ret;
+       char *subvol;
+       u64 last_gen;
+
+       subvol = argv[1];
+       last_gen = atoll(argv[2]);
+
+       ret = test_issubvolume(subvol);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: error accessing '%s'\n", subvol);
+               return 12;
+       }
+       if (!ret) {
+               fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol);
+               return 13;
+       }
+
+       fd = open_file_or_dir(subvol);
+       if (fd < 0) {
+               fprintf(stderr, "ERROR: can't access '%s'\n", subvol);
+               return 12;
+       }
+       ret = find_updated_files(fd, 0, last_gen);
+       if (ret)
+               return 19;
+       return 0;
+}
+
 int do_subvol_list(int argc, char **argv)
 {
        int fd;
index e8abd99..7bde191 100644 (file)
@@ -30,3 +30,5 @@ int do_subvol_list(int nargs, char **argv);
 int do_set_default_subvol(int nargs, char **argv);
 int list_subvols(int fd);
 int do_df_filesystem(int nargs, char **argv);
+int find_updated_files(int fd, u64 root_id, u64 oldest_gen);
+int do_find_newer(int argc, char **argv);
diff --git a/ctree.h b/ctree.h
index 8c764ce..64ecf12 100644 (file)
--- a/ctree.h
+++ b/ctree.h
@@ -1047,6 +1047,7 @@ BTRFS_SETGET_STACK_FUNCS(block_group_flags,
 
 /* struct btrfs_inode_ref */
 BTRFS_SETGET_FUNCS(inode_ref_name_len, struct btrfs_inode_ref, name_len, 16);
+BTRFS_SETGET_STACK_FUNCS(stack_inode_ref_name_len, struct btrfs_inode_ref, name_len, 16);
 BTRFS_SETGET_FUNCS(inode_ref_index, struct btrfs_inode_ref, index, 64);
 
 /* struct btrfs_inode_item */
@@ -1576,6 +1577,7 @@ static inline unsigned long btrfs_leaf_data(struct extent_buffer *l)
 
 /* struct btrfs_file_extent_item */
 BTRFS_SETGET_FUNCS(file_extent_type, struct btrfs_file_extent_item, type, 8);
+BTRFS_SETGET_STACK_FUNCS(stack_file_extent_type, struct btrfs_file_extent_item, type, 8);
 
 static inline unsigned long btrfs_file_extent_inline_start(struct
                                                   btrfs_file_extent_item *e)
@@ -1592,18 +1594,30 @@ static inline u32 btrfs_file_extent_calc_inline_size(u32 datasize)
 
 BTRFS_SETGET_FUNCS(file_extent_disk_bytenr, struct btrfs_file_extent_item,
                   disk_bytenr, 64);
+BTRFS_SETGET_STACK_FUNCS(stack_file_extent_disk_bytenr, struct btrfs_file_extent_item,
+                  disk_bytenr, 64);
 BTRFS_SETGET_FUNCS(file_extent_generation, struct btrfs_file_extent_item,
                   generation, 64);
+BTRFS_SETGET_STACK_FUNCS(stack_file_extent_generation, struct btrfs_file_extent_item,
+                  generation, 64);
 BTRFS_SETGET_FUNCS(file_extent_disk_num_bytes, struct btrfs_file_extent_item,
                   disk_num_bytes, 64);
 BTRFS_SETGET_FUNCS(file_extent_offset, struct btrfs_file_extent_item,
                  offset, 64);
+BTRFS_SETGET_STACK_FUNCS(stack_file_extent_offset, struct btrfs_file_extent_item,
+                 offset, 64);
 BTRFS_SETGET_FUNCS(file_extent_num_bytes, struct btrfs_file_extent_item,
                   num_bytes, 64);
+BTRFS_SETGET_STACK_FUNCS(stack_file_extent_num_bytes, struct btrfs_file_extent_item,
+                  num_bytes, 64);
 BTRFS_SETGET_FUNCS(file_extent_ram_bytes, struct btrfs_file_extent_item,
                   ram_bytes, 64);
+BTRFS_SETGET_STACK_FUNCS(stack_file_extent_ram_bytes, struct btrfs_file_extent_item,
+                  ram_bytes, 64);
 BTRFS_SETGET_FUNCS(file_extent_compression, struct btrfs_file_extent_item,
                   compression, 8);
+BTRFS_SETGET_STACK_FUNCS(stack_file_extent_compression, struct btrfs_file_extent_item,
+                  compression, 8);
 BTRFS_SETGET_FUNCS(file_extent_encryption, struct btrfs_file_extent_item,
                   encryption, 8);
 BTRFS_SETGET_FUNCS(file_extent_other_encoding, struct btrfs_file_extent_item,
diff --git a/ioctl.h b/ioctl.h
index 0859cae..776d7a9 100644 (file)
--- a/ioctl.h
+++ b/ioctl.h
@@ -72,7 +72,7 @@ struct btrfs_ioctl_search_header {
        __u64 offset;
        __u32 type;
        __u32 len;
-};
+} __attribute__((may_alias));
 
 #define BTRFS_SEARCH_ARGS_BUFSIZE (4096 - sizeof(struct btrfs_ioctl_search_key))
 /*