diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 01ab9e3..c27c9b1 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -1,5 +1,7 @@ #[cfg(feature = "async-std")] use async_std::fs as afs; +#[cfg(feature = "link_to")] +use std::path::PathBuf; #[cfg(all(test, feature = "tokio"))] use tokio::fs as afs; @@ -22,7 +24,6 @@ where use std::fs::{self, File}; use std::io::prelude::*; -use std::path::PathBuf; use criterion::{black_box, criterion_group, criterion_main, Criterion}; diff --git a/src/content/read.rs b/src/content/read.rs index c1a1c9a..704b051 100644 --- a/src/content/read.rs +++ b/src/content/read.rs @@ -199,6 +199,58 @@ pub async fn copy_async<'a>(cache: &'a Path, sri: &'a Integrity, to: &'a Path) - Ok(size as u64) } +pub fn hard_link_unchecked(cache: &Path, sri: &Integrity, to: &Path) -> Result<()> { + let cpath = path::content_path(cache, sri); + std::fs::hard_link(cpath, to).with_context(|| { + format!( + "Failed to link cache contents from {} to {}", + path::content_path(cache, sri).display(), + to.display() + ) + })?; + Ok(()) +} + +pub fn hard_link(cache: &Path, sri: &Integrity, to: &Path) -> Result<()> { + hard_link_unchecked(cache, sri, to)?; + let mut reader = open(cache, sri.clone())?; + let mut buf = [0u8; 1024 * 8]; + loop { + let read = reader.read(&mut buf).with_context(|| { + format!( + "Failed to read cache contents while verifying integrity for {}", + path::content_path(cache, sri).display() + ) + })?; + if read == 0 { + break; + } + } + reader.check()?; + Ok(()) +} + +pub async fn hard_link_async(cache: &Path, sri: &Integrity, to: &Path) -> Result<()> { + hard_link_unchecked(cache, sri, to)?; + let mut reader = open_async(cache, sri.clone()).await?; + let mut buf = [0u8; 1024 * 8]; + loop { + let read = AsyncReadExt::read(&mut reader, &mut buf) + .await + .with_context(|| { + format!( + "Failed to read cache contents while verifying integrity for {}", + path::content_path(cache, sri).display() + ) + })?; + if read == 0 { + break; + } + } + reader.check()?; + Ok(()) +} + pub fn has_content(cache: &Path, sri: &Integrity) -> Option { if path::content_path(cache, sri).exists() { Some(sri.clone()) diff --git a/src/get.rs b/src/get.rs index d9b70f6..07f84f2 100644 --- a/src/get.rs +++ b/src/get.rs @@ -300,6 +300,23 @@ where read::copy_unchecked_async(cache.as_ref(), sri, to.as_ref()).await } +/// Hard links a cache entry by key to a specified location. +pub async fn hard_link(cache: P, key: K, to: Q) -> Result<()> +where + P: AsRef, + K: AsRef, + Q: AsRef, +{ + async fn inner(cache: &Path, key: &str, to: &Path) -> Result<()> { + if let Some(entry) = index::find(cache, key)? { + read::hard_link_async(cache, &entry.integrity, to).await + } else { + Err(Error::EntryNotFound(cache.to_path_buf(), key.into())) + } + } + inner(cache.as_ref(), key.as_ref(), to.as_ref()).await +} + /// Gets the metadata entry for a certain key. /// /// Note that the existence of a metadata entry is not a guarantee that the @@ -573,6 +590,55 @@ where read::copy_unchecked(cache.as_ref(), sri, to.as_ref()) } +/// Hard links a cache entry by key to a specified location. The cache entry +/// contents will not be checked, and all the usual caveats of hard links +/// apply: The potentially-shared cache might be corrupted if the hard link is +/// modified. +pub fn hard_link_unchecked_sync(cache: P, key: K, to: Q) -> Result<()> +where + P: AsRef, + K: AsRef, + Q: AsRef, +{ + fn inner(cache: &Path, key: &str, to: &Path) -> Result<()> { + if let Some(entry) = index::find(cache, key)? { + hard_link_hash_unchecked_sync(cache, &entry.integrity, to) + } else { + Err(Error::EntryNotFound(cache.to_path_buf(), key.into())) + } + } + inner(cache.as_ref(), key.as_ref(), to.as_ref()) +} + +/// Hard links a cache entry by key to a specified location. +pub fn hard_link_sync(cache: P, key: K, to: Q) -> Result<()> +where + P: AsRef, + K: AsRef, + Q: AsRef, +{ + fn inner(cache: &Path, key: &str, to: &Path) -> Result<()> { + if let Some(entry) = index::find(cache, key)? { + read::hard_link(cache, &entry.integrity, to) + } else { + Err(Error::EntryNotFound(cache.to_path_buf(), key.into())) + } + } + inner(cache.as_ref(), key.as_ref(), to.as_ref()) +} + +/// Hard links a cache entry by integrity address to a specified location. The +/// cache entry contents will not be checked, and all the usual caveats of +/// hard links apply: The potentially-shared cache might be corrupted if the +/// hard link is modified. +pub fn hard_link_hash_unchecked_sync(cache: P, sri: &Integrity, to: Q) -> Result<()> +where + P: AsRef, + Q: AsRef, +{ + read::hard_link_unchecked(cache.as_ref(), sri, to.as_ref()) +} + /// Gets metadata for a certain key. /// /// Note that the existence of a metadata entry is not a guarantee that the