1use std::{
4 fs,
5 io::{self, Read},
6 path::{Path, PathBuf},
7};
8
9use lexe_crypto::rng::{RngExt, ThreadFastRng};
10
11pub trait Ffs {
16 #[cfg_attr(not(feature = "unstable"), allow(dead_code))]
20 fn read(&self, filename: &str) -> io::Result<Vec<u8>> {
21 let mut buf = Vec::new();
22 self.read_into(filename, &mut buf)?;
23 Ok(buf)
24 }
25
26 fn read_into(&self, filename: &str, buf: &mut Vec<u8>) -> io::Result<()>;
28
29 #[cfg_attr(not(feature = "unstable"), allow(dead_code))]
31 fn read_dir(&self) -> io::Result<Vec<String>> {
32 let mut filenames = Vec::new();
33 self.read_dir_visitor(|filename| {
34 filenames.push(filename.to_owned());
35 Ok(())
36 })?;
37 Ok(filenames)
38 }
39
40 fn read_dir_visitor(
42 &self,
43 dir_visitor: impl FnMut(&str) -> io::Result<()>,
44 ) -> io::Result<()>;
45
46 fn write(&self, filename: &str, data: &[u8]) -> io::Result<()>;
48
49 fn delete_all(&self) -> io::Result<()>;
52
53 #[cfg_attr(not(feature = "unstable"), allow(dead_code))]
55 fn delete(&self, filename: &str) -> io::Result<()>;
56}
57
58#[derive(Clone)]
60pub struct DiskFs {
61 base_dir: PathBuf,
63
64 write_dir: PathBuf,
73}
74
75impl DiskFs {
76 pub fn create_dir_all(base_dir: PathBuf) -> anyhow::Result<Self> {
82 fs::create_dir_all(base_dir.as_path())?;
84
85 let write_dir = Self::write_dir_path(&base_dir);
88 fsext::remove_dir_all_idempotent(&write_dir)?;
89 fs::create_dir(write_dir.as_path())?;
90
91 Ok(Self {
92 base_dir,
93 write_dir,
94 })
95 }
96
97 pub fn create_clean_dir_all(base_dir: PathBuf) -> anyhow::Result<Self> {
100 fsext::remove_dir_all_idempotent(&base_dir)?;
102 fs::create_dir_all(base_dir.as_path())?;
103
104 let write_dir = Self::write_dir_path(&base_dir);
105 fs::create_dir(write_dir.as_path())?;
106
107 Ok(Self {
108 base_dir,
109 write_dir,
110 })
111 }
112
113 fn write_dir_path(base_dir: &Path) -> PathBuf {
114 base_dir.join(".write")
115 }
116}
117
118impl Ffs for DiskFs {
119 fn read_into(&self, filename: &str, buf: &mut Vec<u8>) -> io::Result<()> {
120 let mut file = fs::File::open(self.base_dir.join(filename).as_path())?;
121 file.read_to_end(buf)?;
122 Ok(())
123 }
124
125 fn read_dir_visitor(
126 &self,
127 mut dir_visitor: impl FnMut(&str) -> io::Result<()>,
128 ) -> io::Result<()> {
129 for maybe_file_entry in self.base_dir.read_dir()? {
130 let file_entry = maybe_file_entry?;
131
132 if file_entry.file_type()?.is_file() {
134 if let Some(filename) = file_entry.file_name().to_str() {
136 dir_visitor(filename)?;
137 }
138 }
139 }
140 Ok(())
141 }
142
143 fn write(&self, filename: &str, data: &[u8]) -> io::Result<()> {
144 let final_dest_path = self.base_dir.join(filename);
145
146 let tmp_write_path = {
152 let name: [u8; 16] = ThreadFastRng::new().gen_alphanum_bytes();
153 let name_str = std::str::from_utf8(name.as_slice())
154 .expect("ASCII is all valid UTF-8");
155 self.write_dir.join(name_str)
156 };
157
158 fs::write(tmp_write_path.as_path(), data)?;
160 fs::rename(tmp_write_path.as_path(), final_dest_path)?;
161 Ok(())
162 }
163
164 fn delete_all(&self) -> io::Result<()> {
165 fs::remove_dir_all(self.base_dir.as_path())?;
166 fs::create_dir(self.base_dir.as_path())?;
167 fs::create_dir(self.write_dir.as_path())?;
169 Ok(())
170 }
171
172 fn delete(&self, filename: &str) -> io::Result<()> {
173 fs::remove_file(self.base_dir.join(filename).as_path())?;
174 Ok(())
175 }
176}
177
178pub mod fsext {
181 use std::{fs, io, path::Path};
182
183 pub fn remove_dir_all_idempotent(dir: &Path) -> io::Result<bool> {
186 match fs::remove_dir_all(dir) {
187 Ok(()) => Ok(true),
188 Err(ref e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
189 Err(e) => Err(e),
190 }
191 }
192}
193
194#[cfg(any(test, feature = "test-utils"))]
196pub mod test_utils {
197 use std::{cell::RefCell, collections::BTreeMap, io};
198
199 use lexe_crypto::rng::{FastRng, RngSliceExt};
200
201 use super::Ffs;
202
203 fn io_err_not_found(filename: &str) -> io::Error {
204 io::Error::new(io::ErrorKind::NotFound, filename)
205 }
206
207 #[derive(Debug)]
209 pub struct InMemoryFfs {
210 inner: RefCell<InMemoryFfsInner>,
211 }
212
213 #[derive(Debug)]
214 struct InMemoryFfsInner {
215 rng: FastRng,
216 files: BTreeMap<String, Vec<u8>>,
217 }
218
219 impl InMemoryFfs {
220 pub fn new() -> Self {
222 Self {
223 inner: RefCell::new(InMemoryFfsInner {
224 rng: FastRng::new(),
225 files: BTreeMap::new(),
226 }),
227 }
228 }
229
230 pub fn from_rng(rng: FastRng) -> Self {
232 Self {
233 inner: RefCell::new(InMemoryFfsInner {
234 rng,
235 files: BTreeMap::new(),
236 }),
237 }
238 }
239 }
240
241 impl Default for InMemoryFfs {
242 fn default() -> Self {
243 Self::new()
244 }
245 }
246
247 impl Ffs for InMemoryFfs {
248 fn read_into(
249 &self,
250 filename: &str,
251 buf: &mut Vec<u8>,
252 ) -> io::Result<()> {
253 match self.inner.borrow().files.get(filename) {
254 Some(data) => buf.extend_from_slice(data),
255 None => return Err(io_err_not_found(filename)),
256 }
257 Ok(())
258 }
259
260 fn read_dir_visitor(
261 &self,
262 mut dir_visitor: impl FnMut(&str) -> io::Result<()>,
263 ) -> io::Result<()> {
264 let mut filenames = self
266 .inner
267 .borrow()
268 .files
269 .keys()
270 .cloned()
271 .collect::<Vec<_>>();
272 filenames.shuffle(&mut self.inner.borrow_mut().rng);
273
274 for filename in &filenames {
275 dir_visitor(filename)?;
276 }
277 Ok(())
278 }
279
280 fn write(&self, filename: &str, data: &[u8]) -> io::Result<()> {
281 self.inner
282 .borrow_mut()
283 .files
284 .insert(filename.to_owned(), data.to_owned());
285 Ok(())
286 }
287
288 fn delete_all(&self) -> io::Result<()> {
289 self.inner.borrow_mut().files = BTreeMap::new();
290 Ok(())
291 }
292
293 fn delete(&self, filename: &str) -> io::Result<()> {
294 match self.inner.borrow_mut().files.remove(filename) {
295 Some(_) => Ok(()),
296 None => Err(io_err_not_found(filename)),
297 }
298 }
299 }
300}