//! Functions for writing to cache. use std::io::prelude::*; use std::path::{Path, PathBuf}; use nix::unistd::{Gid, Uid}; use serde_json::Value; use ssri::{Algorithm, Integrity}; use crate::content::write; use crate::errors::Error; use crate::index; /// Writes `data` to the `cache`, indexing it under `key`. pub fn data(cache: P, key: K, data: D) -> Result where P: AsRef, D: AsRef<[u8]>, K: AsRef, { let mut writer = PutOpts::new() .algorithm(Algorithm::Sha256) .open(cache.as_ref(), key.as_ref())?; writer.write_all(data.as_ref())?; writer.commit() } /// Options and flags for opening a new cache file to write data into. #[derive(Clone, Default)] pub struct PutOpts { pub(crate) algorithm: Option, pub(crate) sri: Option, pub(crate) size: Option, pub(crate) time: Option, pub(crate) metadata: Option, pub(crate) uid: Option, pub(crate) gid: Option, } impl PutOpts { /// Creates a blank set of cache writing options. pub fn new() -> PutOpts { Default::default() } /// Opens the file handle for writing, returning a Put instance. pub fn open(self, cache: P, key: K) -> Result where P: AsRef, K: AsRef, { Ok(Put { cache: cache.as_ref().to_path_buf(), key: String::from(key.as_ref()), written: 0, writer: write::Writer::new( cache.as_ref(), *self.algorithm.as_ref().unwrap_or(&Algorithm::Sha256), )?, opts: self, }) } /// Configures the algorithm to write data under. pub fn algorithm(mut self, algo: Algorithm) -> Self { self.algorithm = Some(algo); self } /// Sets the expected size of the data to write. If there's a date size /// mismatch, `put.commit()` will return an error. pub fn size(mut self, size: usize) -> Self { self.size = Some(size); self } /// Sets arbitrary additional metadata to associate with the index entry. pub fn metadata(mut self, metadata: Value) -> Self { self.metadata = Some(metadata); self } /// Sets the specific time in unix milliseconds to associate with this /// entry. This is usually automatically set to the write time, but can be /// useful to change for tests and such. pub fn time(mut self, time: u128) -> Self { self.time = Some(time); self } /// Sets the expected integrity hash of the written data. If there's a /// mismatch between this Integrity and the one calculated by the write, /// `put.commit()` will error. pub fn integrity(mut self, sri: Integrity) -> Self { self.sri = Some(sri); self } /// Configures the uid and gid to write data as. Useful when dropping /// privileges while in `sudo` mode. pub fn chown(mut self, uid: Option, gid: Option) -> Self { self.uid = uid; self.gid = gid; self } } /// A reference to an open file writing to the cache. pub struct Put { cache: PathBuf, key: String, written: usize, pub(crate) writer: write::Writer, opts: PutOpts, } impl Write for Put { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.writer.write(buf) } fn flush(&mut self) -> std::io::Result<()> { self.writer.flush() } } impl Put { /// Closes the Put handle and writes content and index entries. Also /// verifies data against `size` and `integrity` options, if provided. /// Must be called manually in order to complete the writing process, /// otherwise everything will be thrown out. pub fn commit(self) -> Result { let writer_sri = self.writer.close()?; if let Some(sri) = &self.opts.sri { // TODO - ssri should have a .matches method let algo = sri.pick_algorithm(); let matched = sri .hashes .iter() .take_while(|h| h.algorithm == algo) .find(|&h| *h == writer_sri.hashes[0]); if matched.is_none() { return Err(Error::IntegrityError); } } if let Some(size) = self.opts.size { if size != self.written { return Err(Error::SizeError); } } index::insert(&self.cache, &self.key, self.opts)?; Ok(writer_sri) } }