1use crate::CoreError;
18use crate::error::syscall_ret;
19use crate::reactor::Fd;
20use std::io::Error as IoError;
21use std::os::unix::ffi::OsStrExt;
22use std::os::unix::fs::FileTypeExt;
23use std::os::unix::io::AsRawFd;
24use std::path::Path;
25
26#[inline(always)]
27fn errno() -> i32 {
28 IoError::last_os_error().raw_os_error().unwrap_or(0)
29}
30
31pub struct UnixListenerFd {
33 pub fd: Fd,
35}
36
37pub struct UnixStreamFd {
39 pub fd: Fd,
41}
42
43pub enum UnixConnectResult {
45 Connected(UnixStreamFd),
47 InProgress(UnixStreamFd),
50}
51
52#[derive(Clone, Copy, Debug)]
54pub enum UnixSocketAddr<'a> {
55 Path(&'a Path),
57 Abstract(&'a [u8]),
59}
60
61#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
63pub enum StaleSocketPolicy {
64 #[default]
66 Preserve,
67 UnlinkSocketOnly,
69 UnlinkAnyPath,
74}
75
76#[derive(Clone, Copy, Debug, Default)]
78pub struct UnixSocketBindOptions {
79 pub stale_socket_policy: StaleSocketPolicy,
81 pub mode: Option<u32>,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub struct PeerCred {
88 pub pid: Option<i32>,
90 pub uid: u32,
92 pub gid: u32,
94}
95
96impl UnixListenerFd {
97 pub fn accept(&self) -> Result<Option<UnixStreamFd>, CoreError> {
111 self.accept_timeout(0)
112 }
113
114 pub fn accept_timeout(&self, timeout_ms: i32) -> Result<Option<UnixStreamFd>, CoreError> {
130 if timeout_ms != 0 {
131 let mut pollfd = libc::pollfd {
132 fd: self.fd.as_raw_fd(),
133 events: libc::POLLIN,
134 revents: 0,
135 };
136 let ret = unsafe { libc::poll(&mut pollfd, 1, timeout_ms) };
137 if ret < 0 {
138 let e = errno();
139 if e == libc::EINTR {
140 return Ok(None);
141 }
142 return Err(CoreError::sys(e, "poll(accept)"));
143 }
144 if ret == 0 {
145 return Ok(None);
146 }
147 }
148
149 loop {
150 let fd = unsafe {
151 libc::accept4(
152 self.fd.as_raw_fd(),
153 std::ptr::null_mut(),
154 std::ptr::null_mut(),
155 libc::SOCK_CLOEXEC | libc::SOCK_NONBLOCK,
156 )
157 };
158 if fd >= 0 {
159 return Ok(Some(UnixStreamFd {
160 fd: Fd::new(fd, "accept4")?,
161 }));
162 }
163
164 let e = errno();
165 if e == libc::EINTR {
166 continue;
167 }
168 if e == libc::EAGAIN || e == libc::EWOULDBLOCK {
169 return Ok(None);
170 }
171 return Err(CoreError::sys(e, "accept4"));
172 }
173 }
174}
175
176impl UnixStreamFd {
177 pub fn peer_cred(&self) -> Result<Option<PeerCred>, CoreError> {
194 peer_cred_raw(&self.fd)
195 }
196
197 pub fn check_connect_error(&self) -> Result<Option<i32>, CoreError> {
205 let mut code: libc::c_int = 0;
206 let mut len = std::mem::size_of::<libc::c_int>() as libc::socklen_t;
207 let ret = unsafe {
208 libc::getsockopt(
209 self.fd.as_raw_fd(),
210 libc::SOL_SOCKET,
211 libc::SO_ERROR,
212 (&mut code as *mut libc::c_int).cast(),
213 &mut len,
214 )
215 };
216 syscall_ret(ret, "getsockopt(SO_ERROR)")?;
217 if code == 0 { Ok(None) } else { Ok(Some(code)) }
218 }
219
220 pub fn finish_connect(self) -> Result<Self, CoreError> {
229 match self.check_connect_error()? {
230 None => Ok(self),
231 Some(code) => Err(CoreError::sys(code, "connect(SO_ERROR)")),
232 }
233 }
234}
235
236pub fn bind_unix_listener(
251 addr: UnixSocketAddr<'_>,
252 opts: UnixSocketBindOptions,
253) -> Result<UnixListenerFd, CoreError> {
254 let encoded = UnixSockAddr::new(addr, "unix bind address")?;
255
256 match addr {
257 UnixSocketAddr::Path(path) => {
258 apply_stale_socket_policy(path, opts.stale_socket_policy)?;
259 }
260 UnixSocketAddr::Abstract(_) => {
261 if opts.stale_socket_policy != StaleSocketPolicy::Preserve || opts.mode.is_some() {
262 return Err(CoreError::sys(libc::EINVAL, "abstract unix bind options"));
263 }
264 }
265 }
266
267 let fd = new_unix_stream_socket()?;
268 let ret = unsafe { libc::bind(fd.as_raw_fd(), encoded.as_ptr(), encoded.len()) };
269 syscall_ret(ret, "bind")?;
270
271 if let (UnixSocketAddr::Path(path), Some(mode)) = (addr, opts.mode) {
272 if let Err(err) = chmod_unix_socket(UnixSocketAddr::Path(path), mode) {
273 cleanup_created_path(addr);
274 return Err(err);
275 }
276 }
277
278 let ret = unsafe { libc::listen(fd.as_raw_fd(), libc::SOMAXCONN) };
279 if let Err(err) = syscall_ret(ret, "listen") {
280 cleanup_created_path(addr);
281 return Err(err);
282 }
283
284 Ok(UnixListenerFd { fd })
285}
286
287pub fn connect_unix_stream(addr: UnixSocketAddr<'_>) -> Result<UnixConnectResult, CoreError> {
298 connect_unix_stream_as(addr, None)
299}
300
301pub fn connect_unix_stream_named(
304 remote: UnixSocketAddr<'_>,
305 local: UnixSocketAddr<'_>,
306) -> Result<UnixConnectResult, CoreError> {
307 connect_unix_stream_as(remote, Some(local))
308}
309
310fn connect_unix_stream_as(
311 remote: UnixSocketAddr<'_>,
312 local: Option<UnixSocketAddr<'_>>,
313) -> Result<UnixConnectResult, CoreError> {
314 let encoded = UnixSockAddr::new(remote, "unix connect address")?;
315 let fd = new_unix_stream_socket()?;
316
317 if let Some(la) = local {
318 let lb = UnixSockAddr::new(la, "unix bind local name")?;
319 let r = unsafe { libc::bind(fd.as_raw_fd(), lb.as_ptr(), lb.len()) };
320 if r < 0 {
321 return Err(CoreError::sys(errno(), "bind local name"));
322 }
323 }
324
325 loop {
326 let ret = unsafe { libc::connect(fd.as_raw_fd(), encoded.as_ptr(), encoded.len()) };
327 if ret == 0 {
328 return Ok(UnixConnectResult::Connected(UnixStreamFd { fd }));
329 }
330
331 let e = errno();
332 if e == libc::EINTR {
333 continue;
334 }
335 if e == libc::EINPROGRESS || e == libc::EALREADY {
336 return Ok(UnixConnectResult::InProgress(UnixStreamFd { fd }));
337 }
338 if e == libc::EISCONN {
339 return Ok(UnixConnectResult::Connected(UnixStreamFd { fd }));
340 }
341 return Err(CoreError::sys(e, "connect"));
342 }
343}
344
345pub fn socketpair() -> Result<(UnixStreamFd, UnixStreamFd), CoreError> {
355 let mut fds = [0 as libc::c_int; 2];
356 let ret = unsafe {
357 libc::socketpair(
358 libc::AF_UNIX,
359 libc::SOCK_STREAM | libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC,
360 0,
361 fds.as_mut_ptr(),
362 )
363 };
364 syscall_ret(ret, "socketpair")?;
365 let a = UnixStreamFd { fd: Fd::new(fds[0], "socketpair.0")? };
366 let b = UnixStreamFd { fd: Fd::new(fds[1], "socketpair.1")? };
367 Ok((a, b))
368}
369
370pub fn chmod_unix_socket(addr: UnixSocketAddr<'_>, mode: u32) -> Result<(), CoreError> {
377 match addr {
378 UnixSocketAddr::Path(path) => {
379 let metadata = std::fs::symlink_metadata(path).map_err(|err| {
380 CoreError::sys(
381 err.raw_os_error().unwrap_or(libc::EIO),
382 "lstat unix socket path",
383 )
384 })?;
385 if !metadata.file_type().is_socket() {
386 return Err(CoreError::sys(libc::EINVAL, "chmod unix socket path"));
387 }
388 let c_path = path_cstring(path, "chmod unix socket path")?;
389 let ret = unsafe { libc::chmod(c_path.as_ptr(), mode as libc::mode_t) };
390 syscall_ret(ret, "chmod")
391 }
392 UnixSocketAddr::Abstract(_) => Err(CoreError::sys(libc::EINVAL, "chmod abstract socket")),
393 }
394}
395
396pub fn chmod_socket_path(path: impl AsRef<Path>, mode: u32) -> Result<(), CoreError> {
398 chmod_unix_socket(UnixSocketAddr::Path(path.as_ref()), mode)
399}
400
401fn new_unix_stream_socket() -> Result<Fd, CoreError> {
405 let fd = unsafe {
406 libc::socket(
407 libc::AF_UNIX,
408 libc::SOCK_STREAM | libc::SOCK_CLOEXEC | libc::SOCK_NONBLOCK,
409 0,
410 )
411 };
412 syscall_ret(fd, "socket(AF_UNIX)")?;
413 Fd::new(fd, "socket(AF_UNIX)")
414}
415
416fn apply_stale_socket_policy(path: &Path, policy: StaleSocketPolicy) -> Result<(), CoreError> {
417 match policy {
418 StaleSocketPolicy::Preserve => Ok(()),
419 StaleSocketPolicy::UnlinkSocketOnly => {
420 let metadata = match std::fs::symlink_metadata(path) {
421 Ok(metadata) => metadata,
422 Err(err) if err.raw_os_error() == Some(libc::ENOENT) => return Ok(()),
423 Err(err) => {
424 return Err(CoreError::sys(
425 err.raw_os_error().unwrap_or(libc::EIO),
426 "lstat unix socket path",
427 ));
428 }
429 };
430 if !metadata.file_type().is_socket() {
431 return Err(CoreError::sys(libc::EEXIST, "stale unix socket path"));
432 }
433 unlink_path(path, "unlink stale unix socket")
434 }
435 StaleSocketPolicy::UnlinkAnyPath => unlink_path(path, "unlink unix socket path"),
436 }
437}
438
439fn unlink_path(path: &Path, op: &'static str) -> Result<(), CoreError> {
440 match std::fs::remove_file(path) {
441 Ok(()) => Ok(()),
442 Err(err) if err.raw_os_error() == Some(libc::ENOENT) => Ok(()),
443 Err(err) => Err(CoreError::sys(err.raw_os_error().unwrap_or(libc::EIO), op)),
444 }
445}
446
447fn cleanup_created_path(addr: UnixSocketAddr<'_>) {
448 if let UnixSocketAddr::Path(path) = addr {
449 let _ = std::fs::remove_file(path);
450 }
451}
452
453struct UnixSockAddr {
454 inner: libc::sockaddr_un,
455 len: libc::socklen_t,
456}
457
458impl UnixSockAddr {
459 fn new(addr: UnixSocketAddr<'_>, op: &'static str) -> Result<Self, CoreError> {
460 let mut inner: libc::sockaddr_un = unsafe { std::mem::zeroed() };
461 inner.sun_family = libc::AF_UNIX as libc::sa_family_t;
462 let sun_path_offset = std::mem::offset_of!(libc::sockaddr_un, sun_path);
463
464 let len = match addr {
465 UnixSocketAddr::Path(path) => {
466 let bytes = path.as_os_str().as_bytes();
467 if bytes.is_empty() {
468 return Err(CoreError::sys(libc::EINVAL, op));
469 }
470 if bytes.contains(&0) {
471 return Err(CoreError::sys(libc::EINVAL, op));
472 }
473 if bytes.len() >= inner.sun_path.len() {
474 return Err(CoreError::sys(libc::ENAMETOOLONG, op));
475 }
476
477 for (slot, byte) in inner.sun_path.iter_mut().zip(bytes.iter().copied()) {
478 *slot = byte as libc::c_char;
479 }
480 sun_path_offset + bytes.len() + 1
481 }
482 UnixSocketAddr::Abstract(name) => {
483 validate_abstract_supported()?;
484 if name.is_empty() {
485 return Err(CoreError::sys(libc::EINVAL, op));
486 }
487 if name.len() + 1 > inner.sun_path.len() {
488 return Err(CoreError::sys(libc::ENAMETOOLONG, op));
489 }
490
491 inner.sun_path[0] = 0;
492 for (slot, byte) in inner.sun_path[1..].iter_mut().zip(name.iter().copied()) {
493 *slot = byte as libc::c_char;
494 }
495 sun_path_offset + 1 + name.len()
496 }
497 };
498 let len = libc::socklen_t::try_from(len).map_err(|_| CoreError::sys(libc::EINVAL, op))?;
499
500 Ok(Self { inner, len })
501 }
502
503 fn len(&self) -> libc::socklen_t {
504 self.len
505 }
506
507 fn as_ptr(&self) -> *const libc::sockaddr {
508 (&self.inner as *const libc::sockaddr_un).cast()
509 }
510}
511
512fn validate_abstract_supported() -> Result<(), CoreError> {
513 if cfg!(any(target_os = "linux", target_os = "android")) {
514 Ok(())
515 } else {
516 Err(CoreError::sys(libc::ENOSYS, "abstract unix socket"))
517 }
518}
519
520fn path_cstring(path: &Path, op: &'static str) -> Result<std::ffi::CString, CoreError> {
521 std::ffi::CString::new(path.as_os_str().as_bytes())
522 .map_err(|_| CoreError::sys(libc::EINVAL, op))
523}
524
525#[cfg(any(target_os = "linux", target_os = "android"))]
526fn peer_cred_raw(fd: &Fd) -> Result<Option<PeerCred>, CoreError> {
527 let mut cred: libc::ucred = unsafe { std::mem::zeroed() };
528 let mut len = std::mem::size_of::<libc::ucred>() as libc::socklen_t;
529 let ret = unsafe {
530 libc::getsockopt(
531 fd.as_raw_fd(),
532 libc::SOL_SOCKET,
533 libc::SO_PEERCRED,
534 (&mut cred as *mut libc::ucred).cast(),
535 &mut len,
536 )
537 };
538 syscall_ret(ret, "getsockopt(SO_PEERCRED)")?;
539
540 Ok(Some(PeerCred {
541 pid: Some(cred.pid),
542 uid: cred.uid,
543 gid: cred.gid,
544 }))
545}
546
547#[cfg(not(any(target_os = "linux", target_os = "android")))]
548fn peer_cred_raw(_fd: &Fd) -> Result<Option<PeerCred>, CoreError> {
549 Ok(None)
550}