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