/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::io::Cursor;

use bytes::BytesMut;
use tokio_util::codec::Decoder;

/// Splits length-prefixed protobuf.
pub struct ProtobufSplitter;

impl Decoder for ProtobufSplitter {
    type Item = BytesMut;
    type Error = anyhow::Error;

    fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
        let orig_len = src.len();

        let data_length = match prost::decode_length_delimiter(Cursor::new(src.as_mut())) {
            Ok(length) => length,
            Err(..) => {
                // 10 bytes is the largest length of an encoded size
                if orig_len > 10 {
                    return Err(anyhow::anyhow!("Corrupted stream"));
                } else {
                    return Ok(None);
                }
            }
        };

        let required_len = prost::length_delimiter_len(data_length) + data_length;
        if orig_len < required_len {
            return Ok(None);
        }
        let data = src.split_to(required_len);

        Ok(Some(data))
    }
}

#[cfg(test)]
mod test {
    use anyhow::Context as _;
    use futures::stream::StreamExt;
    use prost::Message;
    use tokio_util::codec::FramedRead;
    use tokio_util::io::StreamReader;

    use super::*;

    #[derive(Message, PartialEq)]
    struct TestMessage {
        #[prost(string, optional, tag = "1")]
        payload: Option<String>,
    }

    #[tokio::test]
    async fn test_split() -> anyhow::Result<()> {
        let mut buffer = Vec::new();

        let foo = TestMessage {
            payload: Some("foo".to_owned()),
        };

        let bar = TestMessage {
            payload: Some("bar".to_owned()),
        };

        foo.encode_length_delimited(&mut buffer)?;
        bar.encode_length_delimited(&mut buffer)?;

        // 1 byte at a time.
        let stream = StreamReader::new(futures::stream::iter(
            buffer
                .into_iter()
                .map(|byte| std::io::Result::Ok(BytesMut::from([byte].as_slice()))),
        ));

        let mut stream = FramedRead::new(stream, ProtobufSplitter);
        assert_eq!(
            TestMessage::decode_length_delimited(stream.next().await.context("Missing `foo`")??)?,
            foo
        );
        assert_eq!(
            TestMessage::decode_length_delimited(stream.next().await.context("Missing `bar`")??)?,
            bar
        );
        assert!(stream.next().await.is_none());

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::fmt::Display;
use std::path::Path;
use std::path::PathBuf;

use anyhow::Context;
use gazebo::prelude::StrExt;

pub const BUCK_AUTH_TOKEN_HEADER: &str = "x-buck-auth-token";

#[derive(Debug, thiserror::Error)]
enum ConnectionTypeError {
    #[error("Failed to parse correct endpoint information {0}")]
    ParseError(String),
}

#[derive(Debug, Clone)]
pub enum ConnectionType {
    // TODO: remove Unix support a week after D40394826 lands.
    Uds { unix_socket: PathBuf },
    Tcp { port: u16 },
}

impl Display for ConnectionType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // NOTE: `Display` must be compatible with `parse`.
        match self {
            ConnectionType::Uds { unix_socket } => write!(f, "uds:{}", unix_socket.display()),
            ConnectionType::Tcp { port } => write!(f, "tcp:{}", port),
        }
    }
}

impl ConnectionType {
    pub fn parse(endpoint: &str) -> anyhow::Result<ConnectionType> {
        let (protocol, endpoint) = endpoint.split1(":");
        match protocol {
            "uds" => Ok(ConnectionType::Uds {
                unix_socket: Path::new(endpoint).to_path_buf(),
            }),
            "tcp" => Ok(ConnectionType::Tcp {
                port: endpoint
                    .parse()
                    .with_context(|| format!("port number is incorrect in `{}`", endpoint))?,
            }),
            _ => Err(ConnectionTypeError::ParseError(endpoint.to_owned()).into()),
        }
    }
}

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

    use crate::buckd_connection::ConnectionType;

    #[test]
    fn test_fmt_parse() {
        assert_eq!(
            "tcp:1719",
            &format!("{}", ConnectionType::Tcp { port: 1719 })
        );

        let path = if cfg!(windows) {
            PathBuf::from("c:\\path")
        } else {
            PathBuf::from("/path")
        };
        assert_eq!(
            &format!("uds:{}", path.display()),
            &format!("{}", ConnectionType::Uds { unix_socket: path })
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::borrow::Borrow;
use std::fmt;
use std::hash::Hash;
use std::hash::Hasher;
use std::io::Read;
use std::marker::PhantomData;
use std::sync::atomic::AtomicI64;
use std::sync::atomic::Ordering;
use std::sync::Arc;

use allocative::Allocative;
use chrono::DateTime;
use chrono::TimeZone;
use chrono::Utc;
use derivative::Derivative;
use derive_more::Display;
use digest::Digest;
use dupe::Clone_;
use dupe::Dupe;
use dupe::Dupe_;
use num_enum::TryFromPrimitive;
use once_cell::sync::Lazy;
use sha1::Sha1;
use sha2::Sha256;
use thiserror::Error;

/// The number of bytes required by a SHA-1 hash
pub const SHA1_SIZE: usize = 20;

/// The number of bytes required by a SHA-256 hash
pub const SHA256_SIZE: usize = 32;

/// The number of bytes required by a Blake3 hash
pub const BLAKE3_SIZE: usize = 32;

/// The bytes that make up a file digest.
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Allocative, Clone)]
pub enum RawDigest {
    // TODO: Perhaps this should be represented as a (DigestAlgorithmKind, [0;32])
    Sha1([u8; SHA1_SIZE]),
    Sha256([u8; SHA256_SIZE]),
    Blake3([u8; BLAKE3_SIZE]),
    Blake3Keyed([u8; BLAKE3_SIZE]),
}

// We consider copying 20 bytes is cheap enough not to qualify for Dupe
impl Dupe for RawDigest {}

impl RawDigest {
    pub fn as_bytes(&self) -> &[u8] {
        match self {
            Self::Sha1(x) => x,
            Self::Sha256(x) => x,
            Self::Blake3(x) => x,
            Self::Blake3Keyed(x) => x,
        }
    }

    pub fn algorithm(&self) -> DigestAlgorithmKind {
        match self {
            Self::Sha1(..) => DigestAlgorithmKind::Sha1,
            Self::Sha256(..) => DigestAlgorithmKind::Sha256,
            Self::Blake3(..) => DigestAlgorithmKind::Blake3,
            Self::Blake3Keyed(..) => DigestAlgorithmKind::Blake3Keyed,
        }
    }

    pub fn parse_sha1(data: &[u8]) -> Result<Self, CasDigestParseError> {
        let mut sha1 = [0; SHA1_SIZE];
        hex::decode_to_slice(data, &mut sha1).map_err(CasDigestParseError::InvalidSha1)?;
        Ok(RawDigest::Sha1(sha1))
    }

    pub fn parse_sha256(data: &[u8]) -> Result<Self, CasDigestParseError> {
        let mut sha256 = [0; SHA256_SIZE];
        hex::decode_to_slice(data, &mut sha256).map_err(CasDigestParseError::InvalidSha256)?;
        Ok(RawDigest::Sha256(sha256))
    }

    pub fn parse_blake3(data: &[u8]) -> Result<Self, CasDigestParseError> {
        let mut blake3 = [0; BLAKE3_SIZE];
        hex::decode_to_slice(data, &mut blake3).map_err(CasDigestParseError::InvalidBlake3)?;
        Ok(RawDigest::Blake3(blake3))
    }

    pub fn parse_blake3_keyed(data: &[u8]) -> Result<Self, CasDigestParseError> {
        let mut blake3 = [0; BLAKE3_SIZE];
        hex::decode_to_slice(data, &mut blake3).map_err(CasDigestParseError::InvalidBlake3)?;
        Ok(RawDigest::Blake3Keyed(blake3))
    }
}

impl fmt::Display for RawDigest {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", hex::encode(self.as_bytes()))
    }
}

/// The kind of digest algorithm associated with a digest. This tells you what kind of digest it
/// is, but it might not be sufficient in order to actually recreate the digest. For example, this
/// could contain keyed digests, but then you wouldn't have the key. We use this to store our
/// digest kind when it's informative-only, like in our materializer state on disk.
#[derive(
    Debug,
    Display,
    Eq,
    PartialEq,
    TryFromPrimitive,
    Copy,
    Clone,
    Dupe,
    Hash,
    Allocative
)]
#[repr(u8)]
pub enum DigestAlgorithmKind {
    #[display(fmt = "SHA1")]
    Sha1,
    #[display(fmt = "SHA256")]
    Sha256,
    #[display(fmt = "BLAKE3")]
    Blake3,
    #[display(fmt = "BLAKE3-KEYED")]
    Blake3Keyed,
}

#[derive(Error, Debug)]
#[error("Invalid Digest algorithm: `{0}`")]
pub struct InvalidDigestAlgorithmKind(String);

impl std::str::FromStr for DigestAlgorithmKind {
    type Err = InvalidDigestAlgorithmKind;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s == "SHA1" {
            return Ok(Self::Sha1);
        }

        if s == "SHA256" {
            return Ok(Self::Sha256);
        }

        if s == "BLAKE3" {
            return Ok(Self::Blake3);
        }

        if s == "BLAKE3-KEYED" {
            return Ok(Self::Blake3Keyed);
        }

        Err(InvalidDigestAlgorithmKind(s.to_owned()))
    }
}

/// An actual digest algorithm you can use to hash data.
#[derive(Debug, Eq, PartialEq, Copy, Clone, Dupe, Hash, Allocative)]
pub enum DigestAlgorithm {
    Sha1,
    Sha256,
    Blake3,
    Blake3Keyed { key: &'static [u8; 32] },
}

impl DigestAlgorithm {
    fn kind(self) -> DigestAlgorithmKind {
        match self {
            Self::Sha1 => DigestAlgorithmKind::Sha1,
            Self::Sha256 => DigestAlgorithmKind::Sha256,
            Self::Blake3 => DigestAlgorithmKind::Blake3,
            Self::Blake3Keyed { .. } => DigestAlgorithmKind::Blake3Keyed,
        }
    }
}

#[derive(Copy, Clone, Dupe, Debug, Allocative, Hash, Eq, PartialEq)]
pub struct CasDigestConfig {
    inner: &'static CasDigestConfigInner,
}

impl CasDigestConfig {
    pub fn testing_default() -> Self {
        static COMPAT: Lazy<CasDigestConfigInner> =
            Lazy::new(|| CasDigestConfigInner::new(vec![DigestAlgorithm::Sha1], None).unwrap());

        Self { inner: &COMPAT }
    }

    /// We just Box::leak this since we create one per daemon and as a result just use
    /// CasDigestConfig as a pointer.
    pub fn leak_new(
        algorithms: Vec<DigestAlgorithm>,
        preferred_source_algorithm: Option<DigestAlgorithm>,
    ) -> Result<Self, CasDigestConfigError> {
        let inner = Box::leak(Box::new(CasDigestConfigInner::new(
            algorithms,
            preferred_source_algorithm,
        )?));
        Ok(Self { inner })
    }

    /// Allow optimizing the empty file digest path, we do that by having the CasDigestConfig hold
    /// a cell for it (later in this stack).
    pub fn empty_file_digest(self) -> crate::file_ops::TrackedFileDigest {
        self.inner.empty_file_digest.dupe()
    }

    pub fn preferred_algorithm(self) -> DigestAlgorithm {
        self.inner.preferred_algorithm
    }

    pub fn digest160(self) -> Option<DigestAlgorithm> {
        self.inner.digest160
    }

    pub fn digest256(self) -> Option<DigestAlgorithm> {
        self.inner.digest256
    }

    pub fn allows_sha1(self) -> bool {
        self.inner.digest160 == Some(DigestAlgorithm::Sha1)
    }

    pub fn allows_sha256(self) -> bool {
        self.inner.digest256 == Some(DigestAlgorithm::Sha256)
    }

    pub fn allows_blake3_keyed(self) -> bool {
        matches!(
            self.inner.digest256,
            Some(DigestAlgorithm::Blake3Keyed { .. })
        )
    }

    /// Access the config for source files. Note that there is no method to go back to the
    /// non-source config.
    pub fn source_files_config(self) -> Self {
        match &self.inner.source {
            SourceFilesConfig::UseSelf => self,
            SourceFilesConfig::UseThis(other) => Self { inner: other },
        }
    }
}

#[derive(Debug, Allocative, Hash, Eq, PartialEq)]
struct CasDigestConfigInner {
    /// The algorithm we use for non-source files, action digests, etc.
    preferred_algorithm: DigestAlgorithm,
    digest160: Option<DigestAlgorithm>,
    digest256: Option<DigestAlgorithm>,
    empty_file_digest: crate::file_ops::TrackedFileDigest,
    /// A potentially different configuration to use when digesting source files.
    source: SourceFilesConfig,
}

#[derive(Debug, Allocative, Hash, Eq, PartialEq)]
enum SourceFilesConfig {
    UseSelf,
    UseThis(Box<CasDigestConfigInner>),
}

impl CasDigestConfigInner {
    /// Initialize a CasDigestConfigInner. The algorithms should be listed in decreasing order of
    /// preference.
    fn new(
        algorithms: Vec<DigestAlgorithm>,
        preferred_source_algorithm: Option<DigestAlgorithm>,
    ) -> Result<Self, CasDigestConfigError> {
        let preferred_algorithm = *algorithms
            .first()
            .ok_or(CasDigestConfigError::NotConfigured)?;

        let preferred_source_algorithm = preferred_source_algorithm
            .map(|a| {
                if algorithms.contains(&a) {
                    Ok(a)
                } else {
                    Err(CasDigestConfigError::InvalidPreferredSourceAlgorithm)
                }
            })
            .transpose()?;

        let mut digest160 = None;
        let mut digest256 = None;

        for algo in algorithms {
            let slot = match algo {
                DigestAlgorithm::Sha1 => &mut digest160,
                DigestAlgorithm::Sha256 => &mut digest256,
                DigestAlgorithm::Blake3 => &mut digest256,
                DigestAlgorithm::Blake3Keyed { .. } => &mut digest256,
            };

            if let Some(slot) = &slot {
                return Err(CasDigestConfigError::Conflict(*slot, algo));
            }

            *slot = Some(algo);
        }

        let empty_file_digest = TrackedCasDigest {
            inner: Arc::new(TrackedCasDigestInner {
                data: CasDigest::from_content_for_algorithm(&[], preferred_algorithm),
                expires: AtomicI64::new(0),
            }),
        };

        let source = match preferred_source_algorithm {
            Some(algo) => SourceFilesConfig::UseThis(Box::new(Self::new(vec![algo], None)?)),
            None => SourceFilesConfig::UseSelf,
        };

        Ok(Self {
            preferred_algorithm,
            digest160,
            digest256,
            empty_file_digest,
            source,
        })
    }
}

#[derive(Error, Debug)]
pub enum CasDigestConfigError {
    #[error("At least one algorithm must be enabled")]
    NotConfigured,
    #[error("The preferred source algorithm must be in the algorithms list")]
    InvalidPreferredSourceAlgorithm,
    #[error("Two algorithms were enabled for the same size: `{}` and `{}`", .0.kind(), .1.kind())]
    Conflict(DigestAlgorithm, DigestAlgorithm),
}

pub struct Digester<Kind> {
    variant: DigesterVariant,
    size: u64,
    kind: PhantomData<Kind>,
}

enum DigesterVariant {
    Sha1(Sha1),
    Sha256(Sha256),
    Blake3(Box<blake3::Hasher>),      // This is unusually large.
    Blake3Keyed(Box<blake3::Hasher>), // Same as above
}

impl<Kind> Digester<Kind> {
    pub fn update(&mut self, data: &[u8]) {
        // Explicit dynamic dispatch because we need to match on which variant it was.
        match &mut self.variant {
            DigesterVariant::Sha1(h) => {
                h.update(data);
            }
            DigesterVariant::Sha256(h) => {
                h.update(data);
            }
            DigesterVariant::Blake3(h) => {
                h.update(data);
            }
            DigesterVariant::Blake3Keyed(h) => {
                h.update(data);
            }
        };

        self.size += data.len() as u64;
    }

    pub fn finalize(self) -> CasDigest<Kind> {
        match self.variant {
            DigesterVariant::Sha1(h) => CasDigest::new_sha1(h.finalize().into(), self.size),
            DigesterVariant::Sha256(h) => CasDigest::new_sha256(h.finalize().into(), self.size),
            DigesterVariant::Blake3(h) => CasDigest::new_blake3(h.finalize().into(), self.size),
            DigesterVariant::Blake3Keyed(h) => {
                CasDigest::new_blake3_keyed(h.finalize().into(), self.size)
            }
        }
    }

    pub fn algorithm(&self) -> DigestAlgorithmKind {
        match &self.variant {
            DigesterVariant::Sha1(..) => DigestAlgorithmKind::Sha1,
            DigesterVariant::Sha256(..) => DigestAlgorithmKind::Sha256,
            DigesterVariant::Blake3(..) => DigestAlgorithmKind::Blake3,
            DigesterVariant::Blake3Keyed(..) => DigestAlgorithmKind::Blake3Keyed,
        }
    }

    pub fn bytes_read(&self) -> u64 {
        self.size
    }
}

/// Separate struct to allow us to use  `repr(transparent)` below and guarantee an identical
/// layout.
#[derive(Display, PartialEq, Eq, PartialOrd, Ord, Hash, Allocative, Clone, Dupe)]
#[display(fmt = "{}:{}", digest, size)]
struct CasDigestData {
    size: u64,
    digest: RawDigest,
}

#[derive(Display, Derivative, Allocative, Clone_, Dupe_)]
#[allocative(bound = "")]
#[derivative(PartialEq, Eq, PartialOrd, Ord, Hash)]
#[display(fmt = "{}", data)]
#[repr(transparent)]
pub struct CasDigest<Kind> {
    data: CasDigestData,
    #[derivative(
        Hash = "ignore",
        PartialEq = "ignore",
        PartialOrd = "ignore",
        Ord = "ignore"
    )]
    kind: PhantomData<Kind>,
}

impl<Kind> fmt::Debug for CasDigest<Kind> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self)
    }
}

impl<Kind> CasDigest<Kind> {
    pub fn from_digest_bytes(
        kind: DigestAlgorithmKind,
        digest: &[u8],
        size: u64,
    ) -> anyhow::Result<Self> {
        Ok(match kind {
            DigestAlgorithmKind::Sha1 => Self::new_sha1(digest.try_into()?, size),
            DigestAlgorithmKind::Sha256 => Self::new_sha256(digest.try_into()?, size),
            DigestAlgorithmKind::Blake3 => Self::new_blake3(digest.try_into()?, size),
            DigestAlgorithmKind::Blake3Keyed => Self::new_blake3_keyed(digest.try_into()?, size),
        })
    }

    pub fn new(digest: RawDigest, size: u64) -> Self {
        Self {
            data: CasDigestData { size, digest },
            kind: PhantomData,
        }
    }

    pub fn new_sha1(sha1: [u8; SHA1_SIZE], size: u64) -> Self {
        Self {
            data: CasDigestData {
                size,
                digest: RawDigest::Sha1(sha1),
            },
            kind: PhantomData,
        }
    }

    pub fn new_sha256(sha256: [u8; SHA256_SIZE], size: u64) -> Self {
        Self {
            data: CasDigestData {
                size,
                digest: RawDigest::Sha256(sha256),
            },
            kind: PhantomData,
        }
    }

    pub fn new_blake3(blake3: [u8; BLAKE3_SIZE], size: u64) -> Self {
        Self {
            data: CasDigestData {
                size,
                digest: RawDigest::Blake3(blake3),
            },
            kind: PhantomData,
        }
    }

    pub fn new_blake3_keyed(blake3: [u8; BLAKE3_SIZE], size: u64) -> Self {
        Self {
            data: CasDigestData {
                size,
                digest: RawDigest::Blake3Keyed(blake3),
            },
            kind: PhantomData,
        }
    }

    pub fn raw_digest(&self) -> &RawDigest {
        &self.data.digest
    }

    pub fn size(&self) -> u64 {
        self.data.size
    }

    /// A tiny representation of this digest, useful for logging when the full sha1 presentation is
    /// too expensive.
    pub fn tiny_digest(&self) -> TinyDigest<'_, Kind> {
        TinyDigest { of: self }
    }

    pub fn parse_digest(
        s: &str,
        config: CasDigestConfig,
    ) -> Result<(Self, DigestAlgorithm), CasDigestParseError> {
        let (digest, size) = s
            .split_once(':')
            .ok_or(CasDigestParseError::MissingSizeSeparator)?;

        let (digest, algo) = CasDigest::<Kind>::parse_digest_without_size(digest, config)?;
        let size = size.parse().map_err(CasDigestParseError::InvalidSize)?;

        Ok((Self::new(digest, size), algo))
    }

    pub fn parse_digest_without_size(
        data: &str,
        config: CasDigestConfig,
    ) -> Result<(RawDigest, DigestAlgorithm), CasDigestParseError> {
        let algo = if data.len() == 40 {
            config.digest160()
        } else if data.len() == 64 {
            config.digest256()
        } else {
            None
        };

        let algo = algo.ok_or(CasDigestParseError::UnsupportedDigest(data.len()))?;

        let digest = match algo {
            DigestAlgorithm::Sha1 => RawDigest::parse_sha1(data.as_bytes()),
            DigestAlgorithm::Sha256 => RawDigest::parse_sha256(data.as_bytes()),
            DigestAlgorithm::Blake3 => RawDigest::parse_blake3(data.as_bytes()),
            DigestAlgorithm::Blake3Keyed { .. } => RawDigest::parse_blake3_keyed(data.as_bytes()),
        }?;

        Ok((digest, algo))
    }

    /// Return the digest of an empty string
    pub fn empty(config: CasDigestConfig) -> Self {
        Self::from_content(&[], config)
    }

    pub fn digester(config: CasDigestConfig) -> Digester<Kind> {
        Self::digester_for_algorithm(config.preferred_algorithm())
    }

    pub fn digester_for_algorithm(algorithm: DigestAlgorithm) -> Digester<Kind> {
        let variant = match algorithm {
            DigestAlgorithm::Sha1 => DigesterVariant::Sha1(Sha1::new()),
            DigestAlgorithm::Sha256 => DigesterVariant::Sha256(Sha256::new()),
            DigestAlgorithm::Blake3 => DigesterVariant::Blake3(Box::new(blake3::Hasher::new())),
            DigestAlgorithm::Blake3Keyed { key } => {
                DigesterVariant::Blake3Keyed(Box::new(blake3::Hasher::new_keyed(key)))
            }
        };

        Digester {
            variant,
            size: 0,
            kind: PhantomData,
        }
    }

    pub fn from_content(bytes: &[u8], config: CasDigestConfig) -> Self {
        Self::from_content_for_algorithm(bytes, config.preferred_algorithm())
    }

    pub fn from_content_for_algorithm(bytes: &[u8], algorithm: DigestAlgorithm) -> Self {
        let mut digester = Self::digester_for_algorithm(algorithm);
        digester.update(bytes);
        digester.finalize()
    }

    pub fn from_reader<R: Read>(reader: R, config: CasDigestConfig) -> anyhow::Result<Self> {
        Self::from_reader_for_algorithm(reader, config.preferred_algorithm())
    }

    pub fn from_reader_for_algorithm<R: Read>(
        mut reader: R,
        algorithm: DigestAlgorithm,
    ) -> anyhow::Result<Self> {
        let mut digester = Self::digester_for_algorithm(algorithm);

        // Buffer size chosen based on benchmarks at D26176645
        // Also optimal for Blake3's SIMD implementation.
        let mut buffer = [0; 16 * 1024];
        loop {
            let count = reader.read(&mut buffer)?;
            if count == 0 {
                break;
            }
            digester.update(&buffer[..count]);
        }

        Ok(digester.finalize())
    }

    pub fn coerce<NewKind>(self) -> CasDigest<NewKind> {
        CasDigest {
            data: self.data,
            kind: PhantomData,
        }
    }
}

pub trait TrackedCasDigestKind: Sized + 'static {
    /// This needs to be a concrete implementation since we share the empty instance in a static
    /// but we can't have static generics.
    fn empty_digest(config: CasDigestConfig) -> Option<TrackedCasDigest<Self>>;
}

#[derive(Display)]
#[display(fmt = "{}", "hex::encode(&of.raw_digest().as_bytes()[0..4])")]
pub struct TinyDigest<'a, Kind> {
    of: &'a CasDigest<Kind>,
}

#[derive(Error, Debug)]
pub enum CasDigestParseError {
    #[error("The digest is missing a size separator, it should look like `HASH:SIZE`")]
    MissingSizeSeparator,

    #[error("The digest part of the CAS digest is not a valid SHA1 digest")]
    InvalidSha1(#[source] hex::FromHexError),

    #[error("The digest part of the CAS digest is not a valid SHA256 digest")]
    InvalidSha256(#[source] hex::FromHexError),

    #[error("The digest part of the CAS digest is not a valid BLAKE3 hash")]
    InvalidBlake3(#[source] hex::FromHexError),

    #[error("The digest size ({} chars) does not correspond to a supported digest size", .0)]
    UnsupportedDigest(usize),

    #[error("The size part of the CAS digest is invalid")]
    InvalidSize(#[source] std::num::ParseIntError),
}

/// A digest to interact with RE. This, despite the name, can be a file or a directory. We track
/// the sha1 and the size of the underlying blob. We *also* keep track of its expiry in the CAS.
/// Note that for directory, the expiry represents that of the directory's blob, not its underlying
/// contents.
#[derive(Allocative)]
#[allocative(bound = "")]
struct TrackedCasDigestInner<Kind> {
    data: CasDigest<Kind>,
    expires: AtomicI64,
}

#[derive(Display, Dupe_, Allocative)]
#[allocative(bound = "")]
#[display(fmt = "{}", "self.data()")]
pub struct TrackedCasDigest<Kind> {
    inner: Arc<TrackedCasDigestInner<Kind>>,
}

impl<Kind> Clone for TrackedCasDigest<Kind> {
    fn clone(&self) -> Self {
        Self {
            inner: self.inner.dupe(),
        }
    }
}

impl<Kind> Borrow<CasDigest<Kind>> for TrackedCasDigest<Kind> {
    fn borrow(&self) -> &CasDigest<Kind> {
        self.data()
    }
}

impl<'a, Kind> Borrow<CasDigest<Kind>> for &'a TrackedCasDigest<Kind> {
    fn borrow(&self) -> &CasDigest<Kind> {
        self.data()
    }
}

impl<Kind> PartialOrd for TrackedCasDigest<Kind> {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.data().partial_cmp(other.data())
    }
}

impl<Kind> Ord for TrackedCasDigest<Kind> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.data().cmp(other.data())
    }
}

impl<Kind> PartialEq for TrackedCasDigest<Kind> {
    fn eq(&self, other: &Self) -> bool {
        self.data().eq(other.data())
    }
}

impl<Kind> Eq for TrackedCasDigest<Kind> {}

impl<Kind> Hash for TrackedCasDigest<Kind> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.data().hash(state)
    }
}

impl<Kind> fmt::Debug for TrackedCasDigest<Kind> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "[{} expires at {}]",
            self,
            self.inner.expires.load(Ordering::Relaxed)
        )
    }
}

impl<Kind> buck2_core::directory::DirectoryDigest for TrackedCasDigest<Kind> {}

impl<Kind> TrackedCasDigest<Kind> {
    pub fn new(data: CasDigest<Kind>, config: CasDigestConfig) -> Self
    where
        Kind: TrackedCasDigestKind,
    {
        if data.size() == 0 {
            return Self::empty(config);
        }

        Self {
            inner: Arc::new(TrackedCasDigestInner {
                data,
                expires: AtomicI64::new(0),
            }),
        }
    }

    pub fn new_expires(
        data: CasDigest<Kind>,
        expiry: DateTime<Utc>,
        config: CasDigestConfig,
    ) -> Self
    where
        Kind: TrackedCasDigestKind,
    {
        let res = Self::new(data, config);
        res.update_expires(expiry);
        res
    }

    pub fn empty(config: CasDigestConfig) -> Self
    where
        Kind: TrackedCasDigestKind,
    {
        match Kind::empty_digest(config) {
            Some(o) => o,
            None => Self {
                inner: Arc::new(TrackedCasDigestInner {
                    data: CasDigest::empty(config),
                    expires: AtomicI64::new(0),
                }),
            },
        }
    }

    pub fn from_content(bytes: &[u8], config: CasDigestConfig) -> Self
    where
        Kind: TrackedCasDigestKind,
    {
        if bytes.is_empty() {
            return Self::empty(config);
        }

        Self {
            inner: Arc::new(TrackedCasDigestInner {
                data: CasDigest::from_content(bytes, config),
                expires: AtomicI64::new(0),
            }),
        }
    }

    pub fn data(&self) -> &CasDigest<Kind> {
        &self.inner.data
    }

    pub fn raw_digest(&self) -> &RawDigest {
        self.inner.data.raw_digest()
    }

    pub fn size(&self) -> u64 {
        self.inner.data.size()
    }

    pub fn expires(&self) -> DateTime<Utc> {
        Utc.timestamp_opt(self.inner.expires.load(Ordering::Relaxed), 0)
            .unwrap()
    }

    pub fn update_expires(&self, time: DateTime<Utc>) {
        self.inner
            .expires
            .store(time.timestamp(), Ordering::Relaxed)
    }
}

pub mod testing {
    use super::*;

    pub fn sha1_sha256() -> CasDigestConfig {
        static CONFIG: Lazy<CasDigestConfigInner> = Lazy::new(|| {
            CasDigestConfigInner::new(vec![DigestAlgorithm::Sha1, DigestAlgorithm::Sha256], None)
                .unwrap()
        });
        CasDigestConfig { inner: &CONFIG }
    }

    pub fn sha1_blake3() -> CasDigestConfig {
        static CONFIG: Lazy<CasDigestConfigInner> = Lazy::new(|| {
            CasDigestConfigInner::new(vec![DigestAlgorithm::Sha1, DigestAlgorithm::Blake3], None)
                .unwrap()
        });
        CasDigestConfig { inner: &CONFIG }
    }

    pub fn sha256_sha1() -> CasDigestConfig {
        static CONFIG: Lazy<CasDigestConfigInner> = Lazy::new(|| {
            CasDigestConfigInner::new(vec![DigestAlgorithm::Sha256, DigestAlgorithm::Sha1], None)
                .unwrap()
        });
        CasDigestConfig { inner: &CONFIG }
    }

    pub fn sha1() -> CasDigestConfig {
        static CONFIG: Lazy<CasDigestConfigInner> =
            Lazy::new(|| CasDigestConfigInner::new(vec![DigestAlgorithm::Sha1], None).unwrap());
        CasDigestConfig { inner: &CONFIG }
    }

    pub fn sha256() -> CasDigestConfig {
        static CONFIG: Lazy<CasDigestConfigInner> =
            Lazy::new(|| CasDigestConfigInner::new(vec![DigestAlgorithm::Sha256], None).unwrap());
        CasDigestConfig { inner: &CONFIG }
    }

    pub fn blake3() -> CasDigestConfig {
        static CONFIG: Lazy<CasDigestConfigInner> =
            Lazy::new(|| CasDigestConfigInner::new(vec![DigestAlgorithm::Blake3], None).unwrap());
        CasDigestConfig { inner: &CONFIG }
    }

    pub fn blake3_keyed() -> CasDigestConfig {
        static CONFIG: Lazy<CasDigestConfigInner> = Lazy::new(|| {
            CasDigestConfigInner::new(vec![DigestAlgorithm::Blake3Keyed { key: &[0; 32] }], None)
                .unwrap()
        });
        CasDigestConfig { inner: &CONFIG }
    }
}

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

    #[test]
    fn test_digest_from_str() {
        let s = "0000000000000000000000000000000000000000:123";
        let config = CasDigestConfig::testing_default();
        assert_eq!(
            CasDigest::<()>::parse_digest(s, config)
                .unwrap()
                .0
                .to_string(),
            s
        );
    }

    #[test]
    fn test_digest_from_reader() {
        let content = &b"foo"[..];

        assert_eq!(
            CasDigest::<()>::from_reader_for_algorithm(content, DigestAlgorithm::Sha1)
                .unwrap()
                .to_string(),
            "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33:3"
        );
        assert_eq!(
            CasDigest::<()>::from_reader_for_algorithm(content, DigestAlgorithm::Sha256)
                .unwrap()
                .to_string(),
            "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae:3"
        );
        assert_eq!(
            CasDigest::<()>::from_reader_for_algorithm(content, DigestAlgorithm::Blake3)
                .unwrap()
                .to_string(),
            "04e0bb39f30b1a3feb89f536c93be15055482df748674b00d26e5a75777702e9:3"
        );
    }

    #[test]
    fn test_digest_from_content() {
        let content = &b"foo"[..];

        assert_eq!(
            CasDigest::<()>::from_content_for_algorithm(content, DigestAlgorithm::Sha1).to_string(),
            "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33:3"
        );
        assert_eq!(
            CasDigest::<()>::from_content_for_algorithm(content, DigestAlgorithm::Sha256)
                .to_string(),
            "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae:3"
        );
        assert_eq!(
            CasDigest::<()>::from_content_for_algorithm(content, DigestAlgorithm::Blake3)
                .to_string(),
            "04e0bb39f30b1a3feb89f536c93be15055482df748674b00d26e5a75777702e9:3"
        );
    }

    #[test]
    fn test_preferred_algorithm() {
        let content = &b"foo"[..];

        assert_eq!(
            CasDigest::<()>::from_content(content, testing::sha1(),).to_string(),
            "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33:3"
        );
        assert_eq!(
            CasDigest::<()>::from_content(content, testing::sha256(),).to_string(),
            "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae:3"
        );
        assert_eq!(
            CasDigest::<()>::from_content(content, testing::blake3(),).to_string(),
            "04e0bb39f30b1a3feb89f536c93be15055482df748674b00d26e5a75777702e9:3"
        );
    }

    #[test]
    fn test_parse_digest() {
        let sha1 = CasDigest::<()>::parse_digest(
            "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33:3",
            testing::sha256_sha1(),
        )
        .unwrap()
        .0;
        assert_eq!(sha1.raw_digest().algorithm(), DigestAlgorithmKind::Sha1);
        assert_eq!(
            sha1.to_string(),
            "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33:3"
        );

        let sha256 = CasDigest::<()>::parse_digest(
            "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae:3",
            testing::sha1_sha256(),
        )
        .unwrap()
        .0;
        assert_eq!(sha256.raw_digest().algorithm(), DigestAlgorithmKind::Sha256);
        assert_eq!(
            sha256.to_string(),
            "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae:3"
        );

        let blake3 = CasDigest::<()>::parse_digest(
            "04e0bb39f30b1a3feb89f536c93be15055482df748674b00d26e5a75777702e9:3",
            testing::sha1_blake3(),
        )
        .unwrap()
        .0;
        assert_eq!(blake3.raw_digest().algorithm(), DigestAlgorithmKind::Blake3);
        assert_eq!(
            blake3.to_string(),
            "04e0bb39f30b1a3feb89f536c93be15055482df748674b00d26e5a75777702e9:3"
        );

        let blake3_keyed = CasDigest::<()>::parse_digest(
            "04e0bb39f30b1a3feb89f536c93be15055482df748674b00d26e5a75777702e9:3",
            testing::blake3_keyed(),
        )
        .unwrap()
        .0;
        assert_eq!(
            blake3_keyed.raw_digest().algorithm(),
            DigestAlgorithmKind::Blake3Keyed
        );
        assert_eq!(
            blake3_keyed.to_string(),
            "04e0bb39f30b1a3feb89f536c93be15055482df748674b00d26e5a75777702e9:3"
        );
    }

    #[test]
    fn test_digest_algorithm_kind_roundtrip() {
        for v in [
            DigestAlgorithmKind::Sha1,
            DigestAlgorithmKind::Sha256,
            DigestAlgorithmKind::Blake3,
            DigestAlgorithmKind::Blake3Keyed,
        ] {
            assert_eq!(v, v.to_string().parse().unwrap());
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::cmp;
use std::net::Ipv4Addr;
use std::path::Path;
use std::time::Duration;

use anyhow::Context;
use futures::Future;
use tokio::time::Instant;
use tonic::transport::Channel;
use tonic::transport::Endpoint;

pub static UDS_DAEMON_FILENAME: &str = "buckd.uds";

#[cfg(unix)]
pub async fn get_channel_uds(
    unix_socket: &Path,
    change_to_parent_dir: bool,
) -> anyhow::Result<Channel> {
    use buck2_core::fs::fs_util;

    use crate::home_buck_tmp::home_buck_tmp_dir;
    use crate::temp_path::TempPath;

    // Symlink to temp file to connect to unix domain socket
    // since the unix domain socket path is limited to 108 characters.
    // https://man7.org/linux/man-pages/man7/unix.7.html
    if change_to_parent_dir {
        let symlink = TempPath::new_in(home_buck_tmp_dir()?)?;

        fs_util::symlink(unix_socket, symlink.path())?;

        let r = get_channel_uds_no_symlink(symlink.path())
            .await
            .with_context(|| {
                format!(
                    "Failed to connect to unix domain socket `{}` using symlink `{}`",
                    unix_socket.display(),
                    symlink.path().display(),
                )
            })?;

        symlink.close()?;

        Ok(r)
    } else {
        get_channel_uds_no_symlink(unix_socket)
            .await
            .with_context(|| {
                format!(
                    "Failed to connect to unix domain socket `{}`",
                    unix_socket.display()
                )
            })
    }
}

#[cfg(unix)]
async fn get_channel_uds_no_symlink(connect_to: &Path) -> anyhow::Result<Channel> {
    use tonic::codegen::http::Uri;
    use tower::service_fn;

    let io = tokio::net::UnixStream::connect(&connect_to).await?;

    let mut io = Some(io);
    // This URL string is not relevant to the connection. Some URL is required for the function to work but the closure running inside connect_with_connector()
    // deals with connecting to the unix domain socket.
    Ok(Endpoint::try_from("http://[::]:50051")?
        .connect_with_connector(service_fn(move |_: Uri| {
            let io = io
                .take()
                .with_context(|| "Cannot reconnect after connection loss to uds");
            futures::future::ready(io)
        }))
        .await?)
}

#[cfg(windows)]
pub async fn get_channel_uds(_unix_filename: &Path, _chg_dir: bool) -> anyhow::Result<Channel> {
    Err(anyhow::Error::msg(
        "Unix domain sockets are not supported on Windows",
    ))
}

pub async fn get_channel_tcp(socket_addr: Ipv4Addr, port: u16) -> anyhow::Result<Channel> {
    Endpoint::try_from(format!("http://{}:{}", socket_addr, port))?
        .connect()
        .await
        .with_context(|| format!("failed to connect to port {}", port))
}

#[derive(thiserror::Error, Debug)]
pub enum RetryError<E> {
    #[error("Timed out after {0:.2}s")]
    Timeout(f64),
    #[error("Failed after {tries} attempts over {timeout_secs:.2}s, with error: {last_error}")]
    Failed {
        timeout_secs: f64,
        tries: i32,
        last_error: E,
    },
}

pub async fn retrying<L, E, Fut: Future<Output = Result<L, E>>, F: FnMut() -> Fut>(
    initial_delay: Duration,
    max_delay: Duration,
    timeout: Duration,
    mut func: F,
) -> Result<L, RetryError<E>> {
    let deadline = Instant::now() + timeout;
    let mut wait = initial_delay;
    let mut tries = 0;
    let mut last_error = None;

    loop {
        tries += 1;
        match tokio::time::timeout_at(deadline, func()).await {
            Ok(Ok(val)) => return Ok(val),
            Ok(Err(e)) => {
                last_error = Some(e);
                let now = Instant::now();
                if now >= deadline {
                    return Err(make_error(tries, last_error, timeout));
                }
                tokio::time::sleep_until(cmp::min(deadline, now + wait)).await;
                wait = cmp::min(max_delay, wait * 2);
            }
            Err(_) => return Err(make_error(tries, last_error, timeout)),
        }
    }

    fn make_error<E>(tries: i32, last_error: Option<E>, timeout: Duration) -> RetryError<E> {
        let timeout_secs = timeout.as_secs_f64();
        match last_error {
            Some(e) => RetryError::Failed {
                timeout_secs,
                tries,
                last_error: e,
            },
            None => RetryError::Timeout(timeout_secs),
        }
    }
}

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

    use crate::client_utils::retrying;
    use crate::client_utils::RetryError;

    #[tokio::test]
    async fn test_retrying_error_forever() {
        // Testing against real timer is not good,
        // but setting up proper mock timer is too much boilerplate.
        let future = retrying(
            Duration::from_millis(1),
            Duration::from_millis(1),
            Duration::from_millis(1),
            || async { Err(anyhow::anyhow!("test")) },
        );
        let result: Result<(), RetryError<anyhow::Error>> = future.await;
        assert!(result.is_err());
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::time::Duration;

pub trait ProstDurationExt {
    fn try_into_duration(&self) -> anyhow::Result<Duration>;
}

impl ProstDurationExt for prost_types::Duration {
    fn try_into_duration(&self) -> anyhow::Result<Duration> {
        Ok(Duration::from_secs(self.seconds.try_into()?)
            + Duration::from_nanos(self.nanos.try_into()?))
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use buck2_core::fs::paths::file_name::FileName;

/// `~/.buck/buckd/repo-path` directory.
#[derive(Debug, Clone, derive_more::Display)]
#[display(fmt = "{}", path.display())]
pub struct DaemonDir {
    pub path: AbsNormPathBuf,
}

impl DaemonDir {
    /// Path to `buckd.info` file.
    pub fn buckd_info(&self) -> AbsNormPathBuf {
        self.path.join(FileName::new("buckd.info").unwrap())
    }

    /// Path to `buckd.stdout` file.
    pub fn buckd_stdout(&self) -> AbsNormPathBuf {
        self.path.join(FileName::new("buckd.stdout").unwrap())
    }

    /// Path to `buckd.stderr` file.
    pub fn buckd_stderr(&self) -> AbsNormPathBuf {
        self.path.join(FileName::new("buckd.stderr").unwrap())
    }

    /// Path to `buckd.pid` file.
    pub fn buckd_pid(&self) -> AbsNormPathBuf {
        self.path.join(FileName::new("buckd.pid").unwrap())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use crate::result::recursive_shared_downcast_ref;
use crate::result::MayProvideAnyhowError;

pub trait CreateErrorReport {
    fn create_error_report(&self) -> Option<buck2_data::ErrorReport>;
}

impl<T> CreateErrorReport for T
where
    T: MayProvideAnyhowError,
{
    fn create_error_report(&self) -> Option<buck2_data::ErrorReport> {
        let err = self.as_anyhow()?;

        // Infra error by default if no category tag is set
        let category = Some(
            recursive_shared_downcast_ref::<buck2_data::ErrorCategory>(err)
                .map_or(buck2_data::ErrorCategory::Infra as i32, |c| *c as i32),
        );
        let cause = recursive_shared_downcast_ref::<buck2_data::ErrorCause>(err).map(|c| *c as i32);
        let error_message = format!("{:#}", err);

        Some(buck2_data::ErrorReport {
            category,
            cause,
            error_message,
        })
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_events::dispatch::EventDispatcher;
use dice::UserComputationData;

pub trait HasEvents {
    fn get_dispatcher(&self) -> &EventDispatcher;
}

impl HasEvents for UserComputationData {
    fn get_dispatcher(&self) -> &EventDispatcher {
        self.data
            .get::<EventDispatcher>()
            .expect("Event dispatcher should be set")
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::fmt::Display;
use std::fmt::Formatter;
use std::hash::Hash;
use std::hash::Hasher;
use std::str::FromStr;
use std::sync::Arc;

use allocative::Allocative;
use buck2_util::collections::sorted_map::SortedMap;
use derive_more::Display;
use dupe::Dupe;
use internment_tweaks::Intern;
use internment_tweaks::StaticInterner;
use once_cell::sync::Lazy;

#[derive(Debug, Default, Eq, Hash, PartialEq, Clone, Dupe, Allocative)]
pub struct LocalExecutorOptions {
    pub use_persistent_workers: bool,
}

#[derive(Debug, Eq, PartialEq, Copy, Clone, Dupe, Display, Allocative)]
pub struct RemoteExecutorUseCase(Intern<String>);

impl RemoteExecutorUseCase {
    pub fn new(use_case: String) -> Self {
        static USE_CASE_INTERNER: StaticInterner<String> = StaticInterner::new();
        Self(USE_CASE_INTERNER.intern(&use_case))
    }

    pub fn as_str(&self) -> &'static str {
        self.0.deref_static().as_str()
    }

    /// The "buck2-default" use case. This is meant to be used when no use case is configured. It's
    /// not meant to be used for convenience when a use case is not available where it's needed!
    pub fn buck2_default() -> Self {
        static USE_CASE: Lazy<RemoteExecutorUseCase> =
            Lazy::new(|| RemoteExecutorUseCase::new("buck2-default".to_owned()));
        *USE_CASE
    }
}

// The derived PartialEq (which uses pointer equality on the interned data) is still correct.
#[allow(clippy::derived_hash_with_manual_eq)]
impl Hash for RemoteExecutorUseCase {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.hash(state);
    }
}

#[derive(Debug, Default, Eq, PartialEq, Clone, Hash, Allocative)]
pub struct RemoteExecutorOptions {
    pub re_max_input_files_bytes: Option<u64>,
    pub re_max_queue_time_ms: Option<u64>,
}

/// The actual executor portion of a RemoteEnabled executor. It's possible for a RemoteEnabled
/// executor to wrap a local executor, which is a glorified way of saying "this is a local executor
/// with a RE backend for caching".
#[derive(Display, Debug, Eq, PartialEq, Clone, Hash, Allocative)]
pub enum RemoteEnabledExecutor {
    #[display(fmt = "local")]
    Local(LocalExecutorOptions),
    #[display(fmt = "remote")]
    Remote(RemoteExecutorOptions),
    #[display(fmt = "hybrid")]
    Hybrid {
        local: LocalExecutorOptions,
        remote: RemoteExecutorOptions,
        level: HybridExecutionLevel,
    },
}

#[derive(Debug, Eq, PartialEq, Clone, Hash, Allocative)]
pub enum Executor {
    /// This executor only runs local commands.
    Local(LocalExecutorOptions),

    /// This executor interacts with a RE backend. It may use that to read or write to caches, or
    /// to execute commands.
    RemoteEnabled {
        executor: RemoteEnabledExecutor,
        re_properties: SortedMap<String, String>,
        re_use_case: RemoteExecutorUseCase,
        re_action_key: Option<String>,
        cache_upload_behavior: CacheUploadBehavior,
        remote_cache_enabled: bool,
    },
}

impl Display for Executor {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Local(options) => {
                write!(
                    f,
                    "Local + use persistent workers {}",
                    options.use_persistent_workers
                )
            }
            Self::RemoteEnabled {
                executor,
                re_properties: _,
                re_use_case: _,
                re_action_key: _,
                cache_upload_behavior,
                remote_cache_enabled,
            } => {
                let cache = match remote_cache_enabled {
                    true => "enabled",
                    false => "disabled",
                };
                write!(
                    f,
                    "RemoteEnabled + executor {} + remote cache {} + cache upload {}",
                    executor, cache, cache_upload_behavior
                )
            }
        }
    }
}

#[derive(Debug, Eq, PartialEq, Clone, Copy, Dupe, Hash, Allocative)]
pub enum PathSeparatorKind {
    Unix,
    Windows,
}

impl PathSeparatorKind {
    pub fn system_default() -> Self {
        if cfg!(windows) {
            Self::Windows
        } else {
            Self::Unix
        }
    }
}

/// Controls how we implement output_dirs, output_files, output_paths in RE actions.
#[derive(Debug, Eq, PartialEq, Clone, Copy, Dupe, Hash, Allocative)]
pub enum OutputPathsBehavior {
    /// Ask for things as either files or directories.
    Strict,
    /// Ask for things as either directories when certain or files AND directories.
    Compatibility,
    /// Ask for things using output_paths.
    OutputPaths,
}

impl FromStr for OutputPathsBehavior {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "strict" => Ok(OutputPathsBehavior::Strict),
            "compatibility" => Ok(OutputPathsBehavior::Compatibility),
            #[cfg(not(fbcode_build))]
            "output_paths" => Ok(OutputPathsBehavior::OutputPaths),
            _ => Err(anyhow::anyhow!("Invalid OutputPathsBehavior: `{}`", s)),
        }
    }
}

impl Default for OutputPathsBehavior {
    fn default() -> Self {
        if buck2_core::is_open_source() {
            Self::OutputPaths
        } else {
            Self::Compatibility
        }
    }
}

#[derive(Display, Debug, Eq, PartialEq, Clone, Copy, Dupe, Hash, Allocative)]
pub enum CacheUploadBehavior {
    #[display(fmt = "enabled")]
    Enabled { max_bytes: Option<u64> },
    #[display(fmt = "disabled")]
    Disabled,
}

impl Default for CacheUploadBehavior {
    fn default() -> Self {
        Self::Disabled
    }
}

#[derive(Debug, Eq, PartialEq, Clone, Copy, Dupe, Hash, Allocative)]
pub struct CommandGenerationOptions {
    pub path_separator: PathSeparatorKind,
    pub output_paths_behavior: OutputPathsBehavior,
}

#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
pub struct CommandExecutorConfig {
    pub executor: Executor,
    pub options: CommandGenerationOptions,
}

#[derive(Debug, Eq, PartialEq, Clone, Copy, Dupe, Hash, Allocative)]
pub enum HybridExecutionLevel {
    /// Expose both executors but only run it in one preferred executor.
    Limited,
    /// Expose both executors, fallback to the non-preferred executor if execution on the preferred
    /// executor doesn't provide a successful response. By default, we fallback only on errors (i.e.
    /// the infra failed), but not on failures (i.e. the job exited with 1). If
    /// `fallback_on_failure` is set, then we also fallback on failures.
    Fallback { fallback_on_failure: bool },
    /// Race both executors.
    Full {
        fallback_on_failure: bool,
        low_pass_filter: bool,
    },
}

impl CommandExecutorConfig {
    pub fn testing_local() -> Arc<CommandExecutorConfig> {
        Arc::new(CommandExecutorConfig {
            executor: Executor::Local(LocalExecutorOptions::default()),
            options: CommandGenerationOptions {
                path_separator: PathSeparatorKind::system_default(),
                output_paths_behavior: Default::default(),
            },
        })
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::fmt;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;

use allocative::Allocative;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
use dupe::Dupe;

type Utf8Path = String;

/// Represents a path containing a symlink that resolves to an external path.
/// What path does the symlink resolve to (`abs_target`), and what goes after
/// that (`remaining_path`).
///
/// E.g. foo/bar/file, where foo/bar -> /root, would be represented as:
///      ExternalSymlink { abs_target: "/root", remaining_path: "file" }
#[derive(Debug, Hash, PartialEq, Eq, Clone, Allocative)]
pub struct ExternalSymlink {
    /// The external target the symlink resolves to.
    // We can't use AbsPathBuf because there might be "." or ".." in the path
    abs_target: Utf8Path,
    /// What goes after the external target path.
    remaining_path: Option<ForwardRelativePathBuf>,
}

impl fmt::Display for ExternalSymlink {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.to_path_buf().display())
    }
}

impl ExternalSymlink {
    pub fn new(
        abs_target: PathBuf,
        remaining_path: Option<ForwardRelativePathBuf>,
    ) -> anyhow::Result<Self> {
        let abs_target = match abs_target.into_os_string().into_string() {
            Ok(string) => string,
            Err(os_string) => {
                return Err(anyhow::anyhow!(
                    "Found external symlink that's not utf-8. Lossy representation: {}",
                    os_string.to_string_lossy()
                ));
            }
        };
        Ok(Self {
            abs_target,
            remaining_path,
        })
    }

    pub fn target(&self) -> &Path {
        Path::new(&self.abs_target)
    }

    pub fn remaining_path(&self) -> Option<&ForwardRelativePath> {
        self.remaining_path.as_ref().map(|p| p.as_ref())
    }

    /// Returns the complete path as a [`PathBuf`]
    pub fn to_path_buf(&self) -> PathBuf {
        match &self.remaining_path {
            Some(p) => Path::new(&self.abs_target).join(p.as_str()),
            None => Path::new(&self.abs_target).to_owned(),
        }
    }

    /// Returns a new `ExternalSymlink` with its target being the full target
    /// of `self` (i.e. `{self.target}/{self.remaining_path}`).
    pub fn with_full_target(self: &Arc<Self>) -> anyhow::Result<Arc<Self>> {
        if self.remaining_path.is_some() {
            Ok(Arc::new(Self::new(self.to_path_buf(), None)?))
        } else {
            Ok(self.dupe())
        }
    }

    /// Returns a new `ExternalSymlink` with `remaining_path` discarded.
    pub fn without_remaining_path(self: &Arc<Self>) -> Arc<Self> {
        if self.remaining_path.is_some() {
            Arc::new(Self {
                abs_target: self.abs_target.clone(),
                remaining_path: None,
            })
        } else {
            self.dupe()
        }
    }

    pub fn target_str(&self) -> &str {
        &self.abs_target
    }

    /// Given a `path = "[...a]/[...b]"`, and `remaining_path = Some("[...b]")`,
    /// returns `Some("[...a]")`. It returns `None` if `path` doesn't end with
    /// `remaining_path`.
    pub fn fix_source_path<'a>(
        &self,
        path: &'a ForwardRelativePath,
    ) -> Option<&'a ForwardRelativePath> {
        if let Some(remaining) = self.remaining_path() {
            path.as_str()
                .strip_suffix(remaining.as_str())
                .map(|p| p.strip_suffix('/').unwrap_or(p))
                .map(ForwardRelativePath::unchecked_new)
        } else {
            Some(path)
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::fs::File;
use std::hash::Hash;
use std::sync::Arc;

use allocative::Allocative;
use async_trait::async_trait;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::cells::cell_path::CellPathRef;
use buck2_core::env_helper::EnvHelper;
use buck2_core::fs::paths::abs_path::AbsPath;
use buck2_core::fs::paths::file_name::FileName;
use buck2_core::fs::paths::file_name::FileNameBuf;
use cmp_any::PartialEqAny;
use compact_str::CompactString;
use derive_more::Display;
use dupe::Dupe;
use gazebo::variants::VariantName;

use crate::cas_digest::CasDigest;
use crate::cas_digest::CasDigestConfig;
use crate::cas_digest::TrackedCasDigest;
use crate::cas_digest::TrackedCasDigestKind;
use crate::external_symlink::ExternalSymlink;

#[derive(Debug, thiserror::Error)]
enum FileOpsError {
    #[error("File not found: `{0}`")]
    FileNotFound(String),
}

/// std::fs::FileType is an opaque type that isn't constructible. This is
/// basically the equivalent.
#[derive(Clone, Dupe, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Allocative)]
pub enum FileType {
    Directory,
    File,
    Symlink,
    /// Some special files (for example, unix domain sockets) will have a
    /// filetype that we don't recognize.
    Unknown,
}

impl From<std::fs::FileType> for FileType {
    fn from(fs_type: std::fs::FileType) -> Self {
        if fs_type.is_file() {
            FileType::File
        } else if fs_type.is_dir() {
            FileType::Directory
        } else if fs_type.is_symlink() {
            FileType::Symlink
        } else {
            FileType::Unknown
        }
    }
}

impl FileType {
    pub fn is_dir(&self) -> bool {
        matches!(self, FileType::Directory)
    }

    pub fn is_file(&self) -> bool {
        matches!(self, FileType::File)
    }

    pub fn is_symlink(&self) -> bool {
        matches!(self, FileType::Symlink)
    }
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Allocative)]
pub struct SimpleDirEntry {
    // Put the `file_name` first so we sort by it (which is what people expect)
    pub file_name: FileNameBuf,
    pub file_type: FileType,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Allocative)]
pub struct RawDirEntry {
    // Put the `file_name` first so we sort by it (which is what people expect)
    /// Not all file names are accepted as file names in Buck. Such files are automatically ignored.
    /// This should probably be something like `CompactOsString`.
    pub file_name: CompactString,
    pub file_type: FileType,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Dupe, Allocative)]
pub struct ReadDirOutput {
    /// Sorted.
    pub included: Arc<[SimpleDirEntry]>,
}

impl ReadDirOutput {
    /// Is the file name in the directory listing. Ignores files that were explicitly ignored.
    pub fn contains(&self, file_name: &FileName) -> bool {
        self.included.iter().any(|x| x.file_name == file_name)
    }
}

#[derive(Allocative)]
pub struct FileDigestKind {
    _private: (),
}

impl TrackedCasDigestKind for FileDigestKind {
    fn empty_digest(config: CasDigestConfig) -> Option<TrackedCasDigest<Self>> {
        Some(config.empty_file_digest())
    }
}

pub type FileDigest = CasDigest<FileDigestKind>;

pub type TrackedFileDigest = TrackedCasDigest<FileDigestKind>;

#[derive(Copy, Dupe, Clone)]
pub struct FileDigestConfig {
    config: CasDigestConfig,
}

impl FileDigestConfig {
    /// Obtain a FileDigestConfig for hashing source files.
    pub fn source(config: CasDigestConfig) -> Self {
        Self {
            config: config.source_files_config(),
        }
    }

    /// Obtain a FileDigestConfig for hashing output files.
    pub fn build(config: CasDigestConfig) -> Self {
        Self { config }
    }

    /// Obtain the underlying CasDigestConfig.
    pub fn as_cas_digest_config(&self) -> CasDigestConfig {
        self.config
    }
}

impl FileDigest {
    /// Obtain the digest of the file if you can.
    pub fn from_file(file: &AbsPath, config: FileDigestConfig) -> anyhow::Result<Self> {
        static DISABLE_FILE_ATTR: EnvHelper<bool> = EnvHelper::new("BUCK2_DISABLE_FILE_ATTR");

        if !DISABLE_FILE_ATTR.get_copied()?.unwrap_or_default() {
            if let Some(digest) = Self::from_file_attr(file, config) {
                return Ok(digest);
            }
        }

        Self::from_file_disk(file, config)
    }

    /// Read the file from the xattr, or skip if it's not available.
    #[cfg(unix)]
    fn from_file_attr(file: &AbsPath, config: FileDigestConfig) -> Option<Self> {
        use buck2_core::fs::fs_util;

        use crate::cas_digest::RawDigest;

        enum Digest {
            Sha1,
            Blake3Keyed,
        }

        let digest = if config.as_cas_digest_config().allows_sha1() {
            Digest::Sha1
        } else if config.as_cas_digest_config().allows_blake3_keyed() {
            Digest::Blake3Keyed
        } else {
            return None;
        };

        let meta = fs_util::symlink_metadata(file).ok()?;

        // This really shouldn't happen (and it better not because we're about to read the xattr of
        // a symlink otherwise) but this method doesn't have the ability to return an error.
        if meta.is_symlink() {
            return None;
        }

        let raw_digest = match digest {
            Digest::Sha1 => xattr::get(file.as_maybe_relativized(), "user.sha1")
                .ok()
                .flatten()
                .and_then(|v| RawDigest::parse_sha1(&v).ok()),
            // NOTE: Eden returns *keyed* blake3 in user.blake3, so we use that.
            Digest::Blake3Keyed => xattr::get(file.as_maybe_relativized(), "user.blake3")
                .ok()
                .flatten()
                .and_then(|v| RawDigest::parse_blake3_keyed(&v).ok()),
        };

        raw_digest.map(|raw| Self::new(raw, meta.len()))
    }

    /// Windows doesn't support extended file attributes.
    #[cfg(windows)]
    fn from_file_attr(_file: &AbsPath, _config: FileDigestConfig) -> Option<Self> {
        None
    }

    /// Get the digest from disk. You should usually prefer `from_file`
    /// which also uses faster methods of getting the SHA1 if it can.
    pub fn from_file_disk(file: &AbsPath, config: FileDigestConfig) -> anyhow::Result<Self> {
        let f = File::open(file.as_maybe_relativized())?;
        FileDigest::from_reader(f, config.as_cas_digest_config())
    }
}

/// Stores the relevant metadata for a file.
// New fields should be added as needed, and unused fields removed.
#[derive(Debug, Dupe, Hash, PartialEq, Eq, Clone, Display, Allocative)]
#[display(fmt = "File({})", digest)]
pub struct FileMetadata {
    pub digest: TrackedFileDigest,
    pub is_executable: bool,
}

impl FileMetadata {
    /// Metadata of an empty file
    pub fn empty(config: CasDigestConfig) -> Self {
        Self {
            digest: TrackedFileDigest::empty(config),
            is_executable: false,
        }
    }
}

/// Stores the relevant metadata for a path.
#[derive(Debug, Dupe, PartialEq, Eq, Clone)]
pub enum PathMetadata {
    File(FileMetadata),
    ExternalSymlink(Arc<ExternalSymlink>),
    Directory,
}

#[derive(Debug, PartialEq, Dupe, Eq, Clone, Allocative, VariantName)]
pub enum RawPathMetadata<T = Arc<CellPath>> {
    Symlink { at: T, to: RawSymlink<T> },
    File(FileMetadata),
    Directory,
}

#[derive(Debug, Dupe, Hash, PartialEq, Eq, Clone, Allocative, VariantName)]
pub enum RawSymlink<T> {
    Relative(T),
    External(Arc<ExternalSymlink>),
}

/// Stores the relevant metadata for a path.
#[derive(Debug, Dupe, PartialEq, Eq, Clone)]
pub enum PathMetadataOrRedirection<T = Arc<CellPath>> {
    PathMetadata(PathMetadata),
    Redirection(T),
}

impl<T> PathMetadataOrRedirection<T> {
    pub fn map<O>(self, f: impl Fn(T) -> O) -> PathMetadataOrRedirection<O> {
        match self {
            Self::PathMetadata(meta) => PathMetadataOrRedirection::PathMetadata(meta),
            Self::Redirection(r) => PathMetadataOrRedirection::Redirection(f(r)),
        }
    }
}

impl<T> From<RawPathMetadata<T>> for PathMetadataOrRedirection<T> {
    fn from(meta: RawPathMetadata<T>) -> Self {
        match meta {
            RawPathMetadata::Directory => {
                PathMetadataOrRedirection::PathMetadata(PathMetadata::Directory)
            }
            RawPathMetadata::File(file) => {
                PathMetadataOrRedirection::PathMetadata(PathMetadata::File(file))
            }
            RawPathMetadata::Symlink {
                at: _,
                to: RawSymlink::Relative(r),
            } => PathMetadataOrRedirection::Redirection(r),
            RawPathMetadata::Symlink {
                at: _,
                to: RawSymlink::External(e),
            } => PathMetadataOrRedirection::PathMetadata(PathMetadata::ExternalSymlink(e)),
        }
    }
}

impl<T> RawPathMetadata<T> {
    pub fn map<O>(self, f: impl Fn(T) -> O) -> RawPathMetadata<O> {
        match Self::try_map::<O, RawPathMetadata<O>>(self, |v| Ok(f(v))) {
            Ok(out) => out,
            Err(e) => e,
        }
    }

    pub fn try_map<O, E>(self, f: impl Fn(T) -> Result<O, E>) -> Result<RawPathMetadata<O>, E> {
        match self {
            Self::Directory => Ok(RawPathMetadata::Directory),
            Self::File(file) => Ok(RawPathMetadata::File(file)),
            Self::Symlink {
                at,
                to: RawSymlink::Relative(dest),
            } => Ok(RawPathMetadata::Symlink {
                at: f(at)?,
                to: RawSymlink::Relative(f(dest)?),
            }),
            Self::Symlink {
                at,
                to: RawSymlink::External(e),
            } => Ok(RawPathMetadata::Symlink {
                at: f(at)?,
                to: RawSymlink::External(e),
            }),
        }
    }
}

impl<T> From<PathMetadata> for PathMetadataOrRedirection<T> {
    fn from(meta: PathMetadata) -> Self {
        Self::PathMetadata(meta)
    }
}

#[async_trait]
pub trait FileOps: Allocative + Send + Sync {
    async fn read_file_if_exists(
        &self,
        path: CellPathRef<'async_trait>,
    ) -> anyhow::Result<Option<String>>;

    /// Return the list of file outputs, sorted.
    async fn read_dir(&self, path: CellPathRef<'async_trait>) -> anyhow::Result<ReadDirOutput>;

    async fn is_ignored(&self, path: CellPathRef<'async_trait>) -> anyhow::Result<bool>;

    async fn read_path_metadata_if_exists(
        &self,
        path: CellPathRef<'async_trait>,
    ) -> anyhow::Result<Option<RawPathMetadata>>;

    fn eq_token(&self) -> PartialEqAny;
}

impl dyn FileOps + '_ {
    pub async fn read_file(&self, path: CellPathRef<'_>) -> anyhow::Result<String> {
        self.read_file_if_exists(path)
            .await?
            .ok_or_else(|| FileOpsError::FileNotFound(path.to_string()).into())
    }

    pub async fn try_exists(&self, path: CellPathRef<'_>) -> anyhow::Result<bool> {
        Ok(self.read_path_metadata_if_exists(path).await?.is_some())
    }

    pub async fn read_path_metadata(
        &self,
        path: CellPathRef<'_>,
    ) -> anyhow::Result<RawPathMetadata> {
        self.read_path_metadata_if_exists(path)
            .await?
            .ok_or_else(|| FileOpsError::FileNotFound(path.to_string()).into())
    }
}

impl PartialEq for dyn FileOps {
    fn eq(&self, other: &dyn FileOps) -> bool {
        self.eq_token() == other.eq_token()
    }
}

pub mod testing {
    use std::collections::BTreeMap;
    use std::collections::BTreeSet;
    use std::sync::Arc;

    use allocative::Allocative;
    use async_trait::async_trait;
    use buck2_core::cells::cell_path::CellPath;
    use buck2_core::cells::cell_path::CellPathRef;
    use cmp_any::PartialEqAny;
    use dupe::Dupe;
    use itertools::Itertools;

    use crate::cas_digest::CasDigestConfig;
    use crate::external_symlink::ExternalSymlink;
    use crate::file_ops::FileMetadata;
    use crate::file_ops::FileOps;
    use crate::file_ops::FileType;
    use crate::file_ops::RawPathMetadata;
    use crate::file_ops::RawSymlink;
    use crate::file_ops::ReadDirOutput;
    use crate::file_ops::SimpleDirEntry;
    use crate::file_ops::TrackedFileDigest;

    enum TestFileOpsEntry {
        File(String /*data*/, FileMetadata),
        ExternalSymlink(Arc<ExternalSymlink>),
        Directory(BTreeSet<SimpleDirEntry>),
    }

    #[derive(Allocative)]
    pub struct TestFileOps {
        #[allocative(skip)]
        entries: BTreeMap<CellPath, TestFileOpsEntry>,
    }

    impl TestFileOps {
        fn new(entries: BTreeMap<CellPath, TestFileOpsEntry>) -> Self {
            let mut file_ops = Self {
                entries: BTreeMap::new(),
            };
            for (path, entry) in entries {
                file_ops.add_entry(path, entry);
            }
            file_ops
        }

        pub fn new_with_files(files: BTreeMap<CellPath, String>) -> Self {
            let cas_digest_config = CasDigestConfig::testing_default();

            Self::new(
                files
                    .into_iter()
                    .map(|(path, data)| {
                        (
                            path,
                            TestFileOpsEntry::File(
                                data.clone(),
                                FileMetadata {
                                    digest: TrackedFileDigest::from_content(
                                        data.as_bytes(),
                                        cas_digest_config,
                                    ),
                                    is_executable: false,
                                },
                            ),
                        )
                    })
                    .collect::<BTreeMap<CellPath, TestFileOpsEntry>>(),
            )
        }

        pub fn new_with_files_metadata(files: BTreeMap<CellPath, FileMetadata>) -> Self {
            Self::new(
                files
                    .into_iter()
                    .map(|(path, m)| (path, TestFileOpsEntry::File("".to_owned(), m)))
                    .collect::<BTreeMap<CellPath, TestFileOpsEntry>>(),
            )
        }

        pub fn new_with_symlinks(symlinks: BTreeMap<CellPath, Arc<ExternalSymlink>>) -> Self {
            Self::new(
                symlinks
                    .into_iter()
                    .map(|(path, s)| (path, TestFileOpsEntry::ExternalSymlink(s)))
                    .collect::<BTreeMap<CellPath, TestFileOpsEntry>>(),
            )
        }

        fn add_entry(&mut self, path: CellPath, entry: TestFileOpsEntry) {
            let mut file_type = match entry {
                TestFileOpsEntry::Directory(..) => FileType::Directory,
                TestFileOpsEntry::ExternalSymlink(..) => FileType::Symlink,
                TestFileOpsEntry::File(..) => FileType::File,
            };
            // make sure the test setup is correct and concise
            assert!(
                self.entries.insert(path.to_owned(), entry).is_none(),
                "Adding `{}`, it already exists.",
                path
            );

            let mut path = path.as_ref();

            // now add to / create the parent directories
            while let (Some(dir), Some(name)) = (path.parent(), path.path().file_name()) {
                let dir_entry = self
                    .entries
                    .entry(dir.to_owned())
                    .or_insert_with(|| TestFileOpsEntry::Directory(BTreeSet::new()));
                match dir_entry {
                    TestFileOpsEntry::Directory(listing) => {
                        listing.insert(SimpleDirEntry {
                            file_type,
                            file_name: name.to_owned(),
                        });
                        file_type = FileType::Directory;
                        path = dir;
                    }
                    _ => panic!("Adding `{}`, but `{}` exists and is not a dir", path, dir),
                };
            }
        }
    }

    #[async_trait]
    impl FileOps for TestFileOps {
        async fn read_file_if_exists(
            &self,
            path: CellPathRef<'async_trait>,
        ) -> anyhow::Result<Option<String>> {
            Ok(self.entries.get(&path.to_owned()).and_then(|e| match e {
                TestFileOpsEntry::File(data, ..) => Some(data.clone()),
                _ => None,
            }))
        }

        async fn read_dir(&self, path: CellPathRef<'async_trait>) -> anyhow::Result<ReadDirOutput> {
            let included = self
                .entries
                .get(&path.to_owned())
                .and_then(|e| match e {
                    TestFileOpsEntry::Directory(listing) => {
                        Some(listing.iter().cloned().sorted().collect::<Vec<_>>().into())
                    }
                    _ => None,
                })
                .ok_or_else(|| anyhow::anyhow!("couldn't find dir {:?}", path))?;
            Ok(ReadDirOutput { included })
        }

        async fn read_path_metadata_if_exists(
            &self,
            path: CellPathRef<'async_trait>,
        ) -> anyhow::Result<Option<RawPathMetadata>> {
            self.entries.get(&path.to_owned()).map_or(Ok(None), |e| {
                match e {
                    TestFileOpsEntry::File(_data, metadata) => {
                        Ok(RawPathMetadata::File(metadata.to_owned()))
                    }
                    TestFileOpsEntry::ExternalSymlink(sym) => Ok(RawPathMetadata::Symlink {
                        at: Arc::new(path.to_owned()),
                        to: RawSymlink::External(sym.dupe()),
                    }),
                    _ => Err(anyhow::anyhow!("couldn't get metadata for {:?}", path)),
                }
                .map(Some)
            })
        }

        async fn is_ignored(&self, _path: CellPathRef<'async_trait>) -> anyhow::Result<bool> {
            Ok(false)
        }

        fn eq_token(&self) -> PartialEqAny {
            PartialEqAny::always_false()
        }
    }
}

#[cfg(test)]
mod tests {
    use std::borrow::Borrow;
    use std::collections::hash_map::DefaultHasher;
    use std::hash::Hasher;

    use super::*;

    #[test]
    fn test_tracked_file_digest_equivalence() {
        let digest_config = CasDigestConfig::testing_default();
        let digest = FileDigest::from_content(b"foo", digest_config);
        let tracked_digest = TrackedFileDigest::new(digest.dupe(), digest_config);

        assert_eq!(&digest, tracked_digest.borrow());

        let mut hasher_digest = DefaultHasher::new();
        digest.hash(&mut hasher_digest);

        let mut hasher_tracked_digest = DefaultHasher::new();
        tracked_digest.hash(&mut hasher_tracked_digest);

        assert_eq!(hasher_digest.finish(), hasher_tracked_digest.finish());
        assert_eq!(digest.to_string(), tracked_digest.to_string());
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_core::fs::paths::file_name::FileName;
use buck2_core::fs::paths::file_name::FileNameBuf;

use crate::file_ops::SimpleDirEntry;

pub fn find_buildfile<'a>(
    buildfile_candidates: &'a [FileNameBuf],
    dir_listing: &[SimpleDirEntry],
) -> Option<&'a FileName> {
    for candidate in buildfile_candidates {
        for entry in dir_listing {
            if entry.file_name == *candidate {
                return Some(candidate.as_ref());
            }
        }
    }
    None
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::time::SystemTime;

use buck2_core::fs::fs_util;
use buck2_core::fs::paths::abs_norm_path::AbsNormPath;
use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use buck2_core::fs::paths::file_name::FileName;
use once_cell::sync::Lazy;

use crate::invocation_paths::home_buck_dir;
use crate::result::SharedResult;
use crate::result::ToSharedResultExt;

/// `~/.buck/tmp` after old files removed.
///
/// We use this directory when we need tmp dir with short file names (to connect to unix socket).
#[allow(clippy::needless_borrow)] // False positive.
pub fn home_buck_tmp_dir() -> anyhow::Result<&'static AbsNormPath> {
    fn remove_old_files(tmp_dir: &AbsNormPath) -> anyhow::Result<()> {
        let mut now = None;

        for entry in fs_util::read_dir(&tmp_dir)? {
            let entry = entry?;
            let timestamp = match entry.metadata().and_then(|m| m.modified()) {
                Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
                    // Possible if invoked concurrently.
                    continue;
                }
                Err(e) => return Err(e.into()),
                Ok(metadata) => metadata,
            };

            let now = *now.get_or_insert_with(SystemTime::now);
            if now.duration_since(timestamp).unwrap_or_default().as_secs() > 3 * 86400 {
                fs_util::remove_all(&entry.path())?;
            }
        }

        Ok(())
    }

    fn find_dir() -> anyhow::Result<AbsNormPathBuf> {
        let home_buck_dir = home_buck_dir()?;
        let tmp_dir = home_buck_dir.join(FileName::new("tmp")?);
        fs_util::create_dir_all(&tmp_dir)?;
        remove_old_files(&tmp_dir)?;
        Ok(tmp_dir)
    }

    static DIR: Lazy<SharedResult<AbsNormPathBuf>> = Lazy::new(|| find_dir().shared_error());

    Ok(&Lazy::force(&DIR).as_ref()?)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

//!
//! Defines utilities to obtain the basic paths for buck2 client and the daemon.
//!

use std::borrow::Cow;

use allocative::Allocative;
use anyhow::Context;
use buck2_core::fs::paths::abs_norm_path::AbsNormPath;
use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use buck2_core::fs::paths::file_name::FileName;
use buck2_core::fs::paths::file_name::FileNameBuf;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
use buck2_core::fs::project::ProjectRoot;
use buck2_core::fs::project_rel_path::ProjectRelativePath;
use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
use once_cell::sync::Lazy;

use crate::daemon_dir::DaemonDir;
use crate::invocation_roots::InvocationRoots;
use crate::result::SharedResult;
use crate::result::ToSharedResultExt;

/// `~/.buck`.
#[allow(clippy::needless_borrow)] // False positive.
pub(crate) fn home_buck_dir() -> anyhow::Result<&'static AbsNormPath> {
    fn find_dir() -> anyhow::Result<AbsNormPathBuf> {
        let home = dirs::home_dir().context("Expected a HOME directory to be available")?;
        let home = AbsNormPathBuf::new(home).context("Expected an absolute HOME directory")?;
        Ok(home.join(FileName::new(".buck")?))
    }

    static DIR: Lazy<SharedResult<AbsNormPathBuf>> = Lazy::new(|| find_dir().shared_error());

    Ok(&Lazy::force(&DIR).as_ref()?)
}

const BUCKD_DIR_PREFIX: &FileName = FileName::unchecked_new("buckd");

#[derive(Clone, Allocative)]
pub struct InvocationPaths {
    pub roots: InvocationRoots,

    /// The isolation dir is a dir relative path used to create unique directories for
    /// all on-disk state relating to a daemon. This allows multiple daemons to run in
    /// the same project root.
    ///
    /// The daemon metadata directory is post-fixed with the isolation prefix
    /// (i.e `$HOME/.buck/buckd/<projectroot>/<isolationdir>`).
    /// The buck-out is `<projectroot>/buck-out/<isolationdir>/`
    ///
    /// Any on-disk state from the daemon (including build outputs and similar) should only
    /// be written or read from directories that include this component.
    ///
    /// This form of isolation is currently supported primarily for two uses:
    /// 1. testing - it allows us to run isolated daemons on a project for tests. This is
    /// particularly useful to allow a test in a project to recursively invoke buck, but also
    /// useful to write tests against a project's macros and rules and using a project's real
    /// configuration.
    /// 2. generally to support recursive buck invocations. while our ideal may be that these
    /// eventually are not allowed, the most pragmatic approach currently is to support them
    /// but push them into isolated, temporary daemons.
    pub isolation: FileNameBuf,
}

impl InvocationPaths {
    pub fn daemon_dir(&self) -> anyhow::Result<DaemonDir> {
        #[cfg(windows)]
        let root_relative: Cow<ForwardRelativePath> = {
            use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathNormalizer;

            // Get drive letter, network share name, etc.
            // Network share contains '\' therefore it needs to be normalized.
            let prefix = self.roots.project_root.root().windows_prefix()?;
            let stripped_path = ForwardRelativePathNormalizer::normalize_path(
                self.roots.project_root.root().strip_windows_prefix()?,
            )?;
            Cow::Owned(ForwardRelativePathNormalizer::normalize_path(&prefix)?.join(stripped_path))
        };
        #[cfg(not(windows))]
        let root_relative: Cow<ForwardRelativePath> = self
            .roots
            .project_root
            .root()
            .strip_prefix(AbsNormPath::new("/")?)?;
        // TODO(cjhopman): We currently place all buckd info into a directory owned by the user.
        // This is broken when multiple users try to share the same checkout.
        //
        // **This is different than the behavior of buck1.**
        //
        // In buck1, the buck daemon is shared across users. Due to the fact that `buck run`
        // will run whatever command is returned by the daemon, buck1 has a privilege escalation
        // vulnerability.
        //
        // There's a couple ways we could resolve this:
        // 1. Use a shared .buckd information directory and have the client verify the identity of
        // the server before doing anything with it. If the identity is different, kill it and
        // start a new one.
        // 2. Keep user-owned .buckd directory, use some other mechanism to move ownership of
        // output directories between different buckd instances.
        let home_buck_dir = home_buck_dir()?;

        let mut ret = AbsNormPathBuf::with_capacity(
            home_buck_dir.as_os_str().len()
                + 1
                + BUCKD_DIR_PREFIX.as_str().len()
                + 1
                + root_relative.as_str().len()
                + 1
                + self.isolation.as_str().len(),
            home_buck_dir,
        );

        ret.push(ForwardRelativePath::new(BUCKD_DIR_PREFIX)?);
        ret.push(root_relative.as_ref());
        ret.push(&self.isolation);

        Ok(DaemonDir { path: ret })
    }

    pub fn common_buckd_dir() -> anyhow::Result<AbsNormPathBuf> {
        Ok(home_buck_dir()?.join(BUCKD_DIR_PREFIX))
    }

    pub fn cell_root(&self) -> &AbsNormPath {
        &self.roots.cell_root
    }

    pub fn project_root(&self) -> &ProjectRoot {
        &self.roots.project_root
    }

    pub fn log_dir(&self) -> AbsNormPathBuf {
        self.buck_out_path()
            .join(ForwardRelativePath::unchecked_new("log"))
    }

    pub fn tmp_dir(&self) -> AbsNormPathBuf {
        self.buck_out_path()
            .join(ForwardRelativePath::unchecked_new("tmp"))
    }

    pub fn re_logs_dir(&self) -> AbsNormPathBuf {
        self.buck_out_path()
            .join(ForwardRelativePath::unchecked_new("re_logs"))
    }

    pub fn build_count_dir(&self) -> AbsNormPathBuf {
        self.buck_out_path()
            .join(ForwardRelativePath::unchecked_new("build_count"))
    }

    pub fn dice_dump_dir(&self) -> AbsNormPathBuf {
        self.buck_out_path()
            .join(ForwardRelativePath::unchecked_new("dice_dump"))
    }

    pub fn buck_out_dir_prefix() -> &'static ProjectRelativePath {
        ProjectRelativePath::unchecked_new("buck-out")
    }

    pub fn buck_out_dir(&self) -> ProjectRelativePathBuf {
        Self::buck_out_dir_prefix().join(&self.isolation)
    }

    pub fn buck_out_path(&self) -> AbsNormPathBuf {
        self.roots.project_root.root().join(self.buck_out_dir())
    }

    /// Directory containing on-disk cache
    pub fn cache_dir(&self) -> ProjectRelativePathBuf {
        self.buck_out_dir()
            .join(ForwardRelativePath::unchecked_new("cache"))
    }

    pub fn cache_dir_path(&self) -> AbsNormPathBuf {
        self.roots.project_root.root().join(self.cache_dir())
    }

    /// Subdirectory of `cache_dir` responsible for storing materializer state
    pub fn materializer_state_path(&self) -> AbsNormPathBuf {
        self.cache_dir_path()
            .join(self.materializer_state_dir_name())
    }

    /// This is used by the forkserver to write the miniperf wrapper binary (if used), as well as
    /// temporary files used by miniperf. We put this in buck-out because that directory gets
    /// allowlisted for execution (because we write lots of tools there).
    pub fn forkserver_state_dir(&self) -> AbsNormPathBuf {
        self.buck_out_path()
            .join(ForwardRelativePath::unchecked_new("forkserver"))
    }

    pub fn materializer_state_dir_name(&self) -> &FileName {
        FileName::unchecked_new("materializer_state")
    }

    pub fn valid_cache_dirs(&self) -> Vec<&FileName> {
        vec![self.materializer_state_dir_name()]
    }
}

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

    use buck2_core::fs::paths::abs_norm_path::AbsNormPath;
    use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
    use buck2_core::fs::paths::file_name::FileNameBuf;
    use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
    use buck2_core::fs::project::ProjectRoot;
    use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;

    use crate::invocation_paths::InvocationPaths;
    use crate::invocation_roots::InvocationRoots;

    #[test]
    fn test_paths() {
        let cell_root = if cfg!(windows) {
            "C:\\my\\project\\root\\cell"
        } else {
            "/my/project/root/cell"
        };
        let project_root = if cfg!(windows) {
            "C:\\my\\project"
        } else {
            "/my/project"
        };
        let paths = InvocationPaths {
            roots: InvocationRoots {
                cell_root: AbsNormPathBuf::try_from(cell_root.to_owned()).unwrap(),
                project_root: ProjectRoot::new_unchecked(
                    AbsNormPathBuf::try_from(project_root.to_owned()).unwrap(),
                ),
            },
            isolation: FileNameBuf::unchecked_new("isolation"),
        };

        let expected_path = if cfg!(windows) {
            ".buck\\buckd\\C\\my\\project\\isolation"
        } else {
            ".buck/buckd/my/project/isolation"
        };
        assert_eq!(
            paths.daemon_dir().unwrap().path.as_os_str(),
            AbsNormPathBuf::try_from(
                dirs::home_dir().expect("Expected a HOME directory to be available")
            )
            .expect("Expected an absolute HOME directory")
            .join_normalized(ForwardRelativePath::unchecked_new(expected_path))
            .unwrap()
            .as_os_str()
        );

        let expected_path = if cfg!(windows) {
            "C:\\my\\project\\root\\cell"
        } else {
            "/my/project/root/cell"
        };
        assert_eq!(paths.cell_root().as_os_str(), OsStr::new(expected_path));
        let expected_path = if cfg!(windows) {
            "C:\\my\\project"
        } else {
            "/my/project"
        };
        assert_eq!(
            paths.project_root().root().as_os_str(),
            AbsNormPath::new(expected_path).unwrap().as_os_str()
        );

        assert_eq!(
            paths.buck_out_dir(),
            ProjectRelativePathBuf::unchecked_new("buck-out/isolation".to_owned())
        );
        let expected_path = if cfg!(windows) {
            "C:\\my\\project\\buck-out\\isolation"
        } else {
            "/my/project/buck-out/isolation"
        };
        assert_eq!(paths.buck_out_path().as_os_str(), OsStr::new(expected_path));

        let expected_path = if cfg!(windows) {
            "C:\\my\\project\\buck-out\\isolation\\log"
        } else {
            "/my/project/buck-out/isolation/log"
        };
        assert_eq!(paths.log_dir().as_os_str(), OsStr::new(expected_path));
        let expected_path = if cfg!(windows) {
            "C:\\my\\project\\buck-out\\isolation\\dice_dump"
        } else {
            "/my/project/buck-out/isolation/dice_dump"
        };
        assert_eq!(paths.dice_dump_dir().as_os_str(), OsStr::new(expected_path));

        assert_eq!(
            paths.cache_dir(),
            ProjectRelativePathBuf::unchecked_new("buck-out/isolation/cache".to_owned())
        );

        let expected_path = if cfg!(windows) {
            "C:\\my\\project\\buck-out\\isolation\\cache\\materializer_state"
        } else {
            "/my/project/buck-out/isolation/cache/materializer_state"
        };
        assert_eq!(
            paths.materializer_state_path().as_os_str(),
            OsStr::new(expected_path),
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::path::Path;
use std::path::PathBuf;

use allocative::Allocative;
use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use buck2_core::fs::project::ProjectRoot;
use thiserror::Error;

#[derive(Debug, Error)]
enum BuckCliError {
    #[error(
        "Couldn't find a buck project root for directory `{0}`. Expected to find a .buckconfig file."
    )]
    NoBuckRoot(PathBuf),
}

#[derive(Clone, Allocative)]
pub struct InvocationRoots {
    pub cell_root: AbsNormPathBuf,
    pub project_root: ProjectRoot,
}

/// This finds the cell root and the project root.
///
/// This uses a rather liberal definition of "roots". It traverses the directory and
/// its parents looking for all .buckconfig files, the closest one (i.e. with the longest
/// path) will be detected as the "cell root" and the furthest one (with the shortest
/// path) will be detected as the "project root".
///
/// This definition requires that (1) every cell has a .buckconfig file (to make the "cell
/// root" correct) and (2) that the cell's buckconfig actually references the outermost
/// .buckconfig directory as one of its cells, which in practice means you can't stick a
/// buck cell/project within another one without them referencing each other (or not
/// running buck commands within the inner one).
///
/// We also look for .buckroot files, and if we find one of them, we don't traverse further upwards.
/// The contents of the .buckroot file is entirely ignored.
///
/// Doing this without those requirements (i.e. doing it correctly), would require us to
/// parse the buckconfig files (including all file includes). It's unclear if we'll ever
/// do that within the buckd client dir.
pub fn find_invocation_roots(from: &Path) -> anyhow::Result<InvocationRoots> {
    let mut cell_root = None;
    let mut project_root = None;

    let home_dir = dirs::home_dir();
    for curr in from.ancestors() {
        if curr.join(".buckconfig").exists() {
            // Do not allow /home/{unixname}, /home or / to be a cell,
            // and /home/{unixname}/.buckconfig is used for config override
            if let Some(home_dir_path) = &home_dir {
                if home_dir_path == curr {
                    break;
                }
            }
            if cell_root.is_none() {
                cell_root = Some(curr.to_owned());
            }
            project_root = Some(curr.to_owned());
        }

        if curr.join(".buckroot").exists() {
            break;
        }
    }
    match (project_root, cell_root) {
        (Some(project_root), Some(cell_root)) => Ok(InvocationRoots {
            cell_root: AbsNormPathBuf::try_from(cell_root)?,
            project_root: ProjectRoot::new(AbsNormPathBuf::try_from(project_root)?)?,
        }),
        _ => Err(BuckCliError::NoBuckRoot(from.to_owned()).into()),
    }
}