@@ -32,8 +32,12 @@ use io::{Reader, Writer, io_error, IoError, OtherIoError,
32
32
standard_error, EndOfFile } ;
33
33
use libc;
34
34
use option:: { Option , Some , None } ;
35
+ use prelude:: drop;
35
36
use result:: { Ok , Err } ;
37
+ use rt:: local:: Local ;
36
38
use rt:: rtio:: { DontClose , IoFactory , LocalIo , RtioFileStream , RtioTTY } ;
39
+ use rt:: task:: Task ;
40
+ use util;
37
41
38
42
// And so begins the tale of acquiring a uv handle to a stdio stream on all
39
43
// platforms in all situations. Our story begins by splitting the world into two
@@ -101,6 +105,44 @@ pub fn stderr() -> StdWriter {
101
105
src ( libc:: STDERR_FILENO , false , |src| StdWriter { inner : src } )
102
106
}
103
107
108
+ fn reset_helper ( w : ~Writer ,
109
+ f : |& mut Task , ~Writer | -> Option < ~Writer > ) -> Option < ~Writer > {
110
+ let mut t = Local :: borrow ( None :: < Task > ) ;
111
+ // Be sure to flush any pending output from the writer
112
+ match f ( t. get ( ) , w) {
113
+ Some ( mut w) => {
114
+ drop ( t) ;
115
+ w. flush ( ) ;
116
+ Some ( w)
117
+ }
118
+ None => None
119
+ }
120
+ }
121
+
122
+ /// Resets the task-local stdout handle to the specified writer
123
+ ///
124
+ /// This will replace the current task's stdout handle, returning the old
125
+ /// handle. All future calls to `print` and friends will emit their output to
126
+ /// this specified handle.
127
+ ///
128
+ /// Note that this does not need to be called for all new tasks; the default
129
+ /// output handle is to the process's stdout stream.
130
+ pub fn set_stdout ( stdout : ~Writer ) -> Option < ~Writer > {
131
+ reset_helper ( stdout, |t, w| util:: replace ( & mut t. stdout , Some ( w) ) )
132
+ }
133
+
134
+ /// Resets the task-local stderr handle to the specified writer
135
+ ///
136
+ /// This will replace the current task's stderr handle, returning the old
137
+ /// handle. Currently, the stderr handle is used for printing failure messages
138
+ /// during task failure.
139
+ ///
140
+ /// Note that this does not need to be called for all new tasks; the default
141
+ /// output handle is to the process's stderr stream.
142
+ pub fn set_stderr ( stderr : ~Writer ) -> Option < ~Writer > {
143
+ reset_helper ( stderr, |t, w| util:: replace ( & mut t. stderr , Some ( w) ) )
144
+ }
145
+
104
146
// Helper to access the local task's stdout handle
105
147
//
106
148
// Note that this is not a safe function to expose because you can create an
@@ -112,38 +154,49 @@ pub fn stderr() -> StdWriter {
112
154
// })
113
155
// })
114
156
fn with_task_stdout ( f: |& mut Writer |) {
115
- use rt:: local:: Local ;
116
- use rt:: task:: Task ;
117
-
118
- unsafe {
119
- let task: Option < * mut Task > = Local :: try_unsafe_borrow ( ) ;
120
- match task {
121
- Some ( task) => {
122
- match ( * task) . stdout_handle {
123
- Some ( ref mut handle) => f ( * handle) ,
124
- None => {
125
- let handle = ~LineBufferedWriter :: new ( stdout ( ) ) ;
126
- let mut handle = handle as ~Writer ;
127
- f ( handle) ;
128
- ( * task) . stdout_handle = Some ( handle) ;
129
- }
130
- }
157
+ let task: Option < ~Task > = Local :: try_take ( ) ;
158
+ match task {
159
+ Some ( mut task) => {
160
+ // Printing may run arbitrary code, so ensure that the task is in
161
+ // TLS to allow all std services. Note that this means a print while
162
+ // printing won't use the task's normal stdout handle, but this is
163
+ // necessary to ensure safety (no aliasing).
164
+ let mut my_stdout = task. stdout . take ( ) ;
165
+ Local :: put ( task) ;
166
+
167
+ if my_stdout. is_none ( ) {
168
+ my_stdout = Some ( ~LineBufferedWriter :: new ( stdout ( ) ) as ~Writer ) ;
131
169
}
170
+ f ( * my_stdout. get_mut_ref ( ) ) ;
171
+
172
+ // Note that we need to be careful when putting the stdout handle
173
+ // back into the task. If the handle was set to `Some` while
174
+ // printing, then we can run aribitrary code when destroying the
175
+ // previous handle. This means that the local task needs to be in
176
+ // TLS while we do this.
177
+ //
178
+ // To protect against this, we do a little dance in which we
179
+ // temporarily take the task, swap the handles, put the task in TLS,
180
+ // and only then drop the previous handle.
181
+ let mut t = Local :: borrow ( None :: < Task > ) ;
182
+ let prev = util:: replace ( & mut t. get ( ) . stdout , my_stdout) ;
183
+ drop ( t) ;
184
+ drop ( prev) ;
185
+ }
132
186
133
- None => {
134
- struct Stdout ;
135
- impl Writer for Stdout {
136
- fn write ( & mut self , data : & [ u8 ] ) {
137
- unsafe {
138
- libc:: write ( libc:: STDOUT_FILENO ,
139
- data. as_ptr ( ) as * libc:: c_void ,
140
- data. len ( ) as libc:: size_t ) ;
141
- }
187
+ None => {
188
+ struct Stdout ;
189
+ impl Writer for Stdout {
190
+ fn write ( & mut self , data : & [ u8 ] ) {
191
+ unsafe {
192
+ libc:: write ( libc:: STDOUT_FILENO ,
193
+ data. as_ptr ( ) as * libc:: c_void ,
194
+ data. len ( ) as libc:: size_t ) ;
142
195
}
143
196
}
144
- let mut io = Stdout ;
145
- f ( & mut io as & mut Writer ) ;
146
197
}
198
+ let mut io = Stdout ;
199
+ f ( & mut io as & mut Writer ) ;
147
200
}
148
201
}
149
202
}
@@ -313,4 +366,29 @@ mod tests {
313
366
stdout( ) ;
314
367
stderr( ) ;
315
368
} )
369
+
370
+ iotest ! ( fn capture_stdout( ) {
371
+ use io:: comm_adapters:: { PortReader , ChanWriter } ;
372
+
373
+ let ( p, c) = Chan :: new( ) ;
374
+ let ( mut r, w) = ( PortReader :: new( p) , ChanWriter :: new( c) ) ;
375
+ do spawn {
376
+ set_stdout( ~w as ~Writer ) ;
377
+ println!( "hello!" ) ;
378
+ }
379
+ assert_eq!( r. read_to_str( ) , ~"hello!\n ") ;
380
+ } )
381
+
382
+ iotest ! ( fn capture_stderr( ) {
383
+ use io:: comm_adapters:: { PortReader , ChanWriter } ;
384
+
385
+ let ( p, c) = Chan :: new( ) ;
386
+ let ( mut r, w) = ( PortReader :: new( p) , ChanWriter :: new( c) ) ;
387
+ do spawn {
388
+ set_stderr( ~w as ~Writer ) ;
389
+ fail!( "my special message" ) ;
390
+ }
391
+ let s = r. read_to_str( ) ;
392
+ assert!( s. contains( "my special message" ) ) ;
393
+ } )
316
394
}
0 commit comments