├── .github └── workflows │ └── rust-build.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-BSD ├── README.md ├── rustfmt.toml └── src ├── aliases.rs ├── chunk ├── chunk.rs ├── mod.rs └── stripe.rs ├── constants.rs ├── core ├── dev_item.rs ├── inode_item.rs ├── key.rs ├── mod.rs ├── node.rs ├── root_backup.rs ├── root_item.rs ├── root_ref.rs ├── super_block.rs └── time.rs ├── dev ├── dev_extent.rs └── mod.rs ├── extent ├── block_group_item.rs ├── extent_data_ref.rs ├── extent_inline_ref.rs ├── mod.rs └── shared_data_ref.rs ├── lib.rs └── types.rs /.github/workflows/rust-build.yml: -------------------------------------------------------------------------------- 1 | name: Rust build 2 | 3 | on: 4 | push: 5 | branches: ["master"] 6 | paths: 7 | - "src/**" 8 | - "Cargo.*" 9 | - "rustfmt.toml" 10 | - ".github/**" 11 | pull_request: 12 | branches: ["master"] 13 | paths: 14 | - "src/**" 15 | - "Cargo.*" 16 | - "rustfmt.toml" 17 | - ".github/**" 18 | 19 | env: 20 | CARGO_TERM_COLOR: always 21 | 22 | jobs: 23 | build: 24 | runs-on: ubuntu-latest 25 | env: 26 | RUSTFLAGS: "-D warnings" # Fail compilation on any warnings 27 | 28 | steps: 29 | - uses: actions/checkout@v4 30 | 31 | - uses: actions-rs/toolchain@v1 32 | with: 33 | toolchain: stable 34 | components: clippy 35 | override: true 36 | 37 | - name: Build 38 | run: cargo build --verbose 39 | 40 | - name: Check formatting 41 | run: cargo fmt --check 42 | 43 | - name: Run lints 44 | run: cargo clippy -- -D clippy::all -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "btrfs-diskformat" 3 | version = "0.5.1" 4 | authors = ["Christopher Tam "] 5 | edition = "2024" 6 | description = "An implementation of the BTRFS disk format." 7 | readme = "README.md" 8 | repository = "https://github.com/GodTamIt/btrfs-diskformat" 9 | license = "BSD-2-Clause" 10 | keywords = ["btrfs", "filesystem", "diskformat"] 11 | categories = ["filesystem", "no-std"] 12 | 13 | [features] 14 | default = [] 15 | alloc = ["zerocopy/alloc"] 16 | std = ["strum/std", "zerocopy/std"] 17 | 18 | [dependencies] 19 | bitflags = "^2.9" 20 | num_enum = { version = "^0.7", default-features = false } 21 | static_assertions = "^1.1.0" 22 | strum = { version = "^0.27", features = ["derive"], default-features = false } 23 | zerocopy = { version = "^0.8", default-features = false } 24 | zerocopy-derive = "^0.8" 25 | -------------------------------------------------------------------------------- /LICENSE-BSD: -------------------------------------------------------------------------------- 1 | Copyright 2020 Christopher Tam 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BTRFS Disk Format 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/btrfs-diskformat.svg)](https://crates.io/crates/btrfs-diskformat) 4 | [![Crates.io](https://img.shields.io/badge/license-BSD%202--Clause-blue)](https://crates.io/crates/btrfs-diskformat) 5 | 6 | Clean-room implementation of the [btrfs] disk format in Rust. 7 | 8 | [Documentation](https://docs.rs/btrfs-diskformat/) 9 | 10 | ## License 11 | 12 | `btrfs-diskformat` is distributed under the terms of the BSD 2-Clause license. 13 | 14 | See the [LICENSE-BSD](LICENSE-BSD) file in this repository for more information. 15 | 16 | ## Cargo Features 17 | 18 | - `alloc`: Enables allocation and the `alloc` feature in `zerocopy`. 19 | - `std`: By default, the crate is `no_std`. This enables `std` features from 20 | the `zerocopy` and `strum` dependencies. 21 | 22 | ## Contributing 23 | 24 | Because this codebase is developed without knowledge of the Linux btrfs source code and is released under a more permissive license(s) than GPLv2, development is heavily dependent on information released on the [btrfs wiki] and reverse engineering the effects of operations made by `btrfs-progs` and other utilities. As a result, contributions to this codebase must strictly follow the same siloed approach. 25 | 26 | [btrfs]: https://btrfs.wiki.kernel.org/index.php/Main_Page 27 | [btrfs wiki]: https://btrfs.wiki.kernel.org/index.php/Main_Page 28 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | 3 | use_small_heuristics = "Max" 4 | 5 | # Prevent carriage returns 6 | newline_style = "Unix" -------------------------------------------------------------------------------- /src/aliases.rs: -------------------------------------------------------------------------------- 1 | //! This module contains aliases from the crate's types to the wiki-style snake case names for 2 | //! convenience. 3 | 4 | #![allow(non_camel_case_types)] 5 | 6 | use crate::*; 7 | 8 | // chunk 9 | pub type btrfs_chunk = Chunk; 10 | pub type btrfs_stripe = Stripe; 11 | 12 | // core 13 | pub type btrfs_dev_item = DevItem; 14 | pub type btrfs_inode_item = InodeItem; 15 | pub type btrfs_disk_key = Key; 16 | pub type btrfs_header = Header; 17 | pub type btrfs_root_backup = RootBackup; 18 | pub type btrfs_root_item = RootItem; 19 | pub type btrfs_root_ref = RootRef; 20 | pub type btrfs_super_block = SuperBlock; 21 | pub type btrfs_timespec = Time; 22 | 23 | pub const BTRFS_CSUM_TYPE_CRC32: u16 = ChecksumType::CRC32C as u16; 24 | pub const BTRFS_CSUM_TYPE_XXHASH: u16 = ChecksumType::XXHASH64 as u16; 25 | pub const BTRFS_CSUM_TYPE_SHA256: u16 = ChecksumType::SHA256 as u16; 26 | pub const BTRFS_CSUM_TYPE_BLAKE2: u16 = ChecksumType::BLAKE2b as u16; 27 | 28 | pub const BTRFS_INODE_NODATASUM: u64 = InodeFlags::NO_DATA_SUM.bits(); 29 | pub const BTRFS_INODE_NODATACOW: u64 = InodeFlags::NO_DATA_COW.bits(); 30 | pub const BTRFS_INODE_READONLY: u64 = InodeFlags::READ_ONLY.bits(); 31 | pub const BTRFS_INODE_NOCOMPRESS: u64 = InodeFlags::NO_COMPRESS.bits(); 32 | pub const BTRFS_INODE_PREALLOC: u64 = InodeFlags::PREALLOC.bits(); 33 | pub const BTRFS_INODE_SYNC: u64 = InodeFlags::SYNC.bits(); 34 | pub const BTRFS_INODE_IMMUTABLE: u64 = InodeFlags::IMMUTABLE.bits(); 35 | pub const BTRFS_INODE_APPEND: u64 = InodeFlags::APPEND.bits(); 36 | pub const BTRFS_INODE_NODUMP: u64 = InodeFlags::NO_DUMP.bits(); 37 | pub const BTRFS_INODE_NOATIME: u64 = InodeFlags::NO_ATIME.bits(); 38 | pub const BTRFS_INODE_DIRSYNC: u64 = InodeFlags::DIR_SYNC.bits(); 39 | pub const BTRFS_INODE_COMPRESS: u64 = InodeFlags::COMPRESS.bits(); 40 | 41 | // dev 42 | pub type btrfs_dev_extent = DevExtent; 43 | 44 | // extent 45 | pub type btrfs_block_group_item = BlockGroupItem; 46 | pub type btrfs_extent_data_ref = ExtentDataRef; 47 | pub type btrfs_extent_inline_ref = ExtentInlineRefHeader; 48 | pub type btrfs_shared_data_ref = SharedDataRef; 49 | 50 | pub const BTRFS_BLOCK_GROUP_DATA: u64 = AllocationType::DATA.bits(); 51 | pub const BTRFS_BLOCK_GROUP_SYSTEM: u64 = AllocationType::SYSTEM.bits(); 52 | pub const BTRFS_BLOCK_GROUP_METADATA: u64 = AllocationType::METADATA.bits(); 53 | pub const BTRFS_BLOCK_GROUP_RAID0: u64 = ReplicationPolicy::RAID0.bits(); 54 | pub const BTRFS_BLOCK_GROUP_RAID1: u64 = ReplicationPolicy::RAID1.bits(); 55 | pub const BTRFS_BLOCK_GROUP_DUP: u64 = ReplicationPolicy::DUP.bits(); 56 | pub const BTRFS_BLOCK_GROUP_RAID10: u64 = ReplicationPolicy::RAID10.bits(); 57 | pub const BTRFS_BLOCK_GROUP_RAID5: u64 = ReplicationPolicy::RAID5.bits(); 58 | pub const BTRFS_BLOCK_GROUP_RAID6: u64 = ReplicationPolicy::RAID6.bits(); 59 | pub const BTRFS_BLOCK_GROUP_RAID1C3: u64 = ReplicationPolicy::RAID1C3.bits(); 60 | pub const BTRFS_BLOCK_GROUP_RAID1C4: u64 = ReplicationPolicy::RAID1C4.bits(); 61 | 62 | pub const BTRFS_TREE_BLOCK_REF_KEY: u8 = ExtentInlineRefType::TreeBlockRef as u8; 63 | pub const BTRFS_SHARED_BLOCK_REF_KEY: u8 = ExtentInlineRefType::SharedBlockRef as u8; 64 | pub const BTRFS_EXTENT_DATA_REF_KEY: u8 = ExtentInlineRefType::ExtentDataRef as u8; 65 | pub const BTRFS_SHARED_DATA_REF_KEY: u8 = ExtentInlineRefType::SharedDataRef as u8; 66 | -------------------------------------------------------------------------------- /src/chunk/chunk.rs: -------------------------------------------------------------------------------- 1 | use crate::Stripe; 2 | use static_assertions::const_assert_eq; 3 | use zerocopy::{ 4 | CastError, FromBytes as _, 5 | little_endian::{U16 as U16LE, U32 as U32LE, U64 as U64LE}, 6 | }; 7 | use zerocopy_derive::*; 8 | 9 | /// This structure contains the mapping from a virtualized usable byte range within the backing 10 | /// storage to a set of one or more stripes on individual backing devices. In addition to the 11 | /// mapping, hints on optimal I/O parameters for this chunk. It is associated with `CHUNK_ITEM`. 12 | /// 13 | /// Although the structure definition only contains one stripe member, `CHUNK_ITEM` contain as 14 | /// many struct [`Stripe`] structures as specified in the [`num_stripes`] and [`sub_stripes`] 15 | /// fields. 16 | /// 17 | /// For the dynamically-sized version of this type with the stripes following, see [`ChunkDynamic`]. 18 | /// 19 | /// [`Stripe`]: crate::Stripe 20 | /// [`num_stripes`]: Chunk::num_stripes 21 | /// [`sub_stripes`]: Chunk::sub_stripes 22 | #[derive(Copy, Clone, Debug, Hash, IntoBytes, FromBytes, Unaligned, KnownLayout, Immutable)] 23 | #[repr(C, packed)] 24 | pub struct Chunk { 25 | /// The size of this chunk, in bytes. 26 | pub length: U64LE, 27 | 28 | /// The object ID of the root referencing this chunk. This is always the ID of an extent root. 29 | pub owner: U64LE, 30 | 31 | /// The replication stripe length. 32 | pub stripe_len: U64LE, 33 | 34 | /// Flags indicating allocation type and replication policy. 35 | pub chunk_type: U64LE, 36 | 37 | /// The optimal I/O alignment for this chunk. 38 | pub io_align: U32LE, 39 | 40 | /// The optimal I/O width for this chunk. 41 | pub io_width: U32LE, 42 | 43 | /// The minimal I/O size for this chunk. 44 | pub sector_size: U32LE, 45 | 46 | /// The number of replication stripes. 47 | pub num_stripes: U16LE, 48 | 49 | /// The number of sub-stripes. This is only used for RAID-10. 50 | /// 51 | /// This is 2 for RAID-10, and 1 for all other chunk types. 52 | pub sub_stripes: U16LE, 53 | } 54 | const_assert_eq!(core::mem::size_of::(), 48); 55 | 56 | impl Chunk { 57 | /// A convenience method for converting a [`Chunk`] into a [`ChunkDynamic`]. 58 | /// 59 | /// For a safe version, use [`ChunkDynamic::ref_from_prefix_with_elems`]. 60 | /// 61 | /// # Safety 62 | /// This function is unsafe because it assumes that the bytes following the `Chunk` structure 63 | /// are valid and contain the expected number of `Stripe` structures as specified by 64 | /// `num_stripes`. If this assumption is violated, it may lead to undefined behavior. 65 | pub unsafe fn into_dynamic( 66 | &self, 67 | num_stripes: usize, 68 | ) -> Result<&ChunkDynamic, CastError<&[u8], ChunkDynamic>> { 69 | // This is simple arithmetic, since this is a packed structure. 70 | let expected_size = 71 | core::mem::size_of::() + num_stripes * core::mem::size_of::(); 72 | 73 | // Safety: We assume the bytes for `num_stripes` after the `Chunk` structure are valid, as 74 | // part of the contract of this function. 75 | let bytes = unsafe { 76 | core::slice::from_raw_parts(self as *const Chunk as *const u8, expected_size) 77 | }; 78 | 79 | ChunkDynamic::ref_from_prefix_with_elems(bytes, num_stripes).map(|(chunk, _)| chunk) 80 | } 81 | } 82 | 83 | /// This structure contains the mapping from a virtualized usable byte range within the backing 84 | /// storage to a set of one or more stripes on individual backing devices. In addition to the 85 | /// mapping, hints on optimal I/O parameters for this chunk. It is associated with `CHUNK_ITEM`. 86 | /// 87 | /// For the constant-sized version of the type, see [`Chunk`].` 88 | #[derive(IntoBytes, FromBytes, Unaligned, KnownLayout, Immutable)] 89 | #[repr(C, packed)] 90 | pub struct ChunkDynamic { 91 | /// The size of this chunk, in bytes. 92 | pub length: U64LE, 93 | 94 | /// The object ID of the root referencing this chunk. This is always the ID of an extent root. 95 | pub owner: U64LE, 96 | 97 | /// The replication stripe length. 98 | pub stripe_len: U64LE, 99 | 100 | /// Flags indicating allocation type and replication policy. 101 | pub chunk_type: U64LE, 102 | 103 | /// The optimal I/O alignment for this chunk. 104 | pub io_align: U32LE, 105 | 106 | /// The optimal I/O width for this chunk. 107 | pub io_width: U32LE, 108 | 109 | /// The minimal I/O size for this chunk. 110 | pub sector_size: U32LE, 111 | 112 | /// The number of replication stripes. 113 | pub num_stripes: U16LE, 114 | 115 | /// The number of sub-stripes. This is only used for RAID-10. 116 | pub sub_stripes: U16LE, 117 | 118 | /// The first of one or more stripes that map to device extents. 119 | pub stripe: [Stripe], 120 | } 121 | -------------------------------------------------------------------------------- /src/chunk/mod.rs: -------------------------------------------------------------------------------- 1 | #[allow(clippy::module_inception)] 2 | mod chunk; 3 | mod stripe; 4 | 5 | pub use chunk::*; 6 | pub use stripe::*; 7 | -------------------------------------------------------------------------------- /src/chunk/stripe.rs: -------------------------------------------------------------------------------- 1 | use crate::UuidBytes; 2 | use static_assertions::const_assert_eq; 3 | use zerocopy::little_endian::U64 as U64LE; 4 | use zerocopy_derive::*; 5 | 6 | /// This structure is used to define the backing device storage that compose a 7 | /// [`Chunk`]. 8 | /// 9 | /// [`Chunk`]: crate::Chunk 10 | #[derive( 11 | Copy, Clone, Debug, Hash, PartialEq, IntoBytes, FromBytes, Unaligned, KnownLayout, Immutable, 12 | )] 13 | #[repr(C, packed)] 14 | pub struct Stripe { 15 | /// The ID of the device that contains this stripe. 16 | pub devid: U64LE, 17 | 18 | /// Location of the start of the stripe, in bytes. 19 | /// 20 | /// The length is determined by the `stripe_len` field of the associated 21 | /// `Chunk`. 22 | pub offset: U64LE, 23 | 24 | /// UUID of the device that contains this stripe. 25 | /// 26 | /// This can be used to confirm that the correct device has been retrieved. 27 | pub dev_uuid: UuidBytes, 28 | } 29 | const_assert_eq!(core::mem::size_of::(), 32); 30 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | /// The physical address of the primary superblock. 2 | pub const PRIMARY_SUPERBLOCK_ADDR: u64 = 0x10000; 3 | 4 | /// The physical addresses of superblocks. 5 | pub const SUPERBLOCK_ADDRS: [u64; 3] = [PRIMARY_SUPERBLOCK_ADDR, 0x4000000, 0x4000000000]; 6 | 7 | pub const MAGIC: u64 = 0x4D5F53665248425F; 8 | 9 | /// Corresponds to `BTRFS_CSUM_SIZE`. 10 | pub const CSUM_SIZE: usize = 32; 11 | 12 | /// Corresponds to `BTRFS_FSID_SIZE`. 13 | pub const FSID_SIZE: usize = 16; 14 | 15 | /// Corresponds to `BTRFS_LABEL_SIZE`. 16 | pub const LABEL_SIZE: usize = 256; 17 | 18 | /// Corresponds to `BTRFS_UUID_SIZE`. 19 | pub const UUID_SIZE: usize = 16; 20 | 21 | /// Corresponds to `BTRFS_SYSTEM_CHUNK_ARRAY_SIZE`. 22 | pub const MAX_SYSTEM_CHUNK_ARRAY_SIZE: usize = 2048; 23 | 24 | /// Corresponds to `BTRFS_NUM_BACKUP_ROOTS`. 25 | pub const NUM_BACKUP_ROOTS: usize = 4; 26 | -------------------------------------------------------------------------------- /src/core/dev_item.rs: -------------------------------------------------------------------------------- 1 | use crate::UuidBytes; 2 | use static_assertions::const_assert_eq; 3 | use zerocopy::little_endian::{U32 as U32LE, U64 as U64LE}; 4 | use zerocopy_derive::*; 5 | 6 | /// Represents a complete block device. 7 | #[derive(Copy, Clone, Debug, Hash, IntoBytes, FromBytes, Unaligned, KnownLayout, Immutable)] 8 | #[repr(C, packed)] 9 | pub struct DevItem { 10 | /// The internal btrfs device ID. 11 | /// 12 | /// This should match the devid found in the filesystem's list of devices. 13 | pub devid: U64LE, 14 | 15 | /// The size of the device. 16 | pub total_bytes: U64LE, 17 | 18 | /// The bytes in use by the filesystem on the device. 19 | pub bytes_used: U64LE, 20 | 21 | /// The optimal I/O alignment for this device. 22 | pub io_align: U32LE, 23 | 24 | /// The optimal I/O width for this device. 25 | pub io_width: U32LE, 26 | 27 | /// The minimum I/O size for this device. 28 | pub sector_size: U32LE, 29 | 30 | /// The type and info for this device. 31 | pub dev_type: U64LE, 32 | 33 | /// The expected generation for this device. 34 | pub generation: U64LE, 35 | 36 | /// The starting byte of this partition on the device, to allow for stripe 37 | /// alignment. 38 | pub start_offset: U64LE, 39 | 40 | /// Grouping information for allocation decisions. 41 | pub dev_group: U32LE, 42 | 43 | /// The seek speed of the device on a scale from 0 to 100, where 100 is the 44 | /// fastest. 45 | pub seek_speed: u8, 46 | 47 | /// The bandwidth of the device on a scale from 0 to 100, where 100 is the 48 | /// fastest. 49 | pub bandwith: u8, 50 | 51 | /// The generated UUID for this device. 52 | pub uuid: UuidBytes, 53 | 54 | /// The UUID of the filesystem that owns this device. 55 | pub fsid: UuidBytes, 56 | } 57 | const_assert_eq!(core::mem::size_of::(), 98); 58 | -------------------------------------------------------------------------------- /src/core/inode_item.rs: -------------------------------------------------------------------------------- 1 | use crate::Time; 2 | use bitflags::bitflags; 3 | use static_assertions::const_assert_eq; 4 | use zerocopy::little_endian::{U32 as U32LE, U64 as U64LE}; 5 | use zerocopy_derive::*; 6 | 7 | /// Contains traditional inode data and attributes. 8 | #[derive(Copy, Clone, Debug, Hash, IntoBytes, FromBytes, Unaligned, KnownLayout, Immutable)] 9 | #[repr(C, packed)] 10 | pub struct InodeItem { 11 | // FIXME: add documentation! 12 | pub generation: U64LE, 13 | 14 | // FIXME: add documentation! 15 | pub transid: U64LE, 16 | 17 | /// The size of a file, in bytes. 18 | pub size: U64LE, 19 | 20 | /// The size allocated to the file, in bytes. 21 | /// 22 | /// This is equal to the sum of all of the extent data for the inode. 23 | /// This is 0 for directories. 24 | pub nbytes: U64LE, 25 | 26 | /// This contains the byte offset of a block group when structure is a free space inode. 27 | /// 28 | /// This value is unused for normal inodes. 29 | pub block_group: U64LE, 30 | 31 | /// Count of inode references for the inode. 32 | /// 33 | /// When used outside of a file tree, this value is 1. 34 | pub nlink: U32LE, 35 | 36 | /// The user ID of the owner in Unix. 37 | pub uid: U32LE, 38 | 39 | /// The group ID of the group owner in Unix. 40 | pub gid: U32LE, 41 | 42 | /// The Unix protection mode. 43 | pub mode: U32LE, 44 | 45 | /// The device identifier (if a special file). 46 | pub rdev: U64LE, 47 | 48 | /// Flags for the inode. See [InodeFlags] for values. 49 | pub flags: U64LE, 50 | 51 | /// A sequence number used for compatibility with NFS. 52 | /// 53 | /// This value is initialized to 0 and incremented each time [mtime] is updated. 54 | /// 55 | /// [mtime]: InodeItem::mtime 56 | pub sequence: U64LE, 57 | 58 | pub _unused: [u64; 4], 59 | 60 | /// Timestamp of the last access to the inode. 61 | pub atime: Time, 62 | 63 | /// Timestamp of the last change to the inode's properties. 64 | pub ctime: Time, 65 | 66 | /// Timestamp of the last change to the inode's contents. 67 | pub mtime: Time, 68 | 69 | /// Timestamp of the creation of the inode. 70 | pub otime: Time, 71 | } 72 | const_assert_eq!(core::mem::size_of::(), 160); 73 | 74 | bitflags! { 75 | pub struct InodeFlags: u64 { 76 | /// Do not perform checksum operations. 77 | const NO_DATA_SUM = 0x1; 78 | 79 | /// Do not perform copy-on-write for data extents when the reference count is 1. 80 | const NO_DATA_COW = 0x2; 81 | 82 | /// The inode is read-only, regardless of permissions or ownership. 83 | const READ_ONLY = 0x4; 84 | 85 | /// Do not perform compression. 86 | const NO_COMPRESS = 0x8; 87 | 88 | /// Denotes preallocated extents are present. Hints that filesystem should avoid CoWing 89 | /// those extents. 90 | const PREALLOC = 0x10; 91 | 92 | /// Operations on this inode should be performed synchronously. 93 | const SYNC = 0x20; 94 | 95 | /// The inode is read-only; regardless of permissions or ownership. 96 | const IMMUTABLE = 0x40; 97 | 98 | /// The inode is append-only. 99 | const APPEND = 0x80; 100 | 101 | /// Do not consider the inode for dumping when using the Unix program `dump`. 102 | const NO_DUMP = 0x100; 103 | 104 | /// Do not update [`atime`](InodeItem::atime). 105 | const NO_ATIME = 0x200; 106 | 107 | /// Operations on directory should be performed synchronously. 108 | const DIR_SYNC = 0x400; 109 | 110 | /// Perform compression for the inode. 111 | const COMPRESS = 0x800; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/core/key.rs: -------------------------------------------------------------------------------- 1 | use static_assertions::const_assert_eq; 2 | use zerocopy::little_endian::U64 as U64LE; 3 | use zerocopy_derive::*; 4 | 5 | /// A key used to describe and locate any item in any tree. 6 | #[derive(Copy, Clone, Debug, IntoBytes, FromBytes, Unaligned, KnownLayout, Immutable)] 7 | #[repr(C, packed)] 8 | pub struct Key { 9 | pub objectid: U64LE, 10 | pub key_type: u8, 11 | pub offset: U64LE, 12 | } 13 | const_assert_eq!(core::mem::size_of::(), 17); 14 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | mod dev_item; 2 | mod inode_item; 3 | mod key; 4 | mod node; 5 | mod root_backup; 6 | mod root_item; 7 | mod root_ref; 8 | mod super_block; 9 | mod time; 10 | 11 | pub use dev_item::*; 12 | pub use inode_item::*; 13 | pub use key::*; 14 | pub use node::*; 15 | pub use root_backup::*; 16 | pub use root_item::*; 17 | pub use root_ref::*; 18 | pub use super_block::*; 19 | pub use time::*; 20 | -------------------------------------------------------------------------------- /src/core/node.rs: -------------------------------------------------------------------------------- 1 | use crate::{Key, UuidBytes, constants::CSUM_SIZE}; 2 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 3 | use static_assertions::const_assert_eq; 4 | use strum::EnumIter; 5 | use zerocopy::little_endian::{U32 as U32LE, U64 as U64LE}; 6 | use zerocopy_derive::*; 7 | 8 | /// The data stored at the start of every node. 9 | #[derive(Clone, Debug, IntoBytes, TryFromBytes, Unaligned, KnownLayout)] 10 | #[repr(C, packed)] 11 | pub struct Header { 12 | /// The checksum of everything after this field, including the 13 | /// internal/leaf node specific part. 14 | pub csum: [u8; CSUM_SIZE], 15 | 16 | /// The filesystem UUID. 17 | pub fs_uuid: UuidBytes, 18 | 19 | /// The logical address of this node. 20 | pub logical_address: U64LE, 21 | 22 | /// The first 7 bits represent flags. 23 | pub flags: [u8; 7], 24 | 25 | /// The backref revision, which maps to a [`BackrefRevision`] value. 26 | pub backref_rev: BackrefRevision, 27 | 28 | /// The chunk tree UUID. 29 | pub chunk_tree_uuid: UuidBytes, 30 | 31 | /// The generation of this node. 32 | pub generation: U64LE, 33 | 34 | /// The ID of the tree containing this node. 35 | pub tree_id: U64LE, 36 | 37 | /// The number of items held in this node. 38 | pub num_items: U32LE, 39 | 40 | /// The level of this node. 0 indicates it is a leaf node. 41 | pub level: u8, 42 | } 43 | const_assert_eq!(core::mem::size_of::
(), 101); 44 | 45 | /// For internal (non-leaf) nodes, the [node header] is followed by a dynamic amount of key 46 | /// pointers. 47 | /// 48 | /// [node header]: Header 49 | #[derive(Copy, Clone, Debug, IntoBytes, FromBytes, Unaligned, KnownLayout)] 50 | #[repr(C, packed)] 51 | pub struct KeyPointer { 52 | pub key: Key, 53 | pub block_pointer: U64LE, 54 | pub generation: U64LE, 55 | } 56 | const_assert_eq!(core::mem::size_of::(), 33); 57 | 58 | /// For leaf nodes, the [node header] is followed by a dynamic number of items. 59 | /// 60 | /// The item data is stored at the end of the node, as pointed to by the [offset] and [size]. 61 | /// The contents of the item are specified in the [key]. 62 | /// 63 | /// [node header]: Header 64 | /// [offset]: Item::offset 65 | /// [size]: Item::size 66 | /// [key]: Item::key 67 | #[derive(Copy, Clone, Debug, IntoBytes, FromBytes, Unaligned, KnownLayout)] 68 | #[repr(C, packed)] 69 | pub struct Item { 70 | /// The key that contains the ID and contents of this [Item]. 71 | pub key: Key, 72 | 73 | /// Offset relative to the end of the header. 74 | pub offset: U32LE, 75 | 76 | /// The size of the data. 77 | pub size: U32LE, 78 | } 79 | const_assert_eq!(core::mem::size_of::(), 25); 80 | 81 | #[derive( 82 | Copy, 83 | Clone, 84 | Debug, 85 | Hash, 86 | PartialEq, 87 | EnumIter, 88 | IntoPrimitive, 89 | TryFromPrimitive, 90 | IntoBytes, 91 | TryFromBytes, 92 | Unaligned, 93 | KnownLayout, 94 | )] 95 | #[repr(u8)] 96 | pub enum BackrefRevision { 97 | /// Indicates asn old filesystem. 98 | Old = 0, 99 | 100 | /// Indicates a new filesystem. 101 | Mixed = 1, 102 | } 103 | const_assert_eq!(core::mem::size_of::(), 1); 104 | -------------------------------------------------------------------------------- /src/core/root_backup.rs: -------------------------------------------------------------------------------- 1 | use static_assertions::const_assert_eq; 2 | use zerocopy::little_endian::U64 as U64LE; 3 | use zerocopy_derive::*; 4 | 5 | #[derive(Copy, Clone, Debug, FromBytes, IntoBytes, Unaligned, KnownLayout)] 6 | #[repr(C, packed)] 7 | pub struct RootBackup { 8 | pub tree_root: U64LE, 9 | pub tree_root_gen: U64LE, 10 | 11 | pub chunk_root: U64LE, 12 | pub chunk_root_gen: U64LE, 13 | 14 | pub extent_root: U64LE, 15 | pub extent_root_gen: U64LE, 16 | 17 | pub fs_root: U64LE, 18 | pub fs_root_gen: U64LE, 19 | 20 | pub dev_root: U64LE, 21 | pub dev_root_gen: U64LE, 22 | 23 | pub csum_root: U64LE, 24 | pub csum_root_gen: U64LE, 25 | 26 | pub total_bytes: U64LE, 27 | pub bytes_used: U64LE, 28 | 29 | pub num_devices: U64LE, 30 | 31 | /// Reserved for future use. 32 | pub _unused_u64s: [u64; 4], 33 | 34 | pub tree_root_level: u8, 35 | 36 | pub chunk_root_level: u8, 37 | 38 | pub extent_root_level: u8, 39 | 40 | pub fs_root_level: u8, 41 | 42 | pub dev_root_level: u8, 43 | 44 | pub csum_root_level: u8, 45 | 46 | /// Reserved for future use. 47 | pub _unused_u8s: [u8; 10], 48 | } 49 | const_assert_eq!(core::mem::size_of::(), 168); 50 | -------------------------------------------------------------------------------- /src/core/root_item.rs: -------------------------------------------------------------------------------- 1 | use crate::{InodeItem, Key, Time, UuidBytes}; 2 | use static_assertions::const_assert_eq; 3 | use zerocopy::little_endian::{U32 as U32LE, U64 as U64LE}; 4 | use zerocopy_derive::*; 5 | 6 | /// Defines the location and parameters of the root of a b-tree. 7 | #[derive(Copy, Clone, Debug, IntoBytes, FromBytes, Unaligned, KnownLayout)] 8 | #[repr(C, packed)] 9 | pub struct RootItem { 10 | pub inode: InodeItem, 11 | 12 | pub generation: U64LE, 13 | 14 | pub root_dirid: U64LE, 15 | 16 | pub bytenr: U64LE, 17 | 18 | /// Currently unused. Always 0. 19 | pub byte_limit: U64LE, 20 | 21 | /// Currently unused. 22 | pub bytes_used: U64LE, 23 | 24 | /// The transaction ID of the last transaction that created a snapshot of this root. 25 | pub last_snapshot: U64LE, 26 | 27 | pub flags: U64LE, 28 | 29 | /// Only 0 or 1. Historically contained a reference count. 30 | pub refs: U32LE, 31 | 32 | /// Contains the key of the last dropped item during subvolume removal or relocation. 33 | /// 34 | /// Value will be zeroed out otherwise. 35 | pub drop_progress: Key, 36 | 37 | /// The tree level of the node referenced in [drop_progress](RootItem::drop_progress). 38 | pub drop_level: u8, 39 | 40 | /// The height of this root's tree. 41 | pub level: u8, 42 | 43 | /// Value to help determine whether this root has been modified by an older btrfs 44 | /// implementation. 45 | /// 46 | /// If the value is equal to [generation], the fields below are valid. Otherwise, this indicates 47 | /// the fields are invalid but recoverable. 48 | /// 49 | /// [generation]: RootItem::generation 50 | pub generation_v2: U64LE, 51 | 52 | /// The subvolume's UUID. 53 | pub uuid: UuidBytes, 54 | 55 | /// The parent's subvolume UUID. 56 | /// 57 | /// This is used during send/receive. 58 | pub parent_uuid: UuidBytes, 59 | 60 | /// The received UUID. 61 | /// 62 | /// This is used during send/receive. 63 | pub received_uuid: UuidBytes, 64 | 65 | /// The transaction ID of the last transaction that modified the tree. 66 | /// 67 | /// Note: some operations like internal caches or relocation will not update this value. 68 | pub ctransid: U64LE, 69 | 70 | /// The transaction ID of the transaction that created the tree. 71 | pub otransid: U64LE, 72 | 73 | /// The transaction ID for the transaction that sent this subvolume. 74 | /// 75 | /// This value is non-zero for a received subvolume. 76 | pub stransid: U64LE, 77 | 78 | /// The transaction ID for the transaction that received this subvolume. 79 | /// 80 | /// This value is non-zero for a received subvolume. 81 | pub rtransid: U64LE, 82 | 83 | /// The timestamp of the [`ctransid`](RootItem::ctransid). 84 | pub ctime: Time, 85 | 86 | /// The timestamp of the [`otransid`](RootItem::otransid). 87 | pub otime: Time, 88 | 89 | /// The timestamp of the [`stransid`](RootItem::stransid). 90 | pub stime: Time, 91 | 92 | /// The timestamp of the [`rtransid`](RootItem::rtransid). 93 | pub rtime: Time, 94 | 95 | /// Currently unused. Reserved for future use. 96 | pub _unused: [u64; 8], 97 | } 98 | const_assert_eq!(core::mem::size_of::(), 439); 99 | -------------------------------------------------------------------------------- /src/core/root_ref.rs: -------------------------------------------------------------------------------- 1 | use static_assertions::const_assert_eq; 2 | use zerocopy::little_endian::{U16 as U16LE, U64 as U64LE}; 3 | use zerocopy_derive::*; 4 | 5 | /// References a subvolume filesystem tree root. This is used for both forward and 6 | /// backward root references. 7 | /// 8 | /// The name of the tree is stored after the end of the struct. 9 | #[derive(Copy, Clone, Debug, IntoBytes, FromBytes, Unaligned, KnownLayout)] 10 | #[repr(C, packed)] 11 | pub struct RootRef { 12 | /// The subtree ID. 13 | pub dirid: U64LE, 14 | 15 | /// The directory sequence number of the subtree entry. 16 | pub sequence: U64LE, 17 | 18 | /// The length of the subtree name, stored after this field. 19 | pub name_len: U16LE, 20 | } 21 | const_assert_eq!(core::mem::size_of::(), 18); 22 | -------------------------------------------------------------------------------- /src/core/super_block.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | DevItem, RootBackup, 3 | constants::{CSUM_SIZE, FSID_SIZE, LABEL_SIZE, MAX_SYSTEM_CHUNK_ARRAY_SIZE, NUM_BACKUP_ROOTS}, 4 | }; 5 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 6 | use static_assertions::const_assert_eq; 7 | use strum::EnumIter; 8 | use zerocopy::little_endian::{U32 as U32LE, U64 as U64LE}; 9 | use zerocopy_derive::*; 10 | 11 | /// The layout of the superblock. A valid superblock must exist for most btrfs implementations to 12 | /// mount the filesystem. 13 | /// 14 | /// The primary superblock is located at [`PRIMARY_SUPERBLOCK_ADDR`]. 15 | /// 16 | /// There are additional copies of the superblock located at [`SUPERBLOCK_ADDRS`], if those addresses 17 | /// are valid, respectively. 18 | /// 19 | /// 20 | /// [`PRIMARY_SUPERBLOCK_ADDR`]: crate::constants::PRIMARY_SUPERBLOCK_ADDR 21 | /// [`SUPERBLOCK_ADDRS`]: crate::constants::SUPERBLOCK_ADDRS 22 | /// 23 | /// # Resources 24 | /// 25 | /// * 26 | /// * 27 | #[derive(Copy, Clone, IntoBytes, TryFromBytes, Unaligned, KnownLayout)] 28 | #[repr(C, packed)] 29 | pub struct SuperBlock { 30 | /// Checksum of everything past this field. 31 | pub csum: [u8; CSUM_SIZE], 32 | 33 | /// Filesystem UUID. 34 | pub fsid: [u8; FSID_SIZE], 35 | 36 | /// The physical address of this block. 37 | pub bytenr: U64LE, 38 | 39 | /// Flags 40 | pub flags: U64LE, 41 | 42 | /// The magic must be equal to `"_BHRfS_M"` in ASCII. 43 | pub magic: U64LE, 44 | 45 | /// The generation of the superblock. In SSD mode, not all superblocks may be updated, so the 46 | /// latest generation superblock should be used. 47 | pub generation: U64LE, 48 | 49 | /// The logical address of the root tree's root. 50 | pub root: U64LE, 51 | 52 | /// The logical address of the chunk tree's root. 53 | pub chunk_root: U64LE, 54 | 55 | /// The logical address of the log tree's root. 56 | pub log_root: U64LE, 57 | 58 | /// FIXME: find out what this is! 59 | pub log_root_transid: U64LE, 60 | 61 | /// FIXME: document this! 62 | pub total_bytes: U64LE, 63 | 64 | pub bytes_used: U64LE, 65 | 66 | /// The root directory's object ID, which is typically 6. 67 | pub root_dir_objectid: U64LE, 68 | 69 | /// The number of devices the current filesystem spans. 70 | pub num_devices: U64LE, 71 | 72 | /// The size of a sector. 73 | pub sectorsize: U32LE, 74 | 75 | pub nodesize: U32LE, 76 | 77 | /// This is currently unused. 78 | pub leafsize: U32LE, 79 | 80 | pub stripesize: U32LE, 81 | 82 | /// The size of [`sys_chunk_array`] found in the superblock. 83 | /// 84 | /// [`sys_chunk_array`]: SuperBlock::sys_chunk_array 85 | pub sys_chunk_array_size: U32LE, 86 | 87 | pub chunk_root_generation: U64LE, 88 | 89 | pub compat_flags: U64LE, 90 | 91 | /// Only implementations that support these flags can write to the filesystem. 92 | pub compat_ro_flags: U64LE, 93 | 94 | /// Only implementations that support these flags can use the filesystem. 95 | pub incompat_flags: U64LE, 96 | 97 | /// The checksum type. 98 | /// 99 | /// This should correspond with a value from [`ChecksumType`]. 100 | /// 101 | /// [`ChecksumType`]: crate::ChecksumType 102 | pub csum_type: ChecksumType, 103 | 104 | pub root_level: u8, 105 | 106 | pub chunk_root_level: u8, 107 | 108 | pub log_root_level: u8, 109 | 110 | pub dev_item: DevItem, 111 | 112 | /// The label represented as a null-terminated UTF-8 string. May not contain `'/'` or `'\\'`. 113 | pub label: [u8; LABEL_SIZE], 114 | 115 | pub cache_generation: U64LE, 116 | 117 | pub uuid_tree_generation: U64LE, 118 | 119 | /// Reserved for extensibility. 120 | pub _reserved: [U64LE; 30], 121 | 122 | pub sys_chunk_array: [u8; MAX_SYSTEM_CHUNK_ARRAY_SIZE], 123 | 124 | pub super_roots: [RootBackup; NUM_BACKUP_ROOTS], 125 | 126 | pub _unused1: [u8; 565], 127 | } 128 | const_assert_eq!(core::mem::size_of::(), 4096); 129 | 130 | /// The hashing algorithm used for checksumming. 131 | #[derive( 132 | Copy, 133 | Clone, 134 | Debug, 135 | Hash, 136 | PartialEq, 137 | EnumIter, 138 | IntoPrimitive, 139 | TryFromPrimitive, 140 | TryFromBytes, 141 | IntoBytes, 142 | KnownLayout, 143 | )] 144 | #[repr(u16)] 145 | pub enum ChecksumType { 146 | CRC32C = 0u16.to_le(), 147 | XXHASH64 = 1u16.to_le(), 148 | SHA256 = 2u16.to_le(), 149 | BLAKE2b = 3u16.to_le(), 150 | } 151 | const_assert_eq!(core::mem::size_of::(), 2); 152 | -------------------------------------------------------------------------------- /src/core/time.rs: -------------------------------------------------------------------------------- 1 | use static_assertions::const_assert_eq; 2 | use zerocopy::little_endian::{I64 as I64LE, U32 as U32LE}; 3 | use zerocopy_derive::*; 4 | 5 | /// The layout of timestamps on disk. 6 | #[derive( 7 | Copy, 8 | Clone, 9 | Debug, 10 | Hash, 11 | PartialEq, 12 | Eq, 13 | PartialOrd, 14 | IntoBytes, 15 | FromBytes, 16 | Unaligned, 17 | KnownLayout, 18 | Immutable, 19 | )] 20 | #[repr(C, packed)] 21 | pub struct Time { 22 | /// The timestamp using Unix time convention. 23 | pub timestamp: I64LE, 24 | 25 | /// The number of nanoseconds past the beginning of the second denoted in [timestamp]. 26 | /// 27 | /// [timestamp]: Time::timestamp 28 | pub nanoseconds: U32LE, 29 | } 30 | const_assert_eq!(core::mem::size_of::