]> git.proxmox.com Git - proxmox-backup.git/blame - src/tools.rs
src/api2/admin/datastore.rs: use new WorkerTask::new_thread()
[proxmox-backup.git] / src / tools.rs
CommitLineData
51b499db
DM
1//! Tools and utilities
2//!
3//! This is a collection of small and useful tools.
f12f8ff1
DM
4use failure::*;
5use nix::unistd;
6use nix::sys::stat;
7
74a69302
DM
8use lazy_static::lazy_static;
9
365bb90f 10use std::fs::{File, OpenOptions};
f12f8ff1
DM
11use std::io::Write;
12use std::path::Path;
43eeef28
DM
13use std::io::Read;
14use std::io::ErrorKind;
1628a4c7 15use std::time::Duration;
f12f8ff1 16
443f3743 17use std::os::unix::io::RawFd;
eae8aa3a 18use std::os::unix::io::AsRawFd;
365bb90f 19
496a6784
DM
20use std::collections::HashMap;
21
af926291 22use serde_json::Value;
0fe5d605 23
8cf6e764 24pub mod timer;
7f0d67cf 25pub mod wrapped_reader_stream;
8f973f81
DM
26#[macro_use]
27pub mod common_regex;
8d04280b 28pub mod ticket;
6ed25cbe 29pub mod borrow;
b4d5787d 30pub mod fs;
c9b296f1 31pub mod tty;
f54c1998 32pub mod signalfd;
dce94d0e 33pub mod daemon;
3c2012f9 34pub mod procfs;
8cf6e764 35
a650f503
DM
36mod process_locker;
37pub use process_locker::*;
38
3b151414
DM
39#[macro_use]
40mod file_logger;
41pub use file_logger::*;
42
e80503d2
DM
43/// Macro to write error-handling blocks (like perl eval {})
44///
45/// #### Example:
46/// ```
fe651dd6
DM
47/// # #[macro_use] extern crate proxmox_backup;
48/// # use failure::*;
49/// # let some_condition = false;
e80503d2
DM
50/// let result = try_block!({
51/// if (some_condition) {
52/// bail!("some error");
53/// }
54/// Ok(())
55/// })
56/// .map_err(|e| format_err!("my try block returned an error - {}", e));
57/// ```
58
f0dbba8c
DM
59#[macro_export]
60macro_rules! try_block {
61 { $($token:tt)* } => {{ (|| -> Result<_,_> { $($token)* })() }}
62}
63
5d14eb6a 64
fded74d0 65/// The `BufferedRead` trait provides a single function
0a72e267
DM
66/// `buffered_read`. It returns a reference to an internal buffer. The
67/// purpose of this traid is to avoid unnecessary data copies.
fded74d0 68pub trait BufferedRead {
318564ac
DM
69 /// This functions tries to fill the internal buffers, then
70 /// returns a reference to the available data. It returns an empty
71 /// buffer if `offset` points to the end of the file.
0a72e267
DM
72 fn buffered_read(&mut self, offset: u64) -> Result<&[u8], Error>;
73}
74
51b499db
DM
75/// Directly map a type into a binary buffer. This is mostly useful
76/// for reading structured data from a byte stream (file). You need to
77/// make sure that the buffer location does not change, so please
78/// avoid vec resize while you use such map.
79///
80/// This function panics if the buffer is not large enough.
50ea4396 81pub fn map_struct<T>(buffer: &[u8]) -> Result<&T, Error> {
dc3de618
DM
82 if buffer.len() < ::std::mem::size_of::<T>() {
83 bail!("unable to map struct - buffer too small");
84 }
95bd5dfe 85 Ok(unsafe { & * (buffer.as_ptr() as *const T) })
dc3de618
DM
86}
87
51b499db
DM
88/// Directly map a type into a mutable binary buffer. This is mostly
89/// useful for writing structured data into a byte stream (file). You
90/// need to make sure that the buffer location does not change, so
91/// please avoid vec resize while you use such map.
92///
93/// This function panics if the buffer is not large enough.
50ea4396 94pub fn map_struct_mut<T>(buffer: &mut [u8]) -> Result<&mut T, Error> {
dc3de618
DM
95 if buffer.len() < ::std::mem::size_of::<T>() {
96 bail!("unable to map struct - buffer too small");
97 }
95bd5dfe 98 Ok(unsafe { &mut * (buffer.as_ptr() as *mut T) })
dc3de618
DM
99}
100
6235a418 101pub fn file_read_firstline<P: AsRef<Path>>(path: P) -> Result<String, Error> {
447787ab
DM
102
103 let path = path.as_ref();
104
6235a418
DM
105 try_block!({
106 let file = std::fs::File::open(path)?;
447787ab 107
6235a418 108 use std::io::{BufRead, BufReader};
447787ab 109
6235a418 110 let mut reader = BufReader::new(file);
447787ab 111
6235a418 112 let mut line = String::new();
447787ab 113
6235a418 114 let _ = reader.read_line(&mut line)?;
447787ab 115
6235a418
DM
116 Ok(line)
117 }).map_err(|err: Error| format_err!("unable to read {:?} - {}", path, err))
447787ab
DM
118}
119
12400210
DM
120pub fn file_get_contents<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, Error> {
121
122 let path = path.as_ref();
123
124 try_block!({
125 std::fs::read(path)
126 }).map_err(|err| format_err!("unable to read {:?} - {}", path, err))
53157ca6
DM
127}
128
49cf9f3d 129pub fn file_get_json<P: AsRef<Path>>(path: P, default: Option<Value>) -> Result<Value, Error> {
53cafb59
DM
130
131 let path = path.as_ref();
132
49cf9f3d
DM
133 let raw = match std::fs::read(path) {
134 Ok(v) => v,
135 Err(err) => {
136 if err.kind() == std::io::ErrorKind::NotFound {
137 if let Some(v) = default {
138 return Ok(v);
139 }
140 }
141 bail!("unable to read json {:?} - {}", path, err);
142 }
143 };
53cafb59
DM
144
145 try_block!({
146 let data = String::from_utf8(raw)?;
147 let json = serde_json::from_str(&data)?;
148 Ok(json)
49cf9f3d 149 }).map_err(|err: Error| format_err!("unable to parse json from {:?} - {}", path, err))
53cafb59
DM
150}
151
51b499db
DM
152/// Atomically write a file. We first create a temporary file, which
153/// is then renamed.
f12f8ff1
DM
154pub fn file_set_contents<P: AsRef<Path>>(
155 path: P,
156 data: &[u8],
157 perm: Option<stat::Mode>,
158) -> Result<(), Error> {
159
160 let path = path.as_ref();
161
d64d80d2
DM
162 // Note: we use mkstemp heŕe, because this worka with different
163 // processes, threads, and even tokio tasks.
f12f8ff1
DM
164 let mut template = path.to_owned();
165 template.set_extension("tmp_XXXXXX");
166 let (fd, tmp_path) = match unistd::mkstemp(&template) {
167 Ok((fd, path)) => (fd, path),
168 Err(err) => bail!("mkstemp {:?} failed: {}", template, err),
169 };
170
171 let tmp_path = tmp_path.as_path();
172
1a7bc3dd 173 let mode : stat::Mode = perm.unwrap_or(stat::Mode::from(
f12f8ff1
DM
174 stat::Mode::S_IRUSR | stat::Mode::S_IWUSR |
175 stat::Mode::S_IRGRP | stat::Mode::S_IROTH
1a7bc3dd 176 ));
f12f8ff1
DM
177
178 if let Err(err) = stat::fchmod(fd, mode) {
179 let _ = unistd::unlink(tmp_path);
180 bail!("fchmod {:?} failed: {}", tmp_path, err);
181 }
182
183 use std::os::unix::io::FromRawFd;
184 let mut file = unsafe { File::from_raw_fd(fd) };
185
186 if let Err(err) = file.write_all(data) {
187 let _ = unistd::unlink(tmp_path);
188 bail!("write failed: {}", err);
189 }
190
191 if let Err(err) = std::fs::rename(tmp_path, path) {
192 let _ = unistd::unlink(tmp_path);
193 bail!("Atomic rename failed for file {:?} - {}", path, err);
194 }
195
196 Ok(())
197}
43eeef28 198
51b499db
DM
199/// Create a file lock using fntl. This function allows you to specify
200/// a timeout if you want to avoid infinite blocking.
1628a4c7
WB
201pub fn lock_file<F: AsRawFd>(
202 file: &mut F,
203 exclusive: bool,
204 timeout: Option<Duration>,
eb90c9e3 205) -> Result<(), Error> {
1628a4c7
WB
206 let lockarg =
207 if exclusive {
208 nix::fcntl::FlockArg::LockExclusive
209 } else {
210 nix::fcntl::FlockArg::LockShared
211 };
212
213 let timeout = match timeout {
214 None => {
215 nix::fcntl::flock(file.as_raw_fd(), lockarg)?;
216 return Ok(());
217 }
218 Some(t) => t,
219 };
220
221 // unblock the timeout signal temporarily
222 let _sigblock_guard = timer::unblock_timeout_signal();
223
224 // setup a timeout timer
225 let mut timer = timer::Timer::create(
226 timer::Clock::Realtime,
227 timer::TimerEvent::ThisThreadSignal(timer::SIGTIMEOUT))?;
228
229 timer.arm(timer::TimerSpec::new()
230 .value(Some(timeout))
231 .interval(Some(Duration::from_millis(10))))?;
232
233 nix::fcntl::flock(file.as_raw_fd(), lockarg)?;
234 Ok(())
235}
365bb90f 236
51b499db
DM
237/// Open or create a lock file (append mode). Then try to
238/// aquire a lock using `lock_file()`.
1628a4c7
WB
239pub fn open_file_locked<P: AsRef<Path>>(path: P, timeout: Duration)
240 -> Result<File, Error>
241{
242 let path = path.as_ref();
243 let mut file =
244 match OpenOptions::new()
245 .create(true)
246 .append(true)
247 .open(path)
248 {
365bb90f
DM
249 Ok(file) => file,
250 Err(err) => bail!("Unable to open lock {:?} - {}",
251 path, err),
252 };
28b96b56
DM
253 match lock_file(&mut file, true, Some(timeout)) {
254 Ok(_) => Ok(file),
255 Err(err) => bail!("Unable to aquire lock {:?} - {}",
256 path, err),
257 }
365bb90f
DM
258}
259
51b499db
DM
260/// Split a file into equal sized chunks. The last chunk may be
261/// smaller. Note: We cannot implement an `Iterator`, because iterators
262/// cannot return a borrowed buffer ref (we want zero-copy)
43eeef28
DM
263pub fn file_chunker<C, R>(
264 mut file: R,
265 chunk_size: usize,
606ce64b 266 mut chunk_cb: C
43eeef28 267) -> Result<(), Error>
606ce64b 268 where C: FnMut(usize, &[u8]) -> Result<bool, Error>,
43eeef28
DM
269 R: Read,
270{
271
272 const READ_BUFFER_SIZE: usize = 4*1024*1024; // 4M
273
274 if chunk_size > READ_BUFFER_SIZE { bail!("chunk size too large!"); }
275
276 let mut buf = vec![0u8; READ_BUFFER_SIZE];
277
278 let mut pos = 0;
279 let mut file_pos = 0;
280 loop {
281 let mut eof = false;
282 let mut tmp = &mut buf[..];
283 // try to read large portions, at least chunk_size
284 while pos < chunk_size {
285 match file.read(tmp) {
286 Ok(0) => { eof = true; break; },
287 Ok(n) => {
288 pos += n;
289 if pos > chunk_size { break; }
290 tmp = &mut tmp[n..];
291 }
292 Err(ref e) if e.kind() == ErrorKind::Interrupted => { /* try again */ }
5f0c2d56 293 Err(e) => bail!("read chunk failed - {}", e.to_string()),
43eeef28
DM
294 }
295 }
43eeef28
DM
296 let mut start = 0;
297 while start + chunk_size <= pos {
298 if !(chunk_cb)(file_pos, &buf[start..start+chunk_size])? { break; }
299 file_pos += chunk_size;
300 start += chunk_size;
301 }
302 if eof {
303 if start < pos {
304 (chunk_cb)(file_pos, &buf[start..pos])?;
305 //file_pos += pos - start;
306 }
307 break;
308 } else {
309 let rest = pos - start;
310 if rest > 0 {
311 let ptr = buf.as_mut_ptr();
312 unsafe { std::ptr::copy_nonoverlapping(ptr.add(start), ptr, rest); }
313 pos = rest;
314 } else {
315 pos = 0;
316 }
317 }
318 }
319
320 Ok(())
43eeef28 321}
0fe5d605 322
5d14eb6a
DM
323// Returns the Unix uid/gid for the sepcified system user.
324pub fn getpwnam_ugid(username: &str) -> Result<(libc::uid_t,libc::gid_t), Error> {
325 let info = unsafe { libc::getpwnam(std::ffi::CString::new(username).unwrap().as_ptr()) };
326 if info == std::ptr::null_mut() {
327 bail!("getwpnam '{}' failed", username);
328 }
329
330 let info = unsafe { *info };
331
332 Ok((info.pw_uid, info.pw_gid))
333}
334
335// Returns the hosts node name (UTS node name)
74a69302
DM
336pub fn nodename() -> &'static str {
337
338 lazy_static!{
339 static ref NODENAME: String = {
340
0d38dcb4
DM
341 nix::sys::utsname::uname()
342 .nodename()
343 .split('.')
344 .next()
345 .unwrap()
346 .to_owned()
74a69302
DM
347 };
348 }
349
350 &NODENAME
351}
352
f5f13ebc
DM
353pub fn json_object_to_query(data: Value) -> Result<String, Error> {
354
355 let mut query = url::form_urlencoded::Serializer::new(String::new());
356
357 let object = data.as_object().ok_or_else(|| {
358 format_err!("json_object_to_query: got wrong data type (expected object).")
359 })?;
360
361 for (key, value) in object {
362 match value {
363 Value::Bool(b) => { query.append_pair(key, &b.to_string()); }
364 Value::Number(n) => { query.append_pair(key, &n.to_string()); }
365 Value::String(s) => { query.append_pair(key, &s); }
366 Value::Array(arr) => {
367 for element in arr {
368 match element {
369 Value::Bool(b) => { query.append_pair(key, &b.to_string()); }
370 Value::Number(n) => { query.append_pair(key, &n.to_string()); }
371 Value::String(s) => { query.append_pair(key, &s); }
372 _ => bail!("json_object_to_query: unable to handle complex array data types."),
373 }
374 }
375 }
376 _ => bail!("json_object_to_query: unable to handle complex data types."),
377 }
378 }
379
380 Ok(query.finish())
381}
382
0fe5d605
DM
383pub fn required_string_param<'a>(param: &'a Value, name: &str) -> Result<&'a str, Error> {
384 match param[name].as_str() {
385 Some(s) => Ok(s),
386 None => bail!("missing parameter '{}'", name),
387 }
388}
0d38dcb4
DM
389
390pub fn required_integer_param<'a>(param: &'a Value, name: &str) -> Result<i64, Error> {
391 match param[name].as_i64() {
392 Some(s) => Ok(s),
393 None => bail!("missing parameter '{}'", name),
f8dfbb45
DM
394 }
395}
396
397pub fn required_array_param<'a>(param: &'a Value, name: &str) -> Result<Vec<Value>, Error> {
398 match param[name].as_array() {
399 Some(s) => Ok(s.to_vec()),
400 None => bail!("missing parameter '{}'", name),
0d38dcb4
DM
401 }
402}
383e8577 403
496a6784 404pub fn complete_file_name(arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
383e8577
DM
405
406 let mut result = vec![];
407
408 use nix::fcntl::OFlag;
409 use nix::sys::stat::Mode;
410 use nix::fcntl::AtFlags;
411
806500cd 412 let mut dirname = std::path::PathBuf::from(if arg.len() == 0 { "./" } else { arg });
383e8577
DM
413
414 let is_dir = match nix::sys::stat::fstatat(libc::AT_FDCWD, &dirname, AtFlags::empty()) {
415 Ok(stat) => (stat.st_mode & libc::S_IFMT) == libc::S_IFDIR,
416 Err(_) => false,
417 };
418
419 if !is_dir {
420 if let Some(parent) = dirname.parent() {
421 dirname = parent.to_owned();
422 }
423 }
424
425 let mut dir = match nix::dir::Dir::openat(libc::AT_FDCWD, &dirname, OFlag::O_DIRECTORY, Mode::empty()) {
426 Ok(d) => d,
427 Err(_) => return result,
428 };
429
430 for item in dir.iter() {
431 if let Ok(entry) = item {
432 if let Ok(name) = entry.file_name().to_str() {
433 if name == "." || name == ".." { continue; }
434 let mut newpath = dirname.clone();
435 newpath.push(name);
436
437 if let Ok(stat) = nix::sys::stat::fstatat(libc::AT_FDCWD, &newpath, AtFlags::empty()) {
438 if (stat.st_mode & libc::S_IFMT) == libc::S_IFDIR {
439 newpath.push("");
440 if let Some(newpath) = newpath.to_str() {
441 result.push(newpath.to_owned());
442 }
443 continue;
444 }
445 }
446 if let Some(newpath) = newpath.to_str() {
447 result.push(newpath.to_owned());
448 }
449
450 }
451 }
452 }
453
454 result
455}
443f3743
DM
456
457/// Scan directory for matching file names.
458///
459/// Scan through all directory entries and call `callback()` function
460/// if the entry name matches the regular expression. This function
461/// used unix `openat()`, so you can pass absolute or relative file
462/// names. This function simply skips non-UTF8 encoded names.
463pub fn scandir<P, F>(
464 dirfd: RawFd,
121f18ef 465 path: &P,
443f3743 466 regex: &regex::Regex,
cce1676a 467 mut callback: F
443f3743 468) -> Result<(), Error>
cce1676a 469 where F: FnMut(RawFd, &str, nix::dir::Type) -> Result<(), Error>,
121f18ef 470 P: ?Sized + nix::NixPath,
443f3743 471{
121f18ef 472 for entry in self::fs::scan_subdir(dirfd, path, regex)? {
443f3743
DM
473 let entry = entry?;
474 let file_type = match entry.file_type() {
475 Some(file_type) => file_type,
476 None => bail!("unable to detect file type"),
477 };
443f3743 478
121f18ef 479 callback(entry.parent_fd(), unsafe { entry.file_name_utf8_unchecked() }, file_type)?;
443f3743
DM
480 }
481 Ok(())
482}
7e13b2d6
DM
483
484pub fn get_hardware_address() -> Result<String, Error> {
485
1631c54f 486 static FILENAME: &str = "/etc/ssh/ssh_host_rsa_key.pub";
7e13b2d6 487
1631c54f 488 let contents = file_get_contents(FILENAME)?;
7e13b2d6
DM
489 let digest = md5::compute(contents);
490
491 Ok(format!("{:0x}", digest))
492}
22968600
DM
493
494pub fn digest_to_hex(digest: &[u8]) -> String {
495
496 const HEX_CHARS: &'static [u8; 16] = b"0123456789abcdef";
497
498 let mut buf = Vec::<u8>::with_capacity(digest.len()*2);
499
500 for i in 0..digest.len() {
501 buf.push(HEX_CHARS[(digest[i] >> 4) as usize]);
502 buf.push(HEX_CHARS[(digest[i] & 0xf) as usize]);
503 }
504
505 unsafe { String::from_utf8_unchecked(buf) }
506}
507
af2fddea
DM
508pub fn assert_if_modified(digest1: &str, digest2: &str) -> Result<(), Error> {
509 if digest1 != digest2 {
510 bail!("detected modified configuration - file changed by other user? Try again.");
511 }
512 Ok(())
513}
b9903d63
DM
514
515/// Extract authentication cookie from cookie header.
516/// We assume cookie_name is already url encoded.
517pub fn extract_auth_cookie(cookie: &str, cookie_name: &str) -> Option<String> {
518
519 for pair in cookie.split(';') {
520
521 let (name, value) = match pair.find('=') {
522 Some(i) => (pair[..i].trim(), pair[(i + 1)..].trim()),
523 None => return None, // Cookie format error
524 };
525
526 if name == cookie_name {
527 use url::percent_encoding::percent_decode;
528 if let Ok(value) = percent_decode(value.as_bytes()).decode_utf8() {
529 return Some(value.into());
530 } else {
531 return None; // Cookie format error
532 }
533 }
534 }
535
536 None
537}
af53186e
DM
538
539pub fn join(data: &Vec<String>, sep: char) -> String {
540
541 let mut list = String::new();
542
543 for item in data {
544 if list.len() != 0 { list.push(sep); }
545 list.push_str(item);
546 }
547
548 list
549}
ff7049d4
WB
550
551pub fn fd_change_cloexec(fd: RawFd, on: bool) -> Result<(), Error> {
552 use nix::fcntl::{fcntl, F_GETFD, F_SETFD, FdFlag};
553 let mut flags = FdFlag::from_bits(fcntl(fd, F_GETFD)?)
554 .ok_or_else(|| format_err!("unhandled file flags"))?; // nix crate is stupid this way...
555 flags.set(FdFlag::FD_CLOEXEC, on);
556 fcntl(fd, F_SETFD(flags))?;
557 Ok(())
558}
9136f857
DM
559
560
561static mut SHUTDOWN_REQUESTED: bool = false;
562
563pub fn request_shutdown() {
564 unsafe { SHUTDOWN_REQUESTED = true; }
565}
566
567#[inline(always)]
568pub fn shutdown_requested() -> bool {
569 unsafe { SHUTDOWN_REQUESTED }
570}
92da93b2
DM
571
572pub fn fail_on_shutdown() -> Result<(), Error> {
573 if shutdown_requested() {
574 bail!("Server shutdown requested - aborting task");
575 }
576 Ok(())
577}