1use crate::io_util::{read_exact_at, write_all_at};
2use slate_kv_hal::Flash;
3use std::fs::File;
4#[cfg(target_os = "macos")]
5use std::os::unix::io::AsRawFd;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Durability {
9 Full,
10 OsCache,
11}
12
13#[cfg(target_os = "macos")]
14fn flush_durable(f: &File, durability: Durability) -> std::io::Result<()> {
15 match durability {
16 Durability::Full => {
17 let rc = unsafe { libc::fcntl(f.as_raw_fd(), libc::F_FULLFSYNC) };
20 if rc == -1 {
21 Err(std::io::Error::last_os_error())
22 } else {
23 Ok(())
24 }
25 }
26 Durability::OsCache => f.sync_data(),
27 }
28}
29
30#[cfg(not(target_os = "macos"))]
31fn flush_durable(f: &File, _durability: Durability) -> std::io::Result<()> {
32 f.sync_data()
33}
34
35#[derive(Debug)]
36pub enum FileFlashError {
37 Io(std::io::Error),
38 Unaligned,
39 OutOfBounds,
40 ProgramWithoutErase,
41}
42
43impl From<std::io::Error> for FileFlashError {
44 fn from(err: std::io::Error) -> Self {
45 FileFlashError::Io(err)
46 }
47}
48
49#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
58pub struct FlashCounters {
59 pub read_ops: u64,
60 pub read_bytes: u64,
61 pub read_pages: u64,
62 pub program_ops: u64,
63 pub program_bytes: u64,
64 pub erase_ops: u64,
65}
66
67pub struct FileFlash {
68 file: File,
69 capacity: u32,
70 page_size: usize,
71 block_size: usize,
72 durability: Durability,
73 counters: FlashCounters,
74}
75
76impl FileFlash {
77 pub fn new(
78 file: File,
79 capacity: u32,
80 page_size: usize,
81 block_size: usize,
82 durability: Durability,
83 ) -> Result<Self, std::io::Error> {
84 let meta = file.metadata()?;
85 if meta.len() != capacity as u64 {
86 file.set_len(capacity as u64)?;
87 let ff = vec![0xFF; capacity as usize];
89 write_all_at(&file, &ff, 0)?;
90 flush_durable(&file, durability)?;
91 }
92 Ok(Self {
93 file,
94 capacity,
95 page_size,
96 block_size,
97 durability,
98 counters: FlashCounters::default(),
99 })
100 }
101
102 pub fn counters(&self) -> FlashCounters {
104 self.counters
105 }
106
107 fn pages_spanned(&self, addr: usize, len: usize) -> u64 {
109 if len == 0 {
110 return 0;
111 }
112 let first = addr / self.page_size;
113 let last = (addr + len - 1) / self.page_size;
114 (last - first + 1) as u64
115 }
116}
117
118impl Flash for FileFlash {
119 type Error = FileFlashError;
120
121 fn page_size(&self) -> usize {
122 self.page_size
123 }
124
125 fn block_size(&self) -> usize {
126 self.block_size
127 }
128
129 fn capacity(&self) -> u32 {
130 self.capacity
131 }
132
133 fn read(&mut self, addr: u32, buf: &mut [u8]) -> Result<(), Self::Error> {
134 let addr = addr as usize;
135 if addr + buf.len() > self.capacity as usize {
136 return Err(FileFlashError::OutOfBounds);
137 }
138 read_exact_at(&self.file, buf, addr as u64)?;
139 self.counters.read_ops += 1;
140 self.counters.read_bytes += buf.len() as u64;
141 self.counters.read_pages += self.pages_spanned(addr, buf.len());
142 Ok(())
143 }
144
145 fn program(&mut self, addr: u32, buf: &[u8]) -> Result<(), Self::Error> {
146 let addr = addr as usize;
147 if addr % self.page_size != 0 || buf.len() % self.page_size != 0 {
148 return Err(FileFlashError::Unaligned);
149 }
150 if addr + buf.len() > self.capacity as usize {
151 return Err(FileFlashError::OutOfBounds);
152 }
153
154 let mut check_buf = vec![0u8; buf.len()];
156 read_exact_at(&self.file, &mut check_buf, addr as u64)?;
157 if !check_buf.iter().all(|&b| b == 0xFF) {
158 return Err(FileFlashError::ProgramWithoutErase);
159 }
160
161 write_all_at(&self.file, buf, addr as u64)?;
163 flush_durable(&self.file, self.durability)?;
164 self.counters.program_ops += 1;
165 self.counters.program_bytes += buf.len() as u64;
166 Ok(())
167 }
168
169 fn erase(&mut self, block_addr: u32) -> Result<(), Self::Error> {
170 let block_addr = block_addr as usize;
171 if block_addr % self.block_size != 0 {
172 return Err(FileFlashError::Unaligned);
173 }
174 if block_addr + self.block_size > self.capacity as usize {
175 return Err(FileFlashError::OutOfBounds);
176 }
177
178 let ff = vec![0xFF; self.block_size];
179 write_all_at(&self.file, &ff, block_addr as u64)?;
180 flush_durable(&self.file, self.durability)?;
181 self.counters.erase_ops += 1;
182 Ok(())
183 }
184}