rhg: Propagate permission errors when finding a repository
The Rust standard library has a `Path::is_dir` method that returns false
for any I/O error (such as a permission error),
not just "No such file or directory".
Instead add an `is_dir` function that returns false for non-directories
and for "No such file or directory" errors, but propagates other I/O errors.
Differential Revision: https://phab.mercurial-scm.org/D11230
--- a/rust/hg-core/src/errors.rs Thu Jul 29 11:53:03 2021 +0200
+++ b/rust/hg-core/src/errors.rs Thu Jul 29 12:22:25 2021 +0200
@@ -47,6 +47,8 @@
/// Details about where an I/O error happened
#[derive(Debug)]
pub enum IoErrorContext {
+ /// `std::fs::metadata`
+ ReadingMetadata(std::path::PathBuf),
ReadingFile(std::path::PathBuf),
WritingFile(std::path::PathBuf),
RemovingFile(std::path::PathBuf),
@@ -108,6 +110,9 @@
impl fmt::Display for IoErrorContext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
+ IoErrorContext::ReadingMetadata(path) => {
+ write!(f, "when reading metadata of {}", path.display())
+ }
IoErrorContext::ReadingFile(path) => {
write!(f, "when reading {}", path.display())
}
--- a/rust/hg-core/src/repo.rs Thu Jul 29 11:53:03 2021 +0200
+++ b/rust/hg-core/src/repo.rs Thu Jul 29 12:22:25 2021 +0200
@@ -6,6 +6,7 @@
use crate::utils::SliceExt;
use memmap::{Mmap, MmapOptions};
use std::collections::HashSet;
+use std::io::ErrorKind;
use std::path::{Path, PathBuf};
/// A repository on disk
@@ -51,7 +52,7 @@
// ancestors() is inclusive: it first yields `current_directory`
// as-is.
for ancestor in current_directory.ancestors() {
- if ancestor.join(".hg").is_dir() {
+ if is_dir(ancestor.join(".hg"))? {
return Ok(ancestor.to_path_buf());
}
}
@@ -73,9 +74,9 @@
explicit_path: Option<PathBuf>,
) -> Result<Self, RepoError> {
if let Some(root) = explicit_path {
- if root.join(".hg").is_dir() {
+ if is_dir(root.join(".hg"))? {
Self::new_at_path(root.to_owned(), config)
- } else if root.is_file() {
+ } else if is_file(&root)? {
Err(HgError::unsupported("bundle repository").into())
} else {
Err(RepoError::NotFound {
@@ -130,7 +131,7 @@
if relative {
shared_path = dot_hg.join(shared_path)
}
- if !shared_path.is_dir() {
+ if !is_dir(&shared_path)? {
return Err(HgError::corrupted(format!(
".hg/sharedpath points to nonexistent directory {}",
shared_path.display()
@@ -286,3 +287,29 @@
.with_context(|| IoErrorContext::RenamingFile { from, to })
}
}
+
+fn fs_metadata(
+ path: impl AsRef<Path>,
+) -> Result<Option<std::fs::Metadata>, HgError> {
+ let path = path.as_ref();
+ match std::fs::metadata(path) {
+ Ok(meta) => Ok(Some(meta)),
+ Err(error) => match error.kind() {
+ // TODO: when we require a Rust version where `NotADirectory` is
+ // stable, invert this logic and return None for it and `NotFound`
+ // and propagate any other error.
+ ErrorKind::PermissionDenied => Err(error).with_context(|| {
+ IoErrorContext::ReadingMetadata(path.to_owned())
+ }),
+ _ => Ok(None),
+ },
+ }
+}
+
+fn is_dir(path: impl AsRef<Path>) -> Result<bool, HgError> {
+ Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_dir()))
+}
+
+fn is_file(path: impl AsRef<Path>) -> Result<bool, HgError> {
+ Ok(fs_metadata(path)?.map_or(false, |meta| meta.is_file()))
+}