diff --git a/seaweed-volume/src/server/grpc_server.rs b/seaweed-volume/src/server/grpc_server.rs index c86eed78f8..b2c4a2b5ce 100644 --- a/seaweed-volume/src/server/grpc_server.rs +++ b/seaweed-volume/src/server/grpc_server.rs @@ -2766,6 +2766,58 @@ impl VolumeServer for VolumeGrpcService { } } + // Copy the generation-0 bitrot checksum sidecar (.ecsum) when requested, so + // protection travels with the shards. Tolerant of a missing source (no-op): + // this non-2PC path has no Prepare backstop, and an unprotected source + // simply leaves the copy unprotected. + if req.copy_ecsum_file { + let copy_req = volume_server_pb::CopyFileRequest { + volume_id: req.volume_id, + collection: req.collection.clone(), + is_ec_volume: true, + ext: ".ecsum".to_string(), + compaction_revision: u32::MAX, + stop_offset: i64::MAX as u64, + ignore_source_file_not_found: true, + ..Default::default() + }; + let mut stream = client + .copy_file(copy_req) + .await + .map_err(|e| { + Status::internal(format!( + "VolumeEcShardsCopy volume {} copy .ecsum: {}", + vid, e + )) + })? + .into_inner(); + + let file_path = { + let base = + crate::storage::volume::volume_file_name(&dest_dir, &req.collection, vid); + format!("{}.ecsum", base) + }; + let mut file = std::fs::File::create(&file_path) + .map_err(|e| Status::internal(format!("create {}: {}", file_path, e)))?; + let mut written: u64 = 0; + while let Some(chunk) = stream + .message() + .await + .map_err(|e| Status::internal(format!("recv .ecsum: {}", e)))? + { + use std::io::Write; + file.write_all(&chunk.file_content) + .map_err(|e| Status::internal(format!("write {}: {}", file_path, e)))?; + written += chunk.file_content.len() as u64; + } + // A missing source yields an empty stream; drop the 0-byte file so mount + // sees no sidecar (protection Off) rather than a truncated/invalid one. + if written == 0 { + drop(file); + let _ = std::fs::remove_file(&file_path); + } + } + Ok(Response::new( volume_server_pb::VolumeEcShardsCopyResponse {}, ))