fix(storage): handle cross-device rename and MKCOL on existing folders
- dedup_service: fall back to copy+delete when rename() fails with EXDEV (os error 18), which occurs when temp and blob dirs are on different filesystems - NC webdav_handler: return 405 instead of 500 when MKCOL targets an existing folder (RFC 4918 §9.3.1)
This commit is contained in:
@@ -276,9 +276,20 @@ impl DedupService {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
if let Err(e) = fs::rename(&temp_path, &blob_path).await {
|
if let Err(e) = fs::rename(&temp_path, &blob_path).await {
|
||||||
// Another writer already placed the blob — discard ours
|
if e.raw_os_error() == Some(18) {
|
||||||
let _ = fs::remove_file(&temp_path).await;
|
// EXDEV: cross-device link — fall back to copy+delete
|
||||||
tracing::debug!("Blob file already placed by concurrent writer: {}", e);
|
fs::copy(&temp_path, &blob_path).await.map_err(|ce| {
|
||||||
|
DomainError::internal_error(
|
||||||
|
"Dedup",
|
||||||
|
format!("Failed to copy temp blob cross-device: {}", ce),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let _ = fs::remove_file(&temp_path).await;
|
||||||
|
} else {
|
||||||
|
// Another writer already placed the blob — discard ours
|
||||||
|
let _ = fs::remove_file(&temp_path).await;
|
||||||
|
tracing::debug!("Blob file already placed by concurrent writer: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,8 +376,17 @@ impl DedupService {
|
|||||||
// dirs live on different filesystems (rare), this falls back to
|
// dirs live on different filesystems (rare), this falls back to
|
||||||
// copy+delete which is slower but still correct.
|
// copy+delete which is slower but still correct.
|
||||||
if let Err(e) = fs::rename(source_path, &blob_path).await {
|
if let Err(e) = fs::rename(source_path, &blob_path).await {
|
||||||
// Another writer may have placed the blob concurrently
|
if e.raw_os_error() == Some(18) {
|
||||||
if blob_path.exists() {
|
// EXDEV: cross-device link — fall back to copy+delete
|
||||||
|
fs::copy(source_path, &blob_path).await.map_err(|ce| {
|
||||||
|
DomainError::internal_error(
|
||||||
|
"Dedup",
|
||||||
|
format!("Failed to copy file to blob store: {}", ce),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let _ = fs::remove_file(source_path).await;
|
||||||
|
} else if blob_path.exists() {
|
||||||
|
// Another writer may have placed the blob concurrently
|
||||||
let _ = fs::remove_file(source_path).await;
|
let _ = fs::remove_file(source_path).await;
|
||||||
tracing::debug!("Blob file placed by concurrent writer: {}", e);
|
tracing::debug!("Blob file placed by concurrent writer: {}", e);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -572,15 +572,23 @@ async fn handle_mkcol(
|
|||||||
parent_id: Some(parent_folder.id.clone()),
|
parent_id: Some(parent_folder.id.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
folder_service
|
match folder_service.create_folder(dto).await {
|
||||||
.create_folder(dto)
|
Ok(_) => Ok(Response::builder()
|
||||||
.await
|
.status(StatusCode::CREATED)
|
||||||
.map_err(|e| AppError::internal_error(format!("Failed to create folder: {}", e)))?;
|
.body(Body::empty())
|
||||||
|
.unwrap()),
|
||||||
Ok(Response::builder()
|
Err(e) if e.message.contains("already exists") || e.message.contains("Already Exists") => {
|
||||||
.status(StatusCode::CREATED)
|
// RFC 4918 §9.3.1: MKCOL on existing resource → 405
|
||||||
.body(Body::empty())
|
Ok(Response::builder()
|
||||||
.unwrap())
|
.status(StatusCode::METHOD_NOT_ALLOWED)
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
Err(e) => Err(AppError::internal_error(format!(
|
||||||
|
"Failed to create folder: {}",
|
||||||
|
e
|
||||||
|
))),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ──────────────────── DELETE ────────────────────
|
// ──────────────────── DELETE ────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user