Skip to main content

fs_ext4/
file_mut.rs

1//! File-write high-level planner (E10).
2//!
3//! Composes bitmap allocation (E5/E6), extent-tree mutation (E7), and
4//! directory-entry mutation (E8) into file-level operations:
5//!
6//! - `split_into_block_writes` — turn a `(offset, data)` byte-range write into
7//!   a list of `(logical_block, offset_in_block, payload_slice)` — the unit
8//!   E11 journals.
9//! - `plan_truncate_shrink` — free the tail extents that fall past a new,
10//!   smaller size; delegates to `extent_mut::plan_free_extent`.
11//! - `plan_truncate_grow` — growing a file without writing is a size-only
12//!   update (creates a sparse hole); emits no extent mutations.
13//! - `plan_append_logical_blocks` — given a file's current size and how many
14//!   bytes the caller wants to append, returns the logical-block range the
15//!   write touches and flags whether the last block is a partial write.
16//!
17//! Plan-only. E11 wraps plan application in a JBD2 transaction.
18
19use crate::error::{Error, Result};
20use crate::extent::{Extent, ExtentHeader};
21use crate::extent_mut::{plan_free_extent, ExtentMutation};
22
23/// Plan a shrink through all supported ext4 extent-tree depths. No allocation
24/// or writes occur while planning. Empty subtrees are reclaimed, including
25/// their index/leaf blocks; nonempty nodes retain their original allocation.
26/// The caller verifies external-node checksums in `read` and patches checksums
27/// on returned writes before committing one inode/bitmap/tree transaction.
28pub fn plan_truncate_shrink_deep(
29    old_size: u64,
30    new_size: u64,
31    root: &[u8],
32    block_size: u32,
33    blocks_count: u64,
34    read: &mut impl FnMut(u64) -> Result<Vec<u8>>,
35) -> Result<(SizeChange, Vec<ExtentMutation>)> {
36    if new_size > old_size || !(1024..=65536).contains(&block_size) || !block_size.is_power_of_two()
37    {
38        return Err(Error::InvalidArgument(
39            "invalid truncate size or block size",
40        ));
41    }
42    let header = ExtentHeader::parse(root)?;
43    if root.len() != 60 || header.depth > crate::extent::EXT4_EXT_MAX_DEPTH {
44        return Err(Error::CorruptExtentTree("invalid inline extent root"));
45    }
46    let mut walk = ShrinkWalk {
47        end: new_size.div_ceil(block_size as u64),
48        block_size,
49        blocks_count,
50        read,
51        visited: std::collections::BTreeSet::new(),
52        physical: Vec::new(),
53        logical_end: 0,
54        mutations: Vec::new(),
55    };
56    let (mut bytes, first) = walk.node(root, header.depth, true)?;
57    if first.is_none() {
58        bytes[6..8].copy_from_slice(&0u16.to_le_bytes());
59    }
60    // Ext4 does not share data/tree blocks between extents. Check before any
61    // bitmap is freed, including data aliases into a node encountered later.
62    walk.physical.sort_unstable();
63    for pair in walk.physical.windows(2) {
64        if pair[0].1 > pair[1].0 {
65            return Err(Error::CorruptExtentTree(
66                "overlapping physical extent ranges",
67            ));
68        }
69    }
70    walk.mutations
71        .insert(0, ExtentMutation::WriteRoot { bytes });
72    Ok((SizeChange { old_size, new_size }, walk.mutations))
73}
74
75struct ShrinkWalk<'a, R> {
76    end: u64,
77    block_size: u32,
78    blocks_count: u64,
79    read: &'a mut R,
80    visited: std::collections::BTreeSet<u64>,
81    physical: Vec<(u64, u64)>,
82    logical_end: u64,
83    mutations: Vec<ExtentMutation>,
84}
85
86impl<R: FnMut(u64) -> Result<Vec<u8>>> ShrinkWalk<'_, R> {
87    fn node(
88        &mut self,
89        original: &[u8],
90        depth: u16,
91        inline: bool,
92    ) -> Result<(Vec<u8>, Option<u32>)> {
93        use crate::extent::{ExtentIdx, EXT4_EXT_NODE_SIZE as N};
94        let header = ExtentHeader::parse(original)?;
95        let body = original.len().saturating_sub(if inline { 0 } else { 4 });
96        if header.depth != depth || header.max == 0 || N * (1 + header.max as usize) > body {
97            return Err(Error::CorruptExtentTree(
98                "invalid extent node depth or capacity",
99            ));
100        }
101        let mut bytes = original.to_vec();
102        // Keep unused bytes untouched: they are covered by the node checksum.
103        let mut kept = 0usize;
104        let mut first = None;
105        let mut previous_index = None;
106        for index in 0..header.entries as usize {
107            let off = N * (index + 1);
108            let mut encoded: [u8; N] = original[off..off + N].try_into().unwrap();
109            if depth == 0 {
110                let extent = Extent::parse(&encoded)?;
111                let begin = extent.logical_block as u64;
112                let end = begin + extent.length as u64;
113                let physical_end = extent.physical_block + extent.length as u64;
114                if extent.length == 0
115                    || begin < self.logical_end
116                    || end > u32::MAX as u64 + 1
117                    || extent.physical_block == 0
118                    || physical_end > self.blocks_count
119                {
120                    return Err(Error::CorruptExtentTree(
121                        "invalid or overlapping leaf extent",
122                    ));
123                }
124                self.logical_end = end;
125                self.physical.push((extent.physical_block, physical_end));
126                let keep = self.end.saturating_sub(begin).min(extent.length as u64);
127                if keep < extent.length as u64 {
128                    self.mutations.push(ExtentMutation::FreePhysicalRun {
129                        start: extent.physical_block + keep,
130                        len: extent.length as u32 - keep as u32,
131                    });
132                }
133                if keep == 0 {
134                    continue;
135                }
136                let encoded_len = keep as u16
137                    + if extent.uninitialized {
138                        crate::extent::EXT_INIT_MAX_LEN
139                    } else {
140                        0
141                    };
142                encoded[4..6].copy_from_slice(&encoded_len.to_le_bytes());
143                first.get_or_insert(extent.logical_block);
144            } else {
145                let child = ExtentIdx::parse(&encoded)?;
146                if previous_index.is_some_and(|prior| prior >= child.logical_block)
147                    || child.leaf_block == 0
148                    || child.leaf_block >= self.blocks_count
149                    || !self.visited.insert(child.leaf_block)
150                {
151                    return Err(Error::CorruptExtentTree("invalid or repeated extent index"));
152                }
153                previous_index = Some(child.logical_block);
154                self.physical.push((child.leaf_block, child.leaf_block + 1));
155                let node = (self.read)(child.leaf_block)?;
156                if node.len() != self.block_size as usize {
157                    return Err(Error::CorruptExtentTree("short extent node read"));
158                }
159                let child_header = ExtentHeader::parse(&node)?;
160                if child_header.entries == 0
161                    || u32::from_le_bytes(node[12..16].try_into().unwrap()) != child.logical_block
162                {
163                    return Err(Error::CorruptExtentTree(
164                        "extent index does not match its child",
165                    ));
166                }
167                let (updated, child_first) = self.node(&node, depth - 1, false)?;
168                let Some(child_first) = child_first else {
169                    self.mutations.push(ExtentMutation::FreePhysicalRun {
170                        start: child.leaf_block,
171                        len: 1,
172                    });
173                    continue;
174                };
175                encoded[..4].copy_from_slice(&child_first.to_le_bytes());
176                if updated != node {
177                    self.mutations.push(ExtentMutation::WriteTreeBlock {
178                        block: child.leaf_block,
179                        bytes: updated,
180                    });
181                }
182                first.get_or_insert(child_first);
183            }
184            bytes[N * (kept + 1)..N * (kept + 2)].copy_from_slice(&encoded);
185            kept += 1;
186        }
187        bytes[2..4].copy_from_slice(&(kept as u16).to_le_bytes());
188        Ok((bytes, first))
189    }
190}
191
192/// One unit of a data-block write. `payload.len() <= block_size`.
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct BlockWrite {
195    /// Logical block number within the file (offset / block_size).
196    pub logical_block: u64,
197    /// Byte offset within the block where the payload starts (0..block_size).
198    pub offset_in_block: u32,
199    /// Bytes to write. For a full-block write, `payload.len() == block_size`
200    /// and `offset_in_block == 0`.
201    pub payload: Vec<u8>,
202}
203
204/// Split a byte-range write `(offset, data)` into per-block `BlockWrite` units.
205///
206/// Returned writes are in ascending logical order. Each non-final write either
207/// starts at `offset_in_block == 0` AND covers the full block, OR is the
208/// first write whose offset is not block-aligned. The last write may be
209/// partial if `data.len()` does not end on a block boundary.
210pub fn split_into_block_writes(offset: u64, data: &[u8], block_size: u32) -> Vec<BlockWrite> {
211    if data.is_empty() {
212        return Vec::new();
213    }
214    let bs = block_size as u64;
215    let mut out = Vec::new();
216    let mut cur = offset;
217    let end = offset + data.len() as u64;
218    let mut src = 0usize;
219
220    while cur < end {
221        let logical_block = cur / bs;
222        let off_in_block = (cur % bs) as u32;
223        let block_remaining = (bs - off_in_block as u64) as usize;
224        let take = block_remaining.min(end.saturating_sub(cur) as usize);
225        out.push(BlockWrite {
226            logical_block,
227            offset_in_block: off_in_block,
228            payload: data[src..src + take].to_vec(),
229        });
230        src += take;
231        cur += take as u64;
232    }
233    out
234}
235
236/// Describes the logical-block coverage of a write.
237#[derive(Debug, Clone, Copy, PartialEq, Eq)]
238pub struct WriteBlockRange {
239    /// First logical block touched.
240    pub first: u64,
241    /// Last logical block touched (inclusive).
242    pub last: u64,
243    /// True iff the write starts and ends on block boundaries (no RMW needed
244    /// for the head or tail block).
245    pub full_blocks: bool,
246}
247
248/// Compute the logical block range for a byte-range write.
249pub fn compute_write_range(offset: u64, length: u64, block_size: u32) -> Option<WriteBlockRange> {
250    if length == 0 {
251        return None;
252    }
253    let bs = block_size as u64;
254    let first = offset / bs;
255    let last = (offset + length - 1) / bs;
256    let full_blocks = offset.is_multiple_of(bs) && (offset + length).is_multiple_of(bs);
257    Some(WriteBlockRange {
258        first,
259        last,
260        full_blocks,
261    })
262}
263
264/// Plan to grow `inode_size` to `new_size` without writing any data blocks.
265/// Returns the size delta only — growing a file sparsely costs no blocks,
266/// and the hole reads as zeros (via `extent::lookup` returning None).
267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
268pub struct SizeChange {
269    pub old_size: u64,
270    pub new_size: u64,
271}
272
273pub fn plan_truncate_grow(old_size: u64, new_size: u64) -> Result<SizeChange> {
274    if new_size < old_size {
275        return Err(Error::Corrupt("plan_truncate_grow: new_size < old_size"));
276    }
277    Ok(SizeChange { old_size, new_size })
278}
279
280/// Plan to shrink `inode_size` to `new_size`: drops whole leaf extents that
281/// fall entirely past the new EOF, and — if the new EOF falls mid-extent —
282/// splits that extent and frees the tail half.
283///
284/// Returns a vec of `ExtentMutation`s (the `WriteRoot` + `FreePhysicalRun`
285/// pairs). Caller pairs these with block-bitmap frees (E5) when applying.
286///
287/// Does NOT issue any bitmap updates itself — callers of the plan apply the
288/// `FreePhysicalRun` entries against the bitmap allocator.
289///
290/// Only handles inline-root (depth 0) trees for now. Multi-level trees
291/// return [`Error::Unsupported`]. Filesystem callers use
292/// [`plan_truncate_shrink_deep`] to traverse external nodes too.
293pub fn plan_truncate_shrink(
294    old_size: u64,
295    new_size: u64,
296    root_bytes: &[u8],
297    block_size: u32,
298) -> Result<(SizeChange, Vec<ExtentMutation>)> {
299    if new_size > old_size {
300        return Err(Error::Corrupt("plan_truncate_shrink: new_size > old_size"));
301    }
302    let header = ExtentHeader::parse(root_bytes)?;
303    if !header.is_leaf() {
304        return Err(Error::Unsupported(
305            "inline truncate planner requires an inline extent root",
306        ));
307    }
308
309    let bs = block_size as u64;
310    let new_logical_end = new_size.div_ceil(bs); // exclusive upper bound
311
312    // Read entries; identify which fully past end → drop, which straddle → split.
313    let mut entries: Vec<Extent> = Vec::new();
314    for i in 0..header.entries {
315        let off = crate::extent::EXT4_EXT_NODE_SIZE * (1 + i as usize);
316        entries.push(Extent::parse(
317            &root_bytes[off..off + crate::extent::EXT4_EXT_NODE_SIZE],
318        )?);
319    }
320
321    let mut muts: Vec<ExtentMutation> = Vec::new();
322    // Walk from tail so indices into the live root stay valid for plan_free_extent.
323    // Note: plan_free_extent re-parses the root each call, so we apply mutations
324    // back onto a local copy and re-emit at the end. Simpler: compute final
325    // entries ourselves + emit FreePhysicalRun for each discarded range, then
326    // serialize once.
327    let mut kept: Vec<Extent> = Vec::new();
328    for e in entries.into_iter() {
329        let e_start = e.logical_block as u64;
330        let e_end = e_start + e.length as u64;
331        if e_end <= new_logical_end {
332            // Fully within the retained range — keep.
333            kept.push(e);
334        } else if e_start >= new_logical_end {
335            // Entirely past EOF — free whole extent.
336            muts.push(ExtentMutation::FreePhysicalRun {
337                start: e.physical_block,
338                len: e.length as u32,
339            });
340        } else {
341            // Straddles EOF — keep the head, free the tail.
342            let keep_len = (new_logical_end - e_start) as u16;
343            let free_len = e.length - keep_len;
344            let free_phys = e.physical_block + keep_len as u64;
345            kept.push(Extent {
346                logical_block: e.logical_block,
347                length: keep_len,
348                physical_block: e.physical_block,
349                uninitialized: e.uninitialized,
350            });
351            muts.push(ExtentMutation::FreePhysicalRun {
352                start: free_phys,
353                len: free_len as u32,
354            });
355        }
356    }
357
358    // Emit the new root with the retained entries.
359    let mut new_root = vec![0u8; root_bytes.len()];
360    new_root[0..2].copy_from_slice(&crate::extent::EXT4_EXT_MAGIC.to_le_bytes());
361    new_root[2..4].copy_from_slice(&(kept.len() as u16).to_le_bytes());
362    new_root[4..6].copy_from_slice(&header.max.to_le_bytes());
363    new_root[6..8].copy_from_slice(&header.depth.to_le_bytes());
364    new_root[8..12].copy_from_slice(&header.generation.to_le_bytes());
365    for (i, e) in kept.iter().enumerate() {
366        let off = crate::extent::EXT4_EXT_NODE_SIZE * (1 + i);
367        new_root[off..off + 4].copy_from_slice(&e.logical_block.to_le_bytes());
368        let ee_len = if e.uninitialized {
369            e.length + crate::extent::EXT_INIT_MAX_LEN
370        } else {
371            e.length
372        };
373        new_root[off + 4..off + 6].copy_from_slice(&ee_len.to_le_bytes());
374        let phys_hi = ((e.physical_block >> 32) & 0xFFFF) as u16;
375        let phys_lo = (e.physical_block & 0xFFFF_FFFF) as u32;
376        new_root[off + 6..off + 8].copy_from_slice(&phys_hi.to_le_bytes());
377        new_root[off + 8..off + 12].copy_from_slice(&phys_lo.to_le_bytes());
378    }
379    muts.insert(0, ExtentMutation::WriteRoot { bytes: new_root });
380
381    // Silence unused-variable warning for now; plan_free_extent is re-exported
382    // for future per-entry flows (E11 may call it directly).
383    let _ = plan_free_extent;
384
385    Ok((SizeChange { old_size, new_size }, muts))
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391
392    // -----------------------------------------------------------------------
393    // split_into_block_writes
394    // -----------------------------------------------------------------------
395
396    #[test]
397    fn split_aligned_single_block() {
398        let data = vec![7u8; 4096];
399        let out = split_into_block_writes(0, &data, 4096);
400        assert_eq!(out.len(), 1);
401        assert_eq!(out[0].logical_block, 0);
402        assert_eq!(out[0].offset_in_block, 0);
403        assert_eq!(out[0].payload.len(), 4096);
404    }
405
406    #[test]
407    fn split_crosses_block_boundary() {
408        let data = vec![1u8; 6000]; // starts at 3000, spans blocks 0, 1, 2
409        let out = split_into_block_writes(3000, &data, 4096);
410        assert_eq!(out.len(), 3);
411        // block 0: offset 3000 to end, 1096 bytes
412        assert_eq!(out[0].logical_block, 0);
413        assert_eq!(out[0].offset_in_block, 3000);
414        assert_eq!(out[0].payload.len(), 1096);
415        // block 1: full 4096 bytes
416        assert_eq!(out[1].logical_block, 1);
417        assert_eq!(out[1].offset_in_block, 0);
418        assert_eq!(out[1].payload.len(), 4096);
419        // block 2: offset 0, 6000 - 1096 - 4096 = 808 bytes
420        assert_eq!(out[2].logical_block, 2);
421        assert_eq!(out[2].offset_in_block, 0);
422        assert_eq!(out[2].payload.len(), 808);
423    }
424
425    #[test]
426    fn split_empty_data_returns_empty() {
427        assert!(split_into_block_writes(100, &[], 4096).is_empty());
428    }
429
430    #[test]
431    fn split_sub_block_write() {
432        let data = vec![2u8; 100];
433        let out = split_into_block_writes(1000, &data, 4096);
434        assert_eq!(out.len(), 1);
435        assert_eq!(out[0].offset_in_block, 1000);
436        assert_eq!(out[0].payload.len(), 100);
437    }
438
439    // -----------------------------------------------------------------------
440    // compute_write_range
441    // -----------------------------------------------------------------------
442
443    #[test]
444    fn write_range_full_blocks() {
445        let r = compute_write_range(0, 8192, 4096).unwrap();
446        assert_eq!(r.first, 0);
447        assert_eq!(r.last, 1);
448        assert!(r.full_blocks);
449    }
450
451    #[test]
452    fn write_range_partial_head_and_tail() {
453        let r = compute_write_range(100, 5000, 4096).unwrap();
454        assert_eq!(r.first, 0);
455        assert_eq!(r.last, 1);
456        assert!(!r.full_blocks);
457    }
458
459    #[test]
460    fn write_range_zero_length_returns_none() {
461        assert!(compute_write_range(100, 0, 4096).is_none());
462    }
463
464    // -----------------------------------------------------------------------
465    // plan_truncate_grow
466    // -----------------------------------------------------------------------
467
468    #[test]
469    fn grow_returns_delta() {
470        let s = plan_truncate_grow(1000, 5000).unwrap();
471        assert_eq!(s.old_size, 1000);
472        assert_eq!(s.new_size, 5000);
473    }
474
475    #[test]
476    fn grow_rejects_shrink() {
477        assert!(plan_truncate_grow(5000, 1000).is_err());
478    }
479
480    // -----------------------------------------------------------------------
481    // plan_truncate_shrink
482    // -----------------------------------------------------------------------
483
484    /// Build an inline root with the given extents.
485    fn mk_root(extents: &[Extent]) -> Vec<u8> {
486        let mut buf = vec![0u8; 60];
487        buf[0..2].copy_from_slice(&crate::extent::EXT4_EXT_MAGIC.to_le_bytes());
488        buf[2..4].copy_from_slice(&(extents.len() as u16).to_le_bytes());
489        buf[4..6].copy_from_slice(&4u16.to_le_bytes());
490        buf[6..8].copy_from_slice(&0u16.to_le_bytes());
491        for (i, e) in extents.iter().enumerate() {
492            let off = crate::extent::EXT4_EXT_NODE_SIZE * (1 + i);
493            buf[off..off + 4].copy_from_slice(&e.logical_block.to_le_bytes());
494            let ee_len = if e.uninitialized {
495                e.length + crate::extent::EXT_INIT_MAX_LEN
496            } else {
497                e.length
498            };
499            buf[off + 4..off + 6].copy_from_slice(&ee_len.to_le_bytes());
500            let phys_hi = ((e.physical_block >> 32) & 0xFFFF) as u16;
501            let phys_lo = (e.physical_block & 0xFFFF_FFFF) as u32;
502            buf[off + 6..off + 8].copy_from_slice(&phys_hi.to_le_bytes());
503            buf[off + 8..off + 12].copy_from_slice(&phys_lo.to_le_bytes());
504        }
505        buf
506    }
507
508    fn ext(log: u32, len: u16, phys: u64) -> Extent {
509        Extent {
510            logical_block: log,
511            length: len,
512            physical_block: phys,
513            uninitialized: false,
514        }
515    }
516
517    #[test]
518    fn shrink_drops_whole_tail_extent() {
519        // File has extents [0..10]=1000 + [10..20]=2000.
520        // Shrink to 10 blocks → drop second extent entirely.
521        let root = mk_root(&[ext(0, 10, 1000), ext(10, 10, 2000)]);
522        let (sc, muts) = plan_truncate_shrink(20 * 4096, 10 * 4096, &root, 4096).unwrap();
523        assert_eq!(sc.new_size, 10 * 4096);
524        // muts[0] = WriteRoot with only the first extent.
525        // muts[1] = FreePhysicalRun { start: 2000, len: 10 }.
526        assert_eq!(muts.len(), 2);
527        match &muts[1] {
528            ExtentMutation::FreePhysicalRun { start, len } => {
529                assert_eq!(*start, 2000);
530                assert_eq!(*len, 10);
531            }
532            _ => panic!("expected FreePhysicalRun"),
533        }
534    }
535
536    #[test]
537    fn shrink_splits_straddling_extent() {
538        // One extent [0..10] → shrink to 4 blocks = keep 4, free 6.
539        let root = mk_root(&[ext(0, 10, 1000)]);
540        let (_, muts) = plan_truncate_shrink(10 * 4096, 4 * 4096, &root, 4096).unwrap();
541        assert_eq!(muts.len(), 2);
542        match &muts[1] {
543            ExtentMutation::FreePhysicalRun { start, len } => {
544                assert_eq!(*start, 1004);
545                assert_eq!(*len, 6);
546            }
547            _ => panic!("expected FreePhysicalRun for tail"),
548        }
549    }
550
551    #[test]
552    fn shrink_to_zero_frees_all() {
553        let root = mk_root(&[ext(0, 5, 500), ext(5, 5, 1000)]);
554        let (_, muts) = plan_truncate_shrink(10 * 4096, 0, &root, 4096).unwrap();
555        // 1 WriteRoot + 2 FreePhysicalRun.
556        assert_eq!(muts.len(), 3);
557        let freed: Vec<_> = muts
558            .iter()
559            .filter_map(|m| match m {
560                ExtentMutation::FreePhysicalRun { start, len } => Some((*start, *len)),
561                _ => None,
562            })
563            .collect();
564        assert_eq!(freed, vec![(500, 5), (1000, 5)]);
565    }
566
567    #[test]
568    fn shrink_rejects_grow_direction() {
569        let root = mk_root(&[ext(0, 10, 1000)]);
570        assert!(plan_truncate_shrink(4096, 8192, &root, 4096).is_err());
571    }
572}