]> git.proxmox.com Git - proxmox-backup.git/blame - src/tools/mod.rs
move client to pbs-client subcrate
[proxmox-backup.git] / src / tools / mod.rs
CommitLineData
51b499db
DM
1//! Tools and utilities
2//!
3//! This is a collection of small and useful tools.
6100071f 4use std::any::Any;
98c259b4 5use std::fs::File;
ba0ccc59 6use std::io::{self, BufRead};
98c259b4 7use std::os::unix::io::RawFd;
6100071f 8use std::path::Path;
365bb90f 9
f7d4e4b5 10use anyhow::{bail, format_err, Error};
af926291 11use serde_json::Value;
c5946faf 12use openssl::hash::{hash, DigestBytes, MessageDigest};
0fe5d605 13
00ec8d16 14pub use proxmox::tools::fd::Fd;
95f36925 15use proxmox::tools::fs::{create_path, CreateOptions};
00ec8d16 16
1d781c5b 17use proxmox_http::{
7d2be91b
FG
18 client::SimpleHttp,
19 client::SimpleHttpOptions,
20 ProxyConfig,
21};
57889533 22
a5951b4f 23pub use pbs_tools::json;
18cdf20a 24pub use pbs_tools::nom;
e57841c4 25pub use pbs_tools::{run_command, command_output, command_output_as_string};
83771aa0
WB
26pub use pbs_tools::process_locker::{
27 ProcessLocker, ProcessLockExclusiveGuard, ProcessLockSharedGuard
28};
e57841c4 29
e6513bd5 30pub mod apt;
556eb70e 31pub mod async_io;
ea62611d 32pub mod compression;
bc5c1a9a 33pub mod config;
a5bdc987 34pub mod cpio;
6100071f 35pub mod daemon;
10effc98 36pub mod disks;
fb01fd3a 37pub mod fuse_loop;
e5ef69ec 38
fda19dcc
DM
39mod memcom;
40pub use memcom::Memcom;
41
8074d2b0 42pub mod logrotate;
45f9b32e 43pub mod loopdev;
fb01fd3a 44pub mod lru_cache;
5446bfbb 45pub mod async_lru_cache;
59e94227 46pub mod serde_filter;
fb01fd3a 47pub mod statistics;
7b22fb25 48pub mod subscription;
fb01fd3a
WB
49pub mod systemd;
50pub mod ticket;
9372c078 51pub mod sgutils2;
639a6782 52pub mod paperkey;
f1d99e3f 53
fb01fd3a
WB
54pub mod parallel_handler;
55pub use parallel_handler::ParallelHandler;
3c9b3702 56
f1d99e3f 57mod wrapped_reader_stream;
fb01fd3a 58pub use wrapped_reader_stream::{AsyncReaderStream, StdChannelStream, WrappedReaderStream};
dcd033a5 59
943479f5 60mod async_channel_writer;
fb01fd3a 61pub use async_channel_writer::AsyncChannelWriter;
943479f5 62
3b151414 63mod file_logger;
fb01fd3a 64pub use file_logger::{FileLogger, FileLogOptions};
3b151414 65
4805edc4 66pub use pbs_tools::broadcast_future::{BroadcastData, BroadcastFuture};
2b7f8dd5 67pub use pbs_tools::ops::ControlFlow;
490be29e 68
fded74d0 69/// The `BufferedRead` trait provides a single function
0a72e267
DM
70/// `buffered_read`. It returns a reference to an internal buffer. The
71/// purpose of this traid is to avoid unnecessary data copies.
fded74d0 72pub trait BufferedRead {
318564ac
DM
73 /// This functions tries to fill the internal buffers, then
74 /// returns a reference to the available data. It returns an empty
75 /// buffer if `offset` points to the end of the file.
0a72e267
DM
76 fn buffered_read(&mut self, offset: u64) -> Result<&[u8], Error>;
77}
78
0fe5d605 79pub fn required_string_param<'a>(param: &'a Value, name: &str) -> Result<&'a str, Error> {
6100071f 80 match param[name].as_str() {
0fe5d605
DM
81 Some(s) => Ok(s),
82 None => bail!("missing parameter '{}'", name),
83 }
84}
0d38dcb4 85
e17d5d86
DM
86pub fn required_string_property<'a>(param: &'a Value, name: &str) -> Result<&'a str, Error> {
87 match param[name].as_str() {
88 Some(s) => Ok(s),
89 None => bail!("missing property '{}'", name),
90 }
91}
92
a4ba60be 93pub fn required_integer_param(param: &Value, name: &str) -> Result<i64, Error> {
6100071f 94 match param[name].as_i64() {
0d38dcb4
DM
95 Some(s) => Ok(s),
96 None => bail!("missing parameter '{}'", name),
f8dfbb45
DM
97 }
98}
99
a4ba60be 100pub fn required_integer_property(param: &Value, name: &str) -> Result<i64, Error> {
e17d5d86
DM
101 match param[name].as_i64() {
102 Some(s) => Ok(s),
103 None => bail!("missing property '{}'", name),
104 }
105}
106
35304303 107pub fn required_array_param<'a>(param: &'a Value, name: &str) -> Result<&'a [Value], Error> {
6100071f 108 match param[name].as_array() {
35304303 109 Some(s) => Ok(&s),
f8dfbb45 110 None => bail!("missing parameter '{}'", name),
0d38dcb4
DM
111 }
112}
383e8577 113
35304303 114pub fn required_array_property<'a>(param: &'a Value, name: &str) -> Result<&'a [Value], Error> {
e17d5d86 115 match param[name].as_array() {
35304303 116 Some(s) => Ok(&s),
e17d5d86
DM
117 None => bail!("missing property '{}'", name),
118 }
119}
120
c5946faf
WB
121/// Shortcut for md5 sums.
122pub fn md5sum(data: &[u8]) -> Result<DigestBytes, Error> {
123 hash(MessageDigest::md5(), data).map_err(Error::from)
124}
125
7e13b2d6 126pub fn get_hardware_address() -> Result<String, Error> {
1631c54f 127 static FILENAME: &str = "/etc/ssh/ssh_host_rsa_key.pub";
7e13b2d6 128
72c0e102
TL
129 let contents = proxmox::tools::fs::file_get_contents(FILENAME)
130 .map_err(|e| format_err!("Error getting host key - {}", e))?;
131 let digest = md5sum(&contents)
132 .map_err(|e| format_err!("Error digesting host key - {}", e))?;
7e13b2d6 133
52fe9e8e 134 Ok(proxmox::tools::bin_to_hex(&digest).to_uppercase())
7e13b2d6 135}
22968600 136
af2fddea
DM
137pub fn assert_if_modified(digest1: &str, digest2: &str) -> Result<(), Error> {
138 if digest1 != digest2 {
6100071f 139 bail!("detected modified configuration - file changed by other user? Try again.");
af2fddea
DM
140 }
141 Ok(())
142}
b9903d63 143
09f12d1c 144/// Extract a specific cookie from cookie header.
b9903d63 145/// We assume cookie_name is already url encoded.
09f12d1c 146pub fn extract_cookie(cookie: &str, cookie_name: &str) -> Option<String> {
b9903d63 147 for pair in cookie.split(';') {
b9903d63
DM
148 let (name, value) = match pair.find('=') {
149 Some(i) => (pair[..i].trim(), pair[(i + 1)..].trim()),
150 None => return None, // Cookie format error
151 };
152
153 if name == cookie_name {
8a1028e0 154 use percent_encoding::percent_decode;
b9903d63
DM
155 if let Ok(value) = percent_decode(value.as_bytes()).decode_utf8() {
156 return Some(value.into());
157 } else {
158 return None; // Cookie format error
159 }
160 }
161 }
162
163 None
164}
af53186e 165
002a191a
DM
166/// Detect modified configuration files
167///
add5861e 168/// This function fails with a reasonable error message if checksums do not match.
002a191a
DM
169pub fn detect_modified_configuration_file(digest1: &[u8;32], digest2: &[u8;32]) -> Result<(), Error> {
170 if digest1 != digest2 {
a4ba60be 171 bail!("detected modified configuration - file changed by other user? Try again.");
002a191a
DM
172 }
173 Ok(())
174}
175
3578d99f
DM
176/// normalize uri path
177///
178/// Do not allow ".", "..", or hidden files ".XXXX"
179/// Also remove empty path components
180pub fn normalize_uri_path(path: &str) -> Result<(String, Vec<&str>), Error> {
3578d99f
DM
181 let items = path.split('/');
182
183 let mut path = String::new();
184 let mut components = vec![];
185
186 for name in items {
6100071f
WB
187 if name.is_empty() {
188 continue;
189 }
62ee2eb4 190 if name.starts_with('.') {
3578d99f
DM
191 bail!("Path contains illegal components.");
192 }
193 path.push('/');
194 path.push_str(name);
195 components.push(name);
196 }
197
198 Ok((path, components))
199}
200
ff7049d4 201pub fn fd_change_cloexec(fd: RawFd, on: bool) -> Result<(), Error> {
6100071f 202 use nix::fcntl::{fcntl, FdFlag, F_GETFD, F_SETFD};
ff7049d4
WB
203 let mut flags = FdFlag::from_bits(fcntl(fd, F_GETFD)?)
204 .ok_or_else(|| format_err!("unhandled file flags"))?; // nix crate is stupid this way...
205 flags.set(FdFlag::FD_CLOEXEC, on);
206 fcntl(fd, F_SETFD(flags))?;
207 Ok(())
208}
9136f857 209
9136f857
DM
210static mut SHUTDOWN_REQUESTED: bool = false;
211
212pub fn request_shutdown() {
6100071f
WB
213 unsafe {
214 SHUTDOWN_REQUESTED = true;
215 }
7a630df7 216 crate::server::server_shutdown();
9136f857
DM
217}
218
219#[inline(always)]
220pub fn shutdown_requested() -> bool {
221 unsafe { SHUTDOWN_REQUESTED }
222}
92da93b2
DM
223
224pub fn fail_on_shutdown() -> Result<(), Error> {
225 if shutdown_requested() {
226 bail!("Server shutdown requested - aborting task");
227 }
228 Ok(())
229}
d96bb7f1 230
c4044009
WB
231/// safe wrapper for `nix::unistd::pipe2` defaulting to `O_CLOEXEC` and guarding the file
232/// descriptors.
efd1536e
WB
233pub fn pipe() -> Result<(Fd, Fd), Error> {
234 let (pin, pout) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?;
235 Ok((Fd(pin), Fd(pout)))
236}
2edc341b 237
c4044009
WB
238/// safe wrapper for `nix::sys::socket::socketpair` defaulting to `O_CLOEXEC` and guarding the file
239/// descriptors.
240pub fn socketpair() -> Result<(Fd, Fd), Error> {
241 use nix::sys::socket;
242 let (pa, pb) = socket::socketpair(
243 socket::AddressFamily::Unix,
244 socket::SockType::Stream,
245 None,
246 socket::SockFlag::SOCK_CLOEXEC,
247 )?;
248 Ok((Fd(pa), Fd(pb)))
249}
250
251
2edc341b
DM
252/// An easy way to convert types to Any
253///
254/// Mostly useful to downcast trait objects (see RpcEnvironment).
255pub trait AsAny {
dd5495d6 256 fn as_any(&self) -> &dyn Any;
2edc341b
DM
257}
258
259impl<T: Any> AsAny for T {
6100071f
WB
260 fn as_any(&self) -> &dyn Any {
261 self
262 }
2edc341b 263}
8a1028e0 264
32413921
FG
265/// The default 2 hours are far too long for PBS
266pub const PROXMOX_BACKUP_TCP_KEEPALIVE_TIME: u32 = 120;
57889533
FG
267pub const DEFAULT_USER_AGENT_STRING: &'static str = "proxmox-backup-client/1.0";
268
269/// Returns a new instance of `SimpleHttp` configured for PBS usage.
270pub fn pbs_simple_http(proxy_config: Option<ProxyConfig>) -> SimpleHttp {
271 let options = SimpleHttpOptions {
272 proxy_config,
273 user_agent: Some(DEFAULT_USER_AGENT_STRING.to_string()),
274 tcp_keepalive: Some(PROXMOX_BACKUP_TCP_KEEPALIVE_TIME),
275 ..Default::default()
276 };
277
278 SimpleHttp::with_options(options)
279}
32413921 280
386990ba
WB
281/// Get an iterator over lines of a file, skipping empty lines and comments (lines starting with a
282/// `#`).
283pub fn file_get_non_comment_lines<P: AsRef<Path>>(
284 path: P,
285) -> Result<impl Iterator<Item = io::Result<String>>, Error> {
286 let path = path.as_ref();
287
288 Ok(io::BufReader::new(
289 File::open(path).map_err(|err| format_err!("error opening {:?}: {}", path, err))?,
290 )
291 .lines()
292 .filter_map(|line| match line {
293 Ok(line) => {
294 let line = line.trim();
295 if line.is_empty() || line.starts_with('#') {
296 None
297 } else {
298 Some(Ok(line.to_string()))
299 }
300 }
301 Err(err) => Some(Err(err)),
302 }))
303}
e693818a 304
ac7513e3
DM
305pub fn setup_safe_path_env() {
306 std::env::set_var("PATH", "/sbin:/bin:/usr/sbin:/usr/bin");
307 // Make %ENV safer - as suggested by https://perldoc.perl.org/perlsec.html
308 for name in &["IFS", "CDPATH", "ENV", "BASH_ENV"] {
309 std::env::remove_var(name);
310 }
311}
cdf1da28 312
014dc5f9
WB
313/// Create the base run-directory.
314///
315/// This exists to fixate the permissions for the run *base* directory while allowing intermediate
316/// directories after it to have different permissions.
317pub fn create_run_dir() -> Result<(), Error> {
95f36925
WB
318 let backup_user = crate::backup::backup_user()?;
319 let opts = CreateOptions::new()
320 .owner(backup_user.uid)
321 .group(backup_user.gid);
af06decd 322 let _: bool = create_path(pbs_buildcfg::PROXMOX_BACKUP_RUN_DIR_M!(), None, Some(opts))?;
014dc5f9
WB
323 Ok(())
324}