1
0
forked from brl/citadel-tools
citadel-tools/libcitadel/src/util.rs

229 lines
6.6 KiB
Rust

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;
pub fn is_valid_name(name: &str, maxsize: usize) -> bool {
name.len() <= maxsize &&
// Also false on empty string
is_first_char_alphabetic(name) &&
name.chars().all(is_alphanum_or_dash)
}
fn is_alphanum_or_dash(c: char) -> bool {
is_ascii(c) && (c.is_alphanumeric() || c == '-')
}
fn is_ascii(c: char) -> bool {
c as u32 <= 0x7F
}
pub fn is_first_char_alphabetic(s: &str) -> bool {
if let Some(c) = s.chars().next() {
return is_ascii(c) && c.is_alphabetic()
}
false
}
fn search_path(filename: &str) -> Result<PathBuf> {
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<S: AsRef<str>>(cmd_path: &str, args: S) -> Result<()> {
ensure_command_exists(cmd_path)?;
let args: Vec<&str> = args.as_ref().split_whitespace().collect::<Vec<_>>();
let status = Command::new(cmd_path)
.args(args)
.stderr(Stdio::inherit())
.status()?;
check_cmd_status(cmd_path, &status)
}
pub fn exec_cmdline_quiet<S: AsRef<str>>(cmd_path: &str, args: S) -> Result<()> {
ensure_command_exists(cmd_path)?;
let args: Vec<&str> = args.as_ref().split_whitespace().collect::<Vec<_>>();
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<S: AsRef<str>>(cmd_path: &str, args: S) -> Result<String> {
ensure_command_exists(cmd_path)?;
let args: Vec<&str> = args.as_ref().split_whitespace().collect::<Vec<_>>();
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<P: AsRef<Path>>(path: P) -> Result<String> {
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<P: AsRef<Path>>(path: P, range: FileRange) -> Result<Box<dyn Read>> {
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<S,P>(cmd_path: &str, args: S, input: P, range: FileRange) -> Result<String>
where S: AsRef<str>, P: AsRef<Path>
{
let mut r = ranged_reader(input.as_ref(), range)?;
ensure_command_exists(cmd_path)?;
let args: Vec<&str> = args.as_ref().split_whitespace().collect::<Vec<_>>();
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<P: AsRef<Path>>(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<P: AsRef<Path>>(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<P: AsRef<Path>>(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<P: AsRef<Path>>(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)
}
}