use crate::{
items,
layout::{BlockLayout, SYSTEM_GROUP_OFFSET, SYSTEM_GROUP_SIZE, TreeId},
superblock::SuperblockBuilder,
tree::{Key, LeafBuilder, LeafHeader},
write::{self, SUPER_INFO_OFFSET},
};
use anyhow::{Context, Result, bail};
use btrfs_disk::raw;
use std::{
fs::{File, OpenOptions},
mem,
os::unix::fs::FileTypeExt,
path::Path,
time::SystemTime,
};
use uuid::Uuid;
pub struct MkfsConfig {
pub nodesize: u32,
pub sectorsize: u32,
pub total_bytes: u64,
pub label: Option<String>,
pub fs_uuid: Uuid,
pub dev_uuid: Uuid,
pub chunk_tree_uuid: Uuid,
pub incompat_flags: u64,
pub compat_ro_flags: u64,
}
impl MkfsConfig {
pub fn default_incompat_flags() -> u64 {
raw::BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF as u64
| raw::BTRFS_FEATURE_INCOMPAT_BIG_METADATA as u64
| raw::BTRFS_FEATURE_INCOMPAT_EXTENDED_IREF as u64
| raw::BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA as u64
| raw::BTRFS_FEATURE_INCOMPAT_NO_HOLES as u64
}
pub fn default_compat_ro_flags() -> u64 {
raw::BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE as u64
| raw::BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID as u64
}
pub fn skinny_metadata(&self) -> bool {
self.incompat_flags & raw::BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA as u64
!= 0
}
pub fn has_free_space_tree(&self) -> bool {
self.compat_ro_flags
& raw::BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE as u64
!= 0
}
pub fn has_block_group_tree(&self) -> bool {
self.compat_ro_flags
& raw::BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE as u64
!= 0
}
}
pub fn make_btrfs(path: &Path, cfg: &MkfsConfig) -> Result<()> {
let min_size = SYSTEM_GROUP_OFFSET + SYSTEM_GROUP_SIZE;
if cfg.total_bytes < min_size {
bail!(
"device too small: {} bytes, need at least {} bytes",
cfg.total_bytes,
min_size
);
}
let file = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.with_context(|| format!("failed to open {}", path.display()))?;
let layout = BlockLayout::new(cfg.nodesize);
let generation = 1u64;
let leaf_header = |tree: TreeId| LeafHeader {
fsid: cfg.fs_uuid,
chunk_tree_uuid: cfg.chunk_tree_uuid,
generation,
owner: tree.objectid(),
bytenr: layout.block_addr(tree),
};
let root_tree = build_root_tree(cfg, &layout, &leaf_header)?;
let extent_tree = build_extent_tree(cfg, &layout, &leaf_header)?;
let chunk_tree = build_chunk_tree(cfg, &layout, &leaf_header)?;
let dev_tree = build_dev_tree(cfg, &layout, &leaf_header)?;
let fs_tree = build_root_dir_tree(cfg, &leaf_header(TreeId::Fs))?;
let csum_tree = build_empty_tree(cfg.nodesize, &leaf_header(TreeId::Csum));
let free_space_tree = build_free_space_tree(cfg, &layout, &leaf_header)?;
let data_reloc_tree =
build_root_dir_tree(cfg, &leaf_header(TreeId::DataReloc))?;
let trees = [
(TreeId::Root, root_tree),
(TreeId::Extent, extent_tree),
(TreeId::Chunk, chunk_tree),
(TreeId::Dev, dev_tree),
(TreeId::Fs, fs_tree),
(TreeId::Csum, csum_tree),
(TreeId::FreeSpace, free_space_tree),
(TreeId::DataReloc, data_reloc_tree),
];
for (tree_id, mut block) in trees {
write::fill_csum(&mut block);
write::pwrite_all(&file, &block, layout.block_addr(tree_id))
.with_context(|| {
format!("failed to write {tree_id:?} tree block")
})?;
}
let superblock = build_superblock(cfg, &layout)?;
write::pwrite_all(&file, &superblock, SUPER_INFO_OFFSET)
.context("failed to write superblock")?;
file.sync_all().context("fsync failed")?;
Ok(())
}
fn build_root_tree(
cfg: &MkfsConfig,
layout: &BlockLayout,
leaf_header: &dyn Fn(TreeId) -> LeafHeader,
) -> Result<Vec<u8>> {
let mut leaf = LeafBuilder::new(cfg.nodesize, &leaf_header(TreeId::Root));
let generation = 1u64;
struct RootEntry {
objectid: u64,
bytenr: u64,
is_fs_tree: bool,
}
let mut entries: Vec<RootEntry> = TreeId::ROOT_ITEM_TREES
.iter()
.map(|&tree| RootEntry {
objectid: tree.objectid(),
bytenr: layout.block_addr(tree),
is_fs_tree: tree == TreeId::Fs,
})
.collect();
entries.sort_by_key(|e| e.objectid);
for entry in &entries {
let key = Key::new(entry.objectid, raw::BTRFS_ROOT_ITEM_KEY as u8, 0);
let mut data = items::root_item(
generation,
entry.bytenr,
raw::BTRFS_FIRST_FREE_OBJECTID as u64,
);
if entry.is_fs_tree {
let uuid = Uuid::new_v4();
let inode_size = mem::size_of::<raw::btrfs_inode_item>();
let uuid_off = mem::offset_of!(raw::btrfs_root_item, uuid);
btrfs_disk::util::write_uuid(&mut data, uuid_off, &uuid);
let flags_off = mem::offset_of!(raw::btrfs_inode_item, flags);
btrfs_disk::util::write_le_u64(
&mut data,
flags_off,
raw::BTRFS_INODE_ROOT_ITEM_INIT as u64,
);
btrfs_disk::util::write_le_u64(&mut data, 16, 3);
btrfs_disk::util::write_le_u64(&mut data, 24, cfg.nodesize as u64);
btrfs_disk::util::write_le_u64(
&mut data,
inode_size + 32,
cfg.nodesize as u64,
);
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let ctime_off = mem::offset_of!(raw::btrfs_root_item, ctime);
let otime_off = mem::offset_of!(raw::btrfs_root_item, otime);
let ts_size = mem::size_of::<raw::btrfs_timespec>();
btrfs_disk::util::write_le_u64(&mut data, otime_off, now);
btrfs_disk::util::write_le_u32(&mut data, otime_off + 8, 0);
btrfs_disk::util::write_le_u64(&mut data, ctime_off, now);
btrfs_disk::util::write_le_u32(&mut data, ctime_off + 8, 0);
let _ = ts_size; }
leaf.push(key, &data)
.map_err(|e| anyhow::anyhow!("root tree: {e}"))?;
}
Ok(leaf.finish())
}
fn build_extent_tree(
cfg: &MkfsConfig,
layout: &BlockLayout,
leaf_header: &dyn Fn(TreeId) -> LeafHeader,
) -> Result<Vec<u8>> {
let mut leaf = LeafBuilder::new(cfg.nodesize, &leaf_header(TreeId::Extent));
let generation = 1u64;
let skinny = cfg.skinny_metadata();
let bg_key = Key::new(
SYSTEM_GROUP_OFFSET,
raw::BTRFS_BLOCK_GROUP_ITEM_KEY as u8,
SYSTEM_GROUP_SIZE,
);
let bg_data = items::block_group_item(
layout.total_used(),
raw::BTRFS_FIRST_CHUNK_TREE_OBJECTID as u64,
raw::BTRFS_BLOCK_GROUP_SYSTEM as u64,
);
let add_block_group = !cfg.has_block_group_tree();
let mut bg_inserted = false;
for &tree in &TreeId::ALL {
let addr = layout.block_addr(tree);
if add_block_group && !bg_inserted && addr > SYSTEM_GROUP_OFFSET {
leaf.push(bg_key, &bg_data)
.map_err(|e| anyhow::anyhow!("extent tree: {e}"))?;
bg_inserted = true;
}
let item_type = if skinny {
raw::BTRFS_METADATA_ITEM_KEY as u8
} else {
raw::BTRFS_EXTENT_ITEM_KEY as u8
};
let offset = if skinny { 0 } else { cfg.nodesize as u64 };
let key = Key::new(addr, item_type, offset);
let data = items::extent_item(1, generation, skinny);
leaf.push(key, &data)
.map_err(|e| anyhow::anyhow!("extent tree: {e}"))?;
let ref_key = Key::new(
addr,
raw::BTRFS_TREE_BLOCK_REF_KEY as u8,
tree.objectid(),
);
leaf.push_empty(ref_key)
.map_err(|e| anyhow::anyhow!("extent tree: {e}"))?;
}
Ok(leaf.finish())
}
fn build_chunk_tree(
cfg: &MkfsConfig,
_layout: &BlockLayout,
leaf_header: &dyn Fn(TreeId) -> LeafHeader,
) -> Result<Vec<u8>> {
let mut leaf = LeafBuilder::new(cfg.nodesize, &leaf_header(TreeId::Chunk));
let dev_data = items::dev_item(
1,
cfg.total_bytes,
SYSTEM_GROUP_SIZE,
cfg.sectorsize,
&cfg.dev_uuid,
&cfg.fs_uuid,
);
let dev_key = Key::new(
raw::BTRFS_DEV_ITEMS_OBJECTID as u64,
raw::BTRFS_DEV_ITEM_KEY as u8,
1,
);
leaf.push(dev_key, &dev_data)
.map_err(|e| anyhow::anyhow!("chunk tree: {e}"))?;
let chunk_data = items::chunk_item_single(
SYSTEM_GROUP_SIZE,
raw::BTRFS_EXTENT_TREE_OBJECTID as u64,
raw::BTRFS_BLOCK_GROUP_SYSTEM as u64,
cfg.sectorsize,
1,
SYSTEM_GROUP_OFFSET,
&cfg.dev_uuid,
);
let chunk_key = Key::new(
raw::BTRFS_FIRST_CHUNK_TREE_OBJECTID as u64,
raw::BTRFS_CHUNK_ITEM_KEY as u8,
SYSTEM_GROUP_OFFSET,
);
leaf.push(chunk_key, &chunk_data)
.map_err(|e| anyhow::anyhow!("chunk tree: {e}"))?;
Ok(leaf.finish())
}
fn build_dev_tree(
cfg: &MkfsConfig,
_layout: &BlockLayout,
leaf_header: &dyn Fn(TreeId) -> LeafHeader,
) -> Result<Vec<u8>> {
let mut leaf = LeafBuilder::new(cfg.nodesize, &leaf_header(TreeId::Dev));
let stats_key = Key::new(
raw::BTRFS_DEV_STATS_OBJECTID as u64,
raw::BTRFS_PERSISTENT_ITEM_KEY as u8,
1,
);
leaf.push(stats_key, &items::dev_stats_zeroed())
.map_err(|e| anyhow::anyhow!("dev tree: {e}"))?;
let extent_data = items::dev_extent(
raw::BTRFS_CHUNK_TREE_OBJECTID as u64,
raw::BTRFS_FIRST_CHUNK_TREE_OBJECTID as u64,
SYSTEM_GROUP_OFFSET,
SYSTEM_GROUP_SIZE,
&cfg.chunk_tree_uuid,
);
let extent_key = Key::new(
1, raw::BTRFS_DEV_EXTENT_KEY as u8,
SYSTEM_GROUP_OFFSET,
);
leaf.push(extent_key, &extent_data)
.map_err(|e| anyhow::anyhow!("dev tree: {e}"))?;
Ok(leaf.finish())
}
fn build_empty_tree(nodesize: u32, header: &LeafHeader) -> Vec<u8> {
LeafBuilder::new(nodesize, header).finish()
}
fn build_root_dir_tree(
cfg: &MkfsConfig,
header: &LeafHeader,
) -> Result<Vec<u8>> {
let mut leaf = LeafBuilder::new(cfg.nodesize, header);
let generation = 1u64;
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let inode_key = Key::new(
raw::BTRFS_FIRST_FREE_OBJECTID as u64,
raw::BTRFS_INODE_ITEM_KEY as u8,
0,
);
let inode_data =
items::inode_item_dir(generation, cfg.nodesize as u64, now);
leaf.push(inode_key, &inode_data)
.map_err(|e| anyhow::anyhow!("root dir tree: {e}"))?;
let ref_key = Key::new(
raw::BTRFS_FIRST_FREE_OBJECTID as u64,
raw::BTRFS_INODE_REF_KEY as u8,
raw::BTRFS_FIRST_FREE_OBJECTID as u64,
);
let ref_data = items::inode_ref(0, b"..");
leaf.push(ref_key, &ref_data)
.map_err(|e| anyhow::anyhow!("root dir tree: {e}"))?;
Ok(leaf.finish())
}
fn build_free_space_tree(
cfg: &MkfsConfig,
layout: &BlockLayout,
leaf_header: &dyn Fn(TreeId) -> LeafHeader,
) -> Result<Vec<u8>> {
if !cfg.has_free_space_tree() {
return Ok(build_empty_tree(
cfg.nodesize,
&leaf_header(TreeId::FreeSpace),
));
}
let mut leaf =
LeafBuilder::new(cfg.nodesize, &leaf_header(TreeId::FreeSpace));
let free_start = SYSTEM_GROUP_OFFSET + layout.total_used();
let free_length = SYSTEM_GROUP_OFFSET + SYSTEM_GROUP_SIZE - free_start;
let info_key = Key::new(
SYSTEM_GROUP_OFFSET,
raw::BTRFS_FREE_SPACE_INFO_KEY as u8,
SYSTEM_GROUP_SIZE,
);
leaf.push(info_key, &items::free_space_info(1, 0))
.map_err(|e| anyhow::anyhow!("free space tree: {e}"))?;
let extent_key = Key::new(
free_start,
raw::BTRFS_FREE_SPACE_EXTENT_KEY as u8,
free_length,
);
leaf.push_empty(extent_key)
.map_err(|e| anyhow::anyhow!("free space tree: {e}"))?;
Ok(leaf.finish())
}
fn build_superblock(cfg: &MkfsConfig, layout: &BlockLayout) -> Result<Vec<u8>> {
let generation = 1u64;
let chunk_key = Key::new(
raw::BTRFS_FIRST_CHUNK_TREE_OBJECTID as u64,
raw::BTRFS_CHUNK_ITEM_KEY as u8,
SYSTEM_GROUP_OFFSET,
);
let chunk_data = items::chunk_item_single(
SYSTEM_GROUP_SIZE,
raw::BTRFS_EXTENT_TREE_OBJECTID as u64,
raw::BTRFS_BLOCK_GROUP_SYSTEM as u64,
cfg.sectorsize,
1,
SYSTEM_GROUP_OFFSET,
&cfg.dev_uuid,
);
let mut sys_chunk_array = items::disk_key(&chunk_key);
sys_chunk_array.extend_from_slice(&chunk_data);
let dev_item_bytes = items::dev_item(
1,
cfg.total_bytes,
SYSTEM_GROUP_SIZE,
cfg.sectorsize,
&cfg.dev_uuid,
&cfg.fs_uuid,
);
let cache_generation = if cfg.has_free_space_tree() {
0
} else {
u64::MAX
};
let mut sb = SuperblockBuilder::new();
sb.set_bytenr(SUPER_INFO_OFFSET)
.set_magic()
.set_fsid(&cfg.fs_uuid)
.set_generation(generation)
.set_root(layout.block_addr(TreeId::Root))
.set_chunk_root(layout.block_addr(TreeId::Chunk))
.set_chunk_root_generation(generation)
.set_total_bytes(cfg.total_bytes)
.set_bytes_used(layout.total_used())
.set_root_dir_objectid(raw::BTRFS_FIRST_FREE_OBJECTID as u64)
.set_num_devices(1)
.set_sectorsize(cfg.sectorsize)
.set_nodesize(cfg.nodesize)
.set_stripesize(cfg.sectorsize)
.set_incompat_flags(cfg.incompat_flags)
.set_compat_ro_flags(cfg.compat_ro_flags)
.set_csum_type(0) .set_cache_generation(cache_generation)
.set_dev_item(&dev_item_bytes)
.set_sys_chunk_array(&sys_chunk_array);
if let Some(label) = &cfg.label {
sb.set_label(label);
}
let mut buf = sb.finish();
write::fill_csum(&mut buf);
Ok(buf.to_vec())
}
nix::ioctl_read!(blk_getsize64, 0x12, 114, u64);
pub fn device_size(path: &Path) -> Result<u64> {
let metadata = std::fs::metadata(path)
.with_context(|| format!("failed to stat {}", path.display()))?;
if metadata.file_type().is_block_device() {
let file = File::open(path)
.with_context(|| format!("failed to open {}", path.display()))?;
let mut size: u64 = 0;
unsafe {
blk_getsize64(
std::os::unix::io::AsRawFd::as_raw_fd(&file),
&mut size,
)
}
.with_context(|| {
format!("BLKGETSIZE64 failed on {}", path.display())
})?;
Ok(size)
} else {
Ok(metadata.len())
}
}