Merge pull request #89 from Smithay/xwayland

xwayland: core infrastructure
This commit is contained in:
Victor Berger 2018-05-04 12:06:38 +02:00 committed by GitHub
commit 582e8de316
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 535 additions and 6 deletions

View File

@ -42,6 +42,7 @@ env:
- FEATURES="backend_session_udev"
- FEATURES="backend_session_logind"
- FEATURES="renderer_glium"
- FEATURES="xwayland"
# test default features
- FEATURES="default"
# test all features simultaneously

View File

@ -7,15 +7,15 @@ description = "Smithay is a library for writing wayland compositors."
repository = "https://github.com/Smithay/smithay"
[dependencies]
wayland-server = "0.20.1"
wayland-sys = "0.20.1"
wayland-server = "0.20.2"
wayland-sys = "0.20.2"
nix = "0.10.0"
xkbcommon = "0.2.1"
tempfile = "2.1.5"
slog = { version = "2.1.1" }
slog-stdlog = "3.0.2"
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 }
drm = { version = "^0.3.1", optional = true }
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 }
dbus = { version = "0.6.1", 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"
error-chain = "0.11.0"
lazy_static = "1.0.0"
@ -38,7 +38,7 @@ slog-async = "2.2"
rand = "0.3"
[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_drm = ["drm", "gbm"]
backend_libinput = ["input"]
@ -47,4 +47,4 @@ backend_session_udev = ["udev", "backend_session"]
backend_session_logind = ["dbus", "systemd", "backend_session"]
backend_udev = ["udev", "backend_drm", "backend_session_udev"]
renderer_glium = ["glium"]
xwayland = []

View File

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

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

@ -0,0 +1,137 @@
use std::io::{Read, Write};
use std::os::unix::io::FromRawFd;
use std::os::unix::net::UnixStream;
use nix::{Error as NixError, Result as NixResult};
use nix::errno::Errno;
use nix::sys::socket;
/// Find a free X11 display slot and setup
pub(crate) fn prepare_x11_sockets(log: ::slog::Logger) -> Result<(X11Lock, [UnixStream; 2]), ()> {
for d in 0..33 {
// if fails, try the next one
if let Ok(lock) = X11Lock::grab(d, log.clone()) {
// we got a lockfile, try and create the socket
if let Ok(sockets) = open_x11_sockets_for_display(d) {
return Ok((lock, sockets));
}
}
}
// If we reach here, all values from 0 to 32 failed
// we need to stop trying at some point
return Err(());
}
pub(crate) struct X11Lock {
display: u32,
log: ::slog::Logger,
}
impl X11Lock {
/// Try to grab a lockfile for given X display number
fn grab(display: u32, log: ::slog::Logger) -> Result<X11Lock, ()> {
debug!(log, "Attempting to aquire an X11 display lock"; "D" => display);
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 {
debug!(log, "X11 lock aquired"; "D" => display);
// we got the lockfile and wrote our pid to it, all is good
return Ok(X11Lock { display, log });
}
}
Err(_) => {
debug!(log, "Failed to acquire lock"; "D" => display);
// 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
if let Ok(()) = ::std::fs::remove_file(filename) {
debug!(log, "Lock was blocked by a defunct X11 server, trying again"; "D" => display);
return X11Lock::grab(display, log);
} else {
// we could not remove the lockfile, abort
return Err(());
}
}
// if we reach here, this lockfile exists and is probably in use, give up
return Err(());
}
}
}
pub(crate) fn display(&self) -> u32 {
self.display
}
}
impl Drop for X11Lock {
fn drop(&mut self) {
info!(self.log, "Cleaning up X11 lock.");
// Cleanup all the X11 files
if let Err(e) = ::std::fs::remove_file(format!("/tmp/.X11-unix/X{}", self.display)) {
warn!(self.log, "Failed to remove X11 socket"; "error" => format!("{:?}", e));
}
if let Err(e) = ::std::fs::remove_file(format!("/tmp/.X{}-lock", self.display)) {
warn!(self.log, "Failed to remove X11 lockfile"; "error" => format!("{:?}", e));
}
}
}
/// 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<[UnixStream; 2]> {
let path = format!("/tmp/.X11-unix/X{}", display);
// We know this path is not to long, these unwrap cannot fail
let fs_addr = socket::UnixAddr::new(path.as_bytes()).unwrap();
let abs_addr = socket::UnixAddr::new_abstract(path.as_bytes()).unwrap();
let fs_socket = open_socket(fs_addr)?;
let abstract_socket = open_socket(abs_addr)?;
Ok([fs_socket, abstract_socket])
}
/// Open an unix socket for listening and bind it to given path
fn open_socket(addr: socket::UnixAddr) -> NixResult<UnixStream> {
// 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(unsafe { FromRawFd::from_raw_fd(fd) })
}

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

@ -0,0 +1,384 @@
/*
* 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::{AsRawFd, IntoRawFd};
use std::os::unix::net::UnixStream;
use nix::{Error as NixError, Result as NixResult};
use nix::errno::Errno;
use nix::unistd::{fork, ForkResult, Pid};
use nix::sys::signal;
use wayland_server::{Client, Display, LoopToken};
use wayland_server::sources::{SignalEvent, Source};
use super::x11_sockets::{X11Lock, 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 `UnixStream`
fn xwayland_ready(&mut self, connection: UnixStream, 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<L>(
wm: WM,
token: LoopToken,
display: Rc<RefCell<Display>>,
logger: L,
) -> Result<XWayland<WM>, ()>
where
L: Into<Option<::slog::Logger>>,
{
let log = ::slog_or_stdlog(logger);
let inner = Rc::new(RefCell::new(Inner {
wm,
token,
wayland_display: display,
instance: None,
log: log.new(o!("smithay_module" => "XWayland")),
}));
launch(&inner)?;
Ok(XWayland { inner })
}
}
impl<WM: XWindowManager> Drop for XWayland<WM> {
fn drop(&mut self) {
self.inner.borrow_mut().shutdown();
}
}
struct XWaylandInstance {
display_lock: X11Lock,
wayland_client: Client,
sigusr1_handler: Option<Source<SignalEvent>>,
wm_fd: Option<UnixStream>,
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>,
log: ::slog::Logger,
}
// 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(());
}
info!(guard.log, "Starting XWayland");
let (x_wm_x11, x_wm_me) = UnixStream::pair().map_err(|_| ())?;
let (wl_x11, wl_me) = UnixStream::pair().map_err(|_| ())?;
let (lock, x_fds) = prepare_x11_sockets(guard.log.clone())?;
// 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.into_raw_fd())
};
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 = guard
.token
.add_signal_event_source(signal::Signal::SIGUSR1, move |_, ()| {
xwayland_ready(&my_inner)
})
.map_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(lock.display(), wl_x11, x_wm_x11, &x_fds) {
Ok(x) => match x {},
Err(e) => {
// well, what can we do ?
error!(guard.log, "exec XWayland failed"; "err" => format!("{:?}", e));
unsafe { ::nix::libc::exit(1) };
}
}
}
Err(e) => {
// well, what can we do ?
error!(guard.log, "XWayland second fork failed"; "err" => format!("{:?}", e));
unsafe { ::nix::libc::exit(1) };
}
}
}
Err(e) => {
error!(guard.log, "XWayland first fork failed"; "err" => format!("{:?}", e));
return Err(());
}
};
guard.instance = Some(XWaylandInstance {
display_lock: lock,
wayland_client: client,
sigusr1_handler: Some(sigusr1_handler),
wm_fd: Some(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() {
info!(self.log, "Shutting down XWayland.");
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();
}
// All connexions and lockfiles are cleaned by their destructors
// Remove DISPLAY from the env
::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 {
warn!(inner.borrow().log, "XWayland crashed, restarting.");
let _ = launch(&inner);
} else {
warn!(
inner.borrow().log,
"XWayland crashed less than 5 seconds after its startup, not restarting."
);
}
}
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 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
info!(inner.log, "XWayland is ready, signaling the WM.");
wm.xwayland_ready(
instance.wm_fd.take().unwrap(), // This is a bug if None
instance.wayland_client.clone(),
);
// setup the environemnt
::std::env::set_var("DISPLAY", format!(":{}", instance.display_lock.display()));
} else {
error!(
inner.log,
"XWayland crashed at startup, will not try to restart it."
);
}
// 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: UnixStream,
wm_socket: UnixStream,
listen_sockets: &[UnixStream],
) -> 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.as_raw_fd())).unwrap(),
];
for socket in listen_sockets {
args.push(CString::new("-listen").unwrap());
args.push(CString::new(format!("{}", socket.as_raw_fd())).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.as_raw_fd()));
// 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<F: AsRawFd>(fd: &F) -> NixResult<()> {
use nix::fcntl::{fcntl, FcntlArg, FdFlag};
fcntl(fd.as_raw_fd(), FcntlArg::F_SETFD(FdFlag::empty()))?;
Ok(())
}