diff --git a/btrfs-list.c b/btrfs-list.c index f2f119be..77417059 100644 --- a/btrfs-list.c +++ b/btrfs-list.c @@ -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); + 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); - 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); + 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 f1a8806c..ab5e57fe 100644 --- a/btrfs.c +++ b/btrfs.c @@ -19,6 +19,7 @@ #include #include +#include "kerncompat.h" #include "btrfs_cmds.h" #include "version.h" @@ -60,6 +61,9 @@ static struct Command commands[] = { { do_subvol_list, 1, "subvolume list", "\n" "List the snapshot/subvolume of a filesystem." }, + { do_find_newer, 2, "subvolume find-new", " \n" + "List the recently modified files in a filesystem." + }, { do_defrag, -1, "filesystem defragment", "[-vcf] [-s start] [-l len] [-t size] | [|...]\n" "Defragment a file or a directory." diff --git a/btrfs_cmds.c b/btrfs_cmds.c index 05134fd6..8031c589 100644 --- a/btrfs_cmds.c +++ b/btrfs_cmds.c @@ -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; diff --git a/btrfs_cmds.h b/btrfs_cmds.h index e8abd995..7bde1910 100644 --- a/btrfs_cmds.h +++ b/btrfs_cmds.h @@ -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 8c764cea..64ecf12b 100644 --- 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 0859caed..776d7a9f 100644 --- 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)) /*