]>
Commit | Line | Data |
---|---|---|
85aaf69f SL |
1 | // Copyright 2012-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 | ||
abe05a73 | 11 | use ascii::AsciiExt; |
85aaf69f SL |
12 | use collections::HashMap; |
13 | use collections; | |
bd371182 | 14 | use env::split_paths; |
85aaf69f SL |
15 | use env; |
16 | use ffi::{OsString, OsStr}; | |
17 | use fmt; | |
c34b1796 | 18 | use fs; |
7453a54e | 19 | use io::{self, Error, ErrorKind}; |
92a42be0 | 20 | use libc::c_void; |
bd371182 | 21 | use mem; |
c34b1796 | 22 | use os::windows::ffi::OsStrExt; |
bd371182 | 23 | use path::Path; |
85aaf69f | 24 | use ptr; |
a7813a04 | 25 | use sys::mutex::Mutex; |
bd371182 | 26 | use sys::c; |
d9579d0f | 27 | use sys::fs::{OpenOptions, File}; |
7453a54e SL |
28 | use sys::handle::Handle; |
29 | use sys::pipe::{self, AnonPipe}; | |
bd371182 | 30 | use sys::stdio; |
85aaf69f | 31 | use sys::{self, cvt}; |
85aaf69f SL |
32 | use sys_common::{AsInner, FromInner}; |
33 | ||
34 | //////////////////////////////////////////////////////////////////////////////// | |
35 | // Command | |
36 | //////////////////////////////////////////////////////////////////////////////// | |
37 | ||
38 | fn mk_key(s: &OsStr) -> OsString { | |
39 | FromInner::from_inner(sys::os_str::Buf { | |
40 | inner: s.as_inner().inner.to_ascii_uppercase() | |
41 | }) | |
42 | } | |
43 | ||
7453a54e SL |
44 | fn ensure_no_nuls<T: AsRef<OsStr>>(str: T) -> io::Result<T> { |
45 | if str.as_ref().encode_wide().any(|b| b == 0) { | |
46 | Err(io::Error::new(ErrorKind::InvalidInput, "nul byte found in provided data")) | |
47 | } else { | |
48 | Ok(str) | |
49 | } | |
50 | } | |
51 | ||
85aaf69f | 52 | pub struct Command { |
7453a54e SL |
53 | program: OsString, |
54 | args: Vec<OsString>, | |
55 | env: Option<HashMap<OsString, OsString>>, | |
56 | cwd: Option<OsString>, | |
476ff2be | 57 | flags: u32, |
7453a54e SL |
58 | detach: bool, // not currently exposed in std::process |
59 | stdin: Option<Stdio>, | |
60 | stdout: Option<Stdio>, | |
61 | stderr: Option<Stdio>, | |
62 | } | |
63 | ||
64 | pub enum Stdio { | |
65 | Inherit, | |
66 | Null, | |
67 | MakePipe, | |
68 | Handle(Handle), | |
69 | } | |
70 | ||
71 | pub struct StdioPipes { | |
72 | pub stdin: Option<AnonPipe>, | |
73 | pub stdout: Option<AnonPipe>, | |
74 | pub stderr: Option<AnonPipe>, | |
85aaf69f SL |
75 | } |
76 | ||
a7813a04 XL |
77 | struct DropGuard<'a> { |
78 | lock: &'a Mutex, | |
79 | } | |
80 | ||
85aaf69f SL |
81 | impl Command { |
82 | pub fn new(program: &OsStr) -> Command { | |
83 | Command { | |
84 | program: program.to_os_string(), | |
85 | args: Vec::new(), | |
86 | env: None, | |
87 | cwd: None, | |
476ff2be | 88 | flags: 0, |
85aaf69f | 89 | detach: false, |
7453a54e SL |
90 | stdin: None, |
91 | stdout: None, | |
92 | stderr: None, | |
85aaf69f SL |
93 | } |
94 | } | |
95 | ||
96 | pub fn arg(&mut self, arg: &OsStr) { | |
97 | self.args.push(arg.to_os_string()) | |
98 | } | |
85aaf69f SL |
99 | fn init_env_map(&mut self){ |
100 | if self.env.is_none() { | |
101 | self.env = Some(env::vars_os().map(|(key, val)| { | |
102 | (mk_key(&key), val) | |
103 | }).collect()); | |
104 | } | |
105 | } | |
106 | pub fn env(&mut self, key: &OsStr, val: &OsStr) { | |
107 | self.init_env_map(); | |
108 | self.env.as_mut().unwrap().insert(mk_key(key), val.to_os_string()); | |
109 | } | |
110 | pub fn env_remove(&mut self, key: &OsStr) { | |
111 | self.init_env_map(); | |
112 | self.env.as_mut().unwrap().remove(&mk_key(key)); | |
113 | } | |
114 | pub fn env_clear(&mut self) { | |
115 | self.env = Some(HashMap::new()) | |
116 | } | |
117 | pub fn cwd(&mut self, dir: &OsStr) { | |
118 | self.cwd = Some(dir.to_os_string()) | |
119 | } | |
7453a54e SL |
120 | pub fn stdin(&mut self, stdin: Stdio) { |
121 | self.stdin = Some(stdin); | |
122 | } | |
123 | pub fn stdout(&mut self, stdout: Stdio) { | |
124 | self.stdout = Some(stdout); | |
125 | } | |
126 | pub fn stderr(&mut self, stderr: Stdio) { | |
127 | self.stderr = Some(stderr); | |
128 | } | |
476ff2be SL |
129 | pub fn creation_flags(&mut self, flags: u32) { |
130 | self.flags = flags; | |
131 | } | |
62682a34 | 132 | |
54a0048b | 133 | pub fn spawn(&mut self, default: Stdio, needs_stdin: bool) |
7453a54e | 134 | -> io::Result<(Process, StdioPipes)> { |
bd371182 AL |
135 | // To have the spawning semantics of unix/windows stay the same, we need |
136 | // to read the *child's* PATH if one is provided. See #15149 for more | |
137 | // details. | |
7453a54e | 138 | let program = self.env.as_ref().and_then(|env| { |
85aaf69f | 139 | for (key, v) in env { |
9346a6ac | 140 | if OsStr::new("PATH") != &**key { continue } |
85aaf69f SL |
141 | |
142 | // Split the value and test each path to see if the | |
143 | // program exists. | |
144 | for path in split_paths(&v) { | |
7453a54e | 145 | let path = path.join(self.program.to_str().unwrap()) |
85aaf69f | 146 | .with_extension(env::consts::EXE_EXTENSION); |
c34b1796 AL |
147 | if fs::metadata(&path).is_ok() { |
148 | return Some(path.into_os_string()) | |
85aaf69f SL |
149 | } |
150 | } | |
151 | break | |
152 | } | |
153 | None | |
154 | }); | |
155 | ||
bd371182 | 156 | let mut si = zeroed_startupinfo(); |
92a42be0 SL |
157 | si.cb = mem::size_of::<c::STARTUPINFO>() as c::DWORD; |
158 | si.dwFlags = c::STARTF_USESTDHANDLES; | |
85aaf69f | 159 | |
7453a54e | 160 | let program = program.as_ref().unwrap_or(&self.program); |
54a0048b | 161 | let mut cmd_str = make_command_line(program, &self.args)?; |
bd371182 | 162 | cmd_str.push(0); // add null terminator |
85aaf69f | 163 | |
bd371182 | 164 | // stolen from the libuv code. |
476ff2be | 165 | let mut flags = self.flags | c::CREATE_UNICODE_ENVIRONMENT; |
7453a54e | 166 | if self.detach { |
92a42be0 | 167 | flags |= c::DETACHED_PROCESS | c::CREATE_NEW_PROCESS_GROUP; |
bd371182 | 168 | } |
85aaf69f | 169 | |
54a0048b SL |
170 | let (envp, _data) = make_envp(self.env.as_ref())?; |
171 | let (dirp, _data) = make_dirp(self.cwd.as_ref())?; | |
bd371182 | 172 | let mut pi = zeroed_process_information(); |
bd371182 | 173 | |
7453a54e SL |
174 | // Prepare all stdio handles to be inherited by the child. This |
175 | // currently involves duplicating any existing ones with the ability to | |
176 | // be inherited by child processes. Note, however, that once an | |
177 | // inheritable handle is created, *any* spawned child will inherit that | |
178 | // handle. We only want our own child to inherit this handle, so we wrap | |
179 | // the remaining portion of this spawn in a mutex. | |
180 | // | |
181 | // For more information, msdn also has an article about this race: | |
182 | // http://support.microsoft.com/kb/315939 | |
a7813a04 XL |
183 | static CREATE_PROCESS_LOCK: Mutex = Mutex::new(); |
184 | let _guard = DropGuard::new(&CREATE_PROCESS_LOCK); | |
7453a54e SL |
185 | |
186 | let mut pipes = StdioPipes { | |
187 | stdin: None, | |
188 | stdout: None, | |
189 | stderr: None, | |
190 | }; | |
54a0048b SL |
191 | let null = Stdio::Null; |
192 | let default_stdin = if needs_stdin {&default} else {&null}; | |
193 | let stdin = self.stdin.as_ref().unwrap_or(default_stdin); | |
7453a54e SL |
194 | let stdout = self.stdout.as_ref().unwrap_or(&default); |
195 | let stderr = self.stderr.as_ref().unwrap_or(&default); | |
54a0048b SL |
196 | let stdin = stdin.to_handle(c::STD_INPUT_HANDLE, &mut pipes.stdin)?; |
197 | let stdout = stdout.to_handle(c::STD_OUTPUT_HANDLE, | |
198 | &mut pipes.stdout)?; | |
199 | let stderr = stderr.to_handle(c::STD_ERROR_HANDLE, | |
200 | &mut pipes.stderr)?; | |
7453a54e SL |
201 | si.hStdInput = stdin.raw(); |
202 | si.hStdOutput = stdout.raw(); | |
203 | si.hStdError = stderr.raw(); | |
204 | ||
54a0048b | 205 | unsafe { |
92a42be0 SL |
206 | cvt(c::CreateProcessW(ptr::null(), |
207 | cmd_str.as_mut_ptr(), | |
208 | ptr::null_mut(), | |
209 | ptr::null_mut(), | |
210 | c::TRUE, flags, envp, dirp, | |
211 | &mut si, &mut pi)) | |
54a0048b | 212 | }?; |
85aaf69f | 213 | |
bd371182 AL |
214 | // We close the thread handle because we don't care about keeping |
215 | // the thread id valid, and we aren't keeping the thread handle | |
216 | // around to be able to close it later. | |
217 | drop(Handle::new(pi.hThread)); | |
85aaf69f | 218 | |
7453a54e | 219 | Ok((Process { handle: Handle::new(pi.hProcess) }, pipes)) |
85aaf69f SL |
220 | } |
221 | ||
7453a54e SL |
222 | } |
223 | ||
224 | impl fmt::Debug for Command { | |
225 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
54a0048b | 226 | write!(f, "{:?}", self.program)?; |
7453a54e | 227 | for arg in &self.args { |
54a0048b | 228 | write!(f, " {:?}", arg)?; |
7453a54e SL |
229 | } |
230 | Ok(()) | |
231 | } | |
232 | } | |
233 | ||
a7813a04 XL |
234 | impl<'a> DropGuard<'a> { |
235 | fn new(lock: &'a Mutex) -> DropGuard<'a> { | |
236 | unsafe { | |
237 | lock.lock(); | |
238 | DropGuard { lock: lock } | |
239 | } | |
240 | } | |
241 | } | |
242 | ||
243 | impl<'a> Drop for DropGuard<'a> { | |
244 | fn drop(&mut self) { | |
245 | unsafe { | |
246 | self.lock.unlock(); | |
247 | } | |
248 | } | |
249 | } | |
250 | ||
7453a54e SL |
251 | impl Stdio { |
252 | fn to_handle(&self, stdio_id: c::DWORD, pipe: &mut Option<AnonPipe>) | |
253 | -> io::Result<Handle> { | |
254 | match *self { | |
255 | // If no stdio handle is available, then inherit means that it | |
256 | // should still be unavailable so propagate the | |
257 | // INVALID_HANDLE_VALUE. | |
258 | Stdio::Inherit => { | |
259 | match stdio::get(stdio_id) { | |
cc61c64b XL |
260 | Ok(io) => { |
261 | let io = Handle::new(io.handle()); | |
262 | let ret = io.duplicate(0, true, | |
263 | c::DUPLICATE_SAME_ACCESS); | |
264 | io.into_raw(); | |
265 | return ret | |
266 | } | |
7453a54e SL |
267 | Err(..) => Ok(Handle::new(c::INVALID_HANDLE_VALUE)), |
268 | } | |
269 | } | |
270 | ||
271 | Stdio::MakePipe => { | |
476ff2be SL |
272 | let ours_readable = stdio_id != c::STD_INPUT_HANDLE; |
273 | let pipes = pipe::anon_pipe(ours_readable)?; | |
274 | *pipe = Some(pipes.ours); | |
54a0048b | 275 | cvt(unsafe { |
476ff2be | 276 | c::SetHandleInformation(pipes.theirs.handle().raw(), |
7453a54e SL |
277 | c::HANDLE_FLAG_INHERIT, |
278 | c::HANDLE_FLAG_INHERIT) | |
54a0048b | 279 | })?; |
476ff2be | 280 | Ok(pipes.theirs.into_handle()) |
7453a54e SL |
281 | } |
282 | ||
283 | Stdio::Handle(ref handle) => { | |
284 | handle.duplicate(0, true, c::DUPLICATE_SAME_ACCESS) | |
285 | } | |
286 | ||
287 | // Open up a reference to NUL with appropriate read/write | |
288 | // permissions as well as the ability to be inherited to child | |
289 | // processes (as this is about to be inherited). | |
290 | Stdio::Null => { | |
291 | let size = mem::size_of::<c::SECURITY_ATTRIBUTES>(); | |
292 | let mut sa = c::SECURITY_ATTRIBUTES { | |
293 | nLength: size as c::DWORD, | |
294 | lpSecurityDescriptor: ptr::null_mut(), | |
295 | bInheritHandle: 1, | |
296 | }; | |
297 | let mut opts = OpenOptions::new(); | |
298 | opts.read(stdio_id == c::STD_INPUT_HANDLE); | |
299 | opts.write(stdio_id != c::STD_INPUT_HANDLE); | |
300 | opts.security_attributes(&mut sa); | |
301 | File::open(Path::new("NUL"), &opts).map(|file| { | |
302 | file.into_handle() | |
303 | }) | |
304 | } | |
305 | } | |
306 | } | |
307 | } | |
308 | ||
041b39d2 XL |
309 | impl From<AnonPipe> for Stdio { |
310 | fn from(pipe: AnonPipe) -> Stdio { | |
311 | Stdio::Handle(pipe.into_handle()) | |
312 | } | |
313 | } | |
314 | ||
315 | impl From<File> for Stdio { | |
316 | fn from(file: File) -> Stdio { | |
317 | Stdio::Handle(file.into_handle()) | |
318 | } | |
319 | } | |
320 | ||
7453a54e SL |
321 | //////////////////////////////////////////////////////////////////////////////// |
322 | // Processes | |
323 | //////////////////////////////////////////////////////////////////////////////// | |
324 | ||
325 | /// A value representing a child process. | |
326 | /// | |
327 | /// The lifetime of this value is linked to the lifetime of the actual | |
328 | /// process - the Process destructor calls self.finish() which waits | |
329 | /// for the process to terminate. | |
330 | pub struct Process { | |
331 | handle: Handle, | |
332 | } | |
333 | ||
334 | impl Process { | |
335 | pub fn kill(&mut self) -> io::Result<()> { | |
54a0048b | 336 | cvt(unsafe { |
7453a54e | 337 | c::TerminateProcess(self.handle.raw(), 1) |
54a0048b | 338 | })?; |
85aaf69f SL |
339 | Ok(()) |
340 | } | |
341 | ||
62682a34 SL |
342 | pub fn id(&self) -> u32 { |
343 | unsafe { | |
344 | c::GetProcessId(self.handle.raw()) as u32 | |
345 | } | |
346 | } | |
347 | ||
7453a54e | 348 | pub fn wait(&mut self) -> io::Result<ExitStatus> { |
85aaf69f | 349 | unsafe { |
92a42be0 SL |
350 | let res = c::WaitForSingleObject(self.handle.raw(), c::INFINITE); |
351 | if res != c::WAIT_OBJECT_0 { | |
352 | return Err(Error::last_os_error()) | |
85aaf69f | 353 | } |
92a42be0 | 354 | let mut status = 0; |
54a0048b | 355 | cvt(c::GetExitCodeProcess(self.handle.raw(), &mut status))?; |
92a42be0 | 356 | Ok(ExitStatus(status)) |
85aaf69f SL |
357 | } |
358 | } | |
62682a34 | 359 | |
8bb4bdeb | 360 | pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> { |
32a655c1 SL |
361 | unsafe { |
362 | match c::WaitForSingleObject(self.handle.raw(), 0) { | |
363 | c::WAIT_OBJECT_0 => {} | |
364 | c::WAIT_TIMEOUT => { | |
8bb4bdeb | 365 | return Ok(None); |
32a655c1 SL |
366 | } |
367 | _ => return Err(io::Error::last_os_error()), | |
368 | } | |
369 | let mut status = 0; | |
370 | cvt(c::GetExitCodeProcess(self.handle.raw(), &mut status))?; | |
8bb4bdeb | 371 | Ok(Some(ExitStatus(status))) |
32a655c1 SL |
372 | } |
373 | } | |
374 | ||
62682a34 | 375 | pub fn handle(&self) -> &Handle { &self.handle } |
c1a9b12d SL |
376 | |
377 | pub fn into_handle(self) -> Handle { self.handle } | |
85aaf69f SL |
378 | } |
379 | ||
380 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] | |
92a42be0 | 381 | pub struct ExitStatus(c::DWORD); |
85aaf69f SL |
382 | |
383 | impl ExitStatus { | |
384 | pub fn success(&self) -> bool { | |
385 | self.0 == 0 | |
386 | } | |
387 | pub fn code(&self) -> Option<i32> { | |
92a42be0 | 388 | Some(self.0 as i32) |
85aaf69f SL |
389 | } |
390 | } | |
391 | ||
a7813a04 XL |
392 | impl From<c::DWORD> for ExitStatus { |
393 | fn from(u: c::DWORD) -> ExitStatus { | |
394 | ExitStatus(u) | |
395 | } | |
396 | } | |
397 | ||
85aaf69f SL |
398 | impl fmt::Display for ExitStatus { |
399 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
400 | write!(f, "exit code: {}", self.0) | |
401 | } | |
402 | } | |
403 | ||
92a42be0 SL |
404 | fn zeroed_startupinfo() -> c::STARTUPINFO { |
405 | c::STARTUPINFO { | |
85aaf69f SL |
406 | cb: 0, |
407 | lpReserved: ptr::null_mut(), | |
408 | lpDesktop: ptr::null_mut(), | |
409 | lpTitle: ptr::null_mut(), | |
410 | dwX: 0, | |
411 | dwY: 0, | |
412 | dwXSize: 0, | |
413 | dwYSize: 0, | |
414 | dwXCountChars: 0, | |
415 | dwYCountCharts: 0, | |
416 | dwFillAttribute: 0, | |
417 | dwFlags: 0, | |
418 | wShowWindow: 0, | |
419 | cbReserved2: 0, | |
420 | lpReserved2: ptr::null_mut(), | |
92a42be0 SL |
421 | hStdInput: c::INVALID_HANDLE_VALUE, |
422 | hStdOutput: c::INVALID_HANDLE_VALUE, | |
423 | hStdError: c::INVALID_HANDLE_VALUE, | |
85aaf69f SL |
424 | } |
425 | } | |
426 | ||
92a42be0 SL |
427 | fn zeroed_process_information() -> c::PROCESS_INFORMATION { |
428 | c::PROCESS_INFORMATION { | |
85aaf69f SL |
429 | hProcess: ptr::null_mut(), |
430 | hThread: ptr::null_mut(), | |
431 | dwProcessId: 0, | |
432 | dwThreadId: 0 | |
433 | } | |
434 | } | |
435 | ||
7453a54e SL |
436 | // Produces a wide string *without terminating null*; returns an error if |
437 | // `prog` or any of the `args` contain a nul. | |
438 | fn make_command_line(prog: &OsStr, args: &[OsString]) -> io::Result<Vec<u16>> { | |
d9579d0f AL |
439 | // Encode the command and arguments in a command line string such |
440 | // that the spawned process may recover them using CommandLineToArgvW. | |
85aaf69f | 441 | let mut cmd: Vec<u16> = Vec::new(); |
041b39d2 XL |
442 | // Always quote the program name so CreateProcess doesn't interpret args as |
443 | // part of the name if the binary wasn't found first time. | |
444 | append_arg(&mut cmd, prog, true)?; | |
85aaf69f SL |
445 | for arg in args { |
446 | cmd.push(' ' as u16); | |
041b39d2 | 447 | append_arg(&mut cmd, arg, false)?; |
85aaf69f | 448 | } |
7453a54e | 449 | return Ok(cmd); |
85aaf69f | 450 | |
041b39d2 | 451 | fn append_arg(cmd: &mut Vec<u16>, arg: &OsStr, force_quotes: bool) -> io::Result<()> { |
85aaf69f SL |
452 | // If an argument has 0 characters then we need to quote it to ensure |
453 | // that it actually gets passed through on the command line or otherwise | |
454 | // it will be dropped entirely when parsed on the other end. | |
54a0048b | 455 | ensure_no_nuls(arg)?; |
85aaf69f | 456 | let arg_bytes = &arg.as_inner().inner.as_inner(); |
041b39d2 | 457 | let quote = force_quotes || arg_bytes.iter().any(|c| *c == b' ' || *c == b'\t') |
9346a6ac | 458 | || arg_bytes.is_empty(); |
85aaf69f SL |
459 | if quote { |
460 | cmd.push('"' as u16); | |
461 | } | |
462 | ||
463 | let mut iter = arg.encode_wide(); | |
d9579d0f | 464 | let mut backslashes: usize = 0; |
85aaf69f | 465 | while let Some(x) = iter.next() { |
d9579d0f AL |
466 | if x == '\\' as u16 { |
467 | backslashes += 1; | |
85aaf69f | 468 | } else { |
d9579d0f AL |
469 | if x == '"' as u16 { |
470 | // Add n+1 backslashes to total 2n+1 before internal '"'. | |
471 | for _ in 0..(backslashes+1) { | |
472 | cmd.push('\\' as u16); | |
473 | } | |
474 | } | |
475 | backslashes = 0; | |
85aaf69f | 476 | } |
d9579d0f | 477 | cmd.push(x); |
85aaf69f SL |
478 | } |
479 | ||
480 | if quote { | |
d9579d0f AL |
481 | // Add n backslashes to total 2n before ending '"'. |
482 | for _ in 0..backslashes { | |
483 | cmd.push('\\' as u16); | |
484 | } | |
85aaf69f SL |
485 | cmd.push('"' as u16); |
486 | } | |
7453a54e | 487 | Ok(()) |
85aaf69f SL |
488 | } |
489 | } | |
490 | ||
bd371182 | 491 | fn make_envp(env: Option<&collections::HashMap<OsString, OsString>>) |
7453a54e | 492 | -> io::Result<(*mut c_void, Vec<u16>)> { |
85aaf69f SL |
493 | // On Windows we pass an "environment block" which is not a char**, but |
494 | // rather a concatenation of null-terminated k=v\0 sequences, with a final | |
495 | // \0 to terminate. | |
496 | match env { | |
497 | Some(env) => { | |
498 | let mut blk = Vec::new(); | |
499 | ||
500 | for pair in env { | |
54a0048b | 501 | blk.extend(ensure_no_nuls(pair.0)?.encode_wide()); |
85aaf69f | 502 | blk.push('=' as u16); |
54a0048b | 503 | blk.extend(ensure_no_nuls(pair.1)?.encode_wide()); |
85aaf69f SL |
504 | blk.push(0); |
505 | } | |
506 | blk.push(0); | |
7453a54e | 507 | Ok((blk.as_mut_ptr() as *mut c_void, blk)) |
85aaf69f | 508 | } |
7453a54e | 509 | _ => Ok((ptr::null_mut(), Vec::new())) |
85aaf69f SL |
510 | } |
511 | } | |
512 | ||
7453a54e SL |
513 | fn make_dirp(d: Option<&OsString>) -> io::Result<(*const u16, Vec<u16>)> { |
514 | ||
85aaf69f | 515 | match d { |
bd371182 | 516 | Some(dir) => { |
54a0048b | 517 | let mut dir_str: Vec<u16> = ensure_no_nuls(dir)?.encode_wide().collect(); |
bd371182 | 518 | dir_str.push(0); |
7453a54e | 519 | Ok((dir_str.as_ptr(), dir_str)) |
bd371182 | 520 | }, |
7453a54e | 521 | None => Ok((ptr::null(), Vec::new())) |
85aaf69f SL |
522 | } |
523 | } | |
524 | ||
525 | #[cfg(test)] | |
526 | mod tests { | |
85aaf69f SL |
527 | use ffi::{OsStr, OsString}; |
528 | use super::make_command_line; | |
529 | ||
530 | #[test] | |
531 | fn test_make_command_line() { | |
532 | fn test_wrapper(prog: &str, args: &[&str]) -> String { | |
7453a54e SL |
533 | let command_line = &make_command_line(OsStr::new(prog), |
534 | &args.iter() | |
535 | .map(|a| OsString::from(a)) | |
536 | .collect::<Vec<OsString>>()) | |
537 | .unwrap(); | |
538 | String::from_utf16(command_line).unwrap() | |
85aaf69f SL |
539 | } |
540 | ||
541 | assert_eq!( | |
542 | test_wrapper("prog", &["aaa", "bbb", "ccc"]), | |
041b39d2 | 543 | "\"prog\" aaa bbb ccc" |
85aaf69f SL |
544 | ); |
545 | ||
546 | assert_eq!( | |
547 | test_wrapper("C:\\Program Files\\blah\\blah.exe", &["aaa"]), | |
548 | "\"C:\\Program Files\\blah\\blah.exe\" aaa" | |
549 | ); | |
550 | assert_eq!( | |
551 | test_wrapper("C:\\Program Files\\test", &["aa\"bb"]), | |
552 | "\"C:\\Program Files\\test\" aa\\\"bb" | |
553 | ); | |
554 | assert_eq!( | |
555 | test_wrapper("echo", &["a b c"]), | |
041b39d2 | 556 | "\"echo\" \"a b c\"" |
85aaf69f | 557 | ); |
d9579d0f AL |
558 | assert_eq!( |
559 | test_wrapper("echo", &["\" \\\" \\", "\\"]), | |
041b39d2 | 560 | "\"echo\" \"\\\" \\\\\\\" \\\\\" \\" |
d9579d0f | 561 | ); |
85aaf69f SL |
562 | assert_eq!( |
563 | test_wrapper("\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}", &[]), | |
041b39d2 | 564 | "\"\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}\"" |
85aaf69f SL |
565 | ); |
566 | } | |
567 | } |