efs/fs/ext2/
file.rs

1//! Interface to manipulate UNIX files on an ext2 filesystem.
2
3use alloc::borrow::ToOwned;
4use alloc::ffi::CString;
5use alloc::string::{String, ToString};
6use alloc::vec;
7use alloc::vec::Vec;
8use core::fmt::Debug;
9use core::ptr::{addr_of, addr_of_mut, slice_from_raw_parts};
10
11use bitflags::Flags;
12use deku::no_std_io::{Read, Seek, SeekFrom, Write};
13use itertools::Itertools;
14use spin::Mutex;
15
16use super::Ext2Fs;
17use super::directory::{self, Entry, FileType};
18use super::error::Ext2Error;
19use super::inode::{Inode, TypePermissions};
20use crate::arch::{u32_to_usize, u64_to_usize, usize_to_u64};
21use crate::dev::Device;
22use crate::dev::address::Address;
23use crate::error::Error;
24use crate::fs::PATH_MAX;
25use crate::fs::error::FsError;
26use crate::fs::ext2::block::Block;
27use crate::fs::ext2::inode::DIRECT_BLOCK_POINTER_COUNT;
28use crate::fs::file::{self, DirectoryEntry, DirectoryRead, Stat, Type, TypeWithFile};
29use crate::fs::permissions::Permissions;
30use crate::fs::structures::indirection::IndirectedBlocks;
31use crate::fs::types::{Blkcnt, Blksize, Gid, Ino, Mode, Nlink, Off, Time, Timespec, Uid};
32use crate::path::{CUR_DIR, PARENT_DIR, UnixStr};
33
34/// Limit in bytes for the length of a pointed path of a symbolic link to be store in an inode and not in a separate
35/// data block.
36pub const SYMBOLIC_LINK_INODE_STORE_LIMIT: usize = 60;
37
38/// General file implementation.
39pub struct File<Dev: Device> {
40    /// Ext2 object associated with the device containing this file.
41    filesystem: Ext2Fs<Dev>,
42
43    /// Inode number of the inode corresponding to the file.
44    inode_number: u32,
45
46    /// Inode corresponding to the file.
47    inode: Inode,
48
49    /// Read/Write offset in bytes (can be manipulated with [`Seek`]).
50    io_offset: u64,
51}
52
53impl<Dev: Device> Debug for File<Dev> {
54    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
55        formatter
56            .debug_struct("File")
57            .field("device_id", &self.filesystem.lock().device_id)
58            .field("inode_number", &self.inode_number)
59            .field("inode", &self.inode)
60            .field("io_offset", &self.io_offset)
61            .finish()
62    }
63}
64
65impl<Dev: Device> Clone for File<Dev> {
66    fn clone(&self) -> Self {
67        Self {
68            filesystem: self.filesystem.clone(),
69            inode_number: self.inode_number,
70            inode: self.inode,
71            io_offset: self.io_offset,
72        }
73    }
74}
75
76impl<Dev: Device> File<Dev> {
77    /// Returns a new ext2's [`File`] from an [`Ext2Fs`] instance and the inode number of the file.
78    ///
79    /// # Errors
80    ///
81    /// Returns the same errors as [`Inode::parse`].
82    pub fn new(filesystem: &Ext2Fs<Dev>, inode_number: u32) -> Result<Self, Error<Ext2Error>> {
83        let fs = filesystem.lock();
84        let inode = Inode::parse(&fs, inode_number)?;
85        Ok(Self {
86            filesystem: filesystem.clone(),
87            inode_number,
88            inode,
89            io_offset: 0,
90        })
91    }
92
93    /// Updates the inner [`Inode`].
94    fn update_inner_inode(&mut self) -> Result<(), Error<Ext2Error>> {
95        let fs = self.filesystem.lock();
96        self.inode = Inode::parse(&fs, self.inode_number)?;
97        Ok(())
98    }
99
100    ///  Sets the file's inode to the given object.
101    ///
102    /// # Errors
103    ///
104    /// Returns an [`Error::IO`] if the device cannot be written.
105    ///
106    /// # Safety
107    ///
108    /// Must ensure that the given inode is coherent with the current state of the filesystem.
109    unsafe fn set_inode(&mut self, inode: &Inode) -> Result<(), Error<Ext2Error>> {
110        let fs = self.filesystem.lock();
111        unsafe { Inode::write_on_device(&fs, self.inode_number, *inode) }?;
112        drop(fs);
113
114        self.update_inner_inode()?;
115
116        Ok(())
117    }
118
119    /// General implementation of [`truncate`](file::Regular::truncate) for ext2's [`File`].
120    ///
121    /// # Errors
122    ///
123    /// Returns the same errors as [`truncate`](file::Regular::truncate).
124    pub fn truncate(&mut self, size: u64) -> Result<(), Error<Ext2Error>> {
125        if self.inode.data_size() <= size {
126            return Ok(());
127        }
128
129        let mut fs = self.filesystem.lock();
130
131        let mut new_inode = self.inode;
132        // SAFETY: the result is smaller than `u32::MAX`
133        new_inode.size = unsafe { u32::try_from(u64::from(u32::MAX) & size).unwrap_unchecked() };
134
135        let time = fs.get_time();
136        new_inode.atime = time;
137        new_inode.mtime = time;
138
139        let kept_data_blocks_number = if size == 0 {
140            0
141        } else {
142            // SAFETY: the result is a u32 as `size` is valid (it has been checked)
143            unsafe {
144                1 + u32::try_from(size.saturating_sub(1) / u64::from(fs.superblock().block_size())).unwrap_unchecked()
145            }
146        };
147        let indirection_blocks = self.inode.indirected_blocks(&fs)?;
148
149        let mut new_indirection_blocks = indirection_blocks.clone();
150        new_indirection_blocks.truncate_back_data_blocks(kept_data_blocks_number);
151
152        new_inode.blocks = (new_indirection_blocks.data_block_count()
153            + new_indirection_blocks.indirection_block_count())
154            * fs.superblock().block_size()
155            / 512;
156
157        let mut direct_block_pointers = new_inode.direct_block_pointers;
158        for i in 0..u32_to_usize(DIRECT_BLOCK_POINTER_COUNT) {
159            // SAFETY: there is exactly `DIRECT_BLOCK_POINTER_COUNT` direct block pointers in an inode
160            let block = unsafe { direct_block_pointers.get_mut(i).unwrap_unchecked() };
161            *block = new_indirection_blocks.direct_blocks.get(i).copied().unwrap_or_default();
162        }
163        new_inode.direct_block_pointers = direct_block_pointers;
164        new_inode.singly_indirect_block_pointer = new_indirection_blocks.singly_indirected_blocks.0;
165        new_inode.doubly_indirect_block_pointer = new_indirection_blocks.doubly_indirected_blocks.0;
166        new_inode.triply_indirect_block_pointer = new_indirection_blocks.triply_indirected_blocks.0;
167
168        let symmetrical_difference = indirection_blocks.truncate_front_data_blocks(kept_data_blocks_number);
169
170        let mut deallocated_blocks = symmetrical_difference.changed_data_blocks();
171        deallocated_blocks.append(
172            &mut symmetrical_difference
173                .changed_indirected_blocks()
174                .into_iter()
175                .map(|(_, (indirection_block, _))| indirection_block)
176                .collect_vec(),
177        );
178
179        // SAFETY: this writes an inode at the starting address of the inode
180        unsafe {
181            Inode::write_on_device(&fs, self.inode_number, new_inode)?;
182        };
183
184        fs.deallocate_blocks(&deallocated_blocks)?;
185
186        drop(fs);
187
188        self.io_offset = 0;
189
190        self.update_inner_inode()
191    }
192
193    /// Reads all the content of the file and returns it in a byte vector.
194    ///
195    /// Does not move the offset for I/O operations used by [`Seek`].
196    ///
197    /// # Errors
198    ///
199    /// Returns the same errors as [`Inode::read_data`].
200    pub fn read_all(&mut self) -> Result<Vec<u8>, Error<Ext2Error>> {
201        let mut buffer = vec![0_u8; u64_to_usize(self.inode.data_size()).map_err(Error::from_infallible)?];
202        let previous_offset = self.seek(SeekFrom::Start(0))?;
203        self.read_exact(&mut buffer)?;
204        self.seek(SeekFrom::Start(previous_offset))?;
205        Ok(buffer)
206    }
207}
208
209impl<Dev: Device> file::Base for File<Dev> {
210    type FsError = Ext2Error;
211}
212
213impl<Dev: Device> file::FileRead for File<Dev> {
214    fn stat(&self) -> file::Stat {
215        let filesystem = self.filesystem.lock();
216
217        Stat {
218            dev: crate::fs::types::Dev(filesystem.device_id),
219            ino: Ino(u64::from(self.inode_number)),
220            mode: Mode(self.inode.mode),
221            nlink: Nlink(u32::from(self.inode.links_count)),
222            uid: Uid(self.inode.uid.into()),
223            gid: Gid(self.inode.gid.into()),
224            rdev: crate::fs::types::Dev::default(),
225            size: Off(self.inode.data_size().try_into().unwrap_or_default()),
226            atim: Timespec {
227                tv_sec: Time(self.inode.atime.into()),
228                tv_nsec: u32::default(),
229            },
230            mtim: Timespec {
231                tv_sec: Time(self.inode.mtime.into()),
232                tv_nsec: u32::default(),
233            },
234            ctim: Timespec {
235                tv_sec: Time(self.inode.ctime.into()),
236                tv_nsec: u32::default(),
237            },
238            // SAFETY: it is safe to assume that `block_size << isize::MAX` with `isize` at least `i32`
239            blksize: Blksize(unsafe { u32_to_usize(filesystem.superblock.block_size()).try_into().unwrap_unchecked() }),
240            blkcnt: Blkcnt(self.inode.blocks.into()),
241        }
242    }
243
244    fn get_type(&self) -> file::Type {
245        self.inode.file_type().unwrap_or_else(|_| {
246            panic!(
247                "The inner inode with number {} is in an incoherent state: its file type is not valid",
248                self.inode_number
249            )
250        })
251    }
252}
253
254impl<Dev: Device> file::File for File<Dev> {
255    fn set_mode(&mut self, mode: Mode) -> Result<(), Error<Self::FsError>> {
256        let mut new_inode = self.inode;
257        new_inode.mode = *mode | self.inode.type_permissions().file_type().bits();
258        // SAFETY: only the mode has changed
259        unsafe { self.set_inode(&new_inode) }
260    }
261
262    fn set_uid(&mut self, uid: Uid) -> Result<(), Error<Self::FsError>> {
263        let mut new_inode = self.inode;
264        new_inode.uid = TryInto::<u16>::try_into(uid.0)
265            .map_err(|_| Error::Fs(FsError::Implementation(Ext2Error::UidTooLarge(uid.0))))?;
266        // SAFETY: only the UID has changed
267        unsafe { self.set_inode(&new_inode) }
268    }
269
270    fn set_gid(&mut self, gid: Gid) -> Result<(), Error<Self::FsError>> {
271        let mut new_inode = self.inode;
272        new_inode.gid = TryInto::<u16>::try_into(gid.0)
273            .map_err(|_| Error::Fs(FsError::Implementation(Ext2Error::GidTooLarge(gid.0))))?;
274        // SAFETY: only the GID has changed
275        unsafe { self.set_inode(&new_inode) }
276    }
277
278    fn set_atim(&mut self, atim: Timespec) -> Result<(), Error<Self::FsError>> {
279        let mut new_inode = self.inode;
280        // SAFETY: `X % i64::from(u32::MAX) < u32::MAX`
281        new_inode.atime = unsafe { u32::try_from(*atim.tv_sec % i64::from(u32::MAX)).unwrap_unchecked() };
282        // SAFETY: only the atime has changed
283        unsafe { self.set_inode(&new_inode) }
284    }
285
286    fn set_mtim(&mut self, mtim: Timespec) -> Result<(), Error<Self::FsError>> {
287        let mut new_inode = self.inode;
288        // SAFETY: `X % i64::from(u32::MAX) < u32::MAX`
289        new_inode.mtime = unsafe { u32::try_from(*mtim.tv_sec % i64::from(u32::MAX)).unwrap_unchecked() };
290        // SAFETY: only the mtime has changed
291        unsafe { self.set_inode(&new_inode) }
292    }
293
294    fn set_ctim(&mut self, ctim: Timespec) -> Result<(), Error<Self::FsError>> {
295        let mut new_inode = self.inode;
296        // SAFETY: `X % i64::from(u32::MAX) < u32::MAX`
297        new_inode.ctime = unsafe { u32::try_from(*ctim.tv_sec % i64::from(u32::MAX)).unwrap_unchecked() };
298        // SAFETY: only the ctime has changed
299        unsafe { self.set_inode(&new_inode) }
300    }
301}
302
303macro_rules! impl_file {
304    ($id:ident) => {
305        impl<Dev: Device> crate::fs::file::Base for $id<Dev> {
306            type FsError = Ext2Error;
307        }
308
309        impl<Dev: Device> crate::fs::file::FileRead for $id<Dev> {
310            fn stat(&self) -> Stat {
311                self.file.stat()
312            }
313
314            fn get_type(&self) -> file::Type {
315                self.file.get_type()
316            }
317        }
318
319        impl<Dev: Device> crate::fs::file::File for $id<Dev> {
320            fn set_mode(&mut self, mode: Mode) -> Result<(), Error<Self::FsError>> {
321                self.file.set_mode(mode)
322            }
323
324            fn set_uid(&mut self, uid: Uid) -> Result<(), Error<Self::FsError>> {
325                self.file.set_uid(uid)
326            }
327
328            fn set_gid(&mut self, gid: Gid) -> Result<(), Error<Self::FsError>> {
329                self.file.set_gid(gid)
330            }
331
332            fn set_atim(&mut self, atim: Timespec) -> Result<(), Error<Self::FsError>> {
333                self.file.set_atim(atim)
334            }
335
336            fn set_mtim(&mut self, mtim: Timespec) -> Result<(), Error<Self::FsError>> {
337                self.file.set_mtim(mtim)
338            }
339
340            fn set_ctim(&mut self, ctim: Timespec) -> Result<(), Error<Self::FsError>> {
341                self.file.set_ctim(ctim)
342            }
343        }
344    };
345}
346
347impl<Dev: Device> Read for File<Dev> {
348    fn read(&mut self, buf: &mut [u8]) -> deku::no_std_io::Result<usize> {
349        let filesystem = self.filesystem.lock();
350        let bytes = self
351            .inode
352            .read_data(&filesystem, buf, self.io_offset)
353            .inspect(|&bytes| {
354                self.io_offset += usize_to_u64(bytes);
355            })
356            .map_err(|err| deku::no_std_io::Error::new(deku::no_std_io::ErrorKind::InvalidData, err.to_string()))?;
357
358        let mut device = filesystem.device.lock();
359        if let Some(now) = device.now() {
360            drop(device);
361
362            let mut new_inode = self.inode;
363            // SAFETY: the result will always be under u32::MAX
364            new_inode.atime = unsafe { (now.tv_sec.0 & i64::from(u32::MAX)).try_into().unwrap_unchecked() };
365            // SAFETY: only the access time has been updated
366            unsafe {
367                Inode::write_on_device(&filesystem, self.inode_number, new_inode).map_err(|err| {
368                    deku::no_std_io::Error::new(deku::no_std_io::ErrorKind::InvalidData, err.to_string())
369                })?;
370            };
371        }
372
373        Ok(bytes)
374    }
375}
376
377impl<Dev: Device> Write for File<Dev> {
378    #[allow(clippy::too_many_lines)]
379    fn write(&mut self, buf: &[u8]) -> deku::no_std_io::Result<usize> {
380        let mut fs = self.filesystem.lock();
381        let superblock = fs.superblock().clone();
382        let block_size = u64::from(fs.superblock().block_size());
383
384        let buf_len = usize_to_u64(buf.len());
385        if buf_len > fs.superblock().max_file_size() {
386            return Err(deku::no_std_io::Error::new(
387                deku::no_std_io::ErrorKind::InvalidInput,
388                "Tried to read a file from a buffer with a length greater than the max file length",
389            ));
390        }
391
392        // Calcul of the number of needed data blocks
393        let bytes_to_write = buf_len;
394        let data_blocks_needed =
395            // SAFETY: there are at most u32::MAX blocks on the filesystem
396            1 + unsafe { u32::try_from((bytes_to_write + self.io_offset - 1) / block_size).unwrap_unchecked() };
397
398        if !fs.options().large_files && u64::from(data_blocks_needed) * block_size >= u64::from(u32::MAX) {
399            return Err(deku::no_std_io::Error::new(
400                deku::no_std_io::ErrorKind::InvalidInput,
401                "Tried to write a large file while the filesystem does not have the required feature set",
402            ));
403        }
404
405        let mut indirected_blocks = self.inode.indirected_blocks(&fs)?;
406        // SAFETY: there are at most u32::MAX blocks on the filesystem
407        indirected_blocks.truncate_back_data_blocks(unsafe {
408            // In case of blocks that are not used and not 0
409            1 + u32::try_from((self.inode.data_size().max(1) - 1) / block_size).unwrap_unchecked()
410        });
411
412        let current_data_block_count = indirected_blocks.data_block_count();
413        let data_blocks_to_request = data_blocks_needed.saturating_sub(current_data_block_count);
414
415        let current_indirection_block_count = indirected_blocks.indirection_block_count();
416        let indirection_blocks_to_request =
417            IndirectedBlocks::<DIRECT_BLOCK_POINTER_COUNT>::necessary_indirection_block_count(
418                data_blocks_needed,
419                fs.superblock().base().block_size() / 4,
420            ) - current_indirection_block_count;
421
422        let start_block_group = indirected_blocks
423            .last_data_block_allocated()
424            .map(|(block, _)| superblock.block_group(block))
425            .unwrap_or_default();
426
427        let free_blocks =
428            fs.free_blocks_offset(data_blocks_to_request + indirection_blocks_to_request, start_block_group)?;
429
430        fs.allocate_blocks(&free_blocks)?;
431
432        drop(fs);
433
434        let (new_indirected_blocks, changed_blocks) = indirected_blocks.append_blocks_with_difference(
435            &free_blocks,
436            // SAFETY: this result points to a block which is encoded on 32 bits
437            Some(unsafe { u32::try_from(self.io_offset / u64::from(superblock.block_size())).unwrap_unchecked() }),
438        );
439
440        for (starting_index, (indirection_block, blocks)) in changed_blocks.changed_indirected_blocks() {
441            let mut block = Block::new(self.filesystem.clone(), indirection_block);
442            if starting_index != 0 {
443                block.seek(SeekFrom::Start(usize_to_u64(starting_index)))?;
444            }
445
446            // SAFETY: it is always possible to cast a u32 to 4 u8
447            block.write_all(unsafe { &*slice_from_raw_parts(blocks.as_ptr().cast::<u8>(), blocks.len() * 4) })?;
448        }
449
450        let mut written_bytes = 0_usize;
451
452        let changed_data_blocks = changed_blocks.changed_data_blocks();
453        let changed_data_blocks_iterator = &mut changed_data_blocks.iter();
454
455        if let Some(block_number) = changed_data_blocks_iterator.next() {
456            let mut block = Block::new(self.filesystem.clone(), *block_number);
457            block.seek(SeekFrom::Start(self.io_offset % u64::from(superblock.block_size())))?;
458            written_bytes += block.write(buf)?;
459        }
460
461        for block_number in changed_data_blocks_iterator {
462            let mut block = Block::new(self.filesystem.clone(), *block_number);
463            let Some(buffer_end) = buf.get(
464                written_bytes
465                    ..written_bytes
466                        + u32_to_usize(superblock.base().blocks_per_group)
467                            .min(u64_to_usize(buf_len).map_err(Error::<Ext2Error>::from_infallible)? - written_bytes),
468            ) else {
469                break;
470            };
471
472            let new_written_bytes = block.write(buffer_end)?;
473            if new_written_bytes == 0 {
474                break;
475            }
476            written_bytes += new_written_bytes;
477        }
478
479        let mut updated_inode = self.inode;
480
481        let total_block_used =
482            new_indirected_blocks.data_block_count() + new_indirected_blocks.indirection_block_count();
483        let (
484            mut direct_block_pointers,
485            singly_indirected_block_pointer,
486            doubly_indirected_block_pointer,
487            triply_indirected_block_pointer,
488        ) = new_indirected_blocks.blocks();
489
490        direct_block_pointers
491            .append(&mut vec![0_u32; 12].into_iter().take(12 - direct_block_pointers.len()).collect_vec());
492
493        let mut updated_direct_block_pointers = updated_inode.direct_block_pointers;
494        updated_direct_block_pointers.clone_from_slice(&direct_block_pointers);
495        updated_inode.direct_block_pointers = updated_direct_block_pointers;
496
497        updated_inode.singly_indirect_block_pointer = singly_indirected_block_pointer.0;
498        updated_inode.doubly_indirect_block_pointer = doubly_indirected_block_pointer.0;
499        updated_inode.triply_indirect_block_pointer = triply_indirected_block_pointer.0;
500
501        let new_size = self.inode.data_size().max(self.io_offset + buf_len);
502
503        // SAFETY: the result cannot be greater than `u32::MAX`
504        updated_inode.size = unsafe { u32::try_from(new_size & u64::from(u32::MAX)).unwrap_unchecked() };
505        updated_inode.blocks = (total_block_used * self.filesystem.lock().superblock().block_size()) / 512;
506
507        // SAFETY: the result cannot be greater than `u32::MAX`
508        updated_inode.dir_acl = unsafe { u32::try_from((new_size >> 32) & u64::from(u32::MAX)).unwrap_unchecked() };
509
510        let fs = self.filesystem.lock();
511
512        let time = fs.get_time();
513        updated_inode.atime = time;
514        updated_inode.mtime = time;
515
516        drop(fs);
517
518        // SAFETY: the updated inode contains the right inode created in this function
519        unsafe { self.set_inode(&updated_inode) }?;
520
521        self.seek(SeekFrom::Current(i64::try_from(buf_len).expect("Could not fit the buffer length on an i64")))?;
522
523        Ok(written_bytes)
524    }
525
526    fn flush(&mut self) -> deku::no_std_io::Result<()> {
527        Ok(())
528    }
529}
530
531impl<Dev: Device> Seek for File<Dev> {
532    fn seek(&mut self, pos: SeekFrom) -> deku::no_std_io::Result<u64> {
533        // SAFETY: it is safe to assume that the file length is smaller than 2^63 bytes long
534        let file_length = unsafe { i64::try_from(self.inode.data_size()).unwrap_unchecked() };
535
536        let previous_offset = self.io_offset;
537        match pos {
538            SeekFrom::Start(offset) => self.io_offset = offset,
539            SeekFrom::End(back_offset) => {
540                self.io_offset = TryInto::<u64>::try_into(file_length + back_offset).map_err(|_err| {
541                    deku::no_std_io::Error::new(
542                        deku::no_std_io::ErrorKind::InvalidInput,
543                        "Invalid seek to a negative or overflowing position",
544                    )
545                })?;
546            },
547            SeekFrom::Current(add_offset) => {
548                // SAFETY: it is safe to assume that the file has a length smaller than 2^63 bytes.
549                self.io_offset = (unsafe { TryInto::<i64>::try_into(previous_offset).unwrap_unchecked() } + add_offset)
550                    .try_into()
551                    .map_err(|_err| {
552                        deku::no_std_io::Error::new(
553                            deku::no_std_io::ErrorKind::InvalidInput,
554                            "Invalid seek to a negative or overflowing position",
555                        )
556                    })?;
557            },
558        }
559
560        if self.io_offset > self.inode.data_size() {
561            Err(deku::no_std_io::Error::new(
562                deku::no_std_io::ErrorKind::InvalidInput,
563                "Invalid seek to a negative or overflowing position",
564            ))
565        } else {
566            Ok(previous_offset)
567        }
568    }
569}
570
571/// Implementation of a regular file.
572#[derive(Debug)]
573pub struct Regular<Dev: Device> {
574    /// Inner file containing the generic file.
575    file: File<Dev>,
576}
577
578impl<Dev: Device> Regular<Dev> {
579    /// Returns a new ext2's [`Regular`] from an [`Ext2Fs`] instance and the inode number of the file.
580    ///
581    /// # Errors
582    ///
583    /// Returns the same errors  as [`Ext2::inode`](super::Ext2::inode).
584    pub fn new(filesystem: &Ext2Fs<Dev>, inode_number: u32) -> Result<Self, Error<Ext2Error>> {
585        Ok(Self {
586            file: File::new(&filesystem.clone(), inode_number)?,
587        })
588    }
589
590    /// Reads all the content of the file and returns it in a byte vector.
591    ///
592    /// Does not move the offset for I/O operations used by [`Seek`].
593    ///
594    /// # Errors
595    ///
596    /// Returns the same errors as [`Inode::read_data`].
597    pub fn read_all(&mut self) -> Result<Vec<u8>, Error<Ext2Error>> {
598        self.file.read_all()
599    }
600}
601
602impl<Dev: Device> Clone for Regular<Dev> {
603    fn clone(&self) -> Self {
604        Self {
605            file: self.file.clone(),
606        }
607    }
608}
609
610impl_file!(Regular);
611
612impl<Dev: Device> Read for Regular<Dev> {
613    fn read(&mut self, buf: &mut [u8]) -> deku::no_std_io::Result<usize> {
614        self.file.read(buf)
615    }
616}
617
618impl<Dev: Device> Write for Regular<Dev> {
619    fn write(&mut self, buf: &[u8]) -> deku::no_std_io::Result<usize> {
620        self.file.write(buf)
621    }
622
623    fn flush(&mut self) -> deku::no_std_io::Result<()> {
624        self.file.flush()
625    }
626}
627
628impl<Dev: Device> Seek for Regular<Dev> {
629    fn seek(&mut self, pos: SeekFrom) -> deku::no_std_io::Result<u64> {
630        self.file.seek(pos)
631    }
632}
633
634impl<Dev: Device> file::RegularRead for Regular<Dev> {}
635
636impl<Dev: Device> file::Regular for Regular<Dev> {
637    fn truncate(&mut self, size: u64) -> Result<(), Error<Self::FsError>> {
638        self.file.truncate(size)
639    }
640}
641
642/// Interface for ext2's directories.
643///
644/// In ext2, the content of a directory is a list of [`Entry`], which are the children of the directory. In particular,
645/// `.` and `..` are always children of a directory.
646#[derive(Debug)]
647pub struct Directory<Dev: Device> {
648    /// Inner file containing the generic file.
649    file: File<Dev>,
650
651    /// Entries contained in this directory.
652    ///
653    /// They are stored as a list of entries in each data block.
654    entries: Mutex<Vec<Vec<Entry>>>,
655}
656
657impl<Dev: Device> Directory<Dev> {
658    /// Returns the directory located at the given inode number.
659    ///
660    /// # Errors
661    ///
662    /// Returns the same errors as [`Entry::parse`].
663    pub fn new(filesystem: &Ext2Fs<Dev>, inode_number: u32) -> Result<Self, Error<Ext2Error>> {
664        let file = File::new(filesystem, inode_number)?;
665        let entries = Mutex::new(Self::parse(&file)?);
666
667        Ok(Self { file, entries })
668    }
669
670    /// Parse this inode's content as a list of directory entries.
671    ///
672    /// # Errors
673    ///
674    /// Returns the same errors as [`Entry::parse`].
675    fn parse(file: &File<Dev>) -> Result<Vec<Vec<Entry>>, Error<Ext2Error>> {
676        let fs = file.filesystem.lock();
677
678        let block_size = u64::from(fs.superblock().block_size());
679        let data_size = file.inode.data_size();
680        let data_blocks = 1 + (data_size - 1) / block_size;
681
682        let mut indirected_blocks = file.inode.indirected_blocks(&fs)?;
683        // SAFETY: there are at most u32::MAX blocks on this filesystem
684        indirected_blocks.truncate_back_data_blocks(unsafe { u32::try_from(data_blocks).unwrap_unchecked() });
685
686        let mut entries = Vec::new();
687
688        for block in indirected_blocks.flatten_data_blocks() {
689            let mut entries_in_block = Vec::new();
690            let mut accumulated_size = 0_u64;
691            while accumulated_size < block_size {
692                let starting_addr = Address::from(
693                    u64_to_usize(u64::from(block) * block_size + accumulated_size).map_err(Error::from_infallible)?,
694                );
695
696                let entry = Entry::parse(&fs, starting_addr)?;
697                accumulated_size += u64::from(entry.rec_len);
698                entries_in_block.push(entry);
699            }
700            entries.push(entries_in_block);
701        }
702
703        Ok(entries)
704    }
705
706    /// Updates the inner `entries` field of this directory.
707    ///
708    /// # Errors
709    ///
710    /// Returns the same errors as [`Entry::parse`].
711    fn update_inner_entries(&self) -> Result<(), Error<Ext2Error>> {
712        *self.entries.lock() = Self::parse(&self.file)?;
713        Ok(())
714    }
715
716    /// Writes all the entries of the block `block_index`.
717    ///
718    /// This function does not perform any check: the entries **MUST** be in a coherent state. It is recommanded to
719    /// perform [`defragment`](Directory::defragment) beforehand.
720    ///
721    /// # Safety
722    ///
723    /// Must ensure that the entries are in a valid state regarding to the completion of data blocks and the number of
724    /// entry per data block. Furthermore, `block_index` must be a valid index of `self.entries`.
725    unsafe fn write_block_entry(&mut self, block_index: usize) -> Result<(), Error<Ext2Error>> {
726        let block_size = u64::from(self.file.filesystem.lock().superblock().block_size());
727        self.file.seek(SeekFrom::Start(usize_to_u64(block_index) * block_size))?;
728
729        let mut buffer = Vec::new();
730        let entries = self.entries.lock();
731        for entry in unsafe { entries.get_unchecked(block_index) } {
732            buffer.append(&mut entry.as_bytes().clone());
733            buffer.append(&mut vec![0_u8; u32_to_usize(entry.free_space().into())]);
734        }
735        self.file.write_all(&buffer)?;
736
737        Ok(())
738    }
739
740    /// Writes all the entries.
741    ///
742    /// This function does not perform any check: the entries **MUST** be in a coherent state. It is recommanded to
743    /// perform [`defragment`](Directory::defragment) beforehand.
744    ///
745    /// # Safety
746    ///
747    /// Must ensure that the entries are in a valid state regarding to the completion of data blocks and the number of
748    /// entry per data block.
749    unsafe fn write_all_entries(&mut self) -> Result<(), Error<Ext2Error>> {
750        self.file.truncate(0)?;
751        let nb_entries = self.entries.lock().len();
752        for block_index in 0..nb_entries {
753            unsafe { self.write_block_entry(block_index) }?;
754        }
755        Ok(())
756    }
757
758    /// Defragments the directory by compacting (if necessary) all the entries.
759    fn defragment(&self) {
760        let block_size = u16::try_from(self.file.filesystem.lock().superblock().block_size())
761            .expect("Ill-formed superblock: block size should be castable in a u16");
762
763        let mut new_entries = Vec::new();
764
765        let mut entries_in_block = Vec::<Entry>::new();
766        let mut accumulated_size = 0_u16;
767        for mut entry in self.entries.lock().clone().into_iter().flatten() {
768            if accumulated_size + entry.minimal_size() > block_size {
769                if let Some(ent) = entries_in_block.last_mut() {
770                    ent.rec_len = block_size - accumulated_size;
771                }
772                new_entries.push(entries_in_block);
773                accumulated_size = 0;
774                entries_in_block = Vec::new();
775            }
776            entry.rec_len = entry.minimal_size();
777            accumulated_size += entry.minimal_size();
778            entries_in_block.push(entry);
779        }
780
781        if let Some(ent) = entries_in_block.last_mut() {
782            ent.rec_len = block_size - accumulated_size;
783            new_entries.push(entries_in_block);
784        }
785
786        *self.entries.lock() = new_entries;
787    }
788
789    /// Returns, if it exists, the block index and the entry index containing at least `necessary_space` free space.
790    fn find_space(&self, necessary_space: u16) -> Option<(usize, usize)> {
791        for (block_index, entries_in_block) in self.entries.lock().iter().enumerate() {
792            for (entry_index, entry) in entries_in_block.iter().enumerate() {
793                if entry.free_space() >= necessary_space {
794                    return Some((block_index, entry_index));
795                }
796            }
797        }
798
799        None
800    }
801}
802
803impl<Dev: Device> Clone for Directory<Dev> {
804    fn clone(&self) -> Self {
805        Self {
806            file: self.file.clone(),
807            entries: Mutex::new(self.entries.lock().clone()),
808        }
809    }
810}
811
812impl_file!(Directory);
813
814impl<Dev: Device> file::DirectoryRead for Directory<Dev> {
815    type BlockDevice = BlockDevice<Dev>;
816    type CharacterDevice = CharacterDevice<Dev>;
817    type Fifo = Fifo<Dev>;
818    type Regular = Regular<Dev>;
819    type Socket = Socket<Dev>;
820    type SymbolicLink = SymbolicLink<Dev>;
821
822    fn entries(&self) -> Result<Vec<file::DirectoryEntry<'_, Self>>, Error<Ext2Error>> {
823        let mut entries = Vec::new();
824
825        self.update_inner_entries()?;
826
827        for entry in self.entries.lock().iter().flatten() {
828            entries.push(DirectoryEntry {
829                filename: entry
830                    .name
831                    .clone()
832                    .try_into()
833                    .unwrap_or_else(|_| panic!("The entry with name {:?} is not a valid UTF-8 sequence", entry.name)),
834                file: self.file.filesystem.file(entry.inode)?,
835            });
836        }
837
838        Ok(entries)
839    }
840}
841
842impl<Dev: Device> file::Directory for Directory<Dev> {
843    fn add_entry(
844        &mut self,
845        name: UnixStr<'_>,
846        file_type: Type,
847        permissions: Permissions,
848        user_id: Uid,
849        group_id: Gid,
850    ) -> Result<TypeWithFile<Self>, Error<Self::FsError>> {
851        if let Ok(file) = self.entry(name.clone())
852            && file.is_some()
853        {
854            return Err(Error::Fs(FsError::EntryAlreadyExist(name.to_string())));
855        }
856
857        let mut fs = self.file.filesystem.lock();
858        let block_size = fs.superblock().block_size();
859
860        let inode_number = fs.free_inode()?;
861        fs.allocate_inode(
862            inode_number,
863            TypePermissions::from(permissions) | TypePermissions::from(file_type),
864            user_id
865                .0
866                .try_into()
867                .map_err(|_| Error::Fs(FsError::Implementation(Ext2Error::UidTooLarge(user_id.0))))?,
868            group_id
869                .0
870                .try_into()
871                .map_err(|_| Error::Fs(FsError::Implementation(Ext2Error::GidTooLarge(group_id.0))))?,
872            Flags::empty(),
873            0,
874            [0; 12],
875        )?;
876
877        let file_type_feature = fs.options.file_type;
878
879        drop(fs);
880
881        if file_type == Type::Directory {
882            let mut dir = File::new(&self.file.filesystem, inode_number)?;
883            let self_and_parent = [
884                &Entry {
885                    inode: inode_number,
886                    rec_len: 9,
887                    name_len: 1,
888                    file_type: if file_type_feature { u8::from(FileType::Dir) } else { 0 },
889                    // SAFETY: "." is a valid CString
890                    name: unsafe { CString::from_vec_unchecked(vec![b'.']) },
891                },
892                &Entry {
893                    inode: self.file.inode_number,
894                    rec_len: u16::try_from(block_size - 9)
895                        .expect("Ill-formed superblock: block size should be castable in a u16"),
896                    name_len: 2,
897                    file_type: if file_type_feature { u8::from(FileType::Dir) } else { 0 },
898                    // SAFETY: ".." is a valid CString
899                    name: unsafe { CString::from_vec_unchecked(vec![b'.', b'.']) },
900                },
901            ];
902
903            let self_and_parent_bytes = self_and_parent.map(Entry::as_bytes).concat();
904            dir.seek(SeekFrom::Start(0))?;
905            dir.write_all(&self_and_parent_bytes)?;
906            dir.flush()?;
907        }
908
909        let mut new_entry = Entry {
910            inode: inode_number,
911            rec_len: 0,
912            name_len: u8::try_from(name.to_string().len())
913                .map_err(|_err| Error::Fs(FsError::Implementation(Ext2Error::NameTooLong(name.to_string()))))?,
914            file_type: directory::FileType::from(file_type).into(),
915            name: name.into(),
916        };
917        new_entry.rec_len = new_entry.minimal_size();
918        if let Some((block_index, entry_index)) = self.find_space(new_entry.minimal_size()) {
919            let mut self_entries = self.entries.lock();
920            // SAFETY: `find_space` returns a valid block index
921            let entries_in_block = unsafe { self_entries.get_unchecked_mut(block_index) };
922            // SAFETY: `find_space` returs a valid entry index
923            let previous_entry = unsafe { entries_in_block.get_unchecked_mut(entry_index) };
924
925            new_entry.rec_len = previous_entry.rec_len - previous_entry.minimal_size();
926            previous_entry.rec_len = previous_entry.minimal_size();
927
928            entries_in_block.insert(entry_index + 1, new_entry);
929            drop(self_entries);
930
931            // SAFETY: all necessary changes have been made
932            unsafe { self.write_block_entry(block_index) }?;
933        } else {
934            self.entries.lock().push(vec![new_entry]);
935            self.defragment();
936            // SAFETY: `defragment` has been called above
937            unsafe { self.write_all_entries() }?;
938        }
939
940        let fs = self.file.filesystem.lock();
941        let mut new_inode = fs.inode(inode_number)?;
942
943        let time = fs.get_time();
944        new_inode.atime = time;
945        new_inode.mtime = time;
946        new_inode.ctime = time;
947
948        unsafe { Inode::write_on_device(&fs, inode_number, new_inode)? };
949        drop(fs);
950
951        self.file.filesystem.file(inode_number)
952    }
953
954    fn remove_entry(&mut self, entry_name: crate::path::UnixStr) -> Result<(), Error<Self::FsError>> {
955        if entry_name == *CUR_DIR || entry_name == *PARENT_DIR {
956            return Err(Error::Fs(FsError::RemoveRefused));
957        }
958
959        let block_size = u64::from(self.file.filesystem.lock().superblock().block_size());
960        let entry_name_cstring = Into::<CString>::into(entry_name.clone());
961        let entries_clone = self.entries.lock().clone();
962        for (block_index, entries_in_block) in entries_clone.into_iter().enumerate() {
963            for (index, entry) in entries_in_block.clone().into_iter().enumerate() {
964                if entry.name == entry_name_cstring {
965                    let mut self_entries = self.entries.lock();
966                    // SAFETY: `block_index` is returned by `enumerate`
967                    let block = unsafe { self_entries.get_unchecked_mut(block_index) };
968                    block.remove(index);
969
970                    // Case: the removed entry is not the first of the block
971                    if index > 0
972                        && let Some(previous_entry) = block.get_mut(index - 1)
973                    {
974                        previous_entry.rec_len += entry.rec_len;
975                    }
976                    // Case: the removed entry is the first of the block
977                    else if let Some(next_entry) = block.get_mut(index) {
978                        next_entry.rec_len += entry.rec_len;
979                    }
980                    // Case: the removed entry is the first and only entry of the block
981                    else {
982                        self.entries.lock().remove(block_index);
983                        let nb_entries = self.entries.lock().len();
984                        self.file.truncate(block_size * usize_to_u64(nb_entries))?;
985                    }
986
987                    let block_len = block.len();
988                    drop(self_entries);
989                    if index > 0 || block_len > 0 {
990                        // SAFETY: the content of this block is coherent
991                        unsafe { self.write_block_entry(block_index) }?;
992                    } else {
993                        let nb_entries = self.entries.lock().len();
994                        for i in block_index..nb_entries {
995                            // SAFETY: this block is untouched
996                            unsafe { self.write_block_entry(i) }?;
997                        }
998                    }
999
1000                    if let TypeWithFile::Directory(mut dir) = self.file.filesystem.file(entry.inode)? {
1001                        let mut new_inode = self.file.inode;
1002                        new_inode.links_count -= 1;
1003                        let sub_entries = dir.entries.lock().clone().into_iter().flatten().collect_vec();
1004                        for sub_entry in sub_entries {
1005                            let sub_entry_name: UnixStr<'_> = sub_entry.name.clone().try_into().unwrap_or_else(|_| {
1006                                panic!("The entry with name {:?} is not a valid UTF-8 sequence", sub_entry.name)
1007                            });
1008                            if sub_entry_name != *CUR_DIR && sub_entry_name != *PARENT_DIR {
1009                                dir.remove_entry(sub_entry_name.clone())?;
1010                            }
1011                        }
1012
1013                        // SAFETY: the new number of links is exactly the previous one minus all the children that are
1014                        // directories
1015                        unsafe {
1016                            self.file.set_inode(&new_inode)?;
1017                        };
1018                    }
1019
1020                    let mut fs = self.file.filesystem.lock();
1021                    return fs.deallocate_inode(entry.inode);
1022                }
1023            }
1024        }
1025
1026        Err(Error::Fs(FsError::NotFound(entry_name.to_string())))
1027    }
1028}
1029
1030/// Interface for ext2's symbolic links.
1031#[derive(Debug)]
1032pub struct SymbolicLink<Dev: Device> {
1033    /// Inner file containing the generic file.
1034    file: File<Dev>,
1035
1036    /// Read/Write offset (can be manipulated with [`Seek`]).
1037    pointed_file: String,
1038}
1039
1040impl<Dev: Device> SymbolicLink<Dev> {
1041    /// Returns a new ext2's [`SymbolicLink`] from an [`Ext2Fs`] instance and the inode number of the file.
1042    ///
1043    /// # Errors
1044    ///
1045    /// Returns a [`BadString`](Ext2Error::BadString) if the content of the given inode does not look like a valid path.
1046    ///
1047    /// Returns a [`NameTooLong`](crate::fs::error::FsError::NameTooLong) if the size of the inode's content is greater
1048    /// than [`PATH_MAX`].
1049    ///
1050    /// Otherwise, returns the same errors as [`Ext2::inode`](super::Ext2::inode).
1051    pub fn new(filesystem: &Ext2Fs<Dev>, inode_number: u32) -> Result<Self, Error<Ext2Error>> {
1052        let file = File::new(&filesystem.clone(), inode_number)?;
1053
1054        let data_size = usize::try_from(file.inode.data_size()).unwrap_or(PATH_MAX);
1055
1056        let mut buffer = vec![0_u8; data_size];
1057
1058        if data_size < SYMBOLIC_LINK_INODE_STORE_LIMIT {
1059            // SAFETY: it is always possible to read a slice of u8
1060            buffer.clone_from_slice(unsafe {
1061                core::slice::from_raw_parts(addr_of!(file.inode.direct_block_pointers).cast(), data_size)
1062            });
1063        } else {
1064            let _: usize = file.inode.read_data(&filesystem.lock(), &mut buffer, 0)?;
1065        }
1066        let pointed_file = buffer
1067            .split(|char| *char == b'\0')
1068            .next()
1069            .ok_or(Ext2Error::BadString)
1070            .map_err(FsError::Implementation)?
1071            .to_vec();
1072        Ok(Self {
1073            file,
1074            pointed_file: String::from_utf8(pointed_file)
1075                .map_err(|_err| Ext2Error::BadString)
1076                .map_err(FsError::Implementation)?,
1077        })
1078    }
1079}
1080
1081impl<Dev: Device> Clone for SymbolicLink<Dev> {
1082    fn clone(&self) -> Self {
1083        Self {
1084            file: self.file.clone(),
1085            pointed_file: self.pointed_file.clone(),
1086        }
1087    }
1088}
1089
1090impl_file!(SymbolicLink);
1091
1092impl<Dev: Device> file::SymbolicLinkRead for SymbolicLink<Dev> {
1093    fn get_pointed_file(&self) -> Result<&str, Error<Self::FsError>> {
1094        Ok(&self.pointed_file)
1095    }
1096}
1097
1098impl<Dev: Device> file::SymbolicLink for SymbolicLink<Dev> {
1099    fn set_pointed_file(&mut self, pointed_file: &str) -> Result<(), Error<Self::FsError>> {
1100        let bytes = pointed_file.as_bytes();
1101
1102        if bytes.len() > PATH_MAX {
1103            return Err(Error::Fs(FsError::NameTooLong(pointed_file.to_owned())));
1104        } else if bytes.len() > SYMBOLIC_LINK_INODE_STORE_LIMIT {
1105            if self.pointed_file.len() <= SYMBOLIC_LINK_INODE_STORE_LIMIT {
1106                let mut new_inode = self.file.inode;
1107
1108                let data_ptr = addr_of_mut!(new_inode.direct_block_pointers).cast::<u8>();
1109                // SAFETY: there are `SYMBOLIC_LINK_INODE_STORE_LIMIT` bytes available to store the data
1110                let data_slice = unsafe { core::slice::from_raw_parts_mut(data_ptr, SYMBOLIC_LINK_INODE_STORE_LIMIT) };
1111                data_slice.clone_from_slice(&[b'\0'; SYMBOLIC_LINK_INODE_STORE_LIMIT]);
1112
1113                let fs = self.file.filesystem.lock();
1114                // SAFETY: the starting address correspond to the one of this inode
1115                unsafe {
1116                    Inode::write_on_device(&fs, self.file.inode_number, new_inode)?;
1117                };
1118
1119                drop(fs);
1120
1121                self.file.update_inner_inode()?;
1122            }
1123
1124            self.file.seek(SeekFrom::Start(0))?;
1125            self.file.write_all(bytes)?;
1126            self.file.truncate(usize_to_u64(bytes.len()))?;
1127        } else {
1128            self.file.truncate(0)?;
1129
1130            let mut new_inode = self.file.inode;
1131            // SAFETY: `bytes.len() < PATH_MAX << u32::MAX`
1132            new_inode.size = unsafe { u32::try_from(bytes.len()).unwrap_unchecked() };
1133
1134            let data_ptr = addr_of_mut!(new_inode.direct_block_pointers).cast::<u8>();
1135            // SAFETY: there are `SYMBOLIC_LINK_INODE_STORE_LIMIT` bytes available to store the data
1136            let data_slice = unsafe { core::slice::from_raw_parts_mut(data_ptr, bytes.len()) };
1137            data_slice.clone_from_slice(bytes);
1138
1139            let fs = self.file.filesystem.lock();
1140            // SAFETY: the starting address correspond to the one of this inode
1141            unsafe {
1142                Inode::write_on_device(&fs, self.file.inode_number, new_inode)?;
1143            };
1144            drop(fs);
1145
1146            self.file.update_inner_inode()?;
1147        }
1148
1149        pointed_file.clone_into(&mut self.pointed_file);
1150        Ok(())
1151    }
1152}
1153
1154macro_rules! generic_file {
1155    ($id:ident) => {
1156        #[doc = concat!("Basic implementation of a [`", stringify!($id), "`](crate::fs::file::", stringify!($id), ") for ext2.")]
1157        #[derive(Debug)]
1158        pub struct $id<Dev: Device> {
1159            /// Inner file containing the generic file.
1160            file: File<Dev>,
1161        }
1162
1163        impl<Dev: Device> Clone for $id<Dev> {
1164            fn clone(&self) -> Self {
1165                Self {
1166                    file: self.file.clone(),
1167                }
1168            }
1169        }
1170
1171        impl_file!($id);
1172
1173        impl<Dev: Device> $id<Dev> {
1174            #[doc = concat!("Returns a new ext2's [`", stringify!($id), "`] from an [`Ext2Fs`] instance and the inode number of the file.")]
1175            ///
1176            /// # Errors
1177            ///
1178            /// Returns the same errors as [`File::new`].
1179            pub fn new(filesystem: &Ext2Fs<Dev>, inode_number: u32) -> Result<Self, Error<Ext2Error>> {
1180                Ok(Self { file: File::new(filesystem, inode_number)? })
1181            }
1182        }
1183
1184        impl<Dev: Device> crate::fs::file::$id for $id<Dev> {}
1185        impl<Dev: Device> crate::fs::file::${concat($id, Read)}  for $id<Dev> {}
1186    };
1187}
1188
1189generic_file!(Fifo);
1190generic_file!(CharacterDevice);
1191generic_file!(BlockDevice);
1192generic_file!(Socket);
1193
1194#[cfg(test)]
1195mod test {
1196    use alloc::string::{String, ToString};
1197    use alloc::vec;
1198    use alloc::vec::Vec;
1199    use core::time::Duration;
1200    use std::fs::File;
1201
1202    use deku::no_std_io::{Read, Seek, SeekFrom, Write};
1203    use itertools::Itertools;
1204
1205    use crate::arch::usize_to_u64;
1206    use crate::dev::address::Address;
1207    use crate::fs::FilesystemRead;
1208    use crate::fs::ext2::directory::Entry;
1209    use crate::fs::ext2::file::Directory;
1210    use crate::fs::ext2::inode::{Inode, ROOT_DIRECTORY_INODE, TypePermissions};
1211    use crate::fs::ext2::{Ext2, Ext2Fs};
1212    use crate::fs::file::{DirectoryRead, FileRead, Regular, SymbolicLink, SymbolicLinkRead, Type, TypeWithFile};
1213    use crate::fs::permissions::Permissions;
1214    use crate::fs::types::{Gid, Mode, Uid};
1215    use crate::path::{Path, UnixStr};
1216    use crate::tests::{LOREM, LOREM_LENGTH, new_device_id};
1217
1218    fn parse_root(file: File) {
1219        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1220        let root = Directory::new(&ext2, ROOT_DIRECTORY_INODE).unwrap();
1221        assert_eq!(
1222            root.entries
1223                .lock()
1224                .clone()
1225                .into_iter()
1226                .flatten()
1227                .map(|entry| entry.name.to_string_lossy().to_string())
1228                .collect::<Vec<String>>(),
1229            vec![".", "..", "lost+found", "big_file", "symlink"]
1230        );
1231    }
1232
1233    fn parse_root_entries(file: File) {
1234        let fs = Ext2::new(file, new_device_id()).unwrap();
1235        let root_inode = Inode::parse(&fs, ROOT_DIRECTORY_INODE).unwrap();
1236
1237        let dot = Entry::parse(
1238            &fs,
1239            Address::new(u64::from(root_inode.direct_block_pointers[0]) * u64::from(fs.superblock().block_size())),
1240        )
1241        .unwrap();
1242        let two_dots = Entry::parse(
1243            &fs,
1244            Address::new(
1245                u64::from(root_inode.direct_block_pointers[0]) * u64::from(fs.superblock().block_size())
1246                    + u64::from(dot.rec_len),
1247            ),
1248        )
1249        .unwrap();
1250        let lost_and_found = Entry::parse(
1251            &fs,
1252            Address::new(
1253                (u64::from(root_inode.direct_block_pointers[0]) * u64::from(fs.superblock().block_size()))
1254                    + u64::from(dot.rec_len + two_dots.rec_len),
1255            ),
1256        )
1257        .unwrap();
1258        let big_file = Entry::parse(
1259            &fs,
1260            Address::new(
1261                (u64::from(root_inode.direct_block_pointers[0]) * u64::from(fs.superblock().block_size()))
1262                    + u64::from(dot.rec_len + two_dots.rec_len + lost_and_found.rec_len),
1263            ),
1264        )
1265        .unwrap();
1266        let symlink = Entry::parse(
1267            &fs,
1268            Address::new(
1269                (u64::from(root_inode.direct_block_pointers[0]) * u64::from(fs.superblock().block_size()))
1270                    + u64::from(dot.rec_len + two_dots.rec_len + lost_and_found.rec_len + big_file.rec_len),
1271            ),
1272        )
1273        .unwrap();
1274
1275        assert_eq!(dot.name.as_c_str().to_string_lossy(), ".");
1276        assert_eq!(two_dots.name.as_c_str().to_string_lossy(), "..");
1277        assert_eq!(lost_and_found.name.as_c_str().to_string_lossy(), "lost+found");
1278        assert_eq!(big_file.name.as_c_str().to_string_lossy(), "big_file");
1279        assert_eq!(symlink.name.as_c_str().to_string_lossy(), "symlink");
1280    }
1281
1282    fn parse_big_file_inode_data(file: File) {
1283        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1284        let root = Directory::new(&ext2, ROOT_DIRECTORY_INODE).unwrap();
1285
1286        let fs = ext2.lock();
1287        let big_file_inode_number = root
1288            .entries
1289            .lock()
1290            .iter()
1291            .flatten()
1292            .find(|entry| entry.name.to_string_lossy() == "big_file")
1293            .unwrap()
1294            .inode;
1295        let big_file_inode = fs.inode(big_file_inode_number).unwrap();
1296
1297        let singly_indirect_block_pointer = big_file_inode.singly_indirect_block_pointer;
1298        let doubly_indirect_block_pointer = big_file_inode.doubly_indirect_block_pointer;
1299        assert_ne!(singly_indirect_block_pointer, 0);
1300        assert_ne!(doubly_indirect_block_pointer, 0);
1301
1302        assert_ne!(big_file_inode.data_size(), 0);
1303
1304        for offset in 0_usize..1_024_usize {
1305            let mut buffer = [0_u8; 1_024];
1306            big_file_inode.read_data(&fs, &mut buffer, usize_to_u64(offset * 1_024)).unwrap();
1307
1308            assert_eq!(buffer.iter().all_equal_value(), Ok(&1));
1309        }
1310
1311        let mut buffer = [0_u8; 1_024];
1312        big_file_inode.read_data(&fs, &mut buffer, 0x0010_0000).unwrap();
1313        assert_eq!(buffer.iter().all_equal_value(), Ok(&0));
1314    }
1315
1316    fn read_file(file: File) {
1317        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1318
1319        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1320            panic!("The root is always a directory.")
1321        };
1322        let TypeWithFile::Regular(mut foo) = root.entry(UnixStr::new("foo.txt").unwrap()).unwrap().unwrap() else {
1323            panic!("`foo.txt` has been created as a regular file")
1324        };
1325
1326        assert_eq!(foo.file.read_all().unwrap(), b"Hello world!\n");
1327    }
1328
1329    fn read_file_with_offset(file: File) {
1330        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1331
1332        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1333            panic!("The root is always a directory.")
1334        };
1335        let TypeWithFile::Regular(mut foo) = root.entry(UnixStr::new("foo.txt").unwrap()).unwrap().unwrap() else {
1336            panic!("`foo.txt` has been created as a regular file")
1337        };
1338
1339        foo.seek(SeekFrom::Start(6)).unwrap();
1340        let mut buf = [0; 7];
1341        foo.file.read_exact(&mut buf).unwrap();
1342
1343        assert_eq!(&buf, b"world!\n");
1344    }
1345
1346    fn read_lorem(file: File) {
1347        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1348
1349        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1350            panic!("The root is always a directory.")
1351        };
1352        let TypeWithFile::Regular(mut lorem) = root.entry(UnixStr::new("lorem.txt").unwrap()).unwrap().unwrap() else {
1353            panic!("`lorem.txt` has been created as a regular file")
1354        };
1355
1356        assert_eq!(LOREM.as_bytes(), lorem.read_all().unwrap());
1357
1358        lorem.seek(SeekFrom::Start(504)).unwrap();
1359        let mut buf = [0_u8; 3000];
1360        lorem.read_exact(&mut buf).unwrap();
1361        assert_eq!(buf, LOREM.as_bytes()[504..(504 + 3000)]);
1362
1363        lorem.seek(SeekFrom::End(-830)).unwrap();
1364        let mut buf = [0_u8; 300];
1365        lorem.read_exact(&mut buf).unwrap();
1366        assert_eq!(buf, LOREM.as_bytes()[(LOREM_LENGTH - 830)..(LOREM_LENGTH - 830 + 300)]);
1367    }
1368
1369    fn read_symlink(file: File) {
1370        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1371        let root = Directory::new(&ext2, ROOT_DIRECTORY_INODE).unwrap();
1372
1373        let TypeWithFile::SymbolicLink(symlink) = root.entry(UnixStr::new("symlink").unwrap()).unwrap().unwrap() else {
1374            panic!("`symlink` has been created as a symbolic link")
1375        };
1376
1377        assert_eq!(symlink.pointed_file, "big_file");
1378    }
1379
1380    fn set_inode(file: File) {
1381        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1382        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1383            panic!("The root is always a directory.")
1384        };
1385        let TypeWithFile::Regular(mut foo) = root.entry(UnixStr::new("foo.txt").unwrap()).unwrap().unwrap() else {
1386            panic!("`foo.txt` has been created as a regular file")
1387        };
1388
1389        let mut new_inode = foo.file.inode;
1390        new_inode.uid = 0x1234;
1391        new_inode.gid = 0x2345;
1392        new_inode.flags = 0xabcd;
1393        unsafe { foo.file.set_inode(&new_inode) }.unwrap();
1394
1395        assert_eq!(foo.file.inode, Inode::parse(&ext2.lock(), foo.file.inode_number).unwrap());
1396        assert_eq!(foo.file.inode, new_inode);
1397    }
1398
1399    fn write_file_dbp_replace_without_allocation(file: File) {
1400        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1401        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1402            panic!("The root is always a directory.")
1403        };
1404        let TypeWithFile::Regular(mut foo) = root.entry(UnixStr::new("foo.txt").unwrap()).unwrap().unwrap() else {
1405            panic!("`foo.txt` has been created as a regular file")
1406        };
1407
1408        foo.seek(SeekFrom::Start(6)).unwrap();
1409        let replace_text = b"earth";
1410        foo.write_all(replace_text).unwrap();
1411        foo.flush().unwrap();
1412
1413        assert_eq!(foo.file.inode, Inode::parse(&ext2.lock(), foo.file.inode_number).unwrap());
1414
1415        assert_eq!(String::from_utf8(foo.read_all().unwrap()).unwrap(), "Hello earth!\n");
1416    }
1417
1418    fn write_file_dbp_extend_without_allocation(file: File) {
1419        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1420        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1421            panic!("The root is always a directory.")
1422        };
1423        let TypeWithFile::Regular(mut foo) = root.entry(UnixStr::new("foo.txt").unwrap()).unwrap().unwrap() else {
1424            panic!("`foo.txt` has been created as a regular file")
1425        };
1426
1427        foo.seek(SeekFrom::Start(6)).unwrap();
1428        let replace_text = b"earth!\nI love dogs!\n";
1429        foo.write_all(replace_text).unwrap();
1430        foo.flush().unwrap();
1431
1432        assert_eq!(foo.file.inode, Inode::parse(&ext2.lock(), foo.file.inode_number).unwrap());
1433
1434        assert_eq!(foo.read_all().unwrap(), b"Hello earth!\nI love dogs!\n");
1435    }
1436
1437    fn write_file_dbp_extend_with_allocation(file: File) {
1438        const BYTES_TO_WRITE: usize = 12_000;
1439
1440        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1441        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1442            panic!("The root is always a directory.")
1443        };
1444        let TypeWithFile::Regular(mut foo) = root.entry(UnixStr::new("foo.txt").unwrap()).unwrap().unwrap() else {
1445            panic!("`foo.txt` has been created as a regular file")
1446        };
1447
1448        let replace_text = &[b'a'; BYTES_TO_WRITE];
1449        foo.write_all(replace_text).unwrap();
1450        foo.flush().unwrap();
1451
1452        assert_eq!(foo.file.inode, Inode::parse(&ext2.lock(), foo.file.inode_number).unwrap());
1453
1454        assert_eq!(foo.read_all().unwrap().len(), BYTES_TO_WRITE);
1455        assert_eq!(foo.read_all().unwrap().into_iter().all_equal_value(), Ok(b'a'));
1456    }
1457
1458    fn write_file_singly_indirect_block_pointer(file: File) {
1459        const BYTES_TO_WRITE: usize = 23_000;
1460
1461        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1462        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1463            panic!("The root is always a directory.")
1464        };
1465        let TypeWithFile::Regular(mut foo) = root.entry(UnixStr::new("foo.txt").unwrap()).unwrap().unwrap() else {
1466            panic!("`foo.txt` has been created as a regular file")
1467        };
1468
1469        let mut replace_text = vec![];
1470        for i in 0..=u8::MAX {
1471            replace_text.append(&mut vec![i; BYTES_TO_WRITE / 256]);
1472        }
1473        replace_text.append(&mut vec![b'a'; BYTES_TO_WRITE - 256 * (BYTES_TO_WRITE / 256)]);
1474
1475        foo.write_all(&replace_text).unwrap();
1476        foo.flush().unwrap();
1477
1478        assert_eq!(foo.file.inode, Inode::parse(&ext2.lock(), foo.file.inode_number).unwrap());
1479        assert_eq!(foo.read_all().unwrap().len(), BYTES_TO_WRITE);
1480        assert_eq!(foo.read_all().unwrap(), replace_text);
1481    }
1482
1483    fn write_file_doubly_indirect_block_pointer(file: File) {
1484        const BYTES_TO_WRITE: usize = 400_000;
1485
1486        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1487        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1488            panic!("The root is always a directory.")
1489        };
1490        let TypeWithFile::Regular(mut foo) = root.entry(UnixStr::new("foo.txt").unwrap()).unwrap().unwrap() else {
1491            panic!("`foo.txt` has been created as a regular file")
1492        };
1493
1494        let mut replace_text = vec![];
1495        for i in 0..=u8::MAX {
1496            replace_text.append(&mut vec![i; BYTES_TO_WRITE / 256]);
1497        }
1498        replace_text.append(&mut vec![b'a'; BYTES_TO_WRITE - 256 * (BYTES_TO_WRITE / 256)]);
1499
1500        foo.write_all(&replace_text).unwrap();
1501        foo.flush().unwrap();
1502
1503        assert_eq!(foo.file.inode, Inode::parse(&ext2.lock(), foo.file.inode_number).unwrap());
1504        assert_eq!(foo.read_all().unwrap().len(), BYTES_TO_WRITE);
1505        assert_eq!(foo.read_all().unwrap(), replace_text);
1506    }
1507
1508    fn write_file_triply_indirect_block_pointer(file: File) {
1509        const BYTES_TO_WRITE: usize = 70_000_000;
1510
1511        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1512        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1513            panic!("The root is always a directory.")
1514        };
1515        let TypeWithFile::Regular(mut foo) = root.entry(UnixStr::new("foo.txt").unwrap()).unwrap().unwrap() else {
1516            panic!("`foo.txt` has been created as a regular file")
1517        };
1518
1519        let mut replace_text = vec![b'a'; BYTES_TO_WRITE / 2];
1520        replace_text.append(&mut vec![b'b'; BYTES_TO_WRITE / 2]);
1521        foo.write_all(&replace_text).unwrap();
1522        foo.flush().unwrap();
1523
1524        assert_eq!(foo.file.inode, Inode::parse(&ext2.lock(), foo.file.inode_number).unwrap());
1525        assert_eq!(foo.read_all().unwrap().len(), BYTES_TO_WRITE);
1526        assert_eq!(foo.read_all().unwrap(), replace_text);
1527    }
1528
1529    fn write_file_twice(file: File) {
1530        const BYTES_TO_WRITE: usize = 23_000;
1531
1532        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1533        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1534            panic!("The root is always a directory.")
1535        };
1536        let TypeWithFile::Regular(mut foo) = root.entry(UnixStr::new("foo.txt").unwrap()).unwrap().unwrap() else {
1537            panic!("`foo.txt` has been created as a regular file")
1538        };
1539
1540        let mut replace_text = vec![];
1541        for i in 0..=u8::MAX {
1542            replace_text.append(&mut vec![i; BYTES_TO_WRITE / 256]);
1543        }
1544        replace_text.append(&mut vec![b'a'; BYTES_TO_WRITE - 256 * (BYTES_TO_WRITE / 256)]);
1545
1546        foo.write_all(&replace_text[..BYTES_TO_WRITE / 2]).unwrap();
1547        foo.write_all(&replace_text[BYTES_TO_WRITE / 2..]).unwrap();
1548        foo.flush().unwrap();
1549
1550        assert_eq!(foo.file.inode, Inode::parse(&ext2.lock(), foo.file.inode_number).unwrap());
1551        assert_eq!(foo.read_all().unwrap().len(), BYTES_TO_WRITE);
1552        assert_eq!(foo.read_all().unwrap(), replace_text);
1553    }
1554
1555    #[allow(clippy::similar_names)]
1556    fn file_mode(file: File) {
1557        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1558        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1559            panic!("The root is always a directory.")
1560        };
1561        let TypeWithFile::Regular(mut foo) = root.entry(UnixStr::new("foo.txt").unwrap()).unwrap().unwrap() else {
1562            panic!("`foo.txt` has been created as a regular file")
1563        };
1564
1565        assert_eq!(foo.get_type(), Type::Regular);
1566        assert_eq!(
1567            foo.permissions(),
1568            Permissions::USER_READ | Permissions::USER_WRITE | Permissions::GROUP_READ | Permissions::OTHER_READ
1569        );
1570
1571        crate::fs::file::File::set_mode(&mut foo, Mode::from(Permissions::USER_READ | Permissions::USER_WRITE))
1572            .unwrap();
1573        crate::fs::file::File::set_uid(&mut foo, Uid(1)).unwrap();
1574        crate::fs::file::File::set_gid(&mut foo, Gid(2)).unwrap();
1575
1576        let fs = ext2.lock();
1577        let inode = Inode::parse(&fs, foo.file.inode_number).unwrap();
1578
1579        let mode = inode.mode;
1580        assert_eq!(mode, (TypePermissions::REGULAR_FILE | TypePermissions::USER_R | TypePermissions::USER_W).bits());
1581        let uid = inode.uid;
1582        assert_eq!(uid, 1);
1583        let gid = inode.gid;
1584        assert_eq!(gid, 2);
1585    }
1586
1587    fn file_truncation(file: File) {
1588        const BYTES_TO_WRITE: usize = 400_000;
1589
1590        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1591        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1592            panic!("The root is always a directory.")
1593        };
1594        let TypeWithFile::Regular(mut foo) = root.entry(UnixStr::new("foo.txt").unwrap()).unwrap().unwrap() else {
1595            panic!("`foo.txt` has been created as a regular file")
1596        };
1597
1598        let initial_free_block_number = ext2.lock().superblock().base().free_blocks_count;
1599        let initial_foo_size = foo.file.inode.data_size();
1600
1601        let replace_text = vec![b'a'; BYTES_TO_WRITE];
1602        foo.write_all(&replace_text).unwrap();
1603        foo.flush().unwrap();
1604
1605        assert_eq!(foo.file.inode.data_size(), usize_to_u64(BYTES_TO_WRITE));
1606
1607        foo.truncate(10000).unwrap();
1608
1609        assert_eq!(foo.file.inode.data_size(), 10000);
1610        assert_eq!(foo.read_all().unwrap().len(), 10000);
1611
1612        foo.truncate(initial_foo_size).unwrap();
1613        let new_free_block_number = ext2.lock().superblock().base().free_blocks_count;
1614
1615        assert_eq!(foo.file.inode.data_size(), initial_foo_size);
1616        assert!(new_free_block_number >= initial_free_block_number); // Non used blocks could be deallocated
1617    }
1618
1619    fn file_symlinks(file: File) {
1620        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1621        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1622            panic!("The root is always a directory.");
1623        };
1624        let TypeWithFile::SymbolicLink(mut bar) = root.entry(UnixStr::new("bar.txt").unwrap()).unwrap().unwrap() else {
1625            panic!("`bar.txt` has been created as a symbolic link");
1626        };
1627
1628        assert_eq!(bar.get_type(), Type::SymbolicLink);
1629
1630        assert_eq!(bar.get_pointed_file().unwrap(), "foo.txt");
1631        assert_eq!(bar.file.inode.data_size(), 7);
1632
1633        bar.set_pointed_file("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
1634            .unwrap();
1635        assert_eq!(
1636            bar.get_pointed_file().unwrap(),
1637            "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1638        );
1639        assert_eq!(
1640            bar.file.read_all().unwrap(),
1641            b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1642        );
1643        assert_eq!(bar.file.inode.data_size(), 70);
1644
1645        bar.set_pointed_file("foo.txt").unwrap();
1646
1647        assert_eq!(bar.get_pointed_file().unwrap(), "foo.txt");
1648        assert!(bar.file.read_all().is_err());
1649        assert_eq!(bar.file.inode.data_size(), 7);
1650    }
1651
1652    fn new_files(file: File) {
1653        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1654        let TypeWithFile::Directory(mut root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1655            panic!("The root is always a directory.");
1656        };
1657
1658        assert!(root.entry(UnixStr::new("boo.txt").unwrap()).is_ok_and(|res| res.is_none()));
1659        let TypeWithFile::Regular(mut boo) = crate::fs::file::Directory::add_entry(
1660            &mut root,
1661            UnixStr::new("boo.txt").unwrap(),
1662            Type::Regular,
1663            Permissions::USER_READ | Permissions::USER_WRITE | Permissions::USER_EXECUTION,
1664            Uid(0),
1665            Gid(0),
1666        )
1667        .unwrap() else {
1668            panic!("boo has been created as a regular file.")
1669        };
1670
1671        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1672            panic!("The root is always a directory.");
1673        };
1674        assert!(root.entry(UnixStr::new("boo.txt").unwrap()).is_ok_and(|res| res.is_some()));
1675
1676        let ctime = boo.file.inode.ctime;
1677        let atime = boo.file.inode.atime;
1678        let mtime = boo.file.inode.mtime;
1679        assert_ne!(ctime, 0);
1680        assert_ne!(atime, 0);
1681        assert_ne!(mtime, 0);
1682
1683        let root_atime = root.file.inode.atime;
1684        assert!(atime - root_atime < 1);
1685
1686        boo.write_all(b"Hello earth!\n").unwrap();
1687        assert_eq!(boo.read_all().unwrap(), b"Hello earth!\n");
1688    }
1689
1690    #[allow(clippy::similar_names)]
1691    fn remove_files(file: File) {
1692        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1693        let TypeWithFile::Directory(mut root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1694            panic!("The root is always a directory.");
1695        };
1696
1697        assert!(root.entry(UnixStr::new("bar.txt").unwrap()).is_ok_and(|res| res.is_some()));
1698        crate::fs::file::Directory::remove_entry(&mut root, UnixStr::new("bar.txt").unwrap()).unwrap();
1699        let TypeWithFile::Directory(mut root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1700            panic!("The root is always a directory.");
1701        };
1702        assert!(root.entry(UnixStr::new("bar.txt").unwrap()).is_ok_and(|res| res.is_none()));
1703
1704        let TypeWithFile::Regular(ex1) = ext2
1705            .get_file(&Path::new(UnixStr::new("/folder/ex1.txt").unwrap()), root.clone(), false)
1706            .unwrap()
1707        else {
1708            panic!("ex1.txt is a regular file.");
1709        };
1710        let ex1_inode = ex1.file.inode_number;
1711
1712        let TypeWithFile::SymbolicLink(ex2) = ext2
1713            .get_file(&Path::new(UnixStr::new("/folder/ex2.txt").unwrap()), root.clone(), false)
1714            .unwrap()
1715        else {
1716            panic!("ex2.txt is a symbolic link.");
1717        };
1718        let ex2_inode = ex2.file.inode_number;
1719
1720        let fs = ext2.lock();
1721        let superblock = fs.superblock();
1722        let ex1_bitmap = fs.get_inode_bitmap(Inode::block_group(superblock, ex1_inode)).unwrap();
1723        assert!(Inode::is_used(ex1_inode, superblock, &ex1_bitmap));
1724        let ex2_bitmap = fs.get_inode_bitmap(Inode::block_group(superblock, ex2_inode)).unwrap();
1725        assert!(Inode::is_used(ex2_inode, superblock, &ex2_bitmap));
1726        drop(fs);
1727
1728        crate::fs::file::Directory::remove_entry(&mut root, UnixStr::new("folder").unwrap()).unwrap();
1729
1730        let fs = ext2.lock();
1731        let superblock = fs.superblock();
1732        let ex1_bitmap = fs.get_inode_bitmap(Inode::block_group(superblock, ex1_inode)).unwrap();
1733        assert!(Inode::is_free(ex1_inode, superblock, &ex1_bitmap));
1734        let ex2_bitmap = fs.get_inode_bitmap(Inode::block_group(superblock, ex2_inode)).unwrap();
1735        assert!(Inode::is_free(ex2_inode, superblock, &ex2_bitmap));
1736    }
1737
1738    fn atime_and_mtime(file: File) {
1739        let ext2 = Ext2Fs::new(file, new_device_id()).unwrap();
1740        let TypeWithFile::Directory(root) = ext2.file(ROOT_DIRECTORY_INODE).unwrap() else {
1741            panic!("The root is always a directory.")
1742        };
1743        let TypeWithFile::Regular(mut foo) = root.entry(UnixStr::new("foo.txt").unwrap()).unwrap().unwrap() else {
1744            panic!("`foo.txt` has been created as a regular file")
1745        };
1746
1747        let atime = foo.file.inode.atime;
1748        let mtime = foo.file.inode.mtime;
1749
1750        std::thread::sleep(Duration::from_secs(2));
1751
1752        foo.write_all(b"Hello earth!").unwrap();
1753
1754        let fs = ext2.lock();
1755        let new_inode = Inode::parse(&fs, foo.file.inode_number).unwrap();
1756        drop(fs);
1757        assert!(new_inode.atime > atime);
1758        assert!(new_inode.mtime > mtime);
1759
1760        let atime = new_inode.atime;
1761        let mtime = new_inode.mtime;
1762
1763        std::thread::sleep(Duration::from_secs(1));
1764
1765        foo.write_all(b" and other planets too!").unwrap();
1766
1767        let fs = ext2.lock();
1768        let new_inode = Inode::parse(&fs, foo.file.inode_number).unwrap();
1769        drop(fs);
1770        assert!(new_inode.atime < atime + 3);
1771        assert!(new_inode.mtime < mtime + 3);
1772    }
1773
1774    mod generated {
1775        use crate::tests::{PostCheck, generate_fs_test};
1776
1777        generate_fs_test!(parse_root, "./tests/fs/ext2/extended.ext2", PostCheck::Ext);
1778        generate_fs_test!(parse_root_entries, "./tests/fs/ext2/extended.ext2", PostCheck::Ext);
1779        generate_fs_test!(parse_big_file_inode_data, "./tests/fs/ext2/extended.ext2", PostCheck::Ext);
1780        generate_fs_test!(read_file, "./tests/fs/ext2/io_operations.ext2", PostCheck::Ext);
1781        generate_fs_test!(read_file_with_offset, "./tests/fs/ext2/io_operations.ext2", PostCheck::Ext);
1782        generate_fs_test!(read_lorem, "./tests/fs/ext2/io_operations.ext2", PostCheck::Ext);
1783        generate_fs_test!(read_symlink, "./tests/fs/ext2/extended.ext2", PostCheck::Ext);
1784        generate_fs_test!(
1785            write_file_dbp_extend_without_allocation,
1786            "./tests/fs/ext2/io_operations.ext2",
1787            PostCheck::Ext
1788        );
1789        generate_fs_test!(
1790            write_file_dbp_replace_without_allocation,
1791            "./tests/fs/ext2/io_operations.ext2",
1792            PostCheck::Ext
1793        );
1794        generate_fs_test!(write_file_dbp_extend_with_allocation, "./tests/fs/ext2/io_operations.ext2", PostCheck::Ext);
1795        generate_fs_test!(
1796            write_file_singly_indirect_block_pointer,
1797            "./tests/fs/ext2/io_operations.ext2",
1798            PostCheck::Ext
1799        );
1800        generate_fs_test!(
1801            write_file_doubly_indirect_block_pointer,
1802            "./tests/fs/ext2/io_operations.ext2",
1803            PostCheck::Ext
1804        );
1805        generate_fs_test!(
1806            write_file_triply_indirect_block_pointer,
1807            "./tests/fs/ext2/io_operations.ext2",
1808            PostCheck::Ext
1809        );
1810        generate_fs_test!(write_file_twice, "./tests/fs/ext2/io_operations.ext2", PostCheck::Ext);
1811        generate_fs_test!(file_mode, "./tests/fs/ext2/io_operations.ext2", PostCheck::Ext);
1812        generate_fs_test!(file_truncation, "./tests/fs/ext2/io_operations.ext2", PostCheck::Ext);
1813        generate_fs_test!(file_symlinks, "./tests/fs/ext2/io_operations.ext2", PostCheck::Ext);
1814        generate_fs_test!(new_files, "./tests/fs/ext2/io_operations.ext2", PostCheck::Ext);
1815        generate_fs_test!(remove_files, "./tests/fs/ext2/io_operations.ext2", PostCheck::Ext);
1816        generate_fs_test!(atime_and_mtime, "./tests/fs/ext2/io_operations.ext2", PostCheck::Ext);
1817
1818        // Unsound changes on the ext2 filesystem are made so there should not be a e2fsck check afterward.
1819        generate_fs_test!(set_inode, "./tests/fs/ext2/io_operations.ext2", PostCheck::None);
1820    }
1821}