Skip to main content

firewood_storage/linear/
mod.rs

1// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
2// See the file LICENSE.md for licensing terms.
3
4//! This module provides the [`ReadableStorage`] and [`WritableStorage`] traits,
5//! which define the interface for reading and writing to a linear store.
6
7#![expect(
8    clippy::missing_errors_doc,
9    reason = "Found 4 occurrences after enabling the lint."
10)]
11
12use std::fmt::Debug;
13use std::io::{Cursor, Read};
14use std::ops::Deref;
15use std::path::PathBuf;
16
17use crate::{CacheReadStrategy, LinearAddress, MaybePersistedNode, SharedNode};
18pub(super) mod filebacked;
19#[cfg(feature = "io-uring")]
20mod io_uring;
21pub mod memory;
22
23/// An error that occurs when reading or writing to a [`ReadableStorage`] or [`WritableStorage`]
24///
25/// This error is used to wrap errors that occur when reading or writing to a file.
26/// It contains the filename, offset, and context of the error.
27#[derive(Debug)]
28pub struct FileIoError {
29    inner: std::io::Error,
30    filename: Option<PathBuf>,
31    offset: u64,
32    context: Option<String>,
33}
34
35impl From<std::convert::Infallible> for FileIoError {
36    fn from(error: std::convert::Infallible) -> Self {
37        match error {}
38    }
39}
40
41impl FileIoError {
42    /// Create a new [`FileIoError`] from a generic error
43    ///
44    /// Only use this constructor if you do not have any file or line information.
45    ///
46    /// # Arguments
47    ///
48    /// * `error` - The error to wrap
49    pub fn from_generic_no_file<T: std::error::Error>(error: T, context: &str) -> Self {
50        Self {
51            inner: std::io::Error::other(error.to_string()),
52            filename: None,
53            offset: 0,
54            context: Some(context.into()),
55        }
56    }
57
58    /// Create a new [`FileIoError`]
59    ///
60    /// # Arguments
61    ///
62    /// * `inner` - The inner error
63    /// * `filename` - The filename of the file that caused the error
64    /// * `offset` - The offset of the file that caused the error
65    /// * `context` - The context of this error
66    #[must_use]
67    pub const fn new(
68        inner: std::io::Error,
69        filename: Option<PathBuf>,
70        offset: u64,
71        context: Option<String>,
72    ) -> Self {
73        Self {
74            inner,
75            filename,
76            offset,
77            context,
78        }
79    }
80
81    #[cfg(test)]
82    pub(crate) fn context(&self) -> Option<&str> {
83        self.context.as_deref()
84    }
85}
86
87impl std::error::Error for FileIoError {
88    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
89        Some(&self.inner)
90    }
91}
92
93impl std::fmt::Display for FileIoError {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        write!(
96            f,
97            "{inner} at offset {offset} of file '{filename}' {context}",
98            inner = self.inner,
99            offset = self.offset,
100            filename = self
101                .filename
102                .as_ref()
103                .unwrap_or(&PathBuf::from("[unknown]"))
104                .display(),
105            context = self.context.as_ref().unwrap_or(&String::new())
106        )
107    }
108}
109
110impl Deref for FileIoError {
111    type Target = std::io::Error;
112
113    fn deref(&self) -> &Self::Target {
114        &self.inner
115    }
116}
117
118#[derive(Debug)]
119pub enum ReadableNodeMode {
120    Open,
121    Read,
122    ReconRead,
123    Write,
124}
125
126impl ReadableNodeMode {
127    pub const fn as_str(&self) -> &'static str {
128        match self {
129            ReadableNodeMode::Open => "open",
130            ReadableNodeMode::Read => "read",
131            ReadableNodeMode::ReconRead => "recon-read",
132            ReadableNodeMode::Write => "write",
133        }
134    }
135}
136
137/// Trait for readable storage.
138pub trait ReadableStorage: Debug + Sync + Send {
139    /// The node hash algorithm used by this storage.
140    fn node_hash_algorithm(&self) -> crate::NodeHashAlgorithm;
141
142    /// Stream data from the specified address.
143    ///
144    /// # Arguments
145    ///
146    /// * `addr` - The address from which to stream the data.
147    ///
148    /// # Returns
149    ///
150    /// A `Result` containing a boxed `Read` trait object, or an `Error` if the operation fails.
151    fn stream_from(&self, addr: u64) -> Result<impl OffsetReader, FileIoError>;
152
153    /// Return the size of the underlying storage, in bytes
154    fn size(&self) -> Result<u64, FileIoError>;
155
156    /// Read a node from the cache (if any)
157    fn read_cached_node(
158        &self,
159        _addr: LinearAddress,
160        _mode: ReadableNodeMode,
161    ) -> Option<SharedNode> {
162        None
163    }
164
165    /// Fetch the next pointer from the freelist cache
166    fn free_list_cache(&self, _addr: LinearAddress) -> Option<Option<LinearAddress>> {
167        None
168    }
169
170    /// Return the cache read strategy for this readable storage
171    fn cache_read_strategy(&self) -> &CacheReadStrategy {
172        &CacheReadStrategy::WritesOnly
173    }
174
175    /// Cache a node for future reads
176    fn cache_node(&self, _addr: LinearAddress, _node: SharedNode) {}
177
178    /// Return the filename of the underlying storage
179    fn filename(&self) -> Option<PathBuf> {
180        None
181    }
182
183    /// Convert an `io::Error` into a `FileIoError`
184    fn file_io_error(
185        &self,
186        error: std::io::Error,
187        offset: u64,
188        context: Option<String>,
189    ) -> FileIoError {
190        FileIoError {
191            inner: error,
192            filename: self.filename(),
193            offset,
194            context,
195        }
196    }
197}
198
199/// Trait for writable storage.
200pub trait WritableStorage: ReadableStorage {
201    /// Writes the given object at the specified offset.
202    ///
203    /// # Arguments
204    ///
205    /// * `offset` - The offset at which to write the object.
206    /// * `object` - The object to write.
207    ///
208    /// # Returns
209    ///
210    /// The number of bytes written, or an error if the write operation fails.
211    fn write(&self, offset: u64, object: &[u8]) -> Result<usize, FileIoError>;
212
213    /// Write a batch of objects to the storage.
214    ///
215    /// Implementations may provide a more efficient way to write multiple
216    /// objects at once.
217    ///
218    /// The iterator is expected to be cheap to clone without copying the data.
219    fn write_batch<'a, I: IntoIterator<Item = (u64, &'a [u8])> + Clone>(
220        &self,
221        writes: I,
222    ) -> Result<usize, FileIoError> {
223        writes
224            .into_iter()
225            .try_fold(0_usize, |acc, (offset, object)| {
226                let bytes_written = self.write(offset, object)?;
227                acc.checked_add(bytes_written).ok_or_else(|| {
228                    self.file_io_error(
229                        std::io::Error::other("Overflow when summing bytes written"),
230                        offset,
231                        Some("write_batch".into()),
232                    )
233                })
234            })
235    }
236
237    /// Write all nodes to the cache (if any) and persist them
238    fn write_cached_nodes(
239        &self,
240        _nodes: impl IntoIterator<Item = MaybePersistedNode>,
241    ) -> Result<(), FileIoError> {
242        Ok(())
243    }
244
245    /// Invalidate all nodes that are part of a specific revision, as these will never be referenced again
246    fn invalidate_cached_nodes<'a>(
247        &self,
248        _addresses: impl Iterator<Item = &'a MaybePersistedNode>,
249    ) {
250    }
251
252    /// Add a new entry to the freelist cache
253    fn add_to_free_list_cache(&self, _addr: LinearAddress, _next: Option<LinearAddress>) {}
254}
255
256pub trait OffsetReader: Read {
257    fn offset(&self) -> u64;
258}
259
260impl<T> OffsetReader for Cursor<T>
261where
262    Cursor<T>: Read,
263{
264    fn offset(&self) -> u64 {
265        self.position()
266    }
267}