use std::fmt::{Display, Write};
use std::io::{self, Write as _};
use std::process::Command;
use std::str::FromStr;
use getopts::Options;
use sysinfo::{Pid, System};
use tracing::{debug, error, trace, warn};
use tracing_subscriber::FmtSubscriber;
pub const ENV_LOG_LEVEL: &str = "NETPULSE_LOG_LEVEL";
trait CommandExt {
fn parts(&self) -> Vec<&str>;
fn to_string(&self) -> String {
trace!("using custom to_string function");
let mut buf: String = String::new();
let parts = self.parts();
let len = parts.len();
for (idx, p) in parts.iter().enumerate() {
write!(buf, "{p}").expect("could not append to buffer");
if idx < len - 1 {
write!(buf, " ").expect("could not append to buffer");
}
}
buf
}
}
impl CommandExt for Command {
fn parts(&self) -> Vec<&str> {
let mut v: Vec<&str> = vec![self
.get_program()
.to_str()
.expect("program was not a proper string?")];
v.extend(
self.get_args()
.map(|a| a.to_str().expect("arg was not a proper string?")),
);
v
}
}
pub fn root_guard() {
if !nix::unistd::getuid().is_root() {
eprintln!("This needs to be run as root");
std::process::exit(1)
}
}
pub fn print_usage(program: &str, opts: Options) -> ! {
let brief = format!("Usage: {program} [options]");
print!("{}", opts.usage(&brief));
std::process::exit(0)
}
pub fn init_logging(level: tracing::Level) {
let level: tracing::Level = match std::env::var(ENV_LOG_LEVEL) {
Err(_) => level,
Ok(raw) => match tracing::Level::from_str(&raw) {
Err(e) => {
eprintln!("Bad log level was given with the environment variable '{ENV_LOG_LEVEL}': '{raw}', must be one of 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'");
eprintln!("{e}");
std::process::exit(1)
}
Ok(ll) => ll,
},
};
let subscriber = FmtSubscriber::builder()
.with_max_level(level)
.without_time()
.with_target(false)
.finish();
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
trace!("logging initialized with level {level}");
}
pub fn confirm(message: impl Display) -> bool {
print!("{message} y/N: ");
io::stdout().flush().unwrap();
let mut input = String::new();
if let Err(e) = io::stdin().read_line(&mut input) {
error!("could not read from stdin: {e}");
return false;
}
let input = input.trim().to_lowercase();
matches!(input.as_str(), "y" | "yes")
}
pub fn exec_cmd_for_user(cmd: &mut Command, skip_checks: bool) {
if !skip_checks && !confirm(format!("running cmd: {}", cmd.to_string())) {
trace!("returning early from exec_cmd_for_user because not confirmed");
return;
}
let out = match cmd.output() {
Err(e) => {
error!("{e}");
std::process::exit(1)
}
Ok(o) => o,
};
if !out.status.success() {
let info = String::from_utf8_lossy(&out.stdout);
let err = String::from_utf8_lossy(&out.stderr);
error!("command failed: {cmd:?}\nSTDERR:\n{err}\nSTDIN:\n{info}");
std::process::exit(1)
}
}
pub fn getpid_running() -> Option<Pid> {
let pid_of_current_process = std::process::id();
let s = System::new_all();
let mut processes: Vec<&sysinfo::Process> = s
.processes_by_exact_name("netpulsed".as_ref())
.filter(
|p| p.thread_kind().is_none(),
)
.filter(|p| p.pid().as_u32() != pid_of_current_process) .collect();
if processes.is_empty() {
None
} else if processes.len() == 1 {
Some(processes[0].pid())
} else {
warn!("netpulsed is running multiple times ({})", processes.len());
processes.sort_by_key(|a| a.pid());
debug!(
"listing netpulsed processes: {:?}",
processes.iter().map(|p| p.pid()).collect::<Vec<_>>()
);
Some(processes[0].pid())
}
}
pub fn setup_panic_handler() {
if !cfg!(debug_assertions) {
std::panic::set_hook(Box::new(|panic_info| {
let mut message = String::new();
message.push_str("\nWell, this is embarrassing.\n\n");
message.push_str(&format!(
"{} had a problem and crashed. This is a bug and should be reported!\n\n",
env!("CARGO_PKG_NAME")
));
message.push_str("Technical details:\n");
message.push_str(&format!("Version: {}\n", env!("CARGO_PKG_VERSION")));
#[cfg(target_os = "linux")]
let os = "linux";
#[cfg(target_os = "macos")]
let os = "macos";
#[cfg(target_os = "windows")]
let os = "windows";
message.push_str(&format!("OS: {} {}\n", os, std::env::consts::ARCH));
let args: Vec<_> = std::env::args().collect();
message.push_str(&format!("Command: {}\n", args.join(" ")));
if let Some(msg) = panic_info.payload().downcast_ref::<&str>() {
message.push_str(&format!("Error: {msg}\n"));
} else if let Some(msg) = panic_info.payload().downcast_ref::<String>() {
message.push_str(&format!("Error: {msg}\n"));
}
if let Some(location) = panic_info.location() {
message.push_str(&format!(
"Location: {}:{}\n",
location.file(),
location.line()
));
}
message.push_str(
"\nPlease create a new issue at https://github.com/PlexSheep/netpulse/issues\n",
);
message.push_str(
"with the above technical details and what you were doing when this happened.\n",
);
eprintln!("{message}");
std::process::exit(1);
}));
}
}