/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Logic to run at exit (`std::process:exit`).
//! Intended to be used as an alternative to Python's
//! `except KeyboardInterrupt`.

use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::Weak;

use once_cell::sync::Lazy;

/// Call `drop` on drop if `ignored` is `false`.
pub struct AtExit {
    drop: Option<Box<dyn FnOnce() + Send + Sync>>,
    ignored: AtomicBool,
}

/// Reference to `AtExit`. Can be used to cancel it.
/// Dropping `AtExitRef` does not call `drop`.
pub struct AtExitRef {
    // Private to prevent Arc::upgrade.
    inner: Weak<AtExit>,
}

/// Central place for global `AtExit`s.
/// Use `drop_queued` to drop them.
static AT_EXIT_QUEUED: Lazy<Mutex<Vec<Arc<AtExit>>>> = Lazy::new(Default::default);

impl Drop for AtExit {
    fn drop(&mut self) {
        let mut drop = None;
        std::mem::swap(&mut drop, &mut self.drop);
        if let Some(func) = drop {
            if !self.ignored.load(Ordering::Acquire) {
                func();
            }
        }
    }
}

impl AtExit {
    /// Create `AtExit` that calls `drop` on drop.
    ///
    /// The `AtExit` is intended to be a (Rust) stack variable that gets dropped
    /// when exiting the (Rust) function. `exit()` will unroll stacks so `drop`
    /// will be called if another thread calls `exit()`.
    ///
    /// If you don't want the drop behavior on (Rust) function return, or have
    /// to store the `AtExit` in heap, consider using `queued()`. For example,
    /// in a CPython function, the Python objects that wraps the `AtExit` are
    /// not on (Rust) stack and won't be cleaned up on `exit()`. So for Python
    /// logic `queued()` should probably be always used.
    pub fn new(drop: Box<dyn FnOnce() + Send + Sync>) -> Self {
        Self {
            drop: Some(drop),
            ignored: AtomicBool::new(false),
        }
    }

    /// Move the `AtExit` to a global queue.
    ///
    /// Return `AtExitRef`, which can be used to cancel the `drop`.
    /// Dropping `AtExitRef` wouldn't trigger `drop`.
    ///
    /// The global queue can be dropped by `drop_queued`.
    pub fn queued(self) -> AtExitRef {
        let arc = Arc::new(self);
        let weak = Arc::downgrade(&arc);
        let mut queue = AT_EXIT_QUEUED.lock().unwrap();
        queue.push(arc);
        AtExitRef { inner: weak }
    }

    /// Skip calling `drop` on drop.
    pub fn cancel(&self) {
        self.ignored.store(true, Ordering::Release);
    }
}

impl AtExitRef {
    /// Skip calling `drop` on drop.
    pub fn cancel(&self) {
        if let Some(arc) = self.inner.upgrade() {
            arc.cancel();
        }
    }
}

/// Drop `AtExit`s that are previously `queued`.
/// This is usually called at the end of a program.
pub fn drop_queued() {
    if let Ok(mut lock) = AT_EXIT_QUEUED.lock() {
        let mut to_drop: Vec<_> = lock.drain(..).collect();
        // Unlock first so drop(to_drop) can call `drop_queued`
        // without deadlock.
        drop(lock);
        // Drop in reverse push order (first push last drop)
        // as if it is a stack.
        to_drop.drain(..).rev().for_each(drop);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn bool_atexit() -> (Arc<AtomicBool>, AtExit) {
        let v = Arc::new(AtomicBool::new(false));
        let a = {
            let v = v.clone();
            AtExit::new(Box::new(move || v.store(true, Ordering::Release)))
        };
        (v, a)
    }

    #[test]
    fn test_drop() {
        let (v, a) = bool_atexit();
        drop(a);
        assert!(v.load(Ordering::Acquire));
    }

    #[test]
    fn test_cancel() {
        let (v, a) = bool_atexit();
        a.cancel();
        drop(a);
        assert!(!v.load(Ordering::Acquire));
    }

    #[test]
    fn test_queued() {
        let (v1, a1) = bool_atexit();
        let (v2, a2) = bool_atexit();
        let r1 = a1.queued();
        let r2 = a2.queued();
        r1.cancel();
        assert!(!v1.load(Ordering::Acquire));
        assert!(!v2.load(Ordering::Acquire));
        drop_queued();
        assert!(!v1.load(Ordering::Acquire));
        assert!(v2.load(Ordering::Acquire));
        drop(r2);

        // Does not deadlock if drop_queued is called by AtExit
        // inside drop_queued.
        let r3 = AtExit::new(Box::new(drop_queued));
        let _r3 = r3.queued();
        drop_queued();
    }

    #[test]
    fn test_queued_drop_order() {
        let drop_order = Arc::new(Mutex::new(Vec::new()));
        let push_atexit = |value: u8| -> AtExit {
            let drop_order = drop_order.clone();
            AtExit::new(Box::new(move || {
                drop_order.lock().unwrap().push(value);
            }))
        };

        let a1 = push_atexit(1);
        let a2 = push_atexit(2);
        let a3 = push_atexit(3);
        a1.queued();
        a3.queued();
        a2.queued();

        drop_queued();
        assert_eq!(drop_order.lock().unwrap().clone(), [2, 3, 1]);
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::collections::HashSet;

/// Return whether plain mode is active, similar to python ui.plain().
pub fn is_plain(feature: Option<&str>) -> bool {
    let plain = identity::try_env_var("PLAIN");
    let plain_except = identity::try_env_var("PLAINEXCEPT");

    if plain.is_err() && plain_except.is_err() {
        return false;
    }

    if let Some(feature) = feature {
        !plain_except
            .unwrap_or_default()
            .split(',')
            .any(|s| s == feature)
    } else {
        true
    }
}

pub fn exceptions() -> HashSet<String> {
    match identity::try_env_var("PLAINEXCEPT") {
        Ok(value) => value.split(',').map(|s| s.to_string()).collect(),
        Err(_) => HashSet::new(),
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

extern crate proc_macro;
use proc_macro::Delimiter;
use proc_macro::Group;
use proc_macro::Ident;
use proc_macro::Punct;
use proc_macro::Spacing;
use proc_macro::Span;
use proc_macro::TokenStream;
use proc_macro::TokenTree;

/// Attribute version of `define_ipc!`.
#[proc_macro_attribute]
pub fn ipc(attr: TokenStream, stream: TokenStream) -> TokenStream {
    let mut new_stream = TokenStream::new();
    // Wrap in `::nodeipc::define_ipc! { ... }`
    if attr.to_string() != "test" {
        // Skip `::nodeipc::` for `#[ipc(test)]`.
        // `::nodeipc` does not resolve inside `nodeipc`.
        new_stream.extend([
            TokenTree::Punct(Punct::new(':', Spacing::Joint)),
            TokenTree::Punct(Punct::new(':', Spacing::Alone)),
            TokenTree::Ident(Ident::new("nodeipc", Span::call_site())),
            TokenTree::Punct(Punct::new(':', Spacing::Joint)),
            TokenTree::Punct(Punct::new(':', Spacing::Alone)),
        ]);
    }
    new_stream.extend([
        TokenTree::Ident(Ident::new("define_ipc", Span::call_site())),
        TokenTree::Punct(Punct::new('!', Spacing::Alone)),
        TokenTree::Group(Group::new(Delimiter::Brace, stream)),
    ]);
    new_stream
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use serde_json::json;
use serde_json::Value;

fn main() {
    if let Some(ipc) = nodeipc::get_singleton() {
        ipc.send("HELLO FROM CHILD").unwrap();
        while let Some(message) = ipc.recv::<Value>().unwrap() {
            println!("[Child] Got message from parent: {:?}", message);
            if message.as_str() == Some("BYE") {
                break;
            } else {
                ipc.send(json!(["Echo from child", message])).unwrap();
            }
        }
    } else {
        println!("[Child] IPC is not setup. Is this process started by nodejs with 'ipc'?");
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::env;
use std::io::Write;
use std::mem;
use std::process::Command;

use filedescriptor::AsRawFileDescriptor;
use filedescriptor::FileDescriptor;
use filedescriptor::FromRawFileDescriptor;
use filedescriptor::RawFileDescriptor;
use nodeipc::NodeIpc;
use serde_json::json;
use serde_json::Value;

fn main() {
    let is_child = env::args().nth(1).as_deref() == Some("child");

    if is_child {
        child_main();
    } else {
        parent_main();
    }
}

fn child_main() {
    let handle = env::args().nth(2).unwrap().parse::<u64>().unwrap() as RawFileDescriptor;
    println!("Child: started with IPC handle {:x}", handle as u64);

    let ipc = NodeIpc::from_raw_file_descriptor(handle).unwrap();
    // Needed to read from the inherited socketpair handle.
    maybe_init_winsock();

    let message: Option<String> = ipc.recv().unwrap();
    println!("Child: got message {:?}", message);

    let payload = ipc.recv_fd_vec().unwrap();
    println!("Child: got sendfd payload {:?}", &payload);

    for fd in payload.raw_fds {
        #[cfg(windows)]
        if fd.is_null() {
            continue;
        }
        println!("Child: writing \"something\\n\" to fd {:?}", fd);
        let mut fd = unsafe { FileDescriptor::from_raw_file_descriptor(fd) };
        if let Err(e) = fd.write_all(b"something\n") {
            println!("Child: write failed: {:?}", e);
        }
        // Do not make FileDescriptor close the handle. We might still need it for `println!`.
        mem::forget(fd);
    }

    ipc.recv_stdio().unwrap();
    println!("Child: got stdio");
    eprintln!("Child: write to stderr");

    if let Some(ipc) = nodeipc::get_singleton() {
        println!("Child: has IPC singleton");
        ipc.send("HELLO FROM CHILD").unwrap();
        while let Some(message) = ipc.recv::<Value>().unwrap() {
            println!("Child: Got message: {:?}", message);
            if message.as_str() == Some("BYE") {
                break;
            } else {
                ipc.send(json!(["Echo from child", message])).unwrap();
            }
        }
    } else {
        println!("Child: no IPC singleton");
    }
}

fn parent_main() {
    let (server_socket, client_socket) = filedescriptor::socketpair().unwrap();
    let client_raw_fd = client_socket.as_raw_file_descriptor();

    // Make the client_socket inheritable.
    #[cfg(windows)]
    unsafe {
        use winapi::um::handleapi::SetHandleInformation;
        use winapi::um::winbase::HANDLE_FLAG_INHERIT;
        SetHandleInformation(client_raw_fd as _, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
    }
    #[cfg(unix)]
    unsafe {
        let flags = libc::fcntl(client_raw_fd, libc::F_GETFD);
        let new_flags = flags & !libc::FD_CLOEXEC;
        let ret = libc::fcntl(client_raw_fd, libc::F_SETFD, new_flags);
        assert!(ret == 0);
    }

    let ipc = NodeIpc::from_socket(server_socket).unwrap();

    println!("Parent: spawning child");
    let mut child = Command::new(env::current_exe().unwrap())
        .arg("child")
        .arg((client_raw_fd as u64).to_string())
        .spawn()
        .unwrap();

    drop(client_socket);

    println!("Parent: sending hello");
    ipc.send("hello").unwrap();

    println!("Parent: sending a.txt file descriptor");
    let file = std::fs::OpenOptions::new()
        .create(true)
        .append(true)
        .open("a.txt")
        .unwrap();
    let fds = [file.as_raw_file_descriptor()];
    ipc.send_fd_vec(&fds).unwrap();

    println!("Parent: sending stdio");
    ipc.send_stdio().unwrap();

    println!("Parent: waiting for child to exit");
    child.wait().unwrap();
}

fn maybe_init_winsock() {
    #[cfg(windows)]
    unsafe {
        use winapi::um::winsock2::WSAStartup;
        use winapi::um::winsock2::WSADATA;

        let mut data: WSADATA = mem::zeroed();
        let ret = WSAStartup(
            0x202, // version 2.2
            &mut data,
        );
        assert_eq!(ret, 0, "failed to initialize winsock");
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::borrow::Cow;

pub use anyhow;
pub use paste::paste;
use serde::Deserialize;
use serde::Serialize;
pub use serde_json;
pub use serde_json::Value;

use crate::NodeIpc;

/// Serialiable message over IPC.
#[derive(Serialize, Deserialize)]
pub enum IpcMessage {
    /// The sender requests the receiver to evaluate a function call.
    Call(CallArgs),
    /// The sender completes a `Call` and is returning its result.
    Return(ReturnArgs),
}

/// Calling a function. Name and arguments.
#[derive(Serialize, Deserialize)]
pub struct CallArgs(pub Cow<'static, str>, pub Value);

/// Return value of a function call.
#[derive(Serialize, Deserialize)]
pub struct ReturnArgs(pub Value);

/// Indicates a struct contains `NodeIpc`.
pub trait HasIpc {
    /// Get the `NodeIpc`.
    fn ipc(&self) -> &NodeIpc;
}

/// "Dynamic" dispatch.
pub trait Call {
    /// "Dynamic" dispatch. Input and output are normalized to `Value`.
    fn call(&self, value: CallArgs) -> anyhow::Result<Value>;
}

/// Main serve loop. Auto implemented for `HasIpc` + `Call`.
pub trait Serve {
    /// Serve a client until it disconnects.
    fn serve(&self) -> anyhow::Result<()>;
}

impl<T: HasIpc + Call> Serve for T {
    fn serve(&self) -> anyhow::Result<()> {
        let ipc = self.ipc();
        loop {
            let message = match ipc.recv::<IpcMessage>() {
                Ok(Some(v)) => v,
                // Err(_) can also be caused by "end of stream".
                Ok(None) | Err(_) => break,
            };
            match message {
                IpcMessage::Call(call) => {
                    let value = self.call(call)?;
                    ipc.send(IpcMessage::Return(ReturnArgs(value)))?;
                }
                IpcMessage::Return(ReturnArgs(ret)) => {
                    anyhow::bail!("Unmatched return {:?}", ret);
                }
            }
        }
        Ok(())
    }
}

/// Derive IPC methods.
///
/// For example, the following code:
///
/// ```rust,ignore
/// define_ipc! {
///     impl PlusService {
///         fn plus(&self, a: i32, b: i32) -> i32 {
///             a + b
///         }
///     }
/// }
/// ```
///
/// will generate both the server and client code:
///
/// ```rust,ignore
/// // Server. Requires `HasIpc`. Use `Serve::serve` to start the service.
/// impl Call for PlusService {
///     ...
/// }
///
/// // Client. Requires `HasIpc` and `Call` (in case the server calls methods back).
/// trait PlusServiceIpc {
///     fn plus(&self, a: i32, b: i32) -> anyhow::Result<i32> {
///         ...
///     }
/// }
/// ```
///
/// The IPC is designed to be blocking, sequential, and bi-directional.
/// The client also provides methods (`Call`) the server can call.
/// See the unit test for an example.
#[macro_export]
macro_rules! define_ipc {
    {
        impl $impl_name:ident $(< $impl_lifetime:lifetime >)? {
            $(
                $(#[$fn_meta:meta])*
                $fn_vis:vis fn $fn_name:ident (&$self:ident $(, $fn_arg_name:ident : $fn_arg_type:ty)* ) -> $fn_ret:ty
                {
                    $($fn_body:tt)*
                }
            )*
        }
    } => {
        // Original code.
        impl $impl_name $(< $impl_lifetime >)? {
            $(
                $(#[$fn_meta])*
                $fn_vis fn $fn_name (&$self $(, $fn_arg_name : $fn_arg_type)* ) -> $fn_ret
                {
                    $($fn_body)*
                }
            )*
        }

        // Derived logic for server (Serve::serve).
        impl $crate::derive::Call for $impl_name $(< $impl_lifetime >)? {
            fn call(&self, value: $crate::derive::CallArgs) -> $crate::derive::anyhow::Result<$crate::derive::Value> {
                #[allow(unused_variables)]
                let $crate::derive::CallArgs(fn_name, fn_args) = value;
                $(
                    if fn_name == stringify!($fn_name) {
                        let ( $($fn_arg_name,)* ) = $crate::derive::serde_json::from_value(fn_args)?;
                        let result = self. $fn_name (  $($fn_arg_name,)* );
                        let value = $crate::derive::serde_json::to_value(result)?;
                        return Ok(value);
                    }
                )*
                $crate::derive::anyhow::bail!("{} received unknown method {}", stringify!($impl_name), fn_name);
            }
        }

        // Derived logic for client (NodeIpc).
        $crate::derive::paste! {
            /// Generated trait to call methods via ipc.
            pub trait [< $impl_name Ipc >] {
                $(
                    fn $fn_name (&self $(, $fn_arg_name : $fn_arg_type)* ) -> $crate::derive::anyhow::Result<$fn_ret>;
                )*
            }

            impl<T> [< $impl_name Ipc >] for T
            where
                T: $crate::derive::HasIpc,
                T: $crate::derive::Call,
            {
                $(
                    fn $fn_name (&self $(, $fn_arg_name : $fn_arg_type)* ) -> $crate::derive::anyhow::Result<$fn_ret> {
                        use $crate::derive::IpcMessage;
                        use $crate::derive::ReturnArgs;
                        use $crate::derive::serde_json;
                        let args = serde_json::to_value(($($fn_arg_name,)*))?;
                        let msg = IpcMessage::Call($crate::derive::CallArgs(::std::borrow::Cow::Borrowed(stringify!($fn_name)), args));
                        self.ipc().send(msg)?;
                        loop {
                            let received = self.ipc().recv::<$crate::derive::IpcMessage>()?;
                            match received {
                                Some(IpcMessage::Call(call)) => {
                                    let value = self.call(call)?;
                                    self.ipc().send(IpcMessage::Return(ReturnArgs(value)))?;
                                }
                                Some(IpcMessage::Return(ReturnArgs(value))) => {
                                    let ret: $fn_ret = serde_json::from_value(value)?;
                                    return Ok(ret);
                                }
                                None => $crate::derive::anyhow::bail!("Did not get a response of {}::{}", stringify!($impl_name), stringify!($fn_name))
                            }
                        }
                    }
                )*
            }
        }
    };
}

#[cfg(test)]
mod tests {
    use std::thread;

    use super::*;
    use crate::ipc;
    use crate::NodeIpc;

    #[test]
    fn test_mutural_recursive() {
        struct EvenService<'a>(NodeIpc, &'a (dyn (Fn(u32) -> Option<bool>) + Send + Sync));
        struct OddService(NodeIpc);

        define_ipc! {
            impl EvenService<'_> {
                fn is_even(&self, n: u32) -> bool {
                    if let Some(result) = (self.1)(n) {
                        return result;
                    }

                    // self.is_odd() is provided by the generated OddServiceIpc trait.
                    self.is_odd(n - 1).unwrap()
                }
            }
        }

        #[ipc(test)]
        impl OddService {
            pub fn is_odd(&self, n: u32) -> bool {
                // self.is_even() is provided by the generated EvenServiceIpc trait.
                self.is_even(n - 1).unwrap()
            }
        }

        impl<'a> HasIpc for EvenService<'a> {
            fn ipc(&self) -> &NodeIpc {
                &self.0
            }
        }

        impl HasIpc for OddService {
            fn ipc(&self) -> &NodeIpc {
                &self.0
            }
        }

        let (server_socket, client_socket) = filedescriptor::socketpair().unwrap();
        let server_ipc = NodeIpc::from_socket(server_socket).unwrap();
        let client_ipc = NodeIpc::from_socket(client_socket).unwrap();

        let is_even = |v: u32| match v {
            1 => Some(false),
            0 => Some(true),
            _ => None,
        };
        let even_service = EvenService(server_ipc, &is_even);
        let odd_service = OddService(client_ipc);

        thread::scope(|s| {
            s.spawn(|| {
                even_service.serve().unwrap();
            });

            // This will call the EvenService (server).
            // The EvenService (server) will ask OddService (client) questions too.
            let v = odd_service.is_odd(10);
            assert!(!v);
            drop(odd_service);
        })
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! NDJSON (new line delimited json) IPC over a duplex file descriptor
//! that is compatible with nodejs child process IPC.
//!
//! Note this library is meant to be simple and intentionally avoids complex
//! parts of the nodejs IPC, including:
//! - The "advanced" serialization [1]. It is implemented in V8's C++ code
//!   and does not have a Rust equivalent.
//! - The ability to send file descriptor around. This is complicated [2]
//!   and not yet interesting for Sapling's use-cases.
//!
//! [1]: https://github.com/nodejs/node/blob/fe514bf960ca1243b71657af662e7df29f5b57cf/lib/internal/child_process/serialization.js#L54
//! [2]: https://github.com/nodejs/node/commit/db6253f94a7e499b2bacf5998a246c7cd06f7245

pub mod derive;
pub(crate) mod nodeipc;
mod sendfd;
pub(crate) mod singleton;

/// Shortcut for the `define_ipc!` macro.
pub use nodeipc_derive::ipc;

pub use self::nodeipc::NodeIpc;
pub use self::singleton::get_singleton;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::borrow::Cow;
use std::env;
use std::fmt;
use std::io;
use std::io::BufRead;
use std::io::Read;
use std::io::Write;
use std::mem::ManuallyDrop;
use std::sync::Mutex;

use anyhow::Context;
use filedescriptor::FileDescriptor;
use filedescriptor::FromRawFileDescriptor;
use filedescriptor::IntoRawSocketDescriptor;
use filedescriptor::RawFileDescriptor;
use serde::de::DeserializeOwned;
use serde::Serialize;

// 0, 1, 2, ..., file descriptor used by libc (or msvcrt, ucrt).
//
// This is different from RawFileDescriptor, which is
// RawHandle on Windows, and requires conversion using
// `libc::get_osfhandle`.
pub type LibcFd = libc::c_int;

/// State needed to communicate with nodejs `child_process` IPC.
/// Search `'ipc'` in https://nodejs.org/api/child_process.html
/// for details.
///
/// Under the hood, the IPC uses a single duplex file descriptor
/// for communication. Messages are in new line delimited JSON by
/// default.
pub struct NodeIpc {
    // Mutex is used so the static singleton is easier to use
    // (send and recv do not take &mut self).
    // `r` and `w` share a same file descriptor. `FileDescriptor` closes the underlying
    // fd on drop. Use `ManuallyDrop` to avoid duplicated closing.
    pub(crate) w: Mutex<ManuallyDrop<FileDescriptor>>,
    pub(crate) r: Mutex<io::BufReader<FileDescriptor>>,
    // Whether compatible with libuv.
    // If true, on Windows, we'll add extra frame headers per message.
    pub(crate) libuv_compat: bool,
}

impl NodeIpc {
    /// Optionally construct `NodeIpc` from the environment variables.
    /// The environment variables are set by nodejs when it spawns
    /// a child process with `ipc` in `stdio` option.
    ///
    /// On success, return the `NodeIpc` and removes the related environment
    /// variables so they don't leak to child processes accidentally.
    ///
    /// Returns `None` if the environment variables are not set, or if there
    /// are errors initializing internal states.
    pub fn from_env() -> Option<Self> {
        let fd_str = env::var_os("NODE_CHANNEL_FD")?;

        let serialization_mode = env::var_os("NODE_CHANNEL_SERIALIZATION_MODE");
        if let Some(mode) = serialization_mode.as_ref() {
            if mode != "json" {
                // Only JSON serialization is supported.
                return None;
            }
        }

        let libc_fd: LibcFd = fd_str.to_str()?.parse().ok()?;
        let ipc = Self::from_libc_fd(libc_fd).ok()?.with_libuv_compat();

        env::remove_var("NODE_CHANNEL_FD");
        if serialization_mode.is_some() {
            env::remove_var("NODE_CHANNEL_SERIALIZATION_MODE");
        }

        Some(ipc)
    }

    /// Initialize `NodeIpc` from a libc file descriptor directly.
    /// This is lower level than `from_env` and might be useful
    /// for non-nodejs use-cases. For example, setting up IPC
    /// channel with other processes for talking about other
    /// things.
    pub fn from_libc_fd(libc_fd: LibcFd) -> anyhow::Result<Self> {
        let os_raw_fd: RawFileDescriptor = libc_fd_to_raw_filedescriptor(libc_fd)?;
        Self::from_raw_file_descriptor(os_raw_fd)
    }

    /// Initialize `NodeIpc` from a OS raw file descriptor.
    /// The `RawFileDescriptor` is libc fd on POSIX, or `HANDLE` on Windows.
    ///
    /// Note: `NodeIpc` will close the underlying file descriptor on drop.
    /// So the callsite needs to "forget" the file descriptor if it does not
    /// want to close it twice.
    pub fn from_raw_file_descriptor(
        raw_file_descriptor: RawFileDescriptor,
    ) -> anyhow::Result<Self> {
        let get_fd = || unsafe { FileDescriptor::from_raw_file_descriptor(raw_file_descriptor) };
        let fd = get_fd();

        let r = Mutex::new(io::BufReader::new(fd));
        let w = Mutex::new(ManuallyDrop::new(get_fd()));
        let libuv_compat = false;
        let ipc = Self { r, w, libuv_compat };
        Ok(ipc)
    }

    /// Initialize `NodeIpc` from a socket-ish. A socket-ish provides the raw socket
    /// fd (or handle) that supports read and write.
    ///
    /// `NodeIpc` will consume `socket` and close the underlying socket on drop.
    pub fn from_socket(socket: impl IntoRawSocketDescriptor) -> anyhow::Result<Self> {
        let raw_file_descriptor = socket.into_socket_descriptor();
        Self::from_raw_file_descriptor(raw_file_descriptor as _)
    }

    /// Enable libuv pipe compatibility.
    /// On Windows, frame headers are added per message.
    /// On Unix, frame headers are disabled.
    pub fn with_libuv_compat(mut self) -> Self {
        self.libuv_compat = true;
        self
    }

    /// Send a message to the other side. Might block if the OS buffer is full
    /// and the other side is not receiving the message.
    pub fn send(&self, message: impl Serialize) -> anyhow::Result<()> {
        let mut line = serde_json::to_string(&message)
            .context("in NodeIpc::send, when converting message to JSON")?;
        line.push('\n');
        self.send_line(line)
    }

    /// Receive a message sent by the other side. Block if there are no new
    /// messages. Returns `None` if the other side has closed the channel.
    pub fn recv<V: DeserializeOwned>(&self) -> anyhow::Result<Option<V>> {
        let line = match self
            .recv_line()
            .context("in NodeIpc::recv, when reading line from file descriptor")?
        {
            None => return Ok(None),
            Some(line) => line,
        };
        let result = serde_json::from_str(&line).with_context(|| {
            format!(
                "in NodeIpc::recv, when deserializing {} to {}",
                FmtString(line.trim_end()),
                std::any::type_name::<V>(),
            )
        })?;
        Ok(Some(result))
    }

    /// Send a line. Blocking. The line should include the ending '\n'.
    #[inline(never)]
    fn send_line(&self, line: String) -> anyhow::Result<()> {
        let mut w = self.w.lock().unwrap();

        let payload = if cfg!(windows) || !self.libuv_compat {
            // Emulate libuv pipe frame header on Windows, or if libuv_compat is false.
            // The header provides a hint about the payload size, which can be useful
            // to prevent over-read that loses special control messages from sendmsg().
            // See https://github.com/libuv/libuv/blob/e1143f12657444c750e47ab3e1fb70ae6a030620/src/win/pipe.c#L1745-L1752
            let mut header = UvPipeWin32FrameHeader::default();
            let len = line.len();
            if len > 0 {
                assert!(len <= u32::MAX as usize);
                const UV__IPC_FRAME_HAS_DATA: u32 = 1;
                header.flags |= UV__IPC_FRAME_HAS_DATA;
                header.data_length = len as u32;
            }
            let header: [u8; std::mem::size_of::<UvPipeWin32FrameHeader>()] =
                unsafe { std::mem::transmute(header) };
            let mut payload = Vec::with_capacity(header.len() + len);
            payload.extend_from_slice(&header);
            payload.extend_from_slice(line.as_bytes());
            Cow::Owned(payload)
        } else {
            Cow::Borrowed(line.as_bytes())
        };

        w.write_all(payload.as_ref()).with_context(|| {
            format!(
                "in NodeIpc::send, when sending message {}",
                FmtString(line.trim_end())
            )
        })
    }

    /// Receive a line. Blocking. The line would include the ending '\n'.
    #[inline(never)]
    fn recv_line(&self) -> anyhow::Result<Option<String>> {
        let mut r = self.r.lock().unwrap();
        if cfg!(windows) || !self.libuv_compat {
            // Use unbuffered read to avoid over reading.
            assert!(r.buffer().is_empty());
            let r = r.get_mut();
            let mut libuv_pipe_frame_header = [0u8; std::mem::size_of::<UvPipeWin32FrameHeader>()];
            r.read_exact(&mut libuv_pipe_frame_header)
                .context("in NodeIpc::recv, when reading frame header")?;
            let header: UvPipeWin32FrameHeader =
                unsafe { std::mem::transmute(libuv_pipe_frame_header) };
            let size = header.data_length as usize;
            if size == 0 {
                return Ok(None);
            }
            let mut buf = vec![0u8; size];
            r.read_exact(&mut buf).context("in NodeIpc::recv")?;
            let line = String::from_utf8(buf).context("in NodeIpc::recv")?;
            return Ok(Some(line));
        }
        let mut line = String::new();
        let n = r.read_line(&mut line).context("in NodeIpc::recv")?;
        if n == 0 { Ok(None) } else { Ok(Some(line)) }
    }
}

fn libc_fd_to_raw_filedescriptor(fd: LibcFd) -> anyhow::Result<RawFileDescriptor> {
    #[cfg(windows)]
    {
        let handle = unsafe { libc::get_osfhandle(fd) };
        // -1: INVALID_HANDLE; -2: Not associated.
        anyhow::ensure!(
            handle != -1 && handle != -2,
            "libc fd {fd} is invalid ({handle})"
        );
        return Ok(handle as _);
    }

    #[cfg(unix)]
    {
        return Ok(fd);
    }

    #[allow(unreachable_code)]
    {
        unreachable!("unsupported platform");
    }
}

/// Adaptive format of a potentially long string.
struct FmtString<'a>(&'a str);

impl<'a> fmt::Display for FmtString<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = self.0;
        let len = s.len();
        if len == 0 {
            write!(f, "<an empty string>")
        } else if len > 128 {
            write!(f, "<string with {len} bytes>")
        } else if s.as_bytes().iter().any(|&b| b == 0) {
            self.0.fmt(f)
        } else {
            write!(f, "<string {:?}>", s.as_bytes())
        }
    }
}

// See https://github.com/libuv/libuv/blob/e1143f12657444c750e47ab3e1fb70ae6a030620/src/win/pipe.c#L74-L79
#[repr(C)]
#[derive(Default)]
struct UvPipeWin32FrameHeader {
    flags: u32,
    reversed1: u32,   // Ignored
    data_length: u32, // Must be zero if there is no data
    reserved2: u32,   // Must be zero
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::sync::Arc;

use anyhow::Context;
use filedescriptor::AsRawFileDescriptor;
use filedescriptor::RawFileDescriptor;
use serde::Deserialize;
use serde::Serialize;

use crate::nodeipc::NodeIpc;
use crate::singleton::IPC;

impl NodeIpc {
    /// Send a list of fd (or HANDLE on Windows).
    /// The other side can use `recv_fd_vec` to receive them.
    ///
    /// Note: if the other side is nodejs, it will not understand this special
    /// message. Nodejs has a different implementation. You can use
    /// `subprocess.send(message, sendHandle)` between nodejs processes.
    pub fn send_fd_vec(&self, fds: &[RawFileDescriptor]) -> anyhow::Result<()> {
        self.check_sendfd_compatibility()?;

        #[cfg(windows)]
        {
            use winapi::um::fileapi::GetFileType;
            use winapi::um::winbase::FILE_TYPE_CHAR;
            use winapi::um::winnt::HANDLE;

            let mut sendable_fds = Vec::with_capacity(fds.len());
            for &handle in fds {
                let file_type = unsafe { GetFileType(handle as HANDLE) };
                if file_type == FILE_TYPE_CHAR {
                    sendable_fds.push(std::ptr::null_mut());
                } else {
                    sendable_fds.push(handle);
                }
            }
            let payload = SendFdPayload {
                pid: std::process::id(),
                raw_fds: sendable_fds,
            };
            return self.send(payload);
        }

        #[cfg(unix)]
        {
            use std::mem;

            let fds_byte_size = mem::size_of_val(fds);
            let (mut cmsgs, opaque, hdr) = cmsg_vec_and_msghdr(fds_byte_size);

            let cmsg = &mut cmsgs[0];
            cmsg.cmsg_level = libc::SOL_SOCKET;
            cmsg.cmsg_type = libc::SCM_RIGHTS;
            cmsg.cmsg_len = unsafe { libc::CMSG_LEN(fds_byte_size as u32) } as _;

            // The man page warns that `CMSG_DATA` is not aligned (to `RawFileDescriptor`)
            // and suggests `memcpy`.
            let cmsg_data = unsafe { libc::CMSG_DATA(cmsg) };
            unsafe { libc::memcpy(cmsg_data as *mut _, fds.as_ptr() as *const _, fds_byte_size) };

            let w = self.w.lock().unwrap();
            let socket_fd = w.as_raw_file_descriptor();
            let ret = unsafe { libc::sendmsg(socket_fd, &hdr, 0) };
            if ret < 0 {
                return Err(std::io::Error::last_os_error())
                    .with_context(|| format!("Failed to sendmsg with fds {:?}", &fds));
            }
            drop((cmsgs, opaque));

            return Ok(());
        }

        #[allow(unreachable_code)]
        {
            anyhow::bail!("platform is not supported for sending file descriptors.");
        }
    }

    /// The other end of `send_fd_vec`. Return `SendFdPayload` with `raw_fds`
    /// containing the received fds.
    ///
    /// This cannot be used to receive handles sent via nodejs'
    /// `subprocess.send(message, sendHandle)` API.
    ///
    /// On POSIX systems, at most 32 fds can be received once.
    /// See `MAX_FD_COUNT` below.
    pub fn recv_fd_vec(&self) -> anyhow::Result<SendFdPayload> {
        self.check_sendfd_compatibility()?;

        #[cfg(windows)]
        {
            use winapi::um::handleapi::CloseHandle;
            use winapi::um::handleapi::DuplicateHandle;
            use winapi::um::processthreadsapi::GetCurrentProcess;
            use winapi::um::processthreadsapi::OpenProcess;
            use winapi::um::winnt::DUPLICATE_SAME_ACCESS;
            use winapi::um::winnt::HANDLE;
            use winapi::um::winnt::PROCESS_DUP_HANDLE;

            let mut payload: SendFdPayload = match self.recv::<SendFdPayload>()? {
                Some(payload) => payload,
                None => anyhow::bail!("Unexpected EOF when receiving fd"),
            };
            let mut received_handles = Vec::with_capacity(payload.raw_fds.len());
            let mut process_handle: HANDLE = std::ptr::null_mut();

            struct CloseOnDrop(HANDLE);
            impl Drop for CloseOnDrop {
                fn drop(&mut self) {
                    unsafe { CloseHandle(self.0) };
                }
            }

            let mut close_on_drop = None;

            for source_handle in payload.raw_fds {
                if source_handle.is_null() {
                    received_handles.push(source_handle);
                    continue;
                }
                // Open process for handle duplication.
                if process_handle.is_null() {
                    process_handle = unsafe {
                        OpenProcess(PROCESS_DUP_HANDLE, /* bInheritHandle */ 0, payload.pid)
                    };
                    if process_handle.is_null() {
                        return Err(std::io::Error::last_os_error()).with_context(|| {
                            format!("OpenProcess(pid={}) for DuplicateHandle", payload.pid)
                        });
                    }
                    close_on_drop = Some(CloseOnDrop(process_handle));
                }

                // DuplicateHandle can "steal" a handle from another process.
                let mut dup_handle = std::ptr::null_mut();
                let ret = unsafe {
                    DuplicateHandle(
                        process_handle,
                        source_handle as HANDLE,
                        GetCurrentProcess(),
                        &mut dup_handle,
                        /* dwDesiredAccess */ 0,
                        /* bInheritHandle */ 0,
                        DUPLICATE_SAME_ACCESS,
                    )
                };
                if ret == 0 {
                    return Err(std::io::Error::last_os_error()).with_context(|| {
                        format!(
                            "DuplicateHandle(pid={}, handle={:?})",
                            payload.pid, source_handle
                        )
                    });
                }
                received_handles.push(dup_handle as _);
            }

            // Replace raw_fds. They were in the source process. Now we got `received_handles` in this process.
            payload.raw_fds = received_handles;

            // Shut rustc up about unused variable or assignment.
            drop(close_on_drop);

            return Ok(payload);
        }

        #[cfg(unix)]
        unsafe {
            use std::mem;

            const MAX_FD_COUNT: usize = 32;
            let fds_byte_size = mem::size_of::<RawFileDescriptor>() * MAX_FD_COUNT;
            let (cmsgs, opaque, mut hdr) = cmsg_vec_and_msghdr(fds_byte_size);

            let r = self.r.lock().unwrap();
            assert!(r.buffer().is_empty());
            let socket_fd = r.get_ref().as_raw_file_descriptor();

            let ret = libc::recvmsg(socket_fd, &mut hdr, 0);
            if ret < 0 {
                return Err(std::io::Error::last_os_error()).context("Failed to recvmsg");
            }

            let mut received_fds = Vec::<RawFileDescriptor>::new();
            let mut cmsg = libc::CMSG_FIRSTHDR(&hdr);
            while !cmsg.is_null() {
                if (*cmsg).cmsg_level == libc::SOL_SOCKET && (*cmsg).cmsg_type == libc::SCM_RIGHTS {
                    let data = libc::CMSG_DATA(cmsg);
                    let data_size = (*cmsg).cmsg_len as usize - libc::CMSG_LEN(0) as usize;
                    let mut fds = vec![
                        -1 as RawFileDescriptor;
                        data_size / mem::size_of::<RawFileDescriptor>()
                    ];
                    assert_eq!(fds.len() * mem::size_of::<RawFileDescriptor>(), data_size);
                    // `data` might be not aligned. Use `memcpy` to copy.
                    libc::memcpy(fds.as_mut_ptr() as *mut _, data as *const _, data_size);
                    received_fds.extend(fds);
                }
                cmsg = libc::CMSG_NXTHDR(&hdr, cmsg);
            }
            drop((cmsgs, opaque));

            let payload = SendFdPayload {
                raw_fds: received_fds,
            };

            return Ok(payload);
        }

        #[allow(unreachable_code)]
        {
            anyhow::bail!("platform is not supported for receiving file descriptors.");
        }
    }

    /// Send the stdio and optionally the `NODE_CHANNEL_FD` file descriptor
    /// (the singleton) for the other end to "attach".
    pub fn send_stdio(&self) -> anyhow::Result<()> {
        let mut fds = Vec::with_capacity(4);

        #[cfg(windows)]
        unsafe {
            use winapi::um::processenv::GetStdHandle;

            fds.extend(
                stdio_constants()
                    .iter()
                    .map(|&h| GetStdHandle(h) as RawFileDescriptor),
            );
        }

        #[cfg(unix)]
        {
            fds.extend_from_slice(stdio_constants())
        }

        // Optionally, include the singleton file descriptor.
        if let Some(ipc) = crate::get_singleton() {
            if let Ok(w) = ipc.w.lock() {
                fds.push(w.as_raw_file_descriptor());
            }
        }

        self.send_fd_vec(&fds)?;
        Ok(())
    }

    /// Replace the stdio using the one sent from the other end.
    /// Update the singleton to match the sender.
    ///
    /// On Windows, the console might be replaced to the sender's.
    pub fn recv_stdio(&self) -> anyhow::Result<()> {
        let payload = self.recv_fd_vec()?;

        // Replace the stdio.
        #[cfg(unix)]
        {
            for (&received_fd, &std_fd) in payload.raw_fds.iter().zip(stdio_constants()) {
                if received_fd > 0 && received_fd != std_fd {
                    unsafe {
                        libc::dup2(received_fd, std_fd);
                        libc::close(received_fd);
                    }
                }
            }
        }

        #[cfg(windows)]
        {
            use winapi::um::processenv::SetStdHandle;
            use winapi::um::wincon::AttachConsole;
            use winapi::um::wincon::FreeConsole;

            if payload.raw_fds.iter().any(|h| h.is_null()) {
                unsafe {
                    FreeConsole();
                    AttachConsole(payload.pid)
                };
            }

            for (&received_handle, &std_constant) in payload.raw_fds.iter().zip(stdio_constants()) {
                if !received_handle.is_null() {
                    unsafe { SetStdHandle(std_constant, received_handle as _) };
                }
            }
        }

        // Replace the singleton.
        let mut ipc = IPC.write().unwrap();
        if let Some(&raw_fd) = payload.raw_fds.get(stdio_constants().len()) {
            let new_ipc = NodeIpc::from_raw_file_descriptor(raw_fd)?.with_libuv_compat();
            *ipc = Some(Some(Arc::new(new_ipc)));
        } else {
            *ipc = Some(None);
        }

        Ok(())
    }

    fn check_sendfd_compatibility(&self) -> anyhow::Result<()> {
        anyhow::ensure!(
            !self.libuv_compat,
            "send_fd_vec() and recv_fd_vec() are incompatible with libuv compatibility."
        );
        Ok(())
    }
}

#[derive(Serialize, Deserialize, Debug)]
pub struct SendFdPayload {
    #[cfg(windows)]
    /// Sender pid. Useful for `AttachConsole` on Windows.
    pub pid: u32,

    /// Raw handles or fds. Normalized as u64 for serialization.
    /// On Winodws, `null` is a placeholder indicating an absent handle.
    #[serde(with = "serde_raw_fds")]
    pub raw_fds: Vec<RawFileDescriptor>,
}

// Serialize raw fds as u64. Note serde_json can round-trip u64 just fine,
// even if f64 cannot represent the number.
mod serde_raw_fds {
    use filedescriptor::RawFileDescriptor;
    use serde::ser::SerializeSeq;
    use serde::Deserialize;
    use serde::Deserializer;
    use serde::Serializer;

    pub fn serialize<S>(raw_fds: &Vec<RawFileDescriptor>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut seq = serializer.serialize_seq(Some(raw_fds.len()))?;
        for &fd in raw_fds {
            let item = fd as u64;
            seq.serialize_element(&item)?;
        }
        seq.end()
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<RawFileDescriptor>, D::Error>
    where
        D: Deserializer<'de>,
    {
        // In theory we can implement a serde visitor to avoid allocating a Vec.
        // But it's too verbose.
        let seq: Vec<u64> = Deserialize::deserialize(deserializer)?;
        let raw_fds: Vec<RawFileDescriptor> =
            seq.into_iter().map(|v| v as RawFileDescriptor).collect();
        Ok(raw_fds)
    }
}

/// Create a `cmsg` buffer for `msghdr.msg_control`. Then create a `msghdr` that refers to `cmsg`
/// buffer, with a dummy iov buffer.
///
/// Returns `(cmsgs, opaque, msghdr)`.
/// The callsite needs to keep `cmsgs` and `opaque` alive before dropping `msghdr`,
/// since `msghdr` contains pointers to them.
/// The callsite might want to modify `cmsgs[0]` to customize the control message.
/// Note the `cmsgs` is actually a union with bytes payload, so `cmsgs[1]` should
/// not be used.
#[cfg(unix)]
fn cmsg_vec_and_msghdr(
    byte_size: usize,
) -> (Vec<libc::cmsghdr>, (impl Drop, impl Drop), libc::msghdr) {
    use std::mem;

    // See `man cmsg`.
    let cmsg_space: usize = unsafe { libc::CMSG_SPACE(byte_size as _) } as _;
    let cmsg_vec_len: usize = {
        let cmsghdr_byte_size = mem::size_of::<libc::cmsghdr>();
        (cmsg_space + cmsghdr_byte_size - 1) / cmsghdr_byte_size
    };
    assert!(cmsg_vec_len >= 1);
    let mut cmsg_buf: Vec<libc::cmsghdr> = vec![unsafe { mem::zeroed() }; cmsg_vec_len];

    // See `man sendmsg`. We need a non-empty dummy message to actually send information out.
    let mut iov_buf = vec![b'\n'];
    let mut dummy_iov = Box::new(libc::iovec {
        iov_base: iov_buf.as_mut_ptr() as *mut _,
        iov_len: iov_buf.len(),
    });
    let hdr = libc::msghdr {
        msg_iov: dummy_iov.as_mut(),
        msg_iovlen: 1,
        msg_control: cmsg_buf.as_mut_ptr() as *mut _,
        msg_controllen: (cmsg_buf.len() * mem::size_of_val(&cmsg_buf[0])) as _,
        ..unsafe { mem::zeroed() }
    };

    (cmsg_buf, (iov_buf, dummy_iov), hdr)
}

#[cfg(windows)]
type StdioConstant = winapi::shared::minwindef::DWORD;
#[cfg(unix)]
type StdioConstant = RawFileDescriptor;

fn stdio_constants() -> &'static [StdioConstant] {
    #[cfg(windows)]
    {
        use winapi::um::winbase;
        return &[
            winbase::STD_INPUT_HANDLE,
            winbase::STD_OUTPUT_HANDLE,
            winbase::STD_ERROR_HANDLE,
        ];
    }

    #[cfg(unix)]
    {
        return &[libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO];
    }

    #[allow(unreachable_code)]
    &[]
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::sync::Arc;
use std::sync::RwLock;

use crate::NodeIpc;

// None: Not initialized yet.
// Some(None): Initialized and got None.
// Some(Some(...)): Initialized and has the value.
pub(crate) static IPC: RwLock<Option<Option<Arc<NodeIpc>>>> = RwLock::new(None);

/// [`NodeIpc`] initialized from the environment variable on demand.
///
/// See [`NodeIpc::from_env`] for details. Accessing this state for
/// the first time might have side effects on environment variables.
/// So it's recommended to access this before creating threads.
pub fn get_singleton() -> Option<Arc<NodeIpc>> {
    let ipc = IPC.read().unwrap();
    if let Some(ref ipc) = *ipc {
        return ipc.clone();
    }
    drop(ipc);

    let mut ipc = IPC.write().unwrap();
    if let Some(ref ipc) = *ipc {
        return ipc.clone();
    }
    let new_ipc = NodeIpc::from_env().map(Arc::new);
    *ipc = Some(new_ipc.clone());
    new_ipc
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#[cfg(windows)]
use std::os::windows::process::CommandExt;
use std::process::Child;
use std::process::Command;
#[cfg(unix)]
use std::process::Stdio;

use anyhow::Result;
#[cfg(windows)]
use winapi::um::winbase::CREATE_NEW_PROCESS_GROUP;
#[cfg(windows)]
use winapi::um::winbase::CREATE_NO_WINDOW;

pub fn run_background(mut command: Command) -> Result<Child> {
    #[cfg(windows)]
    {
        command.creation_flags(CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP);
        command.spawn().map_err(|e| e.into())
    }
    #[cfg(unix)]
    {
        command.stderr(Stdio::null());
        command.stdout(Stdio::null());
        command.stdin(Stdio::null());
        command.spawn().map_err(|e| e.into())
    }
}

#[cfg(test)]
mod tests {
    use tempdir::TempDir;

    use super::*;

    #[test]
    fn test_basic() {
        let dir = TempDir::new("test_hgrcpath").unwrap();
        let file_path = dir.path().join("temp_file");

        #[cfg(unix)]
        let cmd = {
            let mut cmd = Command::new("/bin/sh");
            cmd.arg("-c")
                .arg(format!("echo foo > {}", file_path.to_string_lossy()));
            cmd
        };
        #[cfg(windows)]
        let cmd = {
            let mut cmd = Command::new("cmd.exe");
            cmd.arg("/c")
                .arg(format!("echo foo > {}", file_path.to_string_lossy()));
            cmd
        };

        let mut child = run_background(cmd).unwrap();
        child.wait().unwrap();

        assert!(file_path.exists());
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::path::Path;

use lazystr::LazyStr;

/// IOResult is a replacement for std::io::Result that forces you to
/// contextualize IO errors using `IOContext`.
pub type IOResult<T> = Result<T, IOError>;

#[derive(Debug, thiserror::Error)]
#[error("{msg}: {source}")]
pub struct IOError {
    msg: String,
    source: std::io::Error,
}

impl IOError {
    pub fn from_path(err: std::io::Error, msg: impl AsRef<str>, path: impl AsRef<Path>) -> IOError {
        IOError {
            msg: format!("{}: '{}'", msg.as_ref(), path.as_ref().display()),
            source: err,
        }
    }

    pub fn to_io_err(&self) -> std::io::Error {
        std::io::Error::new(
            self.source.kind(),
            format!("{}: {}", self.msg, self.source.to_string()),
        )
    }

    pub fn kind(&self) -> std::io::ErrorKind {
        self.source.kind()
    }

    pub fn is_not_found(&self) -> bool {
        self.source.kind() == std::io::ErrorKind::NotFound
    }
}

pub trait IOContext<T> {
    fn io_context(self, msg: impl LazyStr) -> Result<T, IOError>;

    fn path_context(self, msg: impl LazyStr, path: impl AsRef<Path>) -> Result<T, IOError>
    where
        Self: Sized,
    {
        self.io_context(|| format!("{}: '{}'", msg.to_str(), path.as_ref().display()))
    }
}

impl<T> IOContext<T> for std::io::Result<T> {
    fn io_context(self, msg: impl LazyStr) -> Result<T, IOError> {
        self.map_err(|err| IOError {
            msg: msg.to_str().to_string(),
            source: err,
        })
    }
}

impl<T> IOContext<T> for IOResult<T> {
    fn io_context(self, msg: impl LazyStr) -> Result<T, IOError> {
        self.map_err(|err| IOError {
            msg: format!("{}: {}", msg.to_str(), err.msg),
            source: err.source,
        })
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_context() {
        let res: std::io::Result<()> = Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists));
        let path: &Path = "/tmp/foo".as_ref();

        let res: IOResult<()> = res.path_context("error flimflamming file", path);

        // Can wrap further with more context.
        let res = res.io_context(|| "flibbertigibbet".to_string());

        let err = res.unwrap_err();
        assert_eq!(
            format!("{}", err),
            "flibbertigibbet: error flimflamming file: '/tmp/foo': entity already exists"
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::fs::File;
use std::fs::OpenOptions;
use std::io;
use std::path::Path;
use std::path::PathBuf;

#[cfg(unix)]
use once_cell::sync::Lazy;

use crate::errors::IOContext;
use crate::errors::IOResult;

#[cfg(unix)]
static UMASK: Lazy<u32> = Lazy::new(|| unsafe {
    let umask = libc::umask(0);
    libc::umask(umask);
    #[allow(clippy::useless_conversion)] // mode_t is u16 on mac and u32 on linux
    umask.into()
});

#[cfg(unix)]
pub fn apply_umask(mode: u32) -> u32 {
    mode & !*UMASK
}

pub fn atomic_write(path: &Path, op: impl FnOnce(&mut File) -> io::Result<()>) -> IOResult<File> {
    atomicfile::atomic_write(path, 0o644, false, op).path_context("error atomic writing file", path)
}

/// Open a path for atomic writing.
pub fn atomic_open(path: &Path) -> IOResult<atomicfile::AtomicFile> {
    atomicfile::AtomicFile::open(path, 0o644, false).path_context("error atomic opening file", path)
}

pub fn open(path: impl AsRef<Path>, mode: &str) -> IOResult<File> {
    let path = path.as_ref();

    let mut opts = OpenOptions::new();
    for opt in mode.chars() {
        match opt {
            'r' => opts.read(true),
            'w' => opts.write(true),
            'a' => opts.append(true),
            'c' => opts.create(true),
            't' => opts.truncate(true),
            'x' => opts.create_new(true),
            _ => {
                return Err(io::Error::new(
                    io::ErrorKind::Other,
                    format!("invalid open() mode {}", opt),
                ))
                .path_context("error opening file", path);
            }
        };
    }

    opts.open(path).path_context("error opening file", path)
}

pub fn create(path: impl AsRef<Path>) -> IOResult<File> {
    open(path, "wct")
}

pub fn read(path: impl AsRef<Path>) -> IOResult<Vec<u8>> {
    std::fs::read(path.as_ref()).path_context("error reading file", path.as_ref())
}

pub fn read_to_string(path: impl AsRef<Path>) -> IOResult<String> {
    std::fs::read_to_string(path.as_ref()).path_context("error reading file", path.as_ref())
}

pub fn exists(path: impl AsRef<Path>) -> IOResult<Option<std::fs::Metadata>> {
    match std::fs::metadata(path.as_ref()) {
        Ok(m) => Ok(Some(m)),
        Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
        Err(err) => Err(err).path_context("error reading file", path.as_ref()),
    }
}

pub fn read_link(path: impl AsRef<Path>) -> IOResult<PathBuf> {
    std::fs::read_link(path.as_ref()).path_context("error reading link", path.as_ref())
}

#[cfg(test)]
mod test {
    use anyhow::Result;
    use tempfile::tempdir;

    use super::*;

    #[test]
    fn test_open_context() -> Result<()> {
        let dir = tempdir()?;

        let path = dir.path().join("doesnt").join("exist");
        let err_str = format!("{}", open(&path, "cwa").unwrap_err());

        // Make sure error contains path.
        assert!(err_str.contains(path.display().to_string().as_str()));

        // And the original error.
        let orig_err = format!("{}", std::fs::File::open(&path).unwrap_err());
        assert!(err_str.contains(&orig_err));

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Utilities interacting with the OS.

// What functions belong here? The theme is similar to mercurial/util.py
//
// Cross platform filesystem / network / process / string / data structures
// utilities that cannot be trivially written using Rust stdlib.
//
// Prefer using the Rust stdlib directly if possible.

mod bgprocess;
pub mod errors;
pub mod file;
pub mod lock;
pub mod math;
pub mod path;
pub mod sys;
pub mod utf8;

pub use bgprocess::run_background;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::fs::File;
use std::path::Path;

use fs2::FileExt;

use crate::errors::IOContext;
use crate::errors::IOResult;
use crate::file::open;

/// RAII lock on a filesystem path.
#[derive(Debug)]
pub struct PathLock {
    file: File,
}

impl PathLock {
    /// Take an exclusive lock on `path`. The lock file will be created on
    /// demand.
    pub fn exclusive<P: AsRef<Path>>(path: P) -> IOResult<Self> {
        let file = open(path.as_ref(), "wc").io_context("lock file")?;
        file.lock_exclusive()
            .path_context("error locking file", path.as_ref())?;
        Ok(PathLock { file })
    }

    pub fn as_file(&self) -> &File {
        &self.file
    }
}

impl Drop for PathLock {
    fn drop(&mut self) {
        self.file.unlock().expect("unlock");
    }
}

#[cfg(test)]
mod tests {
    use std::sync::mpsc::channel;
    use std::thread;

    use super::*;

    #[test]
    fn test_path_lock() -> anyhow::Result<()> {
        let dir = tempfile::tempdir()?;
        let path = dir.path().join("a");
        let (tx, rx) = channel();
        const N: usize = 50;
        let threads: Vec<_> = (0..N)
            .map(|i| {
                let path = path.clone();
                let tx = tx.clone();
                thread::spawn(move || {
                    // Write 2 values that are the same, protected by the lock.
                    let _locked = PathLock::exclusive(&path);
                    tx.send(i).unwrap();
                    tx.send(i).unwrap();
                })
            })
            .collect();

        for thread in threads {
            thread.join().expect("joined");
        }

        for _ in 0..N {
            // Read 2 values. They should be the same.
            let v1 = rx.recv().unwrap();
            let v2 = rx.recv().unwrap();
            assert_eq!(v1, v2);
        }

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

/// Truncate `val` to the most significant `digits` decimal digits.
pub fn truncate_int(val: u64, digits: u32) -> u64 {
    let mut factor = 1;
    while val > factor {
        factor *= 10;
    }
    factor = (factor / 10_u64.pow(digits)).max(1);
    val / factor * factor
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_truncate_int() {
        assert_eq!(truncate_int(0, 0), 0);
        assert_eq!(truncate_int(1, 1), 1);
        assert_eq!(truncate_int(11, 1), 10);
        assert_eq!(truncate_int(19, 1), 10);
        assert_eq!(truncate_int(123456, 1), 100000);
        assert_eq!(truncate_int(123456, 3), 123000);
        assert_eq!(truncate_int(123456, 7), 123456);
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Path-related utilities.

use std::borrow::Cow;
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::fs::remove_file as fs_remove_file;
use std::io;
use std::io::ErrorKind;
use std::io::Write;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::Component;
use std::path::Path;
use std::path::PathBuf;

use crate::errors::IOContext;
use crate::errors::IOResult;

/// Pick a random file name `path.$RAND.atomic` as `real_path`. Write `data` to
/// it.  Then modify the symlink `path` to point to `real_path`.  Attempt to
/// delete files that are no longer referred.
///
/// Since the symlink itself cannot be mmap-ed on Windows, this function is
/// suitable for large mmap buffer on Windows. Without a symlink the mmap
/// file has to be removed first, otherwise it cannot be replaced.
///
/// Unlike `tempfile::NamedTempFile`, this function does not `chmod` the file.
///
/// This function has a side effect of creating a `path.lock` file for
/// locking.
///
/// Attention: the deletion attempt is based on file name. So do not use
/// confusing file names like `path.0001.atomic` in the same directory.
pub fn atomic_write_symlink(path: &Path, data: &[u8]) -> IOResult<()> {
    let append_name = |suffix: &str| -> PathBuf {
        let mut s = path.to_path_buf().into_os_string();
        s.push(suffix);
        s.into()
    };
    let temp_name = || -> PathBuf { append_name(&format!(".{:x}.atomic", rand::random::<u32>())) };

    // Protect racy write operations by a lock.
    let _lock = crate::lock::PathLock::exclusive(&append_name(".lock"))?;

    // Pick a name. Open the file.
    let (real_path, mut file) = loop {
        let real_path = temp_name();
        match fs::OpenOptions::new()
            .create_new(true)
            .write(true)
            .open(&real_path)
        {
            Ok(file) => break Ok((real_path, file)),
            Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue, // try another file name
            Err(e) => {
                break Err(e).path_context("error opening atomic symlink real path", &real_path);
            }
        }
    }?;
    let real_file_name = real_path
        .file_name()
        .expect("real_path should have a file name");

    // Write the content.
    file.write_all(data)
        .path_context("error writing atomic symlink data to real path", &real_path)?;
    drop(file);

    // Update the symlink by creating a temporary symlink and rename it.
    let symlink_tmp_path = loop {
        let symlink_path = temp_name();
        match symlink_file(Path::new(real_file_name), &symlink_path) {
            Ok(()) => break Ok(symlink_path),
            Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue, // try another file name
            Err(e) => {
                break Err(e).path_context("error creating temp atomic symlink", &symlink_path);
            }
        }
    }?;

    // Overwrite the original symlink. This works on both Windows and Linux.
    fs::rename(&symlink_tmp_path, path).path_context("error renaming temp atomic symlink", path)?;

    // Scan. Remove unreferenced files.
    let _ = (|| -> IOResult<()> {
        let looks_like_atomic = |s: &OsStr, prefix: &OsStr| -> bool {
            if let (Some(s), Some(prefix)) = (s.to_str(), prefix.to_str()) {
                s.starts_with(prefix) && s.ends_with(".atomic")
            } else {
                false
            }
        };
        if let (Some(dir), Some(prefix)) = (path.parent(), path.file_name()) {
            for entry in fs::read_dir(dir).path_context("error reading atomic symlink dir", dir)? {
                let entry = entry.path_context("error reading atomic symlink dir entry", dir)?;
                let name = entry.file_name();
                if name != prefix && looks_like_atomic(&name, prefix) && name != real_file_name {
                    let _ = remove_file(&entry.path());
                }
            }
        }
        Ok(())
    })();

    Ok(())
}

/// Create symlink for a file.
pub fn symlink_file(src: &Path, dst: &Path) -> io::Result<()> {
    #[cfg(windows)]
    return std::os::windows::fs::symlink_file(src, dst);
    #[cfg(unix)]
    return std::os::unix::fs::symlink(src, dst);
    #[cfg(all(not(unix), not(windows)))]
    return Err(io::Error::new(
        ErrorKind::Other,
        "symlink is not supported by the system",
    ));
}

/// Removes the UNC prefix `\\?\` on Windows. Does nothing on unices.
pub fn strip_unc_prefix(path: &Path) -> &Path {
    path.strip_prefix(r"\\?\").unwrap_or(path)
}

/// Return the absolute and normalized path without accessing the filesystem.
///
/// Unlike [`fs::canonicalize`], do not follow symlinks.
///
/// This function does not access the filesystem. Therefore it can behave
/// differently from the kernel or other library functions in corner cases.
/// For example:
///
/// - On some systems with symlink support, `foo/bar/..` and `foo` can be
///   different as seen by the kernel, if `foo/bar` is a symlink. This
///   function always returns `foo` in this case.
/// - On Windows, the official normalization rules are much more complicated.
///   See https://github.com/rust-lang/rust/pull/47363#issuecomment-357069527.
///   For example, this function cannot translate "drive relative" path like
///   "X:foo" to an absolute path.
///
/// Return an error if `std::env::current_dir()` fails or if this function
/// fails to produce an absolute path.
pub fn absolute(path: impl AsRef<Path>) -> io::Result<PathBuf> {
    let path = path.as_ref();
    let path = if path.is_absolute() {
        path.to_path_buf()
    } else {
        std::env::current_dir()?.join(path)
    };

    if !path.is_absolute() {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            format!("cannot get absolute path from {:?}", path),
        ));
    }

    Ok(normalize(&path))
}

/// Normalize path to collapse "..", ".", and duplicate separators. This
/// function does not access the filesystem, so it can return an
/// incorrect result if the path contains symlinks.
///
///     # use std::path::Path;
///     # use util::path::normalize;
///     assert_eq!(normalize("foo/.//bar/../baz/".as_ref()), Path::new("foo/baz"));
///
///     // Interesting edge cases:
///     assert_eq!(normalize("".as_ref()), Path::new("."));
///     assert_eq!(normalize("..".as_ref()), Path::new(".."));
///     assert_eq!(normalize("/..".as_ref()), Path::new("/"));
///
/// This behavior matches that of Python's `os.path.normpath` and Go's `path.Clean`.
pub fn normalize(path: &Path) -> PathBuf {
    let mut result = PathBuf::new();
    let mut poppable: usize = 0;
    let mut has_root = false;
    for component in path.components() {
        match component {
            Component::Normal(_) => {
                poppable += 1;
                result.push(component);
            }
            Component::RootDir => {
                has_root = true;
                result.push(component);
            }
            Component::Prefix(_) => {
                result.push(component);
            }
            Component::ParentDir => {
                if poppable > 0 {
                    result.pop();
                    poppable -= 1;
                } else if !has_root {
                    result.push(component);
                }
            }
            Component::CurDir => {}
        }
    }

    if result.as_os_str().is_empty() {
        return ".".into();
    }

    result
}

/// Remove the file pointed by `path`.
pub fn remove_file<P: AsRef<Path>>(path: P) -> io::Result<()> {
    let path = path.as_ref();
    // On Windows, try to rename the file before removing.
    // This allows re-creating a same file.
    // See https://boostgsoc13.github.io/boost.afio/doc/html/afio/FAQ/deleting_open_files.html
    let path: Cow<Path> = if cfg!(windows) {
        let tmp_path = path.with_extension(format!("tmp.{:x}", rand::random::<u16>()));
        fs::rename(path, &tmp_path)?;
        Cow::Owned(tmp_path)
    } else {
        Cow::Borrowed(path)
    };
    let result = fs_remove_file(&path);
    #[cfg(windows)]
    match &result {
        Err(e) if e.kind() == io::ErrorKind::PermissionDenied => {
            // This file might be mmapp-ed. Try a different way.
            if windows_remove_mmap_file(&path).is_ok() {
                return Ok(());
            }
        }
        _ => {}
    }
    result.map_err(Into::into)
}

/// Deletes a file even if it is being mmap-ed on Windows.
/// See https://stackoverflow.com/questions/54138684.
#[cfg(windows)]
fn windows_remove_mmap_file(path: &Path) -> io::Result<()> {
    use std::os::windows::ffi::OsStrExt;

    use winapi::shared::minwindef::FALSE;
    use winapi::um::fileapi::CreateFileW;
    use winapi::um::fileapi::OPEN_EXISTING;
    use winapi::um::handleapi::CloseHandle;
    use winapi::um::handleapi::INVALID_HANDLE_VALUE;
    use winapi::um::winbase::FILE_FLAG_DELETE_ON_CLOSE;
    use winapi::um::winnt::DELETE;
    use winapi::um::winnt::FILE_SHARE_DELETE;
    use winapi::um::winnt::FILE_SHARE_READ;
    use winapi::um::winnt::FILE_SHARE_WRITE;

    let wpath: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
    let handle = unsafe {
        CreateFileW(
            wpath.as_ptr(),
            DELETE,
            FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
            std::ptr::null_mut(),
            OPEN_EXISTING,
            FILE_FLAG_DELETE_ON_CLOSE,
            std::ptr::null_mut(),
        )
    };
    if handle == INVALID_HANDLE_VALUE {
        let err = io::Error::last_os_error();
        if err.kind() == io::ErrorKind::NotFound {
            Ok(())
        } else {
            Err(err)
        }
    } else if unsafe { CloseHandle(handle) } == FALSE {
        Err(io::Error::last_os_error())
    } else {
        Ok(())
    }
}

/// Create the directory with specified permission on UNIX systems. We create a temporary
/// directory at the parent directory of the the directory being created, run chmod to change the
/// permission then rename the temporary directory to the desired name to prevent leaking directory
/// with incorrect permissions.
#[cfg(unix)]
fn create_dir_with_mode_impl(path: &Path, mode: u32) -> io::Result<()> {
    if path.exists() {
        if path.is_dir() {
            // If metadata operation fails, it's fine.
            if let Ok(metadata) = path.metadata() {
                if metadata.permissions().mode() & mode != mode {
                    // We only attempt to fix the permission. If we can't, proceed.
                    // TODO: We should at least generate a warning here. We cannot because we
                    // cannot print messages in Mercurial Rust yet.
                    let _ = fs::set_permissions(path, fs::Permissions::from_mode(mode));
                }
            }
        }

        return Err(io::ErrorKind::AlreadyExists.into());
    }
    let parent = path.parent().ok_or_else(|| {
        io::Error::new(
            ErrorKind::NotFound,
            format!("`{:?}` does not have a parent directory", path),
        )
    })?;
    let temp = tempfile::TempDir::new_in(&parent)?;
    fs::set_permissions(&temp, fs::Permissions::from_mode(mode))?;

    let temp = temp.into_path();
    if let Err(e) = fs::rename(&temp, path) {
        // In the unlikely event where the rename fails, we attempt to clean up the
        // previously leaked temporary file before returning.
        let _ = fs::remove_dir(&temp);

        // The rename may fail if the desinated directory already exists and is not empty. In this
        // case it will return `ENOTEMPTY` instead of `EEXIST`. Rust does not have an
        // `io::ErrorKind` for such error, and it will be categorized into `ErrorKind::Other`. We
        // have to use `libc::ENOTEMPTY` because the integer value of `ENOTEMPTY` varies depends on
        // platform we are on.
        // Similarly, when the destinated directory is a file, we get `ENOTDIR` instead of `EEXIST`.
        match e.raw_os_error() {
            Some(libc::ENOTEMPTY) | Some(libc::ENOTDIR) => Err(ErrorKind::AlreadyExists.into()),
            _ => Err(e),
        }
    } else {
        Ok(())
    }
}

/// Create the directory. The mode argument is ignored on non-UNIX systems.
#[cfg(not(unix))]
fn create_dir_with_mode_impl(path: &Path, _mode: u32) -> io::Result<()> {
    fs::create_dir(path)
}

fn create_dir_with_mode(path: &Path, mode: u32) -> io::Result<()> {
    match create_dir_with_mode_impl(path, mode) {
        Ok(()) => Ok(()),
        Err(e) => {
            if e.kind() == ErrorKind::AlreadyExists && path.is_dir() {
                Ok(())
            } else {
                Err(e)
            }
        }
    }
}

/// Create the directory and ignore failures when a directory of the same name already exists.
pub fn create_dir(path: impl AsRef<Path>) -> io::Result<()> {
    create_dir_with_mode(path.as_ref(), 0o755)
}

/// Create the directory with group write permission on UNIX systems.
pub fn create_shared_dir(path: impl AsRef<Path>) -> io::Result<()> {
    create_dir_with_mode(path.as_ref(), 0o2775)
}

/// Create the directory and its ancestors. The mode argument is ignored on non-UNIX systems.
pub fn create_dir_all_with_mode(path: impl AsRef<Path>, mode: u32) -> io::Result<()> {
    let mut to_create = vec![path.as_ref()];
    while let Some(dir) = to_create.pop() {
        match create_dir_with_mode(dir, mode) {
            Ok(()) => continue,
            Err(err) if err.kind() == io::ErrorKind::AlreadyExists => continue,
            Err(err) if err.kind() == io::ErrorKind::NotFound => {
                to_create.push(dir);
                match dir.parent() {
                    Some(parent) => to_create.push(parent),
                    None => return Err(err),
                }
            }
            Err(err) => return Err(err),
        }
    }
    Ok(())
}

/// Create the directory and ancestors with group write permission on UNIX systems.
pub fn create_shared_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
    create_dir_all_with_mode(path, 0o2775)
}

/// Expand the user's home directory and any environment variables references in
/// the given path.
///
/// This function is designed to emulate the behavior of Mercurial's `util.expandpath`
/// function, which in turn uses Python's `os.path.expand{user,vars}` functions. This
/// results in behavior that is notably different from the default expansion behavior
/// of the `shellexpand` crate. In particular:
///
/// - If a reference to an environment variable is missing or invalid, the reference
///   is left unchanged in the resulting path rather than emitting an error.
///
/// - Home directory expansion explicitly happens after environment variable
///   expansion, meaning that if an environment variable is expanded into a
///   string starting with a tilde (`~`), the tilde will be expanded into the
///   user's home directory.
///
pub fn expand_path(path: impl AsRef<str>) -> PathBuf {
    expand_path_impl(path.as_ref(), |k| env::var(k).ok(), dirs::home_dir)
}

/// Same as `expand_path` but explicitly takes closures for environment variable
/// and home directory lookup for the sake of testability.
fn expand_path_impl<E, H>(path: &str, getenv: E, homedir: H) -> PathBuf
where
    E: FnMut(&str) -> Option<String>,
    H: FnOnce() -> Option<PathBuf>,
{
    // The shellexpand crate does not expand Windows environment variables
    // like `%PROGRAMDATA%`. We'd like to expand them too. So let's do some
    // pre-processing.
    //
    // XXX: Doing this preprocessing has the unfortunate side-effect that
    // if an environment variable fails to expand on Windows, the resulting
    // string will contain a UNIX-style environment variable reference.
    //
    // e.g., "/foo/%MISSING%/bar" will expand to "/foo/${MISSING}/bar"
    //
    // The current approach is good enough for now, but likely needs to
    // be improved later for correctness.
    let path = {
        let mut new_path = String::new();
        let mut is_starting = true;
        for ch in path.chars() {
            if ch == '%' {
                if is_starting {
                    new_path.push_str("${");
                } else {
                    new_path.push('}');
                }
                is_starting = !is_starting;
            } else if cfg!(windows) && ch == '/' {
                // Only on Windows, change "/" to "\" automatically.
                // This makes sure "%include /foo" works as expected.
                new_path.push('\\')
            } else {
                new_path.push(ch);
            }
        }
        new_path
    };

    let path = shellexpand::env_with_context_no_errors(&path, getenv);
    shellexpand::tilde_with_context(&path, homedir)
        .as_ref()
        .into()
}

/// Return a relative [`PathBuf`] to the path from the base path.
pub fn relativize(base: &Path, path: &Path) -> PathBuf {
    let mut base_iter = base.iter();
    let mut path_iter = path.iter();
    let mut rel_path = PathBuf::new();
    loop {
        match (base_iter.next(), path_iter.next()) {
            (Some(ref c), Some(ref p)) if c == p => continue,

            // Examples:
            // b: foo/bar/baz/
            // p: foo/bar/biz/buzz.html
            // This is the common case where we have to go up some number of directories
            // (so one "../" per unique path component of base) and then back down.
            //
            // b: foo/bar/baz/biz/
            // p: foo/bar/
            // If foo/bar was a file and then the user replaced it with a directory, and now
            // the user is in a subdirectory of that directory, then one "../" per unique path
            // component of base.
            (Some(_c), remaining_path) => {
                // Find the common prefix of path and base. Prefix with one "../" per unique
                // path component of base and then append the unique sequence of components from
                // path.
                rel_path.push(".."); // This is for the current component, c.
                for _ in base_iter {
                    rel_path.push("..");
                }

                if let Some(p) = remaining_path {
                    rel_path.push(p);
                    for component in path_iter {
                        rel_path.push(component);
                    }
                }
                break;
            }

            // Example:
            // b: foo/bar/
            // p: foo/bar/baz/buzz.html
            (None, Some(p)) => {
                rel_path.push(p);
                for component in path_iter {
                    rel_path.push(component);
                }
                break;
            }

            // Example:
            // b: foo/bar/baz/
            // p: foo/bar/baz/
            // If foo/bar/baz was a file and then the user replaced it with a directory, which
            // is also the user's current directory, then "" should be returned.
            (None, None) => {
                break;
            }
        }
    }

    rel_path
}

#[cfg(test)]
mod tests {
    use std::fs::File;

    use anyhow::Result;
    use tempfile::TempDir;

    use super::*;

    #[cfg(windows)]
    mod windows {
        use super::*;

        #[test]
        fn test_absolute_fullpath() {
            assert_eq!(absolute("C:/foo").unwrap(), Path::new("C:\\foo"));
            assert_eq!(
                absolute("x:\\a/b\\./.\\c").unwrap(),
                Path::new("x:\\a\\b\\c")
            );
            assert_eq!(
                absolute("y:/a/b\\../..\\c\\../d\\./.").unwrap(),
                Path::new("y:\\d")
            );
            assert_eq!(
                absolute("z:/a/b\\../..\\../..\\..").unwrap(),
                Path::new("z:\\")
            );
        }

        #[test]
        fn test_normalize_path() {
            assert_eq!(normalize(r"a/b\c\..\.".as_ref()), Path::new(r"a\b"));
            assert_eq!(normalize("z:/a//b/./".as_ref()), Path::new(r"z:\a\b"));
        }
    }

    #[cfg(unix)]
    mod unix {
        use super::*;

        #[test]
        fn test_absolute_fullpath() {
            assert_eq!(absolute("/a/./b\\c/../d/.").unwrap(), Path::new("/a/d"));
            assert_eq!(absolute("/a/../../../../b").unwrap(), Path::new("/b"));
            assert_eq!(absolute("/../../..").unwrap(), Path::new("/"));
            assert_eq!(absolute("/../../../").unwrap(), Path::new("/"));
            assert_eq!(
                absolute("//foo///bar//baz").unwrap(),
                Path::new("/foo/bar/baz")
            );
            assert_eq!(absolute("//").unwrap(), Path::new("/"));
        }

        #[test]
        fn test_normalize_path() {
            assert_eq!(normalize("/a/./b/../d/.".as_ref()), Path::new("/a/d"));
            assert_eq!(normalize("./a/b/c/../../".as_ref()), Path::new("a"));
            assert_eq!(normalize("".as_ref()), Path::new("."));
            assert_eq!(normalize(".".as_ref()), Path::new("."));
            assert_eq!(normalize("..".as_ref()), Path::new(".."));
            assert_eq!(normalize("/..".as_ref()), Path::new("/"));
            assert_eq!(normalize("/../..".as_ref()), Path::new("/"));
            assert_eq!(normalize("./..".as_ref()), Path::new(".."));
            assert_eq!(normalize("../../..".as_ref()), Path::new("../../.."));
            assert_eq!(normalize("////".as_ref()), Path::new("/"));
        }

        #[test]
        fn test_create_dir_mode() -> Result<()> {
            let tempdir = TempDir::new()?;
            let mut path = tempdir.path().to_path_buf();
            path.push("dir");
            create_dir(&path)?;
            assert!(path.is_dir());
            let metadata = path.metadata()?;
            assert_eq!(metadata.permissions().mode(), 0o40755);
            // check we don't have temporary directory left
            assert_eq!(tempdir.path().read_dir()?.count(), 1);
            Ok(())
        }

        #[test]
        fn test_create_shared_dir() -> Result<()> {
            let tempdir = TempDir::new()?;
            let mut path = tempdir.path().to_path_buf();
            path.push("shared");
            create_shared_dir(&path)?;
            assert!(path.is_dir());
            let metadata = path.metadata()?;
            assert_eq!(metadata.permissions().mode(), 0o42775);
            // check we don't have temporary directory left
            assert_eq!(tempdir.path().read_dir()?.count(), 1);
            Ok(())
        }

        #[test]
        fn test_fixup_perms() -> Result<()> {
            let tempdir = TempDir::new()?;
            let mut path = tempdir.path().to_path_buf();
            path.push("shared");

            // Create it without the SGID bit.
            create_dir_with_mode(&path, 0o775)?;
            let metadata = path.metadata()?;
            assert_eq!(metadata.permissions().mode(), 0o40775);

            // Fix it up.
            create_shared_dir(&path)?;
            let metadata = path.metadata()?;
            assert_eq!(metadata.permissions().mode(), 0o42775);

            Ok(())
        }
    }

    fn test_create_dir_all_fn(
        create_fn: &dyn Fn(&PathBuf) -> io::Result<()>,
        mode: u32,
    ) -> Result<()> {
        let tempdir = TempDir::new()?;
        let path = tempdir.path().join("foo").join("bar");
        create_fn(&path)?;
        assert!(path.is_dir());

        #[cfg(unix)]
        {
            let metadata = path.metadata()?;
            assert_eq!(metadata.permissions().mode(), mode);
        }

        Ok(())
    }

    #[test]
    #[cfg_attr(windows, ignore)] // FIXME: see D45267362, this needs a different test on Windows on 1.69+
    fn test_atomic_write_symlink() -> Result<()> {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path();
        let j = |name| -> PathBuf { path.join(name) };
        // a: unrelated file
        fs::write(j("a"), b"1")?;
        // c: symlink -> c.xxxx: "2"
        atomic_write_symlink(&j("c"), b"2")?;
        // Keep an mmap of the current "c".
        let file = fs::OpenOptions::new().read(true).open(&j("c"))?;
        let mmap = unsafe { memmap2::Mmap::map(&file) }?;
        // This file should be automatically deleted.
        fs::write(j("c.aaaa.atomic"), b"0")?;
        // Rewrite c with different data.
        atomic_write_symlink(&j("c"), b"3")?;
        // mmap has the old content.
        assert_eq!(mmap.as_ref(), b"2");
        // Reading c gets new data.
        assert_eq!(fs::read(&j("c"))?, b"3");
        // a: should exist
        assert!(j("a").exists());
        // 4 files: a, c, c.xxxx, c.lock
        let count = || {
            fs::read_dir(&path)
                .unwrap()
                .filter(|e| e.as_ref().unwrap().path().exists())
                .count()
        };
        assert_eq!(count(), 4);

        // It's possible to replace a non-symlink to a symlink.
        // (but this might fail if "a" is mmap-ed on Windows).
        atomic_write_symlink(&j("a"), b"4")?;
        // Exercise the GC logic a bit.
        atomic_write_symlink(&j("a"), b"5")?;
        atomic_write_symlink(&j("a"), b"6")?;
        // 6 files: a, a.xxxx, a.lock, c, c.xxxx, c.lock
        assert_eq!(count(), 6);
        Ok(())
    }

    #[test]
    fn test_mmap_delete() {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join("foo");
        fs::write(&path, b"bar").unwrap();
        let file = fs::OpenOptions::new().read(true).open(&path).unwrap();
        let _mmap = unsafe { memmap2::Mmap::map(&file) }.unwrap();
        remove_file(&path).unwrap();
        assert!(!path.exists());
        // The file can be recreated in-place.
        fs::write(&path, b"baz").unwrap();
    }

    #[test]
    fn test_create_dir_non_exist() -> Result<()> {
        let tempdir = TempDir::new()?;
        let mut path = tempdir.path().to_path_buf();
        path.push("dir");
        create_dir(&path)?;
        assert!(path.is_dir());
        Ok(())
    }

    #[test]
    fn test_create_dir_exist() -> Result<()> {
        let tempdir = TempDir::new()?;
        let mut path = tempdir.path().to_path_buf();
        path.push("dir");
        create_dir(&path)?;
        assert!(&path.is_dir());
        create_dir(&path)?;
        assert!(&path.is_dir());
        Ok(())
    }

    #[test]
    fn test_create_dir_file_exist() -> Result<()> {
        let tempdir = TempDir::new()?;
        let mut path = tempdir.path().to_path_buf();
        path.push("dir");
        File::create(&path)?;
        let err = create_dir(&path).unwrap_err();
        assert_eq!(err.kind(), ErrorKind::AlreadyExists);
        Ok(())
    }

    #[test]
    fn test_create_dir_with_nonexistent_parent() -> Result<()> {
        let tempdir = TempDir::new()?;
        let mut path = tempdir.path().to_path_buf();
        path.push("nonexistentparent");
        path.push("dir");
        let err = create_dir(&path).unwrap_err();
        assert_eq!(err.kind(), ErrorKind::NotFound);
        Ok(())
    }

    #[test]
    fn test_create_dir_without_empty_path() {
        let empty = Path::new("");
        let err = create_dir(&empty).unwrap_err();
        assert_eq!(err.kind(), ErrorKind::NotFound);
    }

    #[test]
    fn test_path_expansion() {
        fn getenv(key: &str) -> Option<String> {
            match key {
                "foo" => Some("~/a".into()),
                "bar" => Some("b".into()),
                _ => None,
            }
        }

        fn homedir() -> Option<PathBuf> {
            Some(PathBuf::from("/home/user"))
        }

        let path = "$foo/${bar}/$baz";
        let expected = PathBuf::from("/home/user/a/b/$baz");

        assert_eq!(expand_path_impl(&path, getenv, homedir), expected);
    }

    #[test]
    fn test_create_shared_dir_all() -> Result<()> {
        test_create_dir_all_fn(&|path| create_shared_dir_all(path), 0o42775)
    }

    #[test]
    fn test_create_dir_all_with_mode() -> Result<()> {
        test_create_dir_all_fn(&|path| create_dir_all_with_mode(path, 0o777), 0o40777)
    }

    #[test]
    fn test_relativize_absolute_paths() {
        let check = |base, path, expected| {
            assert_eq!(
                relativize(Path::new(base), Path::new(path)),
                Path::new(expected)
            );
        };
        check("/", "/", "");
        check("/foo/bar/baz", "/foo/bar/baz", "");
        check("/foo/bar", "/foo/bar/baz", "baz");
        check("/foo", "/foo/bar/baz", "bar/baz");
        check("/foo/bar/baz", "/foo/bar", "..");
        check("/foo/bar/baz", "/foo", "../..");
        check("/foo/bar/baz", "/foo/BAR", "../../BAR");
        check("/foo/bar/baz", "/foo/BAR/BAZ", "../../BAR/BAZ");
    }

    #[test]
    fn test_relativize_platform_absolute_paths() {
        // This test with Windows-style absolute paths on Windows, and Unix-style path on Unix
        let cwd = Path::new(".").canonicalize().unwrap();
        let result = relativize(&cwd, &cwd.join("a").join("b"));
        assert_eq!(result, Path::new("a").join("b"));
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use crate::errors::IOContext;
use crate::errors::IOResult;

pub fn hostname() -> IOResult<String> {
    Ok(hostname::get()
        .io_context("error getting hostname")?
        .to_string_lossy()
        .into())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::fmt::Write;

/// Convert input bytes to String by escaping invalid UTF-8 as escaped hex bytes
/// (e.g. "\xC3").
pub fn escape_non_utf8(mut input: &[u8]) -> String {
    let mut output = String::new();

    while !input.is_empty() {
        let (valid_len, invalid_len) = match std::str::from_utf8(input) {
            Ok(_) => (input.len(), 0),
            Err(err) => (
                err.valid_up_to(),
                err.error_len().unwrap_or(input.len() - err.valid_up_to()),
            ),
        };

        // input starts with valid_len bytes of utf8 followed by invalid_len
        // bytes of non-utf8 (followed by more bytes that need checking).

        output.push_str(unsafe { std::str::from_utf8_unchecked(&input[..valid_len]) });
        input = &input[valid_len..];

        for b in &input[..invalid_len] {
            write!(output, r"\x{:X}", b).unwrap();
        }
        input = &input[invalid_len..]
    }

    output
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_escape_non_utf8() {
        assert_eq!(escape_non_utf8(b""), "");
        assert_eq!(escape_non_utf8(b"hello"), "hello");

        assert_eq!(escape_non_utf8(b"\xc3"), r"\xC3");
        assert_eq!(escape_non_utf8(b"\xc3A"), r"\xC3A");
        assert_eq!(escape_non_utf8(b"A\xc3"), r"A\xC3");

        let nihao = "ä½ å¥½".as_bytes();
        assert_eq!(escape_non_utf8(nihao), "ä½ å¥½");
        assert_eq!(escape_non_utf8(&[b"\xc3", nihao].concat()), r"\xC3ä½ å¥½");
        assert_eq!(escape_non_utf8(&[nihao, b"\xc3"].concat()), r"ä½ å¥½\xC3");
        assert_eq!(
            escape_non_utf8(&[nihao, b"\xc3", nihao].concat()),
            r"ä½ å¥½\xC3ä½ å¥½"
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::collections::HashMap;
use std::env;
use std::ffi::OsStr;
use std::ffi::OsString;

use once_cell::sync::Lazy;
use parking_lot::Mutex;
use parking_lot::MutexGuard;

impl<'a> EnvLock<'a> {
    pub fn new(guard: MutexGuard<'a, ()>) -> Self {
        Self {
            vars: HashMap::new(),
            _guard: guard,
        }
    }

    pub fn set(&mut self, name: impl ToString, val: Option<&str>) {
        let var = self
            .vars
            .entry(name.to_string())
            .or_insert_with(|| ScopedEnvVar::new(name));

        var.set(val);
    }
}

pub struct ScopedEnvVar {
    name: String,
    old: Option<OsString>,
}

impl Drop for ScopedEnvVar {
    fn drop(&mut self) {
        self.set(self.old.as_deref())
    }
}

impl ScopedEnvVar {
    pub fn new(name: impl ToString) -> Self {
        let name = name.to_string();
        let old = env::var_os(&name);
        Self { name, old }
    }

    pub fn set(&self, val: Option<impl AsRef<OsStr>>) {
        match val {
            None => env::remove_var(&self.name),
            Some(val) => env::set_var(&self.name, val),
        }
    }
}

/// EnvLock allows changing env vars that are unset automatically when
/// the EnvLock goes out of scope.
pub struct EnvLock<'a> {
    // vars must be declared first to get dropped before mutex guard.
    vars: HashMap<String, ScopedEnvVar>,

    _guard: MutexGuard<'a, ()>,
}

static ENV_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));

/// Lock the environment and return an object that allows setting env
/// vars, undoing env changes when the object goes out of scope. This
/// should be used by tests that rely on particular environment
/// variable values that might be overwritten by other tests.
pub fn lock_env() -> EnvLock<'static> {
    EnvLock::new(ENV_LOCK.lock())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

pub mod envs;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Wraps low level uds in a higher level API for ease-of-use:
//! - Use `NodeIpc` to support structured messages and fd sending.
//! - Maintains file deletion transparently.

use std::fs;
use std::io;
use std::path::Path;
use std::path::PathBuf;

use nodeipc::NodeIpc;

use crate::uds;
use crate::uds::UnixListener;

/// Serve at the given path.
///
/// Return a iterator that yields a new `NodeIpc` for each client.
/// Dropping the iterator deletes the unix domain socket.
pub fn serve(path: PathBuf) -> anyhow::Result<Incoming> {
    let _ = fs::remove_file(&path);
    let listener = uds::bind(&path)?;
    let private_path = path.with_extension("private");
    let incoming = Incoming {
        listener,
        path,
        private_path,
    };

    Ok(incoming)
}

/// Connect to the given path.
///
/// Delete dead (ECONNREFUSED) files automatically.
pub fn connect(path: &Path) -> anyhow::Result<NodeIpc> {
    let stream = match uds::connect(path) {
        Ok(stream) => stream,
        Err(e) => {
            if let Some(e) = e.downcast_ref::<io::Error>() {
                if e.kind() == io::ErrorKind::ConnectionRefused {
                    // Dead socket (server was killed? reboot?). Remove it.
                    let _ = fs::remove_file(path);
                }
            }
            return Err(e);
        }
    };

    // Wrap in NodeIpc for ease of use.
    stream.set_nonblocking(false)?;
    stream.set_read_timeout(None)?;
    stream.set_write_timeout(None)?;
    let ipc = NodeIpc::from_socket(stream)?;

    Ok(ipc)
}

/// Similar to `std::net::Incoming` but:
/// - Owns `listener`. Does not use lifetime.
/// - Deletes the domain sockets on drop.
/// - Provides `get_is_alive_func()` to check if the socket file is still on disk.
pub struct Incoming {
    listener: UnixListener,
    path: PathBuf,
    private_path: PathBuf,
}

impl Incoming {
    /// Get a function to check if the socket file is still on disk.
    /// This can be useful to decide whether to exit in a loop.
    pub fn get_is_alive_func(&self) -> Box<dyn (Fn() -> bool) + Send + Sync + 'static> {
        let path = self.path.clone();
        let private_path = self.private_path.clone();
        Box::new(move || path.exists() || private_path.exists())
    }
}

impl Iterator for Incoming {
    type Item = NodeIpc;

    fn next(&mut self) -> Option<Self::Item> {
        let stream = self.listener.accept().ok()?.0;
        stream.set_read_timeout(None).ok()?;
        stream.set_write_timeout(None).ok()?;
        stream.set_nonblocking(false).ok()?;
        let ipc = NodeIpc::from_socket(stream).ok()?;
        Some(ipc)
    }
}

impl Drop for Incoming {
    fn drop(&mut self) {
        if fs::remove_file(&self.path).is_err() {
            let _ = fs::remove_file(&self.private_path);
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Unix-domain socket IPC, with `NodeIpc` integration.

pub mod ipc;
pub mod pool;
pub mod uds;