]>
Commit | Line | Data |
---|---|---|
51b499db DM |
1 | //! Tools and utilities |
2 | //! | |
3 | //! This is a collection of small and useful tools. | |
6100071f WB |
4 | use std::any::Any; |
5 | use std::collections::HashMap; | |
365bb90f | 6 | use std::fs::{File, OpenOptions}; |
43eeef28 | 7 | use std::io::ErrorKind; |
6100071f | 8 | use std::io::Read; |
d96bb7f1 | 9 | use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; |
6100071f WB |
10 | use std::path::Path; |
11 | use std::time::Duration; | |
365bb90f | 12 | |
6100071f | 13 | use failure::*; |
af926291 | 14 | use serde_json::Value; |
0fe5d605 | 15 | |
f35197f4 WB |
16 | use proxmox::tools::vec; |
17 | ||
6100071f | 18 | pub mod acl; |
d82ed9b0 | 19 | pub mod async_mutex; |
6ed25cbe | 20 | pub mod borrow; |
6100071f | 21 | pub mod daemon; |
b4d5787d | 22 | pub mod fs; |
6100071f | 23 | pub mod futures; |
6100071f WB |
24 | pub mod ticket; |
25 | pub mod timer; | |
26 | pub mod tty; | |
27 | pub mod wrapped_reader_stream; | |
2dcdd3b4 | 28 | pub mod xattr; |
8cf6e764 | 29 | |
a650f503 DM |
30 | mod process_locker; |
31 | pub use process_locker::*; | |
32 | ||
3b151414 DM |
33 | mod file_logger; |
34 | pub use file_logger::*; | |
35 | ||
490be29e DM |
36 | mod broadcast_future; |
37 | pub use broadcast_future::*; | |
38 | ||
fded74d0 | 39 | /// The `BufferedRead` trait provides a single function |
0a72e267 DM |
40 | /// `buffered_read`. It returns a reference to an internal buffer. The |
41 | /// purpose of this traid is to avoid unnecessary data copies. | |
fded74d0 | 42 | pub trait BufferedRead { |
318564ac DM |
43 | /// This functions tries to fill the internal buffers, then |
44 | /// returns a reference to the available data. It returns an empty | |
45 | /// buffer if `offset` points to the end of the file. | |
0a72e267 DM |
46 | fn buffered_read(&mut self, offset: u64) -> Result<&[u8], Error>; |
47 | } | |
48 | ||
51b499db DM |
49 | /// Directly map a type into a binary buffer. This is mostly useful |
50 | /// for reading structured data from a byte stream (file). You need to | |
51 | /// make sure that the buffer location does not change, so please | |
52 | /// avoid vec resize while you use such map. | |
53 | /// | |
54 | /// This function panics if the buffer is not large enough. | |
50ea4396 | 55 | pub fn map_struct<T>(buffer: &[u8]) -> Result<&T, Error> { |
dc3de618 DM |
56 | if buffer.len() < ::std::mem::size_of::<T>() { |
57 | bail!("unable to map struct - buffer too small"); | |
58 | } | |
6100071f | 59 | Ok(unsafe { &*(buffer.as_ptr() as *const T) }) |
dc3de618 DM |
60 | } |
61 | ||
51b499db DM |
62 | /// Directly map a type into a mutable binary buffer. This is mostly |
63 | /// useful for writing structured data into a byte stream (file). You | |
64 | /// need to make sure that the buffer location does not change, so | |
65 | /// please avoid vec resize while you use such map. | |
66 | /// | |
67 | /// This function panics if the buffer is not large enough. | |
50ea4396 | 68 | pub fn map_struct_mut<T>(buffer: &mut [u8]) -> Result<&mut T, Error> { |
dc3de618 DM |
69 | if buffer.len() < ::std::mem::size_of::<T>() { |
70 | bail!("unable to map struct - buffer too small"); | |
71 | } | |
6100071f | 72 | Ok(unsafe { &mut *(buffer.as_ptr() as *mut T) }) |
dc3de618 DM |
73 | } |
74 | ||
51b499db DM |
75 | /// Create a file lock using fntl. This function allows you to specify |
76 | /// a timeout if you want to avoid infinite blocking. | |
1628a4c7 WB |
77 | pub fn lock_file<F: AsRawFd>( |
78 | file: &mut F, | |
79 | exclusive: bool, | |
80 | timeout: Option<Duration>, | |
eb90c9e3 | 81 | ) -> Result<(), Error> { |
6100071f WB |
82 | let lockarg = if exclusive { |
83 | nix::fcntl::FlockArg::LockExclusive | |
84 | } else { | |
85 | nix::fcntl::FlockArg::LockShared | |
86 | }; | |
1628a4c7 WB |
87 | |
88 | let timeout = match timeout { | |
89 | None => { | |
90 | nix::fcntl::flock(file.as_raw_fd(), lockarg)?; | |
91 | return Ok(()); | |
92 | } | |
93 | Some(t) => t, | |
94 | }; | |
95 | ||
96 | // unblock the timeout signal temporarily | |
97 | let _sigblock_guard = timer::unblock_timeout_signal(); | |
98 | ||
99 | // setup a timeout timer | |
100 | let mut timer = timer::Timer::create( | |
101 | timer::Clock::Realtime, | |
6100071f WB |
102 | timer::TimerEvent::ThisThreadSignal(timer::SIGTIMEOUT), |
103 | )?; | |
1628a4c7 | 104 | |
6100071f WB |
105 | timer.arm( |
106 | timer::TimerSpec::new() | |
107 | .value(Some(timeout)) | |
108 | .interval(Some(Duration::from_millis(10))), | |
109 | )?; | |
1628a4c7 WB |
110 | |
111 | nix::fcntl::flock(file.as_raw_fd(), lockarg)?; | |
112 | Ok(()) | |
113 | } | |
365bb90f | 114 | |
51b499db DM |
115 | /// Open or create a lock file (append mode). Then try to |
116 | /// aquire a lock using `lock_file()`. | |
6100071f | 117 | pub fn open_file_locked<P: AsRef<Path>>(path: P, timeout: Duration) -> Result<File, Error> { |
1628a4c7 | 118 | let path = path.as_ref(); |
6100071f WB |
119 | let mut file = match OpenOptions::new().create(true).append(true).open(path) { |
120 | Ok(file) => file, | |
121 | Err(err) => bail!("Unable to open lock {:?} - {}", path, err), | |
122 | }; | |
28b96b56 DM |
123 | match lock_file(&mut file, true, Some(timeout)) { |
124 | Ok(_) => Ok(file), | |
6100071f | 125 | Err(err) => bail!("Unable to aquire lock {:?} - {}", path, err), |
28b96b56 | 126 | } |
365bb90f DM |
127 | } |
128 | ||
51b499db DM |
129 | /// Split a file into equal sized chunks. The last chunk may be |
130 | /// smaller. Note: We cannot implement an `Iterator`, because iterators | |
131 | /// cannot return a borrowed buffer ref (we want zero-copy) | |
6100071f WB |
132 | pub fn file_chunker<C, R>(mut file: R, chunk_size: usize, mut chunk_cb: C) -> Result<(), Error> |
133 | where | |
134 | C: FnMut(usize, &[u8]) -> Result<bool, Error>, | |
135 | R: Read, | |
43eeef28 | 136 | { |
6100071f | 137 | const READ_BUFFER_SIZE: usize = 4 * 1024 * 1024; // 4M |
43eeef28 | 138 | |
6100071f WB |
139 | if chunk_size > READ_BUFFER_SIZE { |
140 | bail!("chunk size too large!"); | |
141 | } | |
43eeef28 | 142 | |
8ea3b1d1 | 143 | let mut buf = vec::undefined(READ_BUFFER_SIZE); |
43eeef28 DM |
144 | |
145 | let mut pos = 0; | |
146 | let mut file_pos = 0; | |
147 | loop { | |
148 | let mut eof = false; | |
149 | let mut tmp = &mut buf[..]; | |
6100071f | 150 | // try to read large portions, at least chunk_size |
43eeef28 DM |
151 | while pos < chunk_size { |
152 | match file.read(tmp) { | |
6100071f WB |
153 | Ok(0) => { |
154 | eof = true; | |
155 | break; | |
156 | } | |
43eeef28 DM |
157 | Ok(n) => { |
158 | pos += n; | |
6100071f WB |
159 | if pos > chunk_size { |
160 | break; | |
161 | } | |
43eeef28 DM |
162 | tmp = &mut tmp[n..]; |
163 | } | |
164 | Err(ref e) if e.kind() == ErrorKind::Interrupted => { /* try again */ } | |
5f0c2d56 | 165 | Err(e) => bail!("read chunk failed - {}", e.to_string()), |
43eeef28 DM |
166 | } |
167 | } | |
43eeef28 DM |
168 | let mut start = 0; |
169 | while start + chunk_size <= pos { | |
6100071f WB |
170 | if !(chunk_cb)(file_pos, &buf[start..start + chunk_size])? { |
171 | break; | |
172 | } | |
43eeef28 DM |
173 | file_pos += chunk_size; |
174 | start += chunk_size; | |
175 | } | |
176 | if eof { | |
177 | if start < pos { | |
178 | (chunk_cb)(file_pos, &buf[start..pos])?; | |
179 | //file_pos += pos - start; | |
180 | } | |
181 | break; | |
182 | } else { | |
183 | let rest = pos - start; | |
184 | if rest > 0 { | |
185 | let ptr = buf.as_mut_ptr(); | |
6100071f WB |
186 | unsafe { |
187 | std::ptr::copy_nonoverlapping(ptr.add(start), ptr, rest); | |
188 | } | |
43eeef28 DM |
189 | pos = rest; |
190 | } else { | |
191 | pos = 0; | |
192 | } | |
193 | } | |
194 | } | |
195 | ||
196 | Ok(()) | |
43eeef28 | 197 | } |
0fe5d605 | 198 | |
eea81319 | 199 | /// Returns the Unix uid/gid for the sepcified system user. |
6100071f | 200 | pub fn getpwnam_ugid(username: &str) -> Result<(libc::uid_t, libc::gid_t), Error> { |
5d14eb6a DM |
201 | let info = unsafe { libc::getpwnam(std::ffi::CString::new(username).unwrap().as_ptr()) }; |
202 | if info == std::ptr::null_mut() { | |
203 | bail!("getwpnam '{}' failed", username); | |
204 | } | |
205 | ||
206 | let info = unsafe { *info }; | |
207 | ||
208 | Ok((info.pw_uid, info.pw_gid)) | |
209 | } | |
210 | ||
f5f13ebc | 211 | pub fn json_object_to_query(data: Value) -> Result<String, Error> { |
f5f13ebc DM |
212 | let mut query = url::form_urlencoded::Serializer::new(String::new()); |
213 | ||
214 | let object = data.as_object().ok_or_else(|| { | |
215 | format_err!("json_object_to_query: got wrong data type (expected object).") | |
216 | })?; | |
217 | ||
218 | for (key, value) in object { | |
219 | match value { | |
6100071f WB |
220 | Value::Bool(b) => { |
221 | query.append_pair(key, &b.to_string()); | |
222 | } | |
223 | Value::Number(n) => { | |
224 | query.append_pair(key, &n.to_string()); | |
225 | } | |
226 | Value::String(s) => { | |
227 | query.append_pair(key, &s); | |
228 | } | |
f5f13ebc DM |
229 | Value::Array(arr) => { |
230 | for element in arr { | |
231 | match element { | |
6100071f WB |
232 | Value::Bool(b) => { |
233 | query.append_pair(key, &b.to_string()); | |
234 | } | |
235 | Value::Number(n) => { | |
236 | query.append_pair(key, &n.to_string()); | |
237 | } | |
238 | Value::String(s) => { | |
239 | query.append_pair(key, &s); | |
240 | } | |
241 | _ => bail!( | |
242 | "json_object_to_query: unable to handle complex array data types." | |
243 | ), | |
f5f13ebc DM |
244 | } |
245 | } | |
246 | } | |
247 | _ => bail!("json_object_to_query: unable to handle complex data types."), | |
248 | } | |
249 | } | |
250 | ||
251 | Ok(query.finish()) | |
252 | } | |
253 | ||
0fe5d605 | 254 | pub fn required_string_param<'a>(param: &'a Value, name: &str) -> Result<&'a str, Error> { |
6100071f | 255 | match param[name].as_str() { |
0fe5d605 DM |
256 | Some(s) => Ok(s), |
257 | None => bail!("missing parameter '{}'", name), | |
258 | } | |
259 | } | |
0d38dcb4 DM |
260 | |
261 | pub fn required_integer_param<'a>(param: &'a Value, name: &str) -> Result<i64, Error> { | |
6100071f | 262 | match param[name].as_i64() { |
0d38dcb4 DM |
263 | Some(s) => Ok(s), |
264 | None => bail!("missing parameter '{}'", name), | |
f8dfbb45 DM |
265 | } |
266 | } | |
267 | ||
268 | pub fn required_array_param<'a>(param: &'a Value, name: &str) -> Result<Vec<Value>, Error> { | |
6100071f | 269 | match param[name].as_array() { |
f8dfbb45 DM |
270 | Some(s) => Ok(s.to_vec()), |
271 | None => bail!("missing parameter '{}'", name), | |
0d38dcb4 DM |
272 | } |
273 | } | |
383e8577 | 274 | |
496a6784 | 275 | pub fn complete_file_name(arg: &str, _param: &HashMap<String, String>) -> Vec<String> { |
383e8577 DM |
276 | let mut result = vec![]; |
277 | ||
6100071f | 278 | use nix::fcntl::AtFlags; |
383e8577 DM |
279 | use nix::fcntl::OFlag; |
280 | use nix::sys::stat::Mode; | |
383e8577 | 281 | |
806500cd | 282 | let mut dirname = std::path::PathBuf::from(if arg.len() == 0 { "./" } else { arg }); |
383e8577 DM |
283 | |
284 | let is_dir = match nix::sys::stat::fstatat(libc::AT_FDCWD, &dirname, AtFlags::empty()) { | |
285 | Ok(stat) => (stat.st_mode & libc::S_IFMT) == libc::S_IFDIR, | |
286 | Err(_) => false, | |
287 | }; | |
288 | ||
289 | if !is_dir { | |
290 | if let Some(parent) = dirname.parent() { | |
291 | dirname = parent.to_owned(); | |
292 | } | |
293 | } | |
294 | ||
6100071f WB |
295 | let mut dir = |
296 | match nix::dir::Dir::openat(libc::AT_FDCWD, &dirname, OFlag::O_DIRECTORY, Mode::empty()) { | |
297 | Ok(d) => d, | |
298 | Err(_) => return result, | |
299 | }; | |
383e8577 DM |
300 | |
301 | for item in dir.iter() { | |
302 | if let Ok(entry) = item { | |
303 | if let Ok(name) = entry.file_name().to_str() { | |
6100071f WB |
304 | if name == "." || name == ".." { |
305 | continue; | |
306 | } | |
383e8577 DM |
307 | let mut newpath = dirname.clone(); |
308 | newpath.push(name); | |
309 | ||
6100071f WB |
310 | if let Ok(stat) = |
311 | nix::sys::stat::fstatat(libc::AT_FDCWD, &newpath, AtFlags::empty()) | |
312 | { | |
383e8577 DM |
313 | if (stat.st_mode & libc::S_IFMT) == libc::S_IFDIR { |
314 | newpath.push(""); | |
315 | if let Some(newpath) = newpath.to_str() { | |
316 | result.push(newpath.to_owned()); | |
317 | } | |
318 | continue; | |
6100071f | 319 | } |
383e8577 DM |
320 | } |
321 | if let Some(newpath) = newpath.to_str() { | |
322 | result.push(newpath.to_owned()); | |
323 | } | |
6100071f | 324 | } |
383e8577 DM |
325 | } |
326 | } | |
327 | ||
328 | result | |
329 | } | |
443f3743 DM |
330 | |
331 | /// Scan directory for matching file names. | |
332 | /// | |
333 | /// Scan through all directory entries and call `callback()` function | |
334 | /// if the entry name matches the regular expression. This function | |
335 | /// used unix `openat()`, so you can pass absolute or relative file | |
336 | /// names. This function simply skips non-UTF8 encoded names. | |
337 | pub fn scandir<P, F>( | |
338 | dirfd: RawFd, | |
121f18ef | 339 | path: &P, |
443f3743 | 340 | regex: ®ex::Regex, |
6100071f | 341 | mut callback: F, |
443f3743 | 342 | ) -> Result<(), Error> |
6100071f WB |
343 | where |
344 | F: FnMut(RawFd, &str, nix::dir::Type) -> Result<(), Error>, | |
345 | P: ?Sized + nix::NixPath, | |
443f3743 | 346 | { |
121f18ef | 347 | for entry in self::fs::scan_subdir(dirfd, path, regex)? { |
443f3743 DM |
348 | let entry = entry?; |
349 | let file_type = match entry.file_type() { | |
350 | Some(file_type) => file_type, | |
351 | None => bail!("unable to detect file type"), | |
352 | }; | |
443f3743 | 353 | |
6100071f WB |
354 | callback( |
355 | entry.parent_fd(), | |
356 | unsafe { entry.file_name_utf8_unchecked() }, | |
357 | file_type, | |
358 | )?; | |
443f3743 DM |
359 | } |
360 | Ok(()) | |
361 | } | |
7e13b2d6 DM |
362 | |
363 | pub fn get_hardware_address() -> Result<String, Error> { | |
1631c54f | 364 | static FILENAME: &str = "/etc/ssh/ssh_host_rsa_key.pub"; |
7e13b2d6 | 365 | |
e18a6c9e | 366 | let contents = proxmox::tools::fs::file_get_contents(FILENAME)?; |
7e13b2d6 DM |
367 | let digest = md5::compute(contents); |
368 | ||
369 | Ok(format!("{:0x}", digest)) | |
370 | } | |
22968600 | 371 | |
af2fddea DM |
372 | pub fn assert_if_modified(digest1: &str, digest2: &str) -> Result<(), Error> { |
373 | if digest1 != digest2 { | |
6100071f | 374 | bail!("detected modified configuration - file changed by other user? Try again."); |
af2fddea DM |
375 | } |
376 | Ok(()) | |
377 | } | |
b9903d63 DM |
378 | |
379 | /// Extract authentication cookie from cookie header. | |
380 | /// We assume cookie_name is already url encoded. | |
381 | pub fn extract_auth_cookie(cookie: &str, cookie_name: &str) -> Option<String> { | |
b9903d63 | 382 | for pair in cookie.split(';') { |
b9903d63 DM |
383 | let (name, value) = match pair.find('=') { |
384 | Some(i) => (pair[..i].trim(), pair[(i + 1)..].trim()), | |
385 | None => return None, // Cookie format error | |
386 | }; | |
387 | ||
388 | if name == cookie_name { | |
389 | use url::percent_encoding::percent_decode; | |
390 | if let Ok(value) = percent_decode(value.as_bytes()).decode_utf8() { | |
391 | return Some(value.into()); | |
392 | } else { | |
393 | return None; // Cookie format error | |
394 | } | |
395 | } | |
396 | } | |
397 | ||
398 | None | |
399 | } | |
af53186e DM |
400 | |
401 | pub fn join(data: &Vec<String>, sep: char) -> String { | |
af53186e DM |
402 | let mut list = String::new(); |
403 | ||
404 | for item in data { | |
6100071f WB |
405 | if list.len() != 0 { |
406 | list.push(sep); | |
407 | } | |
af53186e DM |
408 | list.push_str(item); |
409 | } | |
410 | ||
411 | list | |
412 | } | |
ff7049d4 | 413 | |
3578d99f DM |
414 | /// normalize uri path |
415 | /// | |
416 | /// Do not allow ".", "..", or hidden files ".XXXX" | |
417 | /// Also remove empty path components | |
418 | pub fn normalize_uri_path(path: &str) -> Result<(String, Vec<&str>), Error> { | |
3578d99f DM |
419 | let items = path.split('/'); |
420 | ||
421 | let mut path = String::new(); | |
422 | let mut components = vec![]; | |
423 | ||
424 | for name in items { | |
6100071f WB |
425 | if name.is_empty() { |
426 | continue; | |
427 | } | |
3578d99f DM |
428 | if name.starts_with(".") { |
429 | bail!("Path contains illegal components."); | |
430 | } | |
431 | path.push('/'); | |
432 | path.push_str(name); | |
433 | components.push(name); | |
434 | } | |
435 | ||
436 | Ok((path, components)) | |
437 | } | |
438 | ||
ff7049d4 | 439 | pub fn fd_change_cloexec(fd: RawFd, on: bool) -> Result<(), Error> { |
6100071f | 440 | use nix::fcntl::{fcntl, FdFlag, F_GETFD, F_SETFD}; |
ff7049d4 WB |
441 | let mut flags = FdFlag::from_bits(fcntl(fd, F_GETFD)?) |
442 | .ok_or_else(|| format_err!("unhandled file flags"))?; // nix crate is stupid this way... | |
443 | flags.set(FdFlag::FD_CLOEXEC, on); | |
444 | fcntl(fd, F_SETFD(flags))?; | |
445 | Ok(()) | |
446 | } | |
9136f857 | 447 | |
9136f857 DM |
448 | static mut SHUTDOWN_REQUESTED: bool = false; |
449 | ||
450 | pub fn request_shutdown() { | |
6100071f WB |
451 | unsafe { |
452 | SHUTDOWN_REQUESTED = true; | |
453 | } | |
7a630df7 | 454 | crate::server::server_shutdown(); |
9136f857 DM |
455 | } |
456 | ||
457 | #[inline(always)] | |
458 | pub fn shutdown_requested() -> bool { | |
459 | unsafe { SHUTDOWN_REQUESTED } | |
460 | } | |
92da93b2 DM |
461 | |
462 | pub fn fail_on_shutdown() -> Result<(), Error> { | |
463 | if shutdown_requested() { | |
464 | bail!("Server shutdown requested - aborting task"); | |
465 | } | |
466 | Ok(()) | |
467 | } | |
d96bb7f1 WB |
468 | |
469 | /// Guard a raw file descriptor with a drop handler. This is mostly useful when access to an owned | |
470 | /// `RawFd` is required without the corresponding handler object (such as when only the file | |
471 | /// descriptor number is required in a closure which may be dropped instead of being executed). | |
472 | pub struct Fd(pub RawFd); | |
473 | ||
474 | impl Drop for Fd { | |
475 | fn drop(&mut self) { | |
476 | if self.0 != -1 { | |
477 | unsafe { | |
478 | libc::close(self.0); | |
479 | } | |
480 | } | |
481 | } | |
482 | } | |
483 | ||
484 | impl AsRawFd for Fd { | |
485 | fn as_raw_fd(&self) -> RawFd { | |
486 | self.0 | |
487 | } | |
488 | } | |
489 | ||
490 | impl IntoRawFd for Fd { | |
491 | fn into_raw_fd(mut self) -> RawFd { | |
492 | let fd = self.0; | |
493 | self.0 = -1; | |
494 | fd | |
495 | } | |
496 | } | |
497 | ||
498 | impl FromRawFd for Fd { | |
499 | unsafe fn from_raw_fd(fd: RawFd) -> Self { | |
500 | Self(fd) | |
501 | } | |
502 | } | |
efd1536e WB |
503 | |
504 | // wrap nix::unistd::pipe2 + O_CLOEXEC into something returning guarded file descriptors | |
505 | pub fn pipe() -> Result<(Fd, Fd), Error> { | |
506 | let (pin, pout) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?; | |
507 | Ok((Fd(pin), Fd(pout))) | |
508 | } | |
2edc341b DM |
509 | |
510 | /// An easy way to convert types to Any | |
511 | /// | |
512 | /// Mostly useful to downcast trait objects (see RpcEnvironment). | |
513 | pub trait AsAny { | |
dd5495d6 | 514 | fn as_any(&self) -> &dyn Any; |
2edc341b DM |
515 | } |
516 | ||
517 | impl<T: Any> AsAny for T { | |
6100071f WB |
518 | fn as_any(&self) -> &dyn Any { |
519 | self | |
520 | } | |
2edc341b | 521 | } |