tokio-rs/tokio · error · io::Error
not in O_WRONLY or O_RDWR access mode
Error message
not in O_WRONLY or O_RDWR access mode
What it means
Sender::from_owned_fd's second guard: after confirming the fd is a pipe, it reads the file flags and checks has_write_access; if neither O_WRONLY nor O_RDWR is set, it errors InvalidInput 'not in O_WRONLY or O_RDWR access mode'. You cannot wrap a read-only pipe end as a Sender.
Source
Thrown at tokio/src/net/unix/pipe.rs:429
/// # Panics
///
/// This function panics if it is not called from within a runtime with
/// IO enabled.
///
/// The runtime is usually set implicitly when this function is called
/// from a future driven by a tokio runtime, otherwise runtime can be set
/// explicitly with [`Runtime::enter`](crate::runtime::Runtime::enter) function.
pub fn from_owned_fd(owned_fd: OwnedFd) -> io::Result<Sender> {
if !is_pipe(owned_fd.as_fd())? {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "not a pipe"));
}
let flags = get_file_flags(owned_fd.as_fd())?;
if has_write_access(flags) {
set_nonblocking(owned_fd.as_fd(), flags)?;
Sender::from_owned_fd_unchecked(owned_fd)
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"not in O_WRONLY or O_RDWR access mode",
))
}
}
/// Creates a new `Sender` from a [`File`] without checking pipe properties.
///
/// This function is intended to construct a pipe from a File representing
/// a special FIFO file. The conversion assumes nothing about the underlying
/// file; it is left up to the user to make sure it is opened with write access,
/// represents a pipe and is set in non-blocking mode.
///
/// # Examples
///
/// ```no_run
/// use tokio::net::unix::pipe;
/// use std::fs::OpenOptions;View on GitHub (pinned to 625954f365)
Solutions
- Pass the write end of the pipe pair to Sender::from_owned_fd (and the read end to Receiver::from_owned_fd).
- Open the FIFO with write access (and O_NONBLOCK) if you intend to send.
- Check the fd's access mode (getfl) before calling and route to Receiver vs Sender accordingly.
- Use os_pipe::pipe() / tokio's own pipe() helpers so the ends are typed correctly from the start.
Example fix
// before let (read, write) = os_pipe::pipe()?; let s = Sender::from_owned_fd(read)?; // wrong end // after let (read, write) = os_pipe::pipe()?; let s = Sender::from_owned_fd(write)?; let r = Receiver::from_owned_fd(read)?;
Defensive patterns
Strategy: validation
Validate before calling
use nix::fcntl::FcntlFlag;
use nix::fcntl::fcntl;
use std::os::unix::io::AsRawFd;
let flags = FcntlFlag::from_bits_truncate(fcntl(fd.as_raw_fd(), Fcntl::F_GETFL)?);
if !flags.intersects(FcntlFlag::O_WRONLY | FcntlFlag::O_RDWR) {
return Err(anyhow::anyhow!("fd not opened for writing"));
}
Sender::from_owned_fd(fd)?; Type guard
fn is_not_writable(e: &io::Error) -> bool {
e.kind() == io::ErrorKind::InvalidInput && e.to_string().contains("O_WRONLY")
} Try / catch
match Sender::from_owned_fd(fd) {
Ok(s) => Ok(s),
Err(e) if e.to_string().contains("access mode") => {
Err(anyhow::anyhow!("fd is read-only; pass the write end of the pipe"))
}
Err(e) => Err(e.into()),
} Prevention
- Pass the write end of pipe() to Sender::from_owned_fd.
- Open FIFOs intended for sending with write access and O_NONBLOCK.
- Check the access mode with F_GETFL before calling.
- Use typed helpers (os_pipe::pipe) so the ends aren't swapped.
When it happens
Trigger: Calling Sender::from_owned_fd with a pipe fd that was opened read-only (O_RDONLY) — typically the read end of a pipe pair, or a FIFO opened without write permission.
Common situations: Mixing up the read and write ends of pipe(); opening a FIFO with O_RDONLY and trying to send; permission bits on the FIFO that exclude write for the opener.
Related errors
- not in O_RDONLY or O_RDWR access mode
- not a pipe
- listen cannot be called on a datagram socket
- connect cannot be called on a datagram socket
- datagram cannot be called on a stream socket
AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11).
Data as JSON: /api/errors/136d9a0e5f8cc59a.
Report an issue: GitHub.