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

  1. Pass the read end of the pipe pair to Receiver::from_owned_fd.
  2. Open the FIFO with read access if you intend to receive.
  3. Check getfl for the access mode and route to Sender vs Receiver accordingly.
  4. 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

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


AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11). Data as JSON: /api/errors/6bfcdbd348e664d8. Report an issue: GitHub.