1use crate::bgd::{self, BlockGroupDescriptor};
4use crate::block_io::BlockDevice;
5use crate::checksum::Checksummer;
6use crate::error::{Error, Result};
7use crate::features;
8use crate::inode::{set_inode_time, Inode, InodeTime};
9use crate::superblock::Superblock;
10use std::borrow::Cow;
11use std::collections::{BTreeMap, HashMap};
12use std::sync::{Arc, Mutex};
13
14pub(crate) struct BlockBuffer {
23 pub dirty: BTreeMap<u64, Vec<u8>>,
24 pub uninit_cleared: BTreeMap<usize, u16>,
33}
34
35impl BlockBuffer {
36 pub fn new(_block_size: u32) -> Self {
45 Self {
46 dirty: BTreeMap::new(),
47 uninit_cleared: BTreeMap::new(),
48 }
49 }
50
51 pub fn get_mut(&mut self, fs: &Filesystem, block: u64) -> Result<&mut Vec<u8>> {
55 if let std::collections::btree_map::Entry::Vacant(e) = self.dirty.entry(block) {
56 let buf = fs.read_block(block)?;
57 e.insert(buf);
58 }
59 Ok(self.dirty.get_mut(&block).unwrap())
60 }
61
62 pub fn put(&mut self, block: u64, bytes: Vec<u8>) {
66 self.dirty.insert(block, bytes);
67 }
68}
69
70fn patch_counter_u32(buf: &mut [u8], lo_off: usize, hi_off: Option<usize>, delta: i32) {
76 let cur_lo = u16::from_le_bytes(buf[lo_off..lo_off + 2].try_into().unwrap()) as u32;
77 let cur_hi = hi_off
78 .map(|h| u16::from_le_bytes(buf[h..h + 2].try_into().unwrap()) as u32)
79 .unwrap_or(0);
80 let cur = (cur_hi << 16) | cur_lo;
81 let new = (cur as i64 + delta as i64).clamp(0, u32::MAX as i64) as u32;
82 buf[lo_off..lo_off + 2].copy_from_slice(&((new & 0xFFFF) as u16).to_le_bytes());
83 if let Some(h) = hi_off {
84 buf[h..h + 2].copy_from_slice(&(((new >> 16) & 0xFFFF) as u16).to_le_bytes());
85 }
86}
87
88fn patch_bgd_counter_fields(
92 block: &mut [u8],
93 off: usize,
94 desc_size: u16,
95 free_blocks_delta: i32,
96 free_inodes_delta: i32,
97 used_dirs_delta: i32,
98) {
99 use crate::bgd::{
100 OFF_FREE_BLOCKS_HI, OFF_FREE_BLOCKS_LO, OFF_FREE_INODES_HI, OFF_FREE_INODES_LO,
101 OFF_USED_DIRS_HI, OFF_USED_DIRS_LO,
102 };
103 let has_hi = desc_size >= 64;
104 for (lo, hi, delta) in [
105 (OFF_FREE_BLOCKS_LO, OFF_FREE_BLOCKS_HI, free_blocks_delta),
106 (OFF_FREE_INODES_LO, OFF_FREE_INODES_HI, free_inodes_delta),
107 (OFF_USED_DIRS_LO, OFF_USED_DIRS_HI, used_dirs_delta),
108 ] {
109 patch_counter_u32(block, off + lo, has_hi.then_some(off + hi), delta);
110 }
111}
112
113#[inline]
119fn pack_nsec_lo(nsec: u32) -> u32 {
120 (nsec & 0x3FFF_FFFF) << 2
121}
122
123pub const TIME_OMIT: i64 = i64::MIN;
131
132pub const UTIME_NOW: u32 = (1 << 30) - 1;
139
140pub const UTIME_OMIT: u32 = (1 << 30) - 2;
145
146const NSEC_PER_SEC: u32 = 1_000_000_000;
148
149#[derive(Clone, Copy)]
151enum TimeUpdate {
152 Omit,
153 Set(i64, u32),
154}
155
156impl TimeUpdate {
157 fn resolve(sec: i64, nsec: u32, now: i64) -> Result<Self> {
160 if sec == TIME_OMIT || nsec == UTIME_OMIT {
161 return Ok(Self::Omit);
162 }
163 if nsec == UTIME_NOW {
164 return Ok(Self::Set(now, 0));
165 }
166 if nsec >= NSEC_PER_SEC {
167 return Err(Error::InvalidArgument(
168 "nanoseconds must be below 1e9 (or UTIME_NOW / UTIME_OMIT)",
169 ));
170 }
171 if !(crate::inode::MIN_ENCODABLE_TIME..=crate::inode::MAX_ENCODABLE_TIME).contains(&sec) {
172 return Err(Error::InvalidArgument(
173 "timestamp outside the range ext4 can store (1901..2446)",
174 ));
175 }
176 Ok(Self::Set(sec, nsec))
177 }
178
179 fn check(sec: i64, nsec: u32) -> Result<()> {
182 Self::resolve(sec, nsec, 0).map(|_| ())
183 }
184}
185
186#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
201pub struct InodeRef {
202 pub ino: u32,
204 pub generation: Option<u32>,
206}
207
208impl InodeRef {
209 pub const fn new(ino: u32, generation: u32) -> Self {
211 Self {
212 ino,
213 generation: Some(generation),
214 }
215 }
216
217 pub const fn any(ino: u32) -> Self {
219 Self {
220 ino,
221 generation: None,
222 }
223 }
224}
225
226impl From<u32> for InodeRef {
227 fn from(ino: u32) -> Self {
228 Self::any(ino)
229 }
230}
231
232fn check_entry_name(name: &[u8]) -> Result<()> {
236 if name.is_empty() {
237 return Err(Error::InvalidArgument("empty name"));
238 }
239 if name.contains(&b'/') {
240 return Err(Error::InvalidArgument("a name cannot contain '/'"));
241 }
242 if name.contains(&0) {
245 return Err(Error::InvalidArgument("a name cannot contain a NUL byte"));
246 }
247 Ok(())
248}
249
250fn check_new_entry_name(name: &[u8]) -> Result<()> {
253 check_entry_name(name)?;
254 if name.len() > 255 {
255 return Err(Error::NameTooLong);
256 }
257 Ok(())
258}
259
260fn is_dot_or_dotdot(name: &[u8]) -> bool {
263 name == b"." || name == b".."
264}
265
266fn split_parent_and_base(path: &[u8]) -> Result<(&[u8], &[u8])> {
271 let end = path.iter().rposition(|&b| b != b'/').map_or(0, |i| i + 1);
272 let trimmed = &path[..end];
273 if trimmed.is_empty() {
274 return Err(Error::InvalidArgument("empty path"));
275 }
276 let last_slash = trimmed
277 .iter()
278 .rposition(|&b| b == b'/')
279 .ok_or(Error::InvalidArgument("relative path"))?;
280 let base = &trimmed[last_slash + 1..];
281 let parent: &[u8] = if last_slash == 0 {
282 b"/"
283 } else {
284 &trimmed[..last_slash]
285 };
286 if base.is_empty() {
287 return Err(Error::NotADirectory);
289 }
290 if base.contains(&0) {
295 return Err(Error::InvalidArgument("a name cannot contain a NUL byte"));
296 }
297 Ok((parent, base))
298}
299
300pub(crate) struct FsBlockReader<'a> {
309 pub(crate) fs: &'a Filesystem,
310}
311
312impl<'a> crate::extent_mut::DeepReader for FsBlockReader<'a> {
313 fn read_block(&self, block: u64, out: &mut [u8]) -> Result<()> {
314 let bytes = self.fs.read_block(block)?;
315 if bytes.len() != out.len() {
316 return Err(Error::Corrupt(
317 "FsBlockReader: block length mismatch (callers must pass a buffer sized to fs block_size)",
318 ));
319 }
320 out.copy_from_slice(&bytes);
321 Ok(())
322 }
323}
324
325struct StagedTreeNodes<'a> {
338 fs: &'a Filesystem,
339 nodes: &'a std::collections::BTreeMap<u64, Vec<u8>>,
340}
341
342impl crate::extent_mut::DeepReader for StagedTreeNodes<'_> {
343 fn read_block(&self, block: u64, out: &mut [u8]) -> Result<()> {
344 match self.nodes.get(&block) {
345 Some(bytes) if bytes.len() == out.len() => {
346 out.copy_from_slice(bytes);
347 Ok(())
348 }
349 Some(_) => Err(Error::Corrupt(
350 "StagedTreeNodes: block length mismatch (callers must pass a buffer sized to fs block_size)",
351 )),
352 None => FsBlockReader { fs: self.fs }.read_block(block, out),
353 }
354 }
355}
356
357impl crate::block_io::BlockDevice for StagedTreeNodes<'_> {
358 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
359 self.fs.dev.read_at(offset, buf)?;
360 let bs = self.fs.sb.block_size() as u64;
361 let end = offset + buf.len() as u64;
362 for (&block, bytes) in self.nodes.range(offset / bs..end.div_ceil(bs)) {
363 let (lo, hi) = ((block * bs).max(offset), ((block + 1) * bs).min(end));
364 if lo < hi {
365 buf[(lo - offset) as usize..(hi - offset) as usize].copy_from_slice(
366 &bytes[(lo - block * bs) as usize..(hi - block * bs) as usize],
367 );
368 }
369 }
370 Ok(())
371 }
372
373 fn size_bytes(&self) -> u64 {
374 self.fs.dev.size_bytes()
375 }
376}
377
378const EXT4_CASEFOLD_FL: u32 = 0x4000_0000;
380const EXT4_ENCRYPT_FL: u32 = 0x0000_0800;
382
383fn write_inode_timestamps(raw: &mut [u8], now: i64) {
391 set_inode_time(raw, InodeTime::Atime, now);
392 set_inode_time(raw, InodeTime::Ctime, now);
393 set_inode_time(raw, InodeTime::Mtime, now);
394 set_inode_time(raw, InodeTime::Crtime, now);
397}
398
399fn write_inode_generation(raw: &mut [u8], generation: u32) {
401 use crate::inode::OFF_GENERATION;
402 raw[OFF_GENERATION..OFF_GENERATION + 4].copy_from_slice(&generation.to_le_bytes());
403}
404
405fn write_inode_extra_isize(raw: &mut [u8]) {
408 use crate::inode::{EXTRA_ISIZE_DEFAULT, INODE_SIZE_WITH_EXTRA, OFF_EXTRA_ISIZE};
409 if raw.len() >= INODE_SIZE_WITH_EXTRA {
410 raw[OFF_EXTRA_ISIZE..OFF_EXTRA_ISIZE + 2]
411 .copy_from_slice(&EXTRA_ISIZE_DEFAULT.to_le_bytes());
412 }
413}
414
415#[derive(Default)]
422struct AllocationState {
423 cleared: HashMap<usize, u16>,
425 overridden: Option<Arc<[BlockGroupDescriptor]>>,
428}
429
430impl AllocationState {
431 fn publish(&mut self, cleared: BTreeMap<usize, u16>) {
433 if cleared.is_empty() {
434 return;
435 }
436 for (gi, flags) in cleared {
437 self.cleared
438 .entry(gi)
439 .and_modify(|f| *f &= flags)
440 .or_insert(flags);
441 }
442 self.overridden = None;
443 }
444
445 fn reset(&mut self) {
447 self.cleared.clear();
448 self.overridden = None;
449 }
450}
451
452pub(crate) enum AllocationGroups<'a> {
455 Snapshot(&'a [BlockGroupDescriptor]),
456 Overridden(Arc<[BlockGroupDescriptor]>),
457}
458
459impl std::ops::Deref for AllocationGroups<'_> {
460 type Target = [BlockGroupDescriptor];
461 fn deref(&self) -> &[BlockGroupDescriptor] {
462 match self {
463 Self::Snapshot(groups) => groups,
464 Self::Overridden(groups) => groups,
465 }
466 }
467}
468
469#[derive(Clone, Copy, Debug, PartialEq, Eq)]
471pub(crate) enum DirBlockRole {
472 IndexRoot,
474 IndexNode,
476 Entries,
478}
479
480pub struct Filesystem {
481 runtime: Arc<dyn crate::runtime::Runtime>,
482 managed_recovery: bool,
483 cache: Arc<crate::block_cache::CachedDevice>,
485 pub dev: Arc<dyn BlockDevice>,
486 pub sb: Superblock,
487 pub groups: Vec<BlockGroupDescriptor>,
488 uninit_cleared: Mutex<AllocationState>,
502 pub csum: Checksummer,
503 pub flavor: features::FsFlavor,
508 pub journal: Option<std::sync::Mutex<crate::journal_writer::JournalWriter>>,
513 marked_not_clean: std::sync::atomic::AtomicBool,
516 state_found: std::sync::atomic::AtomicU16,
520 direct_commit_failed: std::sync::atomic::AtomicBool,
529 replay_pending: std::sync::atomic::AtomicBool,
539}
540
541impl Drop for Filesystem {
547 fn drop(&mut self) {
548 let journal_failed = self
551 .journal
552 .as_ref()
553 .is_some_and(|w| w.lock().map_or(true, |w| !w.is_healthy()));
554 if self
555 .marked_not_clean
556 .load(std::sync::atomic::Ordering::SeqCst)
557 && self.dev.is_writable()
558 && !journal_failed
559 && !self
560 .direct_commit_failed
561 .load(std::sync::atomic::Ordering::SeqCst)
562 {
563 let state = self.state_found.load(std::sync::atomic::Ordering::SeqCst);
564 let _ = self.write_superblock_state(state);
565 }
566 }
567}
568
569struct NewInodePlan {
574 new_ino: u32,
576 parent_ino: u32,
578 parent_inode: crate::inode::Inode,
580 buf: BlockBuffer,
582}
583
584#[derive(Clone, Copy, PartialEq, Eq)]
587pub(crate) enum BgdUninitFlag {
588 Inode,
589 Block,
590}
591
592const PWRITE_CHUNK_MAX_BYTES: u64 = 32 * 1024 * 1024;
599
600pub const VOLUME_LABEL_MAX: usize = 16;
603
604pub const DEFAULT_CACHE_BLOCKS: usize = 256;
608
609impl Filesystem {
610 pub fn mount_recovering(dev: Arc<dyn BlockDevice>) -> Result<Self> {
615 let mut fs = Self::mount_lazy(dev)?;
616 fs.replay_pending
619 .store(false, std::sync::atomic::Ordering::SeqCst);
620 fs.refuse_write()?;
621 let jsb = crate::jbd2::read_superblock(&fs)?.ok_or(Error::Unsupported(
622 "checked recovery requires an internal journal",
623 ))?;
624 jsb.validate_plain_recovery(fs.sb.block_size(), fs.sb.blocks_count)?;
625 if fs.sb.state & crate::superblock::EXT4_ERROR_FS != 0 {
626 return Err(Error::Corrupt("filesystem records an outstanding error"));
627 }
628 fs.journal = None;
629 fs.set_recovery_marker(true)?;
630 crate::journal_apply::replay_if_dirty(&fs)?;
631 fs.refresh_metadata()?;
632 fs.refuse_write()?;
633 crate::jbd2::read_superblock(&fs)?
634 .ok_or(Error::Corrupt("journal disappeared"))?
635 .validate_plain_recovery(fs.sb.block_size(), fs.sb.blocks_count)?;
636 fs.set_recovery_marker(true)?;
637 fs.journal = crate::journal_writer::JournalWriter::open(&fs)?
638 .map(|w| Mutex::new(w.holding_needs_recovery()));
639 fs.recover_orphans()?;
640 fs.refresh_metadata()?;
641 fs.managed_recovery = true;
642 Ok(fs)
643 }
644
645 pub fn finish(mut self) -> Result<()> {
649 self.flush()?;
650 if self.managed_recovery {
651 self.refresh_metadata()?;
652 if self.sb.last_orphan != 0 {
653 return Err(Error::Corrupt("orphan recovery remains incomplete"));
654 }
655 self.restore_state_found()?;
660 return self.set_recovery_marker(false);
661 }
662 self.restore_state_found()
663 }
664
665 fn restore_state_found(&self) -> Result<()> {
669 use std::sync::atomic::Ordering;
670 if !self.marked_not_clean.load(Ordering::SeqCst) {
671 return Ok(());
672 }
673 self.write_superblock_state(self.state_found.load(Ordering::SeqCst))?;
674 self.marked_not_clean.store(false, Ordering::SeqCst);
675 Ok(())
676 }
677
678 pub fn flush(&self) -> Result<()> {
687 if self
688 .direct_commit_failed
689 .load(std::sync::atomic::Ordering::SeqCst)
690 {
691 return Err(Error::Corrupt(
692 "direct commit failed; reopen and check the volume",
693 ));
694 }
695 let _writer = match &self.journal {
696 Some(writer) => {
697 let guard = writer
698 .lock()
699 .map_err(|_| Error::Corrupt("journal writer poisoned"))?;
700 if !guard.is_healthy() {
701 return Err(Error::Corrupt(
702 "journal operation failed; reopen for recovery",
703 ));
704 }
705 Some(guard)
706 }
707 None => None,
708 };
709 self.dev.flush()?;
710 if !self.managed_recovery {
711 return Ok(());
712 }
713 let jsb =
714 crate::jbd2::read_superblock(self)?.ok_or(Error::Corrupt("journal disappeared"))?;
715 if !jsb.is_clean() || jsb.errno != 0 {
716 return Err(Error::Corrupt("journal is not checkpointed"));
717 }
718 Ok(())
719 }
720
721 pub fn cache_pinned_blocks(&self) -> usize {
726 self.cache.pinned_blocks()
727 }
728
729 pub fn fresh_read(&mut self) -> Result<()> {
733 self.flush()?;
734 if self.journal.is_some() {
740 let jsb =
741 crate::jbd2::read_superblock(self)?.ok_or(Error::Corrupt("journal disappeared"))?;
742 if !jsb.is_clean() || jsb.errno != 0 {
743 return Err(Error::Corrupt("journal is not checkpointed"));
744 }
745 self.dev.unpin_all();
746 }
747 self.dev.invalidate_cache()?;
748 self.refresh_metadata()
749 }
750
751 fn refresh_metadata(&mut self) -> Result<()> {
752 let sb = Superblock::read(self.dev.as_ref())?;
753 if sb.block_size() != self.sb.block_size()
754 || sb.blocks_count != self.sb.blocks_count
755 || sb.uuid != self.sb.uuid
756 || sb.raw[0xd0..0xe8] != self.sb.raw[0xd0..0xe8]
757 {
758 return Err(Error::Corrupt(
759 "journal changed filesystem identity or geometry",
760 ));
761 }
762 features::check_mountable(sb.feature_incompat, sb.feature_ro_compat)?;
763 let csum = Checksummer::from_superblock(&sb);
764 if csum.enabled && !csum.verify_superblock(&sb.raw) {
765 return Err(Error::BadChecksum {
766 what: "replayed superblock",
767 });
768 }
769 let groups = bgd::read_all(self.dev.as_ref(), &sb, &csum)?;
770 self.flavor = features::FsFlavor::detect(sb.feature_compat, sb.feature_incompat);
771 self.sb = sb;
772 self.groups = groups;
773 self.csum = csum;
774 self.uninit_cleared
775 .lock()
776 .map_err(|_| Error::Corrupt("allocation state poisoned"))?
777 .reset();
778 Ok(())
779 }
780
781 fn set_recovery_marker(&mut self, needed: bool) -> Result<()> {
782 let recover = features::Incompat::RECOVER.bits();
783 let on_disk = Superblock::read(self.dev.as_ref())?.feature_incompat & recover != 0;
784 if on_disk != needed {
785 crate::journal_apply::write_needs_recovery(self.dev.as_ref(), needed)?;
786 self.dev.flush()?;
787 }
788 self.refresh_metadata()
789 }
790
791 pub fn mount(dev: Arc<dyn BlockDevice>) -> Result<Self> {
798 Self::mount_inner(
799 dev,
800 false,
801 DEFAULT_CACHE_BLOCKS,
802 Arc::new(crate::runtime::SystemRuntime),
803 )
804 }
805
806 pub fn mount_with_cache(dev: Arc<dyn BlockDevice>, blocks: usize) -> Result<Self> {
812 Self::mount_inner(dev, false, blocks, Arc::new(crate::runtime::SystemRuntime))
813 }
814
815 pub fn mount_lazy(dev: Arc<dyn BlockDevice>) -> Result<Self> {
829 Self::mount_inner(
830 dev,
831 true,
832 DEFAULT_CACHE_BLOCKS,
833 Arc::new(crate::runtime::SystemRuntime),
834 )
835 }
836
837 pub fn mount_with_runtime(
840 dev: Arc<dyn BlockDevice>,
841 runtime: Arc<dyn crate::runtime::Runtime>,
842 ) -> Result<Self> {
843 Self::mount_inner(dev, false, DEFAULT_CACHE_BLOCKS, runtime)
844 }
845
846 fn mount_inner(
847 dev: Arc<dyn BlockDevice>,
848 defer_replay: bool,
849 cache_blocks: usize,
850 runtime: Arc<dyn crate::runtime::Runtime>,
851 ) -> Result<Self> {
852 let sb = Superblock::read(dev.as_ref())?;
853 features::check_mountable(sb.feature_incompat, sb.feature_ro_compat)?;
854 let flavor = features::FsFlavor::detect(sb.feature_compat, sb.feature_incompat);
855 let csum = Checksummer::from_superblock(&sb);
856 if csum.enabled && !csum.verify_superblock(&sb.raw) {
857 return Err(Error::BadChecksum { what: "superblock" });
858 }
859 let groups = bgd::read_all(dev.as_ref(), &sb, &csum)?;
860 sb.check_fits_device(dev.size_bytes())?;
861 let cache = Arc::new(crate::block_cache::CachedDevice::new(
869 dev,
870 sb.block_size(),
871 cache_blocks,
872 ));
873 let dev: Arc<dyn BlockDevice> = cache.clone();
874 let mut fs = Self {
875 runtime,
876 managed_recovery: false,
877 cache,
878 dev,
879 sb,
880 groups,
881 uninit_cleared: Mutex::new(AllocationState::default()),
882 csum,
883 flavor,
884 journal: None,
885 marked_not_clean: std::sync::atomic::AtomicBool::new(false),
886 direct_commit_failed: std::sync::atomic::AtomicBool::new(false),
887 replay_pending: std::sync::atomic::AtomicBool::new(false),
888 state_found: std::sync::atomic::AtomicU16::new(0),
889 };
890
891 let write_breaking = crate::features::write_breaking_incompat(fs.sb.feature_incompat);
927 if fs.dev.is_writable() && write_breaking != 0 {
928 return Err(crate::error::Error::UnsupportedIncompat(write_breaking));
929 }
930
931 let replayed = if !fs.dev.is_writable() {
932 crate::journal_apply::replay_into_cache(&fs)?
933 } else if !defer_replay {
934 crate::journal_apply::replay_if_dirty(&fs)?
938 } else {
939 0
940 };
941 if replayed > 0 {
942 fs.reload_geometry()?;
943 }
944
945 if fs.write_refusal().is_ok() {
963 if let Some(jw) = crate::journal_writer::JournalWriter::open(&fs)? {
964 fs.journal = Some(std::sync::Mutex::new(jw));
965 }
966 }
967
968 if !fs.dev.is_writable() || defer_replay {
973 let dirty = crate::jbd2::read_superblock(&fs)?.is_some_and(|j| !j.is_clean());
974 fs.replay_pending
975 .store(dirty, std::sync::atomic::Ordering::SeqCst);
976 }
977
978 if !defer_replay {
984 fs.recover_orphans()?;
987 fs.refresh_metadata()?;
988 }
989
990 Ok(fs)
991 }
992
993 fn reload_geometry(&mut self) -> Result<()> {
997 let sb = Superblock::read(self.dev.as_ref())?;
998 features::check_mountable(sb.feature_incompat, sb.feature_ro_compat)?;
999 let csum = Checksummer::from_superblock(&sb);
1000 if csum.enabled && !csum.verify_superblock(&sb.raw) {
1001 return Err(Error::BadChecksum { what: "superblock" });
1002 }
1003 self.groups = bgd::read_all(self.dev.as_ref(), &sb, &csum)?;
1004 sb.check_fits_device(self.dev.size_bytes())?;
1005 self.uninit_cleared
1007 .get_mut()
1008 .map_err(|_| Error::Corrupt("allocation state poisoned"))?
1009 .overridden = None;
1010 self.flavor = features::FsFlavor::detect(sb.feature_compat, sb.feature_incompat);
1011 self.csum = csum;
1012 self.sb = sb;
1013 Ok(())
1014 }
1015
1016 pub(crate) fn refuse_write(&self) -> Result<()> {
1041 self.write_refusal()?;
1042 self.mark_not_clean_once()
1043 }
1044
1045 fn write_refusal(&self) -> Result<()> {
1049 if !self.dev.is_writable() {
1050 return Err(Error::ReadOnly);
1051 }
1052 if self
1056 .direct_commit_failed
1057 .load(std::sync::atomic::Ordering::SeqCst)
1058 {
1059 return Err(Error::ReadOnly);
1060 }
1061 if self
1062 .replay_pending
1063 .load(std::sync::atomic::Ordering::SeqCst)
1064 {
1065 return Err(Error::JournalNotReplayed);
1066 }
1067 let unmaintained = features::unmaintained_ro_compat(self.sb.feature_ro_compat);
1068 if unmaintained != 0 {
1069 return Err(Error::UnsupportedRoCompat(unmaintained));
1070 }
1071 let write_breaking = features::write_breaking_incompat(self.sb.feature_incompat);
1080 if write_breaking != 0 {
1081 return Err(Error::UnsupportedIncompat(write_breaking));
1082 }
1083 Ok(())
1084 }
1085
1086 fn mark_not_clean_once(&self) -> Result<()> {
1099 use std::sync::atomic::Ordering;
1100 if self.marked_not_clean.load(Ordering::SeqCst) {
1101 return Ok(());
1102 }
1103 self.state_found.store(self.sb.state, Ordering::SeqCst);
1104 self.write_superblock_state(self.sb.state & !crate::superblock::EXT4_VALID_FS)?;
1105 self.marked_not_clean.store(true, Ordering::SeqCst);
1106 Ok(())
1107 }
1108
1109 pub fn set_volume_label(&mut self, label: &[u8]) -> Result<()> {
1126 if label.len() > VOLUME_LABEL_MAX {
1127 return Err(Error::InvalidArgument(
1128 "volume label: longer than the 16 bytes s_volume_name holds",
1129 ));
1130 }
1131 if label.contains(&0) {
1132 return Err(Error::InvalidArgument(
1133 "volume label: a NUL byte would end it early",
1134 ));
1135 }
1136 self.refuse_write()?;
1137 let mut field = [0u8; VOLUME_LABEL_MAX];
1138 field[..label.len()].copy_from_slice(label);
1139
1140 let bs = u64::from(self.sb.block_size());
1141 let primary = crate::superblock::SUPERBLOCK_OFFSET;
1142 let mut buf = BlockBuffer::new(self.sb.block_size());
1143 for g in 0..self.sb.block_group_count() {
1144 if !self.sb.group_has_super(g) {
1145 continue;
1146 }
1147 let (block, at) = if g == 0 {
1150 (primary / bs, (primary % bs) as usize)
1151 } else {
1152 let start =
1153 u64::from(self.sb.first_data_block) + g * u64::from(self.sb.blocks_per_group);
1154 (start, 0)
1155 };
1156 let bytes = buf.get_mut(self, block)?;
1157 let sb = &mut bytes[at..at + crate::superblock::SUPERBLOCK_SIZE];
1158 if u16::from_le_bytes([sb[0x38], sb[0x39]]) != crate::superblock::EXT4_MAGIC {
1159 continue;
1160 }
1161 sb[0x78..0x88].copy_from_slice(&field);
1162 if self.csum.enabled {
1163 let csum = crate::checksum::linux_crc32c(!0, &sb[..0x3FC]);
1164 sb[0x3FC..0x400].copy_from_slice(&csum.to_le_bytes());
1165 }
1166 }
1167 self.commit_block_buffer(buf)?;
1168
1169 if self.sb.raw.len() >= 0x88 {
1170 self.sb.raw[0x78..0x88].copy_from_slice(&field);
1171 }
1172 self.sb.volume_name = String::from_utf8_lossy(label).into_owned();
1173 Ok(())
1174 }
1175
1176 fn write_superblock_state(&self, state: u16) -> Result<()> {
1180 let at = crate::superblock::SUPERBLOCK_OFFSET;
1181 let mut sb = vec![0u8; 1024];
1182 self.dev.read_at(at, &mut sb)?;
1183 sb[0x3A..0x3C].copy_from_slice(&state.to_le_bytes());
1184 if self.csum.enabled {
1185 let csum = crate::checksum::linux_crc32c(!0, &sb[..0x3FC]);
1186 sb[0x3FC..0x400].copy_from_slice(&csum.to_le_bytes());
1187 }
1188 self.dev.write_at(at, &sb)?;
1189 self.dev.flush()
1190 }
1191
1192 pub fn replay_journal_if_dirty(&mut self) -> Result<usize> {
1199 let dirty = self.dev.is_writable()
1200 && crate::jbd2::read_superblock(self)?.is_some_and(|j| !j.is_clean());
1201 let n = crate::journal_apply::replay_if_dirty(self)?;
1202 if self.dev.is_writable() {
1205 self.replay_pending
1206 .store(false, std::sync::atomic::Ordering::SeqCst);
1207 }
1208 if n > 0 {
1217 self.dev.unpin_all();
1218 self.reload_geometry()?;
1224 self.uninit_cleared
1228 .lock()
1229 .map_err(|_| Error::Corrupt("allocation state poisoned"))?
1230 .reset();
1231 }
1232 let failed = self
1242 .journal
1243 .as_ref()
1244 .is_some_and(|w| w.lock().map_or(true, |w| !w.is_healthy()));
1245 if (dirty || self.journal.is_none()) && !failed && self.write_refusal().is_ok() {
1246 self.journal = crate::journal_writer::JournalWriter::open(self)?.map(Mutex::new);
1247 }
1248 Ok(n)
1249 }
1250
1251 pub fn orphan_list(&self) -> Result<Vec<u32>> {
1269 let mut out = Vec::new();
1270 let mut cur = self.sb.last_orphan;
1271 let cap = self.sb.inodes_count;
1272 let mut steps = 0u32;
1273 while cur != 0 {
1274 if steps > cap {
1275 return Err(Error::Corrupt(
1276 "orphan_list: chain longer than inodes_count (cycle?)",
1277 ));
1278 }
1279 out.push(cur);
1280 let raw = self.read_inode_raw(cur)?;
1284 if raw.len() < 0x18 {
1285 return Err(Error::Corrupt("orphan_list: inode too short"));
1286 }
1287 cur = u32::from_le_bytes(raw[0x14..0x18].try_into().unwrap());
1288 steps += 1;
1289 }
1290 Ok(out)
1291 }
1292
1293 fn orphan_chain_to_recover(&self) -> Result<Vec<u32>> {
1309 let mut out = Vec::new();
1310 let mut cur = self.sb.last_orphan;
1311 while cur != 0 {
1312 if out.len() as u64 > u64::from(self.sb.inodes_count) {
1313 return Err(Error::Corrupt(
1314 "orphan_list: chain longer than inodes_count (cycle?)",
1315 ));
1316 }
1317 let allocated = self.inode_bit_is_set(cur)?;
1318 if !allocated && (cur > self.sb.inodes_count || cur < self.sb.first_inode) {
1319 break;
1320 }
1321 let raw = self.read_inode_raw(cur)?;
1322 if raw.len() < 0x18 {
1323 return Err(Error::Corrupt("orphan_list: inode too short"));
1324 }
1325 let next = u32::from_le_bytes(raw[0x14..0x18].try_into().unwrap());
1326 if !allocated && next > self.sb.inodes_count {
1327 break;
1328 }
1329 out.push(cur);
1330 cur = next;
1331 }
1332 Ok(out)
1333 }
1334
1335 fn inode_bit_is_set(&self, ino: u32) -> Result<bool> {
1338 if ino == 0 || ino > self.sb.inodes_count || self.sb.inodes_per_group == 0 {
1339 return Ok(false);
1340 }
1341 let ipg = self.sb.inodes_per_group;
1342 let groups = self.allocation_groups();
1343 let Some(group) = groups.get(((ino - 1) / ipg) as usize) else {
1344 return Ok(false);
1345 };
1346 if group.flags().contains(crate::bgd::BgdFlags::INODE_UNINIT) {
1347 return Ok(false);
1348 }
1349 let bitmap = self.read_block(group.inode_bitmap)?;
1350 let bit = ((ino - 1) % ipg) as usize;
1351 Ok(bitmap
1352 .get(bit / 8)
1353 .is_some_and(|byte| byte & (1 << (bit % 8)) != 0))
1354 }
1355
1356 pub fn recover_orphans(&self) -> Result<usize> {
1411 if self.write_refusal().is_err() {
1418 return Ok(0);
1419 }
1420 if self.sb.last_orphan == 0 {
1421 return Ok(0);
1422 }
1423 self.mark_not_clean_once()?;
1425 let chain = self.orphan_chain_to_recover()?;
1426
1427 let bs = self.sb.block_size();
1428 let mut reclaimed = 0usize;
1429 let mut unstamped: Option<(u32, u32)> = None;
1432
1433 if chain.is_empty() {
1434 let mut buf = BlockBuffer::new(bs);
1435 self.buffer_patch_sb_last_orphan(&mut buf, 0)?;
1436 return self.commit_block_buffer(buf).map(|()| 0);
1437 }
1438
1439 for (at, &orphan_ino) in chain.iter().enumerate() {
1440 let next = chain.get(at + 1).copied().unwrap_or(0);
1441 let mut buf = BlockBuffer::new(bs);
1442 if let Some((ino, dtime)) = unstamped.take() {
1443 self.buffer_stamp_dtime(&mut buf, ino, dtime)?;
1444 }
1445 let Some((blocks, final_dtime, deleted)) =
1446 self.buffer_recover_orphan(&mut buf, orphan_ino, next)?
1447 else {
1448 self.commit_block_buffer(buf)?;
1455 break;
1456 };
1457 self.buffer_patch_sb_counters(&mut buf, blocks as i64, i32::from(deleted))?;
1458 reclaimed += usize::from(deleted);
1459 if next == 0 {
1460 self.buffer_stamp_dtime(&mut buf, orphan_ino, final_dtime)?;
1461 } else {
1462 unstamped = Some((orphan_ino, final_dtime));
1463 }
1464 self.buffer_patch_sb_last_orphan(&mut buf, next)?;
1465 self.commit_block_buffer(buf)?;
1466 }
1467 Ok(reclaimed)
1468 }
1469
1470 fn buffer_stamp_dtime(&self, buf: &mut BlockBuffer, ino: u32, dtime: u32) -> Result<()> {
1472 let (block, offset) = bgd::locate_inode(&self.sb, &self.groups, ino)?;
1473 let inode_size = self.sb.inode_size as usize;
1474 let bytes = buf.get_mut(self, block)?;
1475 let raw = bytes
1476 .get_mut(offset as usize..offset as usize + inode_size)
1477 .ok_or(Error::Corrupt("inode slice exceeds block data"))?;
1478 raw[0x14..0x18].copy_from_slice(&dtime.to_le_bytes());
1479 let generation = u32::from_le_bytes(raw[0x64..0x68].try_into().unwrap());
1480 let mut owned = raw.to_vec();
1481 self.finalize_inode_raw(ino, generation, &mut owned)?;
1482 raw.copy_from_slice(&owned);
1483 Ok(())
1484 }
1485
1486 fn buffer_recover_orphan(
1494 &self,
1495 buf: &mut BlockBuffer,
1496 orphan_ino: u32,
1497 link: u32,
1498 ) -> Result<Option<(u64, u32, bool)>> {
1499 let bs = self.sb.block_size();
1500 let mut raw = self.read_inode_raw(orphan_ino)?;
1503 let parsed = match Inode::parse(&raw) {
1504 Ok(i) => i,
1505 Err(_) => return Ok(None), };
1507
1508 if parsed.links_count != 0 {
1512 let Some(freed) =
1513 self.buffer_finish_interrupted_truncate(buf, orphan_ino, &parsed, raw)?
1514 else {
1515 return Ok(None);
1516 };
1517 self.buffer_stamp_dtime(buf, orphan_ino, link)?;
1518 return Ok(Some((freed, 0, false)));
1519 }
1520
1521 let mut freed = 0u64;
1522 if !parsed.has_extents() && Self::holds_block_map(&parsed, bs) {
1533 let data_blocks = u32::try_from(parsed.size.div_ceil(bs as u64))
1534 .map_err(|_| Error::Corrupt("orphan: indirect file too large to map"))?;
1535 let Ok(tree) = crate::indirect_mut::collect_for_free(
1536 &parsed.block,
1537 bs,
1538 data_blocks,
1539 self.dev.as_ref(),
1540 ) else {
1541 return Ok(None);
1542 };
1543 let fs_blocks = self.sb.blocks_count;
1544 let in_range = |start: u64, len: u64| {
1545 start >= self.sb.first_data_block as u64
1546 && start.checked_add(len).is_some_and(|end| end <= fs_blocks)
1547 };
1548 if !tree
1552 .data_runs
1553 .iter()
1554 .all(|r| in_range(r.start, r.len as u64))
1555 || !tree.indirect_blocks.iter().all(|&b| in_range(b, 1))
1556 {
1557 return Ok(None);
1558 }
1559 for run in &tree.data_runs {
1560 freed += self.buffer_free_block_run_and_bgd(buf, run.start, run.len as u64)?;
1561 }
1562 for &block in &tree.indirect_blocks {
1563 freed += self.buffer_free_block_run_and_bgd(buf, block, 1)?;
1564 }
1565 }
1566 if parsed.has_extents() {
1567 let Ok(runs) = self.extent_tree_runs(orphan_ino, &parsed) else {
1568 return Ok(None);
1569 };
1570 freed += self.buffer_free_runs(buf, &runs)?;
1571 }
1572 if parsed.file_acl != 0 {
1574 freed += self.buffer_release_xattr_block(buf, parsed.file_acl)?;
1575 }
1576 self.buffer_free_inode_slot(buf, orphan_ino)?;
1578
1579 let inode_size = self.sb.inode_size as usize;
1582 let old_gen = parsed.generation;
1583 for b in &mut raw[..inode_size] {
1584 *b = 0;
1585 }
1586 raw[0x14..0x18].copy_from_slice(&link.to_le_bytes());
1587 raw[0x64..0x68].copy_from_slice(&old_gen.to_le_bytes());
1588 self.finalize_inode_raw(orphan_ino, old_gen, &mut raw)?;
1589 self.buffer_write_inode(buf, orphan_ino, &raw)?;
1590 Ok(Some((freed, self.dtime_now(), true)))
1591 }
1592
1593 fn buffer_finish_interrupted_truncate(
1617 &self,
1618 buf: &mut BlockBuffer,
1619 ino: u32,
1620 parsed: &Inode,
1621 mut raw: Vec<u8>,
1622 ) -> Result<Option<u64>> {
1623 let bs = self.sb.block_size() as u64;
1624 let mut freed_blocks: u64 = 0;
1625 let mut freed_sectors: u64 = 0;
1626
1627 if parsed.has_extents() {
1628 {
1632 let Ok((_sc, muts)) = self.plan_inode_truncate(ino, parsed, parsed.size) else {
1635 return Ok(None);
1636 };
1637 for m in &muts {
1638 match m {
1639 crate::extent_mut::ExtentMutation::WriteRoot { bytes } => {
1640 Self::patch_inode_block_area(&mut raw, bytes)?;
1641 }
1642 crate::extent_mut::ExtentMutation::WriteTreeBlock { block, bytes } => {
1643 buf.put(*block, bytes.clone());
1644 }
1645 crate::extent_mut::ExtentMutation::FreePhysicalRun { start, len } => {
1646 freed_blocks +=
1647 self.buffer_free_block_run_and_bgd(buf, *start, *len as u64)?;
1648 freed_sectors += (*len as u64) * (bs / 512);
1649 }
1650 _ => {
1651 return Err(Error::Corrupt(
1652 "orphan truncate: unexpected mutation type",
1653 ));
1654 }
1655 }
1656 }
1657 let new_blocks = parsed.blocks.saturating_sub(freed_sectors);
1658 Self::patch_inode_size_and_blocks(&mut raw, parsed.size, new_blocks)?;
1659 }
1660 }
1661
1662 raw[0x14..0x18].copy_from_slice(&0u32.to_le_bytes());
1667 self.finalize_inode_raw(ino, parsed.generation, &mut raw)?;
1668 self.buffer_write_inode(buf, ino, &raw)?;
1669 Ok(Some(freed_blocks))
1670 }
1671
1672 pub fn read_block(&self, block_num: u64) -> Result<Vec<u8>> {
1677 let block_size = self.sb.block_size() as usize;
1678 let byte_offset = block_num
1679 .checked_mul(block_size as u64)
1680 .ok_or(Error::Corrupt("block byte offset overflow"))?;
1681 let mut buf = vec![0u8; block_size];
1682 self.dev.read_at(byte_offset, &mut buf)?;
1683 Ok(buf)
1684 }
1685
1686 pub fn read_inode_raw(&self, ino: u32) -> Result<Vec<u8>> {
1688 let (block, offset) = bgd::locate_inode(&self.sb, &self.groups, ino)?;
1689 let block_data = self.read_block(block)?;
1690 let inode_size = self.sb.inode_size as usize;
1691 let off = offset as usize;
1692 let end = off
1693 .checked_add(inode_size)
1694 .ok_or(Error::Corrupt("inode slice end overflows usize"))?;
1695 if end > block_data.len() {
1696 return Err(Error::Corrupt("inode slice exceeds block data"));
1697 }
1698 Ok(block_data[off..end].to_vec())
1699 }
1700
1701 pub fn read_inode_verified(&self, ino: u32) -> Result<(Inode, Vec<u8>)> {
1707 let raw = self.read_inode_raw(ino)?;
1708 let inode = Inode::parse(&raw)?;
1709 if self.csum.enabled && !self.csum.verify_inode(ino, inode.generation, &raw) {
1710 return Err(Error::BadChecksum { what: "inode" });
1711 }
1712 if inode.is_dir() && inode.size > self.byte_ceiling()? {
1728 return Err(Error::Corrupt(
1729 "directory inode declares more bytes than the filesystem holds",
1730 ));
1731 }
1732 Ok((inode, raw))
1733 }
1734
1735 pub fn read_link(&self, ino: u32) -> Result<Vec<u8>> {
1747 const I_BLOCK_BYTES: u64 = 60;
1748 const PATH_MAX: u64 = 4096;
1752
1753 let (inode, _raw) = self.read_inode_verified(ino)?;
1754 if !inode.is_symlink() {
1755 return Err(Error::InvalidArgument("not a symlink"));
1756 }
1757 if inode.size > PATH_MAX {
1758 return Err(Error::Corrupt("symlink target is longer than any path"));
1759 }
1760 crate::file_io::refuse_encrypted(&inode)?;
1763 if inode.size < I_BLOCK_BYTES {
1764 return Ok(inode.block[..inode.size as usize].to_vec());
1765 }
1766 let mut out = vec![0u8; inode.size as usize];
1767 crate::file_io::read_verified(self, &inode, ino, 0, inode.size, &mut out)?;
1768 Ok(out)
1769 }
1770
1771 pub fn byte_ceiling(&self) -> Result<u64> {
1778 let bytes = self.sb.filesystem_bytes().ok_or(Error::Corrupt(
1779 "superblock: blocks_count * block_size overflows",
1780 ))?;
1781 Ok(bytes.min(self.dev.size_bytes()))
1782 }
1783
1784 pub fn lookup_path_bytes(&self, path: &[u8]) -> Result<u32> {
1801 let mut reader = |ino: u32| self.read_inode_verified(ino).map(|(i, _)| i);
1802 crate::path::lookup_bytes_with_csum(
1803 self.dev.as_ref(),
1804 &self.sb,
1805 &mut reader,
1806 path,
1807 &self.csum,
1808 )
1809 }
1810
1811 fn resolve(&self, path: &[u8]) -> Result<u32> {
1812 self.lookup_path_bytes(path)
1813 }
1814
1815 fn resolve_parent<'p>(&self, path: &'p [u8]) -> Result<(u32, &'p [u8])> {
1817 let (parent, base) = split_parent_and_base(path)?;
1818 Ok((self.resolve(parent)?, base))
1819 }
1820
1821 fn resolve_new_parent<'p>(&self, path: &'p [u8]) -> Result<(u32, &'p [u8])> {
1825 let (parent, base) = split_parent_and_base(path)?;
1826 if base.len() > 255 {
1827 return Err(Error::NameTooLong);
1828 }
1829 Ok((self.resolve(parent)?, base))
1830 }
1831
1832 pub(crate) fn live_inode(&self, r: InodeRef) -> Result<(Inode, Vec<u8>)> {
1839 let ino = r.ino;
1840 let reserved = ino < self.sb.first_inode && ino != crate::path::EXT4_ROOT_INODE;
1841 if ino == 0 || ino > self.sb.inodes_count || reserved {
1842 return Err(Error::Stale);
1843 }
1844 if !self.inode_bit_is_set(ino)? {
1847 return Err(Error::Stale);
1848 }
1849 let (inode, raw) = self.read_inode_verified(ino)?;
1850 if inode.links_count == 0 || inode.mode == 0 {
1851 return Err(Error::Stale);
1852 }
1853 if r.generation.is_some_and(|g| g != inode.generation) {
1854 return Err(Error::Stale);
1855 }
1856 Ok((inode, raw))
1857 }
1858
1859 fn live_dir(&self, r: InodeRef) -> Result<(Inode, Vec<u8>)> {
1861 let (inode, raw) = self.live_inode(r)?;
1862 if !inode.is_dir() {
1863 return Err(Error::NotADirectory);
1864 }
1865 Ok((inode, raw))
1866 }
1867
1868 pub fn stat_ino(&self, r: impl Into<InodeRef>) -> Result<Inode> {
1871 Ok(self.live_inode(r.into())?.0)
1872 }
1873
1874 pub fn lookup_at(&self, dir: impl Into<InodeRef>, name: &[u8]) -> Result<u32> {
1878 check_entry_name(name)?;
1879 let dir = dir.into();
1880 let (dir_inode, _) = self.live_dir(dir)?;
1881 crate::path::find_entry(
1882 self.dev.as_ref(),
1883 &self.sb,
1884 dir.ino,
1885 &dir_inode,
1886 name,
1887 &self.csum,
1888 )
1889 }
1890
1891 pub fn read_dir_ino(&self, dir: impl Into<InodeRef>) -> Result<Vec<crate::dir::DirEntry>> {
1894 let dir = dir.into();
1895 let (inode, raw) = self.live_dir(dir)?;
1896 self.dir_entries(dir.ino, &inode, &raw)
1897 }
1898
1899 fn dir_entries(
1902 &self,
1903 ino: u32,
1904 inode: &Inode,
1905 inode_raw: &[u8],
1906 ) -> Result<Vec<crate::dir::DirEntry>> {
1907 use crate::dir::DirBlockIter;
1908 crate::file_io::refuse_encrypted_names(inode)?;
1909 let block_size = self.sb.block_size();
1910 let has_filetype = self.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
1911
1912 if inode.has_inline_data() {
1918 return crate::inline_data::read_dir(
1919 self.dev.as_ref(),
1920 ino,
1921 inode,
1922 inode_raw,
1923 self.sb.inode_size,
1924 block_size,
1925 has_filetype,
1926 );
1927 }
1928 if !inode.has_extents() {
1929 return Err(Error::Corrupt("legacy (non-extent) dirs not yet supported"));
1930 }
1931
1932 const MAX_DIR_ENTRIES: usize = 1_000_000;
1937 let mut entries = Vec::new();
1938 let push = |entries: &mut Vec<crate::dir::DirEntry>, e| {
1939 if entries.len() >= MAX_DIR_ENTRIES {
1940 return Err(Error::Corrupt("dir entries exceed MAX_DIR_ENTRIES"));
1941 }
1942 entries.push(e);
1943 Ok(())
1944 };
1945
1946 let total_blocks = inode.size.div_ceil(block_size as u64);
1947 let mut block_buf = vec![0u8; block_size as usize];
1948 for logical in 0..total_blocks {
1949 let Some(phys) =
1950 crate::extent::map_logical(&inode.block, self.dev.as_ref(), block_size, logical)?
1951 else {
1952 continue; };
1954 self.dev.read_at(phys * block_size as u64, &mut block_buf)?;
1955 for entry in DirBlockIter::new(&block_buf, has_filetype) {
1956 push(&mut entries, entry?)?;
1957 }
1958 }
1959 Ok(entries)
1960 }
1961
1962 pub fn read_ino(&self, r: impl Into<InodeRef>, offset: u64, out: &mut [u8]) -> Result<usize> {
1966 let r = r.into();
1967 let (inode, raw) = self.live_inode(r)?;
1968 if !inode.is_file() {
1969 return Err(Error::InvalidArgument("not a regular file"));
1970 }
1971 let length = (out.len() as u64).min(inode.size);
1972 crate::file_io::read_with_raw_verified(
1973 self,
1974 &inode,
1975 &raw,
1976 r.ino,
1977 offset,
1978 length,
1979 &mut out[..length as usize],
1980 )
1981 .map(|n| n as usize)
1982 }
1983
1984 pub fn read_link_ino(&self, r: impl Into<InodeRef>) -> Result<Vec<u8>> {
1986 let r = r.into();
1987 self.live_inode(r)?;
1988 self.read_link(r.ino)
1989 }
1990
1991 pub fn apply_truncate_ino(&self, r: impl Into<InodeRef>, new_size: u64) -> Result<()> {
1995 self.refuse_write()?;
1996 let r = r.into();
1997 let (inode, _) = self.live_inode(r)?;
1998 if inode.is_dir() {
2001 return Err(Error::IsADirectory);
2002 }
2003 if !inode.is_file() {
2004 return Err(Error::InvalidArgument(
2005 "truncate target is not a regular file",
2006 ));
2007 }
2008 if new_size >= inode.size {
2010 self.apply_truncate_grow(r.ino, new_size)
2011 } else {
2012 self.apply_truncate_shrink(r.ino, new_size)
2013 }
2014 }
2015
2016 pub fn map_inode_logical(&self, inode: &Inode, logical_block: u64) -> Result<Option<u64>> {
2039 if inode.has_inline_data() {
2045 return Err(Error::Unsupported(
2046 "inline-data inode: i_block holds data, not a block map",
2047 ));
2048 }
2049 let bs = self.sb.block_size();
2050 if (inode.flags & crate::inode::InodeFlags::EXTENTS.bits()) != 0 {
2051 crate::extent::map_logical(&inode.block, self.dev.as_ref(), bs, logical_block)
2052 } else {
2053 let mut cache = crate::indirect::IndirectCache::new();
2054 crate::indirect::lookup(
2055 &inode.block,
2056 self.dev.as_ref(),
2057 bs,
2058 logical_block,
2059 &mut cache,
2060 )
2061 }
2062 }
2063
2064 pub fn write_inode_raw(&self, ino: u32, raw: &[u8]) -> Result<()> {
2076 if raw.len() != self.sb.inode_size as usize {
2077 return Err(Error::Corrupt("write_inode_raw: length != inode_size"));
2078 }
2079 self.refuse_write()?;
2080 let (block, offset) = bgd::locate_inode(&self.sb, &self.groups, ino)?;
2081 let block_size = self.sb.block_size() as u64;
2082 let byte_offset = block * block_size + offset as u64;
2083 self.dev.write_at(byte_offset, raw)?;
2084 Ok(())
2085 }
2086
2087 pub(crate) fn write_file_acl(raw: &mut [u8], block_nr: u64) -> Result<()> {
2116 if raw.len() < 0x6C {
2117 return Err(Error::Corrupt(
2118 "write_file_acl: inode buffer too small for i_file_acl_lo",
2119 ));
2120 }
2121 let (hi, lo) = crate::extent_mut::split_phys_block(block_nr);
2122 if raw.len() < 0x78 && hi != 0 {
2123 return Err(Error::Corrupt(
2124 "write_file_acl: this inode is too small to hold i_file_acl_hi and the \
2125 external xattr block needs it",
2126 ));
2127 }
2128 raw[0x68..0x6C].copy_from_slice(&lo.to_le_bytes());
2129 if raw.len() >= 0x78 {
2130 raw[0x76..0x78].copy_from_slice(&hi.to_le_bytes());
2131 }
2132 Ok(())
2133 }
2134
2135 pub fn patch_inode_size_and_blocks(
2140 raw: &mut [u8],
2141 new_size: u64,
2142 new_block_count: u64,
2143 ) -> Result<()> {
2144 if raw.len() < 128 {
2145 return Err(Error::Corrupt("patch_inode: buffer too small"));
2146 }
2147 let size_lo = (new_size & 0xFFFF_FFFF) as u32;
2149 let size_hi = (new_size >> 32) as u32;
2150 raw[0x04..0x08].copy_from_slice(&size_lo.to_le_bytes());
2151 raw[0x6C..0x70].copy_from_slice(&size_hi.to_le_bytes());
2152 let blocks_lo = (new_block_count & 0xFFFF_FFFF) as u32;
2154 let blocks_hi = ((new_block_count >> 32) & 0xFFFF) as u16;
2155 raw[0x1C..0x20].copy_from_slice(&blocks_lo.to_le_bytes());
2156 raw[0x74..0x76].copy_from_slice(&blocks_hi.to_le_bytes());
2157 Ok(())
2158 }
2159
2160 pub fn patch_inode_block_area(raw: &mut [u8], new_root: &[u8]) -> Result<()> {
2163 if raw.len() < 128 {
2164 return Err(Error::Corrupt("patch_inode_block_area: buffer too small"));
2165 }
2166 if new_root.len() != 60 {
2167 return Err(Error::Corrupt(
2168 "patch_inode_block_area: new_root != 60 bytes",
2169 ));
2170 }
2171 raw[0x28..0x64].copy_from_slice(new_root);
2172 Ok(())
2173 }
2174
2175 fn plan_inode_truncate(
2178 &self,
2179 ino: u32,
2180 inode: &Inode,
2181 new_size: u64,
2182 ) -> Result<(
2183 crate::file_mut::SizeChange,
2184 Vec<crate::extent_mut::ExtentMutation>,
2185 )> {
2186 if !inode.has_extents() {
2187 return Err(Error::Unsupported("truncate of legacy block mappings"));
2188 }
2189 let mut read = |block| {
2190 let bytes = self.read_block(block)?;
2191 if !self.csum.verify_extent_tail(ino, inode.generation, &bytes) {
2192 return Err(Error::BadChecksum {
2193 what: "extent block",
2194 });
2195 }
2196 Ok(bytes)
2197 };
2198 let (change, mut mutations) = crate::file_mut::plan_truncate_shrink_deep(
2199 inode.size,
2200 new_size,
2201 &inode.block,
2202 self.sb.block_size(),
2203 self.sb.blocks_count,
2204 &mut read,
2205 )?;
2206 for mutation in &mut mutations {
2207 if let crate::extent_mut::ExtentMutation::WriteTreeBlock { bytes, .. } = mutation {
2208 self.csum.patch_extent_tail(ino, inode.generation, bytes);
2209 }
2210 }
2211 Ok((change, mutations))
2212 }
2213
2214 pub fn apply_truncate_shrink(&self, ino: u32, new_size: u64) -> Result<()> {
2230 self.refuse_write()?;
2231 let (inode, mut raw) = self.read_inode_verified(ino)?;
2232 Self::refuse_truncate_of(&inode)?;
2233 if new_size > inode.size {
2234 return Err(Error::InvalidArgument(
2235 "truncate: new_size > old_size (grow not supported)",
2236 ));
2237 }
2238
2239 if new_size == inode.size {
2240 return Ok(());
2241 }
2242 if inode.has_inline_data() {
2243 return self.truncate_inline_file(ino, &inode, raw, new_size);
2244 }
2245 let (_size_change, muts) = self.plan_inode_truncate(ino, &inode, new_size)?;
2246
2247 let bs = self.sb.block_size() as u64;
2248 let mut freed_sectors: u64 = 0;
2249 let mut freed_blocks: u64 = 0;
2250
2251 let mut buf = BlockBuffer::new(self.sb.block_size());
2254
2255 for m in &muts {
2256 match m {
2257 crate::extent_mut::ExtentMutation::WriteRoot { bytes } => {
2258 Self::patch_inode_block_area(&mut raw, bytes)?;
2259 }
2260 crate::extent_mut::ExtentMutation::WriteTreeBlock { block, bytes } => {
2261 buf.put(*block, bytes.clone());
2262 }
2263 crate::extent_mut::ExtentMutation::FreePhysicalRun { start, len } => {
2264 freed_blocks +=
2265 self.buffer_free_block_run_and_bgd(&mut buf, *start, *len as u64)?;
2266 freed_sectors += (*len as u64) * (bs / 512);
2267 }
2268 _ => {
2269 return Err(Error::Corrupt(
2270 "apply_truncate_shrink: unexpected mutation type",
2271 ));
2272 }
2273 }
2274 }
2275
2276 let tail = (new_size % bs) as usize;
2279 if tail != 0 {
2280 if let Some(block) = self.map_inode_logical(&inode, new_size / bs)? {
2281 buf.get_mut(self, block)?[tail..].fill(0);
2282 }
2283 }
2284 let new_blocks = inode.blocks.saturating_sub(freed_sectors);
2286 Self::patch_inode_size_and_blocks(&mut raw, new_size, new_blocks)?;
2287 self.finalize_inode_raw(ino, inode.generation, &mut raw)?;
2288 self.buffer_write_inode(&mut buf, ino, &raw)?;
2289
2290 if freed_blocks > 0 {
2291 self.buffer_patch_sb_counters(&mut buf, freed_blocks as i64, 0)?;
2292 }
2293
2294 self.commit_block_buffer(buf)
2295 }
2296
2297 fn refuse_truncate_of(inode: &Inode) -> Result<()> {
2310 if inode.is_dir() {
2311 return Err(Error::IsADirectory);
2312 }
2313 if !inode.is_file() {
2314 return Err(Error::InvalidArgument(
2315 "truncate: only a regular file has a size to change",
2316 ));
2317 }
2318 Ok(())
2319 }
2320
2321 fn buffer_store_inline_file(
2338 &self,
2339 buf: &mut BlockBuffer,
2340 ino: u32,
2341 inode: &Inode,
2342 raw: &mut [u8],
2343 data: &[u8],
2344 ) -> Result<bool> {
2345 match crate::inline_mut::store(raw, self.sb.inode_size, data) {
2346 Ok(()) => {}
2347 Err(Error::NoSpaceLeftOnDevice) => return Ok(false),
2348 Err(e) => return Err(e),
2349 }
2350 self.finalize_inode_raw_after_write(ino, raw, inode, data.len() as u64, inode.blocks)?;
2351 self.buffer_write_inode(buf, ino, raw)?;
2352 Ok(true)
2353 }
2354
2355 fn buffer_convert_inline_file(
2366 &self,
2367 buf: &mut BlockBuffer,
2368 ino: u32,
2369 inode: &Inode,
2370 raw: &[u8],
2371 keep: bool,
2372 ) -> Result<(Inode, Vec<u8>)> {
2373 if self.sb.feature_incompat & features::Incompat::EXTENTS.bits() == 0 {
2374 return Err(Error::Unsupported(
2375 "converting an inline-data file out of the inode on a volume without extents",
2376 ));
2377 }
2378 let content = if keep {
2379 crate::file_io::read_inline(self, inode, raw)?
2380 } else {
2381 Vec::new()
2382 };
2383 let bs = self.sb.block_size();
2384 if content.len() > bs as usize {
2385 return Err(Error::Corrupt("inline data larger than a block"));
2386 }
2387 let mut raw = raw.to_vec();
2388 crate::inline_mut::strip(&mut raw, self.sb.inode_size)?;
2389 let flags = u32::from_le_bytes(raw[0x20..0x24].try_into().unwrap())
2390 | crate::inode::InodeFlags::EXTENTS.bits();
2391 raw[0x20..0x24].copy_from_slice(&flags.to_le_bytes());
2392
2393 let mut root = vec![0u8; 60];
2394 root[0..2].copy_from_slice(&crate::extent::EXT4_EXT_MAGIC.to_le_bytes());
2395 root[4..6].copy_from_slice(&4u16.to_le_bytes()); let mut blocks = inode.blocks;
2397 if !content.is_empty() {
2398 let phys = self.buffer_allocate_block(buf, ino)?;
2399 let mut block = vec![0u8; bs as usize];
2400 block[..content.len()].copy_from_slice(&content);
2401 buf.put(phys, block);
2402 let extent = crate::extent::Extent {
2403 logical_block: 0,
2404 length: 1,
2405 physical_block: phys,
2406 uninitialized: false,
2407 };
2408 for m in crate::extent_mut::plan_insert_extent(&root, extent)? {
2409 if let crate::extent_mut::ExtentMutation::WriteRoot { bytes } = m {
2410 root = bytes;
2411 }
2412 }
2413 blocks += u64::from(bs / 512);
2414 }
2415 Self::patch_inode_block_area(&mut raw, &root)?;
2416 Self::patch_inode_size_and_blocks(&mut raw, inode.size, blocks)?;
2417 let converted = Inode::parse(&raw)?;
2418 Ok((converted, raw))
2419 }
2420
2421 fn truncate_inline_file(
2424 &self,
2425 ino: u32,
2426 inode: &Inode,
2427 mut raw: Vec<u8>,
2428 new_size: u64,
2429 ) -> Result<()> {
2430 let mut buf = BlockBuffer::new(self.sb.block_size());
2431 if new_size <= crate::inline_mut::ceiling(self.sb.inode_size) as u64 {
2432 let mut content = crate::file_io::read_inline(self, inode, &raw)?;
2433 content.resize(new_size as usize, 0);
2434 if self.buffer_store_inline_file(&mut buf, ino, inode, &mut raw, &content)? {
2435 return self.commit_block_buffer(buf);
2436 }
2437 }
2438 let (converted, mut raw) =
2439 self.buffer_convert_inline_file(&mut buf, ino, inode, &raw, true)?;
2440 self.finalize_inode_raw_after_write(ino, &mut raw, &converted, new_size, converted.blocks)?;
2441 self.buffer_write_inode(&mut buf, ino, &raw)?;
2442 self.commit_block_buffer(buf)
2443 }
2444
2445 fn buffered_inline_dir(
2463 &self,
2464 buf: &BlockBuffer,
2465 ino: u32,
2466 ) -> Result<(Inode, Vec<u8>, crate::inline_mut::InlineDir)> {
2467 let (inode, raw) = self.buffered_inode_verified(buf, ino)?;
2468 let dir = crate::inline_mut::InlineDir::load(
2469 self.dev.as_ref(),
2470 &inode,
2471 &raw,
2472 self.sb.inode_size,
2473 self.sb.block_size(),
2474 )?;
2475 Ok((inode, raw, dir))
2476 }
2477
2478 fn buffer_store_inline_dir(
2480 &self,
2481 buf: &mut BlockBuffer,
2482 ino: u32,
2483 inode: &Inode,
2484 mut raw: Vec<u8>,
2485 dir: &crate::inline_mut::InlineDir,
2486 ) -> Result<()> {
2487 dir.store(&mut raw, self.sb.inode_size)?;
2488 self.buffer_write_dir_inode(buf, ino, inode.generation, &mut raw)
2489 }
2490
2491 fn buffer_add_inline_dir_entry(
2496 &self,
2497 buf: &mut BlockBuffer,
2498 ino: u32,
2499 name: &[u8],
2500 target: u32,
2501 file_type: crate::dir::DirEntryType,
2502 ) -> Result<()> {
2503 let has_ft = self.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
2504 let (inode, raw, mut dir) = self.buffered_inline_dir(buf, ino)?;
2505 if dir.add(target, name, file_type, has_ft)?
2506 || (dir.expand(&raw, self.sb.inode_size) && dir.add(target, name, file_type, has_ft)?)
2507 {
2508 return self.buffer_store_inline_dir(buf, ino, &inode, raw, &dir);
2509 }
2510 let mut entries = dir.entries(has_ft)?;
2511 entries.push(crate::dir::DirEntry {
2512 inode: target,
2513 name: name.to_vec(),
2514 file_type,
2515 });
2516 self.buffer_convert_inline_dir(buf, ino, &inode, raw, dir.parent, &entries)
2517 }
2518
2519 fn buffer_convert_inline_dir(
2524 &self,
2525 buf: &mut BlockBuffer,
2526 ino: u32,
2527 inode: &Inode,
2528 mut raw: Vec<u8>,
2529 parent: u32,
2530 entries: &[crate::dir::DirEntry],
2531 ) -> Result<()> {
2532 let has_ft = self.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
2533 let reserved_tail = if self.csum.enabled { 12 } else { 0 };
2534 let mut block = self.seed_directory_block(ino, parent, inode.generation)?;
2535 for e in entries {
2536 crate::dir::add_entry_to_block(
2537 &mut block,
2538 e.inode,
2539 &e.name,
2540 e.file_type,
2541 has_ft,
2542 reserved_tail,
2543 )
2544 .map_err(|e| match e {
2545 Error::OutOfBounds => {
2546 Error::Corrupt("an inline directory's entries do not fit in one block")
2547 }
2548 e => e,
2549 })?;
2550 }
2551 if self.csum.enabled {
2552 self.csum
2553 .patch_dir_entry_tail(ino, inode.generation, &mut block);
2554 }
2555 let phys = self.buffer_alloc_dir_block(buf, ino)?;
2556 buf.put(phys, block);
2557 crate::inline_mut::strip(&mut raw, self.sb.inode_size)?;
2558 self.map_one_block(&mut raw, phys)?;
2559 let bs = u64::from(self.sb.block_size());
2560 Self::patch_inode_size_and_blocks(&mut raw, bs, inode.blocks + bs / 512)?;
2561 self.buffer_write_dir_inode(buf, ino, inode.generation, &mut raw)
2562 }
2563
2564 fn buffer_remove_inline_dir_entry(
2567 &self,
2568 buf: &mut BlockBuffer,
2569 ino: u32,
2570 name: &[u8],
2571 ) -> Result<()> {
2572 let has_ft = self.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
2573 let (inode, raw, mut dir) = self.buffered_inline_dir(buf, ino)?;
2574 if !dir.remove(name, has_ft)? {
2575 return Err(Error::NotFound);
2576 }
2577 self.buffer_store_inline_dir(buf, ino, &inode, raw, &dir)
2578 }
2579
2580 fn refuse_nonempty_inline_dir(&self, ino: u32, inode: &Inode, raw: &[u8]) -> Result<()> {
2583 let has_ft = self.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
2584 let entries = crate::inline_data::read_dir(
2585 self.dev.as_ref(),
2586 ino,
2587 inode,
2588 raw,
2589 self.sb.inode_size,
2590 self.sb.block_size(),
2591 has_ft,
2592 )?;
2593 if entries.iter().any(|e| !is_dot_or_dotdot(&e.name)) {
2594 return Err(Error::DirectoryNotEmpty);
2595 }
2596 Ok(())
2597 }
2598
2599 pub fn apply_truncate_grow(&self, ino: u32, new_size: u64) -> Result<()> {
2600 self.refuse_write()?;
2601 let (inode, mut raw) = self.read_inode_verified(ino)?;
2602 Self::refuse_truncate_of(&inode)?;
2603 if new_size < inode.size {
2604 return Err(Error::InvalidArgument(
2605 "apply_truncate_grow: new_size < old_size (use apply_truncate_shrink)",
2606 ));
2607 }
2608 if inode.has_inline_data() {
2609 return self.truncate_inline_file(ino, &inode, raw, new_size);
2610 }
2611 Self::patch_inode_size_and_blocks(&mut raw, new_size, inode.blocks)?;
2612
2613 let now = self.runtime.now_unix_seconds();
2614 set_inode_time(&mut raw, InodeTime::Ctime, now);
2615 set_inode_time(&mut raw, InodeTime::Mtime, now);
2616
2617 self.finalize_inode_raw(ino, inode.generation, &mut raw)?;
2618 self.commit_inode_write(ino, &raw)
2619 }
2620
2621 pub fn apply_fallocate_keep_size(&self, ino: u32, offset: u64, len: u64) -> Result<()> {
2638 self.refuse_write()?;
2639 if len == 0 {
2640 return Ok(());
2641 }
2642 let bs = self.sb.block_size() as u64;
2643 let bs_u32 = self.sb.block_size();
2644 let first_block = offset / bs;
2645 let last_block_excl = offset
2646 .checked_add(len)
2647 .ok_or(Error::InvalidArgument("fallocate: offset+len overflow"))?
2648 .div_ceil(bs);
2649 let need_blocks_u64 = last_block_excl - first_block;
2650 if need_blocks_u64 > u32::MAX as u64 {
2651 return Err(Error::InvalidArgument(
2652 "fallocate: range exceeds u32 block count",
2653 ));
2654 }
2655 let need_blocks = need_blocks_u64 as u32;
2656
2657 let (inode, mut raw) = self.read_inode_verified(ino)?;
2658 if !inode.is_file() {
2659 return Err(Error::InvalidArgument(
2660 "fallocate: target is not a regular file",
2661 ));
2662 }
2663 if !inode.has_extents() {
2664 return Err(Error::InvalidArgument(
2665 "fallocate: legacy (non-extents) inodes not supported",
2666 ));
2667 }
2668
2669 for log in first_block..last_block_excl {
2673 if crate::extent::map_logical(&inode.block, self.dev.as_ref(), bs_u32, log)?.is_some() {
2674 return Err(Error::InvalidArgument(
2675 "fallocate: range partially mapped (v1 limitation)",
2676 ));
2677 }
2678 }
2679
2680 let inode_group = (ino - 1) / self.sb.inodes_per_group;
2682 let mut bitmap_reader = |block: u64| self.read_block(block);
2683 let plan = crate::alloc::plan_block_allocation(
2684 &self.sb,
2685 &self.allocation_groups(),
2686 need_blocks,
2687 inode_group,
2688 &mut bitmap_reader,
2689 )?;
2690
2691 if need_blocks > 0x7FFF {
2695 return Err(Error::InvalidArgument(
2696 "fallocate: single-extent length > 32K blocks (split needed)",
2697 ));
2698 }
2699 let new_extent = crate::extent::Extent {
2700 logical_block: first_block as u32,
2701 length: need_blocks as u16,
2702 physical_block: plan.first_block,
2703 uninitialized: true,
2704 };
2705 let mut buf = BlockBuffer::new(self.sb.block_size());
2709 self.buffer_mark_block_run_used(&mut buf, plan.first_block, need_blocks as u64)?;
2710 self.buffer_patch_bgd_counters(
2711 &mut buf,
2712 plan.bgd.group_idx as usize,
2713 plan.bgd.free_blocks_delta,
2714 plan.bgd.free_inodes_delta,
2715 plan.bgd.used_dirs_delta,
2716 )?;
2717
2718 let mut meta_blocks: u64 = 0;
2722 let new_root = match crate::extent_mut::plan_insert_extent(&inode.block, new_extent) {
2723 Ok(muts) => muts
2724 .into_iter()
2725 .find_map(|m| match m {
2726 crate::extent_mut::ExtentMutation::WriteRoot { bytes } => Some(bytes),
2727 _ => None,
2728 })
2729 .ok_or(Error::Corrupt("fallocate: extent insert wrote no root"))?,
2730 Err(Error::CorruptExtentTree(msg))
2731 if msg.contains("LEAF_FULL_NEEDS_PROMOTION")
2732 || msg.contains("multi-level tree mutation") =>
2733 {
2734 let reader = FsBlockReader { fs: self };
2735 let deep_plan = {
2736 let mut alloc_node = || -> Result<u64> {
2737 let p = self.plan_buffered_block_allocation(&buf, 1, inode_group)?;
2738 self.buffer_mark_block_run_used(&mut buf, p.first_block, 1)?;
2739 self.buffer_patch_bgd_counters(
2740 &mut buf,
2741 p.bgd.group_idx as usize,
2742 p.bgd.free_blocks_delta,
2743 0,
2744 0,
2745 )?;
2746 meta_blocks += 1;
2747 Ok(p.first_block)
2748 };
2749 crate::extent_mut::plan_insert_extent_deep(
2750 &inode.block,
2751 new_extent,
2752 bs_u32,
2753 &reader,
2754 &mut alloc_node,
2755 )?
2756 };
2757 for (block, mut bytes) in deep_plan.block_writes {
2758 if self.csum.enabled {
2759 self.csum
2760 .patch_extent_tail(ino, inode.generation, &mut bytes);
2761 }
2762 buf.put(block, bytes);
2763 }
2764 deep_plan.new_root
2765 }
2766 Err(e) => return Err(e),
2767 };
2768 self.buffer_patch_sb_counters(
2769 &mut buf,
2770 plan.sb.free_blocks_delta - meta_blocks as i64,
2771 plan.sb.free_inodes_delta,
2772 )?;
2773
2774 Self::patch_inode_block_area(&mut raw, &new_root)?;
2776
2777 let sectors_per_block = bs / 512;
2779 let new_i_blocks = inode
2780 .blocks
2781 .saturating_add((need_blocks as u64 + meta_blocks) * sectors_per_block);
2782 Self::patch_inode_size_and_blocks(&mut raw, inode.size, new_i_blocks)?;
2783
2784 let now = self.runtime.now_unix_seconds();
2786 set_inode_time(&mut raw, InodeTime::Ctime, now);
2787 set_inode_time(&mut raw, InodeTime::Mtime, now);
2788
2789 self.finalize_inode_raw(ino, inode.generation, &mut raw)?;
2790 self.buffer_write_inode(&mut buf, ino, &raw)?;
2791
2792 self.commit_block_buffer(buf)
2793 }
2794
2795 pub fn apply_fallocate_punch_hole(&self, ino: u32, offset: u64, len: u64) -> Result<()> {
2814 self.refuse_write()?;
2815 if len == 0 {
2816 return Ok(());
2817 }
2818 let bs = self.sb.block_size() as u64;
2819 let bs_u32 = self.sb.block_size();
2820 let end = offset
2821 .checked_add(len)
2822 .ok_or(Error::InvalidArgument("punch_hole: offset+len overflow"))?;
2823 let punch_first = offset.div_ceil(bs);
2828 let punch_last_excl = end / bs;
2829
2830 let (inode, mut raw) = self.read_inode_verified(ino)?;
2831 if !inode.is_file() {
2832 return Err(Error::InvalidArgument("punch_hole: not a regular file"));
2833 }
2834 if !inode.has_extents() {
2835 return Err(Error::InvalidArgument(
2836 "punch_hole: legacy (non-extents) inodes not supported",
2837 ));
2838 }
2839
2840 let (extents, tree_nodes) =
2841 crate::extent::collect_all_with_nodes(&inode.block, self.dev.as_ref(), bs_u32)?;
2842 let mut new_entries: Vec<crate::extent::Extent> = Vec::new();
2843 let mut freed_blocks: u64 = 0;
2844 let mut buf = BlockBuffer::new(bs_u32);
2845
2846 let (head_block, tail_block) = (offset / bs, end / bs);
2849 let edges = if head_block == tail_block {
2850 vec![head_block]
2851 } else {
2852 vec![head_block, tail_block]
2853 };
2854 for lb in edges {
2855 let lo = offset.max(lb * bs);
2856 let hi = end.min((lb + 1) * bs);
2857 if lo >= hi || hi - lo == bs {
2858 continue; }
2860 let Some(phys) =
2861 crate::extent::map_logical(&inode.block, self.dev.as_ref(), bs_u32, lb)?
2862 else {
2863 continue;
2864 };
2865 let block = buf.get_mut(self, phys)?;
2866 let (from, to) = ((lo - lb * bs) as usize, (hi - lb * bs) as usize);
2867 block[from..to].fill(0);
2868 }
2869
2870 let frees_blocks = punch_first < punch_last_excl;
2873 for e in extents.iter().filter(|_| frees_blocks) {
2874 let el = e.logical_block as u64;
2875 let er = el + e.length as u64;
2876
2877 if er <= punch_first || el >= punch_last_excl {
2878 new_entries.push(*e);
2880 continue;
2881 }
2882 if el >= punch_first && er <= punch_last_excl {
2883 freed_blocks += self.buffer_free_block_run_and_bgd(
2885 &mut buf,
2886 e.physical_block,
2887 e.length as u64,
2888 )?;
2889 continue;
2890 }
2891 let free_lo = el.max(punch_first);
2894 let free_hi = er.min(punch_last_excl);
2895 let free_offset_in_e = free_lo - el;
2896 let free_len = (free_hi - free_lo) as u32;
2897 let free_phys = e.physical_block + free_offset_in_e;
2898 freed_blocks +=
2899 self.buffer_free_block_run_and_bgd(&mut buf, free_phys, free_len as u64)?;
2900
2901 if el < punch_first {
2902 new_entries.push(crate::extent::Extent {
2903 logical_block: el as u32,
2904 length: (punch_first - el) as u16,
2905 physical_block: e.physical_block,
2906 uninitialized: e.uninitialized,
2907 });
2908 }
2909 if er > punch_last_excl {
2910 new_entries.push(crate::extent::Extent {
2911 logical_block: punch_last_excl as u32,
2912 length: (er - punch_last_excl) as u16,
2913 physical_block: e.physical_block + (punch_last_excl - el),
2914 uninitialized: e.uninitialized,
2915 });
2916 }
2917 }
2918
2919 let mut allocated_blocks = 0;
2927 if frees_blocks {
2928 let gen = u32::from_le_bytes(inode.block[8..12].try_into().unwrap());
2929 let repacked = {
2930 let mut alloc = || self.buffer_allocate_block(&mut buf, ino);
2931 crate::extent_mut::plan_repack_tree(
2932 gen,
2933 &new_entries,
2934 bs_u32,
2935 &tree_nodes,
2936 &mut alloc,
2937 )?
2938 };
2939 allocated_blocks = repacked.allocated_blocks.len() as u64;
2940 for (block, mut bytes) in repacked.block_writes {
2941 if self.csum.enabled {
2942 self.csum
2943 .patch_extent_tail(ino, inode.generation, &mut bytes);
2944 }
2945 buf.put(block, bytes);
2946 }
2947 for &node in &tree_nodes {
2948 if repacked.used_nodes.contains(&node) {
2949 continue;
2950 }
2951 freed_blocks += self.buffer_free_block_run_and_bgd(&mut buf, node, 1)?;
2952 }
2953 Self::patch_inode_block_area(&mut raw, &repacked.new_root)?;
2954 }
2955
2956 let sectors_per_block = bs / 512;
2959 let new_i_blocks = inode
2960 .blocks
2961 .saturating_sub(freed_blocks * sectors_per_block)
2962 + allocated_blocks * sectors_per_block;
2963 Self::patch_inode_size_and_blocks(&mut raw, inode.size, new_i_blocks)?;
2964 let now = self.runtime.now_unix_seconds();
2965 set_inode_time(&mut raw, InodeTime::Ctime, now);
2966 set_inode_time(&mut raw, InodeTime::Mtime, now);
2967 self.finalize_inode_raw(ino, inode.generation, &mut raw)?;
2968 self.buffer_write_inode(&mut buf, ino, &raw)?;
2969
2970 if freed_blocks > 0 {
2971 self.buffer_patch_sb_counters(&mut buf, freed_blocks as i64, 0)?;
2972 }
2973
2974 self.commit_block_buffer(buf)
2975 }
2976
2977 pub fn apply_fallocate_zero_range(&self, ino: u32, offset: u64, len: u64) -> Result<()> {
2987 if len == 0 {
2988 return Ok(());
2989 }
2990 self.apply_fallocate_punch_hole(ino, offset, len)?;
2991 let bs = self.sb.block_size() as u64;
2994 let end = offset
2995 .checked_add(len)
2996 .ok_or(Error::InvalidArgument("zero_range: offset+len overflow"))?;
2997 let (first, last) = (offset.div_ceil(bs) * bs, end / bs * bs);
2998 if first >= last {
2999 return Ok(());
3000 }
3001 self.apply_fallocate_keep_size(ino, first, last - first)
3002 }
3003
3004 pub fn apply_chmod(&self, path: &str, mode: u16) -> Result<()> {
3012 self.apply_chmod_bytes(path.as_bytes(), mode)
3013 }
3014
3015 pub(crate) fn apply_chmod_bytes(&self, path: &[u8], mode: u16) -> Result<()> {
3017 self.refuse_write()?;
3018 let ino = self.resolve(path)?;
3019 self.apply_chmod_ino(ino, mode)
3020 }
3021
3022 pub fn apply_chmod_ino(&self, r: impl Into<InodeRef>, mode: u16) -> Result<()> {
3024 self.refuse_write()?;
3025 let r = r.into();
3026 let ino = r.ino;
3027 let (inode, mut raw) = self.live_inode(r)?;
3028
3029 let file_type_bits = inode.mode & crate::inode::S_IFMT;
3032 let new_mode = file_type_bits | (mode & 0x0FFF);
3033 raw[0x00..0x02].copy_from_slice(&new_mode.to_le_bytes());
3034
3035 let now = self.runtime.now_unix_seconds();
3037 set_inode_time(&mut raw, InodeTime::Ctime, now);
3038
3039 self.finalize_inode_raw(ino, inode.generation, &mut raw)?;
3040 self.commit_inode_write(ino, &raw)
3041 }
3042
3043 fn commit_inode_write(&self, ino: u32, new_inode_raw: &[u8]) -> Result<()> {
3051 let mut buf = BlockBuffer::new(self.sb.block_size());
3052 self.buffer_write_inode(&mut buf, ino, new_inode_raw)?;
3053 self.commit_block_buffer(buf)
3054 }
3055
3056 pub(crate) fn buffer_write_inode(
3069 &self,
3070 buf: &mut BlockBuffer,
3071 ino: u32,
3072 inode_raw: &[u8],
3073 ) -> Result<()> {
3074 let (block, offset) = bgd::locate_inode(&self.sb, &self.groups, ino)?;
3075 let it_buf = buf.get_mut(self, block)?;
3076 let off = offset as usize;
3077 it_buf[off..off + inode_raw.len()].copy_from_slice(inode_raw);
3078 Ok(())
3079 }
3080
3081 pub(crate) fn extent_tree_runs(&self, ino: u32, inode: &Inode) -> Result<Vec<(u64, u64)>> {
3093 let (extents, nodes) = crate::extent::collect_all_with_nodes(
3094 &inode.block,
3095 self.dev.as_ref(),
3096 self.sb.block_size(),
3097 )?;
3098 for &node in &nodes {
3099 let bytes = self.read_block(node)?;
3100 if !self.csum.verify_extent_tail(ino, inode.generation, &bytes) {
3101 return Err(Error::BadChecksum {
3102 what: "extent block",
3103 });
3104 }
3105 }
3106 Ok(extents
3107 .iter()
3108 .map(|e| (e.physical_block, e.length as u64))
3109 .chain(nodes.into_iter().map(|n| (n, 1)))
3110 .collect())
3111 }
3112
3113 pub(crate) fn buffer_free_runs(
3115 &self,
3116 buf: &mut BlockBuffer,
3117 runs: &[(u64, u64)],
3118 ) -> Result<u64> {
3119 let mut freed = 0;
3120 for &(start, len) in runs {
3121 freed += self.buffer_free_block_run_and_bgd(buf, start, len)?;
3122 }
3123 Ok(freed)
3124 }
3125
3126 pub(crate) fn buffer_free_block_run_and_bgd(
3138 &self,
3139 buf: &mut BlockBuffer,
3140 start: u64,
3141 len: u64,
3142 ) -> Result<u64> {
3143 for (gi, bit_start, chunk) in self.group_chunks(start, len)? {
3144 let bitmap_block = self.groups[gi].block_bitmap;
3145 {
3146 let bm = buf.get_mut(self, bitmap_block)?;
3147 for i in 0..chunk {
3148 let bit = bit_start + i;
3149 let byte = (bit / 8) as usize;
3150 let mask = 1u8 << (bit % 8);
3151 if byte < bm.len() {
3152 bm[byte] &= !mask;
3153 }
3154 }
3155 }
3156 self.buffer_refresh_bitmap_csum(buf, gi, false)?;
3157 self.buffer_patch_bgd_counters(buf, gi, chunk as i32, 0, 0)?;
3158 }
3159 Ok(len)
3160 }
3161
3162 fn group_chunks(&self, start: u64, len: u64) -> Result<Vec<(usize, u64, u64)>> {
3169 let bpg = self.sb.blocks_per_group as u64;
3170 let first_data = self.sb.first_data_block as u64;
3171 let end = start.checked_add(len).ok_or(Error::InvalidBlock(start))?;
3172 if start < first_data || bpg == 0 {
3173 return Err(Error::InvalidBlock(start));
3174 }
3175 if end > self.sb.blocks_count {
3181 return Err(Error::InvalidBlock(self.sb.blocks_count));
3182 }
3183 let mut chunks = Vec::new();
3184 let mut at = start;
3185 while at < end {
3186 let gi = ((at - first_data) / bpg) as usize;
3187 if gi >= self.groups.len() {
3188 return Err(Error::InvalidBlock(at));
3189 }
3190 let group_start = first_data + gi as u64 * bpg;
3191 let chunk = end.min(group_start + bpg) - at;
3192 chunks.push((gi, at - group_start, chunk));
3193 at += chunk;
3194 }
3195 Ok(chunks)
3196 }
3197
3198 fn restamp_group_desc_csum(&self, block: &mut [u8], off: usize, gi: usize) {
3202 let end = off + self.sb.desc_size as usize;
3203 if let Some(c) =
3204 crate::checksum::group_desc_csum(&self.sb, &self.csum, gi as u32, &block[off..end])
3205 {
3206 block[off + 0x1E..off + 0x20].copy_from_slice(&c.to_le_bytes());
3207 }
3208 }
3209
3210 fn clear_bgd_uninit_flag_if_set(
3218 &self,
3219 buf: &mut BlockBuffer,
3220 gi: usize,
3221 which: BgdUninitFlag,
3222 ) -> Result<bool> {
3223 let flag = match which {
3224 BgdUninitFlag::Inode => bgd::BgdFlags::INODE_UNINIT.bits(),
3225 BgdUninitFlag::Block => bgd::BgdFlags::BLOCK_UNINIT.bits(),
3226 };
3227
3228 let (bgt_block, off) = self.sb.descriptor_location(gi as u64);
3230
3231 let block = buf.get_mut(self, bgt_block)?;
3232 let flags_off = off + 0x12;
3233 let flags = u16::from_le_bytes(block[flags_off..flags_off + 2].try_into().unwrap());
3234 if flags & flag == 0 {
3235 return Ok(false);
3236 }
3237 let new_flags = flags & !flag;
3238 block[flags_off..flags_off + 2].copy_from_slice(&new_flags.to_le_bytes());
3239 buf.uninit_cleared
3250 .entry(gi)
3251 .and_modify(|f| *f &= !flag)
3252 .or_insert(new_flags);
3253 Ok(true)
3254 }
3255
3256 fn allocation_groups(&self) -> AllocationGroups<'_> {
3266 let mut state = self.uninit_cleared.lock().unwrap();
3267 if state.cleared.is_empty() {
3268 return AllocationGroups::Snapshot(&self.groups);
3269 }
3270 if state.overridden.is_none() {
3271 let mut groups = self.groups.clone();
3272 for (&gi, &flags) in state.cleared.iter() {
3273 groups[gi].flags = flags;
3274 }
3275 state.overridden = Some(groups.into());
3276 }
3277 AllocationGroups::Overridden(state.overridden.clone().unwrap())
3278 }
3279
3280 fn plan_buffered_block_allocation(
3289 &self,
3290 buf: &BlockBuffer,
3291 count: u32,
3292 hint: u32,
3293 ) -> Result<crate::alloc::BlockAllocationPlan> {
3294 self.plan_buffered_block_allocation_excluding(buf, count, hint, &[])
3295 }
3296
3297 fn plan_buffered_block_allocation_excluding(
3300 &self,
3301 buf: &BlockBuffer,
3302 count: u32,
3303 hint: u32,
3304 reserved: &[u64],
3305 ) -> Result<crate::alloc::BlockAllocationPlan> {
3306 let base = self.allocation_groups();
3307 let mut groups = Cow::Borrowed(&*base);
3308 for (&gi, &flags) in &buf.uninit_cleared {
3309 if let Some(g) = groups.to_mut().get_mut(gi) {
3310 g.flags = flags;
3311 }
3312 }
3313 crate::alloc::plan_block_allocation_excluding(
3314 &self.sb,
3315 &groups,
3316 count,
3317 hint,
3318 reserved,
3319 |block| match buf.dirty.get(&block) {
3320 Some(bytes) => Ok(bytes.clone()),
3321 None => self.read_block(block),
3322 },
3323 )
3324 }
3325
3326 pub(crate) fn buffer_mark_block_run_used(
3327 &self,
3328 buf: &mut BlockBuffer,
3329 start: u64,
3330 len: u64,
3331 ) -> Result<()> {
3332 let bpg = self.sb.blocks_per_group as u64;
3333 let first_data = self.sb.first_data_block as u64;
3334 let gi = ((start - first_data) / bpg) as usize;
3335 if gi >= self.groups.len() {
3336 return Err(Error::InvalidBlock(start));
3337 }
3338 let group_start = first_data + gi as u64 * bpg;
3339 let bit_start = (start - group_start) as u32;
3340
3341 let was_uninit = self.clear_bgd_uninit_flag_if_set(buf, gi, BgdUninitFlag::Block)?;
3356 let implied = was_uninit
3360 .then(|| crate::alloc::uninit_block_bitmap(&self.sb, &self.groups, gi as u32));
3361 let bitmap_block = self.groups[gi].block_bitmap;
3362 let bm = buf.get_mut(self, bitmap_block)?;
3363 if let Some(implied) = implied {
3364 let n = implied.len().min(bm.len());
3365 bm[..n].copy_from_slice(&implied[..n]);
3366 }
3367 for i in 0..len {
3368 let bit = bit_start as u64 + i;
3369 let byte = (bit / 8) as usize;
3370 let mask = 1u8 << (bit % 8);
3371 if byte < bm.len() {
3372 bm[byte] |= mask;
3373 }
3374 }
3375 self.buffer_refresh_bitmap_csum(buf, gi, false)?;
3376 Ok(())
3377 }
3378
3379 pub(crate) fn buffer_refresh_bitmap_csum(
3385 &self,
3386 buf: &mut BlockBuffer,
3387 gi: usize,
3388 inode_bitmap: bool,
3389 ) -> Result<()> {
3390 if !self.csum.enabled {
3391 return Ok(());
3392 }
3393 let (bitmap_block, coverage, lo_off, hi_off) = if inode_bitmap {
3394 (
3395 self.groups[gi].inode_bitmap,
3396 (self.sb.inodes_per_group as usize).div_ceil(8),
3397 0x1A,
3398 0x3A,
3399 )
3400 } else {
3401 (
3402 self.groups[gi].block_bitmap,
3403 (self.sb.blocks_per_group as usize).div_ceil(8),
3404 0x18,
3405 0x38,
3406 )
3407 };
3408 let csum = {
3409 let bm = buf.get_mut(self, bitmap_block)?;
3410 let end = coverage.min(bm.len());
3411 crate::checksum::linux_crc32c(self.csum.seed, &bm[..end])
3412 };
3413
3414 let desc_size = self.sb.desc_size as u64;
3415 let (bgt_block, off) = self.sb.descriptor_location(gi as u64);
3417 let has_hi = desc_size >= 0x40;
3418 let block = buf.get_mut(self, bgt_block)?;
3419 block[off + lo_off..off + lo_off + 2]
3420 .copy_from_slice(&((csum & 0xFFFF) as u16).to_le_bytes());
3421 if has_hi {
3422 block[off + hi_off..off + hi_off + 2]
3423 .copy_from_slice(&(((csum >> 16) & 0xFFFF) as u16).to_le_bytes());
3424 }
3425 self.restamp_group_desc_csum(block, off, gi);
3427 Ok(())
3428 }
3429
3430 pub(crate) fn buffer_free_inode_slot(&self, buf: &mut BlockBuffer, ino: u32) -> Result<()> {
3436 let ipg = self.sb.inodes_per_group;
3437 let gi = ((ino - 1) / ipg) as usize;
3438 if gi >= self.groups.len() {
3439 return Err(Error::InvalidInode(ino));
3440 }
3441 let bit = ((ino - 1) % ipg) as u64;
3442 let bitmap_block = self.groups[gi].inode_bitmap;
3443 {
3444 let bm = buf.get_mut(self, bitmap_block)?;
3445 let byte = (bit / 8) as usize;
3446 let mask = 1u8 << (bit % 8);
3447 if byte < bm.len() {
3448 bm[byte] &= !mask;
3449 }
3450 }
3451 self.buffer_refresh_bitmap_csum(buf, gi, true)?;
3452 self.buffer_patch_bgd_counters(buf, gi, 0, 1, 0)
3453 }
3454
3455 pub(crate) fn buffer_mark_inode_used(&self, buf: &mut BlockBuffer, ino: u32) -> Result<()> {
3460 let ipg = self.sb.inodes_per_group;
3461 let gi = ((ino - 1) / ipg) as usize;
3462 if gi >= self.groups.len() {
3463 return Err(Error::InvalidInode(ino));
3464 }
3465 let bit = ((ino - 1) % ipg) as u64;
3466 let bitmap_block = self.groups[gi].inode_bitmap;
3467
3468 let was_uninit = self.clear_bgd_uninit_flag_if_set(buf, gi, BgdUninitFlag::Inode)?;
3485 let bm = buf.get_mut(self, bitmap_block)?;
3486 if was_uninit {
3487 bm.iter_mut().for_each(|byte| *byte = 0);
3488 let bits_per_block = (bm.len() as u64) * 8;
3496 for pad_bit in (ipg as u64)..bits_per_block {
3497 let byte = (pad_bit / 8) as usize;
3498 let mask = 1u8 << (pad_bit % 8);
3499 bm[byte] |= mask;
3500 }
3501 }
3502 let byte = (bit / 8) as usize;
3503 let mask = 1u8 << (bit % 8);
3504 if byte < bm.len() {
3505 bm[byte] |= mask;
3506 }
3507 self.buffer_refresh_bitmap_csum(buf, gi, true)?;
3508
3509 let floor = ipg.saturating_sub(bit as u32 + 1);
3518 let desc_size = self.sb.desc_size as u64;
3519 let (bgt_block, off) = self.sb.descriptor_location(gi as u64);
3521 let has_hi = desc_size >= 0x40;
3522 let block = buf.get_mut(self, bgt_block)?;
3523 let cur_lo = u16::from_le_bytes(block[off + 0x1C..off + 0x1E].try_into().unwrap()) as u32;
3524 let cur_hi = if has_hi {
3525 u16::from_le_bytes(block[off + 0x32..off + 0x34].try_into().unwrap()) as u32
3526 } else {
3527 0
3528 };
3529 let cur = (cur_hi << 16) | cur_lo;
3530 if floor < cur {
3531 block[off + 0x1C..off + 0x1E].copy_from_slice(&((floor & 0xFFFF) as u16).to_le_bytes());
3532 if has_hi {
3533 block[off + 0x32..off + 0x34]
3534 .copy_from_slice(&(((floor >> 16) & 0xFFFF) as u16).to_le_bytes());
3535 }
3536 self.restamp_group_desc_csum(block, off, gi);
3537 }
3538 Ok(())
3539 }
3540
3541 pub(crate) fn buffer_patch_bgd_counters(
3545 &self,
3546 buf: &mut BlockBuffer,
3547 gi: usize,
3548 free_blocks_delta: i32,
3549 free_inodes_delta: i32,
3550 used_dirs_delta: i32,
3551 ) -> Result<()> {
3552 let (bgt_block, off_in_block) = self.sb.descriptor_location(gi as u64);
3554
3555 let block = buf.get_mut(self, bgt_block)?;
3556 patch_bgd_counter_fields(
3557 block,
3558 off_in_block,
3559 self.sb.desc_size,
3560 free_blocks_delta,
3561 free_inodes_delta,
3562 used_dirs_delta,
3563 );
3564
3565 self.restamp_group_desc_csum(&mut block[..], off_in_block, gi);
3566 Ok(())
3567 }
3568
3569 pub(crate) fn buffer_patch_sb_counters(
3575 &self,
3576 buf: &mut BlockBuffer,
3577 free_blocks_delta: i64,
3578 free_inodes_delta: i32,
3579 ) -> Result<()> {
3580 let bs = self.sb.block_size() as u64;
3581 let sb_offset = crate::superblock::SUPERBLOCK_OFFSET; let sb_block = sb_offset / bs;
3583 let off_in_block = (sb_offset % bs) as usize;
3584
3585 let block = buf.get_mut(self, sb_block)?;
3586 let sb = &mut block[off_in_block..off_in_block + 1024];
3587
3588 let fi = u32::from_le_bytes(sb[0x10..0x14].try_into().unwrap()) as i64;
3590 let fi_new = (fi + free_inodes_delta as i64).max(0) as u32;
3591 sb[0x10..0x14].copy_from_slice(&fi_new.to_le_bytes());
3592
3593 let lo = u32::from_le_bytes(sb[0x0C..0x10].try_into().unwrap()) as u64;
3595 let hi = u32::from_le_bytes(sb[0x158..0x15C].try_into().unwrap()) as u64;
3596 let cur = ((hi << 32) | lo) as i64;
3597 let new = (cur + free_blocks_delta).max(0) as u64;
3598 sb[0x0C..0x10].copy_from_slice(&(new as u32).to_le_bytes());
3599 sb[0x158..0x15C].copy_from_slice(&((new >> 32) as u32).to_le_bytes());
3600
3601 if self.csum.enabled {
3602 let csum = crate::checksum::linux_crc32c(!0, &sb[..0x3FC]);
3603 sb[0x3FC..0x400].copy_from_slice(&csum.to_le_bytes());
3604 }
3605 Ok(())
3606 }
3607
3608 pub(crate) fn buffer_patch_sb_compat(&self, buf: &mut BlockBuffer, bits: u32) -> Result<()> {
3610 let bs = self.sb.block_size() as u64;
3611 let sb_offset = crate::superblock::SUPERBLOCK_OFFSET;
3612 let block = buf.get_mut(self, sb_offset / bs)?;
3613 let off = (sb_offset % bs) as usize;
3614 let sb = &mut block[off..off + 1024];
3615 let compat = u32::from_le_bytes(sb[0x5C..0x60].try_into().unwrap());
3616 sb[0x5C..0x60].copy_from_slice(&(compat | bits).to_le_bytes());
3617 if self.csum.enabled {
3618 let csum = crate::checksum::linux_crc32c(!0, &sb[..0x3FC]);
3619 sb[0x3FC..0x400].copy_from_slice(&csum.to_le_bytes());
3620 }
3621 Ok(())
3622 }
3623
3624 pub(crate) fn buffer_patch_sb_last_orphan(
3628 &self,
3629 buf: &mut BlockBuffer,
3630 value: u32,
3631 ) -> Result<()> {
3632 let bs = self.sb.block_size() as u64;
3633 let sb_offset = crate::superblock::SUPERBLOCK_OFFSET;
3634 let sb_block = sb_offset / bs;
3635 let off_in_block = (sb_offset % bs) as usize;
3636 let block = buf.get_mut(self, sb_block)?;
3637 let sb = &mut block[off_in_block..off_in_block + 1024];
3638 sb[0xE8..0xEC].copy_from_slice(&value.to_le_bytes());
3639 if self.csum.enabled {
3640 let csum = crate::checksum::linux_crc32c(!0, &sb[..0x3FC]);
3641 sb[0x3FC..0x400].copy_from_slice(&csum.to_le_bytes());
3642 }
3643 Ok(())
3644 }
3645
3646 pub(crate) fn buffer_remove_dir_entry(
3651 &self,
3652 buf: &mut BlockBuffer,
3653 parent_ino: u32,
3654 parent_inode: &Inode,
3655 name: &[u8],
3656 ) -> Result<()> {
3657 if parent_inode.has_inline_data() {
3660 let (now, _) = self.buffered_inode_verified(buf, parent_ino)?;
3661 if now.has_inline_data() {
3662 return self.buffer_remove_inline_dir_entry(buf, parent_ino, name);
3663 }
3664 return self.buffer_remove_dir_entry(buf, parent_ino, &now, name);
3665 }
3666 let bs = self.sb.block_size();
3667 let has_ft = self.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
3668 let n_blocks = parent_inode.size.div_ceil(bs as u64);
3669 for logical in 0..n_blocks {
3670 let Some(phys) = self.map_inode_logical(parent_inode, logical)? else {
3671 continue;
3672 };
3673 let block = buf.get_mut(self, phys)?;
3674 if Self::is_htree_index_block(parent_inode, logical, block) {
3678 continue;
3679 }
3680 let reserved_tail = if self.csum.enabled && crate::dir::has_csum_tail(block) {
3681 12
3682 } else {
3683 0
3684 };
3685 if crate::dir::remove_entry_from_block(block, name, has_ft, reserved_tail)? {
3686 if self.csum.enabled && reserved_tail == 12 {
3687 self.csum
3688 .patch_dir_entry_tail(parent_ino, parent_inode.generation, block);
3689 }
3690 return Ok(());
3691 }
3692 }
3693 Err(Error::NotFound)
3694 }
3695
3696 pub(crate) fn buffer_update_dotdot(
3700 &self,
3701 buf: &mut BlockBuffer,
3702 dir_ino: u32,
3703 dir_inode: &Inode,
3704 new_parent_ino: u32,
3705 ) -> Result<()> {
3706 if dir_inode.has_inline_data() {
3709 let (now, _) = self.buffered_inode_verified(buf, dir_ino)?;
3710 if !now.has_inline_data() {
3711 return self.buffer_update_dotdot(buf, dir_ino, &now, new_parent_ino);
3712 }
3713 let (inode, raw, mut dir) = self.buffered_inline_dir(buf, dir_ino)?;
3714 dir.parent = new_parent_ino;
3715 return self.buffer_store_inline_dir(buf, dir_ino, &inode, raw, &dir);
3716 }
3717 let phys = self
3718 .map_inode_logical(dir_inode, 0)?
3719 .ok_or(Error::Corrupt("buffer_update_dotdot: dir block 0 missing"))?;
3720 let block = buf.get_mut(self, phys)?;
3721 if block.len() < 24 {
3722 return Err(Error::Corrupt("buffer_update_dotdot: dir block too small"));
3723 }
3724 self.refuse_unverified_dir_block(dir_ino, dir_inode, 0, block)?;
3729 block[12..16].copy_from_slice(&new_parent_ino.to_le_bytes());
3730 if dir_inode.flags & crate::inode::InodeFlags::INDEX.bits() != 0 {
3731 self.csum
3735 .patch_dx_tail(dir_ino, dir_inode.generation, block, 32);
3736 } else if self.csum.enabled && crate::dir::has_csum_tail(block) {
3737 self.csum
3738 .patch_dir_entry_tail(dir_ino, dir_inode.generation, block);
3739 }
3740 Ok(())
3741 }
3742
3743 pub(crate) fn buffer_add_dir_entry_inplace(
3753 &self,
3754 buf: &mut BlockBuffer,
3755 parent_ino: u32,
3756 parent_inode: &Inode,
3757 name: &[u8],
3758 target_ino: u32,
3759 file_type: crate::dir::DirEntryType,
3760 ) -> Result<()> {
3761 if parent_inode.has_inline_data() {
3765 let (now, _) = self.buffered_inode_verified(buf, parent_ino)?;
3766 if now.has_inline_data() {
3767 return self
3768 .buffer_add_inline_dir_entry(buf, parent_ino, name, target_ino, file_type);
3769 }
3770 return self
3771 .buffer_add_dir_entry_inplace(buf, parent_ino, &now, name, target_ino, file_type);
3772 }
3773 let bs = self.sb.block_size();
3774 let has_ft = self.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
3775 if parent_inode.flags & crate::inode::InodeFlags::INDEX.bits() != 0 {
3776 return self.buffer_add_dir_entry_indexed(
3777 buf,
3778 parent_ino,
3779 parent_inode,
3780 name,
3781 target_ino,
3782 file_type,
3783 has_ft,
3784 );
3785 }
3786 let n_blocks = parent_inode.size.div_ceil(bs as u64);
3787 for logical in 0..n_blocks {
3788 let Some(phys) = self.map_inode_logical(parent_inode, logical)? else {
3789 continue;
3790 };
3791 let block = buf.get_mut(self, phys)?;
3792 let reserved_tail = if self.csum.enabled && crate::dir::has_csum_tail(block) {
3793 12
3794 } else {
3795 0
3796 };
3797 match crate::dir::add_entry_to_block(
3798 block,
3799 target_ino,
3800 name,
3801 file_type,
3802 has_ft,
3803 reserved_tail,
3804 ) {
3805 Ok(()) => {
3806 if self.csum.enabled && reserved_tail == 12 {
3807 self.csum
3808 .patch_dir_entry_tail(parent_ino, parent_inode.generation, block);
3809 }
3810 return Ok(());
3811 }
3812 Err(Error::OutOfBounds) => continue,
3813 Err(e) => return Err(e),
3814 }
3815 }
3816 Err(Error::OutOfBounds)
3818 }
3819
3820 #[allow(clippy::too_many_arguments)]
3835 fn buffer_add_dir_entry_indexed(
3836 &self,
3837 buf: &mut BlockBuffer,
3838 parent_ino: u32,
3839 parent_inode: &Inode,
3840 name: &[u8],
3841 target_ino: u32,
3842 file_type: crate::dir::DirEntryType,
3843 has_ft: bool,
3844 ) -> Result<()> {
3845 if parent_inode.flags & (EXT4_CASEFOLD_FL | EXT4_ENCRYPT_FL) != 0 {
3846 return Err(Error::OutOfBounds);
3847 }
3848 let mut read_logical = |logical: u64| -> Result<Vec<u8>> {
3849 let phys = self
3850 .map_inode_logical(parent_inode, logical)?
3851 .ok_or(Error::CorruptDirEntry("htree block is not mapped"))?;
3852 Ok(buf.get_mut(self, phys)?.to_vec())
3853 };
3854 let root = read_logical(0)?;
3858 self.check_dx_block(parent_ino, parent_inode, &root, true)?;
3859 let leaf = crate::htree::lookup_leaf_with(
3860 name,
3861 &root,
3862 &self.sb.hash_seed,
3863 self.sb.unsigned_hash(),
3864 |logical| {
3865 let node = read_logical(u64::from(logical))?;
3866 self.check_dx_block(parent_ino, parent_inode, &node, false)?;
3867 Ok(node)
3868 },
3869 )?
3870 .ok_or(Error::CorruptDirEntry("htree root has no entries"))?;
3871 if leaf == 0 {
3872 return Err(Error::CorruptDirEntry(
3873 "htree routes a name to its own root",
3874 ));
3875 }
3876 let phys = self
3877 .map_inode_logical(parent_inode, u64::from(leaf))?
3878 .ok_or(Error::CorruptDirEntry("htree leaf is not mapped"))?;
3879 let block = buf.get_mut(self, phys)?;
3880 let reserved_tail = if self.csum.enabled && crate::dir::has_csum_tail(block) {
3881 12
3882 } else {
3883 0
3884 };
3885 crate::dir::add_entry_to_block(block, target_ino, name, file_type, has_ft, reserved_tail)?;
3886 if self.csum.enabled && reserved_tail == 12 {
3887 self.csum
3888 .patch_dir_entry_tail(parent_ino, parent_inode.generation, block);
3889 }
3890 Ok(())
3891 }
3892
3893 fn check_dx_block(&self, dir_ino: u32, dir: &Inode, block: &[u8], root: bool) -> Result<()> {
3900 let count_offset = if root {
3901 match block.get(29) {
3907 Some(8) => 32,
3908 _ => return Err(Error::Corrupt("htree root info_length is not 8")),
3909 }
3910 } else {
3911 8
3912 };
3913 match self
3914 .csum
3915 .verify_dx_tail(dir_ino, dir.generation, block, count_offset)
3916 {
3917 Some(false) => Err(Error::BadChecksum {
3918 what: "htree index block",
3919 }),
3920 _ => Ok(()),
3921 }
3922 }
3923
3924 fn buffer_drop_htree_index(&self, buf: &mut BlockBuffer, dir_ino: u32) -> Result<()> {
3946 let (inode, mut raw) = self.buffered_inode_verified(buf, dir_ino)?;
3947 if inode.flags & crate::inode::InodeFlags::INDEX.bits() == 0 {
3948 return Ok(());
3949 }
3950 let bs = self.sb.block_size() as usize;
3951 let physical = |logical: u64| {
3952 self.map_inode_logical(&inode, logical)?
3953 .ok_or(Error::CorruptDirEntry("htree block is not mapped"))
3954 };
3955
3956 let root_phys = physical(0)?;
3959 let root = self.buffered_block(buf, root_phys)?;
3960 self.check_dx_block(dir_ino, &inode, &root, true)?;
3961 let mut nodes = Vec::new();
3962 if let (Ok(info), Ok((_, entries))) = (
3963 crate::htree::parse_root_info(&root),
3964 crate::htree::parse_root_entries(&root),
3965 ) {
3966 let mut level: Vec<u32> = entries.iter().map(|e| e.block).collect();
3967 for _ in 0..info.indirect_levels {
3968 let mut next = Vec::new();
3969 for logical in level {
3970 let phys = physical(u64::from(logical))?;
3971 let block = self.buffered_block(buf, phys)?;
3972 self.check_dx_block(dir_ino, &inode, &block, false)?;
3973 let (_, entries) = crate::htree::parse_node_entries(&block)?;
3974 next.extend(entries.iter().map(|e| e.block));
3975 nodes.push(phys);
3976 }
3977 level = next;
3978 }
3979 }
3980
3981 if self.csum.enabled {
3982 let spanning =
3984 std::iter::once((root_phys, 12)).chain(nodes.into_iter().map(|p| (p, 0)));
3985 for (phys, at) in spanning {
3986 let block = buf.get_mut(self, phys)?;
3987 block[at + 4..at + 6].copy_from_slice(&((bs - at - 12) as u16).to_le_bytes());
3988 self.csum
3989 .patch_dir_entry_tail(dir_ino, inode.generation, block);
3990 }
3991 }
3992
3993 let flags = inode.flags & !crate::inode::InodeFlags::INDEX.bits();
3994 raw[0x20..0x24].copy_from_slice(&flags.to_le_bytes());
3995 self.buffer_write_dir_inode(buf, dir_ino, inode.generation, &mut raw)
3996 }
3997
3998 pub(crate) fn commit_block_buffer(&self, buf: BlockBuffer) -> Result<()> {
4008 if buf.dirty.is_empty() {
4009 return Ok(());
4010 }
4011 let cleared = buf.uninit_cleared.clone();
4012 let publish = |fs: &Self| fs.uninit_cleared.lock().unwrap().publish(cleared);
4013 if let Some(jw_mu) = &self.journal {
4014 let mut jw = jw_mu.lock().map_err(|_| {
4015 Error::Corrupt("journal writer mutex poisoned (prior write panicked)")
4016 })?;
4017 let mut tx = jw.begin();
4018 for (block, bytes) in &buf.dirty {
4019 tx.add_write(*block, bytes.clone())?;
4020 }
4021 jw.commit(self.dev.as_ref(), &tx)?;
4022 publish(self);
4029 Ok(())
4030 } else {
4031 let descriptors: std::collections::BTreeSet<u64> = buf
4032 .uninit_cleared
4033 .keys()
4034 .map(|&gi| self.sb.descriptor_location(gi as u64).0)
4035 .collect();
4036 if let Err(e) = self.write_direct(buf.dirty, &descriptors) {
4037 self.direct_commit_failed
4040 .store(true, std::sync::atomic::Ordering::SeqCst);
4041 return Err(e);
4042 }
4043 publish(self);
4044 Ok(())
4045 }
4046 }
4047
4048 fn write_direct(
4069 &self,
4070 mut dirty: std::collections::BTreeMap<u64, Vec<u8>>,
4071 descriptors: &std::collections::BTreeSet<u64>,
4072 ) -> Result<()> {
4073 let bs = self.sb.block_size() as u64;
4074 let sb_block = crate::superblock::SUPERBLOCK_OFFSET / bs;
4075 let superblock = dirty.remove(&sb_block);
4076 let (late, early): (Vec<_>, Vec<_>) = dirty
4077 .into_iter()
4078 .partition(|(block, _)| descriptors.contains(block));
4079 for stage in [
4080 early,
4081 late,
4082 superblock.map(|b| (sb_block, b)).into_iter().collect(),
4083 ] {
4084 if stage.is_empty() {
4085 continue;
4086 }
4087 for (block, bytes) in stage {
4088 self.dev.write_at(block * bs, &bytes)?;
4089 }
4090 self.dev.flush()?;
4091 }
4092 Ok(())
4093 }
4094
4095 pub fn apply_chown(&self, path: &str, uid: u32, gid: u32) -> Result<()> {
4103 self.apply_chown_bytes(path.as_bytes(), uid, gid)
4104 }
4105
4106 pub(crate) fn apply_chown_bytes(&self, path: &[u8], uid: u32, gid: u32) -> Result<()> {
4108 self.refuse_write()?;
4109 let ino = self.resolve(path)?;
4110 self.apply_chown_ino(ino, uid, gid)
4111 }
4112
4113 pub fn apply_chown_ino(&self, r: impl Into<InodeRef>, uid: u32, gid: u32) -> Result<()> {
4115 self.refuse_write()?;
4116 let r = r.into();
4117 let ino = r.ino;
4118 let (inode, mut raw) = self.live_inode(r)?;
4119
4120 if uid != u32::MAX {
4121 let lo = (uid & 0xFFFF) as u16;
4122 let hi = ((uid >> 16) & 0xFFFF) as u16;
4123 raw[0x02..0x04].copy_from_slice(&lo.to_le_bytes());
4124 raw[0x78..0x7A].copy_from_slice(&hi.to_le_bytes());
4125 }
4126 if gid != u32::MAX {
4127 let lo = (gid & 0xFFFF) as u16;
4128 let hi = ((gid >> 16) & 0xFFFF) as u16;
4129 raw[0x18..0x1A].copy_from_slice(&lo.to_le_bytes());
4130 raw[0x7A..0x7C].copy_from_slice(&hi.to_le_bytes());
4131 }
4132
4133 let now = self.runtime.now_unix_seconds();
4134 set_inode_time(&mut raw, InodeTime::Ctime, now);
4135
4136 self.finalize_inode_raw(ino, inode.generation, &mut raw)?;
4137 self.commit_inode_write(ino, &raw)
4138 }
4139
4140 pub fn apply_set_flags(&self, path: &str, flags: u32) -> Result<()> {
4150 self.apply_set_flags_bytes(path.as_bytes(), flags)
4151 }
4152
4153 pub(crate) fn apply_set_flags_bytes(&self, path: &[u8], flags: u32) -> Result<()> {
4155 use crate::inode::{OFF_FLAGS, USER_MODIFIABLE_FLAGS};
4156 self.refuse_write()?;
4157 let ino = self.resolve(path)?;
4158 let (inode, mut raw) = self.read_inode_verified(ino)?;
4159
4160 if (flags ^ inode.flags) & !USER_MODIFIABLE_FLAGS != 0 {
4161 return Err(Error::InvalidArgument(
4162 "set_flags: only the user-modifiable inode flags may change \
4163 (INDEX, EXTENTS, INLINE_DATA, HUGE_FILE, ENCRYPT, VERITY, CASEFOLD and the like are managed)",
4164 ));
4165 }
4166
4167 raw[OFF_FLAGS..OFF_FLAGS + 4].copy_from_slice(&flags.to_le_bytes());
4168
4169 let now = self.runtime.now_unix_seconds();
4170 set_inode_time(&mut raw, InodeTime::Ctime, now);
4171
4172 self.finalize_inode_raw(ino, inode.generation, &mut raw)?;
4173 self.commit_inode_write(ino, &raw)
4174 }
4175
4176 pub fn apply_removexattr(&self, path: &str, name: &str) -> Result<()> {
4193 self.apply_removexattr_bytes(path.as_bytes(), name)
4194 }
4195
4196 pub(crate) fn apply_removexattr_bytes(&self, path: &[u8], name: &str) -> Result<()> {
4198 self.refuse_write()?;
4199 let ino = self.resolve(path)?;
4200 let (inode, mut raw) = self.read_inode_verified(ino)?;
4201
4202 let inode_size = self.sb.inode_size as usize;
4204 let i_extra_isize = if raw.len() >= 0x82 {
4205 u16::from_le_bytes(raw[0x80..0x82].try_into().unwrap()) as usize
4206 } else {
4207 0
4208 };
4209 let region_start = 128 + i_extra_isize;
4210 let region_end = inode_size.min(raw.len());
4211
4212 let mut found = false;
4217 if i_extra_isize != 0 && region_start + 4 <= region_end {
4220 let region = &mut raw[region_start..region_end];
4221 if crate::xattr::plan_remove_in_inode_region(region, name)?
4222 == crate::xattr::RemoveOutcome::Removed
4223 {
4224 found = true;
4225 }
4226 }
4227 let mut buf = BlockBuffer::new(self.sb.block_size());
4228 if self.buffer_remove_from_external_block(&mut buf, ino, &inode, &mut raw, name)? {
4229 found = true;
4230 }
4231 if !found {
4232 return Err(Error::NotFound);
4233 }
4234 set_inode_time(&mut raw, InodeTime::Ctime, self.runtime.now_unix_seconds());
4239 self.finalize_inode_raw(ino, inode.generation, &mut raw)?;
4240 self.buffer_write_inode(&mut buf, ino, &raw)?;
4241 self.commit_block_buffer(buf)
4242 }
4243
4244 fn buffer_remove_from_external_block(
4253 &self,
4254 buf: &mut BlockBuffer,
4255 ino: u32,
4256 inode: &crate::inode::Inode,
4257 raw: &mut [u8],
4258 name: &str,
4259 ) -> Result<bool> {
4260 if inode.file_acl == 0 {
4261 return Ok(false);
4262 }
4263 let block_nr = inode.file_acl;
4264 let mut block = self.buffer_read_xattr_block(buf, block_nr)?;
4265 let refs = u32::from_le_bytes(block[4..8].try_into().unwrap());
4266 match crate::xattr::plan_remove_from_external_block(&mut block, name, 1)? {
4267 crate::xattr::BlockRemoveOutcome::NotFound => Ok(false),
4268 crate::xattr::BlockRemoveOutcome::Removed if refs > 1 => {
4269 let new_nr = self.buffer_unshare_xattr_block(buf, ino, block_nr, block)?;
4272 Self::write_file_acl(raw, new_nr)?;
4273 Ok(true)
4274 }
4275 crate::xattr::BlockRemoveOutcome::Removed => {
4276 if self.csum.enabled {
4277 self.csum.patch_xattr_block(block_nr, &mut block);
4278 }
4279 buf.put(block_nr, block);
4280 Ok(true)
4281 }
4282 crate::xattr::BlockRemoveOutcome::RemovedNowEmpty => {
4283 let freed = self.buffer_release_xattr_block(buf, block_nr)?;
4287 self.buffer_patch_sb_counters(buf, freed as i64, 0)?;
4288 Self::write_file_acl(raw, 0)?;
4290 let sectors_per_block = self.sb.block_size() as u64 / 512;
4291 let new_blocks = inode.blocks.saturating_sub(sectors_per_block);
4292 Self::patch_inode_size_and_blocks(raw, inode.size, new_blocks)?;
4293 Ok(true)
4294 }
4295 }
4296 }
4297
4298 pub fn apply_setxattr(&self, path: &str, name: &str, value: &[u8]) -> Result<()> {
4311 self.apply_setxattr_bytes(path.as_bytes(), name, value)
4312 }
4313
4314 pub(crate) fn apply_setxattr_bytes(&self, path: &[u8], name: &str, value: &[u8]) -> Result<()> {
4316 self.refuse_write()?;
4317 let ino = self.resolve(path)?;
4318 let (inode, mut raw) = self.read_inode_verified(ino)?;
4319
4320 let inode_size = self.sb.inode_size as usize;
4321 let mut i_extra_isize = if raw.len() >= 0x82 {
4322 u16::from_le_bytes(raw[0x80..0x82].try_into().unwrap()) as usize
4323 } else {
4324 0
4325 };
4326 let extra = crate::inode::EXTRA_ISIZE_DEFAULT as usize;
4336 if i_extra_isize == 0 && inode_size.min(raw.len()) >= 128 + extra {
4337 raw[0x80..0x80 + extra].fill(0);
4338 write_inode_extra_isize(&mut raw);
4339 i_extra_isize = extra;
4340 }
4341 let region_start = 128 + i_extra_isize;
4342 let region_end = inode_size.min(raw.len());
4343 let inline_capable = i_extra_isize != 0 && region_start + 8 <= region_end;
4344
4345 let inline_result = if inline_capable {
4347 let region = &mut raw[region_start..region_end];
4348 crate::xattr::plan_set_in_inode_region(region, name, value)
4349 } else {
4350 Err(Error::NoSpaceLeftOnDevice)
4351 };
4352
4353 match inline_result {
4361 Ok(_) => {
4362 let mut buf = BlockBuffer::new(self.sb.block_size());
4364 if self.buffer_remove_from_external_block(&mut buf, ino, &inode, &mut raw, name)? {
4365 set_inode_time(&mut raw, InodeTime::Ctime, self.runtime.now_unix_seconds());
4366 }
4367 self.finalize_inode_raw(ino, inode.generation, &mut raw)?;
4368 self.buffer_write_inode(&mut buf, ino, &raw)?;
4369 self.commit_block_buffer(buf)
4370 }
4371 Err(Error::NoSpaceLeftOnDevice) => {
4372 if inline_capable {
4376 let region = &mut raw[region_start..region_end];
4377 crate::xattr::plan_remove_in_inode_region(region, name)?;
4378 }
4379 self.apply_setxattr_external_block(ino, &inode, &mut raw, name, value)
4380 }
4381 Err(e) => Err(e),
4382 }
4383 }
4384
4385 pub(crate) fn finalize_inode_raw(
4388 &self,
4389 ino: u32,
4390 generation: u32,
4391 raw: &mut [u8],
4392 ) -> Result<()> {
4393 if self.csum.enabled {
4394 self.csum.patch_inode_checksum(ino, generation, raw);
4395 }
4396 Ok(())
4397 }
4398
4399 fn apply_setxattr_external_block(
4403 &self,
4404 ino: u32,
4405 inode: &crate::inode::Inode,
4406 raw: &mut [u8],
4407 name: &str,
4408 value: &[u8],
4409 ) -> Result<()> {
4410 let bs = self.sb.block_size();
4411 let bs_u64 = bs as u64;
4412
4413 let mut buf = BlockBuffer::new(bs);
4416 if self.sb.feature_compat & features::Compat::EXT_ATTR.bits() == 0 {
4422 self.buffer_patch_sb_compat(&mut buf, features::Compat::EXT_ATTR.bits())?;
4423 }
4424
4425 if inode.file_acl != 0 {
4427 let block_nr = inode.file_acl;
4428 let mut block = self.buffer_read_xattr_block(&mut buf, block_nr)?;
4429 let refs = u32::from_le_bytes(block[4..8].try_into().unwrap());
4430 crate::xattr::plan_set_in_external_block(&mut block, name, value, 1)?;
4431 if refs > 1 {
4432 let new_nr = self.buffer_unshare_xattr_block(&mut buf, ino, block_nr, block)?;
4435 Self::write_file_acl(raw, new_nr)?;
4436 } else {
4437 if self.csum.enabled {
4438 self.csum.patch_xattr_block(block_nr, &mut block);
4439 }
4440 buf.put(block_nr, block);
4441 }
4442 let now = self.runtime.now_unix_seconds();
4444 set_inode_time(raw, InodeTime::Ctime, now);
4445 self.finalize_inode_raw(ino, inode.generation, raw)?;
4446 self.buffer_write_inode(&mut buf, ino, raw)?;
4447 return self.commit_block_buffer(buf);
4448 }
4449
4450 let mut bitmap_reader = |block: u64| self.read_block(block);
4453 let inode_group = (ino - 1) / self.sb.inodes_per_group;
4454 let plan = crate::alloc::plan_block_allocation(
4455 &self.sb,
4456 &self.allocation_groups(),
4457 1,
4458 inode_group,
4459 &mut bitmap_reader,
4460 )?;
4461 let block_nr = plan.first_block;
4462
4463 let mut block = vec![0u8; bs as usize];
4464 crate::xattr::plan_set_in_external_block(&mut block, name, value, 1)?;
4465 if self.csum.enabled {
4466 self.csum.patch_xattr_block(block_nr, &mut block);
4467 }
4468 buf.put(block_nr, block);
4469
4470 self.buffer_mark_block_run_used(&mut buf, block_nr, 1)?;
4472 self.buffer_patch_bgd_counters(
4473 &mut buf,
4474 plan.bgd.group_idx as usize,
4475 plan.bgd.free_blocks_delta,
4476 plan.bgd.free_inodes_delta,
4477 plan.bgd.used_dirs_delta,
4478 )?;
4479 self.buffer_patch_sb_counters(
4480 &mut buf,
4481 plan.sb.free_blocks_delta,
4482 plan.sb.free_inodes_delta,
4483 )?;
4484
4485 Self::write_file_acl(raw, block_nr)?;
4489 let sectors_per_block = bs_u64 / 512;
4492 let new_blocks = inode.blocks.saturating_add(sectors_per_block);
4493 Self::patch_inode_size_and_blocks(raw, inode.size, new_blocks)?;
4494 let now = self.runtime.now_unix_seconds();
4495 set_inode_time(raw, InodeTime::Ctime, now);
4496 self.finalize_inode_raw(ino, inode.generation, raw)?;
4497 self.buffer_write_inode(&mut buf, ino, raw)?;
4498
4499 self.commit_block_buffer(buf)
4500 }
4501
4502 fn buffer_allocate_block(&self, buf: &mut BlockBuffer, ino: u32) -> Result<u64> {
4510 let plan =
4511 self.plan_buffered_block_allocation(buf, 1, (ino - 1) / self.sb.inodes_per_group)?;
4512 self.buffer_mark_block_run_used(buf, plan.first_block, 1)?;
4513 self.buffer_patch_bgd_counters(
4514 buf,
4515 plan.bgd.group_idx as usize,
4516 plan.bgd.free_blocks_delta,
4517 plan.bgd.free_inodes_delta,
4518 plan.bgd.used_dirs_delta,
4519 )?;
4520 self.buffer_patch_sb_counters(buf, plan.sb.free_blocks_delta, plan.sb.free_inodes_delta)?;
4521 Ok(plan.first_block)
4522 }
4523
4524 pub(crate) fn buffer_release_xattr_block(
4534 &self,
4535 buf: &mut BlockBuffer,
4536 block_nr: u64,
4537 ) -> Result<u64> {
4538 let mut block = self.buffer_read_xattr_block(buf, block_nr)?;
4539 let refs = u32::from_le_bytes(block[4..8].try_into().unwrap());
4540 if refs <= 1 {
4541 return self.buffer_free_block_run_and_bgd(buf, block_nr, 1);
4542 }
4543 block[4..8].copy_from_slice(&(refs - 1).to_le_bytes());
4544 if self.csum.enabled {
4545 self.csum.patch_xattr_block(block_nr, &mut block);
4546 }
4547 buf.put(block_nr, block);
4548 Ok(0)
4549 }
4550
4551 fn buffer_read_xattr_block(&self, buf: &mut BlockBuffer, block_nr: u64) -> Result<Vec<u8>> {
4564 let block = buf.get_mut(self, block_nr)?.clone();
4565 crate::xattr::check_external_block(&self.csum, block_nr, &block)?;
4566 Ok(block)
4567 }
4568
4569 fn buffer_unshare_xattr_block(
4573 &self,
4574 buf: &mut BlockBuffer,
4575 ino: u32,
4576 old_nr: u64,
4577 mut block: Vec<u8>,
4578 ) -> Result<u64> {
4579 let new_nr = self.buffer_allocate_block(buf, ino)?;
4580 block[4..8].copy_from_slice(&1u32.to_le_bytes());
4581 if self.csum.enabled {
4582 self.csum.patch_xattr_block(new_nr, &mut block);
4583 }
4584 buf.put(new_nr, block);
4585 self.buffer_release_xattr_block(buf, old_nr)?;
4586 Ok(new_nr)
4587 }
4588
4589 fn dtime_now(&self) -> u32 {
4593 self.runtime.now_unix_seconds() as u32
4594 }
4595
4596 pub fn apply_utimens(
4624 &self,
4625 path: &str,
4626 atime_sec: i64,
4627 atime_nsec: u32,
4628 mtime_sec: i64,
4629 mtime_nsec: u32,
4630 ) -> Result<()> {
4631 self.apply_utimens_bytes(
4632 path.as_bytes(),
4633 atime_sec,
4634 atime_nsec,
4635 mtime_sec,
4636 mtime_nsec,
4637 )
4638 }
4639
4640 pub(crate) fn apply_utimens_bytes(
4642 &self,
4643 path: &[u8],
4644 atime_sec: i64,
4645 atime_nsec: u32,
4646 mtime_sec: i64,
4647 mtime_nsec: u32,
4648 ) -> Result<()> {
4649 self.refuse_write()?;
4650 TimeUpdate::check(atime_sec, atime_nsec)?;
4653 TimeUpdate::check(mtime_sec, mtime_nsec)?;
4654 let ino = self.resolve(path)?;
4655 self.apply_utimens_ino(ino, atime_sec, atime_nsec, mtime_sec, mtime_nsec)
4656 }
4657
4658 pub fn apply_utimens_ino(
4660 &self,
4661 r: impl Into<InodeRef>,
4662 atime_sec: i64,
4663 atime_nsec: u32,
4664 mtime_sec: i64,
4665 mtime_nsec: u32,
4666 ) -> Result<()> {
4667 self.refuse_write()?;
4668 let now = self.runtime.now_unix_seconds();
4670 let atime = TimeUpdate::resolve(atime_sec, atime_nsec, now)?;
4671 let mtime = TimeUpdate::resolve(mtime_sec, mtime_nsec, now)?;
4672 let r = r.into();
4673 let ino = r.ino;
4674 let (inode, mut raw) = self.live_inode(r)?;
4675
4676 if let (TimeUpdate::Omit, TimeUpdate::Omit) = (atime, mtime) {
4677 return Ok(());
4678 }
4679
4680 let i_extra_isize = if raw.len() >= 0x82 {
4687 u16::from_le_bytes(raw[0x80..0x82].try_into().unwrap())
4688 } else {
4689 0
4690 };
4691 let has_mtime_extra = i_extra_isize >= 12 && raw.len() >= 0x8C;
4692 let has_atime_extra = i_extra_isize >= 16 && raw.len() >= 0x90;
4693
4694 let fields = [
4696 (atime, 0x08, has_atime_extra.then_some(0x8C)),
4697 (mtime, 0x10, has_mtime_extra.then_some(0x88)),
4698 ];
4699
4700 for (update, _, extra) in fields {
4703 if let TimeUpdate::Set(sec, _) = update {
4704 if crate::inode::pack_seconds(sec).1 != 0 && extra.is_none() {
4705 return Err(Error::InvalidArgument(
4706 "timestamp past 2038 needs an *_extra field this inode is too small to hold",
4707 ));
4708 }
4709 }
4710 }
4711
4712 for (update, base_off, extra) in fields {
4713 let TimeUpdate::Set(sec, nsec) = update else {
4714 continue;
4715 };
4716 let (base, epoch) = crate::inode::pack_seconds(sec);
4717 raw[base_off..base_off + 4].copy_from_slice(&base.to_le_bytes());
4718 if let Some(off) = extra {
4719 let packed = pack_nsec_lo(nsec) | epoch;
4720 raw[off..off + 4].copy_from_slice(&packed.to_le_bytes());
4721 }
4722 }
4723
4724 set_inode_time(&mut raw, InodeTime::Ctime, now);
4727
4728 self.finalize_inode_raw(ino, inode.generation, &mut raw)?;
4729 self.commit_inode_write(ino, &raw)
4730 }
4731
4732 pub fn apply_unlink(&self, path: &str) -> Result<()> {
4749 self.apply_unlink_bytes(path.as_bytes())
4750 }
4751
4752 pub(crate) fn apply_unlink_bytes(&self, path: &[u8]) -> Result<()> {
4754 self.refuse_write()?;
4755 let trailing_slash = path.len() > 1 && path.ends_with(b"/");
4760 let (parent_path, base_name) = split_parent_and_base(path)?;
4761 let parent_ino = self.resolve(parent_path)?;
4762 if trailing_slash {
4763 let (parent_inode, _) = self.live_dir(parent_ino.into())?;
4768 let target_ino = self.find_entry_in_dir(parent_ino, &parent_inode, base_name)?;
4769 let (target_inode, _) = self.live_inode(target_ino.into())?;
4770 return Err(if target_inode.is_dir() {
4771 Error::IsADirectory
4772 } else {
4773 Error::NotADirectory
4774 });
4775 }
4776 self.apply_unlink_at(parent_ino, base_name)
4777 }
4778
4779 pub fn apply_unlink_at(&self, dir: impl Into<InodeRef>, name: &[u8]) -> Result<()> {
4782 self.refuse_write()?;
4783 check_entry_name(name)?;
4784 let dir = dir.into();
4785 let parent_ino_num = dir.ino;
4786 let (parent_inode, _parent_raw) = self.live_dir(dir)?;
4787
4788 let target_ino = self.find_entry_in_dir(parent_ino_num, &parent_inode, name)?;
4789 let (target_inode, mut target_raw) = self.live_inode(target_ino.into())?;
4790 if target_inode.is_dir() {
4791 return Err(Error::IsADirectory);
4794 }
4795
4796 let mut buf = BlockBuffer::new(self.sb.block_size());
4798
4799 let bs = self.sb.block_size();
4802 self.buffer_remove_dir_entry(&mut buf, parent_ino_num, &parent_inode, name)?;
4803
4804 let new_links = target_inode.links_count.saturating_sub(1);
4806 target_raw[0x1A..0x1C].copy_from_slice(&new_links.to_le_bytes());
4807
4808 if new_links > 0 {
4809 self.finalize_inode_raw(target_ino, target_inode.generation, &mut target_raw)?;
4810 self.buffer_write_inode(&mut buf, target_ino, &target_raw)?;
4811 return self.commit_block_buffer(buf);
4812 }
4813
4814 let mut freed_sectors: u64 = 0;
4817 let sectors_per_block = bs as u64 / 512;
4818 if target_inode.has_extents() {
4819 let runs = self.extent_tree_runs(target_ino, &target_inode)?;
4820 freed_sectors += self.buffer_free_runs(&mut buf, &runs)? * sectors_per_block;
4821 } else {
4822 freed_sectors +=
4826 self.buffer_free_block_map(&mut buf, &target_inode)? * sectors_per_block;
4827 }
4828
4829 if target_inode.file_acl != 0 {
4832 freed_sectors += self.buffer_release_xattr_block(&mut buf, target_inode.file_acl)?
4833 * sectors_per_block;
4834 }
4835
4836 self.buffer_free_inode_slot(&mut buf, target_ino)?;
4840
4841 let freed_blocks = freed_sectors.checked_div(sectors_per_block).unwrap_or(0);
4842 self.buffer_patch_sb_counters(&mut buf, freed_blocks as i64, 1)?;
4843
4844 let inode_size = self.sb.inode_size as usize;
4847 let old_gen = target_inode.generation;
4848 for b in &mut target_raw[..inode_size] {
4849 *b = 0;
4850 }
4851 let dtime = self.dtime_now();
4852 target_raw[0x14..0x18].copy_from_slice(&dtime.to_le_bytes()); target_raw[0x64..0x68].copy_from_slice(&old_gen.to_le_bytes()); self.finalize_inode_raw(target_ino, old_gen, &mut target_raw)?;
4855 self.buffer_write_inode(&mut buf, target_ino, &target_raw)?;
4856
4857 self.commit_block_buffer(buf)
4858 }
4859
4860 fn plan_new_inode_in_dir(&self, dir: InodeRef, name: &[u8]) -> Result<NewInodePlan> {
4865 check_new_entry_name(name)?;
4866 let parent_ino = dir.ino;
4867 let (parent_inode, _) = self.live_dir(dir)?;
4868 if self.entry_exists(parent_ino, &parent_inode, name)? {
4869 return Err(Error::AlreadyExists);
4870 }
4871
4872 let parent_group = (parent_ino - 1) / self.sb.inodes_per_group;
4873 let bs = self.sb.block_size();
4874 let mut bitmap_reader = |block: u64| self.read_block(block);
4875 let plan = crate::alloc::plan_inode_allocation(
4876 &self.sb,
4877 &self.allocation_groups(),
4878 false,
4879 parent_group,
4880 &mut bitmap_reader,
4881 )?;
4882 let new_ino = plan.inode;
4883
4884 let mut buf = BlockBuffer::new(bs);
4885 self.buffer_mark_inode_used(&mut buf, new_ino)?;
4886 self.buffer_patch_bgd_counters(
4887 &mut buf,
4888 plan.bgd.group_idx as usize,
4889 plan.bgd.free_blocks_delta,
4890 plan.bgd.free_inodes_delta,
4891 plan.bgd.used_dirs_delta,
4892 )?;
4893 self.buffer_patch_sb_counters(
4894 &mut buf,
4895 plan.sb.free_blocks_delta,
4896 plan.sb.free_inodes_delta,
4897 )?;
4898
4899 Ok(NewInodePlan {
4900 new_ino,
4901 parent_ino,
4902 parent_inode,
4903 buf,
4904 })
4905 }
4906
4907 pub fn apply_create(&self, path: &str, mode: u16) -> Result<u32> {
4922 self.apply_create_bytes(path.as_bytes(), mode)
4923 }
4924
4925 pub(crate) fn apply_create_bytes(&self, path: &[u8], mode: u16) -> Result<u32> {
4927 self.refuse_write()?;
4928 let (parent, name) = self.resolve_new_parent(path)?;
4929 self.apply_create_at(parent, name, mode)
4930 }
4931
4932 pub fn apply_create_at(&self, dir: impl Into<InodeRef>, name: &[u8], mode: u16) -> Result<u32> {
4935 self.refuse_write()?;
4936 let dir = dir.into();
4937 let NewInodePlan {
4938 new_ino,
4939 parent_ino,
4940 parent_inode,
4941 mut buf,
4942 } = self.plan_new_inode_in_dir(dir, name)?;
4943
4944 let raw = self.build_regular_file_inode(new_ino, mode)?;
4945 self.buffer_write_inode(&mut buf, new_ino, &raw)?;
4946
4947 match self.buffer_add_dir_entry_inplace(
4952 &mut buf,
4953 parent_ino,
4954 &parent_inode,
4955 name,
4956 new_ino,
4957 crate::dir::DirEntryType::RegFile,
4958 ) {
4959 Ok(()) => {
4960 self.commit_block_buffer(buf)?;
4961 Ok(new_ino)
4962 }
4963 Err(Error::OutOfBounds) => {
4964 self.extend_dir_and_add_entry(
4967 &mut buf,
4968 parent_ino,
4969 name,
4970 new_ino,
4971 crate::dir::DirEntryType::RegFile,
4972 )?;
4973 self.commit_block_buffer(buf)?;
4974 Ok(new_ino)
4975 }
4976 Err(e) => Err(e),
4977 }
4978 }
4979
4980 pub fn apply_mknod(&self, path: &str, mode: u16, major: u32, minor: u32) -> Result<u32> {
4985 self.apply_mknod_bytes(path.as_bytes(), mode, major, minor)
4986 }
4987
4988 pub(crate) fn apply_mknod_bytes(
4990 &self,
4991 path: &[u8],
4992 mode: u16,
4993 major: u32,
4994 minor: u32,
4995 ) -> Result<u32> {
4996 self.refuse_write()?;
4997 Self::mknod_entry_type(mode)?;
4998 let (parent, name) = self.resolve_new_parent(path)?;
4999 self.apply_mknod_at(parent, name, mode, major, minor)
5000 }
5001
5002 fn mknod_entry_type(mode: u16) -> Result<crate::dir::DirEntryType> {
5005 let file_type = mode & crate::inode::S_IFMT;
5006 match file_type {
5007 crate::inode::S_IFCHR
5008 | crate::inode::S_IFBLK
5009 | crate::inode::S_IFIFO
5010 | crate::inode::S_IFSOCK => Ok(crate::dir::DirEntryType::from_mode(file_type)),
5011 _ => Err(Error::InvalidArgument(
5012 "mknod: unsupported type; use create/mkdir for reg/dir",
5013 )),
5014 }
5015 }
5016
5017 pub fn apply_mknod_at(
5020 &self,
5021 dir: impl Into<InodeRef>,
5022 name: &[u8],
5023 mode: u16,
5024 major: u32,
5025 minor: u32,
5026 ) -> Result<u32> {
5027 self.refuse_write()?;
5028 let dir = dir.into();
5029 let dir_entry_type = Self::mknod_entry_type(mode)?;
5030 let NewInodePlan {
5031 new_ino,
5032 parent_ino,
5033 parent_inode,
5034 mut buf,
5035 } = self.plan_new_inode_in_dir(dir, name)?;
5036
5037 let raw = self.build_special_file_inode(new_ino, mode, major, minor)?;
5038 self.buffer_write_inode(&mut buf, new_ino, &raw)?;
5039
5040 match self.buffer_add_dir_entry_inplace(
5041 &mut buf,
5042 parent_ino,
5043 &parent_inode,
5044 name,
5045 new_ino,
5046 dir_entry_type,
5047 ) {
5048 Ok(()) => {
5049 self.commit_block_buffer(buf)?;
5050 Ok(new_ino)
5051 }
5052 Err(Error::OutOfBounds) => {
5053 self.extend_dir_and_add_entry(&mut buf, parent_ino, name, new_ino, dir_entry_type)?;
5054 self.commit_block_buffer(buf)?;
5055 Ok(new_ino)
5056 }
5057 Err(e) => Err(e),
5058 }
5059 }
5060
5061 fn stamp_inode_checksum(&self, raw: &mut [u8], ino: u32, generation: u32) {
5064 if self.csum.enabled {
5065 self.csum.patch_inode_checksum(ino, generation, raw);
5066 }
5067 }
5068
5069 fn build_special_file_inode(
5070 &self,
5071 ino: u32,
5072 mode: u16,
5073 major: u32,
5074 minor: u32,
5075 ) -> Result<Vec<u8>> {
5076 use crate::inode::{OFF_BLOCK, OFF_LINKS_COUNT, OFF_MODE};
5077 let inode_size = self.sb.inode_size as usize;
5078 let mut raw = vec![0u8; inode_size];
5079
5080 raw[OFF_MODE..OFF_MODE + 2].copy_from_slice(&mode.to_le_bytes());
5081 raw[OFF_LINKS_COUNT..OFF_LINKS_COUNT + 2].copy_from_slice(&1u16.to_le_bytes());
5082
5083 let file_type = mode & crate::inode::S_IFMT;
5086 if file_type == crate::inode::S_IFBLK || file_type == crate::inode::S_IFCHR {
5087 let old_dev = (major << 8) | (minor & 0xff);
5088 raw[OFF_BLOCK..OFF_BLOCK + 4].copy_from_slice(&old_dev.to_le_bytes());
5089 let new_dev = (minor & 0xff) | (major << 8) | ((minor & !0xff) << 12);
5090 raw[OFF_BLOCK + 4..OFF_BLOCK + 8].copy_from_slice(&new_dev.to_le_bytes());
5091 }
5092
5093 write_inode_extra_isize(&mut raw);
5095 let now = self.runtime.now_unix_seconds();
5096 write_inode_timestamps(&mut raw, now);
5097 let generation = self.runtime.next_inode_generation();
5098 write_inode_generation(&mut raw, generation);
5099 self.stamp_inode_checksum(&mut raw, ino, generation);
5100 Ok(raw)
5101 }
5102
5103 pub fn apply_symlink(&self, target: &str, linkpath: &str) -> Result<u32> {
5118 self.apply_symlink_bytes(target.as_bytes(), linkpath.as_bytes())
5119 }
5120
5121 pub(crate) fn apply_symlink_bytes(&self, target: &[u8], linkpath: &[u8]) -> Result<u32> {
5123 self.refuse_write()?;
5124 self.check_symlink_target(target)?;
5125 let (parent, name) = self.resolve_new_parent(linkpath)?;
5126 self.apply_symlink_at(parent, name, target)
5127 }
5128
5129 fn check_symlink_target(&self, target: &[u8]) -> Result<()> {
5130 if target.is_empty() {
5131 return Err(Error::InvalidArgument("symlink target is empty"));
5132 }
5133 if target.contains(&0) {
5135 return Err(Error::InvalidArgument(
5136 "a symlink target cannot contain a NUL byte",
5137 ));
5138 }
5139 let max_target = 4096usize.min(self.sb.block_size() as usize);
5143 if target.len() > max_target {
5144 return Err(Error::NameTooLong);
5145 }
5146 Ok(())
5147 }
5148
5149 pub fn apply_symlink_at(
5152 &self,
5153 dir: impl Into<InodeRef>,
5154 name: &[u8],
5155 target: &[u8],
5156 ) -> Result<u32> {
5157 self.refuse_write()?;
5158 let dir = dir.into();
5159 self.check_symlink_target(target)?;
5160
5161 let NewInodePlan {
5162 new_ino,
5163 parent_ino,
5164 parent_inode,
5165 mut buf,
5166 } = self.plan_new_inode_in_dir(dir, name)?;
5167
5168 let parent_group = (parent_ino - 1) / self.sb.inodes_per_group;
5169 let bs = self.sb.block_size();
5170
5171 let raw = if target.len() < 60 {
5178 self.build_fast_symlink_inode(new_ino, target)?
5179 } else {
5180 let mut bitmap_reader = |block: u64| self.read_block(block);
5181 let bplan = crate::alloc::plan_block_allocation(
5182 &self.sb,
5183 &self.allocation_groups(),
5184 1,
5185 parent_group,
5186 &mut bitmap_reader,
5187 )?;
5188 let data_phys = bplan.first_block;
5189
5190 self.buffer_mark_block_run_used(&mut buf, data_phys, 1)?;
5191 self.buffer_patch_bgd_counters(
5192 &mut buf,
5193 bplan.bgd.group_idx as usize,
5194 bplan.bgd.free_blocks_delta,
5195 bplan.bgd.free_inodes_delta,
5196 bplan.bgd.used_dirs_delta,
5197 )?;
5198 self.buffer_patch_sb_counters(
5199 &mut buf,
5200 bplan.sb.free_blocks_delta,
5201 bplan.sb.free_inodes_delta,
5202 )?;
5203
5204 let mut block = vec![0u8; bs as usize];
5205 block[..target.len()].copy_from_slice(target);
5206 buf.put(data_phys, block);
5207
5208 self.build_slow_symlink_inode(new_ino, target, data_phys)?
5209 };
5210 self.buffer_write_inode(&mut buf, new_ino, &raw)?;
5211
5212 match self.buffer_add_dir_entry_inplace(
5213 &mut buf,
5214 parent_ino,
5215 &parent_inode,
5216 name,
5217 new_ino,
5218 crate::dir::DirEntryType::Symlink,
5219 ) {
5220 Ok(()) => {
5221 self.commit_block_buffer(buf)?;
5222 Ok(new_ino)
5223 }
5224 Err(Error::OutOfBounds) => {
5225 self.extend_dir_and_add_entry(
5226 &mut buf,
5227 parent_ino,
5228 name,
5229 new_ino,
5230 crate::dir::DirEntryType::Symlink,
5231 )?;
5232 self.commit_block_buffer(buf)?;
5233 Ok(new_ino)
5234 }
5235 Err(e) => Err(e),
5236 }
5237 }
5238
5239 fn build_fast_symlink_inode(&self, ino: u32, target: &[u8]) -> Result<Vec<u8>> {
5244 use crate::inode::{OFF_BLOCK, OFF_FLAGS, OFF_LINKS_COUNT, OFF_MODE, OFF_SIZE_LO};
5245 debug_assert!(target.len() < 60);
5246 let mut raw = vec![0u8; self.sb.inode_size as usize];
5247
5248 let mode_bits = crate::inode::S_IFLNK | 0o0777;
5251 raw[OFF_MODE..OFF_MODE + 2].copy_from_slice(&mode_bits.to_le_bytes());
5252 raw[OFF_SIZE_LO..OFF_SIZE_LO + 4].copy_from_slice(&(target.len() as u32).to_le_bytes());
5253 raw[OFF_LINKS_COUNT..OFF_LINKS_COUNT + 2].copy_from_slice(&1u16.to_le_bytes());
5254 raw[OFF_FLAGS..OFF_FLAGS + 4].copy_from_slice(&0u32.to_le_bytes());
5256 let inline_target_off = OFF_BLOCK;
5257 raw[inline_target_off..inline_target_off + target.len()].copy_from_slice(target);
5258
5259 write_inode_extra_isize(&mut raw);
5261 let now = self.runtime.now_unix_seconds();
5262 write_inode_timestamps(&mut raw, now);
5263 let generation = self.runtime.next_inode_generation();
5264 write_inode_generation(&mut raw, generation);
5265 self.stamp_inode_checksum(&mut raw, ino, generation);
5266 Ok(raw)
5267 }
5268
5269 fn map_one_block(&self, raw: &mut [u8], phys: u64) -> Result<()> {
5275 use crate::inode::{OFF_BLOCK, OFF_FLAGS};
5276 if !self.flavor.uses_extents() {
5277 let direct =
5278 u32::try_from(phys).map_err(|_| Error::Corrupt("block past a 32-bit block map"))?;
5279 raw[OFF_BLOCK..OFF_BLOCK + 4].copy_from_slice(&direct.to_le_bytes());
5280 return Ok(());
5281 }
5282 let flags = u32::from_le_bytes(raw[OFF_FLAGS..OFF_FLAGS + 4].try_into().unwrap());
5283 raw[OFF_FLAGS..OFF_FLAGS + 4]
5284 .copy_from_slice(&(flags | crate::inode::InodeFlags::EXTENTS.bits()).to_le_bytes());
5285
5286 let header = OFF_BLOCK;
5289 raw[header..header + 2].copy_from_slice(&crate::extent::EXT4_EXT_MAGIC.to_le_bytes());
5290 raw[header + 2..header + 4].copy_from_slice(&1u16.to_le_bytes());
5291 raw[header + 4..header + 6].copy_from_slice(&4u16.to_le_bytes());
5292 raw[header + 6..header + 8].copy_from_slice(&0u16.to_le_bytes());
5293 let entry = header + 12;
5294 raw[entry..entry + 4].copy_from_slice(&0u32.to_le_bytes());
5295 raw[entry + 4..entry + 6].copy_from_slice(&1u16.to_le_bytes());
5296 let (phys_hi, phys_lo) = crate::extent_mut::split_phys_block(phys);
5297 raw[entry + 6..entry + 8].copy_from_slice(&phys_hi.to_le_bytes());
5298 raw[entry + 8..entry + 12].copy_from_slice(&phys_lo.to_le_bytes());
5299 Ok(())
5300 }
5301
5302 fn build_slow_symlink_inode(&self, ino: u32, target: &[u8], data_phys: u64) -> Result<Vec<u8>> {
5310 use crate::inode::{OFF_BLOCKS_LO, OFF_LINKS_COUNT, OFF_MODE, OFF_SIZE_LO};
5311 debug_assert!(target.len() >= 60 && target.len() <= 4096);
5312 let mut raw = vec![0u8; self.sb.inode_size as usize];
5313
5314 let mode_bits = crate::inode::S_IFLNK | 0o0777;
5315 raw[OFF_MODE..OFF_MODE + 2].copy_from_slice(&mode_bits.to_le_bytes());
5316 raw[OFF_SIZE_LO..OFF_SIZE_LO + 4].copy_from_slice(&(target.len() as u32).to_le_bytes());
5317 raw[OFF_LINKS_COUNT..OFF_LINKS_COUNT + 2].copy_from_slice(&1u16.to_le_bytes());
5318 let bs = self.sb.block_size() as u64;
5319 let sectors = bs / 512;
5320 raw[OFF_BLOCKS_LO..OFF_BLOCKS_LO + 4].copy_from_slice(&(sectors as u32).to_le_bytes());
5321 self.map_one_block(&mut raw, data_phys)?;
5322
5323 write_inode_extra_isize(&mut raw);
5325 let now = self.runtime.now_unix_seconds();
5326 write_inode_timestamps(&mut raw, now);
5327 let generation = self.runtime.next_inode_generation();
5328 write_inode_generation(&mut raw, generation);
5329 self.stamp_inode_checksum(&mut raw, ino, generation);
5330 Ok(raw)
5331 }
5332
5333 fn build_regular_file_inode(&self, ino: u32, mode: u16) -> Result<Vec<u8>> {
5338 use crate::inode::{OFF_BLOCK, OFF_FLAGS, OFF_LINKS_COUNT, OFF_MODE};
5339 let mut raw = vec![0u8; self.sb.inode_size as usize];
5340
5341 let mode_bits = crate::inode::S_IFREG | (mode & 0x0FFF);
5342 raw[OFF_MODE..OFF_MODE + 2].copy_from_slice(&mode_bits.to_le_bytes());
5343 raw[OFF_LINKS_COUNT..OFF_LINKS_COUNT + 2].copy_from_slice(&1u16.to_le_bytes());
5344
5345 if self.flavor.uses_extents() {
5351 raw[OFF_FLAGS..OFF_FLAGS + 4]
5352 .copy_from_slice(&crate::inode::InodeFlags::EXTENTS.bits().to_le_bytes());
5353
5354 let extent_header_off = OFF_BLOCK;
5355 raw[extent_header_off..extent_header_off + 2]
5356 .copy_from_slice(&crate::extent::EXT4_EXT_MAGIC.to_le_bytes());
5357 raw[extent_header_off + 2..extent_header_off + 4].copy_from_slice(&0u16.to_le_bytes());
5358 raw[extent_header_off + 4..extent_header_off + 6].copy_from_slice(&4u16.to_le_bytes());
5359 raw[extent_header_off + 6..extent_header_off + 8].copy_from_slice(&0u16.to_le_bytes());
5360 }
5361
5362 write_inode_extra_isize(&mut raw);
5364 let now = self.runtime.now_unix_seconds();
5365 write_inode_timestamps(&mut raw, now);
5366 let generation = self.runtime.next_inode_generation();
5367 write_inode_generation(&mut raw, generation);
5368 self.stamp_inode_checksum(&mut raw, ino, generation);
5369 Ok(raw)
5370 }
5371
5372 pub fn apply_replace_file_content(&self, path: &str, data: &[u8]) -> Result<u64> {
5393 self.apply_replace_file_content_bytes(path.as_bytes(), data)
5394 }
5395
5396 pub(crate) fn apply_replace_file_content_bytes(&self, path: &[u8], data: &[u8]) -> Result<u64> {
5398 self.refuse_write()?;
5399 let ino = self.resolve(path)?;
5400 let (inode, mut raw) = self.read_inode_verified(ino)?;
5401 if !inode.is_file() {
5402 return Err(Error::InvalidArgument(
5403 "write_file target is not a regular file",
5404 ));
5405 }
5406 let bs = self.sb.block_size();
5407 let mut buf = BlockBuffer::new(bs);
5411 let (inode, mut raw) = if inode.has_inline_data() {
5415 if self.buffer_store_inline_file(&mut buf, ino, &inode, &mut raw, data)? {
5416 self.commit_block_buffer(buf)?;
5417 return Ok(data.len() as u64);
5418 }
5419 self.buffer_convert_inline_file(&mut buf, ino, &inode, &raw, false)?
5420 } else {
5421 (inode, raw)
5422 };
5423 if !inode.has_extents() {
5424 return self.apply_replace_file_content_indirect(ino, inode, raw, data);
5431 }
5432
5433 let sectors_per_block = bs as u64 / 512;
5434 let group_idx_of_inode = ((ino - 1) / self.sb.inodes_per_group) as usize;
5435
5436 let runs = self.extent_tree_runs(ino, &inode)?;
5439 let freed_fs_blocks = self.buffer_free_runs(&mut buf, &runs)?;
5440
5441 let mut root = vec![0u8; 60];
5443 root[0..2].copy_from_slice(&crate::extent::EXT4_EXT_MAGIC.to_le_bytes());
5444 root[4..6].copy_from_slice(&4u16.to_le_bytes()); Self::patch_inode_block_area(&mut raw, &root)?;
5446
5447 if data.is_empty() {
5450 self.finalize_inode_raw_after_write(
5451 ino,
5452 &mut raw,
5453 &inode,
5454 0,
5455 Self::xattr_block_sectors(&inode, bs),
5456 )?;
5457 if freed_fs_blocks > 0 {
5458 self.buffer_patch_sb_counters(&mut buf, freed_fs_blocks as i64, 0)?;
5459 }
5460 self.buffer_write_inode(&mut buf, ino, &raw)?;
5461 self.commit_block_buffer(buf)?;
5462 return Ok(0);
5463 }
5464
5465 let needed_blocks: u32 = data.len().div_ceil(bs as usize) as u32;
5467 let mut bitmap_reader = |block: u64| self.read_block(block);
5468 let plan = crate::alloc::plan_block_allocation(
5469 &self.sb,
5470 &self.allocation_groups(),
5471 needed_blocks,
5472 group_idx_of_inode as u32,
5473 &mut bitmap_reader,
5474 )?;
5475
5476 self.buffer_mark_block_run_used(&mut buf, plan.first_block, needed_blocks as u64)?;
5479 self.buffer_patch_bgd_counters(
5480 &mut buf,
5481 plan.bgd.group_idx as usize,
5482 plan.bgd.free_blocks_delta,
5483 plan.bgd.free_inodes_delta,
5484 plan.bgd.used_dirs_delta,
5485 )?;
5486 let net_block_delta = freed_fs_blocks as i64 - needed_blocks as i64;
5487 self.buffer_patch_sb_counters(&mut buf, net_block_delta, 0)?;
5488
5489 for i in 0..needed_blocks as u64 {
5491 let off_in_data = (i as usize) * bs as usize;
5492 let chunk_end = ((i as usize + 1) * bs as usize).min(data.len());
5493 let mut block = vec![0u8; bs as usize];
5494 block[..chunk_end - off_in_data].copy_from_slice(&data[off_in_data..chunk_end]);
5495 buf.put(plan.first_block + i, block);
5496 }
5497
5498 let new_extent = crate::extent::Extent {
5501 logical_block: 0,
5502 length: needed_blocks as u16,
5503 physical_block: plan.first_block,
5504 uninitialized: false,
5505 };
5506 let muts = crate::extent_mut::plan_insert_extent(&root, new_extent)?;
5507 for m in &muts {
5508 if let crate::extent_mut::ExtentMutation::WriteRoot { bytes } = m {
5509 Self::patch_inode_block_area(&mut raw, bytes)?;
5510 }
5511 }
5512 let new_size = data.len() as u64;
5513 let new_sectors =
5514 needed_blocks as u64 * sectors_per_block + Self::xattr_block_sectors(&inode, bs);
5515 self.finalize_inode_raw_after_write(ino, &mut raw, &inode, new_size, new_sectors)?;
5516 self.buffer_write_inode(&mut buf, ino, &raw)?;
5517
5518 self.commit_block_buffer(buf)?;
5519 Ok(new_size)
5520 }
5521
5522 fn holds_block_map(inode: &Inode, block_size: u32) -> bool {
5540 let xattr_sectors = if inode.file_acl != 0 {
5541 u64::from(block_size) / 512
5542 } else {
5543 0
5544 };
5545 inode.flags & crate::inode::InodeFlags::INLINE_DATA.bits() == 0
5546 && inode.blocks > xattr_sectors
5547 }
5548
5549 fn buffer_free_block_map(&self, buf: &mut BlockBuffer, inode: &Inode) -> Result<u64> {
5553 let bs = self.sb.block_size();
5554 if !Self::holds_block_map(inode, bs) {
5555 return Ok(0);
5556 }
5557 let freed = crate::indirect_mut::collect_for_free(
5558 &inode.block,
5559 bs,
5560 inode.size.div_ceil(u64::from(bs)) as u32,
5561 self.dev.as_ref(),
5562 )?;
5563 let mut count = 0;
5564 for run in &freed.data_runs {
5565 count += self.buffer_free_block_run_and_bgd(buf, run.start, run.len as u64)?;
5566 }
5567 for &iblk in &freed.indirect_blocks {
5568 count += self.buffer_free_block_run_and_bgd(buf, iblk, 1)?;
5569 }
5570 Ok(count)
5571 }
5572
5573 fn xattr_block_sectors(inode: &Inode, block_size: u32) -> u64 {
5579 if inode.file_acl != 0 {
5580 u64::from(block_size) / 512
5581 } else {
5582 0
5583 }
5584 }
5585
5586 fn apply_replace_file_content_indirect(
5587 &self,
5588 ino: u32,
5589 inode: Inode,
5590 mut raw: Vec<u8>,
5591 data: &[u8],
5592 ) -> Result<u64> {
5593 let bs = self.sb.block_size();
5594 let sectors_per_block = bs as u64 / 512;
5595 let group_idx_of_inode = ((ino - 1) / self.sb.inodes_per_group) as usize;
5596
5597 let mut buf = BlockBuffer::new(bs);
5604 let mut freed_fs_blocks: u64 = 0;
5605 if inode.size > 0 {
5606 let block_count = inode.size.div_ceil(bs as u64) as u32;
5607 let freed = crate::indirect_mut::collect_for_free(
5608 &inode.block,
5609 bs,
5610 block_count,
5611 self.dev.as_ref(),
5612 )?;
5613 for run in &freed.data_runs {
5614 freed_fs_blocks +=
5615 self.buffer_free_block_run_and_bgd(&mut buf, run.start, run.len as u64)?;
5616 }
5617 for &iblk in &freed.indirect_blocks {
5618 freed_fs_blocks += self.buffer_free_block_run_and_bgd(&mut buf, iblk, 1)?;
5619 }
5620 }
5621 let zero_iblock = [0u8; 60];
5623 Self::patch_inode_block_area(&mut raw, &zero_iblock)?;
5624
5625 if data.is_empty() {
5626 self.finalize_inode_raw_after_write(
5627 ino,
5628 &mut raw,
5629 &inode,
5630 0,
5631 Self::xattr_block_sectors(&inode, bs),
5632 )?;
5633 if freed_fs_blocks > 0 {
5634 self.buffer_patch_sb_counters(&mut buf, freed_fs_blocks as i64, 0)?;
5635 }
5636 self.buffer_write_inode(&mut buf, ino, &raw)?;
5637 self.commit_block_buffer(buf)?;
5638 return Ok(0);
5639 }
5640
5641 let needed_data_blocks: u32 = data.len().div_ceil(bs as usize) as u32;
5647 let n_indirect: u32 = crate::indirect_mut::count_indirect_blocks(needed_data_blocks, bs)
5648 .try_into()
5649 .map_err(|_| Error::Corrupt("indirect_mut: indirect block count overflow"))?;
5650 let total_run = needed_data_blocks
5651 .checked_add(n_indirect)
5652 .ok_or(Error::Corrupt("indirect_mut: total run count overflow"))?;
5653
5654 let mut bitmap_reader = |block: u64| self.read_block(block);
5655 let plan = crate::alloc::plan_block_allocation(
5656 &self.sb,
5657 &self.allocation_groups(),
5658 total_run,
5659 group_idx_of_inode as u32,
5660 &mut bitmap_reader,
5661 )?;
5662 let first_indirect = plan.first_block;
5663 let first_data = plan.first_block + n_indirect as u64;
5664
5665 let mut next_indirect = first_indirect;
5669 let i_plan =
5670 crate::indirect_mut::plan_contiguous(needed_data_blocks, first_data, bs, || {
5671 let v = next_indirect;
5672 next_indirect += 1;
5673 Ok(v)
5674 })?;
5675
5676 self.buffer_mark_block_run_used(&mut buf, plan.first_block, total_run as u64)?;
5679 self.buffer_patch_bgd_counters(
5680 &mut buf,
5681 plan.bgd.group_idx as usize,
5682 plan.bgd.free_blocks_delta,
5683 plan.bgd.free_inodes_delta,
5684 plan.bgd.used_dirs_delta,
5685 )?;
5686 let net_block_delta = freed_fs_blocks as i64 - total_run as i64;
5687 self.buffer_patch_sb_counters(&mut buf, net_block_delta, 0)?;
5688
5689 for i in 0..needed_data_blocks as u64 {
5691 let off_in_data = (i as usize) * bs as usize;
5692 let chunk_end = ((i as usize + 1) * bs as usize).min(data.len());
5693 let mut block = vec![0u8; bs as usize];
5694 block[..chunk_end - off_in_data].copy_from_slice(&data[off_in_data..chunk_end]);
5695 buf.put(first_data + i, block);
5696 }
5697
5698 for (blk, bytes) in &i_plan.block_writes {
5700 buf.put(*blk, bytes.clone());
5701 }
5702
5703 Self::patch_inode_block_area(&mut raw, &i_plan.i_block)?;
5705
5706 let new_size = data.len() as u64;
5710 let new_sectors = (needed_data_blocks as u64 + n_indirect as u64) * sectors_per_block
5711 + Self::xattr_block_sectors(&inode, bs);
5712 self.finalize_inode_raw_after_write(ino, &mut raw, &inode, new_size, new_sectors)?;
5713 self.buffer_write_inode(&mut buf, ino, &raw)?;
5714 self.commit_block_buffer(buf)?;
5715 Ok(new_size)
5716 }
5717
5718 fn pwrite_chunk_blocks(&self) -> u64 {
5736 let bs = self.sb.block_size() as u64;
5737 let memory_bound = (PWRITE_CHUNK_MAX_BYTES / bs).max(1);
5738 let Some(journal) = &self.journal else {
5739 return memory_bound;
5740 };
5741 let capacity = match journal.lock() {
5742 Ok(jw) => jw.max_blocks_per_transaction() as u64,
5743 Err(_) => return memory_bound,
5745 };
5746 let tags_per_desc = ((bs - 12 - 4) / 16).max(1);
5749 let half_node = (((bs - 12) / 12) / 2).max(1);
5752 let groups = self.groups.len() as u64;
5753 let gdt_blocks = (groups * u64::from(self.sb.desc_size)).div_ceil(bs);
5754 let fits = |data: u64| {
5755 let mut tree = 0u64;
5758 let mut level = data;
5759 for _ in 0..5 {
5760 level = level.div_ceil(half_node) + 1;
5761 tree += level;
5762 }
5763 let allocations = data + tree;
5764 let metadata = 2 + allocations.min(groups) + allocations.min(gdt_blocks) + tree;
5767 let tagged = data + metadata;
5768 let descriptors = tagged.div_ceil(tags_per_desc);
5769 let commit_block = 1;
5770 tagged + descriptors + commit_block <= capacity
5771 };
5772 let (mut lo, mut hi) = (1u64, memory_bound);
5774 if fits(hi) {
5775 return hi;
5776 }
5777 while lo + 1 < hi {
5778 let mid = lo + (hi - lo) / 2;
5779 if fits(mid) {
5780 lo = mid;
5781 } else {
5782 hi = mid;
5783 }
5784 }
5785 lo
5786 }
5787
5788 pub fn apply_pwrite(&self, path: &str, offset: u64, data: &[u8]) -> Result<u64> {
5826 self.apply_pwrite_bytes(path.as_bytes(), offset, data)
5827 }
5828
5829 pub(crate) fn apply_pwrite_bytes(&self, path: &[u8], offset: u64, data: &[u8]) -> Result<u64> {
5831 self.refuse_write()?;
5832 let ino = self.resolve(path)?;
5833 self.apply_pwrite_ino(ino, offset, data)
5834 }
5835
5836 pub fn apply_pwrite_ino(
5838 &self,
5839 r: impl Into<InodeRef>,
5840 offset: u64,
5841 data: &[u8],
5842 ) -> Result<u64> {
5843 self.refuse_write()?;
5844 let r = r.into();
5845 let ino = r.ino;
5846 let (inode, mut raw) = self.live_inode(r)?;
5847 if !inode.is_file() {
5848 return Err(Error::InvalidArgument(
5849 "pwrite target is not a regular file",
5850 ));
5851 }
5852 if !inode.has_extents() && !inode.has_inline_data() {
5853 return Err(Error::InvalidArgument(
5854 "pwrite: legacy (non-extents) inodes not supported in v1",
5855 ));
5856 }
5857
5858 if data.is_empty() {
5859 return Ok(inode.size);
5862 }
5863
5864 let bs = self.sb.block_size() as u64;
5865 let bs_usize = bs as usize;
5866 let sectors_per_block = bs / 512;
5867 let len = data.len() as u64;
5868 let end = offset
5869 .checked_add(len)
5870 .ok_or(Error::InvalidArgument("pwrite: offset+len overflow"))?;
5871 let first_lb = offset / bs;
5872 let last_lb_excl = end.div_ceil(bs);
5873
5874 let max_data_blocks = self.pwrite_chunk_blocks();
5884 let max_chunk = max_data_blocks * bs;
5885 if len > max_chunk {
5886 let mut chunk_off = 0u64;
5887 while chunk_off < len {
5888 let take = max_chunk.min(len - chunk_off);
5889 let s = chunk_off as usize;
5890 let e = (chunk_off + take) as usize;
5891 self.apply_pwrite_ino(r, offset + chunk_off, &data[s..e])?;
5892 chunk_off += take;
5893 }
5894 let (after, _) = self.read_inode_verified(ino)?;
5895 return Ok(after.size);
5896 }
5897
5898 let mut buf = BlockBuffer::new(self.sb.block_size());
5899 let (inode, mut raw) = if inode.has_inline_data() {
5904 if end <= crate::inline_mut::ceiling(self.sb.inode_size) as u64 {
5905 let mut content = crate::file_io::read_inline(self, &inode, &raw)?;
5906 if content.len() < end as usize {
5907 content.resize(end as usize, 0);
5908 }
5909 content[offset as usize..end as usize].copy_from_slice(data);
5910 if self.buffer_store_inline_file(&mut buf, ino, &inode, &mut raw, &content)? {
5911 self.commit_block_buffer(buf)?;
5912 return Ok(content.len() as u64);
5913 }
5914 }
5915 self.buffer_convert_inline_file(&mut buf, ino, &inode, &raw, true)?
5916 } else {
5917 (inode, raw)
5918 };
5919
5920 let mut root_bytes: Vec<u8> = inode.block.to_vec();
5924 let mut tree_nodes: std::collections::BTreeMap<u64, Vec<u8>> =
5927 std::collections::BTreeMap::new();
5928
5929 let group_idx_of_inode = (ino - 1) / self.sb.inodes_per_group;
5930
5931 let mut newly_alloc: std::collections::BTreeSet<u64> = std::collections::BTreeSet::new();
5936 let mut alloc_total_blocks: u64 = 0;
5937
5938 let mut preallocated = false;
5947 for lb in first_lb..last_lb_excl.min(u64::from(u32::MAX)) {
5948 if let Some(e) = crate::extent::lookup(
5949 &root_bytes,
5950 &StagedTreeNodes {
5951 fs: self,
5952 nodes: &tree_nodes,
5953 },
5954 self.sb.block_size(),
5955 lb,
5956 )? {
5957 if e.uninitialized {
5958 preallocated = true;
5959 break;
5960 }
5961 }
5962 }
5963 if preallocated {
5964 let (new_root, converted) = crate::extent_mut::plan_initialize_range(
5965 &root_bytes,
5966 first_lb as u32,
5967 last_lb_excl.min(u64::from(u32::MAX)) as u32,
5968 )
5969 .map_err(|e| match e {
5970 Error::CorruptExtentTree(msg)
5971 if msg.contains("multi-level") || msg.contains("LEAF_FULL") =>
5972 {
5973 Error::Unsupported(
5974 "pwrite: writing into a preallocated range needs its extent split \
5975 in a tree deeper than the inode's inline root",
5976 )
5977 }
5978 e => e,
5979 })?;
5980 root_bytes = new_root;
5981 newly_alloc.extend(converted.into_iter().map(u64::from));
5982 }
5983
5984 let mut lb = first_lb;
5990 while lb < last_lb_excl {
5991 let mapped = crate::extent::map_logical(
5992 &root_bytes,
5993 &StagedTreeNodes {
5994 fs: self,
5995 nodes: &tree_nodes,
5996 },
5997 self.sb.block_size(),
5998 lb,
5999 )?;
6000 if mapped.is_some() {
6001 lb += 1;
6002 continue;
6003 }
6004 let mut run_end = lb + 1;
6006 while run_end < last_lb_excl {
6007 let p = crate::extent::map_logical(
6008 &root_bytes,
6009 &StagedTreeNodes {
6010 fs: self,
6011 nodes: &tree_nodes,
6012 },
6013 self.sb.block_size(),
6014 run_end,
6015 )?;
6016 if p.is_some() {
6017 break;
6018 }
6019 run_end += 1;
6020 }
6021 let run_len_u64 = run_end - lb;
6022 if run_len_u64 > u32::MAX as u64 {
6023 return Err(Error::InvalidArgument(
6024 "pwrite: unmapped run exceeds u32 block count",
6025 ));
6026 }
6027
6028 let mut remaining_in_run = run_len_u64 as u32;
6036 let mut sub_lb = lb;
6037 while remaining_in_run > 0 {
6038 let mut want = remaining_in_run;
6039 let plan = loop {
6040 let plan_result =
6041 self.plan_buffered_block_allocation(&buf, want, group_idx_of_inode);
6042 match plan_result {
6043 Ok(p) => break p,
6044 Err(Error::Corrupt(msg)) if msg.contains("contiguous free run") => {
6045 if want == 1 {
6046 return Err(Error::NoSpaceLeftOnDevice);
6049 }
6050 want /= 2;
6055 }
6056 Err(e) => return Err(e),
6057 }
6058 };
6059
6060 let got = want;
6061 let got_u64 = got as u64;
6062
6063 self.buffer_mark_block_run_used(&mut buf, plan.first_block, got_u64)?;
6064 self.buffer_patch_bgd_counters(
6065 &mut buf,
6066 plan.bgd.group_idx as usize,
6067 plan.bgd.free_blocks_delta,
6068 plan.bgd.free_inodes_delta,
6069 plan.bgd.used_dirs_delta,
6070 )?;
6071 alloc_total_blocks += got_u64;
6072
6073 let new_extent = crate::extent::Extent {
6074 logical_block: sub_lb as u32,
6075 length: got as u16,
6076 physical_block: plan.first_block,
6077 uninitialized: false,
6078 };
6079
6080 match crate::extent_mut::plan_insert_extent(&root_bytes, new_extent) {
6084 Ok(muts) => {
6085 for m in &muts {
6086 if let crate::extent_mut::ExtentMutation::WriteRoot { bytes } = m {
6087 root_bytes = bytes.clone();
6088 }
6089 }
6090 }
6091 Err(Error::CorruptExtentTree(msg))
6092 if msg.contains("LEAF_FULL_NEEDS_PROMOTION")
6093 || msg.contains("multi-level tree mutation") =>
6094 {
6095 let reader = StagedTreeNodes {
6108 fs: self,
6109 nodes: &tree_nodes,
6110 };
6111 let mut meta_blocks_alloc: u64 = 0;
6112 let inode_generation = inode.generation;
6113 let deep_plan = {
6114 let mut alloc_closure = || -> Result<u64> {
6115 let p = self.plan_buffered_block_allocation(
6116 &buf,
6117 1,
6118 group_idx_of_inode,
6119 )?;
6120 self.buffer_mark_block_run_used(&mut buf, p.first_block, 1)?;
6121 self.buffer_patch_bgd_counters(
6122 &mut buf,
6123 p.bgd.group_idx as usize,
6124 p.bgd.free_blocks_delta,
6125 0,
6126 0,
6127 )?;
6128 meta_blocks_alloc += 1;
6129 Ok(p.first_block)
6130 };
6131 crate::extent_mut::plan_insert_extent_deep(
6132 &root_bytes,
6133 new_extent,
6134 self.sb.block_size(),
6135 &reader,
6136 &mut alloc_closure,
6137 )?
6138 };
6139 root_bytes = deep_plan.new_root;
6140 for (block, bytes) in deep_plan.block_writes {
6141 let mut bytes = bytes;
6142 if self.csum.enabled {
6143 self.csum
6144 .patch_extent_tail(ino, inode_generation, &mut bytes);
6145 }
6146 tree_nodes.insert(block, bytes.clone());
6150 buf.put(block, bytes);
6151 }
6152 alloc_total_blocks += meta_blocks_alloc;
6153 }
6154 Err(e) => return Err(e),
6155 }
6156
6157 for x in sub_lb..(sub_lb + got_u64) {
6161 newly_alloc.insert(x);
6162 }
6163
6164 sub_lb += got_u64;
6165 remaining_in_run -= got;
6166 }
6167
6168 lb = run_end;
6169 }
6170
6171 let mut data_off: usize = 0;
6173 for cur_lb in first_lb..last_lb_excl {
6174 let block_byte_start = cur_lb * bs;
6175 let block_byte_end = block_byte_start + bs;
6176 let chunk_start = offset.max(block_byte_start);
6177 let chunk_end = end.min(block_byte_end);
6178 let in_block_off = (chunk_start - block_byte_start) as usize;
6179 let chunk_len = (chunk_end - chunk_start) as usize;
6180
6181 let phys = crate::extent::map_logical(
6182 &root_bytes,
6183 &StagedTreeNodes { fs: self, nodes: &tree_nodes },
6184 self.sb.block_size(),
6185 cur_lb,
6186 )?
6187 .ok_or(Error::Corrupt(
6188 "pwrite Phase 2: logical block unmapped after Phase 1 (allocator/extent insert mismatch)",
6189 ))?;
6190
6191 if newly_alloc.contains(&cur_lb) {
6192 let mut block = vec![0u8; bs_usize];
6195 block[in_block_off..in_block_off + chunk_len]
6196 .copy_from_slice(&data[data_off..data_off + chunk_len]);
6197 buf.put(phys, block);
6198 } else {
6199 let block = buf.get_mut(self, phys)?;
6202 if block.len() != bs_usize {
6203 return Err(Error::Corrupt(
6204 "pwrite Phase 2: existing block has wrong size",
6205 ));
6206 }
6207 block[in_block_off..in_block_off + chunk_len]
6208 .copy_from_slice(&data[data_off..data_off + chunk_len]);
6209 }
6210
6211 data_off += chunk_len;
6212 }
6213 debug_assert_eq!(data_off, data.len());
6214
6215 Self::patch_inode_block_area(&mut raw, &root_bytes)?;
6218 let new_size = inode.size.max(end);
6219 let new_sectors = inode
6220 .blocks
6221 .checked_add(alloc_total_blocks * sectors_per_block)
6222 .ok_or(Error::Corrupt("pwrite: i_blocks overflow"))?;
6223 self.finalize_inode_raw_after_write(ino, &mut raw, &inode, new_size, new_sectors)?;
6224 self.buffer_write_inode(&mut buf, ino, &raw)?;
6225
6226 if alloc_total_blocks > 0 {
6228 self.buffer_patch_sb_counters(&mut buf, -(alloc_total_blocks as i64), 0)?;
6229 }
6230
6231 self.commit_block_buffer(buf)?;
6233 Ok(new_size)
6234 }
6235
6236 fn finalize_inode_raw_after_write(
6241 &self,
6242 ino: u32,
6243 raw: &mut [u8],
6244 orig: &Inode,
6245 new_size: u64,
6246 new_sectors: u64,
6247 ) -> Result<()> {
6248 Self::patch_inode_size_and_blocks(raw, new_size, new_sectors)?;
6249 let now = self.runtime.now_unix_seconds();
6250 set_inode_time(raw, InodeTime::Ctime, now);
6251 set_inode_time(raw, InodeTime::Mtime, now);
6252 if self.csum.enabled {
6253 self.csum.patch_inode_checksum(ino, orig.generation, raw);
6254 }
6255 Ok(())
6256 }
6257
6258 pub(crate) fn dir_block_role(dir: &Inode, logical: u64, block: &[u8]) -> DirBlockRole {
6270 let indexed = dir.flags & crate::inode::InodeFlags::INDEX.bits() != 0;
6271 if !indexed || block.len() < 8 {
6272 return DirBlockRole::Entries;
6273 }
6274 if logical == 0 {
6275 return DirBlockRole::IndexRoot;
6276 }
6277 let leading_inode = u32::from_le_bytes([block[0], block[1], block[2], block[3]]);
6278 let leading_span = usize::from(u16::from_le_bytes([block[4], block[5]]));
6279 if leading_inode == 0 && leading_span == block.len() {
6280 DirBlockRole::IndexNode
6281 } else {
6282 DirBlockRole::Entries
6283 }
6284 }
6285
6286 pub(crate) fn is_htree_index_block(dir: &Inode, logical: u64, block: &[u8]) -> bool {
6288 Self::dir_block_role(dir, logical, block) != DirBlockRole::Entries
6289 }
6290
6291 fn refuse_unverified_dir_block(
6312 &self,
6313 ino: u32,
6314 dir: &Inode,
6315 logical: u64,
6316 block: &[u8],
6317 ) -> Result<()> {
6318 match Self::dir_block_role(dir, logical, block) {
6319 DirBlockRole::IndexRoot => return self.check_dx_block(ino, dir, block, true),
6320 DirBlockRole::IndexNode => return Ok(()),
6321 DirBlockRole::Entries => {}
6322 }
6323 if self.csum.enabled
6324 && crate::dir::has_csum_tail(block)
6325 && !self.csum.verify_dir_entry_tail(ino, dir.generation, block)
6326 {
6327 return Err(Error::BadChecksum {
6328 what: "directory block",
6329 });
6330 }
6331 Ok(())
6332 }
6333
6334 fn entry_exists(&self, dir_ino: u32, dir_inode: &Inode, name: &[u8]) -> Result<bool> {
6353 match self.find_entry_in_dir(dir_ino, dir_inode, name) {
6354 Ok(_) => Ok(true),
6355 Err(Error::NotFound) => Ok(false),
6356 Err(e) => Err(e),
6357 }
6358 }
6359
6360 fn find_entry_in_dir(&self, dir_ino: u32, dir_inode: &Inode, name: &[u8]) -> Result<u32> {
6368 if dir_inode.has_inline_data() {
6369 return crate::path::find_entry(
6370 self.dev.as_ref(),
6371 &self.sb,
6372 dir_ino,
6373 dir_inode,
6374 name,
6375 &self.csum,
6376 );
6377 }
6378 let has_ft = self.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
6379 let bs = self.sb.block_size();
6380 let n_blocks = dir_inode.size.div_ceil(bs as u64);
6381 for logical in 0..n_blocks {
6382 let Some(phys) = self.map_inode_logical(dir_inode, logical)? else {
6383 continue;
6384 };
6385 let block = self.read_block(phys)?;
6386 self.refuse_unverified_dir_block(dir_ino, dir_inode, logical, &block)?;
6387 for entry in crate::dir::DirBlockIter::new(&block, has_ft) {
6388 let e = entry?;
6389 if e.name == name {
6390 return Ok(e.inode);
6391 }
6392 }
6393 }
6394 Err(Error::NotFound)
6395 }
6396
6397 pub(crate) fn patch_bgd_counters(
6403 &self,
6404 gi: usize,
6405 free_blocks_delta: i32,
6406 free_inodes_delta: i32,
6407 used_dirs_delta: i32,
6408 ) -> Result<()> {
6409 let bs = self.sb.block_size() as u64;
6410 let (bgt_block, off_in_block) = self.sb.descriptor_location(gi as u64);
6412
6413 let mut block = self.read_block(bgt_block)?;
6414 patch_bgd_counter_fields(
6415 &mut block,
6416 off_in_block,
6417 self.sb.desc_size,
6418 free_blocks_delta,
6419 free_inodes_delta,
6420 used_dirs_delta,
6421 );
6422
6423 self.restamp_group_desc_csum(&mut block[..], off_in_block, gi);
6424 self.dev.write_at(bgt_block * bs, &block)?;
6425 Ok(())
6426 }
6427
6428 pub(crate) fn patch_sb_counters(
6431 &self,
6432 free_blocks_delta: i64,
6433 free_inodes_delta: i32,
6434 ) -> Result<()> {
6435 let mut buf = BlockBuffer::new(self.sb.block_size());
6444 self.buffer_patch_sb_counters(&mut buf, free_blocks_delta, free_inodes_delta)?;
6445 self.commit_block_buffer(buf)?;
6446 Ok(())
6447 }
6448
6449 fn build_directory_inode(&self, ino: u32, mode: u16, data_phys_block: u64) -> Result<Vec<u8>> {
6459 use crate::inode::{
6460 OFF_BLOCKS_HI, OFF_BLOCKS_LO, OFF_LINKS_COUNT, OFF_MODE, OFF_SIZE_HI, OFF_SIZE_LO,
6461 };
6462 let mut raw = vec![0u8; self.sb.inode_size as usize];
6463
6464 let mode_bits = crate::inode::S_IFDIR | (mode & 0x0FFF);
6465 raw[OFF_MODE..OFF_MODE + 2].copy_from_slice(&mode_bits.to_le_bytes());
6466 raw[OFF_LINKS_COUNT..OFF_LINKS_COUNT + 2].copy_from_slice(&2u16.to_le_bytes());
6468 self.map_one_block(&mut raw, data_phys_block)?;
6469
6470 let bs = self.sb.block_size() as u64;
6472 raw[OFF_SIZE_LO..OFF_SIZE_LO + 4]
6473 .copy_from_slice(&((bs & 0xFFFF_FFFF) as u32).to_le_bytes());
6474 raw[OFF_SIZE_HI..OFF_SIZE_HI + 4].copy_from_slice(&((bs >> 32) as u32).to_le_bytes());
6475
6476 let sectors = bs / 512;
6477 raw[OFF_BLOCKS_LO..OFF_BLOCKS_LO + 4].copy_from_slice(&(sectors as u32).to_le_bytes());
6478 raw[OFF_BLOCKS_HI..OFF_BLOCKS_HI + 2]
6479 .copy_from_slice(&(((sectors >> 32) & 0xFFFF) as u16).to_le_bytes());
6480
6481 write_inode_extra_isize(&mut raw);
6483 let now = self.runtime.now_unix_seconds();
6484 write_inode_timestamps(&mut raw, now);
6485 let generation = self.runtime.next_inode_generation();
6486 write_inode_generation(&mut raw, generation);
6487 self.stamp_inode_checksum(&mut raw, ino, generation);
6488 Ok(raw)
6489 }
6490
6491 fn seed_directory_block(
6496 &self,
6497 new_ino: u32,
6498 parent_ino: u32,
6499 new_generation: u32,
6500 ) -> Result<Vec<u8>> {
6501 let bs = self.sb.block_size() as usize;
6502 let mut block = vec![0u8; bs];
6503 let has_ft = self.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
6504 let reserved_tail = if self.csum.enabled { 12 } else { 0 };
6505 let usable = bs - reserved_tail;
6506
6507 block[0..4].copy_from_slice(&new_ino.to_le_bytes());
6509 block[4..6].copy_from_slice(&12u16.to_le_bytes());
6510 block[6] = 1; block[7] = if has_ft {
6512 crate::dir::DirEntryType::Directory as u8
6513 } else {
6514 0
6515 };
6516 block[8] = b'.';
6517
6518 let off = 12;
6520 block[off..off + 4].copy_from_slice(&parent_ino.to_le_bytes());
6521 let rec_len = (usable - off) as u16;
6522 block[off + 4..off + 6].copy_from_slice(&rec_len.to_le_bytes());
6523 block[off + 6] = 2;
6524 block[off + 7] = if has_ft {
6525 crate::dir::DirEntryType::Directory as u8
6526 } else {
6527 0
6528 };
6529 block[off + 8] = b'.';
6530 block[off + 9] = b'.';
6531
6532 if reserved_tail == 12 {
6535 self.csum
6536 .patch_dir_entry_tail(new_ino, new_generation, &mut block);
6537 }
6538
6539 Ok(block)
6540 }
6541
6542 fn has_dir_nlink(&self) -> bool {
6546 self.sb.feature_ro_compat & features::RoCompat::DIR_NLINK.bits() != 0
6547 }
6548
6549 fn check_link_room(&self, inode: &Inode) -> Result<()> {
6556 next_links_count(inode.is_dir(), inode.links_count, 1, self.has_dir_nlink()).map(|_| ())
6557 }
6558
6559 fn patch_inode_nlink(&self, ino: u32, raw: &mut [u8], inode: &Inode, delta: i32) -> Result<()> {
6565 let new_count = next_links_count(
6566 inode.is_dir(),
6567 inode.links_count,
6568 delta,
6569 self.has_dir_nlink(),
6570 )?;
6571 raw[0x1A..0x1C].copy_from_slice(&new_count.to_le_bytes());
6572 if self.csum.enabled {
6573 self.csum.patch_inode_checksum(ino, inode.generation, raw);
6574 }
6575 Ok(())
6576 }
6577
6578 pub fn apply_mkdir(&self, path: &str, mode: u16) -> Result<u32> {
6587 self.apply_mkdir_bytes(path.as_bytes(), mode)
6588 }
6589
6590 pub(crate) fn apply_mkdir_bytes(&self, path: &[u8], mode: u16) -> Result<u32> {
6592 self.refuse_write()?;
6593 let (parent, name) = self.resolve_new_parent(path)?;
6594 self.apply_mkdir_at(parent, name, mode)
6595 }
6596
6597 pub fn apply_mkdir_at(&self, dir: impl Into<InodeRef>, name: &[u8], mode: u16) -> Result<u32> {
6600 self.refuse_write()?;
6601 check_new_entry_name(name)?;
6602 let dir = dir.into();
6603 let parent_ino = dir.ino;
6604 let (parent_inode, _) = self.live_dir(dir)?;
6605 if self.entry_exists(parent_ino, &parent_inode, name)? {
6606 return Err(Error::AlreadyExists);
6607 }
6608 self.check_link_room(&parent_inode)?;
6610
6611 let bs = self.sb.block_size();
6612 let parent_group = (parent_ino - 1) / self.sb.inodes_per_group;
6613 let mut bitmap_reader = |block: u64| self.read_block(block);
6614
6615 let iplan = crate::alloc::plan_inode_allocation(
6617 &self.sb,
6618 &self.allocation_groups(),
6619 true,
6620 parent_group,
6621 &mut bitmap_reader,
6622 )?;
6623 let new_ino = iplan.inode;
6624
6625 let bplan = crate::alloc::plan_block_allocation(
6627 &self.sb,
6628 &self.allocation_groups(),
6629 1,
6630 iplan.bgd.group_idx,
6631 &mut bitmap_reader,
6632 )?;
6633 let data_block = bplan.first_block;
6634
6635 let mut buf = BlockBuffer::new(bs);
6639 self.buffer_mark_inode_used(&mut buf, new_ino)?;
6640 self.buffer_patch_bgd_counters(
6641 &mut buf,
6642 iplan.bgd.group_idx as usize,
6643 iplan.bgd.free_blocks_delta,
6644 iplan.bgd.free_inodes_delta,
6645 iplan.bgd.used_dirs_delta,
6646 )?;
6647 self.buffer_patch_sb_counters(
6648 &mut buf,
6649 iplan.sb.free_blocks_delta,
6650 iplan.sb.free_inodes_delta,
6651 )?;
6652
6653 self.buffer_mark_block_run_used(&mut buf, data_block, 1)?;
6654 self.buffer_patch_bgd_counters(
6655 &mut buf,
6656 bplan.bgd.group_idx as usize,
6657 bplan.bgd.free_blocks_delta,
6658 bplan.bgd.free_inodes_delta,
6659 bplan.bgd.used_dirs_delta,
6660 )?;
6661 self.buffer_patch_sb_counters(
6662 &mut buf,
6663 bplan.sb.free_blocks_delta,
6664 bplan.sb.free_inodes_delta,
6665 )?;
6666
6667 let raw = self.build_directory_inode(new_ino, mode, data_block)?;
6668 let gen = u32::from_le_bytes(raw[0x64..0x68].try_into().unwrap());
6669 self.buffer_write_inode(&mut buf, new_ino, &raw)?;
6670
6671 let seed = self.seed_directory_block(new_ino, parent_ino, gen)?;
6673 buf.put(data_block, seed);
6674
6675 let parent_extends = match self.buffer_add_dir_entry_inplace(
6677 &mut buf,
6678 parent_ino,
6679 &parent_inode,
6680 name,
6681 new_ino,
6682 crate::dir::DirEntryType::Directory,
6683 ) {
6684 Ok(()) => false,
6685 Err(Error::OutOfBounds) => true,
6686 Err(e) => return Err(e),
6687 };
6688
6689 if !parent_extends {
6690 let (parent_now, mut parent_raw) = self.buffered_inode_verified(&buf, parent_ino)?;
6694 self.patch_inode_nlink(parent_ino, &mut parent_raw, &parent_now, 1)?;
6695 self.buffer_write_inode(&mut buf, parent_ino, &parent_raw)?;
6696 self.commit_block_buffer(buf)?;
6697 } else {
6698 self.extend_dir_and_add_entry(
6702 &mut buf,
6703 parent_ino,
6704 name,
6705 new_ino,
6706 crate::dir::DirEntryType::Directory,
6707 )?;
6708 let (grown_parent, mut grown_raw) = self.buffered_inode_verified(&buf, parent_ino)?;
6709 self.patch_inode_nlink(parent_ino, &mut grown_raw, &grown_parent, 1)?;
6710 self.buffer_write_inode(&mut buf, parent_ino, &grown_raw)?;
6711 self.commit_block_buffer(buf)?;
6712 }
6713
6714 Ok(new_ino)
6715 }
6716
6717 pub fn apply_link(&self, src: &str, dst: &str) -> Result<()> {
6728 self.apply_link_bytes(src.as_bytes(), dst.as_bytes())
6729 }
6730
6731 pub(crate) fn apply_link_bytes(&self, src: &[u8], dst: &[u8]) -> Result<()> {
6733 self.refuse_write()?;
6734 let (dst_parent_path, dst_name) = split_parent_and_base(dst)?;
6735 if dst_name.len() > 255 {
6736 return Err(Error::NameTooLong);
6737 }
6738 let src_ino = self.resolve(src)?;
6739 if self.live_inode(src_ino.into())?.0.is_dir() {
6742 return Err(Error::IsADirectory);
6743 }
6744 let dst_parent = self.resolve(dst_parent_path)?;
6745 self.apply_link_at(src_ino, dst_parent, dst_name)
6746 }
6747
6748 pub fn apply_link_at(
6751 &self,
6752 target: impl Into<InodeRef>,
6753 dir: impl Into<InodeRef>,
6754 dst_name: &[u8],
6755 ) -> Result<()> {
6756 self.refuse_write()?;
6757 check_new_entry_name(dst_name)?;
6758 let target = target.into();
6759 let src_ino = target.ino;
6760 let (src_inode, mut src_raw) = self.live_inode(target)?;
6761 if src_inode.is_dir() {
6762 return Err(Error::IsADirectory);
6765 }
6766 self.check_link_room(&src_inode)?;
6767
6768 let dir = dir.into();
6769 let dst_parent_ino = dir.ino;
6770 let (dst_parent_inode, _) = self.live_dir(dir)?;
6771 if self.entry_exists(dst_parent_ino, &dst_parent_inode, dst_name)? {
6772 return Err(Error::AlreadyExists);
6773 }
6774
6775 let dir_type = crate::dir::DirEntryType::from_mode(src_inode.mode);
6776
6777 let mut buf = BlockBuffer::new(self.sb.block_size());
6781 self.patch_inode_nlink(src_ino, &mut src_raw, &src_inode, 1)?;
6782 self.buffer_write_inode(&mut buf, src_ino, &src_raw)?;
6783
6784 match self.buffer_add_dir_entry_inplace(
6785 &mut buf,
6786 dst_parent_ino,
6787 &dst_parent_inode,
6788 dst_name,
6789 src_ino,
6790 dir_type,
6791 ) {
6792 Ok(()) => self.commit_block_buffer(buf),
6793 Err(Error::OutOfBounds) => {
6794 self.extend_dir_and_add_entry(
6797 &mut buf,
6798 dst_parent_ino,
6799 dst_name,
6800 src_ino,
6801 dir_type,
6802 )?;
6803 self.commit_block_buffer(buf)
6804 }
6805 Err(e) => Err(e),
6806 }
6807 }
6808
6809 pub fn apply_rename(&self, src: &str, dst: &str, replace_if_exists: bool) -> Result<()> {
6840 self.apply_rename_bytes(src.as_bytes(), dst.as_bytes(), replace_if_exists)
6841 }
6842
6843 pub(crate) fn apply_rename_bytes(
6845 &self,
6846 src: &[u8],
6847 dst: &[u8],
6848 replace_if_exists: bool,
6849 ) -> Result<()> {
6850 self.write_refusal()?;
6853 let (src_parent_path, src_name) = split_parent_and_base(src)?;
6854 let (dst_parent_path, dst_name) = split_parent_and_base(dst)?;
6855 if dst_name.len() > 255 {
6856 return Err(Error::NameTooLong);
6857 }
6858 let src_parent = self.resolve(src_parent_path)?;
6859 let dst_parent = self.resolve(dst_parent_path)?;
6860 self.apply_rename_at(
6861 src_parent,
6862 src_name,
6863 dst_parent,
6864 dst_name,
6865 replace_if_exists,
6866 )
6867 }
6868
6869 pub fn apply_rename_at(
6874 &self,
6875 src_dir: impl Into<InodeRef>,
6876 src_name: &[u8],
6877 dst_dir: impl Into<InodeRef>,
6878 dst_name: &[u8],
6879 replace_if_exists: bool,
6880 ) -> Result<()> {
6881 self.write_refusal()?;
6884 check_entry_name(src_name)?;
6885 check_new_entry_name(dst_name)?;
6886 if is_dot_or_dotdot(src_name) || is_dot_or_dotdot(dst_name) {
6889 return Err(Error::InvalidArgument("rename: cannot rename . or .."));
6890 }
6891
6892 let (src_dir, dst_dir) = (src_dir.into(), dst_dir.into());
6893 let (src_parent_ino, dst_parent_ino) = (src_dir.ino, dst_dir.ino);
6894 let (src_parent_inode, _) = self.live_dir(src_dir)?;
6895 let (dst_parent_inode, _) = self.live_dir(dst_dir)?;
6896
6897 let src_ino = self.find_entry_in_dir(src_parent_ino, &src_parent_inode, src_name)?;
6898 if src_parent_ino == dst_parent_ino && src_name == dst_name {
6903 return Ok(());
6904 }
6905 self.mark_not_clean_once()?;
6906 let existing_dst_ino =
6910 match self.find_entry_in_dir(dst_parent_ino, &dst_parent_inode, dst_name) {
6911 Ok(ino) => Some(ino),
6912 Err(Error::NotFound) => None,
6913 Err(e) => return Err(e),
6914 };
6915 if existing_dst_ino.is_some() && !replace_if_exists {
6916 return Err(Error::AlreadyExists);
6917 }
6918
6919 let (src_inode, _) = self.live_inode(src_ino.into())?;
6920 let src_is_dir = src_inode.is_dir();
6921
6922 if src_is_dir && self.is_ancestor_or_self(src_ino, dst_parent_ino)? {
6926 return Err(Error::InvalidArgument(
6927 "rename: cannot move directory into its own subtree",
6928 ));
6929 }
6930
6931 if src_is_dir && existing_dst_ino.is_none() && src_parent_ino != dst_parent_ino {
6934 self.check_link_room(&dst_parent_inode)?;
6935 }
6936
6937 let dir_type = crate::dir::DirEntryType::from_mode(src_inode.mode);
6941
6942 if let Some(dst_old_ino) = existing_dst_ino {
6946 if dst_old_ino == src_ino {
6951 return Ok(());
6952 }
6953
6954 let (dst_old_inode, mut dst_old_raw) = self.read_inode_verified(dst_old_ino)?;
6955 let dst_is_dir = dst_old_inode.is_dir();
6956
6957 if !src_is_dir && dst_is_dir {
6960 return Err(Error::IsADirectory);
6961 }
6962 if src_is_dir && !dst_is_dir {
6963 return Err(Error::NotADirectory);
6964 }
6965
6966 if dst_is_dir && dst_old_inode.has_inline_data() {
6969 self.refuse_nonempty_inline_dir(dst_old_ino, &dst_old_inode, &dst_old_raw)?;
6971 } else if dst_is_dir {
6972 let bs = self.sb.block_size();
6973 let has_ft = self.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
6974 let blocks = dst_old_inode.size.div_ceil(bs as u64);
6975 for logical in 0..blocks {
6976 let Some(phys) = self.map_inode_logical(&dst_old_inode, logical)? else {
6978 continue;
6979 };
6980 let block = self.read_block(phys)?;
6981 self.refuse_unverified_dir_block(dst_old_ino, &dst_old_inode, logical, &block)?;
6984 for entry in crate::dir::DirBlockIter::new(&block, has_ft) {
6985 let e = entry?;
6986 if e.name != b"." && e.name != b".." {
6987 return Err(Error::DirectoryNotEmpty);
6988 }
6989 }
6990 }
6991 }
6992
6993 let destination_runs =
6996 if (dst_is_dir || dst_old_inode.links_count <= 1) && dst_old_inode.has_extents() {
6997 self.extent_tree_runs(dst_old_ino, &dst_old_inode)?
6998 } else {
6999 Vec::new()
7000 };
7001
7002 let mut buf = BlockBuffer::new(self.sb.block_size());
7008
7009 let mut parent_nlink: BTreeMap<u32, i32> = BTreeMap::new();
7028
7029 self.buffer_remove_dir_entry(&mut buf, dst_parent_ino, &dst_parent_inode, dst_name)?;
7032
7033 let dst_extends = match self.buffer_add_dir_entry_inplace(
7037 &mut buf,
7038 dst_parent_ino,
7039 &dst_parent_inode,
7040 dst_name,
7041 src_ino,
7042 dir_type,
7043 ) {
7044 Ok(()) => false,
7045 Err(Error::OutOfBounds) => true,
7046 Err(e) => return Err(e),
7047 };
7048 if dst_extends {
7049 self.extend_dir_and_add_entry(
7050 &mut buf,
7051 dst_parent_ino,
7052 dst_name,
7053 src_ino,
7054 dir_type,
7055 )?;
7056 }
7057
7058 let (src_parent_now, _) = self.buffered_inode_verified(&buf, src_parent_ino)?;
7061 self.buffer_remove_dir_entry(&mut buf, src_parent_ino, &src_parent_now, src_name)?;
7062
7063 if src_is_dir && src_parent_ino != dst_parent_ino {
7068 self.buffer_update_dotdot(&mut buf, src_ino, &src_inode, dst_parent_ino)?;
7069 *parent_nlink.entry(src_parent_ino).or_default() -= 1;
7070 *parent_nlink.entry(dst_parent_ino).or_default() += 1;
7071 }
7072
7073 let new_links = dst_old_inode.links_count.saturating_sub(1);
7078 if new_links > 0 && !dst_is_dir {
7079 dst_old_raw[0x1A..0x1C].copy_from_slice(&new_links.to_le_bytes());
7081 self.finalize_inode_raw(dst_old_ino, dst_old_inode.generation, &mut dst_old_raw)?;
7082 self.buffer_write_inode(&mut buf, dst_old_ino, &dst_old_raw)?;
7083 } else {
7084 let bs = self.sb.block_size();
7085 let sectors_per_block = bs as u64 / 512;
7086 let mut freed_sectors: u64 = 0;
7087 if dst_old_inode.has_extents() {
7088 freed_sectors +=
7089 self.buffer_free_runs(&mut buf, &destination_runs)? * sectors_per_block;
7090 } else {
7091 freed_sectors +=
7094 self.buffer_free_block_map(&mut buf, &dst_old_inode)? * sectors_per_block;
7095 }
7096 if dst_old_inode.file_acl != 0 {
7097 freed_sectors += self
7098 .buffer_release_xattr_block(&mut buf, dst_old_inode.file_acl)?
7099 * sectors_per_block;
7100 }
7101
7102 self.buffer_free_inode_slot(&mut buf, dst_old_ino)?;
7103 if dst_is_dir {
7104 let dst_old_gi = ((dst_old_ino - 1) / self.sb.inodes_per_group) as usize;
7106 self.buffer_patch_bgd_counters(&mut buf, dst_old_gi, 0, 0, -1)?;
7107 }
7108 let freed_blocks = freed_sectors.checked_div(sectors_per_block).unwrap_or(0);
7109 self.buffer_patch_sb_counters(&mut buf, freed_blocks as i64, 1)?;
7110
7111 let inode_size = self.sb.inode_size as usize;
7113 let old_gen = dst_old_inode.generation;
7114 for b in &mut dst_old_raw[..inode_size] {
7115 *b = 0;
7116 }
7117 let dtime = self.dtime_now();
7118 dst_old_raw[0x14..0x18].copy_from_slice(&dtime.to_le_bytes());
7119 dst_old_raw[0x64..0x68].copy_from_slice(&old_gen.to_le_bytes());
7120 self.finalize_inode_raw(dst_old_ino, old_gen, &mut dst_old_raw)?;
7121 self.buffer_write_inode(&mut buf, dst_old_ino, &dst_old_raw)?;
7122
7123 if dst_is_dir {
7128 *parent_nlink.entry(dst_parent_ino).or_default() -= 1;
7129 }
7130 }
7131
7132 self.apply_parent_nlink_deltas(&mut buf, &parent_nlink)?;
7133 return self.commit_block_buffer(buf);
7134 }
7135
7136 let mut buf = BlockBuffer::new(self.sb.block_size());
7144 let mut parent_nlink: BTreeMap<u32, i32> = BTreeMap::new();
7145
7146 let dst_extends = match self.buffer_add_dir_entry_inplace(
7147 &mut buf,
7148 dst_parent_ino,
7149 &dst_parent_inode,
7150 dst_name,
7151 src_ino,
7152 dir_type,
7153 ) {
7154 Ok(()) => false,
7155 Err(Error::OutOfBounds) => true,
7156 Err(e) => return Err(e),
7157 };
7158
7159 if dst_extends {
7160 self.extend_dir_and_add_entry(&mut buf, dst_parent_ino, dst_name, src_ino, dir_type)?;
7162 }
7163
7164 let (src_parent_now, _) = self.buffered_inode_verified(&buf, src_parent_ino)?;
7172 self.buffer_remove_dir_entry(&mut buf, src_parent_ino, &src_parent_now, src_name)?;
7173
7174 if src_is_dir && src_parent_ino != dst_parent_ino {
7175 self.buffer_update_dotdot(&mut buf, src_ino, &src_inode, dst_parent_ino)?;
7176 *parent_nlink.entry(src_parent_ino).or_default() -= 1;
7177 *parent_nlink.entry(dst_parent_ino).or_default() += 1;
7178 }
7179
7180 self.apply_parent_nlink_deltas(&mut buf, &parent_nlink)?;
7183 self.commit_block_buffer(buf)
7184 }
7185
7186 fn is_ancestor_or_self(&self, ancestor: u32, dir: u32) -> Result<bool> {
7189 let mut cur = dir;
7190 for _ in 0..=self.sb.inodes_count {
7192 if cur == ancestor {
7193 return Ok(true);
7194 }
7195 if cur == crate::path::EXT4_ROOT_INODE {
7196 return Ok(false);
7197 }
7198 let (inode, _) = self.read_inode_verified(cur)?;
7199 cur = crate::path::find_entry(
7200 self.dev.as_ref(),
7201 &self.sb,
7202 cur,
7203 &inode,
7204 b"..",
7205 &self.csum,
7206 )?;
7207 }
7208 Err(Error::Corrupt(
7209 "directory `..` chain does not reach the root",
7210 ))
7211 }
7212
7213 fn apply_parent_nlink_deltas(
7228 &self,
7229 buf: &mut BlockBuffer,
7230 deltas: &BTreeMap<u32, i32>,
7231 ) -> Result<()> {
7232 for (&ino, &delta) in deltas {
7233 if delta == 0 {
7234 continue;
7235 }
7236 let (inode, mut raw) = self.buffered_inode_verified(buf, ino)?;
7237 self.patch_inode_nlink(ino, &mut raw, &inode, delta)?;
7238 self.buffer_write_inode(buf, ino, &raw)?;
7239 }
7240 Ok(())
7241 }
7242
7243 fn buffer_dir_block_alloc(
7255 &self,
7256 buf: &mut BlockBuffer,
7257 plan: &crate::alloc::BlockAllocationPlan,
7258 ) -> Result<()> {
7259 if plan.count != 1 {
7260 return Err(Error::Corrupt(
7261 "buffer_dir_block_alloc: a directory block plan must allocate exactly one block",
7262 ));
7263 }
7264 self.buffer_mark_block_run_used(buf, plan.first_block, 1)?;
7265 self.buffer_patch_bgd_counters(
7266 buf,
7267 plan.bgd.group_idx as usize,
7268 plan.bgd.free_blocks_delta,
7269 plan.bgd.free_inodes_delta,
7270 plan.bgd.used_dirs_delta,
7271 )?;
7272 self.buffer_patch_sb_counters(buf, plan.sb.free_blocks_delta, plan.sb.free_inodes_delta)
7273 }
7274
7275 fn buffer_alloc_dir_block(&self, buf: &mut BlockBuffer, parent_ino: u32) -> Result<u64> {
7279 let parent_group = (parent_ino - 1) / self.sb.inodes_per_group;
7280 let plan = self.plan_buffered_block_allocation(buf, 1, parent_group)?;
7281 self.buffer_dir_block_alloc(buf, &plan)?;
7282 Ok(plan.first_block)
7283 }
7284
7285 fn buffered_block(&self, buf: &BlockBuffer, block: u64) -> Result<Vec<u8>> {
7287 match buf.dirty.get(&block) {
7288 Some(bytes) => Ok(bytes.clone()),
7289 None => self.read_block(block),
7290 }
7291 }
7292
7293 fn buffered_inode_verified(&self, buf: &BlockBuffer, ino: u32) -> Result<(Inode, Vec<u8>)> {
7300 let (block, offset) = bgd::locate_inode(&self.sb, &self.groups, ino)?;
7301 let Some(staged) = buf.dirty.get(&block) else {
7302 return self.read_inode_verified(ino);
7303 };
7304 let off = offset as usize;
7305 let raw = staged
7306 .get(off..off + self.sb.inode_size as usize)
7307 .ok_or(Error::Corrupt("inode slice exceeds block data"))?
7308 .to_vec();
7309 let inode = Inode::parse(&raw)?;
7310 if self.csum.enabled && !self.csum.verify_inode(ino, inode.generation, &raw) {
7311 return Err(Error::BadChecksum { what: "inode" });
7312 }
7313 Ok((inode, raw))
7314 }
7315
7316 fn buffer_write_dir_inode(
7318 &self,
7319 buf: &mut BlockBuffer,
7320 ino: u32,
7321 generation: u32,
7322 raw: &mut [u8],
7323 ) -> Result<()> {
7324 if self.csum.enabled {
7325 self.csum.patch_inode_checksum(ino, generation, raw);
7326 }
7327 self.buffer_write_inode(buf, ino, raw)
7328 }
7329
7330 fn extend_dir_and_add_entry(
7339 &self,
7340 buf: &mut BlockBuffer,
7341 parent_ino: u32,
7342 name: &[u8],
7343 target_ino: u32,
7344 file_type: crate::dir::DirEntryType,
7345 ) -> Result<()> {
7346 let has_ft = self.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
7347
7348 if self.buffer_split_htree_leaf_and_add_entry(
7354 buf, parent_ino, name, target_ino, file_type, has_ft,
7355 )? {
7356 return Ok(());
7357 }
7358 self.buffer_drop_htree_index(buf, parent_ino)?;
7359
7360 let (parent_inode, _) = self.buffered_inode_verified(buf, parent_ino)?;
7361 let block = self.seeded_dir_block(
7362 &parent_inode,
7363 parent_ino,
7364 name,
7365 target_ino,
7366 file_type,
7367 has_ft,
7368 )?;
7369 self.buffer_append_dir_block(buf, parent_ino, block)
7370 }
7371
7372 fn buffer_split_htree_leaf_and_add_entry(
7390 &self,
7391 buf: &mut BlockBuffer,
7392 dir_ino: u32,
7393 name: &[u8],
7394 target_ino: u32,
7395 file_type: crate::dir::DirEntryType,
7396 has_ft: bool,
7397 ) -> Result<bool> {
7398 let (dir, _) = self.buffered_inode_verified(buf, dir_ino)?;
7399 if dir.flags & crate::inode::InodeFlags::INDEX.bits() == 0
7400 || dir.flags & (EXT4_CASEFOLD_FL | EXT4_ENCRYPT_FL) != 0
7401 {
7402 return Ok(false);
7403 }
7404 let bs = self.sb.block_size() as usize;
7405 let physical = |logical: u32| {
7406 self.map_inode_logical(&dir, u64::from(logical))?
7407 .ok_or(Error::CorruptDirEntry("htree block is not mapped"))
7408 };
7409
7410 let root_phys = physical(0)?;
7411 let root = self.buffered_block(buf, root_phys)?;
7412 self.check_dx_block(dir_ino, &dir, &root, true)?;
7413 let info = crate::htree::parse_root_info(&root)?;
7414 if info.indirect_levels > 1 {
7415 return Ok(false);
7416 }
7417 let version = crate::hash::effective_version(info.hash_version, self.sb.unsigned_hash());
7418 let hash = crate::hash::name_hash(name, version, &self.sb.hash_seed).major;
7419
7420 let (_, root_entries) = crate::htree::parse_root_entries(&root)?;
7421 let routed = crate::htree::find_entry_for_hash(&root_entries, hash).block;
7422 let (parent_phys, parent, count_offset, leaf_logical) = if info.indirect_levels == 0 {
7423 (root_phys, root, 32, routed)
7424 } else {
7425 let node_phys = physical(routed)?;
7426 let node = self.buffered_block(buf, node_phys)?;
7427 self.check_dx_block(dir_ino, &dir, &node, false)?;
7428 let (_, entries) = crate::htree::parse_node_entries(&node)?;
7429 let leaf = crate::htree::find_entry_for_hash(&entries, hash).block;
7430 (node_phys, node, 8, leaf)
7431 };
7432 if leaf_logical == 0 {
7433 return Err(Error::CorruptDirEntry(
7434 "htree routes a name to its own root",
7435 ));
7436 }
7437
7438 let leaf_phys = physical(leaf_logical)?;
7439 let leaf = self.buffered_block(buf, leaf_phys)?;
7440 let reserved_tail = if self.csum.enabled && crate::dir::has_csum_tail(&leaf) {
7441 if !self
7442 .csum
7443 .verify_dir_entry_tail(dir_ino, dir.generation, &leaf)
7444 {
7445 return Err(Error::BadChecksum {
7446 what: "directory block",
7447 });
7448 }
7449 12
7450 } else {
7451 0
7452 };
7453 let Ok(split) = crate::htree_mut::plan_leaf_split(
7454 &leaf,
7455 version,
7456 &self.sb.hash_seed,
7457 has_ft,
7458 bs,
7459 reserved_tail,
7460 ) else {
7461 return Ok(false);
7462 };
7463
7464 let new_logical = u32::try_from(dir.size.div_ceil(bs as u64))
7467 .map_err(|_| Error::Corrupt("directory too large to index another block"))?;
7468 let parent = match if count_offset == 32 {
7469 crate::htree_mut::plan_insert_dx_entry_root(
7470 &parent,
7471 32,
7472 split.split_out_hash,
7473 new_logical,
7474 )
7475 } else {
7476 crate::htree_mut::plan_insert_dx_entry_node(&parent, split.split_out_hash, new_logical)
7477 } {
7478 Ok(bytes) => bytes,
7479 Err(Error::CorruptExtentTree(_)) => return Ok(false),
7481 Err(e) => return Err(e),
7482 };
7483
7484 let (mut left, mut right) = (split.left_bytes, split.right_bytes);
7485 let into = if hash >= split.split_out_hash {
7486 &mut right
7487 } else {
7488 &mut left
7489 };
7490 match crate::dir::add_entry_to_block(
7491 into,
7492 target_ino,
7493 name,
7494 file_type,
7495 has_ft,
7496 reserved_tail,
7497 ) {
7498 Ok(()) => {}
7499 Err(Error::OutOfBounds) => return Ok(false),
7500 Err(e) => return Err(e),
7501 }
7502 if reserved_tail == 12 {
7503 for block in [&mut left, &mut right] {
7504 self.csum
7505 .patch_dir_entry_tail(dir_ino, dir.generation, block);
7506 }
7507 }
7508 let mut parent = parent;
7509 self.csum
7510 .patch_dx_tail(dir_ino, dir.generation, &mut parent, count_offset);
7511
7512 self.buffer_append_dir_block(buf, dir_ino, right)?;
7515 buf.put(leaf_phys, left);
7516 buf.put(parent_phys, parent);
7517 Ok(true)
7518 }
7519
7520 fn seeded_dir_block(
7522 &self,
7523 parent_inode: &Inode,
7524 parent_ino: u32,
7525 name: &[u8],
7526 target_ino: u32,
7527 file_type: crate::dir::DirEntryType,
7528 has_ft: bool,
7529 ) -> Result<Vec<u8>> {
7530 let bs = self.sb.block_size() as usize;
7531 let reserved_tail = if self.csum.enabled { 12 } else { 0 };
7532 let mut block = vec![0u8; bs];
7533 block[4..6].copy_from_slice(&((bs - reserved_tail) as u16).to_le_bytes());
7534 crate::dir::add_entry_to_block(
7535 &mut block,
7536 target_ino,
7537 name,
7538 file_type,
7539 has_ft,
7540 reserved_tail,
7541 )?;
7542 if self.csum.enabled {
7543 self.csum
7544 .patch_dir_entry_tail(parent_ino, parent_inode.generation, &mut block);
7545 }
7546 Ok(block)
7547 }
7548
7549 fn buffer_append_dir_block(
7555 &self,
7556 buf: &mut BlockBuffer,
7557 parent_ino: u32,
7558 block: Vec<u8>,
7559 ) -> Result<()> {
7560 let bs = self.sb.block_size();
7561 let bs_u64 = bs as u64;
7562
7563 let (parent_inode, mut parent_raw) = self.buffered_inode_verified(buf, parent_ino)?;
7564 if !parent_inode.is_dir() {
7565 return Err(Error::NotADirectory);
7566 }
7567 let new_logical_block = parent_inode.size.div_ceil(bs_u64);
7568 if parent_inode.flags & crate::inode::InodeFlags::EXTENTS.bits() == 0 {
7569 return self.buffer_extend_mapped_dir(
7570 buf,
7571 parent_ino,
7572 &parent_inode,
7573 &mut parent_raw,
7574 new_logical_block,
7575 block,
7576 );
7577 }
7578
7579 let parent_group = (parent_ino - 1) / self.sb.inodes_per_group;
7581 let plan = self.plan_buffered_block_allocation(buf, 1, parent_group)?;
7582 let new_phys = plan.first_block;
7583
7584 let new_extent = crate::extent::Extent {
7589 logical_block: new_logical_block as u32,
7590 length: 1,
7591 physical_block: new_phys,
7592 uninitialized: false,
7593 };
7594 let root_header = crate::extent::ExtentHeader::parse(&parent_inode.block)?;
7599 if root_header.depth == 1 {
7600 return self.buffer_extend_dir_depth1(
7601 buf,
7602 parent_ino,
7603 &parent_inode,
7604 &mut parent_raw,
7605 block,
7606 new_extent,
7607 plan,
7608 );
7609 }
7610 if root_header.depth > 1 {
7611 return self.buffer_extend_dir_deep(
7612 buf,
7613 parent_ino,
7614 &parent_inode,
7615 &mut parent_raw,
7616 block,
7617 new_extent,
7618 plan,
7619 );
7620 }
7621
7622 self.buffer_dir_block_alloc(buf, &plan)?;
7625 let (new_root, promoted) =
7626 match crate::extent_mut::plan_insert_extent(&parent_inode.block, new_extent) {
7627 Ok(muts) => {
7628 let root = muts
7629 .into_iter()
7630 .find_map(|m| match m {
7631 crate::extent_mut::ExtentMutation::WriteRoot { bytes } => Some(bytes),
7632 _ => None,
7633 })
7634 .ok_or(Error::Corrupt(
7635 "extend_dir_and_add_entry: plan produced no WriteRoot",
7636 ))?;
7637 (root, false)
7638 }
7639 Err(Error::CorruptExtentTree(msg)) if msg.contains("LEAF_FULL_NEEDS_PROMOTION") => {
7640 let leaf_meta_phys = self.buffer_alloc_dir_block(buf, parent_ino)?;
7642 let promo = crate::extent_mut::plan_promote_leaf(
7643 &parent_inode.block,
7644 new_extent,
7645 bs as usize,
7646 leaf_meta_phys,
7647 self.csum.enabled,
7648 )?;
7649 let mut leaf = promo.leaf_bytes;
7650 if self.csum.enabled {
7651 self.csum
7652 .patch_extent_tail(parent_ino, parent_inode.generation, &mut leaf);
7653 }
7654 buf.put(leaf_meta_phys, leaf);
7655 (promo.new_root_bytes, true)
7656 }
7657 Err(e) => return Err(e),
7658 };
7659 Self::patch_inode_block_area(&mut parent_raw, &new_root)?;
7660
7661 let blocks_consumed: u64 = 1 + u64::from(promoted);
7664 let new_size = parent_inode.size + bs_u64;
7665 let new_blocks = parent_inode.blocks + (bs_u64 / 512) * blocks_consumed;
7666 Self::patch_inode_size_and_blocks(&mut parent_raw, new_size, new_blocks)?;
7667
7668 self.buffer_write_dir_inode(buf, parent_ino, parent_inode.generation, &mut parent_raw)?;
7670 buf.put(new_phys, block);
7671 Ok(())
7672 }
7673
7674 fn buffer_extend_mapped_dir(
7680 &self,
7681 buf: &mut BlockBuffer,
7682 parent_ino: u32,
7683 parent_inode: &Inode,
7684 parent_raw: &mut [u8],
7685 new_logical_block: u64,
7686 block: Vec<u8>,
7687 ) -> Result<()> {
7688 use crate::inode::OFF_BLOCK;
7689 const DIRECT: u64 = 12;
7690 let bs = self.sb.block_size();
7691 let bs_u64 = bs as u64;
7692 let per_block = bs_u64 / 4;
7693 if new_logical_block >= DIRECT + per_block {
7694 return Err(Error::Unsupported(
7695 "growing an ext2/ext3 directory past its single-indirect block",
7696 ));
7697 }
7698 let allocate = |buf: &mut BlockBuffer| -> Result<u32> {
7700 let phys = self.buffer_alloc_dir_block(buf, parent_ino)?;
7701 u32::try_from(phys)
7702 .map_err(|_| Error::Corrupt("directory block past a 32-bit block map"))
7703 };
7704
7705 let new_phys = allocate(buf)?;
7706 let mut blocks_consumed = 1u64;
7707 if new_logical_block < DIRECT {
7708 let at = OFF_BLOCK + 4 * new_logical_block as usize;
7709 parent_raw[at..at + 4].copy_from_slice(&new_phys.to_le_bytes());
7710 } else {
7711 let slot = OFF_BLOCK + 4 * DIRECT as usize;
7712 let mut indirect_phys =
7713 u32::from_le_bytes(parent_raw[slot..slot + 4].try_into().unwrap());
7714 let mut indirect = if indirect_phys == 0 {
7715 indirect_phys = allocate(buf)?;
7716 blocks_consumed += 1;
7717 parent_raw[slot..slot + 4].copy_from_slice(&indirect_phys.to_le_bytes());
7718 vec![0u8; bs as usize]
7719 } else {
7720 self.buffered_block(buf, u64::from(indirect_phys))?
7721 };
7722 let at = 4 * (new_logical_block - DIRECT) as usize;
7723 indirect[at..at + 4].copy_from_slice(&new_phys.to_le_bytes());
7724 buf.put(u64::from(indirect_phys), indirect);
7725 }
7726
7727 let new_size = parent_inode.size + bs_u64;
7728 let new_blocks = parent_inode.blocks + (bs_u64 / 512) * blocks_consumed;
7729 Self::patch_inode_size_and_blocks(parent_raw, new_size, new_blocks)?;
7730 self.buffer_write_dir_inode(buf, parent_ino, parent_inode.generation, parent_raw)?;
7731 buf.put(u64::from(new_phys), block);
7732 Ok(())
7733 }
7734
7735 #[allow(clippy::too_many_arguments)]
7742 fn buffer_extend_dir_deep(
7743 &self,
7744 buf: &mut BlockBuffer,
7745 parent_ino: u32,
7746 parent_inode: &Inode,
7747 parent_raw: &mut [u8],
7748 block: Vec<u8>,
7749 new_extent: crate::extent::Extent,
7750 data_plan: crate::alloc::BlockAllocationPlan,
7751 ) -> Result<()> {
7752 let bs = self.sb.block_size();
7753 let bs_u64 = bs as u64;
7754 let parent_group = (parent_ino - 1) / self.sb.inodes_per_group;
7755
7756 let mut pending_meta: Vec<crate::alloc::BlockAllocationPlan> = Vec::new();
7783
7784 let reader = FsBlockReader { fs: self };
7785 let staged: &BlockBuffer = buf;
7786 let mut alloc_fn = || -> Result<u64> {
7787 let reserved = crate::alloc::reserved_blocks(&data_plan, &pending_meta);
7792 let meta_plan =
7793 self.plan_buffered_block_allocation_excluding(staged, 1, parent_group, &reserved)?;
7794 pending_meta.push(meta_plan);
7795 Ok(pending_meta.last().unwrap().first_block)
7796 };
7797
7798 let deep_plan = crate::extent_mut::plan_insert_extent_deep(
7799 &parent_inode.block,
7800 new_extent,
7801 bs,
7802 &reader,
7803 &mut alloc_fn,
7804 )?;
7805
7806 self.buffer_dir_block_alloc(buf, &data_plan)?;
7808 for plan in &pending_meta {
7809 self.buffer_dir_block_alloc(buf, plan)?;
7810 }
7811
7812 for (block, mut bytes) in deep_plan.block_writes {
7814 if self.csum.enabled {
7815 self.csum
7816 .patch_extent_tail(parent_ino, parent_inode.generation, &mut bytes);
7817 }
7818 buf.put(block, bytes);
7819 }
7820
7821 Self::patch_inode_block_area(parent_raw, &deep_plan.new_root)?;
7823 let new_size = parent_inode.size + bs_u64;
7824 let new_blocks = parent_inode.blocks + (bs_u64 / 512) * (1 + pending_meta.len() as u64);
7825 Self::patch_inode_size_and_blocks(parent_raw, new_size, new_blocks)?;
7826 self.buffer_write_dir_inode(buf, parent_ino, parent_inode.generation, parent_raw)?;
7827 buf.put(data_plan.first_block, block);
7828 Ok(())
7829 }
7830
7831 #[allow(clippy::too_many_arguments)]
7839 fn buffer_extend_dir_depth1(
7840 &self,
7841 buf: &mut BlockBuffer,
7842 parent_ino: u32,
7843 parent_inode: &Inode,
7844 parent_raw: &mut [u8],
7845 block: Vec<u8>,
7846 new_extent: crate::extent::Extent,
7847 plan: crate::alloc::BlockAllocationPlan,
7848 ) -> Result<()> {
7849 let bs = self.sb.block_size();
7850 let bs_u64 = bs as u64;
7851
7852 if crate::extent::ExtentHeader::parse(&parent_inode.block)?.entries != 1 {
7858 return self.buffer_extend_dir_deep(
7859 buf,
7860 parent_ino,
7861 parent_inode,
7862 parent_raw,
7863 block,
7864 new_extent,
7865 plan,
7866 );
7867 }
7868
7869 let idx = crate::extent::ExtentIdx::parse(
7871 &parent_inode.block
7872 [crate::extent::EXT4_EXT_NODE_SIZE..2 * crate::extent::EXT4_EXT_NODE_SIZE],
7873 )?;
7874 let leaf_phys = idx.leaf_block;
7875
7876 let leaf = self.buffered_block(buf, leaf_phys)?;
7881 if self.csum.enabled
7884 && !self
7885 .csum
7886 .verify_extent_tail(parent_ino, parent_inode.generation, &leaf)
7887 {
7888 return Err(Error::BadChecksum {
7889 what: "extent block",
7890 });
7891 }
7892
7893 let muts = match crate::extent_mut::plan_insert_extent(&leaf, new_extent) {
7894 Ok(muts) => muts,
7895 Err(Error::CorruptExtentTree(msg)) if msg.contains("LEAF_FULL_NEEDS_PROMOTION") => {
7896 return self.buffer_extend_dir_deep(
7901 buf,
7902 parent_ino,
7903 parent_inode,
7904 parent_raw,
7905 block,
7906 new_extent,
7907 plan,
7908 );
7909 }
7910 Err(e) => return Err(e),
7911 };
7912 let mut new_leaf = muts
7913 .into_iter()
7914 .find_map(|m| match m {
7915 crate::extent_mut::ExtentMutation::WriteRoot { bytes } => Some(bytes),
7916 _ => None,
7917 })
7918 .ok_or(Error::Corrupt(
7919 "buffer_extend_dir_depth1: plan produced no WriteRoot",
7920 ))?;
7921 if self.csum.enabled {
7922 self.csum
7923 .patch_extent_tail(parent_ino, parent_inode.generation, &mut new_leaf);
7924 }
7925 self.buffer_dir_block_alloc(buf, &plan)?;
7926 buf.put(leaf_phys, new_leaf);
7927
7928 let new_size = parent_inode.size + bs_u64;
7930 let new_blocks = parent_inode.blocks + (bs_u64 / 512);
7931 Self::patch_inode_size_and_blocks(parent_raw, new_size, new_blocks)?;
7932 self.buffer_write_dir_inode(buf, parent_ino, parent_inode.generation, parent_raw)?;
7933 buf.put(plan.first_block, block);
7934 Ok(())
7935 }
7936
7937 pub fn apply_rmdir(&self, path: &str) -> Result<()> {
7941 self.apply_rmdir_bytes(path.as_bytes())
7942 }
7943
7944 pub(crate) fn apply_rmdir_bytes(&self, path: &[u8]) -> Result<()> {
7946 self.refuse_write()?;
7947 let (parent, name) = self.resolve_parent(path)?;
7948 self.apply_rmdir_at(parent, name)
7949 }
7950
7951 pub fn apply_rmdir_at(&self, dir: impl Into<InodeRef>, name: &[u8]) -> Result<()> {
7955 self.refuse_write()?;
7956 check_entry_name(name)?;
7957 if name == b"." {
7960 return Err(Error::InvalidArgument("rmdir: cannot remove ."));
7961 }
7962 if name == b".." {
7963 return Err(Error::DirectoryNotEmpty);
7964 }
7965 let dir = dir.into();
7966 let parent_ino = dir.ino;
7967 let (parent_inode, _) = self.live_dir(dir)?;
7968 let target_ino = self.find_entry_in_dir(parent_ino, &parent_inode, name)?;
7969 let (target_inode, target_raw) = self.live_inode(target_ino.into())?;
7970 if !target_inode.is_dir() {
7971 return Err(Error::NotADirectory);
7972 }
7973
7974 let bs = self.sb.block_size();
7977 let has_ft = self.sb.feature_incompat & features::Incompat::FILETYPE.bits() != 0;
7978 let blocks = if target_inode.has_inline_data() {
7979 self.refuse_nonempty_inline_dir(target_ino, &target_inode, &target_raw)?;
7980 0
7981 } else {
7982 target_inode.size.div_ceil(bs as u64)
7983 };
7984 for logical in 0..blocks {
7985 let Some(phys) = self.map_inode_logical(&target_inode, logical)? else {
7988 continue;
7989 };
7990 let block = self.read_block(phys)?;
7991 self.refuse_unverified_dir_block(target_ino, &target_inode, logical, &block)?;
7995 for entry in crate::dir::DirBlockIter::new(&block, has_ft) {
7996 let e = entry?;
7997 if e.name != b"." && e.name != b".." {
7998 return Err(Error::DirectoryNotEmpty);
7999 }
8000 }
8001 }
8002
8003 let mut buf = BlockBuffer::new(bs);
8006
8007 let mut freed_blocks = if target_inode.has_extents() {
8010 let runs = self.extent_tree_runs(target_ino, &target_inode)?;
8011 self.buffer_free_runs(&mut buf, &runs)?
8012 } else {
8013 self.buffer_free_block_map(&mut buf, &target_inode)?
8016 };
8017
8018 if target_inode.file_acl != 0 {
8019 freed_blocks += self.buffer_release_xattr_block(&mut buf, target_inode.file_acl)?;
8020 }
8021
8022 self.buffer_free_inode_slot(&mut buf, target_ino)?;
8026 let target_gi = ((target_ino - 1) / self.sb.inodes_per_group) as usize;
8027 self.buffer_patch_bgd_counters(&mut buf, target_gi, 0, 0, -1)?;
8028 self.buffer_patch_sb_counters(&mut buf, freed_blocks as i64, 1)?;
8030
8031 let inode_size = self.sb.inode_size as usize;
8037 let mut target_raw = vec![0u8; inode_size];
8038 let dtime = self.dtime_now();
8039 target_raw[0x14..0x18].copy_from_slice(&dtime.to_le_bytes());
8040 target_raw[0x64..0x68].copy_from_slice(&target_inode.generation.to_le_bytes());
8041 self.finalize_inode_raw(target_ino, target_inode.generation, &mut target_raw)?;
8042 self.buffer_write_inode(&mut buf, target_ino, &target_raw)?;
8043
8044 match self.buffer_remove_dir_entry(&mut buf, parent_ino, &parent_inode, name) {
8047 Err(Error::NotFound) => {
8048 return Err(Error::Corrupt(
8049 "apply_rmdir: entry disappeared mid-operation",
8050 ))
8051 }
8052 r => r?,
8053 }
8054
8055 let (parent_now, mut parent_raw) = self.buffered_inode_verified(&buf, parent_ino)?;
8059 self.patch_inode_nlink(parent_ino, &mut parent_raw, &parent_now, -1)?;
8060 self.buffer_write_inode(&mut buf, parent_ino, &parent_raw)?;
8061
8062 self.commit_block_buffer(buf)
8063 }
8064}
8065
8066pub(crate) const MAX_LINKS: u16 = 65000;
8068
8069pub(crate) fn next_links_count(
8083 is_dir: bool,
8084 count: u16,
8085 delta: i32,
8086 dir_nlink: bool,
8087) -> Result<u16> {
8088 let mut count = count;
8089 for _ in 0..delta.unsigned_abs() {
8090 count = if delta > 0 {
8091 if is_dir && count == 1 {
8092 1
8093 } else if count >= MAX_LINKS {
8094 if is_dir && dir_nlink {
8095 1
8096 } else {
8097 return Err(Error::TooManyLinks);
8098 }
8099 } else {
8100 count + 1
8101 }
8102 } else if is_dir {
8103 if count > 2 {
8104 count - 1
8105 } else {
8106 count
8107 }
8108 } else {
8109 count.saturating_sub(1)
8110 };
8111 }
8112 Ok(count)
8113}
8114
8115#[cfg(test)]
8116mod tests {
8117 use super::*;
8118
8119 use crate::inode::{
8120 EXTRA_ISIZE_DEFAULT, INODE_SIZE_WITH_CRTIME, INODE_SIZE_WITH_EXTRA, OFF_ATIME, OFF_CRTIME,
8121 OFF_CTIME, OFF_EXTRA_ISIZE, OFF_GENERATION, OFF_MTIME,
8122 };
8123
8124 fn read_le32(buf: &[u8], off: usize) -> u32 {
8125 u32::from_le_bytes(buf[off..off + 4].try_into().unwrap())
8126 }
8127 fn read_le16(buf: &[u8], off: usize) -> u16 {
8128 u16::from_le_bytes(buf[off..off + 2].try_into().unwrap())
8129 }
8130
8131 #[test]
8134 fn write_inode_timestamps_sets_atime_ctime_mtime() {
8135 let mut raw = vec![0u8; 256];
8136 write_inode_timestamps(&mut raw, 0x5EAD_BEEF);
8137 assert_eq!(read_le32(&raw, OFF_ATIME), 0x5EAD_BEEF);
8138 assert_eq!(read_le32(&raw, OFF_CTIME), 0x5EAD_BEEF);
8139 assert_eq!(read_le32(&raw, OFF_MTIME), 0x5EAD_BEEF);
8140 }
8141
8142 #[test]
8145 fn write_inode_timestamps_clamps_past_2038_without_extra_fields() {
8146 let mut raw = vec![0u8; 256];
8147 write_inode_timestamps(&mut raw, 0xDEAD_BEEF);
8148 assert_eq!(read_le32(&raw, OFF_ATIME), i32::MAX as u32);
8149 assert_eq!(read_le32(&raw, OFF_CTIME), i32::MAX as u32);
8150 assert_eq!(read_le32(&raw, OFF_MTIME), i32::MAX as u32);
8151 }
8152
8153 #[test]
8155 fn write_inode_timestamps_keeps_epoch_bits_with_extra_fields() {
8156 let mut raw = vec![0u8; 256];
8157 write_inode_extra_isize(&mut raw);
8158 write_inode_timestamps(&mut raw, 0xDEAD_BEEF);
8159 let inode = Inode::parse(&raw).unwrap();
8160 assert_eq!(inode.atime, 0xDEAD_BEEF);
8161 assert_eq!(inode.ctime, 0xDEAD_BEEF);
8162 assert_eq!(inode.mtime, 0xDEAD_BEEF);
8163 assert_eq!(inode.crtime, 0xDEAD_BEEF);
8164 }
8165
8166 #[test]
8167 fn write_inode_timestamps_sets_crtime_when_large_enough() {
8168 let mut raw = vec![0u8; INODE_SIZE_WITH_CRTIME + 4];
8169 write_inode_extra_isize(&mut raw);
8170 write_inode_timestamps(&mut raw, 0x1234_5678);
8171 assert_eq!(read_le32(&raw, OFF_CRTIME), 0x1234_5678);
8172 }
8173
8174 #[test]
8175 fn write_inode_timestamps_skips_crtime_when_too_small() {
8176 let mut raw = vec![0xAAu8; INODE_SIZE_WITH_CRTIME - 1];
8177 write_inode_timestamps(&mut raw, 0x1234_5678);
8178 assert_eq!(read_le32(&raw, OFF_ATIME), 0x1234_5678);
8181 }
8182
8183 #[test]
8184 fn write_inode_timestamps_zero_now() {
8185 let mut raw = vec![0xFFu8; 256];
8186 write_inode_timestamps(&mut raw, 0);
8187 assert_eq!(read_le32(&raw, OFF_ATIME), 0);
8188 assert_eq!(read_le32(&raw, OFF_CTIME), 0);
8189 assert_eq!(read_le32(&raw, OFF_MTIME), 0);
8190 assert_eq!(read_le32(&raw, OFF_CRTIME), 0);
8191 }
8192
8193 #[test]
8196 fn write_inode_generation_writes_at_correct_offset() {
8197 let mut raw = vec![0u8; 256];
8198 write_inode_generation(&mut raw, 0xCAFE_BABE);
8199 assert_eq!(read_le32(&raw, OFF_GENERATION), 0xCAFE_BABE);
8200 }
8201
8202 #[test]
8203 fn write_inode_generation_overwrites_existing() {
8204 let mut raw = vec![0xFFu8; 256];
8205 write_inode_generation(&mut raw, 0);
8206 assert_eq!(read_le32(&raw, OFF_GENERATION), 0);
8207 }
8208
8209 #[test]
8212 fn write_inode_extra_isize_sets_default_when_large_enough() {
8213 let mut raw = vec![0u8; INODE_SIZE_WITH_EXTRA + 4];
8214 write_inode_extra_isize(&mut raw);
8215 assert_eq!(read_le16(&raw, OFF_EXTRA_ISIZE), EXTRA_ISIZE_DEFAULT);
8216 }
8217
8218 #[test]
8219 fn write_inode_extra_isize_skips_when_too_small() {
8220 let mut raw = vec![0u8; INODE_SIZE_WITH_EXTRA - 1];
8221 write_inode_extra_isize(&mut raw); }
8224
8225 #[test]
8228 fn alloc_inode_generation_produces_unique_values() {
8229 let g1 = crate::runtime::Runtime::next_inode_generation(&crate::runtime::SystemRuntime);
8230 let g2 = crate::runtime::Runtime::next_inode_generation(&crate::runtime::SystemRuntime);
8231 assert_ne!(g1, g2, "successive calls must produce distinct values");
8232 }
8233
8234 struct MemDev {
8249 bytes: std::sync::Mutex<Vec<u8>>,
8250 size: u64,
8251 }
8252
8253 impl MemDev {
8254 fn new(size: u64) -> std::sync::Arc<Self> {
8255 std::sync::Arc::new(Self {
8256 bytes: std::sync::Mutex::new(vec![0u8; size as usize]),
8257 size,
8258 })
8259 }
8260 }
8261
8262 impl crate::block_io::BlockDevice for MemDev {
8263 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
8264 let b = self.bytes.lock().unwrap();
8265 let start = offset as usize;
8266 let end = start + buf.len();
8267 if end > b.len() {
8268 return Err(Error::Corrupt("MemDev: read past end"));
8269 }
8270 buf.copy_from_slice(&b[start..end]);
8271 Ok(())
8272 }
8273 fn size_bytes(&self) -> u64 {
8274 self.size
8275 }
8276 fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
8277 let mut b = self.bytes.lock().unwrap();
8278 let start = offset as usize;
8279 let end = start + buf.len();
8280 if end > b.len() {
8281 return Err(Error::Corrupt("MemDev: write past end"));
8282 }
8283 b[start..end].copy_from_slice(buf);
8284 Ok(())
8285 }
8286 fn flush(&self) -> Result<()> {
8287 Ok(())
8288 }
8289 fn is_writable(&self) -> bool {
8290 true
8291 }
8292 }
8293
8294 const BS: u32 = 4096;
8295 const VOL: u64 = 32 * 1024 * 1024;
8296
8297 fn formatted() -> std::sync::Arc<MemDev> {
8298 let dev = MemDev::new(VOL);
8299 crate::mkfs::format_filesystem(dev.as_ref(), Some("orphan"), None, VOL, BS)
8300 .expect("format");
8301 dev
8302 }
8303
8304 fn mount(dev: &std::sync::Arc<MemDev>) -> Filesystem {
8305 Filesystem::mount(dev.clone()).expect("mount")
8306 }
8307
8308 fn file_of_aa(fs: &Filesystem, len: usize) -> u32 {
8310 let ino = fs.apply_create("/f", 0o644).unwrap();
8311 fs.apply_replace_file_content("/f", &vec![0xAAu8; len])
8312 .unwrap();
8313 ino
8314 }
8315
8316 fn bytes_wrong_after_zeroing(
8319 fs: &Filesystem,
8320 ino: u32,
8321 zeroed: std::ops::Range<usize>,
8322 ) -> usize {
8323 let (inode, _) = fs.read_inode_verified(ino).unwrap();
8324 let data = crate::file_io::read_all(fs, &inode).unwrap();
8325 data.iter()
8326 .enumerate()
8327 .filter(|(i, b)| **b != if zeroed.contains(i) { 0 } else { 0xAA })
8328 .count()
8329 }
8330
8331 #[test]
8334 fn an_unaligned_punch_keeps_the_bytes_around_it() {
8335 let dev = formatted();
8336 let fs = mount(&dev);
8337 let ino = file_of_aa(&fs, 8192);
8338 fs.apply_fallocate_punch_hole(ino, 100, 100).unwrap();
8339 assert_eq!(bytes_wrong_after_zeroing(&fs, ino, 100..200), 0);
8340 drop(fs);
8341 let fs = mount(&dev);
8342 assert_eq!(
8343 bytes_wrong_after_zeroing(&fs, ino, 100..200),
8344 0,
8345 "after a remount"
8346 );
8347 }
8348
8349 #[test]
8352 fn a_punch_frees_the_whole_blocks_and_zeroes_the_edges() {
8353 let dev = formatted();
8354 let fs = mount(&dev);
8355 let ino = file_of_aa(&fs, 3 * BS as usize);
8356 drop(fs);
8357 let fs = mount(&dev); let free_before = fs.sb.free_blocks_count;
8359 let (lo, hi) = (100usize, 2 * BS as usize + 100);
8360 fs.apply_fallocate_punch_hole(ino, lo as u64, (hi - lo) as u64)
8361 .unwrap();
8362 drop(fs);
8363 let fs = mount(&dev);
8364 assert_eq!(bytes_wrong_after_zeroing(&fs, ino, lo..hi), 0);
8365 let (inode, _) = fs.read_inode_verified(ino).unwrap();
8366 let mapped: Vec<bool> = (0..3)
8367 .map(|lb| fs.map_inode_logical(&inode, lb).unwrap().is_some())
8368 .collect();
8369 assert_eq!(
8370 mapped,
8371 [true, false, true],
8372 "only the middle block is freed"
8373 );
8374 assert_eq!(fs.sb.free_blocks_count, free_before + 1, "one block freed");
8375 }
8376
8377 #[test]
8379 fn a_punch_inside_one_block_frees_nothing() {
8380 let dev = formatted();
8381 let fs = mount(&dev);
8382 let ino = file_of_aa(&fs, 2 * BS as usize);
8383 drop(fs);
8384 let fs = mount(&dev); let free_before = fs.sb.free_blocks_count;
8386 fs.apply_fallocate_punch_hole(ino, BS as u64 + 1, BS as u64 - 2)
8387 .unwrap();
8388 drop(fs);
8389 let fs = mount(&dev);
8390 let (lo, hi) = (BS as usize + 1, 2 * BS as usize - 1);
8391 assert_eq!(bytes_wrong_after_zeroing(&fs, ino, lo..hi), 0);
8392 assert_eq!(fs.sb.free_blocks_count, free_before);
8393 }
8394
8395 #[test]
8398 fn an_unaligned_zero_range_keeps_the_bytes_around_it() {
8399 let dev = formatted();
8400 let fs = mount(&dev);
8401 let ino = file_of_aa(&fs, 3 * BS as usize);
8402 let (lo, hi) = (100usize, 2 * BS as usize + 100);
8403 fs.apply_fallocate_zero_range(ino, lo as u64, (hi - lo) as u64)
8404 .unwrap();
8405 drop(fs);
8406 let fs = mount(&dev);
8407 assert_eq!(bytes_wrong_after_zeroing(&fs, ino, lo..hi), 0);
8408 }
8409
8410 fn striped_file(fs: &Filesystem) -> u32 {
8413 let ino = fs.apply_create("/f", 0o644).unwrap();
8414 for lb in [0u64, 2, 4, 6, 8, 10] {
8415 fs.apply_pwrite("/f", lb * BS as u64, &[1u8; BS as usize])
8416 .unwrap();
8417 }
8418 let (inode, _) = fs.read_inode_verified(ino).unwrap();
8419 let depth = u16::from_le_bytes(inode.block[6..8].try_into().unwrap());
8420 assert!(depth >= 1, "precondition: depth >= 1, got {depth}");
8421 ino
8422 }
8423
8424 fn fill_leaving_a_few_free(fs: &Filesystem) {
8426 let mut n = fs.sb.free_blocks_count.saturating_sub(4);
8427 loop {
8428 let r = fs.apply_create("/fill", 0o644).and_then(|_| {
8429 fs.apply_replace_file_content("/fill", &vec![0u8; (n * BS as u64) as usize])
8430 });
8431 match r {
8432 Ok(_) => break,
8433 Err(_) => {
8434 let _ = fs.apply_unlink("/fill");
8435 n -= 1;
8436 }
8437 }
8438 }
8439 }
8440
8441 #[test]
8446 fn a_pwrite_that_runs_out_of_space_leaves_the_tree_as_it_was() {
8447 let dev = formatted();
8448 let fs = mount(&dev);
8449 let ino = striped_file(&fs);
8450 drop(fs);
8451 let fs = mount(&dev);
8452 fill_leaving_a_few_free(&fs);
8453 drop(fs);
8454 let fs = mount(&dev);
8455 let left = fs.sb.free_blocks_count;
8456 let bs = BS as u64;
8457 let r = fs.apply_pwrite("/f", 20 * bs, &vec![2u8; ((left + 8) * bs) as usize]);
8458 assert!(r.is_err(), "precondition: ENOSPC ({left} free)");
8459 drop(fs);
8460 let fs = mount(&dev);
8461 let (inode, _) = fs.read_inode_verified(ino).unwrap();
8462 let mapped = fs.map_inode_logical(&inode, 20).unwrap();
8463 assert_eq!(
8464 mapped, None,
8465 "a failed pwrite left logical block 20 mapped ({left} were free)"
8466 );
8467 let data = crate::file_io::read_all(&fs, &inode).unwrap();
8468 assert_eq!(data.len(), 11 * BS as usize, "the size is unchanged");
8469 }
8470
8471 fn formatted_with_group_zero_block_uninit() -> std::sync::Arc<MemDev> {
8481 let dev = formatted();
8482 let fs = mount(&dev);
8483 let (bgt_block, off) = fs.sb.descriptor_location(0);
8484 let ds = fs.sb.desc_size as usize;
8485 let mut raw = fs.read_block(bgt_block).unwrap();
8486 let flags = u16::from_le_bytes(raw[off + 0x12..off + 0x14].try_into().unwrap())
8487 | crate::bgd::BgdFlags::BLOCK_UNINIT.bits();
8488 raw[off + 0x12..off + 0x14].copy_from_slice(&flags.to_le_bytes());
8489 let c = crate::checksum::group_desc_csum(&fs.sb, &fs.csum, 0, &raw[off..off + ds])
8490 .expect("the formatted volume checksums its descriptors");
8491 raw[off + 0x1e..off + 0x20].copy_from_slice(&c.to_le_bytes());
8492 dev.write_at(bgt_block * u64::from(BS), &raw).unwrap();
8493 drop(fs);
8494 dev
8495 }
8496
8497 #[test]
8498 fn buffered_allocations_do_not_reuse_an_uninitialized_groups_first_run() {
8499 let dev = formatted_with_group_zero_block_uninit();
8500 let fs = mount(&dev);
8501 assert!(fs.allocation_groups()[0]
8502 .flags()
8503 .contains(crate::bgd::BgdFlags::BLOCK_UNINIT));
8504 let before = dev.bytes.lock().unwrap().clone();
8505
8506 let mut buf = BlockBuffer::new(BS);
8507 let first = fs.plan_buffered_block_allocation(&buf, 4, 0).unwrap();
8508 fs.buffer_mark_block_run_used(&mut buf, first.first_block, 4)
8509 .unwrap();
8510 let next = fs.plan_buffered_block_allocation(&buf, 1, 0).unwrap();
8511 assert!(
8512 next.first_block < first.first_block || next.first_block >= first.first_block + 4,
8513 "second plan {} overlaps the staged run {}..{}",
8514 next.first_block,
8515 first.first_block,
8516 first.first_block + 4
8517 );
8518
8519 assert!(fs.allocation_groups()[0]
8522 .flags()
8523 .contains(crate::bgd::BgdFlags::BLOCK_UNINIT));
8524 assert!(
8525 *dev.bytes.lock().unwrap() == before,
8526 "planning wrote to the device"
8527 );
8528 drop(buf);
8529 let again = fs
8530 .plan_buffered_block_allocation(&BlockBuffer::new(BS), 4, 0)
8531 .unwrap();
8532 assert_eq!(again.first_block, first.first_block);
8533 }
8534
8535 #[test]
8542 fn one_block_allocations_in_a_transaction_do_not_reuse_an_uninitialized_groups_block() {
8543 let dev = formatted_with_group_zero_block_uninit();
8544 let fs = mount(&dev);
8545 let mut buf = BlockBuffer::new(BS);
8546 let first = fs.buffer_allocate_block(&mut buf, 1).unwrap();
8547 let second = fs.buffer_allocate_block(&mut buf, 1).unwrap();
8548 assert_ne!(
8549 first, second,
8550 "the second allocation handed out block {first} again"
8551 );
8552 }
8553
8554 fn set_group_flag(dev: &std::sync::Arc<MemDev>, gi: u64, flag: crate::bgd::BgdFlags) {
8557 let fs = mount(dev);
8558 let bs = u64::from(fs.sb.block_size());
8559 let (bgt_block, off) = fs.sb.descriptor_location(gi);
8560 let ds = fs.sb.desc_size as usize;
8561 let mut raw = fs.read_block(bgt_block).unwrap();
8562 let flags =
8563 u16::from_le_bytes(raw[off + 0x12..off + 0x14].try_into().unwrap()) | flag.bits();
8564 raw[off + 0x12..off + 0x14].copy_from_slice(&flags.to_le_bytes());
8565 if let Some(c) =
8566 crate::checksum::group_desc_csum(&fs.sb, &fs.csum, gi as u32, &raw[off..off + ds])
8567 {
8568 raw[off + 0x1e..off + 0x20].copy_from_slice(&c.to_le_bytes());
8569 }
8570 drop(fs);
8571 dev.write_at(bgt_block * bs, &raw).unwrap();
8572 }
8573
8574 fn block_uninit(fs: &Filesystem, gi: usize) -> bool {
8575 fs.allocation_groups()[gi]
8576 .flags()
8577 .contains(crate::bgd::BgdFlags::BLOCK_UNINIT)
8578 }
8579
8580 fn clear_and_publish(fs: &Filesystem, gi: usize) {
8583 let mut buf = BlockBuffer::new(fs.sb.block_size());
8584 assert!(fs
8585 .clear_bgd_uninit_flag_if_set(&mut buf, gi, BgdUninitFlag::Block)
8586 .unwrap());
8587 fs.buffer_patch_bgd_counters(&mut buf, gi, 0, 0, 0).unwrap();
8589 fs.commit_block_buffer(buf).unwrap();
8590 }
8591
8592 #[test]
8598 fn a_published_uninit_clear_is_seen_through_the_cached_descriptors() {
8599 const MULTI: u64 = 128 * 1024 * 1024;
8602 let dev = MemDev::new(MULTI);
8603 crate::mkfs::format_filesystem(dev.as_ref(), Some("cache"), None, MULTI, 2048)
8604 .expect("format");
8605 for gi in [1, 2, 3] {
8606 set_group_flag(&dev, gi, crate::bgd::BgdFlags::BLOCK_UNINIT);
8607 }
8608 let mut fs = mount(&dev);
8609 assert!(fs.groups.len() >= 4, "{} groups", fs.groups.len());
8610 assert!(block_uninit(&fs, 1) && block_uninit(&fs, 2) && block_uninit(&fs, 3));
8611
8612 clear_and_publish(&fs, 1);
8615 assert!(!block_uninit(&fs, 1));
8616 assert!(block_uninit(&fs, 2));
8617
8618 clear_and_publish(&fs, 2);
8620 assert!(!block_uninit(&fs, 1), "an earlier clear was lost");
8621 assert!(
8622 !block_uninit(&fs, 2),
8623 "a transaction published and the planners still see the group it woke as uninit"
8624 );
8625 assert!(block_uninit(&fs, 3));
8626
8627 fs.fresh_read().unwrap();
8631 assert!(!block_uninit(&fs, 1) && !block_uninit(&fs, 2) && block_uninit(&fs, 3));
8632 clear_and_publish(&fs, 3);
8633 assert!(!block_uninit(&fs, 1) && !block_uninit(&fs, 2) && !block_uninit(&fs, 3));
8634 }
8635
8636 #[test]
8640 fn a_dir_block_commit_refuses_a_plan_for_more_than_one_block() {
8641 let dev = formatted();
8642 let fs = mount(&dev);
8643 let plan = fs
8644 .plan_buffered_block_allocation(&BlockBuffer::new(BS), 2, 0)
8645 .unwrap();
8646 assert_eq!(plan.count, 2);
8647 let before = dev.bytes.lock().unwrap().clone();
8648 let mut buf = BlockBuffer::new(BS);
8649 let got = fs.buffer_dir_block_alloc(&mut buf, &plan);
8650 assert!(
8651 matches!(got, Err(Error::Corrupt(_))),
8652 "a count-2 plan was staged as a directory block: {got:?}"
8653 );
8654 assert!(
8655 buf.dirty.is_empty() && buf.uninit_cleared.is_empty(),
8656 "the refused plan staged {} blocks",
8657 buf.dirty.len()
8658 );
8659 assert!(
8660 *dev.bytes.lock().unwrap() == before,
8661 "the refused plan wrote to the device"
8662 );
8663 }
8664
8665 #[test]
8668 fn set_flags_refuses_index_on_a_linear_directory() {
8669 let dev = formatted();
8670 let fs = mount(&dev);
8671 let d = fs.apply_mkdir("/d", 0o755).unwrap();
8672 let (i, _) = fs.read_inode_verified(d).unwrap();
8673 let r = fs.apply_set_flags("/d", i.flags | crate::inode::InodeFlags::INDEX.bits());
8674 assert!(r.is_err(), "INDEX_FL accepted on a linear directory");
8675 let (after, _) = fs.read_inode_verified(d).unwrap();
8676 assert_eq!(after.flags, i.flags, "a refused set_flags changed i_flags");
8677 }
8678
8679 #[test]
8683 fn set_flags_changes_only_user_modifiable_bits() {
8684 let dev = formatted();
8685 let fs = mount(&dev);
8686 let f = fs.apply_create("/f", 0o644).unwrap();
8687 let (i, _) = fs.read_inode_verified(f).unwrap();
8688 for (bit, name) in [
8689 (0x0000_0800u32, "ENCRYPT"),
8690 (0x0000_1000, "INDEX"),
8691 (0x0004_0000, "HUGE_FILE"),
8692 (0x0010_0000, "VERITY"),
8693 (0x0200_0000, "DAX"),
8694 (0x4000_0000, "CASEFOLD"),
8695 (0x8000_0000, "RESERVED"),
8696 ] {
8697 assert!(
8698 fs.apply_set_flags("/f", i.flags | bit).is_err(),
8699 "{name} ({bit:#x}) accepted"
8700 );
8701 }
8702 let wanted = i.flags
8704 | crate::inode::InodeFlags::IMMUTABLE.bits()
8705 | crate::inode::InodeFlags::NOATIME.bits()
8706 | crate::inode::InodeFlags::NODUMP.bits();
8707 fs.apply_set_flags("/f", wanted).unwrap();
8708 let (after, _) = fs.read_inode_verified(f).unwrap();
8709 assert_eq!(after.flags, wanted);
8710 fs.apply_set_flags("/f", i.flags).unwrap();
8711 let (after, _) = fs.read_inode_verified(f).unwrap();
8712 assert_eq!(after.flags, i.flags);
8713 }
8714
8715 mod checked_recovery {
8717 use super::*;
8718 use std::sync::atomic::{AtomicUsize, Ordering};
8719
8720 fn journalled() -> std::sync::Arc<MemDev> {
8721 let dev = MemDev::new(VOL);
8722 crate::mkfs::format_filesystem_with_flavor(
8723 dev.as_ref(),
8724 Some("checked"),
8725 None,
8726 VOL,
8727 BS,
8728 features::FsFlavor::Ext3,
8729 )
8730 .expect("format");
8731 dev
8732 }
8733
8734 fn on_disk(dev: &MemDev) -> Superblock {
8735 Superblock::read(dev).expect("superblock")
8736 }
8737
8738 fn needs_recovery(dev: &MemDev) -> bool {
8739 on_disk(dev).feature_incompat & features::Incompat::RECOVER.bits() != 0
8740 }
8741
8742 #[test]
8745 fn needs_recovery_stays_set_across_commits_until_finish() {
8746 let dev = journalled();
8747 let fs = Filesystem::mount_recovering(dev.clone()).expect("checked mount");
8748 fs.apply_create("/a", 0o644).expect("create");
8749 assert!(
8750 needs_recovery(&dev),
8751 "a commit on a checked mount cleared needs_recovery before finish"
8752 );
8753 fs.finish().expect("finish");
8754 assert!(!needs_recovery(&dev), "finish left needs_recovery set");
8755 }
8756
8757 #[test]
8760 fn finish_puts_back_the_clean_state_it_found() {
8761 let dev = journalled();
8762 assert_ne!(on_disk(&dev).state & crate::superblock::EXT4_VALID_FS, 0);
8763 let fs = Filesystem::mount_recovering(dev.clone()).expect("checked mount");
8764 fs.apply_create("/a", 0o644).expect("create");
8765 fs.finish().expect("finish");
8766 assert_ne!(
8767 on_disk(&dev).state & crate::superblock::EXT4_VALID_FS,
8768 0,
8769 "a finished checked mount left the volume marked not clean"
8770 );
8771 }
8772
8773 struct Failing {
8775 inner: std::sync::Arc<MemDev>,
8776 events: AtomicUsize,
8777 fail_at: AtomicUsize,
8778 }
8779
8780 impl Failing {
8781 fn gate(&self) -> Result<()> {
8782 if self.events.fetch_add(1, Ordering::SeqCst) >= self.fail_at.load(Ordering::SeqCst)
8783 {
8784 return Err(Error::Corrupt("injected I/O failure"));
8785 }
8786 Ok(())
8787 }
8788 }
8789
8790 impl crate::block_io::BlockDevice for Failing {
8791 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
8792 self.inner.read_at(offset, buf)
8793 }
8794 fn size_bytes(&self) -> u64 {
8795 self.inner.size_bytes()
8796 }
8797 fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
8798 self.gate()?;
8799 self.inner.write_at(offset, buf)
8800 }
8801 fn flush(&self) -> Result<()> {
8802 self.gate()
8803 }
8804 fn is_writable(&self) -> bool {
8805 true
8806 }
8807 }
8808
8809 #[test]
8813 fn a_failure_at_any_release_write_is_not_a_clean_finish() {
8814 let run = |fail_at: usize| {
8815 let dev = std::sync::Arc::new(Failing {
8816 inner: journalled(),
8817 events: AtomicUsize::new(0),
8818 fail_at: AtomicUsize::new(fail_at),
8819 });
8820 let finished = Filesystem::mount_recovering(dev.clone())
8821 .and_then(|fs| fs.apply_create("/a", 0o644).map(|_| fs))
8822 .and_then(|fs| fs.finish());
8823 (finished.is_ok(), dev.events.load(Ordering::SeqCst))
8824 };
8825 let (ok, events) = run(usize::MAX);
8826 assert!(ok, "the uninterrupted run must finish");
8827 for fail_at in 0..events {
8828 assert!(
8829 !run(fail_at).0,
8830 "a failure at device event {fail_at} of {events} was reported as a clean finish"
8831 );
8832 }
8833 }
8834
8835 #[test]
8838 fn a_failed_commit_leaves_finish_and_drop_without_device_io() {
8839 let dev = std::sync::Arc::new(Failing {
8840 inner: journalled(),
8841 events: AtomicUsize::new(0),
8842 fail_at: AtomicUsize::new(usize::MAX),
8843 });
8844 let fs = Filesystem::mount_recovering(dev.clone()).expect("checked mount");
8845 let first = dev.events.load(Ordering::SeqCst) + 2;
8846 dev.fail_at.store(first, Ordering::SeqCst);
8847 assert!(
8848 fs.apply_create("/a", 0o644).is_err(),
8849 "the commit must fail"
8850 );
8851 let after_failure = dev.events.load(Ordering::SeqCst);
8852 assert!(
8853 fs.finish().is_err(),
8854 "a failed journal claimed a clean finish"
8855 );
8856 assert_eq!(
8857 dev.events.load(Ordering::SeqCst),
8858 after_failure,
8859 "finish or drop wrote to the device after the journal failed"
8860 );
8861 }
8862 }
8863
8864 fn resolve(fs: &Filesystem, path: &str) -> Result<u32> {
8865 let mut reader = |ino: u32| fs.read_inode_verified(ino).map(|(i, _)| i);
8866 crate::path::lookup(fs.dev.as_ref(), &fs.sb, &mut reader, path)
8867 }
8868
8869 fn plant_orphan(fs: &Filesystem, ino: u32, links: u16, new_size: Option<u64>) {
8876 let mut buf = BlockBuffer::new(fs.sb.block_size());
8877 let (inode, mut raw) = fs.read_inode_verified(ino).expect("read inode");
8878 if let Some(size) = new_size {
8879 Filesystem::patch_inode_size_and_blocks(&mut raw, size, inode.blocks)
8880 .expect("patch size");
8881 }
8882 raw[0x1A..0x1C].copy_from_slice(&links.to_le_bytes());
8883 raw[0x14..0x18].copy_from_slice(&0u32.to_le_bytes());
8885 fs.finalize_inode_raw(ino, inode.generation, &mut raw)
8886 .expect("finalize");
8887 fs.buffer_write_inode(&mut buf, ino, &raw)
8888 .expect("write inode");
8889 fs.buffer_patch_sb_last_orphan(&mut buf, ino)
8890 .expect("patch s_last_orphan");
8891 fs.commit_block_buffer(buf).expect("commit");
8892 }
8893
8894 fn give_xattr_block(fs: &Filesystem, ino: u32) -> u64 {
8897 let mut buf = BlockBuffer::new(BS);
8898 let xb = fs.buffer_allocate_block(&mut buf, ino).unwrap();
8899 let mut blk = vec![0u8; BS as usize];
8900 crate::xattr::plan_set_in_external_block(&mut blk, "security.selinux", &[b'z'; 32], 1)
8901 .unwrap();
8902 fs.csum.patch_xattr_block(xb, &mut blk);
8903 buf.put(xb, blk);
8904 let (inode, mut raw) = fs.read_inode_verified(ino).unwrap();
8905 Filesystem::write_file_acl(&mut raw, xb).unwrap();
8906 Filesystem::patch_inode_size_and_blocks(&mut raw, inode.size, inode.blocks + 8).unwrap();
8907 fs.finalize_inode_raw(ino, inode.generation, &mut raw)
8908 .unwrap();
8909 fs.buffer_write_inode(&mut buf, ino, &raw).unwrap();
8910 fs.commit_block_buffer(buf).unwrap();
8911 xb
8912 }
8913
8914 fn block_bit_set(fs: &Filesystem, block: u64) -> bool {
8916 let rel = block - fs.sb.first_data_block as u64;
8917 let gi = (rel / fs.sb.blocks_per_group as u64) as usize;
8918 let bit = (rel % fs.sb.blocks_per_group as u64) as usize;
8919 let bm = fs.read_block(fs.groups[gi].block_bitmap).unwrap();
8920 bm[bit / 8] & (1 << (bit % 8)) != 0
8921 }
8922
8923 #[test]
8927 fn recovering_an_orphaned_fast_symlink_frees_no_block_named_by_its_target() {
8928 let dev = formatted();
8929 let fs = mount(&dev);
8930 let (root, _) = fs.read_inode_verified(2).unwrap();
8931 let victim = fs.map_inode_logical(&root, 0).unwrap().unwrap();
8932 let mut tb: Vec<u8> = (victim as u32).to_le_bytes().to_vec();
8933 while tb.last() == Some(&0) {
8934 tb.pop();
8935 }
8936 assert!(
8937 tb.iter().all(|&b| b > 0 && b < 0x80),
8938 "block {victim} not expressible as ASCII"
8939 );
8940 let t = String::from_utf8(tb).unwrap();
8941 let ino = fs.apply_symlink(&t, "/l").unwrap();
8942 let xb = give_xattr_block(&fs, ino);
8943 plant_orphan(&fs, ino, 0, None);
8944 drop(fs);
8945 let fs = mount(&dev); assert!(
8947 block_bit_set(&fs, victim),
8948 "block {victim} (the root directory's) was freed by orphan recovery"
8949 );
8950 assert!(!block_bit_set(&fs, xb), "the xattr block {xb} was kept");
8951 assert_eq!(fs.sb.last_orphan, 0, "the orphan chain was not drained");
8952 }
8953
8954 #[test]
8958 fn an_orphaned_fast_symlink_whose_target_is_no_block_does_not_stall_the_chain() {
8959 let dev = formatted();
8960 let fs = mount(&dev);
8961 let behind = fs.apply_create("/behind", 0o644).unwrap();
8962 plant_orphan(&fs, behind, 0, None);
8963 let ino = fs.apply_symlink("zzzz", "/l").unwrap();
8964 let xb = give_xattr_block(&fs, ino);
8965 let (inode, mut raw) = fs.read_inode_verified(ino).unwrap();
8967 raw[0x1A..0x1C].copy_from_slice(&0u16.to_le_bytes());
8968 raw[0x14..0x18].copy_from_slice(&behind.to_le_bytes());
8969 fs.finalize_inode_raw(ino, inode.generation, &mut raw)
8970 .unwrap();
8971 let mut buf = BlockBuffer::new(BS);
8972 fs.buffer_write_inode(&mut buf, ino, &raw).unwrap();
8973 fs.buffer_patch_sb_last_orphan(&mut buf, ino).unwrap();
8974 fs.commit_block_buffer(buf).unwrap();
8975 drop(fs);
8976 let fs = mount(&dev); assert_eq!(fs.sb.last_orphan, 0, "the orphan chain was not drained");
8978 assert!(!block_bit_set(&fs, xb), "the xattr block {xb} was kept");
8979 for gone in [ino, behind] {
8980 let (i, _) = fs.read_inode_verified(gone).unwrap();
8981 assert_eq!(i.mode, 0, "orphan {gone} was not reclaimed");
8982 }
8983 }
8984
8985 #[test]
8988 fn recovering_an_orphaned_inline_file_frees_no_block_named_by_its_data() {
8989 let dev = formatted();
8990 set_incompat_bit(&dev, crate::features::Incompat::INLINE_DATA.bits());
8991 let fs = mount(&dev);
8992 let (root, _) = fs.read_inode_verified(2).unwrap();
8993 let victim = fs.map_inode_logical(&root, 0).unwrap().unwrap();
8994 let ino = fs.apply_create("/f", 0o644).unwrap();
8995 let (inode, mut raw) = fs.read_inode_verified(ino).unwrap();
8996 let mut flags = u32::from_le_bytes(raw[0x20..0x24].try_into().unwrap());
8997 flags &= !crate::inode::InodeFlags::EXTENTS.bits();
8998 flags |= crate::inode::InodeFlags::INLINE_DATA.bits();
8999 raw[0x20..0x24].copy_from_slice(&flags.to_le_bytes());
9000 raw[0x28..0x64].fill(0);
9001 raw[0x28..0x2C].copy_from_slice(&(victim as u32).to_le_bytes());
9002 raw[0x04..0x08].copy_from_slice(&4u32.to_le_bytes());
9003 fs.finalize_inode_raw(ino, inode.generation, &mut raw)
9004 .unwrap();
9005 fs.write_inode_raw(ino, &raw).unwrap();
9006 let xb = give_xattr_block(&fs, ino);
9007 plant_orphan(&fs, ino, 0, None);
9008 drop(fs);
9009 let fs = mount(&dev); assert!(
9011 block_bit_set(&fs, victim),
9012 "block {victim} (the root directory's) was freed by orphan recovery"
9013 );
9014 assert!(!block_bit_set(&fs, xb), "the xattr block {xb} was kept");
9015 assert_eq!(fs.sb.last_orphan, 0, "the orphan chain was not drained");
9016 }
9017
9018 fn on_disk_state(dev: &std::sync::Arc<MemDev>) -> u16 {
9021 let bytes = dev.bytes.lock().unwrap();
9022 u16::from_le_bytes([bytes[1024 + 0x3A], bytes[1024 + 0x3B]])
9023 }
9024
9025 #[test]
9029 fn a_writing_mount_marks_the_volume_not_clean_until_it_is_dropped() {
9030 use crate::superblock::EXT4_VALID_FS;
9031 let dev = formatted();
9032 assert_ne!(
9033 on_disk_state(&dev) & EXT4_VALID_FS,
9034 0,
9035 "fixture: formatted clean"
9036 );
9037
9038 let fs = mount(&dev);
9039 assert_ne!(
9040 on_disk_state(&dev) & EXT4_VALID_FS,
9041 0,
9042 "mounting alone must not touch the superblock"
9043 );
9044 fs.apply_create("/a.txt", 0o644).expect("create");
9045 assert_eq!(
9046 on_disk_state(&dev) & EXT4_VALID_FS,
9047 0,
9048 "a volume being written must not read as cleanly unmounted"
9049 );
9050 drop(mount(&dev)); drop(fs);
9052 assert_ne!(
9053 on_disk_state(&dev) & EXT4_VALID_FS,
9054 0,
9055 "the unmount must mark the volume clean again"
9056 );
9057 drop(mount(&dev));
9058 }
9059
9060 #[test]
9063 fn a_mount_that_does_not_write_leaves_the_superblock_alone() {
9064 let dev = formatted();
9065 let before = dev.bytes.lock().unwrap()[1024..2048].to_vec();
9066 let fs = mount(&dev);
9067 fs.read_inode_verified(2).expect("read the root");
9068 drop(fs);
9069 assert!(dev.bytes.lock().unwrap()[1024..2048] == before[..]);
9070 }
9071
9072 #[test]
9076 fn an_unclean_volume_stays_unclean_after_a_writing_mount() {
9077 use crate::superblock::EXT4_VALID_FS;
9078 let dev = formatted();
9079 {
9080 let fs = mount(&dev);
9081 fs.write_superblock_state(fs.sb.state & !EXT4_VALID_FS)
9082 .expect("mark not clean, as a crash would leave it");
9083 }
9084 let fs = mount(&dev);
9085 assert!(!fs.sb.is_clean(), "fixture: mounted not clean");
9086 fs.apply_create("/a.txt", 0o644).expect("create");
9087 drop(fs);
9088 assert_eq!(on_disk_state(&dev) & EXT4_VALID_FS, 0);
9089 }
9090
9091 #[test]
9096 fn a_rename_onto_itself_resolves_the_path_and_writes_nothing() {
9097 use crate::superblock::EXT4_VALID_FS;
9098 let dev = formatted();
9099 {
9100 let fs = mount(&dev);
9101 fs.apply_create("/f", 0o644).expect("create");
9102 }
9103 assert_ne!(on_disk_state(&dev) & EXT4_VALID_FS, 0, "fixture: clean");
9104
9105 let fs = mount(&dev);
9106 let mut wrong = Vec::new();
9107 let got = fs.apply_rename("/missing", "/missing", false);
9108 if !matches!(got, Err(Error::NotFound)) {
9109 wrong.push(format!("a missing path onto itself: {got:?}"));
9110 }
9111 let got = fs.apply_rename("/a\0b", "/a\0b", false);
9112 if !matches!(
9113 got,
9114 Err(Error::InvalidArgument("a name cannot contain a NUL byte"))
9115 ) {
9116 wrong.push(format!("a NUL name onto itself: {got:?}"));
9117 }
9118 if on_disk_state(&dev) & EXT4_VALID_FS == 0 {
9119 wrong.push("a refused rename marked the volume not clean".into());
9120 }
9121 let before = dev.bytes.lock().unwrap().clone();
9122 let got = fs.apply_rename("/f", "/f", false);
9123 if got.is_err() {
9124 wrong.push(format!("an existing path onto itself: {got:?}"));
9125 }
9126 if *dev.bytes.lock().unwrap() != before {
9127 wrong.push("renaming an existing path onto itself wrote".into());
9128 }
9129 assert!(wrong.is_empty(), "{}", wrong.join("\n"));
9130 resolve(&fs, "/f").expect("/f is still there");
9131 }
9132
9133 #[test]
9138 fn an_orphan_with_no_links_is_still_reclaimed() {
9139 let dev = formatted();
9140 let ino = {
9141 let fs = mount(&dev);
9142 let ino = fs.apply_create("/gone.txt", 0o644).expect("create");
9143 fs.apply_pwrite("/gone.txt", 0, &[0xAB; 4 * BS as usize])
9144 .expect("write");
9145 ino
9146 };
9147 {
9148 let fs = mount(&dev);
9149 plant_orphan(&fs, ino, 0, None);
9150 }
9151 drop(mount(&dev));
9153
9154 let fs = mount(&dev);
9155 assert!(
9156 fs.orphan_list().expect("orphan_list").is_empty(),
9157 "recovery must empty the chain"
9158 );
9159 let (inode, _) = fs.read_inode_verified(ino).expect("read inode");
9160 assert_eq!(inode.links_count, 0, "an unlinked orphan stays unlinked");
9161 assert_ne!(inode.dtime, 0, "and is stamped as deleted");
9162 assert_eq!(inode.size, 0, "its body is gone");
9163 }
9164
9165 struct CrashDev {
9168 inner: std::sync::Arc<MemDev>,
9169 budget: usize,
9170 writes: std::sync::atomic::AtomicUsize,
9171 }
9172
9173 impl crate::block_io::BlockDevice for CrashDev {
9174 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
9175 self.inner.read_at(offset, buf)
9176 }
9177 fn size_bytes(&self) -> u64 {
9178 self.inner.size_bytes()
9179 }
9180 fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
9181 let n = self
9182 .writes
9183 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
9184 if n < self.budget {
9185 self.inner.write_at(offset, buf)?;
9186 }
9187 Ok(())
9188 }
9189 fn flush(&self) -> Result<()> {
9190 Ok(())
9191 }
9192 fn is_writable(&self) -> bool {
9193 true
9194 }
9195 }
9196
9197 #[test]
9212 fn unjournaled_orphan_recovery_survives_a_cut_after_every_write() {
9213 let dev = formatted();
9214 let planted = {
9215 let fs = mount(&dev);
9216 assert!(
9217 fs.journal.is_none(),
9218 "fixture: this path is the unjournaled one"
9219 );
9220 let ino = fs.apply_create("/gone.txt", 0o644).expect("create");
9221 fs.apply_pwrite("/gone.txt", 0, &[0xAB; 4 * BS as usize])
9222 .expect("write");
9223 ino
9224 };
9225 {
9226 let fs = mount(&dev);
9229 let (root, _) = fs.read_inode_verified(2).expect("root");
9230 let mut buf = BlockBuffer::new(fs.sb.block_size());
9231 fs.buffer_remove_dir_entry(&mut buf, 2, &root, b"gone.txt")
9232 .expect("remove the name");
9233 fs.commit_block_buffer(buf).expect("commit");
9234 plant_orphan(&fs, planted, 0, None);
9235 }
9236 let snapshot = dev.bytes.lock().unwrap().clone();
9237
9238 let mut total = None;
9239 for cut in 0.. {
9240 let image = MemDev::new(VOL);
9241 *image.bytes.lock().unwrap() = snapshot.clone();
9242 let crash = std::sync::Arc::new(CrashDev {
9243 inner: image.clone(),
9244 budget: cut,
9245 writes: std::sync::atomic::AtomicUsize::new(0),
9246 });
9247 drop(Filesystem::mount(crash.clone()).expect("mount through the cut"));
9248 let written = crash.writes.load(std::sync::atomic::Ordering::SeqCst);
9249 total.get_or_insert(written);
9250
9251 {
9252 let ro = std::sync::Arc::new(RoDev(image.clone()));
9253 let fs = Filesystem::mount(ro).expect("read-only mount of the cut image");
9254 let (inode, _) = fs.read_inode_verified(planted).expect("read orphan");
9255 if fs.sb.last_orphan == 0 {
9256 assert!(
9257 inode.dtime != 0 && inode.size == 0,
9258 "cut {cut}: the chain head is cleared but the orphan is not deleted \
9259 (dtime {}, size {}); no mount will retry it",
9260 inode.dtime,
9261 inode.size
9262 );
9263 }
9264 }
9265 {
9266 drop(mount(&image));
9269 let fs = mount(&image);
9270 assert!(
9271 fs.orphan_list()
9272 .unwrap_or_else(|e| panic!("cut {cut}: orphan_list: {e:?}"))
9273 .is_empty(),
9274 "cut {cut}: the next mount left the orphan on the chain"
9275 );
9276 let report = crate::fsck::audit(&fs, u32::MAX, u32::MAX).expect("audit");
9280 let serious: Vec<_> = report
9281 .anomalies
9282 .iter()
9283 .filter(|a| {
9284 !matches!(
9285 a,
9286 crate::fsck::Anomaly::BlockGroupFreeCountDrift { .. }
9287 | crate::fsck::Anomaly::SuperblockFreeCountDrift { .. }
9288 )
9289 })
9290 .collect();
9291 assert!(
9292 serious.is_empty(),
9293 "cut {cut}: after the retry, fsck finds {serious:?}"
9294 );
9295 assert!(
9300 !fs.inode_bit_is_set(planted).expect("inode bitmap"),
9301 "cut {cut}: after the retry the orphan is still allocated"
9302 );
9303 }
9304 if cut >= written {
9305 break;
9306 }
9307 }
9308 assert!(
9309 total.unwrap_or(0) > 1,
9310 "the recovery commit must span several writes to cut"
9311 );
9312 }
9313
9314 #[test]
9332 fn journaled_orphan_recovery_survives_a_cut_after_every_write() {
9333 const JVOL: u64 = 64 * 1024 * 1024;
9334 let dev = MemDev::new(JVOL);
9335 crate::mkfs::format_filesystem_with_flavor(
9336 dev.as_ref(),
9337 Some("jorphan"),
9338 None,
9339 JVOL,
9340 BS,
9341 crate::features::FsFlavor::Ext3,
9342 )
9343 .expect("format");
9344 let planted = {
9345 let fs = mount(&dev);
9346 assert!(
9347 fs.journal.is_some(),
9348 "fixture: this path is the journaled one"
9349 );
9350 let ino = fs.apply_create("/gone.txt", 0o644).expect("create");
9351 let (root, _) = fs.read_inode_verified(2).expect("root");
9352 let mut buf = BlockBuffer::new(fs.sb.block_size());
9353 fs.buffer_remove_dir_entry(&mut buf, 2, &root, b"gone.txt")
9354 .expect("remove the name");
9355 fs.commit_block_buffer(buf).expect("commit");
9356 plant_orphan(&fs, ino, 0, None);
9357 ino
9358 };
9359 let snapshot = dev.bytes.lock().unwrap().clone();
9360
9361 let mut total = None;
9362 for cut in 0.. {
9363 let image = MemDev::new(JVOL);
9364 *image.bytes.lock().unwrap() = snapshot.clone();
9365 let crash = std::sync::Arc::new(CrashDev {
9366 inner: image.clone(),
9367 budget: cut,
9368 writes: std::sync::atomic::AtomicUsize::new(0),
9369 });
9370 drop(Filesystem::mount(crash.clone()).expect("mount through the cut"));
9371 let written = crash.writes.load(std::sync::atomic::Ordering::SeqCst);
9372 total.get_or_insert(written);
9373
9374 drop(mount(&image));
9376 let fs = mount(&image);
9377 assert!(
9378 fs.orphan_list()
9379 .unwrap_or_else(|e| panic!("cut {cut}: orphan_list: {e:?}"))
9380 .is_empty(),
9381 "cut {cut}: the next mount left the orphan on the chain"
9382 );
9383 assert!(
9384 !fs.inode_bit_is_set(planted).expect("inode bitmap"),
9385 "cut {cut}: after the retry the orphan is still allocated"
9386 );
9387 let report = crate::fsck::audit(&fs, u32::MAX, u32::MAX).expect("audit");
9388 let serious: Vec<_> = report
9389 .anomalies
9390 .iter()
9391 .filter(|a| {
9392 !matches!(
9393 a,
9394 crate::fsck::Anomaly::BlockGroupFreeCountDrift { .. }
9395 | crate::fsck::Anomaly::SuperblockFreeCountDrift { .. }
9396 )
9397 })
9398 .collect();
9399 assert!(
9400 serious.is_empty(),
9401 "cut {cut}: after the retry, fsck finds {serious:?}"
9402 );
9403 fs.apply_create("/after.txt", 0o644)
9405 .unwrap_or_else(|e| panic!("cut {cut}: a write after recovery failed: {e:?}"));
9406 if cut >= written {
9407 break;
9408 }
9409 }
9410 assert!(
9411 total.unwrap_or(0) > 1,
9412 "the recovery commit must span several writes to cut"
9413 );
9414 }
9415
9416 #[test]
9427 fn unjournaled_recovery_of_a_chain_across_groups_survives_a_cut_after_every_write() {
9428 const TWO_GROUPS: u64 = 160 * 1024 * 1024;
9431 let dev = MemDev::new(TWO_GROUPS);
9432 crate::mkfs::format_filesystem(dev.as_ref(), Some("orphans"), None, TWO_GROUPS, BS)
9433 .expect("format");
9434 let members = {
9435 let fs = mount(&dev);
9436 assert!(fs.journal.is_none(), "fixture: the unjournaled path");
9437 let ipg = fs.sb.inodes_per_group;
9438 let mut by_group = std::collections::BTreeMap::new();
9439 for d in 0..8 {
9440 let fs = mount(&dev);
9443 fs.apply_mkdir(&format!("/d{d}"), 0o755).expect("mkdir");
9444 let path = format!("/d{d}/gone");
9445 let ino = fs.apply_create(&path, 0o644).expect("create");
9446 fs.apply_pwrite(&path, 0, &[0xCD; 3000]).expect("write");
9447 by_group.entry((ino - 1) / ipg).or_insert((d, ino));
9448 }
9449 let members: Vec<(usize, u32)> = by_group.into_values().take(3).collect();
9450 assert!(
9451 members.len() >= 2,
9452 "fixture: the orphans must sit in different groups, got {by_group_len}",
9453 by_group_len = members.len()
9454 );
9455 members
9456 };
9457 {
9458 let fs = mount(&dev);
9461 for &(d, _) in &members {
9462 let dir = resolve_ino(&fs, &format!("/d{d}"));
9463 let (parent, _) = fs.read_inode_verified(dir).expect("dir");
9464 let mut buf = BlockBuffer::new(fs.sb.block_size());
9465 fs.buffer_remove_dir_entry(&mut buf, dir, &parent, b"gone")
9466 .expect("remove the name");
9467 fs.commit_block_buffer(buf).expect("commit");
9468 }
9469 let mut buf = BlockBuffer::new(fs.sb.block_size());
9470 for (i, &(_, ino)) in members.iter().enumerate() {
9471 let next = members.get(i + 1).map_or(0, |&(_, n)| n);
9472 let (inode, mut raw) = fs.read_inode_verified(ino).expect("read");
9473 raw[0x1A..0x1C].copy_from_slice(&0u16.to_le_bytes());
9474 raw[0x14..0x18].copy_from_slice(&next.to_le_bytes());
9475 fs.finalize_inode_raw(ino, inode.generation, &mut raw)
9476 .expect("finalize");
9477 fs.buffer_write_inode(&mut buf, ino, &raw).expect("write");
9478 }
9479 fs.buffer_patch_sb_last_orphan(&mut buf, members[0].1)
9480 .expect("head");
9481 fs.commit_block_buffer(buf).expect("commit");
9482 }
9483 let snapshot = dev.bytes.lock().unwrap().clone();
9484
9485 let mut total = None;
9486 for cut in 0.. {
9487 let image = MemDev::new(TWO_GROUPS);
9488 *image.bytes.lock().unwrap() = snapshot.clone();
9489 let crash = std::sync::Arc::new(CrashDev {
9490 inner: image.clone(),
9491 budget: cut,
9492 writes: std::sync::atomic::AtomicUsize::new(0),
9493 });
9494 drop(Filesystem::mount(crash.clone()).expect("mount through the cut"));
9495 let written = crash.writes.load(std::sync::atomic::Ordering::SeqCst);
9496 total.get_or_insert(written);
9497
9498 drop(mount(&image));
9500 let fs = mount(&image);
9501 assert!(
9502 fs.orphan_list()
9503 .unwrap_or_else(|e| panic!("cut {cut}: orphan_list: {e:?}"))
9504 .is_empty(),
9505 "cut {cut}: the chain was not cleared"
9506 );
9507 for &(_, ino) in &members {
9508 assert!(
9509 !fs.inode_bit_is_set(ino).expect("inode bitmap"),
9510 "cut {cut}: orphan {ino} is still allocated and named by nothing"
9511 );
9512 }
9513 let report = crate::fsck::audit(&fs, u32::MAX, u32::MAX).expect("audit");
9514 let serious: Vec<_> = report
9515 .anomalies
9516 .iter()
9517 .filter(|a| {
9518 !matches!(
9519 a,
9520 crate::fsck::Anomaly::BlockGroupFreeCountDrift { .. }
9521 | crate::fsck::Anomaly::SuperblockFreeCountDrift { .. }
9522 )
9523 })
9524 .collect();
9525 assert!(
9526 serious.is_empty(),
9527 "cut {cut}: after the retry, fsck finds {serious:?}"
9528 );
9529 if cut >= written {
9530 break;
9531 }
9532 }
9533 assert!(total.unwrap_or(0) > 2, "recovery must span several writes");
9534 }
9535
9536 #[test]
9542 fn an_orphan_whose_blocks_cannot_be_reclaimed_stays_whole_at_the_head() {
9543 let dev = formatted();
9544 let ino = {
9545 let fs = mount(&dev);
9546 let ino = fs.apply_create("/gone.txt", 0o644).expect("create");
9547 fs.apply_pwrite("/gone.txt", 0, &[0xAB; 3 * BS as usize])
9548 .expect("write");
9549 ino
9550 };
9551 {
9552 let fs = mount(&dev);
9553 let (root, _) = fs.read_inode_verified(2).expect("root");
9554 let mut buf = BlockBuffer::new(fs.sb.block_size());
9555 fs.buffer_remove_dir_entry(&mut buf, 2, &root, b"gone.txt")
9556 .expect("remove the name");
9557 let (inode, mut raw) = fs.read_inode_verified(ino).expect("read");
9560 let flags = inode.flags & !crate::inode::InodeFlags::EXTENTS.bits();
9561 raw[0x20..0x24].copy_from_slice(&flags.to_le_bytes());
9562 fs.finalize_inode_raw(ino, inode.generation, &mut raw)
9563 .expect("finalize");
9564 fs.buffer_write_inode(&mut buf, ino, &raw)
9565 .expect("write inode");
9566 fs.commit_block_buffer(buf).expect("commit");
9567 plant_orphan(&fs, ino, 0, None);
9568 let (inode, _) = fs.read_inode_verified(ino).expect("read");
9569 assert!(
9570 !inode.has_extents() && inode.blocks > 0,
9571 "fixture: a non-extent orphan holding blocks"
9572 );
9573 }
9574 drop(mount(&dev));
9576 let fs = mount(&dev);
9577 assert_eq!(fs.sb.last_orphan, ino, "the orphan left the chain");
9578 assert!(
9579 fs.inode_bit_is_set(ino).expect("bitmap"),
9580 "the orphan's inode was freed without its blocks"
9581 );
9582 let (inode, _) = fs.read_inode_verified(ino).expect("read");
9583 assert!(inode.blocks > 0, "the orphan's body was zeroed");
9584 }
9585
9586 fn resolve_ino(fs: &Filesystem, path: &str) -> u32 {
9587 let mut reader = |ino: u32| fs.read_inode_verified(ino).map(|(inode, _)| inode);
9588 crate::path::lookup(fs.dev.as_ref(), &fs.sb, &mut reader, path).expect("resolve")
9589 }
9590
9591 struct RoDev(std::sync::Arc<MemDev>);
9598
9599 impl crate::block_io::BlockDevice for RoDev {
9600 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
9601 self.0.read_at(offset, buf)
9602 }
9603 fn size_bytes(&self) -> u64 {
9604 self.0.size_bytes()
9605 }
9606 fn write_at(&self, _offset: u64, _buf: &[u8]) -> Result<()> {
9607 Err(Error::ReadOnly)
9608 }
9609 fn flush(&self) -> Result<()> {
9610 Ok(())
9611 }
9612 fn is_writable(&self) -> bool {
9613 false
9614 }
9615 }
9616
9617 fn set_incompat_bit(dev: &std::sync::Arc<MemDev>, bit: u32) {
9620 let mut sb = vec![0u8; 1024];
9621 dev.read_at(crate::superblock::SUPERBLOCK_OFFSET, &mut sb)
9622 .expect("read sb");
9623 let cur = u32::from_le_bytes(sb[0x60..0x64].try_into().unwrap());
9624 sb[0x60..0x64].copy_from_slice(&(cur | bit).to_le_bytes());
9625 let csum = crate::checksum::linux_crc32c(!0, &sb[..0x3FC]);
9626 sb[0x3FC..0x400].copy_from_slice(&csum.to_le_bytes());
9627 dev.write_at(crate::superblock::SUPERBLOCK_OFFSET, &sb)
9628 .expect("write sb");
9629 }
9630
9631 fn make_inline_dir(fs: &Filesystem, dir: u32, parent: u32, entry: Option<(u32, &[u8], u8)>) {
9636 let (inode, mut raw) = fs.read_inode_verified(dir).unwrap();
9637 let mut flags = u32::from_le_bytes(raw[0x20..0x24].try_into().unwrap());
9638 flags &= !crate::inode::InodeFlags::EXTENTS.bits();
9639 flags |= crate::inode::InodeFlags::INLINE_DATA.bits();
9640 raw[0x20..0x24].copy_from_slice(&flags.to_le_bytes());
9641 raw[0x28..0x64].fill(0);
9642 raw[0x28..0x2C].copy_from_slice(&parent.to_le_bytes());
9643 let (ino, name, ft) = entry.unwrap_or((0, b"", 0));
9644 raw[0x2C..0x30].copy_from_slice(&ino.to_le_bytes());
9645 raw[0x30..0x32].copy_from_slice(&56u16.to_le_bytes());
9646 raw[0x32] = name.len() as u8;
9647 raw[0x33] = ft;
9648 raw[0x34..0x34 + name.len()].copy_from_slice(name);
9649 raw[0x04..0x08].copy_from_slice(&60u32.to_le_bytes());
9650 fs.finalize_inode_raw(dir, inode.generation, &mut raw)
9651 .unwrap();
9652 fs.write_inode_raw(dir, &raw).unwrap();
9653 }
9654
9655 fn outside_superblock(dev: &std::sync::Arc<MemDev>) -> Vec<u8> {
9658 let mut bytes = dev.bytes.lock().unwrap().clone();
9659 bytes[1024..2048].fill(0);
9660 bytes
9661 }
9662
9663 fn inline_dir_volume() -> std::sync::Arc<MemDev> {
9667 let dev = formatted();
9668 set_incompat_bit(&dev, crate::features::Incompat::INLINE_DATA.bits());
9669 let fs = mount(&dev);
9670 fs.apply_mkdir("/a", 0o755).unwrap();
9671 fs.apply_mkdir("/b", 0o755).unwrap();
9672 fs.apply_mkdir("/a/sub", 0o755).unwrap();
9673 fs.apply_mkdir("/a/empty", 0o755).unwrap();
9674 fs.apply_create("/f2", 0o644).unwrap();
9675 fs.apply_create("/b/y", 0o644).unwrap();
9676 let a = resolve(&fs, "/a").unwrap();
9677 let sub = resolve(&fs, "/a/sub").unwrap();
9678 let empty = resolve(&fs, "/a/empty").unwrap();
9679 let f2 = resolve(&fs, "/f2").unwrap();
9680 make_inline_dir(&fs, sub, a, Some((f2, b"f", 1)));
9681 make_inline_dir(&fs, empty, a, None);
9682 let (i, raw) = fs.read_inode_verified(f2).unwrap();
9683 let mut raw = raw.clone();
9684 raw[0x1A..0x1C].copy_from_slice(&2u16.to_le_bytes());
9685 fs.finalize_inode_raw(f2, i.generation, &mut raw).unwrap();
9686 fs.write_inode_raw(f2, &raw).unwrap();
9687 drop(fs);
9688 dev
9689 }
9690
9691 fn names_in(fs: &Filesystem, path: &str) -> (Vec<String>, bool) {
9694 let ino = resolve(fs, path).unwrap();
9695 let mut names: Vec<String> = fs
9696 .read_dir_ino(ino)
9697 .unwrap()
9698 .into_iter()
9699 .map(|e| String::from_utf8_lossy(&e.name).into_owned())
9700 .filter(|n| n != "." && n != "..")
9701 .collect();
9702 names.sort();
9703 (names, fs.stat_ino(ino).unwrap().has_inline_data())
9704 }
9705
9706 fn links(fs: &Filesystem, path: &str) -> u16 {
9707 fs.stat_ino(resolve(fs, path).unwrap()).unwrap().links_count
9708 }
9709
9710 #[test]
9714 fn renaming_an_inline_directory_across_parents_rewrites_its_parent_number() {
9715 let dev = inline_dir_volume();
9716 let fs = mount(&dev);
9717 let (a, b) = (links(&fs, "/a"), links(&fs, "/b"));
9718 fs.apply_rename("/a/sub", "/b/sub", false).unwrap();
9719 drop(fs);
9720 let fs = mount(&dev);
9721 assert_eq!(
9722 resolve(&fs, "/b/sub/..").unwrap(),
9723 resolve(&fs, "/b").unwrap()
9724 );
9725 assert_eq!(names_in(&fs, "/b/sub"), (vec!["f".into()], true));
9726 assert_eq!((links(&fs, "/a"), links(&fs, "/b")), (a - 1, b + 1));
9727 let report = crate::fsck::audit(&fs, u32::MAX, u32::MAX).unwrap();
9728 assert!(report.anomalies.is_empty(), "{:?}", report.anomalies);
9729 }
9730
9731 #[test]
9736 fn every_mutation_of_an_inline_directory_succeeds() {
9737 type Op = fn(&Filesystem) -> Result<()>;
9738 type Check = fn(&Filesystem);
9739 fn ino(fs: &Filesystem, path: &str) -> u32 {
9740 resolve(fs, path).unwrap()
9741 }
9742 let ops: [(&str, Op, Check); 16] = [
9743 (
9744 "create inside",
9745 |fs| fs.apply_create("/a/sub/g", 0o644).map(drop),
9746 |fs| assert_eq!(names_in(fs, "/a/sub"), (vec!["f".into(), "g".into()], true)),
9747 ),
9748 (
9749 "create_at inside",
9750 |fs| fs.apply_create_at(ino(fs, "/a/sub"), b"g", 0o644).map(drop),
9751 |fs| assert_eq!(names_in(fs, "/a/sub"), (vec!["f".into(), "g".into()], true)),
9752 ),
9753 (
9754 "mkdir inside",
9755 |fs| fs.apply_mkdir("/a/sub/h", 0o755).map(drop),
9756 |fs| {
9757 assert_eq!(names_in(fs, "/a/sub").0, ["f", "h"]);
9758 assert_eq!(links(fs, "/a/sub"), 3);
9759 assert_eq!(ino(fs, "/a/sub/h/.."), ino(fs, "/a/sub"));
9760 },
9761 ),
9762 (
9763 "mkdir_at inside an empty one",
9764 |fs| {
9765 fs.apply_mkdir_at(ino(fs, "/a/empty"), b"h", 0o755)
9766 .map(drop)
9767 },
9768 |fs| {
9769 assert_eq!(names_in(fs, "/a/empty"), (vec!["h".into()], true));
9770 assert_eq!(links(fs, "/a/empty"), 3);
9771 },
9772 ),
9773 (
9774 "symlink inside",
9775 |fs| fs.apply_symlink("t", "/a/sub/s").map(drop),
9776 |fs| assert_eq!(fs.read_link(ino(fs, "/a/sub/s")).unwrap(), b"t"),
9777 ),
9778 (
9779 "link into",
9780 |fs| fs.apply_link("/f2", "/a/sub/l"),
9781 |fs| {
9782 assert_eq!(ino(fs, "/a/sub/l"), ino(fs, "/f2"));
9783 assert_eq!(links(fs, "/f2"), 3);
9784 },
9785 ),
9786 (
9787 "unlink inside",
9788 |fs| fs.apply_unlink("/a/sub/f"),
9789 |fs| {
9790 assert_eq!(names_in(fs, "/a/sub"), (vec![], true));
9791 assert_eq!(links(fs, "/f2"), 1);
9792 },
9793 ),
9794 (
9795 "unlink_at inside",
9796 |fs| fs.apply_unlink_at(ino(fs, "/a/sub"), b"f"),
9797 |fs| assert_eq!(names_in(fs, "/a/sub"), (vec![], true)),
9798 ),
9799 (
9800 "rename out of",
9801 |fs| fs.apply_rename("/a/sub/f", "/b/f", false),
9802 |fs| {
9803 assert_eq!(names_in(fs, "/a/sub"), (vec![], true));
9804 assert_eq!(ino(fs, "/b/f"), ino(fs, "/f2"));
9805 },
9806 ),
9807 (
9808 "rename into",
9809 |fs| fs.apply_rename("/b/y", "/a/sub/y", false),
9810 |fs| assert_eq!(names_in(fs, "/a/sub").0, ["f", "y"]),
9811 ),
9812 (
9813 "rename within",
9814 |fs| {
9815 let sub = ino(fs, "/a/sub");
9816 fs.apply_rename_at(sub, b"f", sub, b"renamed", false)
9817 },
9818 |fs| assert_eq!(names_in(fs, "/a/sub"), (vec!["renamed".into()], true)),
9819 ),
9820 (
9821 "rename over an empty one",
9822 |fs| fs.apply_rename("/b", "/a/empty", true),
9823 |fs| {
9824 assert_eq!(names_in(fs, "/a/empty"), (vec!["y".into()], false));
9825 assert!(matches!(resolve(fs, "/b"), Err(Error::NotFound)));
9826 },
9827 ),
9828 (
9829 "rmdir",
9830 |fs| fs.apply_rmdir("/a/empty"),
9831 |fs| assert_eq!(names_in(fs, "/a").0, ["sub"]),
9832 ),
9833 (
9834 "rmdir_at inside",
9835 |fs| {
9836 fs.apply_mkdir("/a/sub/h", 0o755)?;
9837 fs.apply_rmdir_at(ino(fs, "/a/sub"), b"h")
9838 },
9839 |fs| {
9840 assert_eq!(names_in(fs, "/a/sub"), (vec!["f".into()], true));
9841 assert_eq!(links(fs, "/a/sub"), 2);
9842 },
9843 ),
9844 (
9845 "entries past what the inode holds",
9846 |fs| {
9847 for i in 0..12 {
9848 fs.apply_create(&format!("/a/sub/file-number-{i:02}"), 0o644)?;
9849 }
9850 Ok(())
9851 },
9852 |fs| {
9853 let (names, inline) = names_in(fs, "/a/sub");
9854 assert!(!inline, "twelve more entries converted it to a block");
9855 assert_eq!(names.len(), 13);
9856 assert_eq!(ino(fs, "/a/sub/f"), ino(fs, "/f2"));
9857 assert_eq!(ino(fs, "/a/sub/.."), ino(fs, "/a"));
9858 },
9859 ),
9860 (
9861 "rename within, converting",
9862 |fs| {
9863 let sub = ino(fs, "/a/sub");
9864 fs.apply_rename_at(sub, b"f", sub, &[b'n'; 200], false)
9865 },
9866 |fs| {
9867 let (names, inline) = names_in(fs, "/a/sub");
9868 assert_eq!((names, inline), (vec!["n".repeat(200)], false));
9869 },
9870 ),
9871 ];
9872 for (name, op, check) in ops {
9873 let dev = inline_dir_volume();
9874 let fs = mount(&dev);
9875 op(&fs).unwrap_or_else(|e| panic!("{name}: {e:?}"));
9876 drop(fs);
9877 let fs = mount(&dev);
9878 check(&fs);
9879 let report = crate::fsck::audit(&fs, u32::MAX, u32::MAX).unwrap();
9880 assert!(
9881 report.anomalies.is_empty(),
9882 "{name}: {:?}",
9883 report.anomalies
9884 );
9885 }
9886 }
9887
9888 #[test]
9891 fn a_non_empty_inline_directory_is_not_removed() {
9892 let dev = inline_dir_volume();
9893 let before = outside_superblock(&dev);
9894 let fs = mount(&dev);
9895 assert!(matches!(
9896 fs.apply_rmdir("/a/sub"),
9897 Err(Error::DirectoryNotEmpty)
9898 ));
9899 assert!(matches!(
9900 fs.apply_rename("/b", "/a/sub", true),
9901 Err(Error::DirectoryNotEmpty)
9902 ));
9903 drop(fs);
9904 assert!(outside_superblock(&dev) == before, "a refusal wrote");
9905 let fs = mount(&dev);
9906 assert_eq!(names_in(&fs, "/a/sub"), (vec!["f".into()], true));
9907 }
9908
9909 #[test]
9912 fn listxattr_into_a_short_buffer_writes_whole_names_only() {
9913 let dev = formatted();
9914 {
9915 let fs = mount(&dev);
9916 fs.apply_create("/x.txt", 0o644).expect("create");
9917 fs.apply_setxattr("/x.txt", "user.first", b"1")
9918 .expect("set");
9919 fs.apply_setxattr("/x.txt", "user.second", b"2")
9920 .expect("set");
9921 }
9922 let image = fs_ext4_test_support::temp_path!("fs_ext4_lx_whole_{}.img", std::process::id());
9923 std::fs::write(&image, &*dev.bytes.lock().unwrap()).expect("write image");
9924 let c_image = std::ffi::CString::new(image.clone()).unwrap();
9925 let c_path = std::ffi::CString::new("/x.txt").unwrap();
9926 unsafe {
9927 let handle = crate::capi::fs_ext4_mount(c_image.as_ptr());
9928 assert!(!handle.is_null(), "C mount");
9929 let needed =
9930 crate::capi::fs_ext4_listxattr(handle, c_path.as_ptr(), std::ptr::null_mut(), 0);
9931 let mut full = vec![0u8; needed as usize];
9932 crate::capi::fs_ext4_listxattr(
9933 handle,
9934 c_path.as_ptr(),
9935 full.as_mut_ptr().cast(),
9936 full.len(),
9937 );
9938 let first_len = full.iter().position(|&b| b == 0).unwrap() + 1;
9939 assert!((first_len as i64) < needed, "fixture: two names");
9940
9941 let mut short = vec![0xEEu8; first_len + 3];
9943 let got = crate::capi::fs_ext4_listxattr(
9944 handle,
9945 c_path.as_ptr(),
9946 short.as_mut_ptr().cast(),
9947 short.len(),
9948 );
9949 assert_eq!(got, needed, "the required size is still returned");
9950 assert_eq!(
9951 &short[..first_len],
9952 &full[..first_len],
9953 "the first name, whole"
9954 );
9955 assert_eq!(
9956 &short[first_len..],
9957 &[0xEE; 3],
9958 "no part of the second name"
9959 );
9960 crate::capi::fs_ext4_umount(handle);
9961 }
9962 let _ = std::fs::remove_file(&image);
9963 }
9964
9965 #[test]
9972 fn fsck_repair_refuses_a_volume_whose_features_refuse_writes() {
9973 let dev = formatted();
9974 let mut sb = vec![0u8; 1024];
9975 dev.read_at(crate::superblock::SUPERBLOCK_OFFSET, &mut sb)
9976 .unwrap();
9977 let cur = u32::from_le_bytes(sb[0x64..0x68].try_into().unwrap());
9978 let quota = crate::features::RoCompat::QUOTA.bits();
9979 sb[0x64..0x68].copy_from_slice(&(cur | quota).to_le_bytes());
9980 let csum = crate::checksum::linux_crc32c(!0, &sb[..0x3FC]);
9981 sb[0x3FC..0x400].copy_from_slice(&csum.to_le_bytes());
9982 dev.write_at(crate::superblock::SUPERBLOCK_OFFSET, &sb)
9983 .unwrap();
9984
9985 let fs = mount(&dev);
9986 assert!(
9987 matches!(
9988 fs.apply_create("/x", 0o644),
9989 Err(Error::UnsupportedRoCompat(_))
9990 ),
9991 "control: an ordinary write is refused on this volume"
9992 );
9993 fs.audit_repair(u32::MAX, u32::MAX, false)
9994 .expect("an audit without repair still runs");
9995 match fs.audit_repair(u32::MAX, u32::MAX, true) {
9996 Err(Error::UnsupportedRoCompat(bits)) => assert_eq!(bits & quota, quota),
9997 other => panic!("repair must be refused, got {:?}", other.map(|_| ())),
9998 }
9999 }
10000
10001 #[test]
10003 fn fsck_repair_still_runs_on_an_ordinary_volume() {
10004 let dev = formatted();
10005 let fs = mount(&dev);
10006 fs.audit_repair(u32::MAX, u32::MAX, true)
10007 .expect("repair on an ordinary volume");
10008 }
10009
10010 #[test]
10018 fn a_casefold_volume_is_not_mounted_writable() {
10019 let dev = formatted();
10020 set_incompat_bit(&dev, crate::features::Incompat::CASEFOLD.bits());
10021
10022 let err = match Filesystem::mount(dev.clone()) {
10023 Ok(_) => panic!("a writable mount of a CASEFOLD volume must be refused"),
10024 Err(e) => e,
10025 };
10026 match err {
10027 Error::UnsupportedIncompat(bits) => assert_eq!(
10028 bits,
10029 crate::features::Incompat::CASEFOLD.bits(),
10030 "the refusal must name the bit responsible"
10031 ),
10032 other => panic!("expected UnsupportedIncompat, got {other:?}"),
10033 }
10034 }
10035
10036 #[test]
10039 fn a_casefold_volume_still_mounts_read_only() {
10040 let dev = formatted();
10041 {
10042 let fs = mount(&dev);
10043 fs.apply_create("/before.txt", 0o644).expect("create");
10044 }
10045 set_incompat_bit(&dev, crate::features::Incompat::CASEFOLD.bits());
10046
10047 let ro = std::sync::Arc::new(RoDev(dev.clone()));
10048 let fs = Filesystem::mount(ro).expect("a read-only mount must still work");
10049 assert_eq!(
10050 resolve(&fs, "/before.txt").expect("the directory is still readable"),
10051 resolve(&fs, "/before.txt").expect("stable"),
10052 );
10053 }
10054
10055 struct LaterWritable {
10058 inner: std::sync::Arc<MemDev>,
10059 writable: std::sync::atomic::AtomicBool,
10060 }
10061
10062 impl crate::block_io::BlockDevice for LaterWritable {
10063 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
10064 self.inner.read_at(offset, buf)
10065 }
10066 fn size_bytes(&self) -> u64 {
10067 self.inner.size_bytes()
10068 }
10069 fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
10070 if !self.is_writable() {
10071 return Err(Error::ReadOnly);
10072 }
10073 self.inner.write_at(offset, buf)
10074 }
10075 fn flush(&self) -> Result<()> {
10076 Ok(())
10077 }
10078 fn is_writable(&self) -> bool {
10079 self.writable.load(std::sync::atomic::Ordering::SeqCst)
10080 }
10081 }
10082
10083 #[test]
10089 fn a_volume_that_becomes_writable_after_a_lazy_mount_still_refuses_write_breaking_features() {
10090 for bit in [
10091 crate::features::Incompat::CASEFOLD.bits(),
10092 crate::features::Incompat::MMP.bits(),
10093 ] {
10094 let dev = formatted();
10095 set_incompat_bit(&dev, bit);
10096 let later = std::sync::Arc::new(LaterWritable {
10097 inner: dev.clone(),
10098 writable: std::sync::atomic::AtomicBool::new(false),
10099 });
10100 let fs = Filesystem::mount_lazy(later.clone()).expect("a read-only lazy mount");
10101 later
10102 .writable
10103 .store(true, std::sync::atomic::Ordering::SeqCst);
10104 match fs.apply_create("/late.txt", 0o644) {
10105 Err(Error::UnsupportedIncompat(b)) => assert_eq!(b, bit),
10106 other => panic!("{bit:#x}: a write went through: {:?}", other.map(|_| ())),
10107 }
10108 }
10109 }
10110
10111 #[test]
10114 fn an_ordinary_volume_that_becomes_writable_after_a_lazy_mount_writes() {
10115 let dev = formatted();
10116 let later = std::sync::Arc::new(LaterWritable {
10117 inner: dev,
10118 writable: std::sync::atomic::AtomicBool::new(false),
10119 });
10120 let fs = Filesystem::mount_lazy(later.clone()).expect("a read-only lazy mount");
10121 later
10122 .writable
10123 .store(true, std::sync::atomic::Ordering::SeqCst);
10124 fs.apply_create("/late.txt", 0o644)
10125 .expect("an ordinary volume must accept the write");
10126 }
10127
10128 #[test]
10131 fn an_mmp_volume_is_still_not_mounted_writable() {
10132 let dev = formatted();
10133 set_incompat_bit(&dev, crate::features::Incompat::MMP.bits());
10134 let err = match Filesystem::mount(dev.clone()) {
10135 Ok(_) => panic!("a writable mount of an MMP volume must be refused"),
10136 Err(e) => e,
10137 };
10138 assert!(matches!(
10139 err,
10140 Error::UnsupportedIncompat(b) if b == crate::features::Incompat::MMP.bits()
10141 ));
10142 }
10143
10144 #[test]
10148 fn an_ordinary_volume_still_mounts_writable() {
10149 let dev = formatted();
10150 assert_eq!(
10151 Superblock::read(dev.as_ref()).unwrap().last_orphan,
10152 0,
10153 "new formatter must not overlap hash seed with orphan head"
10154 );
10155 let fs = Filesystem::mount(dev.clone()).expect("mount");
10156 fs.apply_create("/after.txt", 0o644).expect("create");
10157 }
10158
10159 const REPLAY_TARGET_BLOCK: u64 = 4000;
10186
10187 fn ext3_with_a_dirty_journal() -> (std::sync::Arc<MemDev>, Vec<u8>) {
10194 let dev = MemDev::new(VOL);
10195 crate::mkfs::format_filesystem_with_flavor(
10196 dev.as_ref(),
10197 Some("replay"),
10198 None,
10199 VOL,
10200 BS,
10201 crate::features::FsFlavor::Ext3,
10202 )
10203 .expect("format ext3");
10204
10205 let payload = {
10206 let fs = Filesystem::mount(dev.clone()).expect("mount to plant the journal");
10207 let block_size = fs.sb.block_size() as u64;
10208
10209 let raw = fs
10210 .read_inode_raw(fs.sb.journal_inode)
10211 .expect("read the journal inode");
10212 let jinode = crate::inode::Inode::parse(&raw).expect("parse the journal inode");
10213 let jsb = crate::jbd2::read_superblock(&fs)
10214 .expect("read the journal superblock")
10215 .expect("ext3 has a journal");
10216
10217 let mut tx = crate::transaction::Transaction::begin(
10219 jsb.sequence,
10220 block_size as u32,
10221 jsb.uses_64bit(),
10222 jsb.feature_incompat & crate::jbd2::JbdIncompat::CSUM_V3.bits() != 0,
10223 );
10224 let payload: Vec<u8> = (0..block_size as usize)
10225 .map(|i| 0xA5u8.wrapping_add((i & 0xFF) as u8))
10226 .collect();
10227 tx.add_write(REPLAY_TARGET_BLOCK, payload.clone())
10228 .expect("add_write");
10229 let blocks = tx.commit().expect("commit");
10230 assert_eq!(blocks.len(), 3, "descriptor + data + commit");
10231
10232 for (i, blk) in blocks.iter().enumerate() {
10235 let phys = crate::jbd2::journal_block_to_physical(&fs, &jinode, (i as u64) + 1)
10236 .expect("map the journal block")
10237 .expect("the journal is contiguous, so it is mapped");
10238 fs.dev
10239 .write_at(phys * block_size, blk)
10240 .expect("write the journal slot");
10241 }
10242
10243 let jsb_phys = crate::jbd2::journal_block_to_physical(&fs, &jinode, 0)
10246 .expect("map the journal superblock")
10247 .expect("mapped");
10248 let mut jsb_bytes = vec![0u8; block_size as usize];
10249 fs.dev
10250 .read_at(jsb_phys * block_size, &mut jsb_bytes)
10251 .expect("read the journal superblock");
10252 assert_eq!(
10253 u32::from_be_bytes(jsb_bytes[0..4].try_into().unwrap()),
10254 crate::jbd2::JBD2_MAGIC_NUMBER,
10255 "the block the journal inode maps is not a JBD2 superblock"
10256 );
10257 jsb_bytes[0x1C..0x20].copy_from_slice(&1u32.to_be_bytes());
10258 fs.dev
10259 .write_at(jsb_phys * block_size, &jsb_bytes)
10260 .expect("write the journal superblock");
10261 fs.dev.flush().expect("flush");
10262 payload
10263 };
10264
10265 assert!(
10266 !destination_holds_the_payload(&dev, &payload),
10267 "the destination already holds the payload, so replaying could not be observed"
10268 );
10269 (dev, payload)
10270 }
10271
10272 fn destination_holds_the_payload(dev: &std::sync::Arc<MemDev>, payload: &[u8]) -> bool {
10278 let mut buf = vec![0u8; BS as usize];
10279 crate::block_io::BlockDevice::read_at(
10280 dev.as_ref(),
10281 REPLAY_TARGET_BLOCK * BS as u64,
10282 &mut buf,
10283 )
10284 .expect("read the destination block");
10285 buf == payload
10286 }
10287
10288 #[test]
10293 fn a_dirty_journal_is_replayed_at_mount() {
10294 let (dev, payload) = ext3_with_a_dirty_journal();
10295 let fs = Filesystem::mount(dev.clone()).expect("mount");
10296 drop(fs);
10297 assert!(
10298 destination_holds_the_payload(&dev, &payload),
10299 "mount did not replay a dirty journal"
10300 );
10301 }
10302
10303 #[test]
10310 fn an_unsupported_bit_is_refused_before_the_journal_is_replayed() {
10311 let (dev, payload) = ext3_with_a_dirty_journal();
10312 set_incompat_bit(&dev, crate::features::Incompat::CASEFOLD.bits());
10313
10314 match Filesystem::mount(dev.clone()) {
10315 Ok(_) => panic!("a writable mount of a CASEFOLD volume must be refused"),
10316 Err(Error::UnsupportedIncompat(bits)) => assert_eq!(
10317 bits,
10318 crate::features::Incompat::CASEFOLD.bits(),
10319 "the refusal must name the bit responsible"
10320 ),
10321 Err(other) => panic!("expected UnsupportedIncompat, got {other:?}"),
10322 }
10323
10324 assert!(
10325 !destination_holds_the_payload(&dev, &payload),
10326 "the journal was replayed into a volume the driver had already declined to mount"
10327 );
10328 }
10329
10330 #[test]
10335 fn a_lazy_mount_that_becomes_writable_does_not_replay_into_a_write_breaking_volume() {
10336 for bit in [0, crate::features::Incompat::MMP.bits()] {
10337 let (dev, payload) = ext3_with_a_dirty_journal();
10338 if bit != 0 {
10339 set_incompat_bit(&dev, bit);
10340 }
10341 let later = std::sync::Arc::new(LaterWritable {
10342 inner: dev.clone(),
10343 writable: std::sync::atomic::AtomicBool::new(false),
10344 });
10345 let mut fs = Filesystem::mount_lazy(later.clone()).expect("a read-only lazy mount");
10346 later
10347 .writable
10348 .store(true, std::sync::atomic::Ordering::SeqCst);
10349 let result = fs.replay_journal_if_dirty();
10350 if bit == 0 {
10351 assert_eq!(result.expect("the control replays"), 1);
10352 assert!(destination_holds_the_payload(&dev, &payload));
10353 } else {
10354 match result {
10355 Err(Error::UnsupportedIncompat(b)) => assert_eq!(b, bit),
10356 other => panic!("replay was not refused: {other:?}"),
10357 }
10358 assert!(
10359 !destination_holds_the_payload(&dev, &payload),
10360 "the journal was replayed into an MMP volume"
10361 );
10362 }
10363 }
10364 }
10365
10366 #[test]
10373 fn a_lazy_mount_refuses_writes_until_its_journal_is_replayed() {
10374 let (dev, payload) = ext3_with_a_dirty_journal();
10375 let fs = Filesystem::mount_lazy(dev.clone()).expect("lazy mount");
10376 let r = fs.apply_mkdir("/x", 0o755);
10377 drop(fs);
10378 let _ = Filesystem::mount(dev.clone());
10379 assert!(
10380 destination_holds_the_payload(&dev, &payload),
10381 "the unreplayed transaction was lost (write result: {r:?})"
10382 );
10383 }
10384
10385 #[test]
10389 fn a_lazy_mount_writes_once_its_journal_is_replayed() {
10390 let (dev, payload) = ext3_with_a_dirty_journal();
10391 let mut fs = Filesystem::mount_lazy(dev.clone()).expect("lazy mount");
10392 match fs.apply_mkdir("/x", 0o755) {
10393 Err(Error::JournalNotReplayed) => {}
10394 other => panic!("a write over an unreplayed journal: {other:?}"),
10395 }
10396 assert_eq!(fs.replay_journal_if_dirty().expect("replay"), 1);
10397 fs.apply_mkdir("/x", 0o755)
10398 .expect("the write is accepted once the journal is replayed");
10399 drop(fs);
10400 assert!(destination_holds_the_payload(&dev, &payload));
10401 }
10402
10403 #[test]
10406 fn a_lazy_mount_of_a_clean_journal_writes_without_a_replay() {
10407 let (dev, _payload) = ext3_with_a_dirty_journal();
10408 drop(Filesystem::mount(dev.clone()).expect("eager mount replays"));
10409 let fs = Filesystem::mount_lazy(dev.clone()).expect("lazy mount");
10410 fs.apply_mkdir("/x", 0o755)
10411 .expect("a clean journal needs no replay before a write");
10412 }
10413
10414 #[test]
10420 fn a_lazy_mount_that_becomes_writable_refuses_writes_until_its_journal_is_replayed() {
10421 let (dev, payload) = ext3_with_a_dirty_journal();
10422 let later = std::sync::Arc::new(LaterWritable {
10423 inner: dev.clone(),
10424 writable: std::sync::atomic::AtomicBool::new(false),
10425 });
10426 let mut fs = Filesystem::mount_lazy(later.clone()).expect("a read-only lazy mount");
10427 later
10428 .writable
10429 .store(true, std::sync::atomic::Ordering::SeqCst);
10430 match fs.apply_mkdir("/x", 0o755) {
10431 Err(Error::JournalNotReplayed) => {}
10432 other => panic!("a write over an unreplayed journal: {other:?}"),
10433 }
10434 assert_eq!(fs.replay_journal_if_dirty().expect("replay"), 1);
10435 assert!(destination_holds_the_payload(&dev, &payload));
10436 fs.apply_mkdir("/x", 0o755)
10437 .expect("the write is accepted once the journal is replayed");
10438 }
10439
10440 #[test]
10446 fn a_lazy_replay_leaves_the_writer_past_the_replayed_sequence() {
10447 let seq_after = |lazy: bool| {
10448 let (dev, _payload) = ext3_with_a_dirty_journal();
10449 let fs = if lazy {
10450 let mut fs = Filesystem::mount_lazy(dev.clone()).expect("lazy");
10451 assert_eq!(fs.replay_journal_if_dirty().expect("replay"), 1);
10452 fs
10453 } else {
10454 Filesystem::mount(dev.clone()).expect("eager")
10455 };
10456 fs.apply_chmod("/", 0o700).expect("chmod");
10457 crate::jbd2::read_superblock(&fs).unwrap().unwrap().sequence
10458 };
10459 assert_eq!(
10460 seq_after(true),
10461 seq_after(false),
10462 "lazy and eager must agree on the log's sequence"
10463 );
10464 }
10465
10466 #[test]
10471 fn a_lazy_mount_that_becomes_writable_journals_after_the_replay_call() {
10472 let (dev, _payload) = ext3_with_a_dirty_journal();
10473 let later = std::sync::Arc::new(LaterWritable {
10474 inner: dev.clone(),
10475 writable: std::sync::atomic::AtomicBool::new(false),
10476 });
10477 let mut fs = Filesystem::mount_lazy(later.clone()).expect("a read-only lazy mount");
10478 assert!(fs.journal.is_none(), "a read-only mount opens no writer");
10479 later
10480 .writable
10481 .store(true, std::sync::atomic::Ordering::SeqCst);
10482 assert_eq!(fs.replay_journal_if_dirty().expect("replay"), 1);
10483 assert!(
10484 fs.journal.is_some(),
10485 "the handle is writable now, and its writes must be journaled"
10486 );
10487 fs.apply_chmod("/", 0o700).expect("chmod");
10488 let jsb = crate::jbd2::read_superblock(&fs).unwrap().unwrap();
10489 let eager = {
10490 let (dev, _payload) = ext3_with_a_dirty_journal();
10491 let fs = Filesystem::mount(dev.clone()).expect("eager");
10492 fs.apply_chmod("/", 0o700).expect("chmod");
10493 crate::jbd2::read_superblock(&fs).unwrap().unwrap().sequence
10494 };
10495 assert_eq!(
10496 jsb.sequence, eager,
10497 "the chmod went through the journal, past the replayed sequence"
10498 );
10499 }
10500
10501 fn plant_ea_inode_xattr(fs: &Filesystem, ino: u32, name: &str, value_inum: u32, decoy: &[u8]) {
10511 let (inode, mut raw) = fs.read_inode_verified(ino).expect("read inode");
10512 let inode_size = fs.sb.inode_size as usize;
10513 let extra_isize = u16::from_le_bytes(raw[128..130].try_into().unwrap()) as usize;
10514 let start = 128 + extra_isize;
10515 {
10516 let region = &mut raw[start..inode_size];
10517 crate::xattr::plan_set_in_inode_region(region, name, decoy).expect("set xattr");
10518 let e = 4;
10523 region[e + 4..e + 8].copy_from_slice(&value_inum.to_le_bytes());
10524 }
10525 let mut buf = BlockBuffer::new(fs.sb.block_size());
10526 fs.finalize_inode_raw(ino, inode.generation, &mut raw)
10527 .expect("finalize");
10528 fs.buffer_write_inode(&mut buf, ino, &raw).expect("write");
10529 fs.commit_block_buffer(buf).expect("commit");
10530 }
10531
10532 fn mark_as_ea_inode(fs: &Filesystem, ino: u32) {
10535 let (inode, mut raw) = fs.read_inode_verified(ino).expect("read inode");
10536 let flags = u32::from_le_bytes(raw[0x20..0x24].try_into().unwrap());
10537 let flags = flags | crate::inode::InodeFlags::EA_INODE.bits();
10538 raw[0x20..0x24].copy_from_slice(&flags.to_le_bytes());
10539 let mut buf = BlockBuffer::new(fs.sb.block_size());
10540 fs.finalize_inode_raw(ino, inode.generation, &mut raw)
10541 .expect("finalize");
10542 fs.buffer_write_inode(&mut buf, ino, &raw).expect("write");
10543 fs.commit_block_buffer(buf).expect("commit");
10544 }
10545
10546 const DECOY: [u8; 64] = [0xEE; 64];
10547
10548 fn ea_inode_volume() -> (std::sync::Arc<MemDev>, Vec<u8>) {
10549 let dev = formatted();
10550 let real: Vec<u8> = (0..64u8)
10551 .map(|i| i.wrapping_mul(7).wrapping_add(3))
10552 .collect();
10553 {
10554 let fs = mount(&dev);
10555 fs.apply_create("/subject.txt", 0o644).expect("create");
10556 let ea = fs.apply_create("/value.bin", 0o644).expect("create ea");
10557 fs.apply_pwrite("/value.bin", 0, &real)
10558 .expect("write value");
10559 mark_as_ea_inode(&fs, ea);
10560 let subject = resolve(&fs, "/subject.txt").expect("resolve");
10561 plant_ea_inode_xattr(&fs, subject, "user.big", ea, &DECOY);
10562 }
10563 (dev, real)
10564 }
10565
10566 #[test]
10571 fn an_ea_inode_backed_value_is_read_from_the_inode_it_names() {
10572 let (dev, real) = ea_inode_volume();
10573 let fs = mount(&dev);
10574 let ino = resolve(&fs, "/subject.txt").expect("resolve");
10575 let (inode, raw) = fs.read_inode_verified(ino).expect("read inode");
10576
10577 let got = crate::xattr::get_resolved(&fs, &inode, &raw, "user.big")
10578 .expect("get")
10579 .expect("the attribute is present");
10580
10581 assert_ne!(
10582 got, DECOY,
10583 "the value was read from e_value_offs instead of from the EA inode"
10584 );
10585 assert_eq!(got, real, "the value must be the EA inode's file body");
10586 }
10587
10588 #[test]
10600 fn the_list_all_path_resolves_an_ea_inode_value_too() {
10601 let (dev, real) = ea_inode_volume();
10602 let fs = mount(&dev);
10603 let ino = resolve(&fs, "/subject.txt").expect("resolve");
10604 let (inode, raw) = fs.read_inode_verified(ino).expect("read inode");
10605
10606 let entries =
10607 crate::xattr::read_all_resolved(&fs, &inode, &raw).expect("read_all_resolved");
10608 let e = entries
10609 .iter()
10610 .find(|e| e.name == "user.big")
10611 .expect("the attribute is present");
10612
10613 assert!(
10614 !e.value.is_empty(),
10615 "the list-all path returned an empty value with a success return"
10616 );
10617 assert_ne!(
10618 e.value, DECOY,
10619 "the value was read from e_value_offs instead of from the EA inode"
10620 );
10621 assert_eq!(e.value, real, "the value must be the EA inode's file body");
10622 }
10623
10624 #[test]
10633 fn listing_names_does_not_read_an_unreadable_ea_inode_value() {
10634 let dev = formatted();
10635 {
10636 let fs = mount(&dev);
10637 fs.apply_create("/subject.txt", 0o644).expect("create");
10638 let not_ea = fs.apply_create("/plain.bin", 0o644).expect("create");
10639 let subject = resolve(&fs, "/subject.txt").expect("resolve");
10640 plant_ea_inode_xattr(&fs, subject, "user.big", not_ea, &DECOY);
10641 }
10642 let fs = mount(&dev);
10643 let ino = resolve(&fs, "/subject.txt").expect("resolve");
10644 let (inode, raw) = fs.read_inode_verified(ino).expect("read inode");
10645
10646 assert!(
10647 crate::xattr::read_all_resolved(&fs, &inode, &raw).is_err(),
10648 "control: the value behind this attribute cannot be read"
10649 );
10650 let names =
10651 crate::xattr::list_names(&fs, &inode, &raw).expect("listing names needs no value");
10652 assert!(names.iter().any(|n| n == "user.big"), "{names:?}");
10653 drop(fs);
10654
10655 let image =
10658 fs_ext4_test_support::temp_path!("fs_ext4_listxattr_{}.img", std::process::id());
10659 std::fs::write(&image, &*dev.bytes.lock().unwrap()).expect("write image");
10660 let c_image = std::ffi::CString::new(image.clone()).unwrap();
10661 let c_path = std::ffi::CString::new("/subject.txt").unwrap();
10662 unsafe {
10663 let handle = crate::capi::fs_ext4_mount(c_image.as_ptr());
10664 assert!(!handle.is_null(), "C mount");
10665 let needed =
10666 crate::capi::fs_ext4_listxattr(handle, c_path.as_ptr(), std::ptr::null_mut(), 0);
10667 assert!(needed > 0, "the probe returned {needed}");
10668 let mut buf = vec![0u8; needed as usize];
10669 let wrote = crate::capi::fs_ext4_listxattr(
10670 handle,
10671 c_path.as_ptr(),
10672 buf.as_mut_ptr().cast(),
10673 buf.len(),
10674 );
10675 assert_eq!(wrote, needed);
10676 assert!(
10677 buf.split(|&b| b == 0).any(|n| n == b"user.big"),
10678 "{:?}",
10679 String::from_utf8_lossy(&buf)
10680 );
10681 crate::capi::fs_ext4_umount(handle);
10682 }
10683 let _ = std::fs::remove_file(&image);
10684 }
10685
10686 #[test]
10691 fn the_buffer_level_parser_reports_the_pointer_and_no_value() {
10692 let (dev, _real) = ea_inode_volume();
10693 let fs = mount(&dev);
10694 let ino = resolve(&fs, "/subject.txt").expect("resolve");
10695 let (inode, raw) = fs.read_inode_verified(ino).expect("read inode");
10696
10697 let entries = crate::xattr::read_all(
10698 fs.dev.as_ref(),
10699 &inode,
10700 &raw,
10701 fs.sb.inode_size,
10702 fs.sb.block_size(),
10703 )
10704 .expect("read_all");
10705 let e = entries
10706 .iter()
10707 .find(|e| e.name == "user.big")
10708 .expect("the attribute is present");
10709
10710 assert_ne!(e.value_inum, 0, "the pointer must be reported");
10711 assert!(
10712 e.value.is_empty(),
10713 "the value must not be taken from e_value_offs; got {:?}",
10714 e.value
10715 );
10716 }
10717
10718 #[test]
10724 fn rewriting_an_attribute_area_holding_an_ea_inode_entry_is_refused() {
10725 let (dev, _real) = ea_inode_volume();
10726 let fs = mount(&dev);
10727 let ino = resolve(&fs, "/subject.txt").expect("resolve");
10728 let (_inode, mut raw) = fs.read_inode_verified(ino).expect("read inode");
10729 let inode_size = fs.sb.inode_size as usize;
10730 let extra_isize = u16::from_le_bytes(raw[128..130].try_into().unwrap()) as usize;
10731 let region = &mut raw[128 + extra_isize..inode_size];
10732
10733 let removed = crate::xattr::plan_remove_in_inode_region(region, "user.big");
10734 assert!(
10735 matches!(removed, Err(Error::Unsupported(_))),
10736 "removing it must be refused, not silently orphan the EA inode; got {removed:?}"
10737 );
10738
10739 let set = crate::xattr::plan_set_in_inode_region(region, "user.other", b"x");
10740 assert!(
10741 matches!(set, Err(Error::Unsupported(_))),
10742 "setting a DIFFERENT attribute must also be refused, because the rewrite \
10743 re-emits the EA-inode entry too; got {set:?}"
10744 );
10745 }
10746
10747 #[test]
10751 fn an_ordinary_inline_attribute_is_unaffected() {
10752 let dev = formatted();
10753 let fs = mount(&dev);
10754 let ino = fs.apply_create("/plain.txt", 0o644).expect("create");
10755 let (inode, mut raw) = fs.read_inode_verified(ino).expect("read inode");
10756 let inode_size = fs.sb.inode_size as usize;
10757 let extra_isize = u16::from_le_bytes(raw[128..130].try_into().unwrap()) as usize;
10758 {
10759 let region = &mut raw[128 + extra_isize..inode_size];
10760 crate::xattr::plan_set_in_inode_region(region, "user.small", b"hello")
10761 .expect("an ordinary set must still work");
10762 }
10763 let mut buf = BlockBuffer::new(fs.sb.block_size());
10764 fs.finalize_inode_raw(ino, inode.generation, &mut raw)
10765 .expect("finalize");
10766 fs.buffer_write_inode(&mut buf, ino, &raw).expect("write");
10767 fs.commit_block_buffer(buf).expect("commit");
10768
10769 let (inode, raw) = fs.read_inode_verified(ino).expect("re-read");
10770 let got = crate::xattr::get_resolved(&fs, &inode, &raw, "user.small")
10771 .expect("get")
10772 .expect("present");
10773 assert_eq!(got, b"hello");
10774 }
10775
10776 #[test]
10781 fn an_orphan_that_still_has_links_is_truncated_not_deleted() {
10782 let dev = formatted();
10783 let payload: Vec<u8> = (0..4 * BS as usize).map(|i| (i % 251) as u8).collect();
10784
10785 let ino = {
10786 let fs = mount(&dev);
10787 let ino = fs.apply_create("/keep.txt", 0o644).expect("create");
10788 fs.apply_pwrite("/keep.txt", 0, &payload).expect("write");
10789 fs.apply_link("/keep.txt", "/also-keep.txt").expect("link");
10790 ino
10791 };
10792
10793 let before_free = {
10794 let fs = mount(&dev);
10795 plant_orphan(&fs, ino, 2, Some(BS as u64));
10797 fs.sb.free_blocks_count
10798 };
10799
10800 drop(mount(&dev));
10802
10803 let fs = mount(&dev);
10804 assert!(
10805 fs.orphan_list().expect("orphan_list").is_empty(),
10806 "recovery must empty the chain"
10807 );
10808
10809 let (inode, _) = fs.read_inode_verified(ino).expect("read inode");
10810 assert_eq!(
10811 inode.links_count, 2,
10812 "the file is named twice and was never unlinked; recovery deleted it"
10813 );
10814 assert_eq!(inode.dtime, 0, "a live file must not be stamped as deleted");
10815 assert_eq!(
10816 inode.size, BS as u64,
10817 "the interrupted truncate should be finished, not undone"
10818 );
10819
10820 assert_eq!(resolve(&fs, "/keep.txt").expect("keep.txt"), ino);
10821 assert_eq!(resolve(&fs, "/also-keep.txt").expect("also-keep.txt"), ino);
10822
10823 let data = crate::file_io::read_all(&fs, &inode).expect("read back");
10824 assert_eq!(
10825 data.len(),
10826 BS as usize,
10827 "the surviving file is its first i_size bytes"
10828 );
10829 assert_eq!(
10830 &data[..],
10831 &payload[..BS as usize],
10832 "and those bytes are the ones that were written"
10833 );
10834
10835 assert_eq!(
10836 fs.sb.free_blocks_count,
10837 before_free + 3,
10838 "only the three blocks past the new EOF should have been freed"
10839 );
10840 }
10841
10842 #[test]
10849 fn an_interrupted_truncate_over_a_corrupt_deep_tree_stays_on_the_chain() {
10850 let dev = formatted();
10851 let ino = {
10852 let fs = mount(&dev);
10853 let ino = fs.apply_create("/deep.bin", 0o644).expect("create");
10854 for i in 0..12u64 {
10857 fs.apply_pwrite("/deep.bin", i * 2 * BS as u64, &[0x5a; BS as usize])
10858 .expect("write");
10859 }
10860 ino
10861 };
10862
10863 let (leaf, size_before) = {
10864 let fs = mount(&dev);
10865 let (inode, _) = fs.read_inode_verified(ino).expect("read inode");
10866 assert!(
10867 crate::extent::ExtentHeader::parse(&inode.block)
10868 .expect("root")
10869 .depth
10870 >= 1,
10871 "fixture: the tree is deeper than the inline root"
10872 );
10873 let leaf = crate::extent::ExtentIdx::parse(&inode.block[12..24])
10874 .expect("index")
10875 .leaf_block;
10876 plant_orphan(&fs, ino, 1, Some(BS as u64));
10877 (leaf, inode.size)
10878 };
10879 dev.bytes.lock().unwrap()[(leaf * BS as u64 + BS as u64 - 1) as usize] ^= 1;
10881
10882 let fs = mount(&dev);
10883 assert_eq!(
10884 fs.recover_orphans()
10885 .expect("an unplannable member is not an error"),
10886 0
10887 );
10888 assert_eq!(
10889 fs.orphan_list().expect("orphan_list"),
10890 vec![ino],
10891 "the member it could not reclaim stays at the head"
10892 );
10893 let (inode, _) = fs.read_inode_verified(ino).expect("read inode");
10894 assert_eq!(inode.links_count, 1, "the file is still named");
10895 assert_eq!(inode.size, BS as u64, "its lowered size is kept");
10896 assert_ne!(size_before, inode.size, "fixture: the size was lowered");
10897 }
10898
10899 #[test]
10905 fn unlinking_a_file_whose_extent_leaf_fails_its_checksum_is_refused() {
10906 let dev = formatted();
10907 {
10908 let fs = mount(&dev);
10909 fs.apply_create("/deep.bin", 0o644).expect("create");
10910 for i in 0..12u64 {
10911 fs.apply_pwrite("/deep.bin", i * 2 * BS as u64, &[0x5a; BS as usize])
10912 .expect("write");
10913 }
10914 }
10915 let leaf = {
10916 let fs = mount(&dev);
10917 let ino = resolve(&fs, "/deep.bin").expect("resolve");
10918 let (inode, _) = fs.read_inode_verified(ino).expect("read inode");
10919 crate::extent::ExtentIdx::parse(&inode.block[12..24])
10920 .expect("index")
10921 .leaf_block
10922 };
10923 dev.bytes.lock().unwrap()[(leaf * BS as u64 + BS as u64 - 1) as usize] ^= 1;
10924
10925 let fs = mount(&dev);
10926 let free_before = fs.sb.free_blocks_count;
10927 let got = fs.apply_unlink("/deep.bin");
10928 assert!(
10929 matches!(got, Err(Error::BadChecksum { .. })),
10930 "unlink over an unverified extent leaf: {got:?}"
10931 );
10932 drop(fs);
10933 let fs = mount(&dev);
10934 assert!(resolve(&fs, "/deep.bin").is_ok(), "the file is still named");
10935 assert_eq!(fs.sb.free_blocks_count, free_before, "nothing was freed");
10936 }
10937
10938 fn synthetic_inode() -> Vec<u8> {
10953 let mut raw = vec![0u8; 256];
10954 raw[0x00..0x02].copy_from_slice(&0x81A4u16.to_le_bytes()); raw[OFF_EXTRA_ISIZE..OFF_EXTRA_ISIZE + 2]
10956 .copy_from_slice(&EXTRA_ISIZE_DEFAULT.to_le_bytes());
10957 raw
10958 }
10959
10960 #[test]
10968 fn a_file_acl_block_above_2_32_survives_patch_inode_size_and_blocks() {
10969 let block: u64 = 0x0003_1234_5678; let mut raw = synthetic_inode();
10971
10972 Filesystem::write_file_acl(&mut raw, block).expect("write_file_acl");
10973 Filesystem::patch_inode_size_and_blocks(&mut raw, 4096, 0x0000_0002_0000_0008)
10974 .expect("patch_inode_size_and_blocks");
10975
10976 let inode = Inode::parse(&raw).expect("parse");
10977 assert_eq!(
10978 inode.file_acl, block,
10979 "i_file_acl read back as {:#x}, written as {:#x} — the high half is at \
10980 0x76..0x78 and the low at 0x68..0x6C",
10981 inode.file_acl, block
10982 );
10983 }
10984
10985 #[test]
10993 fn the_splice_leaves_i_blocks_hi_to_the_function_that_owns_it() {
10994 let mut raw = synthetic_inode();
10995 Filesystem::patch_inode_size_and_blocks(&mut raw, 4096, 0x0000_0002_0000_0008)
10996 .expect("patch");
10997 let blocks_hi_before = read_le16(&raw, 0x74);
10998 assert_eq!(
10999 blocks_hi_before, 2,
11000 "the fixture must set a nonzero blocks_hi"
11001 );
11002
11003 Filesystem::write_file_acl(&mut raw, 0x0003_1234_5678).expect("write_file_acl");
11004 assert_eq!(
11005 read_le16(&raw, 0x74),
11006 blocks_hi_before,
11007 "the file_acl splice wrote i_blocks_hi (0x74..0x76), which belongs to \
11008 patch_inode_size_and_blocks"
11009 );
11010 assert_eq!(
11011 Inode::parse(&raw).expect("parse").blocks,
11012 0x0000_0002_0000_0008,
11013 "and i_blocks reads back unchanged"
11014 );
11015 }
11016
11017 #[test]
11021 fn clearing_file_acl_clears_the_high_half_too() {
11022 let mut raw = synthetic_inode();
11029 raw[0x68..0x6C].copy_from_slice(&0x1234_5678u32.to_le_bytes());
11030 raw[0x76..0x78].copy_from_slice(&3u16.to_le_bytes());
11031 assert_eq!(
11032 Inode::parse(&raw).expect("parse").file_acl,
11033 0x0003_1234_5678,
11034 "the fixture must present an inode whose file_acl needs both halves"
11035 );
11036
11037 Filesystem::write_file_acl(&mut raw, 0).expect("clear");
11038 Filesystem::patch_inode_size_and_blocks(&mut raw, 4096, 0x0000_0002_0000_0008)
11039 .expect("patch");
11040 assert_eq!(
11041 Inode::parse(&raw).expect("parse").file_acl,
11042 0,
11043 "a freed external xattr block must leave no pointer behind — clearing only \
11044 i_file_acl_lo leaves file_acl == old_hi << 32, at a block already handed \
11045 back to the allocator"
11046 );
11047 }
11048
11049 #[test]
11052 fn a_small_file_acl_block_round_trips_as_it_always_did() {
11053 let mut raw = synthetic_inode();
11054 Filesystem::write_file_acl(&mut raw, 0x1234_5678).expect("write_file_acl");
11055 Filesystem::patch_inode_size_and_blocks(&mut raw, 4096, 8).expect("patch");
11056 assert_eq!(Inode::parse(&raw).expect("parse").file_acl, 0x1234_5678);
11057 }
11058
11059 #[test]
11064 fn an_inode_too_short_for_the_high_half_refuses_a_block_that_needs_one() {
11065 let mut short = vec![0u8; 0x76];
11066 assert!(
11067 Filesystem::write_file_acl(&mut short, 0x0003_1234_5678).is_err(),
11068 "a 0x76-byte inode has no room for 0x76..0x78 and must not truncate"
11069 );
11070 assert_eq!(
11071 read_le32(&short, 0x68),
11072 0,
11073 "a refused write must not leave the truncated low half behind"
11074 );
11075
11076 let mut short = vec![0u8; 0x76];
11079 Filesystem::write_file_acl(&mut short, 0x1234_5678).expect("the low half fits");
11080 assert_eq!(read_le32(&short, 0x68), 0x1234_5678);
11081
11082 let mut tiny = vec![0u8; 0x6B];
11083 assert!(
11084 Filesystem::write_file_acl(&mut tiny, 0).is_err(),
11085 "a buffer too short for even the low half must be refused"
11086 );
11087 }
11088
11089 fn half_word_writes(src: &str, field: &str) -> Vec<String> {
11113 let code: String = src
11114 .lines()
11115 .map(|line| line.find("//").map_or(line, |at| &line[..at]))
11116 .collect::<Vec<_>>()
11117 .join("\n");
11118 code.split(';')
11119 .map(|statement| statement.split_whitespace().collect::<Vec<_>>().join(" "))
11120 .filter(|statement| statement.contains(field) && statement.contains("copy_from_slice"))
11121 .collect()
11122 }
11123
11124 #[test]
11126 fn the_half_word_scan_reads_a_write_rustfmt_wrapped() {
11127 let wrapped = "
11128 let acl_lo_value_for_the_external_xattr_block: u32 = 0;
11129 raw[0x74..0x76]
11130 .copy_from_slice(&acl_hi_value_for_the_external_xattr_block.to_le_bytes());
11131 // raw[0x74..0x76].copy_from_slice(&in_a_comment);
11132 ";
11133 assert_eq!(half_word_writes(wrapped, "0x74..0x76").len(), 1);
11134 let one_line = "raw[0x74..0x76].copy_from_slice(&x.to_le_bytes());";
11135 assert_eq!(half_word_writes(one_line, "0x74..0x76").len(), 1);
11136 assert!(half_word_writes("let a = raw[0x74..0x76][0];", "0x74..0x76").is_empty());
11137 }
11138
11139 #[test]
11140 fn each_inode_half_word_is_written_in_exactly_one_place() {
11141 let whole = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/src/fs.rs"))
11142 .expect("src/fs.rs is readable");
11143
11144 let cut = whole
11150 .find("\n#[cfg(test)]\n")
11151 .expect("src/fs.rs has a #[cfg(test)] module, which is where this test lives");
11152 let src = &whole[..cut];
11153 assert!(
11154 src.contains("fn patch_inode_size_and_blocks"),
11155 "the non-test half must still contain the writers, or a count of 1 means \
11156 the split ate the file"
11157 );
11158
11159 let writes = |field: &str| half_word_writes(src, field);
11160
11161 let blocks_hi = writes("0x74..0x76");
11162 assert_eq!(
11163 blocks_hi.len(),
11164 1,
11165 "i_blocks_hi (0x74..0x76) is written {} times; it belongs to \
11166 patch_inode_size_and_blocks alone. Found: {blocks_hi:?}",
11167 blocks_hi.len()
11168 );
11169 assert!(
11170 blocks_hi[0].contains("blocks_hi"),
11171 "the one 0x74..0x76 write must be the i_blocks_hi one: {:?}",
11172 blocks_hi[0]
11173 );
11174
11175 let file_acl_hi = writes("0x76..0x78");
11176 assert_eq!(
11177 file_acl_hi.len(),
11178 1,
11179 "i_file_acl_hi (0x76..0x78) is written {} times; write_file_acl is the one \
11180 place. Found: {file_acl_hi:?}",
11181 file_acl_hi.len()
11182 );
11183
11184 let file_acl_lo = writes("0x68..0x6C");
11187 assert_eq!(
11188 file_acl_lo.len(),
11189 1,
11190 "i_file_acl_lo (0x68..0x6C) should also be written exactly once, by the same \
11191 function. Found: {file_acl_lo:?}"
11192 );
11193 }
11194
11195 fn make_inline_file(fs: &Filesystem, ino: u32, content: u32) {
11199 let (inode, mut raw) = fs.read_inode_verified(ino).unwrap();
11200 let mut flags = u32::from_le_bytes(raw[0x20..0x24].try_into().unwrap());
11201 flags &= !crate::inode::InodeFlags::EXTENTS.bits();
11202 flags |= crate::inode::InodeFlags::INLINE_DATA.bits();
11203 raw[0x20..0x24].copy_from_slice(&flags.to_le_bytes());
11204 raw[0x28..0x64].fill(0);
11205 raw[0x28..0x2C].copy_from_slice(&content.to_le_bytes());
11206 raw[0x04..0x08].copy_from_slice(&4u32.to_le_bytes());
11207 fs.finalize_inode_raw(ino, inode.generation, &mut raw)
11208 .unwrap();
11209 fs.write_inode_raw(ino, &raw).unwrap();
11210 }
11211
11212 fn block_in_use(fs: &Filesystem, block: u64) -> bool {
11214 let rel = block - fs.sb.first_data_block as u64;
11215 let gi = (rel / fs.sb.blocks_per_group as u64) as usize;
11216 let bit = (rel % fs.sb.blocks_per_group as u64) as usize;
11217 let bm = fs.read_block(fs.groups[gi].block_bitmap).unwrap();
11218 bm[bit / 8] & (1 << (bit % 8)) != 0
11219 }
11220
11221 #[test]
11225 fn replacing_an_inline_file_does_not_free_its_bytes_as_blocks() {
11226 let dev = formatted();
11227 set_incompat_bit(&dev, crate::features::Incompat::INLINE_DATA.bits());
11228 let fs = mount(&dev);
11229 let ino = fs.apply_create("/f", 0o644).unwrap();
11230 let (root, _) = fs.read_inode_verified(2).unwrap();
11231 let victim = fs.map_inode_logical(&root, 0).unwrap().unwrap(); make_inline_file(&fs, ino, victim as u32);
11233 drop(fs);
11234 let fs = mount(&dev);
11235 let r = fs.apply_replace_file_content("/f", b"new content");
11236 drop(fs);
11237 let fs = mount(&dev);
11238 assert!(
11239 block_in_use(&fs, victim),
11240 "root directory block {victim} was freed: {r:?}"
11241 );
11242 assert_eq!(r.unwrap(), 11);
11243 let (inode, raw) = fs.read_inode_verified(ino).unwrap();
11244 assert!(inode.has_inline_data(), "11 bytes fit in the inode");
11245 assert_eq!(
11246 crate::file_io::read_inline(&fs, &inode, &raw).unwrap(),
11247 b"new content"
11248 );
11249 }
11250
11251 fn file_bytes(fs: &Filesystem, ino: u32) -> (Vec<u8>, bool) {
11253 let (inode, raw) = fs.read_inode_verified(ino).unwrap();
11254 let mut out = vec![0u8; inode.size as usize];
11255 let n = crate::file_io::read_with_raw(fs, &inode, &raw, 0, inode.size, &mut out).unwrap();
11256 assert_eq!(n, inode.size);
11257 (out, inode.has_inline_data())
11258 }
11259
11260 #[test]
11266 fn truncating_or_writing_an_inline_file_keeps_it_inline_while_it_fits() {
11267 let dev = formatted();
11268 set_incompat_bit(&dev, crate::features::Incompat::INLINE_DATA.bits());
11269 let fs = mount(&dev);
11270 let ino = fs.apply_create("/f", 0o644).unwrap();
11271 fs.apply_create("/g", 0o644).unwrap();
11272 fs.apply_replace_file_content("/g", b"neighbour").unwrap();
11273 make_inline_file(&fs, ino, u32::from_le_bytes(*b"abc\n"));
11274 drop(fs);
11275
11276 let mut model = b"abc\n".to_vec();
11277 type Step = fn(&Filesystem, u32) -> Result<()>;
11278 type Model = fn(&mut Vec<u8>);
11279 let steps: [(&str, Step, Model, bool); 6] = [
11280 (
11281 "pwrite",
11282 |fs, _| fs.apply_pwrite("/f", 1, b"x").map(drop),
11283 |m| m[1] = b'x',
11284 true,
11285 ),
11286 (
11287 "grow into system.data",
11288 |fs, ino| fs.apply_truncate_grow(ino, 100),
11289 |m| m.resize(100, 0),
11290 true,
11291 ),
11292 (
11293 "pwrite in system.data",
11294 |fs, ino| fs.apply_pwrite_ino(ino, 90, b"tail").map(drop),
11295 |m| m[90..94].copy_from_slice(b"tail"),
11296 true,
11297 ),
11298 (
11299 "shrink",
11300 |fs, ino| fs.apply_truncate_shrink(ino, 2),
11301 |m| m.truncate(2),
11302 true,
11303 ),
11304 (
11305 "grow past the inode",
11306 |fs, ino| fs.apply_truncate_grow(ino, 4096),
11307 |m| m.resize(4096, 0),
11308 false,
11309 ),
11310 (
11311 "pwrite the converted file",
11312 |fs, ino| fs.apply_pwrite_ino(ino, 4000, b"end").map(drop),
11313 |m| m[4000..4003].copy_from_slice(b"end"),
11314 false,
11315 ),
11316 ];
11317 for (name, step, apply, inline) in steps {
11318 let fs = mount(&dev);
11319 step(&fs, ino).unwrap_or_else(|e| panic!("{name}: {e:?}"));
11320 drop(fs);
11321 apply(&mut model);
11322 let fs = mount(&dev);
11323 assert_eq!(file_bytes(&fs, ino), (model.clone(), inline), "{name}");
11324 let g = resolve(&fs, "/g").unwrap();
11325 assert_eq!(file_bytes(&fs, g).0, b"neighbour", "{name}: /g");
11326 let report = crate::fsck::audit(&fs, u32::MAX, u32::MAX).unwrap();
11327 assert!(
11328 report.anomalies.is_empty(),
11329 "{name}: {:?}",
11330 report.anomalies
11331 );
11332 }
11333 }
11334
11335 mod needs_host {
11337 use super::*;
11338
11339 #[test]
11344 fn recovering_an_orphaned_fast_symlink_with_an_xattr_block_leaves_e2fsck_clean() {
11345 let dev = formatted();
11346 {
11347 let fs = mount(&dev);
11348 let (root, _) = fs.read_inode_verified(2).unwrap();
11349 let victim = fs.map_inode_logical(&root, 0).unwrap().unwrap();
11350 let mut tb: Vec<u8> = (victim as u32).to_le_bytes().to_vec();
11351 while tb.last() == Some(&0) {
11352 tb.pop();
11353 }
11354 let target = String::from_utf8(tb).expect("an ASCII block number");
11355 let ino = fs.apply_symlink(&target, "/l").unwrap();
11356 give_xattr_block(&fs, ino);
11357 let (root, _) = fs.read_inode_verified(2).unwrap();
11358 let mut buf = BlockBuffer::new(fs.sb.block_size());
11359 fs.buffer_remove_dir_entry(&mut buf, 2, &root, b"l")
11360 .expect("remove the name");
11361 fs.commit_block_buffer(buf).expect("commit");
11362 plant_orphan(&fs, ino, 0, None);
11363 }
11364 drop(mount(&dev));
11366
11367 let image = fs_ext4_test_support::temp_dir()
11368 .join(format!("fs_ext4_symlink_orphan_{}.img", std::process::id()));
11369 std::fs::write(&image, &*dev.bytes.lock().unwrap()).unwrap();
11370 let judged = fs_ext4_test_support::oracle("e2fsck")
11371 .arg("-fn")
11372 .arg(&image)
11373 .judged();
11374 let _ = std::fs::remove_file(&image);
11375 judged.clean("an orphaned fast symlink with an xattr block, recovered");
11376 }
11377
11378 #[test]
11381 fn a_run_into_the_final_groups_padding_is_refused() {
11382 let dir = fs_ext4_test_support::temp_dir()
11383 .join(format!("ext4-short-group-{}", std::process::id()));
11384 std::fs::create_dir_all(&dir).unwrap();
11385 let img = dir.join("s.img");
11386 std::fs::File::create(&img)
11389 .unwrap()
11390 .set_len(16284 * 4096)
11391 .unwrap();
11392 let made = fs_ext4_test_support::oracle("mkfs.ext4")
11393 .args(["-q", "-F", "-b", "4096", "-g", "4096", "-O", "^has_journal"])
11394 .arg(&img)
11395 .output();
11396 assert!(
11397 made.status.success(),
11398 "{}",
11399 String::from_utf8_lossy(&made.stderr)
11400 );
11401 let fs = Filesystem::mount(std::sync::Arc::new(
11402 crate::block_io::FileDevice::open(img.to_str().unwrap()).unwrap(),
11403 ))
11404 .unwrap();
11405 let last = fs.sb.blocks_count;
11406 assert_ne!(
11407 (last - u64::from(fs.sb.first_data_block)) % u64::from(fs.sb.blocks_per_group),
11408 0,
11409 "fixture: the final group is short"
11410 );
11411 assert!(
11412 fs.group_chunks(last - 2, 2).is_ok(),
11413 "a run ending at the last block"
11414 );
11415 assert!(
11416 matches!(fs.group_chunks(last - 2, 3), Err(Error::InvalidBlock(_))),
11417 "a run one block into the padding"
11418 );
11419 let _ = std::fs::remove_dir_all(&dir);
11420 }
11421
11422 #[test]
11431 fn a_freed_run_across_a_group_boundary_credits_both_groups() {
11432 let dir = fs_ext4_test_support::temp_dir()
11433 .join(format!("ext4-cross-group-{}", std::process::id()));
11434 std::fs::create_dir_all(&dir).unwrap();
11435 let img = dir.join("g.img");
11436 std::fs::File::create(&img)
11437 .unwrap()
11438 .set_len(64 * 1024 * 1024)
11439 .unwrap();
11440 let made = fs_ext4_test_support::oracle("mkfs.ext4")
11441 .args([
11442 "-q",
11443 "-F",
11444 "-b",
11445 "4096",
11446 "-g",
11447 "4096",
11448 "-O",
11449 "^metadata_csum,^has_journal",
11450 ])
11451 .arg(&img)
11452 .output();
11453 assert!(
11454 made.status.success(),
11455 "{}",
11456 String::from_utf8_lossy(&made.stderr)
11457 );
11458 let path = img.to_str().unwrap().to_owned();
11459 let mount = || {
11460 Filesystem::mount(std::sync::Arc::new(
11461 crate::block_io::FileDevice::open_rw(&path).unwrap(),
11462 ))
11463 .unwrap()
11464 };
11465 let boundary = 2 * 4096u64; let bit_set = |fs: &Filesystem, gi: usize, bit: u64| {
11467 let bm = fs.read_block(fs.groups[gi].block_bitmap).unwrap();
11468 bm[(bit / 8) as usize] & (1 << (bit % 8)) != 0
11469 };
11470
11471 {
11473 let fs = mount();
11474 let mut buf = BlockBuffer::new(fs.sb.block_size());
11475 fs.buffer_mark_block_run_used(&mut buf, boundary - 4, 4)
11476 .unwrap();
11477 fs.buffer_mark_block_run_used(&mut buf, boundary, 4)
11478 .unwrap();
11479 fs.commit_block_buffer(buf).unwrap();
11480 }
11481 let (free1, free2) = {
11482 let fs = mount();
11483 assert!(
11484 (0..4).all(|b| bit_set(&fs, 2, b)),
11485 "fixture: group 2's run is allocated"
11486 );
11487 (
11488 fs.groups[1].free_blocks_count,
11489 fs.groups[2].free_blocks_count,
11490 )
11491 };
11492
11493 {
11495 let fs = mount();
11496 let mut buf = BlockBuffer::new(fs.sb.block_size());
11497 assert_eq!(
11498 fs.buffer_free_block_run_and_bgd(&mut buf, boundary - 4, 8)
11499 .unwrap(),
11500 8
11501 );
11502 fs.commit_block_buffer(buf).unwrap();
11503 }
11504 let fs = mount();
11505 assert_eq!(
11506 (
11507 fs.groups[1].free_blocks_count - free1,
11508 fs.groups[2].free_blocks_count - free2
11509 ),
11510 (4, 4),
11511 "(group 1 credited, group 2 credited)"
11512 );
11513 assert!(
11514 (0..4).all(|b| !bit_set(&fs, 2, b)),
11515 "group 2's blocks are still marked allocated"
11516 );
11517 drop(fs);
11518
11519 let _ = std::fs::remove_dir_all(&dir);
11520 }
11521
11522 #[test]
11532 fn an_indirect_mapped_orphan_frees_its_data_and_indirect_blocks() {
11533 const EXT2_VOL: u64 = 32 * 1024 * 1024;
11534 let dev = MemDev::new(EXT2_VOL);
11535 crate::mkfs::format_filesystem_with_flavor(
11536 dev.as_ref(),
11537 Some("ext2orph"),
11538 None,
11539 EXT2_VOL,
11540 BS,
11541 crate::features::FsFlavor::Ext2,
11542 )
11543 .expect("format");
11544 let (ino, free_before) = {
11545 let fs = mount(&dev);
11546 let free_before = fs.sb.free_blocks_count;
11547 let ino = fs.apply_create("/gone.bin", 0o644).expect("create");
11548 fs.apply_replace_file_content("/gone.bin", &vec![0x5A; 20 * BS as usize])
11549 .expect("write");
11550 let (inode, _) = fs.read_inode_verified(ino).expect("read");
11551 assert!(!inode.has_extents(), "fixture: an indirect-mapped file");
11552 assert_ne!(
11553 u32::from_le_bytes(inode.block[48..52].try_into().unwrap()),
11554 0,
11555 "fixture: the file uses its single indirect block"
11556 );
11557 (ino, free_before)
11558 };
11559 {
11560 let fs = mount(&dev);
11561 let (root, _) = fs.read_inode_verified(2).expect("root");
11562 let mut buf = BlockBuffer::new(fs.sb.block_size());
11563 fs.buffer_remove_dir_entry(&mut buf, 2, &root, b"gone.bin")
11564 .expect("remove the name");
11565 fs.commit_block_buffer(buf).expect("commit");
11566 plant_orphan(&fs, ino, 0, None);
11567 }
11568 drop(mount(&dev));
11570
11571 let fs = mount(&dev);
11572 assert!(
11573 fs.orphan_list().expect("orphan_list").is_empty(),
11574 "recovery must take the orphan off the chain"
11575 );
11576 assert!(!fs.inode_bit_is_set(ino).expect("inode bitmap"));
11577 assert_eq!(
11578 fs.sb.free_blocks_count, free_before,
11579 "every block the file used -- 20 data and 1 indirect -- must be free again"
11580 );
11581 let report = crate::fsck::audit(&fs, u32::MAX, u32::MAX).expect("audit");
11582 assert!(report.is_clean(), "fsck: {:?}", report.anomalies);
11583 drop(fs);
11584
11585 let image = fs_ext4_test_support::temp_dir().join(format!(
11588 "fs_ext4_indirect_orphan_{}.img",
11589 std::process::id()
11590 ));
11591 std::fs::write(&image, &*dev.bytes.lock().unwrap()).unwrap();
11592 let judged = fs_ext4_test_support::oracle("e2fsck")
11593 .arg("-fn")
11594 .arg(&image)
11595 .judged();
11596 let _ = std::fs::remove_file(&image);
11597 judged.clean("an indirect-mapped orphan after its data is freed");
11598 }
11599 }
11600
11601 #[test]
11608 fn fsck_does_not_count_an_uninit_groups_bitmaps() {
11609 const SMALL_BS: u32 = 2048;
11610 const SMALL_VOL: u64 = 80 * 1024 * 1024;
11612 let dev = MemDev::new(SMALL_VOL);
11613 crate::mkfs::format_filesystem(dev.as_ref(), Some("uninit"), None, SMALL_VOL, SMALL_BS)
11614 .expect("format");
11615 let gi = 2usize;
11616 {
11617 let fs = mount(&dev);
11618 assert!(fs.groups.len() > gi, "the volume needs a third group");
11619 let g = fs.groups[gi];
11620 assert_eq!(g.free_inodes_count, fs.sb.inodes_per_group);
11621 assert_eq!(g.used_dirs_count, 0);
11622 let (blk, off) = fs.sb.descriptor_location(gi as u64);
11623 let mut raw = fs.read_block(blk).unwrap();
11624 let flags = u16::from_le_bytes(raw[off + 0x12..off + 0x14].try_into().unwrap())
11625 | crate::bgd::BgdFlags::BLOCK_UNINIT.bits()
11626 | crate::bgd::BgdFlags::INODE_UNINIT.bits();
11627 raw[off + 0x12..off + 0x14].copy_from_slice(&flags.to_le_bytes());
11628 fs.restamp_group_desc_csum(&mut raw[..], off, gi);
11629 dev.write_at(blk * u64::from(SMALL_BS), &raw).unwrap();
11630 }
11631 {
11632 let fs = mount(&dev);
11633 let before = fs.audit(1000, 10000).unwrap();
11634 assert!(
11635 before.is_clean(),
11636 "precondition: clean with the real bitmaps still in place: {before:?}"
11637 );
11638 let g = fs.groups[gi];
11639 let bs = SMALL_BS as usize;
11640 dev.write_at(g.block_bitmap * u64::from(SMALL_BS), &vec![0u8; bs])
11641 .unwrap();
11642 dev.write_at(g.inode_bitmap * u64::from(SMALL_BS), &vec![0xFFu8; bs])
11643 .unwrap();
11644 }
11645 let fs = mount(&dev);
11646 let r = fs.audit(1000, 10000).unwrap();
11647 assert!(
11648 r.is_clean(),
11649 "an uninit group's bitmap bytes were counted: {r:?}"
11650 );
11651 }
11652
11653 #[test]
11658 fn buffered_bgd_counter_high_halves_are_written_where_they_are_read() {
11659 let dev = formatted();
11660 let fs = mount(&dev);
11661 assert!(fs.sb.desc_size >= 64, "the fixture must carry high halves");
11662 let (blk, off) = fs.sb.descriptor_location(0);
11663 let mut buf = BlockBuffer::new(BS);
11664 {
11665 let b = buf.get_mut(&fs, blk).unwrap();
11666 b[off + 0x0C..off + 0x0E].copy_from_slice(&0xFFFFu16.to_le_bytes());
11667 b[off + 0x0E..off + 0x10].copy_from_slice(&0xFFFFu16.to_le_bytes());
11668 b[off + 0x10..off + 0x12].copy_from_slice(&0xFFFFu16.to_le_bytes());
11669 }
11670 let parse = |buf: &BlockBuffer| {
11671 crate::bgd::BlockGroupDescriptor::parse(&buf.dirty[&blk][off..off + 64], 64).unwrap()
11672 };
11673 let before = parse(&buf);
11674 fs.buffer_patch_bgd_counters(&mut buf, 0, 1, 1, 1).unwrap();
11675 let after = parse(&buf);
11676 assert_eq!(
11677 after.inode_table, before.inode_table,
11678 "inode table pointer changed"
11679 );
11680 assert_eq!(after.block_bitmap, before.block_bitmap);
11681 assert_eq!(after.inode_bitmap, before.inode_bitmap);
11682 assert_eq!(after.free_blocks_count, 0x1_0000);
11683 assert_eq!(after.free_inodes_count, 0x1_0000);
11684 assert_eq!(after.used_dirs_count, 0x1_0000);
11685 assert_eq!(after.itable_unused, before.itable_unused);
11686
11687 fs.buffer_patch_bgd_counters(&mut buf, 0, -1, -1, -1)
11689 .unwrap();
11690 let back = parse(&buf);
11691 assert_eq!(back.inode_table, before.inode_table);
11692 assert_eq!(back.free_blocks_count, 0xFFFF);
11693 assert_eq!(back.free_inodes_count, 0xFFFF);
11694 assert_eq!(back.used_dirs_count, 0xFFFF);
11695 }
11696
11697 #[test]
11700 fn direct_bgd_counter_high_halves_are_written_where_they_are_read() {
11701 let dev = formatted();
11702 let fs = mount(&dev);
11703 assert!(fs.sb.desc_size >= 64, "the fixture must carry high halves");
11704 let (blk, off) = fs.sb.descriptor_location(0);
11705 let mut raw = fs.read_block(blk).unwrap();
11706 raw[off + 0x0C..off + 0x0E].copy_from_slice(&0xFFFFu16.to_le_bytes());
11707 raw[off + 0x0E..off + 0x10].copy_from_slice(&0xFFFFu16.to_le_bytes());
11708 raw[off + 0x10..off + 0x12].copy_from_slice(&0xFFFFu16.to_le_bytes());
11709 fs.restamp_group_desc_csum(&mut raw[..], off, 0);
11710 dev.write_at(blk * u64::from(BS), &raw).unwrap();
11711 drop(fs);
11712 let fs = mount(&dev);
11713 let before = crate::bgd::BlockGroupDescriptor::parse(&raw[off..off + 64], 64).unwrap();
11714 fs.patch_bgd_counters(0, 1, 1, 1).unwrap();
11715 let raw = fs.read_block(blk).unwrap();
11716 let after = crate::bgd::BlockGroupDescriptor::parse(&raw[off..off + 64], 64).unwrap();
11717 assert_eq!(
11718 after.inode_table, before.inode_table,
11719 "inode table pointer changed"
11720 );
11721 assert_eq!(after.free_blocks_count, 0x1_0000);
11722 assert_eq!(after.free_inodes_count, 0x1_0000);
11723 assert_eq!(after.used_dirs_count, 0x1_0000);
11724 }
11725
11726 fn xattr_copies(fs: &Filesystem, ino: u32, name: &str) -> Vec<usize> {
11731 let (inode, raw) = fs.read_inode_verified(ino).expect("read inode");
11732 crate::xattr::read_all_resolved(fs, &inode, &raw)
11733 .expect("read xattrs")
11734 .into_iter()
11735 .filter(|e| e.name == name)
11736 .map(|e| e.value.len())
11737 .collect()
11738 }
11739
11740 #[test]
11745 fn growing_an_in_inode_attribute_past_the_inode_leaves_one_copy() {
11746 let dev = formatted();
11747 let fs = mount(&dev);
11748 let ino = fs.apply_create("/f", 0o644).expect("create");
11749 fs.apply_setxattr("/f", "user.a", &[1u8; 40])
11750 .expect("set small");
11751 assert_eq!(xattr_copies(&fs, ino, "user.a"), vec![40]);
11752 fs.apply_setxattr("/f", "user.a", &[2u8; 200])
11753 .expect("set big");
11754 assert_eq!(
11755 xattr_copies(&fs, ino, "user.a"),
11756 vec![200],
11757 "one copy, the new value"
11758 );
11759 let (inode, raw) = fs.read_inode_verified(ino).unwrap();
11760 assert_eq!(
11761 crate::xattr::get_resolved(&fs, &inode, &raw, "user.a").unwrap(),
11762 Some(vec![2u8; 200]),
11763 "the value read back is the one written last"
11764 );
11765 fs.apply_removexattr("/f", "user.a").expect("remove");
11767 assert_eq!(xattr_copies(&fs, ino, "user.a"), Vec::<usize>::new());
11768 }
11769
11770 #[test]
11775 fn shrinking_an_external_attribute_into_the_inode_leaves_one_copy() {
11776 let dev = formatted();
11777 let fs = mount(&dev);
11778 let ino = fs.apply_create("/f", 0o644).expect("create");
11779 fs.apply_setxattr("/f", "user.big", &[1u8; 200])
11780 .expect("set big");
11781 assert_eq!(xattr_copies(&fs, ino, "user.big"), vec![200]);
11782 fs.apply_setxattr("/f", "user.big", &[2u8; 8])
11783 .expect("set small");
11784 assert_eq!(
11785 xattr_copies(&fs, ino, "user.big"),
11786 vec![8],
11787 "one copy, the new value"
11788 );
11789 fs.apply_removexattr("/f", "user.big").expect("remove");
11790 assert_eq!(
11791 xattr_copies(&fs, ino, "user.big"),
11792 Vec::<usize>::new(),
11793 "removed means gone"
11794 );
11795 }
11796
11797 fn point_file_acl_at(fs: &Filesystem, ino: u32, block_nr: u64) {
11802 let (inode, mut raw) = fs.read_inode_verified(ino).expect("read inode");
11803 Filesystem::write_file_acl(&mut raw, block_nr).expect("write_file_acl");
11804 fs.finalize_inode_raw(ino, inode.generation, &mut raw)
11805 .expect("finalize");
11806 fs.write_inode_raw(ino, &raw).expect("write inode");
11807 }
11808
11809 fn a_data_block(fs: &Filesystem) -> u64 {
11811 let g = fs.apply_create("/g", 0o644).expect("create g");
11812 fs.apply_replace_file_content("/g", &[0x5Au8; 4096])
11813 .expect("write g");
11814 let (gi, _) = fs.read_inode_verified(g).expect("read g");
11815 fs.extent_tree_runs(g, &gi).expect("g's extents")[0].0
11816 }
11817
11818 fn block_bytes(dev: &std::sync::Arc<MemDev>, block_nr: u64) -> Vec<u8> {
11819 use crate::block_io::BlockDevice;
11820 let mut b = vec![0u8; BS as usize];
11821 dev.read_at(block_nr * BS as u64, &mut b)
11822 .expect("read block");
11823 b
11824 }
11825
11826 #[test]
11830 fn setxattr_refuses_an_external_block_without_the_xattr_magic() {
11831 let dev = formatted();
11832 let fs = mount(&dev);
11833 let f = fs.apply_create("/f", 0o644).expect("create f");
11834 let gblk = a_data_block(&fs);
11835 point_file_acl_at(&fs, f, gblk);
11836 let r = fs.apply_setxattr("/f", "user.big", &[1u8; 200]);
11837 assert!(
11838 matches!(r, Err(Error::Corrupt(_))),
11839 "setxattr on a non-xattr block must be refused as corrupt, got {r:?}"
11840 );
11841 assert!(
11842 block_bytes(&dev, gblk).iter().all(|&x| x == 0x5A),
11843 "g's data must be untouched"
11844 );
11845 }
11846
11847 #[test]
11850 fn removexattr_and_unlink_refuse_an_external_block_without_the_xattr_magic() {
11851 let dev = formatted();
11852 let fs = mount(&dev);
11853 let f = fs.apply_create("/f", 0o644).expect("create f");
11854 let gblk = a_data_block(&fs);
11855 point_file_acl_at(&fs, f, gblk);
11856 let r = fs.apply_removexattr("/f", "user.anything");
11857 assert!(matches!(r, Err(Error::Corrupt(_))), "removexattr: {r:?}");
11858 let r = fs.apply_unlink("/f");
11859 assert!(matches!(r, Err(Error::Corrupt(_))), "unlink: {r:?}");
11860 assert!(
11861 block_bytes(&dev, gblk).iter().all(|&x| x == 0x5A),
11862 "g's data must be untouched"
11863 );
11864 }
11865
11866 #[test]
11869 fn setxattr_refuses_an_external_block_that_fails_its_checksum() {
11870 use crate::block_io::BlockDevice;
11871 let dev = formatted();
11872 let fs = mount(&dev);
11873 assert!(fs.csum.enabled, "fixture: a metadata_csum volume");
11874 let f = fs.apply_create("/f", 0o644).expect("create f");
11875 fs.apply_setxattr("/f", "user.big", &[1u8; 200])
11876 .expect("set big");
11877 let (fi, _) = fs.read_inode_verified(f).unwrap();
11878 assert_ne!(fi.file_acl, 0, "fixture: the value is in the block");
11879 let mut b = block_bytes(&dev, fi.file_acl);
11880 assert!(
11881 fs.csum.verify_xattr_block(fi.file_acl, &b),
11882 "fixture: valid"
11883 );
11884 b[BS as usize - 1] ^= 0xFF; dev.write_at(fi.file_acl * BS as u64, &b).unwrap();
11886 let fs = mount(&dev);
11887 let r = fs.apply_setxattr("/f", "user.other", &[2u8; 200]);
11888 assert!(
11889 matches!(r, Err(Error::BadChecksum { .. })),
11890 "an edit of a block that fails its checksum must be refused, got {r:?}"
11891 );
11892 assert_eq!(block_bytes(&dev, fi.file_acl), b, "the block is untouched");
11893 let r = fs.apply_removexattr("/f", "user.big");
11894 assert!(
11895 matches!(r, Err(Error::BadChecksum { .. })),
11896 "removexattr: {r:?}"
11897 );
11898 assert_eq!(block_bytes(&dev, fi.file_acl), b, "the block is untouched");
11899 let (fi, raw) = fs.read_inode_verified(f).unwrap();
11902 let r = crate::xattr::get_resolved(&fs, &fi, &raw, "user.big");
11903 assert!(
11904 matches!(r, Err(Error::BadChecksum { .. })),
11905 "getxattr: {r:?}"
11906 );
11907 }
11908
11909 #[test]
11912 fn setxattr_refuses_an_external_block_with_h_blocks_other_than_one() {
11913 use crate::block_io::BlockDevice;
11914 let dev = formatted();
11915 let fs = mount(&dev);
11916 let f = fs.apply_create("/f", 0o644).expect("create f");
11917 fs.apply_setxattr("/f", "user.big", &[1u8; 200])
11918 .expect("set big");
11919 let (fi, _) = fs.read_inode_verified(f).unwrap();
11920 let mut b = block_bytes(&dev, fi.file_acl);
11921 b[8..12].copy_from_slice(&2u32.to_le_bytes());
11922 fs.csum.patch_xattr_block(fi.file_acl, &mut b);
11923 dev.write_at(fi.file_acl * BS as u64, &b).unwrap();
11924 let fs = mount(&dev);
11925 let r = fs.apply_setxattr("/f", "user.other", &[2u8; 200]);
11926 assert!(matches!(r, Err(Error::Corrupt(_))), "setxattr: {r:?}");
11927 assert_eq!(block_bytes(&dev, fi.file_acl), b, "the block is untouched");
11928 }
11929
11930 fn patch_inode(fs: &Filesystem, ino: u32, f: impl FnOnce(&mut Vec<u8>)) {
11934 let (inode, mut raw) = fs.read_inode_verified(ino).expect("read inode");
11935 f(&mut raw);
11936 fs.finalize_inode_raw(ino, inode.generation, &mut raw)
11937 .expect("finalize");
11938 fs.write_inode_raw(ino, &raw).expect("write inode");
11939 }
11940
11941 #[test]
11946 fn setxattr_on_an_inode_with_no_extra_isize_does_not_write_at_0x80() {
11947 let dev = formatted();
11948 let fs = mount(&dev);
11949 let ino = fs.apply_create("/f", 0o644).expect("create");
11950 patch_inode(&fs, ino, |raw| raw[0x80..].fill(0));
11951 fs.apply_setxattr("/f", "user.a", b"v").expect("setxattr");
11952 let (inode, raw) = fs
11953 .read_inode_verified(ino)
11954 .expect("the inode still verifies");
11955 let extra = u16::from_le_bytes(raw[0x80..0x82].try_into().unwrap());
11956 assert_eq!(
11957 extra,
11958 32,
11959 "i_extra_isize: the xattr went where the kernel reads i_*_extra: {:02x?}",
11960 &raw[0x80..0xA4]
11961 );
11962 assert_eq!(
11963 raw[0xA0..0xA4],
11964 crate::xattr::EXT4_XATTR_MAGIC.to_le_bytes(),
11965 "the in-inode area starts after the extra fields"
11966 );
11967 assert_eq!(
11968 crate::xattr::get_resolved(&fs, &inode, &raw, "user.a").unwrap(),
11969 Some(b"v".to_vec())
11970 );
11971 }
11972
11973 #[test]
11976 fn an_xattr_area_at_0x80_is_not_read() {
11977 let dev = formatted();
11978 let fs = mount(&dev);
11979 let ino = fs.apply_create("/f", 0o644).expect("create");
11980 patch_inode(&fs, ino, |raw| {
11981 raw[0x80..].fill(0);
11982 let mut region = raw[0x80..].to_vec();
11983 crate::xattr::plan_set_in_inode_region(&mut region, "user.a", b"v").unwrap();
11984 raw[0x80..].copy_from_slice(®ion);
11985 assert_eq!(raw[0x80..0x82], [0, 0]);
11987 });
11988 let (inode, raw) = fs.read_inode_verified(ino).unwrap();
11989 assert_eq!(
11990 crate::xattr::get_resolved(&fs, &inode, &raw, "user.a").unwrap(),
11991 None
11992 );
11993 }
11994
11995 #[test]
12000 fn an_inode_without_room_for_checksum_hi_keeps_its_bytes_at_0x82() {
12001 let dev = formatted();
12002 let fs = mount(&dev);
12003 assert!(fs.csum.enabled, "fixture: a metadata_csum volume");
12004 let ino = fs.apply_create("/f", 0o644).expect("create");
12005 let (inode, mut raw) = fs.read_inode_verified(ino).unwrap();
12006 raw[0x80..].fill(0);
12007 raw[0x82..0x84].copy_from_slice(&[0x02, 0xEA]);
12008 let (lo, _) = fs
12011 .csum
12012 .compute_inode_checksum(ino, inode.generation, &raw)
12013 .unwrap();
12014 raw[0x7C..0x7E].copy_from_slice(&lo.to_le_bytes());
12015 assert!(
12016 fs.csum.verify_inode(ino, inode.generation, &raw),
12017 "a kernel-checksummed inode with i_extra_isize = 0 verifies"
12018 );
12019 let mut rewritten = raw.clone();
12020 fs.finalize_inode_raw(ino, inode.generation, &mut rewritten)
12021 .unwrap();
12022 assert_eq!(rewritten, raw, "re-checksumming changes nothing");
12023 }
12024
12025 fn audit70_patch_inode(fs: &Filesystem, ino: u32, f: impl FnOnce(&mut Vec<u8>)) {
12027 let (inode, mut raw) = fs.read_inode_verified(ino).unwrap();
12028 f(&mut raw);
12029 fs.finalize_inode_raw(ino, inode.generation, &mut raw)
12030 .unwrap();
12031 fs.write_inode_raw(ino, &raw).unwrap();
12032 }
12033
12034 fn audit70_set_links(fs: &Filesystem, ino: u32, count: u16) {
12035 audit70_patch_inode(fs, ino, |raw| {
12036 raw[0x1A..0x1C].copy_from_slice(&count.to_le_bytes())
12037 });
12038 }
12039
12040 fn audit70_links(fs: &Filesystem, ino: u32) -> u16 {
12041 fs.read_inode_verified(ino).unwrap().0.links_count
12042 }
12043
12044 fn set_ro_compat_bit(dev: &std::sync::Arc<MemDev>, bit: u32) {
12047 let mut sb = vec![0u8; 1024];
12048 dev.read_at(crate::superblock::SUPERBLOCK_OFFSET, &mut sb)
12049 .expect("read sb");
12050 let cur = u32::from_le_bytes(sb[0x64..0x68].try_into().unwrap());
12051 sb[0x64..0x68].copy_from_slice(&(cur | bit).to_le_bytes());
12052 let csum = crate::checksum::linux_crc32c(!0, &sb[..0x3FC]);
12053 sb[0x3FC..0x400].copy_from_slice(&csum.to_le_bytes());
12054 dev.write_at(crate::superblock::SUPERBLOCK_OFFSET, &sb)
12055 .expect("write sb");
12056 }
12057
12058 #[test]
12062 fn audit70_rmdir_under_a_dir_nlink_parent_keeps_its_count() {
12063 let dev = formatted();
12064 set_ro_compat_bit(&dev, crate::features::RoCompat::DIR_NLINK.bits());
12065 let fs = mount(&dev);
12066 fs.apply_mkdir("/p", 0o755).unwrap();
12067 fs.apply_mkdir("/p/c", 0o755).unwrap();
12068 let p = resolve(&fs, "/p").unwrap();
12069 audit70_set_links(&fs, p, 1);
12070 fs.apply_rmdir("/p/c").unwrap();
12071 assert_eq!(
12072 audit70_links(&fs, p),
12073 1,
12074 "a directory's count of 1 is not decremented"
12075 );
12076 }
12077
12078 #[test]
12081 fn audit70_mkdir_under_a_dir_nlink_parent_keeps_its_count() {
12082 let dev = formatted();
12083 set_ro_compat_bit(&dev, crate::features::RoCompat::DIR_NLINK.bits());
12084 let fs = mount(&dev);
12085 fs.apply_mkdir("/p", 0o755).unwrap();
12086 let p = resolve(&fs, "/p").unwrap();
12087 audit70_set_links(&fs, p, 1);
12088 fs.apply_mkdir("/p/c", 0o755).unwrap();
12089 assert_eq!(audit70_links(&fs, p), 1);
12090 }
12091
12092 #[test]
12095 fn audit70_mkdir_past_the_maximum_pins_a_dir_nlink_parent_at_one() {
12096 let dev = formatted();
12097 set_ro_compat_bit(&dev, crate::features::RoCompat::DIR_NLINK.bits());
12098 let fs = mount(&dev);
12099 fs.apply_mkdir("/p", 0o755).unwrap();
12100 let p = resolve(&fs, "/p").unwrap();
12101 audit70_set_links(&fs, p, 65000);
12102 fs.apply_mkdir("/p/c", 0o755).unwrap();
12103 assert_eq!(audit70_links(&fs, p), 1);
12104 }
12105
12106 #[test]
12109 fn audit70_mkdir_past_the_maximum_without_dir_nlink_is_refused() {
12110 let dev = formatted();
12111 let fs = mount(&dev);
12112 fs.apply_mkdir("/p", 0o755).unwrap();
12113 let p = resolve(&fs, "/p").unwrap();
12114 audit70_set_links(&fs, p, 65000);
12115 let r = fs.apply_mkdir("/p/c", 0o755);
12116 assert!(
12117 matches!(r, Err(Error::TooManyLinks)),
12118 "EMLINK expected, got {r:?}; count now {}",
12119 audit70_links(&fs, p)
12120 );
12121 assert_eq!(audit70_links(&fs, p), 65000);
12122 assert!(
12123 resolve(&fs, "/p/c").is_err(),
12124 "the refused directory was not created"
12125 );
12126 }
12127
12128 #[test]
12131 fn audit70_a_link_past_the_maximum_is_refused_not_wrapped() {
12132 let dev = formatted();
12133 let fs = mount(&dev);
12134 let f = fs.apply_create("/f", 0o644).unwrap();
12135 audit70_set_links(&fs, f, 65535);
12136 let r = fs.apply_link("/f", "/g");
12137 assert!(
12138 matches!(r, Err(Error::TooManyLinks)),
12139 "EMLINK expected; link count now {}",
12140 audit70_links(&fs, f)
12141 );
12142 assert_eq!(audit70_links(&fs, f), 65535);
12143 }
12144
12145 #[test]
12148 fn audit70_a_link_at_max_links_is_refused() {
12149 let dev = formatted();
12150 let fs = mount(&dev);
12151 let f = fs.apply_create("/f", 0o644).unwrap();
12152 audit70_set_links(&fs, f, 65000);
12153 assert!(matches!(
12154 fs.apply_link("/f", "/g"),
12155 Err(Error::TooManyLinks)
12156 ));
12157 assert!(resolve(&fs, "/g").is_err());
12158 audit70_set_links(&fs, f, 64999);
12159 fs.apply_link("/f", "/g").unwrap();
12160 assert_eq!(audit70_links(&fs, f), 65000);
12161 }
12162
12163 #[test]
12166 fn audit70_rename_of_a_dir_into_a_full_parent_is_refused() {
12167 let dev = formatted();
12168 let fs = mount(&dev);
12169 fs.apply_mkdir("/p", 0o755).unwrap();
12170 fs.apply_mkdir("/d", 0o755).unwrap();
12171 let p = resolve(&fs, "/p").unwrap();
12172 audit70_set_links(&fs, p, 65000);
12173 assert!(matches!(
12174 fs.apply_rename("/d", "/p/d", false),
12175 Err(Error::TooManyLinks)
12176 ));
12177 assert_eq!(audit70_links(&fs, p), 65000);
12178 assert!(
12179 resolve(&fs, "/d").is_ok(),
12180 "the refused rename left the source alone"
12181 );
12182 }
12183
12184 #[test]
12186 fn next_links_count_follows_the_kernel() {
12187 assert_eq!(next_links_count(false, 64999, 1, true).unwrap(), 65000);
12189 assert!(matches!(
12190 next_links_count(false, 65000, 1, true),
12191 Err(Error::TooManyLinks)
12192 ));
12193 assert_eq!(next_links_count(false, 1, -1, true).unwrap(), 0);
12194 assert_eq!(next_links_count(false, 0, -1, true).unwrap(), 0);
12195 assert_eq!(next_links_count(true, 65000, 1, true).unwrap(), 1);
12197 assert!(matches!(
12198 next_links_count(true, 65000, 1, false),
12199 Err(Error::TooManyLinks)
12200 ));
12201 assert_eq!(next_links_count(true, 1, 1, false).unwrap(), 1);
12202 assert_eq!(next_links_count(true, 3, -1, true).unwrap(), 2);
12204 assert_eq!(next_links_count(true, 2, -1, true).unwrap(), 2);
12205 assert_eq!(next_links_count(true, 1, -1, true).unwrap(), 1);
12206 assert_eq!(next_links_count(true, 64999, 2, true).unwrap(), 1);
12208 assert_eq!(next_links_count(true, 5, -2, true).unwrap(), 3);
12209 }
12210
12211 #[test]
12214 fn audit70_fsck_accepts_a_dir_nlink_count_of_one() {
12215 let dev = formatted();
12216 set_ro_compat_bit(&dev, crate::features::RoCompat::DIR_NLINK.bits());
12217 let fs = mount(&dev);
12218 fs.apply_mkdir("/p", 0o755).unwrap();
12219 fs.apply_mkdir("/p/c", 0o755).unwrap();
12220 let p = resolve(&fs, "/p").unwrap();
12221 audit70_set_links(&fs, p, 1);
12222 let report = fs.audit(u32::MAX, u32::MAX).unwrap();
12223 assert!(report.is_clean(), "{:?}", report.anomalies);
12224 }
12225
12226 fn root_entry_type(fs: &Filesystem, name: &[u8]) -> crate::dir::DirEntryType {
12228 let (root, _) = fs.read_inode_verified(2).unwrap();
12229 let phys = fs.map_inode_logical(&root, 0).unwrap().unwrap();
12230 let blk = fs.read_block(phys).unwrap();
12231 crate::dir::DirBlockIter::new(&blk, true)
12232 .map(|e| e.unwrap())
12233 .find(|e| e.name == name)
12234 .unwrap_or_else(|| panic!("{} not found", String::from_utf8_lossy(name)))
12235 .file_type
12236 }
12237
12238 #[test]
12241 fn audit70_rename_keeps_a_special_files_type() {
12242 use crate::dir::DirEntryType;
12243 let dev = formatted();
12244 let fs = mount(&dev);
12245 for (name, mode, want) in [
12246 ("fifo", 0o010644u16, DirEntryType::Fifo),
12247 ("sock", 0o140644, DirEntryType::Socket),
12248 ("chr", 0o020644, DirEntryType::CharDev),
12249 ("blk", 0o060644, DirEntryType::BlockDev),
12250 ] {
12251 let src = format!("/{name}");
12252 fs.apply_mknod(&src, mode, 1, 3).unwrap();
12253 let moved = format!("/{name}_moved");
12255 fs.apply_rename(&src, &moved, false).unwrap();
12256 assert_eq!(
12257 root_entry_type(&fs, &moved.as_bytes()[1..]),
12258 want,
12259 "{name}: rename"
12260 );
12261 let victim = format!("/{name}_victim");
12263 fs.apply_create(&victim, 0o644).unwrap();
12264 fs.apply_rename(&moved, &victim, true).unwrap();
12265 assert_eq!(
12266 root_entry_type(&fs, &victim.as_bytes()[1..]),
12267 want,
12268 "{name}: rename over a file"
12269 );
12270 }
12271 }
12272
12273 mod inline_dir_reads {
12278 use super::*;
12279 use crate::inode::{InodeFlags, OFF_BLOCK, OFF_FLAGS, OFF_SIZE_LO};
12280
12281 fn dirent(ino: u32, name: &[u8], file_type: u8, rec_len: u16) -> Vec<u8> {
12283 let mut e = vec![0u8; rec_len as usize];
12284 e[0..4].copy_from_slice(&ino.to_le_bytes());
12285 e[4..6].copy_from_slice(&rec_len.to_le_bytes());
12286 e[6] = name.len() as u8;
12287 e[7] = file_type;
12288 e[8..8 + name.len()].copy_from_slice(name);
12289 e
12290 }
12291
12292 fn make_inline_dir(
12296 fs: &Filesystem,
12297 dir: u32,
12298 parent: u32,
12299 in_block: &[u8],
12300 continuation: &[u8],
12301 ) {
12302 assert_eq!(in_block.len(), 56, "i_block holds 56 bytes of entries");
12303 let (inode, mut raw) = fs.read_inode_verified(dir).unwrap();
12304 let flags =
12305 (inode.flags & !InodeFlags::EXTENTS.bits()) | InodeFlags::INLINE_DATA.bits();
12306 raw[OFF_FLAGS..OFF_FLAGS + 4].copy_from_slice(&flags.to_le_bytes());
12307 raw[OFF_BLOCK..OFF_BLOCK + 4].copy_from_slice(&parent.to_le_bytes());
12308 raw[OFF_BLOCK + 4..OFF_BLOCK + 60].copy_from_slice(in_block);
12309 let size = 60 + continuation.len() as u32;
12310 raw[OFF_SIZE_LO..OFF_SIZE_LO + 4].copy_from_slice(&size.to_le_bytes());
12311 let extra = u16::from_le_bytes(raw[0x80..0x82].try_into().unwrap()) as usize;
12312 let end = (fs.sb.inode_size as usize).min(raw.len());
12313 crate::xattr::plan_set_in_inode_region(
12314 &mut raw[128 + extra..end],
12315 "system.data",
12316 continuation,
12317 )
12318 .expect("system.data fits in the inode");
12319 fs.finalize_inode_raw(dir, inode.generation, &mut raw)
12320 .unwrap();
12321 fs.write_inode_raw(dir, &raw).unwrap();
12322 }
12323
12324 fn volume() -> (std::sync::Arc<MemDev>, u32, u32, u32, u32) {
12327 let dev = formatted();
12328 set_incompat_bit(&dev, crate::features::Incompat::INLINE_DATA.bits());
12329 let fs = mount(&dev);
12330 let d = fs.apply_mkdir("/d", 0o755).unwrap();
12331 let x = fs.apply_create("/x", 0o644).unwrap();
12332 let sub = fs.apply_mkdir("/sub", 0o755).unwrap();
12333 let zz = fs.apply_create("/zz", 0o644).unwrap();
12334 let mut in_block = dirent(x, b"x", 1, 12);
12335 in_block.extend(dirent(sub, b"sub", 2, 44));
12336 make_inline_dir(&fs, d, 2, &in_block, &dirent(zz, b"zz", 1, 20));
12337 drop(fs);
12338 (dev, d, x, sub, zz)
12339 }
12340
12341 #[test]
12342 fn a_lookup_reads_entries_from_byte_4_and_from_the_continuation() {
12343 let (dev, d, x, sub, zz) = volume();
12344 let fs = mount(&dev);
12345 assert!(fs.read_inode_verified(d).unwrap().0.has_inline_data());
12346 for (path, want) in [
12347 ("/d/x", x),
12348 ("/d/sub", sub),
12349 ("/d/zz", zz),
12350 ("/d/.", d),
12351 ("/d/..", 2),
12352 ] {
12353 assert_eq!(
12354 resolve(&fs, path).map_err(|e| format!("{e:?}")),
12355 Ok(want),
12356 "{path}"
12357 );
12358 }
12359 assert!(matches!(resolve(&fs, "/d/nope"), Err(Error::NotFound)));
12360 }
12361
12362 #[test]
12363 fn readdir_lists_the_synthesised_dots_the_block_entries_and_the_continuation() {
12364 use std::ffi::{CStr, CString};
12365 let (dev, d, x, sub, zz) = volume();
12366 let image = fs_ext4_test_support::temp_dir()
12367 .join(format!("fs_ext4_inline_readdir_{}.img", std::process::id()));
12368 std::fs::write(&image, &*dev.bytes.lock().unwrap()).unwrap();
12369 let path = CString::new(image.to_str().unwrap()).unwrap();
12370 let mut listed = Vec::new();
12371 let err = unsafe {
12372 let fs = crate::capi::fs_ext4_mount(path.as_ptr());
12373 assert!(!fs.is_null(), "mount");
12374 let dir = CString::new("/d").unwrap();
12375 let it = crate::capi::fs_ext4_dir_open(fs, dir.as_ptr());
12376 let err = if it.is_null() {
12377 Some(
12378 CStr::from_ptr(crate::capi::fs_ext4_last_error())
12379 .to_string_lossy()
12380 .into_owned(),
12381 )
12382 } else {
12383 loop {
12384 let e = crate::capi::fs_ext4_dir_next(it);
12385 if e.is_null() {
12386 break;
12387 }
12388 let name = CStr::from_ptr((*e).name.as_ptr())
12389 .to_string_lossy()
12390 .into_owned();
12391 listed.push((name, (*e).inode));
12392 }
12393 crate::capi::fs_ext4_dir_close(it);
12394 None
12395 };
12396 crate::capi::fs_ext4_umount(fs);
12397 err
12398 };
12399 let _ = std::fs::remove_file(&image);
12400 assert_eq!(err, None, "dir_open /d");
12401 listed.sort();
12402 let mut want: Vec<(String, u32)> =
12403 [(".", d), ("..", 2), ("sub", sub), ("x", x), ("zz", zz)]
12404 .iter()
12405 .map(|(n, i)| (n.to_string(), *i))
12406 .collect();
12407 want.sort();
12408 assert_eq!(listed, want);
12409 }
12410
12411 #[test]
12412 fn the_inode_api_reads_an_inline_directory() {
12413 let (dev, d, x, sub, zz) = volume();
12414 let fs = mount(&dev);
12415 for (name, want) in [
12416 (&b"x"[..], x),
12417 (b"sub", sub),
12418 (b"zz", zz),
12419 (b".", d),
12420 (b"..", 2),
12421 ] {
12422 assert_eq!(
12423 fs.lookup_at(d, name).map_err(|e| format!("{e:?}")),
12424 Ok(want),
12425 "lookup_at {}",
12426 String::from_utf8_lossy(name)
12427 );
12428 }
12429 assert!(matches!(fs.lookup_at(d, b"nope"), Err(Error::NotFound)));
12430 let mut listed: Vec<(Vec<u8>, u32)> = fs
12431 .read_dir_ino(d)
12432 .map_err(|e| format!("{e:?}"))
12433 .expect("read_dir_ino")
12434 .into_iter()
12435 .map(|e| (e.name, e.inode))
12436 .collect();
12437 listed.sort();
12438 let mut want: Vec<(Vec<u8>, u32)> =
12439 [(".", d), ("..", 2), ("sub", sub), ("x", x), ("zz", zz)]
12440 .iter()
12441 .map(|(n, i)| (n.as_bytes().to_vec(), *i))
12442 .collect();
12443 want.sort();
12444 assert_eq!(listed, want);
12445 }
12446 }
12447}
12448
12449#[cfg(test)]
12452mod dir_block_role_tests {
12453 use super::{DirBlockRole, Filesystem};
12454 use crate::inode::{Inode, InodeFlags};
12455
12456 fn dir(indexed: bool) -> Inode {
12457 let mut raw = vec![0u8; 256];
12458 raw[0..2].copy_from_slice(&0x41EDu16.to_le_bytes());
12459 if indexed {
12460 raw[0x20..0x24].copy_from_slice(&InodeFlags::INDEX.bits().to_le_bytes());
12461 }
12462 Inode::parse(&raw).unwrap()
12463 }
12464
12465 fn block_starting_with(inode: u32, span: u16) -> Vec<u8> {
12466 let mut block = vec![0u8; 1024];
12467 block[0..4].copy_from_slice(&inode.to_le_bytes());
12468 block[4..6].copy_from_slice(&span.to_le_bytes());
12469 block
12470 }
12471
12472 #[test]
12473 fn roles_follow_the_documented_shapes() {
12474 let node = block_starting_with(0, 1024);
12475 let leaf = block_starting_with(12, 16);
12476 let empty_leaf_start = block_starting_with(0, 16);
12477 let role = Filesystem::dir_block_role;
12478 assert_eq!(role(&dir(true), 0, &leaf), DirBlockRole::IndexRoot);
12479 assert_eq!(role(&dir(true), 3, &node), DirBlockRole::IndexNode);
12480 assert_eq!(role(&dir(true), 3, &leaf), DirBlockRole::Entries);
12481 assert_eq!(
12482 role(&dir(true), 3, &empty_leaf_start),
12483 DirBlockRole::Entries
12484 );
12485 assert_eq!(role(&dir(false), 0, &node), DirBlockRole::Entries);
12487 assert_eq!(role(&dir(false), 3, &node), DirBlockRole::Entries);
12488 }
12489
12490 mod needs_host {
12492 use super::super::{DirBlockRole, Filesystem};
12493 use crate::block_io::FileDevice;
12494 use std::collections::BTreeSet;
12495 use std::sync::Arc;
12496
12497 fn debugfs_tree(image: &str, path: &str) -> (BTreeSet<u64>, BTreeSet<u64>) {
12501 let out = fs_ext4_test_support::oracle("debugfs")
12502 .args(["-R", &format!("htree {path}"), image])
12503 .output();
12504 let text = String::from_utf8_lossy(&out.stdout).into_owned();
12505 let levels: u32 = text
12506 .lines()
12507 .find_map(|l| l.trim().strip_prefix("Indirect levels: "))
12508 .unwrap_or_else(|| panic!("{path} is not indexed:\n{text}"))
12509 .trim()
12510 .parse()
12511 .unwrap();
12512 assert_eq!(
12513 levels, 1,
12514 "{path}: the fixture should have one interior level"
12515 );
12516 let block_of = |l: &str| -> Option<u64> {
12517 l.split("block ")
12518 .nth(1)?
12519 .split(|c: char| !c.is_ascii_digit())
12520 .next()?
12521 .parse()
12522 .ok()
12523 };
12524 let root_map: BTreeSet<u64> = text
12526 .lines()
12527 .skip_while(|l| !l.starts_with("Entry #"))
12528 .take_while(|l| !l.trim().is_empty())
12529 .filter_map(block_of)
12530 .collect();
12531 let leaves: BTreeSet<u64> = text
12532 .lines()
12533 .filter(|l| l.starts_with("Reading directory block "))
12534 .filter_map(|l| l.trim_start_matches("Reading directory ").split(',').next())
12535 .filter_map(block_of)
12536 .collect();
12537 (root_map, leaves)
12538 }
12539
12540 fn classify(built_by: &str, image: &str) {
12541 let (nodes, leaves) = debugfs_tree(image, "/d");
12542 assert!(
12543 nodes.len() >= 2 && leaves.len() > 100,
12544 "[{built_by}] tree too small"
12545 );
12546 let fs = Filesystem::mount(Arc::new(FileDevice::open(image).unwrap())).unwrap();
12547 let ino = fs.lookup_path_bytes(b"/d").unwrap();
12548 let (inode, _) = fs.read_inode_verified(ino).unwrap();
12549 let bytes = crate::file_io::read_all(&fs, &inode).unwrap();
12550 let bs = fs.sb.block_size() as usize;
12551 let mut seen = (0, 0, 0);
12552 for (logical, block) in bytes.chunks(bs).enumerate() {
12553 let logical = logical as u64;
12554 let want = if logical == 0 {
12555 seen.0 += 1;
12556 DirBlockRole::IndexRoot
12557 } else if nodes.contains(&logical) {
12558 seen.1 += 1;
12559 DirBlockRole::IndexNode
12560 } else {
12561 assert!(leaves.contains(&logical), "[{built_by}] block {logical}");
12562 seen.2 += 1;
12563 DirBlockRole::Entries
12564 };
12565 assert_eq!(
12566 Filesystem::dir_block_role(&inode, logical, block),
12567 want,
12568 "[{built_by}] logical block {logical}"
12569 );
12570 fs.refuse_unverified_dir_block(ino, &inode, logical, block)
12572 .unwrap_or_else(|e| panic!("[{built_by}] block {logical}: {e:?}"));
12573 }
12574 assert_eq!(seen, (1, nodes.len(), leaves.len()), "[{built_by}]");
12575 drop(fs);
12576
12577 {
12579 let fs = Filesystem::mount(Arc::new(FileDevice::open_rw(image).unwrap())).unwrap();
12580 fs.apply_create("/d/added", 0o644).unwrap();
12581 fs.apply_unlink(&format!("/d/{}", name(7))).unwrap();
12582 }
12583 fs_ext4_test_support::assert_e2fsck_clean(image, built_by);
12584 }
12585
12586 fn name(i: usize) -> String {
12587 format!("entry_{i:04}_{}", "n".repeat(180))
12588 }
12589
12590 fn volume(tag: &str, source: Option<&str>) -> String {
12591 let image = fs_ext4_test_support::temp_path!(
12592 "fs_ext4_dx_role_{tag}_{}.img",
12593 std::process::id()
12594 );
12595 std::fs::File::create(&image)
12596 .and_then(|f| f.set_len(32 << 20))
12597 .unwrap();
12598 let mut mkfs =
12599 fs_ext4_test_support::oracle("mkfs.ext4").args(["-q", "-F", "-b", "1024"]);
12600 if let Some(dir) = source {
12601 mkfs = mkfs.args(["-d", dir]);
12602 }
12603 let out = mkfs.arg(&image).output();
12604 assert!(
12605 out.status.success(),
12606 "{}",
12607 String::from_utf8_lossy(&out.stderr)
12608 );
12609 image
12610 }
12611
12612 #[test]
12615 fn a_kernel_grown_tree_is_classified_as_debugfs_reads_it() {
12616 let image = volume("kernel", None);
12617 let script = format!(
12618 "mkdir \"$MNT/d\"\ncd \"$MNT/d\"\nfor i in $(seq 0 699); do : > \"$(printf 'entry_%04d_' $i){}\"; done\nsync\n",
12619 "n".repeat(180)
12620 );
12621 let out = fs_ext4_test_support::guest_kernel_write(&image, &script);
12622 assert!(
12623 out.status.success(),
12624 "{}",
12625 String::from_utf8_lossy(&out.stderr)
12626 );
12627 classify("kernel", &image);
12628 let _ = std::fs::remove_file(&image);
12629 }
12630
12631 #[test]
12633 fn an_e2fsck_built_tree_is_classified_as_debugfs_reads_it() {
12634 let root =
12635 fs_ext4_test_support::temp_path!("fs_ext4_dx_role_src_{}", std::process::id());
12636 std::fs::create_dir_all(format!("{root}/d")).unwrap();
12637 for i in 0..700 {
12638 std::fs::write(format!("{root}/d/{}", name(i)), b"").unwrap();
12639 }
12640 let image = volume("e2fsck", Some(&root));
12641 let _ = fs_ext4_test_support::oracle("e2fsck")
12642 .args(["-fyD", &image])
12643 .judged();
12644 classify("e2fsck -D", &image);
12645 let _ = std::fs::remove_dir_all(&root);
12646 let _ = std::fs::remove_file(&image);
12647 }
12648 }
12649}