From 536591518ed31174a2c01fbe48b2c58ec224c4da Mon Sep 17 00:00:00 2001 From: Steven Murawski Date: Wed, 28 Sep 2016 11:15:53 -0500 Subject: [PATCH 1/3] fix cargo fmt error Signed-off-by: Steven Murawski --- components/core/src/os/system/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/core/src/os/system/windows.rs b/components/core/src/os/system/windows.rs index 830548a2db..397f1d96f6 100644 --- a/components/core/src/os/system/windows.rs +++ b/components/core/src/os/system/windows.rs @@ -25,7 +25,7 @@ pub struct Uname { } pub fn uname() -> Result { - Ok(Uname{ + Ok(Uname { sys_name: String::from("Windows"), node_name: String::from("CHEF-WIN10"), release: String::from("10.0.14915"), From a37d105a3d8601e609e10cf119bd944a6e0840ea Mon Sep 17 00:00:00 2001 From: Steven Murawski Date: Tue, 27 Sep 2016 16:42:04 -0500 Subject: [PATCH 2/3] Support windows command resolution Signed-off-by: Steven Murawski --- components/core/src/fs.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/components/core/src/fs.rs b/components/core/src/fs.rs index 26738b5a4e..171d9c030c 100644 --- a/components/core/src/fs.rs +++ b/components/core/src/fs.rs @@ -201,6 +201,12 @@ pub fn find_command(command: &str) -> Option { if candidate.is_file() { return Some(candidate); } + else { + match find_command_with_pathext(&candidate) { + Some(result) => return Some(result), + None => {} + } + } } None } @@ -208,6 +214,26 @@ pub fn find_command(command: &str) -> Option { } } +// Windows relies on path extensions to resolve commands like `docker` to `docker.exe` +// Path extensions are found in the PATHEXT environment variable. +fn find_command_with_pathext(candidate: &PathBuf) -> Option { + match henv::var_os("PATHEXT") { + Some(pathexts) => { + for pathext in env::split_paths(&pathexts) { + let mut source_candidate = candidate.to_path_buf(); + let extension = pathext.to_str().unwrap().trim_matches('.'); + source_candidate.set_extension(extension); + let current_candidate = source_candidate.to_path_buf(); + if current_candidate.is_file() { + return Some(current_candidate); + } + } + }, + None => return None, + }; + None +} + /// Returns whether or not the current process is running with a root effective user id or not. pub fn am_i_root() -> bool { *EUID == 0u32 From 82af3ade3ac2dc2cb2f81733da5c02d47b6fd8d1 Mon Sep 17 00:00:00 2001 From: Steven Murawski Date: Wed, 28 Sep 2016 11:15:14 -0500 Subject: [PATCH 3/3] adding test coverage Signed-off-by: Steven Murawski --- components/core/src/fs.rs | 252 ++++++++++++++++-- .../tests/fixtures/bin/bin_with_extension.exe | 0 .../tests/fixtures/bin/bin_with_no_extension | 0 components/core/tests/fixtures/bin/plan.sh | 0 .../tests/fixtures/bin/win95_dominator.bat | 0 5 files changed, 237 insertions(+), 15 deletions(-) create mode 100644 components/core/tests/fixtures/bin/bin_with_extension.exe create mode 100644 components/core/tests/fixtures/bin/bin_with_no_extension create mode 100644 components/core/tests/fixtures/bin/plan.sh create mode 100644 components/core/tests/fixtures/bin/win95_dominator.bat diff --git a/components/core/src/fs.rs b/components/core/src/fs.rs index 171d9c030c..8ea7545862 100644 --- a/components/core/src/fs.rs +++ b/components/core/src/fs.rs @@ -185,6 +185,48 @@ pub fn svc_var_path(service_name: &str) -> PathBuf { /// /// If the command represents an absolute path, then the `PATH` seaching will not be performed. If /// no absolute path can be found for the command, then `None` is returned. +/// +/// On Windows, the PATHEXT environment variable contains common extensions for commands, +/// for example allowing "docker.exe" to be found when searching for "docker". +/// +/// # Examples +/// +/// Behavior when the command exists on PATH: +/// +/// ``` +/// +/// use std::env; +/// use std::fs; +/// use habitat_core::fs::find_command; +/// +/// let first_path = fs::canonicalize("./tests/fixtures").unwrap(); +/// let second_path = fs::canonicalize("./tests/fixtures/bin").unwrap(); +/// let path_bufs = vec![first_path, second_path]; +/// let new_path = env::join_paths(path_bufs).unwrap(); +/// env::set_var("PATH", &new_path); +/// +/// let result = find_command("bin_with_no_extension"); +/// assert_eq!(result.is_some(), true); +/// ``` +/// +/// Behavior when the command does not exist on PATH: +/// +/// ``` +/// +/// use std::env; +/// use std::fs; +/// use habitat_core::fs::find_command; +/// +/// let first_path = fs::canonicalize("./tests/fixtures").unwrap(); +/// let second_path = fs::canonicalize("./tests/fixtures/bin").unwrap(); +/// let path_bufs = vec![first_path, second_path]; +/// let new_path = env::join_paths(path_bufs).unwrap(); +/// env::set_var("PATH", &new_path); +/// +/// let result = find_command("missing"); +/// assert_eq!(result.is_some(), false); +/// ``` +/// pub fn find_command(command: &str) -> Option { // If the command path is absolute and a file exists, then use that. let candidate = PathBuf::from(command); @@ -200,12 +242,11 @@ pub fn find_command(command: &str) -> Option { let candidate = PathBuf::from(&path).join(command); if candidate.is_file() { return Some(candidate); - } - else { + } else { match find_command_with_pathext(&candidate) { Some(result) => return Some(result), None => {} - } + } } } None @@ -216,21 +257,24 @@ pub fn find_command(command: &str) -> Option { // Windows relies on path extensions to resolve commands like `docker` to `docker.exe` // Path extensions are found in the PATHEXT environment variable. +// We should only search with PATHEXT if the file does not already have an extension. fn find_command_with_pathext(candidate: &PathBuf) -> Option { - match henv::var_os("PATHEXT") { - Some(pathexts) => { - for pathext in env::split_paths(&pathexts) { - let mut source_candidate = candidate.to_path_buf(); - let extension = pathext.to_str().unwrap().trim_matches('.'); - source_candidate.set_extension(extension); - let current_candidate = source_candidate.to_path_buf(); - if current_candidate.is_file() { - return Some(current_candidate); + if candidate.extension().is_none() { + match henv::var_os("PATHEXT") { + Some(pathexts) => { + for pathext in env::split_paths(&pathexts) { + let mut source_candidate = candidate.to_path_buf(); + let extension = pathext.to_str().unwrap().trim_matches('.'); + source_candidate.set_extension(extension); + let current_candidate = source_candidate.to_path_buf(); + if current_candidate.is_file() { + return Some(current_candidate); + } } } - }, - None => return None, - }; + None => {}, + }; + } None } @@ -238,3 +282,181 @@ fn find_command_with_pathext(candidate: &PathBuf) -> Option { pub fn am_i_root() -> bool { *EUID == 0u32 } + +#[cfg(test)] +mod test_find_command { + + use std::env; + use std::fs; + use std::path::PathBuf; + pub use super::find_command; + + fn setup_pathext() { + let path_bufs = vec![PathBuf::from(".COM"), PathBuf::from(".EXE")]; + let new_path = env::join_paths(path_bufs).unwrap(); + env::set_var("PATHEXT", &new_path); + } + + fn setup_empty_pathext() { + match env::var("PATHEXT") { + Ok(val) => env::remove_var("PATHEXT"), + Err(e) => {} + } + } + + fn setup_path() { + let first_path = fs::canonicalize("./tests/fixtures").unwrap(); + let second_path = fs::canonicalize("./tests/fixtures/bin").unwrap(); + let path_bufs = vec![first_path, second_path]; + let new_path = env::join_paths(path_bufs).unwrap(); + env::set_var("PATH", &new_path); + } + + mod without_pathext_set { + use super::{setup_path, setup_empty_pathext}; + pub use super::find_command; + + fn setup_environment() { + setup_path(); + setup_empty_pathext(); + } + + mod argument_without_extension { + use super::{setup_environment, find_command}; + + #[test] + fn command_exists() { + setup_environment(); + let result = find_command("bin_with_no_extension"); + assert_eq!(result.is_some(), true); + } + + #[test] + fn command_does_not_exist() { + setup_environment(); + let result = find_command("missing"); + assert_eq!(result.is_some(), false); + } + + #[test] + fn command_exists_with_extension() { + setup_environment(); + let result = find_command("win95_dominator"); + assert_eq!(result.is_some(), false); + } + } + + mod argument_with_extension { + use std::fs::canonicalize; + use super::{setup_environment, find_command}; + + #[test] + fn command_exists() { + setup_environment(); + let result = find_command("bin_with_extension.exe"); + assert_eq!(result.is_some(), true); + } + + #[test] + fn command_does_not_exist() { + setup_environment(); + let result = find_command("missing.com"); + assert_eq!(result.is_some(), false); + } + + #[test] + fn command_different_extension_does_exist() { + setup_environment(); + let result = find_command("bin_with_extension.com"); + assert_eq!(result.is_some(), false); + } + + #[test] + fn first_command_on_path_found() { + setup_environment(); + let target_path = canonicalize("./tests/fixtures/plan.sh").unwrap(); + let result = find_command("plan.sh"); + let found_path = result.unwrap(); + assert_eq!(found_path, target_path); + } + } + } + + #[cfg(target_os="windows")] + mod with_pathext_set { + use super::{setup_path, setup_pathext}; + pub use super::find_command; + + fn setup_environment() { + setup_path(); + setup_pathext(); + } + + mod argument_without_extension { + use super::{setup_environment, find_command}; + + #[test] + fn command_exists() { + setup_environment(); + let result = find_command("bin_with_no_extension"); + assert_eq!(result.is_some(), true); + } + + #[test] + fn command_does_not_exist() { + setup_environment(); + let result = find_command("missing"); + assert_eq!(result.is_some(), false); + } + + #[test] + fn command_exists_with_extension_in_PATHEXT() { + setup_environment(); + let result = find_command("bin_with_extension"); + assert_eq!(result.is_some(), true); + } + + #[test] + fn command_exists_with_extension_not_in_PATHEXT() { + setup_environment(); + let result = find_command("win95_dominator"); + assert_eq!(result.is_some(), false); + } + } + + mod argument_with_extension { + use std::fs::canonicalize; + use super::{setup_environment, find_command}; + + #[test] + fn command_exists() { + setup_environment(); + let result = find_command("bin_with_extension.exe"); + assert_eq!(result.is_some(), true); + } + + #[test] + fn command_does_not_exist() { + setup_environment(); + let result = find_command("missing.com"); + assert_eq!(result.is_some(), false); + } + + #[test] + fn command_different_extension_does_exist() { + setup_environment(); + let result = find_command("bin_with_extension.com"); + assert_eq!(result.is_some(), false); + } + + #[test] + fn first_command_on_path_found() { + setup_environment(); + let target_path = canonicalize("./tests/fixtures/plan.sh").unwrap(); + let result = find_command("plan.sh"); + let found_path = result.unwrap(); + assert_eq!(found_path, target_path); + } + } + } +} diff --git a/components/core/tests/fixtures/bin/bin_with_extension.exe b/components/core/tests/fixtures/bin/bin_with_extension.exe new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/core/tests/fixtures/bin/bin_with_no_extension b/components/core/tests/fixtures/bin/bin_with_no_extension new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/core/tests/fixtures/bin/plan.sh b/components/core/tests/fixtures/bin/plan.sh new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/core/tests/fixtures/bin/win95_dominator.bat b/components/core/tests/fixtures/bin/win95_dominator.bat new file mode 100644 index 0000000000..e69de29bb2