Skip to content

Commit 60ccee7

Browse files
committed
Cleanup readv & writev + tests
1 parent b2454c9 commit 60ccee7

File tree

15 files changed

+329
-311
lines changed

15 files changed

+329
-311
lines changed

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,8 @@ rand = "0.1.2"
2222
[dev-dependencies.nix-test]
2323
path = "nix-test"
2424
version = "*"
25+
26+
[[test]]
27+
28+
name = "test"
29+
path = "test/test.rs"

nix-test/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
OUT = libnixtest.a
22
CFLAGS = -fPIC -D$(OS)
3-
OBJS = errno.o
3+
OBJS = errno.o \
4+
sizes.o
45

56
$(OUT): $(OBJS)
67
ar -rcs $@ $^

nix-test/src/lib.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,41 @@ use std::ffi::CString;
66
use libc::{c_int};
77

88
mod ffi {
9-
use libc::{c_int, c_char};
9+
use libc::{c_int, c_char, size_t};
1010

1111
#[link(name = "nixtest", kind = "static")]
1212
extern {
1313
pub fn assert_errno_eq(errno: *const c_char) -> c_int;
14+
pub fn size_of(ty: *const c_char) -> size_t;
1415
}
1516
}
1617

17-
pub fn assert_errno_eq(err: &str, val: c_int) {
18+
pub fn assert_errno_eq(name: &str, actual: c_int) {
1819
unsafe {
19-
let name = CString::new(err).unwrap();
20-
let actual = ffi::assert_errno_eq(name.as_ptr());
20+
let cstr = CString::new(name).unwrap();
21+
let expect = ffi::assert_errno_eq(cstr.as_ptr());
2122

22-
assert!(actual > 0);
23+
assert!(expect > 0, "undefined errno {}", name);
2324

24-
if val != actual {
25-
panic!("incorrect value for errno {}; got={}; expected={}",
26-
err, val, actual);
25+
if actual != expect {
26+
panic!("incorrect value for errno {}; expect={}; actual={}",
27+
name, expect, actual);
28+
}
29+
}
30+
}
31+
32+
pub fn assert_size_of<T>(name: &str) {
33+
use std::mem;
34+
35+
unsafe {
36+
let cstr = CString::new(name).unwrap();
37+
let expect = ffi::size_of(cstr.as_ptr()) as usize;
38+
39+
assert!(expect > 0, "undefined type {}", name);
40+
41+
if mem::size_of::<T>() != expect {
42+
panic!("incorrectly sized type; expect={}; actual={}",
43+
expect, mem::size_of::<T>());
2744
}
2845
}
2946
}

nix-test/src/sizes.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include "sys/uio.h"
2+
3+
#define SIZE_OF_T(TYPE) \
4+
do { \
5+
if (0 == strcmp(type, #TYPE)) { \
6+
return sizeof(TYPE); \
7+
} \
8+
} while (0)
9+
10+
#define SIZE_OF_S(TYPE) \
11+
do { \
12+
if (0 == strcmp(type, #TYPE)) { \
13+
return sizeof(struct TYPE); \
14+
} \
15+
} while (0)
16+
17+
size_t
18+
size_of(const char* type) {
19+
// sys/uio
20+
SIZE_OF_S(iovec);
21+
22+
return 0;
23+
}

src/sys/uio.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
2+
mod ffi {
3+
use super::IoVec;
4+
use libc::{ssize_t];
5+
6+
// vectorized version of write
7+
// doc: http://man7.org/linux/man-pages/man2/writev.2.html
8+
pub fn writev(fd: Fd, iov: *const IoVec<&[u8]>, iovcnt: c_int) -> ssize_t;
9+
10+
// vectorized version of read
11+
// doc: http://man7.org/linux/man-pages/man2/readv.2.html
12+
pub fn readv(fd: Fd, iov: *const IoVec<&mut [u8]>, iovcnt: c_int) -> ssize_t;
13+
}
14+
15+
pub fn writev(fd: Fd, iov: &[IoVec<&[u8]>]) -> NixResult<usize> {
16+
let res = unsafe { ffi::writev(fd, iov.as_ptr(), iov.len() as c_int) };
17+
18+
if res < 0 {
19+
return Err(NixError::Sys(Errno::last()));
20+
}
21+
22+
return Ok(res as usize)
23+
}
24+
25+
pub fn readv(fd: Fd, iov: &mut [IoVec<&mut [u8]>]) -> NixResult<usize> {
26+
let res = unsafe { ffi::readv(fd, iov.as_ptr(), iov.len() as c_int) };
27+
if res < 0 {
28+
return Err(NixError::Sys(Errno::last()));
29+
}
30+
31+
return Ok(res as usize)
32+
}
33+
34+
#[repr(C)]
35+
pub struct IoVec<T> {
36+
iov_base: *mut c_void,
37+
iov_len: size_t,
38+
phantom: PhantomData<T>
39+
}
40+
41+
impl<T> IoVec<T> {
42+
#[inline]
43+
pub fn as_slice<'a>(&'a self) -> &'a [u8] {
44+
use std::slice;
45+
46+
unsafe {
47+
slice::from_raw_parts(
48+
self.iov_base as *const u8,
49+
self.iov_len as usize)
50+
}
51+
}
52+
}
53+
54+
impl<'a> IoVec<&'a [u8]> {
55+
pub fn from_slice(buf: &'a [u8]) -> IoVec<&'a [u8]> {
56+
IoVec {
57+
iov_base: buf.as_ptr() as *mut c_void,
58+
iov_len: buf.len() as size_t,
59+
phantom: PhantomData
60+
}
61+
}
62+
}
63+
64+
impl<'a> IoVec<&'a mut [u8]> {
65+
pub fn from_mut_slice(buf: &'a mut [u8]) -> IoVec<&'a mut [u8]> {
66+
IoVec {
67+
iov_base: buf.as_ptr() as *mut c_void,
68+
iov_len: buf.len() as size_t,
69+
phantom: PhantomData
70+
}
71+
}
72+
}
73+
74+
#[test]
75+
pub fn test_size_of_io_vec() {
76+
use nixtest;
77+
nixtest::assert_size_of::<IoVec<&[u8]>>("iovec");
78+
}

src/unistd.rs

Lines changed: 4 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
1-
use std::{mem, ptr};
2-
use std::marker::PhantomData;
3-
use libc::{c_char, c_void, c_int, size_t, pid_t, off_t};
1+
use {NixError, NixResult, NixPath, from_ffi};
42
use errno::Errno;
53
use fcntl::{fcntl, Fd, OFlag, O_NONBLOCK, O_CLOEXEC, FD_CLOEXEC};
64
use fcntl::FcntlArg::{F_SETFD, F_SETFL};
7-
use {NixError, NixResult, NixPath, from_ffi};
8-
9-
use core::raw::Slice as RawSlice;
5+
use libc::{c_char, c_void, c_int, size_t, pid_t, off_t};
6+
use std::{mem, ptr};
107
use std::ffi::CString;
118

129
#[cfg(target_os = "linux")]
1310
pub use self::linux::*;
1411

1512
mod ffi {
16-
use super::{IovecR,IovecW};
17-
use libc::{c_char, c_int, size_t, ssize_t};
13+
use libc::{c_char, c_int, size_t};
1814
pub use libc::{close, read, write, pipe, ftruncate, unlink};
1915
pub use libc::funcs::posix88::unistd::fork;
20-
use fcntl::Fd;
2116

2217
extern {
2318
// duplicate a file descriptor
@@ -44,14 +39,6 @@ mod ffi {
4439
// gets the hostname
4540
// doc: http://man7.org/linux/man-pages/man2/gethostname.2.html
4641
pub fn sethostname(name: *const c_char, len: size_t) -> c_int;
47-
48-
// vectorized version of write
49-
// doc: http://man7.org/linux/man-pages/man2/writev.2.html
50-
pub fn writev(fd: Fd, iov: *const IovecW, iovcnt: c_int) -> ssize_t;
51-
52-
// vectorized version of read
53-
// doc: http://man7.org/linux/man-pages/man2/readv.2.html
54-
pub fn readv(fd: Fd, iov: *const IovecR, iovcnt: c_int) -> ssize_t;
5542
}
5643
}
5744

@@ -91,61 +78,6 @@ pub fn fork() -> NixResult<Fork> {
9178
}
9279
}
9380

94-
// We use phantom types to maintain memory safety.
95-
// If readv/writev were using simple &[Iovec] we could initialize
96-
// Iovec with immutable slice and then pass it to readv, overwriting content
97-
// we dont have write access to:
98-
// let mut v = Vec::new();
99-
// let iov = Iovec::from_slice(immutable_vec.as_slice());
100-
// v.push(iov);
101-
// let _:NixResult<usize> = readv(fd, v.as_slice());
102-
103-
// We do not want <T> to appear in ffi functions, so we provide this aliases.
104-
type IovecR = Iovec<ToRead>;
105-
type IovecW = Iovec<ToWrite>;
106-
107-
#[derive(Copy)]
108-
pub struct ToRead;
109-
#[derive(Copy)]
110-
pub struct ToWrite;
111-
112-
#[repr(C)]
113-
pub struct Iovec<T> {
114-
iov_base: *mut c_void,
115-
iov_len: size_t,
116-
phantom: PhantomData<T>
117-
}
118-
119-
impl <T> Iovec<T> {
120-
#[inline]
121-
pub fn as_slice<'a>(&'a self) -> &'a [u8] {
122-
unsafe { mem::transmute(RawSlice { data: self.iov_base as *const u8, len: self.iov_len as usize }) }
123-
}
124-
}
125-
126-
impl Iovec<ToWrite> {
127-
#[inline]
128-
pub fn from_slice(buf: &[u8]) -> Iovec<ToWrite> {
129-
Iovec {
130-
iov_base: buf.as_ptr() as *mut c_void,
131-
iov_len: buf.len() as size_t,
132-
phantom: PhantomData
133-
}
134-
}
135-
}
136-
137-
impl Iovec<ToRead> {
138-
#[inline]
139-
pub fn from_mut_slice(buf: &mut [u8]) -> Iovec<ToRead> {
140-
Iovec {
141-
iov_base: buf.as_ptr() as *mut c_void,
142-
iov_len: buf.len() as size_t,
143-
phantom: PhantomData
144-
}
145-
}
146-
}
147-
148-
14981
#[inline]
15082
pub fn dup(oldfd: Fd) -> NixResult<Fd> {
15183
let res = unsafe { ffi::dup(oldfd) };
@@ -296,24 +228,6 @@ pub fn write(fd: Fd, buf: &[u8]) -> NixResult<usize> {
296228
return Ok(res as usize)
297229
}
298230

299-
pub fn writev(fd: Fd, iov: &[Iovec<ToWrite>]) -> NixResult<usize> {
300-
let res = unsafe { ffi::writev(fd, iov.as_ptr(), iov.len() as c_int) };
301-
if res < 0 {
302-
return Err(NixError::Sys(Errno::last()));
303-
}
304-
305-
return Ok(res as usize)
306-
}
307-
308-
pub fn readv(fd: Fd, iov: &mut [Iovec<ToRead>]) -> NixResult<usize> {
309-
let res = unsafe { ffi::readv(fd, iov.as_ptr(), iov.len() as c_int) };
310-
if res < 0 {
311-
return Err(NixError::Sys(Errno::last()));
312-
}
313-
314-
return Ok(res as usize)
315-
}
316-
317231
pub fn pipe() -> NixResult<(Fd, Fd)> {
318232
unsafe {
319233
let mut res;

test/sys/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod test_termios;
File renamed without changes.

test/sys/test_uio.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use nix::sys::uio::*;
2+
3+
#[test]
4+
fn test_writev() {
5+
let mut to_write = Vec::with_capacity(16 * 128);
6+
for _ in 0..16 {
7+
let s: String = thread_rng().gen_ascii_chars().take(128).collect();
8+
let b = s.as_bytes();
9+
to_write.extend(b.iter().map(|x| x.clone()));
10+
}
11+
// Allocate and fill iovecs
12+
let mut iovecs = Vec::new();
13+
let mut consumed = 0;
14+
while consumed < to_write.len() {
15+
let left = to_write.len() - consumed;
16+
let slice_len = if left < 64 { left } else { thread_rng().gen_range(64, min(256, left)) };
17+
let b = &to_write[consumed..consumed+slice_len];
18+
iovecs.push(IoVec::from_slice(b));
19+
consumed += slice_len;
20+
}
21+
let pipe_res = pipe();
22+
assert!(pipe_res.is_ok());
23+
let (reader, writer) = pipe_res.ok().unwrap();
24+
// FileDesc will close its filedesc (reader).
25+
let mut read_buf: Vec<u8> = repeat(0u8).take(128 * 16).collect();
26+
// Blocking io, should write all data.
27+
let write_res = writev(writer, iovecs.as_slice());
28+
// Successful write
29+
assert!(write_res.is_ok());
30+
let written = write_res.ok().unwrap();
31+
// Check whether we written all data
32+
assert_eq!(to_write.len(), written);
33+
let read_res = read(reader, read_buf.as_mut_slice());
34+
// Successful read
35+
assert!(read_res.is_ok());
36+
let read = read_res.ok().unwrap() as usize;
37+
// Check we have read as much as we written
38+
assert_eq!(read, written);
39+
// Check equality of written and read data
40+
assert_eq!(to_write.as_slice(), read_buf.as_slice());
41+
let close_res = close(writer);
42+
assert!(close_res.is_ok());
43+
let close_res = close(reader);
44+
assert!(close_res.is_ok());
45+
}
46+
47+
#[test]
48+
fn test_readv() {
49+
let s:String = thread_rng().gen_ascii_chars().take(128).collect();
50+
let to_write = s.as_bytes().to_vec();
51+
let mut storage = Vec::new();
52+
let mut allocated = 0;
53+
while allocated < to_write.len() {
54+
let left = to_write.len() - allocated;
55+
let vec_len = if left < 64 { left } else { thread_rng().gen_range(64, min(256, left)) };
56+
let v: Vec<u8> = repeat(0u8).take(vec_len).collect();
57+
storage.push(v);
58+
allocated += vec_len;
59+
}
60+
let mut iovecs = Vec::with_capacity(storage.len());
61+
for v in storage.iter_mut() {
62+
iovecs.push(IoVec::from_mut_slice(v.as_mut_slice()));
63+
}
64+
let pipe_res = pipe();
65+
assert!(pipe_res.is_ok());
66+
let (reader, writer) = pipe_res.ok().unwrap();
67+
// Blocking io, should write all data.
68+
let write_res = write(writer, to_write.as_slice());
69+
// Successful write
70+
assert!(write_res.is_ok());
71+
let read_res = readv(reader, iovecs.as_mut_slice());
72+
assert!(read_res.is_ok());
73+
let read = read_res.ok().unwrap();
74+
// Check whether we've read all data
75+
assert_eq!(to_write.len(), read);
76+
// Cccumulate data from iovecs
77+
let mut read_buf = Vec::with_capacity(to_write.len());
78+
for iovec in iovecs.iter() {
79+
read_buf.extend(iovec.as_slice().iter().map(|x| x.clone()));
80+
}
81+
// Check whether iovecs contain all written data
82+
assert_eq!(read_buf.len(), to_write.len());
83+
// Check equality of written and read data
84+
assert_eq!(read_buf.as_slice(), to_write.as_slice());
85+
let close_res = close(reader);
86+
assert!(close_res.is_ok());
87+
let close_res = close(writer);
88+
assert!(close_res.is_ok());
89+
}

0 commit comments

Comments
 (0)