diff --git a/src/interface/pipe.rs b/src/interface/pipe.rs index 5a505c737..2848c1ee4 100644 --- a/src/interface/pipe.rs +++ b/src/interface/pipe.rs @@ -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 diff --git a/src/safeposix/syscalls/fs_calls.rs b/src/safeposix/syscalls/fs_calls.rs index 06dc2d0d5..0ef18b0b7 100644 --- a/src/safeposix/syscalls/fs_calls.rs +++ b/src/safeposix/syscalls/fs_calls.rs @@ -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. + /// 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, @@ -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 => { - // 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) { @@ -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) => { @@ -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) { @@ -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, @@ -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, @@ -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 { nonblocking = true; } - + // Write to the pipe using vectored I/O. let retval = pipe_filedesc_obj .pipe diff --git a/src/tests/fs_tests.rs b/src/tests/fs_tests.rs index 7e87ec0fc..7c7588cf6 100644 --- a/src/tests/fs_tests.rs +++ b/src/tests/fs_tests.rs @@ -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(); + 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 + 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(); + } }