Skip to content

Instantly share code, notes, and snippets.

@saiarcot895
Created October 30, 2021 17:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saiarcot895/41d04eae0dffd19b667f4e127188f908 to your computer and use it in GitHub Desktop.
Save saiarcot895/41d04eae0dffd19b667f4e127188f908 to your computer and use it in GitHub Desktop.
Getting list of nested subvolumes easily
#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