Skip to content

Commit d4ba02f

Browse files
committed
Auto merge of rust-lang#483 - asomers:aio2, r=posborne
Add POSIX AIO support POSIX AIO is a standard for asynchronous file I/O. Read, write, and fsync operations can all take place in the background, with completion notification delivered by a signal, by a new thread, by kqueue, or not at all. The SigEvent class, used for AIO notifications among other things, is also added.
2 parents 8865e78 + b740156 commit d4ba02f

File tree

7 files changed

+748
-0
lines changed

7 files changed

+748
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ This project adheres to [Semantic Versioning](http://semver.org/).
66
## [Unreleased]
77

88
### Added
9+
- Added support for POSIX AIO
10+
([#483](https://github.com/nix-rust/nix/pull/483))
911
- Added support for XNU system control sockets
1012
([#478](https://github.com/nix-rust/nix/pull/478))
1113
- Added support for `ioctl` calls on BSD platforms
1214
([#478](https://github.com/nix-rust/nix/pull/478))
1315
- Added struct `TimeSpec`
1416
([#475](https://github.com/nix-rust/nix/pull/475))
17+
([#483](https://github.com/nix-rust/nix/pull/483))
1518
- Added complete definitions for all kqueue-related constants on all supported
1619
OSes
1720
([#415](https://github.com/nix-rust/nix/pull/415))

src/sys/aio.rs

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
use {Error, Errno, Result};
2+
use std::os::unix::io::RawFd;
3+
use libc::{c_void, off_t, size_t};
4+
use libc;
5+
use std::marker::PhantomData;
6+
use std::mem;
7+
use std::ptr::{null, null_mut};
8+
use sys::signal::*;
9+
use sys::time::TimeSpec;
10+
11+
/// Mode for `aio_fsync`. Controls whether only data or both data and metadata
12+
/// are synced.
13+
#[repr(i32)]
14+
#[derive(Clone, Copy, Debug, PartialEq)]
15+
pub enum AioFsyncMode {
16+
/// do it like `fsync`
17+
O_SYNC = libc::O_SYNC,
18+
/// on supported operating systems only, do it like `fdatasync`
19+
#[cfg(any(target_os = "openbsd", target_os = "bitrig",
20+
target_os = "netbsd", target_os = "macos", target_os = "ios",
21+
target_os = "linux"))]
22+
O_DSYNC = libc::O_DSYNC
23+
}
24+
25+
/// When used with `lio_listio`, determines whether a given `aiocb` should be
26+
/// used for a read operation, a write operation, or ignored. Has no effect for
27+
/// any other aio functions.
28+
#[repr(i32)]
29+
#[derive(Clone, Copy, Debug, PartialEq)]
30+
pub enum LioOpcode {
31+
LIO_NOP = libc::LIO_NOP,
32+
LIO_WRITE = libc::LIO_WRITE,
33+
LIO_READ = libc::LIO_READ
34+
}
35+
36+
/// Mode for `lio_listio`.
37+
#[repr(i32)]
38+
#[derive(Clone, Copy, Debug, PartialEq)]
39+
pub enum LioMode {
40+
/// Requests that `lio_listio` block until all requested operations have
41+
/// been completed
42+
LIO_WAIT = libc::LIO_WAIT,
43+
/// Requests that `lio_listio` return immediately
44+
LIO_NOWAIT = libc::LIO_NOWAIT,
45+
}
46+
47+
/// Return values for `aio_cancel`
48+
#[repr(i32)]
49+
#[derive(Clone, Copy, Debug, PartialEq)]
50+
pub enum AioCancelStat {
51+
/// All outstanding requests were canceled
52+
AioCanceled = libc::AIO_CANCELED,
53+
/// Some requests were not canceled. Their status should be checked with
54+
/// `aio_error`
55+
AioNotCanceled = libc::AIO_NOTCANCELED,
56+
/// All of the requests have already finished
57+
AioAllDone = libc::AIO_ALLDONE,
58+
}
59+
60+
/// The basic structure used by all aio functions. Each `aiocb` represents one
61+
/// I/O request.
62+
#[repr(C)]
63+
pub struct AioCb<'a> {
64+
aiocb: libc::aiocb,
65+
phantom: PhantomData<&'a mut [u8]>
66+
}
67+
68+
impl<'a> AioCb<'a> {
69+
/// Constructs a new `AioCb` with no associated buffer.
70+
///
71+
/// The resulting `AioCb` structure is suitable for use with `aio_fsync`.
72+
/// * `fd` File descriptor. Required for all aio functions.
73+
/// * `prio` If POSIX Prioritized IO is supported, then the operation will
74+
/// be prioritized at the process's priority level minus `prio`
75+
/// * `sigev_notify` Determines how you will be notified of event
76+
/// completion.
77+
pub fn from_fd(fd: RawFd, prio: ::c_int,
78+
sigev_notify: SigevNotify) -> AioCb<'a> {
79+
let mut a = AioCb::common_init(fd, prio, sigev_notify);
80+
a.aio_offset = 0;
81+
a.aio_nbytes = 0;
82+
a.aio_buf = null_mut();
83+
84+
let aiocb = AioCb { aiocb: a, phantom: PhantomData};
85+
aiocb
86+
}
87+
88+
/// Constructs a new `AioCb`.
89+
///
90+
/// * `fd` File descriptor. Required for all aio functions.
91+
/// * `offs` File offset
92+
/// * `buf` A memory buffer
93+
/// * `prio` If POSIX Prioritized IO is supported, then the operation will
94+
/// be prioritized at the process's priority level minus `prio`
95+
/// * `sigev_notify` Determines how you will be notified of event
96+
/// completion.
97+
/// * `opcode` This field is only used for `lio_listio`. It determines
98+
/// which operation to use for this individual aiocb
99+
pub fn from_mut_slice(fd: RawFd, offs: off_t, buf: &'a mut [u8],
100+
prio: ::c_int, sigev_notify: SigevNotify,
101+
opcode: LioOpcode) -> AioCb {
102+
let mut a = AioCb::common_init(fd, prio, sigev_notify);
103+
a.aio_offset = offs;
104+
a.aio_nbytes = buf.len() as size_t;
105+
a.aio_buf = buf.as_ptr() as *mut c_void;
106+
a.aio_lio_opcode = opcode as ::c_int;
107+
108+
let aiocb = AioCb { aiocb: a, phantom: PhantomData};
109+
aiocb
110+
}
111+
112+
/// Like `from_mut_slice`, but works on constant slices rather than
113+
/// mutable slices.
114+
///
115+
/// This is technically unsafe, but in practice it's fine
116+
/// to use with any aio functions except `aio_read` and `lio_listio` (with
117+
/// `opcode` set to `LIO_READ`). This method is useful when writing a const
118+
/// buffer with `aio_write`, since from_mut_slice can't work with const
119+
/// buffers.
120+
// Note: another solution to the problem of writing const buffers would be
121+
// to genericize AioCb for both &mut [u8] and &[u8] buffers. aio_read could
122+
// take the former and aio_write could take the latter. However, then
123+
// lio_listio wouldn't work, because that function needs a slice of AioCb,
124+
// and they must all be the same type. We're basically stuck with using an
125+
// unsafe function, since aio (as designed in C) is an unsafe API.
126+
pub unsafe fn from_slice(fd: RawFd, offs: off_t, buf: &'a [u8],
127+
prio: ::c_int, sigev_notify: SigevNotify,
128+
opcode: LioOpcode) -> AioCb {
129+
let mut a = AioCb::common_init(fd, prio, sigev_notify);
130+
a.aio_offset = offs;
131+
a.aio_nbytes = buf.len() as size_t;
132+
a.aio_buf = buf.as_ptr() as *mut c_void;
133+
a.aio_lio_opcode = opcode as ::c_int;
134+
135+
let aiocb = AioCb { aiocb: a, phantom: PhantomData};
136+
aiocb
137+
}
138+
139+
fn common_init(fd: RawFd, prio: ::c_int,
140+
sigev_notify: SigevNotify) -> libc::aiocb {
141+
// Use mem::zeroed instead of explicitly zeroing each field, because the
142+
// number and name of reserved fields is OS-dependent. On some OSes,
143+
// some reserved fields are used the kernel for state, and must be
144+
// explicitly zeroed when allocated.
145+
let mut a = unsafe { mem::zeroed::<libc::aiocb>()};
146+
a.aio_fildes = fd;
147+
a.aio_reqprio = prio;
148+
a.aio_sigevent = SigEvent::new(sigev_notify).sigevent();
149+
a
150+
}
151+
152+
/// Update the notification settings for an existing `aiocb`
153+
pub fn set_sigev_notify(&mut self, sigev_notify: SigevNotify) {
154+
self.aiocb.aio_sigevent = SigEvent::new(sigev_notify).sigevent();
155+
}
156+
}
157+
158+
/// Cancels outstanding AIO requests. If `aiocb` is `None`, then all requests
159+
/// for `fd` will be cancelled. Otherwise, only the given `AioCb` will be
160+
/// cancelled.
161+
pub fn aio_cancel(fd: RawFd, aiocb: Option<&mut AioCb>) -> Result<AioCancelStat> {
162+
let p: *mut libc::aiocb = match aiocb {
163+
None => null_mut(),
164+
Some(x) => &mut x.aiocb
165+
};
166+
match unsafe { libc::aio_cancel(fd, p) } {
167+
libc::AIO_CANCELED => Ok(AioCancelStat::AioCanceled),
168+
libc::AIO_NOTCANCELED => Ok(AioCancelStat::AioNotCanceled),
169+
libc::AIO_ALLDONE => Ok(AioCancelStat::AioAllDone),
170+
-1 => Err(Error::last()),
171+
_ => panic!("unknown aio_cancel return value")
172+
}
173+
}
174+
175+
/// Retrieve error status of an asynchronous operation. If the request has not
176+
/// yet completed, returns `EINPROGRESS`. Otherwise, returns `Ok` or any other
177+
/// error.
178+
pub fn aio_error(aiocb: &mut AioCb) -> Result<()> {
179+
let p: *mut libc::aiocb = &mut aiocb.aiocb;
180+
match unsafe { libc::aio_error(p) } {
181+
0 => Ok(()),
182+
num if num > 0 => Err(Error::from_errno(Errno::from_i32(num))),
183+
-1 => Err(Error::last()),
184+
num => panic!("unknown aio_error return value {:?}", num)
185+
}
186+
}
187+
188+
/// An asynchronous version of `fsync`.
189+
pub fn aio_fsync(mode: AioFsyncMode, aiocb: &mut AioCb) -> Result<()> {
190+
let p: *mut libc::aiocb = &mut aiocb.aiocb;
191+
Errno::result(unsafe { libc::aio_fsync(mode as ::c_int, p) }).map(drop)
192+
}
193+
194+
/// Asynchronously reads from a file descriptor into a buffer
195+
pub fn aio_read(aiocb: &mut AioCb) -> Result<()> {
196+
let p: *mut libc::aiocb = &mut aiocb.aiocb;
197+
Errno::result(unsafe { libc::aio_read(p) }).map(drop)
198+
}
199+
200+
/// Retrieve return status of an asynchronous operation. Should only be called
201+
/// once for each `AioCb`, after `aio_error` indicates that it has completed.
202+
/// The result the same as for `read`, `write`, of `fsync`.
203+
pub fn aio_return(aiocb: &mut AioCb) -> Result<isize> {
204+
let p: *mut libc::aiocb = &mut aiocb.aiocb;
205+
Errno::result(unsafe { libc::aio_return(p) })
206+
}
207+
208+
/// Suspends the calling process until at least one of the specified `AioCb`s
209+
/// has completed, a signal is delivered, or the timeout has passed. If
210+
/// `timeout` is `None`, `aio_suspend` will block indefinitely.
211+
pub fn aio_suspend(list: &[&AioCb], timeout: Option<TimeSpec>) -> Result<()> {
212+
// We must use transmute because Rust doesn't understand that a pointer to a
213+
// Struct is the same as a pointer to its first element.
214+
let plist = unsafe {
215+
mem::transmute::<&[&AioCb], *const [*const libc::aiocb]>(list)
216+
};
217+
let p = plist as *const *const libc::aiocb;
218+
let timep = match timeout {
219+
None => null::<libc::timespec>(),
220+
Some(x) => x.as_ref() as *const libc::timespec
221+
};
222+
Errno::result(unsafe {
223+
libc::aio_suspend(p, list.len() as i32, timep)
224+
}).map(drop)
225+
}
226+
227+
/// Asynchronously writes from a buffer to a file descriptor
228+
pub fn aio_write(aiocb: &mut AioCb) -> Result<()> {
229+
let p: *mut libc::aiocb = &mut aiocb.aiocb;
230+
Errno::result(unsafe { libc::aio_write(p) }).map(drop)
231+
}
232+
233+
/// Submits multiple asynchronous I/O requests with a single system call. The
234+
/// order in which the requests are carried out is not specified.
235+
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
236+
pub fn lio_listio(mode: LioMode, list: &[&mut AioCb],
237+
sigev_notify: SigevNotify) -> Result<()> {
238+
let sigev = SigEvent::new(sigev_notify);
239+
let sigevp = &mut sigev.sigevent() as *mut libc::sigevent;
240+
// We must use transmute because Rust doesn't understand that a pointer to a
241+
// Struct is the same as a pointer to its first element.
242+
let plist = unsafe {
243+
mem::transmute::<&[&mut AioCb], *const [*mut libc::aiocb]>(list)
244+
};
245+
let p = plist as *const *mut libc::aiocb;
246+
Errno::result(unsafe {
247+
libc::lio_listio(mode as i32, p, list.len() as i32, sigevp)
248+
}).map(drop)
249+
}

src/sys/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
#[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "ios",
2+
target_os = "netbsd", target_os = "macos", target_os = "linux"))]
3+
pub mod aio;
4+
15
#[cfg(any(target_os = "linux", target_os = "android"))]
26
pub mod epoll;
37

src/sys/signal.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
use libc;
55
use {Errno, Error, Result};
66
use std::mem;
7+
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
8+
use std::os::unix::io::RawFd;
79
use std::ptr;
810

911
// Currently there is only one definition of c_int in libc, as well as only one
@@ -403,6 +405,107 @@ pub fn raise(signal: Signal) -> Result<()> {
403405
Errno::result(res).map(drop)
404406
}
405407

408+
409+
#[cfg(target_os = "freebsd")]
410+
pub type type_of_thread_id = libc::lwpid_t;
411+
#[cfg(target_os = "linux")]
412+
pub type type_of_thread_id = libc::pid_t;
413+
414+
/// Used to request asynchronous notification of certain events, for example,
415+
/// with POSIX AIO, POSIX message queues, and POSIX timers.
416+
// sigval is actually a union of a int and a void*. But it's never really used
417+
// as a pointer, because neither libc nor the kernel ever dereference it. nix
418+
// therefore presents it as an intptr_t, which is how kevent uses it.
419+
#[derive(Clone, Copy, Debug, PartialEq)]
420+
pub enum SigevNotify {
421+
/// No notification will be delivered
422+
SigevNone,
423+
/// The signal given by `signal` will be delivered to the process. The
424+
/// value in `si_value` will be present in the `si_value` field of the
425+
/// `siginfo_t` structure of the queued signal.
426+
SigevSignal { signal: Signal, si_value: libc::intptr_t },
427+
// Note: SIGEV_THREAD is not implemented because libc::sigevent does not
428+
// expose a way to set the union members needed by SIGEV_THREAD.
429+
/// A new `kevent` is posted to the kqueue `kq`. The `kevent`'s `udata`
430+
/// field will contain the value in `udata`.
431+
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
432+
SigevKevent { kq: RawFd, udata: libc::intptr_t },
433+
/// The signal `signal` is queued to the thread whose LWP ID is given in
434+
/// `thread_id`. The value stored in `si_value` will be present in the
435+
/// `si_value` of the `siginfo_t` structure of the queued signal.
436+
#[cfg(any(target_os = "freebsd", target_os = "linux"))]
437+
SigevThreadId { signal: Signal, thread_id: type_of_thread_id,
438+
si_value: libc::intptr_t },
439+
}
440+
441+
/// Used to request asynchronous notification of the completion of certain
442+
/// events, such as POSIX AIO and timers.
443+
#[repr(C)]
444+
pub struct SigEvent {
445+
sigevent: libc::sigevent
446+
}
447+
448+
impl SigEvent {
449+
// Note: this constructor does not allow the user to set the
450+
// sigev_notify_kevent_flags field. That's considered ok because on FreeBSD
451+
// at least those flags don't do anything useful. That field is part of a
452+
// union that shares space with the more genuinely useful
453+
// Note: This constructor also doesn't allow the caller to set the
454+
// sigev_notify_function or sigev_notify_attributes fields, which are
455+
// required for SIGEV_THREAD. That's considered ok because on no operating
456+
// system is SIGEV_THREAD the most efficient way to deliver AIO
457+
// notification. FreeBSD and Dragonfly programs should prefer SIGEV_KEVENT.
458+
// Linux, Solaris, and portable programs should prefer SIGEV_THREAD_ID or
459+
// SIGEV_SIGNAL. That field is part of a union that shares space with the
460+
// more genuinely useful sigev_notify_thread_id
461+
pub fn new(sigev_notify: SigevNotify) -> SigEvent {
462+
let mut sev = unsafe { mem::zeroed::<libc::sigevent>()};
463+
sev.sigev_notify = match sigev_notify {
464+
SigevNotify::SigevNone => libc::SIGEV_NONE,
465+
SigevNotify::SigevSignal{..} => libc::SIGEV_SIGNAL,
466+
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
467+
SigevNotify::SigevKevent{..} => libc::SIGEV_KEVENT,
468+
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
469+
SigevNotify::SigevThreadId{..} => libc::SIGEV_THREAD_ID
470+
};
471+
sev.sigev_signo = match sigev_notify {
472+
SigevNotify::SigevSignal{ signal, .. } => signal as ::c_int,
473+
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
474+
SigevNotify::SigevKevent{ kq, ..} => kq,
475+
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
476+
SigevNotify::SigevThreadId{ signal, .. } => signal as ::c_int,
477+
_ => 0
478+
};
479+
sev.sigev_value.sival_ptr = match sigev_notify {
480+
SigevNotify::SigevNone => ptr::null_mut::<libc::c_void>(),
481+
SigevNotify::SigevSignal{ si_value, .. } => si_value as *mut ::c_void,
482+
#[cfg(any(target_os = "dragonfly", target_os = "freebsd"))]
483+
SigevNotify::SigevKevent{ udata, .. } => udata as *mut ::c_void,
484+
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
485+
SigevNotify::SigevThreadId{ si_value, .. } => si_value as *mut ::c_void,
486+
};
487+
SigEvent::set_tid(&mut sev, &sigev_notify);
488+
SigEvent{sigevent: sev}
489+
}
490+
491+
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
492+
fn set_tid(sev: &mut libc::sigevent, sigev_notify: &SigevNotify) {
493+
sev.sigev_notify_thread_id = match sigev_notify {
494+
&SigevNotify::SigevThreadId { thread_id, .. } => thread_id,
495+
_ => 0 as type_of_thread_id
496+
};
497+
}
498+
499+
#[cfg(not(any(target_os = "freebsd", target_os = "linux")))]
500+
fn set_tid(_sev: &mut libc::sigevent, _sigev_notify: &SigevNotify) {
501+
}
502+
503+
pub fn sigevent(&self) -> libc::sigevent {
504+
self.sigevent
505+
}
506+
}
507+
508+
406509
#[cfg(test)]
407510
mod tests {
408511
use super::*;

0 commit comments

Comments
 (0)