1use crate::{raw, util::get_uuid};
13use bytes::Buf;
14use std::{fmt, mem};
15use uuid::Uuid;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub enum KeyType {
20 InodeItem,
23 InodeRef,
27 InodeExtref,
31 XattrItem,
35 VerityDescItem,
38 VerityMerkleItem,
41 OrphanItem,
45 DirLogItem,
49 DirLogIndex,
53 DirItem,
57 DirIndex,
61 ExtentData,
65 ExtentCsum,
69 RootItem,
73 RootBackref,
77 RootRef,
81 ExtentItem,
85 MetadataItem,
90 ExtentOwnerRef,
94 TreeBlockRef,
98 ExtentDataRef,
102 SharedBlockRef,
106 SharedDataRef,
110 BlockGroupItem,
114 FreeSpaceInfo,
118 FreeSpaceExtent,
122 FreeSpaceBitmap,
126 DeviceExtent,
130 DeviceItem,
134 ChunkItem,
138 RaidStripe,
142 QgroupStatus,
146 QgroupInfo,
150 QgroupLimit,
153 QgroupRelation,
157 TemporaryItem,
161 PersistentItem,
165 DeviceReplace,
169 UuidKeySubvol,
173 UuidKeyReceivedSubvol,
177 StringItem,
181 Unknown(u8),
183}
184
185impl KeyType {
186 #[must_use]
188 pub fn from_raw(value: u8) -> Self {
189 match u32::from(value) {
190 raw::BTRFS_INODE_ITEM_KEY => Self::InodeItem,
191 raw::BTRFS_INODE_REF_KEY => Self::InodeRef,
192 raw::BTRFS_INODE_EXTREF_KEY => Self::InodeExtref,
193 raw::BTRFS_XATTR_ITEM_KEY => Self::XattrItem,
194 raw::BTRFS_VERITY_DESC_ITEM_KEY => Self::VerityDescItem,
195 raw::BTRFS_VERITY_MERKLE_ITEM_KEY => Self::VerityMerkleItem,
196 raw::BTRFS_ORPHAN_ITEM_KEY => Self::OrphanItem,
197 raw::BTRFS_DIR_LOG_ITEM_KEY => Self::DirLogItem,
198 raw::BTRFS_DIR_LOG_INDEX_KEY => Self::DirLogIndex,
199 raw::BTRFS_DIR_ITEM_KEY => Self::DirItem,
200 raw::BTRFS_DIR_INDEX_KEY => Self::DirIndex,
201 raw::BTRFS_EXTENT_DATA_KEY => Self::ExtentData,
202 raw::BTRFS_EXTENT_CSUM_KEY => Self::ExtentCsum,
203 raw::BTRFS_ROOT_ITEM_KEY => Self::RootItem,
204 raw::BTRFS_ROOT_BACKREF_KEY => Self::RootBackref,
205 raw::BTRFS_ROOT_REF_KEY => Self::RootRef,
206 raw::BTRFS_EXTENT_ITEM_KEY => Self::ExtentItem,
207 raw::BTRFS_METADATA_ITEM_KEY => Self::MetadataItem,
208 raw::BTRFS_EXTENT_OWNER_REF_KEY => Self::ExtentOwnerRef,
209 raw::BTRFS_TREE_BLOCK_REF_KEY => Self::TreeBlockRef,
210 raw::BTRFS_EXTENT_DATA_REF_KEY => Self::ExtentDataRef,
211 raw::BTRFS_SHARED_BLOCK_REF_KEY => Self::SharedBlockRef,
212 raw::BTRFS_SHARED_DATA_REF_KEY => Self::SharedDataRef,
213 raw::BTRFS_BLOCK_GROUP_ITEM_KEY => Self::BlockGroupItem,
214 raw::BTRFS_FREE_SPACE_INFO_KEY => Self::FreeSpaceInfo,
215 raw::BTRFS_FREE_SPACE_EXTENT_KEY => Self::FreeSpaceExtent,
216 raw::BTRFS_FREE_SPACE_BITMAP_KEY => Self::FreeSpaceBitmap,
217 raw::BTRFS_DEV_EXTENT_KEY => Self::DeviceExtent,
218 raw::BTRFS_DEV_ITEM_KEY => Self::DeviceItem,
219 raw::BTRFS_CHUNK_ITEM_KEY => Self::ChunkItem,
220 raw::BTRFS_RAID_STRIPE_KEY => Self::RaidStripe,
221 raw::BTRFS_QGROUP_STATUS_KEY => Self::QgroupStatus,
222 raw::BTRFS_QGROUP_INFO_KEY => Self::QgroupInfo,
223 raw::BTRFS_QGROUP_LIMIT_KEY => Self::QgroupLimit,
224 raw::BTRFS_QGROUP_RELATION_KEY => Self::QgroupRelation,
225 raw::BTRFS_TEMPORARY_ITEM_KEY => Self::TemporaryItem,
227 raw::BTRFS_PERSISTENT_ITEM_KEY => Self::PersistentItem,
229 raw::BTRFS_DEV_REPLACE_KEY => Self::DeviceReplace,
230 raw::BTRFS_UUID_KEY_SUBVOL => Self::UuidKeySubvol,
231 raw::BTRFS_UUID_KEY_RECEIVED_SUBVOL => Self::UuidKeyReceivedSubvol,
232 raw::BTRFS_STRING_ITEM_KEY => Self::StringItem,
233 _ => Self::Unknown(value),
234 }
235 }
236
237 #[must_use]
241 #[allow(clippy::cast_possible_truncation)]
242 pub fn to_raw(self) -> u8 {
243 match self {
244 Self::InodeItem => raw::BTRFS_INODE_ITEM_KEY as u8,
245 Self::InodeRef => raw::BTRFS_INODE_REF_KEY as u8,
246 Self::InodeExtref => raw::BTRFS_INODE_EXTREF_KEY as u8,
247 Self::XattrItem => raw::BTRFS_XATTR_ITEM_KEY as u8,
248 Self::VerityDescItem => raw::BTRFS_VERITY_DESC_ITEM_KEY as u8,
249 Self::VerityMerkleItem => raw::BTRFS_VERITY_MERKLE_ITEM_KEY as u8,
250 Self::OrphanItem => raw::BTRFS_ORPHAN_ITEM_KEY as u8,
251 Self::DirLogItem => raw::BTRFS_DIR_LOG_ITEM_KEY as u8,
252 Self::DirLogIndex => raw::BTRFS_DIR_LOG_INDEX_KEY as u8,
253 Self::DirItem => raw::BTRFS_DIR_ITEM_KEY as u8,
254 Self::DirIndex => raw::BTRFS_DIR_INDEX_KEY as u8,
255 Self::ExtentData => raw::BTRFS_EXTENT_DATA_KEY as u8,
256 Self::ExtentCsum => raw::BTRFS_EXTENT_CSUM_KEY as u8,
257 Self::RootItem => raw::BTRFS_ROOT_ITEM_KEY as u8,
258 Self::RootBackref => raw::BTRFS_ROOT_BACKREF_KEY as u8,
259 Self::RootRef => raw::BTRFS_ROOT_REF_KEY as u8,
260 Self::ExtentItem => raw::BTRFS_EXTENT_ITEM_KEY as u8,
261 Self::MetadataItem => raw::BTRFS_METADATA_ITEM_KEY as u8,
262 Self::ExtentOwnerRef => raw::BTRFS_EXTENT_OWNER_REF_KEY as u8,
263 Self::TreeBlockRef => raw::BTRFS_TREE_BLOCK_REF_KEY as u8,
264 Self::ExtentDataRef => raw::BTRFS_EXTENT_DATA_REF_KEY as u8,
265 Self::SharedBlockRef => raw::BTRFS_SHARED_BLOCK_REF_KEY as u8,
266 Self::SharedDataRef => raw::BTRFS_SHARED_DATA_REF_KEY as u8,
267 Self::BlockGroupItem => raw::BTRFS_BLOCK_GROUP_ITEM_KEY as u8,
268 Self::FreeSpaceInfo => raw::BTRFS_FREE_SPACE_INFO_KEY as u8,
269 Self::FreeSpaceExtent => raw::BTRFS_FREE_SPACE_EXTENT_KEY as u8,
270 Self::FreeSpaceBitmap => raw::BTRFS_FREE_SPACE_BITMAP_KEY as u8,
271 Self::DeviceExtent => raw::BTRFS_DEV_EXTENT_KEY as u8,
272 Self::DeviceItem => raw::BTRFS_DEV_ITEM_KEY as u8,
273 Self::ChunkItem => raw::BTRFS_CHUNK_ITEM_KEY as u8,
274 Self::RaidStripe => raw::BTRFS_RAID_STRIPE_KEY as u8,
275 Self::QgroupStatus => raw::BTRFS_QGROUP_STATUS_KEY as u8,
276 Self::QgroupInfo => raw::BTRFS_QGROUP_INFO_KEY as u8,
277 Self::QgroupLimit => raw::BTRFS_QGROUP_LIMIT_KEY as u8,
278 Self::QgroupRelation => raw::BTRFS_QGROUP_RELATION_KEY as u8,
279 Self::TemporaryItem => raw::BTRFS_TEMPORARY_ITEM_KEY as u8,
280 Self::PersistentItem => raw::BTRFS_PERSISTENT_ITEM_KEY as u8,
281 Self::DeviceReplace => raw::BTRFS_DEV_REPLACE_KEY as u8,
282 Self::UuidKeySubvol => raw::BTRFS_UUID_KEY_SUBVOL as u8,
283 Self::UuidKeyReceivedSubvol => {
284 raw::BTRFS_UUID_KEY_RECEIVED_SUBVOL as u8
285 }
286 Self::StringItem => raw::BTRFS_STRING_ITEM_KEY as u8,
287 Self::Unknown(v) => v,
288 }
289 }
290}
291
292impl fmt::Display for KeyType {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 match self {
295 Self::InodeItem => write!(f, "INODE_ITEM"),
296 Self::InodeRef => write!(f, "INODE_REF"),
297 Self::InodeExtref => write!(f, "INODE_EXTREF"),
298 Self::XattrItem => write!(f, "XATTR_ITEM"),
299 Self::VerityDescItem => write!(f, "VERITY_DESC_ITEM"),
300 Self::VerityMerkleItem => write!(f, "VERITY_MERKLE_ITEM"),
301 Self::OrphanItem => write!(f, "ORPHAN_ITEM"),
302 Self::DirLogItem => write!(f, "DIR_LOG_ITEM"),
303 Self::DirLogIndex => write!(f, "DIR_LOG_INDEX"),
304 Self::DirItem => write!(f, "DIR_ITEM"),
305 Self::DirIndex => write!(f, "DIR_INDEX"),
306 Self::ExtentData => write!(f, "EXTENT_DATA"),
307 Self::ExtentCsum => write!(f, "EXTENT_CSUM"),
308 Self::RootItem => write!(f, "ROOT_ITEM"),
309 Self::RootBackref => write!(f, "ROOT_BACKREF"),
310 Self::RootRef => write!(f, "ROOT_REF"),
311 Self::ExtentItem => write!(f, "EXTENT_ITEM"),
312 Self::MetadataItem => write!(f, "METADATA_ITEM"),
313 Self::ExtentOwnerRef => write!(f, "EXTENT_OWNER_REF"),
314 Self::TreeBlockRef => write!(f, "TREE_BLOCK_REF"),
315 Self::ExtentDataRef => write!(f, "EXTENT_DATA_REF"),
316 Self::SharedBlockRef => write!(f, "SHARED_BLOCK_REF"),
317 Self::SharedDataRef => write!(f, "SHARED_DATA_REF"),
318 Self::BlockGroupItem => write!(f, "BLOCK_GROUP_ITEM"),
319 Self::FreeSpaceInfo => write!(f, "FREE_SPACE_INFO"),
320 Self::FreeSpaceExtent => write!(f, "FREE_SPACE_EXTENT"),
321 Self::FreeSpaceBitmap => write!(f, "FREE_SPACE_BITMAP"),
322 Self::DeviceExtent => write!(f, "DEV_EXTENT"),
323 Self::DeviceItem => write!(f, "DEV_ITEM"),
324 Self::ChunkItem => write!(f, "CHUNK_ITEM"),
325 Self::RaidStripe => write!(f, "RAID_STRIPE"),
326 Self::QgroupStatus => write!(f, "QGROUP_STATUS"),
327 Self::QgroupInfo => write!(f, "QGROUP_INFO"),
328 Self::QgroupLimit => write!(f, "QGROUP_LIMIT"),
329 Self::QgroupRelation => write!(f, "QGROUP_RELATION"),
330 Self::TemporaryItem => write!(f, "TEMPORARY_ITEM"),
331 Self::PersistentItem => write!(f, "PERSISTENT_ITEM"),
332 Self::DeviceReplace => write!(f, "DEV_REPLACE"),
333 Self::UuidKeySubvol => write!(f, "UUID_KEY_SUBVOL"),
334 Self::UuidKeyReceivedSubvol => {
335 write!(f, "UUID_KEY_RECEIVED_SUBVOL")
336 }
337 Self::StringItem => write!(f, "STRING_ITEM"),
338 Self::Unknown(v) => write!(f, "UNKNOWN.{v}"),
339 }
340 }
341}
342
343#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
346pub enum ObjectId {
347 RootTree,
350 ExtentTree,
354 ChunkTree,
358 DevTree,
361 FsTree,
365 RootTreeDir,
368 CsumTree,
371 QuotaTree,
374 UuidTree,
377 FreeSpaceTree,
381 BlockGroupTree,
385 RaidStripeTree,
389 RemapTree,
392 DeviceStats,
395 Balance,
398 Orphan,
401 TreeLog,
404 TreeLogFixup,
407 TreeReloc,
411 DataRelocTree,
414 ExtentCsum,
417 FreeSpace,
420 FreeIno,
423 CsumChange,
426 Multiple,
429 FirstFree,
432 Id(u64),
434}
435
436impl ObjectId {
437 #[must_use]
442 #[allow(clippy::cast_sign_loss, clippy::cast_lossless)]
443 pub fn from_raw(value: u64) -> Self {
444 match value {
446 v if v == raw::BTRFS_ROOT_TREE_OBJECTID as u64 => Self::RootTree,
447 v if v == raw::BTRFS_EXTENT_TREE_OBJECTID as u64 => {
448 Self::ExtentTree
449 }
450 v if v == raw::BTRFS_CHUNK_TREE_OBJECTID as u64 => Self::ChunkTree,
451 v if v == raw::BTRFS_DEV_TREE_OBJECTID as u64 => Self::DevTree,
452 v if v == raw::BTRFS_FS_TREE_OBJECTID as u64 => Self::FsTree,
453 v if v == raw::BTRFS_ROOT_TREE_DIR_OBJECTID as u64 => {
454 Self::RootTreeDir
455 }
456 v if v == raw::BTRFS_CSUM_TREE_OBJECTID as u64 => Self::CsumTree,
457 v if v == raw::BTRFS_QUOTA_TREE_OBJECTID as u64 => Self::QuotaTree,
458 v if v == raw::BTRFS_UUID_TREE_OBJECTID as u64 => Self::UuidTree,
459 v if v == raw::BTRFS_FREE_SPACE_TREE_OBJECTID as u64 => {
460 Self::FreeSpaceTree
461 }
462 v if v == raw::BTRFS_BLOCK_GROUP_TREE_OBJECTID as u64 => {
463 Self::BlockGroupTree
464 }
465 v if v == raw::BTRFS_RAID_STRIPE_TREE_OBJECTID as u64 => {
466 Self::RaidStripeTree
467 }
468 v if v == raw::BTRFS_REMAP_TREE_OBJECTID as u64 => Self::RemapTree,
469 v if v == raw::BTRFS_BALANCE_OBJECTID as u64 => Self::Balance,
471 v if v == raw::BTRFS_ORPHAN_OBJECTID as u64 => Self::Orphan,
472 v if v == raw::BTRFS_TREE_LOG_OBJECTID as u64 => Self::TreeLog,
473 v if v == raw::BTRFS_TREE_LOG_FIXUP_OBJECTID as u64 => {
474 Self::TreeLogFixup
475 }
476 v if v == raw::BTRFS_TREE_RELOC_OBJECTID as u64 => Self::TreeReloc,
477 v if v == raw::BTRFS_DATA_RELOC_TREE_OBJECTID as u64 => {
478 Self::DataRelocTree
479 }
480 v if v == raw::BTRFS_EXTENT_CSUM_OBJECTID as u64 => {
481 Self::ExtentCsum
482 }
483 v if v == raw::BTRFS_FREE_SPACE_OBJECTID as u64 => Self::FreeSpace,
484 v if v == raw::BTRFS_FREE_INO_OBJECTID as u64 => Self::FreeIno,
485 v if v == raw::BTRFS_CSUM_CHANGE_OBJECTID as u64 => {
486 Self::CsumChange
487 }
488 v if v == raw::BTRFS_MULTIPLE_OBJECTIDS as u64 => Self::Multiple,
489 _ => Self::Id(value),
490 }
491 }
492
493 #[must_use]
495 #[allow(clippy::cast_sign_loss, clippy::cast_lossless)]
496 pub fn to_raw(self) -> u64 {
497 match self {
498 Self::RootTree => raw::BTRFS_ROOT_TREE_OBJECTID as u64,
499 Self::ExtentTree => raw::BTRFS_EXTENT_TREE_OBJECTID as u64,
500 Self::ChunkTree => raw::BTRFS_CHUNK_TREE_OBJECTID as u64,
501 Self::DevTree => raw::BTRFS_DEV_TREE_OBJECTID as u64,
502 Self::FsTree => raw::BTRFS_FS_TREE_OBJECTID as u64,
503 Self::RootTreeDir => raw::BTRFS_ROOT_TREE_DIR_OBJECTID as u64,
504 Self::CsumTree => raw::BTRFS_CSUM_TREE_OBJECTID as u64,
505 Self::QuotaTree => raw::BTRFS_QUOTA_TREE_OBJECTID as u64,
506 Self::UuidTree => raw::BTRFS_UUID_TREE_OBJECTID as u64,
507 Self::FreeSpaceTree => raw::BTRFS_FREE_SPACE_TREE_OBJECTID as u64,
508 Self::BlockGroupTree => raw::BTRFS_BLOCK_GROUP_TREE_OBJECTID as u64,
509 Self::RaidStripeTree => raw::BTRFS_RAID_STRIPE_TREE_OBJECTID as u64,
510 Self::RemapTree => raw::BTRFS_REMAP_TREE_OBJECTID as u64,
511 Self::DeviceStats => raw::BTRFS_DEV_STATS_OBJECTID as u64,
512 Self::Balance => raw::BTRFS_BALANCE_OBJECTID as u64,
513 Self::Orphan => raw::BTRFS_ORPHAN_OBJECTID as u64,
514 Self::TreeLog => raw::BTRFS_TREE_LOG_OBJECTID as u64,
515 Self::TreeLogFixup => raw::BTRFS_TREE_LOG_FIXUP_OBJECTID as u64,
516 Self::TreeReloc => raw::BTRFS_TREE_RELOC_OBJECTID as u64,
517 Self::DataRelocTree => raw::BTRFS_DATA_RELOC_TREE_OBJECTID as u64,
518 Self::ExtentCsum => raw::BTRFS_EXTENT_CSUM_OBJECTID as u64,
519 Self::FreeSpace => raw::BTRFS_FREE_SPACE_OBJECTID as u64,
520 Self::FreeIno => raw::BTRFS_FREE_INO_OBJECTID as u64,
521 Self::CsumChange => raw::BTRFS_CSUM_CHANGE_OBJECTID as u64,
522 Self::Multiple => raw::BTRFS_MULTIPLE_OBJECTIDS as u64,
523 Self::FirstFree => raw::BTRFS_FIRST_FREE_OBJECTID as u64,
524 Self::Id(v) => v,
525 }
526 }
527
528 #[must_use]
535 #[allow(clippy::cast_lossless)]
536 pub fn display_with_type(self, key_type: KeyType) -> String {
537 let raw = self.to_raw();
538 if raw == raw::BTRFS_DEV_ITEMS_OBJECTID as u64
540 && key_type == KeyType::DeviceItem
541 {
542 return "DEV_ITEMS".to_string();
543 }
544 if raw == raw::BTRFS_DEV_STATS_OBJECTID as u64
545 && key_type == KeyType::PersistentItem
546 {
547 return "DEV_STATS".to_string();
548 }
549 if raw == raw::BTRFS_FIRST_CHUNK_TREE_OBJECTID as u64
550 && key_type == KeyType::ChunkItem
551 {
552 return "FIRST_CHUNK_TREE".to_string();
553 }
554 if key_type == KeyType::DeviceExtent {
556 return raw.to_string();
557 }
558 self.to_string()
559 }
560
561 pub fn from_tree_name(name: &str) -> Option<Self> {
563 match name.to_lowercase().as_str() {
564 "root" => Some(Self::RootTree),
565 "extent" => Some(Self::ExtentTree),
566 "chunk" => Some(Self::ChunkTree),
567 "device" | "dev" => Some(Self::DevTree),
568 "fs" => Some(Self::FsTree),
569 "root_tree_dir" => Some(Self::RootTreeDir),
570 "csum" | "checksum" => Some(Self::CsumTree),
571 "quota" => Some(Self::QuotaTree),
572 "uuid" => Some(Self::UuidTree),
573 "free_space" | "free-space" => Some(Self::FreeSpaceTree),
574 "block_group" | "block-group" => Some(Self::BlockGroupTree),
575 "raid_stripe" | "raid-stripe" => Some(Self::RaidStripeTree),
576 "remap" => Some(Self::RemapTree),
577 "tree_log" | "tree-log" => Some(Self::TreeLog),
578 "tree_log_fixup" | "tree-log-fixup" => Some(Self::TreeLogFixup),
579 "tree_reloc" | "tree-reloc" => Some(Self::TreeReloc),
580 "data_reloc" | "data-reloc" => Some(Self::DataRelocTree),
581 _ => name.parse::<u64>().ok().map(Self::from_raw),
582 }
583 }
584}
585
586impl fmt::Display for ObjectId {
587 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
588 match self {
589 Self::RootTree => write!(f, "ROOT_TREE"),
590 Self::ExtentTree => write!(f, "EXTENT_TREE"),
591 Self::ChunkTree => write!(f, "CHUNK_TREE"),
592 Self::DevTree => write!(f, "DEV_TREE"),
593 Self::FsTree => write!(f, "FS_TREE"),
594 Self::RootTreeDir => write!(f, "ROOT_TREE_DIR"),
595 Self::CsumTree => write!(f, "CSUM_TREE"),
596 Self::QuotaTree => write!(f, "QUOTA_TREE"),
597 Self::UuidTree => write!(f, "UUID_TREE"),
598 Self::FreeSpaceTree => write!(f, "FREE_SPACE_TREE"),
599 Self::BlockGroupTree => write!(f, "BLOCK_GROUP_TREE"),
600 Self::RaidStripeTree => write!(f, "RAID_STRIPE_TREE"),
601 Self::RemapTree => write!(f, "REMAP_TREE"),
602 Self::DeviceStats => write!(f, "DEV_STATS"),
603 Self::Balance => write!(f, "BALANCE"),
604 Self::Orphan => write!(f, "ORPHAN"),
605 Self::TreeLog => write!(f, "TREE_LOG"),
606 Self::TreeLogFixup => write!(f, "TREE_LOG_FIXUP"),
607 Self::TreeReloc => write!(f, "TREE_RELOC"),
608 Self::DataRelocTree => write!(f, "DATA_RELOC_TREE"),
609 Self::ExtentCsum => write!(f, "EXTENT_CSUM"),
610 Self::FreeSpace => write!(f, "FREE_SPACE"),
611 Self::FreeIno => write!(f, "FREE_INO"),
612 Self::CsumChange => write!(f, "CSUM_CHANGE"),
613 Self::Multiple => write!(f, "MULTIPLE"),
614 Self::FirstFree => write!(f, "256"),
615 Self::Id(v) => write!(f, "{v}"),
616 }
617 }
618}
619
620#[derive(Debug, Clone, Copy, PartialEq, Eq)]
625pub struct DiskKey {
626 pub objectid: u64,
628 pub key_type: KeyType,
630 pub offset: u64,
632}
633
634impl DiskKey {
635 #[must_use]
638 pub fn parse(buf: &[u8], off: usize) -> Self {
639 let mut buf = &buf[off..];
640 let objectid = buf.get_u64_le();
641 let key_type = KeyType::from_raw(buf.get_u8());
642 let offset = buf.get_u64_le();
643 Self {
644 objectid,
645 key_type,
646 offset,
647 }
648 }
649}
650
651#[must_use]
658pub fn format_key(key: &DiskKey) -> String {
659 let objectid_str = format_key_objectid(key);
660 let type_str = format_key_type(key);
661 let offset_str = format_key_offset(key);
662 format!("({objectid_str} {type_str} {offset_str})")
663}
664
665fn format_key_objectid(key: &DiskKey) -> String {
666 match key.key_type {
667 KeyType::QgroupRelation
668 | KeyType::QgroupInfo
669 | KeyType::QgroupLimit => {
670 let level = key.objectid >> 48;
671 let subvolid = key.objectid & ((1u64 << 48) - 1);
672 format!("{level}/{subvolid}")
673 }
674 KeyType::UuidKeySubvol | KeyType::UuidKeyReceivedSubvol => {
675 format!("0x{:016x}", key.objectid)
676 }
677 _ => {
678 let oid = ObjectId::from_raw(key.objectid);
679 oid.display_with_type(key.key_type)
680 }
681 }
682}
683
684#[allow(clippy::cast_sign_loss)]
685fn format_key_type(key: &DiskKey) -> String {
686 if key.key_type == KeyType::Unknown(0)
688 && key.objectid == raw::BTRFS_FREE_SPACE_OBJECTID as u64
689 {
690 return "UNTYPED".to_string();
691 }
692 key.key_type.to_string()
693}
694
695fn format_key_offset(key: &DiskKey) -> String {
696 match key.key_type {
697 KeyType::QgroupRelation
698 | KeyType::QgroupStatus
699 | KeyType::QgroupInfo
700 | KeyType::QgroupLimit => {
701 let level = key.offset >> 48;
702 let subvolid = key.offset & ((1u64 << 48) - 1);
703 format!("{level}/{subvolid}")
704 }
705 KeyType::UuidKeySubvol | KeyType::UuidKeyReceivedSubvol => {
706 format!("0x{:016x}", key.offset)
707 }
708 _ => format!("{}", key.offset),
709 }
710}
711
712bitflags::bitflags! {
713 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
716 pub struct HeaderFlags: u64 {
717 const WRITTEN = raw::BTRFS_HEADER_FLAG_WRITTEN as u64;
718 const RELOC = raw::BTRFS_HEADER_FLAG_RELOC as u64;
719 const _ = !0;
721 }
722}
723
724impl fmt::Display for HeaderFlags {
725 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
726 let known = Self::WRITTEN | Self::RELOC;
727 let mut parts = Vec::new();
728 if self.contains(Self::WRITTEN) {
729 parts.push("WRITTEN");
730 }
731 if self.contains(Self::RELOC) {
732 parts.push("RELOC");
733 }
734 if !(*self & !known).is_empty() {
735 parts.push("UNKNOWN");
736 }
737 if parts.is_empty() {
738 write!(f, "0x0")
739 } else {
740 write!(f, "{}", parts.join("|"))
741 }
742 }
743}
744
745#[derive(Debug, Clone)]
751pub struct Header {
752 pub csum: [u8; 32],
754 pub fsid: Uuid,
756 pub bytenr: u64,
758 pub flags: HeaderFlags,
760 pub chunk_tree_uuid: Uuid,
762 pub generation: u64,
764 pub owner: u64,
766 pub nritems: u32,
768 pub level: u8,
770}
771
772const HEADER_SIZE: usize = mem::size_of::<raw::btrfs_header>();
774
775impl Header {
776 #[must_use]
782 pub fn parse(buf: &[u8]) -> Self {
783 assert!(
784 buf.len() >= HEADER_SIZE,
785 "buffer too small for btrfs_header: {} < {HEADER_SIZE}",
786 buf.len()
787 );
788 let mut b = buf;
789 let mut csum = [0u8; 32];
790 csum.copy_from_slice(&b[..32]);
791 b.advance(32);
792 let fsid = get_uuid(&mut b);
793 let bytenr = b.get_u64_le();
794 let flags = HeaderFlags::from_bits_truncate(b.get_u64_le());
795 let chunk_tree_uuid = get_uuid(&mut b);
796 let generation = b.get_u64_le();
797 let owner = b.get_u64_le();
798 let nritems = b.get_u32_le();
799 let level = b.get_u8();
800 Self {
801 csum,
802 fsid,
803 bytenr,
804 flags,
805 chunk_tree_uuid,
806 generation,
807 owner,
808 nritems,
809 level,
810 }
811 }
812
813 #[must_use]
815 pub fn backref_rev(&self) -> u64 {
816 self.flags.bits() >> raw::BTRFS_BACKREF_REV_SHIFT
817 }
818
819 #[must_use]
821 pub fn block_flags(&self) -> HeaderFlags {
822 let mask = (u64::from(raw::BTRFS_BACKREF_REV_MAX) - 1)
823 << raw::BTRFS_BACKREF_REV_SHIFT;
824 HeaderFlags::from_bits_truncate(self.flags.bits() & !mask)
825 }
826}
827
828#[derive(Debug, Clone, Copy)]
830pub struct KeyPtr {
831 pub key: DiskKey,
833 pub blockptr: u64,
835 pub generation: u64,
837}
838
839const KEY_PTR_SIZE: usize = mem::size_of::<raw::btrfs_key_ptr>();
841
842impl KeyPtr {
843 fn parse(buf: &[u8], off: usize) -> Self {
845 let key = DiskKey::parse(buf, off);
846 let mut buf = &buf[off + 17..];
847 Self {
848 key,
849 blockptr: buf.get_u64_le(),
850 generation: buf.get_u64_le(),
851 }
852 }
853}
854
855#[derive(Debug, Clone, Copy)]
857pub struct Item {
858 pub key: DiskKey,
860 pub offset: u32,
865 pub size: u32,
867}
868
869const ITEM_SIZE: usize = mem::size_of::<raw::btrfs_item>();
871
872impl Item {
873 fn parse(buf: &[u8], off: usize) -> Self {
875 let key = DiskKey::parse(buf, off);
876 let mut buf = &buf[off + 17..];
877 Self {
878 key,
879 offset: buf.get_u32_le(),
880 size: buf.get_u32_le(),
881 }
882 }
883}
884
885pub enum TreeBlock {
891 Node {
893 header: Header,
895 ptrs: Vec<KeyPtr>,
897 },
898 Leaf {
900 header: Header,
902 items: Vec<Item>,
904 data: Vec<u8>,
906 },
907}
908
909impl TreeBlock {
910 #[must_use]
912 pub fn parse(buf: &[u8]) -> Self {
913 let header = Header::parse(buf);
914 let nritems = header.nritems as usize;
915
916 if header.level > 0 {
917 let mut ptrs = Vec::with_capacity(nritems);
918 for i in 0..nritems {
919 let off = HEADER_SIZE + i * KEY_PTR_SIZE;
920 ptrs.push(KeyPtr::parse(buf, off));
921 }
922 Self::Node { header, ptrs }
923 } else {
924 let mut items = Vec::with_capacity(nritems);
925 for i in 0..nritems {
926 let off = HEADER_SIZE + i * ITEM_SIZE;
927 items.push(Item::parse(buf, off));
928 }
929 Self::Leaf {
930 header,
931 items,
932 data: buf.to_vec(),
933 }
934 }
935 }
936
937 #[must_use]
939 pub fn header(&self) -> &Header {
940 match self {
941 Self::Node { header, .. } | Self::Leaf { header, .. } => header,
942 }
943 }
944
945 #[must_use]
951 pub fn item_data(&self, index: usize) -> Option<&[u8]> {
952 match self {
953 Self::Leaf { items, data, .. } => {
954 let item = items.get(index)?;
955 let start = HEADER_SIZE + item.offset as usize;
956 let end = start + item.size as usize;
957 if end <= data.len() {
958 Some(&data[start..end])
959 } else {
960 None
961 }
962 }
963 Self::Node { .. } => None,
964 }
965 }
966}
967
968#[cfg(test)]
969mod tests {
970 use super::*;
971
972 fn make_disk_key(objectid: u64, key_type: u8, offset: u64) -> [u8; 17] {
974 let mut buf = [0u8; 17];
975 buf[0..8].copy_from_slice(&objectid.to_le_bytes());
976 buf[8] = key_type;
977 buf[9..17].copy_from_slice(&offset.to_le_bytes());
978 buf
979 }
980
981 fn make_header(
983 bytenr: u64,
984 generation: u64,
985 owner: u64,
986 nritems: u32,
987 level: u8,
988 ) -> Vec<u8> {
989 let mut buf = vec![0u8; HEADER_SIZE];
990 buf[48..56].copy_from_slice(&bytenr.to_le_bytes());
993 buf[56..64].copy_from_slice(
995 &(raw::BTRFS_HEADER_FLAG_WRITTEN as u64).to_le_bytes(),
996 );
997 buf[80..88].copy_from_slice(&generation.to_le_bytes());
999 buf[88..96].copy_from_slice(&owner.to_le_bytes());
1000 buf[96..100].copy_from_slice(&nritems.to_le_bytes());
1001 buf[100] = level;
1002 buf
1003 }
1004
1005 #[test]
1006 fn key_type_round_trip() {
1007 for raw_val in 0..=255u8 {
1008 let kt = KeyType::from_raw(raw_val);
1009 assert_eq!(kt.to_raw(), raw_val);
1010 }
1011 }
1012
1013 #[test]
1014 fn key_type_display() {
1015 assert_eq!(KeyType::InodeItem.to_string(), "INODE_ITEM");
1016 assert_eq!(KeyType::ChunkItem.to_string(), "CHUNK_ITEM");
1017 assert_eq!(KeyType::Unknown(99).to_string(), "UNKNOWN.99");
1018 }
1019
1020 #[test]
1021 fn objectid_round_trip() {
1022 let cases: &[u64] = &[
1023 1,
1024 2,
1025 3,
1026 4,
1027 5,
1028 6,
1029 7,
1030 8,
1031 9,
1032 10,
1033 11,
1034 12,
1035 13,
1036 256,
1037 1000,
1038 raw::BTRFS_BALANCE_OBJECTID as u64,
1040 raw::BTRFS_ORPHAN_OBJECTID as u64,
1041 raw::BTRFS_TREE_LOG_OBJECTID as u64,
1042 raw::BTRFS_DATA_RELOC_TREE_OBJECTID as u64,
1043 ];
1044 for &v in cases {
1045 let oid = ObjectId::from_raw(v);
1046 assert_eq!(oid.to_raw(), v, "round-trip failed for {v}");
1047 }
1048 }
1049
1050 #[test]
1051 fn objectid_display() {
1052 assert_eq!(ObjectId::RootTree.to_string(), "ROOT_TREE");
1053 assert_eq!(ObjectId::FsTree.to_string(), "FS_TREE");
1054 assert_eq!(ObjectId::Id(256).to_string(), "256");
1055 assert_eq!(ObjectId::Id(u64::MAX).to_string(), "18446744073709551615");
1056 }
1057
1058 #[test]
1059 fn objectid_display_with_type() {
1060 let oid = ObjectId::from_raw(1);
1062 assert_eq!(oid.display_with_type(KeyType::RootItem), "ROOT_TREE");
1063 assert_eq!(oid.display_with_type(KeyType::DeviceItem), "DEV_ITEMS");
1064 }
1065
1066 #[test]
1067 fn objectid_from_tree_name() {
1068 assert_eq!(ObjectId::from_tree_name("root"), Some(ObjectId::RootTree));
1069 assert_eq!(
1070 ObjectId::from_tree_name("CHUNK"),
1071 Some(ObjectId::ChunkTree)
1072 );
1073 assert_eq!(
1074 ObjectId::from_tree_name("free-space"),
1075 Some(ObjectId::FreeSpaceTree)
1076 );
1077 assert_eq!(ObjectId::from_tree_name("5"), Some(ObjectId::FsTree));
1078 assert_eq!(ObjectId::from_tree_name("256"), Some(ObjectId::Id(256)));
1079 assert_eq!(ObjectId::from_tree_name("nosuch"), None);
1080 }
1081
1082 #[test]
1083 fn parse_disk_key() {
1084 let buf = make_disk_key(42, raw::BTRFS_INODE_ITEM_KEY as u8, 100);
1085 let key = DiskKey::parse(&buf, 0);
1086 assert_eq!(key.objectid, 42);
1087 assert_eq!(key.key_type, KeyType::InodeItem);
1088 assert_eq!(key.offset, 100);
1089 }
1090
1091 #[test]
1092 fn parse_header() {
1093 let buf = make_header(65536, 7, 5, 10, 0);
1094 let hdr = Header::parse(&buf);
1095 assert_eq!(hdr.bytenr, 65536);
1096 assert_eq!(hdr.generation, 7);
1097 assert_eq!(hdr.owner, 5);
1098 assert_eq!(hdr.nritems, 10);
1099 assert_eq!(hdr.level, 0);
1100 assert_eq!(
1101 hdr.block_flags(),
1102 HeaderFlags::from_bits_truncate(
1103 raw::BTRFS_HEADER_FLAG_WRITTEN as u64
1104 )
1105 );
1106 }
1107
1108 #[test]
1109 fn header_flags_display_written() {
1110 let flags = HeaderFlags::from_bits_truncate(
1111 raw::BTRFS_HEADER_FLAG_WRITTEN as u64,
1112 );
1113 assert_eq!(flags.to_string(), "WRITTEN");
1114 }
1115
1116 #[test]
1117 fn header_flags_display_multiple() {
1118 let flags = HeaderFlags::from_bits_truncate(
1119 raw::BTRFS_HEADER_FLAG_WRITTEN as u64
1120 | raw::BTRFS_HEADER_FLAG_RELOC as u64,
1121 );
1122 assert_eq!(flags.to_string(), "WRITTEN|RELOC");
1123 }
1124
1125 #[test]
1126 fn parse_leaf_block() {
1127 let nodesize = 4096usize;
1128 let nritems = 2u32;
1129 let mut buf = vec![0u8; nodesize];
1130
1131 let hdr = make_header(65536, 7, 5, nritems, 0);
1133 buf[..HEADER_SIZE].copy_from_slice(&hdr);
1134
1135 let key0 = make_disk_key(256, raw::BTRFS_INODE_ITEM_KEY as u8, 0);
1138 let item0_off = HEADER_SIZE;
1139 buf[item0_off..item0_off + 17].copy_from_slice(&key0);
1140 buf[item0_off + 17..item0_off + 21]
1141 .copy_from_slice(&3800u32.to_le_bytes());
1142 buf[item0_off + 21..item0_off + 25]
1143 .copy_from_slice(&160u32.to_le_bytes());
1144
1145 let key1 = make_disk_key(256, raw::BTRFS_DIR_ITEM_KEY as u8, 100);
1147 let item1_off = HEADER_SIZE + ITEM_SIZE;
1148 buf[item1_off..item1_off + 17].copy_from_slice(&key1);
1149 buf[item1_off + 17..item1_off + 21]
1150 .copy_from_slice(&3700u32.to_le_bytes());
1151 buf[item1_off + 21..item1_off + 25]
1152 .copy_from_slice(&50u32.to_le_bytes());
1153
1154 let data0_start = HEADER_SIZE + 3800;
1157 buf[data0_start] = 0xAA;
1158 let data1_start = HEADER_SIZE + 3700;
1160 buf[data1_start] = 0xBB;
1161
1162 let block = TreeBlock::parse(&buf);
1163
1164 match &block {
1165 TreeBlock::Leaf { header, items, .. } => {
1166 assert_eq!(header.level, 0);
1167 assert_eq!(items.len(), 2);
1168 assert_eq!(items[0].key.key_type, KeyType::InodeItem);
1169 assert_eq!(items[1].key.key_type, KeyType::DirItem);
1170 }
1171 TreeBlock::Node { .. } => panic!("expected leaf"),
1172 }
1173
1174 let data0 = block.item_data(0).unwrap();
1175 assert_eq!(data0[0], 0xAA);
1176 assert_eq!(data0.len(), 160);
1177
1178 let data1 = block.item_data(1).unwrap();
1179 assert_eq!(data1[0], 0xBB);
1180 assert_eq!(data1.len(), 50);
1181 }
1182
1183 #[test]
1184 fn parse_node_block() {
1185 let nodesize = 4096usize;
1186 let nritems = 3u32;
1187 let mut buf = vec![0u8; nodesize];
1188
1189 let hdr = make_header(131072, 10, 2, nritems, 1);
1191 buf[..HEADER_SIZE].copy_from_slice(&hdr);
1192
1193 for i in 0..3u64 {
1195 let off = HEADER_SIZE + i as usize * KEY_PTR_SIZE;
1196 let key = make_disk_key(i + 1, raw::BTRFS_ROOT_ITEM_KEY as u8, 0);
1197 buf[off..off + 17].copy_from_slice(&key);
1198 let blockptr = (i + 1) * 65536;
1199 buf[off + 17..off + 25].copy_from_slice(&blockptr.to_le_bytes());
1200 let generation = 10 - i;
1201 buf[off + 25..off + 33].copy_from_slice(&generation.to_le_bytes());
1202 }
1203
1204 let block = TreeBlock::parse(&buf);
1205
1206 match &block {
1207 TreeBlock::Node { header, ptrs } => {
1208 assert_eq!(header.level, 1);
1209 assert_eq!(header.bytenr, 131072);
1210 assert_eq!(ptrs.len(), 3);
1211 assert_eq!(ptrs[0].blockptr, 65536);
1212 assert_eq!(ptrs[1].blockptr, 131072);
1213 assert_eq!(ptrs[2].blockptr, 196608);
1214 assert_eq!(ptrs[0].generation, 10);
1215 assert_eq!(ptrs[2].generation, 8);
1216 }
1217 TreeBlock::Leaf { .. } => panic!("expected node"),
1218 }
1219
1220 assert!(block.item_data(0).is_none());
1222 }
1223
1224 #[test]
1225 fn format_key_basic() {
1226 let key = DiskKey {
1227 objectid: 256,
1228 key_type: KeyType::InodeItem,
1229 offset: 0,
1230 };
1231 assert_eq!(format_key(&key), "(256 INODE_ITEM 0)");
1232 }
1233
1234 #[test]
1235 fn format_key_well_known_objectid() {
1236 let key = DiskKey {
1237 objectid: raw::BTRFS_FS_TREE_OBJECTID as u64,
1238 key_type: KeyType::RootItem,
1239 offset: u64::MAX,
1240 };
1241 assert_eq!(
1242 format_key(&key),
1243 "(FS_TREE ROOT_ITEM 18446744073709551615)"
1244 );
1245 }
1246
1247 #[test]
1248 fn format_key_qgroup() {
1249 let key = DiskKey {
1250 objectid: 0, key_type: KeyType::QgroupInfo,
1252 offset: (0u64 << 48) | 256, };
1254 assert_eq!(format_key(&key), "(0/0 QGROUP_INFO 0/256)");
1255 }
1256
1257 #[test]
1258 fn format_key_uuid() {
1259 let key = DiskKey {
1260 objectid: 0xdeadbeef12345678,
1261 key_type: KeyType::UuidKeySubvol,
1262 offset: 0xabcdef0123456789,
1263 };
1264 assert_eq!(
1265 format_key(&key),
1266 "(0xdeadbeef12345678 UUID_KEY_SUBVOL 0xabcdef0123456789)"
1267 );
1268 }
1269
1270 #[test]
1271 fn format_key_dev_items() {
1272 let key = DiskKey {
1273 objectid: 1,
1274 key_type: KeyType::DeviceItem,
1275 offset: 1,
1276 };
1277 assert_eq!(format_key(&key), "(DEV_ITEMS DEV_ITEM 1)");
1278 }
1279}