Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import svccfg(1), svcprop(1), devprop(1) and passwd(3C) #1

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ repository = "https://github.com/illumos/rust-illumos/"
keywords = ["illumos"]

[dependencies]
anyhow = "1.0"
libc = "0.2"
log = "0.4"
243 changes: 238 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,240 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
use anyhow::{bail, Result};
use log::{debug, error, info};
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Read, Write};
use std::process::{Command, Stdio};

mod os;
mod unix;

static SVCCFG_BIN: &str = "/usr/sbin/svccfg";
static SVCPROP_BIN: &str = "/usr/bin/svcprop";
static DEVPROP_BIN: &str = "/sbin/devprop";

fn spawn_reader<T>(
name: &str,
stream: Option<T>,
) -> Option<std::thread::JoinHandle<()>>
where
T: Read + Send + 'static,
{
let name = name.to_string();
let stream = match stream {
Some(stream) => stream,
None => return None,
};

Some(std::thread::spawn(move || {
let mut r = BufReader::new(stream);

loop {
let mut buf = String::new();

match r.read_line(&mut buf) {
Ok(0) => {
/*
* EOF.
*/
return;
}
Ok(_) => {
let s = buf.trim();

if !s.is_empty() {
info!(target: "illumos-rs", "{}| {}", name, s);
}
}
Err(e) => {
error!(target: "illumos-rs", "failed to read {}: {}", name, e);
std::process::exit(100);
}
}
}
}))
}

pub fn devprop<S: AsRef<str>>(key: S) -> Result<String> {
let key = key.as_ref();
let val = run_capture_stdout(vec![DEVPROP_BIN, key].as_ref(), None)?;
let lines: Vec<_> = val.lines().collect();
if lines.len() != 1 {
bail!("unexpected output for devprop {}: {:?}", key, lines);
}
Ok(lines[0].trim().to_string())
}

pub fn svccfg<S: AsRef<str>>(args: &[S], alt_root: Option<S>) -> Result<()> {
let svccfg: Vec<&str> = vec![SVCCFG_BIN];
let env = if let Some(alt_root) = alt_root {
let alt_root = alt_root.as_ref();
let dtd_path =
format!("{}/usr/share/lib/xml/dtd/service_bundle.dtd.1", alt_root);
let repo_path = format!("{}/etc/svc/repository.db", alt_root);
let configd_path = format!("{}/lib/svc/bin/svc.configd", alt_root);
Some(
vec![
("SVCCFG_CHECKHASH", "1"),
("PKG_INSTALL_ROOT", alt_root),
("SVCCFG_DTD", &dtd_path),
("SVCCFG_REPOSITORY", &repo_path),
("SVCCFG_CONFIGD_PATH", &configd_path),
]
.as_ref(),
)
} else {
None
};
let mut stdin = String::new();
for arg in args {
let arg = arg.as_ref();
stdin += &format!("{}\n", arg)
}

run_with_stdin(&svccfg, env, stdin)
}

pub fn svcprop(fmri: &str, prop_val: &str) -> Result<String> {
let val = run_capture_stdout(
vec![SVCPROP_BIN, "-p", prop_val, fmri].as_ref(),
None,
)?;
let lines: Vec<_> = val.lines().collect();
if lines.len() != 1 {
bail!("unexpected output for svcprop {}: {:?}", fmri, lines);
}
Ok(lines[0].trim().to_string())
}

pub fn run_with_stdin<S: AsRef<str>>(
args: &[S],
env: Option<&[(S, S)]>,
stdin: String,
) -> Result<()> {
let args: Vec<&str> = args.iter().map(|s| s.as_ref()).collect();
let env = build_env(env);
let mut cmd = build_cmd(args, env);

cmd.stdin(Stdio::piped());
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());

let mut child = cmd.spawn()?;
let mut child_stdin = child.stdin.take().unwrap();
std::thread::spawn(move || {
child_stdin.write_all(stdin.as_bytes()).unwrap();
});

let readout = spawn_reader("O", child.stdout.take());
let readerr = spawn_reader("E", child.stderr.take());

if let Some(t) = readout {
t.join().expect("join stdout thread");
}
if let Some(t) = readerr {
t.join().expect("join stderr thread");
}

match child.wait() {
Err(e) => Err(e.into()),
Ok(es) => {
if !es.success() {
bail!("exec {:?}: failed {:?}", &args, &es)
} else {
Ok(())
}
}
}
}

pub fn run<S: AsRef<str>>(args: &[S], env: Option<&[(S, S)]>) -> Result<()> {
let args: Vec<&str> = args.iter().map(|s| s.as_ref()).collect();
let env = build_env(env);
let mut cmd = build_cmd(args, env);

cmd.stdin(Stdio::null());
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());

let mut child = cmd.spawn()?;

let readout = spawn_reader("O", child.stdout.take());
let readerr = spawn_reader("E", child.stderr.take());

if let Some(t) = readout {
t.join().expect("join stdout thread");
}
if let Some(t) = readerr {
t.join().expect("join stderr thread");
}

match child.wait() {
Err(e) => Err(e.into()),
Ok(es) => {
if !es.success() {
bail!("exec {:?}: failed {:?}", &args, &es)
} else {
Ok(())
}
}
}
}

pub fn run_capture_stdout<S: AsRef<str>>(
args: &[S],
env: Option<&[(S, S)]>,
) -> Result<String> {
let args: Vec<&str> = args.iter().map(|s| s.as_ref()).collect();
let env = build_env(env);
let mut cmd = build_cmd(args, env);

cmd.stdin(Stdio::null());
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());

let output = cmd.output()?;
if output.status.success() {
Ok(String::from_utf8(output.stdout)?)
} else {
bail!(
"exec {:?}: failed {:?}",
&args,
String::from_utf8(output.stderr)?
)
}
}

fn build_env<S: AsRef<str>>(
env: Option<&[(S, S)]>,
) -> Option<Vec<(&str, &str)>> {
if let Some(env) = env {
let env: Vec<(&str, &str)> =
env.iter().map(|(k, v)| (k.as_ref(), v.as_ref())).collect();
Some(env)
} else {
None
}
}

fn build_cmd(args: Vec<&str>, env: Option<Vec<(&str, &str)>>) -> Command {
let mut cmd = Command::new(&args[0]);
cmd.env_remove("LANG");
cmd.env_remove("LC_CTYPE");
cmd.env_remove("LC_NUMERIC");
cmd.env_remove("LC_TIME");
cmd.env_remove("LC_COLLATE");
cmd.env_remove("LC_MONETARY");
cmd.env_remove("LC_MESSAGES");
cmd.env_remove("LC_ALL");

if args.len() > 1 {
cmd.args(&args[1..]);
}

if let Some(env) = env {
cmd.envs(env);
debug!(target: "illumos-rs", "exec: {:?} env={:?}", &args, &env);
} else {
debug!(target: "illumos-rs", "exec: {:?}", &args);
}
cmd
}
138 changes: 138 additions & 0 deletions src/os/illumos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright 2020 Oxide Computer Company
*/

use anyhow::Result;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};
use std::process::exit;

pub fn errno() -> i32 {
unsafe {
let enp = libc::___errno();
*enp
}
}

pub fn clear_errno() {
unsafe {
let enp = libc::___errno();
*enp = 0;
}
}

#[derive(Debug, PartialEq)]
pub struct UserAttr {
pub name: String,
pub attr: HashMap<String, String>,
}

impl UserAttr {
pub fn profiles(&self) -> Vec<String> {
if let Some(p) = self.attr.get("profiles") {
p.split(',')
.map(|s| s.trim().to_string())
.collect::<Vec<_>>()
} else {
Vec::new()
}
}
}

#[repr(C)]
struct Kv {
key: *const c_char,
value: *const c_char,
}

impl Kv {
fn name(&self) -> &CStr {
unsafe { CStr::from_ptr(self.key) }
}

fn value(&self) -> &CStr {
unsafe { CStr::from_ptr(self.value) }
}
}

#[repr(C)]
struct Kva {
length: c_int,
data: *const Kv,
}

impl Kva {
fn values(&self) -> &[Kv] {
unsafe { std::slice::from_raw_parts(self.data, self.length as usize) }
}
}

#[repr(C)]
struct UserAttrRaw {
name: *mut c_char,
qualifier: *mut c_char,
res1: *mut c_char,
res2: *mut c_char,
attr: *mut Kva,
}

#[link(name = "secdb")]
extern "C" {
fn getusernam(buf: *const c_char) -> *mut UserAttrRaw;
fn free_userattr(userattr: *mut UserAttrRaw);
}

pub fn get_user_attr_by_name(name: &str) -> Result<Option<UserAttr>> {
let mut out = UserAttr {
name: name.to_string(),
attr: HashMap::new(),
};

let name = CString::new(name.to_owned())?;
let ua = unsafe { getusernam(name.as_ptr()) };
if ua.is_null() {
return Ok(None);
}

for kv in unsafe { (*(*ua).attr).values() } {
if let (Ok(k), Ok(v)) = (kv.name().to_str(), kv.value().to_str()) {
out.attr.insert(k.to_string(), v.to_string());
} else {
continue;
}
}

unsafe { free_userattr(ua) };

Ok(Some(out))
}

#[link(name = "c")]
extern "C" {
fn getzoneid() -> i32;
fn getzonenamebyid(id: i32, buf: *mut u8, buflen: usize) -> isize;
}

pub fn zoneid() -> i32 {
unsafe { getzoneid() }
}

pub fn zonename() -> String {
let buf = unsafe {
let mut buf: [u8; 64] = std::mem::zeroed(); /* ZONENAME_MAX */

let sz = getzonenamebyid(getzoneid(), buf.as_mut_ptr(), 64);
if sz > 64 || sz < 0 {
eprintln!("getzonenamebyid failure");
exit(100);
}

Vec::from(&buf[0..sz as usize])
};
std::ffi::CStr::from_bytes_with_nul(&buf)
.unwrap()
.to_str()
.unwrap()
.to_string()
}
3 changes: 3 additions & 0 deletions src/os/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod illumos;

pub use illumos::*;
Loading