@@ -19,12 +19,12 @@ use crate::path::{Path, PathBuf};
1919use crate :: ptr;
2020use crate :: sys:: c;
2121use crate :: sys:: c:: NonZeroDWORD ;
22+ use crate :: sys:: cvt;
2223use crate :: sys:: fs:: { File , OpenOptions } ;
2324use crate :: sys:: handle:: Handle ;
2425use crate :: sys:: path;
2526use crate :: sys:: pipe:: { self , AnonPipe } ;
2627use 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 } ;
@@ -269,8 +269,13 @@ impl Command {
269269 None
270270 } ;
271271 let program = resolve_exe ( & self . program , || env:: var_os ( "PATH" ) , child_paths) ?;
272+ // Case insensitive "ends_with" of UTF-16 encoded ".bat" or ".cmd"
273+ let is_batch_file = matches ! (
274+ program. len( ) . checked_sub( 5 ) . and_then( |i| program. get( i..) ) ,
275+ Some ( [ 46 , 98 | 66 , 97 | 65 , 116 | 84 , 0 ] | [ 46 , 99 | 67 , 109 | 77 , 100 | 68 , 0 ] )
276+ ) ;
272277 let mut cmd_str =
273- make_command_line ( program. as_os_str ( ) , & self . args , self . force_quotes_enabled ) ?;
278+ make_command_line ( & program, & self . args , self . force_quotes_enabled , is_batch_file ) ?;
274279 cmd_str. push ( 0 ) ; // add null terminator
275280
276281 // stolen from the libuv code.
@@ -309,7 +314,6 @@ impl Command {
309314 si. hStdOutput = stdout. as_raw_handle ( ) ;
310315 si. hStdError = stderr. as_raw_handle ( ) ;
311316
312- let program = to_u16s ( & program) ?;
313317 unsafe {
314318 cvt ( c:: CreateProcessW (
315319 program. as_ptr ( ) ,
@@ -366,7 +370,7 @@ fn resolve_exe<'a>(
366370 exe_path : & ' a OsStr ,
367371 parent_paths : impl FnOnce ( ) -> Option < OsString > ,
368372 child_paths : Option < & OsStr > ,
369- ) -> io:: Result < PathBuf > {
373+ ) -> io:: Result < Vec < u16 > > {
370374 // Early return if there is no filename.
371375 if exe_path. is_empty ( ) || path:: has_trailing_slash ( exe_path) {
372376 return Err ( io:: const_io_error!(
@@ -388,19 +392,19 @@ fn resolve_exe<'a>(
388392 if has_exe_suffix {
389393 // The application name is a path to a `.exe` file.
390394 // Let `CreateProcessW` figure out if it exists or not.
391- return Ok ( exe_path . into ( ) ) ;
395+ return path :: maybe_verbatim ( Path :: new ( exe_path ) ) ;
392396 }
393397 let mut path = PathBuf :: from ( exe_path) ;
394398
395399 // Append `.exe` if not already there.
396400 path = path:: append_suffix ( path, EXE_SUFFIX . as_ref ( ) ) ;
397- if program_exists ( & path) {
401+ if let Some ( path ) = program_exists ( & path) {
398402 return Ok ( path) ;
399403 } else {
400404 // It's ok to use `set_extension` here because the intent is to
401405 // remove the extension that was just added.
402406 path. set_extension ( "" ) ;
403- return Ok ( path) ;
407+ return path :: maybe_verbatim ( & path) ;
404408 }
405409 } else {
406410 ensure_no_nuls ( exe_path) ?;
@@ -415,7 +419,7 @@ fn resolve_exe<'a>(
415419 if !has_extension {
416420 path. set_extension ( EXE_EXTENSION ) ;
417421 }
418- if program_exists ( & path) { Some ( path ) } else { None }
422+ program_exists ( & path)
419423 } ) ;
420424 if let Some ( path) = result {
421425 return Ok ( path) ;
@@ -431,10 +435,10 @@ fn search_paths<Paths, Exists>(
431435 parent_paths : Paths ,
432436 child_paths : Option < & OsStr > ,
433437 mut exists : Exists ,
434- ) -> Option < PathBuf >
438+ ) -> Option < Vec < u16 > >
435439where
436440 Paths : FnOnce ( ) -> Option < OsString > ,
437- Exists : FnMut ( PathBuf ) -> Option < PathBuf > ,
441+ Exists : FnMut ( PathBuf ) -> Option < Vec < u16 > > ,
438442{
439443 // 1. Child paths
440444 // This is for consistency with Rust's historic behaviour.
@@ -486,17 +490,18 @@ where
486490}
487491
488492/// Check if a file exists without following symlinks.
489- fn program_exists ( path : & Path ) -> bool {
493+ fn program_exists ( path : & Path ) -> Option < Vec < u16 > > {
490494 unsafe {
491- to_u16s ( path)
492- . map ( |path| {
493- // Getting attributes using `GetFileAttributesW` does not follow symlinks
494- // and it will almost always be successful if the link exists.
495- // There are some exceptions for special system files (e.g. the pagefile)
496- // but these are not executable.
497- c:: GetFileAttributesW ( path. as_ptr ( ) ) != c:: INVALID_FILE_ATTRIBUTES
498- } )
499- . unwrap_or ( false )
495+ let path = path:: maybe_verbatim ( path) . ok ( ) ?;
496+ // Getting attributes using `GetFileAttributesW` does not follow symlinks
497+ // and it will almost always be successful if the link exists.
498+ // There are some exceptions for special system files (e.g. the pagefile)
499+ // but these are not executable.
500+ if c:: GetFileAttributesW ( path. as_ptr ( ) ) == c:: INVALID_FILE_ATTRIBUTES {
501+ None
502+ } else {
503+ Some ( path)
504+ }
500505 }
501506}
502507
@@ -730,7 +735,12 @@ enum Quote {
730735
731736// Produces a wide string *without terminating null*; returns an error if
732737// `prog` or any of the `args` contain a nul.
733- fn make_command_line ( prog : & OsStr , args : & [ Arg ] , force_quotes : bool ) -> io:: Result < Vec < u16 > > {
738+ fn make_command_line (
739+ prog : & [ u16 ] ,
740+ args : & [ Arg ] ,
741+ force_quotes : bool ,
742+ is_batch_file : bool ,
743+ ) -> io:: Result < Vec < u16 > > {
734744 // Encode the command and arguments in a command line string such
735745 // that the spawned process may recover them using CommandLineToArgvW.
736746 let mut cmd: Vec < u16 > = Vec :: new ( ) ;
@@ -739,17 +749,18 @@ fn make_command_line(prog: &OsStr, args: &[Arg], force_quotes: bool) -> io::Resu
739749 // need to add an extra pair of quotes surrounding the whole command line
740750 // so they are properly passed on to the script.
741751 // See issue #91991.
742- let is_batch_file = Path :: new ( prog)
743- . extension ( )
744- . map ( |ext| ext. eq_ignore_ascii_case ( "cmd" ) || ext. eq_ignore_ascii_case ( "bat" ) )
745- . unwrap_or ( false ) ;
746752 if is_batch_file {
747753 cmd. push ( b'"' as u16 ) ;
748754 }
749755
750- // Always quote the program name so CreateProcess doesn't interpret args as
751- // part of the name if the binary wasn't found first time.
752- append_arg ( & mut cmd, prog, Quote :: Always ) ?;
756+ // Always quote the program name so CreateProcess to avoid ambiguity when
757+ // the child process parses its arguments.
758+ // Note that quotes aren't escaped here because they can't be used in arg0.
759+ // But that's ok because file paths can't contain quotes.
760+ cmd. push ( b'"' as u16 ) ;
761+ cmd. extend_from_slice ( prog. strip_suffix ( & [ 0 ] ) . unwrap_or ( prog) ) ;
762+ cmd. push ( b'"' as u16 ) ;
763+
753764 for arg in args {
754765 cmd. push ( ' ' as u16 ) ;
755766 let ( arg, quote) = match arg {
0 commit comments