use crate::tree::Key;
use btrfs_disk::{
raw,
util::{write_le_u16, write_le_u32, write_le_u64, write_uuid},
};
use std::mem;
use uuid::Uuid;
pub fn root_item(
generation: u64,
bytenr: u64,
root_dirid: u64,
nodesize: u32,
) -> Vec<u8> {
let size = mem::size_of::<raw::btrfs_root_item>();
let mut buf = vec![0u8; size];
let inode_size = mem::size_of::<raw::btrfs_inode_item>();
write_le_u64(&mut buf, 0, generation); write_le_u32(&mut buf, 40, 1); write_le_u32(&mut buf, 52, 0o40755);
write_le_u64(&mut buf, inode_size, generation);
write_le_u64(&mut buf, inode_size + 8, root_dirid);
write_le_u64(&mut buf, inode_size + 16, bytenr);
write_le_u64(&mut buf, inode_size + 32, nodesize as u64); write_le_u32(&mut buf, inode_size + 56, 1);
let level_off = mem::offset_of!(raw::btrfs_root_item, level);
write_le_u64(&mut buf, level_off + 1, generation);
buf
}
pub fn chunk_tree_root_item(
generation: u64,
bytenr: u64,
nodesize: u32,
) -> Vec<u8> {
root_item(
generation,
bytenr,
raw::BTRFS_FIRST_CHUNK_TREE_OBJECTID as u64,
nodesize,
)
}
pub fn extent_item(
refs: u64,
generation: u64,
skinny: bool,
owner_root: u64,
) -> Vec<u8> {
let base_size = mem::size_of::<raw::btrfs_extent_item>();
let tree_block_info_size = if skinny {
0
} else {
mem::size_of::<raw::btrfs_tree_block_info>()
};
let inline_ref_size = 9;
let size = base_size + tree_block_info_size + inline_ref_size;
let mut buf = vec![0u8; size];
write_le_u64(&mut buf, 0, refs);
write_le_u64(&mut buf, 8, generation);
write_le_u64(&mut buf, 16, raw::BTRFS_EXTENT_FLAG_TREE_BLOCK as u64);
let ref_off = base_size + tree_block_info_size;
buf[ref_off] = raw::BTRFS_TREE_BLOCK_REF_KEY as u8;
write_le_u64(&mut buf, ref_off + 1, owner_root);
buf
}
pub fn block_group_item(used: u64, chunk_objectid: u64, flags: u64) -> Vec<u8> {
let size = mem::size_of::<raw::btrfs_block_group_item>();
let mut buf = vec![0u8; size];
write_le_u64(&mut buf, 0, used);
write_le_u64(&mut buf, 8, chunk_objectid);
write_le_u64(&mut buf, 16, flags);
buf
}
pub fn dev_item(
devid: u64,
total_bytes: u64,
bytes_used: u64,
sector_size: u32,
dev_uuid: &Uuid,
fsid: &Uuid,
) -> Vec<u8> {
let size = mem::size_of::<raw::btrfs_dev_item>();
let mut buf = vec![0u8; size];
write_le_u64(&mut buf, 0, devid);
write_le_u64(&mut buf, 8, total_bytes);
write_le_u64(&mut buf, 16, bytes_used);
write_le_u32(&mut buf, 24, sector_size); write_le_u32(&mut buf, 28, sector_size); write_le_u32(&mut buf, 32, sector_size); write_uuid(&mut buf, 66, dev_uuid);
write_uuid(&mut buf, 82, fsid);
buf
}
use crate::layout::StripeInfo;
pub fn chunk_item(
length: u64,
owner: u64,
chunk_type: u64,
io_align: u32,
io_width: u32,
sector_size: u32,
stripes: &[StripeInfo],
) -> Vec<u8> {
let base_size = mem::offset_of!(raw::btrfs_chunk, stripe);
let stripe_entry_size = mem::size_of::<raw::btrfs_stripe>();
let size = base_size + stripes.len() * stripe_entry_size;
let mut buf = vec![0u8; size];
write_le_u64(&mut buf, 0, length);
write_le_u64(&mut buf, 8, owner);
write_le_u64(&mut buf, 16, crate::layout::STRIPE_LEN);
write_le_u64(&mut buf, 24, chunk_type);
write_le_u32(&mut buf, 32, io_align);
write_le_u32(&mut buf, 36, io_width);
write_le_u32(&mut buf, 40, sector_size);
write_le_u16(&mut buf, 44, stripes.len() as u16);
write_le_u16(&mut buf, 46, 0);
for (i, stripe) in stripes.iter().enumerate() {
let off = base_size + i * stripe_entry_size;
write_le_u64(&mut buf, off, stripe.devid);
write_le_u64(&mut buf, off + 8, stripe.offset);
write_uuid(&mut buf, off + 16, &stripe.dev_uuid);
}
buf
}
pub fn chunk_item_bootstrap(
length: u64,
owner: u64,
chunk_type: u64,
sector_size: u32,
stripe: &StripeInfo,
) -> Vec<u8> {
chunk_item(
length,
owner,
chunk_type,
sector_size,
sector_size,
sector_size,
&[StripeInfo {
devid: stripe.devid,
offset: stripe.offset,
dev_uuid: stripe.dev_uuid,
}],
)
}
pub fn dev_extent(
chunk_tree: u64,
chunk_objectid: u64,
chunk_offset: u64,
length: u64,
chunk_tree_uuid: &Uuid,
) -> Vec<u8> {
let size = mem::size_of::<raw::btrfs_dev_extent>();
let mut buf = vec![0u8; size];
write_le_u64(&mut buf, 0, chunk_tree);
write_le_u64(&mut buf, 8, chunk_objectid);
write_le_u64(&mut buf, 16, chunk_offset);
write_le_u64(&mut buf, 24, length);
write_uuid(&mut buf, 32, chunk_tree_uuid);
buf
}
pub fn dev_stats_zeroed() -> Vec<u8> {
vec![0u8; 5 * 8]
}
pub fn free_space_info(extent_count: u32, flags: u32) -> Vec<u8> {
let mut buf = vec![0u8; 8];
write_le_u32(&mut buf, 0, extent_count);
write_le_u32(&mut buf, 4, flags);
buf
}
pub fn inode_item_dir(generation: u64, nbytes: u64, now_sec: u64) -> Vec<u8> {
let size = mem::size_of::<raw::btrfs_inode_item>();
let mut buf = vec![0u8; size];
write_le_u64(&mut buf, 0, generation); write_le_u64(&mut buf, 24, nbytes); write_le_u32(&mut buf, 40, 1); write_le_u32(&mut buf, 52, 0o40755);
let ts_off = mem::offset_of!(raw::btrfs_inode_item, atime);
let ts_size = mem::size_of::<raw::btrfs_timespec>();
for i in 0..4 {
write_le_u64(&mut buf, ts_off + i * ts_size, now_sec);
write_le_u32(&mut buf, ts_off + i * ts_size + 8, 0);
}
buf
}
pub fn inode_ref(index: u64, name: &[u8]) -> Vec<u8> {
let size = 8 + 2 + name.len(); let mut buf = vec![0u8; size];
write_le_u64(&mut buf, 0, index);
write_le_u16(&mut buf, 8, name.len() as u16);
buf[10..10 + name.len()].copy_from_slice(name);
buf
}
pub fn disk_key(key: &Key) -> Vec<u8> {
let mut buf = vec![0u8; 17];
key.write_to(&mut buf, 0);
buf
}
#[cfg(test)]
mod tests {
use super::*;
use btrfs_disk::items;
#[test]
fn roundtrip_block_group_item() {
let data = block_group_item(
4096,
raw::BTRFS_FIRST_CHUNK_TREE_OBJECTID as u64,
raw::BTRFS_BLOCK_GROUP_SYSTEM as u64,
);
let parsed = items::BlockGroupItem::parse(&data).unwrap();
assert_eq!(parsed.used, 4096);
assert_eq!(
parsed.chunk_objectid,
raw::BTRFS_FIRST_CHUNK_TREE_OBJECTID as u64
);
assert_eq!(parsed.flags, items::BlockGroupFlags::SYSTEM);
}
#[test]
fn roundtrip_dev_item() {
let uuid =
Uuid::parse_str("deadbeef-dead-beef-dead-beefdeadbeef").unwrap();
let fsid =
Uuid::parse_str("cafebabe-cafe-babe-cafe-babecafebabe").unwrap();
let data = dev_item(1, 1_000_000_000, 4_000_000, 4096, &uuid, &fsid);
let parsed = items::DeviceItem::parse(&data).unwrap();
assert_eq!(parsed.devid, 1);
assert_eq!(parsed.total_bytes, 1_000_000_000);
assert_eq!(parsed.bytes_used, 4_000_000);
assert_eq!(parsed.io_align, 4096);
assert_eq!(parsed.io_width, 4096);
assert_eq!(parsed.sector_size, 4096);
assert_eq!(parsed.uuid, uuid);
assert_eq!(parsed.fsid, fsid);
}
#[test]
fn roundtrip_chunk_item() {
let uuid =
Uuid::parse_str("deadbeef-dead-beef-dead-beefdeadbeef").unwrap();
let stripe = StripeInfo {
devid: 1,
offset: 0x100000,
dev_uuid: uuid,
};
let data = chunk_item_bootstrap(
4 * 1024 * 1024,
raw::BTRFS_EXTENT_TREE_OBJECTID as u64,
raw::BTRFS_BLOCK_GROUP_SYSTEM as u64,
4096,
&stripe,
);
let parsed = items::ChunkItem::parse(&data).unwrap();
assert_eq!(parsed.length, 4 * 1024 * 1024);
assert_eq!(parsed.owner, raw::BTRFS_EXTENT_TREE_OBJECTID as u64);
assert_eq!(parsed.chunk_type, items::BlockGroupFlags::SYSTEM);
assert_eq!(parsed.num_stripes, 1);
assert_eq!(parsed.stripes.len(), 1);
assert_eq!(parsed.stripes[0].devid, 1);
assert_eq!(parsed.stripes[0].offset, 0x100000);
assert_eq!(parsed.stripes[0].dev_uuid, uuid);
}
#[test]
fn roundtrip_dev_extent() {
let uuid =
Uuid::parse_str("deadbeef-dead-beef-dead-beefdeadbeef").unwrap();
let data = dev_extent(
raw::BTRFS_CHUNK_TREE_OBJECTID as u64,
raw::BTRFS_FIRST_CHUNK_TREE_OBJECTID as u64,
0x100000,
4 * 1024 * 1024,
&uuid,
);
let parsed = items::DeviceExtent::parse(&data).unwrap();
assert_eq!(parsed.chunk_tree, raw::BTRFS_CHUNK_TREE_OBJECTID as u64);
assert_eq!(
parsed.chunk_objectid,
raw::BTRFS_FIRST_CHUNK_TREE_OBJECTID as u64
);
assert_eq!(parsed.chunk_offset, 0x100000);
assert_eq!(parsed.length, 4 * 1024 * 1024);
assert_eq!(parsed.chunk_tree_uuid, uuid);
}
#[test]
fn roundtrip_free_space_info() {
let data = free_space_info(3, 0);
let parsed = items::FreeSpaceInfo::parse(&data).unwrap();
assert_eq!(parsed.extent_count, 3);
assert_eq!(parsed.flags, items::FreeSpaceInfoFlags::empty());
}
#[test]
fn roundtrip_root_item() {
let data = root_item(
1,
0x100000,
raw::BTRFS_FIRST_FREE_OBJECTID as u64,
16384,
);
let parsed = items::RootItem::parse(&data).unwrap();
assert_eq!(parsed.generation, 1);
assert_eq!(parsed.bytenr, 0x100000);
assert_eq!(parsed.root_dirid, raw::BTRFS_FIRST_FREE_OBJECTID as u64);
assert_eq!(parsed.refs, 1);
assert_eq!(parsed.generation_v2, 1);
}
#[test]
fn extent_item_skinny_size() {
let data = extent_item(1, 1, true, 5);
assert_eq!(data.len(), mem::size_of::<raw::btrfs_extent_item>() + 9);
}
#[test]
fn extent_item_non_skinny_size() {
let data = extent_item(1, 1, false, 5);
assert_eq!(
data.len(),
mem::size_of::<raw::btrfs_extent_item>()
+ mem::size_of::<raw::btrfs_tree_block_info>()
+ 9
);
}
#[test]
fn dev_stats_zeroed_size() {
let data = dev_stats_zeroed();
assert_eq!(data.len(), 40);
assert!(data.iter().all(|&b| b == 0));
}
#[test]
fn roundtrip_chunk_item_dup() {
let uuid =
Uuid::parse_str("deadbeef-dead-beef-dead-beefdeadbeef").unwrap();
let stripes = [
StripeInfo {
devid: 1,
offset: 5 * 1024 * 1024,
dev_uuid: uuid,
},
StripeInfo {
devid: 1,
offset: 5 * 1024 * 1024 + 32 * 1024 * 1024,
dev_uuid: uuid,
},
];
let data = chunk_item(
32 * 1024 * 1024,
raw::BTRFS_EXTENT_TREE_OBJECTID as u64,
raw::BTRFS_BLOCK_GROUP_METADATA as u64
| raw::BTRFS_BLOCK_GROUP_DUP as u64,
crate::layout::STRIPE_LEN as u32,
crate::layout::STRIPE_LEN as u32,
4096,
&stripes,
);
let parsed = items::ChunkItem::parse(&data).unwrap();
assert_eq!(parsed.length, 32 * 1024 * 1024);
assert_eq!(parsed.num_stripes, 2);
assert_eq!(parsed.stripes.len(), 2);
assert_eq!(parsed.stripes[0].devid, 1);
assert_eq!(parsed.stripes[0].offset, 5 * 1024 * 1024);
assert_eq!(parsed.stripes[1].devid, 1);
assert_eq!(
parsed.stripes[1].offset,
5 * 1024 * 1024 + 32 * 1024 * 1024
);
assert_eq!(parsed.io_align, crate::layout::STRIPE_LEN as u32);
assert_eq!(parsed.io_width, crate::layout::STRIPE_LEN as u32);
}
#[test]
fn roundtrip_chunk_item_non_bootstrap_single() {
let uuid =
Uuid::parse_str("deadbeef-dead-beef-dead-beefdeadbeef").unwrap();
let stripes = [StripeInfo {
devid: 1,
offset: 69 * 1024 * 1024,
dev_uuid: uuid,
}];
let data = chunk_item(
64 * 1024 * 1024,
raw::BTRFS_EXTENT_TREE_OBJECTID as u64,
raw::BTRFS_BLOCK_GROUP_DATA as u64,
crate::layout::STRIPE_LEN as u32,
crate::layout::STRIPE_LEN as u32,
4096,
&stripes,
);
let parsed = items::ChunkItem::parse(&data).unwrap();
assert_eq!(parsed.length, 64 * 1024 * 1024);
assert_eq!(parsed.num_stripes, 1);
assert_eq!(parsed.io_align, crate::layout::STRIPE_LEN as u32);
assert_eq!(parsed.io_width, crate::layout::STRIPE_LEN as u32);
}
}