@@ -7,24 +7,24 @@ use crate::cmp;
7
7
use crate :: collections:: BTreeMap ;
8
8
use crate :: convert:: { TryFrom , TryInto } ;
9
9
use crate :: env;
10
- use crate :: env:: split_paths ;
10
+ use crate :: env:: consts :: { EXE_EXTENSION , EXE_SUFFIX } ;
11
11
use crate :: ffi:: { OsStr , OsString } ;
12
12
use crate :: fmt;
13
- use crate :: fs;
14
13
use crate :: io:: { self , Error , ErrorKind } ;
15
14
use crate :: mem;
16
15
use crate :: num:: NonZeroI32 ;
17
- use crate :: os:: windows:: ffi:: OsStrExt ;
16
+ use crate :: os:: windows:: ffi:: { OsStrExt , OsStringExt } ;
18
17
use crate :: os:: windows:: io:: { AsRawHandle , FromRawHandle , IntoRawHandle } ;
19
- use crate :: path:: Path ;
18
+ use crate :: path:: { Path , PathBuf } ;
20
19
use crate :: ptr;
21
20
use crate :: sys:: c;
22
21
use crate :: sys:: c:: NonZeroDWORD ;
23
- use crate :: sys:: cvt;
24
22
use crate :: sys:: fs:: { File , OpenOptions } ;
25
23
use crate :: sys:: handle:: Handle ;
24
+ use crate :: sys:: path;
26
25
use crate :: sys:: pipe:: { self , AnonPipe } ;
27
26
use crate :: sys:: stdio;
27
+ use crate :: sys:: { cvt, to_u16s} ;
28
28
use crate :: sys_common:: mutex:: StaticMutex ;
29
29
use crate :: sys_common:: process:: { CommandEnv , CommandEnvs } ;
30
30
use crate :: sys_common:: { AsInner , IntoInner } ;
@@ -258,31 +258,19 @@ impl Command {
258
258
needs_stdin : bool ,
259
259
) -> io:: Result < ( Process , StdioPipes ) > {
260
260
let maybe_env = self . env . capture_if_changed ( ) ;
261
- // To have the spawning semantics of unix/windows stay the same, we need
262
- // to read the *child's* PATH if one is provided. See #15149 for more
263
- // details.
264
- let program = maybe_env. as_ref ( ) . and_then ( |env| {
265
- if let Some ( v) = env. get ( & EnvKey :: new ( "PATH" ) ) {
266
- // Split the value and test each path to see if the
267
- // program exists.
268
- for path in split_paths ( & v) {
269
- let path = path
270
- . join ( self . program . to_str ( ) . unwrap ( ) )
271
- . with_extension ( env:: consts:: EXE_EXTENSION ) ;
272
- if fs:: metadata ( & path) . is_ok ( ) {
273
- return Some ( path. into_os_string ( ) ) ;
274
- }
275
- }
276
- }
277
- None
278
- } ) ;
279
261
280
262
let mut si = zeroed_startupinfo ( ) ;
281
263
si. cb = mem:: size_of :: < c:: STARTUPINFO > ( ) as c:: DWORD ;
282
264
si. dwFlags = c:: STARTF_USESTDHANDLES ;
283
265
284
- let program = program. as_ref ( ) . unwrap_or ( & self . program ) ;
285
- let mut cmd_str = make_command_line ( program, & self . args , self . force_quotes_enabled ) ?;
266
+ let child_paths = if let Some ( env) = maybe_env. as_ref ( ) {
267
+ env. get ( & EnvKey :: new ( "PATH" ) ) . map ( |s| s. as_os_str ( ) )
268
+ } else {
269
+ None
270
+ } ;
271
+ let program = resolve_exe ( & self . program , child_paths) ?;
272
+ let mut cmd_str =
273
+ make_command_line ( program. as_os_str ( ) , & self . args , self . force_quotes_enabled ) ?;
286
274
cmd_str. push ( 0 ) ; // add null terminator
287
275
288
276
// stolen from the libuv code.
@@ -321,9 +309,10 @@ impl Command {
321
309
si. hStdOutput = stdout. as_raw_handle ( ) ;
322
310
si. hStdError = stderr. as_raw_handle ( ) ;
323
311
312
+ let program = to_u16s ( & program) ?;
324
313
unsafe {
325
314
cvt ( c:: CreateProcessW (
326
- ptr :: null ( ) ,
315
+ program . as_ptr ( ) ,
327
316
cmd_str. as_mut_ptr ( ) ,
328
317
ptr:: null_mut ( ) ,
329
318
ptr:: null_mut ( ) ,
@@ -361,6 +350,132 @@ impl fmt::Debug for Command {
361
350
}
362
351
}
363
352
353
+ // Resolve `exe_path` to the executable name.
354
+ //
355
+ // * If the path is simply a file name then use the paths given by `search_paths` to find the executable.
356
+ // * Otherwise use the `exe_path` as given.
357
+ //
358
+ // This function may also append `.exe` to the name. The rationale for doing so is as follows:
359
+ //
360
+ // It is a very strong convention that Windows executables have the `exe` extension.
361
+ // In Rust, it is common to omit this extension.
362
+ // Therefore this functions first assumes `.exe` was intended.
363
+ // It falls back to the plain file name if a full path is given and the extension is omitted
364
+ // or if only a file name is given and it already contains an extension.
365
+ fn resolve_exe < ' a > ( exe_path : & ' a OsStr , child_paths : Option < & OsStr > ) -> io:: Result < PathBuf > {
366
+ // Early return if there is no filename.
367
+ if exe_path. is_empty ( ) || path:: has_trailing_slash ( exe_path) {
368
+ return Err ( io:: Error :: new_const (
369
+ io:: ErrorKind :: InvalidInput ,
370
+ & "program path has no file name" ,
371
+ ) ) ;
372
+ }
373
+ // Test if the file name has the `exe` extension.
374
+ // This does a case-insensitive `ends_with`.
375
+ let has_exe_suffix = if exe_path. len ( ) >= EXE_SUFFIX . len ( ) {
376
+ exe_path. bytes ( ) [ exe_path. len ( ) - EXE_SUFFIX . len ( ) ..]
377
+ . eq_ignore_ascii_case ( EXE_SUFFIX . as_bytes ( ) )
378
+ } else {
379
+ false
380
+ } ;
381
+
382
+ // If `exe_path` is an absolute path or a sub-path then don't search `PATH` for it.
383
+ if !path:: is_file_name ( exe_path) {
384
+ if has_exe_suffix {
385
+ // The application name is a path to a `.exe` file.
386
+ // Let `CreateProcessW` figure out if it exists or not.
387
+ return Ok ( exe_path. into ( ) ) ;
388
+ }
389
+ let mut path = PathBuf :: from ( exe_path) ;
390
+
391
+ // Append `.exe` if not already there.
392
+ path = path:: append_suffix ( path, EXE_SUFFIX . as_ref ( ) ) ;
393
+ if path. try_exists ( ) . unwrap_or ( false ) {
394
+ return Ok ( path) ;
395
+ } else {
396
+ // It's ok to use `set_extension` here because the intent is to
397
+ // remove the extension that was just added.
398
+ path. set_extension ( "" ) ;
399
+ return Ok ( path) ;
400
+ }
401
+ } else {
402
+ ensure_no_nuls ( exe_path) ?;
403
+ // From the `CreateProcessW` docs:
404
+ // > If the file name does not contain an extension, .exe is appended.
405
+ // Note that this rule only applies when searching paths.
406
+ let has_extension = exe_path. bytes ( ) . contains ( & b'.' ) ;
407
+
408
+ // Search the directories given by `search_paths`.
409
+ let result = search_paths ( child_paths, |mut path| {
410
+ path. push ( & exe_path) ;
411
+ if !has_extension {
412
+ path. set_extension ( EXE_EXTENSION ) ;
413
+ }
414
+ if let Ok ( true ) = path. try_exists ( ) { Some ( path) } else { None }
415
+ } ) ;
416
+ if let Some ( path) = result {
417
+ return Ok ( path) ;
418
+ }
419
+ }
420
+ // If we get here then the executable cannot be found.
421
+ Err ( io:: Error :: new_const ( io:: ErrorKind :: NotFound , & "program not found" ) )
422
+ }
423
+
424
+ // Calls `f` for every path that should be used to find an executable.
425
+ // Returns once `f` returns the path to an executable or all paths have been searched.
426
+ fn search_paths < F > ( child_paths : Option < & OsStr > , mut f : F ) -> Option < PathBuf >
427
+ where
428
+ F : FnMut ( PathBuf ) -> Option < PathBuf > ,
429
+ {
430
+ // 1. Child paths
431
+ // This is for consistency with Rust's historic behaviour.
432
+ if let Some ( paths) = child_paths {
433
+ for path in env:: split_paths ( paths) . filter ( |p| !p. as_os_str ( ) . is_empty ( ) ) {
434
+ if let Some ( path) = f ( path) {
435
+ return Some ( path) ;
436
+ }
437
+ }
438
+ }
439
+
440
+ // 2. Application path
441
+ if let Ok ( mut app_path) = env:: current_exe ( ) {
442
+ app_path. pop ( ) ;
443
+ if let Some ( path) = f ( app_path) {
444
+ return Some ( path) ;
445
+ }
446
+ }
447
+
448
+ // 3 & 4. System paths
449
+ // SAFETY: This uses `fill_utf16_buf` to safely call the OS functions.
450
+ unsafe {
451
+ if let Ok ( Some ( path) ) = super :: fill_utf16_buf (
452
+ |buf, size| c:: GetSystemDirectoryW ( buf, size) ,
453
+ |buf| f ( PathBuf :: from ( OsString :: from_wide ( buf) ) ) ,
454
+ ) {
455
+ return Some ( path) ;
456
+ }
457
+ #[ cfg( not( target_vendor = "uwp" ) ) ]
458
+ {
459
+ if let Ok ( Some ( path) ) = super :: fill_utf16_buf (
460
+ |buf, size| c:: GetWindowsDirectoryW ( buf, size) ,
461
+ |buf| f ( PathBuf :: from ( OsString :: from_wide ( buf) ) ) ,
462
+ ) {
463
+ return Some ( path) ;
464
+ }
465
+ }
466
+ }
467
+
468
+ // 5. Parent paths
469
+ if let Some ( parent_paths) = env:: var_os ( "PATH" ) {
470
+ for path in env:: split_paths ( & parent_paths) . filter ( |p| !p. as_os_str ( ) . is_empty ( ) ) {
471
+ if let Some ( path) = f ( path) {
472
+ return Some ( path) ;
473
+ }
474
+ }
475
+ }
476
+ None
477
+ }
478
+
364
479
impl Stdio {
365
480
fn to_handle ( & self , stdio_id : c:: DWORD , pipe : & mut Option < AnonPipe > ) -> io:: Result < Handle > {
366
481
match * self {
0 commit comments