1use crate::byte_log::{ByteLogger, ByteRegion, ByteSection, ByteStorage, read_header, read_main};
3use crate::{
4 ApplicationMetadata, CapacityPolicy, MainHeader, SECTION_HEADER_COMPACT_SIZE, SectionHeader,
5 UnifiedLogRead,
6};
7use alloc::sync::Arc;
8use bincode::{config::standard, decode_from_slice};
9use cu29_traits::{CuError, CuResult, UnifiedLogType};
10use memmap2::{Mmap, MmapMut};
11use std::fs::{File, OpenOptions};
12use std::io::{self, Read};
13use std::path::{Path, PathBuf};
14
15const MAX_SPANS: usize = 64;
16enum Mapping {
17 Read(Mmap),
18 Write(core::cell::UnsafeCell<MmapMut>),
19}
20struct MappedFile {
21 mapping: Mapping,
22 pointer: *mut u8,
23 length: usize,
24 #[cfg(feature = "mmap-fsync")]
25 file: File,
26}
27unsafe impl Send for MappedFile {}
30unsafe impl Sync for MappedFile {}
31impl MappedFile {
32 fn new(file: File, writable: bool) -> io::Result<Self> {
33 let (mapping, pointer, length) = unsafe {
35 if writable {
36 let mut mapping = MmapMut::map_mut(&file)?;
37 let pointer = mapping.as_mut_ptr();
38 let length = mapping.len();
39 (
40 Mapping::Write(core::cell::UnsafeCell::new(mapping)),
41 pointer,
42 length,
43 )
44 } else {
45 let mapping = Mmap::map(&file)?;
46 let pointer = mapping.as_ptr().cast_mut();
47 let length = mapping.len();
48 (Mapping::Read(mapping), pointer, length)
49 }
50 };
51 Ok(Self {
52 mapping,
53 pointer,
54 length,
55 #[cfg(feature = "mmap-fsync")]
56 file,
57 })
58 }
59}
60
61pub struct MmapRegion {
63 maps: [Option<Arc<MappedFile>>; MAX_SPANS],
64 first: usize,
65 slab_size: usize,
66 length: u64,
67}
68impl MmapRegion {
69 fn visit(
70 &self,
71 offset: u64,
72 len: usize,
73 mut visit: impl FnMut(&MappedFile, usize, usize, usize) -> CuResult<()>,
74 ) -> CuResult<()> {
75 if offset
76 .checked_add(len as u64)
77 .is_none_or(|end| end > self.length)
78 {
79 return Err(CuError::from("Mapped range exceeds section"));
80 }
81 let mut cursor = self.first
82 + usize::try_from(offset).map_err(|_| CuError::from("Offset exceeds address space"))?;
83 let mut done = 0;
84 while done < len {
85 let index = cursor / self.slab_size;
86 let local = cursor % self.slab_size;
87 let map = self
88 .maps
89 .get(index)
90 .and_then(Option::as_ref)
91 .ok_or_else(|| CuError::from("Missing mapped slab"))?;
92 let count = (len - done).min(map.length.saturating_sub(local));
93 if count == 0 {
94 return Err(CuError::from("Truncated mapped slab"));
95 }
96 visit(map, local, done, count)?;
97 cursor += count;
98 done += count;
99 }
100 Ok(())
101 }
102}
103impl ByteRegion for MmapRegion {
104 fn len(&self) -> u64 {
105 self.length
106 }
107 fn read(&self, offset: u64, bytes: &mut [u8]) -> CuResult<()> {
108 self.visit(offset, bytes.len(), |map, local, done, count| {
109 unsafe {
111 core::ptr::copy_nonoverlapping(
112 map.pointer.add(local),
113 bytes[done..].as_mut_ptr(),
114 count,
115 );
116 }
117 Ok(())
118 })
119 }
120 fn write(&mut self, offset: u64, bytes: &[u8]) -> CuResult<()> {
121 self.visit(offset, bytes.len(), |map, local, done, count| {
122 let Mapping::Write(_) = &map.mapping else {
123 return Err(CuError::from("Read-only mapped storage"));
124 };
125 unsafe {
129 core::ptr::copy_nonoverlapping(
130 bytes[done..].as_ptr(),
131 map.pointer.add(local),
132 count,
133 );
134 }
135 Ok(())
136 })
137 }
138 fn commit(&mut self) -> CuResult<()> {
139 Ok(())
140 }
141 fn flush(&mut self) -> CuResult<()> {
142 for map in self.maps.iter().flatten() {
143 match &map.mapping {
144 Mapping::Write(mapping) => {
145 unsafe { &*mapping.get() }
147 .flush()
148 .map_err(|e| CuError::new_with_cause("Cannot flush mapped section", e))?;
149 #[cfg(feature = "mmap-fsync")]
150 map.file
151 .sync_all()
152 .map_err(|e| CuError::new_with_cause("Cannot sync backing file", e))?;
153 }
154 Mapping::Read(mapping) => {
155 let _ = mapping.len();
156 }
157 }
158 }
159 Ok(())
160 }
161}
162
163#[doc(hidden)]
164pub struct MmapStorage {
165 path: PathBuf,
166 slab_size: usize,
167 maps: Vec<Arc<MappedFile>>,
168 writable: bool,
169}
170fn slab_path(path: &Path, index: usize) -> io::Result<PathBuf> {
171 let stem = path
172 .file_stem()
173 .and_then(|s| s.to_str())
174 .ok_or_else(|| io::Error::other("Invalid log file name"))?;
175 let extension = path
176 .extension()
177 .and_then(|s| s.to_str())
178 .ok_or_else(|| io::Error::other("Log file requires an extension"))?;
179 Ok(path.with_file_name(format!("{stem}_{index}.{extension}")))
180}
181impl MmapStorage {
182 fn open(path: &Path, writable: bool) -> io::Result<Self> {
183 let first = File::open(slab_path(path, 0)?)?;
184 let slab_size = first.metadata()?.len() as usize;
185 if slab_size < 512 || !slab_size.is_multiple_of(512) {
186 return Err(io::Error::other("Invalid backing slab size"));
187 }
188 let mut maps = Vec::new();
189 for index in 0.. {
190 let file = match OpenOptions::new()
191 .read(true)
192 .write(writable)
193 .open(slab_path(path, index)?)
194 {
195 Ok(file) => file,
196 Err(e) if e.kind() == io::ErrorKind::NotFound => break,
197 Err(e) => return Err(e),
198 };
199 if file.metadata()?.len() != slab_size as u64 {
200 return Err(io::Error::other("Inconsistent backing slab sizes"));
201 }
202 maps.push(Arc::new(MappedFile::new(file, writable)?));
203 }
204 Ok(Self {
205 path: path.to_path_buf(),
206 slab_size,
207 maps,
208 writable,
209 })
210 }
211 fn create(path: &Path, slab_size: usize) -> io::Result<Self> {
212 if slab_size < 1024 || !slab_size.is_multiple_of(512) {
213 return Err(io::Error::other(
214 "Slab size must be a multiple of 512 bytes",
215 ));
216 }
217 let mut storage = Self {
218 path: path.to_path_buf(),
219 slab_size,
220 maps: Vec::new(),
221 writable: true,
222 };
223 storage.grow(slab_size as u64).map_err(io::Error::other)?;
224 for index in 1.. {
226 match std::fs::remove_file(slab_path(path, index)?) {
227 Ok(()) => {}
228 Err(e) if e.kind() == io::ErrorKind::NotFound => break,
229 Err(e) => return Err(e),
230 }
231 }
232 match std::fs::remove_file(path) {
233 Ok(()) => {}
234 Err(e) if e.kind() == io::ErrorKind::NotFound => {}
235 Err(e) => return Err(e),
236 }
237 let target = slab_path(path, 0)?;
238 #[cfg(unix)]
239 std::os::unix::fs::symlink(target.file_name().unwrap(), path)?;
240 #[cfg(not(unix))]
241 std::fs::hard_link(target, path)?;
242 Ok(storage)
243 }
244}
245impl ByteStorage for MmapStorage {
246 type Region = MmapRegion;
247 fn len(&self) -> u64 {
248 self.maps.len() as u64 * self.slab_size as u64
249 }
250 fn grow(&mut self, end: u64) -> CuResult<()> {
251 if end <= self.len() {
252 return Ok(());
253 }
254 if !self.writable {
255 return Err(CuError::from("Read-only storage cannot grow"));
256 }
257 let count = end.div_ceil(self.slab_size as u64);
258 while (self.maps.len() as u64) < count {
259 let path = slab_path(&self.path, self.maps.len())
260 .map_err(|e| CuError::new_with_cause("Invalid slab path", e))?;
261 let file = OpenOptions::new()
262 .read(true)
263 .write(true)
264 .create(true)
265 .truncate(true)
266 .open(path)
267 .map_err(|e| CuError::new_with_cause("Cannot create backing slab", e))?;
268 file.set_len(self.slab_size as u64)
269 .map_err(|e| CuError::new_with_cause("Cannot allocate backing slab", e))?;
270 self.maps
271 .push(Arc::new(MappedFile::new(file, true).map_err(|e| {
272 CuError::new_with_cause("Cannot map backing slab", e)
273 })?));
274 }
275 Ok(())
276 }
277 fn region(&self, offset: u64, length: u64) -> CuResult<MmapRegion> {
278 if offset
279 .checked_add(length)
280 .is_none_or(|end| end > self.len())
281 {
282 return Err(CuError::from("Byte range exceeds backing storage"));
283 }
284 let begin = usize::try_from(offset / self.slab_size as u64)
285 .map_err(|_| CuError::from("Offset exceeds address space"))?;
286 let count =
287 ((offset % self.slab_size as u64 + length).div_ceil(self.slab_size as u64)) as usize;
288 if count > MAX_SPANS {
289 return Err(CuError::from("Section spans more than 64 backing files"));
290 }
291 let mut maps = core::array::from_fn(|_| None);
292 for (slot, mapping) in maps.iter_mut().zip(self.maps[begin..begin + count].iter()) {
293 *slot = Some(mapping.clone());
294 }
295 Ok(MmapRegion {
296 maps,
297 first: (offset % self.slab_size as u64) as usize,
298 slab_size: self.slab_size,
299 length,
300 })
301 }
302}
303pub type MmapSectionStorage = ByteSection<MmapRegion>;
304pub type MmapUnifiedLoggerWrite = ByteLogger<MmapStorage>;
305pub enum MmapUnifiedLogger {
306 Read(MmapUnifiedLoggerRead),
307 Write(MmapUnifiedLoggerWrite),
308}
309
310#[derive(Default)]
311pub struct MmapUnifiedLoggerBuilder {
312 file_base_name: Option<PathBuf>,
313 preallocated_size: Option<usize>,
314 write: bool,
315 create: bool,
316 append: bool,
317 capacity: Option<u64>,
318 policy: CapacityPolicy,
319}
320impl MmapUnifiedLoggerBuilder {
321 pub fn new() -> Self {
322 Self::default()
323 }
324 pub fn file_base_name(mut self, path: &Path) -> Self {
325 self.file_base_name = Some(path.to_path_buf());
326 self
327 }
328 pub fn preallocated_size(mut self, size: usize) -> Self {
329 self.preallocated_size = Some(size);
330 self
331 }
332 pub fn write(mut self, write: bool) -> Self {
333 self.write = write;
334 self
335 }
336 pub fn create(mut self, create: bool) -> Self {
337 self.create = create;
338 self
339 }
340 pub fn append(mut self, append: bool) -> Self {
341 self.append = append;
342 self
343 }
344 pub fn rollover(mut self, size: usize) -> Self {
346 self.capacity = Some(size as u64);
347 self.policy = CapacityPolicy::OverwriteOldest;
348 self
349 }
350 pub fn capacity(mut self, size: u64, policy: CapacityPolicy) -> Self {
351 self.capacity = Some(size);
352 self.policy = policy;
353 self
354 }
355 pub fn build(self) -> io::Result<MmapUnifiedLogger> {
356 let path = self
357 .file_base_name
358 .ok_or_else(|| io::Error::other("Log file path is required"))?;
359 if !(self.write && self.create) {
360 return MmapUnifiedLoggerRead::new(&path).map(MmapUnifiedLogger::Read);
361 }
362 let slab_size = self
363 .preallocated_size
364 .ok_or_else(|| io::Error::other("Preallocated size is required"))?;
365 let slab_size = slab_size
366 .checked_add(511)
367 .ok_or_else(|| io::Error::other("Slab size overflow"))?
368 / 512
369 * 512;
370 let slab_size = slab_size.max(1024);
371 let logger = if self.append {
372 let storage = MmapStorage::open(&path, true)?;
373 if storage.slab_size != slab_size {
374 return Err(io::Error::other("Append slab allocation size mismatch"));
375 }
376 let capacity = self.capacity.unwrap_or(storage.len());
377 ByteLogger::append(storage, self.policy, capacity)
378 } else {
379 ByteLogger::new(
380 MmapStorage::create(&path, slab_size)?,
381 512,
382 self.policy,
383 self.capacity.unwrap_or(slab_size as u64),
384 )
385 }
386 .map_err(io::Error::other)?;
387 Ok(MmapUnifiedLogger::Write(logger))
388 }
389}
390
391#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
393pub struct LogPosition(pub u64);
394pub struct MmapUnifiedLoggerRead {
395 storage: MmapStorage,
396 main_header: MainHeader,
397 cursor: u64,
398 remaining: u64,
399}
400impl MmapUnifiedLoggerRead {
401 pub fn new(path: &Path) -> io::Result<Self> {
402 let storage = MmapStorage::open(path, false)?;
403 let main_header = read_main(&storage).map_err(io::Error::other)?;
404 let cursor = if main_header.metadata_offset != 0 {
405 main_header.metadata_offset
406 } else {
407 main_header.head_section
408 };
409 let remaining = main_header.sections_end / 512 + 2;
410 Ok(Self {
411 storage,
412 main_header,
413 cursor,
414 remaining,
415 })
416 }
417 pub fn raw_main_header(&self) -> &MainHeader {
418 &self.main_header
419 }
420 pub fn position(&self) -> LogPosition {
421 LogPosition(self.cursor)
422 }
423 pub fn seek(&mut self, position: LogPosition) -> CuResult<()> {
424 if position.0 != 0 {
425 self.section_header(position.0)?;
426 }
427 self.cursor = position.0;
428 self.remaining = self.main_header.sections_end / 512 + 2;
429 Ok(())
430 }
431 pub fn application_metadata(&self) -> CuResult<Option<ApplicationMetadata>> {
432 if self.main_header.metadata_offset == 0 {
433 return Ok(None);
434 }
435 let h = self.section_header(self.main_header.metadata_offset)?;
436 if h.entry_type != UnifiedLogType::ApplicationMetadata || h.is_open {
437 return Err(CuError::from("Invalid static application metadata"));
438 }
439 let mut data = vec![0; h.used as usize];
440 self.storage
441 .region(self.main_header.metadata_offset + 512, h.used as u64)?
442 .read(0, &mut data)?;
443 let (metadata, used) = decode_from_slice::<ApplicationMetadata, _>(&data, standard())
444 .map_err(|e| CuError::new_with_cause("Invalid application metadata", e))?;
445 if used != data.len() || metadata.catalog_offset != h.next_section {
446 return Err(CuError::from("Invalid metadata body or catalog offset"));
447 }
448 Ok(Some(metadata))
449 }
450 fn section_header(&self, offset: u64) -> CuResult<SectionHeader> {
451 if !offset.is_multiple_of(512)
452 || offset < self.main_header.page_size as u64
453 || offset >= self.main_header.sections_end
454 {
455 return Err(CuError::from("Invalid section offset"));
456 }
457 let h = read_header(&self.storage, offset)?;
458 let limit = if offset < self.main_header.sections_begin {
459 self.main_header.sections_begin
460 } else {
461 self.main_header.sections_end
462 };
463 if offset
464 .checked_add(h.allocated)
465 .is_none_or(|end| end > limit)
466 {
467 return Err(CuError::from("Section exceeds logical byte bounds"));
468 }
469 if h.next_section != 0
470 && (!h.next_section.is_multiple_of(512)
471 || h.next_section < self.main_header.page_size as u64
472 || h.next_section >= self.main_header.sections_end)
473 {
474 return Err(CuError::from("Invalid section link"));
475 }
476 if offset >= self.main_header.sections_begin {
477 if h.next_section != 0 && h.next_section < self.main_header.sections_begin {
478 return Err(CuError::from("Data links into static metadata"));
479 }
480 if (offset == self.main_header.tail_section) != (h.next_section == 0) {
481 return Err(CuError::from("Invalid tail link"));
482 }
483 }
484 Ok(h)
485 }
486 fn advance(&mut self, h: &SectionHeader) -> CuResult<()> {
487 if self.remaining == 0 {
488 return Err(CuError::from("Cyclic section links"));
489 }
490 self.remaining -= 1;
491 self.cursor = if self.cursor < self.main_header.sections_begin && h.next_section == 0 {
492 self.main_header.head_section
493 } else {
494 h.next_section
495 };
496 Ok(())
497 }
498 pub fn raw_skip_section(&mut self) -> CuResult<SectionHeader> {
499 if self.cursor == 0 {
500 return Ok(SectionHeader {
501 entry_type: UnifiedLogType::LastEntry,
502 is_open: !self.main_header.clean_close,
503 allocated: 512,
504 ..SectionHeader::default()
505 });
506 }
507 let h = self.section_header(self.cursor)?;
508 self.advance(&h)?;
509 Ok(h)
510 }
511 pub fn end_of_log(&mut self) -> CuResult<LogPosition> {
512 if !self.main_header.clean_close {
513 return Err(CuError::from("Log was not cleanly closed"));
514 }
515 while self.cursor != 0 {
516 if self.raw_skip_section()?.is_open {
517 return Err(CuError::from("Log contains an open section"));
518 }
519 }
520 Ok(LogPosition(0))
521 }
522 pub fn scan_section_bytes(&mut self, kind: UnifiedLogType) -> CuResult<u64> {
523 let mut bytes = 0;
524 while self.cursor != 0 {
525 let h = self.raw_skip_section()?;
526 if h.entry_type == kind {
527 bytes += h.used as u64;
528 }
529 }
530 Ok(bytes)
531 }
532}
533impl UnifiedLogRead for MmapUnifiedLoggerRead {
534 fn raw_read_section(&mut self) -> CuResult<(SectionHeader, Vec<u8>)> {
535 if self.cursor == 0 {
536 return Ok((self.raw_skip_section()?, Vec::new()));
537 }
538 let h = self.section_header(self.cursor)?;
539 let mut bytes = vec![0; h.used as usize];
540 self.storage
541 .region(
542 self.cursor + SECTION_HEADER_COMPACT_SIZE as u64,
543 h.used as u64,
544 )?
545 .read(0, &mut bytes)?;
546 self.advance(&h)?;
547 Ok((h, bytes))
548 }
549 fn read_next_section_type(&mut self, kind: UnifiedLogType) -> CuResult<Option<Vec<u8>>> {
550 while self.cursor != 0 {
551 let h = self.section_header(self.cursor)?;
552 if h.entry_type == kind {
553 return self.raw_read_section().map(|(_, bytes)| Some(bytes));
554 }
555 self.advance(&h)?;
556 }
557 Ok(None)
558 }
559}
560pub struct UnifiedLoggerIOReader {
561 logger: MmapUnifiedLoggerRead,
562 log_type: UnifiedLogType,
563 buffer: Vec<u8>,
564 buffer_pos: usize,
565}
566impl UnifiedLoggerIOReader {
567 pub fn new(logger: MmapUnifiedLoggerRead, log_type: UnifiedLogType) -> Self {
568 Self {
569 logger,
570 log_type,
571 buffer: Vec::new(),
572 buffer_pos: 0,
573 }
574 }
575}
576impl Read for UnifiedLoggerIOReader {
577 fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
578 if bytes.is_empty() {
579 return Ok(0);
580 }
581 while self.buffer_pos == self.buffer.len() {
582 match self
583 .logger
584 .read_next_section_type(self.log_type)
585 .map_err(io::Error::other)?
586 {
587 Some(buffer) => {
588 self.buffer = buffer;
589 self.buffer_pos = 0;
590 }
591 None => return Ok(0),
592 }
593 }
594 let count = bytes.len().min(self.buffer.len() - self.buffer_pos);
595 bytes[..count].copy_from_slice(&self.buffer[self.buffer_pos..self.buffer_pos + count]);
596 self.buffer_pos += count;
597 Ok(count)
598 }
599}