X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2Ftools.rs;h=a9b3378d90a8a71484660781e133c995a344fd3d;hb=3a3f31c947bbe3daf2680293e46ac8728f196bdc;hp=74885d354288eeef1e5f46a9e504f22036eb1fbb;hpb=9110a69bd83118bdf0a2609f7788f5946c3909f7;p=proxmox-backup.git diff --git a/src/tools.rs b/src/tools.rs index 74885d35..a9b3378d 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -1,76 +1,65 @@ //! Tools and utilities //! //! This is a collection of small and useful tools. -use failure::*; -use nix::unistd; -use nix::sys::stat; -use nix::{convert_ioctl_res, request_code_read, ioc}; - -use lazy_static::lazy_static; - -use std::fs::{File, OpenOptions}; -use std::io::Write; -use std::path::Path; -use std::io::Read; -use std::io::ErrorKind; -use std::time::Duration; use std::any::Any; - -use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; - use std::collections::HashMap; +use std::hash::BuildHasher; +use std::fs::File; +use std::io::{self, BufRead, Read, Seek, SeekFrom}; +use std::os::unix::io::RawFd; +use std::path::Path; +use anyhow::{bail, format_err, Error}; use serde_json::Value; +use openssl::hash::{hash, DigestBytes, MessageDigest}; +use percent_encoding::AsciiSet; -pub mod async_mutex; -pub mod timer; -pub mod wrapped_reader_stream; -#[macro_use] -pub mod common_regex; -pub mod ticket; +pub use proxmox::tools::fd::Fd; + +pub mod acl; +pub mod apt; +pub mod async_io; pub mod borrow; -pub mod fs; -pub mod tty; -pub mod signalfd; +pub mod cert; pub mod daemon; -pub mod procfs; -pub mod acl; +pub mod disks; +pub mod format; +pub mod fs; +pub mod fuse_loop; +pub mod http; +pub mod logrotate; +pub mod loopdev; +pub mod lru_cache; +pub mod nom; +pub mod runtime; +pub mod socket; +pub mod statistics; +pub mod subscription; +pub mod systemd; +pub mod ticket; pub mod xattr; -pub mod vec; -pub mod io; -pub mod futures; +pub mod zip; + +pub mod parallel_handler; +pub use parallel_handler::ParallelHandler; + +mod wrapped_reader_stream; +pub use wrapped_reader_stream::{AsyncReaderStream, StdChannelStream, WrappedReaderStream}; + +mod async_channel_writer; +pub use async_channel_writer::AsyncChannelWriter; + +mod std_channel_writer; +pub use std_channel_writer::StdChannelWriter; mod process_locker; -pub use process_locker::*; +pub use process_locker::{ProcessLocker, ProcessLockExclusiveGuard, ProcessLockSharedGuard}; -#[macro_use] mod file_logger; -pub use file_logger::*; +pub use file_logger::{FileLogger, FileLogOptions}; mod broadcast_future; -pub use broadcast_future::*; - -/// Macro to write error-handling blocks (like perl eval {}) -/// -/// #### Example: -/// ``` -/// # #[macro_use] extern crate proxmox_backup; -/// # use failure::*; -/// # let some_condition = false; -/// let result = try_block!({ -/// if (some_condition) { -/// bail!("some error"); -/// } -/// Ok(()) -/// }) -/// .map_err(|e| format_err!("my try block returned an error - {}", e)); -/// ``` - -#[macro_export] -macro_rules! try_block { - { $($token:tt)* } => {{ (|| -> Result<_,_> { $($token)* })() }} -} - +pub use broadcast_future::{BroadcastData, BroadcastFuture}; /// The `BufferedRead` trait provides a single function /// `buffered_read`. It returns a reference to an internal buffer. The @@ -82,350 +71,7 @@ pub trait BufferedRead { fn buffered_read(&mut self, offset: u64) -> Result<&[u8], Error>; } -/// Directly map a type into a binary buffer. This is mostly useful -/// for reading structured data from a byte stream (file). You need to -/// make sure that the buffer location does not change, so please -/// avoid vec resize while you use such map. -/// -/// This function panics if the buffer is not large enough. -pub fn map_struct(buffer: &[u8]) -> Result<&T, Error> { - if buffer.len() < ::std::mem::size_of::() { - bail!("unable to map struct - buffer too small"); - } - Ok(unsafe { & * (buffer.as_ptr() as *const T) }) -} - -/// Directly map a type into a mutable binary buffer. This is mostly -/// useful for writing structured data into a byte stream (file). You -/// need to make sure that the buffer location does not change, so -/// please avoid vec resize while you use such map. -/// -/// This function panics if the buffer is not large enough. -pub fn map_struct_mut(buffer: &mut [u8]) -> Result<&mut T, Error> { - if buffer.len() < ::std::mem::size_of::() { - bail!("unable to map struct - buffer too small"); - } - Ok(unsafe { &mut * (buffer.as_ptr() as *mut T) }) -} - -pub fn file_read_firstline>(path: P) -> Result { - - let path = path.as_ref(); - - try_block!({ - let file = std::fs::File::open(path)?; - - use std::io::{BufRead, BufReader}; - - let mut reader = BufReader::new(file); - - let mut line = String::new(); - - let _ = reader.read_line(&mut line)?; - - Ok(line) - }).map_err(|err: Error| format_err!("unable to read {:?} - {}", path, err)) -} - -pub fn file_get_contents>(path: P) -> Result, Error> { - - let path = path.as_ref(); - - try_block!({ - std::fs::read(path) - }).map_err(|err| format_err!("unable to read {:?} - {}", path, err)) -} - -pub fn file_get_json>(path: P, default: Option) -> Result { - - let path = path.as_ref(); - - let raw = match std::fs::read(path) { - Ok(v) => v, - Err(err) => { - if err.kind() == std::io::ErrorKind::NotFound { - if let Some(v) = default { - return Ok(v); - } - } - bail!("unable to read json {:?} - {}", path, err); - } - }; - - try_block!({ - let data = String::from_utf8(raw)?; - let json = serde_json::from_str(&data)?; - Ok(json) - }).map_err(|err: Error| format_err!("unable to parse json from {:?} - {}", path, err)) -} - -/// Atomically write a file -/// -/// We first create a temporary file, which is then renamed. -pub fn file_set_contents>( - path: P, - data: &[u8], - perm: Option, -) -> Result<(), Error> { - file_set_contents_full(path, data, perm, None, None) -} - -/// Atomically write a file with owner and group -pub fn file_set_contents_full>( - path: P, - data: &[u8], - perm: Option, - owner: Option, - group: Option, -) -> Result<(), Error> { - - let path = path.as_ref(); - - // Note: we use mkstemp heŕe, because this worka with different - // processes, threads, and even tokio tasks. - let mut template = path.to_owned(); - template.set_extension("tmp_XXXXXX"); - let (fd, tmp_path) = match unistd::mkstemp(&template) { - Ok((fd, path)) => (fd, path), - Err(err) => bail!("mkstemp {:?} failed: {}", template, err), - }; - - let tmp_path = tmp_path.as_path(); - - let mode : stat::Mode = perm.unwrap_or(stat::Mode::from( - stat::Mode::S_IRUSR | stat::Mode::S_IWUSR | - stat::Mode::S_IRGRP | stat::Mode::S_IROTH - )); - - if let Err(err) = stat::fchmod(fd, mode) { - let _ = unistd::unlink(tmp_path); - bail!("fchmod {:?} failed: {}", tmp_path, err); - } - - if owner != None || group != None { - if let Err(err) = fchown(fd, owner, group) { - let _ = unistd::unlink(tmp_path); - bail!("fchown {:?} failed: {}", tmp_path, err); - } - } - - let mut file = unsafe { File::from_raw_fd(fd) }; - - if let Err(err) = file.write_all(data) { - let _ = unistd::unlink(tmp_path); - bail!("write failed: {}", err); - } - - if let Err(err) = std::fs::rename(tmp_path, path) { - let _ = unistd::unlink(tmp_path); - bail!("Atomic rename failed for file {:?} - {}", path, err); - } - - Ok(()) -} - -/// Create a file lock using fntl. This function allows you to specify -/// a timeout if you want to avoid infinite blocking. -pub fn lock_file( - file: &mut F, - exclusive: bool, - timeout: Option, -) -> Result<(), Error> { - let lockarg = - if exclusive { - nix::fcntl::FlockArg::LockExclusive - } else { - nix::fcntl::FlockArg::LockShared - }; - - let timeout = match timeout { - None => { - nix::fcntl::flock(file.as_raw_fd(), lockarg)?; - return Ok(()); - } - Some(t) => t, - }; - - // unblock the timeout signal temporarily - let _sigblock_guard = timer::unblock_timeout_signal(); - - // setup a timeout timer - let mut timer = timer::Timer::create( - timer::Clock::Realtime, - timer::TimerEvent::ThisThreadSignal(timer::SIGTIMEOUT))?; - - timer.arm(timer::TimerSpec::new() - .value(Some(timeout)) - .interval(Some(Duration::from_millis(10))))?; - - nix::fcntl::flock(file.as_raw_fd(), lockarg)?; - Ok(()) -} - -/// Open or create a lock file (append mode). Then try to -/// aquire a lock using `lock_file()`. -pub fn open_file_locked>(path: P, timeout: Duration) - -> Result -{ - let path = path.as_ref(); - let mut file = - match OpenOptions::new() - .create(true) - .append(true) - .open(path) - { - Ok(file) => file, - Err(err) => bail!("Unable to open lock {:?} - {}", - path, err), - }; - match lock_file(&mut file, true, Some(timeout)) { - Ok(_) => Ok(file), - Err(err) => bail!("Unable to aquire lock {:?} - {}", - path, err), - } -} - -/// Split a file into equal sized chunks. The last chunk may be -/// smaller. Note: We cannot implement an `Iterator`, because iterators -/// cannot return a borrowed buffer ref (we want zero-copy) -pub fn file_chunker( - mut file: R, - chunk_size: usize, - mut chunk_cb: C -) -> Result<(), Error> - where C: FnMut(usize, &[u8]) -> Result, - R: Read, -{ - - const READ_BUFFER_SIZE: usize = 4*1024*1024; // 4M - - if chunk_size > READ_BUFFER_SIZE { bail!("chunk size too large!"); } - - let mut buf = vec::undefined(READ_BUFFER_SIZE); - - let mut pos = 0; - let mut file_pos = 0; - loop { - let mut eof = false; - let mut tmp = &mut buf[..]; - // try to read large portions, at least chunk_size - while pos < chunk_size { - match file.read(tmp) { - Ok(0) => { eof = true; break; }, - Ok(n) => { - pos += n; - if pos > chunk_size { break; } - tmp = &mut tmp[n..]; - } - Err(ref e) if e.kind() == ErrorKind::Interrupted => { /* try again */ } - Err(e) => bail!("read chunk failed - {}", e.to_string()), - } - } - let mut start = 0; - while start + chunk_size <= pos { - if !(chunk_cb)(file_pos, &buf[start..start+chunk_size])? { break; } - file_pos += chunk_size; - start += chunk_size; - } - if eof { - if start < pos { - (chunk_cb)(file_pos, &buf[start..pos])?; - //file_pos += pos - start; - } - break; - } else { - let rest = pos - start; - if rest > 0 { - let ptr = buf.as_mut_ptr(); - unsafe { std::ptr::copy_nonoverlapping(ptr.add(start), ptr, rest); } - pos = rest; - } else { - pos = 0; - } - } - } - - Ok(()) -} - -/// Returns the Unix uid/gid for the sepcified system user. -pub fn getpwnam_ugid(username: &str) -> Result<(libc::uid_t,libc::gid_t), Error> { - let info = unsafe { libc::getpwnam(std::ffi::CString::new(username).unwrap().as_ptr()) }; - if info == std::ptr::null_mut() { - bail!("getwpnam '{}' failed", username); - } - - let info = unsafe { *info }; - - Ok((info.pw_uid, info.pw_gid)) -} - -/// Creates directory at the provided path with specified ownership -/// -/// Simply returns if the directory already exists. -pub fn create_dir_chown>( - path: P, - perm: Option, - owner: Option, - group: Option, -) -> Result<(), nix::Error> -{ - let mode : stat::Mode = perm.unwrap_or(stat::Mode::from_bits_truncate(0o770)); - - let path = path.as_ref(); - - match nix::unistd::mkdir(path, mode) { - Ok(()) => {}, - Err(nix::Error::Sys(nix::errno::Errno::EEXIST)) => { - return Ok(()); - }, - err => return err, - } - - unistd::chown(path, owner, group)?; - - Ok(()) -} - -/// 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).wrapping_sub(1)); - let gid = group.map(Into::into).unwrap_or((0 as libc::gid_t).wrapping_sub(1)); - - let res = unsafe { libc::fchown(fd, uid, gid) }; - nix::errno::Errno::result(res)?; - - Ok(()) -} - -// Returns the hosts node name (UTS node name) -pub fn nodename() -> &'static str { - - lazy_static!{ - static ref NODENAME: String = { - - nix::sys::utsname::uname() - .nodename() - .split('.') - .next() - .unwrap() - .to_owned() - }; - } - - &NODENAME -} - pub fn json_object_to_query(data: Value) -> Result { - let mut query = url::form_urlencoded::Serializer::new(String::new()); let object = data.as_object().ok_or_else(|| { @@ -434,16 +80,30 @@ pub fn json_object_to_query(data: Value) -> Result { for (key, value) in object { match value { - Value::Bool(b) => { query.append_pair(key, &b.to_string()); } - Value::Number(n) => { query.append_pair(key, &n.to_string()); } - Value::String(s) => { query.append_pair(key, &s); } + Value::Bool(b) => { + query.append_pair(key, &b.to_string()); + } + Value::Number(n) => { + query.append_pair(key, &n.to_string()); + } + Value::String(s) => { + query.append_pair(key, &s); + } Value::Array(arr) => { for element in arr { match element { - Value::Bool(b) => { query.append_pair(key, &b.to_string()); } - Value::Number(n) => { query.append_pair(key, &n.to_string()); } - Value::String(s) => { query.append_pair(key, &s); } - _ => bail!("json_object_to_query: unable to handle complex array data types."), + Value::Bool(b) => { + query.append_pair(key, &b.to_string()); + } + Value::Number(n) => { + query.append_pair(key, &n.to_string()); + } + Value::String(s) => { + query.append_pair(key, &s); + } + _ => bail!( + "json_object_to_query: unable to handle complex array data types." + ), } } } @@ -455,35 +115,55 @@ pub fn json_object_to_query(data: Value) -> Result { } pub fn required_string_param<'a>(param: &'a Value, name: &str) -> Result<&'a str, Error> { - match param[name].as_str() { + match param[name].as_str() { Some(s) => Ok(s), None => bail!("missing parameter '{}'", name), } } +pub fn required_string_property<'a>(param: &'a Value, name: &str) -> Result<&'a str, Error> { + match param[name].as_str() { + Some(s) => Ok(s), + None => bail!("missing property '{}'", name), + } +} + pub fn required_integer_param<'a>(param: &'a Value, name: &str) -> Result { - match param[name].as_i64() { + match param[name].as_i64() { Some(s) => Ok(s), None => bail!("missing parameter '{}'", name), } } +pub fn required_integer_property<'a>(param: &'a Value, name: &str) -> Result { + match param[name].as_i64() { + Some(s) => Ok(s), + None => bail!("missing property '{}'", name), + } +} + pub fn required_array_param<'a>(param: &'a Value, name: &str) -> Result, Error> { - match param[name].as_array() { + match param[name].as_array() { Some(s) => Ok(s.to_vec()), None => bail!("missing parameter '{}'", name), } } -pub fn complete_file_name(arg: &str, _param: &HashMap) -> Vec { +pub fn required_array_property<'a>(param: &'a Value, name: &str) -> Result, Error> { + match param[name].as_array() { + Some(s) => Ok(s.to_vec()), + None => bail!("missing property '{}'", name), + } +} +pub fn complete_file_name(arg: &str, _param: &HashMap) -> Vec { let mut result = vec![]; + use nix::fcntl::AtFlags; use nix::fcntl::OFlag; use nix::sys::stat::Mode; - use nix::fcntl::AtFlags; - let mut dirname = std::path::PathBuf::from(if arg.len() == 0 { "./" } else { arg }); + let mut dirname = std::path::PathBuf::from(if arg.is_empty() { "./" } else { arg }); let is_dir = match nix::sys::stat::fstatat(libc::AT_FDCWD, &dirname, AtFlags::empty()) { Ok(stat) => (stat.st_mode & libc::S_IFMT) == libc::S_IFDIR, @@ -496,32 +176,36 @@ pub fn complete_file_name(arg: &str, _param: &HashMap) -> Vec d, - Err(_) => return result, - }; + let mut dir = + match nix::dir::Dir::openat(libc::AT_FDCWD, &dirname, OFlag::O_DIRECTORY, Mode::empty()) { + Ok(d) => d, + Err(_) => return result, + }; for item in dir.iter() { if let Ok(entry) = item { if let Ok(name) = entry.file_name().to_str() { - if name == "." || name == ".." { continue; } + if name == "." || name == ".." { + continue; + } let mut newpath = dirname.clone(); newpath.push(name); - if let Ok(stat) = nix::sys::stat::fstatat(libc::AT_FDCWD, &newpath, AtFlags::empty()) { + if let Ok(stat) = + nix::sys::stat::fstatat(libc::AT_FDCWD, &newpath, AtFlags::empty()) + { if (stat.st_mode & libc::S_IFMT) == libc::S_IFDIR { newpath.push(""); if let Some(newpath) = newpath.to_str() { result.push(newpath.to_owned()); } continue; - } + } } if let Some(newpath) = newpath.to_str() { result.push(newpath.to_owned()); } - - } + } } } @@ -538,10 +222,11 @@ pub fn scandir( dirfd: RawFd, path: &P, regex: ®ex::Regex, - mut callback: F + mut callback: F, ) -> Result<(), Error> - where F: FnMut(RawFd, &str, nix::dir::Type) -> Result<(), Error>, - P: ?Sized + nix::NixPath, +where + F: FnMut(RawFd, &str, nix::dir::Type) -> Result<(), Error>, + P: ?Sized + nix::NixPath, { for entry in self::fs::scan_subdir(dirfd, path, regex)? { let entry = entry?; @@ -550,42 +235,49 @@ pub fn scandir( None => bail!("unable to detect file type"), }; - callback(entry.parent_fd(), unsafe { entry.file_name_utf8_unchecked() }, file_type)?; + callback( + entry.parent_fd(), + unsafe { entry.file_name_utf8_unchecked() }, + file_type, + )?; } Ok(()) } -pub fn get_hardware_address() -> Result { +/// Shortcut for md5 sums. +pub fn md5sum(data: &[u8]) -> Result { + hash(MessageDigest::md5(), data).map_err(Error::from) +} +pub fn get_hardware_address() -> Result { static FILENAME: &str = "/etc/ssh/ssh_host_rsa_key.pub"; - let contents = file_get_contents(FILENAME)?; - let digest = md5::compute(contents); + let contents = proxmox::tools::fs::file_get_contents(FILENAME) + .map_err(|e| format_err!("Error getting host key - {}", e))?; + let digest = md5sum(&contents) + .map_err(|e| format_err!("Error digesting host key - {}", e))?; - Ok(format!("{:0x}", digest)) + Ok(proxmox::tools::bin_to_hex(&digest).to_uppercase()) } - pub fn assert_if_modified(digest1: &str, digest2: &str) -> Result<(), Error> { if digest1 != digest2 { - bail!("detected modified configuration - file changed by other user? Try again."); + bail!("detected modified configuration - file changed by other user? Try again."); } Ok(()) } -/// Extract authentication cookie from cookie header. +/// Extract a specific cookie from cookie header. /// We assume cookie_name is already url encoded. -pub fn extract_auth_cookie(cookie: &str, cookie_name: &str) -> Option { - +pub fn extract_cookie(cookie: &str, cookie_name: &str) -> Option { for pair in cookie.split(';') { - let (name, value) = match pair.find('=') { Some(i) => (pair[..i].trim(), pair[(i + 1)..].trim()), None => return None, // Cookie format error }; if name == cookie_name { - use url::percent_encoding::percent_decode; + use percent_encoding::percent_decode; if let Ok(value) = percent_decode(value.as_bytes()).decode_utf8() { return Some(value.into()); } else { @@ -598,31 +290,43 @@ pub fn extract_auth_cookie(cookie: &str, cookie_name: &str) -> Option { } pub fn join(data: &Vec, sep: char) -> String { - let mut list = String::new(); for item in data { - if list.len() != 0 { list.push(sep); } + if !list.is_empty() { + list.push(sep); + } list.push_str(item); } list } +/// Detect modified configuration files +/// +/// This function fails with a reasonable error message if checksums do not match. +pub fn detect_modified_configuration_file(digest1: &[u8;32], digest2: &[u8;32]) -> Result<(), Error> { + if digest1 != digest2 { + bail!("detected modified configuration - file changed by other user? Try again."); + } + Ok(()) +} + /// normalize uri path /// /// Do not allow ".", "..", or hidden files ".XXXX" /// Also remove empty path components pub fn normalize_uri_path(path: &str) -> Result<(String, Vec<&str>), Error> { - let items = path.split('/'); let mut path = String::new(); let mut components = vec![]; for name in items { - if name.is_empty() { continue; } - if name.starts_with(".") { + if name.is_empty() { + continue; + } + if name.starts_with('.') { bail!("Path contains illegal components."); } path.push('/'); @@ -633,8 +337,66 @@ pub fn normalize_uri_path(path: &str) -> Result<(String, Vec<&str>), Error> { Ok((path, components)) } +/// Helper to check result from std::process::Command output +/// +/// The exit_code_check() function should return true if the exit code +/// is considered successful. +pub fn command_output( + output: std::process::Output, + exit_code_check: Option bool>, +) -> Result, Error> { + + if !output.status.success() { + match output.status.code() { + Some(code) => { + let is_ok = match exit_code_check { + Some(check_fn) => check_fn(code), + None => code == 0, + }; + if !is_ok { + let msg = String::from_utf8(output.stderr) + .map(|m| if m.is_empty() { String::from("no error message") } else { m }) + .unwrap_or_else(|_| String::from("non utf8 error message (suppressed)")); + + bail!("status code: {} - {}", code, msg); + } + } + None => bail!("terminated by signal"), + } + } + + Ok(output.stdout) +} + +/// Helper to check result from std::process::Command output, returns String. +/// +/// The exit_code_check() function should return true if the exit code +/// is considered successful. +pub fn command_output_as_string( + output: std::process::Output, + exit_code_check: Option bool>, +) -> Result { + let output = command_output(output, exit_code_check)?; + let output = String::from_utf8(output)?; + Ok(output) +} + +pub fn run_command( + mut command: std::process::Command, + exit_code_check: Option bool>, +) -> Result { + + let output = command.output() + .map_err(|err| format_err!("failed to execute {:?} - {}", command, err))?; + + let output = command_output_as_string(output, exit_code_check) + .map_err(|err| format_err!("command {:?} failed - {}", command, err))?; + + Ok(output) +} + pub fn fd_change_cloexec(fd: RawFd, on: bool) -> Result<(), Error> { - use nix::fcntl::{fcntl, F_GETFD, F_SETFD, FdFlag}; + use nix::fcntl::{fcntl, FdFlag, F_GETFD, F_SETFD}; let mut flags = FdFlag::from_bits(fcntl(fd, F_GETFD)?) .ok_or_else(|| format_err!("unhandled file flags"))?; // nix crate is stupid this way... flags.set(FdFlag::FD_CLOEXEC, on); @@ -642,11 +404,12 @@ pub fn fd_change_cloexec(fd: RawFd, on: bool) -> Result<(), Error> { Ok(()) } - static mut SHUTDOWN_REQUESTED: bool = false; pub fn request_shutdown() { - unsafe { SHUTDOWN_REQUESTED = true; } + unsafe { + SHUTDOWN_REQUESTED = true; + } crate::server::server_shutdown(); } @@ -662,47 +425,27 @@ pub fn fail_on_shutdown() -> Result<(), Error> { Ok(()) } -/// 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 -/// descriptor number is required in a closure which may be dropped instead of being executed). -pub struct Fd(pub RawFd); - -impl Drop for Fd { - fn drop(&mut self) { - if self.0 != -1 { - unsafe { - libc::close(self.0); - } - } - } -} - -impl AsRawFd for Fd { - fn as_raw_fd(&self) -> RawFd { - self.0 - } -} - -impl IntoRawFd for Fd { - fn into_raw_fd(mut self) -> RawFd { - let fd = self.0; - self.0 = -1; - fd - } -} - -impl FromRawFd for Fd { - unsafe fn from_raw_fd(fd: RawFd) -> Self { - Self(fd) - } -} - -// wrap nix::unistd::pipe2 + O_CLOEXEC into something returning guarded file descriptors +/// safe wrapper for `nix::unistd::pipe2` defaulting to `O_CLOEXEC` and guarding the file +/// descriptors. pub fn pipe() -> Result<(Fd, Fd), Error> { let (pin, pout) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?; Ok((Fd(pin), Fd(pout))) } +/// safe wrapper for `nix::sys::socket::socketpair` defaulting to `O_CLOEXEC` and guarding the file +/// descriptors. +pub fn socketpair() -> Result<(Fd, Fd), Error> { + use nix::sys::socket; + let (pa, pb) = socket::socketpair( + socket::AddressFamily::Unix, + socket::SockType::Stream, + None, + socket::SockFlag::SOCK_CLOEXEC, + )?; + Ok((Fd(pa), Fd(pb))) +} + + /// An easy way to convert types to Any /// /// Mostly useful to downcast trait objects (see RpcEnvironment). @@ -711,34 +454,95 @@ pub trait AsAny { } impl AsAny for T { - fn as_any(&self) -> &dyn Any { self } + fn as_any(&self) -> &dyn Any { + self + } } +/// This used to be: `SIMPLE_ENCODE_SET` plus space, `"`, `#`, `<`, `>`, backtick, `?`, `{`, `}` +pub const DEFAULT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS // 0..1f and 7e + // The SIMPLE_ENCODE_SET adds space and anything >= 0x7e (7e itself is already included above) + .add(0x20) + .add(0x7f) + // the DEFAULT_ENCODE_SET added: + .add(b' ') + .add(b'"') + .add(b'#') + .add(b'<') + .add(b'>') + .add(b'`') + .add(b'?') + .add(b'{') + .add(b'}'); + +/// Get an iterator over lines of a file, skipping empty lines and comments (lines starting with a +/// `#`). +pub fn file_get_non_comment_lines>( + path: P, +) -> Result>, Error> { + let path = path.as_ref(); + + Ok(io::BufReader::new( + File::open(path).map_err(|err| format_err!("error opening {:?}: {}", path, err))?, + ) + .lines() + .filter_map(|line| match line { + Ok(line) => { + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + None + } else { + Some(Ok(line.to_string())) + } + } + Err(err) => Some(Err(err)), + })) +} -// /usr/include/linux/fs.h: #define BLKGETSIZE64 _IOR(0x12,114,size_t) -// return device size in bytes (u64 *arg) -nix::ioctl_read!(blkgetsize64, 0x12, 114, u64); +pub fn setup_safe_path_env() { + std::env::set_var("PATH", "/sbin:/bin:/usr/sbin:/usr/bin"); + // Make %ENV safer - as suggested by https://perldoc.perl.org/perlsec.html + for name in &["IFS", "CDPATH", "ENV", "BASH_ENV"] { + std::env::remove_var(name); + } +} -/// Return file or block device size -pub fn image_size(path: &Path) -> Result { +pub fn strip_ascii_whitespace(line: &[u8]) -> &[u8] { + let line = match line.iter().position(|&b| !b.is_ascii_whitespace()) { + Some(n) => &line[n..], + None => return &[], + }; + match line.iter().rev().position(|&b| !b.is_ascii_whitespace()) { + Some(n) => &line[..(line.len() - n)], + None => &[], + } +} - use std::os::unix::fs::FileTypeExt; +/// Seeks to start of file and computes the SHA256 hash +pub fn compute_file_csum(file: &mut File) -> Result<([u8; 32], u64), Error> { - let file = std::fs::File::open(path)?; - let metadata = file.metadata()?; - let file_type = metadata.file_type(); + file.seek(SeekFrom::Start(0))?; - if file_type.is_block_device() { - let mut size : u64 = 0; - let res = unsafe { blkgetsize64(file.as_raw_fd(), &mut size) }; + let mut hasher = openssl::sha::Sha256::new(); + let mut buffer = proxmox::tools::vec::undefined(256*1024); + let mut size: u64 = 0; - if let Err(err) = res { - bail!("blkgetsize64 failed for {:?} - {}", path, err); + loop { + let count = match file.read(&mut buffer) { + Ok(count) => count, + Err(ref err) if err.kind() == std::io::ErrorKind::Interrupted => { + continue; + } + Err(err) => return Err(err.into()), + }; + if count == 0 { + break; } - Ok(size) - } else if file_type.is_file() { - Ok(metadata.len()) - } else { - bail!("image size failed - got unexpected file type {:?}", file_type); + size += count as u64; + hasher.update(&buffer[..count]); } + + let csum = hasher.finish(); + + Ok((csum, size)) }