xwayland: core infrastructure

This commit is contained in:
Victor Berger 2018-04-30 21:28:17 +02:00
parent 60fc47917d
commit d2cbadc670
5 changed files with 550 additions and 6 deletions

View File

@ -7,15 +7,15 @@ description = "Smithay is a library for writing wayland compositors."
repository = "https://github.com/Smithay/smithay" repository = "https://github.com/Smithay/smithay"
[dependencies] [dependencies]
wayland-server = "0.20.1" wayland-server = "0.20.2"
wayland-sys = "0.20.1" wayland-sys = "0.20.2"
nix = "0.10.0" nix = "0.10.0"
xkbcommon = "0.2.1" xkbcommon = "0.2.1"
tempfile = "2.1.5" tempfile = "2.1.5"
slog = { version = "2.1.1" } slog = { version = "2.1.1" }
slog-stdlog = "3.0.2" slog-stdlog = "3.0.2"
libloading = "0.4.0" libloading = "0.4.0"
wayland-client = { version = "0.20.1", optional = true } wayland-client = { version = "0.20.2", optional = true }
winit = { version = "0.10.0", optional = true } winit = { version = "0.10.0", optional = true }
drm = { version = "^0.3.1", optional = true } drm = { version = "^0.3.1", optional = true }
gbm = { version = "^0.4.0", optional = true, default-features = false, features = ["drm-support"] } gbm = { version = "^0.4.0", optional = true, default-features = false, features = ["drm-support"] }
@ -24,7 +24,7 @@ input = { version = "0.4.0", optional = true }
udev = { version = "0.2.0", optional = true } udev = { version = "0.2.0", optional = true }
dbus = { version = "0.6.1", optional = true } dbus = { version = "0.6.1", optional = true }
systemd = { version = "^0.2.0", optional = true } systemd = { version = "^0.2.0", optional = true }
wayland-protocols = { version = "0.20.1", features = ["unstable_protocols", "server"] } wayland-protocols = { version = "0.20.2", features = ["unstable_protocols", "server"] }
image = "0.17.0" image = "0.17.0"
error-chain = "0.11.0" error-chain = "0.11.0"
lazy_static = "1.0.0" lazy_static = "1.0.0"
@ -38,7 +38,7 @@ slog-async = "2.2"
rand = "0.3" rand = "0.3"
[features] [features]
default = ["backend_winit", "backend_drm", "backend_libinput", "backend_udev", "renderer_glium"] default = ["backend_winit", "backend_drm", "backend_libinput", "backend_udev", "renderer_glium", "xwayland"]
backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen"] backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen"]
backend_drm = ["drm", "gbm"] backend_drm = ["drm", "gbm"]
backend_libinput = ["input"] backend_libinput = ["input"]
@ -47,4 +47,4 @@ backend_session_udev = ["udev", "backend_session"]
backend_session_logind = ["dbus", "systemd", "backend_session"] backend_session_logind = ["dbus", "systemd", "backend_session"]
backend_udev = ["udev", "backend_drm", "backend_session_udev"] backend_udev = ["udev", "backend_drm", "backend_session_udev"]
renderer_glium = ["glium"] renderer_glium = ["glium"]
xwayland = []

View File

@ -52,6 +52,9 @@ pub mod backend;
pub mod wayland; pub mod wayland;
pub mod utils; pub mod utils;
#[cfg(feature = "xwayland")]
pub mod xwayland;
fn slog_or_stdlog<L>(logger: L) -> ::slog::Logger fn slog_or_stdlog<L>(logger: L) -> ::slog::Logger
where where
L: Into<Option<::slog::Logger>>, L: Into<Option<::slog::Logger>>,

4
src/xwayland/mod.rs Normal file
View File

@ -0,0 +1,4 @@
mod xserver;
mod x11_sockets;
pub use self::xserver::{XWayland, XWindowManager};

136
src/xwayland/x11_sockets.rs Normal file
View File

@ -0,0 +1,136 @@
use std::io::{Read, Write};
use std::os::unix::io::RawFd;
use nix::{Error as NixError, Result as NixResult};
use nix::errno::Errno;
use nix::sys::socket;
pub(crate) fn make_pair() -> Result<(RawFd, RawFd), ()> {
socket::socketpair(
socket::AddressFamily::Unix,
socket::SockType::Stream,
None,
socket::SockFlag::SOCK_CLOEXEC,
).map_err(|_| ())
}
/// Find a free X11 display slot and setup
pub(crate) fn prepare_x11_sockets() -> Result<(u32, [RawFd; 2]), ()> {
for d in 0..33 {
// if fails, try the next one
if let Err(()) = grab_lockfile(d) {
continue;
}
// we got a lockfile, try and create the socket
if let Ok(fds) = open_x11_sockets_for_display(d) {
return Ok((d, fds));
}
// creating the sockets failed, for some readon ?
// release the lockfile and try with the next
release_lockfile(d);
}
// If we reach here, all values from 0 to 32 failed
// we need to stop trying at some point
return Err(());
}
/// Remove the X11 sockets for a given display number
pub(crate) fn cleanup_x11_sockets(display: u32) {
let _ = ::std::fs::remove_file(format!("/tmp/.X11-unix/X{}", display));
let _ = ::std::fs::remove_file(format!("/tmp/.X{}-lock", display));
}
/// Try to grab a lockfile for given X display number
fn grab_lockfile(display: u32) -> Result<(), ()> {
let filename = format!("/tmp/.X{}-lock", display);
let lockfile = ::std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&filename);
match lockfile {
Ok(mut file) => {
// we got it, write our PID in it and we're good
let ret = file.write_fmt(format_args!("{:>10}", ::nix::unistd::Pid::this()));
if let Err(_) = ret {
// write to the file failed ? we abandon
::std::mem::drop(file);
let _ = ::std::fs::remove_file(&filename);
return Err(());
} else {
// we got the lockfile and wrote our pid to it, all is good
return Ok(());
}
}
Err(_) => {
// we could not open the file, now we try to read it
// and if it contains the pid of a process that no longer
// exist (so if a previous x server claimed it and did not
// exit gracefully and remove it), we claim it
// if we can't open it, give up
let mut file = ::std::fs::File::open(&filename).map_err(|_| ())?;
let mut spid = [0u8; 11];
file.read_exact(&mut spid).map_err(|_| ())?;
::std::mem::drop(file);
let pid = ::nix::unistd::Pid::from_raw(::std::str::from_utf8(&spid)
.map_err(|_| ())?
.trim()
.parse::<i32>()
.map_err(|_| ())?);
if let Err(NixError::Sys(Errno::ESRCH)) = ::nix::sys::signal::kill(pid, None) {
// no process whose pid equals the contents of the lockfile exists
// remove the lockfile and try grabbing it again
let _ = ::std::fs::remove_file(filename);
return grab_lockfile(display);
}
// if we reach here, this lockfile exists and is probably in use, give up
return Err(());
}
}
}
/// Release an X11 lockfile
fn release_lockfile(display: u32) {
let filename = format!("/tmp/.X{}-lock", display);
let _ = ::std::fs::remove_file(filename);
}
/// Open the two unix sockets an X server listens on
///
/// SHould only be done after the associated lockfile is aquired!
fn open_x11_sockets_for_display(display: u32) -> NixResult<[RawFd; 2]> {
let fs_socket = open_socket(
socket::UnixAddr::new(format!("/tmp/.X11-unix/X{}", display).as_bytes()).unwrap(), // We know this path is not to long, this unwrap cannot fail
)?;
let ret = open_socket(
socket::UnixAddr::new_abstract(format!("/tmp/.X11-unix/X{}", display).as_bytes()).unwrap(), // We know this path is not to long, this unwrap cannot fail
);
match ret {
Ok(abstract_socket) => Ok([fs_socket, abstract_socket]),
Err(e) => {
// close the first socket and return the error
let _ = ::nix::unistd::close(fs_socket);
Err(e)
}
}
}
/// Open an unix socket for listening and bind it to given path
fn open_socket(addr: socket::UnixAddr) -> NixResult<RawFd> {
// create an unix stream socket
let fd = socket::socket(
socket::AddressFamily::Unix,
socket::SockType::Stream,
socket::SockFlag::SOCK_CLOEXEC,
None,
)?;
// bind it to requested address
if let Err(e) = socket::bind(fd, &socket::SockAddr::Unix(addr)) {
let _ = ::nix::unistd::close(fd);
return Err(e);
}
if let Err(e) = socket::listen(fd, 1) {
let _ = ::nix::unistd::close(fd);
return Err(e);
}
Ok(fd)
}

401
src/xwayland/xserver.rs Normal file
View File

@ -0,0 +1,401 @@
/*
* Steps of Xwayland server creation
*
* Sockets to create:
* - a pair for Xwayland to connect to smithay as a wayland client, we use our
* end to insert the Xwayland client in the display
* - a pair for smithay to connect to Xwayland as a WM, we give our end to the
* WM and it deals with it
* - 2 listening sockets on which the Xwayland server will listen. We need to
* bind them ouserlves so we know what value put in the $DISPLAY env variable.
* This involves some dance with a lockfile to ensure there is no collision with
* an other starting xserver
* if we listen on display $D, their paths are respectly:
* - /tmp/.X11-unix/X$D
* - @/tmp/.X11-unix/X$D (abstract socket)
*
* The XWayland server is spawned via fork+exec.
* -> wlroot does a double-fork while weston a single one, why ??
* -> https://stackoverflow.com/questions/881388/
* -> once it is started, it sends us a SIGUSR1, we need to setup a listener
* for it and when we receive it we can launch the WM
* -> we need to track if the Xwayland crashes, to restart it
*
* cf https://github.com/swaywm/wlroots/blob/master/xwayland/xwayland.c
*
*/
use std::cell::RefCell;
use std::rc::Rc;
use std::env;
use std::ffi::CString;
use std::os::unix::io::RawFd;
use nix::{Error as NixError, Result as NixResult};
use nix::errno::Errno;
use nix::unistd::{close, fork, ForkResult, Pid};
use nix::sys::signal;
use wayland_server::{Client, Display, LoopToken};
use wayland_server::sources::{SignalEvent, Source};
use super::x11_sockets::{make_pair, cleanup_x11_sockets, prepare_x11_sockets};
/// The XWayland handle
pub struct XWayland<WM: XWindowManager> {
inner: Rc<RefCell<Inner<WM>>>,
}
/// Trait to be implemented by you WM for Xwayland
///
/// This is a very low-level trait, only notifying you
/// when the connection with XWayland is up, or when
/// it terminates.
///
/// You WM must be able handle the XWayland server connecting
/// then disconnecting several time in a row, but only a single
/// connection will be active at any given time.
pub trait XWindowManager {
/// The XWayland server is ready
///
/// Your previlegied connection to it is this `RawFd`
fn xwayland_ready(&mut self, fd: RawFd, client: Client);
/// The XWayland server has exited
fn xwayland_exited(&mut self);
}
impl<WM: XWindowManager + 'static> XWayland<WM> {
/// Start the XWayland server
pub fn init(wm: WM, token: LoopToken, display: Rc<RefCell<Display>>) -> Result<XWayland<WM>, ()> {
let inner = Rc::new(RefCell::new(Inner {
wm,
token,
wayland_display: display,
instance: None,
}));
launch(&inner)?;
Ok(XWayland { inner })
}
}
impl<WM: XWindowManager> Drop for XWayland<WM> {
fn drop(&mut self) {
self.inner.borrow_mut().shutdown();
}
}
struct XWaylandInstance {
display: u32,
wayland_client: Client,
sigusr1_handler: Option<Source<SignalEvent>>,
wm_fd: RawFd,
started_at: ::std::time::Instant,
child_pid: Option<Pid>,
}
// Inner implementation of the XWayland manager
struct Inner<WM: XWindowManager> {
wm: WM,
token: LoopToken,
wayland_display: Rc<RefCell<Display>>,
instance: Option<XWaylandInstance>,
}
// Launch an XWayland server
//
// Does nothing if there is already a launched instance
fn launch<WM: XWindowManager + 'static>(inner: &Rc<RefCell<Inner<WM>>>) -> Result<(), ()> {
let mut guard = inner.borrow_mut();
if guard.instance.is_some() {
return Ok(());
}
let (display, x_fds) = prepare_x11_sockets()?;
let (x_wm_x11, x_wm_me) = match make_pair() {
Ok(v) => v,
Err(()) => {
// cleanup
for &f in &x_fds {
let _ = close(f);
}
cleanup_x11_sockets(display);
return Err(());
}
};
let (wl_x11, wl_me) = match make_pair() {
Ok(v) => v,
Err(()) => {
// cleanup
for &f in &x_fds {
let _ = close(f);
}
let _ = close(x_wm_x11);
let _ = close(x_wm_me);
cleanup_x11_sockets(display);
return Err(());
}
};
// we have now created all the required sockets
// record launch time
let creation_time = ::std::time::Instant::now();
// create the wayland client for XWayland
let client = unsafe { guard.wayland_display.borrow_mut().create_client(wl_me) };
client.set_user_data(Rc::into_raw(inner.clone()) as *const () as *mut ());
client.set_destructor(client_destroy::<WM>);
// setup the SIGUSR1 handler
let my_inner = inner.clone();
let sigusr1_handler = match guard
.token
.add_signal_event_source(signal::Signal::SIGUSR1, move |_, ()| {
xwayland_ready(&my_inner)
}) {
Ok(v) => v,
Err(_) => {
// If that fails (can it even fail ?), cleanup
for &f in &x_fds {
let _ = close(f);
}
let _ = close(x_wm_x11);
let _ = close(x_wm_me);
let _ = close(wl_x11);
let _ = close(wl_me);
cleanup_x11_sockets(display);
return Err(());
}
};
// all is ready, we can do the fork dance
let child_pid = match fork() {
Ok(ForkResult::Parent { child }) => {
// we are the main smithay process
child
}
Ok(ForkResult::Child) => {
// we are the first child
let ppid = Pid::parent();
let mut set = signal::SigSet::empty();
set.add(signal::Signal::SIGUSR1);
set.add(signal::Signal::SIGCHLD);
// we can't handle errors here anyway
let _ = signal::sigprocmask(signal::SigmaskHow::SIG_BLOCK, Some(&set), None);
match fork() {
Ok(ForkResult::Parent { child }) => {
// we are still the first child
let sig = set.wait();
// send USR1 to parent
let _ = signal::kill(ppid, signal::Signal::SIGUSR1);
// Parent will wait for us and know from out
// exit status if XWayland launch was a success or not =)
if let Ok(signal::Signal::SIGCHLD) = sig {
// Xwayland has exited before being ready
let _ = ::nix::sys::wait::waitpid(child, None);
unsafe { ::nix::libc::exit(1) };
}
unsafe { ::nix::libc::exit(0) };
}
Ok(ForkResult::Child) => {
// we are the second child, we exec xwayland
match exec_xwayland(display, wl_x11, x_wm_x11, &x_fds) {
Ok(x) => match x {},
Err(e) => {
// well, what can we do ?
eprintln!("[smithay] exec XWayland failed: {:?}", e);
unsafe { ::nix::libc::exit(1) };
}
}
}
Err(e) => {
// well, what can we do ?
eprintln!("[smithay] XWayland second fork failed: {:?}", e);
unsafe { ::nix::libc::exit(1) };
}
}
}
Err(e) => {
eprintln!("[smithay] XWayland first fork failed: {:?}", e);
// fork failed ? cleanup
for &f in &x_fds {
let _ = close(f);
}
let _ = close(x_wm_x11);
let _ = close(x_wm_me);
let _ = close(wl_x11);
let _ = close(wl_me);
cleanup_x11_sockets(display);
return Err(());
}
};
// now, close the FDs that were given to XWayland
for &f in &x_fds {
let _ = close(f);
}
let _ = close(x_wm_x11);
let _ = close(wl_x11);
guard.instance = Some(XWaylandInstance {
display,
wayland_client: client,
sigusr1_handler: Some(sigusr1_handler),
wm_fd: x_wm_me,
started_at: creation_time,
child_pid: Some(child_pid),
});
Ok(())
}
impl<WM: XWindowManager> Inner<WM> {
// Shutdown the XWayland server and cleanup everything
fn shutdown(&mut self) {
// don't do anything if not running
if let Some(mut instance) = self.instance.take() {
self.wm.xwayland_exited();
// kill the client
instance.wayland_client.kill();
// remove the event source
if let Some(s) = instance.sigusr1_handler.take() {
s.remove();
}
// close the WM fd
let _ = close(instance.wm_fd);
// cleanup the X11 sockets
cleanup_x11_sockets(instance.display);
::std::env::remove_var("DISPLAY");
// We do like wlroots:
// > We do not kill the Xwayland process, it dies to broken pipe
// > after we close our side of the wm/wl fds. This is more reliable
// > than trying to kill something that might no longer be Xwayland.
}
}
}
fn client_destroy<WM: XWindowManager + 'static>(data: *mut ()) {
let inner = unsafe { Rc::from_raw(data as *const () as *const RefCell<Inner<WM>>) };
// shutdown the server
let started_at = inner.borrow().instance.as_ref().map(|i| i.started_at);
inner.borrow_mut().shutdown();
// restart it, unless we really just started it, if it crashes right
// at startup there is no point
if started_at.map(|t| t.elapsed().as_secs()).unwrap_or(10) > 5 {
let _ = launch(&inner);
}
}
fn xwayland_ready<WM: XWindowManager>(inner: &Rc<RefCell<Inner<WM>>>) {
use nix::sys::wait;
let mut guard = inner.borrow_mut();
let inner = &mut *guard;
// instance should never be None at this point
let instance = inner.instance.as_mut().unwrap();
let wm = &mut inner.wm;
// neither the pid
let pid = instance.child_pid.unwrap();
// find out if the launch was a success by waiting on the intermediate child
let mut success: bool;
loop {
match wait::waitpid(pid, None) {
Ok(wait::WaitStatus::Exited(_, 0)) => {
// XWayland was correctly started :)
success = true;
break;
}
Err(NixError::Sys(Errno::EINTR)) => {
// interupted, retry
continue;
}
_ => {
// something went wrong :(
success = false;
break;
}
}
}
if success {
// signal the WM
wm.xwayland_ready(instance.wm_fd, instance.wayland_client.clone());
// setup the environemnt
::std::env::set_var("DISPLAY", format!(":{}", instance.display));
}
// in all cases, cleanup
if let Some(s) = instance.sigusr1_handler.take() {
s.remove();
}
}
enum Void {}
/// Exec xwayland with given sockets on given display
///
/// If this returns, that means that something failed
fn exec_xwayland(
display: u32,
wayland_socket: RawFd,
wm_socket: RawFd,
listen_sockets: &[RawFd],
) -> NixResult<Void> {
// uset the CLOEXEC flag from the sockets we need to pass
// to xwayland
unset_cloexec(wayland_socket)?;
unset_cloexec(wm_socket)?;
for &socket in listen_sockets {
unset_cloexec(socket)?;
}
// prepare the arguments to Xwayland
let mut args = vec![
CString::new("Xwayland").unwrap(),
CString::new(format!(":{}", display)).unwrap(),
CString::new("-rootless").unwrap(),
CString::new("-terminate").unwrap(),
CString::new("-wm").unwrap(),
CString::new(format!("{}", wm_socket)).unwrap(),
];
for &socket in listen_sockets {
args.push(CString::new("-listen").unwrap());
args.push(CString::new(format!("{}", socket)).unwrap());
}
// setup the environment: clear everything except PATH and XDG_RUNTIME_DIR
for (key, _) in env::vars_os() {
if key.to_str() == Some("PATH") || key.to_str() == Some("XDG_RUNTIME_DIR") {
continue;
}
env::remove_var(key);
}
// the WAYLAND_SOCKET var tells Xwayland where to connect as a wayland client
env::set_var("WAYLAND_SOCKET", format!("{}", wayland_socket));
// ignore SIGUSR1, this will make the Xwayland server send us this
// signal when it is ready apparently
unsafe {
use nix::sys::signal::*;
sigaction(
Signal::SIGUSR1,
&SigAction::new(SigHandler::SigIgn, SaFlags::empty(), SigSet::empty()),
)?;
}
// run it
let ret = ::nix::unistd::execvp(&CString::new("Xwayland").unwrap(), &args)?;
// small dance to actually return Void
match ret {}
}
/// Remove the O_CLOEXEC flag from this Fd
///
/// This means that the Fd will *not* be automatically
/// closed when we exec() into Xwayland
fn unset_cloexec(fd: RawFd) -> NixResult<()> {
use nix::fcntl::{fcntl, FcntlArg, FdFlag};
fcntl(fd, FcntlArg::F_SETFD(FdFlag::empty()))?;
Ok(())
}