mod decide;
mod walk;
use super::{Error, ParallelRuntime, Path, RepositoryMatcher, Result, ScanOptions, ScanRuntime};
use crate::report::ScanTermination;
use walk::{discover_parallel, discover_serial};
pub fn scan_repository_paths(root: impl AsRef<Path>) -> Result<Vec<String>> {
scan_repository_paths_with_runtime(
root.as_ref(),
&ScanOptions::default(),
&ParallelRuntime::global(),
)
}
pub(crate) fn scan_repository_paths_with_runtime(
root: &Path,
options: &ScanOptions,
parallel_runtime: &ParallelRuntime,
) -> Result<Vec<String>> {
let mut paths = discover_paths(root, options, parallel_runtime)?;
paths.sort_unstable();
Ok(paths)
}
fn discover_paths(
root: &Path,
options: &ScanOptions,
parallel_runtime: &ParallelRuntime,
) -> Result<Vec<String>> {
let canonical = resolve_root(root, options)?;
let matcher = RepositoryMatcher::with_options(&canonical, options)?;
let runtime = ScanRuntime::new();
let (paths, runtime) = if options.uses_parallel_traversal() {
discover_parallel(&canonical, options, matcher, runtime, parallel_runtime)?
} else {
discover_serial(&canonical, options, matcher, runtime)?
};
if let Some(reason) = runtime.external_termination(options) {
return Err(interrupted(&canonical, reason));
}
Ok(paths)
}
fn resolve_root(root: &Path, options: &ScanOptions) -> Result<std::path::PathBuf> {
if options.walk.root_symlink_policy == crate::RootSymlinkPolicy::Reject {
let metadata = std::fs::symlink_metadata(root).map_err(|source| Error::io(root, source))?;
if metadata.file_type().is_symlink() {
return Err(Error::io(
root,
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"root symlink rejected by policy",
),
));
}
}
let canonical = root
.canonicalize()
.map_err(|source| Error::io(root, source))?;
if canonical.is_dir() {
Ok(canonical)
} else {
Err(Error::InvalidRoot(canonical))
}
}
pub(super) fn finish(
root: &Path,
paths: Vec<String>,
runtime: ScanRuntime,
reason: ScanTermination,
) -> Result<(Vec<String>, ScanRuntime)> {
if let Some(error) = stop_error(root, reason) {
Err(error)
} else {
Ok((paths, runtime))
}
}
pub(super) fn stop_error(root: &Path, reason: ScanTermination) -> Option<Error> {
match reason {
ScanTermination::Cancelled | ScanTermination::Timeout => Some(interrupted(root, reason)),
ScanTermination::MaxEntries | ScanTermination::MaxTotalBytes => None,
}
}
pub(super) fn interrupted(root: &Path, reason: ScanTermination) -> Error {
let message = match reason {
ScanTermination::Cancelled => "scan cancelled",
ScanTermination::Timeout => "scan timed out",
ScanTermination::MaxEntries => "entry limit reached",
ScanTermination::MaxTotalBytes => "byte limit reached",
};
Error::io(
root,
std::io::Error::new(std::io::ErrorKind::Interrupted, message),
)
}