wl_proxy/
acceptor.rs

1//! Wayland connection acceptor.
2
3use {
4    crate::utils::env::{WAYLAND_DISPLAY, XDG_RUNTIME_DIR},
5    error_reporter::Report,
6    std::{
7        env::{set_var, var},
8        io,
9        os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd},
10        rc::Rc,
11    },
12    thiserror::Error,
13    uapi::{Errno, c, sockaddr_none_mut},
14};
15
16#[cfg(test)]
17mod tests;
18
19/// A file-system acceptor for wayland connections.
20///
21/// This represents a socket in the `XDG_RUNTIME_DIR` directory. Its name follows the
22/// usual `wayland-N` scheme.
23///
24/// # Example
25///
26/// ```
27/// # use std::os::fd::{BorrowedFd, OwnedFd};
28/// # use wl_proxy::acceptor::Acceptor;
29/// # fn handle_wayland_connection(fd: OwnedFd) { }
30/// # fn f() {
31/// let acceptor = Acceptor::new(1000, false).unwrap();
32/// loop {
33///     let con = acceptor.accept().unwrap().unwrap();
34///     handle_wayland_connection(con);
35/// }
36/// # }
37/// ```
38pub struct Acceptor {
39    pub(crate) id: u64,
40    pub(crate) socket: OwnedFd,
41    display: String,
42    _lock_fd: OwnedFd,
43}
44
45/// An error emitted by an acceptor.
46#[derive(Debug, Error)]
47#[error(transparent)]
48pub struct AcceptorError(#[from] AcceptorErrorType);
49
50#[derive(Debug, Error)]
51enum AcceptorErrorType {
52    #[error("{} is not set", XDG_RUNTIME_DIR)]
53    XrdNotSet,
54    #[error("could not create a socket")]
55    CreateSocket(#[source] io::Error),
56    #[error("{} ({:?}) is too long to form a unix socket address", XDG_RUNTIME_DIR, .0)]
57    XrdTooLong(String),
58    #[error("could not open the lock file")]
59    OpenLockFile(#[source] io::Error),
60    #[error("could not lock the lock file")]
61    LockLockFile(#[source] io::Error),
62    #[error("could not stat the existing socket")]
63    SocketStat(#[source] io::Error),
64    #[error("could not bind the socket to an address")]
65    BindFailed(#[source] io::Error),
66    #[error("all wayland addresses in the range 0..1000 are already in use")]
67    AddressesInUse,
68    #[error("could not start listening for incoming connections")]
69    ListenFailed(#[source] io::Error),
70    #[error("could not accept new connection")]
71    Accept(#[source] io::Error),
72}
73
74impl Acceptor {
75    /// Creates a new acceptor.
76    ///
77    /// This will try to allocate the socket `wayland-N` in the `XDG_RUNTIME_DIR`
78    /// directory. The function starts with `N = 1` and then increments `N` until it finds
79    /// an unused socket. The maximum value of `N` is determined by the `max_tries`
80    /// parameter.
81    ///
82    /// If `non_blocking` is true, the created socket will be non-blocking, which means
83    /// that [`Acceptor::accept`] can return `Ok(None)`. In this case you should use a
84    /// mechanism such as epoll to wait for new connections on the socket.
85    ///
86    /// # Example
87    ///
88    /// ```
89    /// # use std::os::fd::{BorrowedFd, OwnedFd};
90    /// # use wl_proxy::acceptor::Acceptor;
91    /// # fn handle_wayland_connection(fd: OwnedFd) { }
92    /// # fn f() {
93    /// let acceptor = Acceptor::new(1000, false).unwrap();
94    /// loop {
95    ///     let con = acceptor.accept().unwrap().unwrap();
96    ///     handle_wayland_connection(con);
97    /// }
98    /// # }
99    /// ```
100    pub fn new(max_tries: u32, non_blocking: bool) -> Result<Rc<Self>, AcceptorError> {
101        Self::create(0, max_tries, non_blocking)
102    }
103
104    pub(crate) fn create(
105        id: u64,
106        max_tries: u32,
107        non_blocking: bool,
108    ) -> Result<Rc<Self>, AcceptorError> {
109        let xrd = match var(XDG_RUNTIME_DIR) {
110            Ok(d) => d,
111            _ => return Err(AcceptorErrorType::XrdNotSet.into()),
112        };
113        let mut ty = c::SOCK_STREAM | c::SOCK_CLOEXEC;
114        if non_blocking {
115            ty |= c::SOCK_NONBLOCK;
116        }
117        let socket = uapi::socket(c::AF_UNIX, ty, 0)
118            .map_err(|e| AcceptorErrorType::CreateSocket(e.into()))?;
119        let socket = socket.into();
120        for i in 1..max_tries {
121            let lock_fd = match bind_socket(&socket, &xrd, i) {
122                Ok(f) => f,
123                Err(e) => {
124                    log::debug!("Cannot use the wayland-{} socket: {}", i, Report::new(e));
125                    continue;
126                }
127            };
128            if let Err(e) = uapi::listen(socket.as_raw_fd(), 1024) {
129                return Err(AcceptorErrorType::ListenFailed(e.into()).into());
130            }
131            return Ok(Rc::new(Acceptor {
132                id,
133                socket,
134                display: format!("wayland-{i}"),
135                _lock_fd: lock_fd,
136            }));
137        }
138        Err(AcceptorErrorType::AddressesInUse.into())
139    }
140
141    /// Returns the display name of this acceptor, for example, `wayland-1`.
142    ///
143    /// # Example
144    ///
145    /// ```
146    /// # use wl_proxy::acceptor::Acceptor;
147    /// # fn f() {
148    /// let acceptor = Acceptor::new(1000, false).unwrap();
149    /// eprintln!("{}", acceptor.display());
150    /// # }
151    /// ```
152    pub fn display(&self) -> &str {
153        &self.display
154    }
155
156    /// Returns the socket file descriptor of this acceptor.
157    ///
158    /// This can be used to asynchronously wait for new connections. The returned file
159    /// descriptor should not be used to modify the file description. Otherwise, the behavior is
160    /// unspecified.
161    ///
162    /// # Example
163    ///
164    /// ```
165    /// # use std::os::fd::{BorrowedFd, OwnedFd};
166    /// # use wl_proxy::acceptor::Acceptor;
167    /// # fn wait_for_descriptor_to_become_readable(fd: BorrowedFd<'_>) { }
168    /// # fn handle_wayland_connection(fd: OwnedFd) { }
169    /// # fn f() {
170    /// let acceptor = Acceptor::new(1000, true).unwrap();
171    /// loop {
172    ///     wait_for_descriptor_to_become_readable(acceptor.socket());
173    ///     let con = acceptor.accept().unwrap().unwrap();
174    ///     handle_wayland_connection(con);
175    /// }
176    /// # }
177    /// ```
178    pub fn socket(&self) -> BorrowedFd<'_> {
179        self.socket.as_fd()
180    }
181
182    /// Sets the `WAYLAND_DISPLAY` environment variable to the display of this acceptor.
183    ///
184    /// # Safety
185    ///
186    /// This function is unsafe because it calls [`set_var`].
187    ///
188    /// # Example
189    ///
190    /// ```
191    /// # use std::os::fd::{BorrowedFd, OwnedFd};
192    /// # use wl_proxy::acceptor::Acceptor;
193    /// # fn f() {
194    /// let acceptor = Acceptor::new(1000, false).unwrap();
195    /// unsafe {
196    ///     acceptor.setenv();
197    /// }
198    /// # }
199    /// ```
200    pub unsafe fn setenv(&self) {
201        // SAFETY: The requirement is forwarded to the caller.
202        unsafe {
203            set_var(WAYLAND_DISPLAY, &self.display);
204        }
205    }
206
207    /// Accepts a new connection.
208    ///
209    /// This can return `None` if and only if this acceptor is non-blocking and there is
210    /// currently no client trying to connect to this acceptor.
211    ///
212    /// # Example
213    ///
214    /// ```
215    /// # use std::os::fd::{BorrowedFd, OwnedFd};
216    /// # use wl_proxy::acceptor::Acceptor;
217    /// # fn handle_wayland_connection(fd: OwnedFd) { }
218    /// # fn f() {
219    /// let acceptor = Acceptor::new(1000, false).unwrap();
220    /// loop {
221    ///     let con = acceptor.accept().unwrap().unwrap();
222    ///     handle_wayland_connection(con);
223    /// }
224    /// # }
225    /// ```
226    pub fn accept(&self) -> Result<Option<OwnedFd>, AcceptorError> {
227        loop {
228            let res = uapi::accept4(
229                self.socket.as_raw_fd(),
230                sockaddr_none_mut(),
231                c::SOCK_CLOEXEC,
232            );
233            match res {
234                Ok((s, _)) => return Ok(Some(s.into())),
235                Err(Errno(c::EAGAIN)) => return Ok(None),
236                Err(Errno(c::EINTR)) => {}
237                Err(e) => return Err(AcceptorErrorType::Accept(e.into()).into()),
238            }
239        }
240    }
241}
242
243impl AsFd for Acceptor {
244    fn as_fd(&self) -> BorrowedFd<'_> {
245        self.socket()
246    }
247}
248
249fn bind_socket(socket: &OwnedFd, xrd: &str, id: u32) -> Result<OwnedFd, AcceptorErrorType> {
250    let mut addr: c::sockaddr_un = uapi::pod_zeroed();
251    addr.sun_family = c::AF_UNIX as _;
252    let name = format!("wayland-{}", id);
253    let path = format!("{}/{}", xrd, name);
254    let lock_path = format!("{}.lock", path);
255    if path.len() + 1 > addr.sun_path.len() {
256        return Err(AcceptorErrorType::XrdTooLong(xrd.to_string()));
257    }
258    let lock_fd = match uapi::open(&*lock_path, c::O_CREAT | c::O_CLOEXEC | c::O_RDWR, 0o644) {
259        Ok(l) => l,
260        Err(e) => return Err(AcceptorErrorType::OpenLockFile(e.into())),
261    };
262    if let Err(e) = uapi::flock(lock_fd.raw(), c::LOCK_EX | c::LOCK_NB) {
263        return Err(AcceptorErrorType::LockLockFile(e.into()));
264    }
265    match uapi::lstat(&*path) {
266        Ok(_) => {
267            let _ = uapi::unlink(&*path);
268        }
269        Err(Errno(c::ENOENT)) => {}
270        Err(e) => return Err(AcceptorErrorType::SocketStat(e.into())),
271    }
272    let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]);
273    sun_path[..path.len()].copy_from_slice(path.as_bytes());
274    sun_path[path.len()] = 0;
275    if let Err(e) = uapi::bind(socket.as_raw_fd(), &addr) {
276        return Err(AcceptorErrorType::BindFailed(e.into()));
277    }
278    Ok(lock_fd.into())
279}