#![cfg_attr(feature = "nightly", feature(asm))]
#[macro_use] extern crate lazy_static;
extern crate libc;
#[cfg(feature = "term")] extern crate term;
#[cfg(feature = "term_size")] extern crate term_size;
use atomic::Atomic;
use std::any::Any;
use std::fs;
use std::fmt;
use std::io::{self, Read};
use std::marker::PhantomData;
use std::os::raw::c_int;
use std::path::Path;
use std::process::{Command, Stdio};
use std::str::{from_utf8_unchecked, FromStr};
use std::sync::Mutex;
use std::sync::atomic::Ordering;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[cfg(test)] use std::thread::sleep;
pub fn filename<'a> (path: &'a str) -> &'a str {
match Path::new (path) .file_name() {
Some (filename) => match filename.to_str() {
Some (fstr) => if fstr.ends_with (".rs") {&fstr[0 .. fstr.len() - 3]} else {fstr},
None => path},
None => path}}
#[macro_export] macro_rules! try_s {
($e: expr) => {match $e {
Ok (ok) => ok,
Err (err) => {return Err (format! ("{}:{}] {}", $crate::filename (file!()), line!(), err));}}}}
#[macro_export] macro_rules! try_f {
($e: expr) => {match $e {
Ok (ok) => ok,
Err (err) => {return Err (From::from (format! ("{}:{}] {}", $crate::filename (file!()), line!(), err)));}}}}
#[macro_export] macro_rules! try_sp {
($e: expr) => {match $e {&Ok (ref ok) => ok,
&Err (ref err) => {return Err (From::from (format! ("{}:{}] {:?}", $crate::filename (file!()), line!(), err)));}}}}
#[macro_export] macro_rules! try_fu {
($e: expr) => {match $e {
Ok (ok) => ok,
Err (err) => {return Box::new (futures::future::err (From::from (err)))}}}}
#[macro_export] macro_rules! try_fus {
($e: expr) => {match $e {
Ok (ok) => ok,
Err (err) => {return Box::new (futures::future::err (ERRL! ("{}", err)))}}}}
#[macro_export] macro_rules! ERRL {
($format: expr, $($args: tt)+) => {format! (concat! ("{}:{}] ", $format), $crate::filename (file!()), line!(), $($args)+)};
($format: expr) => {format! (concat! ("{}:{}] ", $format), $crate::filename (file!()), line!())}}
#[macro_export] macro_rules! ERR {
($format: expr, $($args: tt)+) => {Err (ERRL! ($format, $($args)+))};
($format: expr) => {Err (ERRL! ($format))}}
#[cfg(not(target_arch = "wasm32"))]
fn isatty (fd: c_int) -> c_int {unsafe {libc::isatty (fd)}}
#[cfg(target_arch = "wasm32")]
fn isatty (_fd: c_int) -> c_int {0}
lazy_static! {
static ref STATUS_LINE: Mutex<String> = Mutex::new (String::new());
pub static ref ISATTY: bool = isatty (1) != 0;}
#[cfg(all(feature = "term", feature = "term_size"))]
pub fn status_line (file: &str, line: u32, status: String) {
use io::Write;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
if let Some (mut stdout) = term::stdout() {
if let Ok (mut status_line) = STATUS_LINE.lock() {
let old_hash = {let mut hasher = DefaultHasher::new(); hasher.write (status_line.as_bytes()); hasher.finish()};
status_line.clear();
use std::fmt::Write;
let _ = write! (&mut *status_line, "{}:{}] {}", filename (file), line, status);
let new_hash = {let mut hasher = DefaultHasher::new(); hasher.write (status_line.as_bytes()); hasher.finish()};
if old_hash != new_hash {
match term_size::dimensions() {
Some ((w, _)) if status_line.chars().count() >= w => {
let mut tmp = String::with_capacity (w - 1);
for ch in status_line.chars().take (w - 1) {tmp.push (ch)}
let _ = stdout.get_mut().write (tmp.as_bytes());},
_ => {let _ = stdout.get_mut().write (status_line.as_bytes());}};
let _ = stdout.get_mut().write (b"\x1B[K");
let _ = stdout.carriage_return();
let _ = stdout.get_mut().flush();}}}}
#[cfg(feature = "term")]
pub fn status_line_clear() {
use io::Write;
if let Ok (mut status_line) = STATUS_LINE.lock() {
if *ISATTY && !status_line.is_empty() {
if let Some (mut stdout) = term::stdout() {
status_line.clear();
let _ = stdout.get_mut().write (b"\x1B[K");
let _ = stdout.get_mut().flush();}}}}
#[cfg(feature = "term")]
pub fn with_status_line (code: &dyn Fn()) {
use io::Write;
if let Ok (status_line) = STATUS_LINE.lock() {
if !*ISATTY || status_line.is_empty() {
code()
} else if let Some (mut stdout) = term::stdout() {
let _ = stdout.get_mut().write (b"\x1B[K");
let _ = stdout.get_mut().flush();
code();
let _ = stdout.get_mut().write (status_line.as_bytes());
let _ = stdout.carriage_return();
let _ = stdout.get_mut().flush();}}}
#[cfg(feature = "term")]
#[test] fn test_status_line() {
with_status_line (&|| println! ("hello world"));}
#[macro_export] macro_rules! gstring {($array: ident, $code: block) => {{
let end = {
let mut $array = ::std::io::Cursor::new (&mut $array[..]);
let $array = &mut $array;
$code;
$array.position() as usize};
unsafe {::std::str::from_utf8_unchecked (&$array[0..end])}}}}
pub fn netstring (at: &[u8]) -> Result<(&[u8], &[u8]), String> {
let length_end = match at.iter().position (|&ch| ch < b'0' || ch > b'9') {Some (l) if l > 0 => l, _ => return ERR! ("No len.")};
match at.get (length_end) {Some (&ch) if ch == b':' => (), _ => return ERR! ("No colon.")};
let length = unsafe {from_utf8_unchecked (&at[0 .. length_end])};
let length: usize = try_s! (length.parse());
let bulk_pos = 0 + length_end + 1;
let bulk_end = bulk_pos + length;
match at.get (bulk_end) {Some (&ch) if ch == b',' => (), _ => return ERR! ("No comma.")}
Ok ((&at[bulk_pos .. bulk_end], &at[bulk_end + 1 ..]))}
#[cfg(unix)]
pub fn with_hostname (visitor: &mut FnMut (&[u8])) -> Result<(), std::io::Error> {
use libc::{size_t, gethostname};
use std::ffi::CStr;
use std::io;
let mut buf = [0; 128];
let rc = unsafe {gethostname (buf.as_mut_ptr(), (buf.len() - 1) as size_t)};
if rc == 0 {
let cs = unsafe {CStr::from_ptr (buf.as_ptr())};
Ok (visitor (cs.to_bytes()))
} else {
Err (io::Error::last_os_error())}}
#[cfg(unix)] #[test] fn test_hostname() {
let mut hostname = String::new();
with_hostname (&mut |bytes| hostname = String::from_utf8_lossy (bytes) .into_owned()) .unwrap();}
pub fn slurp (path: &dyn AsRef<Path>) -> Vec<u8> {
let mut file = match fs::File::open (path) {
Ok (f) => f,
Err (ref err) if err.kind() == io::ErrorKind::NotFound => return Vec::new(),
Err (err) => panic! ("Can't open {:?}: {}", path.as_ref(), err)};
let mut buf = Vec::new();
file.read_to_end (&mut buf) .expect ("!read");
buf}
pub fn slurp_prog (command: &str) -> Result<String, String> {
let output = match Command::new ("dash") .arg ("-c") .arg (command) .output() {
Ok (output) => output,
Err (ref err) if err.kind() == io::ErrorKind::NotFound => {
try_s! (Command::new ("sh") .arg ("-c") .arg (command) .output())},
Err (err) => return ERR! ("{}", err)};
let combined_output: String = if output.stderr.is_empty() {
try_s! (String::from_utf8 (output.stdout))
} else if output.stdout.is_empty() {
try_s! (String::from_utf8 (output.stderr))
} else {
let mut buf = String::with_capacity (output.stderr.len() + output.stdout.len());
buf.push_str (try_s! (std::str::from_utf8 (&output.stderr[..])));
buf.push_str (try_s! (std::str::from_utf8 (&output.stdout[..])));
buf};
if output.status.success() {Ok (combined_output)} else {Err (combined_output)}}
#[test] fn test_slurp_prog() {
let foo = match slurp_prog ("echo foo") {Ok (foo) => foo, Err (err) => panic! ("{}", err)};
assert_eq! (foo.trim(), "foo");}
pub fn cmd (cmd: &str) -> Result<(), String> {
println! ("$ {}", cmd);
let status = try_s! (Command::new ("dash") .arg ("-c") .arg (cmd) .stdout (Stdio::inherit()) .stderr (Stdio::inherit()) .status());
if !status.success() {Err (format! ("Command returned an error status: {}", status))} else {Ok(())}}
pub fn any_to_str<'a> (message: &'a dyn Any) -> Option<&'a str> {
if let Some (message) = message.downcast_ref::<&str>() {return Some (message)}
if let Some (message) = message.downcast_ref::<String>() {return Some (&message[..])}
return None}
pub fn duration_to_float (duration: Duration) -> f64 {
duration.as_secs() as f64 + ((duration.subsec_nanos() as f64) / 1000000000.0)}
pub fn now_float() -> f64 {
let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since");
duration_to_float (now)}
#[test] fn test_now_float() {
let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since") .as_secs();
let t1 = now_float();
assert_eq! (now, t1 as u64);
sleep (Duration::from_millis (100));
let t2 = now_float();
let delta = t2 - t1;
assert! (delta >= 0.098 && delta <= 0.150, "delta: {}", delta);}
pub fn duration_to_ms (duration: Duration) -> u64 {
duration.as_secs() * 1000 + (duration.subsec_nanos() / 1000000) as u64}
pub fn now_ms() -> u64 {
let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since");
duration_to_ms (now)}
#[test] fn test_now_ms() {
let t1 = now_ms();
sleep (Duration::from_millis (100));
let t2 = now_ms();
let delta = t2 - t1;
assert! (delta >= 98 && delta <= 150, "delta: {}", delta);}
pub fn last_modified_sec (path: &dyn AsRef<Path>) -> Result<f64, String> {
let meta = match path.as_ref().metadata() {
Ok (m) => m,
Err (ref err) if err.kind() == std::io::ErrorKind::NotFound => return Ok (0.),
Err (err) => return ERR! ("{}", err)};
let lm = try_s! (meta.modified());
let lm = duration_to_float (try_s! (lm.duration_since (UNIX_EPOCH)));
Ok (lm)}
#[macro_export]
macro_rules! take_until_parse_s (
($i: expr, $remainder: ident! ($($args:tt)*)) => ({
let input: &str = $i;
let mut ret = IResult::Error (error_position! (nom::ErrorKind::Custom (0), input));
for (pos, _) in $i.char_indices() {
match $remainder! (&input[pos..], $($args)*) {
IResult::Done (i,o) => {ret = IResult::Done (i, (&input[0..pos], o)); break},
IResult::Error(_) => continue,
IResult::Incomplete(_) => continue}}
if !ret.is_done() {
if let IResult::Done (i,o) = $remainder! ("", $($args)*) {
ret = IResult::Done (i, (input, o))}}
ret});
($i: expr, $f: expr) => (take_until_parse_s! ($i, call! ($f));););
#[macro_export]
macro_rules! parse_replace_s {
($i: expr, $submac: ident! ($($args:tt)*)) => ({
let input: &str = $i;
let mut output = String::with_capacity (input.len() * 2 + 32);
let mut pos = input;
while let IResult::Done (tail, (head, evaluation)) = take_until_parse_s! (pos, $submac! ($($args)*)) {
output.push_str (head);
output.push_str (&evaluation);
pos = tail;}
output.push_str (pos);
output});
($i: expr, $f: expr) => (parse_replace_s! ($i, call! ($f)););}
#[macro_export]
macro_rules! take_until_find_parse_s (
($i: expr, $starts: expr, $remainder: ident! ($($args:tt)*)) => ({
let input: &str = $i;
let mut ret = IResult::Error (error_position! (nom::ErrorKind::Custom (0), input));
for (pos, _) in $i.match_indices ($starts) {
match $remainder! (&input[pos..], $($args)*) {
IResult::Done (i,o) => {ret = IResult::Done (i, (&input[0..pos], o)); break},
IResult::Error(_) => continue,
IResult::Incomplete(_) => continue}}
ret});
($i: expr, $starts: expr, $f: expr) => (take_until_find_parse_s! ($i, $starts, call! ($f));););
#[macro_export]
macro_rules! find_parse_replace_s {
($i: expr, $starts: expr, $submac: ident! ($($args:tt)*)) => ({
let input: &str = $i;
let mut output = String::with_capacity (input.len() * 2 + 32);
let mut pos = input;
while let IResult::Done (tail, (head, evaluation)) =
take_until_find_parse_s! (pos, $starts, $submac! ($($args)*)) {
output.push_str (head);
output.push_str (&evaluation);
pos = tail;}
output.push_str (pos);
output});
($i: expr, $starts: expr, $f: expr) => (find_parse_replace_s! ($i, $starts, call! ($f)););}
#[cfg(feature = "nightly")]
pub fn rdtsc() -> u64 {
#[allow(unused_mut)] unsafe {
let mut low: u32; let mut high: u32;
asm!("rdtsc" : "={eax}" (low), "={edx}" (high) ::: "volatile");
((high as u64) << 32) | (low as u64)}}
#[cfg(feature = "nightly")]
#[test] fn test_rdtsc() {
assert! (rdtsc() != rdtsc())}
pub struct FileLock<'a> {
pub lock_path: &'a dyn AsRef<Path>,
pub ttl_sec: f64,
pub file: std::fs::File}
impl<'a> FileLock<'a> {
pub fn lock (lock_path: &'a dyn AsRef<Path>, ttl_sec: f64) -> Result<Option<FileLock<'a>>, String> {
let mut cycle = 0u8;
loop {
if cycle > 1 {break Ok (None)}
cycle += 1;
match std::fs::OpenOptions::new().write (true) .create_new (true) .open (lock_path.as_ref()) {
Ok (file) => break Ok (Some (FileLock {lock_path, ttl_sec, file})),
Err (ref ie) if ie.kind() == std::io::ErrorKind::AlreadyExists => {
let lm = match last_modified_sec (lock_path) {
Ok (lm) => lm,
Err (ie) => break ERR! ("Error checking {:?}: {}", lock_path.as_ref(), ie)};
if lm == 0. {continue}
if now_float() - lm > ttl_sec {
if let Err (err) = std::fs::remove_file (lock_path.as_ref()) {break ERR! ("Error removing {:?}: {}", lock_path.as_ref(), err)}
continue}
break Ok (None)},
Err (ie) => break ERR! ("Error creating {:?}: {}", lock_path.as_ref(), ie)}}}
#[cfg(target_os = "linux")]
pub fn touch (&self) -> Result<(), String> {
let ts = libc::timespec {tv_sec: 0, tv_nsec: libc::UTIME_NOW};
let times = [ts, ts];
use std::os::unix::io::AsRawFd;
let rc = unsafe {libc::futimens (self.file.as_raw_fd(), ×[0])};
if rc != 0 {
let err = std::io::Error::last_os_error();
return ERR! ("Can't touch {:?}: {}", self.lock_path.as_ref(), err)}
Ok(())}}
impl<'a> Drop for FileLock<'a> {
fn drop (&mut self) {
let _ = std::fs::remove_file (self.lock_path);}}
impl<'a> std::fmt::Debug for FileLock<'a> {
fn fmt (&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write! (f, "FileLock ({:?}, {})", self.lock_path.as_ref(), self.ttl_sec)}}
#[derive(Debug)]
pub struct ProcEn {
pub name: String,
pub path: std::path::PathBuf,
pub cmdline: Vec<String>}
impl ProcEn {
pub fn pid (&self) -> Option<u32> {
if let Some (file_name) = self.path.file_name() {
if let Some (file_name) = file_name.to_str() {
if let Ok (pid) = file_name.parse() {
return Some (pid)}}}
None}}
pub struct ProcIt {read_dir: std::fs::ReadDir}
impl ProcIt {
pub fn new() -> ProcIt {
ProcIt {
read_dir: match Path::new ("/proc") .read_dir() {Ok (it) => it, Err (err) => panic! ("!proc: {}", err)}}}}
impl Iterator for ProcIt {
type Item = ProcEn;
fn next (&mut self) -> Option<ProcEn> {
match self.read_dir.next() {
None => return None,
Some (Err (err)) => panic! ("ProcIt] !read_dir: {}", err),
Some (Ok (proc_en)) => {
let file_type = match proc_en.file_type() {Ok (ft) => ft, Err (err) => panic! ("!file_type: {}", err)};
if !file_type.is_dir() {return self.next()}
let name = proc_en.file_name();
let name = match name.to_str() {Some (name) => name, None => panic! ("ProcIt] !to_str")};
if !name.as_bytes().iter().all (|&b| b >= b'0' && b <= b'9') {
return self.next()}
let path = proc_en.path();
let cmdline = String::from_utf8 (slurp (&path.join ("cmdline"))) .expect ("!from_utf8");
if cmdline.is_empty() {return self.next()}
Some (ProcEn {name: name.into(), path: path, cmdline: cmdline.split ('\0') .map (String::from) .collect()})}}}}
pub mod oneshot {
use std::panic::AssertUnwindSafe;
use std::sync::{Arc, Condvar, Mutex};
pub struct Sender<T> (Arc<Mutex<Option<T>>>, Arc<AssertUnwindSafe<Condvar>>);
impl<T> Sender<T> {
pub fn send (self, v: T) {
{ let arc_mutex = self.0;
{ let lr = arc_mutex.lock();
if let Ok (mut lock) = lr {*lock = Some (v)} } }
self.1.notify_one()}}
pub struct Receiver<T> (Arc<Mutex<Option<T>>>, Arc<AssertUnwindSafe<Condvar>>);
impl<T> Receiver<T> {
pub fn recv (self) -> Result<T, String> {
let mut arc_mutex = self.0;
let arc_condvar = self.1;
loop {
match Arc::try_unwrap (arc_mutex) {
Ok (mutex) => {
if let Some (value) = try_s! (mutex.into_inner()) {return Ok (value)}
else {return ERR! ("recv] Sender gone without providing a value")}},
Err (am) => {
arc_mutex = am;
let locked_value = try_s! (arc_mutex.lock());
if locked_value.is_none() {let _locked_value = try_s! (arc_condvar.wait (locked_value));}}}}}}
pub fn oneshot<T>() -> (Sender<T>, Receiver<T>) {
let arc_mutex = Arc::new (Mutex::new (None));
let arc_condvar = Arc::new (AssertUnwindSafe (Condvar::new()));
(Sender (arc_mutex.clone(), arc_condvar.clone()), Receiver (arc_mutex, arc_condvar))}}
#[test] fn test_oneshot() {
let (tx, rx) = oneshot::oneshot();
std::thread::spawn (|| tx.send (42));
assert_eq! (42, rx.recv().expect("!recv"))}
pub fn binprint (bin: &[u8], blank: u8) -> String {
let mut bin: Vec<u8> = bin.into();
for ch in bin.iter_mut() {if *ch < 0x20 || *ch >= 0x7F {*ch = blank}}
unsafe {String::from_utf8_unchecked (bin)}}
pub struct Constructible<T> {
value: Atomic<usize>,
_phantom: std::marker::PhantomData<T>}
impl<T> Default for Constructible<T> {
fn default() -> Constructible<T> {Constructible {
value: Atomic::new (0),
_phantom: PhantomData}}}
impl<T> From<T> for Constructible<T> {
fn from (v: T) -> Constructible<T> {
let v = Box::new (v);
let v = Box::into_raw (v);
Constructible {
value: Atomic::new (v as usize),
_phantom: PhantomData}}}
impl<T, E> FromStr for Constructible<T> where T: FromStr<Err=E>, E: fmt::Display {
type Err = String;
fn from_str (s: &str) -> Result<Self, Self::Err> {
let v: T = try_s! (s.parse());
Ok (Self::from (v))}}
impl<T> Constructible<T> {
pub fn initialize<'a> (&'a self, v: Box<T>) -> Result<&'a T, String> {
let v = Box::into_raw (v);
if let Err (_) = self.value.compare_exchange (0, v as usize, Ordering::Relaxed, Ordering::Relaxed) {
unsafe {Box::from_raw (v)};
return ERR! ("Cell already initialized")}
Ok (unsafe {&*v})}
pub fn pin<'a> (&'a self, v: T) -> Result<&'a T, String> {self.initialize (Box::new (v))}
pub fn or<'a, F> (&'a self, default: &'a F) -> &'a T where F: Fn() -> &'a T, T: 'a {
let v = self.value.load (Ordering::Relaxed);
if v != 0 {
let v = v as *const T;
unsafe {&*v}
} else {
default()}}
pub fn copy_or (&self, default: T) -> T where T: Copy {
let v = self.value.load (Ordering::Relaxed);
if v != 0 {
let v = v as *const T;
unsafe {*v}
} else {
default}}
pub fn clone_or (&self, default: T) -> T where T: Clone {
let v = self.value.load (Ordering::Relaxed);
if v != 0 {
let v = v as *const T;
unsafe {(*v).clone()}
} else {
default}}
pub fn ok_or<'a, E> (&'a self, error: E) -> Result<&'a T, E> {
let v = self.value.load (Ordering::Relaxed);
if v != 0 {
let v = v as *const T;
Ok (unsafe {&*v})
} else {
Err (error)}}
pub fn as_option<'a> (&'a self) -> Option<&'a T> {
let v = self.value.load (Ordering::Relaxed);
if v != 0 {
let v = v as *const T;
Some (unsafe {&*v})
} else {
None}}
pub fn is_none (&self) -> bool {
self.value.load (Ordering::Relaxed) == 0}
pub fn is_some (&self) -> bool {
self.value.load (Ordering::Relaxed) != 0}
pub fn iter<'a> (&'a self) -> std::option::IntoIter<&'a T> {
self.as_option().into_iter()}}
impl<'a, T> IntoIterator for &'a Constructible<T> {
type Item = &'a T;
type IntoIter = std::option::IntoIter<&'a T>;
fn into_iter (self) -> Self::IntoIter {
self.as_option().into_iter()}}
impl<T> fmt::Debug for Constructible<T> where T: fmt::Debug {
fn fmt (&self, ft: &mut fmt::Formatter) -> fmt::Result {
write! (ft, "{:?}", self.as_option())}}
impl<T> fmt::Display for Constructible<T> where T: fmt::Display {
fn fmt (&self, ft: &mut fmt::Formatter) -> fmt::Result {
match self.as_option() {
Some (v) => write! (ft, "{}", v),
None => write! (ft, "-")}}}
impl<T> Drop for Constructible<T> {
fn drop (&mut self) {
let v = self.value.load (Ordering::Relaxed);
if v == 0 {return}
if self.value.compare_exchange (v, 0, Ordering::Relaxed, Ordering::Relaxed) .is_err() {return}
unsafe {Box::from_raw (v as *mut T)};}}