]> git.proxmox.com Git - proxmox-backup.git/blame - src/tools.rs
src/api2/node/tasks.rs: start upid API
[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
eea81319
DM
152/// Atomically write a file
153///
154/// We first create a temporary file, which is then renamed.
f12f8ff1
DM
155pub fn file_set_contents<P: AsRef<Path>>(
156 path: P,
157 data: &[u8],
158 perm: Option<stat::Mode>,
159) -> Result<(), Error> {
eea81319
DM
160 file_set_contents_full(path, data, perm, None, None)
161}
162
163/// Atomically write a file with owner and group
164pub fn file_set_contents_full<P: AsRef<Path>>(
165 path: P,
166 data: &[u8],
167 perm: Option<stat::Mode>,
1619a720
DM
168 owner: Option<unistd::Uid>,
169 group: Option<unistd::Gid>,
eea81319 170) -> Result<(), Error> {
f12f8ff1
DM
171
172 let path = path.as_ref();
173
d64d80d2
DM
174 // Note: we use mkstemp heŕe, because this worka with different
175 // processes, threads, and even tokio tasks.
f12f8ff1
DM
176 let mut template = path.to_owned();
177 template.set_extension("tmp_XXXXXX");
178 let (fd, tmp_path) = match unistd::mkstemp(&template) {
179 Ok((fd, path)) => (fd, path),
180 Err(err) => bail!("mkstemp {:?} failed: {}", template, err),
181 };
182
183 let tmp_path = tmp_path.as_path();
184
1a7bc3dd 185 let mode : stat::Mode = perm.unwrap_or(stat::Mode::from(
f12f8ff1
DM
186 stat::Mode::S_IRUSR | stat::Mode::S_IWUSR |
187 stat::Mode::S_IRGRP | stat::Mode::S_IROTH
1a7bc3dd 188 ));
f12f8ff1
DM
189
190 if let Err(err) = stat::fchmod(fd, mode) {
191 let _ = unistd::unlink(tmp_path);
192 bail!("fchmod {:?} failed: {}", tmp_path, err);
193 }
194
eea81319
DM
195 if owner != None || group != None {
196 if let Err(err) = fchown(fd, owner, group) {
197 let _ = unistd::unlink(tmp_path);
198 bail!("fchown {:?} failed: {}", tmp_path, err);
199 }
200 }
201
f12f8ff1
DM
202 use std::os::unix::io::FromRawFd;
203 let mut file = unsafe { File::from_raw_fd(fd) };
204
205 if let Err(err) = file.write_all(data) {
206 let _ = unistd::unlink(tmp_path);
207 bail!("write failed: {}", err);
208 }
209
210 if let Err(err) = std::fs::rename(tmp_path, path) {
211 let _ = unistd::unlink(tmp_path);
212 bail!("Atomic rename failed for file {:?} - {}", path, err);
213 }
214
215 Ok(())
216}
43eeef28 217
51b499db
DM
218/// Create a file lock using fntl. This function allows you to specify
219/// a timeout if you want to avoid infinite blocking.
1628a4c7
WB
220pub fn lock_file<F: AsRawFd>(
221 file: &mut F,
222 exclusive: bool,
223 timeout: Option<Duration>,
eb90c9e3 224) -> Result<(), Error> {
1628a4c7
WB
225 let lockarg =
226 if exclusive {
227 nix::fcntl::FlockArg::LockExclusive
228 } else {
229 nix::fcntl::FlockArg::LockShared
230 };
231
232 let timeout = match timeout {
233 None => {
234 nix::fcntl::flock(file.as_raw_fd(), lockarg)?;
235 return Ok(());
236 }
237 Some(t) => t,
238 };
239
240 // unblock the timeout signal temporarily
241 let _sigblock_guard = timer::unblock_timeout_signal();
242
243 // setup a timeout timer
244 let mut timer = timer::Timer::create(
245 timer::Clock::Realtime,
246 timer::TimerEvent::ThisThreadSignal(timer::SIGTIMEOUT))?;
247
248 timer.arm(timer::TimerSpec::new()
249 .value(Some(timeout))
250 .interval(Some(Duration::from_millis(10))))?;
251
252 nix::fcntl::flock(file.as_raw_fd(), lockarg)?;
253 Ok(())
254}
365bb90f 255
51b499db
DM
256/// Open or create a lock file (append mode). Then try to
257/// aquire a lock using `lock_file()`.
1628a4c7
WB
258pub fn open_file_locked<P: AsRef<Path>>(path: P, timeout: Duration)
259 -> Result<File, Error>
260{
261 let path = path.as_ref();
262 let mut file =
263 match OpenOptions::new()
264 .create(true)
265 .append(true)
266 .open(path)
267 {
365bb90f
DM
268 Ok(file) => file,
269 Err(err) => bail!("Unable to open lock {:?} - {}",
270 path, err),
271 };
28b96b56
DM
272 match lock_file(&mut file, true, Some(timeout)) {
273 Ok(_) => Ok(file),
274 Err(err) => bail!("Unable to aquire lock {:?} - {}",
275 path, err),
276 }
365bb90f
DM
277}
278
51b499db
DM
279/// Split a file into equal sized chunks. The last chunk may be
280/// smaller. Note: We cannot implement an `Iterator`, because iterators
281/// cannot return a borrowed buffer ref (we want zero-copy)
43eeef28
DM
282pub fn file_chunker<C, R>(
283 mut file: R,
284 chunk_size: usize,
606ce64b 285 mut chunk_cb: C
43eeef28 286) -> Result<(), Error>
606ce64b 287 where C: FnMut(usize, &[u8]) -> Result<bool, Error>,
43eeef28
DM
288 R: Read,
289{
290
291 const READ_BUFFER_SIZE: usize = 4*1024*1024; // 4M
292
293 if chunk_size > READ_BUFFER_SIZE { bail!("chunk size too large!"); }
294
295 let mut buf = vec![0u8; READ_BUFFER_SIZE];
296
297 let mut pos = 0;
298 let mut file_pos = 0;
299 loop {
300 let mut eof = false;
301 let mut tmp = &mut buf[..];
302 // try to read large portions, at least chunk_size
303 while pos < chunk_size {
304 match file.read(tmp) {
305 Ok(0) => { eof = true; break; },
306 Ok(n) => {
307 pos += n;
308 if pos > chunk_size { break; }
309 tmp = &mut tmp[n..];
310 }
311 Err(ref e) if e.kind() == ErrorKind::Interrupted => { /* try again */ }
5f0c2d56 312 Err(e) => bail!("read chunk failed - {}", e.to_string()),
43eeef28
DM
313 }
314 }
43eeef28
DM
315 let mut start = 0;
316 while start + chunk_size <= pos {
317 if !(chunk_cb)(file_pos, &buf[start..start+chunk_size])? { break; }
318 file_pos += chunk_size;
319 start += chunk_size;
320 }
321 if eof {
322 if start < pos {
323 (chunk_cb)(file_pos, &buf[start..pos])?;
324 //file_pos += pos - start;
325 }
326 break;
327 } else {
328 let rest = pos - start;
329 if rest > 0 {
330 let ptr = buf.as_mut_ptr();
331 unsafe { std::ptr::copy_nonoverlapping(ptr.add(start), ptr, rest); }
332 pos = rest;
333 } else {
334 pos = 0;
335 }
336 }
337 }
338
339 Ok(())
43eeef28 340}
0fe5d605 341
eea81319 342/// Returns the Unix uid/gid for the sepcified system user.
5d14eb6a
DM
343pub fn getpwnam_ugid(username: &str) -> Result<(libc::uid_t,libc::gid_t), Error> {
344 let info = unsafe { libc::getpwnam(std::ffi::CString::new(username).unwrap().as_ptr()) };
345 if info == std::ptr::null_mut() {
346 bail!("getwpnam '{}' failed", username);
347 }
348
349 let info = unsafe { *info };
350
351 Ok((info.pw_uid, info.pw_gid))
352}
353
35950380
DM
354/// Creates directory at the provided path with specified ownership
355///
356/// Simply returns if the directory already exists.
1619a720
DM
357pub fn create_dir_chown<P: AsRef<Path>>(
358 path: P,
359 perm: Option<stat::Mode>,
360 owner: Option<unistd::Uid>,
361 group: Option<unistd::Gid>,
35950380 362) -> Result<(), nix::Error>
1619a720 363{
35950380 364 let mode : stat::Mode = perm.unwrap_or(stat::Mode::from_bits_truncate(0o770));
1619a720
DM
365
366 let path = path.as_ref();
367
35950380
DM
368 match nix::unistd::mkdir(path, mode) {
369 Ok(()) => {},
370 Err(nix::Error::Sys(nix::errno::Errno::EEXIST)) => {
371 return Ok(());
372 },
373 err => return err,
374 }
375
1619a720
DM
376 unistd::chown(path, owner, group)?;
377
378 Ok(())
379}
380
eea81319
DM
381/// Change ownership of an open file handle
382pub fn fchown(
383 fd: RawFd,
384 owner: Option<nix::unistd::Uid>,
385 group: Option<nix::unistd::Gid>
386) -> Result<(), Error> {
387
388 // According to the POSIX specification, -1 is used to indicate that owner and group
389 // are not to be changed. Since uid_t and gid_t are unsigned types, we have to wrap
390 // around to get -1 (copied fron nix crate).
391 let uid = owner.map(Into::into).unwrap_or((0 as libc::uid_t).wrapping_sub(1));
392 let gid = group.map(Into::into).unwrap_or((0 as libc::gid_t).wrapping_sub(1));
393
394 let res = unsafe { libc::fchown(fd, uid, gid) };
395 nix::errno::Errno::result(res)?;
396
397 Ok(())
398}
399
5d14eb6a 400// Returns the hosts node name (UTS node name)
74a69302
DM
401pub fn nodename() -> &'static str {
402
403 lazy_static!{
404 static ref NODENAME: String = {
405
0d38dcb4
DM
406 nix::sys::utsname::uname()
407 .nodename()
408 .split('.')
409 .next()
410 .unwrap()
411 .to_owned()
74a69302
DM
412 };
413 }
414
415 &NODENAME
416}
417
f5f13ebc
DM
418pub fn json_object_to_query(data: Value) -> Result<String, Error> {
419
420 let mut query = url::form_urlencoded::Serializer::new(String::new());
421
422 let object = data.as_object().ok_or_else(|| {
423 format_err!("json_object_to_query: got wrong data type (expected object).")
424 })?;
425
426 for (key, value) in object {
427 match value {
428 Value::Bool(b) => { query.append_pair(key, &b.to_string()); }
429 Value::Number(n) => { query.append_pair(key, &n.to_string()); }
430 Value::String(s) => { query.append_pair(key, &s); }
431 Value::Array(arr) => {
432 for element in arr {
433 match element {
434 Value::Bool(b) => { query.append_pair(key, &b.to_string()); }
435 Value::Number(n) => { query.append_pair(key, &n.to_string()); }
436 Value::String(s) => { query.append_pair(key, &s); }
437 _ => bail!("json_object_to_query: unable to handle complex array data types."),
438 }
439 }
440 }
441 _ => bail!("json_object_to_query: unable to handle complex data types."),
442 }
443 }
444
445 Ok(query.finish())
446}
447
0fe5d605
DM
448pub fn required_string_param<'a>(param: &'a Value, name: &str) -> Result<&'a str, Error> {
449 match param[name].as_str() {
450 Some(s) => Ok(s),
451 None => bail!("missing parameter '{}'", name),
452 }
453}
0d38dcb4
DM
454
455pub fn required_integer_param<'a>(param: &'a Value, name: &str) -> Result<i64, Error> {
456 match param[name].as_i64() {
457 Some(s) => Ok(s),
458 None => bail!("missing parameter '{}'", name),
f8dfbb45
DM
459 }
460}
461
462pub fn required_array_param<'a>(param: &'a Value, name: &str) -> Result<Vec<Value>, Error> {
463 match param[name].as_array() {
464 Some(s) => Ok(s.to_vec()),
465 None => bail!("missing parameter '{}'", name),
0d38dcb4
DM
466 }
467}
383e8577 468
496a6784 469pub fn complete_file_name(arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
383e8577
DM
470
471 let mut result = vec![];
472
473 use nix::fcntl::OFlag;
474 use nix::sys::stat::Mode;
475 use nix::fcntl::AtFlags;
476
806500cd 477 let mut dirname = std::path::PathBuf::from(if arg.len() == 0 { "./" } else { arg });
383e8577
DM
478
479 let is_dir = match nix::sys::stat::fstatat(libc::AT_FDCWD, &dirname, AtFlags::empty()) {
480 Ok(stat) => (stat.st_mode & libc::S_IFMT) == libc::S_IFDIR,
481 Err(_) => false,
482 };
483
484 if !is_dir {
485 if let Some(parent) = dirname.parent() {
486 dirname = parent.to_owned();
487 }
488 }
489
490 let mut dir = match nix::dir::Dir::openat(libc::AT_FDCWD, &dirname, OFlag::O_DIRECTORY, Mode::empty()) {
491 Ok(d) => d,
492 Err(_) => return result,
493 };
494
495 for item in dir.iter() {
496 if let Ok(entry) = item {
497 if let Ok(name) = entry.file_name().to_str() {
498 if name == "." || name == ".." { continue; }
499 let mut newpath = dirname.clone();
500 newpath.push(name);
501
502 if let Ok(stat) = nix::sys::stat::fstatat(libc::AT_FDCWD, &newpath, AtFlags::empty()) {
503 if (stat.st_mode & libc::S_IFMT) == libc::S_IFDIR {
504 newpath.push("");
505 if let Some(newpath) = newpath.to_str() {
506 result.push(newpath.to_owned());
507 }
508 continue;
509 }
510 }
511 if let Some(newpath) = newpath.to_str() {
512 result.push(newpath.to_owned());
513 }
514
515 }
516 }
517 }
518
519 result
520}
443f3743
DM
521
522/// Scan directory for matching file names.
523///
524/// Scan through all directory entries and call `callback()` function
525/// if the entry name matches the regular expression. This function
526/// used unix `openat()`, so you can pass absolute or relative file
527/// names. This function simply skips non-UTF8 encoded names.
528pub fn scandir<P, F>(
529 dirfd: RawFd,
121f18ef 530 path: &P,
443f3743 531 regex: &regex::Regex,
cce1676a 532 mut callback: F
443f3743 533) -> Result<(), Error>
cce1676a 534 where F: FnMut(RawFd, &str, nix::dir::Type) -> Result<(), Error>,
121f18ef 535 P: ?Sized + nix::NixPath,
443f3743 536{
121f18ef 537 for entry in self::fs::scan_subdir(dirfd, path, regex)? {
443f3743
DM
538 let entry = entry?;
539 let file_type = match entry.file_type() {
540 Some(file_type) => file_type,
541 None => bail!("unable to detect file type"),
542 };
443f3743 543
121f18ef 544 callback(entry.parent_fd(), unsafe { entry.file_name_utf8_unchecked() }, file_type)?;
443f3743
DM
545 }
546 Ok(())
547}
7e13b2d6
DM
548
549pub fn get_hardware_address() -> Result<String, Error> {
550
1631c54f 551 static FILENAME: &str = "/etc/ssh/ssh_host_rsa_key.pub";
7e13b2d6 552
1631c54f 553 let contents = file_get_contents(FILENAME)?;
7e13b2d6
DM
554 let digest = md5::compute(contents);
555
556 Ok(format!("{:0x}", digest))
557}
22968600
DM
558
559pub fn digest_to_hex(digest: &[u8]) -> String {
560
561 const HEX_CHARS: &'static [u8; 16] = b"0123456789abcdef";
562
563 let mut buf = Vec::<u8>::with_capacity(digest.len()*2);
564
565 for i in 0..digest.len() {
566 buf.push(HEX_CHARS[(digest[i] >> 4) as usize]);
567 buf.push(HEX_CHARS[(digest[i] & 0xf) as usize]);
568 }
569
570 unsafe { String::from_utf8_unchecked(buf) }
571}
572
af2fddea
DM
573pub fn assert_if_modified(digest1: &str, digest2: &str) -> Result<(), Error> {
574 if digest1 != digest2 {
575 bail!("detected modified configuration - file changed by other user? Try again.");
576 }
577 Ok(())
578}
b9903d63
DM
579
580/// Extract authentication cookie from cookie header.
581/// We assume cookie_name is already url encoded.
582pub fn extract_auth_cookie(cookie: &str, cookie_name: &str) -> Option<String> {
583
584 for pair in cookie.split(';') {
585
586 let (name, value) = match pair.find('=') {
587 Some(i) => (pair[..i].trim(), pair[(i + 1)..].trim()),
588 None => return None, // Cookie format error
589 };
590
591 if name == cookie_name {
592 use url::percent_encoding::percent_decode;
593 if let Ok(value) = percent_decode(value.as_bytes()).decode_utf8() {
594 return Some(value.into());
595 } else {
596 return None; // Cookie format error
597 }
598 }
599 }
600
601 None
602}
af53186e
DM
603
604pub fn join(data: &Vec<String>, sep: char) -> String {
605
606 let mut list = String::new();
607
608 for item in data {
609 if list.len() != 0 { list.push(sep); }
610 list.push_str(item);
611 }
612
613 list
614}
ff7049d4
WB
615
616pub fn fd_change_cloexec(fd: RawFd, on: bool) -> Result<(), Error> {
617 use nix::fcntl::{fcntl, F_GETFD, F_SETFD, FdFlag};
618 let mut flags = FdFlag::from_bits(fcntl(fd, F_GETFD)?)
619 .ok_or_else(|| format_err!("unhandled file flags"))?; // nix crate is stupid this way...
620 flags.set(FdFlag::FD_CLOEXEC, on);
621 fcntl(fd, F_SETFD(flags))?;
622 Ok(())
623}
9136f857
DM
624
625
626static mut SHUTDOWN_REQUESTED: bool = false;
627
628pub fn request_shutdown() {
629 unsafe { SHUTDOWN_REQUESTED = true; }
630}
631
632#[inline(always)]
633pub fn shutdown_requested() -> bool {
634 unsafe { SHUTDOWN_REQUESTED }
635}
92da93b2
DM
636
637pub fn fail_on_shutdown() -> Result<(), Error> {
638 if shutdown_requested() {
639 bail!("Server shutdown requested - aborting task");
640 }
641 Ok(())
642}