diff --git a/src/backend/libc/net/addr.rs b/src/backend/libc/net/addr.rs index 7a26eea7b..dcc969273 100644 --- a/src/backend/libc/net/addr.rs +++ b/src/backend/libc/net/addr.rs @@ -74,6 +74,26 @@ impl SocketAddrUnix { }) } + /// Construct a new unnamed address. + /// + /// The kernel will assign an abstract Unix-domain address to the socket when you call + /// [`bind_unix()`][crate::net::bind_unix]. You can inspect the assigned name with + /// [`getsockname`][crate::net::getsockname]. + /// + /// # References + /// - [Linux] + /// + /// [Linux]: https://www.man7.org/linux/man-pages/man7/unix.7.html + #[cfg(linux_kernel)] + #[inline] + pub fn new_unnamed() -> Self { + Self { + unix: Self::init(), + #[cfg(not(any(bsd, target_os = "haiku")))] + len: offsetof_sun_path() as _, + } + } + const fn init() -> c::sockaddr_un { c::sockaddr_un { #[cfg(any( @@ -101,19 +121,10 @@ impl SocketAddrUnix { /// For a filesystem path address, return the path. #[inline] pub fn path(&self) -> Option<&CStr> { - let len = self.len(); - if len != 0 && self.unix.sun_path[0] != 0 { - let end = len as usize - offsetof_sun_path(); - let bytes = &self.unix.sun_path[..end]; - // SAFETY: `from_raw_parts` to convert from `&[c_char]` to `&[u8]`. - // And `from_bytes_with_nul_unchecked` since the string is - // NUL-terminated. - unsafe { - Some(CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts( - bytes.as_ptr().cast(), - bytes.len(), - ))) - } + let bytes = self.bytes()?; + if !bytes.is_empty() && bytes[0] != 0 { + // SAFETY: `from_bytes_with_nul_unchecked` since the string is NUL-terminated. + Some(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }) } else { None } @@ -123,17 +134,20 @@ impl SocketAddrUnix { #[cfg(linux_kernel)] #[inline] pub fn abstract_name(&self) -> Option<&[u8]> { - let len = self.len(); - if len != 0 && self.unix.sun_path[0] == 0 { - let end = len as usize - offsetof_sun_path(); - let bytes = &self.unix.sun_path[1..end]; - // SAFETY: `from_raw_parts` to convert from `&[c_char]` to `&[u8]`. - unsafe { Some(slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len())) } + if let [0, ref bytes @ ..] = self.bytes()? { + Some(bytes) } else { None } } + /// `true` if the socket address is unnamed. + #[cfg(linux_kernel)] + #[inline] + pub fn is_unnamed(&self) -> bool { + self.bytes() == Some(&[]) + } + #[inline] pub(crate) fn addr_len(&self) -> c::socklen_t { #[cfg(not(any(bsd, target_os = "haiku")))] @@ -150,6 +164,18 @@ impl SocketAddrUnix { pub(crate) fn len(&self) -> usize { self.addr_len() as usize } + + #[inline] + fn bytes(&self) -> Option<&[u8]> { + let len = self.len() as usize; + if len != 0 { + let bytes = &self.unix.sun_path[..len - offsetof_sun_path()]; + // SAFETY: `from_raw_parts` to convert from `&[c_char]` to `&[u8]`. + Some(unsafe { slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len()) }) + } else { + None + } + } } #[cfg(unix)] diff --git a/src/backend/linux_raw/net/addr.rs b/src/backend/linux_raw/net/addr.rs index 5bc84ed69..30c8d4bed 100644 --- a/src/backend/linux_raw/net/addr.rs +++ b/src/backend/linux_raw/net/addr.rs @@ -62,6 +62,26 @@ impl SocketAddrUnix { } } + /// Construct a new unnamed address. + /// + /// The kernel will assign an abstract Unix-domain address to the socket when you call + /// [`bind_unix()`][crate::net::bind_unix]. You can inspect the assigned name with + /// [`getsockname`][crate::net::getsockname]. + /// + /// # References + /// - [Linux] + /// + /// [Linux]: https://www.man7.org/linux/man-pages/man7/unix.7.html + #[cfg(linux_kernel)] + #[inline] + pub fn new_unnamed() -> Self { + Self { + unix: Self::init(), + #[cfg(not(any(bsd, target_os = "haiku")))] + len: offsetof_sun_path() as _, + } + } + const fn init() -> c::sockaddr_un { c::sockaddr_un { sun_family: c::AF_UNIX as _, @@ -72,17 +92,10 @@ impl SocketAddrUnix { /// For a filesystem path address, return the path. #[inline] pub fn path(&self) -> Option<&CStr> { - let len = self.len(); - if len != 0 && self.unix.sun_path[0] as u8 != b'\0' { - let end = len as usize - offsetof_sun_path(); - let bytes = &self.unix.sun_path[..end]; - - // SAFETY: Convert `&[c_char]` to `&[u8]`. - let bytes = unsafe { slice::from_raw_parts(bytes.as_ptr().cast::(), bytes.len()) }; - - // SAFETY: `from_bytes_with_nul_unchecked` since the string is - // NUL-terminated. - unsafe { Some(CStr::from_bytes_with_nul_unchecked(bytes)) } + let bytes = self.bytes()?; + if !bytes.is_empty() && bytes[0] != 0 { + // SAFETY: `from_bytes_with_nul_unchecked` since the string is NUL-terminated. + Some(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }) } else { None } @@ -91,20 +104,20 @@ impl SocketAddrUnix { /// For an abstract address, return the identifier. #[inline] pub fn abstract_name(&self) -> Option<&[u8]> { - let len = self.len(); - if len != 0 && self.unix.sun_path[0] as u8 == b'\0' { - let end = len as usize - offsetof_sun_path(); - let bytes = &self.unix.sun_path[1..end]; - - // SAFETY: Convert `&[c_char]` to `&[u8]`. - let bytes = unsafe { slice::from_raw_parts(bytes.as_ptr().cast::(), bytes.len()) }; - + if let [0, ref bytes @ ..] = self.bytes()? { Some(bytes) } else { None } } + /// `true` if the socket address is unnamed. + #[cfg(linux_kernel)] + #[inline] + pub fn is_unnamed(&self) -> bool { + self.bytes() == Some(&[]) + } + #[inline] pub(crate) fn addr_len(&self) -> c::socklen_t { self.len @@ -114,6 +127,18 @@ impl SocketAddrUnix { pub(crate) fn len(&self) -> usize { self.addr_len() as usize } + + #[inline] + fn bytes(&self) -> Option<&[u8]> { + let len = self.len() as usize; + if len != 0 { + let bytes = &self.unix.sun_path[..len - offsetof_sun_path()]; + // SAFETY: `from_raw_parts` to convert from `&[c_char]` to `&[u8]`. + Some(unsafe { slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len()) }) + } else { + None + } + } } impl PartialEq for SocketAddrUnix { diff --git a/tests/net/unix.rs b/tests/net/unix.rs index 27d0ff1d0..f2fc2aa28 100644 --- a/tests/net/unix.rs +++ b/tests/net/unix.rs @@ -419,6 +419,7 @@ fn test_abstract_unix_msg_unconnected() { } #[cfg(not(any(target_os = "redox", target_os = "wasi")))] +#[cfg(feature = "pipe")] #[test] fn test_unix_msg_with_scm_rights() { crate::init(); @@ -649,7 +650,7 @@ fn test_unix_peercred_explicit() { /// Like `test_unix_peercred_explicit`, but relies on the fact that /// `set_socket_passcred` enables passing of the credentials implicitly /// instead of passing an explicit message to `sendmsg`. -#[cfg(all(feature = "process", linux_kernel))] +#[cfg(all(feature = "pipe", feature = "process", linux_kernel))] #[test] fn test_unix_peercred_implicit() { crate::init(); @@ -707,6 +708,7 @@ fn test_unix_peercred_implicit() { /// Like `test_unix_msg_with_scm_rights`, but with multiple file descriptors /// over multiple control messages. #[cfg(not(any(target_os = "redox", target_os = "wasi")))] +#[cfg(feature = "pipe")] #[test] fn test_unix_msg_with_combo() { crate::init(); @@ -915,3 +917,25 @@ fn test_unix_msg_with_combo() { client.join().unwrap(); server.join().unwrap(); } + +/// Bind socket to an unnamed Unix-domain address, and assert that an abstract Unix-domain name was +/// assigned by the kernel. +#[cfg(linux_kernel)] +#[test] +fn test_bind_unnamed_address() { + let address = SocketAddrUnix::new_unnamed(); + assert!(address.is_unnamed()); + assert_eq!(address.abstract_name(), None); + assert_eq!(address.path(), None); + let sock = socket(AddressFamily::UNIX, SocketType::DGRAM, None).unwrap(); + bind_unix(&sock, &address).unwrap(); + + let address = rustix::net::getsockname(&sock).unwrap(); + let address = match address { + rustix::net::SocketAddrAny::Unix(address) => address, + address => panic!("expected Unix address, got {address:?}"), + }; + assert!(!address.is_unnamed()); + assert_ne!(address.abstract_name(), None); + assert_eq!(address.path(), None); +} diff --git a/tests/net/unix_alloc.rs b/tests/net/unix_alloc.rs index aedc9b736..222c5439d 100644 --- a/tests/net/unix_alloc.rs +++ b/tests/net/unix_alloc.rs @@ -417,6 +417,7 @@ fn test_abstract_unix_msg_unconnected() { } #[cfg(not(any(target_os = "redox", target_os = "wasi")))] +#[cfg(feature = "pipe")] #[test] fn test_unix_msg_with_scm_rights() { crate::init(); @@ -652,6 +653,7 @@ fn test_unix_peercred() { /// Like `test_unix_msg_with_scm_rights`, but with multiple file descriptors /// over multiple control messages. #[cfg(not(any(target_os = "redox", target_os = "wasi")))] +#[cfg(feature = "pipe")] #[test] fn test_unix_msg_with_combo() { crate::init();