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_auto_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};
169pub use reader::{Ext4Read, MemIoError};
170pub use uuid::Uuid;
171
172struct Ext4Inner {
173 superblock: Superblock,
174 block_group_descriptors: Vec<BlockGroupDescriptor>,
175 journal: Journal,
176 block_cache: RefCell<BlockCache>,
177
178 /// Reader providing access to the underlying storage.
179 ///
180 /// Stored as `Box<dyn Ext4Read>` rather than a generic type to make
181 /// the `Ext4` type more convenient to pass around for users of the API.
182 ///
183 /// The `Ext4Read::read` method takes `&mut self`, because readers
184 /// like `std::fs::File` are mutable. However, the `Ext4` API is
185 /// logically const -- it provides read-only access to the
186 /// filesystem. So the box is wrapped in `RefCell` to allow the
187 /// mutable method to be called with an immutable `&Ext4Inner`
188 /// reference. `RefCell` enforces at runtime that only one mutable
189 /// borrow exists at a time.
190 reader: RefCell<Box<dyn Ext4Read>>,
191}
192
193/// Read-only access to an [ext4] filesystem.
194///
195/// [ext4]: https://en.wikipedia.org/wiki/Ext4
196#[derive(Clone)]
197pub struct Ext4(Rc<Ext4Inner>);
198
199impl Ext4 {
200 /// Load an `Ext4` instance from the given `reader`.
201 ///
202 /// This reads and validates the superblock, block group
203 /// descriptors, and journal. No other data is read.
204 pub fn load(mut reader: Box<dyn Ext4Read>) -> Result<Self, Ext4Error> {
205 // The first 1024 bytes are reserved for "weird" stuff like x86
206 // boot sectors.
207 let superblock_start = 1024;
208 let mut data = vec![0; Superblock::SIZE_IN_BYTES_ON_DISK];
209 reader
210 .read(superblock_start, &mut data)
211 .map_err(Ext4Error::Io)?;
212
213 let superblock = Superblock::from_bytes(&data)?;
214 let block_cache =
215 BlockCache::new(superblock.block_size, superblock.blocks_count)?;
216
217 let mut fs = Self(Rc::new(Ext4Inner {
218 block_group_descriptors: BlockGroupDescriptor::read_all(
219 &superblock,
220 &mut *reader,
221 )?,
222 reader: RefCell::new(reader),
223 superblock,
224 // Initialize with an empty journal, because loading the
225 // journal requires a valid `Ext4` object.
226 journal: Journal::empty(),
227 block_cache: RefCell::new(block_cache),
228 }));
229
230 // Load the actual journal, if present.
231 let journal = Journal::load(&fs)?;
232 Rc::get_mut(&mut fs.0).unwrap().journal = journal;
233
234 Ok(fs)
235 }
236
237 /// Load an `Ext4` filesystem from the given `path`.
238 ///
239 /// This reads and validates the superblock and block group
240 /// descriptors. No other data is read.
241 #[cfg(feature = "std")]
242 pub fn load_from_path<P: AsRef<std::path::Path>>(
243 path: P,
244 ) -> Result<Self, Ext4Error> {
245 fn inner(path: &std::path::Path) -> Result<Ext4, Ext4Error> {
246 let file = std::fs::File::open(path)
247 .map_err(|e| Ext4Error::Io(Box::new(e)))?;
248 Ext4::load(Box::new(file))
249 }
250
251 inner(path.as_ref())
252 }
253
254 /// Get the filesystem label.
255 #[must_use]
256 pub fn label(&self) -> &Label {
257 &self.0.superblock.label
258 }
259
260 /// Get the filesystem UUID.
261 #[must_use]
262 pub fn uuid(&self) -> Uuid {
263 self.0.superblock.uuid
264 }
265
266 /// Return true if the filesystem has metadata checksums enabled,
267 /// false otherwise.
268 fn has_metadata_checksums(&self) -> bool {
269 self.0
270 .superblock
271 .read_only_compatible_features
272 .contains(ReadOnlyCompatibleFeatures::METADATA_CHECKSUMS)
273 }
274
275 /// Read the inode of the root `/` directory.
276 fn read_root_inode(&self) -> Result<Inode, Ext4Error> {
277 let root_inode_index = InodeIndex::new(2).unwrap();
278 Inode::read(self, root_inode_index)
279 }
280
281 /// Read data from a block.
282 ///
283 /// `block_index`: an absolute block within the filesystem.
284 ///
285 /// `offset_within_block`: the byte offset within the block to start
286 /// reading from.
287 ///
288 /// `dst`: byte buffer to read into. This also controls the length
289 /// of the read.
290 ///
291 /// The first 1024 bytes of the filesystem are reserved for
292 /// non-filesystem data. Reads are not allowed there.
293 ///
294 /// The read cannot cross block boundaries. This implies that:
295 /// * `offset_within_block < block_size`
296 /// * `offset_within_block + dst.len() <= block_size`
297 ///
298 /// If any of these conditions are violated, a `CorruptKind::BlockRead`
299 /// error is returned.
300 fn read_from_block(
301 &self,
302 original_block_index: FsBlockIndex,
303 offset_within_block: u32,
304 dst: &mut [u8],
305 ) -> Result<(), Ext4Error> {
306 let block_index = self.0.journal.map_block_index(original_block_index);
307
308 let err = || {
309 Ext4Error::from(CorruptKind::BlockRead {
310 block_index,
311 original_block_index,
312 offset_within_block,
313 read_len: dst.len(),
314 })
315 };
316
317 // The first 1024 bytes are reserved for non-filesystem
318 // data. This conveniently allows for something like a null
319 // pointer check.
320 if block_index == 0 && offset_within_block < 1024 {
321 return Err(err());
322 }
323
324 // Check the block index.
325 if block_index >= self.0.superblock.blocks_count {
326 return Err(err());
327 }
328
329 // The start of the read must be less than the block size.
330 let block_size = self.0.superblock.block_size;
331 if offset_within_block >= block_size {
332 return Err(err());
333 }
334
335 // The end of the read must be less than or equal to the block size.
336 let read_end = usize_from_u32(offset_within_block)
337 .checked_add(dst.len())
338 .ok_or_else(err)?;
339 if read_end > block_size {
340 return Err(err());
341 }
342
343 let mut block_cache = self.0.block_cache.borrow_mut();
344 let cached_block = block_cache.get_or_insert_blocks(
345 block_index,
346 |buf: &mut [u8]| {
347 // Get the absolute byte to start reading from.
348 let start_byte = block_index
349 .checked_mul(block_size.to_u64())
350 .ok_or_else(err)?;
351 self.0
352 .reader
353 .borrow_mut()
354 .read(start_byte, buf)
355 .map_err(Ext4Error::Io)
356 },
357 )?;
358
359 dst.copy_from_slice(
360 &cached_block[usize_from_u32(offset_within_block)..read_end],
361 );
362
363 Ok(())
364 }
365
366 /// Read the entire contents of a file into a `Vec<u8>`.
367 ///
368 /// Holes are filled with zero.
369 ///
370 /// Fails with `FileTooLarge` if the size of the file is too large
371 /// to fit in a [`usize`].
372 fn read_inode_file(&self, inode: &Inode) -> Result<Vec<u8>, Ext4Error> {
373 // Get the file size and initialize the output vector.
374 let file_size_in_bytes = usize::try_from(inode.metadata.size_in_bytes)
375 .map_err(|_| Ext4Error::FileTooLarge)?;
376 let mut dst = vec![0; file_size_in_bytes];
377
378 // Use `File` to read the data in chunks.
379 let mut file = File::open_inode(self, inode.clone())?;
380 let mut remaining = dst.as_mut();
381 loop {
382 let bytes_read = file.read_bytes(remaining)?;
383 if bytes_read == 0 {
384 break;
385 }
386 remaining = &mut remaining[bytes_read..];
387 }
388 Ok(dst)
389 }
390
391 /// Follow a path to get an inode.
392 fn path_to_inode(
393 &self,
394 path: Path<'_>,
395 follow: FollowSymlinks,
396 ) -> Result<Inode, Ext4Error> {
397 resolve::resolve_path(self, path, follow).map(|v| v.0)
398 }
399}
400
401/// These methods mirror the [`std::fs`][stdfs] API.
402///
403/// [stdfs]: https://doc.rust-lang.org/std/fs/index.html
404impl Ext4 {
405 /// Get the canonical, absolute form of a path with all intermediate
406 /// components normalized and symbolic links resolved.
407 ///
408 /// # Errors
409 ///
410 /// An error will be returned if:
411 /// * `path` is not absolute.
412 /// * `path` does not exist.
413 ///
414 /// This is not an exhaustive list of errors, see the
415 /// [crate documentation](crate#errors).
416 pub fn canonicalize<'p, P>(&self, path: P) -> Result<PathBuf, Ext4Error>
417 where
418 P: TryInto<Path<'p>>,
419 {
420 let path = path.try_into().map_err(|_| Ext4Error::MalformedPath)?;
421 resolve::resolve_path(self, path, FollowSymlinks::All).map(|v| v.1)
422 }
423
424 /// Open the file at `path`.
425 ///
426 /// # Errors
427 ///
428 /// An error will be returned if:
429 /// * `path` is not absolute.
430 /// * `path` does not exist.
431 /// * `path` is a directory or special file type.
432 ///
433 /// This is not an exhaustive list of errors, see the
434 /// [crate documentation](crate#errors).
435 pub fn open<'p, P>(&self, path: P) -> Result<File, Ext4Error>
436 where
437 P: TryInto<Path<'p>>,
438 {
439 File::open(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
440 }
441
442 /// Read the entire contents of a file as raw bytes.
443 ///
444 /// # Errors
445 ///
446 /// An error will be returned if:
447 /// * `path` is not absolute.
448 /// * `path` does not exist.
449 /// * `path` is a directory or special file type.
450 ///
451 /// This is not an exhaustive list of errors, see the
452 /// [crate documentation](crate#errors).
453 pub fn read<'p, P>(&self, path: P) -> Result<Vec<u8>, Ext4Error>
454 where
455 P: TryInto<Path<'p>>,
456 {
457 fn inner(fs: &Ext4, path: Path<'_>) -> Result<Vec<u8>, Ext4Error> {
458 let inode = fs.path_to_inode(path, FollowSymlinks::All)?;
459
460 if inode.metadata.is_dir() {
461 return Err(Ext4Error::IsADirectory);
462 }
463 if !inode.metadata.file_type.is_regular_file() {
464 return Err(Ext4Error::IsASpecialFile);
465 }
466
467 fs.read_inode_file(&inode)
468 }
469
470 inner(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
471 }
472
473 /// Read the entire contents of a file as a string.
474 ///
475 /// # Errors
476 ///
477 /// An error will be returned if:
478 /// * `path` is not absolute.
479 /// * `path` does not exist.
480 /// * `path` is a directory or special file type.
481 ///
482 /// This is not an exhaustive list of errors, see the
483 /// [crate documentation](crate#errors).
484 pub fn read_to_string<'p, P>(&self, path: P) -> Result<String, Ext4Error>
485 where
486 P: TryInto<Path<'p>>,
487 {
488 fn inner(fs: &Ext4, path: Path<'_>) -> Result<String, Ext4Error> {
489 let content = fs.read(path)?;
490 String::from_utf8(content).map_err(|_| Ext4Error::NotUtf8)
491 }
492
493 inner(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
494 }
495
496 /// Get the target of a symbolic link.
497 ///
498 /// The final component of `path` must be a symlink. If the path
499 /// contains any symlinks in components prior to the end, they will
500 /// be fully resolved as normal.
501 ///
502 /// # Errors
503 ///
504 /// An error will be returned if:
505 /// * `path` is not absolute.
506 /// * The final component of `path` is not a symlink.
507 ///
508 /// This is not an exhaustive list of errors, see the
509 /// [crate documentation](crate#errors).
510 pub fn read_link<'p, P>(&self, path: P) -> Result<PathBuf, Ext4Error>
511 where
512 P: TryInto<Path<'p>>,
513 {
514 fn inner(fs: &Ext4, path: Path<'_>) -> Result<PathBuf, Ext4Error> {
515 let inode =
516 fs.path_to_inode(path, FollowSymlinks::ExcludeFinalComponent)?;
517 inode.symlink_target(fs)
518 }
519
520 inner(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
521 }
522
523 /// Get an iterator over the entries in a directory.
524 ///
525 /// # Errors
526 ///
527 /// An error will be returned if:
528 /// * `path` is not absolute.
529 /// * `path` does not exist
530 /// * `path` is not a directory
531 ///
532 /// This is not an exhaustive list of errors, see the
533 /// [crate documentation](crate#errors).
534 pub fn read_dir<'p, P>(&self, path: P) -> Result<ReadDir, Ext4Error>
535 where
536 P: TryInto<Path<'p>>,
537 {
538 fn inner(fs: &Ext4, path: Path<'_>) -> Result<ReadDir, Ext4Error> {
539 let inode = fs.path_to_inode(path, FollowSymlinks::All)?;
540
541 if !inode.metadata.is_dir() {
542 return Err(Ext4Error::NotADirectory);
543 }
544
545 ReadDir::new(fs.clone(), &inode, path.into())
546 }
547
548 inner(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
549 }
550
551 /// Check if `path` exists.
552 ///
553 /// Returns `Ok(true)` if `path` exists, or `Ok(false)` if it does
554 /// not exist.
555 ///
556 /// # Errors
557 ///
558 /// An error will be returned if:
559 /// * `path` is not absolute.
560 ///
561 /// This is not an exhaustive list of errors, see the
562 /// [crate documentation](crate#errors).
563 pub fn exists<'p, P>(&self, path: P) -> Result<bool, Ext4Error>
564 where
565 P: TryInto<Path<'p>>,
566 {
567 fn inner(fs: &Ext4, path: Path<'_>) -> Result<bool, Ext4Error> {
568 match fs.path_to_inode(path, FollowSymlinks::All) {
569 Ok(_) => Ok(true),
570 Err(Ext4Error::NotFound) => Ok(false),
571 Err(err) => Err(err),
572 }
573 }
574
575 inner(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
576 }
577
578 /// Get [`Metadata`] for `path`.
579 ///
580 /// # Errors
581 ///
582 /// An error will be returned if:
583 /// * `path` is not absolute.
584 /// * `path` does not exist.
585 ///
586 /// This is not an exhaustive list of errors, see the
587 /// [crate documentation](crate#errors).
588 pub fn metadata<'p, P>(&self, path: P) -> Result<Metadata, Ext4Error>
589 where
590 P: TryInto<Path<'p>>,
591 {
592 fn inner(fs: &Ext4, path: Path<'_>) -> Result<Metadata, Ext4Error> {
593 let inode = fs.path_to_inode(path, FollowSymlinks::All)?;
594 Ok(inode.metadata)
595 }
596
597 inner(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
598 }
599
600 /// Get [`Metadata`] for `path`.
601 ///
602 /// If the final component of `path` is a symlink, information about
603 /// the symlink itself will be returned, not the symlink's
604 /// targets. Any other symlink components of `path` are resolved as
605 /// normal.
606 ///
607 /// # Errors
608 ///
609 /// An error will be returned if:
610 /// * `path` is not absolute.
611 /// * `path` does not exist.
612 ///
613 /// This is not an exhaustive list of errors, see the
614 /// [crate documentation](crate#errors).
615 pub fn symlink_metadata<'p, P>(
616 &self,
617 path: P,
618 ) -> Result<Metadata, Ext4Error>
619 where
620 P: TryInto<Path<'p>>,
621 {
622 fn inner(fs: &Ext4, path: Path<'_>) -> Result<Metadata, Ext4Error> {
623 let inode =
624 fs.path_to_inode(path, FollowSymlinks::ExcludeFinalComponent)?;
625 Ok(inode.metadata)
626 }
627
628 inner(self, path.try_into().map_err(|_| Ext4Error::MalformedPath)?)
629 }
630}
631
632impl Debug for Ext4 {
633 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
634 // Exclude the reader field, which does not impl Debug. Even if
635 // it did, it could be annoying to print out (e.g. if the reader
636 // is a Vec it might contain many megabytes of data).
637 f.debug_struct("Ext4")
638 .field("superblock", &self.0.superblock)
639 .field("block_group_descriptors", &self.0.block_group_descriptors)
640 .finish_non_exhaustive()
641 }
642}
643
644#[cfg(feature = "std")]
645#[cfg(test)]
646mod tests {
647 use super::*;
648 use test_util::load_test_disk1;
649
650 #[test]
651 fn test_load_errors() {
652 // Not enough data.
653 assert!(matches!(
654 Ext4::load(Box::new(vec![])).unwrap_err(),
655 Ext4Error::Io(_)
656 ));
657
658 // Invalid superblock.
659 assert_eq!(
660 Ext4::load(Box::new(vec![0; 2048])).unwrap_err(),
661 CorruptKind::SuperblockMagic
662 );
663
664 // Not enough data to read the block group descriptors.
665 let mut fs_data = vec![0; 2048];
666 fs_data[1024..2048]
667 .copy_from_slice(include_bytes!("../test_data/raw_superblock.bin"));
668 assert!(matches!(
669 Ext4::load(Box::new(fs_data.clone())).unwrap_err(),
670 Ext4Error::Io(_)
671 ));
672
673 // Invalid block group descriptor checksum.
674 fs_data.resize(3048usize, 0u8);
675 assert_eq!(
676 Ext4::load(Box::new(fs_data.clone())).unwrap_err(),
677 CorruptKind::BlockGroupDescriptorChecksum(0)
678 );
679 }
680
681 /// Test that loading the data from
682 /// https://github.com/nicholasbishop/ext4-view-rs/issues/280 does not
683 /// panic.
684 #[test]
685 fn test_invalid_ext4_data() {
686 // Fill in zeros for the first 1024 bytes, then add the test data.
687 let mut data = vec![0; 1024];
688 data.extend(include_bytes!("../test_data/not_ext4.bin"));
689
690 assert_eq!(
691 Ext4::load(Box::new(data)).unwrap_err(),
692 CorruptKind::InvalidBlockSize
693 );
694 }
695
696 fn block_read_error(
697 block_index: FsBlockIndex,
698 offset_within_block: u32,
699 read_len: usize,
700 ) -> CorruptKind {
701 CorruptKind::BlockRead {
702 block_index,
703 original_block_index: block_index,
704 offset_within_block,
705 read_len,
706 }
707 }
708
709 /// Test that reading from the first 1024 bytes of the file fails.
710 #[test]
711 fn test_read_from_block_first_1024() {
712 let fs = load_test_disk1();
713 let mut dst = vec![0; 1];
714 assert_eq!(
715 fs.read_from_block(0, 1023, &mut dst).unwrap_err(),
716 block_read_error(0, 1023, 1),
717 );
718 }
719
720 /// Test that reading past the last block of the file fails.
721 #[test]
722 fn test_read_from_block_past_file_end() {
723 let fs = load_test_disk1();
724 let mut dst = vec![0; 1024];
725 assert_eq!(
726 fs.read_from_block(999_999_999, 0, &mut dst).unwrap_err(),
727 block_read_error(999_999_999, 0, 1024),
728 );
729 }
730
731 /// Test that reading at an offset >= the block size fails.
732 #[test]
733 fn test_read_from_block_invalid_offset() {
734 let fs = load_test_disk1();
735 let mut dst = vec![0; 1024];
736 assert_eq!(
737 fs.read_from_block(1, 1024, &mut dst).unwrap_err(),
738 block_read_error(1, 1024, 1024),
739 );
740 }
741
742 /// Test that reading past the end of the block fails.
743 #[test]
744 fn test_read_from_block_past_block_end() {
745 let fs = load_test_disk1();
746 let mut dst = vec![0; 25];
747 assert_eq!(
748 fs.read_from_block(1, 1000, &mut dst).unwrap_err(),
749 block_read_error(1, 1000, 25),
750 );
751 }
752
753 #[test]
754 fn test_path_to_inode() {
755 let fs = load_test_disk1();
756
757 let follow = FollowSymlinks::All;
758
759 let inode = fs
760 .path_to_inode(Path::try_from("/").unwrap(), follow)
761 .unwrap();
762 assert_eq!(inode.index.get(), 2);
763
764 // Successful lookup.
765 assert!(
766 fs.path_to_inode(Path::try_from("/empty_file").unwrap(), follow)
767 .is_ok()
768 );
769
770 // Successful lookup with a "." component.
771 assert!(
772 fs.path_to_inode(Path::try_from("/./empty_file").unwrap(), follow)
773 .is_ok()
774 );
775
776 // Successful lookup with a ".." component.
777 let inode = fs
778 .path_to_inode(Path::try_from("/empty_dir/..").unwrap(), follow)
779 .unwrap();
780 assert_eq!(inode.index.get(), 2);
781
782 // Successful lookup with symlink.
783 assert!(
784 fs.path_to_inode(Path::try_from("/sym_simple").unwrap(), follow)
785 .is_ok()
786 );
787
788 // Error: not an absolute path.
789 assert!(
790 fs.path_to_inode(Path::try_from("empty_file").unwrap(), follow)
791 .is_err()
792 );
793
794 // Error: invalid child of a valid directory.
795 assert!(
796 fs.path_to_inode(
797 Path::try_from("/empty_dir/does_not_exist").unwrap(),
798 follow
799 )
800 .is_err()
801 );
802
803 // Error: attempted to lookup child of a regular file.
804 assert!(
805 fs.path_to_inode(
806 Path::try_from("/empty_file/does_not_exist").unwrap(),
807 follow
808 )
809 .is_err()
810 );
811
812 // TODO: add deeper paths to the test disk and test here.
813 }
814}