interprocess/os/unix/
uds_local_socket.rs

1//! Local sockets implemented using Unix domain sockets.
2
3mod listener;
4mod stream;
5
6pub use {listener::*, stream::*};
7
8/// Async Local sockets for Tokio implemented using Unix domain sockets.
9#[cfg(feature = "tokio")]
10pub mod tokio {
11    mod listener;
12    mod stream;
13    pub use {listener::*, stream::*};
14}
15
16#[cfg(target_os = "android")]
17use std::os::android::net::SocketAddrExt;
18#[cfg(target_os = "linux")]
19use std::os::linux::net::SocketAddrExt;
20use {
21    crate::{
22        local_socket::{Name, NameInner},
23        os::unix::unixprelude::*,
24    },
25    std::{
26        borrow::Cow,
27        ffi::{OsStr, OsString},
28        fs, io, mem,
29        os::unix::net::SocketAddr,
30        path::Path,
31    },
32};
33
34#[derive(Clone, Debug, Default)]
35struct ReclaimGuard(Option<Name<'static>>);
36impl ReclaimGuard {
37    fn new(name: Name<'static>) -> Self { Self(if name.is_path() { Some(name) } else { None }) }
38    #[cfg_attr(not(feature = "tokio"), allow(dead_code))]
39    fn take(&mut self) -> Self { Self(self.0.take()) }
40    fn forget(&mut self) { self.0 = None; }
41}
42impl Drop for ReclaimGuard {
43    fn drop(&mut self) {
44        if let Self(Some(Name(NameInner::UdSocketPath(path)))) = self {
45            let _ = std::fs::remove_file(path);
46        }
47    }
48}
49
50#[allow(clippy::indexing_slicing)]
51fn name_to_addr(name: Name<'_>, create_dirs: bool) -> io::Result<SocketAddr> {
52    match name.0 {
53        NameInner::UdSocketPath(path) => SocketAddr::from_pathname(path),
54        NameInner::UdSocketPseudoNs(name) => construct_and_prepare_pseudo_ns(name, create_dirs),
55        #[cfg(any(target_os = "linux", target_os = "android"))]
56        NameInner::UdSocketNs(name) => SocketAddr::from_abstract_name(name),
57    }
58}
59
60const SUN_LEN: usize = {
61    let dummy = unsafe { mem::zeroed::<libc::sockaddr_un>() };
62    dummy.sun_path.len()
63};
64const NMCAP: usize = SUN_LEN - "/run/user/18446744073709551614/".len();
65
66static TOOLONG: &str = "local socket name length exceeds capacity of sun_path of sockaddr_un";
67
68/// Checks if `/run/user/<ruid>` exists, returning that path if it does.
69fn get_run_user() -> io::Result<Option<OsString>> {
70    let path = format!("/run/user/{}", unsafe { libc::getuid() }).into();
71    match fs::metadata(&path) {
72        Ok(..) => Ok(Some(path)),
73        Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
74        Err(e) => Err(e),
75    }
76}
77
78static TMPDIR: &str = {
79    #[cfg(target_os = "android")]
80    {
81        "/data/local/tmp"
82    }
83    #[cfg(not(target_os = "android"))]
84    {
85        "/tmp"
86    }
87};
88
89#[allow(clippy::indexing_slicing, clippy::arithmetic_side_effects)]
90fn construct_and_prepare_pseudo_ns(
91    name: Cow<'_, OsStr>,
92    create_dirs: bool,
93) -> io::Result<SocketAddr> {
94    let nlen = name.len();
95    if nlen > NMCAP {
96        return Err(io::Error::new(io::ErrorKind::InvalidInput, TOOLONG));
97    }
98    let run_user = get_run_user()?;
99    let pfx = run_user.map(Cow::Owned).unwrap_or(Cow::Borrowed(OsStr::new(TMPDIR)));
100    let pl = pfx.len();
101    let mut path = [0; SUN_LEN];
102    path[..pl].copy_from_slice(pfx.as_bytes());
103    path[pl] = b'/';
104
105    let namestart = pl + 1;
106    let fulllen = pl + 1 + nlen;
107    path[namestart..fulllen].copy_from_slice(name.as_bytes());
108
109    const ESCCHAR: u8 = b'_';
110    for byte in path[namestart..fulllen].iter_mut() {
111        if *byte == 0 {
112            *byte = ESCCHAR;
113        }
114    }
115
116    let opath = Path::new(OsStr::from_bytes(&path[..fulllen]));
117
118    if create_dirs {
119        let parent = opath.parent();
120        if let Some(p) = parent {
121            fs::create_dir_all(p)?;
122        }
123    }
124    SocketAddr::from_pathname(opath)
125}