🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
use std::{
io::{Read, Write},
os::unix::net::UnixStream,
};
use tracing::{debug, info, warn};
use rustix::{io::Errno, net::SocketAddrUnix};
/// Find a free X11 display slot and setup
pub(crate) fn prepare_x11_sockets(
display: Option<u32>,
open_abstract_socket: bool,
) -> Result<(X11Lock, Vec<UnixStream>), std::io::Error> {
match display {
Some(d) => {
if let Ok(lock) = X11Lock::grab(d) {
// we got a lockfile, try and create the socket
match open_x11_sockets_for_display(d, open_abstract_socket) {
Ok(sockets) => return Ok((lock, sockets)),
Err(err) => return Err(std::io::Error::from(err)),
};
}
}
None => {
for d in 0..33 {
// if fails, try the next one
if let Ok(lock) = X11Lock::grab(d) {
// we got a lockfile, try and create the socket
match open_x11_sockets_for_display(d, open_abstract_socket) {
Ok(sockets) => return Ok((lock, sockets)),
Err(err) => warn!(display = d, "Failed to create sockets: {}", err),
}
}
}
// If we reach here, all values from 0 to 32 failed
// we need to stop trying at some point
}
}
Err(std::io::Error::new(
std::io::ErrorKind::AddrInUse,
"Could not find a free socket for the XServer.",
))
}
#[derive(Debug)]
pub(crate) struct X11Lock {
display: u32,
}
impl X11Lock {
/// Try to grab a lockfile for given X display number
fn grab(number: u32) -> Result<X11Lock, ()> {
debug!(display = number, "Attempting to aquire an X11 display lock");
let filename = format!("/tmp/.X{}-lock", number);
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}\n",
rustix::process::Pid::as_raw(Some(rustix::process::getpid()))
));
if ret.is_err() {
// write to the file failed ? we abandon
::std::mem::drop(file);
let _ = ::std::fs::remove_file(&filename);
Err(())
} else {
debug!(display = number, "X11 lock acquired");
// we got the lockfile and wrote our pid to it, all is good
Ok(X11Lock { display: number })
}
}
Err(_) => {
debug!(display = number, "Failed to acquire lock");
// 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 = rustix::process::Pid::from_raw(
::std::str::from_utf8(&spid)
.map_err(|_| ())?
.trim()
.parse::<i32>()
.map_err(|_| ())?,
)
.ok_or(())?;
if let Err(Errno::SRCH) = rustix::process::test_kill_process(pid) {
// 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!(
display = number,
"Lock was blocked by a defunct X11 server, trying again"
);
return X11Lock::grab(number);
} else {
// we could not remove the lockfile, abort
return Err(());
}
}
// if we reach here, this lockfile exists and is probably in use, give up
Err(())
}
}
}
pub(crate) fn display_number(&self) -> u32 {
self.display
}
}
impl Drop for X11Lock {
fn drop(&mut self) {
info!("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!(error = ?e, "Failed to remove X11 socket");
}
if let Err(e) = ::std::fs::remove_file(format!("/tmp/.X{}-lock", self.display)) {
warn!(error = ?e, "Failed to remove X11 lockfile");
}
}
}
/// Open the two unix sockets an X server listens on
///
/// Should only be done after the associated lockfile is acquired!
#[cfg(target_os = "linux")]
fn open_x11_sockets_for_display(
display: u32,
open_abstract_socket: bool,
) -> rustix::io::Result<Vec<UnixStream>> {
let path = format!("/tmp/.X11-unix/X{}", display);
let _ = ::std::fs::remove_file(&path);
// We know this path is not too long, these unwrap cannot fail
let fs_addr = SocketAddrUnix::new(path.as_bytes()).unwrap();
let mut sockets = vec![open_socket(fs_addr)?];
if open_abstract_socket {
let abs_addr = SocketAddrUnix::new_abstract_name(path.as_bytes()).unwrap();
sockets.push(open_socket(abs_addr)?);
}
Ok(sockets)
}
/// Open the two unix sockets an X server listens on
///
/// Should only be done after the associated lockfile is acquired!
#[cfg(not(target_os = "linux"))]
fn open_x11_sockets_for_display(
display: u32,
_open_abstract_socket: bool,
) -> rustix::io::Result<Vec<UnixStream>> {
let path = format!("/tmp/.X11-unix/X{}", display);
let _ = ::std::fs::remove_file(&path);
// We know this path is not too long, these unwrap cannot fail
let fs_addr = SocketAddrUnix::new(path.as_bytes()).unwrap();
Ok(vec![open_socket(fs_addr)?])
}
/// Open an unix socket for listening and bind it to given path
fn open_socket(addr: SocketAddrUnix) -> rustix::io::Result<UnixStream> {
// create an unix stream socket
let fd = rustix::net::socket_with(
rustix::net::AddressFamily::UNIX,
rustix::net::SocketType::STREAM,
rustix::net::SocketFlags::CLOEXEC,
None,
)?;
// bind it to requested address
rustix::net::bind(&fd, &addr)?;
rustix::net::listen(&fd, 1)?;
Ok(UnixStream::from(fd))
}