Created
October 30, 2021 17:57
-
-
Save saiarcot895/41d04eae0dffd19b667f4e127188f908 to your computer and use it in GitHub Desktop.
Getting list of nested subvolumes easily
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <linux/btrfs.h> | |
#include <linux/btrfs_tree.h> | |
#include <endian.h> | |
#include <fcntl.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <stdbool.h> | |
#include <stdint.h> | |
#include <stdlib.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <sys/ioctl.h> | |
int printSubvolumes(int fd, uint64_t tree_id, uint64_t subvol_inode, bool print) { | |
// Looking at the root tree object (which hold all of the tree roots), get | |
// all of the subvolumes that are referenced from tree_id. | |
// | |
// Note that here, the v2 search struct and ioctl can be used instead, but | |
// the space for the results needs to be malloc'ed here. | |
struct btrfs_ioctl_search_args search = { | |
.key = { | |
.tree_id = BTRFS_ROOT_TREE_OBJECTID, | |
.min_objectid = tree_id, | |
.min_type = BTRFS_ROOT_REF_KEY, | |
.min_offset = 0, | |
.max_objectid = tree_id, | |
.max_type = BTRFS_ROOT_REF_KEY, | |
.max_offset = UINT64_MAX, | |
.min_transid = 0, | |
.max_transid = UINT64_MAX, | |
.nr_items = 0, | |
}, | |
}; | |
struct btrfs_ioctl_ino_lookup_args inode_lookup; | |
int ret; | |
int buf_off = 0; | |
while (true) { | |
// Get up to 4096 items (there's not actually enough space for 4096 | |
// results, but the kernel will make sure not to return more results | |
// than there is space for) | |
search.key.nr_items = 4096; | |
ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search); | |
if (ret == -1) { | |
perror("BTRFS_IOC_TREE_SEARCH failed"); | |
return 1; | |
} | |
// No more items, break out of this loop | |
if (search.key.nr_items == 0) | |
return 0; | |
for (int i = 0; i < search.key.nr_items; i++) { | |
const struct btrfs_ioctl_search_header *header; | |
const struct btrfs_root_ref *item; | |
header = (struct btrfs_ioctl_search_header *)(search.buf + buf_off); | |
// This should never be true, since we're only requesting | |
// BTRFS_ROOT_REF_KEY in the search, but keep it here anyways | |
if (header->type != BTRFS_ROOT_REF_KEY) { | |
buf_off += sizeof(*header) + header->len; | |
continue; | |
} | |
// The actual item/result is located at the end of the header | |
item = (struct btrfs_root_ref *)(header + 1); | |
uint64_t child_tree_id = le64toh(header->offset); | |
uint64_t parent_tree_id = le64toh(header->objectid); | |
// Get the path to the directory that this subvolume is at | |
memset(&inode_lookup, 0, sizeof(inode_lookup)); | |
inode_lookup.treeid = parent_tree_id; | |
inode_lookup.objectid = item->dirid; | |
ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &inode_lookup); | |
if (ret == -1) { | |
perror("BTRFS_IOC_INO_LOOKUP failed"); | |
return 1; | |
} | |
if (print) { | |
// The name of the subvolume starts at the end of the item struct | |
printf("%llu -> %llu: %s%*.*s\n", parent_tree_id, child_tree_id, inode_lookup.name, item->name_len, item->name_len, (char*)(item+1)); | |
} | |
printSubvolumes(fd, child_tree_id, subvol_inode, child_tree_id == subvol_inode ? true : print); | |
// Shift to the next entry, and update the starting offset for the | |
// next call to search | |
buf_off += sizeof(*header) + header->len; | |
search.key.min_offset = header->offset + 1; | |
} | |
} | |
return 0; | |
} | |
int main(int argc, char** argv) { | |
int opt; | |
bool onlyUnderCurrentPath = false; | |
while ((opt = getopt(argc, argv, "o")) != -1) { | |
switch (opt) { | |
case 'o': onlyUnderCurrentPath = true; break; | |
default: | |
fprintf(stderr, "Usage: %s [-o] [dir...]\n", argv[0]); | |
exit(1); | |
} | |
} | |
int fd; | |
if (optind < argc) { | |
fd = open(argv[optind], O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY); | |
} else { | |
fd = open(".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY); | |
} | |
if (fd == -1) { | |
perror("Unable to open specified directory"); | |
exit(1); | |
} | |
struct btrfs_ioctl_ino_lookup_args inode_lookup; | |
int ret; | |
// Get the subvolume ID of this directory. This is used if we want to print | |
// only subvolumes that are nested within this subvolume. | |
memset(&inode_lookup, 0, sizeof(inode_lookup)); | |
inode_lookup.treeid = 0; | |
inode_lookup.objectid = BTRFS_FIRST_CHUNK_TREE_OBJECTID; | |
if (onlyUnderCurrentPath) { | |
ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &inode_lookup); | |
if (ret == -1) { | |
perror("BTRFS_IOC_INO_LOOKUP failed"); | |
return 1; | |
} | |
} | |
// Start at the root subvolume (which is identified by the special | |
// constant BTRFS_FS_TREE_OBJECTID (5)) | |
printSubvolumes(fd, BTRFS_FS_TREE_OBJECTID, inode_lookup.treeid, !onlyUnderCurrentPath); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment