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

//! Connection "pool" by having multiple uds files in a directory.

use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::time::Duration;

use fn_error_context::context;
use nodeipc::NodeIpc;

use crate::ipc;

macro_rules! or {
    // `$rest` could be: `continue`, `return None`.
    ($result:expr, $($rest:tt)*) => {
        match $result {
            Ok(value) => value,
            Err(_) => $($rest)*,
        }
    };
}

/// Serve in the given directory.
///
/// The callsite might want to use `is_alive` to check if it should exit.
#[context("Serving at directory {}", dir.display())]
pub fn serve(dir: &Path) -> anyhow::Result<ipc::Incoming> {
    std::fs::create_dir_all(dir)?;
    let path = dir.join(format!("server-{}", std::process::id()));
    ipc::serve(path)
}

/// Connect to any server uds in the given directory.
///
/// If `exclusive` is set, the uds file is first renamed to ".private"
/// to ensure that one uds file only serves one client.
#[context("Connecting to any server socket in {}", dir.display())]
pub fn connect(dir: &Path, exclusive: bool) -> anyhow::Result<NodeIpc> {
    let mut attempts = Vec::new();

    for mut path in list_uds_paths(dir) {
        if exclusive {
            path = or!(path.exclusive(), continue);
        }
        match path.connect() {
            Ok(ipc) => return Ok(ipc),
            Err(e) => {
                attempts.push(e);
            }
        };
    }

    anyhow::bail!(
        "Failed to connect to any uds files in {}. Attempted: {:?}",
        dir.display(),
        attempts,
    )
}

/// Unix-domain-socket path that can potentially be connected.
pub struct ConnectablePath {
    pub(crate) path: PathBuf,
}

impl ConnectablePath {
    /// Connect to this path.
    pub fn connect(self) -> anyhow::Result<NodeIpc> {
        ipc::connect(&self.path)
    }

    /// Make the path exclusive by renaming the file to `.private`.
    pub fn exclusive(mut self) -> anyhow::Result<Self> {
        let path = &self.path;
        let new_path = path.with_extension("private");
        fs::rename(path, &new_path)?;
        self.path = new_path;
        Ok(self)
    }
}

/// List uds paths that are potentially connectable in a directory.
///
/// Errors are ignored.
pub fn list_uds_paths(dir: &Path) -> Box<dyn Iterator<Item = ConnectablePath> + Send> {
    let dir = or!(fs::read_dir(dir), return Box::new(std::iter::empty()));

    let iter = dir.filter_map(|entry| {
        let entry = entry.ok()?;
        let path = entry.path();

        // Skip ".lock" files.
        if path.extension().unwrap_or_default() == "lock" {
            return None;
        }

        // "Taken" by other process?
        if path.extension().unwrap_or_default() == "private" {
            // Remove stale files.
            // Rename bumps "accessed" time. Use it to detect stale files.
            let metadata = entry.metadata().ok()?;
            let accessed = metadata.accessed().ok()?;
            if accessed.elapsed().unwrap_or_default() > Duration::from_secs(60) {
                let _ = fs::remove_file(path);
            }
            return None;
        }

        Some(ConnectablePath { path })
    });

    Box::new(iter)
}


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

//! Low-level unix-domain-socket utilities.
//!
//! - Re-exports `std` or `uds_windows` types.
//! - Supports long (>107 bytes) paths by `chdir` temporarily.

#[cfg(unix)]
pub use std::os::unix::net;
use std::path::Path;

use fn_error_context::context;
pub use net::UnixListener;
pub use net::UnixStream;
#[cfg(windows)]
pub use uds_windows as net;

/// Bind to the unix domain socket for serving.
///
/// Side effect: changes the process's current directory temporarily.
/// This is to support long socket paths.
#[context("Binding unix domain socket at {}", path.display())]
pub fn bind(path: &Path) -> anyhow::Result<UnixListener> {
    maybe_with_chdir(path, |name| UnixListener::bind(name))
}

/// Connect to the unix domain socket.
///
/// Side effect: changes the process's current directory temporarily.
/// This is to support long socket paths.
#[context("Connecting to unix domain socket at {}", path.display())]
pub fn connect(path: &Path) -> anyhow::Result<UnixStream> {
    maybe_with_chdir(path, |name| UnixStream::connect(name))
}

/// Chdir to the directory of `path`, if the `path` is too long for unix-domain-socket.
/// See `sun_path` in `struct sockaddr_un` in `sys/un.h` for the size limit (107 bytes).
///
/// If chdir is not needed, the full path is passed to `func`. Otherwise, the file name
/// is passed to `func`.
fn maybe_with_chdir<T, E: Into<anyhow::Error>>(
    path: &Path,
    func: impl FnOnce(&Path) -> Result<T, E>,
) -> anyhow::Result<T> {
    // Check sys/un.h for this number. Note C needs a tailing '\0' for end-of-string.
    const SOCKADDR_UN_SUN_PATH_SIZE: usize = 108;
    let dir = if path.as_os_str().len() >= SOCKADDR_UN_SUN_PATH_SIZE {
        path.parent()
    } else {
        None
    };

    let (restore_dir, rest_path) = match dir {
        Some(dir) => {
            let file_name = match path.file_name() {
                Some(name) => Path::new(name),
                None => path,
            };
            let restore_dir = std::env::current_dir()?;
            std::env::set_current_dir(dir)?;
            (Some(restore_dir), file_name)
        }
        None => (None, path),
    };

    let result = func(rest_path);

    if let Some(dir) = restore_dir {
        std::env::set_current_dir(dir)?;
    }

    result.map_err(Into::into)
}


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

fn main() {
    println!("cargo:rerun-if-env-changed=SAPLING_VERSION");
}


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

pub static VERSION: &'static str = match option_env!("SAPLING_VERSION") {
    Some(s) => s,
    None => "dev",
};


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

use std::thread;
use std::thread::JoinHandle;

use anyhow::Result;
use crossbeam::channel;
use crossbeam::channel::Receiver;
use crossbeam::channel::Sender;
use minibytes::Bytes;
use tokio::sync::oneshot;
use types::RepoPathBuf;

use crate::UpdateFlag;
use crate::VFS;

pub struct AsyncVfsWriter {
    sender: Option<Sender<WorkItem>>,
    handles: Vec<JoinHandle<()>>,
}

struct WorkItem {
    res: oneshot::Sender<Result<usize>>,
    action: Action,
}
#[derive(Debug)]
enum Action {
    Write(RepoPathBuf, Bytes, UpdateFlag),
    Remove(RepoPathBuf),
    SetExecutable(RepoPathBuf, bool),
    Batch(Vec<Action>),
}

/// Async write interface to `VFS`.
/// Creating `AsyncVfsWriter` spawns worker threads that handle load internally.
/// If the future returned by `AsyncVfsWriter` functions is dropped, it's corresponding job may be dropped from the queue without executing.
/// Drop handler for `AsyncVfsWriter` blocks until underlyning threads terminate.
impl AsyncVfsWriter {
    pub fn spawn_new(vfs: VFS, workers: usize) -> Self {
        let (sender, receiver) = channel::unbounded();
        let sender = Some(sender);
        let mut handles = Vec::with_capacity(workers);
        for _ in 0..workers {
            let receiver = receiver.clone();
            let vfs = vfs.clone();
            handles.push(thread::spawn(move || async_vfs_worker(vfs, receiver)));
        }
        Self { sender, handles }
    }

    pub async fn write<B: Into<Bytes>>(
        &self,
        path: RepoPathBuf,
        data: B,
        flag: UpdateFlag,
    ) -> Result<usize> {
        self.submit_action(Action::Write(path, data.into(), flag))
            .await
    }

    pub async fn write_batch<B: Into<Bytes>>(
        &self,
        batch: impl IntoIterator<Item = (RepoPathBuf, B, UpdateFlag)>,
    ) -> Result<usize> {
        let batch = batch
            .into_iter()
            .map(|(path, data, flag)| Action::Write(path, data.into(), flag))
            .collect();
        self.submit_action(Action::Batch(batch)).await
    }

    pub async fn remove(&self, path: RepoPathBuf) -> Result<()> {
        self.submit_action(Action::Remove(path)).await.map(|_| ())
    }

    pub async fn remove_batch(&self, batch: Vec<RepoPathBuf>) -> Result<()> {
        let batch = batch.into_iter().map(Action::Remove).collect();
        self.submit_action(Action::Batch(batch)).await.map(|_| ())
    }

    pub async fn set_executable(&self, path: RepoPathBuf, flag: bool) -> Result<()> {
        self.submit_action(Action::SetExecutable(path, flag))
            .await
            .map(|_| ())
    }

    async fn submit_action(&self, action: Action) -> Result<usize> {
        let (tx, rx) = oneshot::channel();
        let wi = WorkItem { action, res: tx };
        self.sender.as_ref().unwrap().send(wi).ok();
        rx.await?
    }
}

fn async_vfs_worker(vfs: VFS, receiver: Receiver<WorkItem>) {
    for item in receiver {
        // Quickcheck - if caller future dropped while item was in queue, no reason to execute
        // One use case for this - if calling stream in checkout encounters an error, the stream is dropped
        // However some items are still in queue - we should not execute them at this point
        if item.res.is_closed() {
            continue;
        }
        let result = execute_action(&vfs, item.action);
        item.res.send(result).ok();
    }
}

fn execute_action(vfs: &VFS, action: Action) -> Result<usize> {
    match action {
        Action::Write(path, data, flag) => vfs.write(&path, &data, flag),
        Action::Remove(path) => vfs.remove(&path).map(|_| 0),
        Action::SetExecutable(path, flag) => vfs.set_executable(&path, flag).map(|_| 0),
        Action::Batch(batch) => {
            let mut total = 0;
            for action in batch {
                total += execute_action(vfs, action)?;
            }
            Ok(total)
        }
    }
}

impl Drop for AsyncVfsWriter {
    // Good citizen behavior - waiting until threads stop when AsyncVfs is dropped
    // This also will propagate panic from a worker thread into caller
    fn drop(&mut self) {
        self.sender.take();
        for handle in self.handles.drain(..) {
            handle.join().unwrap();
        }
    }
}


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

mod async_vfs;
mod pathauditor;
mod vfs;

pub use util::lock::PathLock;

pub use crate::async_vfs::AsyncVfsWriter;
pub use crate::pathauditor::AuditError;
pub use crate::pathauditor::PathAuditor;
pub use crate::vfs::UpdateFlag;
pub use crate::vfs::VFS;


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

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

use anyhow::Context;
use anyhow::Result;
use dashmap::DashMap;
use once_cell::sync::Lazy;
use types::RepoPath;
use types::RepoPathBuf;

/// Audit repositories path to make sure that it is safe to write/remove through them.
///
/// This uses caching internally to avoid the heavy cost of querying the OS for each directory in
/// the path of a file.
///
/// The cache is concurrent and is shared between cloned instances of PathAuditor
pub struct PathAuditor {
    root: PathBuf,
    audited: DashMap<RepoPathBuf, ()>,
}

#[cfg(not(windows))]
const SEPARATORS: [char; 1] = ['/'];
#[cfg(windows)]
const SEPARATORS: [char; 2] = ['/', '\\'];

static WINDOWS_SHORTNAME_ALIASES: Lazy<Vec<&'static str>> =
    Lazy::new(|| identity::all().iter().map(|i| i.cli_name()).collect());

static INVALID_COMPONENTS: Lazy<Vec<&'static str>> = Lazy::new(|| {
    let components: [&'static str; 2] = [".", ".."];
    components
        .into_iter()
        .chain(identity::all().iter().map(|i| i.dot_dir()))
        .collect()
});

#[derive(thiserror::Error, Debug)]
pub enum AuditError {
    #[error("Can't read/write file through ancestor symlink \"{0}\"")]
    ThroughSymlink(RepoPathBuf),
    #[error("Invalid path component \"{0}\"")]
    InvalidComponent(String),
}

impl PathAuditor {
    pub fn new(root: impl AsRef<Path>) -> Self {
        let audited = Default::default();
        let root = root.as_ref().to_owned();
        Self { root, audited }
    }

    /// Slow path, query the filesystem for unsupported path. Namely, writing through a symlink
    /// outside of the repo is not supported.
    /// XXX: more checks
    fn audit_fs(&self, path: &RepoPath) -> Result<(), AuditError> {
        let full_path = self.root.join(path.as_str());

        // XXX: Maybe filter by specific errors?
        if let Ok(metadata) = symlink_metadata(&full_path) {
            if metadata.file_type().is_symlink() {
                return Err(AuditError::ThroughSymlink(path.to_owned()));
            }
        }

        Ok(())
    }

    /// Make sure that it is safe to write/remove `path` from the repo.
    pub fn audit(&self, path: &RepoPath) -> Result<PathBuf> {
        audit_invalid_components(path.as_str())
            .with_context(|| format!("Invalid component in \"{}\"", path))?;

        let mut needs_recording_index = std::usize::MAX;
        for (i, parent) in path.reverse_parents().enumerate() {
            // First fast check w/ read lock
            if !self.audited.contains_key(parent) {
                // If fast check failed, do the stat syscall.
                self.audit_fs(parent)
                    .with_context(|| format!("Can't audit path \"{}\"", parent))?;

                // If it passes the audit, we can't record them as audited just yet, since a parent
                // may still fail the audit. Later we'll loop through and record successful audits.
                needs_recording_index = i;
            } else {
                // path.parents() yields the results in deepest-first order, so if we hit a path
                // that has been audited, we know all the future ones have been audited and we can
                // bail early.
                break;
            }
        }

        if needs_recording_index != std::usize::MAX {
            for (i, parent) in path.reverse_parents().enumerate() {
                self.audited.entry(parent.to_owned()).or_default();
                if needs_recording_index == i {
                    break;
                }
            }
        }

        let mut filepath = self.root.to_owned();
        filepath.push(path.as_str());
        Ok(filepath)
    }
}

/// Checks that shortnames (e.g. `SL~1`) are not a component on Windows and that files don't end in
/// a dot (e.g. `sigh....`)
fn valid_windows_component(component: &str) -> bool {
    if cfg!(not(windows)) {
        return true;
    }
    if let Some((l, r)) = component.split_once('~') {
        if r.chars().any(|c| c.is_numeric()) && WINDOWS_SHORTNAME_ALIASES.contains(&l) {
            return false;
        }
    }
    !component.ends_with('.')
}

/// Makes sure that the path does not contain any of the following components:
/// - ``, empty, implies that paths can't start with, end or contain consecutive `SEPARATOR`s
/// - `.`, dot/period, unix current directory
/// - `..`, double dot, unix parent directory
/// - `.sl` or `.hg`,
/// It also checks that no trailing dots are part of the component and checks that shortnames
/// on Windows are valid.
fn audit_invalid_components(path: &str) -> Result<(), AuditError> {
    let path = if cfg!(not(windows)) {
        path.to_owned()
    } else {
        path.to_lowercase()
    };
    for s in path.split(SEPARATORS) {
        if s.is_empty() || INVALID_COMPONENTS.contains(&s) || !valid_windows_component(s) {
            return Err(AuditError::InvalidComponent(s.to_owned()));
        }
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use std::fs::create_dir_all;
    use std::fs::read_link;
    use std::fs::remove_dir_all;

    use tempfile::TempDir;

    use super::*;

    #[test]
    fn test_audit_valid() -> Result<()> {
        let root = TempDir::new()?;

        let auditor = PathAuditor::new(&root);

        let repo_path = RepoPath::from_str("a/b")?;
        assert_eq!(
            auditor.audit(repo_path)?,
            root.as_ref().join(repo_path.as_str())
        );

        Ok(())
    }

    #[test]
    fn test_audit_invalid_components() -> Result<()> {
        assert!(audit_invalid_components("a/../b").is_err());
        assert!(audit_invalid_components("a/./b").is_err());
        assert!(audit_invalid_components("a/.sl/b").is_err());
        assert!(audit_invalid_components("a/.hg/b").is_err());
        Ok(())
    }

    #[cfg(windows)]
    #[test]
    fn test_audit_windows() -> Result<()> {
        let root = TempDir::new()?;

        let auditor = PathAuditor::new(&root);

        let repo_path = RepoPath::from_str("..\\foobar")?;
        assert!(auditor.audit(repo_path).is_err());
        let repo_path = RepoPath::from_str("x/y/SL~123/z")?;
        assert!(auditor.audit(repo_path).is_err());
        let repo_path = RepoPath::from_str("sl~12345/baz")?;
        assert!(auditor.audit(repo_path).is_err());
        let repo_path = RepoPath::from_str("a/.sL")?;
        assert!(auditor.audit(repo_path).is_err());
        let repo_path = RepoPath::from_str("Sure...")?;
        assert!(auditor.audit(repo_path).is_err());

        Ok(())
    }

    #[cfg(not(windows))]
    #[test]
    fn test_audit_invalid_symlink() -> Result<()> {
        let root = TempDir::new()?;
        let other = TempDir::new()?;

        let auditor = PathAuditor::new(&root);

        let link = root.as_ref().join("a");
        std::os::unix::fs::symlink(&other, &link)?;
        let canonical_other = other.as_ref().canonicalize()?;
        assert_eq!(read_link(&link)?.canonicalize()?, canonical_other);

        let repo_path = RepoPath::from_str("a/b")?;
        assert!(auditor.audit(repo_path).is_err());

        Ok(())
    }

    #[cfg(not(windows))]
    #[test]
    fn test_audit_caching() -> Result<()> {
        let root = TempDir::new()?;
        let other = TempDir::new()?;

        let path = root.as_ref().join("a");
        create_dir_all(&path)?;

        let auditor = PathAuditor::new(&root);

        // Populate the auditor cache.
        let repo_path = RepoPath::from_str("a/b")?;
        auditor.audit(&repo_path)?;

        remove_dir_all(&path)?;

        let link = root.as_ref().join("a");
        std::os::unix::fs::symlink(&other, &link)?;
        let canonical_other = other.as_ref().canonicalize()?;
        assert_eq!(read_link(&link)?.canonicalize()?, canonical_other);

        // Even though "a" is now a symlink to outside the repo, the audit will succeed due to the
        // one performed just above.
        let repo_path = RepoPath::from_str("a/b")?;
        auditor.audit(repo_path)?;

        Ok(())
    }
}


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

use std::fs;
use std::fs::create_dir_all;
use std::fs::remove_dir;
use std::fs::remove_dir_all;
#[cfg(unix)]
use std::fs::set_permissions;
use std::fs::symlink_metadata;
use std::fs::File;
use std::fs::Metadata;
use std::fs::OpenOptions;
#[cfg(unix)]
use std::fs::Permissions;
use std::io;
use std::io::ErrorKind;
use std::io::Write;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;

use anyhow::bail;
use anyhow::ensure;
use anyhow::Context;
use anyhow::Result;
use fsinfo::fstype;
use fsinfo::FsType;
use minibytes::Bytes;
use types::RepoPath;
use util::path::remove_file;

use crate::pathauditor::PathAuditor;

#[derive(Clone)]
pub struct VFS {
    inner: Arc<Inner>,
}

struct Inner {
    root: PathBuf,
    auditor: PathAuditor,
    supports_symlinks: bool,
    supports_executables: bool,
    case_sensitive: bool,
}

#[derive(Clone, Copy, Debug)]
pub enum UpdateFlag {
    Regular,
    Symlink,
    Executable,
}

impl VFS {
    pub fn new(root: PathBuf) -> Result<Self> {
        let auditor = PathAuditor::new(&root);
        let fs_type =
            fstype(&root).with_context(|| format!("Can't construct a VFS for {:?}", root))?;
        let supports_symlinks = supports_symlinks(root.as_path())?;
        let supports_executables = supports_executables(&fs_type);
        let case_sensitive = case_sensitive(&root, &fs_type)?;

        Ok(Self {
            inner: Arc::new(Inner {
                root,
                auditor,
                supports_symlinks,
                supports_executables,
                case_sensitive,
            }),
        })
    }

    pub fn root(&self) -> &Path {
        &self.inner.root
    }

    pub fn case_sensitive(&self) -> bool {
        self.inner.case_sensitive
    }

    pub fn join(&self, path: &RepoPath) -> PathBuf {
        self.inner.root.join(path.as_str())
    }

    pub fn metadata(&self, path: &RepoPath) -> Result<Metadata> {
        tracing::trace!(?path, "fetching metadata");

        self.join(path).symlink_metadata().map_err(|e| {
            // If `path` contains a directory that doesn't actually exist on disk, it surfaces as a
            // NotADirectory error. This error type is unstable and can't actually be matched on.
            // See https://github.com/rust-lang/rust/issues/86442
            // For now, let's convert it to a NotFound error, users of vfs probably want to
            // treat it as such.
            #[cfg(unix)]
            const NOTDIR: i32 = 20; // ENOTDIR
            #[cfg(windows)]
            const NOTDIR: i32 = 267; // ERROR_DIRECTORY

            match e.raw_os_error() {
                Some(errno) if errno == NOTDIR => io::Error::from(ErrorKind::NotFound).into(),
                _ => e.into(),
            }
        })
    }

    pub fn is_file(&self, path: &RepoPath) -> Result<bool> {
        let filepath = self.inner.auditor.audit(path)?;
        Ok(filepath.is_file())
    }

    /// The file `path` can't be written to, attempt to fixup the directories and files so the file can
    /// be created.
    ///
    /// This is a slow operation, and should not be called before attempting to create `path`.
    fn clear_conflicts(&self, repo_path: &RepoPath) -> Result<()> {
        let full_path = self.join(repo_path);

        // Walk down our ancestors, removing the first regular file or symlink
        // we find. We have the invariant that path_buf contains no symlinks
        // since we remove the top most symlink we come across.
        let mut path_buf = self.inner.root.clone();
        for part in repo_path.components() {
            path_buf.push(part.as_str());

            let metadata = match symlink_metadata(&path_buf) {
                Ok(metadata) => metadata,
                Err(err) if err.kind() == ErrorKind::NotFound => break,
                Err(err) => bail!("error lstating {:?} in clear_conflicts: {}", path_buf, err),
            };

            let file_type = metadata.file_type();
            if file_type.is_file() || file_type.is_symlink() {
                remove_file(&path_buf)
                    .with_context(|| format!("Can't remove file {:?}", path_buf))?;
                break;
            }

            // If the full destination is a directory, clear it out.
            if file_type.is_dir() && path_buf == full_path {
                remove_dir_all(&path_buf)
                    .with_context(|| format!("Can't remove directory {:?}", path_buf))?;
                break;
            }
        }

        let dir = full_path.parent().unwrap();
        create_dir_all(dir).with_context(|| format!("Can't create directory {:?}", dir))?;

        Ok(())
    }

    fn write_mode(
        &self,
        filepath: &Path,
        content: &[u8],
        #[allow(unused_variables)] exec: bool,
    ) -> Result<usize> {
        let mut options = OpenOptions::new();
        options.write(true).create(true).truncate(true);

        #[cfg(unix)]
        {
            use std::os::unix::fs::OpenOptionsExt;
            options.custom_flags(libc::O_NOFOLLOW);
        }

        let mut f = options.open(filepath)?;

        #[cfg(unix)]
        {
            let metadata = f.metadata()?;
            let mut permissions = metadata.permissions();
            let mode = Self::update_mode(permissions.mode(), exec);
            permissions.set_mode(mode);
            f.set_permissions(permissions)
                .with_context(|| format!("Failed to set permissions on {:?}", filepath))?;
        }

        f.write_all(content)
            .with_context(|| format!("Can't write to {:?}", filepath))?;
        Ok(content.len())
    }

    #[cfg(unix)]
    fn update_mode(mode: u32, exec: bool) -> u32 {
        if exec {
            mode | util::file::apply_umask((mode & 0o444) >> 2)
        } else {
            mode & 0o666
        }
    }

    #[cfg(windows)]
    fn set_exec(&self, _: &Path, _: bool) -> Result<()> {
        return Ok(());
    }

    #[cfg(unix)]
    fn set_exec(&self, filepath: &Path, flag: bool) -> Result<()> {
        let mode = if flag { 0o755 } else { 0o644 };
        let perms = Permissions::from_mode(mode);
        set_permissions(filepath, perms)
            .with_context(|| format!("Can't update exec flag({}) on {:?}", flag, filepath))?;
        Ok(())
    }

    /// On some OS/filesystems, symlinks aren't supported, we simply create a file where it's content
    /// is the symlink destination for these.
    fn plain_symlink_file(link_name: &Path, link_dest: &Path) -> Result<()> {
        let link_dest = match link_dest.to_str() {
            None => bail!("Not a valid UTF-8 path: {:?}", link_dest),
            Some(s) => s,
        };

        Ok(File::create(link_name)?.write_all(link_dest.as_bytes())?)
    }

    /// Add a symlink `link_name` pointing to `link_dest`. On platforms that do not support symlinks,
    /// `link_name` will be a file containing the path to `link_dest`.
    fn symlink(&self, link_name: &Path, link_dest: &Path) -> Result<()> {
        #[cfg(windows)]
        let result = Self::plain_symlink_file(link_name, link_dest);

        #[cfg(not(windows))]
        let result = if self.inner.supports_symlinks {
            std::os::unix::fs::symlink(link_dest, link_name).map_err(Into::into)
        } else {
            Self::plain_symlink_file(link_name, link_dest)
        };

        result.with_context(|| format!("Can't create symlink '{:?} -> {:?}'", link_name, link_dest))
    }

    /// Write a symlink file at `filepath`. The destination is represented by `content`.
    fn write_symlink(&self, filepath: &Path, content: &[u8]) -> Result<usize> {
        let link_dest = Path::new(std::str::from_utf8(content)?);

        self.symlink(filepath, link_dest)?;
        Ok(filepath.as_os_str().len())
    }

    /// Overwrite the content of the file at `path` with `data`. The number of bytes written on
    /// disk will be returned.
    fn write_inner(&self, path: &RepoPath, data: &[u8], flags: UpdateFlag) -> Result<usize> {
        let filepath = self
            .inner
            .auditor
            .audit(path)
            .with_context(|| format!("Can't write into {}", path))?;

        match flags {
            UpdateFlag::Regular => self.write_mode(&filepath, data, false),
            UpdateFlag::Executable => self.write_mode(&filepath, data, true),
            UpdateFlag::Symlink => self.write_symlink(&filepath, data),
        }
    }

    /// Overwrite content of the file, try to clear conflicts if attempt fails
    ///
    /// Return an error if fails to overwrite after clearing conflicts, or if clear conflicts fail
    pub fn write(&self, path: &RepoPath, data: &[u8], flag: UpdateFlag) -> Result<usize> {
        // Fast path: let's try to open the file directly, we'll handle the failure only if this fails.
        match self.write_inner(path, data, flag) {
            Ok(size) => Ok(size),
            Err(e) => {
                // Ideally, we shouldn't need to retry for some failures, but this is the slow path, any
                // failures not due to a conflicting file would show up here again, so let's not worry
                // about it.
                self.clear_conflicts(path).with_context(|| {
                    format!("Can't clear conflicts after handling error \"{:?}\"", e)
                })?;
                self.write_inner(path, data, flag).with_context(|| {
                    format!("Can't write '{:?}' after handling error \"{:?}\"", path, e)
                })
            }
        }
    }

    pub fn set_executable(&self, path: &RepoPath, flag: bool) -> Result<()> {
        let filepath = self
            .inner
            .auditor
            .audit(path)
            .with_context(|| format!("Can't write into {}", path))?;

        self.set_exec(&filepath, flag)
    }

    /// Remove the file at `path`.
    ///
    /// If file does not exist, returns without an error
    ///
    /// The parent directories of this file will be removed recursively if they are empty.
    pub fn remove(&self, path: &RepoPath) -> Result<()> {
        let mut filepath = self.inner.auditor.audit(path)?;
        self.remove_keep_path(&filepath)?;

        // Mercurial doesn't track empty directories, remove them
        // recursively.
        loop {
            if !filepath.pop() || filepath == self.inner.root {
                break;
            }

            if remove_dir(&filepath).is_err() {
                break;
            }
        }
        Ok(())
    }

    // Reads file content
    pub fn read(&self, path: &RepoPath) -> Result<Bytes> {
        let filepath = self.inner.auditor.audit(path)?;
        let metadata = self.metadata(path)?;
        let content = if metadata.is_symlink() {
            match std::fs::read_link(&filepath)?.to_str() {
                Some(p) => p.as_bytes().iter().map(|u| *u).collect(),
                None => bail!("invalid path during vfs::read '{:?}'", filepath),
            }
        } else {
            std::fs::read(filepath)?
        };
        Ok(content.into())
    }

    /// Removes file, but unlike Self::remove, does not delete empty directories.
    fn remove_keep_path(&self, filepath: &PathBuf) -> Result<()> {
        if let Ok(metadata) = symlink_metadata(&filepath) {
            let file_type = metadata.file_type();
            if file_type.is_file() || file_type.is_symlink() {
                let result = remove_file(&filepath)
                    .with_context(|| format!("Can't remove file {:?}", filepath));
                if let Err(e) = result {
                    if let Some(io_error) = e.downcast_ref::<io::Error>() {
                        ensure!(io_error.kind() == ErrorKind::NotFound, e);
                    } else {
                        return Err(e);
                    };
                }
            }
        }

        Ok(())
    }

    pub fn supports_symlinks(&self) -> bool {
        self.inner.supports_symlinks
    }

    pub fn supports_executables(&self) -> bool {
        self.inner.supports_executables
    }
}

#[cfg(unix)]
#[cfg(test)]
mod unix_tests {
    use std::fs;

    use super::*;

    #[test]
    fn test_symlink_overwrite() {
        let tmp = tempfile::tempdir().unwrap();
        let vfs = VFS::new(tmp.path().to_path_buf()).unwrap();
        let path = RepoPath::from_str("a").unwrap();
        vfs.write(path, b"abc", UpdateFlag::Symlink).unwrap();
        vfs.write(path, &[1, 2, 3], UpdateFlag::Regular).unwrap();
        let metadata = fs::symlink_metadata(vfs.join(path)).unwrap();
        assert!(metadata.file_type().is_file())
    }

    #[test]
    fn test_ancestor_symlink_overwrite() {
        let tmp = tempfile::tempdir().unwrap();
        let vfs = VFS::new(tmp.path().to_path_buf()).unwrap();

        let dir = RepoPath::from_str("a").unwrap();
        let file = RepoPath::from_str("a/b").unwrap();

        vfs.write(dir, b"abc", UpdateFlag::Symlink).unwrap();
        vfs.write(file, &[1, 2, 3], UpdateFlag::Regular).unwrap();
        let metadata = fs::symlink_metadata(vfs.join(file)).unwrap();
        assert!(metadata.file_type().is_file())
    }

    #[test]
    fn test_symlink_read() {
        let tmp = tempfile::tempdir().unwrap();
        let vfs = VFS::new(tmp.path().to_path_buf()).unwrap();
        let path = RepoPath::from_str("a").unwrap();
        vfs.write(path, b"abc", UpdateFlag::Symlink).unwrap();
        let buf = vfs.read(path).unwrap();
        assert_eq!(buf, b"abc")
    }

    #[test]
    fn test_exec_overwrite() {
        use std::os::unix::fs::PermissionsExt;

        let tmp = tempfile::tempdir().unwrap();
        let vfs = VFS::new(tmp.path().to_path_buf()).unwrap();
        let path = RepoPath::from_str("a").unwrap();
        vfs.write(path, "abc".as_bytes(), UpdateFlag::Executable)
            .unwrap();
        vfs.write(path, &[1, 2, 3], UpdateFlag::Regular).unwrap();
        let mut buf = tmp.path().to_path_buf();
        buf.push("a");
        let metadata = fs::symlink_metadata(buf).unwrap();
        assert_eq!(0, metadata.permissions().mode() & 0o111)
    }

    #[test]
    fn test_update_mode() {
        assert_eq!(0o644, VFS::update_mode(0o644, false));
        assert_eq!(0o755, VFS::update_mode(0o755, true));

        assert_eq!(0o755, VFS::update_mode(0o644, true));
        assert_eq!(0o644, VFS::update_mode(0o755, false));
    }
}

fn supports_symlinks(path: &Path) -> Result<bool> {
    if std::env::var("SL_DEBUG_DISABLE_SYMLINKS").is_ok() {
        return Ok(false);
    }

    if !cfg!(windows) {
        return Ok(true);
    }

    return Ok(if let Some((root, ident)) = identity::sniff_root(path)? {
        // This assumes that at this point symlinks are already properly checked for as part of
        // the repo initialization
        fs::read_to_string(root.join(ident.dot_dir()).join("requires"))?
            .split_whitespace()
            .any(|s| s == "windowssymlinks")
    } else {
        // There are some cases (e.g., tests) where we do not have an actual repo and thus
        // no dot_dir directory. Gracefully fail in this case.
        false
    });
}

/// Since Windows determines if a file is executable based on its extension, it doesn't support
/// marking files as executable.
fn supports_executables(fs_type: &FsType) -> bool {
    match *fs_type {
        FsType::NTFS => false,
        FsType::EDENFS => !cfg!(windows),
        _ => true,
    }
}

/// determines whether FS located at root is case sensitive
fn case_sensitive(root: &Path, fs_type: &FsType) -> Result<bool> {
    // Logic in this function is consistent with util.fscasesensitive in Python
    // For some FS we know they are case (in)sensitive, so we just return based on fs type
    // For rest of the FS we see if lstat on the upper/lower case variant differs
    match *fs_type {
        FsType::EDENFS => return Ok(cfg!(target_os = "linux")),
        FsType::BTRFS => return Ok(true),
        FsType::EXT4 => return Ok(true),
        FsType::XFS => return Ok(true),
        FsType::UFS => return Ok(true),
        FsType::TMPFS => return Ok(true),
        _ => {}
    }
    detect_case_sensitive(root)
}

fn detect_case_sensitive(root: &Path) -> Result<bool> {
    let original_lstat = root.symlink_metadata()?;
    let root_str = root.to_str().expect("Can't convert root path to string");
    let mut case_different = root_str.to_lowercase();
    if case_different == root_str {
        case_different = root_str.to_uppercase();
    }
    let case_different = PathBuf::from(case_different);
    let case_different_lstat = case_different.symlink_metadata();
    if let Ok(case_different_lstat) = case_different_lstat {
        Ok(!metadata_eq(&case_different_lstat, &original_lstat)?)
    } else {
        Ok(true)
    }
}

/// Roughly compares metadata, only for internal vfs usage
/// Do not make this fn public
fn metadata_eq(m1: &Metadata, m2: &Metadata) -> Result<bool> {
    Ok(m1.modified()? == m2.modified()?
        && m1.accessed()? == m2.accessed()?
        && m1.created()? == m2.created()?
        && m1.file_type() == m2.file_type())
}

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

    #[test]
    fn test_detect_case_sensitive() {
        let tmp = tempfile::tempdir().unwrap();
        let case_sensitive = detect_case_sensitive(tmp.path()).unwrap();
        #[cfg(target_os = "linux")]
        assert!(case_sensitive);
        #[cfg(windows)]
        assert!(!case_sensitive);
        #[cfg(target_os = "macos")]
        assert!(!case_sensitive);
    }
}


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

use std::io::Cursor;

use minibench::bench;
use minibench::elapsed;
use vlqencoding::VLQDecode;
use vlqencoding::VLQDecodeAt;
use vlqencoding::VLQEncode;

const COUNT: u64 = 16384;

fn main() {
    bench("writing via VLQEncode", || {
        let mut cur = Cursor::new(Vec::with_capacity(COUNT as usize * 8));
        elapsed(|| {
            cur.set_position(0);
            for i in 0..COUNT {
                cur.write_vlq(i).expect("write");
            }
        })
    });

    bench("reading via VLQDecode", || {
        let mut cur = Cursor::new(Vec::with_capacity(COUNT as usize * 8));
        for i in 0..COUNT {
            cur.write_vlq(i).expect("write");
        }

        elapsed(|| {
            cur.set_position(0);
            for i in 0..COUNT {
                let v: u64 = cur.read_vlq().unwrap();
                assert_eq!(v, i);
            }
        })
    });

    bench("reading via VLQDecodeAt", || {
        let mut cur = Vec::with_capacity(COUNT as usize * 8);
        let mut offsets = Vec::with_capacity(COUNT as usize);
        for i in 0..COUNT {
            offsets.push(cur.len());
            cur.write_vlq(i).expect("write");
        }

        elapsed(|| {
            for i in 0..COUNT {
                let offset = unsafe { *offsets.get_unchecked(i as usize) };
                let v: u64 = cur.read_vlq_at(offset).unwrap().0;
                assert_eq!(v, i);
            }
        })
    });
}


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

//! VLQ (Variable-length quantity) encoding.

use std::io;
use std::io::Read;
use std::io::Write;
use std::mem::size_of;

pub trait VLQEncode<T> {
    /// Encode an integer to a VLQ byte array and write it directly to a stream.
    ///
    /// # Examples
    ///
    /// ```
    /// use vlqencoding::VLQEncode;
    /// let mut v = vec![];
    ///
    /// let x = 120u8;
    /// v.write_vlq(x).expect("writing an encoded u8 to a vec should work");
    /// assert_eq!(v, vec![120]);
    ///
    /// let x = 22742734291u64;
    /// v.write_vlq(x).expect("writing an encoded u64 to a vec should work");
    ///
    /// assert_eq!(v, vec![120, 211, 171, 202, 220, 84]);
    /// ```
    ///
    /// Signed integers are encoded via zig-zag:
    ///
    /// ```
    /// use vlqencoding::VLQEncode;
    /// let mut v = vec![];
    ///
    /// let x = -3i8;
    /// v.write_vlq(x).expect("writing an encoded i8 to a vec should work");
    /// assert_eq!(v, vec![5]);
    ///
    /// let x = 1000i16;
    /// v.write_vlq(x).expect("writing an encoded i16 to a vec should work");
    /// assert_eq!(v, vec![5, 208, 15]);
    /// ```
    fn write_vlq(&mut self, value: T) -> io::Result<()>;
}

pub trait VLQDecode<T> {
    /// Read a VLQ byte array from stream and decode it to an integer.
    ///
    /// # Examples
    ///
    /// ```
    /// use vlqencoding::VLQDecode;
    /// use std::io::{Cursor,Seek,SeekFrom,ErrorKind};
    ///
    /// let mut c = Cursor::new(vec![120u8, 211, 171, 202, 220, 84]);
    ///
    /// let x: Result<u8, _> = c.read_vlq();
    /// assert_eq!(x.unwrap(), 120u8);
    ///
    /// let x: Result<u16, _> = c.read_vlq();
    /// assert_eq!(x.unwrap_err().kind(), ErrorKind::InvalidData);
    ///
    /// c.seek(SeekFrom::Start(1)).expect("seek should work");
    /// let x: Result<u64, _> = c.read_vlq();
    /// assert_eq!(x.unwrap(), 22742734291u64);
    /// ```
    ///
    /// Signed integers are decoded via zig-zag:
    ///
    /// ```
    /// use vlqencoding::VLQDecode;
    /// use std::io::{Cursor,Seek,SeekFrom,ErrorKind};
    ///
    /// let mut c = Cursor::new(vec![5u8, 208, 15]);
    ///
    /// let x: Result<i8, _> = c.read_vlq();
    /// assert_eq!(x.unwrap(), -3i8);
    ///
    /// let x: Result<i8, _> = c.read_vlq();
    /// assert_eq!(x.unwrap_err().kind(), ErrorKind::InvalidData);
    ///
    /// c.seek(SeekFrom::Start(1)).expect("seek should work");
    /// let x: Result<i32, _> = c.read_vlq();
    /// assert_eq!(x.unwrap(), 1000i32);
    /// ```
    fn read_vlq(&mut self) -> io::Result<T>;
}

pub trait VLQDecodeAt<T> {
    /// Read a VLQ byte array from the given offset and decode it to an integer.
    ///
    /// Returns `Ok((decoded_integer, bytes_read))` on success.
    ///
    /// This is similar to `VLQDecode::read_vlq`. It's for immutable `AsRef<[u8]>` instead of
    /// a mutable `io::Read` object.
    ///
    /// # Examples
    ///
    /// ```
    /// use vlqencoding::VLQDecodeAt;
    /// use std::io::ErrorKind;
    ///
    /// let c = &[120u8, 211, 171, 202, 220, 84, 255];
    ///
    /// let x: Result<(u8, _), _> = c.read_vlq_at(0);
    /// assert_eq!(x.unwrap(), (120u8, 1));
    ///
    /// let x: Result<(u64, _), _> = c.read_vlq_at(1);
    /// assert_eq!(x.unwrap(), (22742734291u64, 5));
    ///
    /// let x: Result<(u64, _), _> = c.read_vlq_at(6);
    /// assert_eq!(x.unwrap_err().kind(), ::std::io::ErrorKind::InvalidData);
    ///
    /// let x: Result<(u64, _), _> = c.read_vlq_at(7);
    /// assert_eq!(x.unwrap_err().kind(), ::std::io::ErrorKind::InvalidData);
    /// ```
    fn read_vlq_at(&self, offset: usize) -> io::Result<(T, usize)>;
}

macro_rules! impl_unsigned_primitive {
    ($T: ident) => {
        impl<W: Write + ?Sized> VLQEncode<$T> for W {
            fn write_vlq(&mut self, value: $T) -> io::Result<()> {
                let mut buf = [0u8];
                let mut value = value;
                loop {
                    let mut byte = (value & 127) as u8;
                    let next = value >> 7;
                    if next != 0 {
                        byte |= 128;
                    }
                    buf[0] = byte;
                    self.write_all(&buf)?;
                    value = next;
                    if value == 0 {
                        break;
                    }
                }
                Ok(())
            }
        }

        impl<R: Read + ?Sized> VLQDecode<$T> for R {
            fn read_vlq(&mut self) -> io::Result<$T> {
                let mut buf = [0u8];
                let mut value = 0 as $T;
                let mut base = 1 as $T;
                let base_multiplier = (1 << 7) as $T;
                loop {
                    self.read_exact(&mut buf)?;
                    let byte = buf[0];
                    value = ($T::from(byte & 127))
                        .checked_mul(base)
                        .and_then(|v| v.checked_add(value))
                        .ok_or(io::ErrorKind::InvalidData)?;
                    if byte & 128 == 0 {
                        break;
                    }
                    base = base
                        .checked_mul(base_multiplier)
                        .ok_or(io::ErrorKind::InvalidData)?;
                }
                Ok(value)
            }
        }

        impl<R: AsRef<[u8]>> VLQDecodeAt<$T> for R {
            fn read_vlq_at(&self, offset: usize) -> io::Result<($T, usize)> {
                let buf = self.as_ref();
                let mut size = 0;
                let mut value = 0 as $T;
                let mut base = 1 as $T;
                let base_multiplier = (1 << 7) as $T;
                loop {
                    if let Some(byte) = buf.get(offset + size) {
                        size += 1;
                        value = ($T::from(byte & 127))
                            .checked_mul(base)
                            .and_then(|v| v.checked_add(value))
                            .ok_or(io::ErrorKind::InvalidData)?;
                        if byte & 128 == 0 {
                            break;
                        }
                        base = base
                            .checked_mul(base_multiplier)
                            .ok_or(io::ErrorKind::InvalidData)?;
                    } else {
                        return Err(io::ErrorKind::InvalidData.into());
                    }
                }
                Ok((value, size))
            }
        }
    };
}

impl_unsigned_primitive!(usize);
impl_unsigned_primitive!(u64);
impl_unsigned_primitive!(u32);
impl_unsigned_primitive!(u16);
impl_unsigned_primitive!(u8);

macro_rules! impl_signed_primitive {
    ($T: ty, $U: ty) => {
        impl<W: Write + ?Sized> VLQEncode<$T> for W {
            fn write_vlq(&mut self, v: $T) -> io::Result<()> {
                self.write_vlq(((v << 1) ^ (v >> (size_of::<$U>() * 8 - 1))) as $U)
            }
        }

        impl<R: Read + ?Sized> VLQDecode<$T> for R {
            fn read_vlq(&mut self) -> io::Result<$T> {
                (self.read_vlq() as Result<$U, _>).map(|n| ((n >> 1) as $T) ^ -((n & 1) as $T))
            }
        }

        impl<R: AsRef<[u8]>> VLQDecodeAt<$T> for R {
            fn read_vlq_at(&self, offset: usize) -> io::Result<($T, usize)> {
                (self.read_vlq_at(offset) as Result<($U, _), _>)
                    .map(|(n, s)| (((n >> 1) as $T) ^ -((n & 1) as $T), s))
            }
        }
    };
}

impl_signed_primitive!(isize, usize);
impl_signed_primitive!(i64, u64);
impl_signed_primitive!(i32, u32);
impl_signed_primitive!(i16, u16);
impl_signed_primitive!(i8, u8);

#[cfg(test)]
mod tests {
    use std::io;
    use std::io::Cursor;
    use std::io::Seek;
    use std::io::SeekFrom;

    use quickcheck::quickcheck;

    use super::*;

    macro_rules! check_round_trip {
        ($N: expr) => {{
            let mut v = vec![];
            let mut x = $N;
            v.write_vlq(x).expect("write");

            // `z` and `y` below are helpful for the compiler to figure out the return type of
            // `read_vlq_at`, and `read_vlq`.
            #[allow(unused_assignments)]
            let mut z = x;
            let t = v.read_vlq_at(0).unwrap();
            z = t.0;

            let mut c = Cursor::new(v);
            let y = x;
            x = c.read_vlq().unwrap();
            x == y && y == z && t.1 == c.position() as usize
        }};
    }

    #[test]
    fn test_round_trip_manual() {
        for i in (0..64)
            .flat_map(|b| vec![1u64 << b, (1 << b) + 1, (1 << b) - 1].into_iter())
            .chain(vec![0xb3a73ce2ff2, 0xab54a98ceb1f0ad2].into_iter())
            .flat_map(|i| vec![i, !i].into_iter())
        {
            assert!(check_round_trip!(i as i8));
            assert!(check_round_trip!(i as i16));
            assert!(check_round_trip!(i as i32));
            assert!(check_round_trip!(i as i64));
            assert!(check_round_trip!(i as isize));
            assert!(check_round_trip!(i as u8));
            assert!(check_round_trip!(i as u16));
            assert!(check_round_trip!(i as u32));
            assert!(check_round_trip!(i as u64));
            assert!(check_round_trip!(i as usize));
        }
    }

    #[test]
    fn test_read_errors() {
        let mut c = Cursor::new(vec![]);
        assert_eq!(
            (c.read_vlq() as io::Result<u64>).unwrap_err().kind(),
            io::ErrorKind::UnexpectedEof
        );

        let mut c = Cursor::new(vec![255, 129]);
        assert_eq!(
            (c.read_vlq() as io::Result<u64>).unwrap_err().kind(),
            io::ErrorKind::UnexpectedEof
        );

        c.seek(SeekFrom::Start(0)).unwrap();
        assert_eq!(
            (c.read_vlq() as io::Result<u8>).unwrap_err().kind(),
            io::ErrorKind::InvalidData
        );
    }

    #[test]
    fn test_zig_zag() {
        let mut c = Cursor::new(vec![]);
        for (i, u) in [
            (0, 0),
            (-1, 1),
            (1, 2),
            (-2, 3),
            (-127, 253),
            (127, 254),
            (-128i8, 255u8),
        ] {
            c.seek(SeekFrom::Start(0)).expect("seek");
            c.write_vlq(i).expect("write");
            c.seek(SeekFrom::Start(0)).expect("seek");
            let x: u8 = c.read_vlq().unwrap();
            assert_eq!(x, u);
        }
    }

    quickcheck! {
        fn test_round_trip_u64_quickcheck(x: u64) -> bool {
            check_round_trip!(x)
        }

        fn test_round_trip_i64_quickcheck(x: i64) -> bool {
            check_round_trip!(x)
        }

        fn test_round_trip_u8_quickcheck(x: u8) -> bool {
            check_round_trip!(x)
        }

        fn test_round_trip_i8_quickcheck(x: i8) -> bool {
            check_round_trip!(x)
        }
    }
}


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

use std::path::PathBuf;
use std::sync::Arc;
use std::time::SystemTime;

use anyhow::anyhow;
use anyhow::Result;
use configmodel::Config;
use io::IO;
use pathmatcher::Matcher;
use thrift_types::edenfs::ScmFileStatus;
use types::RepoPathBuf;
use vfs::VFS;

use crate::filesystem::ChangeType;
use crate::filesystem::PendingChangeResult;
use crate::filesystem::PendingChanges;

pub struct EdenFileSystem {
    root: PathBuf,
}

impl EdenFileSystem {
    pub fn new(vfs: VFS) -> Result<Self> {
        Ok(EdenFileSystem {
            root: vfs.root().to_path_buf(),
        })
    }
}

impl PendingChanges for EdenFileSystem {
    fn pending_changes(
        &self,
        _matcher: Arc<dyn Matcher + Send + Sync + 'static>,
        _ignore_matcher: Arc<dyn Matcher + Send + Sync + 'static>,
        _ignore_dirs: Vec<PathBuf>,
        _last_write: SystemTime,
        _config: &dyn Config,
        _io: &IO,
    ) -> Result<Box<dyn Iterator<Item = Result<PendingChangeResult>>>> {
        let result = edenfs_client::status::get_status(&self.root)?;
        Ok(Box::new(result.status.entries.into_iter().filter_map(
            |(path, status)| {
                {
                    // TODO: Handle non-UTF8 encoded paths from Eden
                    let repo_path = match RepoPathBuf::from_utf8(path) {
                        Ok(repo_path) => repo_path,
                        Err(err) => return Some(Err(anyhow!(err))),
                    };
                    match status {
                        ScmFileStatus::REMOVED => Some(Ok(PendingChangeResult::File(
                            ChangeType::Deleted(repo_path),
                        ))),
                        ScmFileStatus::IGNORED => None,
                        _ => Some(Ok(PendingChangeResult::File(ChangeType::Changed(
                            repo_path,
                        )))),
                    }
                }
            },
        )))
    }
}


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

use thiserror::Error;

#[derive(Debug, Error)]
#[error("repository {0} not found!")]
pub struct RepoNotFound(pub String);


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

use std::collections::HashMap;
use std::sync::Arc;

use anyhow::Result;
use futures::StreamExt;
use manifest::Manifest;
use manifest_tree::TreeManifest;
use parking_lot::RwLock;
use pathmatcher::ExactMatcher;
use progress_model::ProgressBar;
use storemodel::minibytes::Bytes;
use storemodel::ReadFileContents;
use treestate::filestate::StateFlags;
use types::Key;
use types::RepoPathBuf;
use vfs::VFS;

use crate::filesystem::ChangeType;
use crate::metadata;
use crate::metadata::HgModifiedTime;
use crate::metadata::Metadata;

pub type ArcReadFileContents = Arc<dyn ReadFileContents<Error = anyhow::Error> + Send + Sync>;

pub(crate) enum FileChangeResult {
    Yes(ChangeType),
    No(RepoPathBuf),
    Maybe((RepoPathBuf, Metadata)),
}

impl FileChangeResult {
    fn changed(path: RepoPathBuf) -> Self {
        Self::Yes(ChangeType::Changed(path))
    }

    fn deleted(path: RepoPathBuf) -> Self {
        Self::Yes(ChangeType::Deleted(path))
    }
}

#[derive(Debug)]
pub enum ResolvedFileChangeResult {
    Yes(ChangeType),
    No(RepoPathBuf),
}

impl ResolvedFileChangeResult {
    fn changed(path: RepoPathBuf) -> Self {
        Self::Yes(ChangeType::Changed(path))
    }
}

pub(crate) trait FileChangeDetectorTrait:
    IntoIterator<Item = Result<ResolvedFileChangeResult>>
{
    fn submit(&mut self, file: metadata::File);
    fn total_work_hint(&self, _hint: u64) {}
}

pub struct FileChangeDetector {
    vfs: VFS,
    last_write: HgModifiedTime,
    results: Vec<Result<ResolvedFileChangeResult>>,
    lookups: RepoPathMap<Metadata>,
    manifest: Arc<RwLock<TreeManifest>>,
    store: ArcReadFileContents,
    worker_count: usize,
    progress: Arc<ProgressBar>,
}

impl FileChangeDetector {
    pub fn new(
        vfs: VFS,
        last_write: HgModifiedTime,
        manifest: Arc<RwLock<TreeManifest>>,
        store: ArcReadFileContents,
        worker_count: Option<usize>,
    ) -> Self {
        let case_sensitive = vfs.case_sensitive();
        FileChangeDetector {
            vfs,
            last_write,
            lookups: RepoPathMap::new(case_sensitive),
            results: Vec::new(),
            manifest,
            store,
            worker_count: worker_count.unwrap_or(10),
            progress: ProgressBar::register_new("comparing", 0, "files"),
        }
    }
}

const NEED_CHECK: StateFlags = StateFlags::NEED_CHECK;
const EXIST_P1: StateFlags = StateFlags::EXIST_P1;

pub(crate) fn file_changed_given_metadata(
    vfs: &VFS,
    file: metadata::File,
    last_write: HgModifiedTime,
) -> Result<FileChangeResult> {
    let path = file.path;

    let fs_meta = match file.fs_meta {
        Some(fs_meta) => fs_meta,
        None => match vfs.metadata(&path) {
            Ok(metadata) => Some(metadata.into()),
            Err(e) => match e.downcast_ref::<std::io::Error>() {
                Some(e) if e.kind() == std::io::ErrorKind::NotFound => None,
                _ => return Err(e),
            },
        },
    };

    // First handle when metadata is None (i.e. file doesn't exist).
    let (fs_meta, state) = match (fs_meta, file.ts_state) {
        // File was untracked during crawl but no longer exists.
        (None, None) => {
            tracing::trace!(?path, "neither on disk nor in treestate");
            return Ok(FileChangeResult::No(path));
        }

        // File was not found but exists in P1: mark as deleted.
        (None, Some(state)) if state.state.intersects(EXIST_P1) => {
            tracing::trace!(?path, "not on disk, in P1");
            return Ok(FileChangeResult::deleted(path.to_owned()));
        }

        // File doesn't exist, isn't in P1 but exists in treestate.
        // This can happen when watchman is tracking that this file needs
        // checking for example.
        (None, Some(_)) => {
            tracing::trace!(?path, "neither on disk nor in P1");
            return Ok(FileChangeResult::No(path));
        }

        (Some(m), s) => (m, s),
    };

    // Don't check EXIST_P2. If file is only in P2 we want to report "changed"
    // even if its contents happen to match an untracked file on disk.
    let in_parent = matches!(&state, Some(s) if s.state.intersects(EXIST_P1));
    let is_trackable_file = fs_meta.is_file(vfs) || fs_meta.is_symlink(vfs);

    let state = match (in_parent, is_trackable_file) {
        // If the file is not valid (e.g. a directory or a weird file like
        // a fifo file) but exists in P1 (as a valid file at some previous
        // time) then we consider it now deleted.
        (true, false) => {
            tracing::trace!(?path, "changed (in_parent, !trackable)");
            return Ok(FileChangeResult::deleted(path));
        }
        // File not in parent and not trackable - skip it. We can get here if
        // the file was valid during the crawl but no longer is.
        (false, false) => {
            tracing::trace!(?path, "no (!in_parent, !trackable)");
            return Ok(FileChangeResult::No(path));
        }
        // File exists but is not in the treestate (untracked)
        (false, true) => {
            tracing::trace!(?path, "changed (!in_parent, trackable)");
            return Ok(FileChangeResult::changed(path));
        }
        (true, true) => state.unwrap(),
    };

    let flags = state.state;

    let ts_meta: Metadata = state.into();

    // If working copy file size or flags are different from what is in treestate, it has changed.
    // Note: state.size is i32 since Mercurial uses negative numbers to indicate special files.
    // A -1 indicates the file is either in a merge state or a lookup state.
    // A -2 indicates the file comes from the other parent (and may or may not exist in the
    // current parent).
    //
    // Regardless, if the size is negative, we'll do a lookup comparison since we can't
    // determine if the file has changed relative to p1. This logic is a mess and we should get
    // rid of all these negative numbers.
    if let Some(ts_size) = ts_meta.len() {
        let size_different = fs_meta.len() != Some(ts_size);
        let exec_different = fs_meta.is_executable(vfs) != ts_meta.is_executable(vfs);
        let symlink_different = fs_meta.is_symlink(vfs) != ts_meta.is_symlink(vfs);
        if size_different || exec_different || symlink_different {
            tracing::trace!(
                ?path,
                size_different,
                exec_different,
                symlink_different,
                "changed (metadata mismatch)"
            );
            return Ok(FileChangeResult::changed(path.to_owned()));
        }
    } else {
        tracing::trace!(?path, "maybe (no size)");
        return Ok(FileChangeResult::Maybe((path, fs_meta)));
    }

    // If it's marked NEED_CHECK, we always need to do a lookup, regardless of the mtime.
    if flags.intersects(NEED_CHECK) {
        tracing::trace!(?path, "maybe (NEED_CHECK)");
        return Ok(FileChangeResult::Maybe((path, fs_meta)));
    }

    // If the mtime has changed or matches the last normal() write time, we need to compare the
    // file contents in the later Lookups phase.  mtime can be negative as well. A -1 indicates
    // the file is in a lookup state. Since a -1 will always cause the equality comparison
    // below to fail and force a lookup, the -1 is handled correctly without special casing. In
    // theory all -1 files should be marked NEED_CHECK above (I think).
    let ts_mtime = match ts_meta.mtime() {
        None => {
            tracing::trace!(?path, "maybe (no mtime)");
            return Ok(FileChangeResult::Maybe((path, fs_meta)));
        }
        Some(ts) => ts,
    };

    if Some(ts_mtime) != fs_meta.mtime() || ts_mtime == last_write {
        tracing::trace!(?path, "maybe (mtime doesn't match)");
        return Ok(FileChangeResult::Maybe((path, fs_meta)));
    }

    tracing::trace!(?path, "no (fallthrough)");
    Ok(FileChangeResult::No(path))
}

fn compare_repo_bytes_to_disk(
    vfs: &VFS,
    repo_bytes: Bytes,
    path: RepoPathBuf,
) -> Result<ResolvedFileChangeResult> {
    match vfs.read(&path) {
        Ok(disk_bytes) => {
            if disk_bytes == repo_bytes {
                tracing::trace!(?path, "no (contents match)");
                Ok(ResolvedFileChangeResult::No(path))
            } else {
                tracing::trace!(?path, "changed (contents mismatch)");
                Ok(ResolvedFileChangeResult::Yes(ChangeType::Changed(path)))
            }
        }
        Err(e) => {
            if let Some(e) = e.downcast_ref::<std::io::Error>() {
                if e.kind() == std::io::ErrorKind::NotFound {
                    tracing::trace!(?path, "deleted (file missing)");
                    return Ok(ResolvedFileChangeResult::Yes(ChangeType::Deleted(path)));
                }
            }

            if let Some(vfs::AuditError::ThroughSymlink(_)) = e.downcast_ref::<vfs::AuditError>() {
                tracing::trace!(?path, "deleted (read through symlink)");
                return Ok(ResolvedFileChangeResult::Yes(ChangeType::Deleted(path)));
            }

            Err(e)
        }
    }
}

impl FileChangeDetector {
    pub(crate) fn has_changed_with_fresh_metadata(
        &mut self,
        file: metadata::File,
    ) -> Result<FileChangeResult> {
        let res = file_changed_given_metadata(&self.vfs, file, self.last_write);

        if let Ok(FileChangeResult::Maybe((ref path, ref meta))) = res {
            self.lookups.insert(path.to_owned(), meta.clone());
        }

        res
    }
}

impl FileChangeDetectorTrait for FileChangeDetector {
    fn submit(&mut self, file: metadata::File) {
        match self.has_changed_with_fresh_metadata(file) {
            Ok(res) => match res {
                FileChangeResult::Yes(change) => {
                    self.progress.increase_position(1);
                    self.results.push(Ok(ResolvedFileChangeResult::Yes(change)))
                }
                FileChangeResult::No(path) => {
                    self.progress.increase_position(1);
                    self.results.push(Ok(ResolvedFileChangeResult::No(path)))
                }
                FileChangeResult::Maybe((path, meta)) => {
                    self.lookups.insert(path, meta);
                }
            },
            Err(err) => self.results.push(Err(err)),
        };
    }

    fn total_work_hint(&self, hint: u64) {
        self.progress.set_total(hint)
    }
}

fn manifest_flags_mismatch(vfs: &VFS, mf_meta: Metadata, fs_meta: &Metadata) -> bool {
    mf_meta.is_symlink(vfs) != fs_meta.is_symlink(vfs)
        || mf_meta.is_executable(vfs) != fs_meta.is_executable(vfs)
}

// Allows case insensitive tracking of RepoPathBuf->V. We need this because we
// "lose" the caseness of a path after it goes through
// manifest.files(ExectMatcher::new([path], case_sensitive=true)). The manifest
// file we get back has whatever case is in the manifest, so without this it is
// impossible to map back to the original path we gave to ExactMatcher.
struct RepoPathMap<V> {
    case_sensitive: bool,
    map: HashMap<RepoPathBuf, V>,
}

impl<V> RepoPathMap<V> {
    pub fn new(case_sensitive: bool) -> Self {
        Self {
            case_sensitive,
            map: HashMap::new(),
        }
    }

    pub fn insert(&mut self, key: RepoPathBuf, value: V) -> Option<V> {
        match self.case_sensitive {
            true => self.map.insert(key, value),
            false => self.map.insert(key.to_lower_case(), value),
        }
    }

    pub fn get(&self, key: &RepoPathBuf) -> Option<&V> {
        match self.case_sensitive {
            true => self.map.get(key),
            false => self.map.get(&key.to_lower_case()),
        }
    }

    pub fn keys(&self) -> std::collections::hash_map::Keys<'_, RepoPathBuf, V> {
        self.map.keys()
    }

    pub fn len(&self) -> usize {
        self.map.len()
    }
}

impl IntoIterator for FileChangeDetector {
    type Item = Result<ResolvedFileChangeResult>;
    type IntoIter = std::vec::IntoIter<Self::Item>;

    #[tracing::instrument(skip_all)]
    fn into_iter(mut self) -> Self::IntoIter {
        let bar = self.progress;

        let _span = tracing::info_span!("check manifest", lookups = self.lookups.len()).entered();

        // First, get the keys for the paths from the current manifest.
        let matcher = ExactMatcher::new(self.lookups.keys(), self.vfs.case_sensitive());
        let keys = self
            .manifest
            .read()
            .files(matcher)
            .filter_map(|result| {
                let file = match result {
                    Ok(file) => {
                        if manifest_flags_mismatch(
                            &self.vfs,
                            file.meta.file_type.into(),
                            self.lookups.get(&file.path).unwrap(),
                        ) {
                            tracing::trace!(path=?file.path, "changed (mf flags mismatch disk)");
                            self.results
                                .push(Ok(ResolvedFileChangeResult::changed(file.path)));
                            bar.increase_position(1);
                            return None;
                        }

                        file
                    }
                    Err(e) => {
                        self.results.push(Err(e));
                        return None;
                    }
                };
                Some(Key::new(file.path, file.meta.hgid))
            })
            .collect::<Vec<_>>();

        drop(_span);

        let _span = tracing::info_span!("compare contents", keys = keys.len()).entered();

        let (disk_send, disk_recv) = crossbeam::channel::unbounded::<(RepoPathBuf, Bytes)>();
        let (results_send, results_recv) =
            crossbeam::channel::unbounded::<Result<ResolvedFileChangeResult>>();

        for _ in 0..self.worker_count {
            let vfs = self.vfs.clone();
            let disk_recv = disk_recv.clone();
            let results_send = results_send.clone();
            let bar = bar.clone();
            std::thread::spawn(move || {
                for (path, repo_bytes) in disk_recv {
                    results_send
                        .send(compare_repo_bytes_to_disk(&vfs, repo_bytes, path))
                        .unwrap();
                    bar.increase_position(1);
                }
            });
        }

        // Then fetch the contents of each file and check it against the filesystem.
        // TODO: if the underlying stores gain the ability to do hash-based comparisons,
        // switch this to use that (rather than pulling down the entire contents of each
        // file).
        async_runtime::block_on(async {
            let _span = tracing::info_span!("read_file_contents").entered();

            let mut results = self.store.read_file_contents(keys).await;
            while let Some(result) = results.next().await {
                match result {
                    Ok((bytes, key)) => disk_send.send((key.path, bytes)).unwrap(),
                    Err(e) => results_send.send(Err(e)).unwrap(),
                };
            }
        });

        drop(results_send);
        drop(disk_send);

        self.results.extend(results_recv.into_iter());
        self.results.into_iter()
    }
}


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

use anyhow::bail;
use anyhow::Result;
use configloader::config::ConfigSet;
use configloader::Config;

pub struct Submodule {
    pub name: String,
    pub url: String,
    pub path: String,
}

pub fn parse_submodules(data: &[u8]) -> Result<Vec<Submodule>> {
    // strip leading space so things don't look like multiline values
    let data = String::from_utf8_lossy(data)
        .lines()
        .filter_map(|l| match l.trim() {
            "" => None,
            l => Some(l),
        })
        .collect::<Vec<&str>>()
        .join("\n");

    let mut cfg = ConfigSet::new();
    let errors = cfg.parse(data, &".gitmodules".into());
    if !errors.is_empty() {
        bail!("error parsing .gitmodules: {:?}", errors);
    }

    let mut submodules = Vec::new();
    let prefix = "submodule \"";
    let suffix = "\"";
    for sec in cfg.sections().iter() {
        if sec.starts_with(prefix) && sec.ends_with(suffix) && sec.len() > prefix.len() {
            let (mut url, mut path) = (None, None);
            for key in cfg.keys(sec) {
                if key == "url" {
                    url = cfg.get_nonempty(sec, &key);
                } else if key == "path" {
                    path = cfg.get_nonempty(sec, &key);
                }
            }

            if let (Some(url), Some(path)) = (url, path) {
                submodules.push(Submodule {
                    name: sec[prefix.len()..sec.len() - suffix.len()].replace('.', "_"),
                    url: url.to_string(),
                    path: path.to_string(),
                })
            }
        }
    }
    Ok(submodules)
}


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

#[cfg(feature = "eden")]
pub mod edenfs;

mod errors;
mod filechangedetector;
pub mod filesystem;
pub mod git;
mod metadata;
pub mod physicalfs;
pub mod sparse;
pub mod status;
mod util;
pub mod walker;
pub mod watchmanfs;
pub mod workingcopy;


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

#[cfg(unix)]
use std::os::unix::prelude::PermissionsExt;
use std::time::SystemTime;

use anyhow::Error;
use anyhow::Result;
use bitflags::bitflags;
use manifest::FileType;
use treestate::filestate::FileStateV2;
use types::RepoPathBuf;
use vfs::VFS;

#[derive(Debug)]
pub(crate) struct File {
    pub path: RepoPathBuf,
    // Outer Option is whether fs_meta is populated. Inner Option is whether file exists.
    pub fs_meta: Option<Option<Metadata>>,
    pub ts_state: Option<FileStateV2>,
}

bitflags! {
    pub(crate) struct MetadataFlags: u8 {
        const IS_SYMLINK = 1 << 0;
        const IS_EXEC = 1 << 1;
        const IS_REGULAR = 1 << 2;
        const IS_DIR = 1 << 3;
        const HAS_MTIME = 1 << 4;
        const HAS_SIZE = 1 << 5;
    }
}

/// Metadata abstracts across the different places file metadata can come from.
#[derive(Debug, Clone)]
pub(crate) struct Metadata {
    flags: MetadataFlags,
    size: u64,
    mtime: HgModifiedTime,
}

impl Metadata {
    pub fn is_symlink(&self, vfs: &VFS) -> bool {
        vfs.supports_symlinks() && self.flags.intersects(MetadataFlags::IS_SYMLINK)
    }

    pub fn is_executable(&self, vfs: &VFS) -> bool {
        vfs.supports_executables() && self.flags.intersects(MetadataFlags::IS_EXEC)
    }

    pub fn is_file(&self, vfs: &VFS) -> bool {
        if vfs.supports_symlinks() {
            self.flags.intersects(MetadataFlags::IS_REGULAR)
        } else {
            // If symlinks aren't supported, treat symlinks as regular files.
            self.flags
                .intersects(MetadataFlags::IS_REGULAR | MetadataFlags::IS_SYMLINK)
        }
    }

    pub fn len(&self) -> Option<u64> {
        if self.flags.intersects(MetadataFlags::HAS_SIZE) {
            Some(self.size)
        } else {
            None
        }
    }

    pub fn mtime(&self) -> Option<HgModifiedTime> {
        if self.flags.intersects(MetadataFlags::HAS_MTIME) {
            Some(self.mtime)
        } else {
            None
        }
    }

    pub fn from_stat(mode: u32, size: u64, mtime: i64) -> Self {
        // Watchman sends mode_t even on Windows where they aren't fully
        // reflected in libc. Let's just hardcode the values we need.
        const S_IFLNK: u32 = 0o120000;
        const S_IFMT: u32 = 0o170000;
        const S_IFREG: u32 = 0o100000;
        const S_IFDIR: u32 = 0o040000;

        let mut flags = MetadataFlags::HAS_SIZE | MetadataFlags::HAS_MTIME;

        if mode & S_IFMT == S_IFLNK {
            flags |= MetadataFlags::IS_SYMLINK;
        } else if mode & 0o111 != 0 {
            flags |= MetadataFlags::IS_EXEC;
        }

        if mode & S_IFMT == S_IFREG {
            flags |= MetadataFlags::IS_REGULAR;
        }

        if mode & S_IFMT == S_IFDIR {
            flags |= MetadataFlags::IS_DIR;
        }

        Self {
            flags,
            size,
            mtime: mask_stat_mtime(mtime),
        }
    }
}

impl From<FileStateV2> for Metadata {
    fn from(s: FileStateV2) -> Self {
        let mut flags = MetadataFlags::empty();

        let size = match s.size {
            size if size < 0 => 0,
            size => {
                flags |= MetadataFlags::HAS_SIZE;
                size as u64
            }
        };

        let mtime = match s.mtime {
            m if m < 0 => HgModifiedTime(0),
            m => {
                flags |= MetadataFlags::HAS_MTIME;
                HgModifiedTime(m as u64)
            }
        };

        if s.is_symlink() {
            flags |= MetadataFlags::IS_SYMLINK;
        } else {
            flags |= MetadataFlags::IS_REGULAR;
            if s.is_executable() {
                flags |= MetadataFlags::IS_EXEC;
            }
        }

        Self { flags, size, mtime }
    }
}

impl From<std::fs::Metadata> for Metadata {
    fn from(m: std::fs::Metadata) -> Self {
        let mut flags = MetadataFlags::HAS_SIZE;

        if m.is_symlink() {
            flags |= MetadataFlags::IS_SYMLINK;
        } else if m.is_file() {
            flags |= MetadataFlags::IS_REGULAR;

            #[cfg(unix)]
            if m.permissions().mode() & 0o111 != 0 {
                flags |= MetadataFlags::IS_EXEC;
            }
        } else if m.is_dir() {
            flags |= MetadataFlags::IS_DIR;
        }

        let mtime = match m.modified() {
            Err(_) => HgModifiedTime(0),
            Ok(mtime) => {
                flags |= MetadataFlags::HAS_MTIME;
                mtime.into()
            }
        };

        Self {
            flags,
            mtime,
            size: m.len(),
        }
    }
}

impl From<FileType> for Metadata {
    fn from(ft: FileType) -> Self {
        let flags = match ft {
            FileType::Regular => MetadataFlags::IS_REGULAR,
            FileType::Executable => MetadataFlags::IS_EXEC | MetadataFlags::IS_REGULAR,
            FileType::Symlink => MetadataFlags::IS_SYMLINK,
            FileType::GitSubmodule => MetadataFlags::empty(),
        };

        Self {
            flags,
            mtime: HgModifiedTime(0),
            size: 0,
        }
    }
}

/// Represents a file modification time in Mercurial, in seconds since the unix epoch.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HgModifiedTime(u64);

impl From<u64> for HgModifiedTime {
    fn from(value: u64) -> Self {
        HgModifiedTime(value)
    }
}

impl From<u32> for HgModifiedTime {
    fn from(value: u32) -> Self {
        HgModifiedTime(value.into())
    }
}

// Mask used to make "crazy" mtimes operable. We basically take
// "mtime % 2**31-1". Note that 0x7FFFFFFF is in 2038 - not that far off. We may
// want to reconsider this. https://bz.mercurial-scm.org/show_bug.cgi?id=2608 is
// the original upstream introduction of this workaround.
const CRAZY_MTIME_MASK: i64 = 0x7FFFFFFF;

fn mask_stat_mtime(mtime: i64) -> HgModifiedTime {
    // Handle crazy mtimes by masking into reasonable range. This is what
    // dirstate.py does, so we may get some modicum of compatibility by
    // using the same approach.
    HgModifiedTime((mtime & CRAZY_MTIME_MASK) as u64)
}

impl From<SystemTime> for HgModifiedTime {
    fn from(value: SystemTime) -> Self {
        let signed_epoch = match value.duration_since(SystemTime::UNIX_EPOCH) {
            Ok(d) => d.as_secs() as i64,
            // value is before UNIX_EPOCH
            Err(err) => -(err.duration().as_secs() as i64),
        };

        mask_stat_mtime(signed_epoch)
    }
}

impl TryFrom<i32> for HgModifiedTime {
    type Error = Error;
    fn try_from(value: i32) -> Result<Self> {
        Ok(HgModifiedTime(value.try_into()?))
    }
}


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

use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::SystemTime;

use anyhow::Result;
use configmodel::Config;
use configmodel::ConfigExt;
use io::IO;
use manifest_tree::ReadTreeManifest;
use parking_lot::Mutex;
use pathmatcher::Matcher;
use storemodel::ReadFileContents;
use treestate::filestate::StateFlags;
use treestate::tree::VisitorResult;
use treestate::treestate::TreeState;
use types::RepoPathBuf;
use vfs::VFS;

use crate::filechangedetector::FileChangeDetector;
use crate::filechangedetector::FileChangeResult;
use crate::filechangedetector::ResolvedFileChangeResult;
use crate::filesystem::ChangeType;
use crate::filesystem::PendingChangeResult;
use crate::filesystem::PendingChanges as PendingChangesTrait;
use crate::metadata;
use crate::walker::WalkEntry;
use crate::walker::Walker;
use crate::workingcopy::WorkingCopy;

type ArcReadFileContents = Arc<dyn ReadFileContents<Error = anyhow::Error> + Send + Sync>;
type ArcReadTreeManifest = Arc<dyn ReadTreeManifest + Send + Sync>;

pub struct PhysicalFileSystem {
    // TODO: Make this an Arc<Mutex<VFS>> so we can persist the vfs pathauditor cache
    vfs: VFS,
    tree_resolver: ArcReadTreeManifest,
    store: ArcReadFileContents,
    treestate: Arc<Mutex<TreeState>>,
    include_directories: bool,
}

impl PhysicalFileSystem {
    pub fn new(
        vfs: VFS,
        tree_resolver: ArcReadTreeManifest,
        store: ArcReadFileContents,
        treestate: Arc<Mutex<TreeState>>,
        include_directories: bool,
    ) -> Result<Self> {
        Ok(PhysicalFileSystem {
            vfs,
            tree_resolver,
            store,
            treestate,
            include_directories,
        })
    }
}

impl PendingChangesTrait for PhysicalFileSystem {
    fn pending_changes(
        &self,
        matcher: Arc<dyn Matcher + Send + Sync + 'static>,
        _ignore_matcher: Arc<dyn Matcher + Send + Sync + 'static>,
        ignore_dirs: Vec<PathBuf>,
        last_write: SystemTime,
        config: &dyn Config,
        _io: &IO,
    ) -> Result<Box<dyn Iterator<Item = Result<PendingChangeResult>>>> {
        let root = self.vfs.root().to_path_buf();
        let ident = identity::must_sniff_dir(&root)?;
        let walker = Walker::new(
            root,
            ident.dot_dir().to_string(),
            ignore_dirs,
            matcher.clone(),
            false,
        )?;
        let manifests =
            WorkingCopy::current_manifests(&self.treestate.lock(), &self.tree_resolver)?;
        let file_change_detector = FileChangeDetector::new(
            self.vfs.clone(),
            last_write.try_into()?,
            manifests[0].clone(),
            self.store.clone(),
            config.get_opt("workingcopy", "worker-count")?,
        );
        let pending_changes = PendingChanges {
            walker,
            matcher,
            treestate: self.treestate.clone(),
            stage: PendingChangesStage::Walk,
            include_directories: self.include_directories,
            seen: HashSet::new(),
            tree_iter: None,
            lookup_iter: None,
            file_change_detector: Some(file_change_detector),
        };
        Ok(Box::new(pending_changes))
    }
}

pub struct PendingChanges<M: Matcher + Clone + Send + Sync + 'static> {
    walker: Walker<M>,
    matcher: M,
    treestate: Arc<Mutex<TreeState>>,
    stage: PendingChangesStage,
    include_directories: bool,
    seen: HashSet<RepoPathBuf>,
    tree_iter: Option<Box<dyn Iterator<Item = Result<PendingChangeResult>> + Send>>,
    lookup_iter: Option<Box<dyn Iterator<Item = Result<PendingChangeResult>> + Send>>,
    file_change_detector: Option<FileChangeDetector>,
}

#[derive(PartialEq)]
enum PendingChangesStage {
    Walk,
    IterateTree,
    Lookups,
    Finished,
}

impl PendingChangesStage {
    pub fn next(&self) -> PendingChangesStage {
        match self {
            PendingChangesStage::Walk => PendingChangesStage::IterateTree,
            PendingChangesStage::IterateTree => PendingChangesStage::Lookups,
            PendingChangesStage::Lookups => PendingChangesStage::Finished,
            PendingChangesStage::Finished => PendingChangesStage::Finished,
        }
    }
}

impl<M: Matcher + Clone + Send + Sync + 'static> PendingChanges<M> {
    fn next_walk(&mut self) -> Result<Option<PendingChangeResult>> {
        loop {
            match self.walker.next() {
                Some(Ok(WalkEntry::File(mut path, metadata))) => {
                    let mut ts = self.treestate.lock();

                    // On case insensitive systems, normalize the path so
                    // duplicate paths with different case can be detected in
                    // the seen set, but only if the dirstate entry hasn't been
                    // deleted.
                    let (normalized, ts_state) = ts.normalize_path_and_get(path.as_ref())?;
                    if normalized != path.as_byte_slice()
                        && ts_state
                            .as_ref()
                            .map_or(false, |s| s.state.intersects(StateFlags::EXIST_NEXT))
                    {
                        path = RepoPathBuf::from_utf8(normalized.into_owned())?;
                    }
                    self.seen.insert(path.clone());
                    let changed = self
                        .file_change_detector
                        .as_mut()
                        .unwrap()
                        .has_changed_with_fresh_metadata(metadata::File {
                            path,
                            ts_state,
                            fs_meta: Some(Some(metadata.into())),
                        })?;

                    if let FileChangeResult::Yes(change_type) = changed {
                        return Ok(Some(PendingChangeResult::File(change_type)));
                    }
                }
                Some(Ok(WalkEntry::Directory(dir))) => {
                    if self.include_directories {
                        return Ok(Some(PendingChangeResult::SeenDirectory(dir)));
                    }
                }
                Some(Err(e)) => {
                    return Err(e);
                }
                None => {
                    return Ok(None);
                }
            };
        }
    }

    fn next_tree(&mut self) -> Option<Result<PendingChangeResult>> {
        if self.tree_iter.is_none() {
            self.tree_iter = Some(Box::new(self.get_tree_entries().into_iter()));
        }

        self.tree_iter.as_mut().unwrap().next()
    }

    fn get_tree_entries(&mut self) -> Vec<Result<PendingChangeResult>> {
        let tracked = match self.get_tracked_from_p1() {
            Err(e) => return vec![Err(e)],
            Ok(tracked) => tracked,
        };
        let mut ts = self.treestate.lock();

        tracked
            .into_iter()
            .filter_map(|mut path| {
                let normalized = match ts.normalize_path(path.as_ref()) {
                    Ok(path) => path,
                    Err(e) => return Some(Err(e)),
                };
                if normalized != path.as_byte_slice() {
                    path = match RepoPathBuf::from_utf8(normalized.into_owned()) {
                        Ok(path) => path,
                        Err(e) => return Some(Err(e.into())),
                    };
                }

                // Skip this path if we've seen it or it doesn't match the matcher.
                if self.seen.contains(&path) {
                    return None;
                } else {
                    match self.matcher.matches_file(&path) {
                        Err(e) => {
                            return Some(Err(e));
                        }
                        Ok(false) => return None,
                        Ok(true) => {}
                    }
                }

                // This path is EXIST_P1 but not on disk - emit deleted event.
                Some(Ok(PendingChangeResult::File(ChangeType::Deleted(
                    path.to_owned(),
                ))))
            })
            .collect()
    }

    /// Returns the files in the treestate that are from p1.
    /// We only care about files from p1 because pending_changes is relative to p1.
    fn get_tracked_from_p1(&self) -> Result<Vec<RepoPathBuf>> {
        let mut result = Vec::new();
        let mask = StateFlags::EXIST_P1;

        self.treestate.lock().visit(
            &mut |components, _| {
                let path = components.concat();
                let path = RepoPathBuf::from_utf8(path)?;
                result.push(path);
                Ok(VisitorResult::NotChanged)
            },
            &|_path, dir| match dir.get_aggregated_state() {
                None => true,
                Some(state) => state.union.intersects(mask),
            },
            &|_path, file| file.state.intersects(mask),
        )?;
        Ok(result)
    }

    fn next_lookup(&mut self) -> Option<Result<PendingChangeResult>> {
        self.lookup_iter
            .get_or_insert_with(|| {
                let iter = self
                    .file_change_detector
                    .take()
                    .unwrap()
                    .into_iter()
                    .filter_map(|result| match result {
                        Ok(ResolvedFileChangeResult::Yes(change_type)) => {
                            Some(Ok(PendingChangeResult::File(change_type)))
                        }
                        Ok(ResolvedFileChangeResult::No(_)) => None,
                        Err(e) => Some(Err(e)),
                    });
                Box::new(iter)
            })
            .next()
    }
}

impl<M: Matcher + Clone + Send + Sync + 'static> Iterator for PendingChanges<M> {
    type Item = Result<PendingChangeResult>;

    fn next(&mut self) -> Option<Self::Item> {
        // TODO: Try to make this into a chain instead of a manual state machine
        loop {
            let change = match self.stage {
                PendingChangesStage::Walk => self.next_walk().transpose(),
                PendingChangesStage::IterateTree => self.next_tree(),
                PendingChangesStage::Lookups => self.next_lookup(),
                PendingChangesStage::Finished => None,
            };

            if change.is_some() {
                return change;
            }

            self.stage = self.stage.next();
            if self.stage == PendingChangesStage::Finished {
                return None;
            }
        }
    }
}