diff --git a/Cargo.lock b/Cargo.lock index db28b8c..d4f9b0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "bytecheck" version = "0.8.0-alpha.9" @@ -29,12 +35,34 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + [[package]] name = "hashbrown" version = "0.14.5" @@ -51,6 +79,27 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + [[package]] name = "munge" version = "0.4.0" @@ -71,6 +120,12 @@ dependencies = [ "syn", ] +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + [[package]] name = "proc-macro2" version = "1.0.86" @@ -158,7 +213,22 @@ dependencies = [ name = "rkyv_util" version = "0.1.0" dependencies = [ + "memmap2", "rkyv", + "tempfile", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", ] [[package]] @@ -178,6 +248,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -204,3 +287,85 @@ name = "uuid" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index e111b15..664b2f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,12 +6,17 @@ edition = "2021" license = "MIT" [dependencies] +memmap2 = { version = "0.9.4", optional = true } rkyv = { version = "0.8.0-alpha.2", default-features = false, features = ["bytecheck"] } +[dev-dependencies] +tempfile = "3.12.0" + [features] default = ["std"] alloc = ["rkyv/alloc"] std = ["alloc", "rkyv/std"] +memmap2 = ["dep:memmap2"] [patch.crates-io] bytecheck = { git = "https://github.com/rkyv/bytecheck" } diff --git a/src/lib.rs b/src/lib.rs index c331b2a..b2c6f09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,3 +24,9 @@ "#)] pub mod owned; + +#[cfg(feature = "memmap2")] +pub mod mmap; + +#[cfg(feature = "std")] +pub mod std; diff --git a/src/mmap.rs b/src/mmap.rs new file mode 100644 index 0000000..35e8d3e --- /dev/null +++ b/src/mmap.rs @@ -0,0 +1,183 @@ +//! Bindings for working with memory mapped objects in a cleaner way. + +use std::ops::{Deref, DerefMut}; + +use memmap2::{Mmap, MmapMut}; +use rkyv::{ + api::high::HighValidator, bytecheck::CheckBytes, rancor::Source, Archive, + Portable, +}; + +use crate::owned::{OwnedArchive, StableBytes, StableBytesMut}; + +impl OwnedArchive { + /// Creates an OwnedArchive from a memory mapped object. + /// + /// You cannot use the `new` method to construct an OwnedArchive + /// because the [StableBytes] and [StableBytesMut] interfaces only + /// apply to a newtype wrapper. This is to make sure people do not + /// casually create these types without taking into consideration the + /// relevant safety invariants. + /// + /// # Safety + /// This should hold up the same invariants as the [memmap2] crate. + /// More specifically, if the underlying file is modified the buffer could + /// change, therefore compromising the stability. One should therefore + /// guarantee that the underlying file holds up the safety invariants + /// set forth by [StableBytes]. + pub unsafe fn from_mmap(container: Mmap) -> Result + where + T: Archive, + T::Archived: Portable + for<'a> CheckBytes>, + E: Source, + { + Self::new(ContractMmap(container)) + } +} + +impl OwnedArchive { + /// Creates an OwnedArchive from a mutable memory mapped object. + /// + /// You cannot use the `new` method to construct an OwnedArchive + /// because the [StableBytes] and [StableBytesMut] interfaces only + /// apply to a newtype wrapper. This is to make sure people do not + /// casually create these types without taking into consideration the + /// relevant safety invariants. + /// + /// # Safety + /// This should hold up the same invariants as the [memmap2] crate. + /// More specifically, if the underlying file is modified the buffer could + /// change, therefore compromising the stability. One should therefore + /// guarantee that the underlying file holds up the safety invariants + /// set forth by [StableBytes]. + pub unsafe fn from_mmap_mut(container: MmapMut) -> Result + where + T: Archive, + T::Archived: Portable + for<'a> CheckBytes>, + E: Source, + { + Self::new(ContractMmapMut(container)) + } +} + +/// A newtype wrapper around the [Mmap] type. This prevents the creation of +/// [OwnedArchive] through the `new` method and therefore causes the programmer +/// to think about the relevant safety invariants that must be held up. +struct ContractMmap(Mmap); + +impl Deref for ContractMmap { + type Target = Mmap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// A newtype wrapper around the [MmapMut] type. This prevents the creation of +/// [OwnedArchive] through the `new` method and therefore causes the programmer +/// to think about the relevant safety invariants that must be held up. +struct ContractMmapMut(MmapMut); + +impl Deref for ContractMmapMut { + type Target = MmapMut; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ContractMmapMut { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +unsafe impl StableBytes for ContractMmap { + fn bytes(&self) -> &[u8] { + self.as_ref() + } +} + +unsafe impl StableBytes for ContractMmapMut { + fn bytes(&self) -> &[u8] { + self.as_ref() + } +} + +unsafe impl StableBytesMut for ContractMmapMut { + fn bytes_mut(&mut self) -> &mut [u8] { + self.as_mut() + } +} + +#[cfg(test)] +mod tests { + + use ::std::io::{Seek, SeekFrom, Write}; + use memmap2::{Mmap, MmapMut}; + use rkyv::{rancor, Archive, Deserialize, Serialize}; + + use super::OwnedArchive; + + #[derive(Archive, Clone, PartialEq, Deserialize, Serialize, Debug)] + #[rkyv(check_bytes, compare(PartialEq), derive(Debug))] + pub struct ArchiveStub { + hello: u8, + world: u64, + } + + #[test] + fn test_owned_archive_vec_mmap() { + let stub = ArchiveStub { hello: 4, world: 5 }; + + let bytes = rkyv::to_bytes::(&stub).unwrap(); + + let mut tfile = tempfile::tempfile().unwrap(); + + tfile.write_all(&bytes).unwrap(); + tfile.seek(SeekFrom::Start(0)).unwrap(); + // write(tfile.path(), contents) + + let mmap = unsafe { Mmap::map(&tfile) }.unwrap(); + + let owned: OwnedArchive = + unsafe { OwnedArchive::from_mmap::(mmap) }.unwrap(); + + // Finally check to see that both are equal. + assert_eq!(owned.hello, 4); + assert_eq!(owned.world, 5); + + // Finally check to see that both are equal. + assert_eq!(stub, *owned); + } + + #[test] + fn test_owned_archive_vec_mmap_mut() { + let stub = ArchiveStub { hello: 4, world: 5 }; + + let bytes = rkyv::to_bytes::(&stub).unwrap(); + + let mut tfile = tempfile::tempfile().unwrap(); + + tfile.write_all(&bytes).unwrap(); + tfile.seek(SeekFrom::Start(0)).unwrap(); + // write(tfile.path(), contents) + + let mmap = unsafe { MmapMut::map_mut(&tfile) }.unwrap(); + + let mut owned: OwnedArchive = + unsafe { OwnedArchive::from_mmap_mut::(mmap) } + .unwrap(); + + // Finally check to see that both are equal. + assert_eq!(owned.hello, 4); + assert_eq!(owned.world, 5); + + // Finally check to see that both are equal. + assert_eq!(stub, *owned); + + // Modify it + owned.get_mut().hello = 3; + assert_eq!(owned.hello, 3); + } +} diff --git a/src/owned.rs b/src/owned.rs index 5ada6c9..75008b8 100644 --- a/src/owned.rs +++ b/src/owned.rs @@ -4,12 +4,12 @@ //! we want to pass Archives around in channels but we do not want //! to deal with complicated lifetimes. -use core::fmt::Debug; -use std::{marker::PhantomData, ops::Deref, pin::Pin, rc::Rc, sync::Arc}; +use core::{fmt::Debug, marker::PhantomData, ops::Deref, pin::Pin}; +// use memmap2::{Mmap, MmapMut}; use rkyv::{ - api::high::HighValidator, bytecheck::CheckBytes, util::AlignedVec, Archive, - Portable, + api::high::HighValidator, bytecheck::CheckBytes, rancor::Source, + util::AlignedVec, Archive, Portable, }; /// An owned archive type. @@ -54,7 +54,7 @@ impl OwnedArchive { where T: Archive, T::Archived: Portable + for<'a> CheckBytes>, - E: rkyv::rancor::Source, + E: Source, C: StableBytes, { // Here we check if the bytes are good. If so, we will @@ -287,6 +287,12 @@ pub unsafe trait StableBytesMut: StableBytes { // Implementations of `StableBytes` for popular types // ============== +unsafe impl StableBytes for &[u8] { + fn bytes(&self) -> &[u8] { + self + } +} + unsafe impl StableBytesMut for AlignedVec { fn bytes_mut(&mut self) -> &mut [u8] { self.as_mut() @@ -311,18 +317,6 @@ unsafe impl StableBytes for Vec { } } -unsafe impl StableBytes for Arc<[u8]> { - fn bytes(&self) -> &[u8] { - self.as_ref() - } -} - -unsafe impl StableBytes for Rc<[u8]> { - fn bytes(&self) -> &[u8] { - self.as_ref() - } -} - unsafe impl StableBytesMut for Box<[u8]> { fn bytes_mut(&mut self) -> &mut [u8] { self.as_mut() diff --git a/src/std/mod.rs b/src/std/mod.rs new file mode 100644 index 0000000..09790ee --- /dev/null +++ b/src/std/mod.rs @@ -0,0 +1,18 @@ +//! Provides implementations for certain objects in the standard library. +//! This facilitates the use of no-std. + +use std::{rc::Rc, sync::Arc}; + +use crate::owned::StableBytes; + +unsafe impl StableBytes for Arc<[u8]> { + fn bytes(&self) -> &[u8] { + self.as_ref() + } +} + +unsafe impl StableBytes for Rc<[u8]> { + fn bytes(&self) -> &[u8] { + self.as_ref() + } +}