1use alloc::borrow::Cow;
21use core::cell::RefCell;
22use std::fs::{File, OpenOptions};
23use std::io::{Read, Seek, SeekFrom, Write};
24use std::path::Path;
25
26use wasefire_error::Error;
27
28use crate::{Storage, StorageIndex};
29
30pub struct FileStorage {
34 options: FileOptions,
36
37 file: RefCell<File>,
45}
46
47pub struct FileOptions {
49 pub word_size: usize,
51
52 pub page_size: usize,
54
55 pub num_pages: usize,
57}
58
59impl FileStorage {
60 pub fn new(path: &Path, options: FileOptions) -> Result<FileStorage, Error> {
61 let mut file_ref = RefCell::new(
62 OpenOptions::new().read(true).write(true).create(true).truncate(false).open(path)?,
63 );
64 let file = file_ref.get_mut();
65 let file_len = file.metadata()?.len();
66 let store_len: u64 = (options.page_size * options.num_pages) as u64;
67
68 if file_len == 0 {
69 file.seek(SeekFrom::Start(0))?;
70 let buf = vec![0xff; options.page_size];
71 for _ in 0 .. options.num_pages {
72 file.write_all(&buf)?;
73 }
74 } else if file_len != store_len {
75 panic!("Invalid file size {file_len}, should be {store_len}");
78 }
79 Ok(FileStorage { options, file: file_ref })
80 }
81}
82
83impl Storage for FileStorage {
84 fn word_size(&self) -> usize {
85 self.options.word_size
86 }
87
88 fn page_size(&self) -> usize {
89 self.options.page_size
90 }
91
92 fn num_pages(&self) -> usize {
93 self.options.num_pages
94 }
95
96 fn max_word_writes(&self) -> usize {
97 u32::MAX as usize
100 }
101
102 fn max_page_erases(&self) -> usize {
103 u16::MAX as usize
106 }
107
108 fn read_slice(&self, index: StorageIndex, length: usize) -> Result<Cow<'_, [u8]>, Error> {
109 let mut file = self.file.borrow_mut();
110 file.seek(SeekFrom::Start(index.range(length, self)?.start as u64))?;
111 let mut buf = vec![0u8; length];
112 file.read_exact(&mut buf)?;
113 Ok(Cow::Owned(buf))
114 }
115
116 fn write_slice(&mut self, index: StorageIndex, value: &[u8]) -> Result<(), Error> {
117 let mut file = self.file.borrow_mut();
118 file.seek(SeekFrom::Start(index.range(value.len(), self)?.start as u64))?;
119 file.write_all(value)?;
120 Ok(())
121 }
122
123 fn erase_page(&mut self, page: usize) -> Result<(), Error> {
124 let mut file = self.file.borrow_mut();
125 let index = StorageIndex { page, byte: 0 };
126 file.seek(SeekFrom::Start(index.range(self.page_size(), self)?.start as u64))?;
127 file.write_all(&vec![0xff; self.page_size()][..])?;
128 Ok(())
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use std::path::PathBuf;
135
136 use tempfile::TempDir;
137
138 use super::*;
139
140 const BLANK_WORD: &[u8] = &[0xff, 0xff, 0xff, 0xff];
141 const DATA_WORD: &[u8] = &[0xee, 0xdd, 0xbb, 0x77];
142
143 const FILE_NAME: &str = "storage.bin";
144
145 const OPTIONS: FileOptions = FileOptions { word_size: 4, page_size: 0x1000, num_pages: 20 };
146
147 fn make_tmp_dir() -> PathBuf {
148 TempDir::new().unwrap().keep()
149 }
150
151 fn remove_tmp_dir(tmp_dir: &Path) {
152 std::fs::remove_dir_all(tmp_dir).unwrap();
153 }
154
155 fn temp_storage(tmp_dir: &Path) -> FileStorage {
156 FileStorage::new(&tmp_dir.join(FILE_NAME), OPTIONS).unwrap()
157 }
158
159 #[test]
160 fn read_write_persist_ok() {
161 let index = StorageIndex { page: 0, byte: 0 };
162 let next_index = StorageIndex { page: 0, byte: 4 };
163
164 let tmp_dir = make_tmp_dir();
165 {
166 let mut file_storage = temp_storage(&tmp_dir);
167 assert_eq!(file_storage.read_slice(index, 4).unwrap(), BLANK_WORD);
168 file_storage.write_slice(index, DATA_WORD).unwrap();
169 assert_eq!(file_storage.read_slice(index, 4).unwrap(), DATA_WORD);
170 assert_eq!(file_storage.read_slice(next_index, 4).unwrap(), BLANK_WORD);
171 }
172 {
174 let file_storage = temp_storage(&tmp_dir);
175 assert_eq!(file_storage.read_slice(index, 4).unwrap(), DATA_WORD);
176 assert_eq!(file_storage.read_slice(next_index, 4).unwrap(), BLANK_WORD);
177 }
178 remove_tmp_dir(&tmp_dir);
179 }
180}