[pve-devel] [RFC proxmox 2/7] sys: add make_tmp_dir

Wolfgang Bumiller w.bumiller at proxmox.com
Tue Aug 22 10:39:07 CEST 2023


On Mon, Aug 21, 2023 at 03:44:39PM +0200, Lukas Wagner wrote:
> Under the hood, this function calls `mkdtemp` from libc. Unfortunatly
> the nix crate did not provide bindings for this function, so we have
> to call into libc directly.
> 
> Signed-off-by: Lukas Wagner <l.wagner at proxmox.com>
> ---
>  proxmox-sys/src/fs/dir.rs | 73 +++++++++++++++++++++++++++++++++++++--
>  1 file changed, 71 insertions(+), 2 deletions(-)
> 
> diff --git a/proxmox-sys/src/fs/dir.rs b/proxmox-sys/src/fs/dir.rs
> index 6aee316..72bf1ad 100644
> --- a/proxmox-sys/src/fs/dir.rs
> +++ b/proxmox-sys/src/fs/dir.rs
> @@ -1,6 +1,8 @@
> -use std::ffi::CStr;
> +use std::ffi::{CStr, CString, OsStr};
> +use std::fs::File;
> +use std::os::unix::ffi::OsStrExt;
>  use std::os::unix::io::{AsRawFd, OwnedFd};
> -use std::path::Path;
> +use std::path::{Path, PathBuf};
>  
>  use anyhow::{bail, Error};
>  use nix::errno::Errno;
> @@ -8,6 +10,8 @@ use nix::fcntl::OFlag;
>  use nix::sys::stat;
>  use nix::unistd;
>  
> +use proxmox_lang::try_block;
> +
>  use crate::fs::{fchown, CreateOptions};
>  
>  /// Creates directory at the provided path with specified ownership.
> @@ -152,6 +156,54 @@ fn create_path_at_do(
>      }
>  }
>  
> +///  Create a temporary directory.
> +///
> +/// `prefix` determines where the temporary directory will be created. For instance, if
> +/// `prefix` is `/tmp`, on success the function will return a path in the style of
> +/// `/tmp/tmp_XXXXXX`, where X stands for a random string, ensuring that the path is unique.
> +///
> +/// By default, the created directory has `0o700` permissions. If this is not desired, custom
> +/// [`CreateOptions`] can be passed via the `option` parameter.
> +pub fn make_tmp_dir<P: AsRef<Path>>(
> +    prefix: P,
> +    options: Option<CreateOptions>,
> +) -> Result<PathBuf, Error> {
> +    let mut template = prefix.as_ref().to_owned();
> +    template = template.join("tmp_XXXXXX");
> +    let template = CString::new(template.into_os_string().as_bytes())?;
> +
> +    let raw_template_buffer = template.into_raw();

^ This might be shorter without going over the `CString` type with just
a `Vec<u8>` with an explicit `.push(0)` without temporarily giving up
ownership for the `mkdtemp` call.

> +
> +    let path = unsafe {
> +        let raw_returned_buffer = libc::mkdtemp(raw_template_buffer);
> +        if raw_returned_buffer.is_null() {

Need to add

    let err = io::Error::last_os_error();

as the very first thing you do in this branch.

Never give any external libraries a chance to mess with your
`errno` values before you use them, even `std` ;-)

> +            // The returned pointer points to the same buffer, so in case
> +            // of an error we need to make sure to claim it back to that
> +            // it is freed properly.
> +            drop(CString::from_raw(raw_template_buffer));

^ but I think we could avoid this - but as long as you fix up the
`errno` usage the CString code can also just stay this way, no strong
feelings there.

> +            return Err(std::io::Error::last_os_error().into());
> +        }
> +        CString::from_raw(raw_returned_buffer)
> +    };
> +
> +    let path = OsStr::from_bytes(path.as_bytes());
> +    let path = PathBuf::from(path);

^ This seems like there should be a cheap non-copying version:
    PathBuf::from(OsString::from_vec(path.into_bytes())) ?

> +
> +    if let Some(options) = options {
> +        if let Err(err) = try_block!({
> +            let fd = crate::fd::open(&path, OFlag::O_DIRECTORY, stat::Mode::empty())?;
> +            let mut file = File::from(fd);
> +            options.apply_to(&mut file, &path)?;

^ Huh, just noticing this weirdness, can we fix up the apply_to API to
take a `RawFd` or `&BorrowedFd` instead of a `File`? This is... not a
file... :-) And also `CreateOptions` doesn't really need it to be
mutable ;-)

> +            Ok::<(), Error>(())
> +        }) {
> +            let _ = unistd::unlink(&path);

^ This calls `unlink(2)` which does not remove directories. You need to
use either `std::fs::remove_dir()` or `unlinkat` with
`UnlinkatFlags::RemoveDir`.

Also, please also log the error if this fails.

> +            bail!("could not apply create options to new temporary directory: {err}");
> +        }
> +    }
> +
> +    Ok(path)
> +}
> +
>  #[cfg(test)]
>  mod tests {
>      use super::*;
> @@ -169,4 +221,21 @@ mod tests {
>          )
>          .expect("expected create_path to work");
>      }
> +
> +    #[test]
> +    fn test_make_tmp_dir() -> Result<(), Error> {
> +        let options = CreateOptions::new()
> +            .owner(unistd::Uid::effective())
> +            .group(unistd::Gid::effective())
> +            .perm(stat::Mode::from_bits_truncate(0o755));
> +
> +        let path = make_tmp_dir("/tmp", Some(options))?;
> +
> +        assert!(path.exists());
> +        assert!(path.is_dir());
> +
> +        std::fs::remove_dir_all(&path)?;
> +
> +        Ok(())
> +    }
>  }
> -- 
> 2.39.2





More information about the pve-devel mailing list