2013-04-17 19:55:21 -05:00
|
|
|
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
|
|
|
// file at the top-level directory of this distribution and at
|
|
|
|
// http://rust-lang.org/COPYRIGHT.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
2013-10-17 19:04:51 -05:00
|
|
|
/*!
|
|
|
|
|
|
|
|
This modules provides bindings to the local event loop's TTY interface, using it
|
|
|
|
to have synchronous, but non-blocking versions of stdio. These handles can be
|
|
|
|
inspected for information about terminal dimensions or related information
|
|
|
|
about the stream or terminal that it is attached to.
|
|
|
|
|
|
|
|
# Example
|
|
|
|
|
|
|
|
```rust
|
2013-11-11 00:46:32 -06:00
|
|
|
use std::io;
|
2013-10-17 19:04:51 -05:00
|
|
|
|
|
|
|
let mut out = io::stdout();
|
|
|
|
out.write(bytes!("Hello, world!"));
|
|
|
|
```
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
2014-01-06 18:48:51 -06:00
|
|
|
use container::Container;
|
2013-10-20 17:42:24 -05:00
|
|
|
use fmt;
|
2013-12-12 19:30:41 -06:00
|
|
|
use io::buffered::LineBufferedWriter;
|
|
|
|
use io::{Reader, Writer, io_error, IoError, OtherIoError,
|
|
|
|
standard_error, EndOfFile};
|
2013-10-07 15:25:06 -05:00
|
|
|
use libc;
|
|
|
|
use option::{Option, Some, None};
|
2014-01-06 12:26:11 -06:00
|
|
|
use prelude::drop;
|
2013-10-07 15:25:06 -05:00
|
|
|
use result::{Ok, Err};
|
2014-01-06 12:26:11 -06:00
|
|
|
use rt::local::Local;
|
2013-12-05 19:37:02 -06:00
|
|
|
use rt::rtio::{DontClose, IoFactory, LocalIo, RtioFileStream, RtioTTY};
|
2014-01-06 12:26:11 -06:00
|
|
|
use rt::task::Task;
|
2014-01-06 18:48:51 -06:00
|
|
|
use str::StrSlice;
|
2014-01-06 12:26:11 -06:00
|
|
|
use util;
|
2014-01-06 18:48:51 -06:00
|
|
|
use vec::ImmutableVector;
|
2013-10-22 11:36:30 -05:00
|
|
|
|
|
|
|
// And so begins the tale of acquiring a uv handle to a stdio stream on all
|
|
|
|
// platforms in all situations. Our story begins by splitting the world into two
|
|
|
|
// categories, windows and unix. Then one day the creators of unix said let
|
|
|
|
// there be redirection! And henceforth there was redirection away from the
|
|
|
|
// console for standard I/O streams.
|
|
|
|
//
|
|
|
|
// After this day, the world split into four factions:
|
|
|
|
//
|
|
|
|
// 1. Unix with stdout on a terminal.
|
|
|
|
// 2. Unix with stdout redirected.
|
|
|
|
// 3. Windows with stdout on a terminal.
|
|
|
|
// 4. Windows with stdout redirected.
|
|
|
|
//
|
|
|
|
// Many years passed, and then one day the nation of libuv decided to unify this
|
|
|
|
// world. After months of toiling, uv created three ideas: TTY, Pipe, File.
|
|
|
|
// These three ideas propagated throughout the lands and the four great factions
|
|
|
|
// decided to settle among them.
|
|
|
|
//
|
|
|
|
// The groups of 1, 2, and 3 all worked very hard towards the idea of TTY. Upon
|
|
|
|
// doing so, they even enhanced themselves further then their Pipe/File
|
|
|
|
// brethren, becoming the dominant powers.
|
|
|
|
//
|
|
|
|
// The group of 4, however, decided to work independently. They abandoned the
|
|
|
|
// common TTY belief throughout, and even abandoned the fledgling Pipe belief.
|
|
|
|
// The members of the 4th faction decided to only align themselves with File.
|
|
|
|
//
|
|
|
|
// tl;dr; TTY works on everything but when windows stdout is redirected, in that
|
|
|
|
// case pipe also doesn't work, but magically file does!
|
|
|
|
enum StdSource {
|
|
|
|
TTY(~RtioTTY),
|
|
|
|
File(~RtioFileStream),
|
|
|
|
}
|
2013-04-17 19:55:21 -05:00
|
|
|
|
2013-11-18 23:15:42 -06:00
|
|
|
fn src<T>(fd: libc::c_int, readable: bool, f: |StdSource| -> T) -> T {
|
2013-12-12 19:30:41 -06:00
|
|
|
LocalIo::maybe_raise(|io| {
|
|
|
|
Ok(match io.tty_open(fd, readable) {
|
|
|
|
Ok(tty) => f(TTY(tty)),
|
|
|
|
Err(_) => f(File(io.fs_from_raw_fd(fd, DontClose))),
|
|
|
|
})
|
|
|
|
}).unwrap()
|
2013-10-07 15:25:06 -05:00
|
|
|
}
|
2013-04-17 19:55:21 -05:00
|
|
|
|
2013-10-17 19:04:51 -05:00
|
|
|
/// Creates a new non-blocking handle to the stdin of the current process.
|
|
|
|
///
|
|
|
|
/// See `stdout()` for notes about this function.
|
|
|
|
pub fn stdin() -> StdReader {
|
2013-11-20 16:17:12 -06:00
|
|
|
src(libc::STDIN_FILENO, true, |src| StdReader { inner: src })
|
2013-10-17 19:04:51 -05:00
|
|
|
}
|
|
|
|
|
2013-10-07 15:25:06 -05:00
|
|
|
/// Creates a new non-blocking handle to the stdout of the current process.
|
|
|
|
///
|
|
|
|
/// Note that this is a fairly expensive operation in that at least one memory
|
|
|
|
/// allocation is performed. Additionally, this must be called from a runtime
|
|
|
|
/// task context because the stream returned will be a non-blocking object using
|
|
|
|
/// the local scheduler to perform the I/O.
|
|
|
|
pub fn stdout() -> StdWriter {
|
2013-11-20 16:17:12 -06:00
|
|
|
src(libc::STDOUT_FILENO, false, |src| StdWriter { inner: src })
|
2013-10-07 15:25:06 -05:00
|
|
|
}
|
2013-04-17 19:55:21 -05:00
|
|
|
|
2013-10-07 15:25:06 -05:00
|
|
|
/// Creates a new non-blocking handle to the stderr of the current process.
|
|
|
|
///
|
|
|
|
/// See `stdout()` for notes about this function.
|
|
|
|
pub fn stderr() -> StdWriter {
|
2013-11-20 16:17:12 -06:00
|
|
|
src(libc::STDERR_FILENO, false, |src| StdWriter { inner: src })
|
2013-10-07 15:25:06 -05:00
|
|
|
}
|
2013-04-17 19:55:21 -05:00
|
|
|
|
2014-01-06 12:26:11 -06:00
|
|
|
fn reset_helper(w: ~Writer,
|
|
|
|
f: |&mut Task, ~Writer| -> Option<~Writer>) -> Option<~Writer> {
|
|
|
|
let mut t = Local::borrow(None::<Task>);
|
|
|
|
// Be sure to flush any pending output from the writer
|
|
|
|
match f(t.get(), w) {
|
|
|
|
Some(mut w) => {
|
|
|
|
drop(t);
|
|
|
|
w.flush();
|
|
|
|
Some(w)
|
|
|
|
}
|
|
|
|
None => None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Resets the task-local stdout handle to the specified writer
|
|
|
|
///
|
|
|
|
/// This will replace the current task's stdout handle, returning the old
|
|
|
|
/// handle. All future calls to `print` and friends will emit their output to
|
|
|
|
/// this specified handle.
|
|
|
|
///
|
|
|
|
/// Note that this does not need to be called for all new tasks; the default
|
|
|
|
/// output handle is to the process's stdout stream.
|
|
|
|
pub fn set_stdout(stdout: ~Writer) -> Option<~Writer> {
|
|
|
|
reset_helper(stdout, |t, w| util::replace(&mut t.stdout, Some(w)))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Resets the task-local stderr handle to the specified writer
|
|
|
|
///
|
|
|
|
/// This will replace the current task's stderr handle, returning the old
|
|
|
|
/// handle. Currently, the stderr handle is used for printing failure messages
|
|
|
|
/// during task failure.
|
|
|
|
///
|
|
|
|
/// Note that this does not need to be called for all new tasks; the default
|
|
|
|
/// output handle is to the process's stderr stream.
|
|
|
|
pub fn set_stderr(stderr: ~Writer) -> Option<~Writer> {
|
|
|
|
reset_helper(stderr, |t, w| util::replace(&mut t.stderr, Some(w)))
|
|
|
|
}
|
|
|
|
|
2013-11-01 00:09:48 -05:00
|
|
|
// Helper to access the local task's stdout handle
|
|
|
|
//
|
|
|
|
// Note that this is not a safe function to expose because you can create an
|
|
|
|
// aliased pointer very easily:
|
|
|
|
//
|
2013-11-20 16:17:12 -06:00
|
|
|
// with_task_stdout(|io1| {
|
|
|
|
// with_task_stdout(|io2| {
|
2013-11-01 00:09:48 -05:00
|
|
|
// // io1 aliases io2
|
2013-11-20 16:17:12 -06:00
|
|
|
// })
|
|
|
|
// })
|
2013-11-18 23:15:42 -06:00
|
|
|
fn with_task_stdout(f: |&mut Writer|) {
|
2014-01-06 12:26:11 -06:00
|
|
|
let task: Option<~Task> = Local::try_take();
|
|
|
|
match task {
|
|
|
|
Some(mut task) => {
|
|
|
|
// Printing may run arbitrary code, so ensure that the task is in
|
|
|
|
// TLS to allow all std services. Note that this means a print while
|
|
|
|
// printing won't use the task's normal stdout handle, but this is
|
|
|
|
// necessary to ensure safety (no aliasing).
|
|
|
|
let mut my_stdout = task.stdout.take();
|
|
|
|
Local::put(task);
|
|
|
|
|
|
|
|
if my_stdout.is_none() {
|
|
|
|
my_stdout = Some(~LineBufferedWriter::new(stdout()) as ~Writer);
|
2013-11-12 16:38:28 -06:00
|
|
|
}
|
2014-01-06 12:26:11 -06:00
|
|
|
f(*my_stdout.get_mut_ref());
|
|
|
|
|
|
|
|
// Note that we need to be careful when putting the stdout handle
|
|
|
|
// back into the task. If the handle was set to `Some` while
|
|
|
|
// printing, then we can run aribitrary code when destroying the
|
|
|
|
// previous handle. This means that the local task needs to be in
|
|
|
|
// TLS while we do this.
|
|
|
|
//
|
|
|
|
// To protect against this, we do a little dance in which we
|
|
|
|
// temporarily take the task, swap the handles, put the task in TLS,
|
|
|
|
// and only then drop the previous handle.
|
|
|
|
let mut t = Local::borrow(None::<Task>);
|
|
|
|
let prev = util::replace(&mut t.get().stdout, my_stdout);
|
|
|
|
drop(t);
|
|
|
|
drop(prev);
|
|
|
|
}
|
2013-10-24 19:30:36 -05:00
|
|
|
|
2014-01-06 12:26:11 -06:00
|
|
|
None => {
|
|
|
|
struct Stdout;
|
|
|
|
impl Writer for Stdout {
|
|
|
|
fn write(&mut self, data: &[u8]) {
|
|
|
|
unsafe {
|
|
|
|
libc::write(libc::STDOUT_FILENO,
|
|
|
|
data.as_ptr() as *libc::c_void,
|
|
|
|
data.len() as libc::size_t);
|
2013-12-12 19:32:35 -06:00
|
|
|
}
|
|
|
|
}
|
2013-10-24 19:30:36 -05:00
|
|
|
}
|
2014-01-06 12:26:11 -06:00
|
|
|
let mut io = Stdout;
|
|
|
|
f(&mut io as &mut Writer);
|
2013-10-24 19:30:36 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-01 00:09:48 -05:00
|
|
|
/// Flushes the local task's stdout handle.
|
|
|
|
///
|
2013-11-04 17:45:46 -06:00
|
|
|
/// By default, this stream is a line-buffering stream, so flushing may be
|
|
|
|
/// necessary to ensure that all output is printed to the screen (if there are
|
|
|
|
/// no newlines printed).
|
2013-11-01 00:09:48 -05:00
|
|
|
///
|
|
|
|
/// Note that logging macros do not use this stream. Using the logging macros
|
|
|
|
/// will emit output to stderr, and while they are line buffered the log
|
|
|
|
/// messages are always terminated in a newline (no need to flush).
|
|
|
|
pub fn flush() {
|
2013-11-20 16:17:12 -06:00
|
|
|
with_task_stdout(|io| io.flush())
|
2013-11-01 00:09:48 -05:00
|
|
|
}
|
|
|
|
|
2013-10-07 15:25:06 -05:00
|
|
|
/// Prints a string to the stdout of the current process. No newline is emitted
|
|
|
|
/// after the string is printed.
|
|
|
|
pub fn print(s: &str) {
|
2013-11-20 16:17:12 -06:00
|
|
|
with_task_stdout(|io| io.write(s.as_bytes()))
|
2013-04-17 19:55:21 -05:00
|
|
|
}
|
|
|
|
|
2013-10-07 15:25:06 -05:00
|
|
|
/// Prints a string as a line. to the stdout of the current process. A literal
|
|
|
|
/// `\n` character is printed to the console after the string.
|
|
|
|
pub fn println(s: &str) {
|
2013-11-20 16:17:12 -06:00
|
|
|
with_task_stdout(|io| {
|
2013-10-24 19:30:36 -05:00
|
|
|
io.write(s.as_bytes());
|
|
|
|
io.write(['\n' as u8]);
|
2013-11-20 16:17:12 -06:00
|
|
|
})
|
2013-10-07 15:25:06 -05:00
|
|
|
}
|
2013-04-17 19:55:21 -05:00
|
|
|
|
2013-10-20 17:42:24 -05:00
|
|
|
/// Similar to `print`, but takes a `fmt::Arguments` structure to be compatible
|
|
|
|
/// with the `format_args!` macro.
|
|
|
|
pub fn print_args(fmt: &fmt::Arguments) {
|
2013-11-20 16:17:12 -06:00
|
|
|
with_task_stdout(|io| fmt::write(io, fmt))
|
2013-10-20 17:42:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Similar to `println`, but takes a `fmt::Arguments` structure to be
|
|
|
|
/// compatible with the `format_args!` macro.
|
|
|
|
pub fn println_args(fmt: &fmt::Arguments) {
|
2013-11-20 16:17:12 -06:00
|
|
|
with_task_stdout(|io| fmt::writeln(io, fmt))
|
2013-10-20 17:42:24 -05:00
|
|
|
}
|
|
|
|
|
2013-10-07 15:25:06 -05:00
|
|
|
/// Representation of a reader of a standard input stream
|
|
|
|
pub struct StdReader {
|
2013-10-22 11:36:30 -05:00
|
|
|
priv inner: StdSource
|
2013-04-17 19:55:21 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Reader for StdReader {
|
2013-10-07 15:25:06 -05:00
|
|
|
fn read(&mut self, buf: &mut [u8]) -> Option<uint> {
|
2013-10-22 11:36:30 -05:00
|
|
|
let ret = match self.inner {
|
|
|
|
TTY(ref mut tty) => tty.read(buf),
|
2013-10-31 17:09:24 -05:00
|
|
|
File(ref mut file) => file.read(buf).map(|i| i as uint),
|
2013-10-22 11:36:30 -05:00
|
|
|
};
|
|
|
|
match ret {
|
2013-11-09 13:02:16 -06:00
|
|
|
// When reading a piped stdin, libuv will return 0-length reads when
|
|
|
|
// stdin reaches EOF. For pretty much all other streams it will
|
|
|
|
// return an actual EOF error, but apparently for stdin it's a
|
|
|
|
// little different. Hence, here we convert a 0 length read to an
|
|
|
|
// end-of-file indicator so the caller knows to stop reading.
|
|
|
|
Ok(0) => {
|
|
|
|
io_error::cond.raise(standard_error(EndOfFile));
|
|
|
|
None
|
|
|
|
}
|
2013-12-24 17:53:05 -06:00
|
|
|
Ok(amt) => Some(amt),
|
2013-10-07 15:25:06 -05:00
|
|
|
Err(e) => {
|
|
|
|
io_error::cond.raise(e);
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-04-17 19:55:21 -05:00
|
|
|
}
|
|
|
|
|
2013-10-07 15:25:06 -05:00
|
|
|
/// Representation of a writer to a standard output stream
|
|
|
|
pub struct StdWriter {
|
2013-10-22 11:36:30 -05:00
|
|
|
priv inner: StdSource
|
2013-10-16 13:47:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl StdWriter {
|
|
|
|
/// Gets the size of this output window, if possible. This is typically used
|
|
|
|
/// when the writer is attached to something like a terminal, this is used
|
|
|
|
/// to fetch the dimensions of the terminal.
|
|
|
|
///
|
|
|
|
/// If successful, returns Some((width, height)).
|
|
|
|
///
|
|
|
|
/// # Failure
|
|
|
|
///
|
|
|
|
/// This function will raise on the `io_error` condition if an error
|
|
|
|
/// happens.
|
|
|
|
pub fn winsize(&mut self) -> Option<(int, int)> {
|
2013-10-22 11:36:30 -05:00
|
|
|
match self.inner {
|
|
|
|
TTY(ref mut tty) => {
|
|
|
|
match tty.get_winsize() {
|
|
|
|
Ok(p) => Some(p),
|
|
|
|
Err(e) => {
|
|
|
|
io_error::cond.raise(e);
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-11-28 14:22:53 -06:00
|
|
|
File(..) => {
|
2013-10-22 11:36:30 -05:00
|
|
|
io_error::cond.raise(IoError {
|
|
|
|
kind: OtherIoError,
|
|
|
|
desc: "stream is not a tty",
|
|
|
|
detail: None,
|
|
|
|
});
|
2013-10-16 13:47:12 -05:00
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Controls whether this output stream is a "raw stream" or simply a normal
|
|
|
|
/// stream.
|
|
|
|
///
|
|
|
|
/// # Failure
|
|
|
|
///
|
|
|
|
/// This function will raise on the `io_error` condition if an error
|
|
|
|
/// happens.
|
|
|
|
pub fn set_raw(&mut self, raw: bool) {
|
2013-10-22 11:36:30 -05:00
|
|
|
match self.inner {
|
|
|
|
TTY(ref mut tty) => {
|
|
|
|
match tty.set_raw(raw) {
|
|
|
|
Ok(()) => {},
|
|
|
|
Err(e) => io_error::cond.raise(e),
|
|
|
|
}
|
|
|
|
}
|
2013-11-28 14:22:53 -06:00
|
|
|
File(..) => {
|
2013-10-22 11:36:30 -05:00
|
|
|
io_error::cond.raise(IoError {
|
|
|
|
kind: OtherIoError,
|
|
|
|
desc: "stream is not a tty",
|
|
|
|
detail: None,
|
|
|
|
});
|
|
|
|
}
|
2013-10-16 13:47:12 -05:00
|
|
|
}
|
|
|
|
}
|
2013-10-17 19:04:51 -05:00
|
|
|
|
2013-11-07 17:24:30 -06:00
|
|
|
/// Returns whether this stream is attached to a TTY instance or not.
|
2013-10-22 11:36:30 -05:00
|
|
|
pub fn isatty(&self) -> bool {
|
|
|
|
match self.inner {
|
2013-11-28 14:22:53 -06:00
|
|
|
TTY(..) => true,
|
|
|
|
File(..) => false,
|
2013-10-22 11:36:30 -05:00
|
|
|
}
|
|
|
|
}
|
2013-04-17 19:55:21 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Writer for StdWriter {
|
2013-10-07 15:25:06 -05:00
|
|
|
fn write(&mut self, buf: &[u8]) {
|
2013-10-22 11:36:30 -05:00
|
|
|
let ret = match self.inner {
|
|
|
|
TTY(ref mut tty) => tty.write(buf),
|
|
|
|
File(ref mut file) => file.write(buf),
|
|
|
|
};
|
|
|
|
match ret {
|
2013-10-07 15:25:06 -05:00
|
|
|
Ok(()) => {}
|
|
|
|
Err(e) => io_error::cond.raise(e)
|
|
|
|
}
|
|
|
|
}
|
2013-10-16 13:47:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2013-12-12 23:38:57 -06:00
|
|
|
iotest!(fn smoke() {
|
2013-10-16 13:47:12 -05:00
|
|
|
// Just make sure we can acquire handles
|
|
|
|
stdin();
|
|
|
|
stdout();
|
|
|
|
stderr();
|
2013-12-12 23:38:57 -06:00
|
|
|
})
|
2014-01-06 12:26:11 -06:00
|
|
|
|
|
|
|
iotest!(fn capture_stdout() {
|
|
|
|
use io::comm_adapters::{PortReader, ChanWriter};
|
|
|
|
|
|
|
|
let (p, c) = Chan::new();
|
|
|
|
let (mut r, w) = (PortReader::new(p), ChanWriter::new(c));
|
|
|
|
do spawn {
|
|
|
|
set_stdout(~w as ~Writer);
|
|
|
|
println!("hello!");
|
|
|
|
}
|
|
|
|
assert_eq!(r.read_to_str(), ~"hello!\n");
|
|
|
|
})
|
|
|
|
|
|
|
|
iotest!(fn capture_stderr() {
|
|
|
|
use io::comm_adapters::{PortReader, ChanWriter};
|
|
|
|
|
|
|
|
let (p, c) = Chan::new();
|
|
|
|
let (mut r, w) = (PortReader::new(p), ChanWriter::new(c));
|
|
|
|
do spawn {
|
|
|
|
set_stderr(~w as ~Writer);
|
|
|
|
fail!("my special message");
|
|
|
|
}
|
|
|
|
let s = r.read_to_str();
|
|
|
|
assert!(s.contains("my special message"));
|
|
|
|
})
|
2013-04-17 19:55:21 -05:00
|
|
|
}
|