diff options
| author | Alex Crichton <alex@alexcrichton.com> | 2013-11-10 22:46:32 -0800 |
|---|---|---|
| committer | Alex Crichton <alex@alexcrichton.com> | 2013-11-11 20:44:07 -0800 |
| commit | 49ee49296b65f3d807142f3326bee71dd7e13290 (patch) | |
| tree | b3380df09c8a10473820969a62f5775832255fda /src/libstd/io | |
| parent | 8b4683d79d4b74f53808470cd2f98b23a0af9b93 (diff) | |
| download | rust-49ee49296b65f3d807142f3326bee71dd7e13290.tar.gz rust-49ee49296b65f3d807142f3326bee71dd7e13290.zip | |
Move std::rt::io to std::io
Diffstat (limited to 'src/libstd/io')
| -rw-r--r-- | src/libstd/io/buffered.rs | 473 | ||||
| -rw-r--r-- | src/libstd/io/comm_adapters.rs | 57 | ||||
| -rw-r--r-- | src/libstd/io/extensions.rs | 491 | ||||
| -rw-r--r-- | src/libstd/io/flate.rs | 123 | ||||
| -rw-r--r-- | src/libstd/io/fs.rs | 1289 | ||||
| -rw-r--r-- | src/libstd/io/mem.rs | 308 | ||||
| -rw-r--r-- | src/libstd/io/mod.rs | 1226 | ||||
| -rw-r--r-- | src/libstd/io/native/file.rs | 761 | ||||
| -rw-r--r-- | src/libstd/io/native/process.rs | 734 | ||||
| -rw-r--r-- | src/libstd/io/native/stdio.rs | 63 | ||||
| -rw-r--r-- | src/libstd/io/net/addrinfo.rs | 128 | ||||
| -rw-r--r-- | src/libstd/io/net/ip.rs | 449 | ||||
| -rw-r--r-- | src/libstd/io/net/mod.rs | 18 | ||||
| -rw-r--r-- | src/libstd/io/net/tcp.rs | 725 | ||||
| -rw-r--r-- | src/libstd/io/net/udp.rs | 321 | ||||
| -rw-r--r-- | src/libstd/io/net/unix.rs | 295 | ||||
| -rw-r--r-- | src/libstd/io/option.rs | 181 | ||||
| -rw-r--r-- | src/libstd/io/pipe.rs | 89 | ||||
| -rw-r--r-- | src/libstd/io/process.rs | 177 | ||||
| -rw-r--r-- | src/libstd/io/signal.rs | 224 | ||||
| -rw-r--r-- | src/libstd/io/stdio.rs | 321 | ||||
| -rw-r--r-- | src/libstd/io/timer.rs | 186 |
22 files changed, 8639 insertions, 0 deletions
diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs new file mode 100644 index 00000000000..4932f392090 --- /dev/null +++ b/src/libstd/io/buffered.rs @@ -0,0 +1,473 @@ +// 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. + +//! Buffering wrappers for I/O traits +//! +//! It can be excessively inefficient to work directly with a `Reader` or +//! `Writer`. Every call to `read` or `write` on `TcpStream` results in a +//! system call, for example. This module provides structures that wrap +//! `Readers`, `Writers`, and `Streams` and buffer input and output to them. +//! +//! # Examples +//! +//! ``` +//! let tcp_stream = TcpStream::connect(addr); +//! let reader = BufferedReader::new(tcp_stream); +//! +//! let mut buf: ~[u8] = vec::from_elem(100, 0u8); +//! match reader.read(buf.as_slice()) { +//! Some(nread) => println!("Read {} bytes", nread), +//! None => println!("At the end of the stream!") +//! } +//! ``` +//! +//! ``` +//! let tcp_stream = TcpStream::connect(addr); +//! let writer = BufferedWriter::new(tcp_stream); +//! +//! writer.write("hello, world".as_bytes()); +//! writer.flush(); +//! ``` +//! +//! ``` +//! let tcp_stream = TcpStream::connect(addr); +//! let stream = BufferedStream::new(tcp_stream); +//! +//! stream.write("hello, world".as_bytes()); +//! stream.flush(); +//! +//! let mut buf = vec::from_elem(100, 0u8); +//! match stream.read(buf.as_slice()) { +//! Some(nread) => println!("Read {} bytes", nread), +//! None => println!("At the end of the stream!") +//! } +//! ``` +//! + +use prelude::*; + +use num; +use vec; +use str; +use super::{Reader, Writer, Stream, Decorator}; + +// libuv recommends 64k buffers to maximize throughput +// https://groups.google.com/forum/#!topic/libuv/oQO1HJAIDdA +static DEFAULT_CAPACITY: uint = 64 * 1024; + +/// Wraps a Reader and buffers input from it +pub struct BufferedReader<R> { + priv inner: R, + priv buf: ~[u8], + priv pos: uint, + priv cap: uint +} + +impl<R: Reader> BufferedReader<R> { + /// Creates a new `BufferedReader` with with the specified buffer capacity + pub fn with_capacity(cap: uint, inner: R) -> BufferedReader<R> { + // It's *much* faster to create an uninitialized buffer than it is to + // fill everything in with 0. This buffer is entirely an implementation + // detail and is never exposed, so we're safe to not initialize + // everything up-front. This allows creation of BufferedReader instances + // to be very cheap (large mallocs are not nearly as expensive as large + // callocs). + let mut buf = vec::with_capacity(cap); + unsafe { vec::raw::set_len(&mut buf, cap); } + BufferedReader { + inner: inner, + buf: buf, + pos: 0, + cap: 0 + } + } + + /// Creates a new `BufferedReader` with a default buffer capacity + pub fn new(inner: R) -> BufferedReader<R> { + BufferedReader::with_capacity(DEFAULT_CAPACITY, inner) + } + + /// Reads the next line of input, interpreted as a sequence of utf-8 + /// encoded unicode codepoints. If a newline is encountered, then the + /// newline is contained in the returned string. + pub fn read_line(&mut self) -> Option<~str> { + self.read_until('\n' as u8).map(str::from_utf8_owned) + } + + /// Reads a sequence of bytes leading up to a specified delimeter. Once the + /// specified byte is encountered, reading ceases and the bytes up to and + /// including the delimiter are returned. + pub fn read_until(&mut self, byte: u8) -> Option<~[u8]> { + let mut res = ~[]; + let mut used; + loop { + { + let available = self.fill_buffer(); + match available.iter().position(|&b| b == byte) { + Some(i) => { + res.push_all(available.slice_to(i + 1)); + used = i + 1; + break + } + None => { + res.push_all(available); + used = available.len(); + } + } + } + if used == 0 { + break + } + self.pos += used; + } + self.pos += used; + return if res.len() == 0 {None} else {Some(res)}; + } + + fn fill_buffer<'a>(&'a mut self) -> &'a [u8] { + if self.pos == self.cap { + match self.inner.read(self.buf) { + Some(cap) => { + self.pos = 0; + self.cap = cap; + } + None => {} + } + } + return self.buf.slice(self.pos, self.cap); + } +} + +impl<R: Reader> Reader for BufferedReader<R> { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + let nread = { + let available = self.fill_buffer(); + if available.len() == 0 { + return None; + } + let nread = num::min(available.len(), buf.len()); + vec::bytes::copy_memory(buf, available, nread); + nread + }; + self.pos += nread; + Some(nread) + } + + fn eof(&mut self) -> bool { + self.pos == self.cap && self.inner.eof() + } +} + +impl<R: Reader> Decorator<R> for BufferedReader<R> { + fn inner(self) -> R { + self.inner + } + + fn inner_ref<'a>(&'a self) -> &'a R { + &self.inner + } + + fn inner_mut_ref<'a>(&'a mut self) -> &'a mut R { + &mut self.inner + } +} + +/// Wraps a Writer and buffers output to it +/// +/// Note that `BufferedWriter` will NOT flush its buffer when dropped. +pub struct BufferedWriter<W> { + priv inner: W, + priv buf: ~[u8], + priv pos: uint +} + +impl<W: Writer> BufferedWriter<W> { + /// Creates a new `BufferedWriter` with with the specified buffer capacity + pub fn with_capacity(cap: uint, inner: W) -> BufferedWriter<W> { + // See comments in BufferedReader for why this uses unsafe code. + let mut buf = vec::with_capacity(cap); + unsafe { vec::raw::set_len(&mut buf, cap); } + BufferedWriter { + inner: inner, + buf: buf, + pos: 0 + } + } + + /// Creates a new `BufferedWriter` with a default buffer capacity + pub fn new(inner: W) -> BufferedWriter<W> { + BufferedWriter::with_capacity(DEFAULT_CAPACITY, inner) + } +} + +impl<W: Writer> Writer for BufferedWriter<W> { + fn write(&mut self, buf: &[u8]) { + if self.pos + buf.len() > self.buf.len() { + self.flush(); + } + + if buf.len() > self.buf.len() { + self.inner.write(buf); + } else { + let dst = self.buf.mut_slice_from(self.pos); + vec::bytes::copy_memory(dst, buf, buf.len()); + self.pos += buf.len(); + } + } + + fn flush(&mut self) { + if self.pos != 0 { + self.inner.write(self.buf.slice_to(self.pos)); + self.pos = 0; + } + self.inner.flush(); + } +} + +impl<W: Writer> Decorator<W> for BufferedWriter<W> { + fn inner(self) -> W { self.inner } + fn inner_ref<'a>(&'a self) -> &'a W { &self.inner } + fn inner_mut_ref<'a>(&'a mut self) -> &'a mut W { &mut self.inner } +} + +/// Wraps a Writer and buffers output to it, flushing whenever a newline (0xa, +/// '\n') is detected. +/// +/// Note that this structure does NOT flush the output when dropped. +pub struct LineBufferedWriter<W> { + priv inner: BufferedWriter<W>, +} + +impl<W: Writer> LineBufferedWriter<W> { + /// Creates a new `LineBufferedWriter` + pub fn new(inner: W) -> LineBufferedWriter<W> { + // Lines typically aren't that long, don't use a giant buffer + LineBufferedWriter { + inner: BufferedWriter::with_capacity(1024, inner) + } + } +} + +impl<W: Writer> Writer for LineBufferedWriter<W> { + fn write(&mut self, buf: &[u8]) { + match buf.iter().position(|&b| b == '\n' as u8) { + Some(i) => { + self.inner.write(buf.slice_to(i + 1)); + self.inner.flush(); + self.inner.write(buf.slice_from(i + 1)); + } + None => self.inner.write(buf), + } + } + + fn flush(&mut self) { self.inner.flush() } +} + +impl<W: Writer> Decorator<W> for LineBufferedWriter<W> { + fn inner(self) -> W { self.inner.inner() } + fn inner_ref<'a>(&'a self) -> &'a W { self.inner.inner_ref() } + fn inner_mut_ref<'a>(&'a mut self) -> &'a mut W { self.inner.inner_mut_ref() } +} + +struct InternalBufferedWriter<W>(BufferedWriter<W>); + +impl<W: Reader> Reader for InternalBufferedWriter<W> { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + self.inner.read(buf) + } + + fn eof(&mut self) -> bool { + self.inner.eof() + } +} + +/// Wraps a Stream and buffers input and output to and from it +/// +/// Note that `BufferedStream` will NOT flush its output buffer when dropped. +pub struct BufferedStream<S> { + priv inner: BufferedReader<InternalBufferedWriter<S>> +} + +impl<S: Stream> BufferedStream<S> { + pub fn with_capacities(reader_cap: uint, writer_cap: uint, inner: S) + -> BufferedStream<S> { + let writer = BufferedWriter::with_capacity(writer_cap, inner); + let internal_writer = InternalBufferedWriter(writer); + let reader = BufferedReader::with_capacity(reader_cap, + internal_writer); + BufferedStream { inner: reader } + } + + pub fn new(inner: S) -> BufferedStream<S> { + BufferedStream::with_capacities(DEFAULT_CAPACITY, DEFAULT_CAPACITY, + inner) + } +} + +impl<S: Stream> Reader for BufferedStream<S> { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + self.inner.read(buf) + } + + fn eof(&mut self) -> bool { + self.inner.eof() + } +} + +impl<S: Stream> Writer for BufferedStream<S> { + fn write(&mut self, buf: &[u8]) { + self.inner.inner.write(buf) + } + + fn flush(&mut self) { + self.inner.inner.flush() + } +} + +impl<S: Stream> Decorator<S> for BufferedStream<S> { + fn inner(self) -> S { + self.inner.inner.inner() + } + + fn inner_ref<'a>(&'a self) -> &'a S { + self.inner.inner.inner_ref() + } + + fn inner_mut_ref<'a>(&'a mut self) -> &'a mut S { + self.inner.inner.inner_mut_ref() + } +} + +#[cfg(test)] +mod test { + use prelude::*; + use super::*; + use io; + use super::super::mem::{MemReader, MemWriter}; + + #[test] + fn test_buffered_reader() { + let inner = MemReader::new(~[0, 1, 2, 3, 4]); + let mut reader = BufferedReader::with_capacity(2, inner); + + let mut buf = [0, 0, 0]; + let nread = reader.read(buf); + assert_eq!(Some(2), nread); + assert_eq!([0, 1, 0], buf); + assert!(!reader.eof()); + + let mut buf = [0]; + let nread = reader.read(buf); + assert_eq!(Some(1), nread); + assert_eq!([2], buf); + assert!(!reader.eof()); + + let mut buf = [0, 0, 0]; + let nread = reader.read(buf); + assert_eq!(Some(1), nread); + assert_eq!([3, 0, 0], buf); + assert!(!reader.eof()); + + let nread = reader.read(buf); + assert_eq!(Some(1), nread); + assert_eq!([4, 0, 0], buf); + assert!(reader.eof()); + + assert_eq!(None, reader.read(buf)); + } + + #[test] + fn test_buffered_writer() { + let inner = MemWriter::new(); + let mut writer = BufferedWriter::with_capacity(2, inner); + + writer.write([0, 1]); + assert_eq!([], writer.inner_ref().inner_ref().as_slice()); + + writer.write([2]); + assert_eq!([0, 1], writer.inner_ref().inner_ref().as_slice()); + + writer.write([3]); + assert_eq!([0, 1], writer.inner_ref().inner_ref().as_slice()); + + writer.flush(); + assert_eq!([0, 1, 2, 3], writer.inner_ref().inner_ref().as_slice()); + + writer.write([4]); + writer.write([5]); + assert_eq!([0, 1, 2, 3], writer.inner_ref().inner_ref().as_slice()); + + writer.write([6]); + assert_eq!([0, 1, 2, 3, 4, 5], + writer.inner_ref().inner_ref().as_slice()); + + writer.write([7, 8]); + assert_eq!([0, 1, 2, 3, 4, 5, 6], + writer.inner_ref().inner_ref().as_slice()); + + writer.write([9, 10, 11]); + assert_eq!([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + writer.inner_ref().inner_ref().as_slice()); + + writer.flush(); + assert_eq!([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + writer.inner_ref().inner_ref().as_slice()); + } + + // This is just here to make sure that we don't infinite loop in the + // newtype struct autoderef weirdness + #[test] + fn test_buffered_stream() { + use rt; + struct S; + + impl io::Writer for S { + fn write(&mut self, _: &[u8]) {} + } + + impl io::Reader for S { + fn read(&mut self, _: &mut [u8]) -> Option<uint> { None } + fn eof(&mut self) -> bool { true } + } + + let mut stream = BufferedStream::new(S); + let mut buf = []; + stream.read(buf); + stream.eof(); + stream.write(buf); + stream.flush(); + } + + #[test] + fn test_read_until() { + let inner = MemReader::new(~[0, 1, 2, 1, 0]); + let mut reader = BufferedReader::with_capacity(2, inner); + assert_eq!(reader.read_until(0), Some(~[0])); + assert_eq!(reader.read_until(2), Some(~[1, 2])); + assert_eq!(reader.read_until(1), Some(~[1])); + assert_eq!(reader.read_until(8), Some(~[0])); + assert_eq!(reader.read_until(9), None); + } + + #[test] + fn test_line_buffer() { + let mut writer = LineBufferedWriter::new(MemWriter::new()); + writer.write([0]); + assert_eq!(*writer.inner_ref().inner_ref(), ~[]); + writer.write([1]); + assert_eq!(*writer.inner_ref().inner_ref(), ~[]); + writer.flush(); + assert_eq!(*writer.inner_ref().inner_ref(), ~[0, 1]); + writer.write([0, '\n' as u8, 1]); + assert_eq!(*writer.inner_ref().inner_ref(), ~[0, 1, 0, '\n' as u8]); + writer.flush(); + assert_eq!(*writer.inner_ref().inner_ref(), ~[0, 1, 0, '\n' as u8, 1]); + } +} diff --git a/src/libstd/io/comm_adapters.rs b/src/libstd/io/comm_adapters.rs new file mode 100644 index 00000000000..98dbec27fb9 --- /dev/null +++ b/src/libstd/io/comm_adapters.rs @@ -0,0 +1,57 @@ +// 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. + +use option::Option; +use comm::{GenericPort, GenericChan}; +use super::{Reader, Writer}; + +struct PortReader<P>; + +impl<P: GenericPort<~[u8]>> PortReader<P> { + pub fn new(_port: P) -> PortReader<P> { fail!() } +} + +impl<P: GenericPort<~[u8]>> Reader for PortReader<P> { + fn read(&mut self, _buf: &mut [u8]) -> Option<uint> { fail!() } + + fn eof(&mut self) -> bool { fail!() } +} + +struct ChanWriter<C>; + +impl<C: GenericChan<~[u8]>> ChanWriter<C> { + pub fn new(_chan: C) -> ChanWriter<C> { fail!() } +} + +impl<C: GenericChan<~[u8]>> Writer for ChanWriter<C> { + fn write(&mut self, _buf: &[u8]) { fail!() } +} + +struct ReaderPort<R>; + +impl<R: Reader> ReaderPort<R> { + pub fn new(_reader: R) -> ReaderPort<R> { fail!() } +} + +impl<R: Reader> GenericPort<~[u8]> for ReaderPort<R> { + fn recv(&self) -> ~[u8] { fail!() } + + fn try_recv(&self) -> Option<~[u8]> { fail!() } +} + +struct WriterChan<W>; + +impl<W: Writer> WriterChan<W> { + pub fn new(_writer: W) -> WriterChan<W> { fail!() } +} + +impl<W: Writer> GenericChan<~[u8]> for WriterChan<W> { + fn send(&self, _x: ~[u8]) { fail!() } +} diff --git a/src/libstd/io/extensions.rs b/src/libstd/io/extensions.rs new file mode 100644 index 00000000000..ebda2618dcf --- /dev/null +++ b/src/libstd/io/extensions.rs @@ -0,0 +1,491 @@ +// 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. + +//! Utility mixins that apply to all Readers and Writers + +// XXX: Not sure how this should be structured +// XXX: Iteration should probably be considered separately + +use iter::Iterator; +use option::Option; +use io::{Reader, Decorator}; + +/// An iterator that reads a single byte on each iteration, +/// until `.read_byte()` returns `None`. +/// +/// # Notes about the Iteration Protocol +/// +/// The `ByteIterator` may yield `None` and thus terminate +/// an iteration, but continue to yield elements if iteration +/// is attempted again. +/// +/// # Failure +/// +/// Raises the same conditions as the `read` method, for +/// each call to its `.next()` method. +/// Yields `None` if the condition is handled. +pub struct ByteIterator<T> { + priv reader: T, +} + +impl<R: Reader> ByteIterator<R> { + pub fn new(r: R) -> ByteIterator<R> { + ByteIterator { reader: r } + } +} + +impl<R> Decorator<R> for ByteIterator<R> { + fn inner(self) -> R { self.reader } + fn inner_ref<'a>(&'a self) -> &'a R { &self.reader } + fn inner_mut_ref<'a>(&'a mut self) -> &'a mut R { &mut self.reader } +} + +impl<'self, R: Reader> Iterator<u8> for ByteIterator<R> { + #[inline] + fn next(&mut self) -> Option<u8> { + self.reader.read_byte() + } +} + +pub fn u64_to_le_bytes<T>(n: u64, size: uint, + f: &fn(v: &[u8]) -> T) -> T { + assert!(size <= 8u); + match size { + 1u => f(&[n as u8]), + 2u => f(&[n as u8, + (n >> 8) as u8]), + 4u => f(&[n as u8, + (n >> 8) as u8, + (n >> 16) as u8, + (n >> 24) as u8]), + 8u => f(&[n as u8, + (n >> 8) as u8, + (n >> 16) as u8, + (n >> 24) as u8, + (n >> 32) as u8, + (n >> 40) as u8, + (n >> 48) as u8, + (n >> 56) as u8]), + _ => { + + let mut bytes: ~[u8] = ~[]; + let mut i = size; + let mut n = n; + while i > 0u { + bytes.push((n & 255_u64) as u8); + n >>= 8_u64; + i -= 1u; + } + f(bytes) + } + } +} + +pub fn u64_to_be_bytes<T>(n: u64, size: uint, + f: &fn(v: &[u8]) -> T) -> T { + assert!(size <= 8u); + match size { + 1u => f(&[n as u8]), + 2u => f(&[(n >> 8) as u8, + n as u8]), + 4u => f(&[(n >> 24) as u8, + (n >> 16) as u8, + (n >> 8) as u8, + n as u8]), + 8u => f(&[(n >> 56) as u8, + (n >> 48) as u8, + (n >> 40) as u8, + (n >> 32) as u8, + (n >> 24) as u8, + (n >> 16) as u8, + (n >> 8) as u8, + n as u8]), + _ => { + let mut bytes: ~[u8] = ~[]; + let mut i = size; + while i > 0u { + let shift = ((i - 1u) * 8u) as u64; + bytes.push((n >> shift) as u8); + i -= 1u; + } + f(bytes) + } + } +} + +pub fn u64_from_be_bytes(data: &[u8], + start: uint, + size: uint) + -> u64 { + let mut sz = size; + assert!((sz <= 8u)); + let mut val = 0_u64; + let mut pos = start; + while sz > 0u { + sz -= 1u; + val += (data[pos] as u64) << ((sz * 8u) as u64); + pos += 1u; + } + return val; +} + +#[cfg(test)] +mod test { + use option::{None, Option, Some}; + use io::mem::{MemReader, MemWriter}; + use io::{Reader, io_error, placeholder_error}; + use vec::ImmutableVector; + + struct InitialZeroByteReader { + count: int, + } + + impl Reader for InitialZeroByteReader { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + if self.count == 0 { + self.count = 1; + Some(0) + } else { + buf[0] = 10; + Some(1) + } + } + fn eof(&mut self) -> bool { + false + } + } + + struct EofReader; + + impl Reader for EofReader { + fn read(&mut self, _: &mut [u8]) -> Option<uint> { + None + } + fn eof(&mut self) -> bool { + false + } + } + + struct ErroringReader; + + impl Reader for ErroringReader { + fn read(&mut self, _: &mut [u8]) -> Option<uint> { + io_error::cond.raise(placeholder_error()); + None + } + fn eof(&mut self) -> bool { + false + } + } + + struct PartialReader { + count: int, + } + + impl Reader for PartialReader { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + if self.count == 0 { + self.count = 1; + buf[0] = 10; + buf[1] = 11; + Some(2) + } else { + buf[0] = 12; + buf[1] = 13; + Some(2) + } + } + fn eof(&mut self) -> bool { + false + } + } + + struct ErroringLaterReader { + count: int, + } + + impl Reader for ErroringLaterReader { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + if self.count == 0 { + self.count = 1; + buf[0] = 10; + Some(1) + } else { + io_error::cond.raise(placeholder_error()); + None + } + } + fn eof(&mut self) -> bool { + false + } + } + + struct ThreeChunkReader { + count: int, + } + + impl Reader for ThreeChunkReader { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + if self.count == 0 { + self.count = 1; + buf[0] = 10; + buf[1] = 11; + Some(2) + } else if self.count == 1 { + self.count = 2; + buf[0] = 12; + buf[1] = 13; + Some(2) + } else { + None + } + } + fn eof(&mut self) -> bool { + false + } + } + + #[test] + fn read_byte() { + let mut reader = MemReader::new(~[10]); + let byte = reader.read_byte(); + assert!(byte == Some(10)); + } + + #[test] + fn read_byte_0_bytes() { + let mut reader = InitialZeroByteReader { + count: 0, + }; + let byte = reader.read_byte(); + assert!(byte == Some(10)); + } + + #[test] + fn read_byte_eof() { + let mut reader = EofReader; + let byte = reader.read_byte(); + assert!(byte == None); + } + + #[test] + fn read_byte_error() { + let mut reader = ErroringReader; + do io_error::cond.trap(|_| { + }).inside { + let byte = reader.read_byte(); + assert!(byte == None); + } + } + + #[test] + fn bytes_0_bytes() { + let reader = InitialZeroByteReader { + count: 0, + }; + let byte = reader.bytes().next(); + assert!(byte == Some(10)); + } + + #[test] + fn bytes_eof() { + let reader = EofReader; + let byte = reader.bytes().next(); + assert!(byte == None); + } + + #[test] + fn bytes_error() { + let reader = ErroringReader; + let mut it = reader.bytes(); + do io_error::cond.trap(|_| ()).inside { + let byte = it.next(); + assert!(byte == None); + } + } + + #[test] + fn read_bytes() { + let mut reader = MemReader::new(~[10, 11, 12, 13]); + let bytes = reader.read_bytes(4); + assert!(bytes == ~[10, 11, 12, 13]); + } + + #[test] + fn read_bytes_partial() { + let mut reader = PartialReader { + count: 0, + }; + let bytes = reader.read_bytes(4); + assert!(bytes == ~[10, 11, 12, 13]); + } + + #[test] + fn read_bytes_eof() { + let mut reader = MemReader::new(~[10, 11]); + do io_error::cond.trap(|_| { + }).inside { + assert!(reader.read_bytes(4) == ~[10, 11]); + } + } + + #[test] + fn push_bytes() { + let mut reader = MemReader::new(~[10, 11, 12, 13]); + let mut buf = ~[8, 9]; + reader.push_bytes(&mut buf, 4); + assert!(buf == ~[8, 9, 10, 11, 12, 13]); + } + + #[test] + fn push_bytes_partial() { + let mut reader = PartialReader { + count: 0, + }; + let mut buf = ~[8, 9]; + reader.push_bytes(&mut buf, 4); + assert!(buf == ~[8, 9, 10, 11, 12, 13]); + } + + #[test] + fn push_bytes_eof() { + let mut reader = MemReader::new(~[10, 11]); + let mut buf = ~[8, 9]; + do io_error::cond.trap(|_| { + }).inside { + reader.push_bytes(&mut buf, 4); + assert!(buf == ~[8, 9, 10, 11]); + } + } + + #[test] + fn push_bytes_error() { + let mut reader = ErroringLaterReader { + count: 0, + }; + let mut buf = ~[8, 9]; + do io_error::cond.trap(|_| { } ).inside { + reader.push_bytes(&mut buf, 4); + } + assert!(buf == ~[8, 9, 10]); + } + + #[test] + #[should_fail] + fn push_bytes_fail_reset_len() { + // push_bytes unsafely sets the vector length. This is testing that + // upon failure the length is reset correctly. + let mut reader = ErroringLaterReader { + count: 0, + }; + let buf = @mut ~[8, 9]; + do (|| { + reader.push_bytes(&mut *buf, 4); + }).finally { + // NB: Using rtassert here to trigger abort on failure since this is a should_fail test + // FIXME: #7049 This fails because buf is still borrowed + //rtassert!(*buf == ~[8, 9, 10]); + } + } + + #[test] + fn read_to_end() { + let mut reader = ThreeChunkReader { + count: 0, + }; + let buf = reader.read_to_end(); + assert!(buf == ~[10, 11, 12, 13]); + } + + #[test] + #[should_fail] + fn read_to_end_error() { + let mut reader = ThreeChunkReader { + count: 0, + }; + let buf = reader.read_to_end(); + assert!(buf == ~[10, 11]); + } + + #[test] + fn test_read_write_le_mem() { + let uints = [0, 1, 2, 42, 10_123, 100_123_456, ::u64::max_value]; + + let mut writer = MemWriter::new(); + for i in uints.iter() { + writer.write_le_u64(*i); + } + + let mut reader = MemReader::new(writer.inner()); + for i in uints.iter() { + assert!(reader.read_le_u64() == *i); + } + } + + + #[test] + fn test_read_write_be() { + let uints = [0, 1, 2, 42, 10_123, 100_123_456, ::u64::max_value]; + + let mut writer = MemWriter::new(); + for i in uints.iter() { + writer.write_be_u64(*i); + } + + let mut reader = MemReader::new(writer.inner()); + for i in uints.iter() { + assert!(reader.read_be_u64() == *i); + } + } + + #[test] + fn test_read_be_int_n() { + let ints = [::i32::min_value, -123456, -42, -5, 0, 1, ::i32::max_value]; + + let mut writer = MemWriter::new(); + for i in ints.iter() { + writer.write_be_i32(*i); + } + + let mut reader = MemReader::new(writer.inner()); + for i in ints.iter() { + // this tests that the sign extension is working + // (comparing the values as i32 would not test this) + assert!(reader.read_be_int_n(4) == *i as i64); + } + } + + #[test] + fn test_read_f32() { + //big-endian floating-point 8.1250 + let buf = ~[0x41, 0x02, 0x00, 0x00]; + + let mut writer = MemWriter::new(); + writer.write(buf); + + let mut reader = MemReader::new(writer.inner()); + let f = reader.read_be_f32(); + assert!(f == 8.1250); + } + + #[test] + fn test_read_write_f32() { + let f:f32 = 8.1250; + + let mut writer = MemWriter::new(); + writer.write_be_f32(f); + writer.write_le_f32(f); + + let mut reader = MemReader::new(writer.inner()); + assert!(reader.read_be_f32() == 8.1250); + assert!(reader.read_le_f32() == 8.1250); + } + +} diff --git a/src/libstd/io/flate.rs b/src/libstd/io/flate.rs new file mode 100644 index 00000000000..8a5aa171eb8 --- /dev/null +++ b/src/libstd/io/flate.rs @@ -0,0 +1,123 @@ +// 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. + +//! Some various other I/O types + +// FIXME(#3660): should move to libextra + +use prelude::*; +use super::*; + +/// A Writer decorator that compresses using the 'deflate' scheme +pub struct DeflateWriter<W> { + priv inner_writer: W +} + +impl<W: Writer> DeflateWriter<W> { + pub fn new(inner_writer: W) -> DeflateWriter<W> { + DeflateWriter { + inner_writer: inner_writer + } + } +} + +impl<W: Writer> Writer for DeflateWriter<W> { + fn write(&mut self, _buf: &[u8]) { fail!() } + + fn flush(&mut self) { fail!() } +} + +impl<W: Writer> Decorator<W> for DeflateWriter<W> { + fn inner(self) -> W { + match self { + DeflateWriter { inner_writer: w } => w + } + } + + fn inner_ref<'a>(&'a self) -> &'a W { + match *self { + DeflateWriter { inner_writer: ref w } => w + } + } + + fn inner_mut_ref<'a>(&'a mut self) -> &'a mut W { + match *self { + DeflateWriter { inner_writer: ref mut w } => w + } + } +} + +/// A Reader decorator that decompresses using the 'deflate' scheme +pub struct InflateReader<R> { + priv inner_reader: R +} + +impl<R: Reader> InflateReader<R> { + pub fn new(inner_reader: R) -> InflateReader<R> { + InflateReader { + inner_reader: inner_reader + } + } +} + +impl<R: Reader> Reader for InflateReader<R> { + fn read(&mut self, _buf: &mut [u8]) -> Option<uint> { fail!() } + + fn eof(&mut self) -> bool { fail!() } +} + +impl<R: Reader> Decorator<R> for InflateReader<R> { + fn inner(self) -> R { + match self { + InflateReader { inner_reader: r } => r + } + } + + fn inner_ref<'a>(&'a self) -> &'a R { + match *self { + InflateReader { inner_reader: ref r } => r + } + } + + fn inner_mut_ref<'a>(&'a mut self) -> &'a mut R { + match *self { + InflateReader { inner_reader: ref mut r } => r + } + } +} + +#[cfg(test)] +mod test { + use prelude::*; + use super::*; + use super::super::mem::*; + use super::super::Decorator; + + use str; + + #[test] + #[ignore] + fn smoke_test() { + let mem_writer = MemWriter::new(); + let mut deflate_writer = DeflateWriter::new(mem_writer); + let in_msg = "test"; + let in_bytes = in_msg.as_bytes(); + deflate_writer.write(in_bytes); + deflate_writer.flush(); + let buf = deflate_writer.inner().inner(); + let mem_reader = MemReader::new(buf); + let mut inflate_reader = InflateReader::new(mem_reader); + let mut out_bytes = [0, .. 100]; + let bytes_read = inflate_reader.read(out_bytes).unwrap(); + assert_eq!(bytes_read, in_bytes.len()); + let out_msg = str::from_utf8(out_bytes); + assert!(in_msg == out_msg); + } +} diff --git a/src/libstd/io/fs.rs b/src/libstd/io/fs.rs new file mode 100644 index 00000000000..d5514eb14d3 --- /dev/null +++ b/src/libstd/io/fs.rs @@ -0,0 +1,1289 @@ +// 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. + +/*! Synchronous File I/O + +This module provides a set of functions and traits for working +with regular files & directories on a filesystem. + +At the top-level of the module are a set of freestanding functions, associated +with various filesystem operations. They all operate on a `Path` object. + +All operations in this module, including those as part of `File` et al +block the task during execution. Most will raise `std::io::io_error` +conditions in the event of failure. + +Also included in this module is an implementation block on the `Path` object +defined in `std::path::Path`. The impl adds useful methods about inspecting the +metadata of a file. This includes getting the `stat` information, reading off +particular bits of it, etc. + +# Example + + use std::io::{File, fs}; + + let path = Path::new("foo.txt"); + + // create the file, whether it exists or not + let mut file = File::create(&path); + file.write(bytes!("foobar")); + + // open the file in read-only mode + let mut file = File::open(&path); + file.read_to_end(); + + println!("{}", path.stat().size); + fs::symlink(&path, &Path::new("bar.txt")); + fs::unlink(&path); + +*/ + +use c_str::ToCStr; +use clone::Clone; +use iter::Iterator; +use super::{Reader, Writer, Seek}; +use super::{SeekStyle, Read, Write, Open, IoError, Truncate, + FileMode, FileAccess, FileStat, io_error, FilePermission}; +use rt::rtio::{RtioFileStream, IoFactory, with_local_io}; +use io; +use option::{Some, None, Option}; +use result::{Ok, Err, Result}; +use path; +use path::{Path, GenericPath}; +use vec::{OwnedVector, ImmutableVector}; + +/// Unconstrained file access type that exposes read and write operations +/// +/// Can be constructed via `File::open()`, `File::create()`, and +/// `File::open_mode()`. +/// +/// # Errors +/// +/// This type will raise an io_error condition if operations are attempted against +/// it for which its underlying file descriptor was not configured at creation +/// time, via the `FileAccess` parameter to `File::open_mode()`. +pub struct File { + priv fd: ~RtioFileStream, + priv path: Path, + priv last_nread: int, +} + +fn io_raise<T>(f: &fn(io: &mut IoFactory) -> Result<T, IoError>) -> Option<T> { + do with_local_io |io| { + match f(io) { + Ok(t) => Some(t), + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + } +} + +impl File { + /// Open a file at `path` in the mode specified by the `mode` and `access` + /// arguments + /// + /// # Example + /// + /// use std::io::{File, io_error, Open, ReadWrite}; + /// + /// let p = Path::new("/some/file/path.txt"); + /// + /// do io_error::cond.trap(|_| { + /// // hoo-boy... + /// }).inside { + /// let file = match File::open_mode(&p, Open, ReadWrite) { + /// Some(s) => s, + /// None => fail!("whoops! I'm sure this raised, anyways..") + /// }; + /// // do some stuff with that file + /// + /// // the file will be closed at the end of this block + /// } + /// // .. + /// + /// `FileMode` and `FileAccess` provide information about the permissions + /// context in which a given stream is created. More information about them + /// can be found in `std::io`'s docs. If a file is opened with `Write` + /// or `ReadWrite` access, then it will be created it it does not already + /// exist. + /// + /// Note that, with this function, a `File` is returned regardless of the + /// access-limitations indicated by `FileAccess` (e.g. calling `write` on a + /// `File` opened as `Read` will raise an `io_error` condition at runtime). + /// + /// # Errors + /// + /// This function will raise an `io_error` condition under a number of + /// different circumstances, to include but not limited to: + /// + /// * Opening a file that does not exist with `Read` access. + /// * Attempting to open a file with a `FileAccess` that the user lacks + /// permissions for + /// * Filesystem-level errors (full disk, etc) + pub fn open_mode(path: &Path, + mode: FileMode, + access: FileAccess) -> Option<File> { + do with_local_io |io| { + match io.fs_open(&path.to_c_str(), mode, access) { + Ok(fd) => Some(File { + path: path.clone(), + fd: fd, + last_nread: -1 + }), + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + } + } + + /// Attempts to open a file in read-only mode. This function is equivalent to + /// `File::open_mode(path, Open, Read)`, and will raise all of the same + /// errors that `File::open_mode` does. + /// + /// For more information, see the `File::open_mode` function. + /// + /// # Example + /// + /// use std::io::File; + /// + /// let contents = File::open(&Path::new("foo.txt")).read_to_end(); + pub fn open(path: &Path) -> Option<File> { + File::open_mode(path, Open, Read) + } + + /// Attempts to create a file in write-only mode. This function is + /// equivalent to `File::open_mode(path, Truncate, Write)`, and will + /// raise all of the same errors that `File::open_mode` does. + /// + /// For more information, see the `File::open_mode` function. + /// + /// # Example + /// + /// use std::io::File; + /// + /// let mut f = File::create(&Path::new("foo.txt")); + /// f.write(bytes!("This is a sample file")); + pub fn create(path: &Path) -> Option<File> { + File::open_mode(path, Truncate, Write) + } + + /// Returns the original path which was used to open this file. + pub fn path<'a>(&'a self) -> &'a Path { + &self.path + } + + /// Synchronizes all modifications to this file to its permanent storage + /// device. This will flush any internal buffers necessary to perform this + /// operation. + /// + /// # Errors + /// + /// This function will raise on the `io_error` condition on failure. + pub fn fsync(&mut self) { + self.fd.fsync(); + } + + /// This function is similar to `fsync`, except that it may not synchronize + /// file metadata to the filesystem. This is intended for use case which + /// must synchronize content, but don't need the metadata on disk. The goal + /// of this method is to reduce disk operations. + /// + /// # Errors + /// + /// This function will raise on the `io_error` condition on failure. + pub fn datasync(&mut self) { + self.fd.datasync(); + } + + /// Either truncates or extends the underlying file, as extended from the + /// file's current position. This is equivalent to the unix `truncate` + /// function. + /// + /// The offset given is added to the file's current position and the result + /// is the new size of the file. If the new size is less than the current + /// size, then the file is truncated. If the new size is greater than the + /// current size, then the file is expanded to be filled with 0s. + /// + /// # Errors + /// + /// On error, this function will raise on the `io_error` condition. + pub fn truncate(&mut self, offset: i64) { + self.fd.truncate(offset); + } +} + +/// Unlink a file from the underlying filesystem. +/// +/// # Example +/// +/// use std::io::fs; +/// +/// let p = Path::new("/some/file/path.txt"); +/// fs::unlink(&p); +/// // if we made it here without failing, then the +/// // unlink operation was successful +/// +/// Note that, just because an unlink call was successful, it is not +/// guaranteed that a file is immediately deleted (e.g. depending on +/// platform, other open file descriptors may prevent immediate removal) +/// +/// # Errors +/// +/// This function will raise an `io_error` condition if the path points to a +/// directory, the user lacks permissions to remove the file, or if some +/// other filesystem-level error occurs. +pub fn unlink(path: &Path) { + do io_raise |io| { io.fs_unlink(&path.to_c_str()) }; +} + +/// Given a path, query the file system to get information about a file, +/// directory, etc. This function will traverse symlinks to query +/// information about the destination file. +/// +/// Returns a fully-filled out stat structure on succes, and on failure it +/// will return a dummy stat structure (it is expected that the condition +/// raised is handled as well). +/// +/// # Example +/// +/// use std::io; +/// use std::io::fs; +/// +/// let p = Path::new("/some/file/path.txt"); +/// match io::result(|| fs::stat(&p)) { +/// Ok(stat) => { /* ... */ } +/// Err(e) => { /* handle error */ } +/// } +/// +/// # Errors +/// +/// This call will raise an `io_error` condition if the user lacks the +/// requisite permissions to perform a `stat` call on the given path or if +/// there is no entry in the filesystem at the provided path. +pub fn stat(path: &Path) -> FileStat { + do io_raise |io| { + io.fs_stat(&path.to_c_str()) + }.unwrap_or_else(dummystat) +} + +fn dummystat() -> FileStat { + FileStat { + path: Path::new(""), + size: 0, + kind: io::TypeFile, + perm: 0, + created: 0, + modified: 0, + accessed: 0, + unstable: io::UnstableFileStat { + device: 0, + inode: 0, + rdev: 0, + nlink: 0, + uid: 0, + gid: 0, + blksize: 0, + blocks: 0, + flags: 0, + gen: 0, + } + } +} + +/// Perform the same operation as the `stat` function, except that this +/// function does not traverse through symlinks. This will return +/// information about the symlink file instead of the file that it points +/// to. +/// +/// # Errors +/// +/// See `stat` +pub fn lstat(path: &Path) -> FileStat { + do io_raise |io| { + io.fs_lstat(&path.to_c_str()) + }.unwrap_or_else(dummystat) +} + +/// Rename a file or directory to a new name. +/// +/// # Example +/// +/// use std::io::fs; +/// +/// fs::rename(&Path::new("foo"), &Path::new("bar")); +/// // Oh boy, nothing was raised! +/// +/// # Errors +/// +/// Will raise an `io_error` condition if the provided `path` doesn't exist, +/// the process lacks permissions to view the contents, or if some other +/// intermittent I/O error occurs. +pub fn rename(from: &Path, to: &Path) { + do io_raise |io| { + io.fs_rename(&from.to_c_str(), &to.to_c_str()) + }; +} + +/// Copies the contents of one file to another. This function will also +/// copy the permission bits of the original file to the destination file. +/// +/// Note that if `from` and `to` both point to the same file, then the file +/// will likely get truncated by this operation. +/// +/// # Example +/// +/// use std::io::fs; +/// +/// fs::copy(&Path::new("foo.txt"), &Path::new("bar.txt")); +/// // Oh boy, nothing was raised! +/// +/// # Errors +/// +/// Will raise an `io_error` condition is the following situtations, but is +/// not limited to just these cases: +/// +/// * The `from` path is not a file +/// * The `from` file does not exist +/// * The current process does not have the permission rights to access +/// `from` or write `to` +/// +/// Note that this copy is not atomic in that once the destination is +/// ensured to not exist, there is nothing preventing the destination from +/// being created and then destroyed by this operation. +pub fn copy(from: &Path, to: &Path) { + if !from.is_file() { + return io_error::cond.raise(IoError { + kind: io::MismatchedFileTypeForOperation, + desc: "the source path is not an existing file", + detail: None, + }); + } + + let mut reader = match File::open(from) { Some(f) => f, None => return }; + let mut writer = match File::create(to) { Some(f) => f, None => return }; + let mut buf = [0, ..io::DEFAULT_BUF_SIZE]; + + loop { + match reader.read(buf) { + Some(amt) => writer.write(buf.slice_to(amt)), + None => break + } + } + + chmod(to, from.stat().perm) +} + +/// Changes the permission mode bits found on a file or a directory. This +/// function takes a mask from the `io` module +/// +/// # Example +/// +/// use std::io; +/// use std::io::fs; +/// +/// fs::chmod(&Path::new("file.txt"), io::UserFile); +/// fs::chmod(&Path::new("file.txt"), io::UserRead | io::UserWrite); +/// fs::chmod(&Path::new("dir"), io::UserDir); +/// fs::chmod(&Path::new("file.exe"), io::UserExec); +/// +/// # Errors +/// +/// If this funciton encounters an I/O error, it will raise on the `io_error` +/// condition. Some possible error situations are not having the permission to +/// change the attributes of a file or the file not existing. +pub fn chmod(path: &Path, mode: io::FilePermission) { + do io_raise |io| { + io.fs_chmod(&path.to_c_str(), mode) + }; +} + +/// Change the user and group owners of a file at the specified path. +/// +/// # Errors +/// +/// This funtion will raise on the `io_error` condition on failure. +pub fn chown(path: &Path, uid: int, gid: int) { + do io_raise |io| { io.fs_chown(&path.to_c_str(), uid, gid) }; +} + +/// Creates a new hard link on the filesystem. The `dst` path will be a +/// link pointing to the `src` path. Note that systems often require these +/// two paths to both be located on the same filesystem. +/// +/// # Errors +/// +/// This function will raise on the `io_error` condition on failure. +pub fn link(src: &Path, dst: &Path) { + do io_raise |io| { io.fs_link(&src.to_c_str(), &dst.to_c_str()) }; +} + +/// Creates a new symbolic link on the filesystem. The `dst` path will be a +/// symlink pointing to the `src` path. +/// +/// # Errors +/// +/// This function will raise on the `io_error` condition on failure. +pub fn symlink(src: &Path, dst: &Path) { + do io_raise |io| { io.fs_symlink(&src.to_c_str(), &dst.to_c_str()) }; +} + +/// Reads a symlink, returning the file that the symlink points to. +/// +/// # Errors +/// +/// This function will raise on the `io_error` condition on failure. Failure +/// conditions include reading a file that does not exist or reading a file +/// which is not a symlink. +pub fn readlink(path: &Path) -> Option<Path> { + do io_raise |io| { io.fs_readlink(&path.to_c_str()) } +} + +/// Create a new, empty directory at the provided path +/// +/// # Example +/// +/// use std::libc::S_IRWXU; +/// use std::io::fs; +/// +/// let p = Path::new("/some/dir"); +/// fs::mkdir(&p, S_IRWXU as int); +/// // If we got here, our directory exists! Horray! +/// +/// # Errors +/// +/// This call will raise an `io_error` condition if the user lacks permissions +/// to make a new directory at the provided path, or if the directory already +/// exists. +pub fn mkdir(path: &Path, mode: FilePermission) { + do io_raise |io| { + io.fs_mkdir(&path.to_c_str(), mode) + }; +} + +/// Remove an existing, empty directory +/// +/// # Example +/// +/// use std::io::fs; +/// +/// let p = Path::new("/some/dir"); +/// fs::rmdir(&p); +/// // good riddance, you mean ol' directory +/// +/// # Errors +/// +/// This call will raise an `io_error` condition if the user lacks permissions +/// to remove the directory at the provided path, or if the directory isn't +/// empty. +pub fn rmdir(path: &Path) { + do io_raise |io| { + io.fs_rmdir(&path.to_c_str()) + }; +} + +/// Retrieve a vector containing all entries within a provided directory +/// +/// # Example +/// +/// use std::io::fs; +/// +/// // one possible implementation of fs::walk_dir only visiting files +/// fn visit_dirs(dir: &Path, cb: &fn(&Path)) { +/// if dir.is_dir() { +/// let contents = fs::readdir(dir).unwrap(); +/// for entry in contents.iter() { +/// if entry.is_dir() { visit_dirs(entry, cb); } +/// else { cb(entry); } +/// } +/// } +/// else { fail!("nope"); } +/// } +/// +/// # Errors +/// +/// Will raise an `io_error` condition if the provided `from` doesn't exist, +/// the process lacks permissions to view the contents or if the `path` points +/// at a non-directory file +pub fn readdir(path: &Path) -> ~[Path] { + do io_raise |io| { + io.fs_readdir(&path.to_c_str(), 0) + }.unwrap_or_else(|| ~[]) +} + +/// Returns an iterator which will recursively walk the directory structure +/// rooted at `path`. The path given will not be iterated over, and this will +/// perform iteration in a top-down order. +pub fn walk_dir(path: &Path) -> WalkIterator { + WalkIterator { stack: readdir(path) } +} + +/// An iterator which walks over a directory +pub struct WalkIterator { + priv stack: ~[Path], +} + +impl Iterator<Path> for WalkIterator { + fn next(&mut self) -> Option<Path> { + match self.stack.shift_opt() { + Some(path) => { + if path.is_dir() { + self.stack.push_all_move(readdir(&path)); + } + Some(path) + } + None => None + } + } +} + +/// Recursively create a directory and all of its parent components if they +/// are missing. +/// +/// # Errors +/// +/// This function will raise on the `io_error` condition if an error +/// happens, see `fs::mkdir` for more information about error conditions +/// and performance. +pub fn mkdir_recursive(path: &Path, mode: FilePermission) { + // tjc: if directory exists but with different permissions, + // should we return false? + if path.is_dir() { + return + } + if path.filename().is_some() { + mkdir_recursive(&path.dir_path(), mode); + } + mkdir(path, mode) +} + +/// Removes a directory at this path, after removing all its contents. Use +/// carefully! +/// +/// # Errors +/// +/// This function will raise on the `io_error` condition if an error +/// happens. See `file::unlink` and `fs::readdir` for possible error +/// conditions. +pub fn rmdir_recursive(path: &Path) { + let children = readdir(path); + for child in children.iter() { + if child.is_dir() { + rmdir_recursive(child); + } else { + unlink(child); + } + } + // Directory should now be empty + rmdir(path); +} + +/// Changes the timestamps for a file's last modification and access time. +/// The file at the path specified will have its last access time set to +/// `atime` and its modification time set to `mtime`. The times specified should +/// be in milliseconds. +/// +/// # Errors +/// +/// This function will raise on the `io_error` condition if an error +/// happens. +// FIXME(#10301) these arguments should not be u64 +pub fn change_file_times(path: &Path, atime: u64, mtime: u64) { + do io_raise |io| { + io.fs_utime(&path.to_c_str(), atime, mtime) + }; +} + +impl Reader for File { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + match self.fd.read(buf) { + Ok(read) => { + self.last_nread = read; + match read { + 0 => None, + _ => Some(read as uint) + } + }, + Err(ioerr) => { + // EOF is indicated by returning None + if ioerr.kind != io::EndOfFile { + io_error::cond.raise(ioerr); + } + return None; + } + } + } + + fn eof(&mut self) -> bool { self.last_nread == 0 } +} + +impl Writer for File { + fn write(&mut self, buf: &[u8]) { + match self.fd.write(buf) { + Ok(()) => (), + Err(ioerr) => { + io_error::cond.raise(ioerr); + } + } + } +} + +impl Seek for File { + fn tell(&self) -> u64 { + let res = self.fd.tell(); + match res { + Ok(cursor) => cursor, + Err(ioerr) => { + io_error::cond.raise(ioerr); + return -1; + } + } + } + + fn seek(&mut self, pos: i64, style: SeekStyle) { + match self.fd.seek(pos, style) { + Ok(_) => { + // successful seek resets EOF indicator + self.last_nread = -1; + () + }, + Err(ioerr) => { + io_error::cond.raise(ioerr); + } + } + } +} + +impl path::Path { + /// Get information on the file, directory, etc at this path. + /// + /// Consult the `file::stat` documentation for more info. + /// + /// This call preserves identical runtime/error semantics with `file::stat`. + pub fn stat(&self) -> FileStat { stat(self) } + + /// Boolean value indicator whether the underlying file exists on the local + /// filesystem. This will return true if the path points to either a + /// directory or a file. + /// + /// # Errors + /// + /// Will not raise a condition + pub fn exists(&self) -> bool { + io::result(|| self.stat()).is_ok() + } + + /// Whether the underlying implemention (be it a file path, or something + /// else) points at a "regular file" on the FS. Will return false for paths + /// to non-existent locations or directories or other non-regular files + /// (named pipes, etc). + /// + /// # Errors + /// + /// Will not raise a condition + pub fn is_file(&self) -> bool { + match io::result(|| self.stat()) { + Ok(s) => s.kind == io::TypeFile, + Err(*) => false + } + } + + /// Whether the underlying implemention (be it a file path, + /// or something else) is pointing at a directory in the underlying FS. + /// Will return false for paths to non-existent locations or if the item is + /// not a directory (eg files, named pipes, links, etc) + /// + /// # Errors + /// + /// Will not raise a condition + pub fn is_dir(&self) -> bool { + match io::result(|| self.stat()) { + Ok(s) => s.kind == io::TypeDirectory, + Err(*) => false + } + } +} + +#[cfg(test)] +mod test { + use prelude::*; + use io::{SeekSet, SeekCur, SeekEnd, io_error, Read, Open, ReadWrite}; + use io; + use str; + use super::{File, rmdir, mkdir, readdir, rmdir_recursive, mkdir_recursive, + copy, unlink, stat, symlink, link, readlink, chmod, + lstat, change_file_times}; + + fn tmpdir() -> Path { + use os; + use rand; + let ret = os::tmpdir().join(format!("rust-{}", rand::random::<u32>())); + mkdir(&ret, io::UserRWX); + ret + } + + fn free<T>(_: T) {} + + #[test] + fn file_test_io_smoke_test() { + let message = "it's alright. have a good time"; + let filename = &Path::new("./tmp/file_rt_io_file_test.txt"); + { + let mut write_stream = File::open_mode(filename, Open, ReadWrite); + write_stream.write(message.as_bytes()); + } + { + let mut read_stream = File::open_mode(filename, Open, Read); + let mut read_buf = [0, .. 1028]; + let read_str = match read_stream.read(read_buf).unwrap() { + -1|0 => fail!("shouldn't happen"), + n => str::from_utf8(read_buf.slice_to(n)) + }; + assert!(read_str == message.to_owned()); + } + unlink(filename); + } + + #[test] + fn file_test_io_invalid_path_opened_without_create_should_raise_condition() { + let filename = &Path::new("./tmp/file_that_does_not_exist.txt"); + let mut called = false; + do io_error::cond.trap(|_| { + called = true; + }).inside { + let result = File::open_mode(filename, Open, Read); + assert!(result.is_none()); + } + assert!(called); + } + + #[test] + fn file_test_iounlinking_invalid_path_should_raise_condition() { + let filename = &Path::new("./tmp/file_another_file_that_does_not_exist.txt"); + let mut called = false; + do io_error::cond.trap(|_| { + called = true; + }).inside { + unlink(filename); + } + assert!(called); + } + + #[test] + fn file_test_io_non_positional_read() { + let message = "ten-four"; + let mut read_mem = [0, .. 8]; + let filename = &Path::new("./tmp/file_rt_io_file_test_positional.txt"); + { + let mut rw_stream = File::open_mode(filename, Open, ReadWrite); + rw_stream.write(message.as_bytes()); + } + { + let mut read_stream = File::open_mode(filename, Open, Read); + { + let read_buf = read_mem.mut_slice(0, 4); + read_stream.read(read_buf); + } + { + let read_buf = read_mem.mut_slice(4, 8); + read_stream.read(read_buf); + } + } + unlink(filename); + let read_str = str::from_utf8(read_mem); + assert!(read_str == message.to_owned()); + } + + #[test] + fn file_test_io_seek_and_tell_smoke_test() { + let message = "ten-four"; + let mut read_mem = [0, .. 4]; + let set_cursor = 4 as u64; + let mut tell_pos_pre_read; + let mut tell_pos_post_read; + let filename = &Path::new("./tmp/file_rt_io_file_test_seeking.txt"); + { + let mut rw_stream = File::open_mode(filename, Open, ReadWrite); + rw_stream.write(message.as_bytes()); + } + { + let mut read_stream = File::open_mode(filename, Open, Read); + read_stream.seek(set_cursor as i64, SeekSet); + tell_pos_pre_read = read_stream.tell(); + read_stream.read(read_mem); + tell_pos_post_read = read_stream.tell(); + } + unlink(filename); + let read_str = str::from_utf8(read_mem); + assert!(read_str == message.slice(4, 8).to_owned()); + assert!(tell_pos_pre_read == set_cursor); + assert!(tell_pos_post_read == message.len() as u64); + } + + #[test] + fn file_test_io_seek_and_write() { + let initial_msg = "food-is-yummy"; + let overwrite_msg = "-the-bar!!"; + let final_msg = "foo-the-bar!!"; + let seek_idx = 3; + let mut read_mem = [0, .. 13]; + let filename = &Path::new("./tmp/file_rt_io_file_test_seek_and_write.txt"); + { + let mut rw_stream = File::open_mode(filename, Open, ReadWrite); + rw_stream.write(initial_msg.as_bytes()); + rw_stream.seek(seek_idx as i64, SeekSet); + rw_stream.write(overwrite_msg.as_bytes()); + } + { + let mut read_stream = File::open_mode(filename, Open, Read); + read_stream.read(read_mem); + } + unlink(filename); + let read_str = str::from_utf8(read_mem); + assert!(read_str == final_msg.to_owned()); + } + + #[test] + fn file_test_io_seek_shakedown() { + use std::str; // 01234567890123 + let initial_msg = "qwer-asdf-zxcv"; + let chunk_one = "qwer"; + let chunk_two = "asdf"; + let chunk_three = "zxcv"; + let mut read_mem = [0, .. 4]; + let filename = &Path::new("./tmp/file_rt_io_file_test_seek_shakedown.txt"); + { + let mut rw_stream = File::open_mode(filename, Open, ReadWrite); + rw_stream.write(initial_msg.as_bytes()); + } + { + let mut read_stream = File::open_mode(filename, Open, Read); + + read_stream.seek(-4, SeekEnd); + read_stream.read(read_mem); + let read_str = str::from_utf8(read_mem); + assert!(read_str == chunk_three.to_owned()); + + read_stream.seek(-9, SeekCur); + read_stream.read(read_mem); + let read_str = str::from_utf8(read_mem); + assert!(read_str == chunk_two.to_owned()); + + read_stream.seek(0, SeekSet); + read_stream.read(read_mem); + let read_str = str::from_utf8(read_mem); + assert!(read_str == chunk_one.to_owned()); + } + unlink(filename); + } + + #[test] + fn file_test_stat_is_correct_on_is_file() { + let filename = &Path::new("./tmp/file_stat_correct_on_is_file.txt"); + { + let mut fs = File::open_mode(filename, Open, ReadWrite); + let msg = "hw"; + fs.write(msg.as_bytes()); + } + let stat_res = stat(filename); + assert_eq!(stat_res.kind, io::TypeFile); + unlink(filename); + } + + #[test] + fn file_test_stat_is_correct_on_is_dir() { + let filename = &Path::new("./tmp/file_stat_correct_on_is_dir"); + mkdir(filename, io::UserRWX); + let stat_res = filename.stat(); + assert!(stat_res.kind == io::TypeDirectory); + rmdir(filename); + } + + #[test] + fn file_test_fileinfo_false_when_checking_is_file_on_a_directory() { + let dir = &Path::new("./tmp/fileinfo_false_on_dir"); + mkdir(dir, io::UserRWX); + assert!(dir.is_file() == false); + rmdir(dir); + } + + #[test] + fn file_test_fileinfo_check_exists_before_and_after_file_creation() { + let file = &Path::new("./tmp/fileinfo_check_exists_b_and_a.txt"); + File::create(file).write(bytes!("foo")); + assert!(file.exists()); + unlink(file); + assert!(!file.exists()); + } + + #[test] + fn file_test_directoryinfo_check_exists_before_and_after_mkdir() { + let dir = &Path::new("./tmp/before_and_after_dir"); + assert!(!dir.exists()); + mkdir(dir, io::UserRWX); + assert!(dir.exists()); + assert!(dir.is_dir()); + rmdir(dir); + assert!(!dir.exists()); + } + + #[test] + fn file_test_directoryinfo_readdir() { + use std::str; + let dir = &Path::new("./tmp/di_readdir"); + mkdir(dir, io::UserRWX); + let prefix = "foo"; + for n in range(0,3) { + let f = dir.join(format!("{}.txt", n)); + let mut w = File::create(&f); + let msg_str = (prefix + n.to_str().to_owned()).to_owned(); + let msg = msg_str.as_bytes(); + w.write(msg); + } + let files = readdir(dir); + let mut mem = [0u8, .. 4]; + for f in files.iter() { + { + let n = f.filestem_str(); + File::open(f).read(mem); + let read_str = str::from_utf8(mem); + let expected = match n { + None|Some("") => fail!("really shouldn't happen.."), + Some(n) => prefix+n + }; + assert!(expected == read_str); + } + unlink(f); + } + rmdir(dir); + } + + #[test] + fn recursive_mkdir_slash() { + mkdir_recursive(&Path::new("/"), io::UserRWX); + } + + #[test] + fn unicode_path_is_dir() { + assert!(Path::new(".").is_dir()); + assert!(!Path::new("test/stdtest/fs.rs").is_dir()); + + let tmpdir = tmpdir(); + + let mut dirpath = tmpdir.clone(); + dirpath.push(format!("test-가一ー你好")); + mkdir(&dirpath, io::UserRWX); + assert!(dirpath.is_dir()); + + let mut filepath = dirpath; + filepath.push("unicode-file-\uac00\u4e00\u30fc\u4f60\u597d.rs"); + File::create(&filepath); // ignore return; touch only + assert!(!filepath.is_dir()); + assert!(filepath.exists()); + + rmdir_recursive(&tmpdir); + } + + #[test] + fn unicode_path_exists() { + assert!(Path::new(".").exists()); + assert!(!Path::new("test/nonexistent-bogus-path").exists()); + + let tmpdir = tmpdir(); + let unicode = tmpdir.clone(); + let unicode = unicode.join(format!("test-각丁ー再见")); + mkdir(&unicode, io::UserRWX); + assert!(unicode.exists()); + assert!(!Path::new("test/unicode-bogus-path-각丁ー再见").exists()); + rmdir_recursive(&tmpdir); + } + + #[test] + fn copy_file_does_not_exist() { + let from = Path::new("test/nonexistent-bogus-path"); + let to = Path::new("test/other-bogus-path"); + match io::result(|| copy(&from, &to)) { + Ok(*) => fail!(), + Err(*) => { + assert!(!from.exists()); + assert!(!to.exists()); + } + } + } + + #[test] + fn copy_file_ok() { + let tmpdir = tmpdir(); + let input = tmpdir.join("in.txt"); + let out = tmpdir.join("out.txt"); + + File::create(&input).write(bytes!("hello")); + copy(&input, &out); + let contents = File::open(&out).read_to_end(); + assert_eq!(contents.as_slice(), bytes!("hello")); + + assert_eq!(input.stat().perm, out.stat().perm); + rmdir_recursive(&tmpdir); + } + + #[test] + fn copy_file_dst_dir() { + let tmpdir = tmpdir(); + let out = tmpdir.join("out"); + + File::create(&out); + match io::result(|| copy(&out, &tmpdir)) { + Ok(*) => fail!(), Err(*) => {} + } + rmdir_recursive(&tmpdir); + } + + #[test] + fn copy_file_dst_exists() { + let tmpdir = tmpdir(); + let input = tmpdir.join("in"); + let output = tmpdir.join("out"); + + File::create(&input).write("foo".as_bytes()); + File::create(&output).write("bar".as_bytes()); + copy(&input, &output); + + assert_eq!(File::open(&output).read_to_end(), + (bytes!("foo")).to_owned()); + + rmdir_recursive(&tmpdir); + } + + #[test] + fn copy_file_src_dir() { + let tmpdir = tmpdir(); + let out = tmpdir.join("out"); + + match io::result(|| copy(&tmpdir, &out)) { + Ok(*) => fail!(), Err(*) => {} + } + assert!(!out.exists()); + rmdir_recursive(&tmpdir); + } + + #[test] + fn copy_file_preserves_perm_bits() { + let tmpdir = tmpdir(); + let input = tmpdir.join("in.txt"); + let out = tmpdir.join("out.txt"); + + File::create(&input); + chmod(&input, io::UserRead); + copy(&input, &out); + assert!(out.stat().perm & io::UserWrite == 0); + + chmod(&input, io::UserFile); + chmod(&out, io::UserFile); + rmdir_recursive(&tmpdir); + } + + #[test] + #[ignore(cfg(windows))] // FIXME(#10264) operation not permitted? + fn symlinks_work() { + let tmpdir = tmpdir(); + let input = tmpdir.join("in.txt"); + let out = tmpdir.join("out.txt"); + + File::create(&input).write("foobar".as_bytes()); + symlink(&input, &out); + assert_eq!(lstat(&out).kind, io::TypeSymlink); + assert_eq!(stat(&out).size, stat(&input).size); + assert_eq!(File::open(&out).read_to_end(), (bytes!("foobar")).to_owned()); + + rmdir_recursive(&tmpdir); + } + + #[test] + #[ignore(cfg(windows))] // apparently windows doesn't like symlinks + fn symlink_noexist() { + let tmpdir = tmpdir(); + // symlinks can point to things that don't exist + symlink(&tmpdir.join("foo"), &tmpdir.join("bar")); + assert!(readlink(&tmpdir.join("bar")).unwrap() == tmpdir.join("foo")); + rmdir_recursive(&tmpdir); + } + + #[test] + fn readlink_not_symlink() { + let tmpdir = tmpdir(); + match io::result(|| readlink(&tmpdir)) { + Ok(*) => fail!("wanted a failure"), + Err(*) => {} + } + rmdir_recursive(&tmpdir); + } + + #[test] + fn links_work() { + let tmpdir = tmpdir(); + let input = tmpdir.join("in.txt"); + let out = tmpdir.join("out.txt"); + + File::create(&input).write("foobar".as_bytes()); + link(&input, &out); + assert_eq!(lstat(&out).kind, io::TypeFile); + assert_eq!(stat(&out).size, stat(&input).size); + assert_eq!(stat(&out).unstable.nlink, 2); + assert_eq!(File::open(&out).read_to_end(), (bytes!("foobar")).to_owned()); + + // can't link to yourself + match io::result(|| link(&input, &input)) { + Ok(*) => fail!("wanted a failure"), + Err(*) => {} + } + // can't link to something that doesn't exist + match io::result(|| link(&tmpdir.join("foo"), &tmpdir.join("bar"))) { + Ok(*) => fail!("wanted a failure"), + Err(*) => {} + } + + rmdir_recursive(&tmpdir); + } + + #[test] + fn chmod_works() { + let tmpdir = tmpdir(); + let file = tmpdir.join("in.txt"); + + File::create(&file); + assert!(stat(&file).perm & io::UserWrite == io::UserWrite); + chmod(&file, io::UserRead); + assert!(stat(&file).perm & io::UserWrite == 0); + + match io::result(|| chmod(&tmpdir.join("foo"), io::UserRWX)) { + Ok(*) => fail!("wanted a failure"), + Err(*) => {} + } + + chmod(&file, io::UserFile); + rmdir_recursive(&tmpdir); + } + + #[test] + fn sync_doesnt_kill_anything() { + let tmpdir = tmpdir(); + let path = tmpdir.join("in.txt"); + + let mut file = File::open_mode(&path, io::Open, io::ReadWrite).unwrap(); + file.fsync(); + file.datasync(); + file.write(bytes!("foo")); + file.fsync(); + file.datasync(); + free(file); + + rmdir_recursive(&tmpdir); + } + + #[test] + fn truncate_works() { + let tmpdir = tmpdir(); + let path = tmpdir.join("in.txt"); + + let mut file = File::open_mode(&path, io::Open, io::ReadWrite).unwrap(); + file.write(bytes!("foo")); + + // Do some simple things with truncation + assert_eq!(stat(&path).size, 3); + file.truncate(10); + assert_eq!(stat(&path).size, 10); + file.write(bytes!("bar")); + assert_eq!(stat(&path).size, 10); + assert_eq!(File::open(&path).read_to_end(), + (bytes!("foobar", 0, 0, 0, 0)).to_owned()); + + // Truncate to a smaller length, don't seek, and then write something. + // Ensure that the intermediate zeroes are all filled in (we're seeked + // past the end of the file). + file.truncate(2); + assert_eq!(stat(&path).size, 2); + file.write(bytes!("wut")); + assert_eq!(stat(&path).size, 9); + assert_eq!(File::open(&path).read_to_end(), + (bytes!("fo", 0, 0, 0, 0, "wut")).to_owned()); + free(file); + + rmdir_recursive(&tmpdir); + } + + #[test] + fn open_flavors() { + let tmpdir = tmpdir(); + + match io::result(|| File::open_mode(&tmpdir.join("a"), io::Open, + io::Read)) { + Ok(*) => fail!(), Err(*) => {} + } + File::open_mode(&tmpdir.join("b"), io::Open, io::Write).unwrap(); + File::open_mode(&tmpdir.join("c"), io::Open, io::ReadWrite).unwrap(); + File::open_mode(&tmpdir.join("d"), io::Append, io::Write).unwrap(); + File::open_mode(&tmpdir.join("e"), io::Append, io::ReadWrite).unwrap(); + File::open_mode(&tmpdir.join("f"), io::Truncate, io::Write).unwrap(); + File::open_mode(&tmpdir.join("g"), io::Truncate, io::ReadWrite).unwrap(); + + File::create(&tmpdir.join("h")).write("foo".as_bytes()); + File::open_mode(&tmpdir.join("h"), io::Open, io::Read).unwrap(); + { + let mut f = File::open_mode(&tmpdir.join("h"), io::Open, + io::Read).unwrap(); + match io::result(|| f.write("wut".as_bytes())) { + Ok(*) => fail!(), Err(*) => {} + } + } + assert_eq!(stat(&tmpdir.join("h")).size, 3); + { + let mut f = File::open_mode(&tmpdir.join("h"), io::Append, + io::Write).unwrap(); + f.write("bar".as_bytes()); + } + assert_eq!(stat(&tmpdir.join("h")).size, 6); + { + let mut f = File::open_mode(&tmpdir.join("h"), io::Truncate, + io::Write).unwrap(); + f.write("bar".as_bytes()); + } + assert_eq!(stat(&tmpdir.join("h")).size, 3); + + rmdir_recursive(&tmpdir); + } + + #[test] + fn utime() { + let tmpdir = tmpdir(); + let path = tmpdir.join("a"); + File::create(&path); + + change_file_times(&path, 1000, 2000); + assert_eq!(path.stat().accessed, 1000); + assert_eq!(path.stat().modified, 2000); + + rmdir_recursive(&tmpdir); + } + + #[test] + fn utime_noexist() { + let tmpdir = tmpdir(); + + match io::result(|| change_file_times(&tmpdir.join("a"), 100, 200)) { + Ok(*) => fail!(), + Err(*) => {} + } + + rmdir_recursive(&tmpdir); + } +} diff --git a/src/libstd/io/mem.rs b/src/libstd/io/mem.rs new file mode 100644 index 00000000000..e754d9be85e --- /dev/null +++ b/src/libstd/io/mem.rs @@ -0,0 +1,308 @@ +// 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. + +//! Readers and Writers for in-memory buffers +//! +//! # XXX +//! +//! * Should probably have something like this for strings. +//! * Should they implement Closable? Would take extra state. + +use cmp::min; +use prelude::*; +use super::*; +use vec; + +/// Writes to an owned, growable byte vector +pub struct MemWriter { + priv buf: ~[u8], + priv pos: uint, +} + +impl MemWriter { + pub fn new() -> MemWriter { + MemWriter { buf: vec::with_capacity(128), pos: 0 } + } +} + +impl Writer for MemWriter { + fn write(&mut self, buf: &[u8]) { + // Make sure the internal buffer is as least as big as where we + // currently are + let difference = self.pos as i64 - self.buf.len() as i64; + if difference > 0 { + self.buf.grow(difference as uint, &0); + } + + // Figure out what bytes will be used to overwrite what's currently + // there (left), and what will be appended on the end (right) + let cap = self.buf.len() - self.pos; + let (left, right) = if cap <= buf.len() { + (buf.slice_to(cap), buf.slice_from(cap)) + } else { + (buf, &[]) + }; + + // Do the necessary writes + if left.len() > 0 { + vec::bytes::copy_memory(self.buf.mut_slice_from(self.pos), + left, left.len()); + } + if right.len() > 0 { + self.buf.push_all(right); + } + + // Bump us forward + self.pos += buf.len(); + } +} + +impl Seek for MemWriter { + fn tell(&self) -> u64 { self.pos as u64 } + + fn seek(&mut self, pos: i64, style: SeekStyle) { + match style { + SeekSet => { self.pos = pos as uint; } + SeekEnd => { self.pos = self.buf.len() + pos as uint; } + SeekCur => { self.pos += pos as uint; } + } + } +} + +impl Decorator<~[u8]> for MemWriter { + fn inner(self) -> ~[u8] { self.buf } + fn inner_ref<'a>(&'a self) -> &'a ~[u8] { &self.buf } + fn inner_mut_ref<'a>(&'a mut self) -> &'a mut ~[u8] { &mut self.buf } +} + +/// Reads from an owned byte vector +pub struct MemReader { + priv buf: ~[u8], + priv pos: uint +} + +impl MemReader { + pub fn new(buf: ~[u8]) -> MemReader { + MemReader { + buf: buf, + pos: 0 + } + } +} + +impl Reader for MemReader { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + { if self.eof() { return None; } } + + let write_len = min(buf.len(), self.buf.len() - self.pos); + { + let input = self.buf.slice(self.pos, self.pos + write_len); + let output = buf.mut_slice(0, write_len); + assert_eq!(input.len(), output.len()); + vec::bytes::copy_memory(output, input, write_len); + } + self.pos += write_len; + assert!(self.pos <= self.buf.len()); + + return Some(write_len); + } + + fn eof(&mut self) -> bool { self.pos == self.buf.len() } +} + +impl Seek for MemReader { + fn tell(&self) -> u64 { self.pos as u64 } + + fn seek(&mut self, _pos: i64, _style: SeekStyle) { fail!() } +} + +impl Decorator<~[u8]> for MemReader { + + fn inner(self) -> ~[u8] { + match self { + MemReader { buf: buf, _ } => buf + } + } + + fn inner_ref<'a>(&'a self) -> &'a ~[u8] { + match *self { + MemReader { buf: ref buf, _ } => buf + } + } + + fn inner_mut_ref<'a>(&'a mut self) -> &'a mut ~[u8] { + match *self { + MemReader { buf: ref mut buf, _ } => buf + } + } +} + + +/// Writes to a fixed-size byte slice +pub struct BufWriter<'self> { + priv buf: &'self mut [u8], + priv pos: uint +} + +impl<'self> BufWriter<'self> { + pub fn new<'a>(buf: &'a mut [u8]) -> BufWriter<'a> { + BufWriter { + buf: buf, + pos: 0 + } + } +} + +impl<'self> Writer for BufWriter<'self> { + fn write(&mut self, _buf: &[u8]) { fail!() } + + fn flush(&mut self) { fail!() } +} + +impl<'self> Seek for BufWriter<'self> { + fn tell(&self) -> u64 { fail!() } + + fn seek(&mut self, _pos: i64, _style: SeekStyle) { fail!() } +} + + +/// Reads from a fixed-size byte slice +pub struct BufReader<'self> { + priv buf: &'self [u8], + priv pos: uint +} + +impl<'self> BufReader<'self> { + pub fn new<'a>(buf: &'a [u8]) -> BufReader<'a> { + BufReader { + buf: buf, + pos: 0 + } + } +} + +impl<'self> Reader for BufReader<'self> { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + { if self.eof() { return None; } } + + let write_len = min(buf.len(), self.buf.len() - self.pos); + { + let input = self.buf.slice(self.pos, self.pos + write_len); + let output = buf.mut_slice(0, write_len); + assert_eq!(input.len(), output.len()); + vec::bytes::copy_memory(output, input, write_len); + } + self.pos += write_len; + assert!(self.pos <= self.buf.len()); + + return Some(write_len); + } + + fn eof(&mut self) -> bool { self.pos == self.buf.len() } +} + +impl<'self> Seek for BufReader<'self> { + fn tell(&self) -> u64 { self.pos as u64 } + + fn seek(&mut self, _pos: i64, _style: SeekStyle) { fail!() } +} + +///Calls a function with a MemWriter and returns +///the writer's stored vector. +pub fn with_mem_writer(writeFn:&fn(&mut MemWriter)) -> ~[u8] { + let mut writer = MemWriter::new(); + writeFn(&mut writer); + writer.inner() +} + +#[cfg(test)] +mod test { + use prelude::*; + use super::*; + use io::*; + + #[test] + fn test_mem_writer() { + let mut writer = MemWriter::new(); + assert_eq!(writer.tell(), 0); + writer.write([0]); + assert_eq!(writer.tell(), 1); + writer.write([1, 2, 3]); + writer.write([4, 5, 6, 7]); + assert_eq!(writer.tell(), 8); + assert_eq!(*writer.inner_ref(), ~[0, 1, 2, 3, 4, 5, 6, 7]); + + writer.seek(0, SeekSet); + assert_eq!(writer.tell(), 0); + writer.write([3, 4]); + assert_eq!(*writer.inner_ref(), ~[3, 4, 2, 3, 4, 5, 6, 7]); + + writer.seek(1, SeekCur); + writer.write([0, 1]); + assert_eq!(*writer.inner_ref(), ~[3, 4, 2, 0, 1, 5, 6, 7]); + + writer.seek(-1, SeekEnd); + writer.write([1, 2]); + assert_eq!(*writer.inner_ref(), ~[3, 4, 2, 0, 1, 5, 6, 1, 2]); + + writer.seek(1, SeekEnd); + writer.write([1]); + assert_eq!(*writer.inner_ref(), ~[3, 4, 2, 0, 1, 5, 6, 1, 2, 0, 1]); + } + + #[test] + fn test_mem_reader() { + let mut reader = MemReader::new(~[0, 1, 2, 3, 4, 5, 6, 7]); + let mut buf = []; + assert_eq!(reader.read(buf), Some(0)); + assert_eq!(reader.tell(), 0); + let mut buf = [0]; + assert_eq!(reader.read(buf), Some(1)); + assert_eq!(reader.tell(), 1); + assert_eq!(buf, [0]); + let mut buf = [0, ..4]; + assert_eq!(reader.read(buf), Some(4)); + assert_eq!(reader.tell(), 5); + assert_eq!(buf, [1, 2, 3, 4]); + assert_eq!(reader.read(buf), Some(3)); + assert_eq!(buf.slice(0, 3), [5, 6, 7]); + assert!(reader.eof()); + assert_eq!(reader.read(buf), None); + assert!(reader.eof()); + } + + #[test] + fn test_buf_reader() { + let in_buf = ~[0, 1, 2, 3, 4, 5, 6, 7]; + let mut reader = BufReader::new(in_buf); + let mut buf = []; + assert_eq!(reader.read(buf), Some(0)); + assert_eq!(reader.tell(), 0); + let mut buf = [0]; + assert_eq!(reader.read(buf), Some(1)); + assert_eq!(reader.tell(), 1); + assert_eq!(buf, [0]); + let mut buf = [0, ..4]; + assert_eq!(reader.read(buf), Some(4)); + assert_eq!(reader.tell(), 5); + assert_eq!(buf, [1, 2, 3, 4]); + assert_eq!(reader.read(buf), Some(3)); + assert_eq!(buf.slice(0, 3), [5, 6, 7]); + assert!(reader.eof()); + assert_eq!(reader.read(buf), None); + assert!(reader.eof()); + } + + #[test] + fn test_with_mem_writer() { + let buf = with_mem_writer(|wr| wr.write([1,2,3,4,5,6,7])); + assert_eq!(buf, ~[1,2,3,4,5,6,7]); + } +} diff --git a/src/libstd/io/mod.rs b/src/libstd/io/mod.rs new file mode 100644 index 00000000000..8858d0a14ad --- /dev/null +++ b/src/libstd/io/mod.rs @@ -0,0 +1,1226 @@ +// 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. + +/*! Synchronous I/O + +This module defines the Rust interface for synchronous I/O. +It models byte-oriented input and output with the Reader and Writer traits. +Types that implement both `Reader` and `Writer` are called 'streams', +and automatically implement the `Stream` trait. +Implementations are provided for common I/O streams like +file, TCP, UDP, Unix domain sockets. +Readers and Writers may be composed to add capabilities like string +parsing, encoding, and compression. + +# Examples + +Some examples of obvious things you might want to do + +* Read lines from stdin + + for stdin().each_line |line| { + println(line) + } + +* Read a complete file to a string, (converting newlines?) + + let contents = File::open("message.txt").read_to_str(); // read_to_str?? + +* Write a line to a file + + let file = File::open("message.txt", Create, Write); + file.write_line("hello, file!"); + +* Iterate over the lines of a file + + do File::open("message.txt").each_line |line| { + println(line) + } + +* Pull the lines of a file into a vector of strings + + let lines = File::open("message.txt").line_iter().to_vec(); + +* Make an simple HTTP request + + let socket = TcpStream::open("localhost:8080"); + socket.write_line("GET / HTTP/1.0"); + socket.write_line(""); + let response = socket.read_to_end(); + +* Connect based on URL? Requires thinking about where the URL type lives + and how to make protocol handlers extensible, e.g. the "tcp" protocol + yields a `TcpStream`. + + connect("tcp://localhost:8080"); + +# Terms + +* Reader - An I/O source, reads bytes into a buffer +* Writer - An I/O sink, writes bytes from a buffer +* Stream - Typical I/O sources like files and sockets are both Readers and Writers, + and are collectively referred to a `streams`. +* Decorator - A Reader or Writer that composes with others to add additional capabilities + such as encoding or decoding + +# Blocking and synchrony + +When discussing I/O you often hear the terms 'synchronous' and +'asynchronous', along with 'blocking' and 'non-blocking' compared and +contrasted. A synchronous I/O interface performs each I/O operation to +completion before proceeding to the next. Synchronous interfaces are +usually used in imperative style as a sequence of commands. An +asynchronous interface allows multiple I/O requests to be issued +simultaneously, without waiting for each to complete before proceeding +to the next. + +Asynchronous interfaces are used to achieve 'non-blocking' I/O. In +traditional single-threaded systems, performing a synchronous I/O +operation means that the program stops all activity (it 'blocks') +until the I/O is complete. Blocking is bad for performance when +there are other computations that could be done. + +Asynchronous interfaces are most often associated with the callback +(continuation-passing) style popularised by node.js. Such systems rely +on all computations being run inside an event loop which maintains a +list of all pending I/O events; when one completes the registered +callback is run and the code that made the I/O request continues. +Such interfaces achieve non-blocking at the expense of being more +difficult to reason about. + +Rust's I/O interface is synchronous - easy to read - and non-blocking by default. + +Remember that Rust tasks are 'green threads', lightweight threads that +are multiplexed onto a single operating system thread. If that system +thread blocks then no other task may proceed. Rust tasks are +relatively cheap to create, so as long as other tasks are free to +execute then non-blocking code may be written by simply creating a new +task. + +When discussing blocking in regards to Rust's I/O model, we are +concerned with whether performing I/O blocks other Rust tasks from +proceeding. In other words, when a task calls `read`, it must then +wait (or 'sleep', or 'block') until the call to `read` is complete. +During this time, other tasks may or may not be executed, depending on +how `read` is implemented. + + +Rust's default I/O implementation is non-blocking; by cooperating +directly with the task scheduler it arranges to never block progress +of *other* tasks. Under the hood, Rust uses asynchronous I/O via a +per-scheduler (and hence per-thread) event loop. Synchronous I/O +requests are implemented by descheduling the running task and +performing an asynchronous request; the task is only resumed once the +asynchronous request completes. + +For blocking (but possibly more efficient) implementations, look +in the `io::native` module. + +# Error Handling + +I/O is an area where nearly every operation can result in unexpected +errors. It should allow errors to be handled efficiently. +It needs to be convenient to use I/O when you don't care +about dealing with specific errors. + +Rust's I/O employs a combination of techniques to reduce boilerplate +while still providing feedback about errors. The basic strategy: + +* Errors are fatal by default, resulting in task failure +* Errors raise the `io_error` condition which provides an opportunity to inspect + an IoError object containing details. +* Return values must have a sensible null or zero value which is returned + if a condition is handled successfully. This may be an `Option`, an empty + vector, or other designated error value. +* Common traits are implemented for `Option`, e.g. `impl<R: Reader> Reader for Option<R>`, + so that nullable values do not have to be 'unwrapped' before use. + +These features combine in the API to allow for expressions like +`File::new("diary.txt").write_line("met a girl")` without having to +worry about whether "diary.txt" exists or whether the write +succeeds. As written, if either `new` or `write_line` encounters +an error the task will fail. + +If you wanted to handle the error though you might write + + let mut error = None; + do io_error::cond(|e: IoError| { + error = Some(e); + }).in { + File::new("diary.txt").write_line("met a girl"); + } + + if error.is_some() { + println("failed to write my diary"); + } + +XXX: Need better condition handling syntax + +In this case the condition handler will have the opportunity to +inspect the IoError raised by either the call to `new` or the call to +`write_line`, but then execution will continue. + +So what actually happens if `new` encounters an error? To understand +that it's important to know that what `new` returns is not a `File` +but an `Option<File>`. If the file does not open, and the condition +is handled, then `new` will simply return `None`. Because there is an +implementation of `Writer` (the trait required ultimately required for +types to implement `write_line`) there is no need to inspect or unwrap +the `Option<File>` and we simply call `write_line` on it. If `new` +returned a `None` then the followup call to `write_line` will also +raise an error. + +## Concerns about this strategy + +This structure will encourage a programming style that is prone +to errors similar to null pointer dereferences. +In particular code written to ignore errors and expect conditions to be unhandled +will start passing around null or zero objects when wrapped in a condition handler. + +* XXX: How should we use condition handlers that return values? +* XXX: Should EOF raise default conditions when EOF is not an error? + +# Issues with i/o scheduler affinity, work stealing, task pinning + +# Resource management + +* `close` vs. RAII + +# Paths, URLs and overloaded constructors + + + +# Scope + +In scope for core + +* Url? + +Some I/O things don't belong in core + + - url + - net - `fn connect` + - http + - flate + +Out of scope + +* Async I/O. We'll probably want it eventually + + +# XXX Questions and issues + +* Should default constructors take `Path` or `&str`? `Path` makes simple cases verbose. + Overloading would be nice. +* Add overloading for Path and &str and Url &str +* stdin/err/out +* print, println, etc. +* fsync +* relationship with filesystem querying, Directory, File types etc. +* Rename Reader/Writer to ByteReader/Writer, make Reader/Writer generic? +* Can Port and Chan be implementations of a generic Reader<T>/Writer<T>? +* Trait for things that are both readers and writers, Stream? +* How to handle newline conversion +* String conversion +* open vs. connect for generic stream opening +* Do we need `close` at all? dtors might be good enough +* How does I/O relate to the Iterator trait? +* std::base64 filters +* Using conditions is a big unknown since we don't have much experience with them +* Too many uses of OtherIoError + +*/ + +#[allow(missing_doc)]; + +use cast; +use container::Container; +use int; +use iter::Iterator; +use option::{Option, Some, None}; +use path::Path; +use result::{Ok, Err, Result}; +use str::{StrSlice, OwnedStr}; +use to_str::ToStr; +use uint; +use unstable::finally::Finally; +use vec::{OwnedVector, MutableVector}; +use vec; + +// Reexports +pub use self::stdio::stdin; +pub use self::stdio::stdout; +pub use self::stdio::stderr; +pub use self::stdio::print; +pub use self::stdio::println; + +pub use self::fs::File; +pub use self::timer::Timer; +pub use self::net::ip::IpAddr; +pub use self::net::tcp::TcpListener; +pub use self::net::tcp::TcpStream; +pub use self::net::udp::UdpStream; +pub use self::pipe::PipeStream; +pub use self::process::Process; + +/// Synchronous, non-blocking filesystem operations. +pub mod fs; + +/// Synchronous, in-memory I/O. +pub mod pipe; + +/// Child process management. +pub mod process; + +/// Synchronous, non-blocking network I/O. +pub mod net; + +/// Readers and Writers for memory buffers and strings. +pub mod mem; + +/// Non-blocking access to stdin, stdout, stderr +pub mod stdio; + +/// Implementations for Option +mod option; + +/// Basic stream compression. XXX: Belongs with other flate code +pub mod flate; + +/// Interop between byte streams and pipes. Not sure where it belongs +pub mod comm_adapters; + +/// Extension traits +pub mod extensions; + +/// Basic Timer +pub mod timer; + +/// Buffered I/O wrappers +pub mod buffered; + +/// Thread-blocking implementations +pub mod native { + /// Posix file I/O + pub mod file; + /// Process spawning and child management + pub mod process; + /// Posix stdio + pub mod stdio; + + /// Sockets + /// # XXX - implement this + pub mod net { + pub mod tcp { } + pub mod udp { } + #[cfg(unix)] + pub mod unix { } + } +} + +/// Signal handling +pub mod signal; + +/// The default buffer size for various I/O operations +static DEFAULT_BUF_SIZE: uint = 1024 * 64; + +/// The type passed to I/O condition handlers to indicate error +/// +/// # XXX +/// +/// Is something like this sufficient? It's kind of archaic +pub struct IoError { + kind: IoErrorKind, + desc: &'static str, + detail: Option<~str> +} + +// FIXME: #8242 implementing manually because deriving doesn't work for some reason +impl ToStr for IoError { + fn to_str(&self) -> ~str { + let mut s = ~"IoError { kind: "; + s.push_str(self.kind.to_str()); + s.push_str(", desc: "); + s.push_str(self.desc); + s.push_str(", detail: "); + s.push_str(self.detail.to_str()); + s.push_str(" }"); + s + } +} + +#[deriving(Eq)] +pub enum IoErrorKind { + PreviousIoError, + OtherIoError, + EndOfFile, + FileNotFound, + PermissionDenied, + ConnectionFailed, + Closed, + ConnectionRefused, + ConnectionReset, + ConnectionAborted, + NotConnected, + BrokenPipe, + PathAlreadyExists, + PathDoesntExist, + MismatchedFileTypeForOperation, + ResourceUnavailable, + IoUnavailable, +} + +// FIXME: #8242 implementing manually because deriving doesn't work for some reason +impl ToStr for IoErrorKind { + fn to_str(&self) -> ~str { + match *self { + PreviousIoError => ~"PreviousIoError", + OtherIoError => ~"OtherIoError", + EndOfFile => ~"EndOfFile", + FileNotFound => ~"FileNotFound", + PermissionDenied => ~"PermissionDenied", + ConnectionFailed => ~"ConnectionFailed", + Closed => ~"Closed", + ConnectionRefused => ~"ConnectionRefused", + ConnectionReset => ~"ConnectionReset", + NotConnected => ~"NotConnected", + BrokenPipe => ~"BrokenPipe", + PathAlreadyExists => ~"PathAlreadyExists", + PathDoesntExist => ~"PathDoesntExist", + MismatchedFileTypeForOperation => ~"MismatchedFileTypeForOperation", + IoUnavailable => ~"IoUnavailable", + ResourceUnavailable => ~"ResourceUnavailable", + ConnectionAborted => ~"ConnectionAborted", + } + } +} + +// XXX: Can't put doc comments on macros +// Raised by `I/O` operations on error. +condition! { + pub io_error: IoError -> (); +} + +/// Helper for wrapper calls where you want to +/// ignore any io_errors that might be raised +pub fn ignore_io_error<T>(cb: &fn() -> T) -> T { + do io_error::cond.trap(|_| { + // just swallow the error.. downstream users + // who can make a decision based on a None result + // won't care + }).inside { + cb() + } +} + +/// Helper for catching an I/O error and wrapping it in a Result object. The +/// return result will be the last I/O error that happened or the result of the +/// closure if no error occurred. +pub fn result<T>(cb: &fn() -> T) -> Result<T, IoError> { + let mut err = None; + let ret = io_error::cond.trap(|e| { + if err.is_none() { + err = Some(e); + } + }).inside(cb); + match err { + Some(e) => Err(e), + None => Ok(ret), + } +} + +pub trait Reader { + + // Only two methods which need to get implemented for this trait + + /// Read bytes, up to the length of `buf` and place them in `buf`. + /// Returns the number of bytes read. The number of bytes read my + /// be less than the number requested, even 0. Returns `None` on EOF. + /// + /// # Failure + /// + /// Raises the `io_error` condition on error. If the condition + /// is handled then no guarantee is made about the number of bytes + /// read and the contents of `buf`. If the condition is handled + /// returns `None` (XXX see below). + /// + /// # XXX + /// + /// * Should raise_default error on eof? + /// * If the condition is handled it should still return the bytes read, + /// in which case there's no need to return Option - but then you *have* + /// to install a handler to detect eof. + /// + /// This doesn't take a `len` argument like the old `read`. + /// Will people often need to slice their vectors to call this + /// and will that be annoying? + /// Is it actually possible for 0 bytes to be read successfully? + fn read(&mut self, buf: &mut [u8]) -> Option<uint>; + + /// Return whether the Reader has reached the end of the stream. + /// + /// # Example + /// + /// let reader = File::open(&Path::new("foo.txt")) + /// while !reader.eof() { + /// println(reader.read_line()); + /// } + /// + /// # Failure + /// + /// Returns `true` on failure. + fn eof(&mut self) -> bool; + + // Convenient helper methods based on the above methods + + /// Reads a single byte. Returns `None` on EOF. + /// + /// # Failure + /// + /// Raises the same conditions as the `read` method. Returns + /// `None` if the condition is handled. + fn read_byte(&mut self) -> Option<u8> { + let mut buf = [0]; + match self.read(buf) { + Some(0) => { + debug!("read 0 bytes. trying again"); + self.read_byte() + } + Some(1) => Some(buf[0]), + Some(_) => unreachable!(), + None => None + } + } + + /// Reads `len` bytes and appends them to a vector. + /// + /// May push fewer than the requested number of bytes on error + /// or EOF. Returns true on success, false on EOF or error. + /// + /// # Failure + /// + /// Raises the same conditions as `read`. Additionally raises `io_error` + /// on EOF. If `io_error` is handled then `push_bytes` may push less + /// than the requested number of bytes. + fn push_bytes(&mut self, buf: &mut ~[u8], len: uint) { + unsafe { + let start_len = buf.len(); + let mut total_read = 0; + + buf.reserve_additional(len); + vec::raw::set_len(buf, start_len + len); + + do (|| { + while total_read < len { + let len = buf.len(); + let slice = buf.mut_slice(start_len + total_read, len); + match self.read(slice) { + Some(nread) => { + total_read += nread; + } + None => { + io_error::cond.raise(standard_error(EndOfFile)); + break; + } + } + } + }).finally { + vec::raw::set_len(buf, start_len + total_read); + } + } + } + + /// Reads `len` bytes and gives you back a new vector of length `len` + /// + /// # Failure + /// + /// Raises the same conditions as `read`. Additionally raises `io_error` + /// on EOF. If `io_error` is handled then the returned vector may + /// contain less than the requested number of bytes. + fn read_bytes(&mut self, len: uint) -> ~[u8] { + let mut buf = vec::with_capacity(len); + self.push_bytes(&mut buf, len); + return buf; + } + + /// Reads all remaining bytes from the stream. + /// + /// # Failure + /// + /// Raises the same conditions as the `read` method. + fn read_to_end(&mut self) -> ~[u8] { + let mut buf = vec::with_capacity(DEFAULT_BUF_SIZE); + let mut keep_reading = true; + do io_error::cond.trap(|e| { + if e.kind == EndOfFile { + keep_reading = false; + } else { + io_error::cond.raise(e) + } + }).inside { + while keep_reading { + self.push_bytes(&mut buf, DEFAULT_BUF_SIZE) + } + } + return buf; + } + + /// Create an iterator that reads a single byte on + /// each iteration, until EOF. + /// + /// # Failure + /// + /// Raises the same conditions as the `read` method, for + /// each call to its `.next()` method. + /// Ends the iteration if the condition is handled. + fn bytes(self) -> extensions::ByteIterator<Self> { + extensions::ByteIterator::new(self) + } + + // Byte conversion helpers + + /// Reads `n` little-endian unsigned integer bytes. + /// + /// `n` must be between 1 and 8, inclusive. + fn read_le_uint_n(&mut self, nbytes: uint) -> u64 { + assert!(nbytes > 0 && nbytes <= 8); + + let mut val = 0u64; + let mut pos = 0; + let mut i = nbytes; + while i > 0 { + val += (self.read_u8() as u64) << pos; + pos += 8; + i -= 1; + } + val + } + + /// Reads `n` little-endian signed integer bytes. + /// + /// `n` must be between 1 and 8, inclusive. + fn read_le_int_n(&mut self, nbytes: uint) -> i64 { + extend_sign(self.read_le_uint_n(nbytes), nbytes) + } + + /// Reads `n` big-endian unsigned integer bytes. + /// + /// `n` must be between 1 and 8, inclusive. + fn read_be_uint_n(&mut self, nbytes: uint) -> u64 { + assert!(nbytes > 0 && nbytes <= 8); + + let mut val = 0u64; + let mut i = nbytes; + while i > 0 { + i -= 1; + val += (self.read_u8() as u64) << i * 8; + } + val + } + + /// Reads `n` big-endian signed integer bytes. + /// + /// `n` must be between 1 and 8, inclusive. + fn read_be_int_n(&mut self, nbytes: uint) -> i64 { + extend_sign(self.read_be_uint_n(nbytes), nbytes) + } + + /// Reads a little-endian unsigned integer. + /// + /// The number of bytes returned is system-dependant. + fn read_le_uint(&mut self) -> uint { + self.read_le_uint_n(uint::bytes) as uint + } + + /// Reads a little-endian integer. + /// + /// The number of bytes returned is system-dependant. + fn read_le_int(&mut self) -> int { + self.read_le_int_n(int::bytes) as int + } + + /// Reads a big-endian unsigned integer. + /// + /// The number of bytes returned is system-dependant. + fn read_be_uint(&mut self) -> uint { + self.read_be_uint_n(uint::bytes) as uint + } + + /// Reads a big-endian integer. + /// + /// The number of bytes returned is system-dependant. + fn read_be_int(&mut self) -> int { + self.read_be_int_n(int::bytes) as int + } + + /// Reads a big-endian `u64`. + /// + /// `u64`s are 8 bytes long. + fn read_be_u64(&mut self) -> u64 { + self.read_be_uint_n(8) as u64 + } + + /// Reads a big-endian `u32`. + /// + /// `u32`s are 4 bytes long. + fn read_be_u32(&mut self) -> u32 { + self.read_be_uint_n(4) as u32 + } + + /// Reads a big-endian `u16`. + /// + /// `u16`s are 2 bytes long. + fn read_be_u16(&mut self) -> u16 { + self.read_be_uint_n(2) as u16 + } + + /// Reads a big-endian `i64`. + /// + /// `i64`s are 8 bytes long. + fn read_be_i64(&mut self) -> i64 { + self.read_be_int_n(8) as i64 + } + + /// Reads a big-endian `i32`. + /// + /// `i32`s are 4 bytes long. + fn read_be_i32(&mut self) -> i32 { + self.read_be_int_n(4) as i32 + } + + /// Reads a big-endian `i16`. + /// + /// `i16`s are 2 bytes long. + fn read_be_i16(&mut self) -> i16 { + self.read_be_int_n(2) as i16 + } + + /// Reads a big-endian `f64`. + /// + /// `f64`s are 8 byte, IEEE754 double-precision floating point numbers. + fn read_be_f64(&mut self) -> f64 { + unsafe { + cast::transmute::<u64, f64>(self.read_be_u64()) + } + } + + /// Reads a big-endian `f32`. + /// + /// `f32`s are 4 byte, IEEE754 single-precision floating point numbers. + fn read_be_f32(&mut self) -> f32 { + unsafe { + cast::transmute::<u32, f32>(self.read_be_u32()) + } + } + + /// Reads a little-endian `u64`. + /// + /// `u64`s are 8 bytes long. + fn read_le_u64(&mut self) -> u64 { + self.read_le_uint_n(8) as u64 + } + + /// Reads a little-endian `u32`. + /// + /// `u32`s are 4 bytes long. + fn read_le_u32(&mut self) -> u32 { + self.read_le_uint_n(4) as u32 + } + + /// Reads a little-endian `u16`. + /// + /// `u16`s are 2 bytes long. + fn read_le_u16(&mut self) -> u16 { + self.read_le_uint_n(2) as u16 + } + + /// Reads a little-endian `i64`. + /// + /// `i64`s are 8 bytes long. + fn read_le_i64(&mut self) -> i64 { + self.read_le_int_n(8) as i64 + } + + /// Reads a little-endian `i32`. + /// + /// `i32`s are 4 bytes long. + fn read_le_i32(&mut self) -> i32 { + self.read_le_int_n(4) as i32 + } + + /// Reads a little-endian `i16`. + /// + /// `i16`s are 2 bytes long. + fn read_le_i16(&mut self) -> i16 { + self.read_le_int_n(2) as i16 + } + + /// Reads a little-endian `f64`. + /// + /// `f64`s are 8 byte, IEEE754 double-precision floating point numbers. + fn read_le_f64(&mut self) -> f64 { + unsafe { + cast::transmute::<u64, f64>(self.read_le_u64()) + } + } + + /// Reads a little-endian `f32`. + /// + /// `f32`s are 4 byte, IEEE754 single-precision floating point numbers. + fn read_le_f32(&mut self) -> f32 { + unsafe { + cast::transmute::<u32, f32>(self.read_le_u32()) + } + } + + /// Read a u8. + /// + /// `u8`s are 1 byte. + fn read_u8(&mut self) -> u8 { + match self.read_byte() { + Some(b) => b as u8, + None => 0 + } + } + + /// Read an i8. + /// + /// `i8`s are 1 byte. + fn read_i8(&mut self) -> i8 { + match self.read_byte() { + Some(b) => b as i8, + None => 0 + } + } + +} + +impl Reader for ~Reader { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { self.read(buf) } + fn eof(&mut self) -> bool { self.eof() } +} + +impl<'self> Reader for &'self mut Reader { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { self.read(buf) } + fn eof(&mut self) -> bool { self.eof() } +} + +fn extend_sign(val: u64, nbytes: uint) -> i64 { + let shift = (8 - nbytes) * 8; + (val << shift) as i64 >> shift +} + +pub trait Writer { + /// Write the given buffer + /// + /// # Failure + /// + /// Raises the `io_error` condition on error + fn write(&mut self, buf: &[u8]); + + /// Flush this output stream, ensuring that all intermediately buffered + /// contents reach their destination. + /// + /// This is by default a no-op and implementors of the `Writer` trait should + /// decide whether their stream needs to be buffered or not. + fn flush(&mut self) {} + + /// Write the result of passing n through `int::to_str_bytes`. + fn write_int(&mut self, n: int) { + int::to_str_bytes(n, 10u, |bytes| self.write(bytes)) + } + + /// Write the result of passing n through `uint::to_str_bytes`. + fn write_uint(&mut self, n: uint) { + uint::to_str_bytes(n, 10u, |bytes| self.write(bytes)) + } + + /// Write a little-endian uint (number of bytes depends on system). + fn write_le_uint(&mut self, n: uint) { + extensions::u64_to_le_bytes(n as u64, uint::bytes, |v| self.write(v)) + } + + /// Write a little-endian int (number of bytes depends on system). + fn write_le_int(&mut self, n: int) { + extensions::u64_to_le_bytes(n as u64, int::bytes, |v| self.write(v)) + } + + /// Write a big-endian uint (number of bytes depends on system). + fn write_be_uint(&mut self, n: uint) { + extensions::u64_to_be_bytes(n as u64, uint::bytes, |v| self.write(v)) + } + + /// Write a big-endian int (number of bytes depends on system). + fn write_be_int(&mut self, n: int) { + extensions::u64_to_be_bytes(n as u64, int::bytes, |v| self.write(v)) + } + + /// Write a big-endian u64 (8 bytes). + fn write_be_u64(&mut self, n: u64) { + extensions::u64_to_be_bytes(n, 8u, |v| self.write(v)) + } + + /// Write a big-endian u32 (4 bytes). + fn write_be_u32(&mut self, n: u32) { + extensions::u64_to_be_bytes(n as u64, 4u, |v| self.write(v)) + } + + /// Write a big-endian u16 (2 bytes). + fn write_be_u16(&mut self, n: u16) { + extensions::u64_to_be_bytes(n as u64, 2u, |v| self.write(v)) + } + + /// Write a big-endian i64 (8 bytes). + fn write_be_i64(&mut self, n: i64) { + extensions::u64_to_be_bytes(n as u64, 8u, |v| self.write(v)) + } + + /// Write a big-endian i32 (4 bytes). + fn write_be_i32(&mut self, n: i32) { + extensions::u64_to_be_bytes(n as u64, 4u, |v| self.write(v)) + } + + /// Write a big-endian i16 (2 bytes). + fn write_be_i16(&mut self, n: i16) { + extensions::u64_to_be_bytes(n as u64, 2u, |v| self.write(v)) + } + + /// Write a big-endian IEEE754 double-precision floating-point (8 bytes). + fn write_be_f64(&mut self, f: f64) { + unsafe { + self.write_be_u64(cast::transmute(f)) + } + } + + /// Write a big-endian IEEE754 single-precision floating-point (4 bytes). + fn write_be_f32(&mut self, f: f32) { + unsafe { + self.write_be_u32(cast::transmute(f)) + } + } + + /// Write a little-endian u64 (8 bytes). + fn write_le_u64(&mut self, n: u64) { + extensions::u64_to_le_bytes(n, 8u, |v| self.write(v)) + } + + /// Write a little-endian u32 (4 bytes). + fn write_le_u32(&mut self, n: u32) { + extensions::u64_to_le_bytes(n as u64, 4u, |v| self.write(v)) + } + + /// Write a little-endian u16 (2 bytes). + fn write_le_u16(&mut self, n: u16) { + extensions::u64_to_le_bytes(n as u64, 2u, |v| self.write(v)) + } + + /// Write a little-endian i64 (8 bytes). + fn write_le_i64(&mut self, n: i64) { + extensions::u64_to_le_bytes(n as u64, 8u, |v| self.write(v)) + } + + /// Write a little-endian i32 (4 bytes). + fn write_le_i32(&mut self, n: i32) { + extensions::u64_to_le_bytes(n as u64, 4u, |v| self.write(v)) + } + + /// Write a little-endian i16 (2 bytes). + fn write_le_i16(&mut self, n: i16) { + extensions::u64_to_le_bytes(n as u64, 2u, |v| self.write(v)) + } + + /// Write a little-endian IEEE754 double-precision floating-point + /// (8 bytes). + fn write_le_f64(&mut self, f: f64) { + unsafe { + self.write_le_u64(cast::transmute(f)) + } + } + + /// Write a little-endian IEEE754 single-precision floating-point + /// (4 bytes). + fn write_le_f32(&mut self, f: f32) { + unsafe { + self.write_le_u32(cast::transmute(f)) + } + } + + /// Write a u8 (1 byte). + fn write_u8(&mut self, n: u8) { + self.write([n]) + } + + /// Write a i8 (1 byte). + fn write_i8(&mut self, n: i8) { + self.write([n as u8]) + } +} + +impl Writer for ~Writer { + fn write(&mut self, buf: &[u8]) { self.write(buf) } + fn flush(&mut self) { self.flush() } +} + +impl<'self> Writer for &'self mut Writer { + fn write(&mut self, buf: &[u8]) { self.write(buf) } + fn flush(&mut self) { self.flush() } +} + +pub trait Stream: Reader + Writer { } + +impl<T: Reader + Writer> Stream for T {} + +pub enum SeekStyle { + /// Seek from the beginning of the stream + SeekSet, + /// Seek from the end of the stream + SeekEnd, + /// Seek from the current position + SeekCur, +} + +/// # XXX +/// * Are `u64` and `i64` the right choices? +pub trait Seek { + /// Return position of file cursor in the stream + fn tell(&self) -> u64; + + /// Seek to an offset in a stream + /// + /// A successful seek clears the EOF indicator. + /// + /// # XXX + /// + /// * What is the behavior when seeking past the end of a stream? + fn seek(&mut self, pos: i64, style: SeekStyle); +} + +/// A listener is a value that can consume itself to start listening for connections. +/// Doing so produces some sort of Acceptor. +pub trait Listener<T, A: Acceptor<T>> { + /// Spin up the listener and start queueing incoming connections + /// + /// # Failure + /// + /// Raises `io_error` condition. If the condition is handled, + /// then `listen` returns `None`. + fn listen(self) -> Option<A>; +} + +/// An acceptor is a value that presents incoming connections +pub trait Acceptor<T> { + /// Wait for and accept an incoming connection + /// + /// # Failure + /// Raise `io_error` condition. If the condition is handled, + /// then `accept` returns `None`. + fn accept(&mut self) -> Option<T>; + + /// Create an iterator over incoming connection attempts + fn incoming<'r>(&'r mut self) -> IncomingIterator<'r, Self> { + IncomingIterator { inc: self } + } +} + +/// An infinite iterator over incoming connection attempts. +/// Calling `next` will block the task until a connection is attempted. +/// +/// Since connection attempts can continue forever, this iterator always returns Some. +/// The Some contains another Option representing whether the connection attempt was succesful. +/// A successful connection will be wrapped in Some. +/// A failed connection is represented as a None and raises a condition. +struct IncomingIterator<'self, A> { + priv inc: &'self mut A, +} + +impl<'self, T, A: Acceptor<T>> Iterator<Option<T>> for IncomingIterator<'self, A> { + fn next(&mut self) -> Option<Option<T>> { + Some(self.inc.accept()) + } +} + +/// Common trait for decorator types. +/// +/// Provides accessors to get the inner, 'decorated' values. The I/O library +/// uses decorators to add functionality like compression and encryption to I/O +/// streams. +/// +/// # XXX +/// +/// Is this worth having a trait for? May be overkill +pub trait Decorator<T> { + /// Destroy the decorator and extract the decorated value + /// + /// # XXX + /// + /// Because this takes `self' one could never 'undecorate' a Reader/Writer + /// that has been boxed. Is that ok? This feature is mostly useful for + /// extracting the buffer from MemWriter + fn inner(self) -> T; + + /// Take an immutable reference to the decorated value + fn inner_ref<'a>(&'a self) -> &'a T; + + /// Take a mutable reference to the decorated value + fn inner_mut_ref<'a>(&'a mut self) -> &'a mut T; +} + +pub fn standard_error(kind: IoErrorKind) -> IoError { + match kind { + PreviousIoError => { + IoError { + kind: PreviousIoError, + desc: "Failing due to a previous I/O error", + detail: None + } + } + EndOfFile => { + IoError { + kind: EndOfFile, + desc: "End of file", + detail: None + } + } + IoUnavailable => { + IoError { + kind: IoUnavailable, + desc: "I/O is unavailable", + detail: None + } + } + _ => fail!() + } +} + +pub fn placeholder_error() -> IoError { + IoError { + kind: OtherIoError, + desc: "Placeholder error. You shouldn't be seeing this", + detail: None + } +} + +/// A mode specifies how a file should be opened or created. These modes are +/// passed to `File::open_mode` and are used to control where the file is +/// positioned when it is initially opened. +pub enum FileMode { + /// Opens a file positioned at the beginning. + Open, + /// Opens a file positioned at EOF. + Append, + /// Opens a file, truncating it if it already exists. + Truncate, +} + +/// Access permissions with which the file should be opened. `File`s +/// opened with `Read` will raise an `io_error` condition if written to. +pub enum FileAccess { + Read, + Write, + ReadWrite, +} + +/// Different kinds of files which can be identified by a call to stat +#[deriving(Eq)] +pub enum FileType { + TypeFile, + TypeDirectory, + TypeNamedPipe, + TypeBlockSpecial, + TypeSymlink, + TypeUnknown, +} + +pub struct FileStat { + /// The path that this stat structure is describing + path: Path, + /// The size of the file, in bytes + size: u64, + /// The kind of file this path points to (directory, file, pipe, etc.) + kind: FileType, + /// The file permissions currently on the file + perm: FilePermission, + + // FIXME(#10301): These time fields are pretty useless without an actual + // time representation, what are the milliseconds relative + // to? + + /// The time that the file was created at, in platform-dependent + /// milliseconds + created: u64, + /// The time that this file was last modified, in platform-dependent + /// milliseconds + modified: u64, + /// The time that this file was last accessed, in platform-dependent + /// milliseconds + accessed: u64, + + /// Information returned by stat() which is not guaranteed to be + /// platform-independent. This information may be useful on some platforms, + /// but it may have different meanings or no meaning at all on other + /// platforms. + /// + /// Usage of this field is discouraged, but if access is desired then the + /// fields are located here. + #[unstable] + unstable: UnstableFileStat, +} + +/// This structure represents all of the possible information which can be +/// returned from a `stat` syscall which is not contained in the `FileStat` +/// structure. This information is not necessarily platform independent, and may +/// have different meanings or no meaning at all on some platforms. +#[unstable] +pub struct UnstableFileStat { + device: u64, + inode: u64, + rdev: u64, + nlink: u64, + uid: u64, + gid: u64, + blksize: u64, + blocks: u64, + flags: u64, + gen: u64, +} + +/// A set of permissions for a file or directory is represented by a set of +/// flags which are or'd together. +pub type FilePermission = u32; + +// Each permission bit +pub static UserRead: FilePermission = 0x100; +pub static UserWrite: FilePermission = 0x080; +pub static UserExecute: FilePermission = 0x040; +pub static GroupRead: FilePermission = 0x020; +pub static GroupWrite: FilePermission = 0x010; +pub static GroupExecute: FilePermission = 0x008; +pub static OtherRead: FilePermission = 0x004; +pub static OtherWrite: FilePermission = 0x002; +pub static OtherExecute: FilePermission = 0x001; + +// Common combinations of these bits +pub static UserRWX: FilePermission = UserRead | UserWrite | UserExecute; +pub static GroupRWX: FilePermission = GroupRead | GroupWrite | GroupExecute; +pub static OtherRWX: FilePermission = OtherRead | OtherWrite | OtherExecute; + +/// A set of permissions for user owned files, this is equivalent to 0644 on +/// unix-like systems. +pub static UserFile: FilePermission = UserRead | UserWrite | GroupRead | OtherRead; +/// A set of permissions for user owned directories, this is equivalent to 0755 +/// on unix-like systems. +pub static UserDir: FilePermission = UserRWX | GroupRead | GroupExecute | + OtherRead | OtherExecute; +/// A set of permissions for user owned executables, this is equivalent to 0755 +/// on unix-like systems. +pub static UserExec: FilePermission = UserDir; + +/// A mask for all possible permission bits +pub static AllPermissions: FilePermission = 0x1ff; diff --git a/src/libstd/io/native/file.rs b/src/libstd/io/native/file.rs new file mode 100644 index 00000000000..0f1a64edb60 --- /dev/null +++ b/src/libstd/io/native/file.rs @@ -0,0 +1,761 @@ +// 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. + +//! Blocking posix-based file I/O + +#[allow(non_camel_case_types)]; + +use libc; +use os; +use prelude::*; +use super::super::*; + +#[cfg(windows)] +fn get_err(errno: i32) -> (IoErrorKind, &'static str) { + match errno { + libc::EOF => (EndOfFile, "end of file"), + _ => (OtherIoError, "unknown error"), + } +} + +#[cfg(not(windows))] +fn get_err(errno: i32) -> (IoErrorKind, &'static str) { + // XXX: this should probably be a bit more descriptive... + match errno { + libc::EOF => (EndOfFile, "end of file"), + + // These two constants can have the same value on some systems, but + // different values on others, so we can't use a match clause + x if x == libc::EAGAIN || x == libc::EWOULDBLOCK => + (ResourceUnavailable, "resource temporarily unavailable"), + + _ => (OtherIoError, "unknown error"), + } +} + +fn raise_error() { + let (kind, desc) = get_err(os::errno() as i32); + io_error::cond.raise(IoError { + kind: kind, + desc: desc, + detail: Some(os::last_os_error()) + }); +} + +fn keep_going(data: &[u8], f: &fn(*u8, uint) -> i64) -> i64 { + #[cfg(windows)] static eintr: int = 0; // doesn't matter + #[cfg(not(windows))] static eintr: int = libc::EINTR as int; + + let (data, origamt) = do data.as_imm_buf |data, amt| { (data, amt) }; + let mut data = data; + let mut amt = origamt; + while amt > 0 { + let mut ret; + loop { + ret = f(data, amt); + if cfg!(not(windows)) { break } // windows has no eintr + // if we get an eintr, then try again + if ret != -1 || os::errno() as int != eintr { break } + } + if ret == 0 { + break + } else if ret != -1 { + amt -= ret as uint; + data = unsafe { data.offset(ret as int) }; + } else { + return ret; + } + } + return (origamt - amt) as i64; +} + +pub type fd_t = libc::c_int; + +pub struct FileDesc { + priv fd: fd_t, + priv close_on_drop: bool, +} + +impl FileDesc { + /// Create a `FileDesc` from an open C file descriptor. + /// + /// The `FileDesc` will take ownership of the specified file descriptor and + /// close it upon destruction if the `close_on_drop` flag is true, otherwise + /// it will not close the file descriptor when this `FileDesc` is dropped. + /// + /// Note that all I/O operations done on this object will be *blocking*, but + /// they do not require the runtime to be active. + pub fn new(fd: fd_t, close_on_drop: bool) -> FileDesc { + FileDesc { fd: fd, close_on_drop: close_on_drop } + } +} + +impl Reader for FileDesc { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + #[cfg(windows)] type rlen = libc::c_uint; + #[cfg(not(windows))] type rlen = libc::size_t; + let ret = do keep_going(buf) |buf, len| { + unsafe { + libc::read(self.fd, buf as *mut libc::c_void, len as rlen) as i64 + } + }; + if ret == 0 { + None + } else if ret < 0 { + raise_error(); + None + } else { + Some(ret as uint) + } + } + + fn eof(&mut self) -> bool { false } +} + +impl Writer for FileDesc { + fn write(&mut self, buf: &[u8]) { + #[cfg(windows)] type wlen = libc::c_uint; + #[cfg(not(windows))] type wlen = libc::size_t; + let ret = do keep_going(buf) |buf, len| { + unsafe { + libc::write(self.fd, buf as *libc::c_void, len as wlen) as i64 + } + }; + if ret < 0 { + raise_error(); + } + } +} + +impl Drop for FileDesc { + fn drop(&mut self) { + if self.close_on_drop { + unsafe { libc::close(self.fd); } + } + } +} + +pub struct CFile { + priv file: *libc::FILE +} + +impl CFile { + /// Create a `CFile` from an open `FILE` pointer. + /// + /// The `CFile` takes ownership of the `FILE` pointer and will close it upon + /// destruction. + pub fn new(file: *libc::FILE) -> CFile { CFile { file: file } } +} + +impl Reader for CFile { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + let ret = do keep_going(buf) |buf, len| { + unsafe { + libc::fread(buf as *mut libc::c_void, 1, len as libc::size_t, + self.file) as i64 + } + }; + if ret == 0 { + None + } else if ret < 0 { + raise_error(); + None + } else { + Some(ret as uint) + } + } + + fn eof(&mut self) -> bool { + unsafe { libc::feof(self.file) != 0 } + } +} + +impl Writer for CFile { + fn write(&mut self, buf: &[u8]) { + let ret = do keep_going(buf) |buf, len| { + unsafe { + libc::fwrite(buf as *libc::c_void, 1, len as libc::size_t, + self.file) as i64 + } + }; + if ret < 0 { + raise_error(); + } + } + + fn flush(&mut self) { + if unsafe { libc::fflush(self.file) } < 0 { + raise_error(); + } + } +} + +impl Seek for CFile { + fn tell(&self) -> u64 { + let ret = unsafe { libc::ftell(self.file) }; + if ret < 0 { + raise_error(); + } + return ret as u64; + } + + fn seek(&mut self, pos: i64, style: SeekStyle) { + let whence = match style { + SeekSet => libc::SEEK_SET, + SeekEnd => libc::SEEK_END, + SeekCur => libc::SEEK_CUR, + }; + if unsafe { libc::fseek(self.file, pos as libc::c_long, whence) } < 0 { + raise_error(); + } + } +} + +impl Drop for CFile { + fn drop(&mut self) { + unsafe { libc::fclose(self.file); } + } +} + +#[cfg(test)] +mod tests { + use libc; + use os; + use prelude::*; + use io::{io_error, SeekSet}; + use super::*; + + #[ignore(cfg(target_os = "freebsd"))] // hmm, maybe pipes have a tiny buffer + fn test_file_desc() { + // Run this test with some pipes so we don't have to mess around with + // opening or closing files. + unsafe { + let os::Pipe { input, out } = os::pipe(); + let mut reader = FileDesc::new(input, true); + let mut writer = FileDesc::new(out, true); + + writer.write(bytes!("test")); + let mut buf = [0u8, ..4]; + match reader.read(buf) { + Some(4) => { + assert_eq!(buf[0], 't' as u8); + assert_eq!(buf[1], 'e' as u8); + assert_eq!(buf[2], 's' as u8); + assert_eq!(buf[3], 't' as u8); + } + r => fail!("invalid read: {:?}", r) + } + + let mut raised = false; + do io_error::cond.trap(|_| { raised = true; }).inside { + writer.read(buf); + } + assert!(raised); + + raised = false; + do io_error::cond.trap(|_| { raised = true; }).inside { + reader.write(buf); + } + assert!(raised); + } + } + + #[ignore(cfg(windows))] // apparently windows doesn't like tmpfile + fn test_cfile() { + unsafe { + let f = libc::tmpfile(); + assert!(!f.is_null()); + let mut file = CFile::new(f); + + file.write(bytes!("test")); + let mut buf = [0u8, ..4]; + file.seek(0, SeekSet); + match file.read(buf) { + Some(4) => { + assert_eq!(buf[0], 't' as u8); + assert_eq!(buf[1], 'e' as u8); + assert_eq!(buf[2], 's' as u8); + assert_eq!(buf[3], 't' as u8); + } + r => fail!("invalid read: {:?}", r) + } + } + } +} + +// n.b. these functions were all part of the old `std::os` module. There's lots +// of fun little nuances that were taken care of by these functions, but +// they are all thread-blocking versions that are no longer desired (we now +// use a non-blocking event loop implementation backed by libuv). +// +// In theory we will have a thread-blocking version of the event loop (if +// desired), so these functions may just need to get adapted to work in +// those situtations. For now, I'm leaving the code around so it doesn't +// get bitrotted instantaneously. +mod old_os { + use prelude::*; + use libc::{size_t, c_void, c_int}; + use libc; + use vec; + + #[cfg(not(windows))] use c_str::CString; + #[cfg(not(windows))] use libc::fclose; + #[cfg(test)] #[cfg(windows)] use os; + #[cfg(test)] use rand; + #[cfg(windows)] use str; + #[cfg(windows)] use ptr; + + // On Windows, wide character version of function must be used to support + // unicode, so functions should be split into at least two versions, + // which are for Windows and for non-Windows, if necessary. + // See https://github.com/mozilla/rust/issues/9822 for more information. + + mod rustrt { + use libc::{c_char, c_int}; + use libc; + + extern { + pub fn rust_path_is_dir(path: *libc::c_char) -> c_int; + pub fn rust_path_exists(path: *libc::c_char) -> c_int; + } + + // Uses _wstat instead of stat. + #[cfg(windows)] + extern { + pub fn rust_path_is_dir_u16(path: *u16) -> c_int; + pub fn rust_path_exists_u16(path: *u16) -> c_int; + } + } + + /// Recursively walk a directory structure + pub fn walk_dir(p: &Path, f: &fn(&Path) -> bool) -> bool { + let r = list_dir(p); + r.iter().advance(|q| { + let path = &p.join(q); + f(path) && (!path_is_dir(path) || walk_dir(path, |p| f(p))) + }) + } + + #[cfg(unix)] + /// Indicates whether a path represents a directory + pub fn path_is_dir(p: &Path) -> bool { + unsafe { + do p.with_c_str |buf| { + rustrt::rust_path_is_dir(buf) != 0 as c_int + } + } + } + + + #[cfg(windows)] + pub fn path_is_dir(p: &Path) -> bool { + unsafe { + do os::win32::as_utf16_p(p.as_str().unwrap()) |buf| { + rustrt::rust_path_is_dir_u16(buf) != 0 as c_int + } + } + } + + #[cfg(unix)] + /// Indicates whether a path exists + pub fn path_exists(p: &Path) -> bool { + unsafe { + do p.with_c_str |buf| { + rustrt::rust_path_exists(buf) != 0 as c_int + } + } + } + + #[cfg(windows)] + pub fn path_exists(p: &Path) -> bool { + unsafe { + do os::win32::as_utf16_p(p.as_str().unwrap()) |buf| { + rustrt::rust_path_exists_u16(buf) != 0 as c_int + } + } + } + + /// Creates a directory at the specified path + pub fn make_dir(p: &Path, mode: c_int) -> bool { + return mkdir(p, mode); + + #[cfg(windows)] + fn mkdir(p: &Path, _mode: c_int) -> bool { + unsafe { + use os::win32::as_utf16_p; + // FIXME: turn mode into something useful? #2623 + do as_utf16_p(p.as_str().unwrap()) |buf| { + libc::CreateDirectoryW(buf, ptr::mut_null()) + != (0 as libc::BOOL) + } + } + } + + #[cfg(unix)] + fn mkdir(p: &Path, mode: c_int) -> bool { + do p.with_c_str |buf| { + unsafe { + libc::mkdir(buf, mode as libc::mode_t) == (0 as c_int) + } + } + } + } + + /// Creates a directory with a given mode. + /// Returns true iff creation + /// succeeded. Also creates all intermediate subdirectories + /// if they don't already exist, giving all of them the same mode. + + // tjc: if directory exists but with different permissions, + // should we return false? + pub fn mkdir_recursive(p: &Path, mode: c_int) -> bool { + if path_is_dir(p) { + return true; + } + if p.filename().is_some() { + let mut p_ = p.clone(); + p_.pop(); + if !mkdir_recursive(&p_, mode) { + return false; + } + } + return make_dir(p, mode); + } + + /// Lists the contents of a directory + /// + /// Each resulting Path is a relative path with no directory component. + pub fn list_dir(p: &Path) -> ~[Path] { + unsafe { + #[cfg(target_os = "linux")] + #[cfg(target_os = "android")] + #[cfg(target_os = "freebsd")] + #[cfg(target_os = "macos")] + unsafe fn get_list(p: &Path) -> ~[Path] { + use libc::{dirent_t}; + use libc::{opendir, readdir, closedir}; + extern { + fn rust_list_dir_val(ptr: *dirent_t) -> *libc::c_char; + } + let mut paths = ~[]; + debug!("os::list_dir -- BEFORE OPENDIR"); + + let dir_ptr = do p.with_c_str |buf| { + opendir(buf) + }; + + if (dir_ptr as uint != 0) { + debug!("os::list_dir -- opendir() SUCCESS"); + let mut entry_ptr = readdir(dir_ptr); + while (entry_ptr as uint != 0) { + let cstr = CString::new(rust_list_dir_val(entry_ptr), false); + paths.push(Path::new(cstr)); + entry_ptr = readdir(dir_ptr); + } + closedir(dir_ptr); + } + else { + debug!("os::list_dir -- opendir() FAILURE"); + } + debug!("os::list_dir -- AFTER -- \\#: {}", paths.len()); + paths + } + #[cfg(windows)] + unsafe fn get_list(p: &Path) -> ~[Path] { + use libc::consts::os::extra::INVALID_HANDLE_VALUE; + use libc::{wcslen, free}; + use libc::funcs::extra::kernel32::{ + FindFirstFileW, + FindNextFileW, + FindClose, + }; + use libc::types::os::arch::extra::HANDLE; + use os::win32::{ + as_utf16_p + }; + use rt::global_heap::malloc_raw; + + #[nolink] + extern { + fn rust_list_dir_wfd_size() -> libc::size_t; + fn rust_list_dir_wfd_fp_buf(wfd: *libc::c_void) -> *u16; + } + let star = p.join("*"); + do as_utf16_p(star.as_str().unwrap()) |path_ptr| { + let mut paths = ~[]; + let wfd_ptr = malloc_raw(rust_list_dir_wfd_size() as uint); + let find_handle = FindFirstFileW(path_ptr, wfd_ptr as HANDLE); + if find_handle as libc::c_int != INVALID_HANDLE_VALUE { + let mut more_files = 1 as libc::c_int; + while more_files != 0 { + let fp_buf = rust_list_dir_wfd_fp_buf(wfd_ptr); + if fp_buf as uint == 0 { + fail!("os::list_dir() failure: got null ptr from wfd"); + } + else { + let fp_vec = vec::from_buf( + fp_buf, wcslen(fp_buf) as uint); + let fp_str = str::from_utf16(fp_vec); + paths.push(Path::new(fp_str)); + } + more_files = FindNextFileW(find_handle, wfd_ptr as HANDLE); + } + FindClose(find_handle); + free(wfd_ptr) + } + paths + } + } + do get_list(p).move_iter().filter |path| { + path.as_vec() != bytes!(".") && path.as_vec() != bytes!("..") + }.collect() + } + } + + /// Removes a directory at the specified path, after removing + /// all its contents. Use carefully! + pub fn remove_dir_recursive(p: &Path) -> bool { + let mut error_happened = false; + do walk_dir(p) |inner| { + if !error_happened { + if path_is_dir(inner) { + if !remove_dir_recursive(inner) { + error_happened = true; + } + } + else { + if !remove_file(inner) { + error_happened = true; + } + } + } + true + }; + // Directory should now be empty + !error_happened && remove_dir(p) + } + + /// Removes a directory at the specified path + pub fn remove_dir(p: &Path) -> bool { + return rmdir(p); + + #[cfg(windows)] + fn rmdir(p: &Path) -> bool { + unsafe { + use os::win32::as_utf16_p; + return do as_utf16_p(p.as_str().unwrap()) |buf| { + libc::RemoveDirectoryW(buf) != (0 as libc::BOOL) + }; + } + } + + #[cfg(unix)] + fn rmdir(p: &Path) -> bool { + do p.with_c_str |buf| { + unsafe { + libc::rmdir(buf) == (0 as c_int) + } + } + } + } + + /// Deletes an existing file + pub fn remove_file(p: &Path) -> bool { + return unlink(p); + + #[cfg(windows)] + fn unlink(p: &Path) -> bool { + unsafe { + use os::win32::as_utf16_p; + return do as_utf16_p(p.as_str().unwrap()) |buf| { + libc::DeleteFileW(buf) != (0 as libc::BOOL) + }; + } + } + + #[cfg(unix)] + fn unlink(p: &Path) -> bool { + unsafe { + do p.with_c_str |buf| { + libc::unlink(buf) == (0 as c_int) + } + } + } + } + + /// Renames an existing file or directory + pub fn rename_file(old: &Path, new: &Path) -> bool { + unsafe { + do old.with_c_str |old_buf| { + do new.with_c_str |new_buf| { + libc::rename(old_buf, new_buf) == (0 as c_int) + } + } + } + } + + /// Copies a file from one location to another + pub fn copy_file(from: &Path, to: &Path) -> bool { + return do_copy_file(from, to); + + #[cfg(windows)] + fn do_copy_file(from: &Path, to: &Path) -> bool { + unsafe { + use os::win32::as_utf16_p; + return do as_utf16_p(from.as_str().unwrap()) |fromp| { + do as_utf16_p(to.as_str().unwrap()) |top| { + libc::CopyFileW(fromp, top, (0 as libc::BOOL)) != + (0 as libc::BOOL) + } + } + } + } + + #[cfg(unix)] + fn do_copy_file(from: &Path, to: &Path) -> bool { + unsafe { + let istream = do from.with_c_str |fromp| { + do "rb".with_c_str |modebuf| { + libc::fopen(fromp, modebuf) + } + }; + if istream as uint == 0u { + return false; + } + // Preserve permissions + let from_mode = from.stat().perm; + + let ostream = do to.with_c_str |top| { + do "w+b".with_c_str |modebuf| { + libc::fopen(top, modebuf) + } + }; + if ostream as uint == 0u { + fclose(istream); + return false; + } + let bufsize = 8192u; + let mut buf = vec::with_capacity::<u8>(bufsize); + let mut done = false; + let mut ok = true; + while !done { + do buf.as_mut_buf |b, _sz| { + let nread = libc::fread(b as *mut c_void, 1u as size_t, + bufsize as size_t, + istream); + if nread > 0 as size_t { + if libc::fwrite(b as *c_void, 1u as size_t, nread, + ostream) != nread { + ok = false; + done = true; + } + } else { + done = true; + } + } + } + fclose(istream); + fclose(ostream); + + // Give the new file the old file's permissions + if do to.with_c_str |to_buf| { + libc::chmod(to_buf, from_mode as libc::mode_t) + } != 0 { + return false; // should be a condition... + } + return ok; + } + } + } + + #[test] + fn tmpdir() { + let p = os::tmpdir(); + let s = p.as_str(); + assert!(s.is_some() && s.unwrap() != "."); + } + + // Issue #712 + #[test] + fn test_list_dir_no_invalid_memory_access() { + list_dir(&Path::new(".")); + } + + #[test] + fn test_list_dir() { + let dirs = list_dir(&Path::new(".")); + // Just assuming that we've got some contents in the current directory + assert!(dirs.len() > 0u); + + for dir in dirs.iter() { + debug!("{:?}", (*dir).clone()); + } + } + + #[test] + #[cfg(not(windows))] + fn test_list_dir_root() { + let dirs = list_dir(&Path::new("/")); + assert!(dirs.len() > 1); + } + #[test] + #[cfg(windows)] + fn test_list_dir_root() { + let dirs = list_dir(&Path::new("C:\\")); + assert!(dirs.len() > 1); + } + + #[test] + fn test_path_is_dir() { + use io::fs::{mkdir_recursive}; + use io::{File, UserRWX}; + + assert!((path_is_dir(&Path::new(".")))); + assert!((!path_is_dir(&Path::new("test/stdtest/fs.rs")))); + + let mut dirpath = os::tmpdir(); + dirpath.push(format!("rust-test-{}/test-\uac00\u4e00\u30fc\u4f60\u597d", + rand::random::<u32>())); // 가一ー你好 + debug!("path_is_dir dirpath: {}", dirpath.display()); + + mkdir_recursive(&dirpath, UserRWX); + + assert!((path_is_dir(&dirpath))); + + let mut filepath = dirpath; + filepath.push("unicode-file-\uac00\u4e00\u30fc\u4f60\u597d.rs"); + debug!("path_is_dir filepath: {}", filepath.display()); + + File::create(&filepath); // ignore return; touch only + assert!((!path_is_dir(&filepath))); + + assert!((!path_is_dir(&Path::new( + "test/unicode-bogus-dir-\uac00\u4e00\u30fc\u4f60\u597d")))); + } + + #[test] + fn test_path_exists() { + use io::fs::mkdir_recursive; + use io::UserRWX; + + assert!((path_exists(&Path::new(".")))); + assert!((!path_exists(&Path::new( + "test/nonexistent-bogus-path")))); + + let mut dirpath = os::tmpdir(); + dirpath.push(format!("rust-test-{}/test-\uac01\u4e01\u30fc\u518d\u89c1", + rand::random::<u32>())); // 각丁ー再见 + + mkdir_recursive(&dirpath, UserRWX); + assert!((path_exists(&dirpath))); + assert!((!path_exists(&Path::new( + "test/unicode-bogus-path-\uac01\u4e01\u30fc\u518d\u89c1")))); + } +} diff --git a/src/libstd/io/native/process.rs b/src/libstd/io/native/process.rs new file mode 100644 index 00000000000..de03ac1c07d --- /dev/null +++ b/src/libstd/io/native/process.rs @@ -0,0 +1,734 @@ +// Copyright 2012-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. + +use cast; +use libc::{pid_t, c_void, c_int}; +use libc; +use os; +use prelude::*; +use ptr; +use io; +use super::file; + +/** + * A value representing a child process. + * + * The lifetime of this value is linked to the lifetime of the actual + * process - the Process destructor calls self.finish() which waits + * for the process to terminate. + */ +pub struct Process { + /// The unique id of the process (this should never be negative). + priv pid: pid_t, + + /// A handle to the process - on unix this will always be NULL, but on + /// windows it will be a HANDLE to the process, which will prevent the + /// pid being re-used until the handle is closed. + priv handle: *(), + + /// Currently known stdin of the child, if any + priv input: Option<file::FileDesc>, + /// Currently known stdout of the child, if any + priv output: Option<file::FileDesc>, + /// Currently known stderr of the child, if any + priv error: Option<file::FileDesc>, + + /// None until finish() is called. + priv exit_code: Option<int>, +} + +impl Process { + /// Creates a new process using native process-spawning abilities provided + /// by the OS. Operations on this process will be blocking instead of using + /// the runtime for sleeping just this current task. + /// + /// # Arguments + /// + /// * prog - the program to run + /// * args - the arguments to pass to the program, not including the program + /// itself + /// * env - an optional envrionment to specify for the child process. If + /// this value is `None`, then the child will inherit the parent's + /// environment + /// * cwd - an optionally specified current working directory of the child, + /// defaulting to the parent's current working directory + /// * stdin, stdout, stderr - These optionally specified file descriptors + /// dictate where the stdin/out/err of the child process will go. If + /// these are `None`, then this module will bind the input/output to an + /// os pipe instead. This process takes ownership of these file + /// descriptors, closing them upon destruction of the process. + pub fn new(prog: &str, args: &[~str], env: Option<~[(~str, ~str)]>, + cwd: Option<&Path>, + stdin: Option<file::fd_t>, + stdout: Option<file::fd_t>, + stderr: Option<file::fd_t>) -> Process { + let (in_pipe, in_fd) = match stdin { + None => { + let pipe = os::pipe(); + (Some(pipe), pipe.input) + }, + Some(fd) => (None, fd) + }; + let (out_pipe, out_fd) = match stdout { + None => { + let pipe = os::pipe(); + (Some(pipe), pipe.out) + }, + Some(fd) => (None, fd) + }; + let (err_pipe, err_fd) = match stderr { + None => { + let pipe = os::pipe(); + (Some(pipe), pipe.out) + }, + Some(fd) => (None, fd) + }; + + let res = spawn_process_os(prog, args, env, cwd, + in_fd, out_fd, err_fd); + + unsafe { + for pipe in in_pipe.iter() { libc::close(pipe.input); } + for pipe in out_pipe.iter() { libc::close(pipe.out); } + for pipe in err_pipe.iter() { libc::close(pipe.out); } + } + + Process { + pid: res.pid, + handle: res.handle, + input: in_pipe.map(|pipe| file::FileDesc::new(pipe.out, true)), + output: out_pipe.map(|pipe| file::FileDesc::new(pipe.input, true)), + error: err_pipe.map(|pipe| file::FileDesc::new(pipe.input, true)), + exit_code: None, + } + } + + /// Returns the unique id of the process + pub fn id(&self) -> pid_t { self.pid } + + /** + * Returns an io::Writer that can be used to write to this Process's stdin. + * + * Fails if there is no stdinavailable (it's already been removed by + * take_input) + */ + pub fn input<'a>(&'a mut self) -> &'a mut io::Writer { + match self.input { + Some(ref mut fd) => fd as &mut io::Writer, + None => fail!("This process has no stdin") + } + } + + /** + * Returns an io::Reader that can be used to read from this Process's + * stdout. + * + * Fails if there is no stdin available (it's already been removed by + * take_output) + */ + pub fn output<'a>(&'a mut self) -> &'a mut io::Reader { + match self.input { + Some(ref mut fd) => fd as &mut io::Reader, + None => fail!("This process has no stdout") + } + } + + /** + * Returns an io::Reader that can be used to read from this Process's + * stderr. + * + * Fails if there is no stdin available (it's already been removed by + * take_error) + */ + pub fn error<'a>(&'a mut self) -> &'a mut io::Reader { + match self.error { + Some(ref mut fd) => fd as &mut io::Reader, + None => fail!("This process has no stderr") + } + } + + /** + * Takes the stdin of this process, transferring ownership to the caller. + * Note that when the return value is destroyed, the handle will be closed + * for the child process. + */ + pub fn take_input(&mut self) -> Option<~io::Writer> { + self.input.take().map(|fd| ~fd as ~io::Writer) + } + + /** + * Takes the stdout of this process, transferring ownership to the caller. + * Note that when the return value is destroyed, the handle will be closed + * for the child process. + */ + pub fn take_output(&mut self) -> Option<~io::Reader> { + self.output.take().map(|fd| ~fd as ~io::Reader) + } + + /** + * Takes the stderr of this process, transferring ownership to the caller. + * Note that when the return value is destroyed, the handle will be closed + * for the child process. + */ + pub fn take_error(&mut self) -> Option<~io::Reader> { + self.error.take().map(|fd| ~fd as ~io::Reader) + } + + pub fn wait(&mut self) -> int { + for &code in self.exit_code.iter() { + return code; + } + let code = waitpid(self.pid); + self.exit_code = Some(code); + return code; + } + + pub fn signal(&mut self, signum: int) -> Result<(), io::IoError> { + // if the process has finished, and therefore had waitpid called, + // and we kill it, then on unix we might ending up killing a + // newer process that happens to have the same (re-used) id + match self.exit_code { + Some(*) => return Err(io::IoError { + kind: io::OtherIoError, + desc: "can't kill an exited process", + detail: None, + }), + None => {} + } + return unsafe { killpid(self.pid, signum) }; + + #[cfg(windows)] + unsafe fn killpid(pid: pid_t, signal: int) -> Result<(), io::IoError> { + match signal { + io::process::PleaseExitSignal | + io::process::MustDieSignal => { + libc::funcs::extra::kernel32::TerminateProcess( + cast::transmute(pid), 1); + Ok(()) + } + _ => Err(io::IoError { + kind: io::OtherIoError, + desc: "unsupported signal on windows", + detail: None, + }) + } + } + + #[cfg(not(windows))] + unsafe fn killpid(pid: pid_t, signal: int) -> Result<(), io::IoError> { + libc::funcs::posix88::signal::kill(pid, signal as c_int); + Ok(()) + } + } +} + +impl Drop for Process { + fn drop(&mut self) { + // close all these handles + self.take_input(); + self.take_output(); + self.take_error(); + self.wait(); + free_handle(self.handle); + } +} + +struct SpawnProcessResult { + pid: pid_t, + handle: *(), +} + +#[cfg(windows)] +fn spawn_process_os(prog: &str, args: &[~str], + env: Option<~[(~str, ~str)]>, + dir: Option<&Path>, + in_fd: c_int, out_fd: c_int, err_fd: c_int) -> SpawnProcessResult { + use libc::types::os::arch::extra::{DWORD, HANDLE, STARTUPINFO}; + use libc::consts::os::extra::{ + TRUE, FALSE, + STARTF_USESTDHANDLES, + INVALID_HANDLE_VALUE, + DUPLICATE_SAME_ACCESS + }; + use libc::funcs::extra::kernel32::{ + GetCurrentProcess, + DuplicateHandle, + CloseHandle, + CreateProcessA + }; + use libc::funcs::extra::msvcrt::get_osfhandle; + + use mem; + + unsafe { + + let mut si = zeroed_startupinfo(); + si.cb = mem::size_of::<STARTUPINFO>() as DWORD; + si.dwFlags = STARTF_USESTDHANDLES; + + let cur_proc = GetCurrentProcess(); + + let orig_std_in = get_osfhandle(in_fd) as HANDLE; + if orig_std_in == INVALID_HANDLE_VALUE as HANDLE { + fail!("failure in get_osfhandle: {}", os::last_os_error()); + } + if DuplicateHandle(cur_proc, orig_std_in, cur_proc, &mut si.hStdInput, + 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE { + fail!("failure in DuplicateHandle: {}", os::last_os_error()); + } + + let orig_std_out = get_osfhandle(out_fd) as HANDLE; + if orig_std_out == INVALID_HANDLE_VALUE as HANDLE { + fail!("failure in get_osfhandle: {}", os::last_os_error()); + } + if DuplicateHandle(cur_proc, orig_std_out, cur_proc, &mut si.hStdOutput, + 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE { + fail!("failure in DuplicateHandle: {}", os::last_os_error()); + } + + let orig_std_err = get_osfhandle(err_fd) as HANDLE; + if orig_std_err == INVALID_HANDLE_VALUE as HANDLE { + fail!("failure in get_osfhandle: {}", os::last_os_error()); + } + if DuplicateHandle(cur_proc, orig_std_err, cur_proc, &mut si.hStdError, + 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE { + fail!("failure in DuplicateHandle: {}", os::last_os_error()); + } + + let cmd = make_command_line(prog, args); + let mut pi = zeroed_process_information(); + let mut create_err = None; + + do with_envp(env) |envp| { + do with_dirp(dir) |dirp| { + do cmd.with_c_str |cmdp| { + let created = CreateProcessA(ptr::null(), cast::transmute(cmdp), + ptr::mut_null(), ptr::mut_null(), TRUE, + 0, envp, dirp, &mut si, &mut pi); + if created == FALSE { + create_err = Some(os::last_os_error()); + } + } + } + } + + CloseHandle(si.hStdInput); + CloseHandle(si.hStdOutput); + CloseHandle(si.hStdError); + + for msg in create_err.iter() { + fail!("failure in CreateProcess: {}", *msg); + } + + // We close the thread handle because we don't care about keeping the + // thread id valid, and we aren't keeping the thread handle around to be + // able to close it later. We don't close the process handle however + // because we want the process id to stay valid at least until the + // calling code closes the process handle. + CloseHandle(pi.hThread); + + SpawnProcessResult { + pid: pi.dwProcessId as pid_t, + handle: pi.hProcess as *() + } + } +} + +#[cfg(windows)] +fn zeroed_startupinfo() -> libc::types::os::arch::extra::STARTUPINFO { + libc::types::os::arch::extra::STARTUPINFO { + cb: 0, + lpReserved: ptr::mut_null(), + lpDesktop: ptr::mut_null(), + lpTitle: ptr::mut_null(), + dwX: 0, + dwY: 0, + dwXSize: 0, + dwYSize: 0, + dwXCountChars: 0, + dwYCountCharts: 0, + dwFillAttribute: 0, + dwFlags: 0, + wShowWindow: 0, + cbReserved2: 0, + lpReserved2: ptr::mut_null(), + hStdInput: ptr::mut_null(), + hStdOutput: ptr::mut_null(), + hStdError: ptr::mut_null() + } +} + +#[cfg(windows)] +fn zeroed_process_information() -> libc::types::os::arch::extra::PROCESS_INFORMATION { + libc::types::os::arch::extra::PROCESS_INFORMATION { + hProcess: ptr::mut_null(), + hThread: ptr::mut_null(), + dwProcessId: 0, + dwThreadId: 0 + } +} + +// FIXME: this is only pub so it can be tested (see issue #4536) +#[cfg(windows)] +pub fn make_command_line(prog: &str, args: &[~str]) -> ~str { + let mut cmd = ~""; + append_arg(&mut cmd, prog); + for arg in args.iter() { + cmd.push_char(' '); + append_arg(&mut cmd, *arg); + } + return cmd; + + fn append_arg(cmd: &mut ~str, arg: &str) { + let quote = arg.iter().any(|c| c == ' ' || c == '\t'); + if quote { + cmd.push_char('"'); + } + for i in range(0u, arg.len()) { + append_char_at(cmd, arg, i); + } + if quote { + cmd.push_char('"'); + } + } + + fn append_char_at(cmd: &mut ~str, arg: &str, i: uint) { + match arg[i] as char { + '"' => { + // Escape quotes. + cmd.push_str("\\\""); + } + '\\' => { + if backslash_run_ends_in_quote(arg, i) { + // Double all backslashes that are in runs before quotes. + cmd.push_str("\\\\"); + } else { + // Pass other backslashes through unescaped. + cmd.push_char('\\'); + } + } + c => { + cmd.push_char(c); + } + } + } + + fn backslash_run_ends_in_quote(s: &str, mut i: uint) -> bool { + while i < s.len() && s[i] as char == '\\' { + i += 1; + } + return i < s.len() && s[i] as char == '"'; + } +} + +#[cfg(unix)] +fn spawn_process_os(prog: &str, args: &[~str], + env: Option<~[(~str, ~str)]>, + dir: Option<&Path>, + in_fd: c_int, out_fd: c_int, err_fd: c_int) -> SpawnProcessResult { + use libc::funcs::posix88::unistd::{fork, dup2, close, chdir, execvp}; + use libc::funcs::bsd44::getdtablesize; + + mod rustrt { + #[abi = "cdecl"] + extern { + pub fn rust_unset_sigprocmask(); + } + } + + #[cfg(windows)] + unsafe fn set_environ(_envp: *c_void) {} + #[cfg(target_os = "macos")] + unsafe fn set_environ(envp: *c_void) { + extern { fn _NSGetEnviron() -> *mut *c_void; } + + *_NSGetEnviron() = envp; + } + #[cfg(not(target_os = "macos"), not(windows))] + unsafe fn set_environ(envp: *c_void) { + extern { + static mut environ: *c_void; + } + environ = envp; + } + + unsafe { + + let pid = fork(); + if pid < 0 { + fail!("failure in fork: {}", os::last_os_error()); + } else if pid > 0 { + return SpawnProcessResult {pid: pid, handle: ptr::null()}; + } + + rustrt::rust_unset_sigprocmask(); + + if dup2(in_fd, 0) == -1 { + fail!("failure in dup2(in_fd, 0): {}", os::last_os_error()); + } + if dup2(out_fd, 1) == -1 { + fail!("failure in dup2(out_fd, 1): {}", os::last_os_error()); + } + if dup2(err_fd, 2) == -1 { + fail!("failure in dup3(err_fd, 2): {}", os::last_os_error()); + } + // close all other fds + for fd in range(3, getdtablesize()).invert() { + close(fd as c_int); + } + + do with_dirp(dir) |dirp| { + if !dirp.is_null() && chdir(dirp) == -1 { + fail!("failure in chdir: {}", os::last_os_error()); + } + } + + do with_envp(env) |envp| { + if !envp.is_null() { + set_environ(envp); + } + do with_argv(prog, args) |argv| { + execvp(*argv, argv); + // execvp only returns if an error occurred + fail!("failure in execvp: {}", os::last_os_error()); + } + } + } +} + +#[cfg(unix)] +fn with_argv<T>(prog: &str, args: &[~str], cb: &fn(**libc::c_char) -> T) -> T { + use vec; + + // We can't directly convert `str`s into `*char`s, as someone needs to hold + // a reference to the intermediary byte buffers. So first build an array to + // hold all the ~[u8] byte strings. + let mut tmps = vec::with_capacity(args.len() + 1); + + tmps.push(prog.to_c_str()); + + for arg in args.iter() { + tmps.push(arg.to_c_str()); + } + + // Next, convert each of the byte strings into a pointer. This is + // technically unsafe as the caller could leak these pointers out of our + // scope. + let mut ptrs = do tmps.map |tmp| { + tmp.with_ref(|buf| buf) + }; + + // Finally, make sure we add a null pointer. + ptrs.push(ptr::null()); + + ptrs.as_imm_buf(|buf, _| cb(buf)) +} + +#[cfg(unix)] +fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: &fn(*c_void) -> T) -> T { + use vec; + + // On posixy systems we can pass a char** for envp, which is a + // null-terminated array of "k=v\n" strings. Like `with_argv`, we have to + // have a temporary buffer to hold the intermediary `~[u8]` byte strings. + match env { + Some(env) => { + let mut tmps = vec::with_capacity(env.len()); + + for pair in env.iter() { + let kv = format!("{}={}", pair.first(), pair.second()); + tmps.push(kv.to_c_str()); + } + + // Once again, this is unsafe. + let mut ptrs = do tmps.map |tmp| { + tmp.with_ref(|buf| buf) + }; + ptrs.push(ptr::null()); + + do ptrs.as_imm_buf |buf, _| { + unsafe { cb(cast::transmute(buf)) } + } + } + _ => cb(ptr::null()) + } +} + +#[cfg(windows)] +fn with_envp<T>(env: Option<~[(~str, ~str)]>, cb: &fn(*mut c_void) -> T) -> T { + // On win32 we pass an "environment block" which is not a char**, but + // rather a concatenation of null-terminated k=v\0 sequences, with a final + // \0 to terminate. + match env { + Some(env) => { + let mut blk = ~[]; + + for pair in env.iter() { + let kv = format!("{}={}", pair.first(), pair.second()); + blk.push_all(kv.as_bytes()); + blk.push(0); + } + + blk.push(0); + + do blk.as_imm_buf |p, _len| { + unsafe { cb(cast::transmute(p)) } + } + } + _ => cb(ptr::mut_null()) + } +} + +fn with_dirp<T>(d: Option<&Path>, cb: &fn(*libc::c_char) -> T) -> T { + match d { + Some(dir) => dir.with_c_str(|buf| cb(buf)), + None => cb(ptr::null()) + } +} + +#[cfg(windows)] +fn free_handle(handle: *()) { + unsafe { + libc::funcs::extra::kernel32::CloseHandle(cast::transmute(handle)); + } +} + +#[cfg(unix)] +fn free_handle(_handle: *()) { + // unix has no process handle object, just a pid +} + +/** + * Waits for a process to exit and returns the exit code, failing + * if there is no process with the specified id. + * + * Note that this is private to avoid race conditions on unix where if + * a user calls waitpid(some_process.get_id()) then some_process.finish() + * and some_process.destroy() and some_process.finalize() will then either + * operate on a none-existent process or, even worse, on a newer process + * with the same id. + */ +fn waitpid(pid: pid_t) -> int { + return waitpid_os(pid); + + #[cfg(windows)] + fn waitpid_os(pid: pid_t) -> int { + use libc::types::os::arch::extra::DWORD; + use libc::consts::os::extra::{ + SYNCHRONIZE, + PROCESS_QUERY_INFORMATION, + FALSE, + STILL_ACTIVE, + INFINITE, + WAIT_FAILED + }; + use libc::funcs::extra::kernel32::{ + OpenProcess, + GetExitCodeProcess, + CloseHandle, + WaitForSingleObject + }; + + unsafe { + + let process = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, + FALSE, + pid as DWORD); + if process.is_null() { + fail!("failure in OpenProcess: {}", os::last_os_error()); + } + + loop { + let mut status = 0; + if GetExitCodeProcess(process, &mut status) == FALSE { + CloseHandle(process); + fail!("failure in GetExitCodeProcess: {}", os::last_os_error()); + } + if status != STILL_ACTIVE { + CloseHandle(process); + return status as int; + } + if WaitForSingleObject(process, INFINITE) == WAIT_FAILED { + CloseHandle(process); + fail!("failure in WaitForSingleObject: {}", os::last_os_error()); + } + } + } + } + + #[cfg(unix)] + fn waitpid_os(pid: pid_t) -> int { + use libc::funcs::posix01::wait::*; + + #[cfg(target_os = "linux")] + #[cfg(target_os = "android")] + fn WIFEXITED(status: i32) -> bool { + (status & 0xffi32) == 0i32 + } + + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + fn WIFEXITED(status: i32) -> bool { + (status & 0x7fi32) == 0i32 + } + + #[cfg(target_os = "linux")] + #[cfg(target_os = "android")] + fn WEXITSTATUS(status: i32) -> i32 { + (status >> 8i32) & 0xffi32 + } + + #[cfg(target_os = "macos")] + #[cfg(target_os = "freebsd")] + fn WEXITSTATUS(status: i32) -> i32 { + status >> 8i32 + } + + let mut status = 0 as c_int; + if unsafe { waitpid(pid, &mut status, 0) } == -1 { + fail!("failure in waitpid: {}", os::last_os_error()); + } + + return if WIFEXITED(status) { + WEXITSTATUS(status) as int + } else { + 1 + }; + } +} + +#[cfg(test)] +mod tests { + + #[test] #[cfg(windows)] + fn test_make_command_line() { + use super::make_command_line; + assert_eq!( + make_command_line("prog", [~"aaa", ~"bbb", ~"ccc"]), + ~"prog aaa bbb ccc" + ); + assert_eq!( + make_command_line("C:\\Program Files\\blah\\blah.exe", [~"aaa"]), + ~"\"C:\\Program Files\\blah\\blah.exe\" aaa" + ); + assert_eq!( + make_command_line("C:\\Program Files\\test", [~"aa\"bb"]), + ~"\"C:\\Program Files\\test\" aa\\\"bb" + ); + assert_eq!( + make_command_line("echo", [~"a b c"]), + ~"echo \"a b c\"" + ); + } + + // Currently most of the tests of this functionality live inside std::run, + // but they may move here eventually as a non-blocking backend is added to + // std::run +} diff --git a/src/libstd/io/native/stdio.rs b/src/libstd/io/native/stdio.rs new file mode 100644 index 00000000000..68748ab49a3 --- /dev/null +++ b/src/libstd/io/native/stdio.rs @@ -0,0 +1,63 @@ +// 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. + +use libc; +use option::Option; +use io::{Reader, Writer}; +use super::file; + +/// Creates a new handle to the stdin of this process +pub fn stdin() -> StdIn { StdIn::new() } +/// Creates a new handle to the stdout of this process +pub fn stdout() -> StdOut { StdOut::new(libc::STDOUT_FILENO) } +/// Creates a new handle to the stderr of this process +pub fn stderr() -> StdOut { StdOut::new(libc::STDERR_FILENO) } + +pub fn print(s: &str) { + stdout().write(s.as_bytes()) +} + +pub fn println(s: &str) { + let mut out = stdout(); + out.write(s.as_bytes()); + out.write(['\n' as u8]); +} + +pub struct StdIn { + priv fd: file::FileDesc +} + +impl StdIn { + /// Duplicates the stdin file descriptor, returning an io::Reader + pub fn new() -> StdIn { + StdIn { fd: file::FileDesc::new(libc::STDIN_FILENO, false) } + } +} + +impl Reader for StdIn { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { self.fd.read(buf) } + fn eof(&mut self) -> bool { self.fd.eof() } +} + +pub struct StdOut { + priv fd: file::FileDesc +} + +impl StdOut { + /// Duplicates the specified file descriptor, returning an io::Writer + pub fn new(fd: file::fd_t) -> StdOut { + StdOut { fd: file::FileDesc::new(fd, false) } + } +} + +impl Writer for StdOut { + fn write(&mut self, buf: &[u8]) { self.fd.write(buf) } + fn flush(&mut self) { self.fd.flush() } +} diff --git a/src/libstd/io/net/addrinfo.rs b/src/libstd/io/net/addrinfo.rs new file mode 100644 index 00000000000..7e2d1fc5e83 --- /dev/null +++ b/src/libstd/io/net/addrinfo.rs @@ -0,0 +1,128 @@ +// 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. + +/*! + +Synchronous DNS Resolution + +Contains the functionality to perform DNS resolution in a style related to +getaddrinfo() + +*/ + +use option::{Option, Some, None}; +use result::{Ok, Err}; +use io::{io_error}; +use io::net::ip::{SocketAddr, IpAddr}; +use rt::rtio::{IoFactory, with_local_io}; +use vec::ImmutableVector; + +/// Hints to the types of sockets that are desired when looking up hosts +pub enum SocketType { + Stream, Datagram, Raw +} + +/// Flags which can be or'd into the `flags` field of a `Hint`. These are used +/// to manipulate how a query is performed. +/// +/// The meaning of each of these flags can be found with `man -s 3 getaddrinfo` +pub enum Flag { + AddrConfig, + All, + CanonName, + NumericHost, + NumericServ, + Passive, + V4Mapped, +} + +/// A transport protocol associated with either a hint or a return value of +/// `lookup` +pub enum Protocol { + TCP, UDP +} + +/// This structure is used to provide hints when fetching addresses for a +/// remote host to control how the lookup is performed. +/// +/// For details on these fields, see their corresponding definitions via +/// `man -s 3 getaddrinfo` +pub struct Hint { + family: uint, + socktype: Option<SocketType>, + protocol: Option<Protocol>, + flags: uint, +} + +pub struct Info { + address: SocketAddr, + family: uint, + socktype: Option<SocketType>, + protocol: Option<Protocol>, + flags: uint, +} + +/// Easy name resolution. Given a hostname, returns the list of IP addresses for +/// that hostname. +/// +/// # Failure +/// +/// On failure, this will raise on the `io_error` condition. +pub fn get_host_addresses(host: &str) -> Option<~[IpAddr]> { + lookup(Some(host), None, None).map(|a| a.map(|i| i.address.ip)) +} + +/// Full-fleged resolution. This function will perform a synchronous call to +/// getaddrinfo, controlled by the parameters +/// +/// # Arguments +/// +/// * hostname - an optional hostname to lookup against +/// * servname - an optional service name, listed in the system services +/// * hint - see the hint structure, and "man -s 3 getaddrinfo", for how this +/// controls lookup +/// +/// # Failure +/// +/// On failure, this will raise on the `io_error` condition. +/// +/// XXX: this is not public because the `Hint` structure is not ready for public +/// consumption just yet. +fn lookup(hostname: Option<&str>, servname: Option<&str>, + hint: Option<Hint>) -> Option<~[Info]> { + do with_local_io |io| { + match io.get_host_addresses(hostname, servname, hint) { + Ok(i) => Some(i), + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + } +} + +#[cfg(test)] +mod test { + use option::Some; + use io::net::ip::Ipv4Addr; + use super::*; + + #[test] + #[ignore(cfg(target_os="android"))] // cannot give tcp/ip permission without help of apk + fn dns_smoke_test() { + let ipaddrs = get_host_addresses("localhost").unwrap(); + let mut found_local = false; + let local_addr = &Ipv4Addr(127, 0, 0, 1); + for addr in ipaddrs.iter() { + found_local = found_local || addr == local_addr; + } + assert!(found_local); + } +} diff --git a/src/libstd/io/net/ip.rs b/src/libstd/io/net/ip.rs new file mode 100644 index 00000000000..07240a4a509 --- /dev/null +++ b/src/libstd/io/net/ip.rs @@ -0,0 +1,449 @@ +// 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. + +use container::Container; +use from_str::FromStr; +use option::{Option, None, Some}; +use to_str::ToStr; +use vec::{MutableCloneableVector, ImmutableVector}; + +pub type Port = u16; + +#[deriving(Eq, TotalEq, Clone)] +pub enum IpAddr { + Ipv4Addr(u8, u8, u8, u8), + Ipv6Addr(u16, u16, u16, u16, u16, u16, u16, u16) +} + +impl ToStr for IpAddr { + fn to_str(&self) -> ~str { + match *self { + Ipv4Addr(a, b, c, d) => + format!("{}.{}.{}.{}", a, b, c, d), + + // Ipv4 Compatible address + Ipv6Addr(0, 0, 0, 0, 0, 0, g, h) => { + format!("::{}.{}.{}.{}", (g >> 8) as u8, g as u8, + (h >> 8) as u8, h as u8) + } + + // Ipv4-Mapped address + Ipv6Addr(0, 0, 0, 0, 0, 0xFFFF, g, h) => { + format!("::FFFF:{}.{}.{}.{}", (g >> 8) as u8, g as u8, + (h >> 8) as u8, h as u8) + } + + Ipv6Addr(a, b, c, d, e, f, g, h) => + format!("{}:{}:{}:{}:{}:{}:{}:{}", a, b, c, d, e, f, g, h) + } + } +} + +#[deriving(Eq, TotalEq, Clone)] +pub struct SocketAddr { + ip: IpAddr, + port: Port, +} + + +impl ToStr for SocketAddr { + fn to_str(&self) -> ~str { + match self.ip { + Ipv4Addr(*) => format!("{}:{}", self.ip.to_str(), self.port), + Ipv6Addr(*) => format!("[{}]:{}", self.ip.to_str(), self.port), + } + } +} + +struct Parser<'self> { + // parsing as ASCII, so can use byte array + s: &'self [u8], + pos: uint, +} + +impl<'self> Parser<'self> { + fn new(s: &'self str) -> Parser<'self> { + Parser { + s: s.as_bytes(), + pos: 0, + } + } + + fn is_eof(&self) -> bool { + self.pos == self.s.len() + } + + // Commit only if parser returns Some + fn read_atomically<T>(&mut self, cb: &fn(&mut Parser) -> Option<T>) -> Option<T> { + let pos = self.pos; + let r = cb(self); + if r.is_none() { + self.pos = pos; + } + r + } + + // Commit only if parser read till EOF + fn read_till_eof<T>(&mut self, cb: &fn(&mut Parser) -> Option<T>) -> Option<T> { + do self.read_atomically |p| { + cb(p).filtered(|_| p.is_eof()) + } + } + + // Return result of first successful parser + fn read_or<T>(&mut self, parsers: &[&fn(&mut Parser) -> Option<T>]) -> Option<T> { + for pf in parsers.iter() { + match self.read_atomically(|p: &mut Parser| (*pf)(p)) { + Some(r) => return Some(r), + None => {} + } + } + None + } + + // Apply 3 parsers sequentially + fn read_seq_3<A, B, C>(&mut self, + pa: &fn(&mut Parser) -> Option<A>, + pb: &fn(&mut Parser) -> Option<B>, + pc: &fn(&mut Parser) -> Option<C> + ) -> Option<(A, B, C)> + { + do self.read_atomically |p| { + let a = pa(p); + let b = if a.is_some() { pb(p) } else { None }; + let c = if b.is_some() { pc(p) } else { None }; + match (a, b, c) { + (Some(a), Some(b), Some(c)) => Some((a, b, c)), + _ => None + } + } + } + + // Read next char + fn read_char(&mut self) -> Option<char> { + if self.is_eof() { + None + } else { + let r = self.s[self.pos] as char; + self.pos += 1; + Some(r) + } + } + + // Return char and advance iff next char is equal to requested + fn read_given_char(&mut self, c: char) -> Option<char> { + do self.read_atomically |p| { + p.read_char().filtered(|&next| next == c) + } + } + + // Read digit + fn read_digit(&mut self, radix: u8) -> Option<u8> { + fn parse_digit(c: char, radix: u8) -> Option<u8> { + let c = c as u8; + // assuming radix is either 10 or 16 + if c >= '0' as u8 && c <= '9' as u8 { + Some((c - '0' as u8) as u8) + } else if radix > 10 && c >= 'a' as u8 && c < 'a' as u8 + (radix - 10) { + Some((c - 'a' as u8 + 10) as u8) + } else if radix > 10 && c >= 'A' as u8 && c < 'A' as u8 + (radix - 10) { + Some((c - 'A' as u8 + 10) as u8) + } else { + None + } + } + + do self.read_atomically |p| { + p.read_char().and_then(|c| parse_digit(c, radix)) + } + } + + fn read_number_impl(&mut self, radix: u8, max_digits: u32, upto: u32) -> Option<u32> { + let mut r = 0u32; + let mut digit_count = 0; + loop { + match self.read_digit(radix) { + Some(d) => { + r = r * (radix as u32) + (d as u32); + digit_count += 1; + if digit_count > max_digits || r >= upto { + return None + } + } + None => { + if digit_count == 0 { + return None + } else { + return Some(r) + } + } + }; + } + } + + // Read number, failing if max_digits of number value exceeded + fn read_number(&mut self, radix: u8, max_digits: u32, upto: u32) -> Option<u32> { + do self.read_atomically |p| { + p.read_number_impl(radix, max_digits, upto) + } + } + + fn read_ipv4_addr_impl(&mut self) -> Option<IpAddr> { + let mut bs = [0u8, ..4]; + let mut i = 0; + while i < 4 { + if i != 0 && self.read_given_char('.').is_none() { + return None; + } + + let octet = self.read_number(10, 3, 0x100).map(|n| n as u8); + match octet { + Some(d) => bs[i] = d, + None => return None, + }; + i += 1; + } + Some(Ipv4Addr(bs[0], bs[1], bs[2], bs[3])) + } + + // Read IPv4 address + fn read_ipv4_addr(&mut self) -> Option<IpAddr> { + do self.read_atomically |p| { + p.read_ipv4_addr_impl() + } + } + + fn read_ipv6_addr_impl(&mut self) -> Option<IpAddr> { + fn ipv6_addr_from_head_tail(head: &[u16], tail: &[u16]) -> IpAddr { + assert!(head.len() + tail.len() <= 8); + let mut gs = [0u16, ..8]; + gs.copy_from(head); + gs.mut_slice(8 - tail.len(), 8).copy_from(tail); + Ipv6Addr(gs[0], gs[1], gs[2], gs[3], gs[4], gs[5], gs[6], gs[7]) + } + + fn read_groups(p: &mut Parser, groups: &mut [u16, ..8], limit: uint) -> (uint, bool) { + let mut i = 0; + while i < limit { + if i < limit - 1 { + let ipv4 = do p.read_atomically |p| { + if i == 0 || p.read_given_char(':').is_some() { + p.read_ipv4_addr() + } else { + None + } + }; + match ipv4 { + Some(Ipv4Addr(a, b, c, d)) => { + groups[i + 0] = (a as u16 << 8) | (b as u16); + groups[i + 1] = (c as u16 << 8) | (d as u16); + return (i + 2, true); + } + _ => {} + } + } + + let group = do p.read_atomically |p| { + if i == 0 || p.read_given_char(':').is_some() { + p.read_number(16, 4, 0x10000).map(|n| n as u16) + } else { + None + } + }; + match group { + Some(g) => groups[i] = g, + None => return (i, false) + } + i += 1; + } + (i, false) + } + + let mut head = [0u16, ..8]; + let (head_size, head_ipv4) = read_groups(self, &mut head, 8); + + if head_size == 8 { + return Some(Ipv6Addr( + head[0], head[1], head[2], head[3], + head[4], head[5], head[6], head[7])) + } + + // IPv4 part is not allowed before `::` + if head_ipv4 { + return None + } + + // read `::` if previous code parsed less than 8 groups + if !self.read_given_char(':').is_some() || !self.read_given_char(':').is_some() { + return None; + } + + let mut tail = [0u16, ..8]; + let (tail_size, _) = read_groups(self, &mut tail, 8 - head_size); + Some(ipv6_addr_from_head_tail(head.slice(0, head_size), tail.slice(0, tail_size))) + } + + fn read_ipv6_addr(&mut self) -> Option<IpAddr> { + do self.read_atomically |p| { + p.read_ipv6_addr_impl() + } + } + + fn read_ip_addr(&mut self) -> Option<IpAddr> { + let ipv4_addr = |p: &mut Parser| p.read_ipv4_addr(); + let ipv6_addr = |p: &mut Parser| p.read_ipv6_addr(); + self.read_or([ipv4_addr, ipv6_addr]) + } + + fn read_socket_addr(&mut self) -> Option<SocketAddr> { + let ip_addr = |p: &mut Parser| { + let ipv4_p = |p: &mut Parser| p.read_ip_addr(); + let ipv6_p = |p: &mut Parser| { + let open_br = |p: &mut Parser| p.read_given_char('['); + let ip_addr = |p: &mut Parser| p.read_ipv6_addr(); + let clos_br = |p: &mut Parser| p.read_given_char(']'); + p.read_seq_3::<char, IpAddr, char>(open_br, ip_addr, clos_br) + .map(|t| match t { (_, ip, _) => ip }) + }; + p.read_or([ipv4_p, ipv6_p]) + }; + let colon = |p: &mut Parser| p.read_given_char(':'); + let port = |p: &mut Parser| p.read_number(10, 5, 0x10000).map(|n| n as u16); + + // host, colon, port + self.read_seq_3::<IpAddr, char, u16>(ip_addr, colon, port) + .map(|t| match t { (ip, _, port) => SocketAddr { ip: ip, port: port } }) + } +} + +impl FromStr for IpAddr { + fn from_str(s: &str) -> Option<IpAddr> { + do Parser::new(s).read_till_eof |p| { + p.read_ip_addr() + } + } +} + +impl FromStr for SocketAddr { + fn from_str(s: &str) -> Option<SocketAddr> { + do Parser::new(s).read_till_eof |p| { + p.read_socket_addr() + } + } +} + + +#[cfg(test)] +mod test { + use super::*; + use from_str::FromStr; + use option::{Option, Some, None}; + + #[test] + fn test_from_str_ipv4() { + assert_eq!(Some(Ipv4Addr(127, 0, 0, 1)), FromStr::from_str("127.0.0.1")); + assert_eq!(Some(Ipv4Addr(255, 255, 255, 255)), FromStr::from_str("255.255.255.255")); + assert_eq!(Some(Ipv4Addr(0, 0, 0, 0)), FromStr::from_str("0.0.0.0")); + + // out of range + let none: Option<IpAddr> = FromStr::from_str("256.0.0.1"); + assert_eq!(None, none); + // too short + let none: Option<IpAddr> = FromStr::from_str("255.0.0"); + assert_eq!(None, none); + // too long + let none: Option<IpAddr> = FromStr::from_str("255.0.0.1.2"); + assert_eq!(None, none); + // no number between dots + let none: Option<IpAddr> = FromStr::from_str("255.0..1"); + assert_eq!(None, none); + } + + #[test] + fn test_from_str_ipv6() { + assert_eq!(Some(Ipv6Addr(0, 0, 0, 0, 0, 0, 0, 0)), FromStr::from_str("0:0:0:0:0:0:0:0")); + assert_eq!(Some(Ipv6Addr(0, 0, 0, 0, 0, 0, 0, 1)), FromStr::from_str("0:0:0:0:0:0:0:1")); + + assert_eq!(Some(Ipv6Addr(0, 0, 0, 0, 0, 0, 0, 1)), FromStr::from_str("::1")); + assert_eq!(Some(Ipv6Addr(0, 0, 0, 0, 0, 0, 0, 0)), FromStr::from_str("::")); + + assert_eq!(Some(Ipv6Addr(0x2a02, 0x6b8, 0, 0, 0, 0, 0x11, 0x11)), + FromStr::from_str("2a02:6b8::11:11")); + + // too long group + let none: Option<IpAddr> = FromStr::from_str("::00000"); + assert_eq!(None, none); + // too short + let none: Option<IpAddr> = FromStr::from_str("1:2:3:4:5:6:7"); + assert_eq!(None, none); + // too long + let none: Option<IpAddr> = FromStr::from_str("1:2:3:4:5:6:7:8:9"); + assert_eq!(None, none); + // triple colon + let none: Option<IpAddr> = FromStr::from_str("1:2:::6:7:8"); + assert_eq!(None, none); + // two double colons + let none: Option<IpAddr> = FromStr::from_str("1:2::6::8"); + assert_eq!(None, none); + } + + #[test] + fn test_from_str_ipv4_in_ipv6() { + assert_eq!(Some(Ipv6Addr(0, 0, 0, 0, 0, 0, 49152, 545)), + FromStr::from_str("::192.0.2.33")); + assert_eq!(Some(Ipv6Addr(0, 0, 0, 0, 0, 0xFFFF, 49152, 545)), + FromStr::from_str("::FFFF:192.0.2.33")); + assert_eq!(Some(Ipv6Addr(0x64, 0xff9b, 0, 0, 0, 0, 49152, 545)), + FromStr::from_str("64:ff9b::192.0.2.33")); + assert_eq!(Some(Ipv6Addr(0x2001, 0xdb8, 0x122, 0xc000, 0x2, 0x2100, 49152, 545)), + FromStr::from_str("2001:db8:122:c000:2:2100:192.0.2.33")); + + // colon after v4 + let none: Option<IpAddr> = FromStr::from_str("::127.0.0.1:"); + assert_eq!(None, none); + // not enought groups + let none: Option<IpAddr> = FromStr::from_str("1.2.3.4.5:127.0.0.1"); + assert_eq!(None, none); + // too many groups + let none: Option<IpAddr> = + FromStr::from_str("1.2.3.4.5:6:7:127.0.0.1"); + assert_eq!(None, none); + } + + #[test] + fn test_from_str_socket_addr() { + assert_eq!(Some(SocketAddr { ip: Ipv4Addr(77, 88, 21, 11), port: 80 }), + FromStr::from_str("77.88.21.11:80")); + assert_eq!(Some(SocketAddr { ip: Ipv6Addr(0x2a02, 0x6b8, 0, 1, 0, 0, 0, 1), port: 53 }), + FromStr::from_str("[2a02:6b8:0:1::1]:53")); + assert_eq!(Some(SocketAddr { ip: Ipv6Addr(0, 0, 0, 0, 0, 0, 0x7F00, 1), port: 22 }), + FromStr::from_str("[::127.0.0.1]:22")); + + // without port + let none: Option<SocketAddr> = FromStr::from_str("127.0.0.1"); + assert_eq!(None, none); + // without port + let none: Option<SocketAddr> = FromStr::from_str("127.0.0.1:"); + assert_eq!(None, none); + // wrong brackets around v4 + let none: Option<SocketAddr> = FromStr::from_str("[127.0.0.1]:22"); + assert_eq!(None, none); + // port out of range + let none: Option<SocketAddr> = FromStr::from_str("127.0.0.1:123456"); + assert_eq!(None, none); + } + + #[test] + fn ipv6_addr_to_str() { + let a1 = Ipv6Addr(0, 0, 0, 0, 0, 0xffff, 0xc000, 0x280); + assert!(a1.to_str() == ~"::ffff:192.0.2.128" || a1.to_str() == ~"::FFFF:192.0.2.128"); + } + +} diff --git a/src/libstd/io/net/mod.rs b/src/libstd/io/net/mod.rs new file mode 100644 index 00000000000..cf109167089 --- /dev/null +++ b/src/libstd/io/net/mod.rs @@ -0,0 +1,18 @@ +// 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. + +pub use self::addrinfo::get_host_addresses; + +pub mod addrinfo; +pub mod tcp; +pub mod udp; +pub mod ip; +#[cfg(unix)] +pub mod unix; diff --git a/src/libstd/io/net/tcp.rs b/src/libstd/io/net/tcp.rs new file mode 100644 index 00000000000..a59d652d739 --- /dev/null +++ b/src/libstd/io/net/tcp.rs @@ -0,0 +1,725 @@ +// 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. + +use option::{Option, Some, None}; +use result::{Ok, Err}; +use io::net::ip::SocketAddr; +use io::{Reader, Writer, Listener, Acceptor}; +use io::{io_error, EndOfFile}; +use rt::rtio::{IoFactory, with_local_io, + RtioSocket, RtioTcpListener, RtioTcpAcceptor, RtioTcpStream}; + +pub struct TcpStream { + priv obj: ~RtioTcpStream +} + +impl TcpStream { + fn new(s: ~RtioTcpStream) -> TcpStream { + TcpStream { obj: s } + } + + pub fn connect(addr: SocketAddr) -> Option<TcpStream> { + do with_local_io |io| { + match io.tcp_connect(addr) { + Ok(s) => Some(TcpStream::new(s)), + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + } + } + + pub fn peer_name(&mut self) -> Option<SocketAddr> { + match self.obj.peer_name() { + Ok(pn) => Some(pn), + Err(ioerr) => { + debug!("failed to get peer name: {:?}", ioerr); + io_error::cond.raise(ioerr); + None + } + } + } + + pub fn socket_name(&mut self) -> Option<SocketAddr> { + match self.obj.socket_name() { + Ok(sn) => Some(sn), + Err(ioerr) => { + debug!("failed to get socket name: {:?}", ioerr); + io_error::cond.raise(ioerr); + None + } + } + } +} + +impl Reader for TcpStream { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + match self.obj.read(buf) { + Ok(read) => Some(read), + Err(ioerr) => { + // EOF is indicated by returning None + if ioerr.kind != EndOfFile { + io_error::cond.raise(ioerr); + } + return None; + } + } + } + + fn eof(&mut self) -> bool { fail!() } +} + +impl Writer for TcpStream { + fn write(&mut self, buf: &[u8]) { + match self.obj.write(buf) { + Ok(_) => (), + Err(ioerr) => io_error::cond.raise(ioerr), + } + } +} + +pub struct TcpListener { + priv obj: ~RtioTcpListener +} + +impl TcpListener { + pub fn bind(addr: SocketAddr) -> Option<TcpListener> { + do with_local_io |io| { + match io.tcp_bind(addr) { + Ok(l) => Some(TcpListener { obj: l }), + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + } + } + + pub fn socket_name(&mut self) -> Option<SocketAddr> { + match self.obj.socket_name() { + Ok(sn) => Some(sn), + Err(ioerr) => { + debug!("failed to get socket name: {:?}", ioerr); + io_error::cond.raise(ioerr); + None + } + } + } +} + +impl Listener<TcpStream, TcpAcceptor> for TcpListener { + fn listen(self) -> Option<TcpAcceptor> { + match self.obj.listen() { + Ok(acceptor) => Some(TcpAcceptor { obj: acceptor }), + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + } +} + +pub struct TcpAcceptor { + priv obj: ~RtioTcpAcceptor +} + +impl Acceptor<TcpStream> for TcpAcceptor { + fn accept(&mut self) -> Option<TcpStream> { + match self.obj.accept() { + Ok(s) => Some(TcpStream::new(s)), + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use cell::Cell; + use rt::test::*; + use io::net::ip::{Ipv4Addr, SocketAddr}; + use io::*; + use prelude::*; + use rt::comm::oneshot; + + #[test] #[ignore] + fn bind_error() { + do run_in_mt_newsched_task { + let mut called = false; + do io_error::cond.trap(|e| { + assert!(e.kind == PermissionDenied); + called = true; + }).inside { + let addr = SocketAddr { ip: Ipv4Addr(0, 0, 0, 0), port: 1 }; + let listener = TcpListener::bind(addr); + assert!(listener.is_none()); + } + assert!(called); + } + } + + #[test] + fn connect_error() { + do run_in_mt_newsched_task { + let mut called = false; + do io_error::cond.trap(|e| { + let expected_error = if cfg!(unix) { + ConnectionRefused + } else { + // On Win32, opening port 1 gives WSAEADDRNOTAVAIL error. + OtherIoError + }; + assert_eq!(e.kind, expected_error); + called = true; + }).inside { + let addr = SocketAddr { ip: Ipv4Addr(0, 0, 0, 0), port: 1 }; + let stream = TcpStream::connect(addr); + assert!(stream.is_none()); + } + assert!(called); + } + } + + #[test] + fn smoke_test_ip4() { + do run_in_mt_newsched_task { + let addr = next_test_ip4(); + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + let mut stream = acceptor.accept(); + let mut buf = [0]; + stream.read(buf); + assert!(buf[0] == 99); + } + + do spawntask { + port.take().recv(); + let mut stream = TcpStream::connect(addr); + stream.write([99]); + } + } + } + + #[test] + fn smoke_test_ip6() { + do run_in_mt_newsched_task { + let addr = next_test_ip6(); + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + let mut stream = acceptor.accept(); + let mut buf = [0]; + stream.read(buf); + assert!(buf[0] == 99); + } + + do spawntask { + port.take().recv(); + let mut stream = TcpStream::connect(addr); + stream.write([99]); + } + } + } + + #[test] + fn read_eof_ip4() { + do run_in_mt_newsched_task { + let addr = next_test_ip4(); + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + let mut stream = acceptor.accept(); + let mut buf = [0]; + let nread = stream.read(buf); + assert!(nread.is_none()); + } + + do spawntask { + port.take().recv(); + let _stream = TcpStream::connect(addr); + // Close + } + } + } + + #[test] + fn read_eof_ip6() { + do run_in_mt_newsched_task { + let addr = next_test_ip6(); + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + let mut stream = acceptor.accept(); + let mut buf = [0]; + let nread = stream.read(buf); + assert!(nread.is_none()); + } + + do spawntask { + port.take().recv(); + let _stream = TcpStream::connect(addr); + // Close + } + } + } + + #[test] + fn read_eof_twice_ip4() { + do run_in_mt_newsched_task { + let addr = next_test_ip4(); + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + let mut stream = acceptor.accept(); + let mut buf = [0]; + let nread = stream.read(buf); + assert!(nread.is_none()); + do io_error::cond.trap(|e| { + if cfg!(windows) { + assert_eq!(e.kind, NotConnected); + } else { + fail!(); + } + }).inside { + let nread = stream.read(buf); + assert!(nread.is_none()); + } + } + + do spawntask { + port.take().recv(); + let _stream = TcpStream::connect(addr); + // Close + } + } + } + + #[test] + fn read_eof_twice_ip6() { + do run_in_mt_newsched_task { + let addr = next_test_ip6(); + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + let mut stream = acceptor.accept(); + let mut buf = [0]; + let nread = stream.read(buf); + assert!(nread.is_none()); + do io_error::cond.trap(|e| { + if cfg!(windows) { + assert_eq!(e.kind, NotConnected); + } else { + fail!(); + } + }).inside { + let nread = stream.read(buf); + assert!(nread.is_none()); + } + } + + do spawntask { + port.take().recv(); + let _stream = TcpStream::connect(addr); + // Close + } + } + } + + #[test] + fn write_close_ip4() { + do run_in_mt_newsched_task { + let addr = next_test_ip4(); + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + let mut stream = acceptor.accept(); + let buf = [0]; + loop { + let mut stop = false; + do io_error::cond.trap(|e| { + // NB: ECONNRESET on linux, EPIPE on mac, ECONNABORTED + // on windows + assert!(e.kind == ConnectionReset || + e.kind == BrokenPipe || + e.kind == ConnectionAborted, + "unknown error: {:?}", e); + stop = true; + }).inside { + stream.write(buf); + } + if stop { break } + } + } + + do spawntask { + port.take().recv(); + let _stream = TcpStream::connect(addr); + // Close + } + } + } + + #[test] + fn write_close_ip6() { + do run_in_mt_newsched_task { + let addr = next_test_ip6(); + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + let mut stream = acceptor.accept(); + let buf = [0]; + loop { + let mut stop = false; + do io_error::cond.trap(|e| { + // NB: ECONNRESET on linux, EPIPE on mac, ECONNABORTED + // on windows + assert!(e.kind == ConnectionReset || + e.kind == BrokenPipe || + e.kind == ConnectionAborted, + "unknown error: {:?}", e); + stop = true; + }).inside { + stream.write(buf); + } + if stop { break } + } + } + + do spawntask { + port.take().recv(); + let _stream = TcpStream::connect(addr); + // Close + } + } + } + + #[test] + fn multiple_connect_serial_ip4() { + do run_in_mt_newsched_task { + let addr = next_test_ip4(); + let max = 10; + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + for ref mut stream in acceptor.incoming().take(max) { + let mut buf = [0]; + stream.read(buf); + assert_eq!(buf[0], 99); + } + } + + do spawntask { + port.take().recv(); + do max.times { + let mut stream = TcpStream::connect(addr); + stream.write([99]); + } + } + } + } + + #[test] + fn multiple_connect_serial_ip6() { + do run_in_mt_newsched_task { + let addr = next_test_ip6(); + let max = 10; + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + for ref mut stream in acceptor.incoming().take(max) { + let mut buf = [0]; + stream.read(buf); + assert_eq!(buf[0], 99); + } + } + + do spawntask { + port.take().recv(); + do max.times { + let mut stream = TcpStream::connect(addr); + stream.write([99]); + } + } + } + } + + #[test] + fn multiple_connect_interleaved_greedy_schedule_ip4() { + do run_in_mt_newsched_task { + let addr = next_test_ip4(); + static MAX: int = 10; + let (port, chan) = oneshot(); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + for (i, stream) in acceptor.incoming().enumerate().take(MAX as uint) { + let stream = Cell::new(stream); + // Start another task to handle the connection + do spawntask { + let mut stream = stream.take(); + let mut buf = [0]; + stream.read(buf); + assert!(buf[0] == i as u8); + debug!("read"); + } + } + } + + port.recv(); + connect(0, addr); + + fn connect(i: int, addr: SocketAddr) { + if i == MAX { return } + + do spawntask { + debug!("connecting"); + let mut stream = TcpStream::connect(addr); + // Connect again before writing + connect(i + 1, addr); + debug!("writing"); + stream.write([i as u8]); + } + } + } + } + + #[test] + fn multiple_connect_interleaved_greedy_schedule_ip6() { + do run_in_mt_newsched_task { + let addr = next_test_ip6(); + static MAX: int = 10; + let (port, chan) = oneshot(); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + for (i, stream) in acceptor.incoming().enumerate().take(MAX as uint) { + let stream = Cell::new(stream); + // Start another task to handle the connection + do spawntask { + let mut stream = stream.take(); + let mut buf = [0]; + stream.read(buf); + assert!(buf[0] == i as u8); + debug!("read"); + } + } + } + + port.recv(); + connect(0, addr); + + fn connect(i: int, addr: SocketAddr) { + if i == MAX { return } + + do spawntask { + debug!("connecting"); + let mut stream = TcpStream::connect(addr); + // Connect again before writing + connect(i + 1, addr); + debug!("writing"); + stream.write([i as u8]); + } + } + } + } + + #[test] + fn multiple_connect_interleaved_lazy_schedule_ip4() { + do run_in_mt_newsched_task { + let addr = next_test_ip4(); + static MAX: int = 10; + let (port, chan) = oneshot(); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + for stream in acceptor.incoming().take(MAX as uint) { + let stream = Cell::new(stream); + // Start another task to handle the connection + do spawntask_later { + let mut stream = stream.take(); + let mut buf = [0]; + stream.read(buf); + assert!(buf[0] == 99); + debug!("read"); + } + } + } + + port.recv(); + connect(0, addr); + + fn connect(i: int, addr: SocketAddr) { + if i == MAX { return } + + do spawntask_later { + debug!("connecting"); + let mut stream = TcpStream::connect(addr); + // Connect again before writing + connect(i + 1, addr); + debug!("writing"); + stream.write([99]); + } + } + } + } + #[test] + fn multiple_connect_interleaved_lazy_schedule_ip6() { + do run_in_mt_newsched_task { + let addr = next_test_ip6(); + static MAX: int = 10; + let (port, chan) = oneshot(); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + for stream in acceptor.incoming().take(MAX as uint) { + let stream = Cell::new(stream); + // Start another task to handle the connection + do spawntask_later { + let mut stream = stream.take(); + let mut buf = [0]; + stream.read(buf); + assert!(buf[0] == 99); + debug!("read"); + } + } + } + + port.recv(); + connect(0, addr); + + fn connect(i: int, addr: SocketAddr) { + if i == MAX { return } + + do spawntask_later { + debug!("connecting"); + let mut stream = TcpStream::connect(addr); + // Connect again before writing + connect(i + 1, addr); + debug!("writing"); + stream.write([99]); + } + } + } + } + + #[cfg(test)] + fn socket_name(addr: SocketAddr) { + do run_in_mt_newsched_task { + do spawntask { + let mut listener = TcpListener::bind(addr).unwrap(); + + // Make sure socket_name gives + // us the socket we binded to. + let so_name = listener.socket_name(); + assert!(so_name.is_some()); + assert_eq!(addr, so_name.unwrap()); + + } + } + } + + #[cfg(test)] + fn peer_name(addr: SocketAddr) { + do run_in_mt_newsched_task { + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = TcpListener::bind(addr).listen(); + chan.take().send(()); + + acceptor.accept(); + } + + do spawntask { + port.take().recv(); + let stream = TcpStream::connect(addr); + + assert!(stream.is_some()); + let mut stream = stream.unwrap(); + + // Make sure peer_name gives us the + // address/port of the peer we've + // connected to. + let peer_name = stream.peer_name(); + assert!(peer_name.is_some()); + assert_eq!(addr, peer_name.unwrap()); + } + } + } + + #[test] + fn socket_and_peer_name_ip4() { + peer_name(next_test_ip4()); + socket_name(next_test_ip4()); + } + + #[test] + fn socket_and_peer_name_ip6() { + // XXX: peer name is not consistent + //peer_name(next_test_ip6()); + socket_name(next_test_ip6()); + } + +} diff --git a/src/libstd/io/net/udp.rs b/src/libstd/io/net/udp.rs new file mode 100644 index 00000000000..1a2245ac442 --- /dev/null +++ b/src/libstd/io/net/udp.rs @@ -0,0 +1,321 @@ +// 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. + +use option::{Option, Some, None}; +use result::{Ok, Err}; +use io::net::ip::SocketAddr; +use io::{Reader, Writer}; +use io::{io_error, EndOfFile}; +use rt::rtio::{RtioSocket, RtioUdpSocket, IoFactory, with_local_io}; + +pub struct UdpSocket { + priv obj: ~RtioUdpSocket +} + +impl UdpSocket { + pub fn bind(addr: SocketAddr) -> Option<UdpSocket> { + do with_local_io |io| { + match io.udp_bind(addr) { + Ok(s) => Some(UdpSocket { obj: s }), + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + } + } + + pub fn recvfrom(&mut self, buf: &mut [u8]) -> Option<(uint, SocketAddr)> { + match self.obj.recvfrom(buf) { + Ok((nread, src)) => Some((nread, src)), + Err(ioerr) => { + // EOF is indicated by returning None + if ioerr.kind != EndOfFile { + io_error::cond.raise(ioerr); + } + None + } + } + } + + pub fn sendto(&mut self, buf: &[u8], dst: SocketAddr) { + match self.obj.sendto(buf, dst) { + Ok(_) => (), + Err(ioerr) => io_error::cond.raise(ioerr), + } + } + + pub fn connect(self, other: SocketAddr) -> UdpStream { + UdpStream { socket: self, connectedTo: other } + } + + pub fn socket_name(&mut self) -> Option<SocketAddr> { + match self.obj.socket_name() { + Ok(sn) => Some(sn), + Err(ioerr) => { + debug!("failed to get socket name: {:?}", ioerr); + io_error::cond.raise(ioerr); + None + } + } + } +} + +pub struct UdpStream { + priv socket: UdpSocket, + priv connectedTo: SocketAddr +} + +impl UdpStream { + pub fn as_socket<T>(&mut self, f: &fn(&mut UdpSocket) -> T) -> T { f(&mut self.socket) } + + pub fn disconnect(self) -> UdpSocket { self.socket } +} + +impl Reader for UdpStream { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + let peer = self.connectedTo; + do self.as_socket |sock| { + match sock.recvfrom(buf) { + Some((_nread, src)) if src != peer => Some(0), + Some((nread, _src)) => Some(nread), + None => None, + } + } + } + + fn eof(&mut self) -> bool { fail!() } +} + +impl Writer for UdpStream { + fn write(&mut self, buf: &[u8]) { + do self.as_socket |sock| { + sock.sendto(buf, self.connectedTo); + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use rt::test::*; + use io::net::ip::{Ipv4Addr, SocketAddr}; + use io::*; + use option::{Some, None}; + use rt::comm::oneshot; + use cell::Cell; + + #[test] #[ignore] + fn bind_error() { + do run_in_mt_newsched_task { + let mut called = false; + do io_error::cond.trap(|e| { + assert!(e.kind == PermissionDenied); + called = true; + }).inside { + let addr = SocketAddr { ip: Ipv4Addr(0, 0, 0, 0), port: 1 }; + let socket = UdpSocket::bind(addr); + assert!(socket.is_none()); + } + assert!(called); + } + } + + #[test] + fn socket_smoke_test_ip4() { + do run_in_mt_newsched_task { + let server_ip = next_test_ip4(); + let client_ip = next_test_ip4(); + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + match UdpSocket::bind(server_ip) { + Some(ref mut server) => { + chan.take().send(()); + let mut buf = [0]; + match server.recvfrom(buf) { + Some((nread, src)) => { + assert_eq!(nread, 1); + assert_eq!(buf[0], 99); + assert_eq!(src, client_ip); + } + None => fail!() + } + } + None => fail!() + } + } + + do spawntask { + match UdpSocket::bind(client_ip) { + Some(ref mut client) => { + port.take().recv(); + client.sendto([99], server_ip) + } + None => fail!() + } + } + } + } + + #[test] + fn socket_smoke_test_ip6() { + do run_in_mt_newsched_task { + let server_ip = next_test_ip6(); + let client_ip = next_test_ip6(); + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + match UdpSocket::bind(server_ip) { + Some(ref mut server) => { + chan.take().send(()); + let mut buf = [0]; + match server.recvfrom(buf) { + Some((nread, src)) => { + assert_eq!(nread, 1); + assert_eq!(buf[0], 99); + assert_eq!(src, client_ip); + } + None => fail!() + } + } + None => fail!() + } + } + + do spawntask { + match UdpSocket::bind(client_ip) { + Some(ref mut client) => { + port.take().recv(); + client.sendto([99], server_ip) + } + None => fail!() + } + } + } + } + + #[test] + fn stream_smoke_test_ip4() { + do run_in_mt_newsched_task { + let server_ip = next_test_ip4(); + let client_ip = next_test_ip4(); + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + match UdpSocket::bind(server_ip) { + Some(server) => { + let server = ~server; + let mut stream = server.connect(client_ip); + chan.take().send(()); + let mut buf = [0]; + match stream.read(buf) { + Some(nread) => { + assert_eq!(nread, 1); + assert_eq!(buf[0], 99); + } + None => fail!() + } + } + None => fail!() + } + } + + do spawntask { + match UdpSocket::bind(client_ip) { + Some(client) => { + let client = ~client; + let mut stream = client.connect(server_ip); + port.take().recv(); + stream.write([99]); + } + None => fail!() + } + } + } + } + + #[test] + fn stream_smoke_test_ip6() { + do run_in_mt_newsched_task { + let server_ip = next_test_ip6(); + let client_ip = next_test_ip6(); + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + match UdpSocket::bind(server_ip) { + Some(server) => { + let server = ~server; + let mut stream = server.connect(client_ip); + chan.take().send(()); + let mut buf = [0]; + match stream.read(buf) { + Some(nread) => { + assert_eq!(nread, 1); + assert_eq!(buf[0], 99); + } + None => fail!() + } + } + None => fail!() + } + } + + do spawntask { + match UdpSocket::bind(client_ip) { + Some(client) => { + let client = ~client; + let mut stream = client.connect(server_ip); + port.take().recv(); + stream.write([99]); + } + None => fail!() + } + } + } + } + + #[cfg(test)] + fn socket_name(addr: SocketAddr) { + do run_in_mt_newsched_task { + do spawntask { + let server = UdpSocket::bind(addr); + + assert!(server.is_some()); + let mut server = server.unwrap(); + + // Make sure socket_name gives + // us the socket we binded to. + let so_name = server.socket_name(); + assert!(so_name.is_some()); + assert_eq!(addr, so_name.unwrap()); + + } + } + } + + #[test] + fn socket_name_ip4() { + socket_name(next_test_ip4()); + } + + #[test] + fn socket_name_ip6() { + socket_name(next_test_ip6()); + } +} diff --git a/src/libstd/io/net/unix.rs b/src/libstd/io/net/unix.rs new file mode 100644 index 00000000000..c6b4a2f2a42 --- /dev/null +++ b/src/libstd/io/net/unix.rs @@ -0,0 +1,295 @@ +// 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. + +/*! + +Named pipes + +This module contains the ability to communicate over named pipes with +synchronous I/O. On windows, this corresponds to talking over a Named Pipe, +while on Unix it corresponds to UNIX domain sockets. + +These pipes are similar to TCP in the sense that you can have both a stream to a +server and a server itself. The server provided accepts other `UnixStream` +instances as clients. + +*/ + +use prelude::*; + +use c_str::ToCStr; +use rt::rtio::{IoFactory, RtioUnixListener, with_local_io}; +use rt::rtio::{RtioUnixAcceptor, RtioPipe}; +use io::pipe::PipeStream; +use io::{io_error, Listener, Acceptor, Reader, Writer}; + +/// A stream which communicates over a named pipe. +pub struct UnixStream { + priv obj: PipeStream, +} + +impl UnixStream { + fn new(obj: ~RtioPipe) -> UnixStream { + UnixStream { obj: PipeStream::new(obj) } + } + + /// Connect to a pipe named by `path`. This will attempt to open a + /// connection to the underlying socket. + /// + /// The returned stream will be closed when the object falls out of scope. + /// + /// # Failure + /// + /// This function will raise on the `io_error` condition if the connection + /// could not be made. + /// + /// # Example + /// + /// use std::io::net::unix::UnixStream; + /// + /// let server = Path("path/to/my/socket"); + /// let mut stream = UnixStream::connect(&server); + /// stream.write([1, 2, 3]); + /// + pub fn connect<P: ToCStr>(path: &P) -> Option<UnixStream> { + do with_local_io |io| { + match io.unix_connect(&path.to_c_str()) { + Ok(s) => Some(UnixStream::new(s)), + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + } + } +} + +impl Reader for UnixStream { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { self.obj.read(buf) } + fn eof(&mut self) -> bool { self.obj.eof() } +} + +impl Writer for UnixStream { + fn write(&mut self, buf: &[u8]) { self.obj.write(buf) } +} + +pub struct UnixListener { + priv obj: ~RtioUnixListener, +} + +impl UnixListener { + + /// Creates a new listener, ready to receive incoming connections on the + /// specified socket. The server will be named by `path`. + /// + /// This listener will be closed when it falls out of scope. + /// + /// # Failure + /// + /// This function will raise on the `io_error` condition if the specified + /// path could not be bound. + /// + /// # Example + /// + /// use std::io::net::unix::UnixListener; + /// + /// let server = Path("path/to/my/socket"); + /// let mut stream = UnixListener::bind(&server); + /// for client in stream.incoming() { + /// let mut client = client; + /// client.write([1, 2, 3, 4]); + /// } + /// + pub fn bind<P: ToCStr>(path: &P) -> Option<UnixListener> { + do with_local_io |io| { + match io.unix_bind(&path.to_c_str()) { + Ok(s) => Some(UnixListener{ obj: s }), + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + } + } +} + +impl Listener<UnixStream, UnixAcceptor> for UnixListener { + fn listen(self) -> Option<UnixAcceptor> { + match self.obj.listen() { + Ok(acceptor) => Some(UnixAcceptor { obj: acceptor }), + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + } +} + +pub struct UnixAcceptor { + priv obj: ~RtioUnixAcceptor, +} + +impl Acceptor<UnixStream> for UnixAcceptor { + fn accept(&mut self) -> Option<UnixStream> { + match self.obj.accept() { + Ok(s) => Some(UnixStream::new(s)), + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + } +} + +#[cfg(test)] +mod tests { + use prelude::*; + use super::*; + use cell::Cell; + use rt::test::*; + use io::*; + use rt::comm::oneshot; + + fn smalltest(server: ~fn(UnixStream), client: ~fn(UnixStream)) { + let server = Cell::new(server); + let client = Cell::new(client); + do run_in_mt_newsched_task { + let server = Cell::new(server.take()); + let client = Cell::new(client.take()); + let path1 = next_test_unix(); + let path2 = path1.clone(); + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = UnixListener::bind(&path1).listen(); + chan.take().send(()); + server.take()(acceptor.accept().unwrap()); + } + + do spawntask { + port.take().recv(); + client.take()(UnixStream::connect(&path2).unwrap()); + } + } + } + + #[test] + fn bind_error() { + do run_in_mt_newsched_task { + let mut called = false; + do io_error::cond.trap(|e| { + assert!(e.kind == PermissionDenied); + called = true; + }).inside { + let listener = UnixListener::bind(&("path/to/nowhere")); + assert!(listener.is_none()); + } + assert!(called); + } + } + + #[test] + fn connect_error() { + do run_in_mt_newsched_task { + let mut called = false; + do io_error::cond.trap(|e| { + assert_eq!(e.kind, OtherIoError); + called = true; + }).inside { + let stream = UnixStream::connect(&("path/to/nowhere")); + assert!(stream.is_none()); + } + assert!(called); + } + } + + #[test] + fn smoke() { + smalltest(|mut server| { + let mut buf = [0]; + server.read(buf); + assert!(buf[0] == 99); + }, |mut client| { + client.write([99]); + }) + } + + #[test] + fn read_eof() { + smalltest(|mut server| { + let mut buf = [0]; + assert!(server.read(buf).is_none()); + assert!(server.read(buf).is_none()); + }, |_client| { + // drop the client + }) + } + + #[test] + fn write_begone() { + smalltest(|mut server| { + let buf = [0]; + let mut stop = false; + while !stop{ + do io_error::cond.trap(|e| { + assert!(e.kind == BrokenPipe || e.kind == NotConnected, + "unknown error {:?}", e); + stop = true; + }).inside { + server.write(buf); + } + } + }, |_client| { + // drop the client + }) + } + + #[test] + fn accept_lots() { + do run_in_mt_newsched_task { + let times = 10; + let path1 = next_test_unix(); + let path2 = path1.clone(); + let (port, chan) = oneshot(); + let port = Cell::new(port); + let chan = Cell::new(chan); + + do spawntask { + let mut acceptor = UnixListener::bind(&path1).listen(); + chan.take().send(()); + do times.times { + let mut client = acceptor.accept(); + let mut buf = [0]; + client.read(buf); + assert_eq!(buf[0], 100); + } + } + + do spawntask { + port.take().recv(); + do times.times { + let mut stream = UnixStream::connect(&path2); + stream.write([100]); + } + } + } + } + + #[test] + fn path_exists() { + do run_in_mt_newsched_task { + let path = next_test_unix(); + let _acceptor = UnixListener::bind(&path).listen(); + assert!(path.exists()); + } + } +} diff --git a/src/libstd/io/option.rs b/src/libstd/io/option.rs new file mode 100644 index 00000000000..5938252571f --- /dev/null +++ b/src/libstd/io/option.rs @@ -0,0 +1,181 @@ +// 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. + +//! Implementations of I/O traits for the Option type +//! +//! I/O constructors return option types to allow errors to be handled. +//! These implementations allow e.g. `Option<File>` to be used +//! as a `Reader` without unwrapping the option first. + +use option::*; +use super::{Reader, Writer, Listener, Acceptor, Seek, SeekStyle}; +use super::{standard_error, PreviousIoError, io_error, IoError}; + +fn prev_io_error() -> IoError { + standard_error(PreviousIoError) +} + +impl<W: Writer> Writer for Option<W> { + fn write(&mut self, buf: &[u8]) { + match *self { + Some(ref mut writer) => writer.write(buf), + None => io_error::cond.raise(prev_io_error()) + } + } + + fn flush(&mut self) { + match *self { + Some(ref mut writer) => writer.flush(), + None => io_error::cond.raise(prev_io_error()) + } + } +} + +impl<R: Reader> Reader for Option<R> { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + match *self { + Some(ref mut reader) => reader.read(buf), + None => { + io_error::cond.raise(prev_io_error()); + None + } + } + } + + fn eof(&mut self) -> bool { + match *self { + Some(ref mut reader) => reader.eof(), + None => { + io_error::cond.raise(prev_io_error()); + true + } + } + } +} + +impl<S: Seek> Seek for Option<S> { + fn tell(&self) -> u64 { + match *self { + Some(ref seeker) => seeker.tell(), + None => { + io_error::cond.raise(prev_io_error()); + 0 + } + } + } + fn seek(&mut self, pos: i64, style: SeekStyle) { + match *self { + Some(ref mut seeker) => seeker.seek(pos, style), + None => io_error::cond.raise(prev_io_error()) + } + } +} + +impl<T, A: Acceptor<T>, L: Listener<T, A>> Listener<T, A> for Option<L> { + fn listen(self) -> Option<A> { + match self { + Some(listener) => listener.listen(), + None => { + io_error::cond.raise(prev_io_error()); + None + } + } + } +} + +impl<T, A: Acceptor<T>> Acceptor<T> for Option<A> { + fn accept(&mut self) -> Option<T> { + match *self { + Some(ref mut acceptor) => acceptor.accept(), + None => { + io_error::cond.raise(prev_io_error()); + None + } + } + } +} + +#[cfg(test)] +mod test { + use option::*; + use super::super::mem::*; + use rt::test::*; + use super::super::{PreviousIoError, io_error}; + + #[test] + fn test_option_writer() { + do run_in_mt_newsched_task { + let mut writer: Option<MemWriter> = Some(MemWriter::new()); + writer.write([0, 1, 2]); + writer.flush(); + assert_eq!(writer.unwrap().inner(), ~[0, 1, 2]); + } + } + + #[test] + fn test_option_writer_error() { + do run_in_mt_newsched_task { + let mut writer: Option<MemWriter> = None; + + let mut called = false; + do io_error::cond.trap(|err| { + assert_eq!(err.kind, PreviousIoError); + called = true; + }).inside { + writer.write([0, 0, 0]); + } + assert!(called); + + let mut called = false; + do io_error::cond.trap(|err| { + assert_eq!(err.kind, PreviousIoError); + called = true; + }).inside { + writer.flush(); + } + assert!(called); + } + } + + #[test] + fn test_option_reader() { + do run_in_mt_newsched_task { + let mut reader: Option<MemReader> = Some(MemReader::new(~[0, 1, 2, 3])); + let mut buf = [0, 0]; + reader.read(buf); + assert_eq!(buf, [0, 1]); + assert!(!reader.eof()); + } + } + + #[test] + fn test_option_reader_error() { + let mut reader: Option<MemReader> = None; + let mut buf = []; + + let mut called = false; + do io_error::cond.trap(|err| { + assert_eq!(err.kind, PreviousIoError); + called = true; + }).inside { + reader.read(buf); + } + assert!(called); + + let mut called = false; + do io_error::cond.trap(|err| { + assert_eq!(err.kind, PreviousIoError); + called = true; + }).inside { + assert!(reader.eof()); + } + assert!(called); + } +} diff --git a/src/libstd/io/pipe.rs b/src/libstd/io/pipe.rs new file mode 100644 index 00000000000..edbc6fa4285 --- /dev/null +++ b/src/libstd/io/pipe.rs @@ -0,0 +1,89 @@ +// 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. + +//! Synchronous, in-memory pipes. +//! +//! Currently these aren't particularly useful, there only exists bindings +//! enough so that pipes can be created to child processes. + +use prelude::*; +use super::{Reader, Writer}; +use io::{io_error, EndOfFile}; +use io::native::file; +use rt::rtio::{RtioPipe, with_local_io}; + +pub struct PipeStream { + priv obj: ~RtioPipe, +} + +impl PipeStream { + /// Consumes a file descriptor to return a pipe stream that will have + /// synchronous, but non-blocking reads/writes. This is useful if the file + /// descriptor is acquired via means other than the standard methods. + /// + /// This operation consumes ownership of the file descriptor and it will be + /// closed once the object is deallocated. + /// + /// # Example + /// + /// use std::libc; + /// use std::io::pipe; + /// + /// let mut pipe = PipeStream::open(libc::STDERR_FILENO); + /// pipe.write(bytes!("Hello, stderr!")); + /// + /// # Failure + /// + /// If the pipe cannot be created, an error will be raised on the + /// `io_error` condition. + pub fn open(fd: file::fd_t) -> Option<PipeStream> { + do with_local_io |io| { + match io.pipe_open(fd) { + Ok(obj) => Some(PipeStream { obj: obj }), + Err(e) => { + io_error::cond.raise(e); + None + } + } + } + } + + pub fn new(inner: ~RtioPipe) -> PipeStream { + PipeStream { obj: inner } + } +} + +impl Reader for PipeStream { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + match self.obj.read(buf) { + Ok(read) => Some(read), + Err(ioerr) => { + // EOF is indicated by returning None + if ioerr.kind != EndOfFile { + io_error::cond.raise(ioerr); + } + return None; + } + } + } + + fn eof(&mut self) -> bool { false } +} + +impl Writer for PipeStream { + fn write(&mut self, buf: &[u8]) { + match self.obj.write(buf) { + Ok(_) => (), + Err(ioerr) => { + io_error::cond.raise(ioerr); + } + } + } +} diff --git a/src/libstd/io/process.rs b/src/libstd/io/process.rs new file mode 100644 index 00000000000..3941003d1c0 --- /dev/null +++ b/src/libstd/io/process.rs @@ -0,0 +1,177 @@ +// 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. + +//! Bindings for executing child processes + +use prelude::*; +use cell::Cell; + +use libc; +use io; +use io::io_error; +use rt::rtio::{RtioProcess, IoFactory, with_local_io}; + +use fmt; + +// windows values don't matter as long as they're at least one of unix's +// TERM/KILL/INT signals +#[cfg(windows)] pub static PleaseExitSignal: int = 15; +#[cfg(windows)] pub static MustDieSignal: int = 9; +#[cfg(not(windows))] pub static PleaseExitSignal: int = libc::SIGTERM as int; +#[cfg(not(windows))] pub static MustDieSignal: int = libc::SIGKILL as int; + +pub struct Process { + priv handle: ~RtioProcess, + io: ~[Option<io::PipeStream>], +} + +/// This configuration describes how a new process should be spawned. This is +/// translated to libuv's own configuration +pub struct ProcessConfig<'self> { + /// Path to the program to run + program: &'self str, + + /// Arguments to pass to the program (doesn't include the program itself) + args: &'self [~str], + + /// Optional environment to specify for the program. If this is None, then + /// it will inherit the current process's environment. + env: Option<&'self [(~str, ~str)]>, + + /// Optional working directory for the new process. If this is None, then + /// the current directory of the running process is inherited. + cwd: Option<&'self str>, + + /// Any number of streams/file descriptors/pipes may be attached to this + /// process. This list enumerates the file descriptors and such for the + /// process to be spawned, and the file descriptors inherited will start at + /// 0 and go to the length of this array. + /// + /// Standard file descriptors are: + /// + /// 0 - stdin + /// 1 - stdout + /// 2 - stderr + io: &'self [StdioContainer] +} + +/// Describes what to do with a standard io stream for a child process. +pub enum StdioContainer { + /// This stream will be ignored. This is the equivalent of attaching the + /// stream to `/dev/null` + Ignored, + + /// The specified file descriptor is inherited for the stream which it is + /// specified for. + InheritFd(libc::c_int), + + /// Creates a pipe for the specified file descriptor which will be created + /// when the process is spawned. + /// + /// The first boolean argument is whether the pipe is readable, and the + /// second is whether it is writable. These properties are from the view of + /// the *child* process, not the parent process. + CreatePipe(bool /* readable */, bool /* writable */), +} + +/// Describes the result of a process after it has terminated. +#[deriving(Eq)] +pub enum ProcessExit { + /// Normal termination with an exit status. + ExitStatus(int), + + /// Termination by signal, with the signal number. + ExitSignal(int), +} + +impl fmt::Default for ProcessExit { + /// Format a ProcessExit enum, to nicely present the information. + fn fmt(obj: &ProcessExit, f: &mut fmt::Formatter) { + match *obj { + ExitStatus(code) => write!(f.buf, "exit code: {}", code), + ExitSignal(code) => write!(f.buf, "signal: {}", code), + } + } +} + +impl ProcessExit { + /// Was termination successful? Signal termination not considered a success, + /// and success is defined as a zero exit status. + pub fn success(&self) -> bool { + return self.matches_exit_status(0); + } + + /// Checks whether this ProcessExit matches the given exit status. + /// Termination by signal will never match an exit code. + pub fn matches_exit_status(&self, wanted: int) -> bool { + *self == ExitStatus(wanted) + } +} + +impl Process { + /// Creates a new pipe initialized, but not bound to any particular + /// source/destination + pub fn new(config: ProcessConfig) -> Option<Process> { + let config = Cell::new(config); + do with_local_io |io| { + match io.spawn(config.take()) { + Ok((p, io)) => Some(Process{ + handle: p, + io: io.move_iter().map(|p| + p.map(|p| io::PipeStream::new(p)) + ).collect() + }), + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + } + } + + /// Returns the process id of this child process + pub fn id(&self) -> libc::pid_t { self.handle.id() } + + /// Sends the specified signal to the child process, returning whether the + /// signal could be delivered or not. + /// + /// Note that this is purely a wrapper around libuv's `uv_process_kill` + /// function. + /// + /// If the signal delivery fails, then the `io_error` condition is raised on + pub fn signal(&mut self, signal: int) { + match self.handle.kill(signal) { + Ok(()) => {} + Err(err) => { + io_error::cond.raise(err) + } + } + } + + /// Wait for the child to exit completely, returning the status that it + /// exited with. This function will continue to have the same return value + /// after it has been called at least once. + pub fn wait(&mut self) -> ProcessExit { self.handle.wait() } +} + +impl Drop for Process { + fn drop(&mut self) { + // Close all I/O before exiting to ensure that the child doesn't wait + // forever to print some text or something similar. + for _ in range(0, self.io.len()) { + self.io.pop(); + } + + self.wait(); + } +} + +// Tests for this module can be found in the rtio-processes run-pass test, along +// with the justification for why it's not located here. diff --git a/src/libstd/io/signal.rs b/src/libstd/io/signal.rs new file mode 100644 index 00000000000..1310152d05f --- /dev/null +++ b/src/libstd/io/signal.rs @@ -0,0 +1,224 @@ +// 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. + +/*! + +Signal handling + +This modules provides bindings to receive signals safely, built on top of the +local I/O factory. There are a number of defined signals which can be caught, +but not all signals will work across all platforms (windows doesn't have +definitions for a number of signals. + +*/ + +use clone::Clone; +use comm::{Port, SharedChan, stream}; +use container::{Map, MutableMap}; +use hashmap; +use io::io_error; +use option::{Some, None}; +use result::{Err, Ok}; +use rt::rtio::{IoFactory, RtioSignal, with_local_io}; + +#[repr(int)] +#[deriving(Eq, IterBytes)] +pub enum Signum { + /// Equivalent to SIGBREAK, delivered when the user presses Ctrl-Break. + Break = 21i, + /// Equivalent to SIGHUP, delivered when the user closes the terminal + /// window. On delivery of HangUp, the program is given approximately + /// 10 seconds to perfom any cleanup. After that, Windows will + /// unconditionally terminate it. + HangUp = 1i, + /// Equivalent to SIGINT, delivered when the user presses Ctrl-c. + Interrupt = 2i, + /// Equivalent to SIGQUIT, delivered when the user presses Ctrl-\. + Quit = 3i, + /// Equivalent to SIGTSTP, delivered when the user presses Ctrl-z. + StopTemporarily = 20i, + /// Equivalent to SIGUSR1. + User1 = 10i, + /// Equivalent to SIGUSR2. + User2 = 12i, + /// Equivalent to SIGWINCH, delivered when the console has been resized. + /// WindowSizeChange may not be delivered in a timely manner; size change + /// will only be detected when the cursor is being moved. + WindowSizeChange = 28i, +} + +/// Listener provides a port to listen for registered signals. +/// +/// Listener automatically unregisters its handles once it is out of scope. +/// However, clients can still unregister signums manually. +/// +/// # Example +/// +/// ```rust +/// use std::io::signal::{Listener, Interrupt}; +/// +/// let mut listener = Listener::new(); +/// listener.register(signal::Interrupt); +/// +/// do spawn { +/// loop { +/// match listener.port.recv() { +/// Interrupt => println("Got Interrupt'ed"), +/// _ => (), +/// } +/// } +/// } +/// +/// ``` +pub struct Listener { + /// A map from signums to handles to keep the handles in memory + priv handles: hashmap::HashMap<Signum, ~RtioSignal>, + /// chan is where all the handles send signums, which are received by + /// the clients from port. + priv chan: SharedChan<Signum>, + + /// Clients of Listener can `recv()` from this port. This is exposed to + /// allow selection over this port as well as manipulation of the port + /// directly. + port: Port<Signum>, +} + +impl Listener { + /// Creates a new listener for signals. Once created, signals are bound via + /// the `register` method (otherwise nothing will ever be received) + pub fn new() -> Listener { + let (port, chan) = stream(); + Listener { + chan: SharedChan::new(chan), + port: port, + handles: hashmap::HashMap::new(), + } + } + + /// Listen for a signal, returning true when successfully registered for + /// signum. Signals can be received using `recv()`. + /// + /// Once a signal is registered, this listener will continue to receive + /// notifications of signals until it is unregistered. This occurs + /// regardless of the number of other listeners registered in other tasks + /// (or on this task). + /// + /// Signals are still received if there is no task actively waiting for + /// a signal, and a later call to `recv` will return the signal that was + /// received while no task was waiting on it. + /// + /// # Failure + /// + /// If this function fails to register a signal handler, then an error will + /// be raised on the `io_error` condition and the function will return + /// false. + pub fn register(&mut self, signum: Signum) -> bool { + if self.handles.contains_key(&signum) { + return true; // self is already listening to signum, so succeed + } + do with_local_io |io| { + match io.signal(signum, self.chan.clone()) { + Ok(w) => { + self.handles.insert(signum, w); + Some(()) + }, + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + }.is_some() + } + + /// Unregisters a signal. If this listener currently had a handler + /// registered for the signal, then it will stop receiving any more + /// notification about the signal. If the signal has already been received, + /// it may still be returned by `recv`. + pub fn unregister(&mut self, signum: Signum) { + self.handles.pop(&signum); + } +} + +#[cfg(test)] +mod test { + use libc; + use io::timer; + use super::{Listener, Interrupt}; + use comm::{GenericPort, Peekable}; + + // kill is only available on Unixes + #[cfg(unix)] + fn sigint() { + unsafe { + libc::funcs::posix88::signal::kill(libc::getpid(), libc::SIGINT); + } + } + + #[test] #[cfg(unix, not(target_os="android"))] // FIXME(#10378) + fn test_io_signal_smoketest() { + let mut signal = Listener::new(); + signal.register(Interrupt); + sigint(); + timer::sleep(10); + match signal.port.recv() { + Interrupt => (), + s => fail!("Expected Interrupt, got {:?}", s), + } + } + + #[test] #[cfg(unix, not(target_os="android"))] // FIXME(#10378) + fn test_io_signal_two_signal_one_signum() { + let mut s1 = Listener::new(); + let mut s2 = Listener::new(); + s1.register(Interrupt); + s2.register(Interrupt); + sigint(); + timer::sleep(10); + match s1.port.recv() { + Interrupt => (), + s => fail!("Expected Interrupt, got {:?}", s), + } + match s2.port.recv() { + Interrupt => (), + s => fail!("Expected Interrupt, got {:?}", s), + } + } + + #[test] #[cfg(unix, not(target_os="android"))] // FIXME(#10378) + fn test_io_signal_unregister() { + let mut s1 = Listener::new(); + let mut s2 = Listener::new(); + s1.register(Interrupt); + s2.register(Interrupt); + s2.unregister(Interrupt); + sigint(); + timer::sleep(10); + if s2.port.peek() { + fail!("Unexpected {:?}", s2.port.recv()); + } + } + + #[cfg(windows)] + #[test] + fn test_io_signal_invalid_signum() { + use io; + use super::User1; + let mut s = Listener::new(); + let mut called = false; + do io::io_error::cond.trap(|_| { + called = true; + }).inside { + if s.register(User1) { + fail!("Unexpected successful registry of signum {:?}", User1); + } + } + assert!(called); + } +} diff --git a/src/libstd/io/stdio.rs b/src/libstd/io/stdio.rs new file mode 100644 index 00000000000..302d7579873 --- /dev/null +++ b/src/libstd/io/stdio.rs @@ -0,0 +1,321 @@ +// 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. + +/*! + +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 +use std::io; + +let mut out = io::stdout(); +out.write(bytes!("Hello, world!")); +``` + +*/ + +use fmt; +use libc; +use option::{Option, Some, None}; +use result::{Ok, Err}; +use io::buffered::LineBufferedWriter; +use rt::rtio::{IoFactory, RtioTTY, RtioFileStream, with_local_io, + CloseAsynchronously}; +use super::{Reader, Writer, io_error, IoError, OtherIoError, + standard_error, EndOfFile}; + +// 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), +} + +fn src<T>(fd: libc::c_int, readable: bool, f: &fn(StdSource) -> T) -> T { + do with_local_io |io| { + let fd = unsafe { libc::dup(fd) }; + match io.tty_open(fd, readable) { + Ok(tty) => Some(f(TTY(tty))), + Err(_) => { + // It's not really that desirable if these handles are closed + // synchronously, and because they're squirreled away in a task + // structure the destructors will be run when the task is + // attempted to get destroyed. This means that if we run a + // synchronous destructor we'll attempt to do some scheduling + // operations which will just result in sadness. + Some(f(File(io.fs_from_raw_fd(fd, CloseAsynchronously)))) + } + } + }.unwrap() +} + +/// Creates a new non-blocking handle to the stdin of the current process. +/// +/// See `stdout()` for notes about this function. +pub fn stdin() -> StdReader { + do src(libc::STDIN_FILENO, true) |src| { StdReader { inner: src } } +} + +/// 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 { + do src(libc::STDOUT_FILENO, false) |src| { StdWriter { inner: src } } +} + +/// Creates a new non-blocking handle to the stderr of the current process. +/// +/// See `stdout()` for notes about this function. +pub fn stderr() -> StdWriter { + do src(libc::STDERR_FILENO, false) |src| { StdWriter { inner: src } } +} + +// 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: +// +// do with_task_stdout |io1| { +// do with_task_stdout |io2| { +// // io1 aliases io2 +// } +// } +fn with_task_stdout(f: &fn(&mut Writer)) { + use rt::local::Local; + use rt::task::Task; + + unsafe { + // Logging may require scheduling operations, so we can't remove the + // task from TLS right now, hence the unsafe borrow. Sad. + let task: *mut Task = Local::unsafe_borrow(); + + match (*task).stdout_handle { + Some(ref mut handle) => f(*handle), + None => { + let handle = stdout(); + let mut handle = ~LineBufferedWriter::new(handle) as ~Writer; + f(handle); + (*task).stdout_handle = Some(handle); + } + } + } +} + +/// Flushes the local task's stdout handle. +/// +/// 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). +/// +/// 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() { + do with_task_stdout |io| { + io.flush(); + } +} + +/// Prints a string to the stdout of the current process. No newline is emitted +/// after the string is printed. +pub fn print(s: &str) { + do with_task_stdout |io| { + io.write(s.as_bytes()); + } +} + +/// 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) { + do with_task_stdout |io| { + io.write(s.as_bytes()); + io.write(['\n' as u8]); + } +} + +/// Similar to `print`, but takes a `fmt::Arguments` structure to be compatible +/// with the `format_args!` macro. +pub fn print_args(fmt: &fmt::Arguments) { + do with_task_stdout |io| { + fmt::write(io, fmt); + } +} + +/// Similar to `println`, but takes a `fmt::Arguments` structure to be +/// compatible with the `format_args!` macro. +pub fn println_args(fmt: &fmt::Arguments) { + do with_task_stdout |io| { + fmt::writeln(io, fmt); + } +} + +/// Representation of a reader of a standard input stream +pub struct StdReader { + priv inner: StdSource +} + +impl Reader for StdReader { + fn read(&mut self, buf: &mut [u8]) -> Option<uint> { + let ret = match self.inner { + TTY(ref mut tty) => tty.read(buf), + File(ref mut file) => file.read(buf).map(|i| i as uint), + }; + match ret { + // 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 + } + Ok(amt) => Some(amt as uint), + Err(e) => { + io_error::cond.raise(e); + None + } + } + } + + fn eof(&mut self) -> bool { false } +} + +/// Representation of a writer to a standard output stream +pub struct StdWriter { + priv inner: StdSource +} + +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)> { + match self.inner { + TTY(ref mut tty) => { + match tty.get_winsize() { + Ok(p) => Some(p), + Err(e) => { + io_error::cond.raise(e); + None + } + } + } + File(*) => { + io_error::cond.raise(IoError { + kind: OtherIoError, + desc: "stream is not a tty", + detail: None, + }); + 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) { + match self.inner { + TTY(ref mut tty) => { + match tty.set_raw(raw) { + Ok(()) => {}, + Err(e) => io_error::cond.raise(e), + } + } + File(*) => { + io_error::cond.raise(IoError { + kind: OtherIoError, + desc: "stream is not a tty", + detail: None, + }); + } + } + } + + /// Returns whether this stream is attached to a TTY instance or not. + pub fn isatty(&self) -> bool { + match self.inner { + TTY(*) => true, + File(*) => false, + } + } +} + +impl Writer for StdWriter { + fn write(&mut self, buf: &[u8]) { + let ret = match self.inner { + TTY(ref mut tty) => tty.write(buf), + File(ref mut file) => file.write(buf), + }; + match ret { + Ok(()) => {} + Err(e) => io_error::cond.raise(e) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn smoke() { + // Just make sure we can acquire handles + stdin(); + stdout(); + stderr(); + } +} diff --git a/src/libstd/io/timer.rs b/src/libstd/io/timer.rs new file mode 100644 index 00000000000..219f63026d2 --- /dev/null +++ b/src/libstd/io/timer.rs @@ -0,0 +1,186 @@ +// 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. + +/*! + +Synchronous Timers + +This module exposes the functionality to create timers, block the current task, +and create ports which will receive notifications after a period of time. + +# Example + +```rust + +use std::io::Timer; + +let mut timer = Timer::new().unwrap(); +timer.sleep(10); // block the task for awhile + +let timeout = timer.oneshot(10); +// do some work +timeout.recv(); // wait for the timeout to expire + +let periodic = timer.periodic(10); +loop { + periodic.recv(); + // this loop is only executed once every 10ms +} + +``` + +*/ + +use comm::{Port, PortOne}; +use option::{Option, Some, None}; +use result::{Ok, Err}; +use io::io_error; +use rt::rtio::{IoFactory, RtioTimer, with_local_io}; + +pub struct Timer { + priv obj: ~RtioTimer +} + +/// Sleep the current task for `msecs` milliseconds. +pub fn sleep(msecs: u64) { + let mut timer = Timer::new().expect("timer::sleep: could not create a Timer"); + + timer.sleep(msecs) +} + +impl Timer { + /// Creates a new timer which can be used to put the current task to sleep + /// for a number of milliseconds, or to possibly create channels which will + /// get notified after an amount of time has passed. + pub fn new() -> Option<Timer> { + do with_local_io |io| { + match io.timer_init() { + Ok(t) => Some(Timer { obj: t }), + Err(ioerr) => { + debug!("Timer::init: failed to init: {:?}", ioerr); + io_error::cond.raise(ioerr); + None + } + } + + } + } + + /// Blocks the current task for `msecs` milliseconds. + /// + /// Note that this function will cause any other ports for this timer to be + /// invalidated (the other end will be closed). + pub fn sleep(&mut self, msecs: u64) { + self.obj.sleep(msecs); + } + + /// Creates a oneshot port which will have a notification sent when `msecs` + /// milliseconds has elapsed. This does *not* block the current task, but + /// instead returns immediately. + /// + /// Note that this invalidates any previous port which has been created by + /// this timer, and that the returned port will be invalidated once the + /// timer is destroyed (when it falls out of scope). + pub fn oneshot(&mut self, msecs: u64) -> PortOne<()> { + self.obj.oneshot(msecs) + } + + /// Creates a port which will have a continuous stream of notifications + /// being sent every `msecs` milliseconds. This does *not* block the + /// current task, but instead returns immediately. The first notification + /// will not be received immediately, but rather after `msec` milliseconds + /// have passed. + /// + /// Note that this invalidates any previous port which has been created by + /// this timer, and that the returned port will be invalidated once the + /// timer is destroyed (when it falls out of scope). + pub fn periodic(&mut self, msecs: u64) -> Port<()> { + self.obj.period(msecs) + } +} + +#[cfg(test)] +mod test { + use prelude::*; + use super::*; + use rt::test::*; + + #[test] + fn test_io_timer_sleep_simple() { + do run_in_mt_newsched_task { + let mut timer = Timer::new().unwrap(); + timer.sleep(1); + } + } + + #[test] + fn test_io_timer_sleep_oneshot() { + do run_in_mt_newsched_task { + let mut timer = Timer::new().unwrap(); + timer.oneshot(1).recv(); + } + } + + #[test] + fn test_io_timer_sleep_oneshot_forget() { + do run_in_mt_newsched_task { + let mut timer = Timer::new().unwrap(); + timer.oneshot(100000000000); + } + } + + #[test] + fn oneshot_twice() { + do run_in_mt_newsched_task { + let mut timer = Timer::new().unwrap(); + let port1 = timer.oneshot(10000); + let port = timer.oneshot(1); + port.recv(); + assert_eq!(port1.try_recv(), None); + } + } + + #[test] + fn test_io_timer_oneshot_then_sleep() { + do run_in_mt_newsched_task { + let mut timer = Timer::new().unwrap(); + let port = timer.oneshot(100000000000); + timer.sleep(1); // this should invalidate the port + + assert_eq!(port.try_recv(), None); + } + } + + #[test] + fn test_io_timer_sleep_periodic() { + do run_in_mt_newsched_task { + let mut timer = Timer::new().unwrap(); + let port = timer.periodic(1); + port.recv(); + port.recv(); + port.recv(); + } + } + + #[test] + fn test_io_timer_sleep_periodic_forget() { + do run_in_mt_newsched_task { + let mut timer = Timer::new().unwrap(); + timer.periodic(100000000000); + } + } + + #[test] + fn test_io_timer_sleep_standalone() { + do run_in_mt_newsched_task { + sleep(1) + } + } +} |
