ext4_view/
file.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
9use crate::Ext4;
10use crate::error::Ext4Error;
11use crate::inode::Inode;
12use crate::iters::file_blocks::FileBlocks;
13use crate::metadata::Metadata;
14use crate::path::Path;
15use crate::resolve::FollowSymlinks;
16use crate::util::usize_from_u32;
17use core::fmt::{self, Debug, Formatter};
18
19#[cfg(feature = "std")]
20use std::io::{self, ErrorKind, Read, Seek, SeekFrom};
21
22/// An open file within an [`Ext4`] filesystem.
23pub struct File {
24    fs: Ext4,
25    inode: Inode,
26    file_blocks: FileBlocks,
27
28    /// Current byte offset within the file.
29    position: u64,
30
31    /// Current block within the file. This is an absolute block index
32    /// within the filesystem.
33    ///
34    /// If `None`, either the next block needs to be fetched from the
35    /// `file_blocks` iterator, or the end of the file has been reached.
36    block_index: Option<u64>,
37}
38
39impl File {
40    /// Open the file at `path`.
41    pub(crate) fn open(fs: &Ext4, path: Path<'_>) -> Result<Self, Ext4Error> {
42        let inode = fs.path_to_inode(path, FollowSymlinks::All)?;
43
44        if inode.metadata.is_dir() {
45            return Err(Ext4Error::IsADirectory);
46        }
47        if !inode.metadata.file_type.is_regular_file() {
48            return Err(Ext4Error::IsASpecialFile);
49        }
50
51        Self::open_inode(fs, inode)
52    }
53
54    /// Open `inode`. Note that unlike `File::open`, this allows any
55    /// type of `inode` to be opened, including directories and
56    /// symlinks. This is used by `Ext4::read_inode_file`.
57    pub(crate) fn open_inode(
58        fs: &Ext4,
59        inode: Inode,
60    ) -> Result<Self, Ext4Error> {
61        Ok(Self {
62            fs: fs.clone(),
63            position: 0,
64            file_blocks: FileBlocks::new(fs.clone(), &inode)?,
65            inode,
66            block_index: None,
67        })
68    }
69
70    /// Get the file metadata.
71    #[must_use]
72    pub fn metadata(&self) -> &Metadata {
73        &self.inode.metadata
74    }
75
76    /// Read bytes from the file into `buf`, returning how many bytes
77    /// were read. The number may be smaller than the length of the
78    /// input buffer.
79    ///
80    /// This advances the position of the file by the number of bytes
81    /// read, so calling `read_bytes` repeatedly can be used to read the
82    /// entire file.
83    ///
84    /// Returns `Ok(0)` if the end of the file has been reached.
85    pub fn read_bytes(
86        &mut self,
87        mut buf: &mut [u8],
88    ) -> Result<usize, Ext4Error> {
89        // Nothing to do if output buffer is empty.
90        if buf.is_empty() {
91            return Ok(0);
92        }
93
94        // Nothing to do if already at the end of the file.
95        if self.position >= self.inode.metadata.size_in_bytes {
96            return Ok(0);
97        }
98
99        // Get the number of bytes remaining in the file, starting from
100        // the current `position`.
101        //
102        // OK to unwrap: just checked that `position` is less than the
103        // file size.
104        let bytes_remaining = self
105            .inode
106            .metadata
107            .size_in_bytes
108            .checked_sub(self.position)
109            .unwrap();
110
111        // If the the number of bytes remaining is less than the output
112        // buffer length, shrink the buffer.
113        //
114        // If the conversion to `usize` fails, the output buffer is
115        // definitely not larger than the remaining bytes to read.
116        if let Ok(bytes_remaining) = usize::try_from(bytes_remaining) {
117            if buf.len() > bytes_remaining {
118                buf = &mut buf[..bytes_remaining];
119            }
120        }
121
122        let block_size = self.fs.0.superblock.block_size;
123
124        // Get the block to read from.
125        let block_index = if let Some(block_index) = self.block_index {
126            block_index
127        } else {
128            // OK to unwrap: already checked that the position is not at
129            // the end of the file, so there must be at least one more
130            // block to read.
131            let block_index = self.file_blocks.next().unwrap()?;
132
133            self.block_index = Some(block_index);
134
135            block_index
136        };
137
138        // Byte offset within the current block.
139        //
140        // OK to unwrap: block size fits in a `u32`, so an offset within
141        // the block will as well.
142        let offset_within_block: u32 =
143            u32::try_from(self.position % block_size.to_nz_u64()).unwrap();
144
145        // OK to unwrap: `offset_within_block` is always less than or
146        // equal to the block length.
147        //
148        // Note that if this block is at the end of the file, the block
149        // may extend past the actual number of bytes in the file. This
150        // does not matter because the output buffer's length was
151        // already capped earlier against the number of bytes remaining
152        // in the file.
153        let bytes_remaining_in_block: u32 = block_size
154            .to_u32()
155            .checked_sub(offset_within_block)
156            .unwrap();
157
158        // If the output buffer is larger than the number of bytes
159        // remaining in the block, shink the buffer.
160        if buf.len() > usize_from_u32(bytes_remaining_in_block) {
161            buf = &mut buf[..usize_from_u32(bytes_remaining_in_block)];
162        }
163
164        // OK to unwrap: the buffer length has been capped so that it
165        // cannot be larger than the block size, and the block size fits
166        // in a `u32`.
167        let buf_len_u32: u32 = buf.len().try_into().unwrap();
168
169        // Read the block data, or zeros if in a hole.
170        if block_index == 0 {
171            buf.fill(0);
172        } else {
173            self.fs
174                .read_from_block(block_index, offset_within_block, buf)?;
175        }
176
177        // OK to unwrap: reads don't extend past a block, so this is at
178        // most `block_size`, which always fits in a `u32`.
179        let new_offset_within_block: u32 =
180            offset_within_block.checked_add(buf_len_u32).unwrap();
181
182        // If the end of this block has been reached, clear
183        // `self.block_index` so that the next call fetches a new block
184        // from the iterator.
185        if new_offset_within_block >= block_size {
186            self.block_index = None;
187        }
188
189        // OK to unwrap: the buffer length is capped such that this
190        // calculation is at most the length of the file, which fits in
191        // a `u64`.
192        self.position =
193            self.position.checked_add(u64::from(buf_len_u32)).unwrap();
194
195        Ok(buf.len())
196    }
197
198    /// Current position within the file.
199    #[must_use]
200    pub fn position(&self) -> u64 {
201        self.position
202    }
203
204    /// Seek from the start of the file to `position`.
205    ///
206    /// Seeking past the end of the file is allowed.
207    pub fn seek_to(&mut self, position: u64) -> Result<(), Ext4Error> {
208        // Reset iteration.
209        self.file_blocks = FileBlocks::new(self.fs.clone(), &self.inode)?;
210        self.block_index = None;
211
212        // Advance the block iterator by the number of whole blocks in
213        // `position`.
214        let num_blocks = position / self.fs.0.superblock.block_size.to_nz_u64();
215        for _ in 0..num_blocks {
216            self.file_blocks.next();
217        }
218
219        self.position = position;
220
221        Ok(())
222    }
223}
224
225impl Debug for File {
226    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
227        f.debug_struct("File")
228            // Just show the index from `self.inode`, the full `Inode`
229            // output is verbose.
230            .field("inode", &self.inode.index)
231            .field("position", &self.position)
232            // Don't show all fields, as that would make the output less
233            // readable.
234            .finish_non_exhaustive()
235    }
236}
237
238#[cfg(feature = "std")]
239impl Read for File {
240    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
241        Ok(self.read_bytes(buf)?)
242    }
243}
244
245#[cfg(feature = "std")]
246impl Seek for File {
247    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
248        let pos = match pos {
249            SeekFrom::Start(pos) => pos,
250            SeekFrom::End(offset) => {
251                // file_size + offset:
252                i64::try_from(self.inode.metadata.size_in_bytes)
253                    .ok()
254                    .and_then(|pos| pos.checked_add(offset))
255                    .and_then(|pos| pos.try_into().ok())
256                    .ok_or(ErrorKind::InvalidInput)?
257            }
258            SeekFrom::Current(offset) => {
259                // current_pos + offset:
260                i64::try_from(self.position)
261                    .ok()
262                    .and_then(|pos| pos.checked_add(offset))
263                    .and_then(|pos| pos.try_into().ok())
264                    .ok_or(ErrorKind::InvalidInput)?
265            }
266        };
267
268        self.seek_to(pos)?;
269
270        Ok(self.position)
271    }
272}