1use crate::{
13 field_size,
14 raw::{
15 BTRFS_BLOCK_GROUP_ITEM_KEY, BTRFS_CHUNK_ITEM_KEY, BTRFS_CHUNK_TREE_OBJECTID,
16 BTRFS_EXTENT_TREE_OBJECTID, BTRFS_FIRST_CHUNK_TREE_OBJECTID, btrfs_block_group_item,
17 btrfs_chunk, btrfs_stripe,
18 },
19 space::BlockGroupFlags,
20 tree_search::{SearchKey, tree_search},
21};
22use std::os::unix::io::BorrowedFd;
23
24#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct DeviceAllocation {
32 pub devid: u64,
34 pub flags: BlockGroupFlags,
37 pub bytes: u64,
39}
40
41const CHUNK_LENGTH_OFF: usize = std::mem::offset_of!(btrfs_chunk, length);
42const CHUNK_STRIPE_LEN_OFF: usize = std::mem::offset_of!(btrfs_chunk, stripe_len);
43const CHUNK_TYPE_OFF: usize = std::mem::offset_of!(btrfs_chunk, type_);
44const CHUNK_NUM_STRIPES_OFF: usize = std::mem::offset_of!(btrfs_chunk, num_stripes);
45const CHUNK_FIRST_STRIPE_OFF: usize = std::mem::offset_of!(btrfs_chunk, stripe);
46
47const STRIPE_SIZE: usize = std::mem::size_of::<btrfs_stripe>();
48const STRIPE_DEVID_OFF: usize = std::mem::offset_of!(btrfs_stripe, devid);
49const STRIPE_OFFSET_OFF: usize = std::mem::offset_of!(btrfs_stripe, offset);
50
51const CHUNK_MIN_LEN: usize = CHUNK_FIRST_STRIPE_OFF + STRIPE_SIZE; #[derive(Debug, Clone, PartialEq, Eq)]
61pub struct ChunkEntry {
62 pub devid: u64,
64 pub physical_start: u64,
66 pub logical_start: u64,
68 pub length: u64,
71 pub flags: BlockGroupFlags,
73 pub used: u64,
76}
77
78pub fn device_chunk_allocations(fd: BorrowedFd) -> nix::Result<Vec<DeviceAllocation>> {
91 let mut allocs: Vec<DeviceAllocation> = Vec::new();
92
93 tree_search(
94 fd,
95 SearchKey::for_type(
96 BTRFS_CHUNK_TREE_OBJECTID as u64,
97 BTRFS_CHUNK_ITEM_KEY as u32,
98 ),
99 |_hdr, data| {
100 if let Some((stripe_len, flags, stripes)) = parse_chunk(data) {
101 for devid in stripes {
102 accumulate(&mut allocs, devid, flags, stripe_len);
103 }
104 }
105 Ok(())
106 },
107 )?;
108
109 Ok(allocs)
110}
111
112pub fn chunk_list(fd: BorrowedFd) -> nix::Result<Vec<ChunkEntry>> {
122 let mut entries: Vec<ChunkEntry> = Vec::new();
123
124 tree_search(
125 fd,
126 SearchKey::for_objectid_range(
127 BTRFS_CHUNK_TREE_OBJECTID as u64,
128 BTRFS_CHUNK_ITEM_KEY as u32,
129 BTRFS_FIRST_CHUNK_TREE_OBJECTID as u64,
130 BTRFS_FIRST_CHUNK_TREE_OBJECTID as u64,
131 ),
132 |hdr, data| {
133 if let Some(stripes) = parse_chunk_stripes(data) {
134 let logical_start = hdr.offset;
135 let length = read_le_u64(data, CHUNK_LENGTH_OFF);
136 let type_bits = read_le_u64(data, CHUNK_TYPE_OFF);
137 let flags = BlockGroupFlags::from_bits_truncate(type_bits);
138 let used = block_group_used(fd, logical_start).unwrap_or(0);
139 for (devid, physical_start) in stripes {
140 entries.push(ChunkEntry {
141 devid,
142 physical_start,
143 logical_start,
144 length,
145 flags,
146 used,
147 });
148 }
149 }
150 Ok(())
151 },
152 )?;
153
154 Ok(entries)
155}
156
157fn block_group_used(fd: BorrowedFd, logical_start: u64) -> Option<u64> {
162 let mut used: Option<u64> = None;
163 tree_search(
164 fd,
165 SearchKey {
166 tree_id: BTRFS_EXTENT_TREE_OBJECTID as u64,
167 min_objectid: logical_start,
168 max_objectid: logical_start,
169 min_type: BTRFS_BLOCK_GROUP_ITEM_KEY,
170 max_type: BTRFS_BLOCK_GROUP_ITEM_KEY,
171 min_offset: 0,
172 max_offset: u64::MAX,
173 min_transid: 0,
174 max_transid: u64::MAX,
175 },
176 |_hdr, data| {
177 let used_off = std::mem::offset_of!(btrfs_block_group_item, used);
178 if data.len() >= used_off + field_size!(btrfs_block_group_item, used) {
179 used = Some(read_le_u64(data, used_off));
180 }
181 Ok(())
182 },
183 )
184 .ok()?;
185 used
186}
187
188fn parse_chunk(data: &[u8]) -> Option<(u64, BlockGroupFlags, impl Iterator<Item = u64> + '_)> {
193 if data.len() < CHUNK_MIN_LEN {
194 return None;
195 }
196
197 let stripe_len = read_le_u64(data, CHUNK_STRIPE_LEN_OFF);
198 let type_bits = read_le_u64(data, CHUNK_TYPE_OFF);
199 let num_stripes = read_le_u16(data, CHUNK_NUM_STRIPES_OFF) as usize;
200 let _length = read_le_u64(data, CHUNK_LENGTH_OFF);
201
202 let expected_len = CHUNK_FIRST_STRIPE_OFF + num_stripes * STRIPE_SIZE;
204 if data.len() < expected_len || num_stripes == 0 {
205 return None;
206 }
207
208 let flags = BlockGroupFlags::from_bits_truncate(type_bits);
209
210 let devids = (0..num_stripes).map(move |i| {
211 let stripe_off = CHUNK_FIRST_STRIPE_OFF + i * STRIPE_SIZE;
212 read_le_u64(data, stripe_off + STRIPE_DEVID_OFF)
213 });
214
215 Some((stripe_len, flags, devids))
216}
217
218fn parse_chunk_stripes(data: &[u8]) -> Option<impl Iterator<Item = (u64, u64)> + '_> {
223 if data.len() < CHUNK_MIN_LEN {
224 return None;
225 }
226
227 let num_stripes = read_le_u16(data, CHUNK_NUM_STRIPES_OFF) as usize;
228 let expected_len = CHUNK_FIRST_STRIPE_OFF + num_stripes * STRIPE_SIZE;
229 if data.len() < expected_len || num_stripes == 0 {
230 return None;
231 }
232
233 let iter = (0..num_stripes).map(move |i| {
234 let stripe_off = CHUNK_FIRST_STRIPE_OFF + i * STRIPE_SIZE;
235 let devid = read_le_u64(data, stripe_off + STRIPE_DEVID_OFF);
236 let physical_start = read_le_u64(data, stripe_off + STRIPE_OFFSET_OFF);
237 (devid, physical_start)
238 });
239
240 Some(iter)
241}
242
243fn accumulate(allocs: &mut Vec<DeviceAllocation>, devid: u64, flags: BlockGroupFlags, bytes: u64) {
246 if let Some(entry) = allocs
247 .iter_mut()
248 .find(|a| a.devid == devid && a.flags == flags)
249 {
250 entry.bytes += bytes;
251 } else {
252 allocs.push(DeviceAllocation {
253 devid,
254 flags,
255 bytes,
256 });
257 }
258}
259
260fn read_le_u64(buf: &[u8], off: usize) -> u64 {
261 u64::from_le_bytes(buf[off..off + 8].try_into().unwrap())
262}
263
264fn read_le_u16(buf: &[u8], off: usize) -> u16 {
265 u16::from_le_bytes(buf[off..off + 2].try_into().unwrap())
266}