]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/tools.rs
verify jobs: add permissions
[proxmox-backup.git] / src / tools.rs
index 17f867bf49f771df22bfd38757d44ddd94a0c027..5535b8cbe58931c9ec6a5d2b78a7753a0d5ad9be 100644 (file)
@@ -4,12 +4,10 @@
 use std::any::Any;
 use std::collections::HashMap;
 use std::hash::BuildHasher;
-use std::fs::{File, OpenOptions};
-use std::io::ErrorKind;
-use std::io::Read;
-use std::os::unix::io::{AsRawFd, RawFd};
+use std::fs::File;
+use std::io::{self, BufRead, ErrorKind, Read, Seek, SeekFrom};
+use std::os::unix::io::RawFd;
 use std::path::Path;
-use std::time::Duration;
 
 use anyhow::{bail, format_err, Error};
 use serde_json::Value;
@@ -23,6 +21,7 @@ pub use proxmox::tools::fd::Fd;
 pub mod acl;
 pub mod async_io;
 pub mod borrow;
+pub mod cert;
 pub mod daemon;
 pub mod disks;
 pub mod fs;
@@ -30,11 +29,27 @@ pub mod format;
 pub mod lru_cache;
 pub mod runtime;
 pub mod ticket;
-pub mod timer;
+pub mod statistics;
+pub mod systemd;
+pub mod nom;
+pub mod logrotate;
+pub mod loopdev;
+pub mod fuse_loop;
+pub mod socket;
+pub mod subscription;
+pub mod zip;
+pub mod http;
+
+mod parallel_handler;
+pub use parallel_handler::*;
 
 mod wrapped_reader_stream;
 pub use wrapped_reader_stream::*;
 
+mod async_channel_writer;
+pub use async_channel_writer::*;
+
+
 mod std_channel_writer;
 pub use std_channel_writer::*;
 
@@ -59,86 +74,6 @@ 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<T>(buffer: &[u8]) -> Result<&T, Error> {
-    if buffer.len() < ::std::mem::size_of::<T>() {
-        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<T>(buffer: &mut [u8]) -> Result<&mut T, Error> {
-    if buffer.len() < ::std::mem::size_of::<T>() {
-        bail!("unable to map struct - buffer too small");
-    }
-    Ok(unsafe { &mut *(buffer.as_ptr() as *mut T) })
-}
-
-/// 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<F: AsRawFd>(
-    file: &mut F,
-    exclusive: bool,
-    timeout: Option<Duration>,
-) -> 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<P: AsRef<Path>>(path: P, timeout: Duration) -> Result<File, Error> {
-    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)
@@ -390,10 +325,12 @@ pub fn md5sum(data: &[u8]) -> Result<DigestBytes, Error> {
 pub fn get_hardware_address() -> Result<String, Error> {
     static FILENAME: &str = "/etc/ssh/ssh_host_rsa_key.pub";
 
-    let contents = proxmox::tools::fs::file_get_contents(FILENAME)?;
-    let digest = md5sum(&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(proxmox::tools::bin_to_hex(&digest))
+    Ok(proxmox::tools::bin_to_hex(&digest).to_uppercase())
 }
 
 pub fn assert_if_modified(digest1: &str, digest2: &str) -> Result<(), Error> {
@@ -403,9 +340,9 @@ pub fn assert_if_modified(digest1: &str, digest2: &str) -> Result<(), Error> {
     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<String> {
+pub fn extract_cookie(cookie: &str, cookie_name: &str) -> Option<String> {
     for pair in cookie.split(';') {
         let (name, value) = match pair.find('=') {
             Some(i) => (pair[..i].trim(), pair[(i + 1)..].trim()),
@@ -440,7 +377,7 @@ pub fn join(data: &Vec<String>, sep: char) -> String {
 
 /// Detect modified configuration files
 ///
-/// This function fails with a resonable error message if checksums do not match.
+/// 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.");
@@ -473,6 +410,64 @@ 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<fn(i32) -> bool>,
+) -> Result<Vec<u8>, 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<fn(i32) -> bool>,
+) -> Result<String, Error> {
+    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<fn(i32) -> bool>,
+) -> Result<String, Error> {
+
+   let output = command.output()
+        .map_err(|err| format_err!("failed to execute {:?} - {}", command, err))?;
+
+    let output = crate::tools::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, FdFlag, F_GETFD, F_SETFD};
     let mut flags = FdFlag::from_bits(fcntl(fd, F_GETFD)?)
@@ -503,12 +498,27 @@ pub fn fail_on_shutdown() -> Result<(), Error> {
     Ok(())
 }
 
-// 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).
@@ -537,3 +547,75 @@ pub const DEFAULT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS // 0..1f a
     .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<P: AsRef<Path>>(
+    path: P,
+) -> Result<impl Iterator<Item = io::Result<String>>, 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)),
+    }))
+}
+
+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);
+    }
+}
+
+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 => &[],
+    }
+}
+
+/// Seeks to start of file and computes the SHA256 hash
+pub fn compute_file_csum(file: &mut File) -> Result<([u8; 32], u64), Error> {
+
+    file.seek(SeekFrom::Start(0))?;
+
+    let mut hasher = openssl::sha::Sha256::new();
+    let mut buffer = proxmox::tools::vec::undefined(256*1024);
+    let mut size: u64 = 0;
+
+    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;
+        }
+        size += count as u64;
+        hasher.update(&buffer[..count]);
+    }
+
+    let csum = hasher.finish();
+
+    Ok((csum, size))
+}