use std::path::{Path,PathBuf}; use std::process::{Command,ExitStatus,Stdio}; use std::mem; use libc::{self, c_char}; use std::ffi::CStr; use std::str::from_utf8_unchecked; use std::env; use std::fs::File; use std::io::{self,Seek,Read,BufReader,SeekFrom}; use failure::ResultExt; use crate::Result; fn search_path(filename: &str) -> Result { let path_var = env::var("PATH")?; for mut path in env::split_paths(&path_var) { path.push(filename); if path.exists() { return Ok(path); } } Err(format_err!("Could not find {} in $PATH", filename)) } pub fn ensure_command_exists(cmd: &str) -> Result<()> { let path = Path::new(cmd); if !path.is_absolute() { search_path(cmd)?; return Ok(()) } else if path.exists() { return Ok(()) } Err(format_err!("Cannot execute '{}': command does not exist", cmd)) } pub fn exec_cmdline>(cmd_path: &str, args: S) -> Result<()> { ensure_command_exists(cmd_path)?; let args: Vec<&str> = args.as_ref().split_whitespace().collect::>(); let status = Command::new(cmd_path) .args(args) .stderr(Stdio::inherit()) .status()?; check_cmd_status(cmd_path, &status) } pub fn exec_cmdline_quiet>(cmd_path: &str, args: S) -> Result<()> { ensure_command_exists(cmd_path)?; let args: Vec<&str> = args.as_ref().split_whitespace().collect::>(); let status = Command::new(cmd_path) .args(args) .stderr(Stdio::null()) .stdout(Stdio::null()) .status()?; check_cmd_status(cmd_path, &status) } pub fn exec_cmdline_with_output>(cmd_path: &str, args: S) -> Result { ensure_command_exists(cmd_path)?; let args: Vec<&str> = args.as_ref().split_whitespace().collect::>(); let res = Command::new(cmd_path) .args(args) .stderr(Stdio::inherit()) .output() .context(format!("unable to execute {}", cmd_path))?; check_cmd_status(cmd_path, &res.status)?; Ok(String::from_utf8(res.stdout).unwrap().trim().to_owned()) } fn check_cmd_status(cmd_path: &str, status: &ExitStatus) -> Result<()> { if !status.success() { match status.code() { Some(code) => bail!("command {} failed with exit code: {}", cmd_path, code), None => bail!("command {} failed with no exit code", cmd_path), } } Ok(()) } pub fn sha256>(path: P) -> Result { let output = exec_cmdline_with_output("/usr/bin/sha256sum", format!("{}", path.as_ref().display())) .context(format!("failed to calculate sha256 on {}", path.as_ref().display()))?; let v: Vec<&str> = output.split_whitespace().collect(); Ok(v[0].trim().to_owned()) } pub enum FileRange { All, Offset(usize), Range{offset: usize, len: usize}, } fn ranged_reader>(path: P, range: FileRange) -> Result> { let mut f = File::open(path.as_ref())?; let offset = match range { FileRange::All => 0, FileRange::Offset(n) => n, FileRange::Range {offset, len: _} => offset, }; if offset > 0 { f.seek(SeekFrom::Start(offset as u64))?; } let r = BufReader::new(f); if let FileRange::Range {offset: _, len} = range { Ok(Box::new(r.take(len as u64))) } else { Ok(Box::new(r)) } } /// /// Execute a command, pipe the contents of a file to stdin, return the output as a `String` /// pub fn exec_cmdline_pipe_input(cmd_path: &str, args: S, input: P, range: FileRange) -> Result where S: AsRef, P: AsRef { let mut r = ranged_reader(input.as_ref(), range)?; ensure_command_exists(cmd_path)?; let args: Vec<&str> = args.as_ref().split_whitespace().collect::>(); let mut child = Command::new(cmd_path) .args(args) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) .spawn() .context(format!("unable to execute {}", cmd_path))?; let stdin = child.stdin.as_mut().unwrap(); io::copy(&mut r, stdin)?; let output = child.wait_with_output()?; Ok(String::from_utf8(output.stdout).unwrap().trim().to_owned()) } pub fn xz_compress>(path: P) -> Result<()> { exec_cmdline("/usr/bin/xz", format!("-T0 {}", path.as_ref().display())) .context(format!("failed to compress {}", path.as_ref().display()))?; Ok(()) } pub fn xz_decompress>(path: P) -> Result<()> { exec_cmdline("/usr/bin/xz", format!("-d {}", path.as_ref().display())) .context(format!("failed to decompress {}", path.as_ref().display()))?; Ok(()) } pub fn mount>(source: &str, target: P, options: Option<&str>) -> Result<()> { let paths = format!("{} {}", source, target.as_ref().display()); let args = match options { Some(s) => format!("{} {}", s, paths), None => paths, }; exec_cmdline("/usr/bin/mount", args) } pub fn umount>(path: P) -> Result<()> { let args = format!("{}", path.as_ref().display()); exec_cmdline("/usr/bin/umount", args) } #[repr(C)] #[derive(Clone, Copy)] pub struct UtsName(libc::utsname); #[allow(dead_code)] impl UtsName { pub fn sysname(&self) -> &str { to_str(&(&self.0.sysname as *const c_char ) as *const *const c_char) } pub fn nodename(&self) -> &str { to_str(&(&self.0.nodename as *const c_char ) as *const *const c_char) } pub fn release(&self) -> &str { to_str(&(&self.0.release as *const c_char ) as *const *const c_char) } pub fn version(&self) -> &str { to_str(&(&self.0.version as *const c_char ) as *const *const c_char) } pub fn machine(&self) -> &str { to_str(&(&self.0.machine as *const c_char ) as *const *const c_char) } } pub fn uname() -> UtsName { unsafe { let mut ret: UtsName = mem::uninitialized(); libc::uname(&mut ret.0); ret } } #[inline] fn to_str<'a>(s: *const *const c_char) -> &'a str { unsafe { let res = CStr::from_ptr(*s).to_bytes(); from_utf8_unchecked(res) } }