fix: rewrite write_all for O_NONBLOCK pipe support

`wl_clip_persist` hands satellite a `WritePipe` with the `O_NONBLOCK`
flag, which prevented more than 64 KiB (the Linux pipe capacity since
2.6.11) of selection data to be transferred. Since my research concluded
having `O_NONBLOCK` on Wayland's pipe transfer ends was probably valid,
I implemented basic handling for such a scenario.

I previously considered just using `fcntl` to remove the `O_NONBLOCK`
flag, but decided that was way too hacky, as it would not resolve the
underlying problem (which could easily be triggered by the receiving
program resetting `O_NONBLOCK` during the `write_all`.
This commit is contained in:
En-En 2025-10-24 14:23:50 +00:00 committed by Supreeeme
parent 59dc560182
commit 0162ac31ff

View file

@ -2,10 +2,12 @@ use super::{get_atom_name, XState};
use crate::server::selection::{Clipboard, ForeignSelection, Primary, SelectionType};
use crate::{RealServerState, X11Selection};
use log::{debug, error, warn};
use rustix::event::{fd_set_insert, select};
use rustix::fd::AsRawFd;
use smithay_client_toolkit::data_device_manager::WritePipe;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::io::Write;
use std::io::{Error, ErrorKind, Result, Write};
use std::rc::Rc;
use xcb::x;
@ -164,8 +166,32 @@ impl Selection {
}
};
// Since the WritePipe given to us can have the O_NONBLOCK flag, we must respect that and
// use `select` to monitor when the pipe is available to do more I/O.
fn write_all(pipe: &mut WritePipe, mut buf: &[u8]) -> Result<()> {
while !buf.is_empty() {
match pipe.write(buf) {
Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
Ok(n) => buf = &buf[n..],
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
let mut writefds = [Default::default()];
fd_set_insert(&mut writefds, pipe.as_raw_fd());
// SAFETY: nfds is correctly set to the numerically largest raw file
// desciprtor being selected on plus 1, and all one fd is open for the
// duration of the call.
unsafe {
select(pipe.as_raw_fd() + 1, None, Some(&mut writefds), None, None)?;
}
}
Err(e) => return Err(e),
}
}
Ok(())
}
if !psd.incr || !data.is_empty() {
if let Err(e) = psd.pipe.write_all(data) {
if let Err(e) = write_all(&mut psd.pipe, data) {
warn!("Failed to write selection data: {e:?}");
} else if psd.incr {
debug!(