[−][src]Module cratetorrent::iovecs
This crate provides a helper type for a slice of IoVec
s for zero-copy
functionality to bound iovecs by a byte count and to advance the buffer
cursor after partial vectored IO.
Bounding input buffers
This is most useful when writing to a portion of a file while ensuring that the file is not extended if the input buffers are larger than the length of the file slice, which in the torrent scenario may occur if the file is not a multiple of the block size.
If the total size of the buffers exceeds the max length, the buffers are split such that the first half returned is the portion of the buffers that can be written to the file, while the second half is the remainder of the buffer, to be used later. If the size of the total size of the buffers is smaller than or equal to the slice, this is essentially a noop.
In reality, the situation here is more complex than just splitting
buffers in half, but this is taken care of by the IoVecs
implementation.
However, the abstraction leaks through because the iovec at which the
buffers were split may be shrunk to such a size as would enable all
buffers to stay within the file slice length. This can be restored using
IoVecs::into_tail
, but until this is called, the original
buffers cannot be used, which is enforced by the borrow checker.
Advancing the write cursor
IO syscalls generally don't guarantee writing or filling the input buffers
in one system call. This is why these APIs always return the number of bytes
transferred, so that calling code can advance the buffer cursor. This
functionality implemented by the IoVecs::advance
method, which takes
offsets the start of the slices by some number of bytes.
Examples
What follows is a complete example making use of both above mentioned API features.
Visualized, this looks like the following:
------------------------------
| file slice: 25 |
-----------------------------------
| block: 16 | block: 16 ^ |
-----------------------------^-----
^
split here
In this example, the first half of the split would be [0, 25), the second half would be [25, 32).
use { std::{ fs::{OpenOptions, File}, os::unix::io::AsRawFd, }, nix::sys::uio::{pwritev, IoVec}, cratetorrent::iovecs::IoVecs, }; let file = OpenOptions::new().write(true).create(true).open("/tmp/example").unwrap(); let file_len = 25; // total length of buffers is 32 let blocks = vec![(0..16).collect::<Vec<u8>>(), (16..32).collect::<Vec<u8>>()]; // the raw data is in vecs but file IO works with iovecs, so we need to make // at least one allocation here to get a contiguouos memory region of iovecs let mut bufs: Vec<_> = blocks.iter().map(|buf| IoVec::from_slice(&buf)).collect(); let mut iovecs = bufs.as_mut_slice(); // bound iovecs by the file length so that we don't write past its end let mut write_bufs = IoVecs::bounded(iovecs, file_len); // get write buffers as an immutable slice of iovecs, which will return the // first 25 bytes, and write the first 25 bytes of buffers assert_eq!(write_bufs.as_slice().iter().map(|iov| iov.as_slice().len()).sum::<usize>(), file_len); let write_count = write_vectored_at(&file, 0, &mut write_bufs); assert_eq!(write_count, file_len); // after this is done, we can use the second half of the buffers iovecs = write_bufs.into_tail(); assert_eq!(iovecs.iter().map(|iov| iov.as_slice().len()).sum::<usize>(), 32 - file_len); // now we can write the rest of the buffers to another file /// Writes the slice of iovecs to the file. fn write_vectored_at<'a>(file: &File, mut offset: i64, iovecs: &mut IoVecs<'a>) -> usize { let mut total_write_count = 0; while !iovecs.as_slice().is_empty() { let write_count = pwritev( file.as_raw_fd(), iovecs.as_slice(), offset, ) .expect("cannot write to file"); iovecs.advance(write_count); total_write_count += write_count; offset += write_count as i64; } total_write_count }
Structs
IoVec | |
IoVecs | Wrapper over a slice of |
Functions
advance | This function is analogous to |