]>
Commit | Line | Data |
---|---|---|
51b499db DM |
1 | //! Tools and utilities |
2 | //! | |
3 | //! This is a collection of small and useful tools. | |
f12f8ff1 DM |
4 | use failure::*; |
5 | use nix::unistd; | |
6 | use nix::sys::stat; | |
10241c20 | 7 | use nix::{convert_ioctl_res, request_code_read, ioc}; |
f12f8ff1 | 8 | |
74a69302 DM |
9 | use lazy_static::lazy_static; |
10 | ||
365bb90f | 11 | use std::fs::{File, OpenOptions}; |
f12f8ff1 DM |
12 | use std::io::Write; |
13 | use std::path::Path; | |
43eeef28 DM |
14 | use std::io::Read; |
15 | use std::io::ErrorKind; | |
1628a4c7 | 16 | use std::time::Duration; |
2edc341b | 17 | use std::any::Any; |
f12f8ff1 | 18 | |
d96bb7f1 | 19 | use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; |
365bb90f | 20 | |
496a6784 DM |
21 | use std::collections::HashMap; |
22 | ||
af926291 | 23 | use serde_json::Value; |
0fe5d605 | 24 | |
d82ed9b0 | 25 | pub mod async_mutex; |
8cf6e764 | 26 | pub mod timer; |
7f0d67cf | 27 | pub mod wrapped_reader_stream; |
8f973f81 DM |
28 | #[macro_use] |
29 | pub mod common_regex; | |
8d04280b | 30 | pub mod ticket; |
6ed25cbe | 31 | pub mod borrow; |
b4d5787d | 32 | pub mod fs; |
c9b296f1 | 33 | pub mod tty; |
f54c1998 | 34 | pub mod signalfd; |
dce94d0e | 35 | pub mod daemon; |
3c2012f9 | 36 | pub mod procfs; |
897982e2 WB |
37 | pub mod read; |
38 | pub mod write; | |
25e205a1 | 39 | pub mod acl; |
2dcdd3b4 | 40 | pub mod xattr; |
9cdda3f7 WB |
41 | pub mod vec; |
42 | pub mod io; | |
382609b0 | 43 | pub mod futures; |
8cf6e764 | 44 | |
a650f503 DM |
45 | mod process_locker; |
46 | pub use process_locker::*; | |
47 | ||
3b151414 DM |
48 | #[macro_use] |
49 | mod file_logger; | |
50 | pub use file_logger::*; | |
51 | ||
490be29e DM |
52 | mod broadcast_future; |
53 | pub use broadcast_future::*; | |
54 | ||
e80503d2 DM |
55 | /// Macro to write error-handling blocks (like perl eval {}) |
56 | /// | |
57 | /// #### Example: | |
58 | /// ``` | |
fe651dd6 DM |
59 | /// # #[macro_use] extern crate proxmox_backup; |
60 | /// # use failure::*; | |
61 | /// # let some_condition = false; | |
e80503d2 DM |
62 | /// let result = try_block!({ |
63 | /// if (some_condition) { | |
64 | /// bail!("some error"); | |
65 | /// } | |
66 | /// Ok(()) | |
67 | /// }) | |
68 | /// .map_err(|e| format_err!("my try block returned an error - {}", e)); | |
69 | /// ``` | |
70 | ||
f0dbba8c DM |
71 | #[macro_export] |
72 | macro_rules! try_block { | |
73 | { $($token:tt)* } => {{ (|| -> Result<_,_> { $($token)* })() }} | |
74 | } | |
75 | ||
5d14eb6a | 76 | |
fded74d0 | 77 | /// The `BufferedRead` trait provides a single function |
0a72e267 DM |
78 | /// `buffered_read`. It returns a reference to an internal buffer. The |
79 | /// purpose of this traid is to avoid unnecessary data copies. | |
fded74d0 | 80 | pub trait BufferedRead { |
318564ac DM |
81 | /// This functions tries to fill the internal buffers, then |
82 | /// returns a reference to the available data. It returns an empty | |
83 | /// buffer if `offset` points to the end of the file. | |
0a72e267 DM |
84 | fn buffered_read(&mut self, offset: u64) -> Result<&[u8], Error>; |
85 | } | |
86 | ||
51b499db DM |
87 | /// Directly map a type into a binary buffer. This is mostly useful |
88 | /// for reading structured data from a byte stream (file). You need to | |
89 | /// make sure that the buffer location does not change, so please | |
90 | /// avoid vec resize while you use such map. | |
91 | /// | |
92 | /// This function panics if the buffer is not large enough. | |
50ea4396 | 93 | pub fn map_struct<T>(buffer: &[u8]) -> Result<&T, Error> { |
dc3de618 DM |
94 | if buffer.len() < ::std::mem::size_of::<T>() { |
95 | bail!("unable to map struct - buffer too small"); | |
96 | } | |
95bd5dfe | 97 | Ok(unsafe { & * (buffer.as_ptr() as *const T) }) |
dc3de618 DM |
98 | } |
99 | ||
51b499db DM |
100 | /// Directly map a type into a mutable binary buffer. This is mostly |
101 | /// useful for writing structured data into a byte stream (file). You | |
102 | /// need to make sure that the buffer location does not change, so | |
103 | /// please avoid vec resize while you use such map. | |
104 | /// | |
105 | /// This function panics if the buffer is not large enough. | |
50ea4396 | 106 | pub fn map_struct_mut<T>(buffer: &mut [u8]) -> Result<&mut T, Error> { |
dc3de618 DM |
107 | if buffer.len() < ::std::mem::size_of::<T>() { |
108 | bail!("unable to map struct - buffer too small"); | |
109 | } | |
95bd5dfe | 110 | Ok(unsafe { &mut * (buffer.as_ptr() as *mut T) }) |
dc3de618 DM |
111 | } |
112 | ||
6235a418 | 113 | pub fn file_read_firstline<P: AsRef<Path>>(path: P) -> Result<String, Error> { |
447787ab DM |
114 | |
115 | let path = path.as_ref(); | |
116 | ||
6235a418 DM |
117 | try_block!({ |
118 | let file = std::fs::File::open(path)?; | |
447787ab | 119 | |
6235a418 | 120 | use std::io::{BufRead, BufReader}; |
447787ab | 121 | |
6235a418 | 122 | let mut reader = BufReader::new(file); |
447787ab | 123 | |
6235a418 | 124 | let mut line = String::new(); |
447787ab | 125 | |
6235a418 | 126 | let _ = reader.read_line(&mut line)?; |
447787ab | 127 | |
6235a418 DM |
128 | Ok(line) |
129 | }).map_err(|err: Error| format_err!("unable to read {:?} - {}", path, err)) | |
447787ab DM |
130 | } |
131 | ||
12400210 DM |
132 | pub fn file_get_contents<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, Error> { |
133 | ||
134 | let path = path.as_ref(); | |
135 | ||
136 | try_block!({ | |
137 | std::fs::read(path) | |
138 | }).map_err(|err| format_err!("unable to read {:?} - {}", path, err)) | |
53157ca6 DM |
139 | } |
140 | ||
49cf9f3d | 141 | pub fn file_get_json<P: AsRef<Path>>(path: P, default: Option<Value>) -> Result<Value, Error> { |
53cafb59 DM |
142 | |
143 | let path = path.as_ref(); | |
144 | ||
49cf9f3d DM |
145 | let raw = match std::fs::read(path) { |
146 | Ok(v) => v, | |
147 | Err(err) => { | |
148 | if err.kind() == std::io::ErrorKind::NotFound { | |
149 | if let Some(v) = default { | |
150 | return Ok(v); | |
151 | } | |
152 | } | |
153 | bail!("unable to read json {:?} - {}", path, err); | |
154 | } | |
155 | }; | |
53cafb59 DM |
156 | |
157 | try_block!({ | |
158 | let data = String::from_utf8(raw)?; | |
159 | let json = serde_json::from_str(&data)?; | |
160 | Ok(json) | |
49cf9f3d | 161 | }).map_err(|err: Error| format_err!("unable to parse json from {:?} - {}", path, err)) |
53cafb59 DM |
162 | } |
163 | ||
eea81319 DM |
164 | /// Atomically write a file |
165 | /// | |
166 | /// We first create a temporary file, which is then renamed. | |
f12f8ff1 DM |
167 | pub fn file_set_contents<P: AsRef<Path>>( |
168 | path: P, | |
169 | data: &[u8], | |
170 | perm: Option<stat::Mode>, | |
171 | ) -> Result<(), Error> { | |
eea81319 DM |
172 | file_set_contents_full(path, data, perm, None, None) |
173 | } | |
174 | ||
175 | /// Atomically write a file with owner and group | |
176 | pub fn file_set_contents_full<P: AsRef<Path>>( | |
177 | path: P, | |
178 | data: &[u8], | |
179 | perm: Option<stat::Mode>, | |
1619a720 DM |
180 | owner: Option<unistd::Uid>, |
181 | group: Option<unistd::Gid>, | |
eea81319 | 182 | ) -> Result<(), Error> { |
f12f8ff1 DM |
183 | |
184 | let path = path.as_ref(); | |
185 | ||
d64d80d2 DM |
186 | // Note: we use mkstemp heŕe, because this worka with different |
187 | // processes, threads, and even tokio tasks. | |
f12f8ff1 DM |
188 | let mut template = path.to_owned(); |
189 | template.set_extension("tmp_XXXXXX"); | |
190 | let (fd, tmp_path) = match unistd::mkstemp(&template) { | |
191 | Ok((fd, path)) => (fd, path), | |
192 | Err(err) => bail!("mkstemp {:?} failed: {}", template, err), | |
193 | }; | |
194 | ||
195 | let tmp_path = tmp_path.as_path(); | |
196 | ||
1a7bc3dd | 197 | let mode : stat::Mode = perm.unwrap_or(stat::Mode::from( |
f12f8ff1 DM |
198 | stat::Mode::S_IRUSR | stat::Mode::S_IWUSR | |
199 | stat::Mode::S_IRGRP | stat::Mode::S_IROTH | |
1a7bc3dd | 200 | )); |
f12f8ff1 DM |
201 | |
202 | if let Err(err) = stat::fchmod(fd, mode) { | |
203 | let _ = unistd::unlink(tmp_path); | |
204 | bail!("fchmod {:?} failed: {}", tmp_path, err); | |
205 | } | |
206 | ||
eea81319 DM |
207 | if owner != None || group != None { |
208 | if let Err(err) = fchown(fd, owner, group) { | |
209 | let _ = unistd::unlink(tmp_path); | |
210 | bail!("fchown {:?} failed: {}", tmp_path, err); | |
211 | } | |
212 | } | |
213 | ||
f12f8ff1 DM |
214 | let mut file = unsafe { File::from_raw_fd(fd) }; |
215 | ||
216 | if let Err(err) = file.write_all(data) { | |
217 | let _ = unistd::unlink(tmp_path); | |
218 | bail!("write failed: {}", err); | |
219 | } | |
220 | ||
221 | if let Err(err) = std::fs::rename(tmp_path, path) { | |
222 | let _ = unistd::unlink(tmp_path); | |
223 | bail!("Atomic rename failed for file {:?} - {}", path, err); | |
224 | } | |
225 | ||
226 | Ok(()) | |
227 | } | |
43eeef28 | 228 | |
51b499db DM |
229 | /// Create a file lock using fntl. This function allows you to specify |
230 | /// a timeout if you want to avoid infinite blocking. | |
1628a4c7 WB |
231 | pub fn lock_file<F: AsRawFd>( |
232 | file: &mut F, | |
233 | exclusive: bool, | |
234 | timeout: Option<Duration>, | |
eb90c9e3 | 235 | ) -> Result<(), Error> { |
1628a4c7 WB |
236 | let lockarg = |
237 | if exclusive { | |
238 | nix::fcntl::FlockArg::LockExclusive | |
239 | } else { | |
240 | nix::fcntl::FlockArg::LockShared | |
241 | }; | |
242 | ||
243 | let timeout = match timeout { | |
244 | None => { | |
245 | nix::fcntl::flock(file.as_raw_fd(), lockarg)?; | |
246 | return Ok(()); | |
247 | } | |
248 | Some(t) => t, | |
249 | }; | |
250 | ||
251 | // unblock the timeout signal temporarily | |
252 | let _sigblock_guard = timer::unblock_timeout_signal(); | |
253 | ||
254 | // setup a timeout timer | |
255 | let mut timer = timer::Timer::create( | |
256 | timer::Clock::Realtime, | |
257 | timer::TimerEvent::ThisThreadSignal(timer::SIGTIMEOUT))?; | |
258 | ||
259 | timer.arm(timer::TimerSpec::new() | |
260 | .value(Some(timeout)) | |
261 | .interval(Some(Duration::from_millis(10))))?; | |
262 | ||
263 | nix::fcntl::flock(file.as_raw_fd(), lockarg)?; | |
264 | Ok(()) | |
265 | } | |
365bb90f | 266 | |
51b499db DM |
267 | /// Open or create a lock file (append mode). Then try to |
268 | /// aquire a lock using `lock_file()`. | |
1628a4c7 WB |
269 | pub fn open_file_locked<P: AsRef<Path>>(path: P, timeout: Duration) |
270 | -> Result<File, Error> | |
271 | { | |
272 | let path = path.as_ref(); | |
273 | let mut file = | |
274 | match OpenOptions::new() | |
275 | .create(true) | |
276 | .append(true) | |
277 | .open(path) | |
278 | { | |
365bb90f DM |
279 | Ok(file) => file, |
280 | Err(err) => bail!("Unable to open lock {:?} - {}", | |
281 | path, err), | |
282 | }; | |
28b96b56 DM |
283 | match lock_file(&mut file, true, Some(timeout)) { |
284 | Ok(_) => Ok(file), | |
285 | Err(err) => bail!("Unable to aquire lock {:?} - {}", | |
286 | path, err), | |
287 | } | |
365bb90f DM |
288 | } |
289 | ||
51b499db DM |
290 | /// Split a file into equal sized chunks. The last chunk may be |
291 | /// smaller. Note: We cannot implement an `Iterator`, because iterators | |
292 | /// cannot return a borrowed buffer ref (we want zero-copy) | |
43eeef28 DM |
293 | pub fn file_chunker<C, R>( |
294 | mut file: R, | |
295 | chunk_size: usize, | |
606ce64b | 296 | mut chunk_cb: C |
43eeef28 | 297 | ) -> Result<(), Error> |
606ce64b | 298 | where C: FnMut(usize, &[u8]) -> Result<bool, Error>, |
43eeef28 DM |
299 | R: Read, |
300 | { | |
301 | ||
302 | const READ_BUFFER_SIZE: usize = 4*1024*1024; // 4M | |
303 | ||
304 | if chunk_size > READ_BUFFER_SIZE { bail!("chunk size too large!"); } | |
305 | ||
8ea3b1d1 | 306 | let mut buf = vec::undefined(READ_BUFFER_SIZE); |
43eeef28 DM |
307 | |
308 | let mut pos = 0; | |
309 | let mut file_pos = 0; | |
310 | loop { | |
311 | let mut eof = false; | |
312 | let mut tmp = &mut buf[..]; | |
313 | // try to read large portions, at least chunk_size | |
314 | while pos < chunk_size { | |
315 | match file.read(tmp) { | |
316 | Ok(0) => { eof = true; break; }, | |
317 | Ok(n) => { | |
318 | pos += n; | |
319 | if pos > chunk_size { break; } | |
320 | tmp = &mut tmp[n..]; | |
321 | } | |
322 | Err(ref e) if e.kind() == ErrorKind::Interrupted => { /* try again */ } | |
5f0c2d56 | 323 | Err(e) => bail!("read chunk failed - {}", e.to_string()), |
43eeef28 DM |
324 | } |
325 | } | |
43eeef28 DM |
326 | let mut start = 0; |
327 | while start + chunk_size <= pos { | |
328 | if !(chunk_cb)(file_pos, &buf[start..start+chunk_size])? { break; } | |
329 | file_pos += chunk_size; | |
330 | start += chunk_size; | |
331 | } | |
332 | if eof { | |
333 | if start < pos { | |
334 | (chunk_cb)(file_pos, &buf[start..pos])?; | |
335 | //file_pos += pos - start; | |
336 | } | |
337 | break; | |
338 | } else { | |
339 | let rest = pos - start; | |
340 | if rest > 0 { | |
341 | let ptr = buf.as_mut_ptr(); | |
342 | unsafe { std::ptr::copy_nonoverlapping(ptr.add(start), ptr, rest); } | |
343 | pos = rest; | |
344 | } else { | |
345 | pos = 0; | |
346 | } | |
347 | } | |
348 | } | |
349 | ||
350 | Ok(()) | |
43eeef28 | 351 | } |
0fe5d605 | 352 | |
eea81319 | 353 | /// Returns the Unix uid/gid for the sepcified system user. |
5d14eb6a DM |
354 | pub fn getpwnam_ugid(username: &str) -> Result<(libc::uid_t,libc::gid_t), Error> { |
355 | let info = unsafe { libc::getpwnam(std::ffi::CString::new(username).unwrap().as_ptr()) }; | |
356 | if info == std::ptr::null_mut() { | |
357 | bail!("getwpnam '{}' failed", username); | |
358 | } | |
359 | ||
360 | let info = unsafe { *info }; | |
361 | ||
362 | Ok((info.pw_uid, info.pw_gid)) | |
363 | } | |
364 | ||
35950380 DM |
365 | /// Creates directory at the provided path with specified ownership |
366 | /// | |
367 | /// Simply returns if the directory already exists. | |
1619a720 DM |
368 | pub fn create_dir_chown<P: AsRef<Path>>( |
369 | path: P, | |
370 | perm: Option<stat::Mode>, | |
371 | owner: Option<unistd::Uid>, | |
372 | group: Option<unistd::Gid>, | |
35950380 | 373 | ) -> Result<(), nix::Error> |
1619a720 | 374 | { |
35950380 | 375 | let mode : stat::Mode = perm.unwrap_or(stat::Mode::from_bits_truncate(0o770)); |
1619a720 DM |
376 | |
377 | let path = path.as_ref(); | |
378 | ||
35950380 DM |
379 | match nix::unistd::mkdir(path, mode) { |
380 | Ok(()) => {}, | |
381 | Err(nix::Error::Sys(nix::errno::Errno::EEXIST)) => { | |
382 | return Ok(()); | |
383 | }, | |
384 | err => return err, | |
385 | } | |
386 | ||
1619a720 DM |
387 | unistd::chown(path, owner, group)?; |
388 | ||
389 | Ok(()) | |
390 | } | |
391 | ||
eea81319 DM |
392 | /// Change ownership of an open file handle |
393 | pub fn fchown( | |
394 | fd: RawFd, | |
395 | owner: Option<nix::unistd::Uid>, | |
396 | group: Option<nix::unistd::Gid> | |
397 | ) -> Result<(), Error> { | |
398 | ||
399 | // According to the POSIX specification, -1 is used to indicate that owner and group | |
400 | // are not to be changed. Since uid_t and gid_t are unsigned types, we have to wrap | |
401 | // around to get -1 (copied fron nix crate). | |
402 | let uid = owner.map(Into::into).unwrap_or((0 as libc::uid_t).wrapping_sub(1)); | |
403 | let gid = group.map(Into::into).unwrap_or((0 as libc::gid_t).wrapping_sub(1)); | |
404 | ||
405 | let res = unsafe { libc::fchown(fd, uid, gid) }; | |
406 | nix::errno::Errno::result(res)?; | |
407 | ||
408 | Ok(()) | |
409 | } | |
410 | ||
5d14eb6a | 411 | // Returns the hosts node name (UTS node name) |
74a69302 DM |
412 | pub fn nodename() -> &'static str { |
413 | ||
414 | lazy_static!{ | |
415 | static ref NODENAME: String = { | |
416 | ||
0d38dcb4 DM |
417 | nix::sys::utsname::uname() |
418 | .nodename() | |
419 | .split('.') | |
420 | .next() | |
421 | .unwrap() | |
422 | .to_owned() | |
74a69302 DM |
423 | }; |
424 | } | |
425 | ||
426 | &NODENAME | |
427 | } | |
428 | ||
f5f13ebc DM |
429 | pub fn json_object_to_query(data: Value) -> Result<String, Error> { |
430 | ||
431 | let mut query = url::form_urlencoded::Serializer::new(String::new()); | |
432 | ||
433 | let object = data.as_object().ok_or_else(|| { | |
434 | format_err!("json_object_to_query: got wrong data type (expected object).") | |
435 | })?; | |
436 | ||
437 | for (key, value) in object { | |
438 | match value { | |
439 | Value::Bool(b) => { query.append_pair(key, &b.to_string()); } | |
440 | Value::Number(n) => { query.append_pair(key, &n.to_string()); } | |
441 | Value::String(s) => { query.append_pair(key, &s); } | |
442 | Value::Array(arr) => { | |
443 | for element in arr { | |
444 | match element { | |
445 | Value::Bool(b) => { query.append_pair(key, &b.to_string()); } | |
446 | Value::Number(n) => { query.append_pair(key, &n.to_string()); } | |
447 | Value::String(s) => { query.append_pair(key, &s); } | |
448 | _ => bail!("json_object_to_query: unable to handle complex array data types."), | |
449 | } | |
450 | } | |
451 | } | |
452 | _ => bail!("json_object_to_query: unable to handle complex data types."), | |
453 | } | |
454 | } | |
455 | ||
456 | Ok(query.finish()) | |
457 | } | |
458 | ||
0fe5d605 DM |
459 | pub fn required_string_param<'a>(param: &'a Value, name: &str) -> Result<&'a str, Error> { |
460 | match param[name].as_str() { | |
461 | Some(s) => Ok(s), | |
462 | None => bail!("missing parameter '{}'", name), | |
463 | } | |
464 | } | |
0d38dcb4 DM |
465 | |
466 | pub fn required_integer_param<'a>(param: &'a Value, name: &str) -> Result<i64, Error> { | |
467 | match param[name].as_i64() { | |
468 | Some(s) => Ok(s), | |
469 | None => bail!("missing parameter '{}'", name), | |
f8dfbb45 DM |
470 | } |
471 | } | |
472 | ||
473 | pub fn required_array_param<'a>(param: &'a Value, name: &str) -> Result<Vec<Value>, Error> { | |
474 | match param[name].as_array() { | |
475 | Some(s) => Ok(s.to_vec()), | |
476 | None => bail!("missing parameter '{}'", name), | |
0d38dcb4 DM |
477 | } |
478 | } | |
383e8577 | 479 | |
496a6784 | 480 | pub fn complete_file_name(arg: &str, _param: &HashMap<String, String>) -> Vec<String> { |
383e8577 DM |
481 | |
482 | let mut result = vec![]; | |
483 | ||
484 | use nix::fcntl::OFlag; | |
485 | use nix::sys::stat::Mode; | |
486 | use nix::fcntl::AtFlags; | |
487 | ||
806500cd | 488 | let mut dirname = std::path::PathBuf::from(if arg.len() == 0 { "./" } else { arg }); |
383e8577 DM |
489 | |
490 | let is_dir = match nix::sys::stat::fstatat(libc::AT_FDCWD, &dirname, AtFlags::empty()) { | |
491 | Ok(stat) => (stat.st_mode & libc::S_IFMT) == libc::S_IFDIR, | |
492 | Err(_) => false, | |
493 | }; | |
494 | ||
495 | if !is_dir { | |
496 | if let Some(parent) = dirname.parent() { | |
497 | dirname = parent.to_owned(); | |
498 | } | |
499 | } | |
500 | ||
501 | let mut dir = match nix::dir::Dir::openat(libc::AT_FDCWD, &dirname, OFlag::O_DIRECTORY, Mode::empty()) { | |
502 | Ok(d) => d, | |
503 | Err(_) => return result, | |
504 | }; | |
505 | ||
506 | for item in dir.iter() { | |
507 | if let Ok(entry) = item { | |
508 | if let Ok(name) = entry.file_name().to_str() { | |
509 | if name == "." || name == ".." { continue; } | |
510 | let mut newpath = dirname.clone(); | |
511 | newpath.push(name); | |
512 | ||
513 | if let Ok(stat) = nix::sys::stat::fstatat(libc::AT_FDCWD, &newpath, AtFlags::empty()) { | |
514 | if (stat.st_mode & libc::S_IFMT) == libc::S_IFDIR { | |
515 | newpath.push(""); | |
516 | if let Some(newpath) = newpath.to_str() { | |
517 | result.push(newpath.to_owned()); | |
518 | } | |
519 | continue; | |
520 | } | |
521 | } | |
522 | if let Some(newpath) = newpath.to_str() { | |
523 | result.push(newpath.to_owned()); | |
524 | } | |
525 | ||
526 | } | |
527 | } | |
528 | } | |
529 | ||
530 | result | |
531 | } | |
443f3743 DM |
532 | |
533 | /// Scan directory for matching file names. | |
534 | /// | |
535 | /// Scan through all directory entries and call `callback()` function | |
536 | /// if the entry name matches the regular expression. This function | |
537 | /// used unix `openat()`, so you can pass absolute or relative file | |
538 | /// names. This function simply skips non-UTF8 encoded names. | |
539 | pub fn scandir<P, F>( | |
540 | dirfd: RawFd, | |
121f18ef | 541 | path: &P, |
443f3743 | 542 | regex: ®ex::Regex, |
cce1676a | 543 | mut callback: F |
443f3743 | 544 | ) -> Result<(), Error> |
cce1676a | 545 | where F: FnMut(RawFd, &str, nix::dir::Type) -> Result<(), Error>, |
121f18ef | 546 | P: ?Sized + nix::NixPath, |
443f3743 | 547 | { |
121f18ef | 548 | for entry in self::fs::scan_subdir(dirfd, path, regex)? { |
443f3743 DM |
549 | let entry = entry?; |
550 | let file_type = match entry.file_type() { | |
551 | Some(file_type) => file_type, | |
552 | None => bail!("unable to detect file type"), | |
553 | }; | |
443f3743 | 554 | |
121f18ef | 555 | callback(entry.parent_fd(), unsafe { entry.file_name_utf8_unchecked() }, file_type)?; |
443f3743 DM |
556 | } |
557 | Ok(()) | |
558 | } | |
7e13b2d6 DM |
559 | |
560 | pub fn get_hardware_address() -> Result<String, Error> { | |
561 | ||
1631c54f | 562 | static FILENAME: &str = "/etc/ssh/ssh_host_rsa_key.pub"; |
7e13b2d6 | 563 | |
1631c54f | 564 | let contents = file_get_contents(FILENAME)?; |
7e13b2d6 DM |
565 | let digest = md5::compute(contents); |
566 | ||
567 | Ok(format!("{:0x}", digest)) | |
568 | } | |
22968600 | 569 | |
f2269d8f | 570 | const HEX_CHARS: &'static [u8; 16] = b"0123456789abcdef"; |
22968600 | 571 | |
f2269d8f | 572 | pub fn digest_to_hex(digest: &[u8]) -> String { |
22968600 DM |
573 | let mut buf = Vec::<u8>::with_capacity(digest.len()*2); |
574 | ||
575 | for i in 0..digest.len() { | |
576 | buf.push(HEX_CHARS[(digest[i] >> 4) as usize]); | |
577 | buf.push(HEX_CHARS[(digest[i] & 0xf) as usize]); | |
578 | } | |
579 | ||
580 | unsafe { String::from_utf8_unchecked(buf) } | |
581 | } | |
582 | ||
f2269d8f DM |
583 | pub fn hex_to_digest(hex: &str) -> Result<[u8; 32], Error> { |
584 | let mut digest = [0u8; 32]; | |
585 | ||
586 | let bytes = hex.as_bytes(); | |
587 | ||
588 | if bytes.len() != 64 { bail!("got wrong digest length."); } | |
589 | ||
11867a2b DM |
590 | let val = |c| { |
591 | if c >= b'0' && c <= b'9' { return Ok(c - b'0'); } | |
592 | if c >= b'a' && c <= b'f' { return Ok(c - b'a' + 10); } | |
593 | if c >= b'A' && c <= b'F' { return Ok(c - b'A' + 10); } | |
594 | bail!("found illegal hex character."); | |
595 | }; | |
596 | ||
f2269d8f DM |
597 | let mut pos = 0; |
598 | for pair in bytes.chunks(2) { | |
599 | if pos >= digest.len() { bail!("hex digest too long."); } | |
11867a2b DM |
600 | let h = val(pair[0])?; |
601 | let l = val(pair[1])?; | |
f2269d8f DM |
602 | digest[pos] = (h<<4)|l; |
603 | pos +=1; | |
604 | } | |
605 | ||
606 | if pos != digest.len() { bail!("hex digest too short."); } | |
607 | ||
608 | Ok(digest) | |
609 | } | |
610 | ||
af2fddea DM |
611 | pub fn assert_if_modified(digest1: &str, digest2: &str) -> Result<(), Error> { |
612 | if digest1 != digest2 { | |
613 | bail!("detected modified configuration - file changed by other user? Try again."); | |
614 | } | |
615 | Ok(()) | |
616 | } | |
b9903d63 DM |
617 | |
618 | /// Extract authentication cookie from cookie header. | |
619 | /// We assume cookie_name is already url encoded. | |
620 | pub fn extract_auth_cookie(cookie: &str, cookie_name: &str) -> Option<String> { | |
621 | ||
622 | for pair in cookie.split(';') { | |
623 | ||
624 | let (name, value) = match pair.find('=') { | |
625 | Some(i) => (pair[..i].trim(), pair[(i + 1)..].trim()), | |
626 | None => return None, // Cookie format error | |
627 | }; | |
628 | ||
629 | if name == cookie_name { | |
630 | use url::percent_encoding::percent_decode; | |
631 | if let Ok(value) = percent_decode(value.as_bytes()).decode_utf8() { | |
632 | return Some(value.into()); | |
633 | } else { | |
634 | return None; // Cookie format error | |
635 | } | |
636 | } | |
637 | } | |
638 | ||
639 | None | |
640 | } | |
af53186e DM |
641 | |
642 | pub fn join(data: &Vec<String>, sep: char) -> String { | |
643 | ||
644 | let mut list = String::new(); | |
645 | ||
646 | for item in data { | |
647 | if list.len() != 0 { list.push(sep); } | |
648 | list.push_str(item); | |
649 | } | |
650 | ||
651 | list | |
652 | } | |
ff7049d4 | 653 | |
3578d99f DM |
654 | /// normalize uri path |
655 | /// | |
656 | /// Do not allow ".", "..", or hidden files ".XXXX" | |
657 | /// Also remove empty path components | |
658 | pub fn normalize_uri_path(path: &str) -> Result<(String, Vec<&str>), Error> { | |
659 | ||
660 | let items = path.split('/'); | |
661 | ||
662 | let mut path = String::new(); | |
663 | let mut components = vec![]; | |
664 | ||
665 | for name in items { | |
666 | if name.is_empty() { continue; } | |
667 | if name.starts_with(".") { | |
668 | bail!("Path contains illegal components."); | |
669 | } | |
670 | path.push('/'); | |
671 | path.push_str(name); | |
672 | components.push(name); | |
673 | } | |
674 | ||
675 | Ok((path, components)) | |
676 | } | |
677 | ||
ff7049d4 WB |
678 | pub fn fd_change_cloexec(fd: RawFd, on: bool) -> Result<(), Error> { |
679 | use nix::fcntl::{fcntl, F_GETFD, F_SETFD, FdFlag}; | |
680 | let mut flags = FdFlag::from_bits(fcntl(fd, F_GETFD)?) | |
681 | .ok_or_else(|| format_err!("unhandled file flags"))?; // nix crate is stupid this way... | |
682 | flags.set(FdFlag::FD_CLOEXEC, on); | |
683 | fcntl(fd, F_SETFD(flags))?; | |
684 | Ok(()) | |
685 | } | |
9136f857 DM |
686 | |
687 | ||
688 | static mut SHUTDOWN_REQUESTED: bool = false; | |
689 | ||
690 | pub fn request_shutdown() { | |
691 | unsafe { SHUTDOWN_REQUESTED = true; } | |
7a630df7 | 692 | crate::server::server_shutdown(); |
9136f857 DM |
693 | } |
694 | ||
695 | #[inline(always)] | |
696 | pub fn shutdown_requested() -> bool { | |
697 | unsafe { SHUTDOWN_REQUESTED } | |
698 | } | |
92da93b2 DM |
699 | |
700 | pub fn fail_on_shutdown() -> Result<(), Error> { | |
701 | if shutdown_requested() { | |
702 | bail!("Server shutdown requested - aborting task"); | |
703 | } | |
704 | Ok(()) | |
705 | } | |
d96bb7f1 WB |
706 | |
707 | /// Guard a raw file descriptor with a drop handler. This is mostly useful when access to an owned | |
708 | /// `RawFd` is required without the corresponding handler object (such as when only the file | |
709 | /// descriptor number is required in a closure which may be dropped instead of being executed). | |
710 | pub struct Fd(pub RawFd); | |
711 | ||
712 | impl Drop for Fd { | |
713 | fn drop(&mut self) { | |
714 | if self.0 != -1 { | |
715 | unsafe { | |
716 | libc::close(self.0); | |
717 | } | |
718 | } | |
719 | } | |
720 | } | |
721 | ||
722 | impl AsRawFd for Fd { | |
723 | fn as_raw_fd(&self) -> RawFd { | |
724 | self.0 | |
725 | } | |
726 | } | |
727 | ||
728 | impl IntoRawFd for Fd { | |
729 | fn into_raw_fd(mut self) -> RawFd { | |
730 | let fd = self.0; | |
731 | self.0 = -1; | |
732 | fd | |
733 | } | |
734 | } | |
735 | ||
736 | impl FromRawFd for Fd { | |
737 | unsafe fn from_raw_fd(fd: RawFd) -> Self { | |
738 | Self(fd) | |
739 | } | |
740 | } | |
efd1536e WB |
741 | |
742 | // wrap nix::unistd::pipe2 + O_CLOEXEC into something returning guarded file descriptors | |
743 | pub fn pipe() -> Result<(Fd, Fd), Error> { | |
744 | let (pin, pout) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?; | |
745 | Ok((Fd(pin), Fd(pout))) | |
746 | } | |
2edc341b DM |
747 | |
748 | /// An easy way to convert types to Any | |
749 | /// | |
750 | /// Mostly useful to downcast trait objects (see RpcEnvironment). | |
751 | pub trait AsAny { | |
752 | fn as_any(&self) -> &Any; | |
753 | } | |
754 | ||
755 | impl<T: Any> AsAny for T { | |
756 | fn as_any(&self) -> &Any { self } | |
757 | } | |
10241c20 DM |
758 | |
759 | ||
760 | // /usr/include/linux/fs.h: #define BLKGETSIZE64 _IOR(0x12,114,size_t) | |
761 | // return device size in bytes (u64 *arg) | |
762 | nix::ioctl_read!(blkgetsize64, 0x12, 114, u64); | |
763 | ||
764 | /// Return file or block device size | |
765 | pub fn image_size(path: &Path) -> Result<u64, Error> { | |
766 | ||
10241c20 DM |
767 | use std::os::unix::fs::FileTypeExt; |
768 | ||
769 | let file = std::fs::File::open(path)?; | |
770 | let metadata = file.metadata()?; | |
771 | let file_type = metadata.file_type(); | |
772 | ||
773 | if file_type.is_block_device() { | |
774 | let mut size : u64 = 0; | |
775 | let res = unsafe { blkgetsize64(file.as_raw_fd(), &mut size) }; | |
776 | ||
777 | if let Err(err) = res { | |
778 | bail!("blkgetsize64 failed for {:?} - {}", path, err); | |
779 | } | |
780 | Ok(size) | |
781 | } else if file_type.is_file() { | |
782 | Ok(metadata.len()) | |
783 | } else { | |
784 | bail!("image size failed - got unexpected file type {:?}", file_type); | |
785 | } | |
786 | } |