summaryrefslogtreecommitdiffhomepage
path: root/tools/unitctl/unit-client-rs/src/unitd_configure_options.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tools/unitctl/unit-client-rs/src/unitd_configure_options.rs')
-rw-r--r--tools/unitctl/unit-client-rs/src/unitd_configure_options.rs236
1 files changed, 236 insertions, 0 deletions
diff --git a/tools/unitctl/unit-client-rs/src/unitd_configure_options.rs b/tools/unitctl/unit-client-rs/src/unitd_configure_options.rs
new file mode 100644
index 00000000..00ee22a3
--- /dev/null
+++ b/tools/unitctl/unit-client-rs/src/unitd_configure_options.rs
@@ -0,0 +1,236 @@
+use custom_error::custom_error;
+use std::borrow::Cow;
+use std::error::Error as stdError;
+use std::io::{BufRead, BufReader, Lines};
+use std::path::{Path, PathBuf};
+use std::process::{Command, Stdio};
+
+custom_error! {UnitdStderrParseError
+ VersionNotFound = "Version string output not found",
+ BuildSettingsNotFound = "Build settings not found"
+}
+
+#[derive(Debug, Clone)]
+pub struct UnitdConfigureOptions {
+ pub version: Cow<'static, str>,
+ pub all_flags: Cow<'static, str>,
+}
+
+impl UnitdConfigureOptions {
+ pub fn new(unitd_path: &Path) -> Result<UnitdConfigureOptions, Box<dyn stdError>> {
+ fn parse_configure_settings_from_unitd_stderr_output<B: BufRead>(
+ lines: &mut Lines<B>,
+ ) -> Result<UnitdConfigureOptions, Box<dyn stdError>> {
+ const VERSION_PREFIX: &str = "unit version: ";
+ const CONFIGURED_AS_PREFIX: &str = "configured as ";
+ const CONFIGURE_PREFIX: &str = "configured as ./configure ";
+
+ fn aggregate_parsable_lines(
+ mut accum: (Option<String>, Option<String>),
+ line: String,
+ ) -> (Option<String>, Option<String>) {
+ if line.starts_with(VERSION_PREFIX) {
+ accum.0 = line.strip_prefix(VERSION_PREFIX).map(|l| l.to_string());
+ } else if line.starts_with(CONFIGURED_AS_PREFIX) {
+ accum.1 = line.strip_prefix(CONFIGURE_PREFIX).map(|l| l.to_string());
+ }
+
+ accum
+ }
+
+ let options_lines = lines
+ .filter_map(|line| line.ok())
+ .fold((None, None), aggregate_parsable_lines);
+
+ if options_lines.0.is_none() {
+ return Err(Box::new(UnitdStderrParseError::VersionNotFound) as Box<dyn stdError>);
+ } else if options_lines.1.is_none() {
+ return Err(Box::new(UnitdStderrParseError::BuildSettingsNotFound) as Box<dyn stdError>);
+ }
+
+ Ok(UnitdConfigureOptions {
+ version: options_lines.0.unwrap().into(),
+ all_flags: options_lines.1.unwrap().into(),
+ })
+ }
+
+ let program = unitd_path.as_os_str();
+ let child = Command::new(program)
+ .arg("--version")
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .spawn()?;
+ let output = child.wait_with_output()?;
+ let err = BufReader::new(&*output.stderr);
+ parse_configure_settings_from_unitd_stderr_output(&mut err.lines())
+ }
+
+ pub fn has_flag(&self, flag_name: &str) -> bool {
+ self.all_flags
+ .split_ascii_whitespace()
+ .any(|flag| flag.starts_with(format!("--{}", flag_name).as_str()))
+ }
+
+ pub fn get_flag_value(&self, flag_name: &str) -> Option<String> {
+ self.all_flags
+ .split_ascii_whitespace()
+ .find(|flag| flag.starts_with(format!("--{}", flag_name).as_str()))
+ .and_then(|flag| {
+ let parts: Vec<&str> = flag.split('=').collect();
+ if parts.len() >= 2 {
+ Some(parts[1].to_owned())
+ } else {
+ None
+ }
+ })
+ }
+
+ pub fn debug_enabled(&self) -> bool {
+ self.has_flag("debug")
+ }
+
+ pub fn openssl_enabled(&self) -> bool {
+ self.has_flag("openssl")
+ }
+
+ pub fn prefix_path(&self) -> Option<Box<Path>> {
+ self.get_flag_value("prefix")
+ .map(PathBuf::from)
+ .map(PathBuf::into_boxed_path)
+ }
+
+ fn join_to_prefix_path<S>(&self, sub_path: S) -> Option<Box<Path>>
+ where
+ S: Into<String>,
+ {
+ self.prefix_path()
+ .map(|path| path.join(sub_path.into()).into_boxed_path())
+ }
+
+ pub fn default_control_api_socket_address(&self) -> Option<String> {
+ // If the socket address is specific configured in the configure options, we use
+ // that. Otherwise, we use the default path as assumed to be unix:$prefix/control.unit.sock.
+ match self.get_flag_value("control") {
+ Some(socket_address) => Some(socket_address),
+ None => {
+ // Give up if the unitd is compiled with unix sockets disabled
+ if self.has_flag("no-unix-sockets") {
+ return None;
+ }
+ let socket_path = self.join_to_prefix_path("control.unit.sock");
+ socket_path.map(|path| format!("unix:{}", path.to_string_lossy()))
+ }
+ }
+ }
+
+ pub fn default_pid_path(&self) -> Option<Box<Path>> {
+ match self.get_flag_value("pid") {
+ Some(pid_path) => self.join_to_prefix_path(pid_path),
+ None => self.join_to_prefix_path("unit.pid"),
+ }
+ }
+
+ pub fn default_log_path(&self) -> Option<Box<Path>> {
+ match self.get_flag_value("log") {
+ Some(pid_path) => self.join_to_prefix_path(pid_path),
+ None => self.join_to_prefix_path("unit.log"),
+ }
+ }
+
+ pub fn default_modules_directory(&self) -> Option<Box<Path>> {
+ match self.get_flag_value("modules") {
+ Some(modules_dir_name) => self.join_to_prefix_path(modules_dir_name),
+ None => self.join_to_prefix_path("modules"),
+ }
+ }
+
+ pub fn default_state_directory(&self) -> Option<Box<Path>> {
+ match self.get_flag_value("state") {
+ Some(state_dir_name) => self.join_to_prefix_path(state_dir_name),
+ None => self.join_to_prefix_path("state"),
+ }
+ }
+
+ pub fn default_tmp_directory(&self) -> Option<Box<Path>> {
+ match self.get_flag_value("tmp") {
+ Some(tmp_dir_name) => self.join_to_prefix_path(tmp_dir_name),
+ None => self.join_to_prefix_path("tmp"),
+ }
+ }
+ pub fn default_user(&self) -> Option<String> {
+ self.get_flag_value("user").map(String::from)
+ }
+ pub fn default_group(&self) -> Option<String> {
+ self.get_flag_value("group").map(String::from)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::unitd_instance;
+ use crate::unitd_instance::UNITD_PATH_ENV_KEY;
+
+ #[test]
+ fn can_detect_key() {
+ let options = UnitdConfigureOptions {
+ version: Default::default(),
+ all_flags: Cow::from("--debug --openssl --prefix=/opt/unit"),
+ };
+ assert!(options.has_flag("debug"));
+ assert!(options.has_flag("openssl"));
+ assert!(options.has_flag("prefix"));
+ assert!(!options.has_flag("fobar"));
+ }
+
+ #[test]
+ fn can_get_flag_value_by_key() {
+ let expected = "/opt/unit";
+ let options = UnitdConfigureOptions {
+ version: Default::default(),
+ all_flags: Cow::from("--debug --openssl --prefix=/opt/unit"),
+ };
+
+ let actual = options.get_flag_value("prefix");
+ assert_eq!(expected, actual.unwrap())
+ }
+
+ #[test]
+ fn can_get_prefix_path() {
+ let expected: Box<Path> = Path::new("/opt/unit").into();
+ let options = UnitdConfigureOptions {
+ version: Default::default(),
+ all_flags: Cow::from("--debug --openssl --prefix=/opt/unit"),
+ };
+
+ let actual = options.prefix_path();
+ assert_eq!(expected, actual.unwrap())
+ }
+
+ #[test]
+ fn can_parse_complicated_configure_options() {
+ let expected: Box<Path> = Path::new("/usr").into();
+ let options = UnitdConfigureOptions {
+ version: Default::default(),
+ all_flags: Cow::from("--prefix=/usr --state=/var/lib/unit --control=unix:/var/run/control.unit.sock --pid=/var/run/unit.pid --log=/var/log/unit.log --tmp=/var/tmp --user=unit --group=unit --tests --openssl --modules=/usr/lib/unit/modules --libdir=/usr/lib/x86_64-linux-gnu --cc-opt='-g -O2 -fdebug-prefix-map=/data/builder/debuild/unit-1.28.0/pkg/deb/debuild/unit-1.28.0=. -specs=/usr/share/dpkg/no-pie-compile.specs -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --ld-opt='-Wl,-Bsymbolic-functions -specs=/usr/share/dpkg/no-pie-link.specs -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'
+"),
+ };
+
+ let actual = options.prefix_path();
+ assert_eq!(expected, actual.unwrap())
+ }
+
+ #[test]
+ #[ignore] // run this one manually - not in CI
+ fn can_run_unitd() {
+ let specific_path = std::env::var(UNITD_PATH_ENV_KEY).map_err(|error| Box::new(error) as Box<dyn stdError>);
+ let unitd_path = unitd_instance::find_executable_path(specific_path);
+ let config_options = UnitdConfigureOptions::new(&unitd_path.unwrap());
+ match config_options {
+ Ok(options) => {
+ println!("{:?}", options)
+ }
+ Err(error) => panic!("{}", error),
+ };
+ }
+}