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}