@@ -19,12 +19,12 @@ use crate::path::{Path, PathBuf};
19
19
use crate :: ptr;
20
20
use crate :: sys:: c;
21
21
use crate :: sys:: c:: NonZeroDWORD ;
22
+ use crate :: sys:: cvt;
22
23
use crate :: sys:: fs:: { File , OpenOptions } ;
23
24
use crate :: sys:: handle:: Handle ;
24
25
use crate :: sys:: path;
25
26
use crate :: sys:: pipe:: { self , AnonPipe } ;
26
27
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 } ;
@@ -269,8 +269,13 @@ impl Command {
269
269
None
270
270
} ;
271
271
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
+ ) ;
272
277
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 ) ?;
274
279
cmd_str. push ( 0 ) ; // add null terminator
275
280
276
281
// stolen from the libuv code.
@@ -309,7 +314,6 @@ impl Command {
309
314
si. hStdOutput = stdout. as_raw_handle ( ) ;
310
315
si. hStdError = stderr. as_raw_handle ( ) ;
311
316
312
- let program = to_u16s ( & program) ?;
313
317
unsafe {
314
318
cvt ( c:: CreateProcessW (
315
319
program. as_ptr ( ) ,
@@ -366,7 +370,7 @@ fn resolve_exe<'a>(
366
370
exe_path : & ' a OsStr ,
367
371
parent_paths : impl FnOnce ( ) -> Option < OsString > ,
368
372
child_paths : Option < & OsStr > ,
369
- ) -> io:: Result < PathBuf > {
373
+ ) -> io:: Result < Vec < u16 > > {
370
374
// Early return if there is no filename.
371
375
if exe_path. is_empty ( ) || path:: has_trailing_slash ( exe_path) {
372
376
return Err ( io:: const_io_error!(
@@ -388,19 +392,19 @@ fn resolve_exe<'a>(
388
392
if has_exe_suffix {
389
393
// The application name is a path to a `.exe` file.
390
394
// Let `CreateProcessW` figure out if it exists or not.
391
- return Ok ( exe_path . into ( ) ) ;
395
+ return path :: maybe_verbatim ( Path :: new ( exe_path ) ) ;
392
396
}
393
397
let mut path = PathBuf :: from ( exe_path) ;
394
398
395
399
// Append `.exe` if not already there.
396
400
path = path:: append_suffix ( path, EXE_SUFFIX . as_ref ( ) ) ;
397
- if program_exists ( & path) {
401
+ if let Some ( path ) = program_exists ( & path) {
398
402
return Ok ( path) ;
399
403
} else {
400
404
// It's ok to use `set_extension` here because the intent is to
401
405
// remove the extension that was just added.
402
406
path. set_extension ( "" ) ;
403
- return Ok ( path) ;
407
+ return path :: maybe_verbatim ( & path) ;
404
408
}
405
409
} else {
406
410
ensure_no_nuls ( exe_path) ?;
@@ -415,7 +419,7 @@ fn resolve_exe<'a>(
415
419
if !has_extension {
416
420
path. set_extension ( EXE_EXTENSION ) ;
417
421
}
418
- if program_exists ( & path) { Some ( path ) } else { None }
422
+ program_exists ( & path)
419
423
} ) ;
420
424
if let Some ( path) = result {
421
425
return Ok ( path) ;
@@ -431,10 +435,10 @@ fn search_paths<Paths, Exists>(
431
435
parent_paths : Paths ,
432
436
child_paths : Option < & OsStr > ,
433
437
mut exists : Exists ,
434
- ) -> Option < PathBuf >
438
+ ) -> Option < Vec < u16 > >
435
439
where
436
440
Paths : FnOnce ( ) -> Option < OsString > ,
437
- Exists : FnMut ( PathBuf ) -> Option < PathBuf > ,
441
+ Exists : FnMut ( PathBuf ) -> Option < Vec < u16 > > ,
438
442
{
439
443
// 1. Child paths
440
444
// This is for consistency with Rust's historic behaviour.
@@ -486,17 +490,18 @@ where
486
490
}
487
491
488
492
/// Check if a file exists without following symlinks.
489
- fn program_exists ( path : & Path ) -> bool {
493
+ fn program_exists ( path : & Path ) -> Option < Vec < u16 > > {
490
494
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
+ }
500
505
}
501
506
}
502
507
@@ -730,7 +735,12 @@ enum Quote {
730
735
731
736
// Produces a wide string *without terminating null*; returns an error if
732
737
// `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 > > {
734
744
// Encode the command and arguments in a command line string such
735
745
// that the spawned process may recover them using CommandLineToArgvW.
736
746
let mut cmd: Vec < u16 > = Vec :: new ( ) ;
@@ -739,17 +749,18 @@ fn make_command_line(prog: &OsStr, args: &[Arg], force_quotes: bool) -> io::Resu
739
749
// need to add an extra pair of quotes surrounding the whole command line
740
750
// so they are properly passed on to the script.
741
751
// 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 ) ;
746
752
if is_batch_file {
747
753
cmd. push ( b'"' as u16 ) ;
748
754
}
749
755
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
+
753
764
for arg in args {
754
765
cmd. push ( ' ' as u16 ) ;
755
766
let ( arg, quote) = match arg {
0 commit comments