]>
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 | ||
85aaf69f SL |
11 | use ascii::*; |
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) { | |
260 | Ok(io) => io.handle().duplicate(0, true, | |
261 | c::DUPLICATE_SAME_ACCESS), | |
262 | Err(..) => Ok(Handle::new(c::INVALID_HANDLE_VALUE)), | |
263 | } | |
264 | } | |
265 | ||
266 | Stdio::MakePipe => { | |
476ff2be SL |
267 | let ours_readable = stdio_id != c::STD_INPUT_HANDLE; |
268 | let pipes = pipe::anon_pipe(ours_readable)?; | |
269 | *pipe = Some(pipes.ours); | |
54a0048b | 270 | cvt(unsafe { |
476ff2be | 271 | c::SetHandleInformation(pipes.theirs.handle().raw(), |
7453a54e SL |
272 | c::HANDLE_FLAG_INHERIT, |
273 | c::HANDLE_FLAG_INHERIT) | |
54a0048b | 274 | })?; |
476ff2be | 275 | Ok(pipes.theirs.into_handle()) |
7453a54e SL |
276 | } |
277 | ||
278 | Stdio::Handle(ref handle) => { | |
279 | handle.duplicate(0, true, c::DUPLICATE_SAME_ACCESS) | |
280 | } | |
281 | ||
282 | // Open up a reference to NUL with appropriate read/write | |
283 | // permissions as well as the ability to be inherited to child | |
284 | // processes (as this is about to be inherited). | |
285 | Stdio::Null => { | |
286 | let size = mem::size_of::<c::SECURITY_ATTRIBUTES>(); | |
287 | let mut sa = c::SECURITY_ATTRIBUTES { | |
288 | nLength: size as c::DWORD, | |
289 | lpSecurityDescriptor: ptr::null_mut(), | |
290 | bInheritHandle: 1, | |
291 | }; | |
292 | let mut opts = OpenOptions::new(); | |
293 | opts.read(stdio_id == c::STD_INPUT_HANDLE); | |
294 | opts.write(stdio_id != c::STD_INPUT_HANDLE); | |
295 | opts.security_attributes(&mut sa); | |
296 | File::open(Path::new("NUL"), &opts).map(|file| { | |
297 | file.into_handle() | |
298 | }) | |
299 | } | |
300 | } | |
301 | } | |
302 | } | |
303 | ||
304 | //////////////////////////////////////////////////////////////////////////////// | |
305 | // Processes | |
306 | //////////////////////////////////////////////////////////////////////////////// | |
307 | ||
308 | /// A value representing a child process. | |
309 | /// | |
310 | /// The lifetime of this value is linked to the lifetime of the actual | |
311 | /// process - the Process destructor calls self.finish() which waits | |
312 | /// for the process to terminate. | |
313 | pub struct Process { | |
314 | handle: Handle, | |
315 | } | |
316 | ||
317 | impl Process { | |
318 | pub fn kill(&mut self) -> io::Result<()> { | |
54a0048b | 319 | cvt(unsafe { |
7453a54e | 320 | c::TerminateProcess(self.handle.raw(), 1) |
54a0048b | 321 | })?; |
85aaf69f SL |
322 | Ok(()) |
323 | } | |
324 | ||
62682a34 SL |
325 | pub fn id(&self) -> u32 { |
326 | unsafe { | |
327 | c::GetProcessId(self.handle.raw()) as u32 | |
328 | } | |
329 | } | |
330 | ||
7453a54e | 331 | pub fn wait(&mut self) -> io::Result<ExitStatus> { |
85aaf69f | 332 | unsafe { |
92a42be0 SL |
333 | let res = c::WaitForSingleObject(self.handle.raw(), c::INFINITE); |
334 | if res != c::WAIT_OBJECT_0 { | |
335 | return Err(Error::last_os_error()) | |
85aaf69f | 336 | } |
92a42be0 | 337 | let mut status = 0; |
54a0048b | 338 | cvt(c::GetExitCodeProcess(self.handle.raw(), &mut status))?; |
92a42be0 | 339 | Ok(ExitStatus(status)) |
85aaf69f SL |
340 | } |
341 | } | |
62682a34 | 342 | |
8bb4bdeb | 343 | pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> { |
32a655c1 SL |
344 | unsafe { |
345 | match c::WaitForSingleObject(self.handle.raw(), 0) { | |
346 | c::WAIT_OBJECT_0 => {} | |
347 | c::WAIT_TIMEOUT => { | |
8bb4bdeb | 348 | return Ok(None); |
32a655c1 SL |
349 | } |
350 | _ => return Err(io::Error::last_os_error()), | |
351 | } | |
352 | let mut status = 0; | |
353 | cvt(c::GetExitCodeProcess(self.handle.raw(), &mut status))?; | |
8bb4bdeb | 354 | Ok(Some(ExitStatus(status))) |
32a655c1 SL |
355 | } |
356 | } | |
357 | ||
62682a34 | 358 | pub fn handle(&self) -> &Handle { &self.handle } |
c1a9b12d SL |
359 | |
360 | pub fn into_handle(self) -> Handle { self.handle } | |
85aaf69f SL |
361 | } |
362 | ||
363 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] | |
92a42be0 | 364 | pub struct ExitStatus(c::DWORD); |
85aaf69f SL |
365 | |
366 | impl ExitStatus { | |
367 | pub fn success(&self) -> bool { | |
368 | self.0 == 0 | |
369 | } | |
370 | pub fn code(&self) -> Option<i32> { | |
92a42be0 | 371 | Some(self.0 as i32) |
85aaf69f SL |
372 | } |
373 | } | |
374 | ||
a7813a04 XL |
375 | impl From<c::DWORD> for ExitStatus { |
376 | fn from(u: c::DWORD) -> ExitStatus { | |
377 | ExitStatus(u) | |
378 | } | |
379 | } | |
380 | ||
85aaf69f SL |
381 | impl fmt::Display for ExitStatus { |
382 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
383 | write!(f, "exit code: {}", self.0) | |
384 | } | |
385 | } | |
386 | ||
92a42be0 SL |
387 | fn zeroed_startupinfo() -> c::STARTUPINFO { |
388 | c::STARTUPINFO { | |
85aaf69f SL |
389 | cb: 0, |
390 | lpReserved: ptr::null_mut(), | |
391 | lpDesktop: ptr::null_mut(), | |
392 | lpTitle: ptr::null_mut(), | |
393 | dwX: 0, | |
394 | dwY: 0, | |
395 | dwXSize: 0, | |
396 | dwYSize: 0, | |
397 | dwXCountChars: 0, | |
398 | dwYCountCharts: 0, | |
399 | dwFillAttribute: 0, | |
400 | dwFlags: 0, | |
401 | wShowWindow: 0, | |
402 | cbReserved2: 0, | |
403 | lpReserved2: ptr::null_mut(), | |
92a42be0 SL |
404 | hStdInput: c::INVALID_HANDLE_VALUE, |
405 | hStdOutput: c::INVALID_HANDLE_VALUE, | |
406 | hStdError: c::INVALID_HANDLE_VALUE, | |
85aaf69f SL |
407 | } |
408 | } | |
409 | ||
92a42be0 SL |
410 | fn zeroed_process_information() -> c::PROCESS_INFORMATION { |
411 | c::PROCESS_INFORMATION { | |
85aaf69f SL |
412 | hProcess: ptr::null_mut(), |
413 | hThread: ptr::null_mut(), | |
414 | dwProcessId: 0, | |
415 | dwThreadId: 0 | |
416 | } | |
417 | } | |
418 | ||
7453a54e SL |
419 | // Produces a wide string *without terminating null*; returns an error if |
420 | // `prog` or any of the `args` contain a nul. | |
421 | fn make_command_line(prog: &OsStr, args: &[OsString]) -> io::Result<Vec<u16>> { | |
d9579d0f AL |
422 | // Encode the command and arguments in a command line string such |
423 | // that the spawned process may recover them using CommandLineToArgvW. | |
85aaf69f | 424 | let mut cmd: Vec<u16> = Vec::new(); |
54a0048b | 425 | append_arg(&mut cmd, prog)?; |
85aaf69f SL |
426 | for arg in args { |
427 | cmd.push(' ' as u16); | |
54a0048b | 428 | append_arg(&mut cmd, arg)?; |
85aaf69f | 429 | } |
7453a54e | 430 | return Ok(cmd); |
85aaf69f | 431 | |
7453a54e | 432 | fn append_arg(cmd: &mut Vec<u16>, arg: &OsStr) -> io::Result<()> { |
85aaf69f SL |
433 | // If an argument has 0 characters then we need to quote it to ensure |
434 | // that it actually gets passed through on the command line or otherwise | |
435 | // it will be dropped entirely when parsed on the other end. | |
54a0048b | 436 | ensure_no_nuls(arg)?; |
85aaf69f SL |
437 | let arg_bytes = &arg.as_inner().inner.as_inner(); |
438 | let quote = arg_bytes.iter().any(|c| *c == b' ' || *c == b'\t') | |
9346a6ac | 439 | || arg_bytes.is_empty(); |
85aaf69f SL |
440 | if quote { |
441 | cmd.push('"' as u16); | |
442 | } | |
443 | ||
444 | let mut iter = arg.encode_wide(); | |
d9579d0f | 445 | let mut backslashes: usize = 0; |
85aaf69f | 446 | while let Some(x) = iter.next() { |
d9579d0f AL |
447 | if x == '\\' as u16 { |
448 | backslashes += 1; | |
85aaf69f | 449 | } else { |
d9579d0f AL |
450 | if x == '"' as u16 { |
451 | // Add n+1 backslashes to total 2n+1 before internal '"'. | |
452 | for _ in 0..(backslashes+1) { | |
453 | cmd.push('\\' as u16); | |
454 | } | |
455 | } | |
456 | backslashes = 0; | |
85aaf69f | 457 | } |
d9579d0f | 458 | cmd.push(x); |
85aaf69f SL |
459 | } |
460 | ||
461 | if quote { | |
d9579d0f AL |
462 | // Add n backslashes to total 2n before ending '"'. |
463 | for _ in 0..backslashes { | |
464 | cmd.push('\\' as u16); | |
465 | } | |
85aaf69f SL |
466 | cmd.push('"' as u16); |
467 | } | |
7453a54e | 468 | Ok(()) |
85aaf69f SL |
469 | } |
470 | } | |
471 | ||
bd371182 | 472 | fn make_envp(env: Option<&collections::HashMap<OsString, OsString>>) |
7453a54e | 473 | -> io::Result<(*mut c_void, Vec<u16>)> { |
85aaf69f SL |
474 | // On Windows we pass an "environment block" which is not a char**, but |
475 | // rather a concatenation of null-terminated k=v\0 sequences, with a final | |
476 | // \0 to terminate. | |
477 | match env { | |
478 | Some(env) => { | |
479 | let mut blk = Vec::new(); | |
480 | ||
481 | for pair in env { | |
54a0048b | 482 | blk.extend(ensure_no_nuls(pair.0)?.encode_wide()); |
85aaf69f | 483 | blk.push('=' as u16); |
54a0048b | 484 | blk.extend(ensure_no_nuls(pair.1)?.encode_wide()); |
85aaf69f SL |
485 | blk.push(0); |
486 | } | |
487 | blk.push(0); | |
7453a54e | 488 | Ok((blk.as_mut_ptr() as *mut c_void, blk)) |
85aaf69f | 489 | } |
7453a54e | 490 | _ => Ok((ptr::null_mut(), Vec::new())) |
85aaf69f SL |
491 | } |
492 | } | |
493 | ||
7453a54e SL |
494 | fn make_dirp(d: Option<&OsString>) -> io::Result<(*const u16, Vec<u16>)> { |
495 | ||
85aaf69f | 496 | match d { |
bd371182 | 497 | Some(dir) => { |
54a0048b | 498 | let mut dir_str: Vec<u16> = ensure_no_nuls(dir)?.encode_wide().collect(); |
bd371182 | 499 | dir_str.push(0); |
7453a54e | 500 | Ok((dir_str.as_ptr(), dir_str)) |
bd371182 | 501 | }, |
7453a54e | 502 | None => Ok((ptr::null(), Vec::new())) |
85aaf69f SL |
503 | } |
504 | } | |
505 | ||
506 | #[cfg(test)] | |
507 | mod tests { | |
85aaf69f SL |
508 | use ffi::{OsStr, OsString}; |
509 | use super::make_command_line; | |
510 | ||
511 | #[test] | |
512 | fn test_make_command_line() { | |
513 | fn test_wrapper(prog: &str, args: &[&str]) -> String { | |
7453a54e SL |
514 | let command_line = &make_command_line(OsStr::new(prog), |
515 | &args.iter() | |
516 | .map(|a| OsString::from(a)) | |
517 | .collect::<Vec<OsString>>()) | |
518 | .unwrap(); | |
519 | String::from_utf16(command_line).unwrap() | |
85aaf69f SL |
520 | } |
521 | ||
522 | assert_eq!( | |
523 | test_wrapper("prog", &["aaa", "bbb", "ccc"]), | |
524 | "prog aaa bbb ccc" | |
525 | ); | |
526 | ||
527 | assert_eq!( | |
528 | test_wrapper("C:\\Program Files\\blah\\blah.exe", &["aaa"]), | |
529 | "\"C:\\Program Files\\blah\\blah.exe\" aaa" | |
530 | ); | |
531 | assert_eq!( | |
532 | test_wrapper("C:\\Program Files\\test", &["aa\"bb"]), | |
533 | "\"C:\\Program Files\\test\" aa\\\"bb" | |
534 | ); | |
535 | assert_eq!( | |
536 | test_wrapper("echo", &["a b c"]), | |
537 | "echo \"a b c\"" | |
538 | ); | |
d9579d0f AL |
539 | assert_eq!( |
540 | test_wrapper("echo", &["\" \\\" \\", "\\"]), | |
541 | "echo \"\\\" \\\\\\\" \\\\\" \\" | |
542 | ); | |
85aaf69f SL |
543 | assert_eq!( |
544 | test_wrapper("\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}", &[]), | |
545 | "\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}" | |
546 | ); | |
547 | } | |
548 | } |