Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Writev syscall comments and tests #291

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 44 additions & 7 deletions src/safeposix/syscalls/fs_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1840,7 +1840,42 @@ impl Cage {
}

//------------------------------------WRITEV SYSCALL------------------------------------

/// ## `writev_syscall`
///
/// ### Description
/// This function writes data to a file descriptor from multiple buffers.
/// Currently, it supports writing to sockets and pipes.
/// The function first retrieves the file descriptor object associated with the provided file descriptor
/// and then matches the file descriptor type and calls the appropriate write function based on the type.
/// * `Sockets`: The function writes data to the connected socket, handling both TCP and UDP sockets.
/// * Checks if the socket is connected (either fully connected or connected for write-only).
/// * If connected, calls the underlying `writev` function on the raw socket.
/// * Handles errors returned by the underlying `writev` function.
/// * `Pipes`: The function writes data to the pipe, supporting non-blocking writes.
/// * Checks if the pipe is open for writing.
/// * Handles non-blocking writes.
/// * Triggers `SIGPIPE` if the pipe is closed on the other end.
///
/// ### Function Arguments
/// * `fd`: The file descriptor to write to.
/// * `iovec`: A pointer to an array of `IovecStruct` objects representing the data buffers to write.
/// The IovecStruct structure enables efficient writing of data from multiple buffers to a file
/// descriptor in a single operation using the writev system call, potentially avoiding unnecessary data copying.
/// * `iovcnt`: The number of `IovecStruct` objects in the array.
///
/// ### Returns
/// * The number of bytes written on success.
/// * A negative value on error, with the specific error code set in `errno`.
///
/// ### Errors
/// * `EBADF(9)`: The file descriptor is invalid.
/// * `ENOTCONN(107)`: The socket is not connected (for sockets).
/// * `EOPNOTSUPP(95)`: The system call is not supported for the given socket protocol.
/// * `EAGAIN(11)`: There is no data available right now (for pipes).
/// * `EPIPE(32)`: The pipe has been closed on the other end (for pipes).
/// ### Panics
/// * If an unknown error code is returned by the underlying socket writev function.
/// [writev(2)](https://linux.die.net/man/2/writev)
pub fn writev_syscall(
&self,
fd: i32,
Expand All @@ -1855,12 +1890,12 @@ impl Cage {
Socket(socket_filedesc_obj) => {
let sock_tmp = socket_filedesc_obj.handle.clone();
let sockhandle = sock_tmp.write();

// Check the domain of the socket (IPv4 or IPv6)
match sockhandle.domain {
AF_INET | AF_INET6 => match sockhandle.protocol {
// Handle TCP protocol.
IPPROTO_TCP => {
// to be able to send here we either need to be fully connected, or
// connected for write only
// We need to be either fully connected or connected for write-only.
if (sockhandle.state != ConnState::CONNECTED)
&& (sockhandle.state != ConnState::CONNWRONLY)
{
Expand All @@ -1878,6 +1913,7 @@ impl Cage {
.as_ref()
.unwrap()
.writev(iovec, iovcnt);
// Handle errors
if retval < 0 {
match Errno::from_discriminant(interface::get_errno()) {
Ok(i) => {
Expand Down Expand Up @@ -1906,8 +1942,7 @@ impl Cage {
AF_UNIX => {
match sockhandle.protocol {
IPPROTO_TCP => {
// to be able to send here we either need to be fully connected,
// or connected for write only
// We need to be either fully connected or connected for write-only.
if (sockhandle.state != ConnState::CONNECTED)
&& (sockhandle.state != ConnState::CONNWRONLY)
{
Expand All @@ -1923,6 +1958,7 @@ impl Cage {
if socket_filedesc_obj.flags & O_NONBLOCK != 0 {
nonblocking = true;
}
// Write data to the socket pipe.
let retval = match sockinfo.sendpipe.as_ref() {
Some(sendpipe) => sendpipe.write_vectored_to_pipe(
iovec,
Expand Down Expand Up @@ -1958,6 +1994,7 @@ impl Cage {
}
}
Pipe(pipe_filedesc_obj) => {
// Check if the pipe is open for writing.
if is_rdonly(pipe_filedesc_obj.flags) {
return syscall_error(
Errno::EBADF,
Expand All @@ -1970,7 +2007,7 @@ impl Cage {
if pipe_filedesc_obj.flags & O_NONBLOCK != 0 {
nonblocking = true;
}

// Write to the pipe using vectored I/O.
let retval =
pipe_filedesc_obj
.pipe
Expand Down
74 changes: 74 additions & 0 deletions src/tests/fs_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2458,6 +2458,80 @@ pub mod fs_tests {
// Validate the size of the file to be 0 now as should be truncated
assert_eq!(statdata.st_size, 0);

assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
lindrustfinalize();
}
use crate::tests::FileDescriptor::Socket;
use std::thread;
#[test]
fn ut_lind_fs_writev_socketpair() {
//acquiring a lock on TESTMUTEX prevents other tests from running concurrently,
// and also performs clean env setup
let _thelock = setup::lock_and_init();
let cage = interface::cagetable_getref(1);

let mut socketpair = interface::SockPair::default();
assert_eq!(
Cage::socketpair_syscall(cage.clone(), AF_UNIX, SOCK_STREAM, 0, &mut socketpair),
0
);

// Prepare data to write.
let data = b"Hello, world!";
let iovec = interface::IovecStruct {
iov_base: data.as_ptr() as *mut libc::c_void,
iov_len: data.len(),
};
// Write the data to the first socket using writev_syscall.
let bytes_written = cage.writev_syscall(socketpair.sock1, &iovec, 1);
assert_eq!(bytes_written, data.len() as i32);
// Read the data from the second socket.
let mut buffer = vec![0u8; data.len()];
let bytes_read = cage.recv_syscall(socketpair.sock2, buffer.as_mut_ptr(), buffer.len(), 0);
assert_eq!(bytes_read, data.len() as i32);
// Verify that the data
assert_eq!(buffer, data);
// Close both sockets.
assert_eq!(cage.close_syscall(socketpair.sock1), 0);
assert_eq!(cage.close_syscall(socketpair.sock2), 0);

assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
lindrustfinalize();
}

#[test]
fn ut_lind_fs_writev_pipe() {
let _thelock = setup::lock_and_init();
let cage = interface::cagetable_getref(1);

// Create a pipe
let mut pipe_fds = PipeArray::default();
assert_eq!(cage.pipe_syscall(&mut pipe_fds), 0);
let read_fd = pipe_fds.readfd;
let write_fd = pipe_fds.writefd;

// Prepare the data to be written using an iovec structure
let data = b"Hello, pipe!";
let iovec = interface::IovecStruct {
iov_base: data.as_ptr() as *mut libc::c_void,
iov_len: data.len(),
};

// Write the data to the pipe using writev_syscall
let bytes_written = cage.writev_syscall(write_fd, &iovec, 1);
assert_eq!(bytes_written, data.len() as i32);

// Read the data from the pipe
let mut buffer = vec![0u8; data.len()];
let bytes_read = cage.read_syscall(read_fd, buffer.as_mut_ptr(), buffer.len());
assert_eq!(bytes_read, data.len() as i32);

// Verify that the data read is the same as the data written
assert_eq!(buffer, data);

assert_eq!(cage.close_syscall(read_fd), 0);
assert_eq!(cage.close_syscall(write_fd), 0);

assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS);
lindrustfinalize();
}
Expand Down