Skip to content

splice and sendfile lose input into a nonblocking pipe #468

Open

Description

@xalestar

splice and sendfile lose input when the output is a pipe the guest set O_NONBLOCK. The emulation reads a whole chunk from the input before it writes it (sys_splice and splice_drain_chunk, copy_fd_range for sendfile in src/syscall/io.c). When the nonblocking write places only part of the chunk, the rest has already been consumed, and io_rewind_unsent cannot hand it back to a pipe or socket input.

Measured with a static aarch64 probe: pipe a holds 8 KiB, pipe b has O_NONBLOCK set by the guest and 4 KiB of free room, then the probe calls splice(a[0], NULL, b[1], NULL, 8192, 0) and, in a second run, sendfile(b[1], a[0], NULL, 8192).

Call Linux (qemu) elfuse main (e9fdbbb)
splice returns 4096, 4 KiB still in a returns 4096, a empty: 4 KiB lost
sendfile -1, EINVAL (a pipe input is refused) returns 4096, a empty: 4 KiB lost

The sendfile row has a second difference: Linux refuses a pipe as the sendfile input, and elfuse accepts it.

#463 fixed the same loss for SPLICE_F_NONBLOCK: a pipe output is polled for POLLOUT before each read and the chunk is capped at PIPE_BUF, since a ready macOS pipe has at least that much free. The O_NONBLOCK case still reaches the old path.

A blocking socket output has a related gap. Linux ignores SPLICE_F_NONBLOCK for a socket output and follows the socket's own O_NONBLOCK, so the call blocks there too. Measured: splice(pipe_with_5_bytes, NULL, full_blocking_af_unix_socket, NULL, 5, SPLICE_F_NONBLOCK) with a 2 s alarm and a handler returns -1 with EINTR on Linux under qemu. elfuse never returns: the chunk has already been read from the pipe and cannot be put back, so splice_drain_chunk keeps retrying the write instead of reporting the signal.

Goal: when the output cannot take a whole chunk without blocking, splice and sendfile read only what the output can take and report what moved, so no input byte is consumed without being written. sendfile refuses a pipe input with EINVAL, as Linux does.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions