--- /dev/null
+#ifndef CRUCIBLE_BTRFS_TREE_H
+#define CRUCIBLE_BTRFS_TREE_H
+
+#include "crucible/fd.h"
+#include "crucible/fs.h"
+#include "crucible/bytevector.h"
+
+namespace crucible {
+ using namespace std;
+
+ class BtrfsTreeItem {
+ uint64_t m_objectid = 0;
+ uint64_t m_offset = 0;
+ uint64_t m_transid = 0;
+ ByteVector m_data;
+ uint8_t m_type = 0;
+ public:
+ uint64_t objectid() const { return m_objectid; }
+ uint64_t offset() const { return m_offset; }
+ uint64_t transid() const { return m_transid; }
+ uint8_t type() const { return m_type; }
+ const ByteVector data() const { return m_data; }
+ BtrfsTreeItem() = default;
+ BtrfsTreeItem(const BtrfsIoctlSearchHeader &bish);
+ BtrfsTreeItem& operator=(const BtrfsIoctlSearchHeader &bish);
+ bool operator!() const;
+
+ /// Member access methods. Invoking a method on the
+ /// wrong type of item will throw an exception.
+
+ /// @{ Block group items
+ uint64_t block_group_flags() const;
+ uint64_t block_group_used() const;
+ /// @}
+
+ /// @{ Dev extent items (physical byte ranges)
+ uint64_t dev_extent_chunk_offset() const;
+ uint64_t dev_extent_length() const;
+ /// @}
+
+ /// @{ Dev items (devices)
+ uint64_t dev_item_total_bytes() const;
+ uint64_t dev_item_bytes_used() const;
+ /// @}
+
+ /// @{ Inode items
+ uint64_t inode_size() const;
+ /// @}
+
+ /// @{ Extent refs (EXTENT_DATA)
+ uint64_t file_extent_logical_bytes() const;
+ uint64_t file_extent_generation() const;
+ uint64_t file_extent_offset() const;
+ uint64_t file_extent_bytenr() const;
+ uint8_t file_extent_type() const;
+ btrfs_compression_type file_extent_compression() const;
+ /// @}
+
+ /// @{ Extent items (EXTENT_ITEM)
+ uint64_t extent_begin() const;
+ uint64_t extent_end() const;
+ uint64_t extent_generation() const;
+ /// @}
+
+ /// @{ Root items
+ uint64_t root_flags() const;
+ /// @}
+
+ /// @{ Root backref items.
+ uint64_t root_ref_dirid() const;
+ string root_ref_name() const;
+ uint64_t root_ref_parent_rootid() const;
+ /// @}
+ };
+
+ ostream &operator<<(ostream &os, const BtrfsTreeItem &bti);
+
+ class BtrfsTreeFetcher {
+ protected:
+ Fd m_fd;
+ BtrfsIoctlSearchKey m_sk;
+ uint64_t m_tree = 0;
+ uint64_t m_min_transid = 0;
+ uint64_t m_max_transid = numeric_limits<uint64_t>::max();
+ uint64_t m_block_size = 0;
+ uint64_t m_lookbehind_size = 0;
+ uint64_t m_scale_size = 0;
+ uint8_t m_type = 0;
+
+ uint64_t scale_logical(uint64_t logical) const;
+ uint64_t unscale_logical(uint64_t logical) const;
+ const static uint64_t s_max_logical = numeric_limits<uint64_t>::max();
+ uint64_t scaled_max_logical() const;
+
+ virtual void fill_sk(BtrfsIoctlSearchKey &key, uint64_t object);
+ virtual void next_sk(BtrfsIoctlSearchKey &key, const BtrfsIoctlSearchHeader &hdr);
+ virtual uint64_t hdr_logical(const BtrfsIoctlSearchHeader &hdr) = 0;
+ virtual bool hdr_match(const BtrfsIoctlSearchHeader &hdr) = 0;
+ virtual bool hdr_stop(const BtrfsIoctlSearchHeader &hdr) = 0;
+ Fd fd() const;
+ void fd(Fd fd);
+ public:
+ virtual ~BtrfsTreeFetcher() = default;
+ BtrfsTreeFetcher(Fd new_fd);
+ void type(uint8_t type);
+ void tree(uint64_t tree);
+ void transid(uint64_t min_transid, uint64_t max_transid = numeric_limits<uint64_t>::max());
+ /// Block size (sectorsize) of filesystem
+ uint64_t block_size() const;
+ /// Fetch last object < logical, null if not found
+ BtrfsTreeItem prev(uint64_t logical);
+ /// Fetch first object > logical, null if not found
+ BtrfsTreeItem next(uint64_t logical);
+ /// Fetch object at exactly logical, null if not found
+ BtrfsTreeItem at(uint64_t);
+ /// Fetch first object >= logical
+ BtrfsTreeItem lower_bound(uint64_t logical);
+ /// Fetch last object <= logical
+ BtrfsTreeItem rlower_bound(uint64_t logical);
+
+ /// Estimated distance between objects
+ virtual uint64_t lookbehind_size() const;
+ virtual void lookbehind_size(uint64_t);
+
+ /// Scale size (normally block size but must be set to 1 for fs trees)
+ uint64_t scale_size() const;
+ void scale_size(uint64_t);
+ };
+
+ class BtrfsTreeObjectFetcher : public BtrfsTreeFetcher {
+ protected:
+ virtual void fill_sk(BtrfsIoctlSearchKey &key, uint64_t logical) override;
+ virtual uint64_t hdr_logical(const BtrfsIoctlSearchHeader &hdr) override;
+ virtual bool hdr_match(const BtrfsIoctlSearchHeader &hdr) override;
+ virtual bool hdr_stop(const BtrfsIoctlSearchHeader &hdr) override;
+ public:
+ using BtrfsTreeFetcher::BtrfsTreeFetcher;
+ };
+
+ class BtrfsTreeOffsetFetcher : public BtrfsTreeFetcher {
+ protected:
+ uint64_t m_objectid = 0;
+ virtual void fill_sk(BtrfsIoctlSearchKey &key, uint64_t offset) override;
+ virtual uint64_t hdr_logical(const BtrfsIoctlSearchHeader &hdr) override;
+ virtual bool hdr_match(const BtrfsIoctlSearchHeader &hdr) override;
+ virtual bool hdr_stop(const BtrfsIoctlSearchHeader &hdr) override;
+ public:
+ using BtrfsTreeFetcher::BtrfsTreeFetcher;
+ void objectid(uint64_t objectid);
+ uint64_t objectid() const;
+ };
+
+ class BtrfsCsumTreeFetcher : public BtrfsTreeOffsetFetcher {
+ public:
+ const uint32_t BTRFS_CSUM_TYPE_UNKNOWN = uint32_t(1) << 16;
+ private:
+ size_t m_sum_size = 0;
+ uint32_t m_sum_type = BTRFS_CSUM_TYPE_UNKNOWN;
+ public:
+ BtrfsCsumTreeFetcher(const Fd &fd);
+
+ uint32_t sum_type() const;
+ size_t sum_size() const;
+ void get_sums(uint64_t logical, size_t count, function<void(uint64_t logical, const uint8_t *buf, size_t count)> output);
+ };
+
+ /// Fetch extent items from extent tree
+ class BtrfsExtentItemFetcher : public BtrfsTreeObjectFetcher {
+ public:
+ BtrfsExtentItemFetcher(const Fd &fd);
+ };
+
+ /// Fetch extent refs from an inode
+ class BtrfsExtentDataFetcher : public BtrfsTreeOffsetFetcher {
+ public:
+ BtrfsExtentDataFetcher(const Fd &fd);
+ };
+
+ /// Fetch inodes from a subvol
+ class BtrfsFsTreeFetcher : public BtrfsTreeObjectFetcher {
+ public:
+ BtrfsFsTreeFetcher(const Fd &fd, uint64_t subvol);
+ };
+
+ class BtrfsInodeFetcher : public BtrfsTreeObjectFetcher {
+ public:
+ BtrfsInodeFetcher(const Fd &fd);
+ BtrfsTreeItem stat(uint64_t subvol, uint64_t inode);
+ };
+
+ class BtrfsRootFetcher : public BtrfsTreeObjectFetcher {
+ public:
+ BtrfsRootFetcher(const Fd &fd);
+ BtrfsTreeItem root(uint64_t subvol);
+ };
+
+}
+
+#endif
--- /dev/null
+#include "crucible/btrfs-tree.h"
+#include "crucible/btrfs.h"
+#include "crucible/error.h"
+#include "crucible/fs.h"
+#include "crucible/hexdump.h"
+#include "crucible/seeker.h"
+
+namespace crucible {
+ using namespace std;
+
+ uint64_t
+ BtrfsTreeItem::extent_begin() const
+ {
+ THROW_CHECK1(invalid_argument, btrfs_search_type_ntoa(m_type), m_type == BTRFS_EXTENT_ITEM_KEY);
+ return m_objectid;
+ }
+
+ uint64_t
+ BtrfsTreeItem::extent_end() const
+ {
+ THROW_CHECK1(invalid_argument, btrfs_search_type_ntoa(m_type), m_type == BTRFS_EXTENT_ITEM_KEY);
+ return m_objectid + m_offset;
+ }
+
+ uint64_t
+ BtrfsTreeItem::extent_generation() const
+ {
+ THROW_CHECK1(invalid_argument, btrfs_search_type_ntoa(m_type), m_type == BTRFS_EXTENT_ITEM_KEY);
+ return btrfs_get_member(&btrfs_extent_item::generation, m_data);
+ }
+
+ uint64_t
+ BtrfsTreeItem::root_ref_dirid() const
+ {
+ THROW_CHECK1(invalid_argument, btrfs_search_type_ntoa(m_type), m_type == BTRFS_ROOT_BACKREF_KEY);
+ return btrfs_get_member(&btrfs_root_ref::dirid, m_data);
+ }
+
+ string
+ BtrfsTreeItem::root_ref_name() const
+ {
+ THROW_CHECK1(invalid_argument, btrfs_search_type_ntoa(m_type), m_type == BTRFS_ROOT_BACKREF_KEY);
+ const auto name_len = btrfs_get_member(&btrfs_root_ref::name_len, m_data);
+ const auto name_start = sizeof(struct btrfs_root_ref);
+ const auto name_end = name_len + name_start;
+ THROW_CHECK2(runtime_error, m_data.size(), name_end, m_data.size() >= name_end);
+ return string(m_data.data() + name_start, m_data.data() + name_end);
+ }
+
+ uint64_t
+ BtrfsTreeItem::root_ref_parent_rootid() const
+ {
+ THROW_CHECK1(invalid_argument, btrfs_search_type_ntoa(m_type), m_type == BTRFS_ROOT_BACKREF_KEY);
+ return offset();
+ }
+
+ uint64_t
+ BtrfsTreeItem::root_flags() const
+ {
+ THROW_CHECK1(invalid_argument, btrfs_search_type_ntoa(m_type), m_type == BTRFS_ROOT_ITEM_KEY);
+ return btrfs_get_member(&btrfs_root_item::flags, m_data);
+ }
+
+ ostream &
+ operator<<(ostream &os, const BtrfsTreeItem &bti)
+ {
+ os << "BtrfsTreeItem {"
+ << " objectid = " << to_hex(bti.objectid())
+ << ", type = " << btrfs_search_type_ntoa(bti.type())
+ << ", offset = " << to_hex(bti.offset())
+ << ", transid = " << bti.transid()
+ << ", data = ";
+ hexdump(os, bti.data());
+ return os;
+ }
+
+ uint64_t
+ BtrfsTreeItem::block_group_flags() const
+ {
+ THROW_CHECK1(invalid_argument, m_type, m_type == BTRFS_BLOCK_GROUP_ITEM_KEY);
+ return btrfs_get_member(&btrfs_block_group_item::flags, m_data);
+ }
+
+ uint64_t
+ BtrfsTreeItem::block_group_used() const
+ {
+ THROW_CHECK1(invalid_argument, m_type, m_type == BTRFS_BLOCK_GROUP_ITEM_KEY);
+ return btrfs_get_member(&btrfs_block_group_item::used, m_data);
+ }
+
+ uint64_t
+ BtrfsTreeItem::dev_extent_chunk_offset() const
+ {
+ THROW_CHECK1(invalid_argument, m_type, m_type == BTRFS_DEV_EXTENT_KEY);
+ return btrfs_get_member(&btrfs_dev_extent::chunk_offset, m_data);
+ }
+
+ uint64_t
+ BtrfsTreeItem::dev_extent_length() const
+ {
+ THROW_CHECK1(invalid_argument, m_type, m_type == BTRFS_DEV_EXTENT_KEY);
+ return btrfs_get_member(&btrfs_dev_extent::length, m_data);
+ }
+
+ uint64_t
+ BtrfsTreeItem::dev_item_total_bytes() const
+ {
+ THROW_CHECK1(invalid_argument, m_type, m_type == BTRFS_DEV_ITEM_KEY);
+ return btrfs_get_member(&btrfs_dev_item::total_bytes, m_data);
+ }
+
+ uint64_t
+ BtrfsTreeItem::dev_item_bytes_used() const
+ {
+ THROW_CHECK1(invalid_argument, m_type, m_type == BTRFS_DEV_ITEM_KEY);
+ return btrfs_get_member(&btrfs_dev_item::bytes_used, m_data);
+ }
+
+ uint64_t
+
+ BtrfsTreeItem::inode_size() const
+ {
+ THROW_CHECK1(invalid_argument, m_type, m_type == BTRFS_INODE_ITEM_KEY);
+ return btrfs_get_member(&btrfs_inode_item::size, m_data);
+ }
+
+ uint64_t
+ BtrfsTreeItem::file_extent_logical_bytes() const
+ {
+ THROW_CHECK1(invalid_argument, m_type, m_type == BTRFS_EXTENT_DATA_KEY);
+ const auto file_extent_item_type = btrfs_get_member(&btrfs_file_extent_item::type, m_data);
+ switch (file_extent_item_type) {
+ case BTRFS_FILE_EXTENT_INLINE:
+ return btrfs_get_member(&btrfs_file_extent_item::ram_bytes, m_data);
+ case BTRFS_FILE_EXTENT_PREALLOC:
+ case BTRFS_FILE_EXTENT_REG:
+ return btrfs_get_member(&btrfs_file_extent_item::num_bytes, m_data);
+ default:
+ THROW_ERROR(runtime_error, "unknown btrfs_file_extent_item type " << file_extent_item_type);
+ }
+ }
+
+ uint64_t
+ BtrfsTreeItem::file_extent_offset() const
+ {
+ THROW_CHECK1(invalid_argument, m_type, m_type == BTRFS_EXTENT_DATA_KEY);
+ const auto file_extent_item_type = btrfs_get_member(&btrfs_file_extent_item::type, m_data);
+ switch (file_extent_item_type) {
+ case BTRFS_FILE_EXTENT_INLINE:
+ THROW_ERROR(invalid_argument, "extent is inline " << *this);
+ case BTRFS_FILE_EXTENT_PREALLOC:
+ case BTRFS_FILE_EXTENT_REG:
+ return btrfs_get_member(&btrfs_file_extent_item::offset, m_data);
+ default:
+ THROW_ERROR(runtime_error, "unknown btrfs_file_extent_item type " << file_extent_item_type << " in " << *this);
+ }
+ }
+
+ uint64_t
+ BtrfsTreeItem::file_extent_generation() const
+ {
+ THROW_CHECK1(invalid_argument, m_type, m_type == BTRFS_EXTENT_DATA_KEY);
+ return btrfs_get_member(&btrfs_file_extent_item::generation, m_data);
+ }
+
+ uint64_t
+ BtrfsTreeItem::file_extent_bytenr() const
+ {
+ THROW_CHECK1(invalid_argument, m_type, m_type == BTRFS_EXTENT_DATA_KEY);
+ auto file_extent_item_type = btrfs_get_member(&btrfs_file_extent_item::type, m_data);
+ switch (file_extent_item_type) {
+ case BTRFS_FILE_EXTENT_INLINE:
+ THROW_ERROR(invalid_argument, "extent is inline " << *this);
+ case BTRFS_FILE_EXTENT_PREALLOC:
+ case BTRFS_FILE_EXTENT_REG:
+ return btrfs_get_member(&btrfs_file_extent_item::disk_bytenr, m_data);
+ default:
+ THROW_ERROR(runtime_error, "unknown btrfs_file_extent_item type " << file_extent_item_type << " in " << *this);
+ }
+ }
+
+ uint8_t
+ BtrfsTreeItem::file_extent_type() const
+ {
+ THROW_CHECK1(invalid_argument, m_type, m_type == BTRFS_EXTENT_DATA_KEY);
+ return btrfs_get_member(&btrfs_file_extent_item::type, m_data);
+ }
+
+ btrfs_compression_type
+ BtrfsTreeItem::file_extent_compression() const
+ {
+ THROW_CHECK1(invalid_argument, m_type, m_type == BTRFS_EXTENT_DATA_KEY);
+ return static_cast<btrfs_compression_type>(btrfs_get_member(&btrfs_file_extent_item::compression, m_data));
+ }
+
+ BtrfsTreeItem::BtrfsTreeItem(const BtrfsIoctlSearchHeader &bish) :
+ m_objectid(bish.objectid),
+ m_offset(bish.offset),
+ m_transid(bish.transid),
+ m_data(bish.m_data),
+ m_type(bish.type)
+ {
+ }
+
+ BtrfsTreeItem &
+ BtrfsTreeItem::operator=(const BtrfsIoctlSearchHeader &bish)
+ {
+ m_objectid = bish.objectid;
+ m_offset = bish.offset;
+ m_transid = bish.transid;
+ m_data = bish.m_data;
+ m_type = bish.type;
+ return *this;
+ }
+
+ bool
+ BtrfsTreeItem::operator!() const
+ {
+ return m_transid == 0 && m_objectid == 0 && m_offset == 0 && m_type == 0;
+ }
+
+ uint64_t
+ BtrfsTreeFetcher::block_size() const
+ {
+ return m_block_size;
+ }
+
+ BtrfsTreeFetcher::BtrfsTreeFetcher(Fd new_fd) :
+ m_fd(new_fd)
+ {
+ BtrfsIoctlFsInfoArgs bifia;
+ bifia.do_ioctl(fd());
+ m_block_size = bifia.sectorsize;
+ THROW_CHECK1(runtime_error, m_block_size, m_block_size > 0);
+ // We don't believe sector sizes that aren't multiples of 4K
+ THROW_CHECK1(runtime_error, m_block_size, (m_block_size % 4096) == 0);
+ m_lookbehind_size = 128 * 1024;
+ m_scale_size = m_block_size;
+ }
+
+ Fd
+ BtrfsTreeFetcher::fd() const
+ {
+ return m_fd;
+ }
+
+ void
+ BtrfsTreeFetcher::fd(Fd fd)
+ {
+ m_fd = fd;
+ }
+
+ void
+ BtrfsTreeFetcher::type(uint8_t type)
+ {
+ m_type = type;
+ }
+
+ void
+ BtrfsTreeFetcher::tree(uint64_t tree)
+ {
+ m_tree = tree;
+ }
+
+ void
+ BtrfsTreeFetcher::transid(uint64_t min_transid, uint64_t max_transid)
+ {
+ m_min_transid = min_transid;
+ m_max_transid = max_transid;
+ }
+
+ uint64_t
+ BtrfsTreeFetcher::lookbehind_size() const
+ {
+ return m_lookbehind_size;
+ }
+
+ void
+ BtrfsTreeFetcher::lookbehind_size(uint64_t lookbehind_size)
+ {
+ m_lookbehind_size = lookbehind_size;
+ }
+
+ uint64_t
+ BtrfsTreeFetcher::scale_size() const
+ {
+ return m_scale_size;
+ }
+
+ void
+ BtrfsTreeFetcher::scale_size(uint64_t scale_size)
+ {
+ m_scale_size = scale_size;
+ }
+
+ void
+ BtrfsTreeFetcher::fill_sk(BtrfsIoctlSearchKey &sk, uint64_t object)
+ {
+ (void)object;
+ // btrfs allows tree ID 0 meaning the current tree, but we do not.
+ THROW_CHECK0(invalid_argument, m_tree != 0);
+ sk.tree_id = m_tree;
+ sk.min_type = m_type;
+ sk.max_type = m_type;
+ sk.min_transid = m_min_transid;
+ sk.max_transid = m_max_transid;
+ sk.nr_items = 1;
+ }
+
+ void
+ BtrfsTreeFetcher::next_sk(BtrfsIoctlSearchKey &key, const BtrfsIoctlSearchHeader &hdr)
+ {
+ key.next_min(hdr, m_type);
+ }
+
+ BtrfsTreeItem
+ BtrfsTreeFetcher::at(uint64_t logical)
+ {
+ BtrfsIoctlSearchKey &sk = m_sk;
+ fill_sk(sk, logical);
+ // Exact match, should return 0 or 1 items
+ sk.max_type = sk.min_type;
+ sk.nr_items = 1;
+ sk.do_ioctl(fd());
+ THROW_CHECK1(runtime_error, sk.m_result.size(), sk.m_result.size() < 2);
+ for (const auto &i : sk.m_result) {
+ if (hdr_logical(i) == logical && hdr_match(i)) {
+ return i;
+ }
+ }
+ return BtrfsTreeItem();
+ }
+
+ uint64_t
+ BtrfsTreeFetcher::scale_logical(const uint64_t logical) const
+ {
+ THROW_CHECK1(invalid_argument, logical, (logical % m_scale_size) == 0 || logical == s_max_logical);
+ return logical / m_scale_size;
+ }
+
+ uint64_t
+ BtrfsTreeFetcher::scaled_max_logical() const
+ {
+ return scale_logical(s_max_logical);
+ }
+
+ uint64_t
+ BtrfsTreeFetcher::unscale_logical(const uint64_t logical) const
+ {
+ THROW_CHECK1(invalid_argument, logical, logical <= scaled_max_logical());
+ if (logical == scaled_max_logical()) {
+ return s_max_logical;
+ }
+ return logical * scale_size();
+ }
+
+ BtrfsTreeItem
+ BtrfsTreeFetcher::rlower_bound(uint64_t logical)
+ {
+ #if 0
+ #define BTFRLB_DEBUG(x) do { cerr << x; } while (false)
+ #else
+ #define BTFRLB_DEBUG(x) do { } while (false)
+ #endif
+ BtrfsTreeItem closest_item;
+ uint64_t closest_logical = 0;
+ BtrfsIoctlSearchKey &sk = m_sk;
+ size_t loops = 0;
+ BTFRLB_DEBUG("rlower_bound: " << to_hex(logical) << endl);
+ seek_backward(scale_logical(logical), [&](uint64_t lower_bound, uint64_t upper_bound) {
+ ++loops;
+ fill_sk(sk, unscale_logical(min(scaled_max_logical(), lower_bound)));
+ set<uint64_t> rv;
+ do {
+ sk.nr_items = 4;
+ sk.do_ioctl(fd());
+ BTFRLB_DEBUG("fetch: loop " << loops << " lower_bound..upper_bound " << to_hex(lower_bound) << ".." << to_hex(upper_bound));
+ for (auto &i : sk.m_result) {
+ next_sk(sk, i);
+ const auto this_logical = hdr_logical(i);
+ const auto scaled_hdr_logical = scale_logical(this_logical);
+ BTFRLB_DEBUG(" " << to_hex(scaled_hdr_logical));
+ if (hdr_match(i)) {
+ if (this_logical <= logical && this_logical > closest_logical) {
+ closest_logical = this_logical;
+ closest_item = i;
+ }
+ BTFRLB_DEBUG("(match)");
+ rv.insert(scaled_hdr_logical);
+ }
+ if (scaled_hdr_logical > upper_bound || hdr_stop(i)) {
+ if (scaled_hdr_logical >= upper_bound) {
+ BTFRLB_DEBUG("(" << to_hex(scaled_hdr_logical) << " >= " << to_hex(upper_bound) << ")");
+ }
+ if (hdr_stop(i)) {
+ rv.insert(numeric_limits<uint64_t>::max());
+ BTFRLB_DEBUG("(stop)");
+ }
+ break;
+ } else {
+ BTFRLB_DEBUG("(cont'd)");
+ }
+ }
+ BTFRLB_DEBUG(endl);
+ // We might get a search result that contains only non-matching items.
+ // Keep looping until we find any matching item or we run out of tree.
+ } while (rv.empty() && !sk.m_result.empty());
+ return rv;
+ }, scale_logical(lookbehind_size()));
+ return closest_item;
+ #undef BTFRLB_DEBUG
+ }
+
+ BtrfsTreeItem
+ BtrfsTreeFetcher::lower_bound(uint64_t logical)
+ {
+ BtrfsIoctlSearchKey &sk = m_sk;
+ fill_sk(sk, logical);
+ do {
+ assert(sk.max_offset == s_max_logical);
+ sk.do_ioctl(fd());
+ for (auto &i : sk.m_result) {
+ if (hdr_match(i)) {
+ return i;
+ }
+ if (hdr_stop(i)) {
+ return BtrfsTreeItem();
+ }
+ next_sk(sk, i);
+ }
+ } while (!sk.m_result.empty());
+ return BtrfsTreeItem();
+ }
+
+ BtrfsTreeItem
+ BtrfsTreeFetcher::next(uint64_t logical)
+ {
+ const auto scaled_logical = scale_logical(logical);
+ if (scaled_logical + 1 > scaled_max_logical()) {
+ return BtrfsTreeItem();
+ }
+ return lower_bound(unscale_logical(scaled_logical + 1));
+ }
+
+ BtrfsTreeItem
+ BtrfsTreeFetcher::prev(uint64_t logical)
+ {
+ const auto scaled_logical = scale_logical(logical);
+ if (scaled_logical < 1) {
+ return BtrfsTreeItem();
+ }
+ return rlower_bound(unscale_logical(scaled_logical - 1));
+ }
+
+ void
+ BtrfsTreeObjectFetcher::fill_sk(BtrfsIoctlSearchKey &sk, uint64_t object)
+ {
+ BtrfsTreeFetcher::fill_sk(sk, object);
+ sk.min_offset = 0;
+ sk.max_offset = numeric_limits<decltype(sk.max_offset)>::max();
+ sk.min_objectid = object;
+ sk.max_objectid = numeric_limits<decltype(sk.max_objectid)>::max();
+ }
+
+ uint64_t
+ BtrfsTreeObjectFetcher::hdr_logical(const BtrfsIoctlSearchHeader &hdr)
+ {
+ return hdr.objectid;
+ }
+
+ bool
+ BtrfsTreeObjectFetcher::hdr_match(const BtrfsIoctlSearchHeader &hdr)
+ {
+ // If you're calling this method without overriding it, you should have set type first
+ assert(m_type);
+ return hdr.type == m_type;
+ }
+
+ bool
+ BtrfsTreeObjectFetcher::hdr_stop(const BtrfsIoctlSearchHeader &hdr)
+ {
+ return false;
+ (void)hdr;
+ }
+
+ uint64_t
+ BtrfsTreeOffsetFetcher::hdr_logical(const BtrfsIoctlSearchHeader &hdr)
+ {
+ return hdr.offset;
+ }
+
+ bool
+ BtrfsTreeOffsetFetcher::hdr_match(const BtrfsIoctlSearchHeader &hdr)
+ {
+ assert(m_type);
+ return hdr.type == m_type && hdr.objectid == m_objectid;
+ }
+
+ bool
+ BtrfsTreeOffsetFetcher::hdr_stop(const BtrfsIoctlSearchHeader &hdr)
+ {
+ assert(m_type);
+ return hdr.objectid > m_objectid || hdr.type > m_type;
+ }
+
+ void
+ BtrfsTreeOffsetFetcher::objectid(uint64_t objectid)
+ {
+ m_objectid = objectid;
+ }
+
+ uint64_t
+ BtrfsTreeOffsetFetcher::objectid() const
+ {
+ return m_objectid;
+ }
+
+ void
+ BtrfsTreeOffsetFetcher::fill_sk(BtrfsIoctlSearchKey &sk, uint64_t offset)
+ {
+ BtrfsTreeFetcher::fill_sk(sk, offset);
+ sk.min_offset = offset;
+ sk.max_offset = numeric_limits<decltype(sk.max_offset)>::max();
+ sk.min_objectid = m_objectid;
+ sk.max_objectid = m_objectid;
+ }
+
+ void
+ BtrfsCsumTreeFetcher::get_sums(uint64_t const logical, size_t count, function<void(uint64_t logical, const uint8_t *buf, size_t bytes)> output)
+ {
+ #if 0
+ #define BCTFGS_DEBUG(x) do { cerr << x; } while (false)
+ #else
+ #define BCTFGS_DEBUG(x) do { } while (false)
+ #endif
+ const uint64_t logical_end = logical + count * block_size();
+ BtrfsTreeItem bti = rlower_bound(logical);
+ size_t loops = 0;
+ BCTFGS_DEBUG("get_sums " << to_hex(logical) << ".." << to_hex(logical_end) << endl);
+ while (!!bti) {
+ BCTFGS_DEBUG("get_sums[" << loops << "]: " << bti << endl);
+ ++loops;
+ // Reject wrong type or objectid
+ THROW_CHECK1(runtime_error, bti.type(), bti.type() == BTRFS_EXTENT_CSUM_KEY);
+ THROW_CHECK1(runtime_error, bti.objectid(), bti.objectid() == BTRFS_EXTENT_CSUM_OBJECTID);
+ // Is this object in range?
+ const uint64_t data_logical = bti.offset();
+ if (data_logical >= logical_end) {
+ // csum object is past end of range, we are done
+ return;
+ }
+ // Figure out how long this csum item is in various units
+ const size_t csum_byte_count = bti.data().size();
+ THROW_CHECK1(runtime_error, csum_byte_count, (csum_byte_count % m_sum_size) == 0);
+ THROW_CHECK1(runtime_error, csum_byte_count, csum_byte_count > 0);
+ const size_t csum_count = csum_byte_count / m_sum_size;
+ const uint64_t data_byte_count = csum_count * block_size();
+ const uint64_t data_logical_end = data_logical + data_byte_count;
+ if (data_logical_end <= logical) {
+ // too low, look at next item
+ bti = lower_bound(logical);
+ continue;
+ }
+ // There is some overlap?
+ const uint64_t overlap_begin = max(logical, data_logical);
+ const uint64_t overlap_end = min(logical_end, data_logical_end);
+ THROW_CHECK2(runtime_error, overlap_begin, overlap_end, overlap_begin < overlap_end);
+ const uint64_t overlap_offset = overlap_begin - data_logical;
+ THROW_CHECK1(runtime_error, overlap_offset, (overlap_offset % block_size()) == 0);
+ const uint64_t overlap_index = overlap_offset * m_sum_size / block_size();
+ const uint64_t overlap_byte_count = overlap_end - overlap_begin;
+ const uint64_t overlap_csum_byte_count = overlap_byte_count * m_sum_size / block_size();
+ // Can't be bigger than a btrfs item
+ THROW_CHECK1(runtime_error, overlap_index, overlap_index < 65536);
+ THROW_CHECK1(runtime_error, overlap_csum_byte_count, overlap_csum_byte_count < 65536);
+ // Yes, process the overlap
+ output(overlap_begin, bti.data().data() + overlap_index, overlap_csum_byte_count);
+ // Advance
+ bti = lower_bound(overlap_end);
+ }
+ #undef BCTFGS_DEBUG
+ }
+
+ uint32_t
+ BtrfsCsumTreeFetcher::sum_type() const
+ {
+ return m_sum_type;
+ }
+
+ size_t
+ BtrfsCsumTreeFetcher::sum_size() const
+ {
+ return m_sum_size;
+ }
+
+ BtrfsCsumTreeFetcher::BtrfsCsumTreeFetcher(const Fd &new_fd) :
+ BtrfsTreeOffsetFetcher(new_fd)
+ {
+ type(BTRFS_EXTENT_CSUM_KEY);
+ tree(BTRFS_CSUM_TREE_OBJECTID);
+ objectid(BTRFS_EXTENT_CSUM_OBJECTID);
+ BtrfsIoctlFsInfoArgs bifia;
+ bifia.do_ioctl(fd());
+ m_sum_type = static_cast<btrfs_compression_type>(bifia.csum_type());
+ m_sum_size = bifia.csum_size();
+ if (m_sum_type == BTRFS_CSUM_TYPE_CRC32 && m_sum_size == 0) {
+ // Older kernel versions don't fill in this field
+ m_sum_size = 4;
+ }
+ THROW_CHECK1(runtime_error, m_sum_size, m_sum_size > 0);
+ }
+
+ BtrfsExtentItemFetcher::BtrfsExtentItemFetcher(const Fd &new_fd) :
+ BtrfsTreeObjectFetcher(new_fd)
+ {
+ tree(BTRFS_EXTENT_TREE_OBJECTID);
+ type(BTRFS_EXTENT_ITEM_KEY);
+ }
+
+ BtrfsExtentDataFetcher::BtrfsExtentDataFetcher(const Fd &new_fd) :
+ BtrfsTreeOffsetFetcher(new_fd)
+ {
+ type(BTRFS_EXTENT_DATA_KEY);
+ }
+
+ BtrfsFsTreeFetcher::BtrfsFsTreeFetcher(const Fd &new_fd, uint64_t subvol) :
+ BtrfsTreeObjectFetcher(new_fd)
+ {
+ tree(subvol);
+ type(BTRFS_EXTENT_DATA_KEY);
+ scale_size(1);
+ }
+
+ BtrfsInodeFetcher::BtrfsInodeFetcher(const Fd &fd) :
+ BtrfsTreeObjectFetcher(fd)
+ {
+ type(BTRFS_INODE_ITEM_KEY);
+ scale_size(1);
+ }
+
+ BtrfsTreeItem
+ BtrfsInodeFetcher::stat(uint64_t subvol, uint64_t inode)
+ {
+ tree(subvol);
+ const auto item = at(inode);
+ if (!!item) {
+ THROW_CHECK2(runtime_error, item.objectid(), inode, inode == item.objectid());
+ THROW_CHECK2(runtime_error, item.type(), BTRFS_INODE_ITEM_KEY, item.type() == BTRFS_INODE_ITEM_KEY);
+ }
+ return item;
+ }
+
+ BtrfsRootFetcher::BtrfsRootFetcher(const Fd &fd) :
+ BtrfsTreeObjectFetcher(fd)
+ {
+ tree(BTRFS_ROOT_TREE_OBJECTID);
+ type(BTRFS_ROOT_ITEM_KEY);
+ scale_size(1);
+ }
+
+ BtrfsTreeItem
+ BtrfsRootFetcher::root(uint64_t subvol)
+ {
+ const auto item = at(subvol);
+ if (!!item) {
+ THROW_CHECK2(runtime_error, item.objectid(), subvol, subvol == item.objectid());
+ THROW_CHECK2(runtime_error, item.type(), BTRFS_ROOT_ITEM_KEY, item.type() == BTRFS_ROOT_ITEM_KEY);
+ }
+ return item;
+ }
+}