1use std::io;
14use std::mem;
15use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
16use std::os::unix::net::UnixListener;
17use std::ptr;
18use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
19use std::sync::{Arc, Mutex};
20use std::thread::{self, JoinHandle};
21
22use super::ring::CONFIG_SIZE;
23
24struct Client {
25 fd: OwnedFd,
26 ready: bool,
28}
29
30struct Shared {
31 clients: Mutex<Vec<Client>>,
32 ready_count: AtomicUsize,
33 stop: AtomicBool,
34}
35
36pub struct Server {
37 path: String,
38 shared: Arc<Shared>,
39 wake: OwnedFd,
40 thread: Option<JoinHandle<()>>,
41}
42
43impl Server {
44 pub fn bind(path: &str, config: [u8; CONFIG_SIZE], ring_fd: RawFd) -> io::Result<Self> {
46 if let Some(dir) = std::path::Path::new(path).parent()
47 && !dir.as_os_str().is_empty() {
48 std::fs::create_dir_all(dir)?;
49 }
50 let _ = std::fs::remove_file(path);
51 let listener = UnixListener::bind(path)?;
52 listener.set_nonblocking(true)?;
53 let wake_raw = unsafe { libc::eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK) };
54 if wake_raw < 0 {
55 return Err(io::Error::last_os_error());
56 }
57 let wake = unsafe { OwnedFd::from_raw_fd(wake_raw) };
58 let shared = Arc::new(Shared {
59 clients: Mutex::new(Vec::new()),
60 ready_count: AtomicUsize::new(0),
61 stop: AtomicBool::new(false),
62 });
63 let t_shared = shared.clone();
64 let t_wake = wake.as_raw_fd();
65 let thread = thread::Builder::new()
66 .name("pixelflux-webcam-ctl".into())
67 .spawn(move || serve(listener, t_wake, config, ring_fd, t_shared))?;
68 Ok(Server { path: path.to_string(), shared, wake, thread: Some(thread) })
69 }
70
71 pub fn path(&self) -> &str {
72 &self.path
73 }
74
75 pub fn client_count(&self) -> usize {
77 self.shared.ready_count.load(Ordering::Relaxed)
78 }
79
80 pub fn ring_doorbell(&self) {
83 let mut clients = self.shared.clients.lock().unwrap_or_else(|e| e.into_inner());
84 let mut i = 0;
85 while i < clients.len() {
86 if !clients[i].ready {
87 i += 1;
88 continue;
89 }
90 let byte = [1u8];
91 let n = unsafe {
92 libc::send(clients[i].fd.as_raw_fd(), byte.as_ptr() as *const _, 1,
93 libc::MSG_DONTWAIT | libc::MSG_NOSIGNAL)
94 };
95 if n < 0 {
96 let err = io::Error::last_os_error();
97 match err.raw_os_error() {
98 Some(libc::EAGAIN) | Some(libc::EINTR) => {}
99 _ => {
100 clients.remove(i);
101 self.shared.ready_count.fetch_sub(1, Ordering::Relaxed);
102 continue;
103 }
104 }
105 }
106 i += 1;
107 }
108 }
109
110 fn wake_thread(&self) {
111 let one: u64 = 1;
112 unsafe { libc::write(self.wake.as_raw_fd(), &one as *const u64 as *const _, 8) };
113 }
114}
115
116impl Drop for Server {
117 fn drop(&mut self) {
120 self.shared.stop.store(true, Ordering::Release);
121 self.wake_thread();
122 if let Some(t) = self.thread.take() {
123 let _ = t.join();
124 }
125 self.shared.clients.lock().unwrap_or_else(|e| e.into_inner()).clear();
126 self.shared.ready_count.store(0, Ordering::Relaxed);
127 let _ = std::fs::remove_file(&self.path);
128 }
129}
130
131fn send_config_with_fd(sock: RawFd, config: &[u8], fd: RawFd) -> io::Result<()> {
133 let mut iov = libc::iovec { iov_base: config.as_ptr() as *mut libc::c_void, iov_len: config.len() };
134 let space = unsafe { libc::CMSG_SPACE(mem::size_of::<RawFd>() as u32) } as usize;
135 let mut cbuf = vec![0u64; space.div_ceil(mem::size_of::<u64>())];
136 let mut msg: libc::msghdr = unsafe { mem::zeroed() };
137 msg.msg_iov = &mut iov;
138 msg.msg_iovlen = 1;
139 msg.msg_control = cbuf.as_mut_ptr() as *mut libc::c_void;
140 msg.msg_controllen = space as _;
141 unsafe {
142 let cmsg = libc::CMSG_FIRSTHDR(&msg);
143 (*cmsg).cmsg_level = libc::SOL_SOCKET;
144 (*cmsg).cmsg_type = libc::SCM_RIGHTS;
145 (*cmsg).cmsg_len = libc::CMSG_LEN(mem::size_of::<RawFd>() as u32) as _;
146 ptr::write_unaligned(libc::CMSG_DATA(cmsg) as *mut RawFd, fd);
147 }
148 loop {
149 let n = unsafe { libc::sendmsg(sock, &msg, libc::MSG_NOSIGNAL) };
150 if n < 0 {
151 let err = io::Error::last_os_error();
152 if err.raw_os_error() == Some(libc::EINTR) {
153 continue;
154 }
155 return Err(err);
156 }
157 if n as usize != config.len() {
158 return Err(io::Error::new(io::ErrorKind::WriteZero, "short config send"));
159 }
160 return Ok(());
161 }
162}
163
164fn serve(listener: UnixListener, wake: RawFd, config: [u8; CONFIG_SIZE], ring_fd: RawFd, shared: Arc<Shared>) {
165 let listen_fd = listener.as_raw_fd();
166 let mut pollfds: Vec<libc::pollfd> = Vec::new();
167 while !shared.stop.load(Ordering::Acquire) {
168 pollfds.clear();
169 pollfds.push(libc::pollfd { fd: listen_fd, events: libc::POLLIN, revents: 0 });
170 pollfds.push(libc::pollfd { fd: wake, events: libc::POLLIN, revents: 0 });
171 {
172 let clients = shared.clients.lock().unwrap_or_else(|e| e.into_inner());
173 for c in clients.iter() {
174 pollfds.push(libc::pollfd { fd: c.fd.as_raw_fd(), events: libc::POLLIN | libc::POLLRDHUP, revents: 0 });
175 }
176 }
177 let n = unsafe { libc::poll(pollfds.as_mut_ptr(), pollfds.len() as libc::nfds_t, -1) };
178 if n < 0 {
179 if io::Error::last_os_error().raw_os_error() == Some(libc::EINTR) {
180 continue;
181 }
182 break;
183 }
184 if pollfds[1].revents != 0 {
185 let mut v: u64 = 0;
186 unsafe { libc::read(wake, &mut v as *mut u64 as *mut _, 8) };
187 if shared.stop.load(Ordering::Acquire) {
188 break;
189 }
190 }
191 if pollfds[0].revents != 0 {
192 accept_clients(&listener, &config, ring_fd, &shared);
193 }
194 let mut clients = shared.clients.lock().unwrap_or_else(|e| e.into_inner());
195 let mut idx = 0;
196 for pfd in pollfds.iter().skip(2) {
197 if idx >= clients.len() {
198 break;
199 }
200 if clients[idx].fd.as_raw_fd() != pfd.fd {
201 idx += 1;
202 continue;
203 }
204 if pfd.revents == 0 {
205 idx += 1;
206 continue;
207 }
208 let mut buf = [0u8; 64];
209 let r = unsafe { libc::recv(clients[idx].fd.as_raw_fd(), buf.as_mut_ptr() as *mut _, buf.len(), libc::MSG_DONTWAIT) };
210 let closed = if r > 0 {
211 if !clients[idx].ready {
212 clients[idx].ready = true;
213 shared.ready_count.fetch_add(1, Ordering::Relaxed);
214 }
215 false
216 } else if r == 0 {
217 true
218 } else {
219 !matches!(io::Error::last_os_error().raw_os_error(), Some(libc::EAGAIN) | Some(libc::EINTR))
220 };
221 if closed || pfd.revents & (libc::POLLHUP | libc::POLLERR | libc::POLLRDHUP) != 0 && r <= 0 {
222 if clients[idx].ready {
223 shared.ready_count.fetch_sub(1, Ordering::Relaxed);
224 }
225 clients.remove(idx);
226 continue;
227 }
228 idx += 1;
229 }
230 }
231}
232
233fn accept_clients(listener: &UnixListener, config: &[u8; CONFIG_SIZE], ring_fd: RawFd, shared: &Shared) {
234 loop {
235 match listener.accept() {
236 Ok((stream, _)) => {
237 let _ = stream.set_nonblocking(true);
238 let fd = OwnedFd::from(stream);
239 match send_config_with_fd(fd.as_raw_fd(), config, ring_fd) {
240 Ok(()) => {
241 shared.clients.lock().unwrap_or_else(|e| e.into_inner()).push(Client { fd, ready: false });
242 }
243 Err(e) => eprintln!("[webcam] interposer handshake failed: {}", e),
244 }
245 }
246 Err(e) if e.kind() == io::ErrorKind::WouldBlock => return,
247 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
248 Err(e) => {
249 eprintln!("[webcam] accept failed: {}", e);
250 return;
251 }
252 }
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259 use std::io::{Read, Write};
260 use std::os::unix::net::UnixStream;
261 use std::time::{Duration, Instant};
262
263 fn recv_config(stream: &UnixStream) -> (Vec<u8>, RawFd) {
264 let mut data = vec![0u8; CONFIG_SIZE];
265 let mut iov = libc::iovec { iov_base: data.as_mut_ptr() as *mut _, iov_len: data.len() };
266 let space = unsafe { libc::CMSG_SPACE(4) } as usize;
267 let mut cbuf = vec![0u64; space.div_ceil(8)];
268 let mut msg: libc::msghdr = unsafe { mem::zeroed() };
269 msg.msg_iov = &mut iov;
270 msg.msg_iovlen = 1;
271 msg.msg_control = cbuf.as_mut_ptr() as *mut _;
272 msg.msg_controllen = space as _;
273 let n = unsafe { libc::recvmsg(stream.as_raw_fd(), &mut msg, 0) };
274 assert_eq!(n as usize, CONFIG_SIZE);
275 let cmsg = unsafe { libc::CMSG_FIRSTHDR(&msg) };
276 assert!(!cmsg.is_null());
277 let fd = unsafe { ptr::read_unaligned(libc::CMSG_DATA(cmsg) as *const RawFd) };
278 (data, fd)
279 }
280
281 #[test]
282 fn handshake_fd_and_doorbells() {
283 let dir = std::env::temp_dir().join(format!("pxwc-{}", std::process::id()));
284 let path = dir.join("cam.sock");
285 let ring = unsafe { libc::memfd_create(c"t".as_ptr(), libc::MFD_CLOEXEC) };
286 assert!(ring >= 0);
287 unsafe { libc::ftruncate(ring, 8192) };
288 let mut config = [0u8; CONFIG_SIZE];
289 config[..4].copy_from_slice(&0xDEADBEEFu32.to_le_bytes());
290 let server = Server::bind(path.to_str().unwrap(), config, ring).unwrap();
291
292 let mut client = UnixStream::connect(&path).unwrap();
293 let (data, fd) = recv_config(&client);
294 assert_eq!(&data[..4], &0xDEADBEEFu32.to_le_bytes());
295 assert!(fd >= 0);
296 let mut st: libc::stat = unsafe { mem::zeroed() };
297 assert_eq!(unsafe { libc::fstat(fd, &mut st) }, 0);
298 assert_eq!(st.st_size, 8192);
299 unsafe { libc::close(fd) };
300
301 server.ring_doorbell();
302 assert_eq!(server.client_count(), 0);
303 client.write_all(&[8u8]).unwrap();
304 let deadline = Instant::now() + Duration::from_secs(3);
305 while server.client_count() != 1 && Instant::now() < deadline {
306 thread::sleep(Duration::from_millis(5));
307 }
308 assert_eq!(server.client_count(), 1);
309 server.ring_doorbell();
310 client.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
311 let mut b = [0u8; 1];
312 client.read_exact(&mut b).unwrap();
313 assert_eq!(b[0], 1);
314
315 drop(client);
316 let deadline = Instant::now() + Duration::from_secs(3);
317 while server.client_count() != 0 && Instant::now() < deadline {
318 server.ring_doorbell();
319 thread::sleep(Duration::from_millis(5));
320 }
321 assert_eq!(server.client_count(), 0);
322 drop(server);
323 assert!(!path.exists());
324 unsafe { libc::close(ring) };
325 let _ = std::fs::remove_dir_all(&dir);
326 }
327}