1use super::{BLCKSZ, BlockCount, BlockNumber, page::Page};
5
6use anyhow::Context;
7
8use std::{
9 collections::HashMap,
10 fmt::Write,
11 fs::{self, File},
12 io,
13 os::unix::fs::FileExt,
14 path::PathBuf,
15 sync::{Arc, Mutex},
16};
17
18#[derive(Clone, Copy, Debug)]
19pub struct BlockIdentifier {
20 pub fork: ForkIdentifier,
21 pub block_number: BlockNumber,
22}
23
24#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
25pub struct ForkIdentifier {
26 pub rel: RelFileNumber,
27 pub kind: ForkKind,
28}
29
30#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
31pub struct RelFileNumber(pub u32);
32
33#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
34pub enum ForkKind {
35 Main = 0,
36 FreeSpaceMap = 1,
37 VisibilityMap = 2,
38 Init = 3,
39}
40
41pub struct StorageManager {
42 dir: PathBuf,
43 n_block_per_segment: BlockCount,
46 opened_forks: Mutex<HashMap<ForkIdentifier, Arc<OpenFork>>>,
47}
48
49#[derive(Debug)]
50pub struct BlockError {
51 pub block: BlockIdentifier,
52 pub inner: BlockErrorInner,
53}
54
55#[derive(Debug)]
56pub struct ForkError {
57 pub fork: ForkIdentifier,
58 pub inner: ForkErrorInner,
59}
60
61#[derive(Debug)]
62pub enum ForkErrorInner {
63 Io(io::Error),
64 SegmentFileInconsistentlyBig {
65 path: PathBuf,
66 file_size: u64,
67 n_blocks_per_segment: BlockCount,
68 },
69}
70
71#[derive(Debug)]
72pub enum BlockErrorInner {
73 BlockDoesNotExist { fork_size: BlockCount },
74 Io(io::Error),
75 ForkError(ForkError),
76}
77
78impl StorageManager {
79 pub fn open(dir: impl Into<PathBuf>) -> anyhow::Result<Self> {
82 Self::with_segment_size(dir, RELSEG_SIZE)
83 }
84
85 pub fn read(&self, block: BlockIdentifier, page: &mut Page) -> Result<(), BlockError> {
87 let fork = self.get_fork(block.fork).map_err(|err| BlockError {
88 block,
89 inner: BlockErrorInner::ForkError(err),
90 })?;
91 if !fork.nb_blocks.contains(block.block_number) {
92 return Err(BlockError {
93 block,
94 inner: BlockErrorInner::BlockDoesNotExist {
95 fork_size: fork.nb_blocks,
96 },
97 });
98 }
99 let Position {
100 segno,
101 offset_bytes,
102 } = self.position(block.block_number);
103 fork.segments[segno as usize]
104 .read_exact_at(page, offset_bytes)
105 .map_err(|err| BlockError {
106 block,
107 inner: BlockErrorInner::Io(err),
108 })
109 }
110
111 fn with_segment_size(
112 dir: impl Into<PathBuf>,
113 n_block_per_segment: BlockCount,
114 ) -> anyhow::Result<Self> {
115 assert!(
116 n_block_per_segment.0 > 0,
117 "segments must hold at least one block"
118 );
119 let dir = dir.into();
120 fs::read_dir(&dir)
121 .with_context(|| format!("could not open directory {}", dir.display()))?;
122 Ok(Self {
123 dir,
124 n_block_per_segment,
125 opened_forks: Mutex::new(HashMap::new()),
126 })
127 }
128
129 fn get_fork(&self, fork: ForkIdentifier) -> Result<Arc<OpenFork>, ForkError> {
130 let mut forks = self.opened_forks.lock().unwrap();
131 if let Some(open) = forks.get(&fork) {
132 return Ok(Arc::clone(open));
133 }
134 let open = Arc::new(self.load_fork(fork)?);
135 forks.insert(fork, Arc::clone(&open));
136 Ok(open)
137 }
138
139 fn load_fork(&self, fork: ForkIdentifier) -> Result<OpenFork, ForkError> {
140 let empty = OpenFork {
141 segments: Vec::new(),
142 nb_blocks: BlockCount(0),
143 };
144 let mut previous_full = true;
145 (0..)
146 .map_while(|segno| {
149 if !previous_full {
150 return None;
151 }
152 let segment = self.open_segment(fork, segno).transpose()?;
153 previous_full = segment
154 .as_ref()
155 .is_ok_and(|segment| segment.nb_blocks == self.n_block_per_segment);
156 Some(segment)
157 })
158 .try_fold(empty, |mut open, segment| {
159 let segment = segment?;
160 open.segments.push(segment.file);
161 open.nb_blocks.0 += segment.nb_blocks.0;
162 Ok(open)
163 })
164 }
165
166 fn open_segment(&self, fork: ForkIdentifier, segno: u32) -> Result<Option<Segment>, ForkError> {
169 let path = self.path(fork, segno);
170 let file = match File::open(&path) {
171 Ok(file) => file,
172 Err(err) if segno > 0 && err.kind() == io::ErrorKind::NotFound => return Ok(None),
174 Err(err) => {
175 return Err(ForkError {
176 fork,
177 inner: ForkErrorInner::Io(err),
178 });
179 }
180 };
181 let file_size = file
184 .metadata()
185 .map_err(|err| ForkError {
186 fork,
187 inner: err.into(),
188 })?
189 .len();
190 let blocks = file_size / (BLCKSZ as u64);
191 if blocks > u64::from(self.n_block_per_segment.0) {
192 return Err(ForkError {
193 fork,
194 inner: ForkErrorInner::SegmentFileInconsistentlyBig {
195 path,
196 file_size,
197 n_blocks_per_segment: self.n_block_per_segment,
198 },
199 });
200 }
201 Ok(Some(Segment {
202 file,
203 nb_blocks: BlockCount(blocks as u32),
204 }))
205 }
206
207 fn path(&self, fork: ForkIdentifier, segno: u32) -> PathBuf {
209 let mut name = fork.rel.0.to_string();
210 match fork.kind {
211 ForkKind::Main => {}
212 ForkKind::FreeSpaceMap => {
213 name.push_str("_fsm");
214 }
215 ForkKind::VisibilityMap => {
216 name.push_str("_vm");
217 }
218 ForkKind::Init => {
219 name.push_str("_init");
220 }
221 }
222 if segno > 0 {
223 write!(&mut name, ".{segno}").unwrap();
224 }
225 self.dir.join(name)
226 }
227
228 fn position(&self, block: BlockNumber) -> Position {
229 let segno = block.0 / self.n_block_per_segment.0;
230 let offset = u64::from(block.0 % self.n_block_per_segment.0) * BLCKSZ as u64;
231 Position {
232 segno,
233 offset_bytes: offset,
234 }
235 }
236}
237
238pub const RELSEG_SIZE: BlockCount = BlockCount(1024 * 1024 * 1024 / (BLCKSZ as u32));
240
241struct OpenFork {
243 segments: Vec<File>,
244 nb_blocks: BlockCount,
245}
246
247struct Segment {
249 file: File,
250 nb_blocks: BlockCount,
251}
252
253struct Position {
254 segno: u32,
255 offset_bytes: u64,
256}
257
258impl From<io::Error> for ForkErrorInner {
259 fn from(err: io::Error) -> Self {
260 ForkErrorInner::Io(err)
261 }
262}
263
264impl From<io::Error> for BlockErrorInner {
265 fn from(err: io::Error) -> Self {
266 BlockErrorInner::Io(err)
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 use std::path::Path;
275
276 const MAIN: ForkIdentifier = ForkIdentifier {
277 rel: RelFileNumber(16384),
278 kind: ForkKind::Main,
279 };
280
281 fn write_blocks(dir: &Path, name: &str, first: u8, count: u8) {
284 let bytes: Vec<u8> = (first..first + count)
285 .flat_map(|byte| [byte; BLCKSZ])
286 .collect();
287 fs::write(dir.join(name), bytes).unwrap();
288 }
289
290 fn open_with_small_segments(dir: &Path) -> StorageManager {
293 StorageManager::with_segment_size(dir, BlockCount(2)).unwrap()
294 }
295
296 fn nb_blocks(smgr: &StorageManager) -> BlockCount {
297 smgr.get_fork(MAIN).unwrap().nb_blocks
298 }
299
300 fn read(smgr: &StorageManager, block_number: u32) -> Result<Page, BlockError> {
301 let block = BlockIdentifier {
302 fork: MAIN,
303 block_number: BlockNumber(block_number),
304 };
305 let mut page = Page { bytes: [0; BLCKSZ] };
306 smgr.read(block, &mut page).map(|()| page)
307 }
308
309 fn is_past_the_end(result: &Result<Page, BlockError>, fork_size: u32) -> bool {
310 matches!(
311 result,
312 Err(BlockError {
313 inner: BlockErrorInner::BlockDoesNotExist { fork_size: size },
314 ..
315 }) if *size == BlockCount(fork_size)
316 )
317 }
318
319 #[test]
320 fn blocks_are_found_across_segments() {
321 let dir = tempfile::tempdir().unwrap();
322 write_blocks(dir.path(), "16384", 0, 2);
323 write_blocks(dir.path(), "16384.1", 2, 2);
324 write_blocks(dir.path(), "16384.2", 4, 1);
325 let smgr = open_with_small_segments(dir.path());
326 assert_eq!(nb_blocks(&smgr), BlockCount(5));
327 for block_number in 0..5 {
328 let page = read(&smgr, block_number).unwrap();
329 assert!(
330 page.bytes == [block_number as u8; BLCKSZ],
331 "block {block_number}"
332 );
333 }
334 assert!(is_past_the_end(&read(&smgr, 5), 5));
335 }
336
337 #[test]
338 fn a_fork_can_end_with_a_full_segment() {
339 let dir = tempfile::tempdir().unwrap();
340 write_blocks(dir.path(), "16384", 0, 2);
341 write_blocks(dir.path(), "16384.1", 2, 2);
342 let smgr = open_with_small_segments(dir.path());
343 assert_eq!(nb_blocks(&smgr), BlockCount(4));
344 assert!(is_past_the_end(&read(&smgr, 4), 4));
345 }
346
347 #[test]
348 fn an_empty_fork_has_no_blocks() {
349 let dir = tempfile::tempdir().unwrap();
350 fs::write(dir.path().join("16384"), []).unwrap();
351 let smgr = open_with_small_segments(dir.path());
352 assert_eq!(nb_blocks(&smgr), BlockCount(0));
353 assert!(is_past_the_end(&read(&smgr, 0), 0));
354 }
355
356 #[test]
359 fn segments_after_a_partial_one_are_ignored() {
360 let dir = tempfile::tempdir().unwrap();
361 write_blocks(dir.path(), "16384", 0, 1);
362 write_blocks(dir.path(), "16384.1", 9, 2);
363 let smgr = open_with_small_segments(dir.path());
364 assert_eq!(nb_blocks(&smgr), BlockCount(1));
365 }
366
367 #[test]
368 fn a_partial_block_at_the_end_does_not_count() {
369 let dir = tempfile::tempdir().unwrap();
370 let mut bytes = vec![0; BLCKSZ];
371 bytes.extend([1; 100]);
372 fs::write(dir.path().join("16384"), bytes).unwrap();
373 let smgr = StorageManager::open(dir.path()).unwrap();
374 assert_eq!(nb_blocks(&smgr), BlockCount(1));
375 assert!(is_past_the_end(&read(&smgr, 1), 1));
376 }
377
378 #[test]
379 fn a_missing_fork_is_an_error() {
380 let dir = tempfile::tempdir().unwrap();
381 let smgr = open_with_small_segments(dir.path());
382 let result = read(&smgr, 0);
383 assert!(
384 matches!(
385 &result,
386 Err(BlockError {
387 inner: BlockErrorInner::ForkError(ForkError {
388 inner: ForkErrorInner::Io(err),
389 ..
390 }),
391 ..
392 }) if err.kind() == io::ErrorKind::NotFound
393 ),
394 "{result:?}"
395 );
396 }
397
398 #[test]
399 fn a_segment_bigger_than_the_segment_size_is_an_error() {
400 let dir = tempfile::tempdir().unwrap();
401 write_blocks(dir.path(), "16384", 0, 3);
402 let smgr = open_with_small_segments(dir.path());
403 let result = smgr.get_fork(MAIN).map(|_| ());
404 assert!(
405 matches!(
406 &result,
407 Err(ForkError {
408 inner: ForkErrorInner::SegmentFileInconsistentlyBig {
409 file_size,
410 n_blocks_per_segment: BlockCount(2),
411 ..
412 },
413 ..
414 }) if *file_size == 3 * BLCKSZ as u64
415 ),
416 "{result:?}"
417 );
418 }
419}