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