use crate::util::get_uuid;
use crate::{
raw,
tree::{DiskKey, KeyType, ObjectId},
util::{raw_crc32c, write_disk_key},
};
use bytes::{Buf, BufMut};
use std::{fmt, mem};
use uuid::Uuid;
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BlockGroupFlags: u64 {
const DATA = raw::BTRFS_BLOCK_GROUP_DATA as u64;
const SYSTEM = raw::BTRFS_BLOCK_GROUP_SYSTEM as u64;
const METADATA = raw::BTRFS_BLOCK_GROUP_METADATA as u64;
const RAID0 = raw::BTRFS_BLOCK_GROUP_RAID0 as u64;
const RAID1 = raw::BTRFS_BLOCK_GROUP_RAID1 as u64;
const DUP = raw::BTRFS_BLOCK_GROUP_DUP as u64;
const RAID10 = raw::BTRFS_BLOCK_GROUP_RAID10 as u64;
const RAID5 = raw::BTRFS_BLOCK_GROUP_RAID5 as u64;
const RAID6 = raw::BTRFS_BLOCK_GROUP_RAID6 as u64;
const RAID1C3 = raw::BTRFS_BLOCK_GROUP_RAID1C3 as u64;
const RAID1C4 = raw::BTRFS_BLOCK_GROUP_RAID1C4 as u64;
const SINGLE = raw::BTRFS_AVAIL_ALLOC_BIT_SINGLE;
const GLOBAL_RSV = raw::BTRFS_SPACE_INFO_GLOBAL_RSV;
}
}
impl BlockGroupFlags {
#[must_use]
pub fn type_name(self) -> &'static str {
if self.contains(Self::GLOBAL_RSV) {
return "GlobalReserve";
}
let ty = self & (Self::DATA | Self::SYSTEM | Self::METADATA);
match ty {
t if t == Self::DATA => "Data",
t if t == Self::SYSTEM => "System",
t if t == Self::METADATA => "Metadata",
t if t == Self::DATA | Self::METADATA => "Data+Metadata",
_ => "unknown",
}
}
#[must_use]
pub fn profile_name(self) -> &'static str {
let profile = self
& (Self::RAID0
| Self::RAID1
| Self::DUP
| Self::RAID10
| Self::RAID5
| Self::RAID6
| Self::RAID1C3
| Self::RAID1C4
| Self::SINGLE);
match profile {
p if p == Self::RAID0 => "RAID0",
p if p == Self::RAID1 => "RAID1",
p if p == Self::DUP => "DUP",
p if p == Self::RAID10 => "RAID10",
p if p == Self::RAID5 => "RAID5",
p if p == Self::RAID6 => "RAID6",
p if p == Self::RAID1C3 => "RAID1C3",
p if p == Self::RAID1C4 => "RAID1C4",
_ => "single",
}
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct InodeFlags: u64 {
const NODATASUM = raw::BTRFS_INODE_NODATASUM as u64;
const NODATACOW = raw::BTRFS_INODE_NODATACOW as u64;
const READONLY = raw::BTRFS_INODE_READONLY as u64;
const NOCOMPRESS = raw::BTRFS_INODE_NOCOMPRESS as u64;
const PREALLOC = raw::BTRFS_INODE_PREALLOC as u64;
const SYNC = raw::BTRFS_INODE_SYNC as u64;
const IMMUTABLE = raw::BTRFS_INODE_IMMUTABLE as u64;
const APPEND = raw::BTRFS_INODE_APPEND as u64;
const NODUMP = raw::BTRFS_INODE_NODUMP as u64;
const NOATIME = raw::BTRFS_INODE_NOATIME as u64;
const DIRSYNC = raw::BTRFS_INODE_DIRSYNC as u64;
const COMPRESS = raw::BTRFS_INODE_COMPRESS as u64;
const ROOT_ITEM_INIT = raw::BTRFS_INODE_ROOT_ITEM_INIT as u64;
const _ = !0;
}
}
impl fmt::Display for InodeFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
const NAMES: &[(InodeFlags, &str)] = &[
(InodeFlags::NODATASUM, "NODATASUM"),
(InodeFlags::NODATACOW, "NODATACOW"),
(InodeFlags::READONLY, "READONLY"),
(InodeFlags::NOCOMPRESS, "NOCOMPRESS"),
(InodeFlags::PREALLOC, "PREALLOC"),
(InodeFlags::SYNC, "SYNC"),
(InodeFlags::IMMUTABLE, "IMMUTABLE"),
(InodeFlags::APPEND, "APPEND"),
(InodeFlags::NODUMP, "NODUMP"),
(InodeFlags::NOATIME, "NOATIME"),
(InodeFlags::DIRSYNC, "DIRSYNC"),
(InodeFlags::COMPRESS, "COMPRESS"),
(InodeFlags::ROOT_ITEM_INIT, "ROOT_ITEM_INIT"),
];
let known: InodeFlags = NAMES
.iter()
.fold(InodeFlags::empty(), |a, &(flag, _)| a | flag);
let mut parts: Vec<String> = NAMES
.iter()
.filter(|&&(flag, _)| self.contains(flag))
.map(|&(_, name)| name.to_string())
.collect();
let unknown = *self & !known;
if !unknown.is_empty() {
parts.push(format!("UNKNOWN: 0x{:x}", unknown.bits()));
}
if parts.is_empty() {
write!(f, "none")
} else {
write!(f, "{}", parts.join("|"))
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Timespec {
pub sec: u64,
pub nsec: u32,
}
impl Timespec {
fn parse(buf: &mut &[u8]) -> Self {
Self {
sec: buf.get_u64_le(),
nsec: buf.get_u32_le(),
}
}
fn write_to(&self, buf: &mut Vec<u8>) {
buf.put_u64_le(self.sec);
buf.put_u32_le(self.nsec);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompressionType {
None,
Zlib,
Lzo,
Zstd,
Unknown(u8),
}
impl CompressionType {
#[must_use]
pub fn from_raw(v: u8) -> Self {
match v {
0 => Self::None,
1 => Self::Zlib,
2 => Self::Lzo,
3 => Self::Zstd,
_ => Self::Unknown(v),
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::None => "none",
Self::Zlib => "zlib",
Self::Lzo => "lzo",
Self::Zstd => "zstd",
Self::Unknown(_) => "unknown",
}
}
#[must_use]
pub fn to_raw(self) -> u8 {
match self {
Self::None => 0,
Self::Zlib => 1,
Self::Lzo => 2,
Self::Zstd => 3,
Self::Unknown(v) => v,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileExtentType {
Inline,
Regular,
Prealloc,
Unknown(u8),
}
impl FileExtentType {
#[must_use]
pub fn from_raw(v: u8) -> Self {
match u32::from(v) {
raw::BTRFS_FILE_EXTENT_INLINE => Self::Inline,
raw::BTRFS_FILE_EXTENT_REG => Self::Regular,
raw::BTRFS_FILE_EXTENT_PREALLOC => Self::Prealloc,
_ => Self::Unknown(v),
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Inline => "inline",
Self::Regular => "regular",
Self::Prealloc => "prealloc",
Self::Unknown(_) => "unknown",
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn to_raw(self) -> u8 {
match self {
Self::Inline => raw::BTRFS_FILE_EXTENT_INLINE as u8,
Self::Regular => raw::BTRFS_FILE_EXTENT_REG as u8,
Self::Prealloc => raw::BTRFS_FILE_EXTENT_PREALLOC as u8,
Self::Unknown(v) => v,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileType {
Unknown,
RegFile,
Dir,
Chrdev,
Blkdev,
Fifo,
Sock,
Symlink,
Xattr,
Other(u8),
}
impl FileType {
#[must_use]
pub fn from_raw(v: u8) -> Self {
match u32::from(v) {
raw::BTRFS_FT_UNKNOWN => Self::Unknown,
raw::BTRFS_FT_REG_FILE => Self::RegFile,
raw::BTRFS_FT_DIR => Self::Dir,
raw::BTRFS_FT_CHRDEV => Self::Chrdev,
raw::BTRFS_FT_BLKDEV => Self::Blkdev,
raw::BTRFS_FT_FIFO => Self::Fifo,
raw::BTRFS_FT_SOCK => Self::Sock,
raw::BTRFS_FT_SYMLINK => Self::Symlink,
raw::BTRFS_FT_XATTR => Self::Xattr,
_ => Self::Other(v),
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Unknown | Self::Other(_) => "UNKNOWN",
Self::RegFile => "FILE",
Self::Dir => "DIR",
Self::Chrdev => "CHRDEV",
Self::Blkdev => "BLKDEV",
Self::Fifo => "FIFO",
Self::Sock => "SOCK",
Self::Symlink => "SYMLINK",
Self::Xattr => "XATTR",
}
}
}
#[derive(Debug, Clone)]
pub struct InodeItem {
pub generation: u64,
pub transid: u64,
pub size: u64,
pub nbytes: u64,
pub block_group: u64,
pub nlink: u32,
pub uid: u32,
pub gid: u32,
pub mode: u32,
pub rdev: u64,
pub flags: InodeFlags,
pub sequence: u64,
pub atime: Timespec,
pub ctime: Timespec,
pub mtime: Timespec,
pub otime: Timespec,
}
impl InodeItem {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < mem::size_of::<raw::btrfs_inode_item>() {
return None;
}
let mut buf = data;
Some(Self {
generation: buf.get_u64_le(),
transid: buf.get_u64_le(),
size: buf.get_u64_le(),
nbytes: buf.get_u64_le(),
block_group: buf.get_u64_le(),
nlink: buf.get_u32_le(),
uid: buf.get_u32_le(),
gid: buf.get_u32_le(),
mode: buf.get_u32_le(),
rdev: buf.get_u64_le(),
flags: InodeFlags::from_bits_truncate(buf.get_u64_le()),
sequence: buf.get_u64_le(),
atime: {
buf.advance(32);
Timespec::parse(&mut buf)
},
ctime: Timespec::parse(&mut buf),
mtime: Timespec::parse(&mut buf),
otime: Timespec::parse(&mut buf),
})
}
}
pub struct InodeItemArgs {
pub generation: u64,
pub size: u64,
pub nbytes: u64,
pub nlink: u32,
pub uid: u32,
pub gid: u32,
pub mode: u32,
pub time: Timespec,
}
impl InodeItemArgs {
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(160);
buf.put_u64_le(self.generation);
buf.put_u64_le(self.generation); buf.put_u64_le(self.size);
buf.put_u64_le(self.nbytes);
buf.put_u64_le(0); buf.put_u32_le(self.nlink);
buf.put_u32_le(self.uid);
buf.put_u32_le(self.gid);
buf.put_u32_le(self.mode);
buf.put_u64_le(0); buf.put_u64_le(0); buf.put_u64_le(0); buf.extend_from_slice(&[0u8; 32]); for _ in 0..4 {
self.time.write_to(&mut buf);
}
debug_assert_eq!(buf.len(), 160);
buf
}
}
#[derive(Debug, Clone)]
pub struct InodeRef {
pub index: u64,
pub name: Vec<u8>,
}
impl InodeRef {
#[must_use]
pub fn parse_all(data: &[u8]) -> Vec<Self> {
let mut result = Vec::new();
let mut buf = data;
while buf.remaining() >= 10 {
let index = buf.get_u64_le();
let name_len = buf.get_u16_le() as usize;
if buf.remaining() < name_len {
break;
}
let name = buf[..name_len].to_vec();
buf.advance(name_len);
result.push(Self { index, name });
}
result
}
#[must_use]
pub fn serialize(index: u64, name: &[u8]) -> Vec<u8> {
let mut buf = Vec::with_capacity(10 + name.len());
buf.put_u64_le(index);
#[allow(clippy::cast_possible_truncation)]
buf.put_u16_le(name.len() as u16);
buf.extend_from_slice(name);
buf
}
}
#[derive(Debug, Clone)]
pub struct InodeExtref {
pub parent: u64,
pub index: u64,
pub name: Vec<u8>,
}
impl InodeExtref {
#[must_use]
pub fn parse_all(data: &[u8]) -> Vec<Self> {
let mut result = Vec::new();
let mut buf = data;
while buf.remaining() >= 18 {
let parent = buf.get_u64_le();
let index = buf.get_u64_le();
let name_len = buf.get_u16_le() as usize;
if buf.remaining() < name_len {
break;
}
let name = buf[..name_len].to_vec();
buf.advance(name_len);
result.push(Self {
parent,
index,
name,
});
}
result
}
}
#[derive(Debug, Clone)]
pub struct DirItem {
pub location: DiskKey,
pub transid: u64,
pub file_type: FileType,
pub name: Vec<u8>,
pub data: Vec<u8>,
}
impl DirItem {
#[must_use]
pub fn parse_all(data: &[u8]) -> Vec<Self> {
let mut result = Vec::new();
let dir_item_size = mem::size_of::<raw::btrfs_dir_item>();
let mut buf = data;
while buf.remaining() >= dir_item_size {
let location = DiskKey::parse(buf, 0);
buf.advance(17); let transid = buf.get_u64_le();
let data_len = buf.get_u16_le() as usize;
let name_len = buf.get_u16_le() as usize;
let file_type = FileType::from_raw(buf.get_u8());
if buf.remaining() < name_len + data_len {
break;
}
let name = buf[..name_len].to_vec();
buf.advance(name_len);
let item_data = buf[..data_len].to_vec();
buf.advance(data_len);
result.push(Self {
location,
transid,
file_type,
name,
data: item_data,
});
}
result
}
#[must_use]
pub fn serialize(
location: &DiskKey,
transid: u64,
file_type: u8,
name: &[u8],
) -> Vec<u8> {
let mut buf = Vec::with_capacity(30 + name.len());
let key_off = buf.len();
buf.extend_from_slice(&[0u8; 17]);
write_disk_key(&mut buf[key_off..], 0, location);
buf.put_u64_le(transid);
buf.put_u16_le(0); #[allow(clippy::cast_possible_truncation)]
buf.put_u16_le(name.len() as u16);
buf.put_u8(file_type);
buf.extend_from_slice(name);
buf
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RootItemFlags: u64 {
const RDONLY = raw::BTRFS_ROOT_SUBVOL_RDONLY as u64;
const DEAD = raw::BTRFS_ROOT_SUBVOL_DEAD;
const _ = !0;
}
}
impl fmt::Display for RootItemFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.contains(Self::RDONLY) {
write!(f, "RDONLY")
} else {
write!(f, "none")
}
}
}
#[derive(Debug, Clone)]
pub struct RootItem {
pub inode_data: Vec<u8>,
pub generation: u64,
pub root_dirid: u64,
pub bytenr: u64,
pub byte_limit: u64,
pub bytes_used: u64,
pub last_snapshot: u64,
pub flags: RootItemFlags,
pub refs: u32,
pub drop_progress: DiskKey,
pub drop_level: u8,
pub level: u8,
pub generation_v2: u64,
pub uuid: Uuid,
pub parent_uuid: Uuid,
pub received_uuid: Uuid,
pub ctransid: u64,
pub otransid: u64,
pub stransid: u64,
pub rtransid: u64,
pub ctime: Timespec,
pub otime: Timespec,
pub stime: Timespec,
pub rtime: Timespec,
}
impl RootItem {
#[must_use]
#[allow(clippy::too_many_lines)]
pub fn parse(data: &[u8]) -> Option<Self> {
let inode_size = mem::size_of::<raw::btrfs_inode_item>();
if data.len() < inode_size + 8 {
return None;
}
let inode_data = data[..inode_size].to_vec();
let mut buf = &data[inode_size..];
let generation = buf.get_u64_le();
let root_dirid = buf.get_u64_le();
let bytenr = buf.get_u64_le();
let byte_limit = buf.get_u64_le();
let bytes_used = buf.get_u64_le();
let last_snapshot = buf.get_u64_le();
let flags = RootItemFlags::from_bits_truncate(buf.get_u64_le());
let refs = buf.get_u32_le();
let dp_off = inode_size + 60;
let drop_progress = if dp_off + 17 <= data.len() {
DiskKey::parse(data, dp_off)
} else {
DiskKey::parse(&[0; 17], 0)
};
let drop_level = if dp_off + 17 < data.len() {
data[dp_off + 17]
} else {
0
};
let level_off = mem::offset_of!(raw::btrfs_root_item, level);
let level = if level_off < data.len() {
data[level_off]
} else {
0
};
let generation_v2 = if level_off + 1 + 8 <= data.len() {
let mut b = &data[level_off + 1..];
b.get_u64_le()
} else {
0
};
let uuid_off = mem::offset_of!(raw::btrfs_root_item, uuid);
let uuid = if uuid_off + 16 <= data.len() {
let mut b = &data[uuid_off..];
get_uuid(&mut b)
} else {
Uuid::nil()
};
let parent_uuid = if uuid_off + 32 <= data.len() {
let mut b = &data[uuid_off + 16..];
get_uuid(&mut b)
} else {
Uuid::nil()
};
let received_uuid = if uuid_off + 48 <= data.len() {
let mut b = &data[uuid_off + 32..];
get_uuid(&mut b)
} else {
Uuid::nil()
};
let ct_off = mem::offset_of!(raw::btrfs_root_item, ctransid);
let ctransid = if ct_off + 8 <= data.len() {
let mut b = &data[ct_off..];
b.get_u64_le()
} else {
0
};
let otransid = if ct_off + 16 <= data.len() {
let mut b = &data[ct_off + 8..];
b.get_u64_le()
} else {
0
};
let stransid = if ct_off + 24 <= data.len() {
let mut b = &data[ct_off + 16..];
b.get_u64_le()
} else {
0
};
let rtransid = if ct_off + 32 <= data.len() {
let mut b = &data[ct_off + 24..];
b.get_u64_le()
} else {
0
};
let ctime_off = mem::offset_of!(raw::btrfs_root_item, ctime);
let ts_size = mem::size_of::<raw::btrfs_timespec>();
let ctime = if ctime_off + ts_size <= data.len() {
let mut b = &data[ctime_off..];
Timespec::parse(&mut b)
} else {
Timespec { sec: 0, nsec: 0 }
};
let otime = if ctime_off + 2 * ts_size <= data.len() {
let mut b = &data[ctime_off + ts_size..];
Timespec::parse(&mut b)
} else {
Timespec { sec: 0, nsec: 0 }
};
let stime = if ctime_off + 3 * ts_size <= data.len() {
let mut b = &data[ctime_off + 2 * ts_size..];
Timespec::parse(&mut b)
} else {
Timespec { sec: 0, nsec: 0 }
};
let rtime = if ctime_off + 4 * ts_size <= data.len() {
let mut b = &data[ctime_off + 3 * ts_size..];
Timespec::parse(&mut b)
} else {
Timespec { sec: 0, nsec: 0 }
};
Some(Self {
inode_data,
generation,
root_dirid,
bytenr,
byte_limit,
bytes_used,
last_snapshot,
flags,
refs,
drop_progress,
drop_level,
level,
generation_v2,
uuid,
parent_uuid,
received_uuid,
ctransid,
otransid,
stransid,
rtransid,
ctime,
otime,
stime,
rtime,
})
}
#[must_use]
pub fn new_internal(generation: u64, bytenr: u64, level: u8) -> Self {
Self {
inode_data: vec![0u8; 160],
generation,
root_dirid: 0,
bytenr,
byte_limit: 0,
bytes_used: 0,
last_snapshot: 0,
flags: RootItemFlags::empty(),
refs: 1,
drop_progress: DiskKey {
objectid: 0,
key_type: KeyType::from_raw(0),
offset: 0,
},
drop_level: 0,
level,
generation_v2: generation,
uuid: Uuid::nil(),
parent_uuid: Uuid::nil(),
received_uuid: Uuid::nil(),
ctransid: 0,
otransid: 0,
stransid: 0,
rtransid: 0,
ctime: Timespec { sec: 0, nsec: 0 },
otime: Timespec { sec: 0, nsec: 0 },
stime: Timespec { sec: 0, nsec: 0 },
rtime: Timespec { sec: 0, nsec: 0 },
}
}
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(496);
buf.extend_from_slice(&self.inode_data);
buf.put_u64_le(self.generation);
buf.put_u64_le(self.root_dirid);
buf.put_u64_le(self.bytenr);
buf.put_u64_le(self.byte_limit);
buf.put_u64_le(self.bytes_used);
buf.put_u64_le(self.last_snapshot);
buf.put_u64_le(self.flags.bits());
buf.put_u32_le(self.refs);
let key_off = buf.len();
buf.extend_from_slice(&[0u8; 17]);
write_disk_key(&mut buf[key_off..], 0, &self.drop_progress);
buf.put_u8(self.drop_level);
buf.put_u8(self.level);
buf.put_u64_le(self.generation_v2);
buf.extend_from_slice(self.uuid.as_bytes());
buf.extend_from_slice(self.parent_uuid.as_bytes());
buf.extend_from_slice(self.received_uuid.as_bytes());
buf.put_u64_le(self.ctransid);
buf.put_u64_le(self.otransid);
buf.put_u64_le(self.stransid);
buf.put_u64_le(self.rtransid);
self.ctime.write_to(&mut buf);
self.otime.write_to(&mut buf);
self.stime.write_to(&mut buf);
self.rtime.write_to(&mut buf);
buf.resize(mem::size_of::<raw::btrfs_root_item>(), 0);
buf
}
}
#[derive(Debug, Clone)]
pub struct RootRef {
pub dirid: u64,
pub sequence: u64,
pub name: Vec<u8>,
}
impl RootRef {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < mem::size_of::<raw::btrfs_root_ref>() {
return None;
}
let mut buf = data;
let dirid = buf.get_u64_le();
let sequence = buf.get_u64_le();
let name_len = buf.get_u16_le() as usize;
let name_start = mem::size_of::<raw::btrfs_root_ref>();
let name = if name_start + name_len <= data.len() {
data[name_start..name_start + name_len].to_vec()
} else {
Vec::new()
};
Some(Self {
dirid,
sequence,
name,
})
}
#[must_use]
pub fn serialize(dirid: u64, sequence: u64, name: &[u8]) -> Vec<u8> {
let header = mem::size_of::<raw::btrfs_root_ref>();
let mut buf = Vec::with_capacity(header + name.len());
buf.put_u64_le(dirid);
buf.put_u64_le(sequence);
buf.put_u16_le(
u16::try_from(name.len())
.expect("RootRef::serialize: name length does not fit in u16"),
);
buf.put_slice(name);
buf
}
}
#[derive(Debug, Clone)]
pub struct FileExtentItem {
pub generation: u64,
pub ram_bytes: u64,
pub compression: CompressionType,
pub extent_type: FileExtentType,
pub body: FileExtentBody,
}
#[derive(Debug, Clone)]
pub enum FileExtentBody {
Inline {
inline_size: usize,
},
Regular {
disk_bytenr: u64,
disk_num_bytes: u64,
offset: u64,
num_bytes: u64,
},
}
impl FileExtentItem {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 21 {
return None;
}
let mut buf = data;
let generation = buf.get_u64_le();
let ram_bytes = buf.get_u64_le();
let compression = CompressionType::from_raw(buf.get_u8());
buf.advance(3); let extent_type = FileExtentType::from_raw(buf.get_u8());
let body = if extent_type == FileExtentType::Inline {
FileExtentBody::Inline {
inline_size: buf.remaining(),
}
} else if buf.remaining() >= 32 {
FileExtentBody::Regular {
disk_bytenr: buf.get_u64_le(),
disk_num_bytes: buf.get_u64_le(),
offset: buf.get_u64_le(),
num_bytes: buf.get_u64_le(),
}
} else {
return None;
};
Some(Self {
generation,
ram_bytes,
compression,
extent_type,
body,
})
}
pub const HEADER_SIZE: usize = 21;
pub const REGULAR_SIZE: usize = 53;
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn to_bytes_regular(
generation: u64,
ram_bytes: u64,
compression: CompressionType,
prealloc: bool,
disk_bytenr: u64,
disk_num_bytes: u64,
offset: u64,
num_bytes: u64,
) -> Vec<u8> {
let extent_type = if prealloc {
FileExtentType::Prealloc
} else {
FileExtentType::Regular
};
let mut buf = Vec::with_capacity(Self::REGULAR_SIZE);
buf.put_u64_le(generation);
buf.put_u64_le(ram_bytes);
buf.put_u8(compression.to_raw());
buf.put_u8(0); buf.put_u16_le(0); buf.put_u8(extent_type.to_raw());
buf.put_u64_le(disk_bytenr);
buf.put_u64_le(disk_num_bytes);
buf.put_u64_le(offset);
buf.put_u64_le(num_bytes);
debug_assert_eq!(buf.len(), Self::REGULAR_SIZE);
buf
}
#[must_use]
pub fn to_bytes_inline(
generation: u64,
ram_bytes: u64,
compression: CompressionType,
data: &[u8],
) -> Vec<u8> {
let mut buf = Vec::with_capacity(Self::HEADER_SIZE + data.len());
buf.put_u64_le(generation);
buf.put_u64_le(ram_bytes);
buf.put_u8(compression.to_raw());
buf.put_u8(0); buf.put_u16_le(0); buf.put_u8(FileExtentType::Inline.to_raw());
debug_assert_eq!(buf.len(), Self::HEADER_SIZE);
buf.extend_from_slice(data);
buf
}
}
#[must_use]
pub fn extent_data_ref_hash(root: u64, objectid: u64, offset: u64) -> u64 {
let high_crc = raw_crc32c(!0u32, &root.to_le_bytes());
let low_crc = raw_crc32c(!0u32, &objectid.to_le_bytes());
let low_crc = raw_crc32c(low_crc, &offset.to_le_bytes());
(u64::from(high_crc) << 31) ^ u64::from(low_crc)
}
#[derive(Debug, Clone)]
pub enum InlineRef {
TreeBlockBackref {
ref_offset: u64,
root: u64,
},
SharedBlockBackref {
ref_offset: u64,
parent: u64,
},
ExtentDataBackref {
ref_offset: u64,
root: u64,
objectid: u64,
offset: u64,
count: u32,
},
SharedDataBackref {
ref_offset: u64,
parent: u64,
count: u32,
},
ExtentOwnerRef {
ref_offset: u64,
root: u64,
},
}
impl InlineRef {
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn raw_type(&self) -> u8 {
match self {
Self::TreeBlockBackref { .. } => {
raw::BTRFS_TREE_BLOCK_REF_KEY as u8
}
Self::SharedBlockBackref { .. } => {
raw::BTRFS_SHARED_BLOCK_REF_KEY as u8
}
Self::ExtentDataBackref { .. } => {
raw::BTRFS_EXTENT_DATA_REF_KEY as u8
}
Self::SharedDataBackref { .. } => {
raw::BTRFS_SHARED_DATA_REF_KEY as u8
}
Self::ExtentOwnerRef { .. } => {
raw::BTRFS_EXTENT_OWNER_REF_KEY as u8
}
}
}
#[must_use]
pub fn raw_offset(&self) -> u64 {
match self {
Self::TreeBlockBackref { ref_offset, .. }
| Self::SharedBlockBackref { ref_offset, .. }
| Self::ExtentDataBackref { ref_offset, .. }
| Self::SharedDataBackref { ref_offset, .. }
| Self::ExtentOwnerRef { ref_offset, .. } => *ref_offset,
}
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn inline_ref_size(type_byte: u8) -> Option<usize> {
match u32::from(type_byte) {
raw::BTRFS_TREE_BLOCK_REF_KEY | raw::BTRFS_EXTENT_OWNER_REF_KEY => {
Some(9)
}
raw::BTRFS_SHARED_BLOCK_REF_KEY => Some(9),
raw::BTRFS_EXTENT_DATA_REF_KEY => Some(29),
raw::BTRFS_SHARED_DATA_REF_KEY => Some(13),
_ => None,
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExtentFlags: u64 {
const DATA = raw::BTRFS_EXTENT_FLAG_DATA as u64;
const TREE_BLOCK = raw::BTRFS_EXTENT_FLAG_TREE_BLOCK as u64;
const FULL_BACKREF = raw::BTRFS_BLOCK_FLAG_FULL_BACKREF as u64;
const _ = !0;
}
}
impl fmt::Display for ExtentFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut parts = Vec::new();
if self.contains(Self::DATA) {
parts.push("DATA");
}
if self.contains(Self::TREE_BLOCK) {
parts.push("TREE_BLOCK");
}
if self.contains(Self::FULL_BACKREF) {
parts.push("FULL_BACKREF");
}
write!(f, "{}", parts.join("|"))
}
}
#[derive(Debug, Clone)]
pub struct ExtentItem {
pub refs: u64,
pub generation: u64,
pub flags: ExtentFlags,
pub tree_block_key: Option<DiskKey>,
pub tree_block_level: Option<u8>,
pub skinny_level: Option<u64>,
pub inline_refs: Vec<InlineRef>,
}
impl ExtentItem {
#[must_use]
pub fn is_data(&self) -> bool {
self.flags.contains(ExtentFlags::DATA)
}
#[must_use]
pub fn is_tree_block(&self) -> bool {
self.flags.contains(ExtentFlags::TREE_BLOCK)
}
#[must_use]
pub fn parse(data: &[u8], key: &DiskKey) -> Option<Self> {
use crate::tree::KeyType;
if data.len() < mem::size_of::<raw::btrfs_extent_item>() {
return None;
}
let mut buf = data;
let refs = buf.get_u64_le();
let generation = buf.get_u64_le();
let flags = ExtentFlags::from_bits_truncate(buf.get_u64_le());
let is_tree_block = flags.contains(ExtentFlags::TREE_BLOCK);
let mut tree_block_key = None;
let mut tree_block_level = None;
if is_tree_block
&& key.key_type == KeyType::ExtentItem
&& buf.remaining() > 17
{
tree_block_key = Some(DiskKey::parse(buf, 0));
buf.advance(17); tree_block_level = Some(buf.get_u8());
}
let skinny_level =
if key.key_type == KeyType::MetadataItem && is_tree_block {
Some(key.offset)
} else {
None
};
let mut inline_refs = Vec::new();
while buf.remaining() > 0 {
let ref_type = buf.get_u8();
let ref_offset = if buf.remaining() >= 8 {
buf.get_u64_le()
} else {
0
};
match u32::from(ref_type) {
raw::BTRFS_TREE_BLOCK_REF_KEY => {
inline_refs.push(InlineRef::TreeBlockBackref {
ref_offset,
root: ref_offset,
});
}
raw::BTRFS_SHARED_BLOCK_REF_KEY => {
inline_refs.push(InlineRef::SharedBlockBackref {
ref_offset,
parent: ref_offset,
});
}
raw::BTRFS_EXTENT_DATA_REF_KEY => {
let root = ref_offset; if buf.remaining() >= 20 {
let oid = buf.get_u64_le();
let off = buf.get_u64_le();
let count = buf.get_u32_le();
let hash = extent_data_ref_hash(root, oid, off);
inline_refs.push(InlineRef::ExtentDataBackref {
ref_offset: hash,
root,
objectid: oid,
offset: off,
count,
});
} else {
break;
}
}
raw::BTRFS_SHARED_DATA_REF_KEY => {
if buf.remaining() >= 4 {
let count = buf.get_u32_le();
inline_refs.push(InlineRef::SharedDataBackref {
ref_offset,
parent: ref_offset,
count,
});
} else {
break;
}
}
raw::BTRFS_EXTENT_OWNER_REF_KEY => {
inline_refs.push(InlineRef::ExtentOwnerRef {
ref_offset,
root: ref_offset,
});
}
_ => break,
}
}
Some(Self {
refs,
generation,
flags,
tree_block_key,
tree_block_level,
skinny_level,
inline_refs,
})
}
pub const SKINNY_SIZE: usize = 33;
pub const NON_SKINNY_SIZE: usize = 51;
#[must_use]
pub fn to_bytes_skinny(
refs: u64,
generation: u64,
root_id: u64,
) -> Vec<u8> {
let mut buf = Vec::with_capacity(Self::SKINNY_SIZE);
buf.put_u64_le(refs);
buf.put_u64_le(generation);
buf.put_u64_le(ExtentFlags::TREE_BLOCK.bits());
buf.put_u8(KeyType::TreeBlockRef.to_raw());
buf.put_u64_le(root_id);
debug_assert_eq!(buf.len(), Self::SKINNY_SIZE);
buf
}
#[must_use]
pub fn to_bytes_non_skinny(
refs: u64,
generation: u64,
root_id: u64,
first_key: &DiskKey,
level: u8,
) -> Vec<u8> {
let mut buf = Vec::with_capacity(Self::NON_SKINNY_SIZE);
buf.put_u64_le(refs);
buf.put_u64_le(generation);
buf.put_u64_le(ExtentFlags::TREE_BLOCK.bits());
let key_off = buf.len();
buf.extend_from_slice(&[0u8; 17]);
write_disk_key(&mut buf[key_off..], 0, first_key);
buf.put_u8(level);
buf.put_u8(KeyType::TreeBlockRef.to_raw());
buf.put_u64_le(root_id);
debug_assert_eq!(buf.len(), Self::NON_SKINNY_SIZE);
buf
}
pub const DATA_INLINE_SIZE: usize = 53;
#[must_use]
pub fn to_bytes_data(
refs: u64,
generation: u64,
root: u64,
objectid: u64,
offset: u64,
count: u32,
) -> Vec<u8> {
let mut buf = Vec::with_capacity(Self::DATA_INLINE_SIZE);
buf.put_u64_le(refs); buf.put_u64_le(generation); buf.put_u64_le(ExtentFlags::DATA.bits()); buf.put_u8(KeyType::ExtentDataRef.to_raw()); buf.put_u64_le(root); buf.put_u64_le(objectid); buf.put_u64_le(offset); buf.put_u32_le(count); debug_assert_eq!(buf.len(), Self::DATA_INLINE_SIZE);
buf
}
}
#[derive(Debug, Clone)]
pub struct ExtentDataRef {
pub root: u64,
pub objectid: u64,
pub offset: u64,
pub count: u32,
}
impl ExtentDataRef {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < mem::size_of::<raw::btrfs_extent_data_ref>() {
return None;
}
let mut buf = data;
Some(Self {
root: buf.get_u64_le(),
objectid: buf.get_u64_le(),
offset: buf.get_u64_le(),
count: buf.get_u32_le(),
})
}
}
#[derive(Debug, Clone)]
pub struct SharedDataRef {
pub count: u32,
}
impl SharedDataRef {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 4 {
return None;
}
let mut buf = data;
Some(Self {
count: buf.get_u32_le(),
})
}
}
#[derive(Debug, Clone)]
pub struct BlockGroupItem {
pub used: u64,
pub chunk_objectid: u64,
pub flags: BlockGroupFlags,
}
impl BlockGroupItem {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < mem::size_of::<raw::btrfs_block_group_item>() {
return None;
}
let mut buf = data;
Some(Self {
used: buf.get_u64_le(),
chunk_objectid: buf.get_u64_le(),
flags: BlockGroupFlags::from_bits_truncate(buf.get_u64_le()),
})
}
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(24);
buf.put_u64_le(self.used);
buf.put_u64_le(self.chunk_objectid);
buf.put_u64_le(self.flags.bits());
buf
}
}
#[derive(Debug, Clone)]
pub struct ChunkItem {
pub length: u64,
pub owner: u64,
pub stripe_len: u64,
pub chunk_type: BlockGroupFlags,
pub io_align: u32,
pub io_width: u32,
pub sector_size: u32,
pub num_stripes: u16,
pub sub_stripes: u16,
pub stripes: Vec<ChunkStripe>,
}
#[derive(Debug, Clone)]
pub struct ChunkStripe {
pub devid: u64,
pub offset: u64,
pub dev_uuid: Uuid,
}
impl ChunkItem {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
let chunk_base_size = mem::offset_of!(raw::btrfs_chunk, stripe);
if data.len() < chunk_base_size {
return None;
}
let mut buf = data;
let length = buf.get_u64_le();
let owner = buf.get_u64_le();
let stripe_len = buf.get_u64_le();
let chunk_type = BlockGroupFlags::from_bits_truncate(buf.get_u64_le());
let io_align = buf.get_u32_le();
let io_width = buf.get_u32_le();
let sector_size = buf.get_u32_le();
let num_stripes = buf.get_u16_le();
let sub_stripes = buf.get_u16_le();
let stripe_size = mem::size_of::<raw::btrfs_stripe>();
let mut stripes = Vec::with_capacity(num_stripes as usize);
let mut sbuf = &data[chunk_base_size..];
for i in 0..num_stripes as usize {
let s_off = chunk_base_size + i * stripe_size;
if s_off + stripe_size > data.len() {
break;
}
let devid = sbuf.get_u64_le();
let offset = sbuf.get_u64_le();
let dev_uuid = get_uuid(&mut sbuf);
stripes.push(ChunkStripe {
devid,
offset,
dev_uuid,
});
}
Some(Self {
length,
owner,
stripe_len,
chunk_type,
io_align,
io_width,
sector_size,
num_stripes,
sub_stripes,
stripes,
})
}
}
impl ChunkItem {
#[must_use]
pub fn to_mapping(&self, logical: u64) -> crate::chunk::ChunkMapping {
crate::chunk::ChunkMapping {
logical,
length: self.length,
stripe_len: self.stripe_len,
chunk_type: self.chunk_type.bits(),
num_stripes: self.num_stripes,
sub_stripes: self.sub_stripes,
stripes: self
.stripes
.iter()
.map(|s| crate::chunk::Stripe {
devid: s.devid,
offset: s.offset,
dev_uuid: s.dev_uuid,
})
.collect(),
}
}
}
#[derive(Debug, Clone)]
pub struct DeviceItem {
pub devid: u64,
pub total_bytes: u64,
pub bytes_used: u64,
pub io_align: u32,
pub io_width: u32,
pub sector_size: u32,
pub dev_type: u64,
pub generation: u64,
pub start_offset: u64,
pub dev_group: u32,
pub seek_speed: u8,
pub bandwidth: u8,
pub uuid: Uuid,
pub fsid: Uuid,
}
impl DeviceItem {
pub fn write_bytes(&self, buf: &mut impl BufMut) {
buf.put_u64_le(self.devid);
buf.put_u64_le(self.total_bytes);
buf.put_u64_le(self.bytes_used);
buf.put_u32_le(self.io_align);
buf.put_u32_le(self.io_width);
buf.put_u32_le(self.sector_size);
buf.put_u64_le(self.dev_type);
buf.put_u64_le(self.generation);
buf.put_u64_le(self.start_offset);
buf.put_u32_le(self.dev_group);
buf.put_u8(self.seek_speed);
buf.put_u8(self.bandwidth);
buf.put_slice(self.uuid.as_bytes());
buf.put_slice(self.fsid.as_bytes());
}
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < mem::size_of::<raw::btrfs_dev_item>() {
return None;
}
let mut buf = data;
let devid = buf.get_u64_le();
let total_bytes = buf.get_u64_le();
let bytes_used = buf.get_u64_le();
let io_align = buf.get_u32_le();
let io_width = buf.get_u32_le();
let sector_size = buf.get_u32_le();
let dev_type = buf.get_u64_le();
let generation = buf.get_u64_le();
let start_offset = buf.get_u64_le();
let dev_group = buf.get_u32_le();
let seek_speed = buf.get_u8();
let bandwidth = buf.get_u8();
let uuid = get_uuid(&mut buf);
let fsid = get_uuid(&mut buf);
Some(Self {
devid,
total_bytes,
bytes_used,
io_align,
io_width,
sector_size,
dev_type,
generation,
start_offset,
dev_group,
seek_speed,
bandwidth,
uuid,
fsid,
})
}
}
#[derive(Debug, Clone)]
pub struct DeviceExtent {
pub chunk_tree: u64,
pub chunk_objectid: u64,
pub chunk_offset: u64,
pub length: u64,
pub chunk_tree_uuid: Uuid,
}
impl DeviceExtent {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < mem::size_of::<raw::btrfs_dev_extent>() {
return None;
}
let mut buf = data;
let chunk_tree = buf.get_u64_le();
let chunk_objectid = buf.get_u64_le();
let chunk_offset = buf.get_u64_le();
let length = buf.get_u64_le();
let chunk_tree_uuid = get_uuid(&mut buf);
Some(Self {
chunk_tree,
chunk_objectid,
chunk_offset,
length,
chunk_tree_uuid,
})
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FreeSpaceInfoFlags: u32 {
const USING_BITMAPS = raw::BTRFS_FREE_SPACE_USING_BITMAPS;
const _ = !0;
}
}
impl fmt::Display for FreeSpaceInfoFlags {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.bits())
}
}
#[derive(Debug, Clone)]
pub struct FreeSpaceInfo {
pub extent_count: u32,
pub flags: FreeSpaceInfoFlags,
}
impl FreeSpaceInfo {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 8 {
return None;
}
let mut buf = data;
Some(Self {
extent_count: buf.get_u32_le(),
flags: FreeSpaceInfoFlags::from_bits_truncate(buf.get_u32_le()),
})
}
}
#[derive(Debug, Clone)]
pub struct QgroupStatus {
pub version: u64,
pub generation: u64,
pub flags: u64,
pub scan: u64,
pub enable_gen: Option<u64>,
}
impl QgroupStatus {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 32 {
return None;
}
let mut buf = data;
let version = buf.get_u64_le();
let generation = buf.get_u64_le();
let flags = buf.get_u64_le();
let scan = buf.get_u64_le();
let enable_gen = if buf.remaining() >= 8 {
Some(buf.get_u64_le())
} else {
None
};
Some(Self {
version,
generation,
flags,
scan,
enable_gen,
})
}
}
#[derive(Debug, Clone)]
pub struct QgroupInfo {
pub generation: u64,
pub referenced: u64,
pub referenced_compressed: u64,
pub exclusive: u64,
pub exclusive_compressed: u64,
}
impl QgroupInfo {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < mem::size_of::<raw::btrfs_qgroup_info_item>() {
return None;
}
let mut buf = data;
Some(Self {
generation: buf.get_u64_le(),
referenced: buf.get_u64_le(),
referenced_compressed: buf.get_u64_le(),
exclusive: buf.get_u64_le(),
exclusive_compressed: buf.get_u64_le(),
})
}
}
#[derive(Debug, Clone)]
pub struct QgroupLimit {
pub flags: u64,
pub max_referenced: u64,
pub max_exclusive: u64,
pub rsv_referenced: u64,
pub rsv_exclusive: u64,
}
impl QgroupLimit {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < mem::size_of::<raw::btrfs_qgroup_limit_item>() {
return None;
}
let mut buf = data;
Some(Self {
flags: buf.get_u64_le(),
max_referenced: buf.get_u64_le(),
max_exclusive: buf.get_u64_le(),
rsv_referenced: buf.get_u64_le(),
rsv_exclusive: buf.get_u64_le(),
})
}
}
#[derive(Debug, Clone)]
pub struct DeviceStats {
pub values: Vec<(String, u64)>,
}
impl DeviceStats {
#[must_use]
pub fn parse(data: &[u8]) -> Self {
let stat_names = [
"write_errs",
"read_errs",
"flush_errs",
"corruption_errs",
"generation",
];
let mut buf = data;
let mut values = Vec::new();
for name in &stat_names {
if buf.remaining() >= 8 {
values.push((name.to_string(), buf.get_u64_le()));
}
}
DeviceStats { values }
}
}
#[derive(Debug, Clone)]
pub struct UuidItem {
pub subvol_ids: Vec<u64>,
}
impl UuidItem {
#[must_use]
pub fn parse(data: &[u8]) -> Self {
let mut buf = data;
let mut subvol_ids = Vec::new();
while buf.remaining() >= 8 {
subvol_ids.push(buf.get_u64_le());
}
Self { subvol_ids }
}
}
pub enum ItemPayload {
InodeItem(InodeItem),
InodeRef(Vec<InodeRef>),
InodeExtref(Vec<InodeExtref>),
DirItem(Vec<DirItem>),
DirLogItem {
end: u64,
},
OrphanItem,
RootItem(RootItem),
RootRef(RootRef),
FileExtentItem(FileExtentItem),
ExtentCsum {
data: Vec<u8>,
},
ExtentItem(ExtentItem),
TreeBlockRef,
SharedBlockRef,
ExtentDataRef(ExtentDataRef),
SharedDataRef(SharedDataRef),
ExtentOwnerRef {
root: u64,
},
BlockGroupItem(BlockGroupItem),
FreeSpaceInfo(FreeSpaceInfo),
FreeSpaceExtent,
FreeSpaceBitmap,
ChunkItem(ChunkItem),
DeviceItem(DeviceItem),
DeviceExtent(DeviceExtent),
QgroupStatus(QgroupStatus),
QgroupInfo(QgroupInfo),
QgroupLimit(QgroupLimit),
QgroupRelation,
DeviceStats(DeviceStats),
BalanceItem {
flags: u64,
},
DeviceReplace(DeviceReplaceItem),
UuidItem(UuidItem),
StringItem(Vec<u8>),
RaidStripe(RaidStripeItem),
Unknown(Vec<u8>),
}
#[derive(Debug, Clone)]
pub struct DeviceReplaceItem {
pub src_devid: u64,
pub cursor_left: u64,
pub cursor_right: u64,
pub replace_mode: u64,
pub replace_state: u64,
pub time_started: u64,
pub time_stopped: u64,
pub num_write_errors: u64,
pub num_uncorrectable_read_errors: u64,
}
impl DeviceReplaceItem {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 80 {
return None;
}
let mut buf = data;
Some(Self {
src_devid: buf.get_u64_le(),
cursor_left: buf.get_u64_le(),
cursor_right: buf.get_u64_le(),
replace_mode: buf.get_u64_le(),
replace_state: buf.get_u64_le(),
time_started: buf.get_u64_le(),
time_stopped: buf.get_u64_le(),
num_write_errors: buf.get_u64_le(),
num_uncorrectable_read_errors: buf.get_u64_le(),
})
}
}
#[derive(Debug, Clone)]
pub struct RaidStripeItem {
pub encoding: u64,
pub stripes: Vec<RaidStripeEntry>,
}
#[derive(Debug, Clone)]
pub struct RaidStripeEntry {
pub devid: u64,
pub physical: u64,
}
impl RaidStripeItem {
#[must_use]
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 8 {
return None;
}
let mut buf = data;
let encoding = buf.get_u64_le();
let mut stripes = Vec::new();
while buf.remaining() >= 16 {
stripes.push(RaidStripeEntry {
devid: buf.get_u64_le(),
physical: buf.get_u64_le(),
});
}
Some(Self { encoding, stripes })
}
}
#[must_use]
#[allow(clippy::too_many_lines)]
pub fn parse_item_payload(key: &DiskKey, data: &[u8]) -> ItemPayload {
use crate::tree::KeyType;
match key.key_type {
KeyType::InodeItem => match InodeItem::parse(data) {
Some(v) => ItemPayload::InodeItem(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::InodeRef => ItemPayload::InodeRef(InodeRef::parse_all(data)),
KeyType::InodeExtref => {
ItemPayload::InodeExtref(InodeExtref::parse_all(data))
}
KeyType::DirItem | KeyType::DirIndex | KeyType::XattrItem => {
ItemPayload::DirItem(DirItem::parse_all(data))
}
KeyType::DirLogItem | KeyType::DirLogIndex => {
let end = if data.len() >= 8 {
let mut buf = data;
buf.get_u64_le()
} else {
0
};
ItemPayload::DirLogItem { end }
}
KeyType::OrphanItem => ItemPayload::OrphanItem,
KeyType::RootItem => match RootItem::parse(data) {
Some(v) => ItemPayload::RootItem(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::RootRef | KeyType::RootBackref => match RootRef::parse(data) {
Some(v) => ItemPayload::RootRef(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::ExtentData => match FileExtentItem::parse(data) {
Some(v) => ItemPayload::FileExtentItem(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::ExtentCsum => ItemPayload::ExtentCsum {
data: data.to_vec(),
},
KeyType::ExtentItem | KeyType::MetadataItem => {
match ExtentItem::parse(data, key) {
Some(v) => ItemPayload::ExtentItem(v),
None => ItemPayload::Unknown(data.to_vec()),
}
}
KeyType::TreeBlockRef => ItemPayload::TreeBlockRef,
KeyType::SharedBlockRef => ItemPayload::SharedBlockRef,
KeyType::ExtentDataRef => match ExtentDataRef::parse(data) {
Some(v) => ItemPayload::ExtentDataRef(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::SharedDataRef => match SharedDataRef::parse(data) {
Some(v) => ItemPayload::SharedDataRef(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::ExtentOwnerRef => {
if data.len() >= 8 {
let mut buf = data;
ItemPayload::ExtentOwnerRef {
root: buf.get_u64_le(),
}
} else {
ItemPayload::Unknown(data.to_vec())
}
}
KeyType::BlockGroupItem => match BlockGroupItem::parse(data) {
Some(v) => ItemPayload::BlockGroupItem(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::FreeSpaceInfo => match FreeSpaceInfo::parse(data) {
Some(v) => ItemPayload::FreeSpaceInfo(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::FreeSpaceExtent => ItemPayload::FreeSpaceExtent,
KeyType::FreeSpaceBitmap => ItemPayload::FreeSpaceBitmap,
KeyType::ChunkItem => match ChunkItem::parse(data) {
Some(v) => ItemPayload::ChunkItem(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::DeviceItem => match DeviceItem::parse(data) {
Some(v) => ItemPayload::DeviceItem(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::DeviceExtent => match DeviceExtent::parse(data) {
Some(v) => ItemPayload::DeviceExtent(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::QgroupStatus => match QgroupStatus::parse(data) {
Some(v) => ItemPayload::QgroupStatus(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::QgroupInfo => match QgroupInfo::parse(data) {
Some(v) => ItemPayload::QgroupInfo(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::QgroupLimit => match QgroupLimit::parse(data) {
Some(v) => ItemPayload::QgroupLimit(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::QgroupRelation => ItemPayload::QgroupRelation,
KeyType::PersistentItem => {
if key.objectid == u64::from(raw::BTRFS_DEV_STATS_OBJECTID) {
ItemPayload::DeviceStats(DeviceStats::parse(data))
} else {
ItemPayload::Unknown(data.to_vec())
}
}
KeyType::TemporaryItem => {
if ObjectId::from_raw(key.objectid) == ObjectId::Balance
&& data.len() >= 8
{
ItemPayload::BalanceItem {
flags: {
let mut buf = data;
buf.get_u64_le()
},
}
} else {
ItemPayload::Unknown(data.to_vec())
}
}
KeyType::DeviceReplace => match DeviceReplaceItem::parse(data) {
Some(v) => ItemPayload::DeviceReplace(v),
None => ItemPayload::Unknown(data.to_vec()),
},
KeyType::UuidKeySubvol | KeyType::UuidKeyReceivedSubvol => {
ItemPayload::UuidItem(UuidItem::parse(data))
}
KeyType::StringItem => ItemPayload::StringItem(data.to_vec()),
KeyType::RaidStripe => match RaidStripeItem::parse(data) {
Some(v) => ItemPayload::RaidStripe(v),
None => ItemPayload::Unknown(data.to_vec()),
},
_ => ItemPayload::Unknown(data.to_vec()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compression_type_round_trip() {
for v in 0..=3 {
let ct = CompressionType::from_raw(v);
assert_eq!(ct.to_raw(), v);
}
assert_eq!(CompressionType::from_raw(0), CompressionType::None);
assert_eq!(CompressionType::from_raw(1), CompressionType::Zlib);
assert_eq!(CompressionType::from_raw(2), CompressionType::Lzo);
assert_eq!(CompressionType::from_raw(3), CompressionType::Zstd);
assert_eq!(CompressionType::from_raw(99), CompressionType::Unknown(99));
assert_eq!(CompressionType::Unknown(99).to_raw(), 99);
}
#[test]
fn compression_type_names() {
assert_eq!(CompressionType::None.name(), "none");
assert_eq!(CompressionType::Zlib.name(), "zlib");
assert_eq!(CompressionType::Lzo.name(), "lzo");
assert_eq!(CompressionType::Zstd.name(), "zstd");
assert_eq!(CompressionType::Unknown(42).name(), "unknown");
}
#[test]
fn file_extent_type_round_trip() {
assert_eq!(FileExtentType::from_raw(0), FileExtentType::Inline);
assert_eq!(FileExtentType::from_raw(1), FileExtentType::Regular);
assert_eq!(FileExtentType::from_raw(2), FileExtentType::Prealloc);
assert_eq!(FileExtentType::from_raw(77), FileExtentType::Unknown(77));
for v in 0..=2 {
let ft = FileExtentType::from_raw(v);
assert_eq!(ft.to_raw(), v);
}
assert_eq!(FileExtentType::Unknown(77).to_raw(), 77);
}
#[test]
fn file_extent_type_names() {
assert_eq!(FileExtentType::Inline.name(), "inline");
assert_eq!(FileExtentType::Regular.name(), "regular");
assert_eq!(FileExtentType::Prealloc.name(), "prealloc");
assert_eq!(FileExtentType::Unknown(5).name(), "unknown");
}
#[test]
fn file_type_from_raw_all_variants() {
assert_eq!(FileType::from_raw(0), FileType::Unknown);
assert_eq!(FileType::from_raw(1), FileType::RegFile);
assert_eq!(FileType::from_raw(2), FileType::Dir);
assert_eq!(FileType::from_raw(3), FileType::Chrdev);
assert_eq!(FileType::from_raw(4), FileType::Blkdev);
assert_eq!(FileType::from_raw(5), FileType::Fifo);
assert_eq!(FileType::from_raw(6), FileType::Sock);
assert_eq!(FileType::from_raw(7), FileType::Symlink);
assert_eq!(FileType::from_raw(8), FileType::Xattr);
assert_eq!(FileType::from_raw(99), FileType::Other(99));
}
#[test]
fn file_type_names() {
assert_eq!(FileType::Unknown.name(), "UNKNOWN");
assert_eq!(FileType::RegFile.name(), "FILE");
assert_eq!(FileType::Dir.name(), "DIR");
assert_eq!(FileType::Chrdev.name(), "CHRDEV");
assert_eq!(FileType::Blkdev.name(), "BLKDEV");
assert_eq!(FileType::Fifo.name(), "FIFO");
assert_eq!(FileType::Sock.name(), "SOCK");
assert_eq!(FileType::Symlink.name(), "SYMLINK");
assert_eq!(FileType::Xattr.name(), "XATTR");
assert_eq!(FileType::Other(200).name(), "UNKNOWN");
}
#[test]
fn block_group_item_parse() {
let mut buf = Vec::new();
buf.extend_from_slice(&1000u64.to_le_bytes()); buf.extend_from_slice(&256u64.to_le_bytes()); buf.extend_from_slice(
&(raw::BTRFS_BLOCK_GROUP_DATA as u64).to_le_bytes(),
);
let item = BlockGroupItem::parse(&buf).unwrap();
assert_eq!(item.used, 1000);
assert_eq!(item.chunk_objectid, 256);
assert_eq!(item.flags, BlockGroupFlags::DATA);
}
#[test]
fn block_group_item_too_short() {
assert!(BlockGroupItem::parse(&[0; 23]).is_none());
}
#[test]
fn free_space_info_parse() {
let mut buf = Vec::new();
buf.extend_from_slice(&42u32.to_le_bytes());
buf.extend_from_slice(&7u32.to_le_bytes());
let info = FreeSpaceInfo::parse(&buf).unwrap();
assert_eq!(info.extent_count, 42);
assert_eq!(info.flags, FreeSpaceInfoFlags::from_bits_truncate(7));
}
#[test]
fn free_space_info_too_short() {
assert!(FreeSpaceInfo::parse(&[0; 7]).is_none());
}
#[test]
fn dev_extent_parse() {
let size = mem::size_of::<raw::btrfs_dev_extent>();
let mut buf = vec![0u8; size];
buf[0..8].copy_from_slice(&3u64.to_le_bytes()); buf[8..16].copy_from_slice(&256u64.to_le_bytes()); buf[16..24].copy_from_slice(&0x10000u64.to_le_bytes()); buf[24..32].copy_from_slice(&0x40000u64.to_le_bytes()); buf[32..48].copy_from_slice(&[0xAB; 16]);
let de = DeviceExtent::parse(&buf).unwrap();
assert_eq!(de.chunk_tree, 3);
assert_eq!(de.chunk_objectid, 256);
assert_eq!(de.chunk_offset, 0x10000);
assert_eq!(de.length, 0x40000);
assert_eq!(de.chunk_tree_uuid.as_bytes(), &[0xAB; 16]);
}
#[test]
fn dev_extent_too_short() {
let size = mem::size_of::<raw::btrfs_dev_extent>();
assert!(DeviceExtent::parse(&vec![0u8; size - 1]).is_none());
}
#[test]
fn extent_data_ref_parse() {
let size = mem::size_of::<raw::btrfs_extent_data_ref>();
let mut buf = vec![0u8; size];
buf[0..8].copy_from_slice(&5u64.to_le_bytes()); buf[8..16].copy_from_slice(&256u64.to_le_bytes()); buf[16..24].copy_from_slice(&0u64.to_le_bytes()); buf[24..28].copy_from_slice(&1u32.to_le_bytes()); let edr = ExtentDataRef::parse(&buf).unwrap();
assert_eq!(edr.root, 5);
assert_eq!(edr.objectid, 256);
assert_eq!(edr.offset, 0);
assert_eq!(edr.count, 1);
}
#[test]
fn extent_data_ref_too_short() {
assert!(ExtentDataRef::parse(&[0; 27]).is_none());
}
#[test]
fn shared_data_ref_parse() {
let buf = 17u32.to_le_bytes();
let sdr = SharedDataRef::parse(&buf).unwrap();
assert_eq!(sdr.count, 17);
}
#[test]
fn shared_data_ref_too_short() {
assert!(SharedDataRef::parse(&[0; 3]).is_none());
}
#[test]
fn qgroup_info_parse() {
let mut buf = Vec::new();
buf.extend_from_slice(&100u64.to_le_bytes()); buf.extend_from_slice(&4096u64.to_le_bytes()); buf.extend_from_slice(&4096u64.to_le_bytes()); buf.extend_from_slice(&2048u64.to_le_bytes()); buf.extend_from_slice(&2048u64.to_le_bytes()); let qi = QgroupInfo::parse(&buf).unwrap();
assert_eq!(qi.generation, 100);
assert_eq!(qi.referenced, 4096);
assert_eq!(qi.referenced_compressed, 4096);
assert_eq!(qi.exclusive, 2048);
assert_eq!(qi.exclusive_compressed, 2048);
}
#[test]
fn qgroup_info_too_short() {
assert!(QgroupInfo::parse(&[0; 39]).is_none());
}
#[test]
fn qgroup_limit_parse() {
let mut buf = Vec::new();
buf.extend_from_slice(&3u64.to_le_bytes()); buf.extend_from_slice(&1_000_000u64.to_le_bytes()); buf.extend_from_slice(&500_000u64.to_le_bytes()); buf.extend_from_slice(&0u64.to_le_bytes()); buf.extend_from_slice(&0u64.to_le_bytes()); let ql = QgroupLimit::parse(&buf).unwrap();
assert_eq!(ql.flags, 3);
assert_eq!(ql.max_referenced, 1_000_000);
assert_eq!(ql.max_exclusive, 500_000);
assert_eq!(ql.rsv_referenced, 0);
assert_eq!(ql.rsv_exclusive, 0);
}
#[test]
fn qgroup_limit_too_short() {
assert!(QgroupLimit::parse(&[0; 39]).is_none());
}
#[test]
fn qgroup_status_parse_minimal() {
let mut buf = Vec::new();
buf.extend_from_slice(&1u64.to_le_bytes()); buf.extend_from_slice(&50u64.to_le_bytes()); buf.extend_from_slice(&2u64.to_le_bytes()); buf.extend_from_slice(&0u64.to_le_bytes()); let qs = QgroupStatus::parse(&buf).unwrap();
assert_eq!(qs.version, 1);
assert_eq!(qs.generation, 50);
assert_eq!(qs.flags, 2);
assert_eq!(qs.scan, 0);
assert!(qs.enable_gen.is_none());
}
#[test]
fn qgroup_status_parse_with_enable_gen() {
let mut buf = Vec::new();
buf.extend_from_slice(&1u64.to_le_bytes());
buf.extend_from_slice(&50u64.to_le_bytes());
buf.extend_from_slice(&2u64.to_le_bytes());
buf.extend_from_slice(&0u64.to_le_bytes());
buf.extend_from_slice(&99u64.to_le_bytes()); let qs = QgroupStatus::parse(&buf).unwrap();
assert_eq!(qs.enable_gen, Some(99));
}
#[test]
fn qgroup_status_too_short() {
assert!(QgroupStatus::parse(&[0; 31]).is_none());
}
#[test]
fn dev_replace_item_parse() {
let mut buf = vec![0u8; 80];
buf[0..8].copy_from_slice(&1u64.to_le_bytes()); buf[8..16].copy_from_slice(&0x1000u64.to_le_bytes()); buf[16..24].copy_from_slice(&0x2000u64.to_le_bytes()); buf[24..32].copy_from_slice(&0u64.to_le_bytes()); buf[32..40].copy_from_slice(&2u64.to_le_bytes()); buf[40..48].copy_from_slice(&1700000000u64.to_le_bytes()); buf[48..56].copy_from_slice(&1700000100u64.to_le_bytes()); buf[56..64].copy_from_slice(&3u64.to_le_bytes()); buf[64..72].copy_from_slice(&5u64.to_le_bytes()); let dri = DeviceReplaceItem::parse(&buf).unwrap();
assert_eq!(dri.src_devid, 1);
assert_eq!(dri.cursor_left, 0x1000);
assert_eq!(dri.cursor_right, 0x2000);
assert_eq!(dri.replace_state, 2);
assert_eq!(dri.time_started, 1700000000);
assert_eq!(dri.time_stopped, 1700000100);
assert_eq!(dri.num_write_errors, 3);
assert_eq!(dri.num_uncorrectable_read_errors, 5);
}
#[test]
fn dev_replace_item_too_short() {
assert!(DeviceReplaceItem::parse(&[0; 79]).is_none());
}
#[test]
fn raid_stripe_item_parse() {
let mut buf = Vec::new();
buf.extend_from_slice(&1u64.to_le_bytes()); buf.extend_from_slice(&1u64.to_le_bytes()); buf.extend_from_slice(&0x10000u64.to_le_bytes()); buf.extend_from_slice(&2u64.to_le_bytes());
buf.extend_from_slice(&0x20000u64.to_le_bytes());
let rsi = RaidStripeItem::parse(&buf).unwrap();
assert_eq!(rsi.encoding, 1);
assert_eq!(rsi.stripes.len(), 2);
assert_eq!(rsi.stripes[0].devid, 1);
assert_eq!(rsi.stripes[0].physical, 0x10000);
assert_eq!(rsi.stripes[1].devid, 2);
assert_eq!(rsi.stripes[1].physical, 0x20000);
}
#[test]
fn raid_stripe_item_no_stripes() {
let buf = 42u64.to_le_bytes();
let rsi = RaidStripeItem::parse(&buf).unwrap();
assert_eq!(rsi.encoding, 42);
assert!(rsi.stripes.is_empty());
}
#[test]
fn raid_stripe_item_too_short() {
assert!(RaidStripeItem::parse(&[0; 7]).is_none());
}
#[test]
fn inode_ref_parse_single() {
let mut buf = Vec::new();
buf.extend_from_slice(&42u64.to_le_bytes()); buf.extend_from_slice(&4u16.to_le_bytes()); buf.extend_from_slice(b"test");
let refs = InodeRef::parse_all(&buf);
assert_eq!(refs.len(), 1);
assert_eq!(refs[0].index, 42);
assert_eq!(refs[0].name, b"test");
}
#[test]
fn inode_ref_parse_multiple() {
let mut buf = Vec::new();
buf.extend_from_slice(&1u64.to_le_bytes());
buf.extend_from_slice(&3u16.to_le_bytes());
buf.extend_from_slice(b"abc");
buf.extend_from_slice(&2u64.to_le_bytes());
buf.extend_from_slice(&2u16.to_le_bytes());
buf.extend_from_slice(b"xy");
let refs = InodeRef::parse_all(&buf);
assert_eq!(refs.len(), 2);
assert_eq!(refs[0].index, 1);
assert_eq!(refs[0].name, b"abc");
assert_eq!(refs[1].index, 2);
assert_eq!(refs[1].name, b"xy");
}
#[test]
fn inode_ref_parse_truncated() {
let mut buf = Vec::new();
buf.extend_from_slice(&1u64.to_le_bytes());
buf.extend_from_slice(&10u16.to_le_bytes()); buf.extend_from_slice(b"abc"); let refs = InodeRef::parse_all(&buf);
assert!(refs.is_empty());
}
#[test]
fn inode_extref_parse_single() {
let mut buf = Vec::new();
buf.extend_from_slice(&256u64.to_le_bytes()); buf.extend_from_slice(&3u64.to_le_bytes()); buf.extend_from_slice(&5u16.to_le_bytes()); buf.extend_from_slice(b"hello");
let refs = InodeExtref::parse_all(&buf);
assert_eq!(refs.len(), 1);
assert_eq!(refs[0].parent, 256);
assert_eq!(refs[0].index, 3);
assert_eq!(refs[0].name, b"hello");
}
#[test]
fn dir_item_parse_single() {
let dir_item_size = mem::size_of::<raw::btrfs_dir_item>();
let mut buf = vec![0u8; dir_item_size];
buf[0..8].copy_from_slice(&256u64.to_le_bytes()); buf[8] = 1; buf[9..17].copy_from_slice(&0u64.to_le_bytes()); buf[17..25].copy_from_slice(&100u64.to_le_bytes());
buf[25..27].copy_from_slice(&0u16.to_le_bytes());
buf[27..29].copy_from_slice(&4u16.to_le_bytes());
buf[29] = 1; buf.extend_from_slice(b"file");
let items = DirItem::parse_all(&buf);
assert_eq!(items.len(), 1);
assert_eq!(items[0].transid, 100);
assert_eq!(items[0].file_type, FileType::RegFile);
assert_eq!(items[0].name, b"file");
assert!(items[0].data.is_empty());
}
#[test]
fn root_ref_parse() {
let hdr_size = mem::size_of::<raw::btrfs_root_ref>();
let mut buf = vec![0u8; hdr_size];
buf[0..8].copy_from_slice(&256u64.to_le_bytes()); buf[8..16].copy_from_slice(&7u64.to_le_bytes()); buf[16..18].copy_from_slice(&6u16.to_le_bytes()); buf.extend_from_slice(b"subvol");
let rr = RootRef::parse(&buf).unwrap();
assert_eq!(rr.dirid, 256);
assert_eq!(rr.sequence, 7);
assert_eq!(rr.name, b"subvol");
}
#[test]
fn root_ref_too_short() {
let hdr_size = mem::size_of::<raw::btrfs_root_ref>();
assert!(RootRef::parse(&vec![0u8; hdr_size - 1]).is_none());
}
#[test]
fn root_ref_serialize_round_trip() {
let bytes = RootRef::serialize(256, 42, b"snapshot-1");
let parsed = RootRef::parse(&bytes).unwrap();
assert_eq!(parsed.dirid, 256);
assert_eq!(parsed.sequence, 42);
assert_eq!(parsed.name, b"snapshot-1");
assert_eq!(bytes.len(), mem::size_of::<raw::btrfs_root_ref>() + 10);
}
#[test]
fn root_ref_serialize_empty_name() {
let bytes = RootRef::serialize(7, 0, b"");
let parsed = RootRef::parse(&bytes).unwrap();
assert_eq!(parsed.dirid, 7);
assert_eq!(parsed.sequence, 0);
assert!(parsed.name.is_empty());
}
#[test]
fn uuid_item_parse() {
let mut buf = Vec::new();
buf.extend_from_slice(&256u64.to_le_bytes());
buf.extend_from_slice(&257u64.to_le_bytes());
buf.extend_from_slice(&258u64.to_le_bytes());
let ui = UuidItem::parse(&buf);
assert_eq!(ui.subvol_ids, vec![256, 257, 258]);
}
#[test]
fn uuid_item_empty() {
let ui = UuidItem::parse(&[]);
assert!(ui.subvol_ids.is_empty());
}
#[test]
fn dev_stats_parse() {
let mut buf = Vec::new();
buf.extend_from_slice(&1u64.to_le_bytes()); buf.extend_from_slice(&2u64.to_le_bytes()); buf.extend_from_slice(&3u64.to_le_bytes()); buf.extend_from_slice(&4u64.to_le_bytes()); buf.extend_from_slice(&5u64.to_le_bytes()); let ds = DeviceStats::parse(&buf);
assert_eq!(ds.values.len(), 5);
assert_eq!(ds.values[0], ("write_errs".to_string(), 1));
assert_eq!(ds.values[1], ("read_errs".to_string(), 2));
assert_eq!(ds.values[2], ("flush_errs".to_string(), 3));
assert_eq!(ds.values[3], ("corruption_errs".to_string(), 4));
assert_eq!(ds.values[4], ("generation".to_string(), 5));
}
#[test]
fn dev_stats_partial() {
let mut buf = Vec::new();
buf.extend_from_slice(&10u64.to_le_bytes());
buf.extend_from_slice(&20u64.to_le_bytes());
let ds = DeviceStats::parse(&buf);
assert_eq!(ds.values.len(), 2);
assert_eq!(ds.values[0].1, 10);
assert_eq!(ds.values[1].1, 20);
}
#[test]
fn file_extent_item_inline() {
let mut buf = vec![0u8; 21 + 10]; buf[0..8].copy_from_slice(&7u64.to_le_bytes()); buf[8..16].copy_from_slice(&10u64.to_le_bytes()); buf[16] = 0; buf[20] = 0; buf[21..31].copy_from_slice(&[0xAA; 10]); let fei = FileExtentItem::parse(&buf).unwrap();
assert_eq!(fei.generation, 7);
assert_eq!(fei.ram_bytes, 10);
assert_eq!(fei.compression, CompressionType::None);
assert_eq!(fei.extent_type, FileExtentType::Inline);
match fei.body {
FileExtentBody::Inline { inline_size } => {
assert_eq!(inline_size, 10)
}
_ => panic!("expected inline body"),
}
}
#[test]
fn file_extent_item_regular() {
let mut buf = vec![0u8; 53];
buf[0..8].copy_from_slice(&100u64.to_le_bytes()); buf[8..16].copy_from_slice(&4096u64.to_le_bytes()); buf[16] = 1; buf[20] = 1; buf[21..29].copy_from_slice(&0x100000u64.to_le_bytes()); buf[29..37].copy_from_slice(&4096u64.to_le_bytes()); buf[37..45].copy_from_slice(&0u64.to_le_bytes()); buf[45..53].copy_from_slice(&4096u64.to_le_bytes()); let fei = FileExtentItem::parse(&buf).unwrap();
assert_eq!(fei.generation, 100);
assert_eq!(fei.compression, CompressionType::Zlib);
assert_eq!(fei.extent_type, FileExtentType::Regular);
match fei.body {
FileExtentBody::Regular {
disk_bytenr,
disk_num_bytes,
offset,
num_bytes,
} => {
assert_eq!(disk_bytenr, 0x100000);
assert_eq!(disk_num_bytes, 4096);
assert_eq!(offset, 0);
assert_eq!(num_bytes, 4096);
}
_ => panic!("expected regular body"),
}
}
#[test]
fn file_extent_item_too_short() {
assert!(FileExtentItem::parse(&[0; 20]).is_none());
}
#[test]
fn file_extent_item_regular_too_short() {
let mut buf = vec![0u8; 21];
buf[20] = 1; assert!(FileExtentItem::parse(&buf).is_none());
}
#[test]
fn file_extent_item_to_bytes_regular_round_trip() {
let bytes = FileExtentItem::to_bytes_regular(
42,
65536,
CompressionType::Zstd,
false,
0x200000,
4096,
0,
65536,
);
assert_eq!(bytes.len(), FileExtentItem::REGULAR_SIZE);
let parsed = FileExtentItem::parse(&bytes).unwrap();
assert_eq!(parsed.generation, 42);
assert_eq!(parsed.ram_bytes, 65536);
assert_eq!(parsed.compression, CompressionType::Zstd);
assert_eq!(parsed.extent_type, FileExtentType::Regular);
match parsed.body {
FileExtentBody::Regular {
disk_bytenr,
disk_num_bytes,
offset,
num_bytes,
} => {
assert_eq!(disk_bytenr, 0x200000);
assert_eq!(disk_num_bytes, 4096);
assert_eq!(offset, 0);
assert_eq!(num_bytes, 65536);
}
_ => panic!("expected regular body"),
}
}
#[test]
fn file_extent_item_to_bytes_regular_prealloc_flag() {
let bytes = FileExtentItem::to_bytes_regular(
1,
4096,
CompressionType::None,
true,
0x10000,
4096,
0,
4096,
);
let parsed = FileExtentItem::parse(&bytes).unwrap();
assert_eq!(parsed.extent_type, FileExtentType::Prealloc);
}
#[test]
fn file_extent_item_to_bytes_inline_round_trip() {
let payload = b"hello inline";
let bytes = FileExtentItem::to_bytes_inline(
7,
payload.len() as u64,
CompressionType::None,
payload,
);
assert_eq!(bytes.len(), FileExtentItem::HEADER_SIZE + payload.len());
let parsed = FileExtentItem::parse(&bytes).unwrap();
assert_eq!(parsed.generation, 7);
assert_eq!(parsed.ram_bytes, payload.len() as u64);
assert_eq!(parsed.compression, CompressionType::None);
assert_eq!(parsed.extent_type, FileExtentType::Inline);
match parsed.body {
FileExtentBody::Inline { inline_size } => {
assert_eq!(inline_size, payload.len());
}
_ => panic!("expected inline body"),
}
assert_eq!(&bytes[FileExtentItem::HEADER_SIZE..], payload);
}
#[test]
fn raw_crc32c_known_value() {
assert_eq!(raw_crc32c(0, &[]), 0);
let raw = raw_crc32c(0, b"123456789");
let standard = crc32c::crc32c(b"123456789");
assert_eq!(standard, 0xE3069283);
assert_ne!(raw, standard);
assert_eq!(raw, raw_crc32c(0, b"123456789"));
let chained = raw_crc32c(raw, b"more");
assert_ne!(chained, raw);
}
#[test]
fn extent_data_ref_hash_deterministic() {
let h1 = extent_data_ref_hash(5, 256, 0);
let h2 = extent_data_ref_hash(5, 256, 0);
assert_eq!(h1, h2);
let h3 = extent_data_ref_hash(5, 256, 4096);
assert_ne!(h1, h3);
}
#[test]
fn block_group_flags_type_name() {
assert_eq!(BlockGroupFlags::DATA.type_name(), "Data");
assert_eq!(BlockGroupFlags::METADATA.type_name(), "Metadata");
assert_eq!(BlockGroupFlags::SYSTEM.type_name(), "System");
assert_eq!(
(BlockGroupFlags::DATA | BlockGroupFlags::METADATA).type_name(),
"Data+Metadata"
);
assert_eq!(BlockGroupFlags::GLOBAL_RSV.type_name(), "GlobalReserve");
}
#[test]
fn block_group_flags_profile_name() {
assert_eq!(BlockGroupFlags::DATA.profile_name(), "single");
assert_eq!(
(BlockGroupFlags::DATA | BlockGroupFlags::DUP).profile_name(),
"DUP"
);
assert_eq!(
(BlockGroupFlags::DATA | BlockGroupFlags::RAID0).profile_name(),
"RAID0"
);
assert_eq!(
(BlockGroupFlags::DATA | BlockGroupFlags::RAID1).profile_name(),
"RAID1"
);
assert_eq!(
(BlockGroupFlags::DATA | BlockGroupFlags::RAID10).profile_name(),
"RAID10"
);
assert_eq!(
(BlockGroupFlags::DATA | BlockGroupFlags::RAID5).profile_name(),
"RAID5"
);
assert_eq!(
(BlockGroupFlags::DATA | BlockGroupFlags::RAID6).profile_name(),
"RAID6"
);
assert_eq!(
(BlockGroupFlags::DATA | BlockGroupFlags::RAID1C3).profile_name(),
"RAID1C3"
);
assert_eq!(
(BlockGroupFlags::DATA | BlockGroupFlags::RAID1C4).profile_name(),
"RAID1C4"
);
}
#[test]
fn extent_item_skinny_size() {
let bytes = ExtentItem::to_bytes_skinny(1, 42, 5);
assert_eq!(bytes.len(), ExtentItem::SKINNY_SIZE);
assert_eq!(bytes.len(), 33);
}
#[test]
fn extent_item_non_skinny_size() {
let key = DiskKey {
objectid: 256,
key_type: KeyType::InodeItem,
offset: 0,
};
let bytes = ExtentItem::to_bytes_non_skinny(1, 42, 5, &key, 0);
assert_eq!(bytes.len(), ExtentItem::NON_SKINNY_SIZE);
assert_eq!(bytes.len(), 51);
}
#[test]
fn extent_item_skinny_non_skinny_header_match() {
let skinny = ExtentItem::to_bytes_skinny(1, 42, 5);
let key = DiskKey {
objectid: 0,
key_type: KeyType::from_raw(0),
offset: 0,
};
let non_skinny = ExtentItem::to_bytes_non_skinny(1, 42, 5, &key, 0);
assert_eq!(&skinny[..24], &non_skinny[..24]);
}
#[test]
fn extent_item_flags_are_tree_block() {
let bytes = ExtentItem::to_bytes_skinny(1, 42, 5);
let flags = u64::from_le_bytes(bytes[16..24].try_into().unwrap());
assert_eq!(flags, ExtentFlags::TREE_BLOCK.bits());
}
#[test]
fn root_item_to_bytes_round_trip() {
let original = RootItem::new_internal(42, 65536, 0);
let bytes = original.to_bytes();
assert_eq!(bytes.len(), 439);
let parsed = RootItem::parse(&bytes).expect("parse failed");
assert_eq!(parsed.generation, 42);
assert_eq!(parsed.bytenr, 65536);
assert_eq!(parsed.level, 0);
assert_eq!(parsed.refs, 1);
}
#[test]
fn block_group_item_to_bytes_round_trip() {
let bg = BlockGroupItem {
used: 1024 * 1024,
chunk_objectid: 256,
flags: BlockGroupFlags::METADATA | BlockGroupFlags::DUP,
};
let bytes = bg.to_bytes();
assert_eq!(bytes.len(), 24);
let parsed = BlockGroupItem::parse(&bytes).unwrap();
assert_eq!(parsed.used, bg.used);
assert_eq!(parsed.chunk_objectid, bg.chunk_objectid);
assert_eq!(parsed.flags, bg.flags);
}
#[test]
fn inode_item_args_to_bytes_size() {
let args = InodeItemArgs {
generation: 7,
size: 42,
nbytes: 4096,
nlink: 1,
uid: 1000,
gid: 1000,
mode: 0o100644,
time: Timespec { sec: 100, nsec: 0 },
};
let bytes = args.to_bytes();
assert_eq!(bytes.len(), 160);
let parsed = InodeItem::parse(&bytes).unwrap();
assert_eq!(parsed.generation, 7);
assert_eq!(parsed.size, 42);
assert_eq!(parsed.nlink, 1);
}
#[test]
fn dir_item_serialize_round_trip() {
let location = DiskKey {
objectid: 257,
key_type: KeyType::InodeItem,
offset: 0,
};
let bytes = DirItem::serialize(
&location,
7,
raw::BTRFS_FT_REG_FILE as u8,
b"hello.txt",
);
let items = DirItem::parse_all(&bytes);
assert_eq!(items.len(), 1);
assert_eq!(items[0].location.objectid, 257);
assert_eq!(items[0].transid, 7);
assert_eq!(items[0].name, b"hello.txt");
}
#[test]
fn inode_ref_serialize_round_trip() {
let bytes = InodeRef::serialize(2, b"hello.txt");
let refs = InodeRef::parse_all(&bytes);
assert_eq!(refs.len(), 1);
assert_eq!(refs[0].index, 2);
assert_eq!(refs[0].name, b"hello.txt");
}
}