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

  1. Pass the write end of the pipe pair to Sender::from_owned_fd (and the read end to Receiver::from_owned_fd).
  2. Open the FIFO with write access (and O_NONBLOCK) if you intend to send.
  3. Check the fd's access mode (getfl) before calling and route to Receiver vs Sender accordingly.
  4. 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

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


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