@@ -32,8 +32,12 @@ use io::{Reader, Writer, io_error, IoError, OtherIoError,
3232 standard_error, EndOfFile } ;
3333use libc;
3434use option:: { Option , Some , None } ;
35+ use prelude:: drop;
3536use result:: { Ok , Err } ;
37+ use rt:: local:: Local ;
3638use rt:: rtio:: { DontClose , IoFactory , LocalIo , RtioFileStream , RtioTTY } ;
39+ use rt:: task:: Task ;
40+ use util;
3741
3842// And so begins the tale of acquiring a uv handle to a stdio stream on all
3943// platforms in all situations. Our story begins by splitting the world into two
@@ -101,6 +105,44 @@ pub fn stderr() -> StdWriter {
101105 src ( libc:: STDERR_FILENO , false , |src| StdWriter { inner : src } )
102106}
103107
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+
104146// Helper to access the local task's stdout handle
105147//
106148// Note that this is not a safe function to expose because you can create an
@@ -112,38 +154,49 @@ pub fn stderr() -> StdWriter {
112154// })
113155// })
114156fn 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 ) ;
131169 }
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+ }
132186
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 ) ;
142195 }
143196 }
144- let mut io = Stdout ;
145- f ( & mut io as & mut Writer ) ;
146197 }
198+ let mut io = Stdout ;
199+ f ( & mut io as & mut Writer ) ;
147200 }
148201 }
149202}
@@ -313,4 +366,29 @@ mod tests {
313366 stdout( ) ;
314367 stderr( ) ;
315368 } )
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+ } )
316394}
0 commit comments