namaste 0.9.0

Simple locks between processes
Documentation
// License: see LICENSE file at root directory of `master` branch

//! # Namaste

use std::{
    io::{self, Error, ErrorKind},
    mem,
};

use crate::Id;

type SunFamily = u16;

// This cast has tests covered
const AF_UNIX: SunFamily = libc::AF_UNIX as SunFamily;

/// # Namaste
///
/// - Required features: _abstract-linux-sockets_.
///
/// ## Usage
///
/// You can call [`make()`][::make()] and pass your ID to that function. When done, simply drop it via `drop()`.
///
/// ## Notes
///
/// - This struct uses Linux's abstract sockets underneath (see `man 7 unix` for details).
/// - After successfully made, it keeps a socket file descriptor inside. When the struct is being dropped, it tries to close that file
///   descriptor, and prints out errors to stderr, if any.
/// - No _compilation limit_ is placed on this truct. But it has only been tested on Linux. Compilations on other platforms such as Windows,
///   macOS... are not tested yet.
/// - Functionalities have only been tested on Linux.
/// - Currently, [`std::os::unix::net`][r::std::os::unix::net] does not (yet) support abstract sockets. If support would be added, this
///   implementation would likely to be switched to using that, instead of [`libc`][crate:libc].
///
/// [::make()]: #method.make
/// [r::std::os::unix::net]: https://doc.rust-lang.org/std/os/unix/net/index.html
/// [crate:libc]: https://crates.io/crates/libc
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct Namaste {

    /// # Socket file descriptor
    socket_fd: i32,

}

impl Namaste {

    /// # Makes new instance
    ///
    /// An error is returned if calling to functions in `libc` fails. Currently, error kind is simply [`Other`][r::ErrorKind::Other]. Error
    /// message will contain the original `libc` function name, function's error code and _possible_ `errno`.
    ///
    /// [r::ErrorKind::Other]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.Other
    pub unsafe fn make(id: Id) -> io::Result<Self> {
        let socket_fd = match libc::socket(libc::AF_UNIX, libc::SOCK_STREAM | libc::SOCK_NONBLOCK, 0) {
            err_code @ -1 => return Err(Error::new(
                ErrorKind::Other, format!("libc::socket() returned {:?}; possible errno: {:?}", err_code, *libc::__errno_location()),
            )),
            other => other,
        };

        let mut addr = libc::sockaddr_un {
            sun_family: AF_UNIX,
            sun_path: [0; mem::size_of::<libc::sockaddr_un>() - mem::size_of::<SunFamily>()],
        };
        for (i, b) in id.into_iter().enumerate() {
            // Array bounds and cast have tests covered
            addr.sun_path[i + 1] = *b as i8;
        }

        match {
            let addr = mem::transmute::<&libc::sockaddr_un, &libc::sockaddr>(&addr);
            // Below cast from usize to u32 has tests covered
            libc::bind(socket_fd, addr, mem::size_of::<libc::sockaddr_un>() as u32)
        } {
            0 => Ok(Self { socket_fd }),
            other => {
                close_fd(socket_fd)?;
                Err(Error::new(ErrorKind::Other, format!("libc::bind() returned {:?}; possible errno: {:?}", other, *libc::__errno_location())))
            },
        }
    }

}

impl Drop for Namaste {

    fn drop(&mut self) {
        if let Err(err) = unsafe {
            close_fd(self.socket_fd)
        } {
            __e!("{}", err);
        }
    }

}

/// # Closes a file descriptor
unsafe fn close_fd(fd: i32) -> io::Result<()> {
    match libc::close(fd) {
        0 => Ok(()),
        other => Err(Error::new(
            ErrorKind::Other, format!("libc::close() on #{} returned {:?}; possible errno: {:?}", fd, other, *libc::__errno_location()),
        )),
    }
}

#[test]
fn test_namaste() {
    // Test for self::AF_UNIX
    let af_unix: i32 = libc::AF_UNIX;
    assert!(af_unix >= 0 && af_unix as u32 <= u32::from(SunFamily::max_value()));

    // Test for stuff in Namaste::make()
    assert!(mem::size_of::<SunFamily>() + mem::size_of::<Id>() + 1 < mem::size_of::<libc::sockaddr_un>());
    assert!(mem::size_of::<libc::sockaddr_un>() < usize::from(u8::max_value()));

    let mut id = tiny_keccak::sha3_512(crate::ID.as_bytes());
    for _ in 0..1000 {
        id = tiny_keccak::sha3_512(&id);

        // Test for cast between [u8] and [i8] in Namaste::make()
        let mut i8_array = [0_i8; mem::size_of::<Id>()];
        for (i, b) in id.iter().enumerate() {
            i8_array[i] = *b as i8;
        }

        let mut u8_array = [0_u8; mem::size_of::<Id>()];
        for (i, n) in i8_array.iter().enumerate() {
            u8_array[i] = *n as u8;
        }
        assert_eq!(&id[..], &u8_array[..]);

        // Test Namaste
        unsafe {
            let namaste = Namaste::make(id).unwrap();

            // Let's try it several times
            for _ in 0..3 {
                assert!(Namaste::make(id).is_err());
            }

            drop(namaste);

            // Let's try it several times
            for _ in 0..3 {
                assert!(Namaste::make(id).is_ok());
            }
        }
    }
}