Skip to main content

running_process_platform_internal/platform_linux/
raw_write.rs

1//! Writing a whole buffer to a caller-owned descriptor (POSIX).
2
3use std::io;
4use std::os::fd::RawFd;
5
6use crate::platform::fs::RawDescriptor;
7
8impl From<RawFd> for RawDescriptor {
9    fn from(fd: RawFd) -> Self {
10        RawDescriptor::from_value(fd as usize)
11    }
12}
13
14/// Write every byte of `bytes`, or report why not.
15///
16/// `write(2)` is allowed to write fewer bytes than asked and to fail with
17/// `EINTR` when a signal arrives mid-call. Neither is an error, and neither
18/// is optional to handle: a caller that treats a short write as a whole one
19/// silently truncates, which for a log tee means losing the middle of a line
20/// rather than noticing anything.
21pub fn write_all_to_descriptor(descriptor: RawDescriptor, mut bytes: &[u8]) -> io::Result<()> {
22    let fd = descriptor.value() as RawFd;
23    while !bytes.is_empty() {
24        // SAFETY: `fd` is a descriptor the caller owns and has asked us to
25        // write to; the pointer and length describe the slice above.
26        let written = unsafe { libc::write(fd, bytes.as_ptr().cast(), bytes.len()) };
27        if written < 0 {
28            let error = io::Error::last_os_error();
29            if error.kind() == io::ErrorKind::Interrupted {
30                continue;
31            }
32            return Err(error);
33        }
34        if written == 0 {
35            return Err(io::Error::new(
36                io::ErrorKind::WriteZero,
37                "raw descriptor write returned zero",
38            ));
39        }
40        bytes = &bytes[written as usize..];
41    }
42    Ok(())
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use std::io::Read as _;
49    use std::os::fd::AsRawFd as _;
50
51    /// A whole buffer arrives, including one larger than a single write.
52    #[test]
53    fn every_byte_is_written() {
54        let file = tempfile::NamedTempFile::new().expect("temp file");
55        let payload = vec![b'x'; 300_000];
56
57        write_all_to_descriptor(RawDescriptor::from(file.as_file().as_raw_fd()), &payload)
58            .expect("write");
59
60        let mut readback = Vec::new();
61        std::fs::File::open(file.path())
62            .expect("reopen")
63            .read_to_end(&mut readback)
64            .expect("read");
65        assert_eq!(readback.len(), payload.len());
66        assert!(readback.iter().all(|b| *b == b'x'));
67    }
68
69    /// Writing nothing succeeds without touching the descriptor.
70    #[test]
71    fn an_empty_write_is_not_an_error() {
72        write_all_to_descriptor(RawDescriptor::from(-1), &[]).expect("empty write");
73    }
74
75    /// A descriptor that is not open reports the host's error.
76    #[test]
77    fn a_closed_descriptor_reports_the_host_error() {
78        let error = write_all_to_descriptor(RawDescriptor::from(-1), b"x").expect_err("bad fd");
79        assert_eq!(error.raw_os_error(), Some(libc::EBADF));
80    }
81}