Skip to content

Commit cb115ac

Browse files
committed
auto merge of #14075 : Rufflewind/rust/patch-3, r=alexcrichton
- Use Unicode-aware versions of `CreateProcess` (Fixes #13815) and `Get/FreeEnvironmentStrings`. - Includes a helper function `os::win32::as_mut_utf16_p`, which does the same thing as `os::win32::as_utf16_p` except the pointer is mutable. - Fixed `make_command_line` to handle Unicode correctly. - Tests for the above.
2 parents 5ad42b3 + b8e3f3a commit cb115ac

File tree

4 files changed

+153
-32
lines changed

4 files changed

+153
-32
lines changed

src/liblibc/lib.rs

+9-8
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ pub use funcs::bsd43::{shutdown};
208208
#[cfg(windows)] pub use consts::os::extra::{TRUE, FALSE, INFINITE};
209209
#[cfg(windows)] pub use consts::os::extra::{PROCESS_TERMINATE, PROCESS_QUERY_INFORMATION};
210210
#[cfg(windows)] pub use consts::os::extra::{STILL_ACTIVE, DETACHED_PROCESS};
211-
#[cfg(windows)] pub use consts::os::extra::{CREATE_NEW_PROCESS_GROUP};
211+
#[cfg(windows)] pub use consts::os::extra::{CREATE_NEW_PROCESS_GROUP, CREATE_UNICODE_ENVIRONMENT};
212212
#[cfg(windows)] pub use consts::os::extra::{FILE_BEGIN, FILE_END, FILE_CURRENT};
213213
#[cfg(windows)] pub use consts::os::extra::{FILE_GENERIC_READ, FILE_GENERIC_WRITE};
214214
#[cfg(windows)] pub use consts::os::extra::{FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_DELETE};
@@ -1937,6 +1937,7 @@ pub mod consts {
19371937

19381938
pub static DETACHED_PROCESS: DWORD = 0x00000008;
19391939
pub static CREATE_NEW_PROCESS_GROUP: DWORD = 0x00000200;
1940+
pub static CREATE_UNICODE_ENVIRONMENT: DWORD = 0x00000400;
19401941

19411942
pub static PIPE_ACCESS_DUPLEX: DWORD = 0x00000003;
19421943
pub static PIPE_ACCESS_INBOUND: DWORD = 0x00000001;
@@ -4193,8 +4194,8 @@ pub mod funcs {
41934194
pub mod kernel32 {
41944195
use types::os::arch::c95::{c_uint};
41954196
use types::os::arch::extra::{BOOL, DWORD, SIZE_T, HMODULE,
4196-
LPCWSTR, LPWSTR, LPCSTR, LPSTR,
4197-
LPCH, LPDWORD, LPVOID,
4197+
LPCWSTR, LPWSTR,
4198+
LPWCH, LPDWORD, LPVOID,
41984199
LPCVOID, LPOVERLAPPED,
41994200
LPSECURITY_ATTRIBUTES,
42004201
LPSTARTUPINFO,
@@ -4211,8 +4212,8 @@ pub mod funcs {
42114212
-> DWORD;
42124213
pub fn SetEnvironmentVariableW(n: LPCWSTR, v: LPCWSTR)
42134214
-> BOOL;
4214-
pub fn GetEnvironmentStringsA() -> LPCH;
4215-
pub fn FreeEnvironmentStringsA(env_ptr: LPCH) -> BOOL;
4215+
pub fn GetEnvironmentStringsW() -> LPWCH;
4216+
pub fn FreeEnvironmentStringsW(env_ptr: LPWCH) -> BOOL;
42164217
pub fn GetModuleFileNameW(hModule: HMODULE,
42174218
lpFilename: LPWSTR,
42184219
nSize: DWORD)
@@ -4251,16 +4252,16 @@ pub mod funcs {
42514252
dwProcessId: DWORD)
42524253
-> HANDLE;
42534254
pub fn GetCurrentProcess() -> HANDLE;
4254-
pub fn CreateProcessA(lpApplicationName: LPCSTR,
4255-
lpCommandLine: LPSTR,
4255+
pub fn CreateProcessW(lpApplicationName: LPCWSTR,
4256+
lpCommandLine: LPWSTR,
42564257
lpProcessAttributes:
42574258
LPSECURITY_ATTRIBUTES,
42584259
lpThreadAttributes:
42594260
LPSECURITY_ATTRIBUTES,
42604261
bInheritHandles: BOOL,
42614262
dwCreationFlags: DWORD,
42624263
lpEnvironment: LPVOID,
4263-
lpCurrentDirectory: LPCSTR,
4264+
lpCurrentDirectory: LPCWSTR,
42644265
lpStartupInfo: LPSTARTUPINFO,
42654266
lpProcessInformation:
42664267
LPPROCESS_INFORMATION)

src/libnative/io/process.rs

+22-14
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ fn spawn_process_os(config: p::ProcessConfig,
258258
GetCurrentProcess,
259259
DuplicateHandle,
260260
CloseHandle,
261-
CreateProcessA
261+
CreateProcessW
262262
};
263263
use libc::funcs::extra::msvcrt::get_osfhandle;
264264

@@ -318,15 +318,15 @@ fn spawn_process_os(config: p::ProcessConfig,
318318
let mut create_err = None;
319319

320320
// stolen from the libuv code.
321-
let mut flags = 0;
321+
let mut flags = libc::CREATE_UNICODE_ENVIRONMENT;
322322
if config.detach {
323323
flags |= libc::DETACHED_PROCESS | libc::CREATE_NEW_PROCESS_GROUP;
324324
}
325325

326326
with_envp(env, |envp| {
327327
with_dirp(dir, |dirp| {
328-
cmd.with_c_str(|cmdp| {
329-
let created = CreateProcessA(ptr::null(), mem::transmute(cmdp),
328+
os::win32::as_mut_utf16_p(cmd, |cmdp| {
329+
let created = CreateProcessW(ptr::null(), cmdp,
330330
ptr::mut_null(), ptr::mut_null(), TRUE,
331331
flags, envp, dirp, &mut si,
332332
&mut pi);
@@ -409,16 +409,17 @@ fn make_command_line(prog: &str, args: &[~str]) -> ~str {
409409
if quote {
410410
cmd.push_char('"');
411411
}
412-
for i in range(0u, arg.len()) {
413-
append_char_at(cmd, arg, i);
412+
let argvec: Vec<char> = arg.chars().collect();
413+
for i in range(0u, argvec.len()) {
414+
append_char_at(cmd, &argvec, i);
414415
}
415416
if quote {
416417
cmd.push_char('"');
417418
}
418419
}
419420

420-
fn append_char_at(cmd: &mut StrBuf, arg: &str, i: uint) {
421-
match arg[i] as char {
421+
fn append_char_at(cmd: &mut StrBuf, arg: &Vec<char>, i: uint) {
422+
match *arg.get(i) {
422423
'"' => {
423424
// Escape quotes.
424425
cmd.push_str("\\\"");
@@ -438,11 +439,11 @@ fn make_command_line(prog: &str, args: &[~str]) -> ~str {
438439
}
439440
}
440441

441-
fn backslash_run_ends_in_quote(s: &str, mut i: uint) -> bool {
442-
while i < s.len() && s[i] as char == '\\' {
442+
fn backslash_run_ends_in_quote(s: &Vec<char>, mut i: uint) -> bool {
443+
while i < s.len() && *s.get(i) == '\\' {
443444
i += 1;
444445
}
445-
return i < s.len() && s[i] as char == '"';
446+
return i < s.len() && *s.get(i) == '"';
446447
}
447448
}
448449

@@ -691,7 +692,7 @@ fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: |*mut c_void| -> T) -> T {
691692

692693
for pair in env.iter() {
693694
let kv = format!("{}={}", *pair.ref0(), *pair.ref1());
694-
blk.push_all(kv.as_bytes());
695+
blk.push_all(kv.to_utf16().as_slice());
695696
blk.push(0);
696697
}
697698

@@ -704,9 +705,12 @@ fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: |*mut c_void| -> T) -> T {
704705
}
705706

706707
#[cfg(windows)]
707-
fn with_dirp<T>(d: Option<&Path>, cb: |*libc::c_char| -> T) -> T {
708+
fn with_dirp<T>(d: Option<&Path>, cb: |*u16| -> T) -> T {
708709
match d {
709-
Some(dir) => dir.with_c_str(|buf| cb(buf)),
710+
Some(dir) => match dir.as_str() {
711+
Some(dir_str) => os::win32::as_utf16_p(dir_str, cb),
712+
None => cb(ptr::null())
713+
},
710714
None => cb(ptr::null())
711715
}
712716
}
@@ -860,5 +864,9 @@ mod tests {
860864
make_command_line("echo", ["a b c".to_owned()]),
861865
"echo \"a b c\"".to_owned()
862866
);
867+
assert_eq!(
868+
make_command_line("\u03c0\u042f\u97f3\u00e6\u221e", []),
869+
"\u03c0\u042f\u97f3\u00e6\u221e".to_owned()
870+
);
863871
}
864872
}

src/libstd/os.rs

+36-10
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
use clone::Clone;
3232
use container::Container;
3333
use libc;
34-
use libc::{c_char, c_void, c_int};
34+
use libc::{c_void, c_int};
3535
use option::{Some, None, Option};
3636
use os;
3737
use ops::Drop;
@@ -49,6 +49,8 @@ use vec::Vec;
4949

5050
#[cfg(unix)]
5151
use c_str::ToCStr;
52+
#[cfg(unix)]
53+
use libc::c_char;
5254
#[cfg(windows)]
5355
use str::OwnedStr;
5456

@@ -141,10 +143,14 @@ pub mod win32 {
141143
}
142144

143145
pub fn as_utf16_p<T>(s: &str, f: |*u16| -> T) -> T {
146+
as_mut_utf16_p(s, |t| { f(t as *u16) })
147+
}
148+
149+
pub fn as_mut_utf16_p<T>(s: &str, f: |*mut u16| -> T) -> T {
144150
let mut t = s.to_utf16();
145151
// Null terminate before passing on.
146152
t.push(0u16);
147-
f(t.as_ptr())
153+
f(t.as_mut_ptr())
148154
}
149155
}
150156

@@ -182,22 +188,42 @@ pub fn env_as_bytes() -> Vec<(~[u8],~[u8])> {
182188
unsafe {
183189
#[cfg(windows)]
184190
unsafe fn get_env_pairs() -> Vec<~[u8]> {
185-
use c_str;
191+
use slice::raw;
186192

187193
use libc::funcs::extra::kernel32::{
188-
GetEnvironmentStringsA,
189-
FreeEnvironmentStringsA
194+
GetEnvironmentStringsW,
195+
FreeEnvironmentStringsW
190196
};
191-
let ch = GetEnvironmentStringsA();
197+
let ch = GetEnvironmentStringsW();
192198
if ch as uint == 0 {
193199
fail!("os::env() failure getting env string from OS: {}",
194200
os::last_os_error());
195201
}
202+
// Here, we lossily decode the string as UTF16.
203+
//
204+
// The docs suggest that the result should be in Unicode, but
205+
// Windows doesn't guarantee it's actually UTF16 -- it doesn't
206+
// validate the environment string passed to CreateProcess nor
207+
// SetEnvironmentVariable. Yet, it's unlikely that returning a
208+
// raw u16 buffer would be of practical use since the result would
209+
// be inherently platform-dependent and introduce additional
210+
// complexity to this code.
211+
//
212+
// Using the non-Unicode version of GetEnvironmentStrings is even
213+
// worse since the result is in an OEM code page. Characters that
214+
// can't be encoded in the code page would be turned into question
215+
// marks.
196216
let mut result = Vec::new();
197-
c_str::from_c_multistring(ch as *c_char, None, |cstr| {
198-
result.push(cstr.as_bytes_no_nul().to_owned());
199-
});
200-
FreeEnvironmentStringsA(ch);
217+
let mut i = 0;
218+
while *ch.offset(i) != 0 {
219+
let p = &*ch.offset(i);
220+
let len = ptr::position(p, |c| *c == 0);
221+
raw::buf_as_slice(p, len, |s| {
222+
result.push(str::from_utf16_lossy(s).into_bytes());
223+
});
224+
i += len as int + 1;
225+
}
226+
FreeEnvironmentStringsW(ch);
201227
result
202228
}
203229
#[cfg(unix)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// no-prefer-dynamic
12+
13+
// The test copies itself into a subdirectory with a non-ASCII name and then
14+
// runs it as a child process within the subdirectory. The parent process
15+
// also adds an environment variable and an argument, both containing
16+
// non-ASCII characters. The child process ensures all the strings are
17+
// intact.
18+
19+
extern crate native;
20+
21+
use std::io;
22+
use std::io::fs;
23+
use std::io::process::Process;
24+
use std::io::process::ProcessConfig;
25+
use std::os;
26+
use std::path::Path;
27+
28+
fn main() {
29+
let my_args = os::args();
30+
let my_cwd = os::getcwd();
31+
let my_env = os::env();
32+
let my_path = Path::new(os::self_exe_name().unwrap());
33+
let my_dir = my_path.dir_path();
34+
let my_ext = my_path.extension_str().unwrap_or("");
35+
36+
// some non-ASCII characters
37+
let blah = "\u03c0\u042f\u97f3\u00e6\u221e";
38+
39+
let child_name = "child";
40+
let child_dir = "process-spawn-with-unicode-params-" + blah;
41+
42+
// parameters sent to child / expected to be received from parent
43+
let arg = blah;
44+
let cwd = my_dir.join(Path::new(child_dir.clone()));
45+
let env = ("RUST_TEST_PROC_SPAWN_UNICODE".to_owned(), blah.to_owned());
46+
47+
// am I the parent or the child?
48+
if my_args.len() == 1 { // parent
49+
50+
let child_filestem = Path::new(child_name);
51+
let child_filename = child_filestem.with_extension(my_ext);
52+
let child_path = cwd.join(child_filename.clone());
53+
54+
// make a separate directory for the child
55+
drop(fs::mkdir(&cwd, io::UserRWX).is_ok());
56+
assert!(fs::copy(&my_path, &child_path).is_ok());
57+
58+
// run child
59+
let p = Process::configure(ProcessConfig {
60+
program: child_path.as_str().unwrap(),
61+
args: [arg.to_owned()],
62+
cwd: Some(&cwd),
63+
env: Some(my_env.append_one(env).as_slice()),
64+
.. ProcessConfig::new()
65+
}).unwrap().wait_with_output();
66+
67+
// display the output
68+
assert!(io::stdout().write(p.output.as_slice()).is_ok());
69+
assert!(io::stderr().write(p.error.as_slice()).is_ok());
70+
71+
// make sure the child succeeded
72+
assert!(p.status.success());
73+
74+
} else { // child
75+
76+
// check working directory (don't try to compare with `cwd` here!)
77+
assert!(my_cwd.ends_with_path(&Path::new(child_dir)));
78+
79+
// check arguments
80+
assert_eq!(my_args.get(1).as_slice(), arg);
81+
82+
// check environment variable
83+
assert!(my_env.contains(&env));
84+
85+
};
86+
}

0 commit comments

Comments
 (0)