]>
Commit | Line | Data |
---|---|---|
51b499db DM |
1 | //! Tools and utilities |
2 | //! | |
3 | //! This is a collection of small and useful tools. | |
6100071f | 4 | use std::any::Any; |
61653382 | 5 | use std::borrow::Borrow; |
6100071f | 6 | use std::collections::HashMap; |
62ee2eb4 | 7 | use std::hash::BuildHasher; |
98c259b4 | 8 | use std::fs::File; |
b649887e | 9 | use std::io::{self, BufRead, Read, Seek, SeekFrom}; |
98c259b4 | 10 | use std::os::unix::io::RawFd; |
6100071f | 11 | use std::path::Path; |
365bb90f | 12 | |
f7d4e4b5 | 13 | use anyhow::{bail, format_err, Error}; |
af926291 | 14 | use serde_json::Value; |
c5946faf | 15 | use openssl::hash::{hash, DigestBytes, MessageDigest}; |
968a0ab2 | 16 | use percent_encoding::{utf8_percent_encode, AsciiSet}; |
0fe5d605 | 17 | |
00ec8d16 | 18 | pub use proxmox::tools::fd::Fd; |
95f36925 | 19 | use proxmox::tools::fs::{create_path, CreateOptions}; |
00ec8d16 | 20 | |
1d781c5b | 21 | use proxmox_http::{ |
7d2be91b FG |
22 | client::SimpleHttp, |
23 | client::SimpleHttpOptions, | |
24 | ProxyConfig, | |
25 | }; | |
57889533 | 26 | |
6100071f | 27 | pub mod acl; |
e6513bd5 | 28 | pub mod apt; |
556eb70e | 29 | pub mod async_io; |
6ed25cbe | 30 | pub mod borrow; |
ec01eead | 31 | pub mod cert; |
ea62611d | 32 | pub mod compression; |
bc5c1a9a | 33 | pub mod config; |
a5bdc987 | 34 | pub mod cpio; |
6100071f | 35 | pub mod daemon; |
10effc98 | 36 | pub mod disks; |
4939255f | 37 | pub mod format; |
fb01fd3a WB |
38 | pub mod fs; |
39 | pub mod fuse_loop; | |
e5ef69ec | 40 | |
fda19dcc DM |
41 | mod memcom; |
42 | pub use memcom::Memcom; | |
43 | ||
9ff747ef | 44 | pub mod json; |
8074d2b0 | 45 | pub mod logrotate; |
45f9b32e | 46 | pub mod loopdev; |
fb01fd3a | 47 | pub mod lru_cache; |
5446bfbb | 48 | pub mod async_lru_cache; |
fb01fd3a WB |
49 | pub mod nom; |
50 | pub mod runtime; | |
59e94227 | 51 | pub mod serde_filter; |
fb01fd3a | 52 | pub mod statistics; |
7b22fb25 | 53 | pub mod subscription; |
fb01fd3a WB |
54 | pub mod systemd; |
55 | pub mod ticket; | |
56 | pub mod xattr; | |
fdce52aa | 57 | pub mod zip; |
9372c078 | 58 | pub mod sgutils2; |
639a6782 | 59 | pub mod paperkey; |
f1d99e3f | 60 | |
fb01fd3a WB |
61 | pub mod parallel_handler; |
62 | pub use parallel_handler::ParallelHandler; | |
3c9b3702 | 63 | |
f1d99e3f | 64 | mod wrapped_reader_stream; |
fb01fd3a | 65 | pub use wrapped_reader_stream::{AsyncReaderStream, StdChannelStream, WrappedReaderStream}; |
dcd033a5 | 66 | |
943479f5 | 67 | mod async_channel_writer; |
fb01fd3a | 68 | pub use async_channel_writer::AsyncChannelWriter; |
943479f5 | 69 | |
dcd033a5 | 70 | mod std_channel_writer; |
fb01fd3a | 71 | pub use std_channel_writer::StdChannelWriter; |
8cf6e764 | 72 | |
f1d76ecf DC |
73 | mod tokio_writer_adapter; |
74 | pub use tokio_writer_adapter::TokioWriterAdapter; | |
75 | ||
a650f503 | 76 | mod process_locker; |
fb01fd3a | 77 | pub use process_locker::{ProcessLocker, ProcessLockExclusiveGuard, ProcessLockSharedGuard}; |
a650f503 | 78 | |
3b151414 | 79 | mod file_logger; |
fb01fd3a | 80 | pub use file_logger::{FileLogger, FileLogOptions}; |
3b151414 | 81 | |
490be29e | 82 | mod broadcast_future; |
fb01fd3a | 83 | pub use broadcast_future::{BroadcastData, BroadcastFuture}; |
490be29e | 84 | |
fded74d0 | 85 | /// The `BufferedRead` trait provides a single function |
0a72e267 DM |
86 | /// `buffered_read`. It returns a reference to an internal buffer. The |
87 | /// purpose of this traid is to avoid unnecessary data copies. | |
fded74d0 | 88 | pub trait BufferedRead { |
318564ac DM |
89 | /// This functions tries to fill the internal buffers, then |
90 | /// returns a reference to the available data. It returns an empty | |
91 | /// buffer if `offset` points to the end of the file. | |
0a72e267 DM |
92 | fn buffered_read(&mut self, offset: u64) -> Result<&[u8], Error>; |
93 | } | |
94 | ||
f5f13ebc | 95 | pub fn json_object_to_query(data: Value) -> Result<String, Error> { |
f5f13ebc DM |
96 | let mut query = url::form_urlencoded::Serializer::new(String::new()); |
97 | ||
98 | let object = data.as_object().ok_or_else(|| { | |
99 | format_err!("json_object_to_query: got wrong data type (expected object).") | |
100 | })?; | |
101 | ||
102 | for (key, value) in object { | |
103 | match value { | |
6100071f WB |
104 | Value::Bool(b) => { |
105 | query.append_pair(key, &b.to_string()); | |
106 | } | |
107 | Value::Number(n) => { | |
108 | query.append_pair(key, &n.to_string()); | |
109 | } | |
110 | Value::String(s) => { | |
111 | query.append_pair(key, &s); | |
112 | } | |
f5f13ebc DM |
113 | Value::Array(arr) => { |
114 | for element in arr { | |
115 | match element { | |
6100071f WB |
116 | Value::Bool(b) => { |
117 | query.append_pair(key, &b.to_string()); | |
118 | } | |
119 | Value::Number(n) => { | |
120 | query.append_pair(key, &n.to_string()); | |
121 | } | |
122 | Value::String(s) => { | |
123 | query.append_pair(key, &s); | |
124 | } | |
125 | _ => bail!( | |
126 | "json_object_to_query: unable to handle complex array data types." | |
127 | ), | |
f5f13ebc DM |
128 | } |
129 | } | |
130 | } | |
131 | _ => bail!("json_object_to_query: unable to handle complex data types."), | |
132 | } | |
133 | } | |
134 | ||
135 | Ok(query.finish()) | |
136 | } | |
137 | ||
0fe5d605 | 138 | pub fn required_string_param<'a>(param: &'a Value, name: &str) -> Result<&'a str, Error> { |
6100071f | 139 | match param[name].as_str() { |
0fe5d605 DM |
140 | Some(s) => Ok(s), |
141 | None => bail!("missing parameter '{}'", name), | |
142 | } | |
143 | } | |
0d38dcb4 | 144 | |
e17d5d86 DM |
145 | pub fn required_string_property<'a>(param: &'a Value, name: &str) -> Result<&'a str, Error> { |
146 | match param[name].as_str() { | |
147 | Some(s) => Ok(s), | |
148 | None => bail!("missing property '{}'", name), | |
149 | } | |
150 | } | |
151 | ||
a4ba60be | 152 | pub fn required_integer_param(param: &Value, name: &str) -> Result<i64, Error> { |
6100071f | 153 | match param[name].as_i64() { |
0d38dcb4 DM |
154 | Some(s) => Ok(s), |
155 | None => bail!("missing parameter '{}'", name), | |
f8dfbb45 DM |
156 | } |
157 | } | |
158 | ||
a4ba60be | 159 | pub fn required_integer_property(param: &Value, name: &str) -> Result<i64, Error> { |
e17d5d86 DM |
160 | match param[name].as_i64() { |
161 | Some(s) => Ok(s), | |
162 | None => bail!("missing property '{}'", name), | |
163 | } | |
164 | } | |
165 | ||
35304303 | 166 | pub fn required_array_param<'a>(param: &'a Value, name: &str) -> Result<&'a [Value], Error> { |
6100071f | 167 | match param[name].as_array() { |
35304303 | 168 | Some(s) => Ok(&s), |
f8dfbb45 | 169 | None => bail!("missing parameter '{}'", name), |
0d38dcb4 DM |
170 | } |
171 | } | |
383e8577 | 172 | |
35304303 | 173 | pub fn required_array_property<'a>(param: &'a Value, name: &str) -> Result<&'a [Value], Error> { |
e17d5d86 | 174 | match param[name].as_array() { |
35304303 | 175 | Some(s) => Ok(&s), |
e17d5d86 DM |
176 | None => bail!("missing property '{}'", name), |
177 | } | |
178 | } | |
179 | ||
a4ba60be WB |
180 | pub fn complete_file_name<S>(arg: &str, _param: &HashMap<String, String, S>) -> Vec<String> |
181 | where | |
182 | S: BuildHasher, | |
183 | { | |
383e8577 DM |
184 | let mut result = vec![]; |
185 | ||
6100071f | 186 | use nix::fcntl::AtFlags; |
383e8577 DM |
187 | use nix::fcntl::OFlag; |
188 | use nix::sys::stat::Mode; | |
383e8577 | 189 | |
62ee2eb4 | 190 | let mut dirname = std::path::PathBuf::from(if arg.is_empty() { "./" } else { arg }); |
383e8577 DM |
191 | |
192 | let is_dir = match nix::sys::stat::fstatat(libc::AT_FDCWD, &dirname, AtFlags::empty()) { | |
193 | Ok(stat) => (stat.st_mode & libc::S_IFMT) == libc::S_IFDIR, | |
194 | Err(_) => false, | |
195 | }; | |
196 | ||
197 | if !is_dir { | |
198 | if let Some(parent) = dirname.parent() { | |
199 | dirname = parent.to_owned(); | |
200 | } | |
201 | } | |
202 | ||
6100071f WB |
203 | let mut dir = |
204 | match nix::dir::Dir::openat(libc::AT_FDCWD, &dirname, OFlag::O_DIRECTORY, Mode::empty()) { | |
205 | Ok(d) => d, | |
206 | Err(_) => return result, | |
207 | }; | |
383e8577 DM |
208 | |
209 | for item in dir.iter() { | |
210 | if let Ok(entry) = item { | |
211 | if let Ok(name) = entry.file_name().to_str() { | |
6100071f WB |
212 | if name == "." || name == ".." { |
213 | continue; | |
214 | } | |
383e8577 DM |
215 | let mut newpath = dirname.clone(); |
216 | newpath.push(name); | |
217 | ||
6100071f WB |
218 | if let Ok(stat) = |
219 | nix::sys::stat::fstatat(libc::AT_FDCWD, &newpath, AtFlags::empty()) | |
220 | { | |
383e8577 DM |
221 | if (stat.st_mode & libc::S_IFMT) == libc::S_IFDIR { |
222 | newpath.push(""); | |
223 | if let Some(newpath) = newpath.to_str() { | |
224 | result.push(newpath.to_owned()); | |
225 | } | |
226 | continue; | |
6100071f | 227 | } |
383e8577 DM |
228 | } |
229 | if let Some(newpath) = newpath.to_str() { | |
230 | result.push(newpath.to_owned()); | |
231 | } | |
6100071f | 232 | } |
383e8577 DM |
233 | } |
234 | } | |
235 | ||
236 | result | |
237 | } | |
443f3743 DM |
238 | |
239 | /// Scan directory for matching file names. | |
240 | /// | |
241 | /// Scan through all directory entries and call `callback()` function | |
242 | /// if the entry name matches the regular expression. This function | |
243 | /// used unix `openat()`, so you can pass absolute or relative file | |
244 | /// names. This function simply skips non-UTF8 encoded names. | |
245 | pub fn scandir<P, F>( | |
246 | dirfd: RawFd, | |
121f18ef | 247 | path: &P, |
443f3743 | 248 | regex: ®ex::Regex, |
6100071f | 249 | mut callback: F, |
443f3743 | 250 | ) -> Result<(), Error> |
6100071f WB |
251 | where |
252 | F: FnMut(RawFd, &str, nix::dir::Type) -> Result<(), Error>, | |
253 | P: ?Sized + nix::NixPath, | |
443f3743 | 254 | { |
121f18ef | 255 | for entry in self::fs::scan_subdir(dirfd, path, regex)? { |
443f3743 DM |
256 | let entry = entry?; |
257 | let file_type = match entry.file_type() { | |
258 | Some(file_type) => file_type, | |
259 | None => bail!("unable to detect file type"), | |
260 | }; | |
443f3743 | 261 | |
6100071f WB |
262 | callback( |
263 | entry.parent_fd(), | |
264 | unsafe { entry.file_name_utf8_unchecked() }, | |
265 | file_type, | |
266 | )?; | |
443f3743 DM |
267 | } |
268 | Ok(()) | |
269 | } | |
7e13b2d6 | 270 | |
c5946faf WB |
271 | /// Shortcut for md5 sums. |
272 | pub fn md5sum(data: &[u8]) -> Result<DigestBytes, Error> { | |
273 | hash(MessageDigest::md5(), data).map_err(Error::from) | |
274 | } | |
275 | ||
7e13b2d6 | 276 | pub fn get_hardware_address() -> Result<String, Error> { |
1631c54f | 277 | static FILENAME: &str = "/etc/ssh/ssh_host_rsa_key.pub"; |
7e13b2d6 | 278 | |
72c0e102 TL |
279 | let contents = proxmox::tools::fs::file_get_contents(FILENAME) |
280 | .map_err(|e| format_err!("Error getting host key - {}", e))?; | |
281 | let digest = md5sum(&contents) | |
282 | .map_err(|e| format_err!("Error digesting host key - {}", e))?; | |
7e13b2d6 | 283 | |
52fe9e8e | 284 | Ok(proxmox::tools::bin_to_hex(&digest).to_uppercase()) |
7e13b2d6 | 285 | } |
22968600 | 286 | |
af2fddea DM |
287 | pub fn assert_if_modified(digest1: &str, digest2: &str) -> Result<(), Error> { |
288 | if digest1 != digest2 { | |
6100071f | 289 | bail!("detected modified configuration - file changed by other user? Try again."); |
af2fddea DM |
290 | } |
291 | Ok(()) | |
292 | } | |
b9903d63 | 293 | |
09f12d1c | 294 | /// Extract a specific cookie from cookie header. |
b9903d63 | 295 | /// We assume cookie_name is already url encoded. |
09f12d1c | 296 | pub fn extract_cookie(cookie: &str, cookie_name: &str) -> Option<String> { |
b9903d63 | 297 | for pair in cookie.split(';') { |
b9903d63 DM |
298 | let (name, value) = match pair.find('=') { |
299 | Some(i) => (pair[..i].trim(), pair[(i + 1)..].trim()), | |
300 | None => return None, // Cookie format error | |
301 | }; | |
302 | ||
303 | if name == cookie_name { | |
8a1028e0 | 304 | use percent_encoding::percent_decode; |
b9903d63 DM |
305 | if let Ok(value) = percent_decode(value.as_bytes()).decode_utf8() { |
306 | return Some(value.into()); | |
307 | } else { | |
308 | return None; // Cookie format error | |
309 | } | |
310 | } | |
311 | } | |
312 | ||
313 | None | |
314 | } | |
af53186e | 315 | |
968a0ab2 DC |
316 | /// percent encode a url component |
317 | pub fn percent_encode_component(comp: &str) -> String { | |
318 | utf8_percent_encode(comp, percent_encoding::NON_ALPHANUMERIC).to_string() | |
319 | } | |
320 | ||
61653382 | 321 | pub fn join<S: Borrow<str>>(data: &[S], sep: char) -> String { |
af53186e DM |
322 | let mut list = String::new(); |
323 | ||
324 | for item in data { | |
62ee2eb4 | 325 | if !list.is_empty() { |
6100071f WB |
326 | list.push(sep); |
327 | } | |
61653382 | 328 | list.push_str(item.borrow()); |
af53186e DM |
329 | } |
330 | ||
331 | list | |
332 | } | |
ff7049d4 | 333 | |
002a191a DM |
334 | /// Detect modified configuration files |
335 | /// | |
add5861e | 336 | /// This function fails with a reasonable error message if checksums do not match. |
002a191a DM |
337 | pub fn detect_modified_configuration_file(digest1: &[u8;32], digest2: &[u8;32]) -> Result<(), Error> { |
338 | if digest1 != digest2 { | |
a4ba60be | 339 | bail!("detected modified configuration - file changed by other user? Try again."); |
002a191a DM |
340 | } |
341 | Ok(()) | |
342 | } | |
343 | ||
3578d99f DM |
344 | /// normalize uri path |
345 | /// | |
346 | /// Do not allow ".", "..", or hidden files ".XXXX" | |
347 | /// Also remove empty path components | |
348 | pub fn normalize_uri_path(path: &str) -> Result<(String, Vec<&str>), Error> { | |
3578d99f DM |
349 | let items = path.split('/'); |
350 | ||
351 | let mut path = String::new(); | |
352 | let mut components = vec![]; | |
353 | ||
354 | for name in items { | |
6100071f WB |
355 | if name.is_empty() { |
356 | continue; | |
357 | } | |
62ee2eb4 | 358 | if name.starts_with('.') { |
3578d99f DM |
359 | bail!("Path contains illegal components."); |
360 | } | |
361 | path.push('/'); | |
362 | path.push_str(name); | |
363 | components.push(name); | |
364 | } | |
365 | ||
366 | Ok((path, components)) | |
367 | } | |
368 | ||
97fab7aa | 369 | /// Helper to check result from std::process::Command output |
143b6545 DM |
370 | /// |
371 | /// The exit_code_check() function should return true if the exit code | |
372 | /// is considered successful. | |
373 | pub fn command_output( | |
374 | output: std::process::Output, | |
144006fa | 375 | exit_code_check: Option<fn(i32) -> bool>, |
e64b9f92 | 376 | ) -> Result<Vec<u8>, Error> { |
97fab7aa DM |
377 | |
378 | if !output.status.success() { | |
379 | match output.status.code() { | |
380 | Some(code) => { | |
143b6545 DM |
381 | let is_ok = match exit_code_check { |
382 | Some(check_fn) => check_fn(code), | |
383 | None => code == 0, | |
384 | }; | |
385 | if !is_ok { | |
97fab7aa DM |
386 | let msg = String::from_utf8(output.stderr) |
387 | .map(|m| if m.is_empty() { String::from("no error message") } else { m }) | |
388 | .unwrap_or_else(|_| String::from("non utf8 error message (suppressed)")); | |
389 | ||
390 | bail!("status code: {} - {}", code, msg); | |
391 | } | |
392 | } | |
393 | None => bail!("terminated by signal"), | |
394 | } | |
395 | } | |
396 | ||
e64b9f92 DM |
397 | Ok(output.stdout) |
398 | } | |
97fab7aa | 399 | |
e64b9f92 DM |
400 | /// Helper to check result from std::process::Command output, returns String. |
401 | /// | |
402 | /// The exit_code_check() function should return true if the exit code | |
403 | /// is considered successful. | |
404 | pub fn command_output_as_string( | |
405 | output: std::process::Output, | |
406 | exit_code_check: Option<fn(i32) -> bool>, | |
407 | ) -> Result<String, Error> { | |
408 | let output = command_output(output, exit_code_check)?; | |
409 | let output = String::from_utf8(output)?; | |
97fab7aa DM |
410 | Ok(output) |
411 | } | |
412 | ||
144006fa DM |
413 | pub fn run_command( |
414 | mut command: std::process::Command, | |
415 | exit_code_check: Option<fn(i32) -> bool>, | |
416 | ) -> Result<String, Error> { | |
417 | ||
418 | let output = command.output() | |
419 | .map_err(|err| format_err!("failed to execute {:?} - {}", command, err))?; | |
420 | ||
f254a270 | 421 | let output = command_output_as_string(output, exit_code_check) |
144006fa DM |
422 | .map_err(|err| format_err!("command {:?} failed - {}", command, err))?; |
423 | ||
424 | Ok(output) | |
425 | } | |
97fab7aa | 426 | |
ff7049d4 | 427 | pub fn fd_change_cloexec(fd: RawFd, on: bool) -> Result<(), Error> { |
6100071f | 428 | use nix::fcntl::{fcntl, FdFlag, F_GETFD, F_SETFD}; |
ff7049d4 WB |
429 | let mut flags = FdFlag::from_bits(fcntl(fd, F_GETFD)?) |
430 | .ok_or_else(|| format_err!("unhandled file flags"))?; // nix crate is stupid this way... | |
431 | flags.set(FdFlag::FD_CLOEXEC, on); | |
432 | fcntl(fd, F_SETFD(flags))?; | |
433 | Ok(()) | |
434 | } | |
9136f857 | 435 | |
9136f857 DM |
436 | static mut SHUTDOWN_REQUESTED: bool = false; |
437 | ||
438 | pub fn request_shutdown() { | |
6100071f WB |
439 | unsafe { |
440 | SHUTDOWN_REQUESTED = true; | |
441 | } | |
7a630df7 | 442 | crate::server::server_shutdown(); |
9136f857 DM |
443 | } |
444 | ||
445 | #[inline(always)] | |
446 | pub fn shutdown_requested() -> bool { | |
447 | unsafe { SHUTDOWN_REQUESTED } | |
448 | } | |
92da93b2 DM |
449 | |
450 | pub fn fail_on_shutdown() -> Result<(), Error> { | |
451 | if shutdown_requested() { | |
452 | bail!("Server shutdown requested - aborting task"); | |
453 | } | |
454 | Ok(()) | |
455 | } | |
d96bb7f1 | 456 | |
c4044009 WB |
457 | /// safe wrapper for `nix::unistd::pipe2` defaulting to `O_CLOEXEC` and guarding the file |
458 | /// descriptors. | |
efd1536e WB |
459 | pub fn pipe() -> Result<(Fd, Fd), Error> { |
460 | let (pin, pout) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?; | |
461 | Ok((Fd(pin), Fd(pout))) | |
462 | } | |
2edc341b | 463 | |
c4044009 WB |
464 | /// safe wrapper for `nix::sys::socket::socketpair` defaulting to `O_CLOEXEC` and guarding the file |
465 | /// descriptors. | |
466 | pub fn socketpair() -> Result<(Fd, Fd), Error> { | |
467 | use nix::sys::socket; | |
468 | let (pa, pb) = socket::socketpair( | |
469 | socket::AddressFamily::Unix, | |
470 | socket::SockType::Stream, | |
471 | None, | |
472 | socket::SockFlag::SOCK_CLOEXEC, | |
473 | )?; | |
474 | Ok((Fd(pa), Fd(pb))) | |
475 | } | |
476 | ||
477 | ||
2edc341b DM |
478 | /// An easy way to convert types to Any |
479 | /// | |
480 | /// Mostly useful to downcast trait objects (see RpcEnvironment). | |
481 | pub trait AsAny { | |
dd5495d6 | 482 | fn as_any(&self) -> &dyn Any; |
2edc341b DM |
483 | } |
484 | ||
485 | impl<T: Any> AsAny for T { | |
6100071f WB |
486 | fn as_any(&self) -> &dyn Any { |
487 | self | |
488 | } | |
2edc341b | 489 | } |
8a1028e0 | 490 | |
32413921 FG |
491 | /// The default 2 hours are far too long for PBS |
492 | pub const PROXMOX_BACKUP_TCP_KEEPALIVE_TIME: u32 = 120; | |
57889533 FG |
493 | pub const DEFAULT_USER_AGENT_STRING: &'static str = "proxmox-backup-client/1.0"; |
494 | ||
495 | /// Returns a new instance of `SimpleHttp` configured for PBS usage. | |
496 | pub fn pbs_simple_http(proxy_config: Option<ProxyConfig>) -> SimpleHttp { | |
497 | let options = SimpleHttpOptions { | |
498 | proxy_config, | |
499 | user_agent: Some(DEFAULT_USER_AGENT_STRING.to_string()), | |
500 | tcp_keepalive: Some(PROXMOX_BACKUP_TCP_KEEPALIVE_TIME), | |
501 | ..Default::default() | |
502 | }; | |
503 | ||
504 | SimpleHttp::with_options(options) | |
505 | } | |
32413921 | 506 | |
8a1028e0 WB |
507 | /// This used to be: `SIMPLE_ENCODE_SET` plus space, `"`, `#`, `<`, `>`, backtick, `?`, `{`, `}` |
508 | pub const DEFAULT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS // 0..1f and 7e | |
509 | // The SIMPLE_ENCODE_SET adds space and anything >= 0x7e (7e itself is already included above) | |
510 | .add(0x20) | |
511 | .add(0x7f) | |
512 | // the DEFAULT_ENCODE_SET added: | |
513 | .add(b' ') | |
514 | .add(b'"') | |
515 | .add(b'#') | |
516 | .add(b'<') | |
517 | .add(b'>') | |
518 | .add(b'`') | |
519 | .add(b'?') | |
520 | .add(b'{') | |
521 | .add(b'}'); | |
386990ba WB |
522 | |
523 | /// Get an iterator over lines of a file, skipping empty lines and comments (lines starting with a | |
524 | /// `#`). | |
525 | pub fn file_get_non_comment_lines<P: AsRef<Path>>( | |
526 | path: P, | |
527 | ) -> Result<impl Iterator<Item = io::Result<String>>, Error> { | |
528 | let path = path.as_ref(); | |
529 | ||
530 | Ok(io::BufReader::new( | |
531 | File::open(path).map_err(|err| format_err!("error opening {:?}: {}", path, err))?, | |
532 | ) | |
533 | .lines() | |
534 | .filter_map(|line| match line { | |
535 | Ok(line) => { | |
536 | let line = line.trim(); | |
537 | if line.is_empty() || line.starts_with('#') { | |
538 | None | |
539 | } else { | |
540 | Some(Ok(line.to_string())) | |
541 | } | |
542 | } | |
543 | Err(err) => Some(Err(err)), | |
544 | })) | |
545 | } | |
e693818a | 546 | |
ac7513e3 DM |
547 | pub fn setup_safe_path_env() { |
548 | std::env::set_var("PATH", "/sbin:/bin:/usr/sbin:/usr/bin"); | |
549 | // Make %ENV safer - as suggested by https://perldoc.perl.org/perlsec.html | |
550 | for name in &["IFS", "CDPATH", "ENV", "BASH_ENV"] { | |
551 | std::env::remove_var(name); | |
552 | } | |
553 | } | |
cdf1da28 WB |
554 | |
555 | pub fn strip_ascii_whitespace(line: &[u8]) -> &[u8] { | |
556 | let line = match line.iter().position(|&b| !b.is_ascii_whitespace()) { | |
557 | Some(n) => &line[n..], | |
558 | None => return &[], | |
559 | }; | |
560 | match line.iter().rev().position(|&b| !b.is_ascii_whitespace()) { | |
561 | Some(n) => &line[..(line.len() - n)], | |
562 | None => &[], | |
563 | } | |
564 | } | |
1bc1d81a DM |
565 | |
566 | /// Seeks to start of file and computes the SHA256 hash | |
567 | pub fn compute_file_csum(file: &mut File) -> Result<([u8; 32], u64), Error> { | |
568 | ||
569 | file.seek(SeekFrom::Start(0))?; | |
570 | ||
571 | let mut hasher = openssl::sha::Sha256::new(); | |
572 | let mut buffer = proxmox::tools::vec::undefined(256*1024); | |
573 | let mut size: u64 = 0; | |
574 | ||
575 | loop { | |
576 | let count = match file.read(&mut buffer) { | |
a4ba60be | 577 | Ok(0) => break, |
1bc1d81a DM |
578 | Ok(count) => count, |
579 | Err(ref err) if err.kind() == std::io::ErrorKind::Interrupted => { | |
580 | continue; | |
581 | } | |
582 | Err(err) => return Err(err.into()), | |
583 | }; | |
1bc1d81a DM |
584 | size += count as u64; |
585 | hasher.update(&buffer[..count]); | |
586 | } | |
587 | ||
588 | let csum = hasher.finish(); | |
589 | ||
590 | Ok((csum, size)) | |
591 | } | |
014dc5f9 WB |
592 | |
593 | /// Create the base run-directory. | |
594 | /// | |
595 | /// This exists to fixate the permissions for the run *base* directory while allowing intermediate | |
596 | /// directories after it to have different permissions. | |
597 | pub fn create_run_dir() -> Result<(), Error> { | |
95f36925 WB |
598 | let backup_user = crate::backup::backup_user()?; |
599 | let opts = CreateOptions::new() | |
600 | .owner(backup_user.uid) | |
601 | .group(backup_user.gid); | |
af06decd | 602 | let _: bool = create_path(pbs_buildcfg::PROXMOX_BACKUP_RUN_DIR_M!(), None, Some(opts))?; |
014dc5f9 WB |
603 | Ok(()) |
604 | } | |
0b6d9442 WB |
605 | |
606 | /// Modeled after the nightly `std::ops::ControlFlow`. | |
607 | #[derive(Clone, Copy, Debug, PartialEq)] | |
608 | pub enum ControlFlow<B, C = ()> { | |
609 | Continue(C), | |
610 | Break(B), | |
611 | } | |
612 | ||
613 | impl<B> ControlFlow<B> { | |
614 | pub const CONTINUE: ControlFlow<B, ()> = ControlFlow::Continue(()); | |
615 | } |