From ffa8b2917c5d85222e7308ae577e590f6ef04d1e Mon Sep 17 00:00:00 2001 From: Vadim Chugunov Date: Sun, 14 Sep 2014 01:33:21 -0700 Subject: [PATCH 1/2] Case-insensitive environment keys. --- src/libstd/io/process.rs | 82 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/src/libstd/io/process.rs b/src/libstd/io/process.rs index 1225dcf1f4a10..2ebafc30718f0 100644 --- a/src/libstd/io/process.rs +++ b/src/libstd/io/process.rs @@ -24,6 +24,9 @@ use rt::rtio::{RtioProcess, ProcessConfig, IoFactory, LocalIo}; use rt::rtio; use c_str::CString; use collections::HashMap; +use std::hash::Hash; +use std::hash::sip::SipState; +use clone::Clone; /// Signal a process to exit, without forcibly killing it. Corresponds to /// SIGTERM on unix platforms. @@ -78,8 +81,56 @@ pub struct Process { pub extra_io: Vec>, } +/// A representation of environment variable name +/// It compares case-insensitive on Windows and case-sensitive everywhere else. +#[cfg(not(windows))] +#[deriving(PartialEq, Eq, Hash, Clone, Show)] +struct EnvKey(CString); + +#[doc(hidden)] +#[cfg(windows)] +#[deriving(Eq, Clone, Show)] +struct EnvKey(CString); + +#[cfg(windows)] +impl Hash for EnvKey { + fn hash(&self, state: &mut SipState) { + let &EnvKey(ref x) = self; + match x.as_str() { + Some(s) => for ch in s.chars() { + (ch as u8 as char).to_lowercase().hash(state); + }, + None => x.hash(state) + } + } +} + +#[cfg(windows)] +impl PartialEq for EnvKey { + fn eq(&self, other: &EnvKey) -> bool { + let &EnvKey(ref x) = self; + let &EnvKey(ref y) = other; + match (x.as_str(), y.as_str()) { + (Some(xs), Some(ys)) => { + if xs.len() != ys.len() { + return false + } else { + for (xch, ych) in xs.chars().zip(ys.chars()) { + if xch.to_lowercase() != ych.to_lowercase() { + return false; + } + } + return true; + } + }, + // If either is not a valid utf8 string, just compare them byte-wise + _ => return x.eq(y) + } + } +} + /// A HashMap representation of environment variables. -pub type EnvMap = HashMap; +pub type EnvMap = HashMap; /// The `Command` type acts as a process builder, providing fine-grained control /// over how a new process should be spawned. A default configuration can be @@ -161,14 +212,14 @@ impl Command { self } // Get a mutable borrow of the environment variable map for this `Command`. - fn get_env_map<'a>(&'a mut self) -> &'a mut EnvMap { + fn get_env_map<'a>(&'a mut self) -> &'a mut EnvMap { match self.env { Some(ref mut map) => map, None => { // if the env is currently just inheriting from the parent's, // materialize the parent's env into a hashtable. self.env = Some(os::env_as_bytes().into_iter() - .map(|(k, v)| (k.as_slice().to_c_str(), + .map(|(k, v)| (EnvKey(k.as_slice().to_c_str()), v.as_slice().to_c_str())) .collect()); self.env.as_mut().unwrap() @@ -177,15 +228,18 @@ impl Command { } /// Inserts or updates an environment variable mapping. + /// + /// Note that environment variable names are case-insensitive (but case-preserving) on Windows, + /// and case-sensitive on all other platforms. pub fn env<'a, T: ToCStr, U: ToCStr>(&'a mut self, key: T, val: U) -> &'a mut Command { - self.get_env_map().insert(key.to_c_str(), val.to_c_str()); + self.get_env_map().insert(EnvKey(key.to_c_str()), val.to_c_str()); self } /// Removes an environment variable mapping. pub fn env_remove<'a, T: ToCStr>(&'a mut self, key: T) -> &'a mut Command { - self.get_env_map().remove(&key.to_c_str()); + self.get_env_map().remove(&EnvKey(key.to_c_str())); self } @@ -195,7 +249,7 @@ impl Command { /// variable, the *rightmost* instance will determine the value. pub fn env_set_all<'a, T: ToCStr, U: ToCStr>(&'a mut self, env: &[(T,U)]) -> &'a mut Command { - self.env = Some(env.iter().map(|&(ref k, ref v)| (k.to_c_str(), v.to_c_str())) + self.env = Some(env.iter().map(|&(ref k, ref v)| (EnvKey(k.to_c_str()), v.to_c_str())) .collect()); self } @@ -273,7 +327,9 @@ impl Command { let env = match self.env { None => None, Some(ref env_map) => - Some(env_map.iter().collect::>()) + Some(env_map.iter() + .map(|(&EnvKey(ref key), val)| (key, val)) + .collect::>()) }; let cfg = ProcessConfig { program: &self.program, @@ -1039,4 +1095,16 @@ mod tests { assert!(cmd.status().unwrap().success()); assert!(fdes.inner_write("extra write\n".as_bytes()).is_ok()); }) + + #[test] + #[cfg(windows)] + fn env_map_keys_ci() { + use super::EnvKey; + let mut cmd = Command::new(""); + cmd.env("path", "foo"); + cmd.env("Path", "bar"); + let env = &cmd.env.unwrap(); + let val = env.find(&EnvKey("PATH".to_c_str())); + assert!(val.unwrap() == &"bar".to_c_str()); + } } From 8b84911120d913294fd9f1c9a3349c560d2f2948 Mon Sep 17 00:00:00 2001 From: Vadim Chugunov Date: Wed, 17 Sep 2014 23:40:27 -0700 Subject: [PATCH 2/2] Pacify lint gods. --- src/libstd/io/process.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libstd/io/process.rs b/src/libstd/io/process.rs index 2ebafc30718f0..83890d2b1275e 100644 --- a/src/libstd/io/process.rs +++ b/src/libstd/io/process.rs @@ -24,9 +24,10 @@ use rt::rtio::{RtioProcess, ProcessConfig, IoFactory, LocalIo}; use rt::rtio; use c_str::CString; use collections::HashMap; -use std::hash::Hash; -use std::hash::sip::SipState; +use hash::Hash; use clone::Clone; +#[cfg(windows)] +use std::hash::sip::SipState; /// Signal a process to exit, without forcibly killing it. Corresponds to /// SIGTERM on unix platforms.