mkdtemp 0.2.0

A thin wrapper around libc's mkdtemp, UNIX only
Documentation
#![cfg(unix)]
//! A thin wrapper around libc's mkdtemp.
//!
//! For most use case you want to use [mkdtemp_in_temp_dir].

use std::{
    env::temp_dir,
    ffi::{CString, OsString},
    io,
    os::unix::prelude::{OsStrExt, OsStringExt},
    path::{Path, PathBuf},
};

/// Thin wrapper around libc's mkdtemp
///
/// The template is used to form the name of the directory. It must end with Xs that will be
/// replaced by random characters. On Linux, there must be six X.
///
/// # Examples
///
/// ```ignore
/// use std::ffi::CString;
/// use mkdtemp::mkdtemp;
///
///let folder_path = mkdtemp(CString::new("/tmp/foobar.XXXXXX").unwrap())?;
/// ```
///
/// # Errors
///
/// This function will return an error if the underlying libc's mkdtemp fails. The most likely
/// cause is that the template doesn't end with enough X. The error contains the underlying os error
/// and the (maybe modified) template string.
pub fn mkdtemp(template: CString) -> Result<CString, (io::Error, CString)> {
    let template_raw = template.into_raw();
    unsafe {
        let result = libc::mkdtemp(template_raw);
        let template = CString::from_raw(template_raw);
        if result.is_null() {
            Err((io::Error::last_os_error(), template))
        } else {
            Ok(template)
        }
    }
}

/// Utility function to create a directory in the system temporary directory (usually `/tmp`, can
/// be overidden with the `TMPDIR` environment variable).
///
/// This function will prefix the provided prefix with the temporary directory and add six X at the
/// end to form the template.
///
/// # Examples
///
/// ```ignore
/// use mkdtemp::mkdtemp_in_temp_dir;
///
/// let folder_path = mkdtemp_in_temp_dir("foobar.")?;
/// ```
///
/// # Errors
///
/// This function will return an error in two cases:
/// * the prefix contains a nul byte
/// * the underlying [`mkdtemp`] returns an error
pub fn mkdtemp_in_temp_dir<P: AsRef<Path>>(prefix: P) -> io::Result<PathBuf> {
    let mut template = temp_dir();
    template.push(prefix);
    let mut template = template.into_os_string();
    template.push("XXXXXX");
    let template = CString::new(template.as_bytes())?;
    mkdtemp(template)
        .map_err(|(io_error, _)| io_error)
        .map(|modified_template| OsString::from_vec(modified_template.into()).into())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn mkdtemp_fail_if_template_doesnt_end_with_x() {
        let template = CString::new("/tmp/foo").unwrap();
        let (io_error, template) = mkdtemp(template).unwrap_err();
        assert_eq!(template, CString::new("/tmp/foo").unwrap());
        assert_eq!(io_error.kind(), io::ErrorKind::InvalidInput)
    }

    #[test]
    fn mkdtemp_in_temp_dir_fail_if_path_contains_a_nul_byte() {
        assert!(mkdtemp_in_temp_dir("foo\0bar").is_err())
    }
}