Skip to content

Commit 617879c

Browse files
committed
fix(processor): ensure correct error on Linux
1 parent 4169478 commit 617879c

File tree

2 files changed

+70
-30
lines changed

2 files changed

+70
-30
lines changed

src/processor/create.rs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use crate::core_types::{Action, CreateStatus};
44
use crate::errors::ProcessError;
55
use std::fs;
6+
use std::io; // Import io for ErrorKind
67
use std::path::Path;
78

89
/// Creates or overwrites a file with the provided content.
@@ -63,21 +64,51 @@ pub(crate) fn process_create(
6364
/// Also checks if the parent path itself is unexpectedly a file.
6465
fn ensure_parent_directory(target_path: &Path, resolved_base: &Path) -> Result<(), ProcessError> {
6566
if let Some(parent_dir) = target_path.parent() {
66-
if parent_dir == resolved_base || parent_dir.exists() {
67-
// If parent exists, ensure it's a directory
68-
if !parent_dir.is_dir() {
69-
return Err(ProcessError::ParentIsNotDirectory {
70-
path: target_path.to_path_buf(),
71-
parent_path: parent_dir.to_path_buf(),
72-
});
67+
// Avoid checking the base directory itself if it's the parent
68+
if parent_dir == resolved_base || parent_dir.as_os_str().is_empty() {
69+
return Ok(()); // Base directory is guaranteed to exist and be a dir, or path is in root
70+
}
71+
72+
match fs::metadata(parent_dir) {
73+
Ok(metadata) => {
74+
// Parent exists, check if it's a directory
75+
if !metadata.is_dir() {
76+
return Err(ProcessError::ParentIsNotDirectory {
77+
path: target_path.to_path_buf(),
78+
parent_path: parent_dir.to_path_buf(),
79+
});
80+
}
81+
// Parent exists and is a directory, all good.
82+
}
83+
Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
84+
// Parent does not exist, try to create it
85+
let relative_parent_dir =
86+
parent_dir.strip_prefix(resolved_base).unwrap_or(parent_dir);
87+
println!(" Creating directory: {}", relative_parent_dir.display());
88+
89+
if let Err(create_err) = fs::create_dir_all(parent_dir) {
90+
// Check if the error is specifically "Not a directory"
91+
// This often indicates an intermediate path component was a file.
92+
if create_err.kind() == io::ErrorKind::NotADirectory {
93+
// Map this specific IO error to our more descriptive error
94+
return Err(ProcessError::ParentIsNotDirectory {
95+
path: target_path.to_path_buf(),
96+
// Report the parent directory we *failed* to create
97+
parent_path: parent_dir.to_path_buf(),
98+
});
99+
} else {
100+
// Other I/O error during creation
101+
return Err(ProcessError::Io { source: create_err });
102+
}
103+
}
104+
// Creation successful
105+
}
106+
Err(e) => {
107+
// Other error getting metadata (permissions?)
108+
return Err(ProcessError::Io { source: e });
73109
}
74-
} else {
75-
// Parent does not exist, create it
76-
let relative_parent_dir = parent_dir.strip_prefix(resolved_base).unwrap_or(parent_dir);
77-
println!(" Creating directory: {}", relative_parent_dir.display());
78-
fs::create_dir_all(parent_dir).map_err(|e| ProcessError::Io { source: e })?;
79110
}
80111
}
81-
// If no parent (e.g., root directory), assume it's okay.
112+
// If no parent (e.g., file directly in base), assume it's okay.
82113
Ok(())
83114
}

src/processor/safety.rs

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,49 @@
11
//! Path safety validation logic.
22
33
use crate::errors::ProcessError;
4+
use std::fs; // Use fs::metadata
45
use std::io::ErrorKind;
56
use std::path::Path;
67

78
/// Checks if the target path is safely within the base directory.
8-
/// Canonicalizes both paths for reliable comparison.
9+
/// Canonicalizes paths for reliable comparison.
910
pub(crate) fn ensure_path_safe(base_dir: &Path, target_path: &Path) -> Result<(), ProcessError> {
1011
// Canonicalize base directory (must succeed as it's resolved in process_actions)
1112
let canonical_base = base_dir
1213
.canonicalize()
1314
.map_err(|e| ProcessError::Io { source: e })?;
1415

15-
// Attempt to canonicalize the target path.
16-
match target_path.canonicalize() {
17-
Ok(canonical_target) => {
18-
// If target exists and canonicalizes, check if it starts with the base
19-
if canonical_target.starts_with(&canonical_base) {
20-
Ok(()) // Path is safe
21-
} else {
22-
Err(ProcessError::PathNotSafe {
23-
resolved_path: canonical_target,
24-
base_path: canonical_base,
25-
})
16+
// Check if the target path *exists* first using metadata.
17+
match fs::metadata(target_path) {
18+
Ok(_) => {
19+
// Target exists. Canonicalize it for the safety check.
20+
match target_path.canonicalize() {
21+
Ok(canonical_target) => {
22+
if canonical_target.starts_with(&canonical_base) {
23+
Ok(()) // Path exists and is safe
24+
} else {
25+
Err(ProcessError::PathNotSafe {
26+
resolved_path: canonical_target,
27+
base_path: canonical_base,
28+
})
29+
}
30+
}
31+
Err(e) => {
32+
// Error canonicalizing an *existing* path (permissions?)
33+
Err(ProcessError::PathResolution {
34+
path: target_path.to_path_buf(),
35+
details: format!("Failed to canonicalize existing target path: {}", e),
36+
})
37+
}
2638
}
2739
}
2840
Err(ref e) if e.kind() == ErrorKind::NotFound => {
2941
// Target doesn't exist: Check safety based on its intended parent.
3042
check_nonexistent_target_safety(target_path, &canonical_base)
3143
}
3244
Err(e) => {
33-
// Other error during canonicalization (e.g., permission denied)
34-
Err(ProcessError::PathResolution {
35-
path: target_path.to_path_buf(),
36-
details: format!("Failed to canonicalize target path: {}", e),
37-
})
45+
// Other error getting metadata (permissions?)
46+
Err(ProcessError::Io { source: e }) // Map other metadata errors to IO
3847
}
3948
}
4049
}

0 commit comments

Comments
 (0)