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}