1
1
use std:: process:: { Command , Stdio , ChildStdout } ;
2
2
use std:: collections:: HashMap ;
3
3
use std:: error:: Error ;
4
- use std:: io:: Read ;
5
- use std:: path:: Path ;
4
+ use std:: io:: { Read , Seek } ;
5
+ use std:: path:: { Path } ;
6
6
use std:: ffi:: OsStr ;
7
+ use std:: fs:: File ;
7
8
use std:: convert:: AsRef ;
8
9
9
10
use maplit:: hashmap;
11
+ use tempfile:: tempfile;
10
12
11
13
use crate :: config;
12
14
use crate :: shell;
@@ -16,6 +18,20 @@ pub struct Native {
16
18
pub cwd : Option < String > ,
17
19
pub envs : HashMap < String , String > ,
18
20
}
21
+ pub struct CaptureTarget {
22
+ pub stdout : File ,
23
+ pub stderr : File ,
24
+ }
25
+ impl CaptureTarget {
26
+ pub fn read_stdout ( & mut self , buf : & mut String ) -> Result < usize , std:: io:: Error > {
27
+ self . stdout . seek ( std:: io:: SeekFrom :: Start ( 0 ) ) ?;
28
+ return self . stdout . read_to_string ( buf) ;
29
+ }
30
+ pub fn read_stderr ( & mut self , buf : & mut String ) -> Result < usize , std:: io:: Error > {
31
+ self . stderr . seek ( std:: io:: SeekFrom :: Start ( 0 ) ) ?;
32
+ return self . stderr . read_to_string ( buf) ;
33
+ }
34
+ }
19
35
impl < ' a > shell:: Shell for Native {
20
36
fn new ( config : & config:: Container ) -> Self {
21
37
return Native {
@@ -43,8 +59,8 @@ impl<'a> shell::Shell for Native {
43
59
& self , args : & Vec < & str > , envs : I , cwd : & Option < P >
44
60
) -> Result < String , shell:: ShellError >
45
61
where I : IntoIterator < Item = ( K , V ) > , K : AsRef < OsStr > , V : AsRef < OsStr > , P : AsRef < Path > {
46
- let mut cmd = self . create_command ( args, envs, cwd, true ) ;
47
- return Native :: get_output ( & mut cmd) ;
62
+ let ( mut cmd, mut ct ) = self . create_command ( args, envs, cwd, true ) ;
63
+ return Native :: get_output ( & mut cmd, & mut ct ) ;
48
64
}
49
65
fn exec < I , K , V , P > (
50
66
& self , args : & Vec < & str > , envs : I , cwd : & Option < P > , capture : bool
@@ -57,18 +73,18 @@ impl<'a> shell::Shell for Native {
57
73
return Ok ( cmd) ;
58
74
} else if config. should_silent_shell_exec ( ) {
59
75
// regardless for the value of `capture`, always capture value
60
- let mut cmd = self . create_command ( args, envs, cwd, true ) ;
61
- return Native :: run_as_child ( & mut cmd) ;
76
+ let ( mut cmd, mut ct ) = self . create_command ( args, envs, cwd, true ) ;
77
+ return Native :: run_as_child ( & mut cmd, & mut ct ) ;
62
78
} else {
63
- let mut cmd = self . create_command ( args, envs, cwd, capture) ;
64
- return Native :: run_as_child ( & mut cmd) ;
79
+ let ( mut cmd, mut ct ) = self . create_command ( args, envs, cwd, capture) ;
80
+ return Native :: run_as_child ( & mut cmd, & mut ct ) ;
65
81
}
66
82
}
67
83
}
68
84
impl Native {
69
85
fn create_command < I , K , V , P > (
70
86
& self , args : & Vec < & str > , envs : I , cwd : & Option < P > , capture : bool
71
- ) -> Command
87
+ ) -> ( Command , Option < CaptureTarget > )
72
88
where I : IntoIterator < Item = ( K , V ) > , K : AsRef < OsStr > , V : AsRef < OsStr > , P : AsRef < Path > {
73
89
let mut c = Command :: new ( args[ 0 ] ) ;
74
90
c. args ( & args[ 1 ..] ) ;
@@ -86,34 +102,71 @@ impl Native {
86
102
}
87
103
}
88
104
c. envs ( & self . envs ) ;
89
- if capture {
90
- c. stdout ( Stdio :: piped ( ) ) ;
91
- c. stderr ( Stdio :: piped ( ) ) ;
92
- }
93
- return c;
105
+ let ct = if capture {
106
+ // windows std::process::Command does not work well with huge (>1kb) output piping.
107
+ // see https://github.com/rust-lang/rust/issues/45572 for detail
108
+ if cfg ! ( windows) {
109
+ let v = CaptureTarget {
110
+ stdout : tempfile ( ) . unwrap ( ) ,
111
+ stderr : tempfile ( ) . unwrap ( ) ,
112
+ } ;
113
+ log:: debug!( "capture to temp file {:?} {:?}" , v. stdout, v. stderr) ;
114
+ c. stdout ( Stdio :: from ( v. stdout . try_clone ( ) . unwrap ( ) ) ) ;
115
+ c. stderr ( Stdio :: from ( v. stderr . try_clone ( ) . unwrap ( ) ) ) ;
116
+ Some ( v)
117
+ } else {
118
+ c. stdout ( Stdio :: piped ( ) ) ;
119
+ c. stderr ( Stdio :: piped ( ) ) ;
120
+ None
121
+ }
122
+ } else {
123
+ None
124
+ } ;
125
+ return ( c, ct) ;
94
126
}
95
- fn get_output ( cmd : & mut Command ) -> Result < String , shell:: ShellError > {
127
+ fn get_output ( cmd : & mut Command , ct : & mut Option < CaptureTarget > ) -> Result < String , shell:: ShellError > {
96
128
// TODO: option to capture stderr as no error case
97
129
match cmd. output ( ) {
98
130
Ok ( output) => {
99
131
if output. status . success ( ) {
100
- match String :: from_utf8 ( output. stdout ) {
101
- Ok ( s) => return Ok ( s. trim ( ) . to_string ( ) ) ,
102
- Err ( err) => return Err ( shell:: ShellError :: OtherFailure {
103
- cause : format ! ( "stdout character code error {:?}" , err) ,
104
- cmd : format ! ( "{:?}" , cmd)
105
- } )
132
+ match ct {
133
+ Some ( v) => {
134
+ let mut buf = String :: new ( ) ;
135
+ v. read_stdout ( & mut buf) . map_err ( |e| shell:: ShellError :: OtherFailure {
136
+ cause : format ! ( "cannot read from stdout tempfile error {:?}" , e) ,
137
+ cmd : format ! ( "{:?}" , cmd)
138
+ } ) ?;
139
+ log:: debug!( "stdout: [{}]" , buf) ;
140
+ return Ok ( buf. trim ( ) . to_string ( ) ) ;
141
+ } ,
142
+ None => match String :: from_utf8 ( output. stdout ) {
143
+ Ok ( s) => return Ok ( s. trim ( ) . to_string ( ) ) ,
144
+ Err ( err) => return Err ( shell:: ShellError :: OtherFailure {
145
+ cause : format ! ( "stdout character code error {:?}" , err) ,
146
+ cmd : format ! ( "{:?}" , cmd)
147
+ } )
148
+ }
106
149
}
107
150
} else {
108
- match String :: from_utf8 ( output. stderr ) {
109
- Ok ( s) => return Err ( shell:: ShellError :: OtherFailure {
110
- cause : format ! ( "command returns error {}" , s) ,
111
- cmd : format ! ( "{:?}" , cmd)
112
- } ) ,
113
- Err ( err) => return Err ( shell:: ShellError :: OtherFailure {
114
- cause : format ! ( "stderr character code error {:?}" , err) ,
115
- cmd : format ! ( "{:?}" , cmd)
116
- } )
151
+ match ct {
152
+ Some ( v) => {
153
+ let mut buf = String :: new ( ) ;
154
+ v. read_stderr ( & mut buf) . map_err ( |e| shell:: ShellError :: OtherFailure {
155
+ cause : format ! ( "cannot read from stderr tempfile error {:?}" , e) ,
156
+ cmd : format ! ( "{:?}" , cmd)
157
+ } ) ?;
158
+ return Ok ( buf. trim ( ) . to_string ( ) ) ;
159
+ } ,
160
+ None => match String :: from_utf8 ( output. stderr ) {
161
+ Ok ( s) => return Err ( shell:: ShellError :: OtherFailure {
162
+ cause : format ! ( "command returns error {}" , s) ,
163
+ cmd : format ! ( "{:?}" , cmd)
164
+ } ) ,
165
+ Err ( err) => return Err ( shell:: ShellError :: OtherFailure {
166
+ cause : format ! ( "stderr character code error {:?}" , err) ,
167
+ cmd : format ! ( "{:?}" , cmd)
168
+ } )
169
+ }
117
170
}
118
171
}
119
172
} ,
@@ -123,48 +176,68 @@ impl Native {
123
176
} )
124
177
}
125
178
}
126
- fn read_stdout_or_empty ( stdout : Option < ChildStdout > ) -> String {
179
+ fn read_stdout_or_empty ( cmd : & Command , stdout : Option < ChildStdout > , ct : & mut Option < CaptureTarget > ) -> Result < String , shell :: ShellError > {
127
180
let mut buf = String :: new ( ) ;
128
- match stdout {
129
- Some ( mut stream) => {
130
- match stream. read_to_string ( & mut buf) {
131
- Ok ( _) => { } ,
132
- Err ( err) => {
133
- log:: error!( "read_stdout_or_empty error: {:?}" , err) ;
134
- }
135
- }
181
+ match ct {
182
+ Some ( v) => {
183
+ let mut buf = String :: new ( ) ;
184
+ v. read_stdout ( & mut buf) . map_err ( |e| shell:: ShellError :: OtherFailure {
185
+ cause : format ! ( "cannot read from stderr tempfile error {:?}" , e) ,
186
+ cmd : format ! ( "{:?}" , cmd)
187
+ } ) ?;
136
188
} ,
137
- None => { }
189
+ None => match stdout {
190
+ Some ( mut stream) => {
191
+ match stream. read_to_string ( & mut buf) {
192
+ Ok ( _) => { } ,
193
+ Err ( err) => {
194
+ log:: error!( "read_stdout_or_empty error: {:?}" , err) ;
195
+ }
196
+ }
197
+ } ,
198
+ None => { }
199
+ }
138
200
}
139
- return buf;
201
+ Ok ( buf)
140
202
}
141
- fn run_as_child ( cmd : & mut Command ) -> Result < String , shell:: ShellError > {
203
+ fn run_as_child ( cmd : & mut Command , ct : & mut Option < CaptureTarget > ) -> Result < String , shell:: ShellError > {
142
204
match cmd. spawn ( ) {
143
205
Ok ( mut process) => {
144
206
match process. wait ( ) {
145
207
Ok ( status) => {
146
208
if status. success ( ) {
147
209
let mut s = String :: new ( ) ;
148
- match process . stdout {
149
- Some ( mut stream ) => match stream . read_to_string ( & mut s ) {
150
- Ok ( _ ) => return Ok ( s . trim ( ) . to_string ( ) ) ,
151
- Err ( err ) => return Err ( shell:: ShellError :: OtherFailure {
152
- cause : format ! ( "read stream error {:?}" , err ) ,
210
+ match ct {
211
+ Some ( v ) => {
212
+ let mut buf = String :: new ( ) ;
213
+ v . read_stdout ( & mut buf ) . map_err ( |e| shell:: ShellError :: OtherFailure {
214
+ cause : format ! ( "cannot read from stderr tempfile error {:?}" , e ) ,
153
215
cmd : format ! ( "{:?}" , cmd)
154
- } )
216
+ } ) ?;
217
+ log:: debug!( "stdout: [{}]" , buf) ;
218
+ return Ok ( buf. trim ( ) . to_string ( ) )
155
219
} ,
156
- None => Ok ( "" . to_string ( ) )
220
+ None => match process. stdout {
221
+ Some ( mut stream) => match stream. read_to_string ( & mut s) {
222
+ Ok ( _) => return Ok ( s. trim ( ) . to_string ( ) ) ,
223
+ Err ( err) => return Err ( shell:: ShellError :: OtherFailure {
224
+ cause : format ! ( "read stream error {:?}" , err) ,
225
+ cmd : format ! ( "{:?}" , cmd)
226
+ } )
227
+ } ,
228
+ None => return Ok ( "" . to_string ( ) )
229
+ }
157
230
}
158
231
} else {
159
232
let mut s = String :: new ( ) ;
160
233
let output = match process. stderr {
161
234
Some ( mut stream) => {
162
235
match stream. read_to_string ( & mut s) {
163
- Ok ( _) => if s. is_empty ( ) { Self :: read_stdout_or_empty ( process. stdout ) } else { s } ,
164
- Err ( _) => Self :: read_stdout_or_empty ( process. stdout )
236
+ Ok ( _) => if s. is_empty ( ) { Self :: read_stdout_or_empty ( & cmd , process. stdout , ct ) ? } else { s } ,
237
+ Err ( _) => Self :: read_stdout_or_empty ( & cmd , process. stdout , ct ) ?
165
238
}
166
239
} ,
167
- None => Self :: read_stdout_or_empty ( process. stdout )
240
+ None => Self :: read_stdout_or_empty ( & cmd , process. stdout , ct ) ?
168
241
} ;
169
242
return match status. code ( ) {
170
243
Some ( _) => Err ( shell:: ShellError :: ExitStatus {
0 commit comments