1use crate::block::{BlockDevice, BlockRead};
5use crate::error::{Error, Result};
6use std::fs::{File, OpenOptions};
7use std::io::{Seek, SeekFrom, Write};
8use std::path::Path;
9use std::sync::Mutex;
10
11pub struct FileDevice {
31 file: File,
32 write_lock: Mutex<()>,
36 size: u64,
37 writable: bool,
38}
39
40impl FileDevice {
41 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
43 let file = File::open(path)?;
44 let size = file.metadata()?.len();
45 Ok(Self {
46 file,
47 write_lock: Mutex::new(()),
48 size,
49 writable: false,
50 })
51 }
52
53 pub fn open_rw<P: AsRef<Path>>(path: P) -> Result<Self> {
55 let file = OpenOptions::new().read(true).write(true).open(path)?;
56 let size = file.metadata()?.len();
57 Ok(Self {
58 file,
59 write_lock: Mutex::new(()),
60 size,
61 writable: true,
62 })
63 }
64
65 pub fn open_best_effort<P: AsRef<Path>>(path: P) -> Result<Self> {
67 let p = path.as_ref();
68 match Self::open_rw(p) {
69 Ok(d) => Ok(d),
70 Err(_) => Self::open(p),
71 }
72 }
73}
74
75impl FileDevice {
76 #[cfg(unix)]
81 fn read_once(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
82 use std::os::unix::fs::FileExt;
83 Ok(self.file.read_at(buf, offset)?)
84 }
85
86 #[cfg(windows)]
90 fn read_once(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
91 use std::os::windows::fs::FileExt;
92 let _guard = self.write_lock.lock().unwrap();
93 Ok(self.file.seek_read(buf, offset)?)
94 }
95}
96
97impl BlockRead for FileDevice {
98 fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
99 let mut total = 0usize;
103 while total < buf.len() {
104 let n = self.read_once(offset + total as u64, &mut buf[total..])?;
105 if n == 0 {
106 return Err(Error::ShortRead {
107 offset,
108 want: buf.len(),
109 got: total,
110 });
111 }
112 total += n;
113 }
114 Ok(())
115 }
116
117 fn size_bytes(&self) -> u64 {
118 self.size
119 }
120}
121
122impl BlockDevice for FileDevice {
123 fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
124 if !self.writable {
125 return Err(Error::ReadOnly);
126 }
127 let _guard = self.write_lock.lock().unwrap();
128 let mut f = &self.file;
129 f.seek(SeekFrom::Start(offset))?;
130 f.write_all(buf)?;
131 Ok(())
132 }
133
134 fn flush(&self) -> Result<()> {
135 if !self.writable {
136 return Ok(());
137 }
138 let _guard = self.write_lock.lock().unwrap();
139 let mut f = &self.file;
140 f.flush()?;
141 self.file.sync_data()?;
142 Ok(())
143 }
144
145 fn is_writable(&self) -> bool {
146 self.writable
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
170 fn many_threads_reading_different_offsets_each_get_their_own_bytes() {
171 let path = temp_path("parallel_reads");
172 let _c = Cleanup(path.clone());
173 let mut bytes = Vec::with_capacity(64 * 256);
176 for page in 0..64u8 {
177 bytes.extend(std::iter::repeat_n(page, 256));
178 }
179 std::fs::write(&path, &bytes).expect("write the image");
180
181 let dev = std::sync::Arc::new(FileDevice::open(&path).expect("open"));
182 let mut handles = Vec::new();
183 for page in 0..64u8 {
184 let dev = dev.clone();
185 handles.push(std::thread::spawn(move || {
186 for _ in 0..50 {
189 let mut buf = [0u8; 256];
190 dev.read_at(u64::from(page) * 256, &mut buf).expect("read");
191 assert!(
192 buf.iter().all(|b| *b == page),
193 "page {page} came back holding another page's bytes"
194 );
195 }
196 }));
197 }
198 for h in handles {
199 h.join().expect("a reader panicked");
200 }
201 }
202
203 use std::sync::atomic::{AtomicU64, Ordering};
204
205 fn temp_path(tag: &str) -> std::path::PathBuf {
207 static N: AtomicU64 = AtomicU64::new(0);
208 let n = N.fetch_add(1, Ordering::Relaxed);
209 let pid = std::process::id();
210 std::env::temp_dir().join(format!("fs_core_{tag}_{pid}_{n}.bin"))
211 }
212
213 struct Cleanup(std::path::PathBuf);
214 impl Drop for Cleanup {
215 fn drop(&mut self) {
216 let _ = std::fs::remove_file(&self.0);
217 }
218 }
219
220 #[test]
221 fn open_rw_round_trips_write_then_read() {
222 let path = temp_path("rw");
223 let _g = Cleanup(path.clone());
224 std::fs::write(&path, vec![0u8; 32]).unwrap();
225
226 let dev = FileDevice::open_rw(&path).unwrap();
227 assert!(dev.is_writable());
228 assert_eq!(dev.size_bytes(), 32);
229
230 dev.write_at(8, &[0xAA, 0xBB, 0xCC, 0xDD]).unwrap();
231 dev.flush().unwrap();
232
233 let mut buf = [0u8; 4];
234 dev.read_at(8, &mut buf).unwrap();
235 assert_eq!(buf, [0xAA, 0xBB, 0xCC, 0xDD]);
236 }
237
238 #[test]
239 fn open_rw_errors_on_missing_path() {
240 let path = temp_path("missing");
241 assert!(FileDevice::open_rw(&path).is_err());
242 }
243
244 #[test]
245 fn open_best_effort_uses_rw_when_writable() {
246 let path = temp_path("best_rw");
247 let _g = Cleanup(path.clone());
248 std::fs::write(&path, vec![0u8; 16]).unwrap();
249
250 let dev = FileDevice::open_best_effort(&path).unwrap();
251 assert!(dev.is_writable());
252 dev.write_at(0, &[0x11; 4]).unwrap();
253 }
254
255 #[test]
256 #[cfg(unix)]
257 fn open_best_effort_falls_back_to_read_only() {
258 use std::os::unix::fs::PermissionsExt;
259
260 let path = temp_path("best_ro");
261 let _g = Cleanup(path.clone());
262 std::fs::write(&path, vec![0xEFu8; 16]).unwrap();
263 std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o444)).unwrap();
265
266 let dev = FileDevice::open_best_effort(&path).unwrap();
267 assert!(!dev.is_writable());
268 assert!(matches!(dev.write_at(0, &[0u8; 4]), Err(Error::ReadOnly)));
270 let mut buf = [0u8; 4];
272 dev.read_at(0, &mut buf).unwrap();
273 assert_eq!(buf, [0xEF; 4]);
274 dev.flush().unwrap();
276 }
277}