about summary refs log tree commit diff
path: root/pkgs/sternenseemann
diff options
context:
space:
mode:
authorsternenseemann <0rpkxez4ksa01gb3typccl0i@systemli.org>2021-02-13 23:30:40 +0100
committersternenseemann <0rpkxez4ksa01gb3typccl0i@systemli.org>2021-02-13 23:42:55 +0100
commit587fcc7b89cda8abbbd1f08722b2b3e1349869c8 (patch)
tree454e8dd7d9ce0f37d311aa9b8ccc08c54dce6f2c /pkgs/sternenseemann
parent059bee06233d1987746f625905d552e219d4bfbc (diff)
pkgs/sternenseemann/temp: add tiny temporary file/dir crate
The crate implements a safe wrapper around mkdtemp(3) and mkstemp(3)
which provide decent temporary file and directory creation. The key
feature we provide is that we wrap the resulting path in a TempFile /
TempDir struct. This allows us to implement the Drop trait such that the
temporary directory / file is automatically deleted when the value goes
out of scope in Rust which saves the programmer from cleaning up
temporary artifacts themselves.

The API is indeed very tiny currently, only implementing creation and
AsRef<Path> which should allow all necessary path / file manipulation.
Diffstat (limited to 'pkgs/sternenseemann')
-rw-r--r--pkgs/sternenseemann/default.nix11
-rw-r--r--pkgs/sternenseemann/rust/default.nix24
-rw-r--r--pkgs/sternenseemann/rust/temp.rs191
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())
+    }
+}