diff --git a/Cargo.toml b/Cargo.toml index 2fe43c2..511b070 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "smf" -version = "0.2.1" +version = "0.2.2" authors = ["Sean Klein "] edition = "2018" repository = "https://github.com/oxidecomputer/smf" diff --git a/src/smf.rs b/src/smf.rs index e47043a..a0f44a3 100644 --- a/src/smf.rs +++ b/src/smf.rs @@ -5,6 +5,7 @@ #![deny(missing_docs)] use std::path::{Path, PathBuf}; +use std::process::{Command, Output}; use std::str::FromStr; use std::string::ToString; use thiserror::Error; @@ -14,6 +15,12 @@ use thiserror::Error; #[error("{0}")] pub struct CommandOutputError(String); +const PFEXEC: &str = "/usr/bin/pfexec"; +const SVCPROP: &str = "/usr/bin/svcprop"; +const SVCS: &str = "/usr/bin/svcs"; +const SVCCFG: &str = "/usr/sbin/svccfg"; +const SVCADM: &str = "/usr/sbin/svcadm"; + trait OutputExt { fn read_stdout(&self) -> Result; } @@ -302,21 +309,22 @@ impl Query { } // Issues command, returns stdout. - fn issue_command(&self, args: Vec) -> Result { - Ok(std::process::Command::new("/usr/bin/svcs") + fn run(&self, args: Vec) -> Result { + Ok(std::process::Command::new(PFEXEC) .env_clear() + .arg(SVCS) .args(args) .output() .map_err(QueryError::Command)? .read_stdout()?) } - fn issue_status_command( + fn run_and_parse_output( &self, args: Vec, ) -> Result, QueryError> { Ok(self - .issue_command(args)? + .run(args)? .split('\n') .map(|s| s.parse::()) .collect::, _>>()? @@ -380,7 +388,7 @@ impl Query { QuerySelection::ByPattern(names) => self.add_patterns(&mut args, names), } - self.issue_status_command(args) + self.run_and_parse_output(args) } // Shared implementation for getting dependencies and dependents. @@ -399,7 +407,7 @@ impl Query { // XXX patterns need cleaning, same in other getters self.add_patterns(&mut args, patterns); - self.issue_status_command(args) + self.run_and_parse_output(args) } /// Returns the statuses of service instances upon which the provided @@ -466,7 +474,7 @@ impl Query { self.add_zone_to_args(&mut args); self.add_patterns(&mut args, patterns); Ok(self - .issue_command(args)? + .run(args)? .split('\n') .map(|s| s.parse::()) .collect::, _>>() @@ -612,17 +620,23 @@ impl ConfigExport { self } - /// Runs the export command, returning the manifest output as a string. - pub fn run>(&mut self, fmri: S) -> Result { + /// Returns the export command without running it. + pub fn as_command>(&mut self, fmri: S) -> Command { let mut args = vec!["export"]; if self.archive { args.push("-a"); } args.push(fmri.as_ref()); - Ok(std::process::Command::new("/usr/sbin/svccfg") - .env_clear() - .args(args) + let mut cmd = std::process::Command::new(PFEXEC); + cmd.env_clear().arg(SVCCFG).args(args); + cmd + } + + /// Runs the export command, returning the manifest output as a string. + pub fn run>(&mut self, fmri: S) -> Result { + Ok(self + .as_command(fmri) .output() .map_err(ConfigError::Command)? .read_stdout()?) @@ -647,8 +661,8 @@ impl ConfigImport { self } - /// Runs the import command. - pub fn run>(&mut self, path: P) -> Result<(), ConfigError> { + /// Returns the import command, without running it. + pub fn as_command>(&mut self, path: P) -> Command { let mut args = vec!["import"]; if self.validate { args.push("-V"); @@ -656,9 +670,14 @@ impl ConfigImport { let path_str = path.as_ref().to_string_lossy(); args.push(&path_str); - std::process::Command::new("/usr/sbin/svccfg") - .env_clear() - .args(args) + let mut cmd = std::process::Command::new(PFEXEC); + cmd.env_clear().arg(SVCCFG).args(args); + cmd + } + + /// Runs the import command. + pub fn run>(&mut self, path: P) -> Result<(), ConfigError> { + self.as_command(path) .output() .map_err(ConfigError::Command)? .read_stdout() @@ -686,17 +705,22 @@ impl ConfigDelete { self } - /// Runs the deletion command. - pub fn run>(&mut self, fmri: S) -> Result<(), ConfigError> { + /// Returns the deletion command, without running it. + pub fn as_command>(&mut self, fmri: S) -> Command { let mut args = vec!["delete"]; if self.force { args.push("-f"); } args.push(fmri.as_ref()); - std::process::Command::new("/usr/sbin/svccfg") - .env_clear() - .args(args) + let mut cmd = std::process::Command::new(PFEXEC); + cmd.env_clear().arg(SVCCFG).args(args); + cmd + } + + /// Runs the deletion command. + pub fn run>(&mut self, fmri: S) -> Result<(), ConfigError> { + self.as_command(fmri) .output() .map_err(ConfigError::Command)? .read_stdout() @@ -715,12 +739,17 @@ impl ConfigAdd { ConfigAdd { fmri } } + /// Returns the command, without running it + pub fn as_command>(&mut self, child: S) -> Command { + let args = vec!["-s", &self.fmri, "add", child.as_ref()]; + let mut cmd = std::process::Command::new(PFEXEC); + cmd.env_clear().arg(SVCCFG).args(args); + cmd + } + /// Runs the add entity command. pub fn run>(&mut self, child: S) -> Result<(), ConfigError> { - let args = vec!["-s", &self.fmri, "add", child.as_ref()]; - std::process::Command::new("/usr/sbin/svccfg") - .env_clear() - .args(args) + self.as_command(child) .output() .map_err(ConfigError::Command)? .read_stdout() @@ -739,8 +768,8 @@ impl ConfigSetProperty { ConfigSetProperty { fmri } } - /// Runs the set property command. - pub fn run(&self, property: Property) -> Result<(), ConfigError> { + /// Returns the command which would set a property + pub fn as_command(&self, property: Property) -> Command { let prop = format!( "{} = {}", property.name.to_string(), @@ -748,9 +777,14 @@ impl ConfigSetProperty { ); let args = vec!["-s", &self.fmri, "setprop", &prop]; - std::process::Command::new("/usr/sbin/svccfg") - .env_clear() - .args(args) + let mut cmd = std::process::Command::new(PFEXEC); + cmd.env_clear().arg(SVCCFG).args(args); + cmd + } + + /// Runs the command to set a property + pub fn run(&self, property: Property) -> Result<(), ConfigError> { + self.as_command(property) .output() .map_err(ConfigError::Command)? .read_stdout() @@ -770,14 +804,19 @@ impl ConfigAddPropertyValue { Self { fmri } } - /// Runs the add property value command. - pub fn run(&self, property: Property) -> Result<(), ConfigError> { + /// Returns the command to add a property + pub fn as_command(&self, property: Property) -> Command { let name = property.name.to_string(); let value = property.value.to_string(); let args = vec!["-s", &self.fmri, "addpropvalue", &name, &value]; - std::process::Command::new("/usr/sbin/svccfg") - .env_clear() - .args(args) + let mut cmd = std::process::Command::new(PFEXEC); + cmd.env_clear().arg(SVCCFG).args(args); + cmd + } + + /// Runs the add property value command. + pub fn run(&self, property: Property) -> Result<(), ConfigError> { + self.as_command(property) .output() .map_err(ConfigError::Command)? .read_stdout() @@ -797,13 +836,18 @@ impl ConfigDeletePropertyValue { Self { fmri } } - /// Runs the delete property value command. - pub fn run(&self, property_name: &PropertyName, value: &str) -> Result<(), ConfigError> { + /// Returns the command without running it. + pub fn as_command(&self, property_name: &PropertyName, value: &str) -> Command { let name = property_name.to_string(); let args = vec!["-s", &self.fmri, "delpropvalue", &name, value]; - std::process::Command::new("/usr/sbin/svccfg") - .env_clear() - .args(args) + let mut cmd = std::process::Command::new(PFEXEC); + cmd.env_clear().arg(SVCCFG).args(args); + cmd + } + + /// Runs the delete property value command. + pub fn run(&self, property_name: &PropertyName, value: &str) -> Result<(), ConfigError> { + self.as_command(property_name, value) .output() .map_err(ConfigError::Command)? .read_stdout() @@ -876,16 +920,6 @@ impl Adm { self } - fn run(args: Vec) -> Result<(), AdmError> { - std::process::Command::new("/usr/sbin/svcadm") - .env_clear() - .args(args) - .output() - .map_err(AdmError::Command)? - .read_stdout()?; - Ok(()) - } - /// Builds a [AdmEnable] object. /// /// ```no_run @@ -975,10 +1009,7 @@ trait AdmSubcommand { } /// Shared mechanism of running all subcommands created by [Adm]. -fn run_adm_subcommand( - subcommand: &C, - selection: AdmSelection, -) -> Result<(), AdmError> +fn as_adm_subcommand(subcommand: &C, selection: AdmSelection) -> Command where C: AdmSubcommand, S: AsRef, @@ -1001,7 +1032,26 @@ where args.extend(pattern.into_iter().map(|s| s.as_ref().to_string())); } } - Adm::run(args) + let mut cmd = std::process::Command::new(PFEXEC); + cmd.env_clear().arg(SVCADM).args(args); + cmd +} + +/// Shared mechanism of running all subcommands created by [Adm]. +fn run_adm_subcommand( + subcommand: &C, + selection: AdmSelection, +) -> Result<(), AdmError> +where + C: AdmSubcommand, + S: AsRef, + I: IntoIterator, +{ + as_adm_subcommand(subcommand, selection) + .output() + .map_err(AdmError::Command)? + .read_stdout()?; + Ok(()) } /// Created by [Adm::enable], enables the service instance(s). @@ -1063,6 +1113,15 @@ impl<'a> AdmEnable<'a> { self } + /// Returns the command, without running it + pub fn as_command(&mut self, selection: AdmSelection) -> Command + where + S: AsRef, + I: IntoIterator, + { + as_adm_subcommand(self, selection) + } + /// Runs the command. pub fn run(&mut self, selection: AdmSelection) -> Result<(), AdmError> where @@ -1132,6 +1191,15 @@ impl<'a> AdmDisable<'a> { self } + /// Returns the command, without running it + pub fn as_command(&mut self, selection: AdmSelection) -> Command + where + S: AsRef, + I: IntoIterator, + { + as_adm_subcommand(self, selection) + } + /// Runs the command. pub fn run(&mut self, selection: AdmSelection) -> Result<(), AdmError> where @@ -1173,6 +1241,15 @@ impl<'a> AdmRestart<'a> { self } + /// Returns the command, without running it + pub fn as_command(&mut self, selection: AdmSelection) -> Command + where + S: AsRef, + I: IntoIterator, + { + as_adm_subcommand(self, selection) + } + /// Runs the command. pub fn run(&mut self, selection: AdmSelection) -> Result<(), AdmError> where @@ -1206,6 +1283,15 @@ impl<'a> AdmRefresh<'a> { Self { adm } } + /// Returns the command, without running it + pub fn as_command(&mut self, selection: AdmSelection) -> Command + where + S: AsRef, + I: IntoIterator, + { + as_adm_subcommand(self, selection) + } + /// Runs the command. pub fn run(&mut self, selection: AdmSelection) -> Result<(), AdmError> where @@ -1240,6 +1326,15 @@ impl<'a> AdmClear<'a> { Self { adm } } + /// Returns the command, without running it + pub fn as_command(&mut self, selection: AdmSelection) -> Command + where + S: AsRef, + I: IntoIterator, + { + as_adm_subcommand(self, selection) + } + /// Runs the command. pub fn run(&mut self, selection: AdmSelection) -> Result<(), AdmError> where @@ -1624,8 +1719,8 @@ impl<'a> PropertyLookup<'a> { } } - /// Looks up a property for a specified FMRI. - pub fn run(&mut self, property: &PropertyName, fmri: S) -> Result + /// Returns the command to lookup a property + pub fn as_command(&mut self, property: &PropertyName, fmri: S) -> Command where S: AsRef, { @@ -1649,15 +1744,28 @@ impl<'a> PropertyLookup<'a> { // Requests a single FMRI. args.push(fmri.as_ref().into()); - let out = std::process::Command::new("/usr/bin/svcprop") - .env_clear() - .args(args) - .output() - .map_err(PropertyError::Command)? - .read_stdout()?; + let mut cmd = std::process::Command::new(PFEXEC); + cmd.env_clear().arg(SVCPROP).args(args); + cmd + } + /// Parses the output for an executed command from [Self::as_command]. + pub fn parse_output(output: &Output) -> Result { + let out = output.read_stdout()?; out.parse().map_err(|err: PropertyParseError| err.into()) } + + /// Looks up a property for a specified FMRI. + pub fn run(&mut self, property: &PropertyName, fmri: S) -> Result + where + S: AsRef, + { + let out = self + .as_command(property, fmri) + .output() + .map_err(PropertyError::Command)?; + Self::parse_output(&out) + } } /// Created by [Properties::wait], a builder object waiting for a property group to change. @@ -1670,15 +1778,8 @@ impl<'a> PropertyWait<'a> { PropertyWait { property_base } } - /// Waits until a specified property group changes before printing. - /// - /// Returns requested property - note that it might not be the - /// property which changed. - pub fn run( - &mut self, - property: &PropertyGroupName, - fmri: S, - ) -> Result + /// Returns the Command to wait for a property, without running it. + pub fn as_command(&mut self, property: &PropertyGroupName, fmri: S) -> Command where S: AsRef, { @@ -1702,15 +1803,35 @@ impl<'a> PropertyWait<'a> { // Requests a single FMRI. args.push(fmri.as_ref().into()); - let out = std::process::Command::new("/usr/bin/svcprop") - .env_clear() - .args(args) - .output() - .map_err(PropertyError::Command)? - .read_stdout()?; + let mut cmd = std::process::Command::new(PFEXEC); + cmd.env_clear().arg(SVCPROP).args(args); + cmd + } + /// Parses the output for an executed command from [Self::as_command]. + pub fn parse_output(output: &Output) -> Result { + let out = output.read_stdout()?; out.parse().map_err(|err: PropertyParseError| err.into()) } + + /// Waits until a specified property group changes before printing. + /// + /// Returns requested property - note that it might not be the + /// property which changed. + pub fn run( + &mut self, + property: &PropertyGroupName, + fmri: S, + ) -> Result + where + S: AsRef, + { + let output = self + .as_command(property, fmri) + .output() + .map_err(PropertyError::Command)?; + Self::parse_output(&output) + } } #[cfg(test)]