tokio-rs/tokio · error · io::Error
not in O_RDONLY or O_RDWR access mode
Error message
not in O_RDONLY or O_RDWR access mode
What it means
Receiver::from_owned_fd's second guard: after confirming the fd is a pipe, it checks has_read_access on the file flags; without O_RDONLY or O_RDWR it errors InvalidInput 'not in O_RDONLY or O_RDWR access mode'. You cannot wrap a write-only pipe end as a Receiver.
Source
Thrown at tokio/src/net/unix/pipe.rs:977
/// # 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<Receiver> {
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_read_access(flags) {
set_nonblocking(owned_fd.as_fd(), flags)?;
Receiver::from_owned_fd_unchecked(owned_fd)
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"not in O_RDONLY or O_RDWR access mode",
))
}
}
/// Creates a new `Receiver` 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 read 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 read end of the pipe pair to Receiver::from_owned_fd.
- Open the FIFO with read access if you intend to receive.
- Check getfl for the access mode and route to Sender vs Receiver accordingly.
- Use os_pipe::pipe() or tokio's pipe() so the ends are correctly typed from creation.
Example fix
// before let (read, write) = os_pipe::pipe()?; let r = Receiver::from_owned_fd(write)?; // wrong end // after let (read, write) = os_pipe::pipe()?; let r = Receiver::from_owned_fd(read)?; let s = Sender::from_owned_fd(write)?;
Defensive patterns
Strategy: validation
Validate before calling
use nix::fcntl::{fcntl, Fcntl, FcntlFlag};
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_RDONLY | FcntlFlag::O_RDWR) {
return Err(anyhow::anyhow!("fd not opened for reading"));
}
Receiver::from_owned_fd(fd)?; Type guard
fn is_not_readable(e: &io::Error) -> bool {
e.kind() == io::ErrorKind::InvalidInput && e.to_string().contains("O_RDONLY")
} Try / catch
match Receiver::from_owned_fd(fd) {
Ok(r) => Ok(r),
Err(e) if e.to_string().contains("access mode") => {
Err(anyhow::anyhow!("fd is write-only; pass the read end of the pipe"))
}
Err(e) => Err(e.into()),
} Prevention
- Pass the read end of pipe() to Receiver::from_owned_fd.
- Open FIFOs intended for receiving with read access and O_NONBLOCK.
- Check F_GETFL for the access mode before wrapping.
- Use os_pipe::pipe or tokio's pipe() so ends are typed from creation.
When it happens
Trigger: Calling Receiver::from_owned_fd with a pipe fd opened write-only (O_WRONLY) — typically the write end of a pipe pair.
Common situations: Passing the wrong end of pipe() to Receiver; opening a FIFO with O_WRONLY and trying to receive; confusing the directionality of a pipe pair.
Related errors
- not in O_WRONLY 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/6bfcdbd348e664d8.
Report an issue: GitHub.