Skip to content

Commit

Permalink
Update write vectored (#323)
Browse files Browse the repository at this point in the history
* update writev

* add format

* update formatting

* update comments

* update description

* update tests and functions

* fixing crate level flags

---------

Co-authored-by: Nicholas Renner <nicholassrenner@gmail.com>
Co-authored-by: Yashaswi Makula <Yashaswi.makula@gmail.com>
  • Loading branch information
3 people authored Sep 3, 2024
1 parent daa5dad commit 6578cf3
Show file tree
Hide file tree
Showing 9 changed files with 509 additions and 60 deletions.
22 changes: 21 additions & 1 deletion src/interface/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use parking_lot::Mutex;
use std::env;
pub use std::ffi::CStr as RustCStr;
use std::fs::{self, canonicalize, File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::io::{IoSlice, Read, Seek, SeekFrom, Write};
pub use std::path::{Component as RustPathComponent, Path as RustPath, PathBuf as RustPathBuf};
use std::slice;
use std::sync::Arc;
Expand Down Expand Up @@ -201,6 +201,26 @@ impl EmulatedFile {
Ok(bytes_written)
}

pub fn write_vectored_at(
&mut self,
bufs: &[IoSlice<'_>],
offset: usize,
) -> std::io::Result<usize> {
let mut total_bytes_written = 0; // To keep track of the total number of bytes written.

if let Some(f) = &self.fobj {
// Use write_vectored_at directly
total_bytes_written = f.lock().write_vectored_at(bufs, offset as u64)?;
}

// Update the recorded file size if we've written past the previous file size
if offset + total_bytes_written > self.filesize {
self.filesize = offset + total_bytes_written;
}

Ok(total_bytes_written)
}

// Reads entire file into bytes
pub fn readfile_to_new_bytes(&self) -> std::io::Result<Vec<u8>> {
match &self.fobj {
Expand Down
62 changes: 62 additions & 0 deletions src/interface/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use std::cell::RefCell;
pub use std::cmp::{max as rust_max, min as rust_min};
pub use std::collections::VecDeque as RustDeque;
use std::fs::File;
pub use std::io::IoSlice as RustIOSlice;
use std::io::{self, Read, Write};
use std::slice;
use std::str::{from_utf8, Utf8Error};
pub use std::sync::atomic::{
AtomicBool as RustAtomicBool, AtomicI32 as RustAtomicI32, AtomicU16 as RustAtomicU16,
Expand Down Expand Up @@ -109,6 +111,66 @@ pub fn cagetable_clear() {
}
}

// It takes a raw pointer (`iovec`) to an array of `IovecStruct` objects, which
// represent data buffers, and the number of `IovecStruct` objects in the array
// (`iovcnt`). It iterates through the array, extracts the data from each
// buffer, and concatenates it into a single `Vec<u8>`.

pub fn concat_iovec_to_slice(iovec: *const interface::IovecStruct, iovcnt: i32) -> Vec<u8> {
// Ensure that the iovec pointer is valid and points to a valid iovec array.
let iovecs = unsafe { slice::from_raw_parts(iovec, iovcnt as usize) };
// Create an empty vector to store the concatenated data.
let mut data = Vec::new();
for iovec in iovecs {
// Create a slice from the current iovec's base pointer and length.
let slice = unsafe { slice::from_raw_parts(iovec.iov_base as *const u8, iovec.iov_len) };
// Extend the data vector with the slice from the current iovec.
data.extend_from_slice(slice);
}
data
}

//This function logs data from a slice to the specified file descriptor.
// It handles logging to standard output


pub fn log_from_slice(_fd: i32, data: &[u8]) -> Result<i32, String> {
match std::str::from_utf8(data) {
Ok(s) => {
// Log everything to stdout, regardless of the file descriptor provided
io::stdout()
.write_all(s.as_bytes())
.map_err(|_| "Failed to write to stdout".to_string())?;

Ok(data.len() as i32)
}
Err(_) => Err("Failed to convert data to string".to_string()),
}
}

// This function converts a raw pointer to an array of `IovecStruct` objects
// into a vector of `IoSlice` objects. It handles the conversion from the
// C-style `iovec` structure to the Rust-style `IoSlice` structure,
// which is used for vectored I/O operations.

pub fn iovec_to_ioslice<'a>(
iovec: *const interface::IovecStruct,
iovcnt: i32,
) -> Vec<RustIOSlice<'a>> {
unsafe {
std::slice::from_raw_parts(iovec, iovcnt as usize)
.iter()
.map(|current_iovec| {
let slice = std::slice::from_raw_parts(
current_iovec.iov_base as *const u8,
current_iovec.iov_len as usize,
);
RustIOSlice::new(slice)
})
.collect()
}
}

pub fn log_from_ptr(buf: *const u8, length: usize) {
if let Ok(s) = from_utf8(unsafe { std::slice::from_raw_parts(buf, length) }) {
log_to_stdout(s);
Expand Down
87 changes: 53 additions & 34 deletions src/interface/pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,32 +290,38 @@ impl EmulatedPipe {

/// ### Description
///
/// write_vectored_to_pipe translates iovecs into a singular buffer so that
/// write_to_pipe can write that data to a circular buffer.
/// `write_vectored_to_pipe` writes data from a set of iovec buffers to a
/// pipe, handling multiple iovec structures in a single operation. This
/// function ensures the complete writing of data from the provided
/// buffers or returns an error code in case of failure.
///
/// ### Arguments
///
/// write_to_pipe accepts three arguments:
/// * `ptr` - a pointer to an Iovec array.
/// * `iovcnt` - the number of iovec indexes in the array
/// * `nonblocking` - if this attempt to write is nonblocking
/// * `ptr` - A pointer to an array of `IovecStruct` which contains the
/// buffers (iovecs) that will be written to the pipe.
/// * `iovcnt` - The number of `IovecStruct` buffers to be written.
/// * `nonblocking` - A boolean flag indicating whether the write operation
/// should be non-blocking.
///
/// ### Returns
///
/// Upon successful completion, the amount of bytes written is returned via
/// write_to_pipe. In case of a failure, an error is returned to the
/// calling syscall.
/// Upon success, the function returns the total number of bytes written. If
/// no bytes could be written due to an empty pipe (EAGAIN), it returns
/// an error code. For other error conditions, such as a broken pipe, an
/// appropriate error code is returned.
///
/// ### Errors
///
/// * `EAGAIN` - Non-blocking is enabled and the write has failed to fully
/// complete (via write_to_pipe).
/// * `EPIPE` - An attempt to write to a pipe when all read references have
/// been closed (via write_to_pipe).
/// * `EPIPE` - Indicates a broken pipe error, where the pipe no longer has
/// an open reading end.
/// * `EAGAIN` - If the pipe is non-blocking and cannot accept data at the
/// moment, this error is returned. If no data was written at all,
/// `EAGAIN` is returned immediately.
///
/// ### Panics
///
/// A panic occurs if the provided pointer is null
/// A panic occurs if the provided `ptr` is null when dereferencing the
/// iovecs.
///
/// To learn more about pipes and the writev syscall
/// [pipe(7)](https://man7.org/linux/man-pages/man7/pipe.7.html)
Expand All @@ -326,31 +332,44 @@ impl EmulatedPipe {
iovcnt: i32,
nonblocking: bool,
) -> i32 {
// unlikely but if we attempt to write 0 iovecs, return 0
// Return 0 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
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![feature(rustc_private)] //for private crate imports for tests
#![feature(vec_into_raw_parts)]
#![feature(thread_local)]
#![feature(unix_file_vectored_at)]
#![allow(unused_imports)]

//! # RustPOSIX
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![feature(rustc_private)] //for private crate imports for tests
#![feature(vec_into_raw_parts)]
#![feature(duration_constants)]
#![feature(unix_file_vectored_at)]
#![allow(unused_imports)]

mod interface;
Expand Down
Loading

0 comments on commit 6578cf3

Please sign in to comment.