]>
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; |
98c259b4 | 5 | use std::fs::File; |
ba0ccc59 | 6 | use std::io::{self, BufRead}; |
98c259b4 | 7 | use std::os::unix::io::RawFd; |
6100071f | 8 | use std::path::Path; |
365bb90f | 9 | |
f7d4e4b5 | 10 | use anyhow::{bail, format_err, Error}; |
af926291 | 11 | use serde_json::Value; |
c5946faf | 12 | use openssl::hash::{hash, DigestBytes, MessageDigest}; |
0fe5d605 | 13 | |
00ec8d16 | 14 | pub use proxmox::tools::fd::Fd; |
95f36925 | 15 | use proxmox::tools::fs::{create_path, CreateOptions}; |
00ec8d16 | 16 | |
1d781c5b | 17 | use proxmox_http::{ |
7d2be91b FG |
18 | client::SimpleHttp, |
19 | client::SimpleHttpOptions, | |
20 | ProxyConfig, | |
21 | }; | |
57889533 | 22 | |
a5951b4f | 23 | pub use pbs_tools::json; |
18cdf20a | 24 | pub use pbs_tools::nom; |
e57841c4 | 25 | pub use pbs_tools::{run_command, command_output, command_output_as_string}; |
83771aa0 WB |
26 | pub use pbs_tools::process_locker::{ |
27 | ProcessLocker, ProcessLockExclusiveGuard, ProcessLockSharedGuard | |
28 | }; | |
e57841c4 | 29 | |
e6513bd5 | 30 | pub mod apt; |
556eb70e | 31 | pub mod async_io; |
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; |
fb01fd3a | 37 | pub mod fuse_loop; |
e5ef69ec | 38 | |
fda19dcc DM |
39 | mod memcom; |
40 | pub use memcom::Memcom; | |
41 | ||
8074d2b0 | 42 | pub mod logrotate; |
45f9b32e | 43 | pub mod loopdev; |
fb01fd3a | 44 | pub mod lru_cache; |
5446bfbb | 45 | pub mod async_lru_cache; |
59e94227 | 46 | pub mod serde_filter; |
fb01fd3a | 47 | pub mod statistics; |
7b22fb25 | 48 | pub mod subscription; |
fb01fd3a WB |
49 | pub mod systemd; |
50 | pub mod ticket; | |
9372c078 | 51 | pub mod sgutils2; |
639a6782 | 52 | pub mod paperkey; |
f1d99e3f | 53 | |
fb01fd3a WB |
54 | pub mod parallel_handler; |
55 | pub use parallel_handler::ParallelHandler; | |
3c9b3702 | 56 | |
f1d99e3f | 57 | mod wrapped_reader_stream; |
fb01fd3a | 58 | pub use wrapped_reader_stream::{AsyncReaderStream, StdChannelStream, WrappedReaderStream}; |
dcd033a5 | 59 | |
943479f5 | 60 | mod async_channel_writer; |
fb01fd3a | 61 | pub use async_channel_writer::AsyncChannelWriter; |
943479f5 | 62 | |
3b151414 | 63 | mod file_logger; |
fb01fd3a | 64 | pub use file_logger::{FileLogger, FileLogOptions}; |
3b151414 | 65 | |
4805edc4 | 66 | pub use pbs_tools::broadcast_future::{BroadcastData, BroadcastFuture}; |
2b7f8dd5 | 67 | pub 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 | 72 | pub 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 | 79 | pub 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 |
86 | pub 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 | 93 | pub 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 | 100 | pub 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 | 107 | pub 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 | 114 | pub 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. |
122 | pub fn md5sum(data: &[u8]) -> Result<DigestBytes, Error> { | |
123 | hash(MessageDigest::md5(), data).map_err(Error::from) | |
124 | } | |
125 | ||
7e13b2d6 | 126 | pub 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 |
137 | pub 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 | 146 | pub 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 |
169 | pub 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 | |
180 | pub 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 | 201 | pub 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 |
210 | static mut SHUTDOWN_REQUESTED: bool = false; |
211 | ||
212 | pub 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)] | |
220 | pub fn shutdown_requested() -> bool { | |
221 | unsafe { SHUTDOWN_REQUESTED } | |
222 | } | |
92da93b2 DM |
223 | |
224 | pub 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 |
233 | pub 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. | |
240 | pub 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). | |
255 | pub trait AsAny { | |
dd5495d6 | 256 | fn as_any(&self) -> &dyn Any; |
2edc341b DM |
257 | } |
258 | ||
259 | impl<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 |
266 | pub const PROXMOX_BACKUP_TCP_KEEPALIVE_TIME: u32 = 120; | |
57889533 FG |
267 | pub const DEFAULT_USER_AGENT_STRING: &'static str = "proxmox-backup-client/1.0"; |
268 | ||
269 | /// Returns a new instance of `SimpleHttp` configured for PBS usage. | |
270 | pub 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 | /// `#`). | |
283 | pub 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 |
305 | pub 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. | |
317 | pub 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 | } |