use libc::{mount, umount, MS_BIND, MS_PRIVATE};
use std::ffi::CString;
use tracing::*;

/// A mounted directory.
#[derive(Debug)]
pub struct Mount {
    target: Option<CString>,
}

/// Mount the existing mounted root private to the current namespace,
/// i.e. stop propagating mount events with the parent namespace.
pub fn make_root_private() -> Result<(), std::io::Error> {
    unsafe {
        let err = mount(
            std::ptr::null(),
            b"/\0".as_ptr() as *const i8,
            std::ptr::null(),
            MS_PRIVATE,
            std::ptr::null(),
        );
        if err == 0 {
            Ok(())
        } else {
            Err(std::io::Error::last_os_error())
        }
    }
}

impl Mount {
    /// Create a bind mount, i.e. a directory into another one.
    pub fn bind<S: AsRef<std::path::Path>, T: AsRef<std::path::Path>>(
        source: S,
        target: T,
    ) -> Result<Mount, std::io::Error> {
        let target = target.as_ref().to_str().unwrap();
        let target = CString::new(target).unwrap();
        let source = source.as_ref().to_str().unwrap();
        let source = CString::new(source).unwrap();
        unsafe {
            let err = mount(
                source.as_ptr(),
                target.as_ptr(),
                std::ptr::null(),
                MS_BIND,
                std::ptr::null(),
            );
            if err == 0 {
                return Ok(Mount {
                    target: Some(target),
                });
            }
            Err(std::io::Error::last_os_error())
        }
    }

    /// Create ramfs mounted onto `target`.
    pub fn ramfs<T: AsRef<std::path::Path>>(target: T) -> Result<Mount, std::io::Error> {
        let target = target.as_ref().to_str().unwrap();
        let target = CString::new(target).unwrap();
        unsafe {
            let err = mount(
                b"ramfs\0".as_ptr() as *const i8,
                target.as_ptr(),
                b"ramfs\0".as_ptr() as *const i8,
                0,
                std::ptr::null(),
            );
            if err == 0 {
                Ok(Mount {
                    target: Some(target),
                })
            } else {
                Err(std::io::Error::last_os_error())
            }
        }
    }
}

impl Drop for Mount {
    fn drop(&mut self) {
        if let Some(target) = self.target.take() {
            unsafe {
                // Attention au Chroot!
                trace!("umount");
                let r = umount(target.as_ptr());
                trace!(
                    "umount done {:?} {:?} {:?}",
                    target,
                    r,
                    std::io::Error::last_os_error()
                );
            }
        }
    }
}