syd 3.52.0

rock-solid application kernel
Documentation
//
// Syd: rock-solid application kernel
// src/unix.rs: UNIX domain socket address helpers
//
// Copyright (c) 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

//! UNIX domain socket address helpers

// SAFETY: This module has been liberated from unsafe code!
// Tests need unsafe for SockaddrStorage::from_raw.
#![deny(unsafe_code)]

use std::os::unix::ffi::OsStrExt;

use memchr::memchr;
use nix::sys::socket::{SockaddrLike, UnixAddr};

// Offset of sun_path inside struct sockaddr_un.
const SUN_PATH_OFFSET: usize = std::mem::offset_of!(libc::sockaddr_un, sun_path);

/// Extract pathname bytes from a `UnixAddr` without trailing NUL padding.
///
/// Returns `None` for abstract and unnamed sockets.
pub fn unix_path_bytes(addr: &UnixAddr) -> Option<&[u8]> {
    addr.path().map(|path| {
        let path = path.as_os_str().as_bytes();
        &path[..memchr(0, path).unwrap_or(path.len())]
    })
}

/// Compute the kernel-compatible address length for a `UnixAddr`.
#[expect(clippy::cast_possible_truncation)]
pub(crate) fn unix_addr_len(addr: &UnixAddr) -> libc::socklen_t {
    if let Some(path) = unix_path_bytes(addr) {
        SUN_PATH_OFFSET.saturating_add(path.len()).saturating_add(1) as libc::socklen_t
    } else if addr.as_abstract().is_some() {
        addr.len()
    } else {
        SUN_PATH_OFFSET as libc::socklen_t
    }
}

#[cfg(test)]
#[expect(unsafe_code)]
mod tests {
    use std::mem::size_of;

    use nix::sys::socket::{SockaddrLike, SockaddrStorage};

    use super::*;

    #[test]
    fn test_unix_path_bytes_1() {
        let addr = UnixAddr::new("/tmp/test.sock").unwrap();
        assert_eq!(unix_path_bytes(&addr), Some(b"/tmp/test.sock".as_slice()));
    }

    #[test]
    fn test_unix_path_bytes_2() {
        let addr = UnixAddr::new("/a").unwrap();
        assert_eq!(unix_path_bytes(&addr), Some(b"/a".as_slice()));
    }

    #[test]
    fn test_unix_path_bytes_3() {
        let long = "/".to_owned() + &"x".repeat(106);
        assert_eq!(long.len(), 107);
        let addr = UnixAddr::new(long.as_str()).unwrap();
        assert_eq!(unix_path_bytes(&addr).unwrap(), long.as_bytes());
    }

    #[test]
    fn test_unix_path_bytes_4() {
        let addr = UnixAddr::new_unnamed();
        assert_eq!(unix_path_bytes(&addr), None);
    }

    #[test]
    fn test_unix_path_bytes_5() {
        let addr = UnixAddr::new_abstract(b"foo").unwrap();
        assert_eq!(unix_path_bytes(&addr), None);
    }

    #[test]
    fn test_unix_path_bytes_6() {
        let addr = UnixAddr::new("/tmp/test.sock").unwrap();
        let full_len = size_of::<libc::sockaddr_un>() as libc::socklen_t;
        let storage =
            unsafe { SockaddrStorage::from_raw(addr.as_ptr().cast(), Some(full_len)) }.unwrap();
        let recovered = storage.as_unix_addr().unwrap();

        assert!(recovered.path().unwrap().as_os_str().as_bytes().len() > 14);
        assert_eq!(
            unix_path_bytes(recovered),
            Some(b"/tmp/test.sock".as_slice())
        );
    }

    #[test]
    fn test_unix_path_bytes_7() {
        let addr = UnixAddr::new("/x").unwrap();
        let full_len = size_of::<libc::sockaddr_un>() as libc::socklen_t;
        let storage =
            unsafe { SockaddrStorage::from_raw(addr.as_ptr().cast(), Some(full_len)) }.unwrap();
        let recovered = storage.as_unix_addr().unwrap();
        assert_eq!(unix_path_bytes(recovered), Some(b"/x".as_slice()));
    }

    #[test]
    fn test_unix_addr_len_1() {
        let addr = UnixAddr::new("/tmp/test.sock").unwrap();
        assert_eq!(unix_addr_len(&addr), 17);
    }

    #[test]
    fn test_unix_addr_len_2() {
        let addr = UnixAddr::new("/tmp/.syd_addrlen_srv.sock").unwrap();
        assert_eq!(unix_addr_len(&addr), 29);
    }

    #[test]
    fn test_unix_addr_len_3() {
        let addr = UnixAddr::new_unnamed();
        assert_eq!(unix_addr_len(&addr), SUN_PATH_OFFSET as libc::socklen_t);
        assert_eq!(unix_addr_len(&addr), 2);
    }

    #[test]
    fn test_unix_addr_len_4() {
        let addr = UnixAddr::new_abstract(b"foo").unwrap();
        assert_eq!(unix_addr_len(&addr), addr.len());
    }

    #[test]
    fn test_unix_addr_len_5() {
        let addr = UnixAddr::new_abstract(b"").unwrap();
        assert_eq!(unix_addr_len(&addr), addr.len());
    }

    #[test]
    fn test_unix_addr_len_6() {
        let addr = UnixAddr::new("/tmp/.syd_addrlen_srv.sock").unwrap();
        let full_len = size_of::<libc::sockaddr_un>() as libc::socklen_t;
        let storage =
            unsafe { SockaddrStorage::from_raw(addr.as_ptr().cast(), Some(full_len)) }.unwrap();
        let recovered = storage.as_unix_addr().unwrap();

        assert_eq!(recovered.len(), full_len);
        assert_eq!(unix_addr_len(recovered), 29);
    }

    #[test]
    fn test_unix_addr_len_7() {
        let addr = UnixAddr::new("/a").unwrap();
        let full_len = size_of::<libc::sockaddr_un>() as libc::socklen_t;
        let storage =
            unsafe { SockaddrStorage::from_raw(addr.as_ptr().cast(), Some(full_len)) }.unwrap();
        let recovered = storage.as_unix_addr().unwrap();
        assert_eq!(unix_addr_len(recovered), 5);
    }

    #[test]
    fn test_unix_addr_len_8() {
        let long = "/".to_owned() + &"x".repeat(106);
        let addr = UnixAddr::new(long.as_str()).unwrap();
        let full_len = size_of::<libc::sockaddr_un>() as libc::socklen_t;
        let storage =
            unsafe { SockaddrStorage::from_raw(addr.as_ptr().cast(), Some(full_len)) }.unwrap();
        let recovered = storage.as_unix_addr().unwrap();
        assert_eq!(unix_addr_len(recovered), 110);
    }

    #[test]
    fn test_unix_addr_len_9() {
        for path in [
            "/a",
            "/tmp/x",
            "/tmp/test.sock",
            "/tmp/.syd_addrlen_da.sock",
            "/tmp/.syd_addrlen_srv.sock",
            "/run/user/1000/bus",
            "/var/run/nscd/socket",
        ] {
            let expected = (SUN_PATH_OFFSET + path.len() + 1) as libc::socklen_t;
            let addr = UnixAddr::new(path).unwrap();
            assert_eq!(unix_addr_len(&addr), expected);

            let full_len = size_of::<libc::sockaddr_un>() as libc::socklen_t;
            let storage =
                unsafe { SockaddrStorage::from_raw(addr.as_ptr().cast(), Some(full_len)) }.unwrap();
            let recovered = storage.as_unix_addr().unwrap();
            assert_eq!(unix_addr_len(recovered), expected);
        }
    }
}