Skip to content

Commit ef3e6ea

Browse files
kerthcetclaude
andcommitted
fix: use async symlink_metadata in restore cleanup
Replace blocking path.is_dir() with async tokio::fs::symlink_metadata() during restore cleanup phase. Benefits: - Non-blocking in async context (avoids blocking filesystem calls) - Consistent with snapshot creation which uses symlink_metadata - Does not follow symlinks (treats symlinks as files, not their targets) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 20d25fd commit ef3e6ea

1 file changed

Lines changed: 18 additions & 12 deletions

File tree

sandd/src/snapshot/manager.rs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,8 @@ impl SnapshotManager {
200200
Ok(())
201201
}
202202

203-
/// Restore tree recursively (always clean - deletes extras after successful restore)
203+
/// Restore tree recursively (restores snapshot entries, then attempts to
204+
/// delete extra entries; cleanup failures are logged)
204205
fn restore_tree<'a>(
205206
&'a self,
206207
tree_hash: &'a str,
@@ -214,11 +215,8 @@ impl SnapshotManager {
214215
let tree: Tree = serde_json::from_slice(&tree_json)?;
215216

216217
// Build set of expected names in this directory (owned strings to avoid borrow issues)
217-
let expected_names: std::collections::HashSet<String> = tree
218-
.entries
219-
.iter()
220-
.map(|e| e.name.clone())
221-
.collect();
218+
let expected_names: std::collections::HashSet<String> =
219+
tree.entries.iter().map(|e| e.name.clone()).collect();
222220

223221
// Phase 1: Restore each entry from snapshot
224222
// Do this FIRST - if restore fails, extras remain untouched (safer)
@@ -300,13 +298,21 @@ impl SnapshotManager {
300298
let path = entry.path();
301299

302300
// Not in snapshot - delete it
303-
if path.is_dir() {
304-
if let Err(e) = fs::remove_dir_all(&path).await {
305-
tracing::warn!("Failed to delete directory {}: {}", path.display(), e);
301+
// Use symlink_metadata (async, no symlink follow) for consistency
302+
match fs::symlink_metadata(&path).await {
303+
Ok(metadata) => {
304+
if metadata.is_dir() {
305+
if let Err(e) = fs::remove_dir_all(&path).await {
306+
tracing::warn!("Failed to delete directory {}: {}", path.display(), e);
307+
}
308+
} else {
309+
if let Err(e) = fs::remove_file(&path).await {
310+
tracing::warn!("Failed to delete file {}: {}", path.display(), e);
311+
}
312+
}
306313
}
307-
} else {
308-
if let Err(e) = fs::remove_file(&path).await {
309-
tracing::warn!("Failed to delete file {}: {}", path.display(), e);
314+
Err(e) => {
315+
tracing::warn!("Failed to stat {}: {}", path.display(), e);
310316
}
311317
}
312318
}

0 commit comments

Comments
 (0)