pub mod paths {
use std::path::PathBuf;
pub const HOME_ENV: &str = "HOME";
pub fn home_dir() -> Option<PathBuf> {
std::env::var_os(HOME_ENV).map(PathBuf::from)
}
}
pub mod control {
use std::io;
use std::path::Path;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Probe {
Live,
Dead,
Indeterminate,
}
pub type Stream = std::os::unix::net::UnixStream;
pub struct Listener(std::os::unix::net::UnixListener);
impl Listener {
pub fn accept(&self) -> io::Result<Stream> {
self.0.accept().map(|(stream, _)| stream)
}
pub fn incoming(&self) -> impl Iterator<Item = io::Result<Stream>> + '_ {
std::iter::from_fn(move || Some(self.accept()))
}
}
pub fn connect(addr: impl AsRef<Path>) -> io::Result<Stream> {
Stream::connect(addr.as_ref())
}
pub fn bind(addr: impl AsRef<Path>) -> io::Result<Listener> {
std::os::unix::net::UnixListener::bind(addr.as_ref()).map(Listener)
}
pub fn probe(addr: impl AsRef<Path>) -> Probe {
match Stream::connect(addr.as_ref()) {
Ok(_) => Probe::Live,
Err(e)
if matches!(
e.kind(),
io::ErrorKind::NotFound | io::ErrorKind::ConnectionRefused
) =>
{
Probe::Dead
}
Err(_) => Probe::Indeterminate,
}
}
}
pub mod tty {
pub fn sanitize() {
unsafe {
if libc::isatty(libc::STDOUT_FILENO) != 1 {
return;
}
let mut t: libc::termios = std::mem::zeroed();
if libc::tcgetattr(libc::STDOUT_FILENO, &mut t) == 0 {
t.c_oflag |= libc::OPOST | libc::ONLCR;
t.c_lflag |= libc::ICANON | libc::ECHO | libc::ECHOE | libc::ISIG;
t.c_iflag |= libc::ICRNL;
let _ = libc::tcsetattr(libc::STDOUT_FILENO, libc::TCSANOW, &t);
}
}
}
pub fn is_stdin_tty() -> bool {
unsafe { libc::isatty(libc::STDIN_FILENO) == 1 }
}
}
pub mod daemon {
use std::process::Command;
pub fn detach(cmd: &mut Command) {
use std::os::unix::process::CommandExt;
unsafe {
cmd.pre_exec(|| {
libc::setsid();
Ok(())
});
}
}
}
pub mod proc {
pub fn uid_tag() -> String {
unsafe { libc::getuid() }.to_string()
}
#[cfg(feature = "ssh")]
pub fn hostname() -> Option<String> {
let mut buf = [0u8; 256];
if unsafe { libc::gethostname(buf.as_mut_ptr().cast::<libc::c_char>(), buf.len()) } != 0 {
return None;
}
let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
let name = String::from_utf8_lossy(&buf[..end]).trim().to_string();
(!name.is_empty()).then_some(name)
}
pub fn kill_matching(needle: &str) {
let _ = std::process::Command::new("pkill")
.arg("-f")
.arg(needle)
.status();
}
pub fn kill_all_mars() {
for needle in ["mars --server", "mars keyd"] {
kill_matching(needle);
}
}
}
pub mod fsperm {
use std::io;
use std::path::Path;
pub fn restrict_dir(path: &Path) -> io::Result<()> {
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700))
}
#[cfg(feature = "ssh")]
pub fn restrict_file(path: &Path) -> io::Result<()> {
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
}
}
pub mod shell {
pub fn default_shell() -> String {
std::env::var("SHELL").unwrap_or_else(|_| "/bin/bash".to_string())
}
}
pub mod health {
use std::path::Path;
pub fn load_avg1() -> Option<f64> {
let mut avg = [0f64; 3];
let n = unsafe { libc::getloadavg(avg.as_mut_ptr(), 3) };
if n >= 1 { Some(avg[0]) } else { None }
}
pub fn disk_free_pct(path: &Path) -> Option<u8> {
use std::os::unix::ffi::OsStrExt;
let c = std::ffi::CString::new(path.as_os_str().as_bytes()).ok()?;
let mut s: libc::statvfs = unsafe { std::mem::zeroed() };
if unsafe { libc::statvfs(c.as_ptr(), &mut s) } != 0 { return None; }
let total = s.f_blocks as u128;
if total == 0 { return None; }
let free = s.f_bavail as u128;
Some(((free * 100 / total) as u8).min(100))
}
pub fn mem_used_pct() -> Option<u8> { imp::mem_used_pct() }
#[cfg(target_os = "macos")]
mod imp {
#[allow(deprecated)]
pub fn mem_used_pct() -> Option<u8> {
let total = memsize()? as u128;
if total == 0 { return None; }
let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
if page <= 0 { return None; }
let page = page as u64;
let mut stats: libc::vm_statistics64 = unsafe { std::mem::zeroed() };
let mut count = (std::mem::size_of::<libc::vm_statistics64>()
/ std::mem::size_of::<libc::integer_t>()) as libc::mach_msg_type_number_t;
let kr = unsafe {
libc::host_statistics64(
libc::mach_host_self(),
libc::HOST_VM_INFO64,
&mut stats as *mut _ as *mut libc::integer_t,
&mut count,
)
};
if kr != libc::KERN_SUCCESS { return None; }
let used_pages = stats.active_count as u64
+ stats.wire_count as u64
+ stats.compressor_page_count as u64;
let used = used_pages.saturating_mul(page) as u128;
Some(((used * 100 / total) as u8).min(100))
}
fn memsize() -> Option<u64> {
let mut val: u64 = 0;
let mut len = std::mem::size_of::<u64>();
let name = b"hw.memsize\0";
let r = unsafe {
libc::sysctlbyname(
name.as_ptr() as *const libc::c_char,
&mut val as *mut _ as *mut libc::c_void,
&mut len,
std::ptr::null_mut(),
0,
)
};
if r == 0 && val > 0 { Some(val) } else { None }
}
}
#[cfg(target_os = "linux")]
mod imp {
pub fn mem_used_pct() -> Option<u8> {
let txt = std::fs::read_to_string("/proc/meminfo").ok()?;
let (mut total, mut avail) = (0u64, 0u64);
for line in txt.lines() {
if let Some(v) = line.strip_prefix("MemTotal:") {
total = v.split_whitespace().next()?.parse().ok()?;
} else if let Some(v) = line.strip_prefix("MemAvailable:") {
avail = v.split_whitespace().next()?.parse().ok()?;
}
}
if total == 0 { return None; }
let used = total.saturating_sub(avail) as u128;
Some(((used * 100 / total as u128) as u8).min(100))
}
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
mod imp {
pub fn mem_used_pct() -> Option<u8> { None }
}
}