Skip to main content

wireshift_fallback/ops/
read.rs

1use std::collections::HashMap;
2use std::io::Read;
3use std::os::unix::fs::FileExt;
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::sync::Arc;
6
7use wireshift_core::buffer::{Buffer, Submitted};
8use wireshift_core::op::CompletionPayload;
9use wireshift_core::{Error, Result};
10
11use crate::ops::retry_eintr;
12
13/// Execute a single read at a specific file offset into a submitted buffer.
14pub fn execute_read(
15    mut file: std::fs::File,
16    offset: u64,
17    mut buffer: Buffer<Submitted>,
18    canceled: Arc<AtomicBool>,
19    len: Option<usize>,
20) -> Result<CompletionPayload> {
21    if canceled.load(Ordering::Relaxed) {
22        return Err(Error::canceled(
23            "read canceled before data transfer started",
24            "avoid canceling the request before the backend starts transferring data",
25        ));
26    }
27    let buf = buffer.backend_mut();
28    let read_len = len.unwrap_or(buf.len());
29    let target_buf = if read_len < buf.len() {
30        &mut buf[..read_len]
31    } else {
32        buf
33    };
34    apply_madv_sequential(target_buf)?;
35
36    let bytes = retry_eintr!(file.read_at(target_buf, offset))
37        .or_else(|error| {
38            let is_espipe = error.raw_os_error() == Some(rustix::io::Errno::SPIPE.raw_os_error());
39            if is_espipe && offset == 0 {
40                retry_eintr!(file.read(target_buf))
41            } else {
42                Err(error)
43            }
44        })
45        .map_err(|error| {
46            Error::io(
47                "read failed",
48                error,
49                "ensure the file descriptor is readable",
50            )
51        })?;
52    let completed = buffer.into_completed(bytes)?;
53    Ok(CompletionPayload::Read {
54        buffer: completed,
55        bytes,
56    })
57}
58
59/// Execute a vectored (scatter) read across multiple buffers.
60pub fn execute_read_vectored(
61    mut file: std::fs::File,
62    offset: u64,
63    buffers: Vec<Buffer<Submitted>>,
64) -> Result<CompletionPayload> {
65    let mut total = 0_usize;
66    let mut current_offset = offset;
67    let mut completed_buffers = Vec::with_capacity(buffers.len());
68    for mut buffer in buffers {
69        let (bytes, is_short) = {
70            let chunk = buffer.backend_mut();
71            apply_madv_sequential(chunk)?;
72            let bytes = retry_eintr!(file.read_at(chunk, current_offset))
73                .or_else(|error| {
74                    let is_espipe =
75                        error.raw_os_error() == Some(rustix::io::Errno::SPIPE.raw_os_error());
76                    if is_espipe && offset == 0 {
77                        retry_eintr!(file.read(chunk))
78                    } else {
79                        Err(error)
80                    }
81                })
82                .map_err(|error| {
83                    Error::io(
84                        "readv segment read failed",
85                        error,
86                        "ensure the file descriptor remains readable for all readv segments",
87                    )
88                })?;
89            (bytes, bytes < chunk.len())
90        };
91        total += bytes;
92        current_offset += bytes as u64;
93        completed_buffers.push(buffer.into_completed(bytes)?);
94        if is_short {
95            break;
96        }
97    }
98    Ok(CompletionPayload::ReadVectored {
99        buffers: completed_buffers,
100        bytes: total,
101    })
102}
103
104/// Execute a read into a page-aligned GPU staging buffer.
105pub fn execute_read_gpu(
106    file: std::fs::File,
107    offset: u64,
108    mut buffer: wireshift_core::ops::AlignedBuffer,
109    canceled: Arc<AtomicBool>,
110) -> Result<CompletionPayload> {
111    if canceled.load(Ordering::Relaxed) {
112        return Err(Error::canceled(
113            "gpu staged read canceled before data transfer started",
114            "avoid canceling the request before the backend starts transferring data",
115        ));
116    }
117    let slice = buffer.as_mut_slice();
118    apply_madv_sequential(slice)?;
119    let bytes = retry_eintr!(file.read_at(slice, offset)).map_err(|error| {
120        Error::io(
121            "gpu staged read failed",
122            error,
123            "ensure the file descriptor is readable and supports positional reads",
124        )
125    })?;
126    Ok(CompletionPayload::GpuRead { buffer, bytes })
127}
128
129/// Execute a read using a pre-registered (fixed) file descriptor slot.
130pub fn execute_read_fixed(
131    slot: u32,
132    offset: u64,
133    mut buffer: Buffer<Submitted>,
134    canceled: Arc<AtomicBool>,
135    fixed_files: &mut HashMap<u32, std::fs::File>,
136) -> Result<CompletionPayload> {
137    if canceled.load(Ordering::Relaxed) {
138        return Err(Error::canceled(
139            "read canceled before data transfer started",
140            "avoid canceling the request before the backend starts transferring data",
141        ));
142    }
143    let file = fixed_files.get(&slot).ok_or_else(|| {
144        Error::completion(
145            format!("direct descriptor slot {slot} was read before open"),
146            "ensure linked reads open the direct descriptor before issuing a fixed read",
147        )
148    })?;
149    let slice = buffer.backend_mut();
150    apply_madv_sequential(slice)?;
151    let bytes = retry_eintr!(file.read_at(slice, offset)).map_err(|error| {
152        Error::io(
153            "read failed",
154            error,
155            "ensure the file descriptor is readable",
156        )
157    })?;
158    let completed = buffer.into_completed(bytes)?;
159    Ok(CompletionPayload::Read {
160        buffer: completed,
161        bytes,
162    })
163}
164
165/// Apply `MADV_SEQUENTIAL` to `buffer`, hinting the kernel to read ahead.
166///
167/// A no-op on an empty buffer.
168pub fn apply_madv_sequential(buffer: &mut [u8]) -> Result<()> {
169    if buffer.is_empty() {
170        return Ok(());
171    }
172
173    let page_size = rustix::param::page_size();
174
175    let start = buffer.as_mut_ptr() as usize;
176    let end = start.checked_add(buffer.len()).ok_or_else(|| {
177        Error::validation(
178            "buffer address overflowed while preparing MADV_SEQUENTIAL",
179            "reduce the read buffer length so address calculations stay in range",
180        )
181    })?;
182    let aligned_start = start / page_size * page_size;
183    let aligned_end = end
184        .checked_add(page_size - 1)
185        .map(|value| value / page_size * page_size)
186        .ok_or_else(|| {
187            Error::validation(
188                "buffer range overflowed while page-aligning MADV_SEQUENTIAL",
189                "reduce the read buffer length so page alignment stays in range",
190            )
191        })?;
192    let aligned_len = aligned_end.checked_sub(aligned_start).ok_or_else(|| {
193        Error::validation(
194            "aligned MADV_SEQUENTIAL range underflowed",
195            "reduce the read buffer length so alignment calculations stay valid",
196        )
197    })?;
198
199    #[cfg(target_os = "linux")]
200    {
201        use rustix::mm::{madvise, Advice};
202        // SAFETY: madvise requires a valid pointer and length; aligned_start and
203        // aligned_len were computed from the buffer pointer and length and
204        // validated for overflow, so the pointer/length pair is safe to pass.
205        let ptr = aligned_start as *mut std::ffi::c_void;
206        unsafe {
207            madvise(ptr, aligned_len, Advice::Sequential).map_err(|error| {
208                Error::io(
209                    "madvise(MADV_SEQUENTIAL) failed",
210                    std::io::Error::from(error),
211                    "ensure the runtime allows calling madvise on the target memory range",
212                )
213            })?;
214        }
215    }
216
217    // On non-linux platforms madvise is a no-op for this backend.
218    Ok(())
219}
220
221
222#[cfg(test)]
223mod tests {
224    use super::apply_madv_sequential;
225
226    #[test]
227    fn verify_apply_madv_sequential() {
228        let mut buf = vec![0u8; 16 * 1024];
229        assert!(apply_madv_sequential(&mut buf).is_ok());
230    }
231}