ext4_view/lib.rs
1// Copyright 2024 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! This crate provides read-only access to [ext4] filesystems. It also
10//! works with [ext2] filesystems.
11//!
12//! The main entry point is the [`Ext4`] struct.
13//!
14//! [ext2]: https://en.wikipedia.org/wiki/Ext2
15//! [ext4]: https://en.wikipedia.org/wiki/Ext4
16//!
17//! # Example
18//!
19//! This example reads the filesystem data from a byte vector, then
20//! looks at files and directories in the filesystem.
21//!
22//! ```
23//! use ext4_view::{Ext4, Ext4Error, Metadata};
24//!
25//! fn in_memory_example(fs_data: Vec<u8>) -> Result<(), Ext4Error> {
26//! let fs = Ext4::load(Box::new(fs_data)).unwrap();
27//!
28//! let path = "/some/file";
29//!
30//! // Read a file's contents.
31//! let file_data: Vec<u8> = fs.read(path)?;
32//!
33//! // Read a file's contents as a string.
34//! let file_str: String = fs.read_to_string(path)?;
35//!
36//! // Check whether a path exists.
37//! let exists: bool = fs.exists(path)?;
38//!
39//! // Get metadata (file type, permissions, etc).
40//! let metadata: Metadata = fs.metadata(path)?;
41//!
42//! // Print each entry in a directory.
43//! for entry in fs.read_dir("/some/dir")? {
44//! let entry = entry?;
45//! println!("{}", entry.path().display());
46//! }
47//!
48//! Ok(())
49//! }
50//! ```
51//! # Loading a filesystem
52//!
53//! Call [`Ext4::load`] to load a filesystem. The source data can be
54//! anything that implements the [`Ext4Read`] trait. The simplest form
55//! of source data is a `Vec<u8>` containing the whole filesystem.
56//!
57//! If the `std` feature is enabled, [`Ext4Read`] is implemented for
58//! [`std::fs::File`]. As a shortcut, you can also use
59//! [`Ext4::load_from_path`] to open a path and read the filesystem from
60//! it.
61//!
62//! For other cases, implement [`Ext4Read`] for your data source. This
63//! trait has a single method which reads bytes into a byte slice.
64//!
65//! Note that the underlying data should never be changed while the
66//! filesystem is in use.
67//!
68//! # Paths
69//!
70//! Paths in the filesystem are represented by [`Path`] and
71//! [`PathBuf`]. These types are similar to the types of the same names
72//! in [`std::path`].
73//!
74//! Functions that take a path as input accept a variety of types
75//! including strings.
76//!
77//! # Errors
78//!
79//! Most functions return [`Ext4Error`] on failure. This type is broadly
80//! similar to [`std::io::Error`], with a few notable additions:
81//! * Errors that come from the underlying reader are returned as
82//! [`Ext4Error::Io`].
83//! * If the filesystem is corrupt in some way, [`Ext4Error::Corrupt`]
84//! is returned.
85//! * If the filesystem can't be read due to a limitation of the
86//! library, [`Ext4Error::Incompatible`] is returned. Please [file a
87//! bug][issues] if you encounter an incompatibility so we know to
88//! prioritize a fix!
89//!
90//! Some functions list specific errors that may occur. These lists are
91//! not exhaustive; calling code should be prepared to handle other
92//! errors such as [`Ext4Error::Io`].
93//!
94//! [issues]: https://github.com/nicholasbishop/ext4-view-rs/issues
95
96#![cfg_attr(not(any(feature = "std", test)), no_std)]
97#![cfg_attr(docsrs, feature(doc_cfg))]
98#![forbid(unsafe_code)]
99#![warn(
100 clippy::arithmetic_side_effects,
101 clippy::as_conversions,
102 clippy::must_use_candidate,
103 clippy::use_self
104)]
105#![warn(missing_docs)]
106#![warn(unreachable_pub)]
107
108extern crate alloc;
109
110mod block_cache;
111mod block_group;
112mod block_index;
113mod block_size;
114mod checksum;
115mod dir;
116mod dir_block;
117mod dir_entry;
118mod dir_entry_hash;
119mod dir_htree;
120mod error;
121mod extent;
122mod features;
123mod file;
124mod file_type;
125mod format;
126mod inode;
127mod iters;
128mod journal;
129mod label;
130mod metadata;
131mod path;
132mod reader;
133mod resolve;
134mod superblock;
135mod util;
136mod uuid;
137
138#[cfg(all(test, feature = "std"))]
139mod test_util;
140
141use alloc::boxed::Box;
142use alloc::rc::Rc;
143use alloc::string::String;
144use alloc::vec;
145use alloc::vec::Vec;
146use block_cache::BlockCache;
147use block_group::BlockGroupDescriptor;
148use block_index::FsBlockIndex;
149use core::cell::RefCell;
150use core::fmt::{self, Debug, Formatter};
151use error::CorruptKind;
152use features::ReadOnlyCompatibleFeatures;
153use inode::{Inode, InodeIndex};
154use journal::Journal;
155use resolve::FollowSymlinks;
156use superblock::Superblock;
157use util::usize_from_u32;
158
159pub use dir_entry::{DirEntry, DirEntryName, DirEntryNameError};
160pub use error::{Corrupt, Ext4Error, Incompatible};
161pub use features::IncompatibleFeatures;
162pub use file::File;
163pub use file_type::FileType;
164pub use format::BytesDisplay;
165pub use iters::read_dir::ReadDir;
166pub use label::Label;
167pub use metadata::Metadata;
168pub use path::{Component, Components, Path, PathBuf, PathError};
169#[cfg(feature = "async")]
170pub use reader::Ext4ReadAsync;
171pub use reader::{Ext4Read, MemIoError};
172pub use uuid::Uuid;
173
174struct Ext4Inner {
175 superblock: Superblock,
176 block_group_descriptors: Vec<BlockGroupDescriptor>,
177 journal: Journal,
178 block_cache: RefCell<BlockCache>,
179
180 /// Reader providing access to the underlying storage.
181 ///
182 /// Stored as `Box<dyn Ext4Read>` rather than a generic type to make
183 /// the `Ext4` type more convenient to pass around for users of the API.
184 ///
185 /// The `Ext4Read::read` method takes `&mut self`, because readers
186 /// like `std::fs::File` are mutable. However, the `Ext4` API is
187 /// logically const -- it provides read-only access to the
188 /// filesystem. So the box is wrapped in `RefCell` to allow the
189 /// mutable method to be called with an immutable `&Ext4Inner`
190 /// reference. `RefCell` enforces at runtime that only one mutable
191 /// borrow exists at a time.
192 reader: RefCell<Box<dyn Ext4Read>>,
193}
194
195/// Read-only access to an [ext4] filesystem.
196///
197/// [ext4]: https://en.wikipedia.org/wiki/Ext4
198#[derive(Clone)]
199pub struct Ext4(Rc<Ext4Inner>);
200
201impl Ext4 {
202 /// Load an `Ext4` instance from the given `reader`.
203 ///
204 /// This reads and validates the superblock, block group
205 /// descriptors, and journal. No other data is read.
206 pub fn load(mut reader: Box<dyn Ext4Read>) -> Result<Self, Ext4Error> {
207 // The first 1024 bytes are reserved for "weird" stuff like x86
208 // boot sectors.
209 let superblock_start = 1024;
210 let mut data = vec![0; Superblock::SIZE_IN_BYTES_ON_DISK];
211 reader
212 .read(superblock_start, &mut data)
213 .map_err(Ext4Error::Io)?;
214
215 let superblock = Superblock::from_bytes(&data)?;
216 let block_cache =
217 BlockCache::new(superblock.block_size, superblock.blocks_count)?;
218
219 let mut fs = Self(Rc::new(Ext4Inner {
220 block_group_descriptors: BlockGroupDescriptor::read_all(
221 &superblock,
222 &mut *reader,
223 )?,
224 reader: RefCell::new(reader),
225 superblock,
226 // Initialize with an empty journal, because loading the
227 // journal requires a valid `Ext4` object.
228 journal: Journal::empty(),
229 block_cache: RefCell::new(block_cache),
230 }));
231
232 // Load the actual journal, if present.
233 let journal = Journal::load(&fs)?;
234 Rc::get_mut(&mut fs.0).unwrap().journal = journal;
235
236 Ok(fs)
237 }
238
239 /// Load an `Ext4` instance from an async-first reader.
240 ///
241 /// This is an adapter around [`Ext4::load`]. The filesystem parser
242 /// remains synchronous, but each backing read is bridged through the
243 /// provided async reader.
244 #[cfg(feature = "async")]
245 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
246 pub async fn load_async(
247 reader: Box<dyn Ext4ReadAsync>,
248 ) -> Result<Self, Ext4Error> {
249 Self::load(Box::new(reader::BlockingAsyncReader::new(reader)))
250 }
251
252 /// Load an `Ext4` filesystem from the given `path`.
253 ///
254 /// This reads and validates the superblock and block group
255 /// descriptors. No other data is read.
256 #[cfg(feature = "std")]
257 pub fn load_from_path<P: AsRef<std::path::Path>>(
258 path: P,
259 ) -> Result<Self, Ext4Error> {
260 fn inner(path: &std::path::Path) -> Result<Ext4, Ext4Error> {
261 let file = std::fs::File::open(path)
262 .map_err(|e| Ext4Error::Io(Box::new(e)))?;
263 Ext4::load(Box::new(file))
264 }
265
266 inner(path.as_ref())
267 }
268
269 /// Get the filesystem label.
270 #[must_use]
271 pub fn label(&self) -> &Label {
272 &self.0.superblock.label
273 }
274
275 /// Get the filesystem UUID.
276 #[must_use]
277 pub fn uuid(&self) -> Uuid {
278 self.0.superblock.uuid
279 }
280
281 /// Return true if the filesystem has metadata checksums enabled,
282 /// false otherwise.
283 fn has_metadata_checksums(&self) -> bool {
284 self.0
285 .superblock
286 .read_only_compatible_features
287 .contains(ReadOnlyCompatibleFeatures::METADATA_CHECKSUMS)
288 }
289
290 /// Read the inode of the root `/` directory.
291 fn read_root_inode(&self) -> Result<Inode, Ext4Error> {
292 let root_inode_index = InodeIndex::new(2).unwrap();
293 Inode::read(self, root_inode_index)
294 }
295
296 /// Read data from a block.
297 ///
298 /// `block_index`: an absolute block within the filesystem.
299 ///
300 /// `offset_within_block`: the byte offset within the block to start
301 /// reading from.
302 ///
303 /// `dst`: byte buffer to read into. This also controls the length
304 /// of the read.
305 ///
306 /// The first 1024 bytes of the filesystem are reserved for
307 /// non-filesystem data. Reads are not allowed there.
308 ///
309 /// The read cannot cross block boundaries. This implies that:
310 /// * `offset_within_block < block_size`
311 /// * `offset_within_block + dst.len() <= block_size`
312 ///
313 /// If any of these conditions are violated, a `CorruptKind::BlockRead`
314 /// error is returned.
315 fn read_from_block(
316 &self,
317 original_block_index: FsBlockIndex,
318 offset_within_block: u32,
319 dst: &mut [u8],
320 ) -> Result<(), Ext4Error> {
321 let block_index = self.0.journal.map_block_index(original_block_index);
322
323 let err = || {
324 Ext4Error::from(CorruptKind::BlockRead {
325 block_index,
326 original_block_index,
327 offset_within_block,
328 read_len: dst.len(),
329 })
330 };
331
332 // The first 1024 bytes are reserved for non-filesystem
333 // data. This conveniently allows for something like a null
334 // pointer check.
335 if block_index == 0 && offset_within_block < 1024 {
336 return Err(err());
337 }
338
339 // Check the block index.
340 if block_index >= self.0.superblock.blocks_count {
341 return Err(err());
342 }
343
344 // The start of the read must be less than the block size.
345 let block_size = self.0.superblock.block_size;
346 if offset_within_block >= block_size {
347 return Err(err());
348 }
349
350 // The end of the read must be less than or equal to the block size.
351 let read_end = usize_from_u32(offset_within_block)
352 .checked_add(dst.len())
353 .ok_or_else(err)?;
354 if read_end > block_size {
355 return Err(err());
356 }
357
358 let mut block_cache = self.0.block_cache.borrow_mut();
359 let cached_block = block_cache.get_or_insert_blocks(
360 block_index,
361 |buf: &mut [u8]| {
362 // Get the absolute byte to start reading from.
363 let start_byte = block_index
364 .checked_mul(block_size.to_u64())
365 .ok_or_else(err)?;
366 self.0
367 .reader
368 .borrow_mut()
369 .read(start_byte, buf)
370 .map_err(Ext4Error::Io)
371 },
372 )?;
373
374 dst.copy_from_slice(
375 &cached_block[usize_from_u32(offset_within_block)..read_end],
376 );
377
378 Ok(())
379 }
380
381 /// Read the entire contents of a file into a `Vec<u8>`.
382 ///
383 /// Holes are filled with zero.
384 ///
385 /// Fails with `FileTooLarge` if the size of the file is too large
386 /// to fit in a [`usize`].
387 fn read_inode_file(&self, inode: &Inode) -> Result<Vec<u8>, Ext4Error> {
388 // Get the file size and initialize the output vector.
389 let file_size_in_bytes = usize::try_from(inode.metadata.size_in_bytes)
390 .map_err(|_| Ext4Error::FileTooLarge)?;
391 let mut dst = vec![0; file_size_in_bytes];
392
393 // Use `File` to read the data in chunks.
394 let mut file = File::open_inode(self, inode.clone())?;
395 let mut remaining = dst.as_mut();
396 loop {
397 let bytes_read = file.read_bytes(remaining)?;
398 if bytes_read == 0 {
399 break;
400 }
401 remaining = &mut remaining[bytes_read..];
402 }
403 Ok(dst)
404 }
405
406 /// Follow a path to get an inode.
407 fn path_to_inode(
408 &self,
409 path: Path<'_>,
410 follow: FollowSymlinks,
411 ) -> Result<Inode, Ext4Error> {
412 resolve::resolve_path(self, path, follow).map(|v| v.0)
413 }
414}
415
416/// These methods mirror the [`std::fs`][stdfs] API.
417///
418/// [stdfs]: https://doc.rust-lang.org/std/fs/index.html
419impl Ext4 {
420 /// Get the canonical, absolute form of a path with all intermediate
421 /// components normalized and symbolic links resolved.
422 ///
423 /// # Errors
424 ///
425 /// An error will be returned if:
426 /// * `path` is not absolute.
427 /// * `path` does not exist.
428 ///
429 /// This is not an exhaustive list of errors, see the
430 /// [crate documentation](crate#errors).
431 pub fn canonicalize<'p, P>(&self, path: P) -> Result<PathBuf, Ext4Error>
432 where
433 P: TryInto<Path<'p>>,
434 {
435 let path = path.try_into().map_err(|_| Ext4Error::MalformedPath)?;
436 resolve::resolve_path(self, path, FollowSymlinks::All).map(|v| v.1)
437 }
438
439 /// Open the file at `path`.
440 ///
441 /// # Errors
442 ///
443 /// An error will be returned if:
444 /// * `path` is not absolute.
445 /// * `path` does not exist.
446 /// * `path` is a directory or special file type.
447 ///
448 /// This is not an exhaustive list of errors, see the
449 /// [crate documentation](crate#errors).
450 pub fn open<'p, P>(&self, path: P) -> Result<File, Ext4Error>
451 where
452 P: TryInto<Path<'p>>,
453 {
454 File::open(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
455 }
456
457 /// Read the entire contents of a file as raw bytes.
458 ///
459 /// # Errors
460 ///
461 /// An error will be returned if:
462 /// * `path` is not absolute.
463 /// * `path` does not exist.
464 /// * `path` is a directory or special file type.
465 ///
466 /// This is not an exhaustive list of errors, see the
467 /// [crate documentation](crate#errors).
468 pub fn read<'p, P>(&self, path: P) -> Result<Vec<u8>, Ext4Error>
469 where
470 P: TryInto<Path<'p>>,
471 {
472 fn inner(fs: &Ext4, path: Path<'_>) -> Result<Vec<u8>, Ext4Error> {
473 let inode = fs.path_to_inode(path, FollowSymlinks::All)?;
474
475 if inode.metadata.is_dir() {
476 return Err(Ext4Error::IsADirectory);
477 }
478 if !inode.metadata.file_type.is_regular_file() {
479 return Err(Ext4Error::IsASpecialFile);
480 }
481
482 fs.read_inode_file(&inode)
483 }
484
485 inner(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
486 }
487
488 /// Read the entire contents of a file as a string.
489 ///
490 /// # Errors
491 ///
492 /// An error will be returned if:
493 /// * `path` is not absolute.
494 /// * `path` does not exist.
495 /// * `path` is a directory or special file type.
496 ///
497 /// This is not an exhaustive list of errors, see the
498 /// [crate documentation](crate#errors).
499 pub fn read_to_string<'p, P>(&self, path: P) -> Result<String, Ext4Error>
500 where
501 P: TryInto<Path<'p>>,
502 {
503 fn inner(fs: &Ext4, path: Path<'_>) -> Result<String, Ext4Error> {
504 let content = fs.read(path)?;
505 String::from_utf8(content).map_err(|_| Ext4Error::NotUtf8)
506 }
507
508 inner(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
509 }
510
511 /// Get the target of a symbolic link.
512 ///
513 /// The final component of `path` must be a symlink. If the path
514 /// contains any symlinks in components prior to the end, they will
515 /// be fully resolved as normal.
516 ///
517 /// # Errors
518 ///
519 /// An error will be returned if:
520 /// * `path` is not absolute.
521 /// * The final component of `path` is not a symlink.
522 ///
523 /// This is not an exhaustive list of errors, see the
524 /// [crate documentation](crate#errors).
525 pub fn read_link<'p, P>(&self, path: P) -> Result<PathBuf, Ext4Error>
526 where
527 P: TryInto<Path<'p>>,
528 {
529 fn inner(fs: &Ext4, path: Path<'_>) -> Result<PathBuf, Ext4Error> {
530 let inode =
531 fs.path_to_inode(path, FollowSymlinks::ExcludeFinalComponent)?;
532 inode.symlink_target(fs)
533 }
534
535 inner(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
536 }
537
538 /// Get an iterator over the entries in a directory.
539 ///
540 /// # Errors
541 ///
542 /// An error will be returned if:
543 /// * `path` is not absolute.
544 /// * `path` does not exist.
545 /// * `path` is not a directory.
546 ///
547 /// This is not an exhaustive list of errors, see the
548 /// [crate documentation](crate#errors).
549 pub fn read_dir<'p, P>(&self, path: P) -> Result<ReadDir, Ext4Error>
550 where
551 P: TryInto<Path<'p>>,
552 {
553 fn inner(fs: &Ext4, path: Path<'_>) -> Result<ReadDir, Ext4Error> {
554 let inode = fs.path_to_inode(path, FollowSymlinks::All)?;
555
556 if !inode.metadata.is_dir() {
557 return Err(Ext4Error::NotADirectory);
558 }
559
560 ReadDir::new(fs.clone(), &inode, path.into())
561 }
562
563 inner(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
564 }
565
566 /// Check if `path` exists.
567 ///
568 /// Returns `Ok(true)` if `path` exists, or `Ok(false)` if it does
569 /// not exist.
570 ///
571 /// # Errors
572 ///
573 /// An error will be returned if:
574 /// * `path` is not absolute.
575 ///
576 /// This is not an exhaustive list of errors, see the
577 /// [crate documentation](crate#errors).
578 pub fn exists<'p, P>(&self, path: P) -> Result<bool, Ext4Error>
579 where
580 P: TryInto<Path<'p>>,
581 {
582 fn inner(fs: &Ext4, path: Path<'_>) -> Result<bool, Ext4Error> {
583 match fs.path_to_inode(path, FollowSymlinks::All) {
584 Ok(_) => Ok(true),
585 Err(Ext4Error::NotFound) => Ok(false),
586 Err(err) => Err(err),
587 }
588 }
589
590 inner(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
591 }
592
593 /// Get [`Metadata`] for `path`.
594 ///
595 /// # Errors
596 ///
597 /// An error will be returned if:
598 /// * `path` is not absolute.
599 /// * `path` does not exist.
600 ///
601 /// This is not an exhaustive list of errors, see the
602 /// [crate documentation](crate#errors).
603 pub fn metadata<'p, P>(&self, path: P) -> Result<Metadata, Ext4Error>
604 where
605 P: TryInto<Path<'p>>,
606 {
607 fn inner(fs: &Ext4, path: Path<'_>) -> Result<Metadata, Ext4Error> {
608 let inode = fs.path_to_inode(path, FollowSymlinks::All)?;
609 Ok(inode.metadata)
610 }
611
612 inner(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
613 }
614
615 /// Get [`Metadata`] for `path`.
616 ///
617 /// If the final component of `path` is a symlink, information about
618 /// the symlink itself will be returned, not the symlink's
619 /// targets. Any other symlink components of `path` are resolved as
620 /// normal.
621 ///
622 /// # Errors
623 ///
624 /// An error will be returned if:
625 /// * `path` is not absolute.
626 /// * `path` does not exist.
627 ///
628 /// This is not an exhaustive list of errors, see the
629 /// [crate documentation](crate#errors).
630 pub fn symlink_metadata<'p, P>(
631 &self,
632 path: P,
633 ) -> Result<Metadata, Ext4Error>
634 where
635 P: TryInto<Path<'p>>,
636 {
637 fn inner(fs: &Ext4, path: Path<'_>) -> Result<Metadata, Ext4Error> {
638 let inode =
639 fs.path_to_inode(path, FollowSymlinks::ExcludeFinalComponent)?;
640 Ok(inode.metadata)
641 }
642
643 inner(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
644 }
645}
646
647impl Debug for Ext4 {
648 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
649 // Exclude the reader field, which does not impl Debug. Even if
650 // it did, it could be annoying to print out (e.g. if the reader
651 // is a Vec it might contain many megabytes of data).
652 f.debug_struct("Ext4")
653 .field("superblock", &self.0.superblock)
654 .field("block_group_descriptors", &self.0.block_group_descriptors)
655 .finish_non_exhaustive()
656 }
657}
658
659#[cfg(feature = "std")]
660#[cfg(test)]
661mod tests {
662 use super::*;
663 use test_util::load_test_disk1;
664
665 #[test]
666 fn test_load_errors() {
667 // Not enough data.
668 assert!(matches!(
669 Ext4::load(Box::new(vec![])).unwrap_err(),
670 Ext4Error::Io(_)
671 ));
672
673 // Invalid superblock.
674 assert_eq!(
675 Ext4::load(Box::new(vec![0; 2048])).unwrap_err(),
676 CorruptKind::SuperblockMagic
677 );
678
679 // Not enough data to read the block group descriptors.
680 let mut fs_data = vec![0; 2048];
681 fs_data[1024..2048]
682 .copy_from_slice(include_bytes!("../test_data/raw_superblock.bin"));
683 assert!(matches!(
684 Ext4::load(Box::new(fs_data.clone())).unwrap_err(),
685 Ext4Error::Io(_)
686 ));
687
688 // Invalid block group descriptor checksum.
689 fs_data.resize(3048usize, 0u8);
690 assert_eq!(
691 Ext4::load(Box::new(fs_data.clone())).unwrap_err(),
692 CorruptKind::BlockGroupDescriptorChecksum(0)
693 );
694 }
695
696 /// Test that loading the data from
697 /// https://github.com/nicholasbishop/ext4-view-rs/issues/280 does not
698 /// panic.
699 #[test]
700 fn test_invalid_ext4_data() {
701 // Fill in zeros for the first 1024 bytes, then add the test data.
702 let mut data = vec![0; 1024];
703 data.extend(include_bytes!("../test_data/not_ext4.bin"));
704
705 assert_eq!(
706 Ext4::load(Box::new(data)).unwrap_err(),
707 CorruptKind::InvalidBlockSize
708 );
709 }
710
711 fn block_read_error(
712 block_index: FsBlockIndex,
713 offset_within_block: u32,
714 read_len: usize,
715 ) -> CorruptKind {
716 CorruptKind::BlockRead {
717 block_index,
718 original_block_index: block_index,
719 offset_within_block,
720 read_len,
721 }
722 }
723
724 /// Test that reading from the first 1024 bytes of the file fails.
725 #[test]
726 fn test_read_from_block_first_1024() {
727 let fs = load_test_disk1();
728 let mut dst = vec![0; 1];
729 assert_eq!(
730 fs.read_from_block(0, 1023, &mut dst).unwrap_err(),
731 block_read_error(0, 1023, 1),
732 );
733 }
734
735 /// Test that reading past the last block of the file fails.
736 #[test]
737 fn test_read_from_block_past_file_end() {
738 let fs = load_test_disk1();
739 let mut dst = vec![0; 1024];
740 assert_eq!(
741 fs.read_from_block(999_999_999, 0, &mut dst).unwrap_err(),
742 block_read_error(999_999_999, 0, 1024),
743 );
744 }
745
746 /// Test that reading at an offset >= the block size fails.
747 #[test]
748 fn test_read_from_block_invalid_offset() {
749 let fs = load_test_disk1();
750 let mut dst = vec![0; 1024];
751 assert_eq!(
752 fs.read_from_block(1, 1024, &mut dst).unwrap_err(),
753 block_read_error(1, 1024, 1024),
754 );
755 }
756
757 /// Test that reading past the end of the block fails.
758 #[test]
759 fn test_read_from_block_past_block_end() {
760 let fs = load_test_disk1();
761 let mut dst = vec![0; 25];
762 assert_eq!(
763 fs.read_from_block(1, 1000, &mut dst).unwrap_err(),
764 block_read_error(1, 1000, 25),
765 );
766 }
767
768 #[test]
769 fn test_path_to_inode() {
770 let fs = load_test_disk1();
771
772 let follow = FollowSymlinks::All;
773
774 let inode = fs
775 .path_to_inode(Path::try_from("/").unwrap(), follow)
776 .unwrap();
777 assert_eq!(inode.index.get(), 2);
778
779 // Successful lookup.
780 assert!(
781 fs.path_to_inode(Path::try_from("/empty_file").unwrap(), follow)
782 .is_ok()
783 );
784
785 // Successful lookup with a "." component.
786 assert!(
787 fs.path_to_inode(Path::try_from("/./empty_file").unwrap(), follow)
788 .is_ok()
789 );
790
791 // Successful lookup with a ".." component.
792 let inode = fs
793 .path_to_inode(Path::try_from("/empty_dir/..").unwrap(), follow)
794 .unwrap();
795 assert_eq!(inode.index.get(), 2);
796
797 // Successful lookup with symlink.
798 assert!(
799 fs.path_to_inode(Path::try_from("/sym_simple").unwrap(), follow)
800 .is_ok()
801 );
802
803 // Error: not an absolute path.
804 assert!(
805 fs.path_to_inode(Path::try_from("empty_file").unwrap(), follow)
806 .is_err()
807 );
808
809 // Error: invalid child of a valid directory.
810 assert!(
811 fs.path_to_inode(
812 Path::try_from("/empty_dir/does_not_exist").unwrap(),
813 follow
814 )
815 .is_err()
816 );
817
818 // Error: attempted to lookup child of a regular file.
819 assert!(
820 fs.path_to_inode(
821 Path::try_from("/empty_file/does_not_exist").unwrap(),
822 follow
823 )
824 .is_err()
825 );
826
827 // TODO: add deeper paths to the test disk and test here.
828 }
829}