@@ -25,10 +25,13 @@ use crate::sys::fs::{File, OpenOptions};
25
25
use crate :: sys:: handle:: Handle ;
26
26
use crate :: sys:: pipe:: { self , AnonPipe } ;
27
27
use crate :: sys:: stdio;
28
+ use crate :: sys:: to_u16s;
28
29
use crate :: sys_common:: mutex:: StaticMutex ;
29
30
use crate :: sys_common:: process:: { CommandEnv , CommandEnvs } ;
30
31
use crate :: sys_common:: AsInner ;
31
32
33
+ use super :: path;
34
+
32
35
use libc:: { c_void, EXIT_FAILURE , EXIT_SUCCESS } ;
33
36
34
37
////////////////////////////////////////////////////////////////////////////////
@@ -256,15 +259,17 @@ impl Command {
256
259
}
257
260
None
258
261
} ) ;
262
+ let program = program. as_ref ( ) . unwrap_or ( & self . program ) ;
263
+
264
+ let mut args = make_command_line ( program, & self . args , self . force_quotes_enabled ) ?;
265
+ args. push ( 0 ) ;
266
+
267
+ let application_name = CommandApp :: new ( program) ?;
259
268
260
269
let mut si = zeroed_startupinfo ( ) ;
261
270
si. cb = mem:: size_of :: < c:: STARTUPINFO > ( ) as c:: DWORD ;
262
271
si. dwFlags = c:: STARTF_USESTDHANDLES ;
263
272
264
- let program = program. as_ref ( ) . unwrap_or ( & self . program ) ;
265
- let mut cmd_str = make_command_line ( program, & self . args , self . force_quotes_enabled ) ?;
266
- cmd_str. push ( 0 ) ; // add null terminator
267
-
268
273
// stolen from the libuv code.
269
274
let mut flags = self . flags | c:: CREATE_UNICODE_ENVIRONMENT ;
270
275
if self . detach {
@@ -303,8 +308,8 @@ impl Command {
303
308
304
309
unsafe {
305
310
cvt ( c:: CreateProcessW (
306
- ptr :: null ( ) ,
307
- cmd_str . as_mut_ptr ( ) ,
311
+ application_name . as_ptr ( ) ,
312
+ args . as_mut_ptr ( ) ,
308
313
ptr:: null_mut ( ) ,
309
314
ptr:: null_mut ( ) ,
310
315
c:: TRUE ,
@@ -552,6 +557,77 @@ fn zeroed_process_information() -> c::PROCESS_INFORMATION {
552
557
}
553
558
}
554
559
560
+ // Finds the application name that should be passed to `c::CreateProcessW`.
561
+ struct CommandApp {
562
+ application : Option < Vec < u16 > > ,
563
+ }
564
+ impl CommandApp {
565
+ fn new ( program : & OsStr ) -> io:: Result < Self > {
566
+ let filename = match path:: parse_filename ( program) {
567
+ Some ( name) => name,
568
+ None => {
569
+ return Err ( io:: Error :: new_const (
570
+ io:: ErrorKind :: InvalidInput ,
571
+ & "the program name cannot be empty" ,
572
+ ) ) ;
573
+ }
574
+ } ;
575
+
576
+ if filename. len ( ) == program. len ( ) {
577
+ // If the path is a plain file name then let the OS search for it.
578
+ Ok ( Self { application : None } )
579
+ } else {
580
+ // Otherwise use the path, appending `.exe` if necessary.
581
+ let mut utf16 = to_u16s ( program) ?;
582
+
583
+ // Possibly append `.exe` to the file name.
584
+ //
585
+ // The rules for doing so are fairly complex and are here to maintain
586
+ // the previous behaviour. It should hopefully be simplified in the
587
+ // future, once the preferred semantics are decided.
588
+ //
589
+ // Basically there are two different cases where `.exe` is added:
590
+ //
591
+ // If the path is absolute or starts with `.\` or `..\` then it will
592
+ // first try the exact path given. If that path is not found then
593
+ // `.exe` is appended and it's retried.
594
+ //
595
+ // Otherwise if the path is a plain file name or in a form such as
596
+ // `directory\file` then `.exe` is always appended unless the file
597
+ // name contains a `.` somewhere (i.e. it already has an extension).
598
+ let has_root = match program. bytes ( ) {
599
+ // Drive paths such as `C:\`, `D:\`, etc.
600
+ // This also includes relative drive paths like `C:`
601
+ [ _, b':' , ..] => true ,
602
+ // Starts with: `\`, `.\` or `..\`
603
+ [ sep, ..] | [ b'.' , sep, ..] | [ b'.' , b'.' , sep, ..] if path:: is_sep_byte ( * sep) => {
604
+ true
605
+ }
606
+ // All other paths. For example:
607
+ // `cmd`, `dir\cmd`, `dir\..\cmd`, etc.
608
+ _ => false ,
609
+ } ;
610
+ let has_extension = filename. bytes ( ) . contains ( & b'.' ) ;
611
+
612
+ // If `try_exists` fails then treat it as existing and let
613
+ // `c:CreateProcessW` try instead.
614
+ if ( has_root && !fs:: try_exists ( program) . unwrap_or ( true ) )
615
+ || ( !has_root && !has_extension)
616
+ {
617
+ // remove null and add the `.exe` extension.
618
+ utf16. pop ( ) ;
619
+ utf16. extend ( env:: consts:: EXE_SUFFIX . encode_utf16 ( ) ) ;
620
+ utf16. push ( 0 ) ;
621
+ }
622
+
623
+ Ok ( Self { application : Some ( utf16) } )
624
+ }
625
+ }
626
+ fn as_ptr ( & self ) -> * const u16 {
627
+ if let Some ( app) = & self . application { app. as_ptr ( ) } else { ptr:: null ( ) }
628
+ }
629
+ }
630
+
555
631
enum Quote {
556
632
// Every arg is quoted
557
633
Always ,
0 commit comments