1 //! Tools and utilities
3 //! This is a collection of small and useful tools.
9 use lazy_static
::lazy_static
;
11 use std
::fs
::{File, OpenOptions}
;
15 use std
::io
::ErrorKind
;
16 use std
::time
::Duration
;
18 use std
::os
::unix
::io
::AsRawFd
;
20 use serde_json
::Value
;
24 /// The `BufferedReader` trait provides a single function
25 /// `buffered_read`. It returns a reference to an internal buffer. The
26 /// purpose of this traid is to avoid unnecessary data copies.
27 pub trait BufferedReader
{
28 /// This functions tries to fill the internal buffers, then
29 /// returns a reference to the available data. It returns an empty
30 /// buffer if `offset` points to the end of the file.
31 fn buffered_read(&mut self, offset
: u64) -> Result
<&[u8], Error
>;
34 /// Directly map a type into a binary buffer. This is mostly useful
35 /// for reading structured data from a byte stream (file). You need to
36 /// make sure that the buffer location does not change, so please
37 /// avoid vec resize while you use such map.
39 /// This function panics if the buffer is not large enough.
40 pub fn map_struct
<T
>(buffer
: &[u8]) -> Result
<&T
, Error
> {
41 if buffer
.len() < ::std
::mem
::size_of
::<T
>() {
42 bail
!("unable to map struct - buffer too small");
44 Ok(unsafe { & * (buffer.as_ptr() as *const T) }
)
47 /// Directly map a type into a mutable binary buffer. This is mostly
48 /// useful for writing structured data into a byte stream (file). You
49 /// need to make sure that the buffer location does not change, so
50 /// please avoid vec resize while you use such map.
52 /// This function panics if the buffer is not large enough.
53 pub fn map_struct_mut
<T
>(buffer
: &mut [u8]) -> Result
<&mut T
, Error
> {
54 if buffer
.len() < ::std
::mem
::size_of
::<T
>() {
55 bail
!("unable to map struct - buffer too small");
57 Ok(unsafe { &mut * (buffer.as_ptr() as *mut T) }
)
60 /// Atomically write a file. We first create a temporary file, which
62 pub fn file_set_contents
<P
: AsRef
<Path
>>(
65 perm
: Option
<stat
::Mode
>,
66 ) -> Result
<(), Error
> {
68 let path
= path
.as_ref();
70 // Note: we use mkstemp heŕe, because this worka with different
71 // processes, threads, and even tokio tasks.
72 let mut template
= path
.to_owned();
73 template
.set_extension("tmp_XXXXXX");
74 let (fd
, tmp_path
) = match unistd
::mkstemp(&template
) {
75 Ok((fd
, path
)) => (fd
, path
),
76 Err(err
) => bail
!("mkstemp {:?} failed: {}", template
, err
),
79 let tmp_path
= tmp_path
.as_path();
81 let mode
: stat
::Mode
= perm
.unwrap_or(stat
::Mode
::from(
82 stat
::Mode
::S_IRUSR
| stat
::Mode
::S_IWUSR
|
83 stat
::Mode
::S_IRGRP
| stat
::Mode
::S_IROTH
86 if let Err(err
) = stat
::fchmod(fd
, mode
) {
87 let _
= unistd
::unlink(tmp_path
);
88 bail
!("fchmod {:?} failed: {}", tmp_path
, err
);
91 use std
::os
::unix
::io
::FromRawFd
;
92 let mut file
= unsafe { File::from_raw_fd(fd) }
;
94 if let Err(err
) = file
.write_all(data
) {
95 let _
= unistd
::unlink(tmp_path
);
96 bail
!("write failed: {}", err
);
99 if let Err(err
) = std
::fs
::rename(tmp_path
, path
) {
100 let _
= unistd
::unlink(tmp_path
);
101 bail
!("Atomic rename failed for file {:?} - {}", path
, err
);
107 /// Create a file lock using fntl. This function allows you to specify
108 /// a timeout if you want to avoid infinite blocking.
109 pub fn lock_file
<F
: AsRawFd
>(
112 timeout
: Option
<Duration
>,
113 ) -> Result
<(), Error
>
117 nix
::fcntl
::FlockArg
::LockExclusive
119 nix
::fcntl
::FlockArg
::LockShared
122 let timeout
= match timeout
{
124 nix
::fcntl
::flock(file
.as_raw_fd(), lockarg
)?
;
130 // unblock the timeout signal temporarily
131 let _sigblock_guard
= timer
::unblock_timeout_signal();
133 // setup a timeout timer
134 let mut timer
= timer
::Timer
::create(
135 timer
::Clock
::Realtime
,
136 timer
::TimerEvent
::ThisThreadSignal(timer
::SIGTIMEOUT
))?
;
138 timer
.arm(timer
::TimerSpec
::new()
139 .value(Some(timeout
))
140 .interval(Some(Duration
::from_millis(10))))?
;
142 nix
::fcntl
::flock(file
.as_raw_fd(), lockarg
)?
;
146 /// Open or create a lock file (append mode). Then try to
147 /// aquire a lock using `lock_file()`.
148 pub fn open_file_locked
<P
: AsRef
<Path
>>(path
: P
, timeout
: Duration
)
149 -> Result
<File
, Error
>
151 let path
= path
.as_ref();
153 match OpenOptions
::new()
159 Err(err
) => bail
!("Unable to open lock {:?} - {}",
162 match lock_file(&mut file
, true, Some(timeout
)) {
164 Err(err
) => bail
!("Unable to aquire lock {:?} - {}",
169 /// Split a file into equal sized chunks. The last chunk may be
170 /// smaller. Note: We cannot implement an `Iterator`, because iterators
171 /// cannot return a borrowed buffer ref (we want zero-copy)
172 pub fn file_chunker
<C
, R
>(
176 ) -> Result
<(), Error
>
177 where C
: FnMut(usize, &[u8]) -> Result
<bool
, Error
>,
181 const READ_BUFFER_SIZE
: usize = 4*1024*1024; // 4M
183 if chunk_size
> READ_BUFFER_SIZE { bail!("chunk size too large!"); }
185 let mut buf
= vec
![0u8; READ_BUFFER_SIZE
];
188 let mut file_pos
= 0;
191 let mut tmp
= &mut buf
[..];
192 // try to read large portions, at least chunk_size
193 while pos
< chunk_size
{
194 match file
.read(tmp
) {
195 Ok(0) => { eof = true; break; }
,
198 if pos
> chunk_size { break; }
201 Err(ref e
) if e
.kind() == ErrorKind
::Interrupted
=> { /* try again */ }
202 Err(e
) => bail
!("read chunk failed - {}", e
.to_string()),
206 while start
+ chunk_size
<= pos
{
207 if !(chunk_cb
)(file_pos
, &buf
[start
..start
+chunk_size
])? { break; }
208 file_pos
+= chunk_size
;
213 (chunk_cb
)(file_pos
, &buf
[start
..pos
])?
;
214 //file_pos += pos - start;
218 let rest
= pos
- start
;
220 let ptr
= buf
.as_mut_ptr();
221 unsafe { std::ptr::copy_nonoverlapping(ptr.add(start), ptr, rest); }
232 pub fn nodename() -> &'
static str {
235 static ref NODENAME
: String
= {
237 nix
::sys
::utsname
::uname()
249 pub fn required_string_param
<'a
>(param
: &'a Value
, name
: &str) -> Result
<&'a
str, Error
> {
250 match param
[name
].as_str() {
252 None
=> bail
!("missing parameter '{}'", name
),
256 pub fn required_integer_param
<'a
>(param
: &'a Value
, name
: &str) -> Result
<i64, Error
> {
257 match param
[name
].as_i64() {
259 None
=> bail
!("missing parameter '{}'", name
),