diff --git a/proxmox-sys/src/fs/dir.rs b/proxmox-sys/src/fs/dir.rs index 6aee3168..403dce9f 100644 --- a/proxmox-sys/src/fs/dir.rs +++ b/proxmox-sys/src/fs/dir.rs @@ -1,8 +1,9 @@ use std::ffi::CStr; +use std::os::fd::FromRawFd; use std::os::unix::io::{AsRawFd, OwnedFd}; use std::path::Path; -use anyhow::{bail, Error}; +use anyhow::{bail, format_err, Error}; use nix::errno::Errno; use nix::fcntl::OFlag; use nix::sys::stat; @@ -27,6 +28,51 @@ pub fn create_dir>(path: P, options: CreateOptions) -> Result<(), Ok(()) } +/// Ensure a directory exists. +/// +/// Like [create_dir], but does not fail if the directory already exists. +/// +/// Directory permissions are verified and raise an error if enforce_permissions is set. +pub fn ensure_dir_exists>( + path: P, + options: &CreateOptions, + enforce_permissions: bool, +) -> Result<(), Error> { + let uid = options.owner; + let gid = options.group; + + let mode: stat::Mode = options + .perm + .unwrap_or(stat::Mode::from_bits_truncate(0o770)); + + let path = path.as_ref(); + + match nix::unistd::mkdir(path, mode) { + Ok(()) => (), + Err(nix::errno::Errno::EEXIST) => { + if enforce_permissions { + return options.check(path); + } else { + if let Err(err) = options.check(path) { + log::error!("{err}"); + } + } + } + Err(err) => bail!("unable to create directory {path:?} - {err}",), + } + + let fd = nix::fcntl::open(path, OFlag::O_DIRECTORY, stat::Mode::empty()) + .map(|fd| unsafe { OwnedFd::from_raw_fd(fd) }) + .map_err(|err| format_err!("unable to open created directory {path:?} - {err}"))?; + // umask defaults to 022 so make sure the mode is fully honowed: + nix::sys::stat::fchmod(fd.as_raw_fd(), mode) + .map_err(|err| format_err!("unable to set mode for directory {path:?} - {err}"))?; + nix::unistd::fchown(fd.as_raw_fd(), uid, gid) + .map_err(|err| format_err!("unable to set ownership directory {path:?} - {err}"))?; + + Ok(()) +} + /// Recursively create a path with separately defined metadata for intermediate directories and the /// final component in the path. /// diff --git a/proxmox-sys/src/fs/mod.rs b/proxmox-sys/src/fs/mod.rs index c94781e2..4dbc3ec9 100644 --- a/proxmox-sys/src/fs/mod.rs +++ b/proxmox-sys/src/fs/mod.rs @@ -1,7 +1,7 @@ //! File system related utilities use std::path::Path; -use anyhow::{bail, Error}; +use anyhow::{bail, Context, Error}; use nix::sys::stat; use nix::unistd::{Gid, Uid}; @@ -84,6 +84,44 @@ impl CreateOptions { Ok(()) } + /// Check file/directory permissions. + /// + /// Make sure that the file or dir is owned by uid/gid and has the correct mode. + pub fn check>(&self, path: P) -> Result<(), Error> { + let path = path.as_ref(); + + let nix::sys::stat::FileStat { + st_uid, + st_gid, + st_mode, + .. + } = nix::sys::stat::stat(path).with_context(|| format!("failed to stat {path:?}"))?; + + if let Some(uid) = self.owner { + let uid = uid.as_raw(); + if st_uid != uid { + bail!("bad owner on {path:?} ({st_uid} != {uid})"); + } + } + + if let Some(group) = self.group { + let gid = group.as_raw(); + if st_gid != gid { + bail!("bad group on {path:?} ({st_gid} != {gid})"); + } + } + + if let Some(mode) = self.perm { + let mode = mode.bits(); + let perms = st_mode & !nix::sys::stat::SFlag::S_IFMT.bits(); + if perms != mode { + bail!("bad permissions on {path:?} (0o{perms:o} != 0o{mode:o})"); + } + } + + Ok(()) + } + /// Convenience shortcut around having to import `Gid` from nix. pub const fn group_root(self) -> Self { self.group(Gid::from_raw(0))