]> git.lizzy.rs Git - rust.git/blob - src/libnative/io/util.rs
c5b1bbec4f1631988026fa98d25f770906440905
[rust.git] / src / libnative / io / util.rs
1 // Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 use libc;
12 use std::cmp;
13 use std::mem;
14 use std::os;
15 use std::ptr;
16 use std::rt::rtio::{IoResult, IoError};
17
18 use super::c;
19 use super::net;
20 use super::{retry, last_error};
21
22 #[deriving(Show)]
23 pub enum SocketStatus {
24     Readable,
25     Writable,
26 }
27
28 pub fn timeout(desc: &'static str) -> IoError {
29     #[cfg(unix)] use libc::ETIMEDOUT as ERROR;
30     #[cfg(windows)] use libc::ERROR_OPERATION_ABORTED as ERROR;
31     IoError {
32         code: ERROR as uint,
33         extra: 0,
34         detail: Some(desc.to_string()),
35     }
36 }
37
38 pub fn short_write(n: uint, desc: &'static str) -> IoError {
39     #[cfg(unix)] use libc::EAGAIN as ERROR;
40     #[cfg(windows)] use libc::ERROR_OPERATION_ABORTED as ERROR;
41     IoError {
42         code: ERROR as uint,
43         extra: n,
44         detail: Some(desc.to_string()),
45     }
46 }
47
48 pub fn eof() -> IoError {
49     IoError {
50         code: libc::EOF as uint,
51         extra: 0,
52         detail: None,
53     }
54 }
55
56 #[cfg(windows)]
57 pub fn ms_to_timeval(ms: u64) -> libc::timeval {
58     libc::timeval {
59         tv_sec: (ms / 1000) as libc::c_long,
60         tv_usec: ((ms % 1000) * 1000) as libc::c_long,
61     }
62 }
63 #[cfg(not(windows))]
64 pub fn ms_to_timeval(ms: u64) -> libc::timeval {
65     libc::timeval {
66         tv_sec: (ms / 1000) as libc::time_t,
67         tv_usec: ((ms % 1000) * 1000) as libc::suseconds_t,
68     }
69 }
70
71 #[cfg(unix)]
72 pub fn wouldblock() -> bool {
73     let err = os::errno();
74     err == libc::EWOULDBLOCK as int || err == libc::EAGAIN as int
75 }
76
77 #[cfg(windows)]
78 pub fn wouldblock() -> bool {
79     let err = os::errno();
80     err == libc::WSAEWOULDBLOCK as uint
81 }
82
83 #[cfg(unix)]
84 pub fn set_nonblocking(fd: net::sock_t, nb: bool) -> IoResult<()> {
85     let set = nb as libc::c_int;
86     super::mkerr_libc(retry(|| unsafe { c::ioctl(fd, c::FIONBIO, &set) }))
87 }
88
89 #[cfg(windows)]
90 pub fn set_nonblocking(fd: net::sock_t, nb: bool) -> IoResult<()> {
91     let mut set = nb as libc::c_ulong;
92     if unsafe { c::ioctlsocket(fd, c::FIONBIO, &mut set) != 0 } {
93         Err(last_error())
94     } else {
95         Ok(())
96     }
97 }
98
99 // See http://developerweb.net/viewtopic.php?id=3196 for where this is
100 // derived from.
101 pub fn connect_timeout(fd: net::sock_t,
102                        addrp: *const libc::sockaddr,
103                        len: libc::socklen_t,
104                        timeout_ms: u64) -> IoResult<()> {
105     use std::os;
106     #[cfg(unix)]    use libc::EINPROGRESS as INPROGRESS;
107     #[cfg(windows)] use libc::WSAEINPROGRESS as INPROGRESS;
108     #[cfg(unix)]    use libc::EWOULDBLOCK as WOULDBLOCK;
109     #[cfg(windows)] use libc::WSAEWOULDBLOCK as WOULDBLOCK;
110
111     // Make sure the call to connect() doesn't block
112     try!(set_nonblocking(fd, true));
113
114     let ret = match unsafe { libc::connect(fd, addrp, len) } {
115         // If the connection is in progress, then we need to wait for it to
116         // finish (with a timeout). The current strategy for doing this is
117         // to use select() with a timeout.
118         -1 if os::errno() as int == INPROGRESS as int ||
119               os::errno() as int == WOULDBLOCK as int => {
120             let mut set: c::fd_set = unsafe { mem::zeroed() };
121             c::fd_set(&mut set, fd);
122             match await(fd, &mut set, timeout_ms) {
123                 0 => Err(timeout("connection timed out")),
124                 -1 => Err(last_error()),
125                 _ => {
126                     let err: libc::c_int = try!(
127                         net::getsockopt(fd, libc::SOL_SOCKET, libc::SO_ERROR));
128                     if err == 0 {
129                         Ok(())
130                     } else {
131                         Err(IoError {
132                             code: err as uint,
133                             extra: 0,
134                             detail: Some(os::error_string(err as uint)),
135                         })
136                     }
137                 }
138             }
139         }
140
141         -1 => Err(last_error()),
142         _ => Ok(()),
143     };
144
145     // be sure to turn blocking I/O back on
146     try!(set_nonblocking(fd, false));
147     return ret;
148
149     #[cfg(unix)]
150     fn await(fd: net::sock_t, set: &mut c::fd_set,
151              timeout: u64) -> libc::c_int {
152         let start = ::io::timer::now();
153         retry(|| unsafe {
154             // Recalculate the timeout each iteration (it is generally
155             // undefined what the value of the 'tv' is after select
156             // returns EINTR).
157             let mut tv = ms_to_timeval(timeout - (::io::timer::now() - start));
158             c::select(fd + 1, ptr::mut_null(), set as *mut _,
159                       ptr::mut_null(), &mut tv)
160         })
161     }
162     #[cfg(windows)]
163     fn await(_fd: net::sock_t, set: &mut c::fd_set,
164              timeout: u64) -> libc::c_int {
165         let mut tv = ms_to_timeval(timeout);
166         unsafe { c::select(1, ptr::mut_null(), set, ptr::mut_null(), &mut tv) }
167     }
168 }
169
170 pub fn await(fds: &[net::sock_t], deadline: Option<u64>,
171              status: SocketStatus) -> IoResult<()> {
172     let mut set: c::fd_set = unsafe { mem::zeroed() };
173     let mut max = 0;
174     for &fd in fds.iter() {
175         c::fd_set(&mut set, fd);
176         max = cmp::max(max, fd + 1);
177     }
178
179     let (read, write) = match status {
180         Readable => (&mut set as *mut _, ptr::mut_null()),
181         Writable => (ptr::mut_null(), &mut set as *mut _),
182     };
183     let mut tv: libc::timeval = unsafe { mem::zeroed() };
184
185     match retry(|| {
186         let now = ::io::timer::now();
187         let tvp = match deadline {
188             None => ptr::mut_null(),
189             Some(deadline) => {
190                 // If we're past the deadline, then pass a 0 timeout to
191                 // select() so we can poll the status
192                 let ms = if deadline < now {0} else {deadline - now};
193                 tv = ms_to_timeval(ms);
194                 &mut tv as *mut _
195             }
196         };
197         let r = unsafe {
198             c::select(max as libc::c_int, read, write, ptr::mut_null(), tvp)
199         };
200         r
201     }) {
202         -1 => Err(last_error()),
203         0 => Err(timeout("timed out")),
204         _ => Ok(()),
205     }
206 }