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:
parent
59dc560182
commit
0162ac31ff
1 changed files with 28 additions and 2 deletions
|
|
@ -2,10 +2,12 @@ use super::{get_atom_name, XState};
|
||||||
use crate::server::selection::{Clipboard, ForeignSelection, Primary, SelectionType};
|
use crate::server::selection::{Clipboard, ForeignSelection, Primary, SelectionType};
|
||||||
use crate::{RealServerState, X11Selection};
|
use crate::{RealServerState, X11Selection};
|
||||||
use log::{debug, error, warn};
|
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 smithay_client_toolkit::data_device_manager::WritePipe;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::io::Write;
|
use std::io::{Error, ErrorKind, Result, Write};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use xcb::x;
|
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 !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:?}");
|
warn!("Failed to write selection data: {e:?}");
|
||||||
} else if psd.incr {
|
} else if psd.incr {
|
||||||
debug!(
|
debug!(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue