@@ -7,24 +7,24 @@ use crate::cmp;
77use crate :: collections:: BTreeMap ;
88use crate :: convert:: { TryFrom , TryInto } ;
99use crate :: env;
10- use crate :: env:: split_paths ;
10+ use crate :: env:: consts :: { EXE_EXTENSION , EXE_SUFFIX } ;
1111use crate :: ffi:: { OsStr , OsString } ;
1212use crate :: fmt;
13- use crate :: fs;
1413use crate :: io:: { self , Error , ErrorKind } ;
1514use crate :: mem;
1615use crate :: num:: NonZeroI32 ;
17- use crate :: os:: windows:: ffi:: OsStrExt ;
16+ use crate :: os:: windows:: ffi:: { OsStrExt , OsStringExt } ;
1817use crate :: os:: windows:: io:: { AsRawHandle , FromRawHandle , IntoRawHandle } ;
19- use crate :: path:: Path ;
18+ use crate :: path:: { Path , PathBuf } ;
2019use crate :: ptr;
2120use crate :: sys:: c;
2221use crate :: sys:: c:: NonZeroDWORD ;
23- use crate :: sys:: cvt;
2422use crate :: sys:: fs:: { File , OpenOptions } ;
2523use crate :: sys:: handle:: Handle ;
24+ use crate :: sys:: path;
2625use crate :: sys:: pipe:: { self , AnonPipe } ;
2726use crate :: sys:: stdio;
27+ use crate :: sys:: { cvt, to_u16s} ;
2828use crate :: sys_common:: mutex:: StaticMutex ;
2929use crate :: sys_common:: process:: { CommandEnv , CommandEnvs } ;
3030use crate :: sys_common:: { AsInner , IntoInner } ;
@@ -258,31 +258,19 @@ impl Command {
258258 needs_stdin : bool ,
259259 ) -> io:: Result < ( Process , StdioPipes ) > {
260260 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- } ) ;
279261
280262 let mut si = zeroed_startupinfo ( ) ;
281263 si. cb = mem:: size_of :: < c:: STARTUPINFO > ( ) as c:: DWORD ;
282264 si. dwFlags = c:: STARTF_USESTDHANDLES ;
283265
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 ) ?;
286274 cmd_str. push ( 0 ) ; // add null terminator
287275
288276 // stolen from the libuv code.
@@ -321,9 +309,10 @@ impl Command {
321309 si. hStdOutput = stdout. as_raw_handle ( ) ;
322310 si. hStdError = stderr. as_raw_handle ( ) ;
323311
312+ let program = to_u16s ( & program) ?;
324313 unsafe {
325314 cvt ( c:: CreateProcessW (
326- ptr :: null ( ) ,
315+ program . as_ptr ( ) ,
327316 cmd_str. as_mut_ptr ( ) ,
328317 ptr:: null_mut ( ) ,
329318 ptr:: null_mut ( ) ,
@@ -361,6 +350,132 @@ impl fmt::Debug for Command {
361350 }
362351}
363352
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+
364479impl Stdio {
365480 fn to_handle ( & self , stdio_id : c:: DWORD , pipe : & mut Option < AnonPipe > ) -> io:: Result < Handle > {
366481 match * self {
0 commit comments