diff options
Diffstat (limited to 'pkgs')
-rw-r--r-- | pkgs/sternenseemann/default.nix | 11 | ||||
-rw-r--r-- | pkgs/sternenseemann/rust/default.nix | 24 | ||||
-rw-r--r-- | pkgs/sternenseemann/rust/temp.rs | 191 |
3 files changed, 226 insertions, 0 deletions
diff --git a/pkgs/sternenseemann/default.nix b/pkgs/sternenseemann/default.nix index 23845195..8ee48ce5 100644 --- a/pkgs/sternenseemann/default.nix +++ b/pkgs/sternenseemann/default.nix @@ -33,6 +33,12 @@ let }; }; + rust = callPackage ./rust { + inherit (profpatsch) + writeRustSimpleLib + ; + }; + in lib.fix (self: { @@ -75,6 +81,11 @@ lib.fix (self: { ''; }); + inherit (rust) + temp + temp-tests + ; + # don't bother hydra with trivial text substitution scripts = dontRecurseIntoAttrs (callPackage ./scripts { inherit (writers) writeBashBin; diff --git a/pkgs/sternenseemann/rust/default.nix b/pkgs/sternenseemann/rust/default.nix new file mode 100644 index 00000000..787cf2e1 --- /dev/null +++ b/pkgs/sternenseemann/rust/default.nix @@ -0,0 +1,24 @@ +{ writeRustSimpleLib +, buildRustCrate +}: + +let + + temp-common = tests: writeRustSimpleLib "temp" { + buildTests = tests; + release = false; + verbose = true; + meta = { + description = "Tiny temp dir/file crate for rust"; + }; + } ./temp.rs; + + temp = temp-common false; + temp-tests = temp-common true; + +in { + inherit + temp + temp-tests + ; + } diff --git a/pkgs/sternenseemann/rust/temp.rs b/pkgs/sternenseemann/rust/temp.rs new file mode 100644 index 00000000..94424b56 --- /dev/null +++ b/pkgs/sternenseemann/rust/temp.rs @@ -0,0 +1,191 @@ +//! Tiny temp dir/file crate. +//! +//! This crate implements a tiny rust wrapper around libc's +//! `mkdtemp(3)` and `mkstemp(3)` which has the following +//! notable features: +//! +//! * Temporary files and directories are distinguished on +//! the type level: [`TempDir`] and [`TempFile`] are different +//! types to ensure the right kind of temporary artifact +//! is passed to a function and dealt with accordingly. +//! * The killer feature: Temporary artifacts are automatically +//! deleted as soon as the associated value goes out of +//! scope using the [`Drop`] trait, meaning a) it is impossible +//! to forget to delete a temporary artifact and b) temporary +//! artifact are cleaned up as soon as possible. +//! +//! The intended use of this crate is to create the desired +//! artifact via [`TempDir::new()`] or [`TempFile::new()`]. +//! Interfacing with the rest of rust's `std` is possible by using +//! the [`AsRef`] trait to get a [`Path`] reference for a given +//! temporary artifact. +//! +//! Note that you need to take care not to let the [`TempDir`] or +//! [`TempFile`] get dropped early while you are still using a copy +//! of the represented path which can easily happen when using +//! `temp_dir.as_ref().join("foo.txt")` to work with a temporary +//! directory. +use std::ffi::OsStr; +use std::io::{Error, ErrorKind}; +use std::os::unix::ffi::{OsStrExt, OsStringExt}; +use std::path::{Path, PathBuf}; + +// libc interaction + +#[link(name = "c")] +extern { + fn mkdtemp(template: *mut u8) -> *mut u8; + fn mkstemp(template: *mut u8) -> *mut u8; +} + +fn template(prefix: &str) -> std::io::Result<Vec<u8>> { + if prefix.contains('/') { + return Err(Error::new(ErrorKind::Other, "prefix may not contain any slashes")); + } + + // TODO(sterni): systemd support + let mut template = std::env::temp_dir(); + template.push(prefix); + + // mkdtemp and mkstemp require the template to end in 6 or more 'X's + template.set_extension("XXXXXX"); + + Ok(template.into_os_string().into_vec()) +} + +// internal implementation for files and directories + +enum TempKind { + File, + Dir, +} + +struct Temp { + path: Vec<u8>, + kind: TempKind, +} + +impl AsRef<Path> for Temp { + fn as_ref(&self) -> &Path { + OsStr::from_bytes(&self.path[..]).as_ref() + } +} + +impl Drop for Temp { + fn drop(&mut self) { + match self.kind { + TempKind::File => std::fs::remove_file(self.as_ref()), + TempKind::Dir => std::fs::remove_dir_all(self.as_ref()), + }; + } +} + +fn temp(kind: TempKind, prefix: &str) -> std::io::Result<Temp> { + let mut tpl = template(prefix)?; + tpl.push(0); + let tpl_ptr: *mut u8 = tpl.as_mut_ptr(); + + + let res: *mut u8 = match kind { + TempKind::Dir => unsafe { mkdtemp(tpl_ptr) }, + TempKind::File => unsafe { mkstemp(tpl_ptr) }, + }; + + if res.is_null() { + Err(Error::last_os_error()) + } else { + // get rid of NUL byte + tpl.pop(); + + Ok(Temp { + path: tpl, + kind: kind, + }) + } +} + +// public, type safe API which wraps the internal one + +pub struct TempDir(Temp); + +impl TempDir { + /// Create a temporary directory in the directory returned + /// by [`std::env::temp_dir()`]. The temporary directory's + /// name will have the following form: `<prefix>.XXXXXX`. + /// The six `X`s are replaced by random characters. + /// + /// See `mkdtemp(3)` for details of the underlying + /// implementation and possible errors. + pub fn new(prefix: &str) -> std::io::Result<TempDir> { + temp(TempKind::Dir, prefix).map(|t| TempDir(t)) + } +} + +impl AsRef<Path> for TempDir { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} + +pub struct TempFile(Temp); + +impl TempFile { + /// Create a temporary file in the directory returned + /// by [`std::env::temp_dir()`]. The temporary file's + /// name will have the following form: `<prefix>.XXXXXX`. + /// The six `X`s are replaced by random characters. + /// + /// See `mkstemp(3)` for details of the underlying + /// implementation and possible errors. + pub fn new(prefix: &str) -> std::io::Result<TempFile> { + temp(TempKind::File, prefix).map(|t| TempFile(t)) + } +} + +impl AsRef<Path> for TempFile { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Check that the temporary artifact is + // + // * created as expected + // * deleted after it goes out of scope + // + // for both temp files and temp dirs. + + #[test] + fn tempdir_exists() { + let temp = TempDir::new("temp-dir-test"); + assert!(temp.map(|p| p.as_ref().exists()).unwrap()) + } + + #[test] + fn tempdir_cleaned() { + let temp_copy = { + let temp = TempDir::new("temp-dir-test").unwrap(); + temp.as_ref().to_path_buf() + }; + assert!(!temp_copy.exists()) + } + + #[test] + fn tempfile_exists() { + let temp = TempFile::new("temp-file-test"); + assert!(temp.map(|p| p.as_ref().exists()).unwrap()) + } + + #[test] + fn tempfile_cleaned() { + let temp_copy = { + let temp = TempFile::new("temp-file-test").unwrap(); + temp.as_ref().to_path_buf() + }; + assert!(!temp_copy.exists()) + } +} |