From 32b69176dd76ec9c4d8ed9518d4e21dc1ce846fb Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Mon, 22 Nov 2021 12:51:33 +0100 Subject: [PATCH] proxmox-sys: imported proxmox tools/sys And split some files into smaller parts. Signed-off-by: Dietmar Maurer --- proxmox-sys/Cargo.toml | 4 + proxmox-sys/debian/control | 4 +- proxmox-sys/src/crypt.rs | 2 +- {proxmox/src/sys => proxmox-sys/src}/error.rs | 2 +- proxmox-sys/src/fd/borrowed_fd.rs | 37 +++ .../src/tools => proxmox-sys/src/fd}/fd.rs | 95 ------- proxmox-sys/src/fd/mod.rs | 22 ++ proxmox-sys/src/fd/raw_fd_num.rs | 55 ++++ proxmox-sys/src/fs/dir.rs | 168 ++++++++++++ .../tools/fs.rs => proxmox-sys/src/fs/file.rs | 256 +----------------- proxmox-sys/src/fs/mod.rs | 104 +++++++ proxmox-sys/src/lib.rs | 6 + .../sys => proxmox-sys/src}/linux/magic.rs | 0 .../src/sys => proxmox-sys/src}/linux/mod.rs | 1 + .../src/sys => proxmox-sys/src}/linux/pid.rs | 6 +- .../src}/linux/procfs/mod.rs | 14 +- .../src}/linux/procfs/mountinfo.rs | 0 .../src/sys => proxmox-sys/src}/linux/pty.rs | 2 +- .../sys => proxmox-sys/src}/linux/socket.rs | 0 .../sys => proxmox-sys/src/linux}/timer.rs | 0 .../src/sys => proxmox-sys/src}/linux/tty.rs | 4 +- proxmox-sys/src/logrotate.rs | 2 +- .../src/sys => proxmox-sys/src}/macros.rs | 0 .../src/tools => proxmox-sys/src}/mmap.rs | 2 +- proxmox/src/lib.rs | 1 - proxmox/src/sys/mod.rs | 7 - proxmox/src/tools/mod.rs | 4 - proxmox/src/tools/parse.rs | 14 - 28 files changed, 427 insertions(+), 385 deletions(-) rename {proxmox/src/sys => proxmox-sys/src}/error.rs (98%) create mode 100644 proxmox-sys/src/fd/borrowed_fd.rs rename {proxmox/src/tools => proxmox-sys/src/fd}/fd.rs (54%) create mode 100644 proxmox-sys/src/fd/mod.rs create mode 100644 proxmox-sys/src/fd/raw_fd_num.rs create mode 100644 proxmox-sys/src/fs/dir.rs rename proxmox/src/tools/fs.rs => proxmox-sys/src/fs/file.rs (62%) create mode 100644 proxmox-sys/src/fs/mod.rs rename {proxmox/src/sys => proxmox-sys/src}/linux/magic.rs (100%) rename {proxmox/src/sys => proxmox-sys/src}/linux/mod.rs (98%) rename {proxmox/src/sys => proxmox-sys/src}/linux/pid.rs (97%) rename {proxmox/src/sys => proxmox-sys/src}/linux/procfs/mod.rs (98%) rename {proxmox/src/sys => proxmox-sys/src}/linux/procfs/mountinfo.rs (100%) rename {proxmox/src/sys => proxmox-sys/src}/linux/pty.rs (99%) rename {proxmox/src/sys => proxmox-sys/src}/linux/socket.rs (100%) rename {proxmox/src/sys => proxmox-sys/src/linux}/timer.rs (100%) rename {proxmox/src/sys => proxmox-sys/src}/linux/tty.rs (98%) rename {proxmox/src/sys => proxmox-sys/src}/macros.rs (100%) rename {proxmox/src/tools => proxmox-sys/src}/mmap.rs (98%) delete mode 100644 proxmox/src/sys/mod.rs delete mode 100644 proxmox/src/tools/parse.rs diff --git a/proxmox-sys/Cargo.toml b/proxmox-sys/Cargo.toml index fac9257c..8a78cb62 100644 --- a/proxmox-sys/Cargo.toml +++ b/proxmox-sys/Cargo.toml @@ -11,9 +11,13 @@ exclude = [ "debian" ] [dependencies] anyhow = "1.0" base64 = "0.13" +lazy_static = "1.4" libc = "0.2.107" log = "0.4" nix = "0.19.1" +serde_json = "1.0" zstd = { version = "0.6", features = [ "bindgen" ] } proxmox = { path = "../proxmox", version = "0.15", default-features = false } +proxmox-io = { path = "../proxmox-io", version = "1.0.0" } +proxmox-lang = { path = "../proxmox-lang", version = "1.0.0" } diff --git a/proxmox-sys/debian/control b/proxmox-sys/debian/control index cac83205..82e5811d 100644 --- a/proxmox-sys/debian/control +++ b/proxmox-sys/debian/control @@ -41,8 +41,8 @@ Provides: librust-proxmox-sys-0+default-dev (= ${binary:Version}), librust-proxmox-sys-0.1-dev (= ${binary:Version}), librust-proxmox-sys-0.1+default-dev (= ${binary:Version}), - librust-proxmox-sys-0.1.1-dev (= ${binary:Version}), - librust-proxmox-sys-0.1.1+default-dev (= ${binary:Version}) + librust-proxmox-sys-0.1.2-dev (= ${binary:Version}), + librust-proxmox-sys-0.1.2+default-dev (= ${binary:Version}) Description: System tools (using nix) - Rust source code This package contains the source for the Rust proxmox-sys crate, packaged by debcargo for use with cargo and dh-cargo. diff --git a/proxmox-sys/src/crypt.rs b/proxmox-sys/src/crypt.rs index c47bfe24..8d1a3755 100644 --- a/proxmox-sys/src/crypt.rs +++ b/proxmox-sys/src/crypt.rs @@ -53,7 +53,7 @@ pub fn crypt(password: &[u8], salt: &[u8]) -> Result { pub fn encrypt_pw(password: &str) -> Result { - let salt = proxmox::sys::linux::random_data(8)?; + let salt = crate::linux::random_data(8)?; let salt = format!("$5${}$", base64::encode_config(&salt, base64::CRYPT)); crypt(password.as_bytes(), salt.as_bytes()) diff --git a/proxmox/src/sys/error.rs b/proxmox-sys/src/error.rs similarity index 98% rename from proxmox/src/sys/error.rs rename to proxmox-sys/src/error.rs index 7adfed78..d495fd86 100644 --- a/proxmox/src/sys/error.rs +++ b/proxmox-sys/src/error.rs @@ -150,7 +150,7 @@ macro_rules! other_error { #[inline] fn into_io_result(self) -> io::Result { - self.map_err($crate::sys::error::io_err_other) + self.map_err($crate::error::io_err_other) } } }; diff --git a/proxmox-sys/src/fd/borrowed_fd.rs b/proxmox-sys/src/fd/borrowed_fd.rs new file mode 100644 index 00000000..2f35f433 --- /dev/null +++ b/proxmox-sys/src/fd/borrowed_fd.rs @@ -0,0 +1,37 @@ +use std::marker::PhantomData; +use std::os::unix::io::{AsRawFd, RawFd}; + +/// A borrowed file raw descriptor. (A `RawFd` with an attached lifetime). +/// +/// For when using `&FdRef` is not an option. +/// +/// This specifically does not implement `IntoRawFd` or `FromRawFd`, since those would drop life +/// times. +#[derive(Debug, Eq, PartialEq)] +pub struct BorrowedFd<'a> { + fd: RawFd, + _borrow: PhantomData<&'a RawFd>, +} + +impl<'a> BorrowedFd<'a> { + #[inline] + pub fn new(fd: &T) -> Self { + Self { + fd: fd.as_raw_fd(), + _borrow: PhantomData, + } + } +} + +impl AsRawFd for BorrowedFd<'_> { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +impl<'a, T: ?Sized + AsRawFd> From<&'a T> for BorrowedFd<'a> { + #[inline] + fn from(fd: &'a T) -> Self { + Self::new(fd) + } +} diff --git a/proxmox/src/tools/fd.rs b/proxmox-sys/src/fd/fd.rs similarity index 54% rename from proxmox/src/tools/fd.rs rename to proxmox-sys/src/fd/fd.rs index 582f884f..a752482e 100644 --- a/proxmox/src/tools/fd.rs +++ b/proxmox-sys/src/fd/fd.rs @@ -1,13 +1,10 @@ -//! Raw file descriptor related structures. use std::borrow::Borrow; -use std::marker::PhantomData; use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; use nix::fcntl::OFlag; use nix::sys::stat::Mode; use nix::NixPath; -use nix::fcntl::{fcntl, FdFlag, F_GETFD, F_SETFD}; /// Guard a raw file descriptor with a drop handler. This is mostly useful when access to an owned /// `RawFd` is required without the corresponding handler object (such as when only the file @@ -92,56 +89,6 @@ impl std::ops::Deref for Fd { } } -/// Raw file descriptor by number. Thin wrapper to provide `AsRawFd` which a simple `RawFd` does -/// not since it's just an `i32`. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct RawFdNum(RawFd); - -impl RawFdNum { - /// Borrow this file descriptor as an `&FdRef`. - pub fn as_fd_ref(&self) -> &FdRef { - unsafe { &*(&self.0 as *const RawFd as *const FdRef) } - } -} - -impl AsRawFd for RawFdNum { - fn as_raw_fd(&self) -> RawFd { - self.0 - } -} - -impl FromRawFd for RawFdNum { - unsafe fn from_raw_fd(fd: RawFd) -> Self { - Self(fd) - } -} - -impl IntoRawFd for RawFdNum { - fn into_raw_fd(self) -> RawFd { - self.0 - } -} - -impl AsRef for RawFdNum { - fn as_ref(&self) -> &FdRef { - self.as_fd_ref() - } -} - -impl Borrow for RawFdNum { - fn borrow(&self) -> &FdRef { - self.as_fd_ref() - } -} - -impl std::ops::Deref for RawFdNum { - type Target = FdRef; - - fn deref(&self) -> &FdRef { - self.as_fd_ref() - } -} - /// A reference to a raw file descriptor. (Strongly typed `&RawFd` which is not equivalent to an /// `&i32`. /// @@ -161,45 +108,3 @@ impl AsRawFd for FdRef { } } -/// A borrowed file raw descriptor. (A `RawFd` with an attached lifetime). -/// -/// For when using `&FdRef` is not an option. -/// -/// This specifically does not implement `IntoRawFd` or `FromRawFd`, since those would drop life -/// times. -#[derive(Debug, Eq, PartialEq)] -pub struct BorrowedFd<'a> { - fd: RawFd, - _borrow: PhantomData<&'a RawFd>, -} - -impl<'a> BorrowedFd<'a> { - #[inline] - pub fn new(fd: &T) -> Self { - Self { - fd: fd.as_raw_fd(), - _borrow: PhantomData, - } - } -} - -impl AsRawFd for BorrowedFd<'_> { - fn as_raw_fd(&self) -> RawFd { - self.fd - } -} - -impl<'a, T: ?Sized + AsRawFd> From<&'a T> for BorrowedFd<'a> { - #[inline] - fn from(fd: &'a T) -> Self { - Self::new(fd) - } -} - -/// Change the `O_CLOEXEC` flag of an existing file descriptor. -pub fn fd_change_cloexec(fd: RawFd, on: bool) -> Result<(), anyhow::Error> { - let mut flags = unsafe { FdFlag::from_bits_unchecked(fcntl(fd, F_GETFD)?) }; - flags.set(FdFlag::FD_CLOEXEC, on); - fcntl(fd, F_SETFD(flags))?; - Ok(()) -} diff --git a/proxmox-sys/src/fd/mod.rs b/proxmox-sys/src/fd/mod.rs new file mode 100644 index 00000000..8d76cef3 --- /dev/null +++ b/proxmox-sys/src/fd/mod.rs @@ -0,0 +1,22 @@ +//! Raw file descriptor related structures. + +mod fd; +pub use fd::*; + +mod raw_fd_num; +pub use raw_fd_num::*; + +mod borrowed_fd; +pub use borrowed_fd::*; + +use std::os::unix::io::RawFd; + +use nix::fcntl::{fcntl, FdFlag, F_GETFD, F_SETFD}; + +/// Change the `O_CLOEXEC` flag of an existing file descriptor. +pub fn fd_change_cloexec(fd: RawFd, on: bool) -> Result<(), anyhow::Error> { + let mut flags = unsafe { FdFlag::from_bits_unchecked(fcntl(fd, F_GETFD)?) }; + flags.set(FdFlag::FD_CLOEXEC, on); + fcntl(fd, F_SETFD(flags))?; + Ok(()) +} diff --git a/proxmox-sys/src/fd/raw_fd_num.rs b/proxmox-sys/src/fd/raw_fd_num.rs new file mode 100644 index 00000000..7356c05f --- /dev/null +++ b/proxmox-sys/src/fd/raw_fd_num.rs @@ -0,0 +1,55 @@ +use std::borrow::Borrow; +use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; + +use super::FdRef; + +/// Raw file descriptor by number. Thin wrapper to provide `AsRawFd` which a simple `RawFd` does +/// not since it's just an `i32`. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct RawFdNum(RawFd); + +impl RawFdNum { + /// Borrow this file descriptor as an `&FdRef`. + pub fn as_fd_ref(&self) -> &FdRef { + unsafe { &*(&self.0 as *const RawFd as *const FdRef) } + } +} + +impl AsRawFd for RawFdNum { + fn as_raw_fd(&self) -> RawFd { + self.0 + } +} + +impl FromRawFd for RawFdNum { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + Self(fd) + } +} + +impl IntoRawFd for RawFdNum { + fn into_raw_fd(self) -> RawFd { + self.0 + } +} + +impl AsRef for RawFdNum { + fn as_ref(&self) -> &FdRef { + self.as_fd_ref() + } +} + +impl Borrow for RawFdNum { + fn borrow(&self) -> &FdRef { + self.as_fd_ref() + } +} + +impl std::ops::Deref for RawFdNum { + type Target = FdRef; + + fn deref(&self) -> &FdRef { + self.as_fd_ref() + } +} + diff --git a/proxmox-sys/src/fs/dir.rs b/proxmox-sys/src/fs/dir.rs new file mode 100644 index 00000000..72785932 --- /dev/null +++ b/proxmox-sys/src/fs/dir.rs @@ -0,0 +1,168 @@ +use std::ffi::CStr; +use std::os::unix::io::AsRawFd; +use std::path::Path; + +use anyhow::{bail, Error}; +use nix::errno::Errno; +use nix::fcntl::OFlag; +use nix::sys::stat; +use nix::unistd; + +use crate::fd::Fd; +use crate::fs::{fchown, CreateOptions}; + +/// Creates directory at the provided path with specified ownership. +/// +/// Errors if the directory already exists. +pub fn create_dir>(path: P, options: CreateOptions) -> Result<(), nix::Error> { + // clippy bug?: from_bits_truncate is actually a const fn... + #[allow(clippy::or_fun_call)] + let mode: stat::Mode = options + .perm + .unwrap_or(stat::Mode::from_bits_truncate(0o770)); + + let path = path.as_ref(); + nix::unistd::mkdir(path, mode)?; + unistd::chown(path, options.owner, options.group)?; + + Ok(()) +} + +/// Recursively create a path with separately defined metadata for intermediate directories and the +/// final component in the path. +/// +/// Returns `true` if the final directory was created. Otherwise `false` is returned and no changes +/// to the directory's metadata have been performed. +/// +/// ```no_run +/// # use nix::sys::stat::Mode; +/// # use nix::unistd::{Gid, Uid}; +/// # use proxmox::tools::fs::{create_path, CreateOptions}; +/// # fn code() -> Result<(), anyhow::Error> { +/// create_path( +/// "/var/lib/mytool/wwwdata", +/// None, +/// Some(CreateOptions::new() +/// .perm(Mode::from_bits(0o777).unwrap()) +/// .owner(Uid::from_raw(33)) +/// ), +/// )?; +/// # Ok(()) +/// # } +/// ``` +pub fn create_path>( + path: P, + intermediate_opts: Option, + final_opts: Option, +) -> Result { + create_path_do(path.as_ref(), intermediate_opts, final_opts) +} + +fn create_path_do( + path: &Path, + intermediate_opts: Option, + final_opts: Option, +) -> Result { + use std::path::Component; + + let mut iter = path.components().peekable(); + let at: Fd = match iter.peek() { + Some(Component::Prefix(_)) => bail!("illegal prefix path component encountered"), + Some(Component::RootDir) => { + let _ = iter.next(); + Fd::open( + unsafe { CStr::from_bytes_with_nul_unchecked(b"/\0") }, + OFlag::O_DIRECTORY, + stat::Mode::empty(), + )? + } + Some(Component::CurDir) => { + let _ = iter.next(); + Fd::cwd() + } + Some(Component::ParentDir) => { + let _ = iter.next(); + Fd::open( + unsafe { CStr::from_bytes_with_nul_unchecked(b"..\0") }, + OFlag::O_DIRECTORY, + stat::Mode::empty(), + )? + } + Some(Component::Normal(_)) => { + // simply do not advance the iterator, heavy lifting happens in create_path_at_do() + Fd::cwd() + } + None => bail!("create_path on empty path?"), + }; + + create_path_at_do(at, iter, intermediate_opts, final_opts) +} + +fn create_path_at_do( + mut at: Fd, + mut iter: std::iter::Peekable, + intermediate_opts: Option, + final_opts: Option, +) -> Result { + let mut created = false; + loop { + use std::path::Component; + + match iter.next() { + None => return Ok(created), + + Some(Component::ParentDir) => { + at = Fd::openat( + &at, + unsafe { CStr::from_bytes_with_nul_unchecked(b"..\0") }, + OFlag::O_DIRECTORY, + stat::Mode::empty(), + )?; + } + + Some(Component::Normal(path)) => { + let opts = if iter.peek().is_some() { + intermediate_opts.as_ref() + } else { + final_opts.as_ref() + }; + + // clippy bug?: from_bits_truncate is actually a const fn... + #[allow(clippy::or_fun_call)] + let mode = opts + .and_then(|o| o.perm) + .unwrap_or(stat::Mode::from_bits_truncate(0o755)); + + created = match stat::mkdirat(at.as_raw_fd(), path, mode) { + Err(nix::Error::Sys(Errno::EEXIST)) => false, + Err(e) => return Err(e.into()), + Ok(_) => true, + }; + at = Fd::openat(&at, path, OFlag::O_DIRECTORY, stat::Mode::empty())?; + + if let (true, Some(opts)) = (created, opts) { + if opts.owner.is_some() || opts.group.is_some() { + fchown(at.as_raw_fd(), opts.owner, opts.group)?; + } + } + } + + // impossible according to the docs: + Some(_) => bail!("encountered unexpected special path component"), + } + } +} + +#[test] +fn test_create_path() { + create_path( + "testdir/testsub/testsub2/testfinal", + Some(CreateOptions::new().perm(stat::Mode::from_bits_truncate(0o755))), + Some( + CreateOptions::new() + .owner(Uid::effective()) + .group(Gid::effective()), + ), + ) + .expect("expected create_path to work"); +} diff --git a/proxmox/src/tools/fs.rs b/proxmox-sys/src/fs/file.rs similarity index 62% rename from proxmox/src/tools/fs.rs rename to proxmox-sys/src/fs/file.rs index b3f2e913..8820335f 100644 --- a/proxmox/src/tools/fs.rs +++ b/proxmox-sys/src/fs/file.rs @@ -1,9 +1,6 @@ -//! File related utilities such as `replace_file`. - -use std::ffi::CStr; use std::fs::File; use std::io::{self, BufRead, BufReader, Write}; -use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; +use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; use std::path::{Path, PathBuf}; use std::time::Duration; @@ -11,15 +8,16 @@ use anyhow::{bail, format_err, Error}; use nix::errno::Errno; use nix::fcntl::OFlag; use nix::sys::stat; -use nix::unistd::{self, Gid, Uid}; +use nix::unistd; use nix::NixPath; use serde_json::Value; use proxmox_lang::try_block; -use crate::sys::error::{SysError, SysResult}; -use crate::sys::timer; -use crate::tools::fd::Fd; +use crate::error::{SysError, SysResult}; +use crate::linux::timer; + +use crate::fs::CreateOptions; /// Read the entire contents of a file into a bytes vector /// @@ -318,248 +316,6 @@ pub fn atomic_open_or_create_file>( } } -/// Change ownership of an open file handle -pub fn fchown(fd: RawFd, owner: Option, group: Option) -> Result<(), Error> { - // According to the POSIX specification, -1 is used to indicate that owner and group - // are not to be changed. Since uid_t and gid_t are unsigned types, we have to wrap - // around to get -1 (copied fron nix crate). - let uid = owner.map(Into::into).unwrap_or(!(0 as libc::uid_t)); - let gid = group.map(Into::into).unwrap_or(!(0 as libc::gid_t)); - - let res = unsafe { libc::fchown(fd, uid, gid) }; - Errno::result(res)?; - - Ok(()) -} - -// FIXME: Consider using derive-builder! -#[derive(Clone, Default)] -pub struct CreateOptions { - perm: Option, - owner: Option, - group: Option, -} - -impl CreateOptions { - // contrary to Default::default() this is const - pub const fn new() -> Self { - Self { - perm: None, - owner: None, - group: None, - } - } - - pub const fn perm(mut self, perm: stat::Mode) -> Self { - self.perm = Some(perm); - self - } - - pub const fn owner(mut self, owner: Uid) -> Self { - self.owner = Some(owner); - self - } - - pub const fn group(mut self, group: Gid) -> Self { - self.group = Some(group); - self - } - - /// Convenience shortcut around having to import `Uid` from nix. - pub const fn owner_root(self) -> Self { - self.owner(nix::unistd::ROOT) - } - - pub fn apply_to(&self, file: &mut File, path: &Path) -> Result<(), Error> { - - // clippy bug?: from_bits_truncate is actually a const fn... - #[allow(clippy::or_fun_call)] - let mode: stat::Mode = self.perm - .unwrap_or(stat::Mode::from_bits_truncate(0o644)); - - if let Err(err) = stat::fchmod(file.as_raw_fd(), mode) { - bail!("fchmod {:?} failed: {}", path, err); - } - - if self.owner.is_some() || self.group.is_some() { - if let Err(err) = fchown(file.as_raw_fd(), self.owner, self.group) { - bail!("fchown {:?} failed: {}", path, err); - } - } - Ok(()) - } - - // TODO: once 'nix' has `const fn` constructors for Uid and Gid we can enable these: - - /* - /// Convenience shortcut around having to import `Gid` from nix. - pub const fn group_root(self) -> Self { - // nix hasn't constified these yet, but it's just an alias to gid_t: - self.group(Gid::from_raw(0)) - } - - /// Convenience shortcut to set both owner and group to 0. - pub const fn root_only(self) -> Self { - self.owner_root().group_root() - } - */ -} - -/// Creates directory at the provided path with specified ownership. -/// -/// Errors if the directory already exists. -pub fn create_dir>(path: P, options: CreateOptions) -> Result<(), nix::Error> { - // clippy bug?: from_bits_truncate is actually a const fn... - #[allow(clippy::or_fun_call)] - let mode: stat::Mode = options - .perm - .unwrap_or(stat::Mode::from_bits_truncate(0o770)); - - let path = path.as_ref(); - nix::unistd::mkdir(path, mode)?; - unistd::chown(path, options.owner, options.group)?; - - Ok(()) -} - -/// Recursively create a path with separately defined metadata for intermediate directories and the -/// final component in the path. -/// -/// Returns `true` if the final directory was created. Otherwise `false` is returned and no changes -/// to the directory's metadata have been performed. -/// -/// ```no_run -/// # use nix::sys::stat::Mode; -/// # use nix::unistd::{Gid, Uid}; -/// # use proxmox::tools::fs::{create_path, CreateOptions}; -/// # fn code() -> Result<(), anyhow::Error> { -/// create_path( -/// "/var/lib/mytool/wwwdata", -/// None, -/// Some(CreateOptions::new() -/// .perm(Mode::from_bits(0o777).unwrap()) -/// .owner(Uid::from_raw(33)) -/// ), -/// )?; -/// # Ok(()) -/// # } -/// ``` -pub fn create_path>( - path: P, - intermediate_opts: Option, - final_opts: Option, -) -> Result { - create_path_do(path.as_ref(), intermediate_opts, final_opts) -} - -fn create_path_do( - path: &Path, - intermediate_opts: Option, - final_opts: Option, -) -> Result { - use std::path::Component; - - let mut iter = path.components().peekable(); - let at: Fd = match iter.peek() { - Some(Component::Prefix(_)) => bail!("illegal prefix path component encountered"), - Some(Component::RootDir) => { - let _ = iter.next(); - Fd::open( - unsafe { CStr::from_bytes_with_nul_unchecked(b"/\0") }, - OFlag::O_DIRECTORY, - stat::Mode::empty(), - )? - } - Some(Component::CurDir) => { - let _ = iter.next(); - Fd::cwd() - } - Some(Component::ParentDir) => { - let _ = iter.next(); - Fd::open( - unsafe { CStr::from_bytes_with_nul_unchecked(b"..\0") }, - OFlag::O_DIRECTORY, - stat::Mode::empty(), - )? - } - Some(Component::Normal(_)) => { - // simply do not advance the iterator, heavy lifting happens in create_path_at_do() - Fd::cwd() - } - None => bail!("create_path on empty path?"), - }; - - create_path_at_do(at, iter, intermediate_opts, final_opts) -} - -fn create_path_at_do( - mut at: Fd, - mut iter: std::iter::Peekable, - intermediate_opts: Option, - final_opts: Option, -) -> Result { - let mut created = false; - loop { - use std::path::Component; - - match iter.next() { - None => return Ok(created), - - Some(Component::ParentDir) => { - at = Fd::openat( - &at, - unsafe { CStr::from_bytes_with_nul_unchecked(b"..\0") }, - OFlag::O_DIRECTORY, - stat::Mode::empty(), - )?; - } - - Some(Component::Normal(path)) => { - let opts = if iter.peek().is_some() { - intermediate_opts.as_ref() - } else { - final_opts.as_ref() - }; - - // clippy bug?: from_bits_truncate is actually a const fn... - #[allow(clippy::or_fun_call)] - let mode = opts - .and_then(|o| o.perm) - .unwrap_or(stat::Mode::from_bits_truncate(0o755)); - - created = match stat::mkdirat(at.as_raw_fd(), path, mode) { - Err(nix::Error::Sys(Errno::EEXIST)) => false, - Err(e) => return Err(e.into()), - Ok(_) => true, - }; - at = Fd::openat(&at, path, OFlag::O_DIRECTORY, stat::Mode::empty())?; - - if let (true, Some(opts)) = (created, opts) { - if opts.owner.is_some() || opts.group.is_some() { - fchown(at.as_raw_fd(), opts.owner, opts.group)?; - } - } - } - - // impossible according to the docs: - Some(_) => bail!("encountered unexpected special path component"), - } - } -} - -#[test] -fn test_create_path() { - create_path( - "testdir/testsub/testsub2/testfinal", - Some(CreateOptions::new().perm(stat::Mode::from_bits_truncate(0o755))), - Some( - CreateOptions::new() - .owner(Uid::effective()) - .group(Gid::effective()), - ), - ) - .expect("expected create_path to work"); -} // /usr/include/linux/fs.h: #define BLKGETSIZE64 _IOR(0x12,114,size_t) // return device size in bytes (u64 *arg) diff --git a/proxmox-sys/src/fs/mod.rs b/proxmox-sys/src/fs/mod.rs new file mode 100644 index 00000000..049c9bdc --- /dev/null +++ b/proxmox-sys/src/fs/mod.rs @@ -0,0 +1,104 @@ +//! File system related utilities +use std::fs::File; +use std::path::Path; + +use anyhow::{bail, Error}; + +use std::os::unix::io::{AsRawFd, RawFd}; +use nix::unistd::{Gid, Uid}; +use nix::sys::stat; +use nix::errno::Errno; + +mod file; +pub use file::*; + +mod dir; +pub use dir::*; + +/// Change ownership of an open file handle +pub fn fchown(fd: RawFd, owner: Option, group: Option) -> Result<(), Error> { + // According to the POSIX specification, -1 is used to indicate that owner and group + // are not to be changed. Since uid_t and gid_t are unsigned types, we have to wrap + // around to get -1 (copied fron nix crate). + let uid = owner.map(Into::into).unwrap_or(!(0 as libc::uid_t)); + let gid = group.map(Into::into).unwrap_or(!(0 as libc::gid_t)); + + let res = unsafe { libc::fchown(fd, uid, gid) }; + Errno::result(res)?; + + Ok(()) +} + +// FIXME: Consider using derive-builder! +#[derive(Clone, Default)] +pub struct CreateOptions { + perm: Option, + owner: Option, + group: Option, +} + +impl CreateOptions { + // contrary to Default::default() this is const + pub const fn new() -> Self { + Self { + perm: None, + owner: None, + group: None, + } + } + + pub const fn perm(mut self, perm: stat::Mode) -> Self { + self.perm = Some(perm); + self + } + + pub const fn owner(mut self, owner: Uid) -> Self { + self.owner = Some(owner); + self + } + + pub const fn group(mut self, group: Gid) -> Self { + self.group = Some(group); + self + } + + /// Convenience shortcut around having to import `Uid` from nix. + pub const fn owner_root(self) -> Self { + self.owner(nix::unistd::ROOT) + } + + pub fn apply_to(&self, file: &mut File, path: &Path) -> Result<(), Error> { + + // clippy bug?: from_bits_truncate is actually a const fn... + #[allow(clippy::or_fun_call)] + let mode: stat::Mode = self.perm + .unwrap_or(stat::Mode::from_bits_truncate(0o644)); + + if let Err(err) = stat::fchmod(file.as_raw_fd(), mode) { + bail!("fchmod {:?} failed: {}", path, err); + } + + if self.owner.is_some() || self.group.is_some() { + if let Err(err) = fchown(file.as_raw_fd(), self.owner, self.group) { + bail!("fchown {:?} failed: {}", path, err); + } + } + Ok(()) + } + + // TODO: once 'nix' has `const fn` constructors for Uid and Gid we can enable these: + + /* + /// Convenience shortcut around having to import `Gid` from nix. + pub const fn group_root(self) -> Self { + // nix hasn't constified these yet, but it's just an alias to gid_t: + self.group(Gid::from_raw(0)) + } + + /// Convenience shortcut to set both owner and group to 0. + pub const fn root_only(self) -> Self { + self.owner_root().group_root() + } + */ +} + diff --git a/proxmox-sys/src/lib.rs b/proxmox-sys/src/lib.rs index 20333849..8567e9db 100644 --- a/proxmox-sys/src/lib.rs +++ b/proxmox-sys/src/lib.rs @@ -1,4 +1,10 @@ pub mod crypt; +pub mod error; +pub mod fd; +pub mod fs; +pub mod linux; pub mod logrotate; +pub mod macros; +pub mod mmap; pub mod process_locker; pub mod worker_task_context; diff --git a/proxmox/src/sys/linux/magic.rs b/proxmox-sys/src/linux/magic.rs similarity index 100% rename from proxmox/src/sys/linux/magic.rs rename to proxmox-sys/src/linux/magic.rs diff --git a/proxmox/src/sys/linux/mod.rs b/proxmox-sys/src/linux/mod.rs similarity index 98% rename from proxmox/src/sys/linux/mod.rs rename to proxmox-sys/src/linux/mod.rs index 311c9d56..749c8ff6 100644 --- a/proxmox/src/sys/linux/mod.rs +++ b/proxmox-sys/src/linux/mod.rs @@ -9,6 +9,7 @@ pub mod pid; pub mod procfs; pub mod pty; pub mod socket; +pub mod timer; pub mod tty; /// Get pseudo random data (/dev/urandom) diff --git a/proxmox/src/sys/linux/pid.rs b/proxmox-sys/src/linux/pid.rs similarity index 97% rename from proxmox/src/sys/linux/pid.rs rename to proxmox-sys/src/linux/pid.rs index 3e4a256a..ad5fdc2f 100644 --- a/proxmox/src/sys/linux/pid.rs +++ b/proxmox-sys/src/linux/pid.rs @@ -13,9 +13,9 @@ use nix::NixPath; use proxmox_lang::c_str; -use crate::sys::error::{io_err_other, SysResult}; -use crate::sys::linux::procfs::{MountInfo, PidStat}; -use crate::tools::fd::Fd; +use crate::error::{io_err_other, SysResult}; +use crate::linux::procfs::{MountInfo, PidStat}; +use crate::fd::Fd; use crate::{c_result, c_try}; /// asm-generic pidfd_open syscall number diff --git a/proxmox/src/sys/linux/procfs/mod.rs b/proxmox-sys/src/linux/procfs/mod.rs similarity index 98% rename from proxmox/src/sys/linux/procfs/mod.rs rename to proxmox-sys/src/linux/procfs/mod.rs index 5784e0e8..487f8dc2 100644 --- a/proxmox/src/sys/linux/procfs/mod.rs +++ b/proxmox-sys/src/linux/procfs/mod.rs @@ -12,8 +12,7 @@ use anyhow::*; use lazy_static::lazy_static; use nix::unistd::Pid; -use crate::tools::fs::file_read_firstline; -use crate::tools::parse::hex_nibble; +use crate::fs::file_read_firstline; pub mod mountinfo; #[doc(inline)] @@ -570,6 +569,17 @@ pub fn read_proc_net_dev() -> Result, Error> { Ok(result) } +// Parse a hexadecimal digit into a byte. +#[inline] +fn hex_nibble(c: u8) -> Result { + Ok(match c { + b'0'..=b'9' => c - b'0', + b'a'..=b'f' => c - b'a' + 0xa, + b'A'..=b'F' => c - b'A' + 0xa, + _ => bail!("not a hex digit: {}", c as char), + }) +} + fn hexstr_to_ipv4addr>(hex: T) -> Result { let hex = hex.as_ref(); if hex.len() != 8 { diff --git a/proxmox/src/sys/linux/procfs/mountinfo.rs b/proxmox-sys/src/linux/procfs/mountinfo.rs similarity index 100% rename from proxmox/src/sys/linux/procfs/mountinfo.rs rename to proxmox-sys/src/linux/procfs/mountinfo.rs diff --git a/proxmox/src/sys/linux/pty.rs b/proxmox-sys/src/linux/pty.rs similarity index 99% rename from proxmox/src/sys/linux/pty.rs rename to proxmox-sys/src/linux/pty.rs index 3f6993ab..d80271f4 100644 --- a/proxmox/src/sys/linux/pty.rs +++ b/proxmox-sys/src/linux/pty.rs @@ -11,7 +11,7 @@ use nix::sys::stat::Mode; use nix::unistd::{dup2, setsid}; use nix::{ioctl_write_int_bad, ioctl_write_ptr_bad, Result}; -use crate::tools::fd::Fd; +use crate::fd::Fd; ioctl_write_int_bad!(set_controlling_tty, libc::TIOCSCTTY); ioctl_write_ptr_bad!(set_size, libc::TIOCSWINSZ, nix::pty::Winsize); diff --git a/proxmox/src/sys/linux/socket.rs b/proxmox-sys/src/linux/socket.rs similarity index 100% rename from proxmox/src/sys/linux/socket.rs rename to proxmox-sys/src/linux/socket.rs diff --git a/proxmox/src/sys/timer.rs b/proxmox-sys/src/linux/timer.rs similarity index 100% rename from proxmox/src/sys/timer.rs rename to proxmox-sys/src/linux/timer.rs diff --git a/proxmox/src/sys/linux/tty.rs b/proxmox-sys/src/linux/tty.rs similarity index 98% rename from proxmox/src/sys/linux/tty.rs rename to proxmox-sys/src/linux/tty.rs index 0b4a3c14..ab3e793b 100644 --- a/proxmox/src/sys/linux/tty.rs +++ b/proxmox-sys/src/linux/tty.rs @@ -9,8 +9,8 @@ use nix::sys::stat::Mode; use proxmox_lang::try_block; use crate::c_try; -use crate::sys::error::SysError; -use crate::tools::fd::Fd; +use crate::error::SysError; +use crate::fd::Fd; /// Get the current size of the terminal (for stdout). /// # Safety diff --git a/proxmox-sys/src/logrotate.rs b/proxmox-sys/src/logrotate.rs index 1062c0a3..483b05a6 100644 --- a/proxmox-sys/src/logrotate.rs +++ b/proxmox-sys/src/logrotate.rs @@ -6,7 +6,7 @@ use std::io::Read; use anyhow::{bail, Error}; use nix::unistd; -use proxmox::tools::fs::{CreateOptions, make_tmp_file}; +use crate::fs::{CreateOptions, make_tmp_file}; /// Used for rotating log files and iterating over them pub struct LogRotate { diff --git a/proxmox/src/sys/macros.rs b/proxmox-sys/src/macros.rs similarity index 100% rename from proxmox/src/sys/macros.rs rename to proxmox-sys/src/macros.rs diff --git a/proxmox/src/tools/mmap.rs b/proxmox-sys/src/mmap.rs similarity index 98% rename from proxmox/src/tools/mmap.rs rename to proxmox-sys/src/mmap.rs index 1ed84d1f..cfbf9f7d 100644 --- a/proxmox/src/tools/mmap.rs +++ b/proxmox-sys/src/mmap.rs @@ -7,7 +7,7 @@ use std::{io, mem, ptr}; use nix::sys::mman; -use crate::sys::error::{io_err_other, SysError}; +use crate::error::{io_err_other, SysError}; pub struct Mmap { data: *mut T, diff --git a/proxmox/src/lib.rs b/proxmox/src/lib.rs index 88bdbc01..414e7aa9 100644 --- a/proxmox/src/lib.rs +++ b/proxmox/src/lib.rs @@ -4,7 +4,6 @@ #[macro_use] pub mod serde_macros; -pub mod sys; pub mod tools; /// An identity (nop) macro. Used by the `#[sortable]` proc macro. diff --git a/proxmox/src/sys/mod.rs b/proxmox/src/sys/mod.rs deleted file mode 100644 index 1b03e735..00000000 --- a/proxmox/src/sys/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! This is a system utility crate used by all our rust projects. - -pub mod macros; - -pub mod error; -pub mod linux; -pub mod timer; diff --git a/proxmox/src/tools/mod.rs b/proxmox/src/tools/mod.rs index 76b76d0a..1b94368b 100644 --- a/proxmox/src/tools/mod.rs +++ b/proxmox/src/tools/mod.rs @@ -9,10 +9,6 @@ use proxmox_io::vec; pub mod common_regex; pub mod email; -pub mod fd; -pub mod fs; -pub mod mmap; -pub mod parse; pub mod serde; pub mod systemd; diff --git a/proxmox/src/tools/parse.rs b/proxmox/src/tools/parse.rs deleted file mode 100644 index 0986e7ac..00000000 --- a/proxmox/src/tools/parse.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Some parsing utilities. - -use anyhow::{bail, Error}; - -/// Parse a hexadecimal digit into a byte. -#[inline] -pub fn hex_nibble(c: u8) -> Result { - Ok(match c { - b'0'..=b'9' => c - b'0', - b'a'..=b'f' => c - b'a' + 0xa, - b'A'..=b'F' => c - b'A' + 0xa, - _ => bail!("not a hex digit: {}", c as char), - }) -}