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 all 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
53 changes: 32 additions & 21 deletions src/interface/pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,31 +326,42 @@ impl EmulatedPipe {
iovcnt: i32,
nonblocking: bool,
) -> i32 {
// unlikely but if we attempt to write 0 iovecs, return 0
// Return immediately if there are no iovecs to write.
if iovcnt == 0 {
return 0;
}

let mut buf = Vec::new();
let mut length = 0;

// we're going to loop through the iovec array and combine the buffers into one
// Rust slice so that we can use the write_to_pipe function, recording the
// length this is hacky but is the best way to do this for now
for _iov in 0..iovcnt {
unsafe {
assert!(!ptr.is_null());
// lets convert this iovec into a Rust slice,
// and then extend our combined buffer
let iovec = *ptr;
let iovbuf = slice::from_raw_parts(iovec.iov_base as *const u8, iovec.iov_len);
buf.extend_from_slice(iovbuf);
length = length + iovec.iov_len
};

// Unsafe block to access the iovecs.
let iovecs = unsafe { slice::from_raw_parts(ptr, iovcnt as usize) };
let mut total_bytes_written = 0;

// Iterate through each iovec.
for iovec in iovecs {
let buf = unsafe { slice::from_raw_parts(iovec.iov_base as *const u8, iovec.iov_len) };

// Write the buffer to the pipe.
let current_write = self.write_to_pipe(buf.as_ptr(), buf.len(), nonblocking);

// Handle successful write.
if current_write > 0 {
total_bytes_written += current_write as usize;
} else {
// Nested error handling.
if current_write == -(Errno::EPIPE as i32) {
// Pipe is broken, return immediately.
return -(Errno::EPIPE as i32);
} else if current_write == -(Errno::EAGAIN as i32) {
// Handling EAGAIN depending on whether data was previously written.
if total_bytes_written == 0 {
return -(Errno::EAGAIN as i32); // No data written yet, return EAGAIN.
} else {
return total_bytes_written as i32; // Return amount of data written before EAGAIN occurred.
}
}
}
}

// now that we have a single buffer we can use the usual write to pipe function
self.write_to_pipe(buf.as_ptr(), length, nonblocking)
// Return the total number of bytes written.
total_bytes_written as i32
}

/// ### Description
Expand Down
55 changes: 48 additions & 7 deletions src/safeposix/syscalls/fs_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2459,7 +2459,43 @@ 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.
rupeshkoushik07 marked this conversation as resolved.
Show resolved Hide resolved
/// 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
rupeshkoushik07 marked this conversation as resolved.
Show resolved Hide resolved
/// * 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 @@ -2474,12 +2510,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 => {
rupeshkoushik07 marked this conversation as resolved.
Show resolved Hide resolved
// 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 @@ -2497,6 +2533,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 @@ -2525,8 +2562,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 @@ -2542,6 +2578,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 @@ -2577,6 +2614,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 @@ -2586,10 +2624,13 @@ impl Cage {
}

let mut nonblocking = false;
// Check if the O_NONBLOCK flag is set in the pipe's flags.
// If it is, enable non-blocking mode for the write operation.
//Non-blocking I/O allows a process to continue doing other tasks while waiting for I/O operations to complete.
if pipe_filedesc_obj.flags & O_NONBLOCK != 0 {
rupeshkoushik07 marked this conversation as resolved.
Show resolved Hide resolved
nonblocking = true;
}

// Write to the pipe using vectored I/O.
let retval =
pipe_filedesc_obj
.pipe
Expand Down
75 changes: 75 additions & 0 deletions src/tests/fs_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4611,4 +4611,79 @@ pub mod fs_tests {
lindrustfinalize();
return;
}

#[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();
rupeshkoushik07 marked this conversation as resolved.
Show resolved Hide resolved
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 received from the second socket matches the original 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() {
//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);

// 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
rupeshkoushik07 marked this conversation as resolved.
Show resolved Hide resolved
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();
}
}
Loading